Skip to content

feat(app, ui): render local images in markdown via /file/content API#30722

Open
Howardzhangdqs wants to merge 4 commits into
anomalyco:devfrom
Howardzhangdqs:dev
Open

feat(app, ui): render local images in markdown via /file/content API#30722
Howardzhangdqs wants to merge 4 commits into
anomalyco:devfrom
Howardzhangdqs:dev

Conversation

@Howardzhangdqs
Copy link
Copy Markdown

@Howardzhangdqs Howardzhangdqs commented Jun 4, 2026

Issue for this PR

Closes #16234

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Enable rendering of local images (e.g. ![](./image.png)) in agent markdown responses in the WebUI.

When an LLM returns markdown with relative image paths, the browser cannot resolve them. This PR adds a marked image renderer that outputs <img data-local-image="..."> placeholders, then resolves them asynchronously by calling GET /file/content?path=... with the session directory and converting the base64 binary response to data: URIs.

How did you verify your code works?

  • Started the backend (bun run --conditions=browser ./src/index.ts serve --port 4096) and frontend dev server (bun dev -- --port 4444)
  • tsc --noEmit passes for both packages/app and packages/ui
  • Verified that agent responses containing ![](./image.png) render the image in the browser

Screenshots / recordings

N/A — feature renders local images inline in markdown.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Copilot AI review requested due to automatic review settings June 4, 2026 10:26
@github-actions github-actions Bot added needs:title needs:compliance This means the issue will auto-close after 2 hours. labels Jun 4, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 4, 2026

Hey! Your PR title feat(app,ui): render local images in markdown via /file/content API doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, opencode).

See CONTRIBUTING.md for details.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds support for rendering “local” Markdown images by tagging non-URL image paths during Markdown parsing and resolving them to data URIs at runtime via a context-provided resolver in the app.

Changes:

  • Extend the Markdown renderer to emit data-local-image for non-URL image paths.
  • Introduce a LocalImageResolver context and wire it into the app via ServerSDK to fetch file content as base64 data URIs.
  • Thread directory through message rendering into the Markdown component so resolution can be directory-aware.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/ui/src/context/marked.tsx Adds custom Marked image() renderer that marks local image paths for later resolution.
packages/ui/src/context/local-image.tsx Introduces a Solid context to provide a local-image resolver function.
packages/ui/src/context/index.ts Re-exports the new local-image context module.
packages/ui/src/components/message-part.tsx Threads directory into Markdown rendering (paced + non-paced).
packages/ui/src/components/markdown.tsx Resolves data-local-image images post-render via the resolver context and directory prop.
packages/app/src/app.tsx Provides a resolver implementation backed by serverSDK.file.read() and wraps the router with the provider.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/ui/src/context/local-image.tsx Outdated
Comment on lines +5 to +8
const ctx = createContext<LocalImageResolver>()

export const LocalImageProvider = ctx.Provider
export const useLocalImageResolver = () => useContext(ctx)
Comment on lines +478 to +484
image({ href, title, text }) {
const titleAttr = title ? ` title="${title}"` : ""
if (href && !/^(https?:|data:|#|\/\/)/.test(href)) {
return `<img data-local-image="${href}" alt="${text}"${titleAttr}>`
}
return `<img src="${href ?? ""}" alt="${text}"${titleAttr}>`
},
Comment thread packages/ui/src/context/marked.tsx Outdated
if (href && !/^(https?:|data:|#|\/\/)/.test(href)) {
return `<img data-local-image="${href}" alt="${text}"${titleAttr}>`
}
return `<img src="${href ?? ""}" alt="${text}"${titleAttr}>`
Comment on lines +336 to +347
const resolver = useLocalImageResolver()
if (resolver && local.directory) {
for (const img of container.querySelectorAll<HTMLImageElement>("[data-local-image]")) {
const path = img.getAttribute("data-local-image")
if (!path || img.src) continue
resolver(path, local.directory)
.then((dataUri) => {
if (dataUri) img.src = dataUri
})
.catch(() => {})
}
}
Comment thread packages/ui/src/components/markdown.tsx Outdated
Comment on lines +338 to +341
for (const img of container.querySelectorAll<HTMLImageElement>("[data-local-image]")) {
const path = img.getAttribute("data-local-image")
if (!path || img.src) continue
resolver(path, local.directory)
Comment thread packages/app/src/app.tsx
Comment on lines +72 to +82
const resolver: LocalImageResolver = async (rawPath, directory) => {
try {
if (!directory) return undefined
const client = serverSDK.createClient({ directory, throwOnError: true })
const resp = await client.file.read({ path: rawPath })
if (resp.data?.type === "binary" && resp.data.encoding === "base64" && resp.data.mimeType) {
return `data:${resp.data.mimeType};base64,${resp.data.content}`
}
} catch {}
return undefined
}
Comment thread packages/app/src/app.tsx Outdated
if (resp.data?.type === "binary" && resp.data.encoding === "base64" && resp.data.mimeType) {
return `data:${resp.data.mimeType};base64,${resp.data.content}`
}
} catch {}
@Howardzhangdqs Howardzhangdqs changed the title feat(app,ui): render local images in markdown via /file/content API feat(app, ui): render local images in markdown via /file/content API Jun 4, 2026
@github-actions github-actions Bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Jun 4, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 4, 2026

Thanks for updating your PR! It now meets our contributing guidelines. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

How to serve and display images in web UI?

2 participants