From 94a2ef340e7503b1ffa5c22c0072873ed2b5d0b2 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Fri, 1 May 2026 21:18:48 +0200 Subject: [PATCH] Card central --- backend/app/main.py | 28 ++++++++++++++-- backend/app/schemas.py | 4 +++ frontend/src/App.jsx | 72 ++++++++++++++--------------------------- frontend/src/styles.css | 63 +++--------------------------------- 4 files changed, 57 insertions(+), 110 deletions(-) diff --git a/backend/app/main.py b/backend/app/main.py index 16b803a..d3bcceb 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -300,9 +300,13 @@ def start_session( assignment = db.query(models.StudentProgramAssignment).filter_by(student_id=student.id).first() assigned_lesson = get_lesson(assignment.lesson_id) if assignment else None if assigned_lesson: + first_asset = (assigned_lesson.get("assets") or [None])[0] + first_section = (first_asset.get("sections") or [None])[0] if first_asset else None + section_label = first_section["title"] if first_section else first_asset["title"] if first_asset else assigned_lesson["title"] message = ( f"Bonjour {student.first_name} ! Aujourd’hui, on travaille la leçon " - f"{assigned_lesson['title']}. Regarde la première fiche, puis dis-moi ce que tu remarques." + f"{assigned_lesson['title']}. Regarde la carte « {section_label} ». " + "Dis-moi si tu comprends cette partie, ou ce qui te gêne." ) previous_messages_count = db.query(models.Message).filter_by(student_id=student.id).count() if previous_messages_count: @@ -313,7 +317,13 @@ def start_session( "première consigne simple." ) if assigned_lesson: - prompt += f" La leçon active choisie par le professeur est: {assigned_lesson['title']}." + first_asset = (assigned_lesson.get("assets") or [None])[0] + first_section = (first_asset.get("sections") or [None])[0] if first_asset else None + section_label = first_section["title"] if first_section else first_asset["title"] if first_asset else assigned_lesson["title"] + prompt += ( + f" La leçon active choisie par le professeur est: {assigned_lesson['title']}. " + f"La carte affichée est: {section_label}. Parle de cette carte, pas d'une ancienne leçon." + ) message = build_llm_reply(db, student.id, prompt) db.add(models.Message(student_id=student.id, role="assistant", content=message)) db.commit() @@ -334,7 +344,19 @@ def chat( db.add(models.Message(student_id=student.id, role="user", content=payload.message)) db.commit() - reply = build_llm_reply(db, payload.student_id, payload.message) + lesson_context = "" + if payload.lesson_title or payload.section_title or payload.asset_title: + lesson_context = ( + "\n\nContexte de l’écran actuellement montré à l’élève: " + f"leçon={payload.lesson_title or 'non précisée'}, " + f"fiche={payload.asset_title or 'non précisée'}, " + f"carte affichée={payload.section_title or 'non précisée'}, " + f"avancement={payload.step_percent if payload.step_percent is not None else 'non précisé'}%. " + "Réponds uniquement à propos de cette carte. Vérifie que l’élève comprend ce qui est affiché. " + "Si l’élève dit qu’il a compris, invite-le à passer à l’étape suivante." + ) + + reply = build_llm_reply(db, payload.student_id, f"{payload.message}{lesson_context}") db.add(models.Message(student_id=student.id, role="assistant", content=reply)) db.commit() diff --git a/backend/app/schemas.py b/backend/app/schemas.py index 260446e..561ccd8 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -54,6 +54,10 @@ class StudentAccountResponse(BaseModel): class ChatRequest(BaseModel): student_id: int message: str + lesson_title: str | None = None + asset_title: str | None = None + section_title: str | None = None + step_percent: int | None = None class ChatResponse(BaseModel): diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 0e99540..58e3fb9 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -213,7 +213,6 @@ function TutorApp({ currentUser, onLogout }) { const [studentProgram, setStudentProgram] = useState(null) const [activeAssetIndex, setActiveAssetIndex] = useState(0) const [activeSectionIndex, setActiveSectionIndex] = useState(0) - const [assetFullscreenOpen, setAssetFullscreenOpen] = useState(false) const [progressPanelOpen, setProgressPanelOpen] = useState(false) const [settingsPanelOpen, setSettingsPanelOpen] = useState(false) const mediaRecorderRef = useRef(null) @@ -512,7 +511,6 @@ function TutorApp({ currentUser, onLogout }) { stopSpeechPlayback() deactivateAutoListening(false) setSessionActive(false) - setAssetFullscreenOpen(false) setProgressPanelOpen(false) setSettingsPanelOpen(false) setCurrentInstruction('') @@ -614,6 +612,22 @@ function TutorApp({ currentUser, onLogout }) { } } + function getLessonContextPayload() { + return { + lesson_title: activeLessonTitle, + asset_title: activeAsset?.title || null, + section_title: activeSection?.title || activeAsset?.title || null, + step_percent: lessonStepPercent, + } + } + + function compactInstruction(text) { + const cleanText = text.trim() + if (!cleanText) return '' + const sentences = cleanText.match(/[^.!?]+[.!?]+|[^.!?]+$/g) || [cleanText] + return sentences.slice(0, 2).join(' ').trim() + } + async function finishSpeechPlayback(debugMessage) { setSpeaking(false) if (audioPlaybackUrlRef.current) { @@ -718,7 +732,11 @@ function TutorApp({ currentUser, onLogout }) { const data = await apiFetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ student_id: Number(selectedStudentId), message: text }), + body: JSON.stringify({ + student_id: Number(selectedStudentId), + message: text, + ...getLessonContextPayload(), + }), }) appendMessage('assistant', data.reply) setCurrentInstruction(data.reply) @@ -1079,7 +1097,7 @@ function TutorApp({ currentUser, onLogout }) { )} {currentInstruction ? ( -

{currentInstruction}

+

{compactInstruction(currentInstruction)}

) : (

Professeur TOP prépare la suite de la leçon.

)} @@ -1106,18 +1124,12 @@ function TutorApp({ currentUser, onLogout }) { - + )} @@ -1195,42 +1207,6 @@ function TutorApp({ currentUser, onLogout }) { )} - {assetFullscreenOpen && activeAsset && ( -
-
- - Étape {activeLessonStep}/{totalLessonSteps} · {activeSection?.title || activeAsset.title} - - -
-
- {activeSection?.title -
-
- - -
-
- )} )} diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 01a698c..d5d467a 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -477,7 +477,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; } font-weight: 700; } .teacher-transcript { - max-height: 4.8rem; + max-height: 3.2rem; overflow: auto; color: #14213d; font-size: clamp(0.95rem, 1.5vw, 1.08rem); @@ -523,43 +523,6 @@ button:disabled { opacity: 0.5; cursor: not-allowed; } border-radius: 14px; background: white; } -.lesson-asset-open span { - display: none; - color: #2563eb; - font-size: 0.9rem; - font-weight: 700; -} -.asset-fullscreen { - position: fixed; - inset: 0; - z-index: 20; - display: grid; - grid-template-rows: auto minmax(0, 1fr) auto; - gap: 0.75rem; - padding: 0.75rem; - background: #f5f7fb; -} -.asset-fullscreen-bar, -.asset-fullscreen-controls { - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.75rem; -} -.asset-fullscreen-stage { - min-height: 0; - overflow: auto; - border: 1px solid #dbeafe; - border-radius: 16px; - background: white; -} -.asset-fullscreen-stage img { - display: block; - width: max(100%, 980px); - height: auto; - min-height: 100%; - object-fit: contain; -} .lesson-bubble { min-height: 68px; } @@ -794,8 +757,8 @@ button:disabled { opacity: 0.5; cursor: not-allowed; } } .teacher-transcript { - max-height: 2.7rem; - font-size: 0.95rem; + max-height: 2.35rem; + font-size: 0.9rem; } .lesson-display-head, @@ -819,26 +782,8 @@ button:disabled { opacity: 0.5; cursor: not-allowed; } font-size: 0.95rem; } - .lesson-asset-open span { - display: block; - } - .lesson-asset-open img { - max-height: 31dvh; - } - - .asset-fullscreen { - padding: 0.5rem; - } - - .asset-fullscreen-stage img { - width: 100%; - min-height: 100%; - } - - .asset-fullscreen-controls { - display: grid; - grid-template-columns: 1fr 1fr; + max-height: none; } .lesson-composer {