Context adj

This commit is contained in:
2026-05-02 18:47:47 +02:00
parent 7dccc51ce1
commit c93d26d696
4 changed files with 52 additions and 41 deletions

View File

@@ -304,27 +304,31 @@ def start_session(
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} ! Aujourdhui, on travaille la leçon "
f"{assigned_lesson['title']}. Regarde la carte « {section_label} ». "
"Dis-moi si tu comprends cette partie, ou ce qui te gêne."
lesson_context = (
f"Leçon courante: {assigned_lesson['title']}\n"
f"Fiche courante: {first_asset['title'] if first_asset else 'non précisée'}\n"
f"Card courante: {section_label}\n"
f"Clé de contexte: {first_section.get('context_key') if first_section else 'non précisée'}\n"
"Avancement affiché: début de leçon\n"
"Règle: commence directement sur cette card. Ne fais pas de reprise de la dernière séance.\n"
)
if first_section and first_section.get("context"):
lesson_context += f"\nTexte pédagogique de la card courante:\n{first_section['context'].strip()}"
lesson_reply = build_lesson_reply(
db,
student.id,
"La séance démarre. Présente la card courante et pose une question de compréhension.",
lesson_context,
)
message = lesson_reply["reply"]
previous_messages_count = db.query(models.Message).filter_by(student_id=student.id).count()
if previous_messages_count:
if previous_messages_count and not assigned_lesson:
prompt = (
"Lélève revient pour une nouvelle séance. Fais une courte reprise: "
"explique où on sétait arrêté la dernière fois à partir de lhistorique, "
"puis annonce clairement ce qu'on va faire aujourd'hui. Termine par une "
"première consigne simple."
)
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"]
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()

View File

@@ -106,7 +106,7 @@ def ensure_student_mastery(db: Session, student: models.Student) -> None:
db.commit()
def get_student_context(db: Session, student_id: int) -> str:
def get_student_context(db: Session, student_id: int, include_history: bool = True) -> str:
student = db.query(models.Student).filter_by(id=student_id).first()
mastery = (
db.query(models.StudentSkillMastery, models.Skill)
@@ -114,13 +114,15 @@ def get_student_context(db: Session, student_id: int) -> str:
.filter(models.StudentSkillMastery.student_id == student_id)
.all()
)
recent_messages = (
db.query(models.Message)
.filter_by(student_id=student_id)
.order_by(models.Message.created_at.desc())
.limit(6)
.all()
)
recent_messages = []
if include_history:
recent_messages = (
db.query(models.Message)
.filter_by(student_id=student_id)
.order_by(models.Message.created_at.desc())
.limit(6)
.all()
)
assignment = db.query(models.StudentProgramAssignment).filter_by(student_id=student_id).first()
lesson = get_lesson(assignment.lesson_id) if assignment else None
@@ -142,9 +144,10 @@ def get_student_context(db: Session, student_id: int) -> str:
lines.append(
f"- {skill.label}: score={mastery_row.mastery_score:.1f}, confiance={mastery_row.confidence:.2f}, preuves={mastery_row.evidence_count}"
)
lines.append("Historique récent:")
for message in reversed(recent_messages):
lines.append(f"- {message.role}: {message.content}")
if include_history:
lines.append("Historique récent:")
for message in reversed(recent_messages):
lines.append(f"- {message.role}: {message.content}")
return "\n".join(lines)
@@ -166,7 +169,7 @@ def build_llm_reply(db: Session, student_id: int, user_message: str) -> str:
def build_lesson_reply(db: Session, student_id: int, user_message: str, lesson_context: str) -> dict:
context = get_student_context(db, student_id)
context = get_student_context(db, student_id, include_history=False)
response = client.responses.create(
model="gpt-4.1-mini",
input=[
@@ -177,6 +180,8 @@ def build_lesson_reply(db: Session, student_id: int, user_message: str, lesson_c
"content": (
"Tu reçois un contexte courant de leçon lié à la card affichée. "
"Tu dois parler uniquement de cette card, sauf si l'élève pose une question de clarification immédiate. "
"N'utilise pas l'historique des séances précédentes pour choisir le thème de ta réponse. "
"Ta réponse visible doit tenir dans une zone de 3 lignes: fais 1 ou 2 phrases très courtes. "
"Si la réponse de l'élève montre clairement qu'il a compris la card courante, mets "
"`advance_lesson_step` à true. Sinon, mets-le à false. "
"Réponds strictement en JSON valide avec les clés `reply` et `advance_lesson_step`. "

View File

@@ -627,7 +627,7 @@ function TutorApp({ currentUser, onLogout }) {
const cleanText = text.trim()
if (!cleanText) return ''
const sentences = cleanText.match(/[^.!?]+[.!?]+|[^.!?]+$/g) || [cleanText]
return sentences.slice(0, 2).join(' ').trim()
return sentences.slice(0, 3).join(' ').trim()
}
async function finishSpeechPlayback(debugMessage) {

View File

@@ -318,10 +318,10 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
.avatar {
width: 144px;
height: 144px;
border-radius: 28px;
border-radius: 0;
overflow: hidden;
background: white;
box-shadow: 0 10px 20px rgba(245, 158, 11, 0.28);
background: transparent;
box-shadow: none;
}
.avatar.speaking { animation: pulse 0.7s infinite alternate; }
.avatar-image {
@@ -440,7 +440,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
.lesson-topbar .avatar {
width: 92px;
height: 92px;
border-radius: 20px;
border-radius: 0;
}
.lesson-topbar .avatar-caption {
display: none;
@@ -454,9 +454,9 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
flex-direction: column;
gap: 0.6rem;
overflow: hidden;
background: #eef6ff;
border: 1px solid #cfe4ff;
border-radius: 18px;
background: transparent;
border: 0;
border-radius: 0;
padding: 1rem;
}
.lesson-display p {
@@ -477,11 +477,12 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
font-weight: 700;
}
.teacher-transcript {
max-height: 3.2rem;
overflow: auto;
min-height: calc(1.35em * 3);
max-height: calc(1.35em * 3);
overflow: hidden;
color: #14213d;
font-size: clamp(0.95rem, 1.5vw, 1.08rem);
line-height: 1.45;
line-height: 1.35;
}
.lesson-asset-view {
min-height: 0;
@@ -521,9 +522,9 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
min-height: 0;
flex: 1 1 auto;
object-fit: contain;
border: 1px solid #dbeafe;
border-radius: 14px;
background: white;
border: 0;
border-radius: 0;
background: transparent;
}
.lesson-bubble {
min-height: 68px;
@@ -742,7 +743,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
.lesson-topbar .avatar {
width: 72px;
height: 72px;
border-radius: 16px;
border-radius: 0;
}
.lesson-topbar h1 {
@@ -761,7 +762,8 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
}
.teacher-transcript {
max-height: 2.15rem;
min-height: calc(1.35em * 3);
max-height: calc(1.35em * 3);
font-size: 0.9rem;
padding: 0 0.65rem;
}