Add CalDAV calendar integration, LLM-suggested tags, and settings refinements

- CalDAV integration: per-user calendar config, create/list/search events
  via caldav library, LLM tools for calendar operations from chat
- LLM-suggested tags: new tag_suggestions service prompts LLM with existing
  tags and note content to suggest 3-5 relevant tags; exposed via API
  endpoints (suggest-tags, append-tag); integrated into editor views
  (suggest button + clickable pills) and chat tool calls (pills in
  ToolCallCard with one-click apply)
- Settings/model UI refinements, generation task improvements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 22:40:20 -05:00
parent 8996b45e50
commit d7bc3f3222
22 changed files with 1158 additions and 837 deletions
+39 -10
View File
@@ -8,6 +8,7 @@ from typing import Literal
import httpx
from fabledassistant.config import Config
from fabledassistant.services.caldav import is_caldav_configured
from fabledassistant.services.notes import get_note, search_notes_for_context
from fabledassistant.services.settings import get_setting
@@ -79,9 +80,10 @@ async def stream_chat(
options: dict | None = None,
) -> AsyncGenerator[str, None]:
"""Stream chat completion from Ollama, yielding content chunks."""
payload: dict = {"model": model, "messages": messages, "stream": True}
merged_options = {"num_ctx": 16384}
if options:
payload["options"] = options
merged_options.update(options)
payload: dict = {"model": model, "messages": messages, "stream": True, "options": merged_options}
async with httpx.AsyncClient(timeout=httpx.Timeout(1800.0, connect=30.0, read=300.0)) as client:
async with client.stream(
"POST",
@@ -119,7 +121,17 @@ async def stream_chat_with_tools(
ChatChunk(type="tool_calls") is yielded. Always ends with
ChatChunk(type="done").
"""
payload: dict = {"model": model, "messages": messages, "stream": True}
options: dict = {"num_ctx": 16384}
# Disable thinking mode for models like qwen3 — it interferes with tool calling
if tools:
options["num_predict"] = 4096
payload: dict = {
"model": model,
"messages": messages,
"stream": True,
"options": options,
"think": False,
}
if tools:
payload["tools"] = tools
async with httpx.AsyncClient(timeout=httpx.Timeout(1800.0, connect=30.0, read=300.0)) as client:
@@ -129,6 +141,7 @@ async def stream_chat_with_tools(
json=payload,
) as resp:
resp.raise_for_status()
accumulated_tool_calls: list[dict] = []
async for line in resp.aiter_lines():
if not line.strip():
continue
@@ -140,11 +153,22 @@ async def stream_chat_with_tools(
if chunk:
yield ChatChunk(type="content", content=chunk)
# Check for tool calls on done
# Collect tool calls from any message (some models
# emit them before the done flag)
tc = msg.get("tool_calls")
if tc:
accumulated_tool_calls.extend(tc)
if data.get("done"):
tool_calls = msg.get("tool_calls")
if tool_calls:
yield ChatChunk(type="tool_calls", tool_calls=tool_calls)
if accumulated_tool_calls:
logger.info(
"Ollama returned %d tool call(s): %s",
len(accumulated_tool_calls),
json.dumps(accumulated_tool_calls)[:500],
)
yield ChatChunk(type="tool_calls", tool_calls=accumulated_tool_calls)
else:
logger.debug("Ollama done with no tool calls")
yield ChatChunk(type="done")
break
@@ -220,14 +244,19 @@ async def build_context(
from datetime import date as date_type
assistant_name = await get_setting(user_id, "assistant_name", "Fable")
today = date_type.today().isoformat()
has_caldav = await is_caldav_configured(user_id)
date_guidance = "For relative dates like 'Friday' or 'next week', resolve them to YYYY-MM-DD format."
if has_caldav:
date_guidance += " For calendar events, use ISO 8601 datetime format (e.g. 2025-01-15T14:00:00)."
system_parts = [
f"You are a helpful assistant named {assistant_name}, integrated into a note-taking and task-tracking app called Fabled Assistant. "
"Help users with their notes, tasks, and general questions. "
"When note context is provided, use it to give relevant answers. "
f"Today's date is {today}. "
"You have tools available to create tasks, create notes, and search the user's notes. "
"Use them when the user asks you to create, add, or find tasks and notes. "
"For relative dates like 'Friday' or 'next week', resolve them to YYYY-MM-DD format."
"When the user asks you to create, add, or find something, use the provided tool functions. "
"Do not describe or write out function calls as text — actually invoke the tools. "
f"{date_guidance}"
]
context_meta: dict = {