French-Coach / frontend /API_CONTRACT.md
Asma-F's picture
Deploy: fixed React routing + OpenBMB backend
cc13fc9 verified
|
Raw
History Blame Contribute Delete
11.4 kB

A newer version of the Gradio SDK is available: 6.20.0

Upgrade

French Coach — Custom UI API contract

All endpoints are mounted on the same Gradio app object (gr.Server / FastAPI instance) returned by demo.launch(prevent_thread_lock=True) in app_custom.py, under the /api/... prefix. The built React app (frontend/dist) is served as static assets from the same server, so the whole thing is one Gradio-SDK process (no separate API host).

User identity: single-user dev mode. Every endpoint operates on a fixed USER_ID = "dev_user" (same default app.py uses when not running on a Space). Hugging Face OAuth for the custom UI on the public Space is not wired up yet — flagged as a follow-up, not a blocker (does not affect Gradio-SDK / model-size eligibility).

All responses are JSON unless noted. Errors: {"error": "<message>"} with a non-200 status code.


Lessons (Notebook + Lessons browser)

GET /api/lessons

List saved lesson pages (excludes page_type == "resource"), newest first.

Response:

{
  "lessons": [
    {"id": "uuid", "title": "Class 4 — Le passé composé", "date": "2026-04-30",
     "category": "Grammar", "page_type": "lesson", "preview": "Aujourd'hui on a vu…"}
  ]
}

GET /api/lessons/{id}

Load one page for editing.

Response:

{"id": "uuid", "title": "Class 4", "raw_text": "Le petit chat...",
 "annotations": {"tokens": [...], "meanings": {...}}}

404 -> {"error": "not found"} if missing or not owned by user.

POST /api/lessons

Save the current editor text as a new page (curator auto-titles it). Awards saved_lesson points.

Body: {"text": "...", "annotations": {...}} Response: {"id": "uuid", "title": "Auto-generated title"}

PUT /api/lessons/{id}

Update an existing page's text/annotations in place (title unchanged).

Body: {"text": "...", "annotations": {...}} Response: {"title": "Existing title"}

PATCH /api/lessons/{id}/title

Rename a page (user override of the auto title).

Body: {"title": "New title"} Response: {"title": "New title"}

DELETE /api/lessons/{id}

Delete a page (exercises cascade).

Response: {"deleted": true}


Resources tab

GET /api/resources

Pages curated as page_type == "resource" that contain links and/or books.

Response:

{
  "resources": [
    {"id": "uuid", "title": "Online Resources",
     "links": [{"url": "https://...", "label": "TV5 Monde", "domain": "tv5monde.com"}],
     "books": [{"title": "Le Petit Prince", "author": "Saint-Exupéry", "note": "easy reader"}]}
  ]
}

Annotation / gender coloring / word card (Notebook + Tools screens)

POST /api/annotate

Run spaCy annotation on arbitrary text (used by Notebook "Annotate" and the Gender Checker tool).

Body: {"text": "...", "colors_on": true} Response:

{"html": "<span data-token=\"1\" data-gender=\"Masc\" ...>Le</span> ...",
 "tokens": [{"text": "Le", "lemma": "le", "pos": "DET", "gender": "Masc", "whitespace": " "}],
 "meanings": {}}

html is the same gender-colored markup nlp.render_html already produces (with data-token/data-text/data-gender/data-pos/data-lemma attributes) — the React Notebook/Tools screens render it with dangerouslySetInnerHTML and use click-event delegation, exactly like the existing Blocks PAGE_JS, so gender colors + click behavior stay identical.

POST /api/render

Re-render cached annotations to gender-colored HTML without re-running spaCy/LLM (used when loading a saved lesson, or toggling the gender-colors checkbox, so cached meanings survive).

Body: {"annotations": {"tokens": [...], "meanings": {...}}, "colors_on": true} Response: {"html": "<span data-token=...>...</span> ..."}

POST /api/word-card

Get (and cache) the LLM meaning/grammar note for one clicked word. Awards word_explored points the first time a given lemma is looked up.

Body:

{"text": "femme", "lemma": "femme", "pos": "NOUN", "gender": "Fem",
 "meanings": {"...cached meanings dict from annotate/lessons..."}}

Response:

{"text": "femme", "lemma": "femme", "pos": "NOUN", "gender": "Fem",
 "meaning": "woman", "grammar": "feminine noun",
 "meanings": {"femme": {"meaning": "woman", "grammar": "feminine noun"}, "...": "..."}}

The client merges the returned meanings dict back into its local annotations object (so a later "Save"/"Update" persists the cache).


Chat coach

POST /api/chat

Non-streaming reply (the custom UI does one round trip per message instead of token-streaming).

Body:

{"message": "Comment dit-on 'thank you'?",
 "history": [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}],
 "lesson_text": "...current notebook text, used as context, optional..."}

Response: {"reply": "On dit « merci »..."}


Exercises

POST /api/exercises/coach

Coach Agent (Day 3): plans a balanced 5-7 item practice set from the current lesson, grounded against the A1/A2 CEFR syllabus (syllabus_full_a1_c2.json), generates each item, critiques it, and regenerates anything that fails review (max 2 attempts/item). Identified concepts are upserted into concepts (covered_on = today) for the Summary tab's "next focus".

Body: {"lesson_text": "...", "page_id": "uuid (optional)"} Response:

{
  "concepts": [{"id": "verb_etre_present", "name": "Verb: Être (Present Tense)", "cefr_level": "A1", "family": "verb_tenses"}],
  "exercises": [
    {"type": "fill_blank", "instruction": "Fill in the blank:", "sentence_with_blank": "...", "answer": "...", "hint": "...", "explanation": "..."},
    {"type": "multiple_choice", "instruction": "Choose the correct answer:", "question": "...", "options": ["...", "...", "...", "..."], "answer": "...", "explanation": "..."},
    {"type": "error_detection", "instruction": "Find and fix the mistake:", "sentence": "...", "answer": "...", "explanation": "..."},
    {"type": "reorder", "instruction": "Put the words in the correct order:", "words": ["...", "..."], "answer": "...", "explanation": "..."},
    {"type": "translation", "instruction": "Translate to French:", "prompt": "...", "answer": "...", "explanation": "..."}
  ]
}

The frontend shows exercises one at a time (see Exercises.jsx CoachExercises).

POST /api/exercises/coach/check

Check one item's answer. fill_blank/multiple_choice are checked by exact match; error_detection/reorder/translation are graded leniently by the LLM (accepts spelling/accent/punctuation variation). Always awards exercise_done points — participation, not correctness; never red/shaming.

Body: {"exercise": {...}, "answer": "..."} Response: {"correct": true, "feedback": "encouraging message", "answer": "model answer"}

POST /api/exercises/dialogue

Start a new dialogue scene from the lesson.

Body: {"lesson_text": "..."} Response:

{"dialogue": {"scene": "...", "agent_role": "...", "user_role": "...", "turns": [...]},
 "replies": [], "hint": "Your turn: ...", "transcript_html": "<div ...>...</div>"}

POST /api/exercises/dialogue/reply

Send the user's next line. Awards dialogue_turn points.

Body: {"dialogue": {...}, "replies": ["..."], "reply": "Bonjour !"} Response:

{"replies": ["Bonjour !"], "transcript_html": "<div ...>...</div>",
 "hint": "Your turn: ..." , "feedback_html": "<div ...>...</div>"}

(hint is "🎉 Dialogue complete! Great work!" once finished.)

POST /api/exercises/visual/sample

Matched-image visual exercise (Day 4): no upload needed. Picks one of ~15 pre-generated images (frontend/public/sample_images/, generated once via generate_sample_images.py) matching the lesson's detected topic (nlp.detect_category), avoiding images this user has already seen (user_image_usage) until the set cycles. Builds 3-5 exercises (with hints) grounded in the image's hand-written description — no vision call at request time. Awards photo_exercise points.

Body: {"lesson_text": "..."} Response:

{
  "image_url": "/custom/sample_images/food_dining.jpg",
  "topic": "Food & Dining",
  "html": "<div ...>...</div>"
}

(html = exercises.render_visual_exercises(result), includes a hint line per exercise.)

POST /api/exercises/pronunciation/target

Body: {"lesson_text": "..."} Response:

{"target": {"phrase": "...", "translation": "...", "tip": "..."}, "html": "<div ...>...</div>"}

POST /api/exercises/pronunciation/check

Awards pronunciation points.

Body: {"target": {"phrase": "..."}, "transcription": "..."} Response: {"html": "<div ...>...</div>"}


Gender Checker + Translator (Tools)

POST /api/gender-check

Look up a single French noun. spaCy (nlp.word_info) gives a lemma/POS hint, but gender itself is not reliable from spaCy on an isolated word (no determiner context to disambiguate — e.g. "pomme" alone tags Masc though it's feminine), so the LLM is authoritative for gender/articles/example/pattern note.

Body: {"word": "pomme"} Response:

{
  "word": "pomme", "lemma": "pomme", "pos": "NOUN",
  "gender": "Fem", "article": "la", "indefinite_article": "une",
  "example": "J'achète une pomme pour le goûter.",
  "example_translation": "I'm buying an apple for the snack.",
  "pattern_note": "Words ending in -e are often feminine."
}

POST /api/translate

Translate a word/phrase with alternatives and a bilingual example. If lesson_text is given, it's used as register/vocabulary context only.

Body: {"text": "good morning", "direction": "en_fr"|"fr_en", "lesson_text": "..."} Response:

{
  "translation": "bonjour",
  "alternatives": [],
  "example_fr": "Bonjour, comment allez-vous ?",
  "example_en": "Good morning, how are you?"
}

example_fr/example_en are always in their named language regardless of direction (the LLM was inconsistent about which side of example/ example_translation was French, so the schema is now language-explicit).


Summary / gamification

GET /api/summary

Calls gamify.try_daily_open (idempotent per day, awards daily_open points once/day) then returns the encouraging daily summary, total points, today's activity stats, and A1-A2 concept progress for the dashboard.

Response:

{
  "summary": "You've covered 6 concepts...",
  "total_points": 142,
  "daily_stats": {
    "pages_today": 1, "exercises_today": 5,
    "dialogue_turns": 0, "words_clicked": 3, "total_points": 142
  },
  "concepts": {
    "covered": ["Personal Subject Pronouns", "Regular -ER Verbs (Present)"],
    "next": "French Pronunciation & Sound System",
    "covered_count": 8, "total_count": 49
  }
}

Screens -> endpoints map (for Phase 3 ordering)

  1. Notebook/api/lessons/{id} (load), /api/annotate, /api/word-card, /api/lessons (save new), /api/lessons/{id} PUT (update), /api/lessons/{id}/title PATCH (rename), /api/lessons/{id} DELETE.
  2. Lessons browser/api/lessons (list, grouped client-side by date/category).
  3. Exercises — the four /api/exercises/... groups.
  4. Chat coach/api/chat.
  5. Summary dashboard/api/summary.
  6. Tools/api/annotate//api/word-card (Text Checker), /api/gender-check (Gender Checker), /api/translate (Translator) — all standalone utilities, separate from the saved notebook.