Note editor sidebar, full-doc assist, persistent drafts, version history
NoteEditorView: two-column sidebar layout (project/milestone/tags/assist always visible), removed assist toggle button, InlineAssistPanel removed. Writing assist: whole_doc mode rewrites entire document; DiffView.vue replaces editor during review showing full-document diff. Scope dropdown in sidebar switches between whole-document and section modes. Persistent drafts: migration 0022 adds note_drafts (UNIQUE per note+user) and note_versions (max 20, auto-pruned) tables. Draft saved after generation completes, restored on editor mount, cleared on accept/reject. Version snapshot created automatically whenever note body changes on save. HistoryPanel.vue: version list + DiffView modal, restore button writes body back to editor. Config: OLLAMA_NUM_CTX default raised to 65536; assist num_predict now tracks Config.OLLAMA_NUM_CTX instead of a hardcoded 4096. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,8 @@ from fabledassistant.services.notes import (
|
||||
)
|
||||
from fabledassistant.services.settings import get_setting
|
||||
from fabledassistant.services.tag_suggestions import suggest_tags
|
||||
from fabledassistant.services.note_drafts import upsert_draft, get_draft, delete_draft
|
||||
from fabledassistant.services.note_versions import list_versions, get_version
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -285,11 +287,15 @@ async def assist_route():
|
||||
target_section = data.get("target_section", "")
|
||||
instruction = data.get("instruction", "")
|
||||
|
||||
if not target_section or not instruction:
|
||||
return jsonify({"error": "target_section and instruction are required"}), 400
|
||||
whole_doc = bool(data.get("whole_doc", False))
|
||||
|
||||
if not whole_doc and not target_section:
|
||||
return jsonify({"error": "target_section is required for section mode"}), 400
|
||||
if not instruction:
|
||||
return jsonify({"error": "instruction is required"}), 400
|
||||
|
||||
model = await get_setting(uid, "default_model", Config.OLLAMA_MODEL) or Config.OLLAMA_MODEL
|
||||
messages = build_assist_messages(body, target_section, instruction)
|
||||
messages = build_assist_messages(body, target_section, instruction, whole_doc=whole_doc)
|
||||
|
||||
buf = create_assist_buffer(uid)
|
||||
asyncio.create_task(run_assist_generation(buf, messages, model))
|
||||
@@ -336,3 +342,63 @@ async def assist_stream_route():
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# ── Draft routes ─────────────────────────────────────────────────────────────
|
||||
|
||||
@notes_bp.route("/<int:note_id>/draft", methods=["GET"])
|
||||
@login_required
|
||||
async def get_draft_route(note_id: int):
|
||||
uid = get_current_user_id()
|
||||
draft = await get_draft(uid, note_id)
|
||||
if draft is None:
|
||||
return jsonify({"error": "No draft found"}), 404
|
||||
return jsonify(draft.to_dict())
|
||||
|
||||
|
||||
@notes_bp.route("/<int:note_id>/draft", methods=["PUT"])
|
||||
@login_required
|
||||
async def upsert_draft_route(note_id: int):
|
||||
uid = get_current_user_id()
|
||||
# Verify note ownership
|
||||
note = await get_note(uid, note_id)
|
||||
if note is None:
|
||||
return jsonify({"error": "Note not found"}), 404
|
||||
data = await request.get_json()
|
||||
draft = await upsert_draft(
|
||||
user_id=uid,
|
||||
note_id=note_id,
|
||||
proposed_body=data.get("proposed_body", ""),
|
||||
original_body=data.get("original_body", ""),
|
||||
instruction=data.get("instruction", ""),
|
||||
scope=data.get("scope", "document"),
|
||||
)
|
||||
return jsonify(draft.to_dict()), 200
|
||||
|
||||
|
||||
@notes_bp.route("/<int:note_id>/draft", methods=["DELETE"])
|
||||
@login_required
|
||||
async def delete_draft_route(note_id: int):
|
||||
uid = get_current_user_id()
|
||||
await delete_draft(uid, note_id)
|
||||
return "", 204
|
||||
|
||||
|
||||
# ── Version routes ────────────────────────────────────────────────────────────
|
||||
|
||||
@notes_bp.route("/<int:note_id>/versions", methods=["GET"])
|
||||
@login_required
|
||||
async def list_versions_route(note_id: int):
|
||||
uid = get_current_user_id()
|
||||
versions = await list_versions(uid, note_id)
|
||||
return jsonify({"versions": [v.to_dict(include_body=False) for v in versions]})
|
||||
|
||||
|
||||
@notes_bp.route("/<int:note_id>/versions/<int:version_id>", methods=["GET"])
|
||||
@login_required
|
||||
async def get_version_route(note_id: int, version_id: int):
|
||||
uid = get_current_user_id()
|
||||
version = await get_version(uid, note_id, version_id)
|
||||
if version is None:
|
||||
return jsonify({"error": "Version not found"}), 404
|
||||
return jsonify(version.to_dict(include_body=True))
|
||||
|
||||
Reference in New Issue
Block a user