Skip to content

feat(web): render extracted images inline in the document reader - #201

Merged
KylinMountain merged 2 commits into
worktree-feat+workbench-doc-readerfrom
feat/doc-reader-images
Jul 22, 2026
Merged

feat(web): render extracted images inline in the document reader#201
KylinMountain merged 2 commits into
worktree-feat+workbench-doc-readerfrom
feat/doc-reader-images

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What

Follow-up to the document reader (#199) — renders extracted images inline in the reader. Source docs embed ![image](sources/images/<doc>/…) references whose files are extracted at ingest but were never displayed (they showed as a literal !image). Now they render as real images.

Stacked on #199 (base = worktree-feat+workbench-doc-reader). Review/merge #199 first; this diff is just the image layer on top.

Changes

  • BackendGET /api/v1/document/image serves wiki/sources/images/**. Narrowed to the images dir + raster suffixes (.png/.jpg/.jpeg/.gif/.webp, no SVG) with a containment/traversal guard, so this read-only sink can never serve wiki pages, skills, or arbitrary files.
  • MarkdownView — gains an optional image token, enabled only when a resolveImageSrc prop is passed (the doc reader). Without it, the ![](…) alternative is omitted from the regex, so chat and wiki-page rendering are byte-for-byte unchanged. A new AuthedImage fetches the API path as a blob (the bearer token can't ride on <img src>; mirrors artifacts.ts) and revokes the object URL on unmount; it shows a placeholder while loading and renders nothing on failure.
  • Doc reader — passes a resolver that normalizes both source-path conventions (wiki-root sources/images/… for long-doc JSON, note-relative images/… for short-doc MD) to the image endpoint. External / data: URLs stay literal text.

Scope note

This makes images visible in the UI. LLM answers remain image-blind — ingestion does no caption/OCR (only extracts files + inserts placeholders), so figure/chart content still doesn't reach the model. That's a separate pipeline change, not attempted here.

Verification

  • Backend: 1251 passed (adds serve / traversal / non-image-suffix / 404 / auth tests); ruff + mypy clean.
  • Frontend: npm run build (i18n + tsc + vite) green.
  • Manually driven in a browser: the Soochow Securities research PDF's figures (previously !image) now render inline in the reader; chat/wiki rendering unaffected.
Follow-up to #197: long/short source docs embed `![image](sources/images/...)`
references whose files were extracted at ingest but never displayed (they showed
as a literal "!image"). Now they render inline in the reader.

- Backend: GET /api/v1/document/image serves wiki/sources/images/** — narrowed
  to the images dir + raster suffixes with a traversal guard (read-only; cannot
  reach wiki pages/skills/arbitrary files).
- MarkdownView: gain an optional image token, ENABLED ONLY when a resolveImageSrc
  prop is passed (the doc reader). Without it the `![](…)` token is omitted from
  the regex, so chat/wiki rendering is byte-for-byte unchanged. A new AuthedImage
  fetches the API path as a blob (the bearer token can't ride on <img src>;
  mirrors artifacts.ts) and revokes the object URL on unmount.
- Doc reader: passes a resolver that normalizes both source path conventions
  (wiki-root `sources/images/...` and note-relative `images/...`) to the
  image endpoint; external/data URLs stay literal text.

Images are now visible in the UI. (LLM answers are still image-blind — ingestion
does no caption/OCR — which is a separate pipeline change.)

Backend adds serve/traversal/suffix/404/auth tests (1251 passed). Verified in a
browser: the Soochow Securities report's figures render inline.
- Image URL regex now tolerates one level of balanced parens, so an image
  whose path contains ')' (a legacy/cloud doc_name with ASCII parentheses,
  e.g. 'report (1)') is no longer truncated to a broken path + stray text [#1].
- The image endpoint sets an explicit media type per suffix instead of letting
  FileResponse guess: a .webp is served as image/webp even where mimetypes has
  no webp entry (would otherwise be text/plain → a blob-loaded <img> refuses to
  render it) [#2]. Adds a webp media-type test.
- AuthedImage shows a muted dashed placeholder (the alt) on load failure instead
  of rendering nothing, so a missing image is visible rather than silently
  dropped [#3].

Verified: the balanced-paren regex extracts full paths for 'report (1)' and
fullwidth-paren names; backend 1252 passed; frontend build green.
@KylinMountain
KylinMountain merged commit dda7f8e into worktree-feat+workbench-doc-reader Jul 22, 2026
2 checks passed
@KylinMountain
KylinMountain deleted the feat/doc-reader-images branch July 22, 2026 03:44
TheNha pushed a commit to TheNha/OpenKB that referenced this pull request Jul 29, 2026
…tifyAI#201)

Cherry-picked from dda7f8e (origin/worktree-feat+workbench-doc-reader),
which was never merged into main/this branch. Documents pane was still
showing !<link> instead of rendering images.

Co-Authored-By: Nguyen The Nha @nha.nt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant