fix: include subfolder in asset download URL for audio/video files (#8684)

## Summary

- `getAssetUrl()` was constructing `/view` URLs without the `subfolder`
query parameter, causing backend to return "file not found" for assets
stored in subfolders (common for audio/video outputs)
- Preview/playback was unaffected because it uses `preview_url` from
`ResultItemImpl.url` which correctly includes `subfolder`
- Fixed `getAssetUrl()` to include `subfolder` from
`asset.user_metadata` when present
- Simplified download functions to prefer `preview_url` (already
correct) with `getAssetUrl` as fallback

## Test plan

- [ ] Generate audio/video output (e.g. via SaveAudio node) that saves
to a subfolder
- [ ] Right-click the asset in the assets sidebar and click Download —
should download successfully
- [ ] Select multiple audio/video assets and use bulk download — should
download all
- [ ] Verify image downloads still work as before
- [ ] Verify cloud environment downloads still work (uses `preview_url`
path)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
  * Added support for organizing and downloading assets from subfolders.

* **Refactor**
* Improved asset URL generation and download handling for better
reliability and performance.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Johnpaul Chiwetelu
2026-02-06 05:06:11 +01:00
committed by GitHub
parent 7f509cc018
commit d05e4eac58
2 changed files with 13 additions and 22 deletions

View File

@@ -65,15 +65,8 @@ export function useMediaAssetActions() {
try {
const filename = targetAsset.name
let downloadUrl: string
// In cloud, use preview_url directly (from cloud storage)
// In OSS/localhost, use the /view endpoint
if (isCloud && targetAsset.preview_url) {
downloadUrl = targetAsset.preview_url
} else {
downloadUrl = getAssetUrl(targetAsset)
}
// Prefer preview_url (already includes subfolder) with getAssetUrl as fallback
const downloadUrl = targetAsset.preview_url || getAssetUrl(targetAsset)
downloadFile(downloadUrl, filename)
@@ -103,15 +96,8 @@ export function useMediaAssetActions() {
try {
assets.forEach((asset) => {
const filename = asset.name
let downloadUrl: string
// In cloud, use preview_url directly (from GCS or other cloud storage)
// In OSS/localhost, use the /view endpoint
if (isCloud && asset.preview_url) {
downloadUrl = asset.preview_url
} else {
downloadUrl = getAssetUrl(asset)
}
// Prefer preview_url (already includes subfolder) with getAssetUrl as fallback
const downloadUrl = asset.preview_url || getAssetUrl(asset)
downloadFile(downloadUrl, filename)
})

View File

@@ -8,7 +8,7 @@ import { getAssetType } from './assetTypeUtil'
/**
* Get the download/view URL for an asset
* Constructs the proper URL with filename encoding and type parameter
* Constructs the proper URL with filename encoding, type, and subfolder parameters
*
* @param asset The asset to get URL for
* @param defaultType Default type if asset doesn't have tags (default: 'output')
@@ -23,7 +23,12 @@ export function getAssetUrl(
defaultType: 'input' | 'output' = 'output'
): string {
const assetType = getAssetType(asset, defaultType)
return api.apiURL(
`/view?filename=${encodeURIComponent(asset.name)}&type=${assetType}`
)
const subfolder = asset.user_metadata?.subfolder
const params = new URLSearchParams()
params.set('filename', asset.name)
params.set('type', assetType)
if (typeof subfolder === 'string' && subfolder) {
params.set('subfolder', subfolder)
}
return api.apiURL(`/view?${params}`)
}