feat(rag): PDF/docx ingest + chat citations
- Upload endpoint (base64 JSON, no multipart dep): extracts text from pdf/docx/txt/md via pypdf + python-docx, then runs the existing chunk/embed pipeline. Documents page uploads files straight through. - Citations: the chat stream emits an SSE `sources` event listing the documents that fed the answer; the UI shows them as chips under the reply. - Deps: pypdf, python-docx (both pure-Python, Windows-safe). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -55,3 +55,19 @@ def test_add_list_search_delete_roundtrip():
|
||||
def test_search_empty_query_returns_nothing():
|
||||
s = _store()
|
||||
assert asyncio.run(s.search_documents("", _fake_embed)) == []
|
||||
|
||||
|
||||
def test_extract_text_by_type():
|
||||
from synapse.main import _extract_text
|
||||
# plain text / markdown -> UTF-8 decode
|
||||
assert _extract_text("notes.md", b"# Title\n\nbody") == "# Title\n\nbody"
|
||||
assert _extract_text("x.txt", "café".encode("utf-8")) == "café"
|
||||
# a real (tiny) PDF built with pypdf -> text extracted back out
|
||||
from pypdf import PdfWriter, PdfReader
|
||||
import io
|
||||
w = PdfWriter()
|
||||
w.add_blank_page(width=200, height=200)
|
||||
buf = io.BytesIO(); w.write(buf)
|
||||
out = _extract_text("blank.pdf", buf.getvalue())
|
||||
assert isinstance(out, str) # blank page -> "" or whitespace, never raises
|
||||
assert PdfReader(io.BytesIO(buf.getvalue())).pages # sanity: it was a valid PDF
|
||||
|
||||
Reference in New Issue
Block a user