Download notebook.py from build-small-hackathon/French-Coach: direct link, hf CLI and curl.
- Browser
- Download file 5.3 kB
-
https://huggingface.co/spaces/build-small-hackathon/French-Coach/resolve/refs%2Fpr%2F1/notebook.py
- Command line
-
hf download hf://spaces/build-small-hackathon/French-Coach@refs/pr/1/notebook.py
-
curl -L -o notebook.py https://huggingface.co/spaces/build-small-hackathon/French-Coach/resolve/refs%2Fpr%2F1/notebook.py
5.3 kB
| import json | |
| from datetime import date | |
| from db import get_cursor | |
| import curator | |
| def save_page(user_id: str, raw_text: str, annotations: dict) -> tuple[str, str]: | |
| """Curates the page (title/summary/type/resources), inserts into DB. Returns (page_id, title).""" | |
| import nlp as _nlp | |
| curated = curator.curate_page(raw_text) | |
| category = _nlp.detect_category(raw_text[:300]) | |
| title = curated["title"] | |
| metadata = { | |
| "category": category, | |
| "summary": curated["summary"], | |
| "page_type": curated["page_type"], | |
| "links": curated["links"], | |
| "books": curated["books"], | |
| } | |
| with get_cursor() as cur: | |
| cur.execute( | |
| """INSERT INTO pages (user_id, title, date, raw_text, annotations, metadata) | |
| VALUES (%s, %s, %s, %s, %s, %s) RETURNING id::text""", | |
| (user_id, title, date.today(), raw_text, | |
| json.dumps(annotations), json.dumps(metadata)), | |
| ) | |
| page_id = cur.fetchone()["id"] | |
| return page_id, title | |
| def list_pages(user_id: str) -> list[dict]: | |
| """Sidebar list — returns [{id, title, date, category, page_type, preview}] newest-first, max 50. | |
| Fetches the first 300 chars of raw_text to detect category and build a hover | |
| preview without pulling the full lesson text over the wire. | |
| """ | |
| import nlp as _nlp | |
| with get_cursor() as cur: | |
| cur.execute( | |
| "SELECT id::text, title, date::text, " | |
| "LEFT(raw_text, 300) AS snippet, " | |
| "COALESCE(metadata->>'category', '') AS stored_category, " | |
| "COALESCE(metadata->>'page_type', 'lesson') AS page_type " | |
| "FROM pages WHERE user_id = %s ORDER BY created_at DESC LIMIT 50", | |
| (user_id,), | |
| ) | |
| rows = [dict(r) for r in cur.fetchall()] | |
| for row in rows: | |
| snippet = row.pop("snippet", "") or "" | |
| stored_cat = row.pop("stored_category", "") or "" | |
| row["category"] = stored_cat if stored_cat else _nlp.detect_category(snippet) | |
| preview = snippet[:100] | |
| row["preview"] = preview + ("…" if len(snippet) > 100 else "") | |
| return rows | |
| def list_resources(user_id: str) -> list[dict]: | |
| """Return resource-type pages: [{id, title, links, books}], newest-first.""" | |
| with get_cursor() as cur: | |
| cur.execute( | |
| "SELECT id::text, title, " | |
| "COALESCE(metadata->'links', '[]'::jsonb) AS links, " | |
| "COALESCE(metadata->'books', '[]'::jsonb) AS books " | |
| "FROM pages WHERE user_id = %s AND metadata->>'page_type' = 'resource' " | |
| "ORDER BY created_at DESC", | |
| (user_id,), | |
| ) | |
| return [dict(r) for r in cur.fetchall()] | |
| def update_resource_links(page_id: str, user_id: str, links: list[dict]) -> bool: | |
| """Overwrite metadata->'links' on a resource page. Returns True if a row was updated.""" | |
| with get_cursor() as cur: | |
| cur.execute( | |
| "UPDATE pages SET metadata = jsonb_set(metadata, '{links}', %s::jsonb) " | |
| "WHERE id = %s AND user_id = %s AND metadata->>'page_type' = 'resource' " | |
| "RETURNING id", | |
| (json.dumps(links), page_id, user_id), | |
| ) | |
| return cur.fetchone() is not None | |
| def update_title(page_id: str, user_id: str, title: str) -> str: | |
| """Persist a user-edited title (overrides the auto-generated one). Returns the saved title.""" | |
| title = (title or "").strip()[:80] or "Untitled Lesson" | |
| with get_cursor() as cur: | |
| cur.execute( | |
| "UPDATE pages SET title = %s WHERE id = %s AND user_id = %s", | |
| (title, page_id, user_id), | |
| ) | |
| return title | |
| def get_page(page_id: str, user_id: str) -> dict | None: | |
| with get_cursor() as cur: | |
| cur.execute( | |
| "SELECT id::text, title, raw_text, annotations " | |
| "FROM pages WHERE id = %s AND user_id = %s", | |
| (page_id, user_id), | |
| ) | |
| row = cur.fetchone() | |
| return dict(row) if row else None | |
| def update_annotations(page_id: str, annotations: dict) -> None: | |
| """Persist LLM-enriched annotations back to the page row.""" | |
| with get_cursor() as cur: | |
| cur.execute( | |
| "UPDATE pages SET annotations = %s WHERE id = %s", | |
| (json.dumps(annotations), page_id), | |
| ) | |
| def update_page(page_id: str, user_id: str, raw_text: str, annotations: dict) -> str: | |
| """Update page content in-place, keep existing title. Returns the title.""" | |
| with get_cursor() as cur: | |
| cur.execute( | |
| "SELECT title FROM pages WHERE id = %s AND user_id = %s", | |
| (page_id, user_id), | |
| ) | |
| row = cur.fetchone() | |
| title = row["title"] if row else raw_text.split("\n")[0][:60] | |
| with get_cursor() as cur: | |
| cur.execute( | |
| "UPDATE pages SET raw_text = %s, annotations = %s WHERE id = %s AND user_id = %s", | |
| (raw_text, json.dumps(annotations), page_id, user_id), | |
| ) | |
| return title | |
| def delete_page(page_id: str, user_id: str) -> bool: | |
| """Delete page row (exercises cascade). Returns True if a row was deleted.""" | |
| with get_cursor() as cur: | |
| cur.execute( | |
| "DELETE FROM pages WHERE id = %s AND user_id = %s RETURNING id", | |
| (page_id, user_id), | |
| ) | |
| return cur.fetchone() is not None | |