48f070f773
- Writing assistant: inject project notes as context (definition-tagged first), wikilink suggestions - Link suggestions: server-side endpoint finds unlinked term occurrences, NoteEditorView sidebar panel - Project-scoped RAG: ChatView ProjectSelector filters semantic+keyword search to selected project - Semantic search tool: LLM search_notes upgraded to hybrid semantic (0.40 threshold) + keyword merge - SSE race condition fix: drain remaining events after stream loop exits in chat.py and notes.py - RAG_AUTO_SNIPPET raised 800→4000; sidebar include uses full note body; MAX_BODY_CHARS 8000→24000 - Enter-to-submit on writing assistant instruction textareas (note and task editors) - DiffView: equal-line collapsing with 3-line context around changes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
93 lines
4.3 KiB
Python
93 lines
4.3 KiB
Python
MAX_BODY_CHARS = 24000
|
|
MAX_CONTEXT_NOTE_CHARS = 1500
|
|
|
|
|
|
def _build_context_block(context_notes: list[dict]) -> str:
|
|
"""Format project context notes into a prompt block."""
|
|
lines = [
|
|
"--- Project Context ---",
|
|
"The following notes are from the same project. Notes tagged 'definition' are "
|
|
"canonical term definitions — treat them as authoritative. Use them to maintain "
|
|
"consistency in terminology, tone, style, and [[wikilinks]] to other documents.",
|
|
"",
|
|
]
|
|
for n in context_notes:
|
|
tags_str = f" [tags: {', '.join(n['tags'])}]" if n.get("tags") else ""
|
|
body_preview = (n["body"] or "")[:MAX_CONTEXT_NOTE_CHARS]
|
|
if len(n["body"] or "") > MAX_CONTEXT_NOTE_CHARS:
|
|
body_preview += "…"
|
|
lines.append(f"**{n['title']}**{tags_str}")
|
|
if body_preview:
|
|
lines.append(body_preview)
|
|
lines.append("")
|
|
lines.append("--- End Project Context ---")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def build_assist_messages(
|
|
body: str,
|
|
target_section: str,
|
|
instruction: str,
|
|
whole_doc: bool = False,
|
|
context_notes: list[dict] | None = None,
|
|
) -> list[dict]:
|
|
"""Build Ollama messages for writing assist.
|
|
|
|
When whole_doc=True, the model revises the entire document.
|
|
When whole_doc=False (section mode), the full body is provided as read-only
|
|
context and the model outputs only the replacement for the target section.
|
|
|
|
context_notes: optional list of {title, tags, body} dicts from the same project,
|
|
injected so the model can maintain consistency with related documents.
|
|
"""
|
|
truncated_body = body[:MAX_BODY_CHARS]
|
|
if len(body) > MAX_BODY_CHARS:
|
|
truncated_body += "\n… (truncated)"
|
|
|
|
context_block = (_build_context_block(context_notes) + "\n\n") if context_notes else ""
|
|
|
|
if whole_doc:
|
|
system_content = (
|
|
"You are a writing assistant. Revise the document per the instruction. "
|
|
"Output ONLY the complete revised document. Preserve all structure and "
|
|
"content not addressed by the instruction. "
|
|
"Preserve all markdown formatting exactly — including bullet lists (- item), "
|
|
"numbered lists (1. item), nested/indented sub-items ( - sub), bold (**text**), "
|
|
"italic (_text_), and code blocks. Never flatten nested lists into plain text. "
|
|
"When referencing concepts defined in the Project Context, use [[wikilinks]] "
|
|
"to link to the relevant note by its exact title."
|
|
)
|
|
user_content = (
|
|
f"{context_block}"
|
|
f"--- Document ---\n{truncated_body}\n--- End Document ---\n\n"
|
|
f"Instruction: {instruction}"
|
|
)
|
|
else:
|
|
system_content = (
|
|
"You are an AI writing assistant integrated into a note-taking app. "
|
|
"The user is editing a document. The full document is shown below for context.\n\n"
|
|
f"--- Full Document ---\n{truncated_body}\n--- End Document ---\n\n"
|
|
"The user will give you a specific section of the document and an instruction. "
|
|
"Output ONLY the replacement text for that section. "
|
|
"If the target section starts with a markdown heading (e.g. ## Heading), "
|
|
"your output MUST also start with a heading at the same level. "
|
|
"You may revise the heading text but do not remove it. "
|
|
"Do not include other sections, explanatory text, or markdown code fences around the output. "
|
|
"Match the document's existing tone and style. "
|
|
"IMPORTANT: Preserve all markdown formatting exactly — including bullet lists (- item), "
|
|
"numbered lists (1. item), nested/indented sub-items ( - sub), bold (**text**), "
|
|
"italic (_text_), and code blocks. Never flatten nested lists into plain text. "
|
|
"When referencing concepts defined in the Project Context, use [[wikilinks]] "
|
|
"to link to the relevant note by its exact title."
|
|
)
|
|
user_content = (
|
|
f"{context_block}"
|
|
f"--- Target Section ---\n{target_section}\n--- End Target Section ---\n\n"
|
|
f"Instruction: {instruction}"
|
|
)
|
|
|
|
return [
|
|
{"role": "system", "content": system_content},
|
|
{"role": "user", "content": user_content},
|
|
]
|