diff --git a/src/lib/apis/terminal/index.ts b/src/lib/apis/terminal/index.ts index ee38a9a8fa..eedbcc3eed 100644 --- a/src/lib/apis/terminal/index.ts +++ b/src/lib/apis/terminal/index.ts @@ -143,7 +143,8 @@ export const downloadFileBlob = async ( if (!res || !res.ok) return null; const filename = path.split('/').pop() ?? 'file'; - const blob = await res.blob(); + const blob = await res.blob().catch(() => null); + if (!blob) return null; return { blob, filename }; }; @@ -170,7 +171,8 @@ export const archiveFromTerminal = async ( const disposition = res.headers.get('content-disposition') ?? ''; const match = disposition.match(/filename="?([^"]+)"?/); const filename = match?.[1] ?? 'download.zip'; - const blob = await res.blob(); + const blob = await res.blob().catch(() => null); + if (!blob) return null; return { blob, filename }; }; diff --git a/src/lib/components/chat/FileNav.svelte b/src/lib/components/chat/FileNav.svelte index b6f2102ace..86c3df609f 100644 --- a/src/lib/components/chat/FileNav.svelte +++ b/src/lib/components/chat/FileNav.svelte @@ -521,22 +521,34 @@ fileLoading = false; }; + let downloading = false; + const downloadFile = async (path: string) => { const terminal = selectedTerminal; - if (!terminal) return; + if (!terminal || downloading) return; - // Directories end with '/' — download as ZIP archive - const isDir = path.endsWith('/'); - const result = isDir - ? await archiveFromTerminal(terminal.url, terminal.key, [path.replace(/\/$/, '')]) - : await downloadFileBlob(terminal.url, terminal.key, path, chatId ?? undefined); - if (!result) return; - const url = URL.createObjectURL(result.blob); - const a = document.createElement('a'); - a.href = url; - a.download = result.filename; - a.click(); - URL.revokeObjectURL(url); + downloading = true; + const toastId = toast.loading($i18n.t('Preparing download...')); + try { + // Directories end with '/', downloaded as a ZIP archive + const isDir = path.endsWith('/'); + const result = isDir + ? await archiveFromTerminal(terminal.url, terminal.key, [path.replace(/\/$/, '')]) + : await downloadFileBlob(terminal.url, terminal.key, path, chatId ?? undefined); + if (!result) { + toast.error($i18n.t('Download failed')); + return; + } + const url = URL.createObjectURL(result.blob); + const a = document.createElement('a'); + a.href = url; + a.download = result.filename; + a.click(); + URL.revokeObjectURL(url); + } finally { + toast.dismiss(toastId); + downloading = false; + } }; // ── Drag-and-drop upload ───────────────────────────────────────────── @@ -785,7 +797,7 @@ const bulkDownload = async () => { const terminal = selectedTerminal; - if (!terminal) return; + if (!terminal || downloading) return; const paths = [...selectedEntries].map((p) => p.replace(/\/$/, '')); if (paths.length === 0) return; @@ -796,15 +808,25 @@ return; } - // Archive everything into a single ZIP - const result = await archiveFromTerminal(terminal.url, terminal.key, paths); - if (!result) return; - const url = URL.createObjectURL(result.blob); - const a = document.createElement('a'); - a.href = url; - a.download = result.filename; - a.click(); - URL.revokeObjectURL(url); + downloading = true; + const toastId = toast.loading($i18n.t('Preparing download...')); + try { + // Archive everything into a single ZIP + const result = await archiveFromTerminal(terminal.url, terminal.key, paths); + if (!result) { + toast.error($i18n.t('Download failed')); + return; + } + const url = URL.createObjectURL(result.blob); + const a = document.createElement('a'); + a.href = url; + a.download = result.filename; + a.click(); + URL.revokeObjectURL(url); + } finally { + toast.dismiss(toastId); + downloading = false; + } }; // Escape to clear selection