视频链接:https://www.bilibili.com/video/BV1b5Et6GEVm/?vd_source=556ccddc7ab547193d4f85a944dcbb44 英客 EToker 是一款本地可运行的 AI 英语口语陪练工具,面向中国英语学习者在面试、考试、职场、留学、旅游和兴趣交流等真实场景中进行多轮英语对话训练。项目覆盖从“开始说”到“复盘再练”的完整学习闭环:目标诊断、场景选择、实时语音对话、低压力开口辅助、纠错反馈、课后报告、错题库、表达库和成长追踪。
当前版本定位为比赛和产品验证阶段的 MVP:主分支可在本地直接启动;没有真实模型密钥时仍可使用规则教练和本地兜底能力完成基础演示;配置 Qwen Omni Realtime 和 OpenAI-compatible 大模型后,可以体验更接近真实 AI 语音教练的完整链路。
项目没有把“能聊天”作为终点,而是围绕口语学习的真实行为设计产品闭环:
- 用户先设置学习目标和当前水平。
- 系统推荐今日任务。
- 用户进入具体场景,用英语和 AI 角色对话。
- 练习中可以请求提示、简化表达、中文思路或重说本轮。
- 结束后生成结构化报告。
- 报告中的错误和好表达沉淀到学习库。
- 成长页把历史练习聚合成趋势和常错点。
AI 口语陪练的核心体验是低延迟语音对话,但语音链路容易受模型、网络、麦克风和浏览器策略影响。因此练习房间会显式展示连接状态、麦克风状态、字幕、AI 回复状态、报告生成进度和 fallback 原因,让用户知道系统正在做什么。
后端把教练、语音、发音评测、翻译和存储都拆成可替换 Provider。当前实现支持:
- 规则教练兜底
- OpenAI-compatible 大模型教练
- Qwen Omni WebSocket Realtime 语音 Provider
- 浏览器语音证据发音评估
- 腾讯翻译优先、本地 quick-local 翻译兜底
- SQLite / JSON 会话存储
这样后续替换模型、迁移云服务或增加新能力时,不需要重写页面流程。
| 模块 | 当前能力 | 设计目的 |
|---|---|---|
| 今日工作台 | 目标诊断、当前路径、今日任务、推荐场景 | 把用户从“选哪个场景”引导到“今天练什么” |
| 场景库 | 60 个课程化场景,覆盖 6 类目标和不同难度 | 给练习提供明确角色、目标和成功标准 |
| 自定义场景 | 用户输入场景,可选 AI 角色和用户角色 | 支持更贴近个人需求的训练任务 |
| 练习房间 | WebSocket 实时对话、Qwen Omni 语音、字幕、反馈 | 承载真实口语陪练体验 |
| 低压力辅助 | 提示、简化表达、中文思路、重说本轮 | 降低开口压力,减少卡住和沉默 |
| 纠错策略 | 可选择逐轮点评或课后统一评分 | 平衡交流流畅度和反馈密度 |
| 翻译 | 对话、反馈、报告、能力详情可按需翻译 | 支持中国学习者理解反馈 |
| 课后报告 | 综合分、等级、雷达图、优势、缺陷、典型错误、训练计划 | 让学习结果可解释、可量化 |
| 学习库 | 错题库、表达库、来源报告跳转 | 把一次练习沉淀为长期资产 |
| 成长页 | 开口分钟、连续练习、平均分、趋势、能力快照、常错点 | 给用户持续进步感 |
| 历史记录 | 会话列表、删除、报告回看 | 支持复盘和演示数据管理 |
| 技术 | 用途 |
|---|---|
| React 18 | 页面和交互状态管理 |
| TypeScript | 前端类型约束,与共享契约保持一致 |
| Vite | 本地开发服务和生产构建 |
| React Router | 页面路由 |
| lucide-react | 图标系统 |
| CSS | 全局设计系统、响应式布局、动效 |
| WebSocket API | 练习房间实时对话和语音通道 |
| Web Audio API | 麦克风采集、PCM 音频处理、远端音频播放 |
| localStorage | 语言偏好、目标诊断、逐轮点评开关、今日任务状态 |
| 技术 | 用途 |
|---|---|
| Go | 后端主语言 |
| net/http | HTTP API 和轻量路由 |
| 自定义 CORS middleware | 支持 Vite 前端跨端口访问 Go API |
| github.com/coder/websocket | WebSocket 实时对话和语音代理 |
| SQLite | 默认本地会话持久化 |
| modernc.org/sqlite | SQLite Go 驱动 |
| OpenAI-compatible Chat Completions | 大模型教练、总结、翻译、辅助 |
| Qwen Omni WebSocket Realtime | 真实语音输入、语音输出、字幕和远端事件 |
| Tencent Cloud TMT API | 中文翻译 Provider |
| Go test | 后端单元和集成测试 |
packages/shared 定义前端使用的 TypeScript 数据结构。Go 后端保持相同 JSON 字段,保证前后端传输稳定。
核心类型包括:
ScenarioPracticeSessionDialogueTurnSessionSummaryLearningAssetClientRealtimeMessageServerRealtimeEvent
flowchart LR
User[用户] --> Web[React Web App]
Web -->|HTTP JSON| API[Go HTTP API]
Web -->|WebSocket| RT[Realtime Session WS]
Web -->|WebSocket + PCM Audio| VoiceWS[Local Voice WS Proxy]
API --> Scenario[Scenario Repository]
API --> Store[(SQLite / JSON Store)]
API --> Coach[Coach Provider]
API --> Speech[Speech Provider]
API --> Translate[Translation Provider]
RT --> Hub[In-memory Realtime Hub]
Hub --> Coach
Hub --> Store
VoiceWS --> VoiceProvider[Qwen Omni Realtime Provider]
VoiceProvider --> DashScope[DashScope Qwen Omni WS]
VoiceWS --> Store
VoiceWS --> Speech
Scenario --> ScenarioData[(data/scenarios/*.json)]
Store --> SessionDB[(data/sessions/sessions.db)]
Coach --> Rule[Rule-based Fallback]
Coach --> LLM[OpenAI-compatible LLM]
Translate --> Tencent[Tencent TMT]
Translate --> QuickLocal[quick-local fallback]
flowchart TB
subgraph Browser[Browser]
React[React App :5173]
Audio[Mic / AudioContext]
LocalStorage[localStorage]
end
subgraph Local[Local Machine]
Go[Go Server :8080]
SQLite[(SQLite DB)]
ScenarioJSON[(Scenario JSON)]
end
subgraph Remote[Optional Remote Providers]
LLM[OpenAI-compatible LLM]
Omni[Qwen Omni Realtime]
TMT[Tencent Translate]
end
React --> Go
Audio --> React
React --> LocalStorage
Go --> SQLite
Go --> ScenarioJSON
Go -.with keys in .env.-> LLM
Go -.with keys in .env.-> Omni
Go -.with keys in .env.-> TMT
flowchart LR
Profile[目标诊断] --> Plan[今日计划]
Plan --> Scenario[场景练习]
Scenario --> Dialogue[实时对话]
Dialogue --> Feedback[逐轮反馈或课后反馈]
Feedback --> Report[结构化报告]
Report --> Assets[错题库 / 表达库]
Assets --> Plan
Report --> Progress[成长页]
Progress --> Profile
sequenceDiagram
participant U as 用户
participant Web as React 前端
participant API as Go API
participant Store as SQLite
participant Scenario as 场景仓库
U->>Web: 选择场景 / 自定义场景
Web->>API: POST /api/sessions
API->>Scenario: 读取场景配置
API->>Store: 创建 PracticeSession
Store-->>API: 保存成功
API-->>Web: 返回 session
Web->>Web: 跳转 /practice/:sessionId
sequenceDiagram
participant Web as React PracticeRoom
participant WS as Go Realtime WS
participant Coach as Coach Provider
participant Store as Session Store
Web->>WS: 建立 /api/sessions/{id}/realtime
WS-->>Web: session.snapshot
Web->>WS: turn.submit(clientMessageId, text)
WS-->>Web: turn.processing
WS-->>Web: turn.user_echo
WS->>Coach: Respond / StreamRespond
Coach-->>WS: thinkingText
WS-->>Web: turn.thinking
Coach-->>WS: assistant delta
WS-->>Web: turn.delta
WS->>Store: 保存 DialogueTurn
WS-->>Web: turn.completed
sequenceDiagram
participant Browser as Browser Mic
participant Web as useOmniRealtimeVoiceSession
participant Go as Go Voice Proxy
participant Omni as Qwen Omni Realtime
participant Store as Session Store
Browser->>Web: getUserMedia + AudioContext
Web->>Go: GET /api/sessions/{id}/voice
Go->>Omni: Connect with server-side API key
Web->>Go: input_audio_buffer.append
Go->>Omni: forward audio
Web->>Go: input_audio_buffer.commit + response.create
Omni-->>Go: transcript / audio.delta / status
Go-->>Web: transcript / remote audio / status
Web->>Go: POST /voice/turns
Go->>Store: 保存语音 turn 和 metadata
sequenceDiagram
participant Web as PracticeRoom
participant WS as Realtime WS
participant API as Server Logic
participant Coach as Summary Provider
participant Store as SQLite
Web->>WS: session.complete(clientMessageId)
Web->>Web: 显示报告生成进度条
WS->>API: completeSession(sessionId)
API->>Coach: Summarize(scenario, session)
Coach-->>API: SessionSummary
API->>API: 补齐指标、等级、训练计划
API->>Store: 保存 completed session
WS-->>Web: session.completed
Web->>Web: 进度到 100%
Web->>Web: 跳转 /summary/:sessionId
apps/web/
src/
app/ 应用入口、路由、全局样式
components/ 通用状态、指标、布局组件
features/
i18n/ 中英文文案、场景本地化
planning/ 目标诊断、今日计划、成长统计
realtime/ 文本 WebSocket 实时会话
settings/ 全局设置弹窗
speech/ 浏览器语音相关能力
voice/ Qwen Omni Realtime 语音 Hook
pages/
Home/ 今日练习工作台
ScenarioSelect/ 场景库
CustomScenario/ 自定义场景
PracticeRoom/ 练习房间
Summary/ 课后报告
Library/ 错题库和表达库
History/ 历史记录
Progress/ 成长页
services/ API Client| 路径 | 页面 | 作用 |
|---|---|---|
/ |
今日工作台 | 目标诊断、今日计划、推荐场景 |
/scenarios |
场景库 | 分类、难度、搜索、课程卡 |
/custom-scenario |
自定义场景 | 输入场景和角色,生成专属练习 |
/practice/:sessionId |
练习房间 | 语音对话、辅助、反馈、结束练习 |
/summary/:sessionId |
课后报告 | 能力报告、翻译、收藏错题和表达 |
/history |
历史记录 | 查看和删除历史会话 |
/library |
学习库 | 错题库、表达库 |
/progress |
成长页 | 趋势、能力快照、常错点 |
flowchart TD
UI[Page UI] --> Hooks[Feature Hooks]
Hooks --> API[services/api.ts]
Hooks --> WS[Realtime / Voice WebSocket]
Hooks --> Storage[localStorage]
Storage --> Locale[语言偏好]
Storage --> Profile[目标诊断]
Storage --> ReviewMode[逐轮点评开关]
Storage --> TaskStatus[今日任务状态]
API --> Server[Go API]
WS --> Server
- 首页直接呈现今日任务,而不是营销页。
- 练习房间把场景、角色、字幕、语音、辅助和反馈放在同一屏。
- 单句模式适合逐句训练,连续模式适合更自然的对话。
- 反馈时机可配置:逐轮点评或课后统一评分。
- 结束练习时不弹突兀弹窗,而是在页面内展示报告生成进度。
- 翻译按钮按需展开,避免中文解释打断英语训练环境。
- 移动端和桌面端都使用响应式布局,核心操作始终可触达。
apps/server/
cmd/server/main.go 服务启动、Provider 组装
internal/api/ HTTP API、WebSocket API、业务入口
internal/config/ .env 和环境变量读取
internal/scenario/ 场景 JSON 加载
internal/session/ 会话、turn、summary、learning asset 模型
internal/coach/ 规则教练和 OpenAI-compatible 教练
internal/speech/ 发音评估 Provider
internal/voice/ Qwen Omni Realtime Provider
internal/realtime/ WebSocket 消息协议和内存 Hub
internal/storage/ SQLite / JSON 存储flowchart TD
Main[cmd/server/main.go] --> Config[internal/config]
Main --> API[internal/api.Server]
Main --> Scenario[internal/scenario.Repository]
Main --> Storage[internal/storage.SessionStore]
Main --> Coach[internal/coach.Provider]
Main --> Speech[internal/speech.Provider]
Main --> Voice[internal/voice.Provider]
API --> Scenario
API --> Storage
API --> Coach
API --> Speech
API --> Voice
API --> Realtime[internal/realtime.Hub]
Coach --> Rule[RuleBasedCoach]
Coach --> LLM[OpenAICompatibleCoach]
Voice --> Disabled[DisabledProvider]
Voice --> Omni[QwenOmniRealtimeProvider]
| Provider | 默认实现 | 可选实现 | 作用 |
|---|---|---|---|
| Coach | rule |
openai / llm / openai-compatible |
生成回复、纠错、总结、辅助 |
| Speech | browser |
预留 | 基于转写、时长、置信度生成发音证据 |
| Voice | disabled |
qwen-omni-ws |
实时语音输入输出 |
| Storage | sqlite |
json |
会话和学习资产持久化 |
| Translation | quick-local |
tencent-tmt |
对话、反馈、报告翻译 |
后端没有引入 Gin、Echo 等 Web 框架,而是使用 Go 标准库 net/http。当前中间件采用 http.Handler 包装方式实现:
func withCORS(next http.Handler) http.Handler作用:
- 设置
Access-Control-Allow-Origin - 支持
GET, POST, DELETE, OPTIONS - 处理 Vite 开发服务跨端口访问
- 对
OPTIONS预检请求直接返回204
erDiagram
SCENARIO ||--o{ PRACTICE_SESSION : creates
PRACTICE_SESSION ||--o{ DIALOGUE_TURN : contains
PRACTICE_SESSION ||--o| SESSION_SUMMARY : generates
PRACTICE_SESSION ||--o{ LEARNING_ASSET : saves
DIALOGUE_TURN ||--o{ LEARNING_ASSET : source
SCENARIO {
string id
string category
string title
string difficulty
string coachRole
string userRole
string openingLine
}
PRACTICE_SESSION {
string id
string scenarioId
string status
string startedAt
string completedAt
}
DIALOGUE_TURN {
string id
string userText
string assistantText
int responseLatencyMs
string coachProvider
}
SESSION_SUMMARY {
int overallScore
string level
string abilityProfile
string coachProvider
}
LEARNING_ASSET {
string id
string kind
string sourceText
string targetText
string category
}
场景是课程化训练的入口,包含:
- 场景标题和描述
- 训练目标
- AI 角色和用户角色
- 开场白
- 关键词
- 成功标准
- 建议表达
- 评分维度
- 可选音色
会话记录完整练习过程:
- 场景 ID 和标题
- 会话状态
- 多轮
DialogueTurn - 聚合指标
- 课后报告
- 错题和表达资产
一轮对话包含:
- 用户文本或语音转写
- AI 回复
- 纠错
- 表达建议
- 发音评估
- 反馈时机
- 语音 metadata
- Provider 和 fallback 原因
| 方法 | 路径 | 作用 |
|---|---|---|
GET |
/api/health |
健康检查 |
GET |
/api/scenarios |
获取场景列表 |
POST |
/api/sessions |
创建普通或自定义会话 |
GET |
/api/sessions |
获取历史会话 |
DELETE |
/api/sessions |
批量删除会话 |
GET |
/api/sessions/{id} |
获取会话详情 |
DELETE |
/api/sessions/{id} |
删除单个会话 |
POST |
/api/sessions/{id}/turns |
HTTP 兼容方式提交一轮回答 |
POST |
/api/sessions/{id}/summary |
结束练习并生成报告 |
POST |
/api/sessions/{id}/voice/turns |
保存语音 turn 和 metadata |
POST |
/api/sessions/{id}/translate |
翻译对话、反馈或报告文本 |
POST |
/api/sessions/{id}/assist |
获取低压力开口辅助 |
POST |
/api/sessions/{id}/learning-assets |
收藏错题或表达 |
DELETE |
/api/sessions/{id}/learning-assets/{assetId} |
删除收藏资产 |
GET |
/api/learning-assets |
获取学习库 |
| 路径 | 作用 |
|---|---|
/api/sessions/{id}/realtime |
文本实时对话、流式回复、结束练习 |
/api/sessions/{id}/voice |
本地语音代理,连接 Qwen Omni Realtime |
client -> server
turn.submit
session.complete
ping
server -> client
session.snapshot
turn.processing
turn.user_echo
turn.thinking
turn.delta
turn.completed
session.completed
error项目默认读取根目录 .env。没有 .env 时,也可以用默认配置启动基础功能。
| 变量 | 默认值 | 说明 |
|---|---|---|
APP_HOST |
127.0.0.1 |
后端监听地址 |
APP_PORT |
8080 |
后端端口 |
APP_DATA_DIR |
data |
数据目录 |
APP_STORAGE_PROVIDER |
sqlite |
sqlite 或 json |
APP_DATABASE_PATH |
data/sessions/sessions.db |
SQLite 数据库路径 |
VITE_API_BASE |
/api |
前端 API 基础路径 |
| 变量 | 默认值 | 说明 |
|---|---|---|
APP_COACH_PROVIDER |
rule |
rule / openai / llm / openai-compatible |
OPENAI_API_KEY / LLM_API_KEY / DASHSCOPE_API_KEY |
空 | 大模型 API Key |
OPENAI_BASE_URL / LLM_BASE_URL |
https://api.openai.com/v1 |
OpenAI-compatible Base URL |
OPENAI_MODEL / LLM_MODEL |
gpt-4o-mini |
模型名称 |
LLM_TIMEOUT_SECONDS |
60 |
请求超时时间 |
| 变量 | 默认值 | 说明 |
|---|---|---|
APP_VOICE_PROVIDER |
disabled |
disabled 或 qwen-omni-ws |
DASHSCOPE_API_KEY / QWEN_OMNI_API_KEY |
空 | Qwen Omni API Key |
QWEN_OMNI_MODEL / VOICE_MODEL |
qwen3.5-omni-flash-realtime |
实时语音模型 |
QWEN_OMNI_REALTIME_URL / VOICE_REALTIME_URL |
wss://dashscope.aliyuncs.com/api-ws/v1/realtime |
Realtime endpoint |
QWEN_OMNI_VOICE / VOICE_NAME |
Tina |
默认音色 |
VOICE_TIMEOUT_SECONDS |
20 |
连接超时 |
| 变量 | 默认值 | 说明 |
|---|---|---|
TENCENT_TRANSLATE_SECRET_ID |
空 | 腾讯云 SecretId |
TENCENT_TRANSLATE_SECRET_KEY |
空 | 腾讯云 SecretKey |
TENCENT_TRANSLATE_REGION |
ap-guangzhou |
区域 |
TENCENT_TRANSLATE_SOURCE |
en |
源语言 |
TENCENT_TRANSLATE_TARGET |
zh |
目标语言 |
TENCENT_TRANSLATE_PROJECT_ID |
0 |
项目 ID |
TENCENT_TRANSLATE_TIMEOUT_SECONDS |
6 |
超时时间 |
不要把真实密钥写进前端 VITE_* 变量,也不要提交到公开仓库。评审演示可以用本地 .env 配置。
| 依赖 | 建议版本 |
|---|---|
| Node.js | 20+ |
| npm | 10+ |
| Go | 1.22+ |
npm installnpm run dev默认地址:
Web: http://127.0.0.1:5173
Server: http://127.0.0.1:8080npm run dev:web
npm run dev:servernpm run check
npm run build
npm run test:server$ports = 8080,5173
$connections = Get-NetTCPConnection -LocalPort $ports -ErrorAction SilentlyContinue | Where-Object { $_.State -eq 'Listen' }
$processIds = $connections | Select-Object -ExpandProperty OwningProcess -Unique
foreach ($processId in $processIds) {
taskkill /PID $processId /T /F
}说明后端端口已被占用。先结束旧进程,再重新运行:
Get-NetTCPConnection -LocalPort 8080 | Select-Object OwningProcess
taskkill /PID <pid> /T /F说明 Vite 前端端口已被占用:
Get-NetTCPConnection -LocalPort 5173 | Select-Object OwningProcess
taskkill /PID <pid> /T /F检查:
APP_VOICE_PROVIDER=qwen-omni-wsDASHSCOPE_API_KEY或QWEN_OMNI_API_KEY已配置QWEN_OMNI_REALTIME_URL是 WebSocket 地址,不是普通 HTTPS 域名- 后端日志出现
voice provider: qwen-omni-ws
检查:
- Go 服务是否启动
- 前端
VITE_API_BASE是否指向/api - 浏览器是否允许 WebSocket
- 代理或防火墙是否拦截本地端口
检查:
- 浏览器是否授权麦克风
- 页面是否运行在
localhost/127.0.0.1或 HTTPS - 系统麦克风输入设备是否正确
- 浏览器是否支持
getUserMedia和AudioContext
检查后端日志:
coach provider: ... request failed ... fallback=rule可能原因:
- API Key 缺失或无效
- Base URL 配置错误
- 模型名不支持
- 网络超时
- Provider 返回格式不符合结构化 JSON
检查:
TENCENT_TRANSLATE_SECRET_IDTENCENT_TRANSLATE_SECRET_KEYTENCENT_TRANSLATE_REGION
未配置时会自动使用 quick-local 兜底,并在日志中打印:
translation provider: tencent-tmt not configured fallback=quick-local当前项目是本地优先 MVP,默认数据存储在本机:
- 场景数据:
data/scenarios - 会话数据:
data/sessions/sessions.db - 旧 JSON 数据:
data/sessions/sessions.json
设计原则:
- 长期 API Key 只在 Go 后端读取。
- 前端不使用
VITE_*暴露模型或语音密钥。 - 练习报告、错题和表达库保存在本地 SQLite。
- 语音 Provider 连接远端模型时,只转发实现语音对话所需的数据。
- 分享报告和云端账号体系不在当前 MVP 范围内。
| 阶段 | 状态 | 说明 |
|---|---|---|
| 场景选择和基础练习 | 已完成 | 场景库、练习房间、历史记录 |
| WebSocket 实时通道 | 已完成 | 实时提交、流式回复、断线重连 |
| SQLite 存储 | 已完成 | 本地会话持久化 |
| 大模型教练 | 已完成 | OpenAI-compatible Provider,规则 fallback |
| Qwen Omni 语音 | 已完成 | WebSocket Realtime 语音输入输出 |
| 开口辅助和纠错时机 | 已完成 | hint、simplify、idea、retry、逐轮/课后反馈 |
| 结构化课后报告 | 已完成 | 雷达图、能力详情、翻译、报告生成进度 |
| 错题库和表达库 | 已完成 | 收藏、去重、学习库页面 |
| 目标诊断和成长页 | 已完成 | 今日计划、趋势、常错点 |
| MVP 演示收口 | 进行中 | 本地化脚本、质量观测、额度模拟、隐私说明 |
暂缓范围:
- 真实支付、订单和会员系统
- 复杂运营后台
- 社区、排行榜、同伴练习
- 真人教师体系
- 儿童版本
- 官方考试分数认证
英客 EToker 的核心不是单点 AI 能力,而是一条可解释、可复盘、可持续的口语学习链路:
flowchart LR
Speak[开口说英语] --> Coach[AI 角色对话]
Coach --> Correct[纠错与表达升级]
Correct --> Report[课后能力报告]
Report --> Library[错题和表达沉淀]
Library --> Plan[下一次训练计划]
Plan --> Speak
这套架构让产品既能在没有云端模型时完成基础演示,也能在接入真实模型和语音服务后升级为更自然、更低延迟、更个性化的 AI 英语口语陪练。