Closes the upgrade-path gap from Phase 3: a KB or thread whose chunks
were ingested under one strategy can now be rebuilt under a different
one without losing the uploaded files.
Backend
- routes/rag.py:
- POST /api/rag/knowledge-bases/{kb_id}/reingest takes optional
chunking_strategy / mode / embedding_model in the body. Validates
the (multimodal, late) constraint via _validate_mode_combo, updates
the rag_knowledge_bases row, wipes scope artifacts (sqlite chunks
via cascade, Qdrant collection, bm25), and re-INSERTs a fresh
rag_documents row + ingestion job per stored file. Returns the new
job IDs so callers can stream progress via the existing SSE.
- POST /api/rag/threads/{thread_id}/reingest is the simpler thread
variant — no body, rebuilds with current defaults.
- Shared _reingest_scope helper strips the UUID upload prefix when
re-naming docs so users see the original filenames again.
Frontend
- rag-api.ts: reingestKnowledgeBase(kbId, opts) and
reingestThreadDocuments(threadId) wrappers + ReingestResponse type.
- rag-store.ts: reingestKB / reingestThread actions refresh the KB +
doc lists and subscribe to every returned job so the existing
IngestionProgress chips render without further wiring.
- kb-reconfigure-dialog.tsx (new): mirrors KBCreateDialog but
pre-fills with the KB's current strategy / mode / embedder, enforces
the same (multimodal + late) constraint with disabled options, and
confirms before submitting. Submit label flips between "Re-index"
(no settings change) and "Reconfigure & re-index".
- kb-detail-panel.tsx: header gains the chunking + mode summary and
a "Reconfigure…" button that opens the dialog. Button is disabled
when the KB has no documents.
- chat-settings-sheet.tsx Retrieval section: "Re-index" button beside
the existing "Clear thread index" when the thread has documents.
Tests
- test_rag_reingest.py: ReingestKBRequest accepts optional fields,
rejects unknown enum values via Pydantic, and the shared mode-combo
guard still bites on the reingest path.