🎨 docs: 更新部署文档和口语页面样式
This commit is contained in:
parent
c8229ced26
commit
00f4447bc9
52
DEPLOY.md
52
DEPLOY.md
|
|
@ -39,12 +39,13 @@ npm install
|
|||
|
||||
| 配置项 | 说明 |
|
||||
|--------|------|
|
||||
| `GRS_API_KEY` | GRS AI 接口密钥(作文批改 / 试题分析) |
|
||||
| `GRS_API_KEY` | GRS AI 接口密钥(作文批改) |
|
||||
| `DOUBAO_KEY` | 豆包 API 密钥 |
|
||||
| `DOUBAO_APP_ID` | 豆包语音 App ID |
|
||||
| `DOUBAO_ACCESS_TOKEN` | 豆包语音访问令牌 |
|
||||
| `DOUBAO_RESOURCE_ID` | 豆包 TTS 资源 ID |
|
||||
| `DOUBAO_ASR_RESOURCE_ID` | 豆包 ASR 资源 ID |
|
||||
| `ARK_API_KEY` | 火山引擎 Ark 大模型密钥 |
|
||||
| `ARK_API_KEY` | 火山引擎 Ark 大模型密钥(口语对话) |
|
||||
| `ARK_MODEL` | Ark 模型名称(如 doubao-pro-4k) |
|
||||
|
||||
### 2.2 跨域响应头要求(重要)
|
||||
|
|
@ -321,14 +322,14 @@ DOUBAO_APP_ID=xxx DOUBAO_ACCESS_TOKEN=xxx node server.js
|
|||
|
||||
| 验证项 | 检查方法 |
|
||||
|--------|----------|
|
||||
| 页面正常加载 | 访问首页,确认 7 个功能卡片显示正常 |
|
||||
| 页面正常加载 | 访问首页,确认 6 个功能卡片显示正常 |
|
||||
| 路由刷新不 404 | 直接访问 `/pronunciation`、`/speaking` 等子路由 |
|
||||
| COOP/COEP 响应头 | 浏览器 DevTools → Network → 查看 index.html 响应头 |
|
||||
| TTS 语音合成 | 进入发音练习页,测试语音播放 |
|
||||
| ASR 语音识别 | 进入口语对话页,测试麦克风录音识别 |
|
||||
| 作文批改 | 进入作文批改页,提交一段英文作文 |
|
||||
| 视频讲解(FFmpeg) | 进入视频讲解页,上传视频文件测试 |
|
||||
| HTTPS 证书 | 确认浏览器地址栏显示锁形图标 |
|
||||
| 试题分析 | 进入试题分析页,上传图片或输入题目 |
|
||||
| 单词拼写练习 | 进入单词拼写页,测试拼写检测功能 |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -366,7 +367,35 @@ NODE_OPTIONS=--max-old-space-size=4096 npm run build
|
|||
|
||||
---
|
||||
|
||||
## 七、目录结构参考
|
||||
## 七、页面路由清单
|
||||
|
||||
| 路由路径 | 页面名称 | 功能说明 |
|
||||
|----------|----------|----------|
|
||||
| `/` | 首页 | 功能导航卡片 |
|
||||
| `/pronunciation` | 发音练习 | TTS 语音合成,音标学习 |
|
||||
| `/speaking` | 口语对话 | ASR 语音识别,AI 对话 |
|
||||
| `/essay-correction` | 作文批改 | 图片/文本作文批改 |
|
||||
| `/exam-analysis` | 试题分析 | 图片题目分析解答 |
|
||||
| `/spell-practice` | 单词拼写 | 单词拼写练习检测 |
|
||||
|
||||
---
|
||||
|
||||
## 八、依赖版本参考
|
||||
|
||||
| 依赖包 | 版本 |
|
||||
|--------|------|
|
||||
| vue | ^3.5.30 |
|
||||
| vue-router | ^5.0.3 |
|
||||
| vite | ^8.0.0 |
|
||||
| @ffmpeg/ffmpeg | ^0.12.15 |
|
||||
| @ffmpeg/util | ^0.12.2 |
|
||||
| axios | ^1.13.6 |
|
||||
| marked | ^17.0.5 |
|
||||
| pako | ^2.1.0 |
|
||||
|
||||
---
|
||||
|
||||
## 九、目录结构参考
|
||||
|
||||
```
|
||||
AI_Demo/
|
||||
|
|
@ -374,7 +403,16 @@ AI_Demo/
|
|||
├── src/
|
||||
│ ├── config/index.js # API 密钥配置(部署前确认)
|
||||
│ ├── router/index.js # 路由定义
|
||||
│ └── views/ # 页面组件
|
||||
│ ├── views/ # 页面组件
|
||||
│ │ ├── HomePage.vue # 首页
|
||||
│ │ ├── Pronunciation.vue # 发音练习
|
||||
│ │ ├── Speaking.vue # 口语对话
|
||||
│ │ ├── EssayCorrection.vue # 作文批改
|
||||
│ │ ├── ExamAnalysis.vue # 试题分析
|
||||
│ │ └── SpellPractice.vue # 单词拼写
|
||||
│ ├── components/ # 公共组件
|
||||
│ ├── assets/ # 静态资源
|
||||
│ └── MainLayout.vue # 布局组件
|
||||
├── vite.config.js # 开发代理配置(生产环境需在服务器复现)
|
||||
├── package.json
|
||||
└── DEPLOY.md # 本文档
|
||||
|
|
|
|||
|
|
@ -189,7 +189,8 @@ const scrollToBottom = async () => {
|
|||
};
|
||||
|
||||
// TTS 合成并播放
|
||||
const synthesizeAndPlay = async (text, msgId) => {
|
||||
// autoPlay: 是否自动播放,默认为 true
|
||||
const synthesizeAndPlay = async (text, msgId, autoPlay = true) => {
|
||||
const payload = {
|
||||
user: { uid: "speaking_" + Date.now() },
|
||||
req_params: {
|
||||
|
|
@ -264,7 +265,9 @@ const synthesizeAndPlay = async (text, msgId) => {
|
|||
const msg = messages.value.find((m) => m.id === msgId);
|
||||
if (msg) msg.audioUrl = url;
|
||||
|
||||
if (autoPlay) {
|
||||
playAudio(url, msgId);
|
||||
}
|
||||
};
|
||||
|
||||
// 播放音频
|
||||
|
|
@ -345,8 +348,8 @@ const sendMessage = async () => {
|
|||
messages.value.push(userMsg);
|
||||
await scrollToBottom();
|
||||
|
||||
// 用户消息 TTS(不阻塞主流程)
|
||||
synthesizeAndPlay(text, userMsgId).catch((e) => console.error("User TTS error:", e));
|
||||
// 用户消息 TTS(不阻塞主流程,不自动播放)
|
||||
synthesizeAndPlay(text, userMsgId, false).catch((e) => console.error("User TTS error:", e));
|
||||
|
||||
// 插入 AI loading 占位
|
||||
const aiMsgId = ++msgIdCounter;
|
||||
|
|
@ -643,8 +646,17 @@ const startRecording = async () => {
|
|||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
if (currentAudioInstance) { currentAudioInstance.pause(); currentAudioInstance = null; }
|
||||
// 停止音频播放
|
||||
if (currentAudioInstance) {
|
||||
currentAudioInstance.pause();
|
||||
currentAudioInstance = null;
|
||||
}
|
||||
// 重置所有消息的播放状态
|
||||
messages.value.forEach((m) => (m.isPlaying = false));
|
||||
// 释放 blob URL
|
||||
blobUrls.forEach((url) => URL.revokeObjectURL(url));
|
||||
blobUrls.length = 0;
|
||||
// 停止录音
|
||||
stopRecording();
|
||||
});
|
||||
</script>
|
||||
|
|
@ -703,8 +715,6 @@ onUnmounted(() => {
|
|||
<span></span><span></span><span></span>
|
||||
</div>
|
||||
<span v-else>{{ msg.content }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 消息播放按钮(AI 和用户消息均显示) -->
|
||||
<div v-if="!msg.isLoading && msg.content" class="audio-controls" :class="{ 'user-audio-controls': msg.role === 'user' }">
|
||||
<span v-if="!msg.audioUrl" class="tts-hint">合成中...</span>
|
||||
|
|
@ -720,6 +730,7 @@ onUnmounted(() => {
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户头像 -->
|
||||
<div v-if="msg.role === 'user'" class="avatar user-avatar">🧑💻</div>
|
||||
|
|
@ -901,7 +912,11 @@ onUnmounted(() => {
|
|||
align-items: flex-end;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.message-row.user { flex-direction: row-reverse; }
|
||||
.message-row.user {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.message-row.user .avatar { order: 2; }
|
||||
.message-row.user .bubble-wrap { order: 1; }
|
||||
|
||||
.avatar {
|
||||
width: 38px;
|
||||
|
|
@ -914,24 +929,27 @@ onUnmounted(() => {
|
|||
justify-content: center;
|
||||
font-size: 1.2rem;
|
||||
flex-shrink: 0;
|
||||
order: 0;
|
||||
}
|
||||
.user-avatar { background: rgba(139,92,246,0.15); border-color: rgba(139,92,246,0.3); }
|
||||
|
||||
.bubble-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
max-width: 70%;
|
||||
order: 1;
|
||||
}
|
||||
.message-row.user .bubble-wrap { align-items: flex-end; }
|
||||
|
||||
.bubble {
|
||||
padding: 0.875rem 1.125rem;
|
||||
padding-bottom: 2.25rem;
|
||||
border-radius: 18px;
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
word-break: break-word;
|
||||
animation: bubbleIn 0.25s ease-out;
|
||||
position: relative;
|
||||
}
|
||||
.bubble.assistant {
|
||||
background: var(--card-bg);
|
||||
|
|
@ -972,6 +990,9 @@ onUnmounted(() => {
|
|||
|
||||
/* Audio Controls */
|
||||
.audio-controls {
|
||||
position: absolute;
|
||||
bottom: 0.5rem;
|
||||
right: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
|
|
|||
Loading…
Reference in New Issue