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} +