80f30b705d
Data model: - Migration 0036: adds note_type TEXT (default 'note') and metadata JSONB to the notes table; index on note_type - Note model: entity_type property, note_type/metadata in to_dict() - create_note() accepts note_type and metadata params Backend: - /api/knowledge — unified paginated endpoint: type/tag/sort/q filters, semantic search via embeddings, excludes tasks - /api/knowledge/tags — distinct tags across knowledge objects - New LLM tools: create_person, create_place, create_list, add_to_list, clear_checked_items — all wired into execute_tool() - People and places auto-injected as compact summary into LLM system prompt Frontend: - KnowledgeView replaces HomeView at /; left filter panel (type+tag), toolbar (search, sort, graph toggle), card grid with type-aware cards (indigo=note, emerald=person, amber=place, sky=list), load-more pagination - Today bar: upcoming events, overdue task count, Briefing/Chat links - Floating mini-chat sticky to bottom: creates/continues a conversation inline, message history expands upward, close button ends session - Graph panel: toggles as a 420px right panel at full viewport width - AppHeader: Knowledge, Chat, Briefing, Calendar, Tasks, Projects - Router: / → KnowledgeView; /knowledge redirect; HomeView import removed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
75 lines
2.6 KiB
Python
75 lines
2.6 KiB
Python
"""Unified Knowledge endpoint — notes, people, places, lists in one queryable feed."""
|
|
import logging
|
|
|
|
from quart import Blueprint, jsonify, request
|
|
|
|
from fabledassistant.auth import get_current_user_id, login_required
|
|
from fabledassistant.routes.utils import parse_pagination
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
knowledge_bp = Blueprint("knowledge", __name__, url_prefix="/api/knowledge")
|
|
|
|
_VALID_TYPES = {"note", "person", "place", "list"}
|
|
_VALID_SORTS = {"modified", "created", "alpha", "type"}
|
|
|
|
|
|
@knowledge_bp.route("", methods=["GET"])
|
|
@login_required
|
|
async def list_knowledge():
|
|
"""Return paginated knowledge objects with optional filtering.
|
|
|
|
Query params:
|
|
type — one of note|person|place|list (omit for all, excludes tasks)
|
|
tags — comma-separated tag filter (AND logic)
|
|
sort — modified|created|alpha|type (default: modified)
|
|
q — search query (semantic when provided, keyword fallback)
|
|
page — 1-based page number (default 1)
|
|
per_page — items per page (default 24, max 100)
|
|
"""
|
|
uid = get_current_user_id()
|
|
note_type = request.args.get("type", "").strip().lower() or None
|
|
tags_raw = request.args.get("tags", "").strip()
|
|
tags = [t.strip() for t in tags_raw.split(",") if t.strip()] if tags_raw else []
|
|
sort = request.args.get("sort", "modified").strip().lower()
|
|
q = request.args.get("q", "").strip() or None
|
|
|
|
if note_type and note_type not in _VALID_TYPES:
|
|
return jsonify({"error": f"Invalid type. Must be one of: {', '.join(sorted(_VALID_TYPES))}"}), 400
|
|
if sort not in _VALID_SORTS:
|
|
sort = "modified"
|
|
|
|
limit, offset = parse_pagination(default_limit=24, max_limit=100)
|
|
page = max(1, int(request.args.get("page", 1)))
|
|
|
|
from fabledassistant.services.knowledge import query_knowledge
|
|
items, total = await query_knowledge(
|
|
user_id=uid,
|
|
note_type=note_type,
|
|
tags=tags,
|
|
sort=sort,
|
|
q=q,
|
|
limit=limit,
|
|
offset=offset,
|
|
)
|
|
|
|
return jsonify({
|
|
"items": items,
|
|
"total": total,
|
|
"page": page,
|
|
"per_page": limit,
|
|
"pages": max(1, (total + limit - 1) // limit),
|
|
})
|
|
|
|
|
|
@knowledge_bp.route("/tags", methods=["GET"])
|
|
@login_required
|
|
async def list_knowledge_tags():
|
|
"""Return all tags used across knowledge objects (excludes tasks)."""
|
|
uid = get_current_user_id()
|
|
note_type = request.args.get("type", "").strip().lower() or None
|
|
|
|
from fabledassistant.services.knowledge import get_knowledge_tags
|
|
tags = await get_knowledge_tags(uid, note_type=note_type)
|
|
return jsonify({"tags": tags})
|