Context adj
This commit is contained in:
@@ -304,27 +304,31 @@ def start_session(
|
|||||||
first_asset = (assigned_lesson.get("assets") or [None])[0]
|
first_asset = (assigned_lesson.get("assets") or [None])[0]
|
||||||
first_section = (first_asset.get("sections") or [None])[0] if first_asset else None
|
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"]
|
section_label = first_section["title"] if first_section else first_asset["title"] if first_asset else assigned_lesson["title"]
|
||||||
message = (
|
lesson_context = (
|
||||||
f"Bonjour {student.first_name} ! Aujourd’hui, on travaille la leçon "
|
f"Leçon courante: {assigned_lesson['title']}\n"
|
||||||
f"{assigned_lesson['title']}. Regarde la carte « {section_label} ». "
|
f"Fiche courante: {first_asset['title'] if first_asset else 'non précisée'}\n"
|
||||||
"Dis-moi si tu comprends cette partie, ou ce qui te gêne."
|
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()
|
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 = (
|
prompt = (
|
||||||
"L’élève revient pour une nouvelle séance. Fais une courte reprise: "
|
"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 l’historique, "
|
"explique où on s’était arrêté la dernière fois à partir de l’historique, "
|
||||||
"puis annonce clairement ce qu'on va faire aujourd'hui. Termine par une "
|
"puis annonce clairement ce qu'on va faire aujourd'hui. Termine par une "
|
||||||
"première consigne simple."
|
"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)
|
message = build_llm_reply(db, student.id, prompt)
|
||||||
db.add(models.Message(student_id=student.id, role="assistant", content=message))
|
db.add(models.Message(student_id=student.id, role="assistant", content=message))
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ def ensure_student_mastery(db: Session, student: models.Student) -> None:
|
|||||||
db.commit()
|
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()
|
student = db.query(models.Student).filter_by(id=student_id).first()
|
||||||
mastery = (
|
mastery = (
|
||||||
db.query(models.StudentSkillMastery, models.Skill)
|
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)
|
.filter(models.StudentSkillMastery.student_id == student_id)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
recent_messages = (
|
recent_messages = []
|
||||||
db.query(models.Message)
|
if include_history:
|
||||||
.filter_by(student_id=student_id)
|
recent_messages = (
|
||||||
.order_by(models.Message.created_at.desc())
|
db.query(models.Message)
|
||||||
.limit(6)
|
.filter_by(student_id=student_id)
|
||||||
.all()
|
.order_by(models.Message.created_at.desc())
|
||||||
)
|
.limit(6)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
assignment = db.query(models.StudentProgramAssignment).filter_by(student_id=student_id).first()
|
assignment = db.query(models.StudentProgramAssignment).filter_by(student_id=student_id).first()
|
||||||
lesson = get_lesson(assignment.lesson_id) if assignment else None
|
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(
|
lines.append(
|
||||||
f"- {skill.label}: score={mastery_row.mastery_score:.1f}, confiance={mastery_row.confidence:.2f}, preuves={mastery_row.evidence_count}"
|
f"- {skill.label}: score={mastery_row.mastery_score:.1f}, confiance={mastery_row.confidence:.2f}, preuves={mastery_row.evidence_count}"
|
||||||
)
|
)
|
||||||
lines.append("Historique récent:")
|
if include_history:
|
||||||
for message in reversed(recent_messages):
|
lines.append("Historique récent:")
|
||||||
lines.append(f"- {message.role}: {message.content}")
|
for message in reversed(recent_messages):
|
||||||
|
lines.append(f"- {message.role}: {message.content}")
|
||||||
return "\n".join(lines)
|
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:
|
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(
|
response = client.responses.create(
|
||||||
model="gpt-4.1-mini",
|
model="gpt-4.1-mini",
|
||||||
input=[
|
input=[
|
||||||
@@ -177,6 +180,8 @@ def build_lesson_reply(db: Session, student_id: int, user_message: str, lesson_c
|
|||||||
"content": (
|
"content": (
|
||||||
"Tu reçois un contexte courant de leçon lié à la card affichée. "
|
"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. "
|
"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 "
|
"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. "
|
"`advance_lesson_step` à true. Sinon, mets-le à false. "
|
||||||
"Réponds strictement en JSON valide avec les clés `reply` et `advance_lesson_step`. "
|
"Réponds strictement en JSON valide avec les clés `reply` et `advance_lesson_step`. "
|
||||||
|
|||||||
@@ -627,7 +627,7 @@ function TutorApp({ currentUser, onLogout }) {
|
|||||||
const cleanText = text.trim()
|
const cleanText = text.trim()
|
||||||
if (!cleanText) return ''
|
if (!cleanText) return ''
|
||||||
const sentences = cleanText.match(/[^.!?]+[.!?]+|[^.!?]+$/g) || [cleanText]
|
const sentences = cleanText.match(/[^.!?]+[.!?]+|[^.!?]+$/g) || [cleanText]
|
||||||
return sentences.slice(0, 2).join(' ').trim()
|
return sentences.slice(0, 3).join(' ').trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function finishSpeechPlayback(debugMessage) {
|
async function finishSpeechPlayback(debugMessage) {
|
||||||
|
|||||||
@@ -318,10 +318,10 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|||||||
.avatar {
|
.avatar {
|
||||||
width: 144px;
|
width: 144px;
|
||||||
height: 144px;
|
height: 144px;
|
||||||
border-radius: 28px;
|
border-radius: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: white;
|
background: transparent;
|
||||||
box-shadow: 0 10px 20px rgba(245, 158, 11, 0.28);
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
.avatar.speaking { animation: pulse 0.7s infinite alternate; }
|
.avatar.speaking { animation: pulse 0.7s infinite alternate; }
|
||||||
.avatar-image {
|
.avatar-image {
|
||||||
@@ -440,7 +440,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|||||||
.lesson-topbar .avatar {
|
.lesson-topbar .avatar {
|
||||||
width: 92px;
|
width: 92px;
|
||||||
height: 92px;
|
height: 92px;
|
||||||
border-radius: 20px;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
.lesson-topbar .avatar-caption {
|
.lesson-topbar .avatar-caption {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -454,9 +454,9 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.6rem;
|
gap: 0.6rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #eef6ff;
|
background: transparent;
|
||||||
border: 1px solid #cfe4ff;
|
border: 0;
|
||||||
border-radius: 18px;
|
border-radius: 0;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
.lesson-display p {
|
.lesson-display p {
|
||||||
@@ -477,11 +477,12 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
.teacher-transcript {
|
.teacher-transcript {
|
||||||
max-height: 3.2rem;
|
min-height: calc(1.35em * 3);
|
||||||
overflow: auto;
|
max-height: calc(1.35em * 3);
|
||||||
|
overflow: hidden;
|
||||||
color: #14213d;
|
color: #14213d;
|
||||||
font-size: clamp(0.95rem, 1.5vw, 1.08rem);
|
font-size: clamp(0.95rem, 1.5vw, 1.08rem);
|
||||||
line-height: 1.45;
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
.lesson-asset-view {
|
.lesson-asset-view {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
@@ -521,9 +522,9 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
border: 1px solid #dbeafe;
|
border: 0;
|
||||||
border-radius: 14px;
|
border-radius: 0;
|
||||||
background: white;
|
background: transparent;
|
||||||
}
|
}
|
||||||
.lesson-bubble {
|
.lesson-bubble {
|
||||||
min-height: 68px;
|
min-height: 68px;
|
||||||
@@ -742,7 +743,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|||||||
.lesson-topbar .avatar {
|
.lesson-topbar .avatar {
|
||||||
width: 72px;
|
width: 72px;
|
||||||
height: 72px;
|
height: 72px;
|
||||||
border-radius: 16px;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lesson-topbar h1 {
|
.lesson-topbar h1 {
|
||||||
@@ -761,7 +762,8 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|||||||
}
|
}
|
||||||
|
|
||||||
.teacher-transcript {
|
.teacher-transcript {
|
||||||
max-height: 2.15rem;
|
min-height: calc(1.35em * 3);
|
||||||
|
max-height: calc(1.35em * 3);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
padding: 0 0.65rem;
|
padding: 0 0.65rem;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user