feat: Visual parser spike#1990
Conversation
…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).
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
67a0d1a to
b0ff919
Compare
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>
|
React Doctor found 13 new issues in 4 files · 3 errors & 10 warnings · score 64 / 100 (Needs work) · 3 fixed · vs Errors
10 warnings
Reviewed by React Doctor for commit |
| const filePhase = filesArray[0]?.phase; | ||
|
|
||
| if (filePhase === "docling" && currentStep < 1) { | ||
| setCurrentStep(1); |
There was a problem hiding this comment.
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
| if (filePhase === "docling" && currentStep < 1) { | ||
| setCurrentStep(1); | ||
| } else if (filePhase === "langflow" && currentStep < 2) { | ||
| setCurrentStep(2); |
There was a problem hiding this comment.
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
| } else if (filePhase === "langflow" && currentStep < 2) { | ||
| setCurrentStep(2); | ||
| } else if (filePhase === "complete" && currentStep < 3) { | ||
| setCurrentStep(3); |
There was a problem hiding this comment.
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
| @@ -49,7 +51,8 @@ export function SharedBucketView({ | |||
| initialSelectedBuckets, | |||
| }: SharedBucketViewProps) { | |||
There was a problem hiding this comment.
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.
| }) { | ||
| const containerRef = useRef<HTMLDivElement>(null); | ||
| const viewerRef = useRef<DoclingImgElement | null>(null); | ||
| const [ready, setReady] = useState(false); |
There was a problem hiding this comment.
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.
| if (item.kind === "picture") { | ||
| return ( | ||
| // biome-ignore lint/suspicious/noArrayIndexKey: parsed items are static | ||
| <DoclingPictureBlock key={`item-${index}`} picture={item.node} /> |
There was a problem hiding this comment.
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.
| } | ||
| return ( | ||
| // biome-ignore lint/suspicious/noArrayIndexKey: parsed items are static | ||
| <DoclingTextLine key={`item-${index}`} item={item.node} /> |
There was a problem hiding this comment.
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.
|
|
||
| if (file.type.startsWith("image/") && objectUrl) { | ||
| return ( | ||
| <img |
| return entry?.status === "failed" || entry?.status === "error"; | ||
| } | ||
|
|
||
| export function IngestPreviewPanel({ |
There was a problem hiding this comment.
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.
| filePath, | ||
| ); | ||
| const [highlightItems, setHighlightItems] = useState<string | undefined>(); | ||
| const [prevTaskId, setPrevTaskId] = useState(activeTaskId); |
There was a problem hiding this comment.
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.
| // won't display PDFs inside an <iframe>. | ||
| return ( | ||
| <object | ||
| data={objectUrl} |
| if (file.type.startsWith("image/") && objectUrl) { | ||
| return ( | ||
| <img | ||
| src={objectUrl} |
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:
/tasks/enhanceddata, 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.image_export_mode=embedded,include_page_images,include_images); on success theDoclingDocumentJSON is cached.GET /api/ingest/preview/{task_id}/docling?file=…→ cached Docling JSONGET /api/ingest/preview/{task_id}/index-proof?file=…→ chunk/embedding proofEntry points
Scope & safeguards
MAX_PREVIEWS_PER_TASK) so large folder/connector syncs can't grow the cache unbounded.MAX_PREVIEW_POLLS) so a file that never produces a preview can't poll forever.Fileobject, 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)
Testing
IngestPreviewServicecache (store/get, per-file, TTL expiry, per-task cap), preview endpoints, Docling preview options, langflow preview caching, router/task-service preview wiring.ruffclean on touched files.biomeclean on touched files;tscshows only pre-existing errors (@docling/docling-componentstypes, test-runner globals).Notes for reviewers
@docling/docling-components(renders the page-image box overlay for paged formats).IngestPreviewServiceis 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_resultis unchanged in shape (an earlier HTML-export path was dropped to keep the blast radius small).