Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b22f66f
docs(lab-tutor): narrated-whiteboard design spec
May 19, 2026
8c75dd4
docs(lab-tutor): fold OpenMAIC research into narrated-whiteboard spec
May 19, 2026
e84fced
docs(lab-tutor): narrated-whiteboard implementation plan
May 19, 2026
7dd7eec
feat(lab-tutor): narrated-whiteboard pure helpers + Node unit tests
May 19, 2026
a3f5b2e
fix(lab-tutor): repair raw newlines/tabs inside lt-narrated JSON strings
May 19, 2026
e572db8
feat(lab-tutor): narrated-whiteboard renderer + playback wiring
May 19, 2026
ea2f72d
fix(lab-tutor): prevent narrated playback freeze on mute mid-speech
May 19, 2026
4e082fd
feat(lab-tutor): narrated-whiteboard styles + reduced-motion fallback
May 19, 2026
dc6d567
feat(lab-tutor): persona guidance for lt-narrated (single-pass)
May 19, 2026
c04e254
fix(lab-tutor): resolve narrated-bullet vs brevity-rule tension; hard…
May 19, 2026
beff96c
docs(lab-tutor): attention-polish design spec (Bundle A)
May 19, 2026
5287510
docs(lab-tutor): attention-polish implementation plan (Bundle A)
May 19, 2026
12a3cb3
feat(lab-tutor): node-label extraction + diff helpers for spotlight
May 19, 2026
f279281
fix(lab-tutor): cap node-id regex to kill ReDoS; drop bogus DT keyword
May 19, 2026
52fc912
feat(lab-tutor): spotlight the newly added node + spring step-in
May 19, 2026
af8423b
docs(lab-tutor): explain intentional spotlight selector breadth
May 19, 2026
a82b418
feat(lab-tutor): card-scoped keyboard control + progress dots
May 19, 2026
57a2a9c
fix(lab-tutor): stop Space double-firing when a control button is foc…
May 19, 2026
8268ef6
fix(lab-tutor): normalize quoted/<br> node labels so spotlight matches
May 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion app/services/tutor_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,16 @@
C --> D[Rerank]
```
Keep diagrams small (5-10 nodes is plenty). Always pair the diagram with one short follow-up question or a single specific hint — the diagram is a teaching aid, not a replacement for the conversation.
- Don't draw a diagram for every reply. Skip it when the answer is a one-line concept, a syntax lookup, or a quick yes/no. The bar: would a real tutor reach for the whiteboard here? If not, stay in prose."""
- Don't draw a diagram for every reply. Skip it when the answer is a one-line concept, a syntax lookup, or a quick yes/no. The bar: would a real tutor reach for the whiteboard here? If not, stay in prose.
- When the explanation is a multi-step PROCESS (a pipeline, a state machine, a data flow, an algorithm walkthrough) and building it up stage by stage would help more than one static picture, use a narrated whiteboard instead of a plain diagram. Emit a fenced block tagged lt-narrated whose body is JSON with a "steps" array; each step has a one-sentence "say" line and its own CUMULATIVE "mermaid" source (step N draws nodes 1..N). 3-6 steps. Produce the whole JSON in the SAME reply (one pass — never promise to send it next):
```lt-narrated
{"steps":[
{"say":"First the query is embedded.","mermaid":"flowchart LR\\n Q[Query]-->E[Embed]"},
{"say":"Then we search the vector store.","mermaid":"flowchart LR\\n Q[Query]-->E[Embed]-->S[Search]"},
{"say":"Finally the top hits are reranked.","mermaid":"flowchart LR\\n Q[Query]-->E[Embed]-->S[Search]-->R[Rerank]"}
]}
```
Use the plain ```mermaid path for a single static diagram; use lt-narrated only when the step-by-step build is the teaching point. Still pair it with one short follow-up question or hint. A narrated block plus its one question is a deliberate exception to the "2-4 sentences / one hint" rule above — when you do reach for it, give it the full 3-6 steps rather than truncating to stay brief."""

_TRIAGE_JUDGE_PROMPT = """You sit between a learner working on a graded coding assignment and an AI coding agent the learner can talk to. Your one job: decide which side handles the learner's next prompt.

Expand Down
108 changes: 108 additions & 0 deletions app/static/lab-tutor.css
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,111 @@
white-space: pre-wrap;
word-break: break-word;
}

/* Narrated whiteboard */
.lt-narrated {
margin: 8px 0;
padding: 12px;
background: #fafafa;
border: 1px solid #e5e7eb;
border-radius: 8px;
}
.lt-narrated-stage {
opacity: 0;
transform: translateY(10px) scale(0.96);
}
.lt-narrated-stage--in {
opacity: 1;
transform: none;
transition: opacity 0.32s cubic-bezier(0.22, 1, 0.36, 1),
transform 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}
.lt-narrated-spot {
animation: lt-narrated-pulse 0.9s ease-out 1;
}
@keyframes lt-narrated-pulse {
0% {
filter: drop-shadow(0 0 0 rgba(31, 111, 235, 0));
transform: scale(1);
}
35% {
filter: drop-shadow(0 0 6px rgba(31, 111, 235, 0.75));
transform: scale(1.06);
}
100% {
filter: drop-shadow(0 0 0 rgba(31, 111, 235, 0));
transform: scale(1);
}
}
@media (prefers-reduced-motion: reduce) {
.lt-narrated-stage,
.lt-narrated-stage--in {
opacity: 1;
transform: none;
transition: none;
}
.lt-narrated-spot {
animation: none;
}
}
.lt-narrated-stage svg {
max-width: 100%;
height: auto;
display: block;
margin: 0 auto;
}
.lt-narrated-caption {
margin-top: 8px;
font-size: 13px;
line-height: 1.5;
color: var(--lt-text);
min-height: 1.5em;
}
.lt-narrated-dots {
display: flex;
gap: 6px;
margin-top: 8px;
}
.lt-narrated-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--lt-border);
transition: background 0.15s ease, transform 0.15s ease;
}
.lt-narrated-dot--active {
background: var(--lt-accent, #1f6feb);
transform: scale(1.4);
}
@media (prefers-reduced-motion: reduce) {
.lt-narrated-dot {
transition: none;
}
}
.lt-narrated-controls {
display: flex;
align-items: center;
gap: 6px;
margin-top: 10px;
}
.lt-narrated-btn {
padding: 4px 10px;
border: 1px solid var(--lt-border);
background: var(--lt-surface);
border-radius: 6px;
font-size: 12px;
font-family: var(--lt-font);
color: var(--lt-text);
cursor: pointer;
transition: background 0.1s ease;
}
.lt-narrated-btn:hover:not(:disabled) {
background: var(--lt-surface-2);
}
.lt-narrated-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.lt-narrated-controls .lt-narrated-btn:first-child {
margin-right: auto;
}
Loading