French-Coach / curator.py
Asma-F's picture
Deploy: French Coach app (MiniCPM4.1-8B ZeroGPU + React frontend)
4fd1234 verified
Raw
History Blame Contribute Delete
1.97 kB
"""
Curator pass — one LLM call per saved page.
Classifies a page as a "lesson" (French class notes/vocab/grammar) or a
"resource" (links, apps, book recommendations) and extracts a friendly
title, a one-line summary, and — for resource pages — structured links/books
for the Resources tab.
"""
import logging
import llm
import prompts
logger = logging.getLogger(__name__)
def _fallback(raw_text: str) -> dict:
first_line = raw_text.strip().split("\n", 1)[0].lstrip("#").strip()
return {
"title": (first_line or "Untitled Lesson")[:80],
"summary": "",
"page_type": "lesson",
"links": [],
"books": [],
}
def curate_page(raw_text: str) -> dict:
"""Return {title, summary, page_type, links, books} for a page's raw text."""
fallback = _fallback(raw_text)
result = llm.chat_json(prompts.CURATOR_SYSTEM, raw_text[:2000], fallback=fallback)
title = (result.get("title") or fallback["title"]).strip()[:80]
summary = (result.get("summary") or "").strip()
page_type = result.get("page_type") if result.get("page_type") in ("lesson", "resource") else "lesson"
links, books = [], []
if page_type == "resource":
for link in result.get("links") or []:
if isinstance(link, dict) and link.get("url"):
links.append({
"label": str(link.get("label") or link["url"])[:120],
"url": str(link["url"]),
})
for book in result.get("books") or []:
if isinstance(book, dict) and book.get("title"):
books.append({
"title": str(book.get("title"))[:200],
"author": str(book.get("author") or "")[:120],
"note": str(book.get("note") or "")[:200],
})
return {
"title": title,
"summary": summary,
"page_type": page_type,
"links": links,
"books": books,
}