@@ -94,6 +94,14 @@ type PresentationMessage = {
9494 sections : PresentationMessageDetailSection [ ] ;
9595 } ;
9696} ;
97+ type NarrativeStageSummary = {
98+ id : string ;
99+ summaryTitle : string ;
100+ status : 'pending' | 'active' | 'done' ;
101+ taskTitle : string ;
102+ body ?: string ;
103+ items : Array < { id : string ; label : string ; phase : 'pending' | 'processing' | 'done' } > ;
104+ } ;
97105type ScriptBubble = { node : string ; text : string ; icon ?: BubbleIcon } ;
98106type ScriptAction = {
99107 id : string ;
@@ -1591,6 +1599,100 @@ function buildFinalDeliveryPresentation(messages: PresentationMessage[]) {
15911599 } ;
15921600}
15931601
1602+ function getFixedStageSummaryTitle ( stageIndex : number ) {
1603+ switch ( stageIndex ) {
1604+ case 0 :
1605+ return 'Onboard Family Domain' ;
1606+ case 1 :
1607+ return 'Discover And Assign Courier' ;
1608+ case 2 :
1609+ return 'Notify And Coordinate Delivery' ;
1610+ case 3 :
1611+ return 'Verify And Close Handoff' ;
1612+ default :
1613+ return `Stage ${ stageIndex + 1 } ` ;
1614+ }
1615+ }
1616+
1617+ function getStageNarrativeSnapshot ( script : DemoScript , stageIndex : number ) {
1618+ const stage = script . stages [ stageIndex ] ;
1619+ if ( ! stage ) {
1620+ return { title : 'Narrative unavailable' , body : '' } ;
1621+ }
1622+ const firstAction = flattenStage ( stage ) [ 0 ] ;
1623+ if ( stageIndex === 0 ) {
1624+ return {
1625+ title : 'Family domain created and ready' ,
1626+ body : '- **Family Domain** is now active.\n- Members: **UE Assistant** and **RobotDog**.\n- Domain ID: `4sg520s2.acn.domain.cmcc`.' ,
1627+ } ;
1628+ }
1629+ if ( stageIndex === 1 ) {
1630+ return {
1631+ title : 'Courier discovered and pickup assigned' ,
1632+ body : '- **RobotArm** has been selected as the courier.\n- The pickup task has been delivered across both gateways.' ,
1633+ } ;
1634+ }
1635+ if ( stageIndex === 2 ) {
1636+ return {
1637+ title : 'User notified and delivery is underway' ,
1638+ body : '- The phone has received the **RobotArm** identity.\n- The delivery workflow is now moving into live coordination.' ,
1639+ } ;
1640+ }
1641+ if ( stageIndex === 3 ) {
1642+ return buildFinalDeliveryPresentation ( [ ] ) ;
1643+ }
1644+ const scenarioIds = firstAction ? ( ACTION_SCENARIO_MAP [ firstAction . id ] ?? STAGE_SCENARIO_MAP [ stageIndex ] ?? [ ] ) : ( STAGE_SCENARIO_MAP [ stageIndex ] ?? [ ] ) ;
1645+ return deriveScenarioNarrative (
1646+ scenarioIds ,
1647+ firstAction ?. presentation ?. title ?? stage . title ,
1648+ firstAction ?. presentation ?. body ?? `- **Current step:** ${ firstAction ?. stepLabel ?? stage . title } ` ,
1649+ ) ;
1650+ }
1651+
1652+ function deriveNarrativeStageSummaries (
1653+ script : DemoScript ,
1654+ playback : PlaybackFrame ,
1655+ presentationCard : { title : string ; body : string ; messages : PresentationMessage [ ] } ,
1656+ ) : NarrativeStageSummary [ ] {
1657+ return script . stages . map ( ( stage , index ) => {
1658+ const stageActions = flattenStage ( stage ) ;
1659+ let status : NarrativeStageSummary [ 'status' ] = 'pending' ;
1660+ if ( playback . phase === 'complete' ) {
1661+ status = 'done' ;
1662+ } else if ( playback . phase !== 'standby' ) {
1663+ if ( index < playback . stageIndex ) {
1664+ status = 'done' ;
1665+ } else if ( index === playback . stageIndex ) {
1666+ status = playback . phase === 'gate' ? 'done' : 'active' ;
1667+ }
1668+ }
1669+
1670+ const fallbackSnapshot = getStageNarrativeSnapshot ( script , index ) ;
1671+ const title = index === playback . stageIndex && playback . phase !== 'standby'
1672+ ? presentationCard . title
1673+ : fallbackSnapshot . title ;
1674+ const body = index === playback . stageIndex && playback . phase !== 'standby'
1675+ ? presentationCard . body
1676+ : fallbackSnapshot . body ;
1677+ const items = index === playback . stageIndex
1678+ ? playback . checklistItems
1679+ : stageActions . map ( ( action ) => ( {
1680+ id : action . id ,
1681+ label : action . checklistTitle ? checklistDisplayId ( action . id ) : action . stepLabel ,
1682+ phase : status === 'done' ? 'done' as const : 'pending' as const ,
1683+ } ) ) ;
1684+
1685+ return {
1686+ id : stage . id ,
1687+ summaryTitle : getFixedStageSummaryTitle ( index ) ,
1688+ status,
1689+ taskTitle : title ,
1690+ body,
1691+ items,
1692+ } ;
1693+ } ) ;
1694+ }
1695+
15941696function derivePresentationCard (
15951697 script : DemoScript ,
15961698 playback : PlaybackFrame ,
@@ -1930,6 +2032,7 @@ function Dashboard() {
19302032 const [ scriptPanelOpen , setScriptPanelOpen ] = useState ( false ) ;
19312033 const [ selectedMessage , setSelectedMessage ] = useState < PresentationMessage | null > ( null ) ;
19322034 const [ sidebarCollapsed , setSidebarCollapsed ] = useState ( false ) ;
2035+ const [ expandedNarrativeStageId , setExpandedNarrativeStageId ] = useState < string | null > ( null ) ;
19332036 const scriptRef = useRef ( scriptDoc ) ;
19342037 const playbackRef = useRef ( playback ) ;
19352038 const pollInFlightRef = useRef ( false ) ;
@@ -2348,6 +2451,19 @@ function Dashboard() {
23482451 ? 'Continue'
23492452 : 'Start' ;
23502453 const presentationCard = derivePresentationCard ( scriptDoc , playback , activeAction ) ;
2454+ const narrativeStages = deriveNarrativeStageSummaries ( scriptDoc , playback , presentationCard ) ;
2455+ const currentNarrativeTitle = presentationCard . title ;
2456+
2457+ useEffect ( ( ) => {
2458+ const currentStageId = scriptDoc . stages [ playback . stageIndex ] ?. id ?? null ;
2459+ if ( currentStageId ) {
2460+ setExpandedNarrativeStageId ( currentStageId ) ;
2461+ return ;
2462+ }
2463+ if ( playback . phase === 'standby' ) {
2464+ setExpandedNarrativeStageId ( null ) ;
2465+ }
2466+ } , [ playback . phase , playback . stageIndex , scriptDoc . stages ] ) ;
23512467
23522468 useEffect ( ( ) => {
23532469 const previousPhase = previousPlaybackPhaseRef . current ;
@@ -2521,10 +2637,83 @@ function Dashboard() {
25212637 < aside className = "sidebar" >
25222638 < div className = "presentation-panel" >
25232639 < div className = "presentation-panel-kicker" > ✨ AI Narrative</ div >
2524- < h2 className = "presentation-panel-title" > { presentationCard . title } </ h2 >
2525- < div className = "presentation-panel-body" > { renderMessageBody ( presentationCard . body ) } </ div >
2640+ < h2 className = "presentation-current-title" > { currentNarrativeTitle } </ h2 >
2641+ < div className = "narrative-progress" >
2642+ < div className = "narrative-progress-list" >
2643+ { narrativeStages . map ( ( stage , index ) => (
2644+ < section
2645+ key = { stage . id }
2646+ className = { cn (
2647+ 'narrative-stage-card' ,
2648+ stage . status === 'active' && 'narrative-stage-card-active' ,
2649+ stage . status === 'done' && 'narrative-stage-card-done' ,
2650+ ) }
2651+ >
2652+ < button
2653+ type = "button"
2654+ className = "narrative-stage-head"
2655+ onClick = { ( ) => setExpandedNarrativeStageId ( ( current ) => current === stage . id ? null : stage . id ) }
2656+ >
2657+ < span className = { cn (
2658+ 'narrative-stage-dot' ,
2659+ stage . status === 'active' && 'narrative-stage-dot-active' ,
2660+ stage . status === 'done' && 'narrative-stage-dot-done' ,
2661+ ) } >
2662+ { stage . status === 'done' ? '✓' : index + 1 }
2663+ </ span >
2664+ < div className = "narrative-stage-copy" >
2665+ < div className = "narrative-stage-summary" > { stage . summaryTitle } </ div >
2666+ < div className = "narrative-stage-task-title" > { stage . taskTitle } </ div >
2667+ </ div >
2668+ </ button >
2669+ { expandedNarrativeStageId === stage . id ? (
2670+ < div className = "narrative-stage-expanded" >
2671+ { stage . items . length ? (
2672+ < div className = "narrative-stage-items" >
2673+ { stage . items . map ( ( item ) => (
2674+ < div
2675+ key = { item . id }
2676+ className = { cn (
2677+ 'step-queue-item' ,
2678+ item . phase === 'processing' && 'step-queue-item-active' ,
2679+ item . phase === 'done' && 'step-queue-item-done' ,
2680+ ) }
2681+ >
2682+ < span className = "step-queue-dot" >
2683+ { item . phase === 'done' ? '✓' : item . phase === 'processing' ? '•' : '' }
2684+ </ span >
2685+ < div className = "step-queue-copy" >
2686+ < span className = "step-queue-label" >
2687+ { item . phase === 'processing' ? 'In Progress' : item . phase === 'done' ? 'Done' : 'Pending' }
2688+ </ span >
2689+ < span className = "step-queue-value" > { item . label } </ span >
2690+ { stage . status === 'active' && item . phase === 'processing' && stage . body ? (
2691+ < div className = "step-queue-description" > { renderMessageBody ( stage . body ) } </ div >
2692+ ) : null }
2693+ </ div >
2694+ </ div >
2695+ ) ) }
2696+ </ div >
2697+ ) : stage . status === 'active' && stage . body ? (
2698+ < div className = "narrative-stage-items" >
2699+ < div className = "step-queue-item step-queue-item-active" >
2700+ < span className = "step-queue-dot" > •</ span >
2701+ < div className = "step-queue-copy" >
2702+ < span className = "step-queue-label" > In Progress</ span >
2703+ < span className = "step-queue-value" > { stage . taskTitle } </ span >
2704+ < div className = "step-queue-description" > { renderMessageBody ( stage . body ) } </ div >
2705+ </ div >
2706+ </ div >
2707+ </ div >
2708+ ) : null }
2709+ </ div >
2710+ ) : null }
2711+ </ section >
2712+ ) ) }
2713+ </ div >
2714+ </ div >
25262715 < div className = "presentation-chat" >
2527- < div className = "presentation-chat-kicker" > Agent Chat & Messages </ div >
2716+ < div className = "presentation-chat-kicker" > Agent Messages</ div >
25282717 < div ref = { presentationChatListRef } className = "presentation-chat-list" >
25292718 { presentationCard . messages . map ( ( message , index ) => (
25302719 < button
@@ -2549,13 +2738,6 @@ function Dashboard() {
25492738 < span className = "presentation-chat-speaker" > { message . title } </ span >
25502739 { message . time ? < span className = "presentation-chat-time" > { message . time } </ span > : null }
25512740 </ div >
2552- { message . chips ?. length ? (
2553- < div className = "presentation-chat-chips" >
2554- { message . chips . map ( ( chip ) => (
2555- < span key = { chip } className = "presentation-chat-chip" > { chip } </ span >
2556- ) ) }
2557- </ div >
2558- ) : null }
25592741 < div className = "presentation-chat-body" > { renderMessageBody ( message . body ) } </ div >
25602742 </ div >
25612743 </ button >
@@ -2579,13 +2761,6 @@ function Dashboard() {
25792761 < h3 className = "message-detail-title" > { selectedMessage . details . title ?? selectedMessage . title } </ h3 >
25802762 { selectedMessage . time ? < span className = "message-detail-time" > { selectedMessage . time } </ span > : null }
25812763 </ div >
2582- { selectedMessage . chips ?. length ? (
2583- < div className = "message-detail-chips" >
2584- { selectedMessage . chips . map ( ( chip ) => (
2585- < span key = { chip } className = "message-detail-chip" > { chip } </ span >
2586- ) ) }
2587- </ div >
2588- ) : null }
25892764 < div className = "message-detail-summary" > { renderMessageBody ( selectedMessage . body ) } </ div >
25902765 < div className = "message-detail-sections" >
25912766 { selectedMessage . details . sections . map ( ( section ) => (
0 commit comments