| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import type { EngineMode, TriggerEvent } from './types'; |
|
|
| |
| |
| |
|
|
| export interface PlaybackRawState { |
| engineMode: EngineMode; |
| lectureSpeech: string | null; |
| liveSpeech: string | null; |
| speakingAgentId: string | null; |
| thinkingState: { stage: string; agentId?: string } | null; |
| isCueUser: boolean; |
| isTopicPending: boolean; |
| chatIsStreaming: boolean; |
| discussionTrigger: TriggerEvent | null; |
| playbackCompleted: boolean; |
| idleText: string | null; |
| |
| speakingStudent: boolean; |
| |
| sessionType: string | null; |
| } |
|
|
| |
| |
| |
|
|
| export type PlaybackPhase = |
| | 'idle' |
| | 'lecturePlaying' |
| | 'lecturePaused' |
| | 'waitingProactive' |
| | 'discussionActive' |
| | 'discussionPaused' |
| | 'cueUser' |
| | 'completed'; |
|
|
| export type BubbleButtonState = 'bars' | 'play' | 'restart' | 'none'; |
|
|
| export interface PlaybackView { |
| |
| phase: PlaybackPhase; |
|
|
| |
| sourceText: string; |
|
|
| |
| bubbleRole: 'teacher' | 'agent' | 'user' | null; |
|
|
| |
| activeRole: 'teacher' | 'agent' | 'user' | null; |
|
|
| |
| buttonState: BubbleButtonState; |
|
|
| |
| isInLiveFlow: boolean; |
|
|
| |
| isTopicActive: boolean; |
| } |
|
|
| |
| |
| |
|
|
| export function computePlaybackView(raw: PlaybackRawState): PlaybackView { |
| const { |
| engineMode, |
| lectureSpeech, |
| liveSpeech, |
| speakingAgentId, |
| thinkingState, |
| isCueUser, |
| isTopicPending, |
| chatIsStreaming, |
| discussionTrigger, |
| playbackCompleted, |
| idleText, |
| speakingStudent, |
| sessionType, |
| } = raw; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| const isInLiveFlow = !!(speakingAgentId || thinkingState || chatIsStreaming || sessionType); |
|
|
| |
| |
| |
| |
| let phase: PlaybackPhase; |
| if (isCueUser) { |
| phase = 'cueUser'; |
| } else if (isTopicPending) { |
| phase = 'discussionPaused'; |
| } else if (speakingAgentId || thinkingState || chatIsStreaming || sessionType) { |
| phase = 'discussionActive'; |
| } else if (discussionTrigger) { |
| phase = 'waitingProactive'; |
| } else if (playbackCompleted) { |
| phase = 'completed'; |
| } else if (engineMode === 'playing') { |
| phase = 'lecturePlaying'; |
| } else if (engineMode === 'paused') { |
| phase = 'lecturePaused'; |
| } else { |
| phase = 'idle'; |
| } |
|
|
| |
| let sourceText: string; |
| if (liveSpeech) { |
| sourceText = liveSpeech; |
| } else if (isInLiveFlow) { |
| |
| sourceText = ''; |
| } else if (lectureSpeech) { |
| sourceText = lectureSpeech; |
| } else if (phase === 'completed') { |
| sourceText = ''; |
| } else { |
| sourceText = idleText || ''; |
| } |
|
|
| |
| const isBubbleLoading = !!(speakingAgentId && !liveSpeech); |
| const isAgentLoading = !!(speakingStudent && !liveSpeech); |
|
|
| |
| let activeRole: 'teacher' | 'agent' | 'user' | null; |
| if (liveSpeech && speakingStudent) { |
| activeRole = 'agent'; |
| } else if (liveSpeech) { |
| activeRole = 'teacher'; |
| } else if (isAgentLoading) { |
| activeRole = 'agent'; |
| } else if (isBubbleLoading) { |
| activeRole = 'teacher'; |
| } else if (isCueUser) { |
| activeRole = null; |
| } else if (lectureSpeech) { |
| activeRole = 'teacher'; |
| } else { |
| activeRole = null; |
| } |
|
|
| |
| let bubbleRole: 'teacher' | 'agent' | 'user' | null; |
| if (liveSpeech && speakingStudent) { |
| bubbleRole = 'agent'; |
| } else if (liveSpeech) { |
| bubbleRole = 'teacher'; |
| } else if (isAgentLoading) { |
| bubbleRole = 'agent'; |
| } else if (isBubbleLoading) { |
| bubbleRole = 'teacher'; |
| } else if (isInLiveFlow) { |
| bubbleRole = null; |
| } else if (isCueUser) { |
| bubbleRole = null; |
| } else if (lectureSpeech || idleText) { |
| bubbleRole = 'teacher'; |
| } else { |
| bubbleRole = null; |
| } |
|
|
| |
| let buttonState: BubbleButtonState; |
| if (isTopicPending) { |
| buttonState = 'play'; |
| } else if (phase === 'lecturePlaying') { |
| buttonState = 'bars'; |
| } else if (phase === 'discussionActive') { |
| buttonState = 'bars'; |
| } else if (phase === 'completed') { |
| buttonState = 'restart'; |
| } else if (phase === 'idle' || phase === 'lecturePaused') { |
| buttonState = 'play'; |
| } else { |
| buttonState = 'none'; |
| } |
|
|
| |
| const isTopicActive = |
| chatIsStreaming || isTopicPending || isCueUser || engineMode === 'live' || !!discussionTrigger; |
|
|
| return { |
| phase, |
| sourceText, |
| bubbleRole, |
| activeRole, |
| buttonState, |
| isInLiveFlow, |
| isTopicActive, |
| }; |
| } |
|
|