Project-aware assist, link suggestions, project-scoped RAG, semantic search tool, SSE race fix
- 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>
This commit is contained in:
@@ -45,7 +45,7 @@ _TOOL_LABELS: dict[str, str] = {
|
||||
"get_note": "Reading note",
|
||||
"list_notes": "Listing notes",
|
||||
"list_tasks": "Searching tasks",
|
||||
"search_notes": "Searching notes",
|
||||
"search_notes": "Searching notes (semantic)",
|
||||
"create_event": "Creating calendar event",
|
||||
"list_events": "Searching calendar",
|
||||
"search_events": "Searching calendar",
|
||||
@@ -149,6 +149,7 @@ async def run_generation(
|
||||
include_note_ids: list[int] | None = None,
|
||||
excluded_note_ids: list[int] | None = None,
|
||||
think: bool = False,
|
||||
rag_project_id: int | None = None,
|
||||
) -> None:
|
||||
"""Stream LLM response into buffer with periodic DB flushes."""
|
||||
MAX_TOOL_ROUNDS = 5
|
||||
@@ -172,13 +173,14 @@ async def run_generation(
|
||||
history_to_use, history_summary = await summarize_history_for_context(history, model)
|
||||
|
||||
# Phase 3: Build context and wait for model in parallel.
|
||||
model_load_task = asyncio.create_task(wait_for_model_loaded(model, timeout=90.0))
|
||||
model_load_task = asyncio.create_task(wait_for_model_loaded(model, timeout=180.0))
|
||||
|
||||
context_task = asyncio.create_task(build_context(
|
||||
user_id, history_to_use, context_note_id, user_content,
|
||||
history_summary=history_summary,
|
||||
include_note_ids=include_note_ids,
|
||||
excluded_note_ids=excluded_note_ids,
|
||||
rag_project_id=rag_project_id,
|
||||
))
|
||||
|
||||
messages, context_meta = await context_task
|
||||
@@ -192,7 +194,7 @@ async def run_generation(
|
||||
buf.append_event("status", {"status": "Loading model..."})
|
||||
loaded = await model_load_task
|
||||
if not loaded:
|
||||
logger.warning("Model %s did not load within 90s — proceeding anyway", model)
|
||||
logger.warning("Model %s did not load within 180s — proceeding anyway", model)
|
||||
|
||||
t_start = time.monotonic()
|
||||
timing: dict = {
|
||||
@@ -415,6 +417,9 @@ async def run_assist_generation(
|
||||
On each retry the accumulated content is reset so the done event
|
||||
always reflects only the successful generation.
|
||||
"""
|
||||
input_chars = sum(len(m.get("content", "")) for m in messages)
|
||||
logger.info("Assist generation started: model=%s, input_chars=%d", model, input_chars)
|
||||
|
||||
last_exc: BaseException | None = None
|
||||
for attempt in range(3):
|
||||
if attempt > 0:
|
||||
@@ -429,9 +434,15 @@ async def run_assist_generation(
|
||||
buf.content_so_far += chunk
|
||||
buf.append_event("chunk", {"chunk": chunk})
|
||||
|
||||
output_chars = len(buf.content_so_far)
|
||||
logger.info(
|
||||
"Assist generation complete: output_chars=%d, events=%d",
|
||||
output_chars, len(buf.events),
|
||||
)
|
||||
buf.state = GenerationState.COMPLETED
|
||||
buf.finished_at = time.monotonic()
|
||||
buf.append_event("done", {"done": True, "full_text": buf.content_so_far})
|
||||
logger.info("Assist done event appended (event index %d)", len(buf.events) - 1)
|
||||
return
|
||||
|
||||
except httpx.HTTPStatusError as exc:
|
||||
|
||||
Reference in New Issue
Block a user