Self-hosted knowledge management for SMEs: a split-screen Markdown editor whose sections an LLM refines while you write, and RAG question answering over the documents that result. FastAPI + Postgres/pgvector on the back, SvelteKit on the front, everything OpenAI-compatible and self-hostable. Squashed into a single commit; the development history stays local. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CA43ZJda8Rbp2hKXNy8f6b
83 lines
3.1 KiB
Python
83 lines
3.1 KiB
Python
"""The citation popover shows prose, not Markdown source.
|
|
|
|
The popover is a small hover surface showing a fragment, so the excerpt is
|
|
reduced to prose rather than rendered: a cited table would otherwise become
|
|
a real table squeezed into ~320px, and a cited heading would render at h2
|
|
size. Clicking the badge opens the document in the side panel, which does
|
|
render Markdown properly.
|
|
|
|
These cases are all real shapes from the fixture corpus.
|
|
"""
|
|
|
|
from app.modes.query import EXCERPT_CHARS
|
|
from app.modes.query import excerpt as make_excerpt
|
|
|
|
|
|
def test_a_cited_table_reads_as_prose() -> None:
|
|
"""The worst case, and the one that sent this back for a second pass:
|
|
the divider row is pure punctuation and survives naive stripping."""
|
|
excerpt = make_excerpt(
|
|
"| Code | Bedeutung | Sofortmaßnahme |\n"
|
|
"|-------|------------|----------------|\n"
|
|
"| E-101 | Not-Aus-Kreis unterbrochen | Alle Not-Aus-Taster prüfen |"
|
|
)
|
|
assert "|" not in excerpt
|
|
assert "---" not in excerpt
|
|
assert excerpt.startswith("Code · Bedeutung · Sofortmaßnahme · E-101")
|
|
|
|
|
|
def test_inline_emphasis_loses_its_markers_not_its_words() -> None:
|
|
for source, expected in [
|
|
("**Eingang erfassen**: im ERP anlegen.", "Eingang erfassen: im ERP anlegen."),
|
|
("die Presse muss *drucklos* sein", "die Presse muss drucklos sein"),
|
|
("__Achtung__ beim Anfahren", "Achtung beim Anfahren"),
|
|
("setze `max_turns` hoch", "setze max_turns hoch"),
|
|
("**fett _und_ kursiv**", "fett und kursiv"),
|
|
]:
|
|
assert make_excerpt(source) == expected
|
|
|
|
|
|
def test_links_keep_their_text_and_drop_their_target() -> None:
|
|
excerpt = make_excerpt(
|
|
"Siehe [das Handbuch](https://intranet.example/doc.pdf) dazu."
|
|
)
|
|
assert excerpt == "Siehe das Handbuch dazu."
|
|
# A URL in a hover preview is noise, and it is also the part a reader
|
|
# cannot click here anyway.
|
|
assert "http" not in excerpt
|
|
|
|
|
|
def test_block_markers_and_fences_are_dropped() -> None:
|
|
excerpt = make_excerpt(
|
|
"## Wartung\n\n"
|
|
"- **Getriebeöl GX-220**: monatlich\n"
|
|
"1. Bettbahnöl wöchentlich\n"
|
|
"> Wichtig: erst entlüften\n"
|
|
"```yaml\n"
|
|
"interval: 30\n"
|
|
"```"
|
|
)
|
|
assert excerpt.startswith("Wartung Getriebeöl GX-220: monatlich")
|
|
for marker in ("##", "```", "> ", "**"):
|
|
assert marker not in excerpt
|
|
|
|
|
|
def test_plain_prose_is_left_alone() -> None:
|
|
source = "Die Kaffeemaschine wird freitags entkalkt."
|
|
assert make_excerpt(source) == source
|
|
|
|
|
|
def test_underscores_inside_identifiers_survive() -> None:
|
|
"""`result_document_id` is not italics. Emphasis needs a non-space
|
|
character on both sides, which is what keeps snake_case intact."""
|
|
assert make_excerpt("das Feld result_document_id bleibt leer") == (
|
|
"das Feld result_document_id bleibt leer"
|
|
)
|
|
|
|
|
|
def test_long_content_is_cut_on_a_word_boundary() -> None:
|
|
excerpt = make_excerpt("Wartung " * 200)
|
|
assert len(excerpt) <= EXCERPT_CHARS + 1 # the ellipsis
|
|
assert excerpt.endswith("…")
|
|
assert not excerpt.rstrip("…").endswith(" ")
|