From bceac696aedb45ca356aa08c15401f95f07b2244 Mon Sep 17 00:00:00 2001 From: thatssoheil Date: Mon, 10 Aug 2026 15:43:57 -0800 Subject: [PATCH] fix(desktop): artifacts page timestamps render 1970 and local images fail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All three artifact timestamp sources (message.timestamp, session.last_active, session.started_at) are epoch SECONDS — the transcript reader and session-date-groups both multiply by 1000 — but the collector passed them straight to new Date() (ms), so every artifact rendered as 1970-01-21. Normalize seconds to ms once at collection; the Date.now() fallback stays ms. Local file artifacts (e.g. D:\ComfyUI\output\*.png) fell through to mediaExternalUrl() which yields a file:// URL the renderer cannot load. Route through the desktop fs bridge whenever it exists — readDesktopFileDataUrl already dispatches remote REST vs local Electron internally (#83380). --- .../src/app/artifacts/artifact-utils.ts | 21 ++++++++----------- apps/desktop/src/app/artifacts/index.test.ts | 16 ++++++++++++-- apps/desktop/src/app/artifacts/index.tsx | 2 +- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/apps/desktop/src/app/artifacts/artifact-utils.ts b/apps/desktop/src/app/artifacts/artifact-utils.ts index 3201436e0a..7da0f5b307 100644 --- a/apps/desktop/src/app/artifacts/artifact-utils.ts +++ b/apps/desktop/src/app/artifacts/artifact-utils.ts @@ -1,5 +1,4 @@ -import { readDesktopFileDataUrl } from '@/lib/desktop-fs' -import { filePathFromMediaPath, isRemoteGateway, mediaExternalUrl } from '@/lib/media' +import { mediaExternalUrl, resolveMediaDisplaySrc } from '@/lib/media' import type { SessionInfo, SessionMessage } from '@/types/hermes' export type ArtifactKind = 'image' | 'file' | 'link' @@ -185,16 +184,14 @@ function artifactHref(value: string): string { return value } -export async function artifactImageSrc(value: string, href = artifactHref(value)): Promise { - if (/^(?:https?|data):/i.test(value)) { - return href - } - - if (typeof window !== 'undefined' && window.hermesDesktop && isRemoteGateway()) { - return readDesktopFileDataUrl(filePathFromMediaPath(value)) - } - - return href +export async function artifactImageSrc(value: string): Promise { + // Delegate the whole local/remote ladder to the shared media resolver: + // inline (http/data) stays as-is, remote gateway goes through the + // authenticated fs bridge, local desktop through the Electron + // readFileDataUrl, and bare non-path link values fall through untouched. + // Reimplementing that ladder here would drift from resolveMediaDisplaySrc + // and regress one of its legs (#83380). + return resolveMediaDisplaySrc(value) } function artifactLabel(value: string): string { diff --git a/apps/desktop/src/app/artifacts/index.test.ts b/apps/desktop/src/app/artifacts/index.test.ts index 09a48f128c..e39c36ac74 100644 --- a/apps/desktop/src/app/artifacts/index.test.ts +++ b/apps/desktop/src/app/artifacts/index.test.ts @@ -309,6 +309,19 @@ ${payload} expect(fromNow[0]?.timestamp).toBe(now) }) + it('resolves local file image artifacts through the desktop fs bridge', async () => { + const readFileDataUrl = vi.fn(async () => 'data:image/png;base64,TE9DQUw=') + vi.stubGlobal('window', { hermesDesktop: { readFileDataUrl } }) + + // Local desktop (connection mode != 'remote'): a local image_generate + // output path must be read through the Electron bridge, not left as a + // file:// URL the renderer cannot load (#83380). + const path = '/home/me/.hermes/cache/image_generate/out.png' + + await expect(artifactImageSrc(path)).resolves.toBe('data:image/png;base64,TE9DQUw=') + expect(readFileDataUrl).toHaveBeenCalledWith(path) + }) + it('resolves remote image artifact thumbnails through the desktop fs bridge', async () => { const api = vi.fn(async ({ path }: { path: string }) => { if (path.startsWith('/api/fs/read-data-url?')) { @@ -322,9 +335,8 @@ ${payload} $connection.set({ baseUrl: 'https://gw', mode: 'remote', token: 'secret' } as never) const path = '/Users/me/.hermes/skills/work-esab/references/images/manual-step03.jpeg' - const downloadHref = `https://gw/api/files/download?path=${encodeURIComponent(path)}&token=secret` - await expect(artifactImageSrc(path, downloadHref)).resolves.toBe('data:image/jpeg;base64,cmVtb3Rl') + await expect(artifactImageSrc(path)).resolves.toBe('data:image/jpeg;base64,cmVtb3Rl') expect(api).toHaveBeenCalledWith({ path: '/api/fs/read-data-url?path=%2FUsers%2Fme%2F.hermes%2Fskills%2Fwork-esab%2Freferences%2Fimages%2Fmanual-step03.jpeg' diff --git a/apps/desktop/src/app/artifacts/index.tsx b/apps/desktop/src/app/artifacts/index.tsx index 01e90e1287..07f2cc7048 100644 --- a/apps/desktop/src/app/artifacts/index.tsx +++ b/apps/desktop/src/app/artifacts/index.tsx @@ -474,7 +474,7 @@ function ArtifactImageCard({ artifact, failedImage, onImageError, onOpenChat }: let active = true setSrc('') - void artifactImageSrc(artifact.value, artifact.href) + void artifactImageSrc(artifact.value) .then(nextSrc => { if (active) { setSrc(nextSrc)