Task work log, inline writing assistant, task editor sidebar layout
Backend: - Migration 0021: task_logs table (FK → notes + users, CASCADE, indexed) - models/task_log.py: SQLAlchemy model with to_dict() - services/task_logs.py: CRUD with ownership checks, _UNSET sentinel for optional duration clear - routes/task_logs.py: GET/POST/PATCH/DELETE /api/tasks/<id>/logs - services/tools.py: log_work LLM tool (resolves task by title, creates log entry) - services/generation_task.py: retry assist generation up to 3× on HTTP 500 Frontend: - types/task.ts: TaskLog interface - TaskLogSection.vue: chronological work log with date+time timestamps, duration badge, inline edit, autofocus - InlineAssistPanel.vue: streaming preview + diff review rendered inline in editor column - useAssist.ts: removed chatStore.chatReady gate; toast notifications for errors - NoteEditorView.vue + TaskEditorView.vue: inline assist panel, aside restricted to idle state - TaskEditorView.vue: two-column layout (editor+log left, metadata sidebar right), body defaults to Preview, sidebarOpen accordion for mobile - editor-shared.css: .assist-active-hint style Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -409,18 +409,40 @@ async def run_assist_generation(
|
||||
messages: list[dict],
|
||||
model: str,
|
||||
) -> None:
|
||||
"""Stream LLM response for assist into buffer. No DB persistence."""
|
||||
try:
|
||||
async for chunk in stream_chat(messages, model, options={"num_predict": 4096}):
|
||||
buf.content_so_far += chunk
|
||||
buf.append_event("chunk", {"chunk": chunk})
|
||||
"""Stream LLM response for assist into buffer. No DB persistence.
|
||||
|
||||
buf.state = GenerationState.COMPLETED
|
||||
buf.finished_at = time.monotonic()
|
||||
buf.append_event("done", {"done": True, "full_text": buf.content_so_far})
|
||||
Retries up to 3 times on Ollama 500 errors (model still loading).
|
||||
On each retry the accumulated content is reset so the done event
|
||||
always reflects only the successful generation.
|
||||
"""
|
||||
last_exc: BaseException | None = None
|
||||
for attempt in range(3):
|
||||
if attempt > 0:
|
||||
delay = 3.0 * attempt
|
||||
logger.warning(
|
||||
"Ollama assist stream 500 (attempt %d/3), retrying in %.0fs", attempt, delay
|
||||
)
|
||||
await asyncio.sleep(delay)
|
||||
try:
|
||||
buf.content_so_far = ""
|
||||
async for chunk in stream_chat(messages, model, options={"num_predict": 4096}):
|
||||
buf.content_so_far += chunk
|
||||
buf.append_event("chunk", {"chunk": chunk})
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("Error in assist generation task")
|
||||
buf.state = GenerationState.ERRORED
|
||||
buf.finished_at = time.monotonic()
|
||||
buf.append_event("error", {"error": str(e)})
|
||||
buf.state = GenerationState.COMPLETED
|
||||
buf.finished_at = time.monotonic()
|
||||
buf.append_event("done", {"done": True, "full_text": buf.content_so_far})
|
||||
return
|
||||
|
||||
except httpx.HTTPStatusError as exc:
|
||||
last_exc = exc
|
||||
if exc.response.status_code != 500:
|
||||
break
|
||||
except Exception as exc:
|
||||
last_exc = exc
|
||||
break
|
||||
|
||||
logger.exception("Error in assist generation task")
|
||||
buf.state = GenerationState.ERRORED
|
||||
buf.finished_at = time.monotonic()
|
||||
buf.append_event("error", {"error": str(last_exc)})
|
||||
|
||||
Reference in New Issue
Block a user