Phase 20: Dedicated tag field — chip input, explicit tags array
Tags are now a first-class field rather than being auto-extracted from the note body. A new TagInput.vue chip component handles tag entry in both editor views with autocomplete, Enter/comma/backspace UX, and space-to-hyphen sanitization. Backend: - routes/notes.py: create reads tags from JSON; update accepts explicit tags (omit = keep existing); append_tag writes to tags array with dedup; suggest-tags accepts current_tags filter; remove extract_tags - routes/tasks.py: same — explicit tags on create/update; remove extract_tags - services/tag_suggestions.py: current_tags param replaces body extraction - services/tools.py: create_note tool schema adds tags param; executor passes it - services/llm.py: system prompt tells LLM to use tags param, not embed #tag in body Frontend: - components/TagInput.vue: new chip-based tag input (autocomplete, keyboard UX) - NoteEditorView.vue / TaskEditorView.vue: tags ref loaded from note.tags; TagInput placed between title and body; save/autosave include tags; suggest now adds chips; fetchTagSuggestions passes current_tags; dirty tracks tags - TiptapEditor.vue: remove fetchTags prop and TagSuggestion extension; keep TagDecoration for legacy inline #tag highlighting No DB migration needed — tags column already correct. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,6 @@ from fabledassistant.services.notes import (
|
||||
)
|
||||
from fabledassistant.services.settings import get_setting
|
||||
from fabledassistant.services.tag_suggestions import suggest_tags
|
||||
from fabledassistant.utils.tags import extract_tags
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -67,7 +66,7 @@ async def create_note_route():
|
||||
uid = get_current_user_id()
|
||||
data = await request.get_json()
|
||||
body = data.get("body", "")
|
||||
tags = extract_tags(body)
|
||||
tags = data.get("tags", [])
|
||||
|
||||
# Optional task fields
|
||||
status = data.get("status")
|
||||
@@ -111,7 +110,8 @@ async def suggest_tags_route():
|
||||
data = await request.get_json()
|
||||
title = data.get("title", "")
|
||||
body = data.get("body", "")
|
||||
tags = await suggest_tags(uid, title, body)
|
||||
current_tags = data.get("current_tags", [])
|
||||
tags = await suggest_tags(uid, title, body, current_tags=current_tags)
|
||||
return jsonify({"suggested_tags": tags})
|
||||
|
||||
|
||||
@@ -128,10 +128,10 @@ async def append_tag_route(note_id: int):
|
||||
if note is None:
|
||||
return jsonify({"error": "Note not found"}), 404
|
||||
|
||||
# Append #tag to body
|
||||
new_body = note.body.rstrip() + f"\n#{tag}" if note.body else f"#{tag}"
|
||||
tags = extract_tags(new_body)
|
||||
updated = await update_note(uid, note_id, body=new_body, tags=tags)
|
||||
existing = list(note.tags or [])
|
||||
if tag not in existing:
|
||||
existing.append(tag)
|
||||
updated = await update_note(uid, note_id, tags=existing)
|
||||
return jsonify(updated.to_dict())
|
||||
|
||||
|
||||
@@ -189,8 +189,8 @@ async def update_note_route(note_id: int):
|
||||
else:
|
||||
fields["due_date"] = None
|
||||
|
||||
if "body" in fields:
|
||||
fields["tags"] = extract_tags(fields["body"])
|
||||
if "tags" in data:
|
||||
fields["tags"] = data["tags"]
|
||||
note = await update_note(uid, note_id, **fields)
|
||||
if note is None:
|
||||
return jsonify({"error": "Note not found"}), 404
|
||||
|
||||
Reference in New Issue
Block a user