Pablan, as it stands

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
This commit is contained in:
ProfessorNova
2026-09-04 09:21:37 +02:00
co-authored by Claude Opus 5
parent 68d3a43191
commit 784b76baf7
346 changed files with 43430 additions and 0 deletions
+131
View File
@@ -0,0 +1,131 @@
"""Find the section of a Markdown document the cursor sits in.
The refinement endpoint refines exactly one section at a time (FIM-style),
so this is the AUTHORITATIVE boundary computation — the client mirrors it for
a visual highlight, but the server owns it. A section runs from the nearest
heading at or above the cursor down to the line before the next heading of
the same or higher level; content before the first heading is its own
section. A section whose body exceeds the chunk cap narrows to the blank-line
paragraph at the cursor, so a large document never refines as one giant block.
Shares the heading regex, fence-awareness and cap with `rag/chunking.py` so
"what is a section" means the same thing to refinement and to indexing.
"""
from dataclasses import dataclass
from app.rag.chunking import HEADING_RE, TARGET_CHUNK_CHARS
@dataclass(frozen=True)
class ActiveSection:
start_line: int # 1-based, inclusive, into content_md
end_line: int # 1-based, inclusive
def _heading_lines(lines: list[str]) -> list[tuple[int, int]]:
"""(line_index_0based, level) for every heading line, ignoring fences."""
headings: list[tuple[int, int]] = []
in_fence = False
for i, line in enumerate(lines):
if line.lstrip().startswith("```"):
in_fence = not in_fence
continue
if in_fence:
continue
match = HEADING_RE.match(line)
if match:
headings.append((i, len(match.group(1))))
return headings
def _paragraph_at(
lines: list[str], start0: int, end0: int, cursor0: int
) -> tuple[int, int] | None:
"""The blank-line-delimited block (fence-aware) at the cursor, within
[start0, end0]. Falls back to the block just before the cursor when it
sits on a blank gap, else the first block."""
blocks: list[tuple[int, int]] = []
block_start: int | None = None
in_fence = False
for i in range(start0, end0 + 1):
line = lines[i]
if line.lstrip().startswith("```"):
in_fence = not in_fence
if block_start is None:
block_start = i
continue
if not line.strip() and not in_fence:
if block_start is not None:
blocks.append((block_start, i - 1))
block_start = None
elif block_start is None:
block_start = i
if block_start is not None:
blocks.append((block_start, end0))
if not blocks:
return None
for b_start, b_end in blocks:
if b_start <= cursor0 <= b_end:
return b_start, b_end
for b_start, b_end in reversed(blocks):
if b_end < cursor0:
return b_start, b_end
return blocks[0]
def active_section(content_md: str, cursor_line: int) -> ActiveSection:
lines = content_md.splitlines()
n = len(lines)
if n == 0:
return ActiveSection(1, 1)
cursor0 = max(1, min(cursor_line, n)) - 1
headings = _heading_lines(lines)
owner: tuple[int, int] | None = None
for idx, level in headings:
if idx <= cursor0:
owner = (idx, level)
else:
break
if owner is None:
# Preamble before the first heading (or a document with no headings).
start0 = 0
end0 = headings[0][0] - 1 if headings else n - 1
else:
start0, owner_level = owner
end0 = n - 1
for idx, level in headings:
if idx > start0 and level <= owner_level:
end0 = idx - 1
break
# Trailing blank lines belong to the separation before the next section,
# not to this one: keeping them in the range would let an accepted
# suggestion swallow the blank line above the next heading.
while end0 > start0 and not lines[end0].strip():
end0 -= 1
body = "\n".join(lines[start0 : end0 + 1])
if len(body) > TARGET_CHUNK_CHARS:
narrowed = _paragraph_at(lines, start0, end0, cursor0)
if narrowed is not None:
start0, end0 = narrowed
return ActiveSection(start_line=start0 + 1, end_line=end0 + 1)
def slice_lines(
content_md: str, start_line: int, end_line: int
) -> tuple[str, str, str]:
"""(prefix, section, suffix) split at the 1-based inclusive line range.
The section is the lines the model refines; prefix/suffix are the rest of
the document, handed to the model as context it must not re-emit.
"""
lines = content_md.splitlines()
prefix = "\n".join(lines[: start_line - 1])
section = "\n".join(lines[start_line - 1 : end_line])
suffix = "\n".join(lines[end_line:])
return prefix, section, suffix