From aa63d6b9f4cb14535fd0191ab358c27f0ee62ce8 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Mon, 19 Oct 2026 23:48:50 +0900 Subject: feat(markdown): render local-path images inline via backend image route --- src/app/views/ChatPane.svelte | 1 + src/features/chat/ui.test.ts | 19 ++++ src/features/chat/ui/ChatView.svelte | 13 ++- src/features/markdown/logic/markdown.test.ts | 93 +++++++++++++++++- src/features/markdown/logic/markdown.ts | 110 ++++++++++++++++++++-- src/features/markdown/logic/streaming-markdown.ts | 61 ++++++++++-- src/features/markdown/ui/Markdown.svelte | 19 +++- 7 files changed, 295 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/app/views/ChatPane.svelte b/src/app/views/ChatPane.svelte index 31e719e..d1630af 100644 --- a/src/app/views/ChatPane.svelte +++ b/src/app/views/ChatPane.svelte @@ -86,6 +86,7 @@ thinkingKeyBase={store.activeChat.thinkingKeyBase} providerRetry={store.activeChat.providerRetry} apiBaseUrl={store.httpBase} + cwd={store.cwd} {cacheTimer} /> {/key} diff --git a/src/features/chat/ui.test.ts b/src/features/chat/ui.test.ts index 10af76c..c557e4f 100644 --- a/src/features/chat/ui.test.ts +++ b/src/features/chat/ui.test.ts @@ -56,6 +56,25 @@ describe("ChatView", () => { expect(screen.getByText("Hello!")).toBeInTheDocument(); }); + it("renders local-path Markdown images through the backend image route", () => { + const chunks: RenderedChunk[] = [ + { + seq: 1, + role: "assistant", + chunk: { type: "text", text: "![pic](/home/u/pic.png)" }, + provisional: false, + }, + ]; + + const { container } = render(ChatView, { + props: { chunks, apiBaseUrl: "http://localhost:24203", cwd: "/w/foo" }, + }); + + expect(container.querySelector("img")?.getAttribute("src")).toBe( + "http://localhost:24203/local-image?path=%2Fhome%2Fu%2Fpic.png", + ); + }); + it("shows the show-earlier button only when earlier history is unloaded, and pages it in", async () => { const chunks: RenderedChunk[] = [ { seq: 26, role: "user", chunk: { type: "text", text: "later" }, provisional: false }, diff --git a/src/features/chat/ui/ChatView.svelte b/src/features/chat/ui/ChatView.svelte index 4fd0911..f34de9a 100644 --- a/src/features/chat/ui/ChatView.svelte +++ b/src/features/chat/ui/ChatView.svelte @@ -30,6 +30,7 @@ thinkingKeyBase = 0, providerRetry = null, apiBaseUrl = "", + cwd = null, cacheTimer = null, }: { chunks: readonly RenderedChunk[]; @@ -59,6 +60,12 @@ * "" (root-relative — a browser resolves `/images/…` against its origin). */ apiBaseUrl?: string; + /** + * The active conversation's working directory (null when unset). Relative + * Markdown image srcs (`![x](shot.png)`) resolve against it; local absolute + * and `file://` paths do not need it. Passed to `Markdown` with `apiBaseUrl`. + */ + cwd?: string | null; /** * The cache timer (gaps between model requests): a running step gap shows * live at the bottom (under the executing tools), a running turn gap live @@ -187,7 +194,11 @@
{#if rendered.chunk.type === "text"} - + {:else if rendered.chunk.type === "error"}