Skip to content

feat: Visual parser spike#1990

Draft
Wallgau wants to merge 6 commits into
mainfrom
visual-parser-spike
Draft

feat: Visual parser spike#1990
Wallgau wants to merge 6 commits into
mainfrom
visual-parser-spike

Conversation

@Wallgau

@Wallgau Wallgau commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds an ephemeral parse preview so users can see exactly how a document is parsed and indexed as it ingests, instead of waiting blindly for it to land in Knowledge.

After a preview-mode ingest, a dialog shows:

  • Parsed layout — Docling's output rendered two ways:
    • paged formats (PDF/images): the real page raster with Docling's bounding-box overlay
    • office/non-paged formats (docx, pptx, xlsx, html, csv, md…): each region Docling detected, rendered as a colored, labeled block (Title / Heading / List / Table / Figure), since these formats have no page raster to draw boxes on
  • Search index — a live step pipeline (layout parsed → chunks created → embeddings → stored in OpenSearch), driven by the existing /tasks/enhanced data, with failures landing on the right step.

The preview is opt-in and ephemeral: results live in an in-memory TTL cache and the UI clearly communicates when the live window has ended (the document remains indexed and searchable).

How it works

  • IngestPreviewService — in-memory TTL cache (single-worker safe), keyed per (user_id, task_id, file_path) so multi-file and folder uploads each hold their own parse preview.
  • Docling is invoked in preview mode (image_export_mode=embedded, include_page_images, include_images); on success the DoclingDocument JSON is cached.
  • Two new endpoints:
    • GET /api/ingest/preview/{task_id}/docling?file=… → cached Docling JSON
    • GET /api/ingest/preview/{task_id}/index-proof?file=… → chunk/embedding proof
  • Frontend polls per-file (bounded), renders boxes (paged) or labeled blocks (non-paged), and falls back to a browser-native preview before Docling returns.

Entry points

  • Onboarding upload
  • Knowledge dropdown (single + multi-file / folder)
  • Cloud picker (Google Drive / OneDrive / SharePoint)
  • Connector file browser — explicit file selection only (bucket-level and sync-all intentionally excluded to avoid bulk preview load)

Scope & safeguards

  • Entire feature gated behind the ingest-preview run-mode flag.
  • Per-task preview cache cap (MAX_PREVIEWS_PER_TASK) so large folder/connector syncs can't grow the cache unbounded.
  • Bounded client polling (MAX_PREVIEW_POLLS) so a file that never produces a preview can't poll forever.
  • Connector files have no browser File object, so they show a spinner until Docling returns (no local pre-parse preview); binary office formats likewise show a placeholder until parsing finishes.

Out of scope (follow-ups)

  • User-facing parsing/chunking customization (re-chunk against the cached parse, structure-aware chunking, presets).
  • Splitting the connector-preview and knowledge-dropdown entry points into separate PRs if reviewers prefer smaller units.

Testing

  • IngestPreviewService cache (store/get, per-file, TTL expiry, per-task cap), preview endpoints, Docling preview options, langflow preview caching, router/task-service preview wiring.
  • Backend: affected unit tests pass; ruff clean on touched files.
  • Frontend: biome clean on touched files; tsc shows only pre-existing errors (@docling/docling-components types, test-runner globals).

Notes for reviewers

  • New dependency: @docling/docling-components (renders the page-image box overlay for paged formats).
  • IngestPreviewService is per-process (TTLCache), consistent with the repo's single-worker constraint; a Redis-backed cache would be required to scale horizontally.
  • docling_service.fetch_task_result is unchanged in shape (an earlier HTML-export path was dropped to keep the blast radius small).

…cument is parsed and indexed as it ingests, instead of waiting blindly for it to land in Knowledge.

After a preview-mode ingest, a dialog shows:
- **Parsed layout** — Docling's output rendered two ways:
  - paged formats (PDF/images): the real page raster with Docling's bounding-box overlay
  - office/non-paged formats (docx, pptx, xlsx, html, csv, md…): each region Docling detected, rendered as a colored, labeled block (Title / Heading / List / Table / Figure), since these formats have no page raster to draw boxes on
- **Search index** — a live step pipeline (layout parsed → chunks created → embeddings → stored in OpenSearch), driven by the existing `/tasks/enhanced` data, with failures landing on the right step.
The preview is **opt-in and ephemeral**: results live in an in-memory TTL cache and the UI clearly communicates when the live window has ended (the document remains indexed and searchable).
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7b90a1dd-addd-4b70-a975-a6b100588af3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch visual-parser-spike

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added frontend 🟨 Issues related to the UI/UX backend 🔷 Issues related to backend services (OpenSearch, Langflow, APIs) tests enhancement 🔵 New feature or request and removed enhancement 🔵 New feature or request labels Jun 30, 2026
Comment thread frontend/components/ingest-preview.tsx Fixed
Comment thread frontend/components/ingest-review.tsx Fixed
@Wallgau Wallgau force-pushed the visual-parser-spike branch from 67a0d1a to b0ff919 Compare June 30, 2026 18:02
@github-actions github-actions Bot added enhancement 🔵 New feature or request and removed enhancement 🔵 New feature or request labels Jun 30, 2026
@github-actions github-actions Bot added enhancement 🔵 New feature or request and removed enhancement 🔵 New feature or request labels Jun 30, 2026
Comment thread frontend/components/ingest-review.tsx Fixed
Resolve conflicts by keeping both ingest preview (preview/preview_mode)
from visual-parser-spike and shared indexing plus main's connector and
dependency updates.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions github-actions Bot added enhancement 🔵 New feature or request and removed enhancement 🔵 New feature or request labels Jul 7, 2026
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

React Doctor found 13 new issues in 4 files · 3 errors & 10 warnings · score 64 / 100 (Needs work) · 3 fixed · vs main

Errors

10 warnings

app/upload/[provider]/page.tsx

  • ⚠️ L35 Many related useState calls prefer-useReducer

components/connectors/shared-bucket-view.tsx

  • ⚠️ L52 Many related useState calls prefer-useReducer

components/ingest-review.tsx

  • ⚠️ L94 State only used in handlers rerender-state-only-in-handlers
  • ⚠️ L337 Plain img ships unoptimized images nextjs-no-img-element
  • ⚠️ L466 Array index used as a key no-array-index-as-key
  • ⚠️ L472 Array index used as a key no-array-index-as-key
  • ⚠️ L477 Array index used as a key no-array-index-as-key
  • ⚠️ L578 Plain img ships unoptimized images nextjs-no-img-element
  • ⚠️ L671 Large component is hard to read and change no-giant-component
  • ⚠️ L722 State only used in handlers rerender-state-only-in-handlers

Reviewed by React Doctor for commit 5091af2. See inline comments for fixes.

@github-actions github-actions Bot added enhancement 🔵 New feature or request and removed enhancement 🔵 New feature or request labels Jul 7, 2026
const filePhase = filesArray[0]?.phase;

if (filePhase === "docling" && currentStep < 1) {
setCurrentStep(1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

React Doctor · react-doctor/no-adjust-state-on-prop-change (error)

This effect adjusts state after a prop changes, so users briefly see the stale value.

Fix → Adjust the state inline during render with a prev-prop comparison (if (prop !== prevProp) { setPrevProp(prop); setX(...); }), or refactor to remove the duplicated state. Routing the adjustment through a useEffect forces an extra render with a stale UI between the two commits. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes

Docs

if (filePhase === "docling" && currentStep < 1) {
setCurrentStep(1);
} else if (filePhase === "langflow" && currentStep < 2) {
setCurrentStep(2);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

React Doctor · react-doctor/no-adjust-state-on-prop-change (error)

This effect adjusts state after a prop changes, so users briefly see the stale value.

Fix → Adjust the state inline during render with a prev-prop comparison (if (prop !== prevProp) { setPrevProp(prop); setX(...); }), or refactor to remove the duplicated state. Routing the adjustment through a useEffect forces an extra render with a stale UI between the two commits. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes

Docs

} else if (filePhase === "langflow" && currentStep < 2) {
setCurrentStep(2);
} else if (filePhase === "complete" && currentStep < 3) {
setCurrentStep(3);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

React Doctor · react-doctor/no-adjust-state-on-prop-change (error)

This effect adjusts state after a prop changes, so users briefly see the stale value.

Fix → Adjust the state inline during render with a prev-prop comparison (if (prop !== prevProp) { setPrevProp(prop); setX(...); }), or refactor to remove the duplicated state. Routing the adjustment through a useEffect forces an extra render with a stale UI between the two commits. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes

Docs

@@ -49,7 +51,8 @@ export function SharedBucketView({
initialSelectedBuckets,
}: SharedBucketViewProps) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

React Doctor · react-doctor/prefer-useReducer (warning)

6 useState calls in "SharedBucketView" can each trigger a separate render.

Fix → Group related state in useReducer so one logical update does not fan out into separate renders.

Docs

}) {
const containerRef = useRef<HTMLDivElement>(null);
const viewerRef = useRef<DoclingImgElement | null>(null);
const [ready, setReady] = useState(false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

React Doctor · react-doctor/rerender-state-only-in-handlers (warning)

Each update to "ready" redraws your component for nothing because this useState is set but never shown on screen.

Fix → Use useRef instead of useState when the value is only set and never shown on screen. ref.current = ... updates it without redrawing the component.

Docs

if (item.kind === "picture") {
return (
// biome-ignore lint/suspicious/noArrayIndexKey: parsed items are static
<DoclingPictureBlock key={`item-${index}`} picture={item.node} />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

React Doctor · react-doctor/no-array-index-as-key (warning)

Your users can see & submit the wrong data when this list reorders or filters, so use a stable id like key={item.id}, not the array index "index".

Fix → Use a stable id from the item, like key={item.id} or key={item.slug}. Index keys break when the list reorders or filters.

Docs

}
return (
// biome-ignore lint/suspicious/noArrayIndexKey: parsed items are static
<DoclingTextLine key={`item-${index}`} item={item.node} />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

React Doctor · react-doctor/no-array-index-as-key (warning)

Your users can see & submit the wrong data when this list reorders or filters, so use a stable id like key={item.id}, not the array index "index".

Fix → Use a stable id from the item, like key={item.id} or key={item.slug}. Index keys break when the list reorders or filters.

Docs


if (file.type.startsWith("image/") && objectUrl) {
return (
<img

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

React Doctor · react-doctor/nextjs-no-img-element (warning)

Plain ships unoptimized, oversized images.

Fix → Use next/image so users get optimized formats, responsive srcsets, and lazy loading instead of oversized image downloads.

Docs

return entry?.status === "failed" || entry?.status === "error";
}

export function IngestPreviewPanel({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

React Doctor · react-doctor/no-giant-component (warning)

Component "IngestPreviewPanel" is 301 lines long, which is hard to read & change. Split it into a few smaller components.

Fix → Pull each section into its own component so the parent is easier to read, test, and change.

Docs

filePath,
);
const [highlightItems, setHighlightItems] = useState<string | undefined>();
const [prevTaskId, setPrevTaskId] = useState(activeTaskId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

React Doctor · react-doctor/rerender-state-only-in-handlers (warning)

Each update to "prevTaskId" redraws your component for nothing because this useState is set but never shown on screen.

Fix → Use useRef instead of useState when the value is only set and never shown on screen. ref.current = ... updates it without redrawing the component.

Docs

// won't display PDFs inside an <iframe>.
return (
<object
data={objectUrl}
if (file.type.startsWith("image/") && objectUrl) {
return (
<img
src={objectUrl}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend 🔷 Issues related to backend services (OpenSearch, Langflow, APIs) enhancement 🔵 New feature or request frontend 🟨 Issues related to the UI/UX tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants