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:
@@ -440,6 +440,34 @@ _CORE_TOOLS = [
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "log_work",
|
||||
"description": (
|
||||
"Add a work log entry to a task to record progress, work done, or time spent. "
|
||||
"Use this when the user says they worked on, completed, or spent time on a task."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task": {
|
||||
"type": "string",
|
||||
"description": "Title or keyword identifying the task (required)",
|
||||
},
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "Description of the work done (required)",
|
||||
},
|
||||
"duration_minutes": {
|
||||
"type": "integer",
|
||||
"description": "Optional time spent in minutes",
|
||||
},
|
||||
},
|
||||
"required": ["task", "content"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
# CalDAV tools — only included when user has CalDAV configured
|
||||
@@ -1455,6 +1483,33 @@ async def execute_tool(user_id: int, tool_name: str, arguments: dict) -> dict:
|
||||
},
|
||||
}
|
||||
|
||||
elif tool_name == "log_work":
|
||||
from fabledassistant.services.task_logs import create_log as _create_log
|
||||
task_query = arguments.get("task", "")
|
||||
content = arguments.get("content", "").strip()
|
||||
if not task_query:
|
||||
return {"success": False, "error": "task is required"}
|
||||
if not content:
|
||||
return {"success": False, "error": "content is required"}
|
||||
duration_minutes = arguments.get("duration_minutes")
|
||||
if duration_minutes is not None:
|
||||
try:
|
||||
duration_minutes = int(duration_minutes)
|
||||
except (TypeError, ValueError):
|
||||
duration_minutes = None
|
||||
# Resolve task by exact title then fuzzy search (tasks only)
|
||||
note = await get_note_by_title(user_id, task_query)
|
||||
if note is None or note.status is None:
|
||||
notes, _ = await list_notes(user_id=user_id, q=task_query, is_task=True, limit=5)
|
||||
note = notes[0] if notes else None
|
||||
if note is None:
|
||||
return {"success": False, "error": f"No task found matching '{task_query}'."}
|
||||
try:
|
||||
log = await _create_log(user_id, note.id, content, duration_minutes)
|
||||
except ValueError as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
return {"success": True, "log": log.to_dict(), "task": note.title}
|
||||
|
||||
else:
|
||||
return {"success": False, "error": f"Unknown tool: {tool_name}"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user