@@ -52,6 +52,43 @@ function asString(value: unknown): string | undefined {
5252 return typeof value === 'string' ? value : undefined ;
5353}
5454
55+ interface AiEvidenceData {
56+ phase ?: string ;
57+ expectedPath ?: string ;
58+ actualPath ?: string ;
59+ patch ?: string ;
60+ output ?: string ;
61+ checklist : string [ ] ;
62+ lineRef ?: number ;
63+ codeRef ?: string ;
64+ }
65+
66+ function getAiEvidenceData (
67+ stepRecord : Record < string , unknown > | undefined ,
68+ lineRef ?: number
69+ ) : AiEvidenceData {
70+ if ( ! stepRecord ) {
71+ return { checklist : [ ] , lineRef } ;
72+ }
73+
74+ const state = isRecord ( stepRecord . algorithmState ) ? stepRecord . algorithmState : undefined ;
75+ const rawChecklist = state ?. checklist ;
76+ const checklist = Array . isArray ( rawChecklist )
77+ ? rawChecklist . filter ( ( item ) : item is string => typeof item === 'string' && item . trim ( ) . length > 0 )
78+ : [ ] ;
79+
80+ return {
81+ phase : asString ( state ?. phase ) ,
82+ expectedPath : asString ( state ?. expectedPath ) ,
83+ actualPath : asString ( state ?. actualPath ) ,
84+ patch : asString ( state ?. patch ) ,
85+ output : asString ( state ?. output ) ,
86+ checklist,
87+ lineRef,
88+ codeRef : asString ( stepRecord . code ) ,
89+ } ;
90+ }
91+
5592export function LessonUnifiedView ( {
5693 code,
5794 steps,
@@ -62,6 +99,7 @@ export function LessonUnifiedView({
6299} : LessonUnifiedViewProps ) {
63100 const { t } = useTranslation ( ) ;
64101 const isMobile = useIsMobile ( ) ;
102+ const isAiLiteracy = languageId === 'ai-literacy' ;
65103 const [ activeVizTab , setActiveVizTab ] = useState < 'flow' | 'memory' | 'jsMemory' > ( 'flow' ) ;
66104 const [ isConceptOpen , setIsConceptOpen ] = useState ( false ) ;
67105
@@ -70,10 +108,15 @@ export function LessonUnifiedView({
70108 steps,
71109 lessonId,
72110 onQuiz,
111+ forceSingleRound : isAiLiteracy ,
73112 } ) ;
74113
75114 const currentStep = steps [ nav . actualStepIndex ] ;
76115 const currentStepRecord = currentStep as Record < string , unknown > | undefined ;
116+ const currentStepIllustrations = Array . isArray ( currentStepRecord ?. illustrations )
117+ ? ( currentStepRecord . illustrations as Array < { src : string ; alt ?: string ; caption ?: string } > )
118+ : undefined ;
119+ const aiEvidence = getAiEvidenceData ( currentStepRecord , currentStep ?. line ) ;
77120 const isExplanationRound = nav . round === 'explanation' ;
78121 const { showMemoryTab, showJsMemoryTab } = useMemo ( ( ) => {
79122 const vizSteps = nav . vizStepIndices . map ( i => steps [ i ] ) ;
@@ -86,7 +129,9 @@ export function LessonUnifiedView({
86129 } ;
87130 } , [ languageId , nav . vizStepIndices , steps ] ) ;
88131 const hasVizTabs = showMemoryTab || showJsMemoryTab ;
89- const flowLanguage = languageId === 'python-practical' ? 'python' : ( languageId || 'c' ) ;
132+ const flowLanguage = languageId === 'python-practical'
133+ ? 'python'
134+ : ( languageId === 'ai-literacy' ? 'javascript' : ( languageId || 'c' ) ) ;
90135 const rawConceptType = asString ( currentStepRecord ?. conceptVisualizationType ) || asString ( currentStepRecord ?. visualizationType ) ;
91136 const conceptType = rawConceptType && CONCEPT_TYPES . has ( rawConceptType ) ? rawConceptType : undefined ;
92137 const conceptState = isRecord ( currentStepRecord ?. conceptState ) ? currentStepRecord . conceptState : undefined ;
@@ -167,16 +212,23 @@ export function LessonUnifiedView({
167212 borderColor : 'var(--theme-lesson-panel-border)' ,
168213 } }
169214 >
170- < div className = "flex gap-1" >
171- < span className = { `round-tab px-2.5 py-1 text-xs md:text-sm font-bold rounded-full ${ isExplanationRound ? 'round-tab-active' : 'round-tab-inactive' } ` } >
172- { t ( 'lesson.explanation' ) }
173- </ span >
174- { nav . hasVizRound && (
175- < span className = { `round-tab px-2.5 py-1 text-xs md:text-sm font-bold rounded-full ${ ! isExplanationRound ? 'round-tab-active' : 'round-tab-inactive' } ` } >
176- { t ( 'lesson.visualization' ) }
215+ { ! isAiLiteracy && (
216+ < div className = "flex gap-1" >
217+ < span className = { `round-tab px-2.5 py-1 text-xs md:text-sm font-bold rounded-full ${ isExplanationRound ? 'round-tab-active' : 'round-tab-inactive' } ` } >
218+ { t ( 'lesson.explanation' ) }
177219 </ span >
178- ) }
179- </ div >
220+ { nav . hasVizRound && (
221+ < span className = { `round-tab px-2.5 py-1 text-xs md:text-sm font-bold rounded-full ${ ! isExplanationRound ? 'round-tab-active' : 'round-tab-inactive' } ` } >
222+ { t ( 'lesson.visualization' ) }
223+ </ span >
224+ ) }
225+ </ div >
226+ ) }
227+ { isAiLiteracy && (
228+ < span className = "text-xs md:text-sm font-semibold opacity-80" >
229+ AI Verification
230+ </ span >
231+ ) }
180232 < span className = "ml-auto text-xs md:text-sm font-semibold opacity-60" >
181233 { nav . stepIndex + 1 } /{ nav . totalInRound } · L{ currentStep ?. line || 1 }
182234 </ span >
@@ -199,7 +251,65 @@ export function LessonUnifiedView({
199251 < StepExplanation
200252 explanation = { currentStep ?. explanation || '' }
201253 stepIndex = { nav . stepIndex }
254+ illustrations = { currentStepIllustrations }
202255 />
256+ { isAiLiteracy && (
257+ < div className = "mt-4 rounded-xl border border-[var(--theme-lesson-panel-border)] bg-[var(--theme-lesson-panel-bg)] overflow-hidden" >
258+ < div className = "px-3 py-2 text-sm font-semibold border-b border-[var(--theme-lesson-panel-border)]" >
259+ Evidence
260+ </ div >
261+ < div className = "px-3 py-3 space-y-2.5 text-xs" >
262+ < div className = "flex items-center gap-2" >
263+ < span className = "font-semibold opacity-70 min-w-[56px]" > Phase</ span >
264+ < span className = "rounded-md border border-[var(--theme-lesson-panel-border)] px-2 py-1 font-semibold" >
265+ { aiEvidence . phase || 'inspect' }
266+ </ span >
267+ </ div >
268+ { aiEvidence . codeRef && (
269+ < div className = "rounded-lg border border-[var(--theme-lesson-panel-border)] px-2.5 py-2" >
270+ < div className = "font-semibold opacity-70 mb-1" > Reference</ div >
271+ < div className = "font-mono break-all text-[11px]" > { aiEvidence . codeRef } </ div >
272+ </ div >
273+ ) }
274+ { ( aiEvidence . expectedPath || aiEvidence . actualPath ) && (
275+ < div className = "rounded-lg border border-[var(--theme-lesson-panel-border)] px-2.5 py-2" >
276+ < div className = "font-semibold opacity-70 mb-1" > Path Check</ div >
277+ { aiEvidence . expectedPath && (
278+ < div className = "font-mono break-all" > expect: { aiEvidence . expectedPath } </ div >
279+ ) }
280+ { aiEvidence . actualPath && (
281+ < div className = "font-mono break-all" > real: { aiEvidence . actualPath } </ div >
282+ ) }
283+ { aiEvidence . expectedPath && aiEvidence . actualPath && (
284+ < div className = "font-mono mt-1 opacity-80" > → { aiEvidence . expectedPath } to { aiEvidence . actualPath } </ div >
285+ ) }
286+ </ div >
287+ ) }
288+ { aiEvidence . patch && (
289+ < div className = "rounded-lg border border-[var(--theme-lesson-panel-border)] px-2.5 py-2" >
290+ < div className = "font-semibold opacity-70 mb-1" > Patch</ div >
291+ < div className = "font-mono break-all" > { aiEvidence . patch } </ div >
292+ </ div >
293+ ) }
294+ { aiEvidence . output && (
295+ < div className = "rounded-lg border border-[var(--theme-lesson-panel-border)] px-2.5 py-2" >
296+ < div className = "font-semibold opacity-70 mb-1" > Output</ div >
297+ < div className = "font-mono break-all" > { aiEvidence . output } </ div >
298+ </ div >
299+ ) }
300+ { aiEvidence . checklist . length > 0 && (
301+ < div className = "rounded-lg border border-[var(--theme-lesson-panel-border)] px-2.5 py-2" >
302+ < div className = "font-semibold opacity-70 mb-1" > Checklist</ div >
303+ < div className = "space-y-1" >
304+ { aiEvidence . checklist . map ( ( item ) => (
305+ < div key = { item } className = "font-mono" > - { item } </ div >
306+ ) ) }
307+ </ div >
308+ </ div >
309+ ) }
310+ </ div >
311+ </ div >
312+ ) }
203313 </ motion . div >
204314 ) : (
205315 < motion . div
@@ -300,6 +410,7 @@ export function LessonUnifiedView({
300410 < LessonCodePanel
301411 code = { code }
302412 highlightLine = { currentStep ?. line || 1 }
413+ pointerLine = { isAiLiteracy ? currentStep ?. line : undefined }
303414 terminalLines = { terminalLines }
304415 onSelectionChange = { onSelectionChange }
305416 orientation = { isMobile ? 'vertical' : 'horizontal' }
0 commit comments