Files
keyboard/keyBoard/Class/AiTalk/新音频流程说明.md
2026-01-23 21:51:37 +08:00

345 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 新音频流程说明
## 📋 需求概述
1. **删除 ElevenLabs 接口**:不再使用 `requestElevenLabsSpeechWithText`
2. **新的流程**
- 请求 `chat/message` 接口,后端返回 `audioId`
- 用户点击语音按钮时,用 `audioId` 请求 `/chat/audio/{audioId}` 获取 MP3 地址
- 如果音频还未生成显示等待效果3秒后自动停止
---
## 🔄 新的流程图
```
用户说话
语音识别Deepgram
添加用户消息到聊天列表
请求 /chat/message 接口
后端返回:
{
"code": 0,
"message": "ok",
"data": {
"aiResponse": "AI 回复文本",
"audioId": "6e3e90575ce04658ab6c45d77a506100",
"llmDuration": 1572
}
}
添加 AI 消息到聊天列表(带 audioId
用户点击语音按钮
请求 /chat/audio/{audioId} 接口
后端返回:
{
"code": 0,
"data": {
"url": "http://example.com/audio.mp3"
}
}
下载音频文件
播放音频
```
---
## 📝 修改清单
### 1. AiVM.h/m网络层
#### 新增字段
```objective-c
@interface KBAiMessageData : NSObject
@property(nonatomic, copy, nullable) NSString *aiResponse; // 新增
@property(nonatomic, copy, nullable) NSString *audioId; // 新增
@property(nonatomic, assign) NSInteger llmDuration; // 新增
@end
```
#### 新增接口
```objective-c
/// 根据 audioId 获取音频 URL
- (void)requestAudioWithAudioId:(NSString *)audioId
completion:(AiVMAudioURLCompletion)completion;
```
#### 删除接口
```objective-c
// ❌ 已删除
- (void)requestElevenLabsSpeechWithText:...
```
---
### 2. KBChatMessage.h/m消息模型
#### 新增字段
```objective-c
/// 音频 ID - 用于异步加载音频
@property (nonatomic, copy, nullable) NSString *audioId;
```
#### 新增构造方法
```objective-c
/// 创建 AI 消息(带 audioId异步加载音频
+ (instancetype)assistantMessageWithText:(NSString *)text
audioId:(nullable NSString *)audioId;
```
---
### 3. KBChatTableView.h/m聊天视图
#### 新增 API
```objective-c
/// 添加 AI 消息(带 audioId异步加载音频
- (void)addAssistantMessage:(NSString *)text
audioId:(nullable NSString *)audioId;
```
#### 新增功能
- **异步加载音频**:点击语音按钮时,如果有 `audioId`,则请求音频 URL
- **等待效果**加载音频时显示播放中状态3秒后自动停止
- **音频缓存**:下载后的音频数据缓存到消息对象,下次点击直接播放
#### 新增方法
```objective-c
- (void)loadAndPlayAudioForMessage:(KBChatMessage *)message atIndexPath:(NSIndexPath *)indexPath;
- (void)downloadAndPlayAudioFromURL:(NSString *)urlString forMessage:(KBChatMessage *)message atIndexPath:(NSIndexPath *)indexPath;
- (void)startWaitingForCell:(NSIndexPath *)indexPath;
- (void)stopWaitingForCell:(NSIndexPath *)indexPath;
- (void)waitingTimeout;
```
---
### 4. KBAiMainVC.m主控制器
#### 删除的代码
```objective-c
// ❌ 已删除
@property(nonatomic, copy) NSString *elevenLabsApiKey;
@property(nonatomic, copy) NSString *elevenLabsVoiceId;
// ❌ 已删除
self.elevenLabsVoiceId = @"...";
self.elevenLabsApiKey = @"...";
// ❌ 已删除 ElevenLabs 相关的所有调用
```
#### 修改的流程
```objective-c
// 原来:
// 1. 请求 chat/message
// 2. 请求 ElevenLabs TTS
// 3. 添加消息(带音频数据)
// 4. 播放音频
// 现在:
// 1. 请求 chat/message返回 audioId
// 2. 添加消息(带 audioId
// 3. 用户点击语音按钮时异步加载音频
```
---
## 🎯 核心实现
### 1. 点击语音按钮的处理逻辑
```objective-c
- (void)assistantMessageCell:(KBChatAssistantMessageCell *)cell
didTapVoiceButtonForMessage:(KBChatMessage *)message {
// 如果有 audioData直接播放
if (message.audioData && message.audioData.length > 0) {
[self playAudioForMessage:message atIndexPath:indexPath];
return;
}
// 如果有 audioId异步加载音频
if (message.audioId.length > 0) {
[self loadAndPlayAudioForMessage:message atIndexPath:indexPath];
return;
}
}
```
---
### 2. 异步加载音频
```objective-c
- (void)loadAndPlayAudioForMessage:(KBChatMessage *)message atIndexPath:(NSIndexPath *)indexPath {
// 1. 开始等待效果(显示播放中状态)
[self startWaitingForCell:indexPath];
// 2. 请求音频 URL
[self.aiVM requestAudioWithAudioId:message.audioId
completion:^(NSString *audioURL, NSError *error) {
// 3. 停止等待效果
[self stopWaitingForCell:indexPath];
if (error) {
NSLog(@"加载音频失败");
return;
}
// 4. 下载音频数据
[self downloadAndPlayAudioFromURL:audioURL
forMessage:message
atIndexPath:indexPath];
}];
}
```
---
### 3. 等待效果3秒超时
```objective-c
- (void)startWaitingForCell:(NSIndexPath *)indexPath {
self.waitingCellIndexPath = indexPath;
// 更新 Cell 为等待状态(显示播放中图标)
KBChatAssistantMessageCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
[cell updateVoicePlayingState:YES];
// 3 秒后自动停止
self.waitingTimer = [NSTimer scheduledTimerWithTimeInterval:3.0
target:self
selector:@selector(waitingTimeout)
userInfo:nil
repeats:NO];
}
- (void)waitingTimeout {
NSLog(@"音频加载超时");
[self stopWaitingForCell:self.waitingCellIndexPath];
}
```
---
### 4. 音频缓存
```objective-c
- (void)downloadAndPlayAudioFromURL:(NSString *)urlString
forMessage:(KBChatMessage *)message
atIndexPath:(NSIndexPath *)indexPath {
// 下载音频
NSURLSessionDataTask *task = [session dataTaskWithURL:url
completionHandler:^(NSData *data, ...) {
// 缓存到消息对象
message.audioData = data;
// 计算时长
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithData:data error:nil];
message.audioDuration = player.duration;
// 刷新 Cell
[self.tableView reloadRowsAtIndexPaths:@[indexPath] ...];
// 播放音频
[self playAudioForMessage:message atIndexPath:indexPath];
}];
[task resume];
}
```
---
## 📊 数据结构
### chat/message 接口返回
```json
{
"code": 0,
"message": "ok",
"data": {
"aiResponse": "Ugh, seriously? It's Tiffany...",
"audioId": "6e3e90575ce04658ab6c45d77a506100",
"llmDuration": 1572
}
}
```
### /chat/audio/{audioId} 接口返回
```json
{
"code": 0,
"data": {
"url": "http://127.0.0.1:4523/m1/7401033-7133645-default/chat/audio/1"
}
}
```
---
## ✅ 优势
1. **后端统一处理 TTS**:不需要前端配置 ElevenLabs API Key
2. **异步加载**:不阻塞 UI用户体验更好
3. **音频缓存**:下载后缓存,再次点击直接播放
4. **等待效果**3秒超时保护避免无限等待
5. **降低耦合**:前端不需要关心 TTS 实现细节
---
## 🧪 测试清单
- [ ] 请求 chat/message 接口成功
- [ ] 返回的 audioId 正确保存
- [ ] 点击语音按钮触发异步加载
- [ ] 等待效果正常显示(播放中图标)
- [ ] 3秒后自动停止等待
- [ ] 音频 URL 请求成功
- [ ] 音频下载成功
- [ ] 音频播放正常
- [ ] 再次点击直接播放(缓存生效)
- [ ] 错误处理正常网络失败、URL 无效等)
---
## 🔧 调试建议
### 1. 查看日志
```objective-c
NSLog(@"[KBChatTableView] 加载音频失败: %@", error);
NSLog(@"[KBChatTableView] 音频 URL: %@", audioURL);
NSLog(@"[KBChatTableView] 音频加载超时");
```
### 2. 检查返回数据
- 确认 `audioId` 不为空
- 确认 `/chat/audio/{audioId}` 返回的 URL 格式正确
- 确认 URL 可以正常访问
### 3. 测试超时
- 故意延迟后端响应,测试 3秒超时是否生效
---
## 🎉 完成!
新的音频流程已经完全实现,具备以下特性:
✅ 后端统一处理 TTS
✅ 异步加载音频
✅ 等待效果3秒超时
✅ 音频缓存
✅ 错误处理
运行项目即可测试新流程!