|
@@ -206,6 +206,7 @@ export class RealDialogueAPI implements DialogueAPI {
|
|
|
vocabulary: config.vocabulary ?? [],
|
|
vocabulary: config.vocabulary ?? [],
|
|
|
sentences: config.sentences ?? [],
|
|
sentences: config.sentences ?? [],
|
|
|
totalRounds: config.totalRounds,
|
|
totalRounds: config.totalRounds,
|
|
|
|
|
+ durationMinutes: config.durationMinutes,
|
|
|
roleId: config.roleId,
|
|
roleId: config.roleId,
|
|
|
}),
|
|
}),
|
|
|
})
|
|
})
|
|
@@ -286,112 +287,6 @@ export class RealDialogueAPI implements DialogueAPI {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// ==================== Mock API ====================
|
|
|
|
|
-
|
|
|
|
|
-const MOCK_AI_REPLIES = [
|
|
|
|
|
- 'Pandas are adorable! Have you seen them at the zoo?',
|
|
|
|
|
- "That's great! What do pandas like to eat?",
|
|
|
|
|
- "Bamboo is their favorite! You did a wonderful job talking about animals today!",
|
|
|
|
|
-]
|
|
|
|
|
-
|
|
|
|
|
-export class MockDialogueAPI implements DialogueAPI {
|
|
|
|
|
- private roundIndex = 0
|
|
|
|
|
-
|
|
|
|
|
- async createSession(_config: SessionConfig): Promise<SessionInfo> {
|
|
|
|
|
- this.roundIndex = 0
|
|
|
|
|
- return {
|
|
|
|
|
- sessionId: 'mock-session-' + Date.now(),
|
|
|
|
|
- totalRounds: _config.totalRounds,
|
|
|
|
|
- currentRound: 1,
|
|
|
|
|
- expiresAt: null,
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async completeSession(_sessionId: string): Promise<void> {
|
|
|
|
|
- return undefined
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async generateGreeting(_sessionId: string, signal?: AbortSignal): Promise<GreetingInfo> {
|
|
|
|
|
- await delay(300, signal)
|
|
|
|
|
- return { aiMessage: "Hi! What's your favorite animal?" }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async generateTaskHint(_sessionId: string): Promise<TaskHint> {
|
|
|
|
|
- await delay(250)
|
|
|
|
|
- return {
|
|
|
|
|
- practice_level: 'grade5-1',
|
|
|
|
|
- conversation_topic: '我最喜欢的动物',
|
|
|
|
|
- current_question: '和 Tom 聊一聊「我最喜欢的动物」,试着用今天的重点词汇和句型表达你的想法。',
|
|
|
|
|
- example_sentences: [
|
|
|
|
|
- { english: 'I like pandas best because they are very cute.', chinese: '我最喜欢大熊猫,因为它们非常可爱。' },
|
|
|
|
|
- { english: "My favorite animal is the elephant. It's really smart!", chinese: '我最喜欢的动物是大象,它真的很聪明!' },
|
|
|
|
|
- { english: 'I enjoy watching animals at the zoo with my family.', chinese: '我喜欢和家人一起在动物园看动物。' },
|
|
|
|
|
- ],
|
|
|
|
|
- key_vocabulary: [
|
|
|
|
|
- { word: 'favorite', meaning: '最喜欢的' },
|
|
|
|
|
- { word: 'adorable', meaning: '可爱的' },
|
|
|
|
|
- { word: 'bamboo', meaning: '竹子' },
|
|
|
|
|
- { word: 'habitat', meaning: '栖息地' },
|
|
|
|
|
- ],
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async *speak(_sessionId: string, _audioBlob: Blob, signal: AbortSignal): AsyncGenerator<SSEEvent> {
|
|
|
|
|
- const mockStudentTexts = [
|
|
|
|
|
- 'I like pandas. They are very cute!',
|
|
|
|
|
- 'Yes, I went to the zoo last month.',
|
|
|
|
|
- 'They like to eat bamboo.',
|
|
|
|
|
- ]
|
|
|
|
|
-
|
|
|
|
|
- // Simulate transcript
|
|
|
|
|
- await delay(300, signal)
|
|
|
|
|
- yield { type: 'transcript', text: mockStudentTexts[this.roundIndex] || 'I think so.' }
|
|
|
|
|
-
|
|
|
|
|
- // Simulate token streaming
|
|
|
|
|
- const aiReply = MOCK_AI_REPLIES[this.roundIndex] || 'That is very interesting!'
|
|
|
|
|
- const words = aiReply.split(' ')
|
|
|
|
|
- for (const word of words) {
|
|
|
|
|
- await delay(80, signal)
|
|
|
|
|
- yield { type: 'token', text: word + ' ' }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.roundIndex++
|
|
|
|
|
- const isComplete = this.roundIndex >= MOCK_AI_REPLIES.length
|
|
|
|
|
-
|
|
|
|
|
- await delay(100, signal)
|
|
|
|
|
- yield { type: 'done', isComplete }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async getReport(_sessionId: string): Promise<DialogueReport> {
|
|
|
|
|
- return {
|
|
|
|
|
- status: 'ready',
|
|
|
|
|
- evaluation: {
|
|
|
|
|
- overallScore: 85,
|
|
|
|
|
- scoreLevel: 'good',
|
|
|
|
|
- percentile: 78,
|
|
|
|
|
- dimensions: { fluency: 82, interaction: 88, vocabulary: 76, grammar: 90 },
|
|
|
|
|
- aiComment: 'Great job! Your pronunciation was clear and your responses were relevant.',
|
|
|
|
|
- highlights: ['Clear pronunciation', 'Good use of complete sentences', 'Natural flow'],
|
|
|
|
|
- improvements: ['Try more adjectives', 'Practice linking words', 'Expand vocabulary'],
|
|
|
|
|
- nextChallenge: { difficulty: 'Medium', unlockedTopic: 'My Dream Job' },
|
|
|
|
|
- statistics: {
|
|
|
|
|
- totalRounds: 3, averageScore: 83, highestScore: 92,
|
|
|
|
|
- highestRound: 2, grammarErrors: 2, excellentExpressions: 4, totalDuration: 180,
|
|
|
|
|
- },
|
|
|
|
|
- sentenceEvaluations: [],
|
|
|
|
|
- },
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-export function createDialogueApi(mode: 'preview' | 'real'): DialogueAPI {
|
|
|
|
|
- return mode === 'real' ? new RealDialogueAPI() : new MockDialogueAPI()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function delay(ms: number, signal?: AbortSignal): Promise<void> {
|
|
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
|
|
- if (signal?.aborted) { reject(new DOMException('Aborted', 'AbortError')); return }
|
|
|
|
|
- const timer = setTimeout(resolve, ms)
|
|
|
|
|
- signal?.addEventListener('abort', () => { clearTimeout(timer); reject(new DOMException('Aborted', 'AbortError')) }, { once: true })
|
|
|
|
|
- })
|
|
|
|
|
|
|
+export function createDialogueApi(): DialogueAPI {
|
|
|
|
|
+ return new RealDialogueAPI()
|
|
|
}
|
|
}
|