From 3b362acf482449533f8c4e3bee11277ac9044efb Mon Sep 17 00:00:00 2001
From: Teknium <127238744+teknium1@users.noreply.github.com>
Date: Sat, 29 Aug 2026 03:34:04 -0700
Subject: [PATCH] =?UTF-8?q?feat(desktop):=20Download=20button=20on=20previ?=
=?UTF-8?q?ew=20file=20cards=20=E2=80=94=20save=20any=20delivered=20file?=
=?UTF-8?q?=20via=20the=20authenticated=20backend=20bridge=20(works=20loca?=
=?UTF-8?q?l=20and=20remote)=20(#97816)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../markdown-text.media-md.test.tsx | 12 +++--
.../components/chat/preview-attachment.tsx | 44 ++++++++++++++++++-
2 files changed, 52 insertions(+), 4 deletions(-)
diff --git a/apps/desktop/src/components/assistant-ui/markdown-text.media-md.test.tsx b/apps/desktop/src/components/assistant-ui/markdown-text.media-md.test.tsx
index f124fd2337..e9c39e4631 100644
--- a/apps/desktop/src/components/assistant-ui/markdown-text.media-md.test.tsx
+++ b/apps/desktop/src/components/assistant-ui/markdown-text.media-md.test.tsx
@@ -29,7 +29,10 @@ describe('markdown documents delivered via MEDIA', () => {
// PreviewAttachment renders an "open preview" toggle button; the old
// MediaAttachment 'file' fallback rendered a bare "Open ..." anchor.
- expect(await screen.findByRole('button')).toBeTruthy()
+ // Two buttons now: Download + Open preview (maintainer-requested).
+ const buttons = await screen.findAllByRole('button')
+ expect(buttons.length).toBe(2)
+ expect(screen.getByText('Download')).toBeTruthy()
expect(screen.queryByText(/^Loading /)).toBeNull()
expect(screen.getByText('report.md')).toBeTruthy()
})
@@ -44,7 +47,9 @@ describe('markdown documents delivered via MEDIA', () => {
render()
- expect(await screen.findByRole('button')).toBeTruthy()
+ const buttons = await screen.findAllByRole('button')
+ expect(buttons.length).toBe(2)
+ expect(screen.getByText('Download')).toBeTruthy()
expect(screen.getByText('archive.zip')).toBeTruthy()
expect(screen.queryByText(/^Open archive/)).toBeNull()
})
@@ -54,7 +59,8 @@ describe('markdown documents delivered via MEDIA', () => {
render()
- expect(await screen.findByRole('button')).toBeTruthy()
+ const buttons = await screen.findAllByRole('button')
+ expect(buttons.length).toBe(2)
expect(screen.getByText('report.pdf')).toBeTruthy()
})
})
diff --git a/apps/desktop/src/components/chat/preview-attachment.tsx b/apps/desktop/src/components/chat/preview-attachment.tsx
index 177aef3ae0..b0f102e824 100644
--- a/apps/desktop/src/components/chat/preview-attachment.tsx
+++ b/apps/desktop/src/components/chat/preview-attachment.tsx
@@ -3,8 +3,9 @@ import { useEffect, useRef, useState } from 'react'
import { useSessionView } from '@/app/chat/session-view'
import { useI18n } from '@/i18n'
-import { MonitorPlay } from '@/lib/icons'
+import { Download, MonitorPlay } from '@/lib/icons'
import { normalizeOrLocalPreviewTarget } from '@/lib/local-preview'
+import { downloadGatewayMediaFile } from '@/lib/media'
import { previewName } from '@/lib/preview-targets'
import { notifyError } from '@/store/notifications'
import { $previewTabSources, closePreviewForSource, openPreview, type PreviewRecordSource } from '@/store/preview'
@@ -16,6 +17,8 @@ export function PreviewAttachment({ source = 'manual', target }: { source?: Prev
const cwd = useStore(useSessionView().$cwd)
const openSources = useStore($previewTabSources)
const [opening, setOpening] = useState(false)
+ const [downloading, setDownloading] = useState(false)
+ const [downloaded, setDownloaded] = useState(false)
const cwdRef = useRef(cwd)
const mountedRef = useRef(false)
const requestTokenRef = useRef(0)
@@ -94,6 +97,34 @@ export function PreviewAttachment({ source = 'manual', target }: { source?: Prev
}
}
+ async function downloadFile() {
+ if (downloading) {
+ return
+ }
+
+ setDownloading(true)
+
+ try {
+ // Works in both modes: the Electron main process fetches the bytes
+ // through the session's backend connection (local gateway or remote)
+ // and prompts for a save location.
+ const result = await downloadGatewayMediaFile(target)
+
+ if (mountedRef.current && result.saved) {
+ setDownloaded(true)
+ setTimeout(() => mountedRef.current && setDownloaded(false), 2000)
+ }
+ } catch (error) {
+ if (mountedRef.current) {
+ notifyError(error, t.fileMenu.downloadFailed)
+ }
+ } finally {
+ if (mountedRef.current) {
+ setDownloading(false)
+ }
+ }
+ }
+
return (
@@ -102,6 +133,17 @@ export function PreviewAttachment({ source = 'manual', target }: { source?: Prev
{name}
+