当前 DialogueChatView.vue 是一个 1041 行的单文件组件,所有对话逻辑(状态管理、录音模拟、评估生成、UI 渲染)全部内联在一个文件中,使用硬编码的 dialogueScript 数组驱动对话轮次。
现有架构问题:
isRecording 布尔值generateMockEvaluation() 随机生成技术约束:
cococlass-english-speaking-api(Python + FastAPI)Goals:
Non-Goals:
选择: llmService(通信层)→ useDialogueEngine(状态编排层)→ DialogueChatView(UI 层)
理由:
选择: fetch() + response.body.getReader() 实现流式读取
理由: EventSource 只支持 GET,fetch streaming 支持 POST + AbortController + 自定义 headers
选择: loading | done | error,不细分内部阶段
理由:
选择: 直接封装 MediaRecorder,不引入第三方录音库
理由: 现代浏览器兼容性已足够,避免额外依赖
选择: DialogueAPI 接口对应后端的 session / speak / report
interface DialogueAPI {
createSession(config: SessionConfig): Promise<SessionInfo>
speak(sessionId: string, audioBlob: Blob, signal: AbortSignal): AsyncGenerator<SSEEvent>
getReport(sessionId: string): Promise<Report>
}
实现: MockDialogueAPI(开发用)和 RealDialogueAPI(对接真实后端)
通过前端 prop mode: 'preview' | 'real' 切换
选择: AI 消息 done 后,前端整段文本调用 Azure TTS 合成语音并播放
理由:
独立编排服务,串联 Azure Speech 和 One-Hub,管理对话 session 状态。
前端
│
▼
cococlass-english-speaking-api(编排层)
├──→ Azure Speech ASR + 发音评估
├──→ One-Hub LLM 流式对话(Chat Completions)
├──→ MySQL session + 消息 + 评估结果
└──→ S3 音频文件存储
| 层 | 选择 |
|---|---|
| 框架 | FastAPI + uvicorn |
| LLM | Chat Completions via One-Hub(openai SDK) |
| ASR | Azure Speech(纯转录模式) |
| 发音评估 | Azure Speech Pronunciation Assessment(后台) |
| SSE | FastAPI 原生 EventSourceResponse + ServerSentEvent |
| 数据库 | MySQL + asyncmy + SQLAlchemy 2.0 |
| 存储 | S3(boto3) |
| 配置 | pydantic-settings + python-dotenv |
理由: 口语对话是简单一问一答,不需要 Thread/Run 机制。对话历史要存自己 DB,Assistants 的 Thread 管理是重复功能。
理由: 前端不应知道后端内部有几步(ASR/LLM/评估)。音频只上传一次,错误处理统一。
理由: 保证练习流畅度。发音评估后台静默执行,结果页才展示。
ASRProvider / LLMProvider / PronunciationAssessor / AudioStorage 四个 Protocol 接口,具体实现可替换可测试。
理由: 3 轮对话只有 3 次 DB 查询,不值得加缓存层。
POST /api/speaking/dialogue/session → JSON { sessionId, aiMessage }
POST /api/speaking/dialogue/speak → SSE { transcript, token, done }
GET /api/speaking/dialogue/report → JSON { 对话历史 + 评分 + LLM 总结 }
onUnmounted 中统一清理录音 Blobexpires_at 判断,/speak 时检查是否超时。前端用 expires_at 显示倒计时,不再是纯前端控制