Fix tag handling for multi-word tags
Tags with spaces (e.g. #science fiction) were breaking extraction because TAG_RE only matched word characters — it would stop at the space and extract #science instead of #science-fiction. - TAG_RE (backend + frontend): add hyphens to character class so #science-fiction is recognized as a single tag: [\w][\w-]* per segment - System prompt: instruct LLM to use hyphens in multi-word tags, never spaces - tag_suggestions.py: update prompt example + sanitize output by replacing spaces with hyphens as a safety net regardless of LLM output - append-tag route: sanitize incoming tag (spaces → hyphens) before appending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
const CODE_FENCE_RE = /```[\s\S]*?```|`[^`\n]+`/g;
|
||||
const TAG_RE = /(?<!\w)(?<!&)#([\w]+(?:\/[\w]+)*)/g;
|
||||
const TAG_RE = /(?<!\w)(?<!&)#([\w][\w-]*(?:\/[\w][\w-]*)*)/g;
|
||||
|
||||
function escapeHtmlAttr(s: string): string {
|
||||
return s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
||||
|
||||
@@ -120,7 +120,7 @@ async def suggest_tags_route():
|
||||
async def append_tag_route(note_id: int):
|
||||
uid = get_current_user_id()
|
||||
data = await request.get_json()
|
||||
tag = data.get("tag", "").strip()
|
||||
tag = data.get("tag", "").strip().replace(" ", "-")
|
||||
if not tag:
|
||||
return jsonify({"error": "tag is required"}), 400
|
||||
|
||||
|
||||
@@ -337,6 +337,7 @@ async def build_context(
|
||||
tool_lines.append("When the user says 'remind me' with a time before an event, use the reminder_minutes parameter.")
|
||||
tool_lines.append("Use search_todos to find a specific CalDAV todo by keyword when list_todos would return too many results.")
|
||||
tool_lines.append("For relative dates like 'Friday' or 'next week', resolve them to YYYY-MM-DD format.")
|
||||
tool_lines.append("When writing #tags in note bodies, use hyphens for multi-word tags (e.g. #science-fiction, #space-travel). Never use spaces inside a tag.")
|
||||
tool_lines.append(
|
||||
"Use update_note to edit/expand an existing note OR to update a task's status/priority/due_date. "
|
||||
"Use create_note ONLY for genuinely new notes with a different title. "
|
||||
|
||||
@@ -33,8 +33,10 @@ async def suggest_tags(user_id: int, title: str, body: str) -> list[str]:
|
||||
"content": (
|
||||
"You are a tag suggestion assistant. Given a note's title and body, "
|
||||
"suggest 3-5 relevant tags. Prefer reusing existing tags when they fit, "
|
||||
"but you may suggest new ones too. Reply with ONLY a JSON array of tag "
|
||||
'strings (without # prefix). Example: ["meeting", "project/alpha", "followup"]\n\n'
|
||||
"but you may suggest new ones too. "
|
||||
"Tags must use hyphens for multi-word names (e.g. science-fiction, space-travel) — never spaces. "
|
||||
"Reply with ONLY a JSON array of tag strings (without # prefix). "
|
||||
'Example: ["meeting", "project/alpha", "science-fiction"]\n\n'
|
||||
f"Existing tags: {existing_list}"
|
||||
),
|
||||
},
|
||||
@@ -64,10 +66,13 @@ def _parse_tag_list(response: str) -> list[str]:
|
||||
text = response.strip()
|
||||
|
||||
# Try direct JSON parse
|
||||
def _clean(tag: str) -> str:
|
||||
return str(tag).strip().lstrip("#").replace(" ", "-")
|
||||
|
||||
try:
|
||||
parsed = json.loads(text)
|
||||
if isinstance(parsed, list):
|
||||
return [str(t).strip().lstrip("#") for t in parsed if t]
|
||||
return [_clean(t) for t in parsed if t]
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
@@ -77,7 +82,7 @@ def _parse_tag_list(response: str) -> list[str]:
|
||||
try:
|
||||
parsed = json.loads(match.group())
|
||||
if isinstance(parsed, list):
|
||||
return [str(t).strip().lstrip("#") for t in parsed if t]
|
||||
return [_clean(t) for t in parsed if t]
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import re
|
||||
|
||||
_CODE_FENCE_RE = re.compile(r"```[\s\S]*?```|`[^`\n]+`")
|
||||
_TAG_RE = re.compile(r"(?<!\w)#([\w]+(?:/[\w]+)*)")
|
||||
_TAG_RE = re.compile(r"(?<!\w)#([\w][\w-]*(?:/[\w][\w-]*)*)")
|
||||
|
||||
|
||||
def extract_tags(body: str) -> list[str]:
|
||||
|
||||
Reference in New Issue
Block a user