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:
@@ -0,0 +1,68 @@
|
||||
"""Task work log routes — /api/tasks/<task_id>/logs."""
|
||||
import logging
|
||||
|
||||
from quart import Blueprint, jsonify, request
|
||||
|
||||
from fabledassistant.auth import get_current_user_id, login_required
|
||||
from fabledassistant.services.task_logs import create_log, delete_log, list_logs, update_log
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
task_logs_bp = Blueprint("task_logs", __name__, url_prefix="/api/tasks")
|
||||
|
||||
|
||||
@task_logs_bp.route("/<int:task_id>/logs", methods=["GET"])
|
||||
@login_required
|
||||
async def list_logs_route(task_id: int):
|
||||
uid = get_current_user_id()
|
||||
logs = await list_logs(uid, task_id)
|
||||
return jsonify({"logs": [l.to_dict() for l in logs]})
|
||||
|
||||
|
||||
@task_logs_bp.route("/<int:task_id>/logs", methods=["POST"])
|
||||
@login_required
|
||||
async def create_log_route(task_id: int):
|
||||
uid = get_current_user_id()
|
||||
data = await request.get_json()
|
||||
content = (data or {}).get("content", "").strip()
|
||||
if not content:
|
||||
return jsonify({"error": "content is required"}), 400
|
||||
duration_minutes = (data or {}).get("duration_minutes")
|
||||
if duration_minutes is not None:
|
||||
try:
|
||||
duration_minutes = int(duration_minutes)
|
||||
except (TypeError, ValueError):
|
||||
return jsonify({"error": "duration_minutes must be an integer"}), 400
|
||||
try:
|
||||
log = await create_log(uid, task_id, content, duration_minutes)
|
||||
except ValueError as e:
|
||||
return jsonify({"error": str(e)}), 404
|
||||
return jsonify(log.to_dict()), 201
|
||||
|
||||
|
||||
@task_logs_bp.route("/<int:task_id>/logs/<int:log_id>", methods=["PATCH"])
|
||||
@login_required
|
||||
async def update_log_route(task_id: int, log_id: int):
|
||||
uid = get_current_user_id()
|
||||
data = await request.get_json() or {}
|
||||
content = data.get("content")
|
||||
if content is not None:
|
||||
content = content.strip() or None
|
||||
|
||||
from fabledassistant.services.task_logs import _UNSET
|
||||
duration_minutes = data.get("duration_minutes", _UNSET)
|
||||
|
||||
log = await update_log(uid, log_id, content=content, duration_minutes=duration_minutes)
|
||||
if log is None:
|
||||
return jsonify({"error": "Log entry not found"}), 404
|
||||
return jsonify(log.to_dict())
|
||||
|
||||
|
||||
@task_logs_bp.route("/<int:task_id>/logs/<int:log_id>", methods=["DELETE"])
|
||||
@login_required
|
||||
async def delete_log_route(task_id: int, log_id: int):
|
||||
uid = get_current_user_id()
|
||||
deleted = await delete_log(uid, log_id)
|
||||
if not deleted:
|
||||
return jsonify({"error": "Log entry not found"}), 404
|
||||
return "", 204
|
||||
Reference in New Issue
Block a user