364 lines
10 KiB
Markdown
364 lines
10 KiB
Markdown
# 新聊天 UI 集成指南
|
||
|
||
## 📦 已创建的文件
|
||
|
||
### Model(数据模型)
|
||
- `KBChatMessage.h/m` - 消息模型(支持用户/AI/时间戳三种类型)
|
||
|
||
### View(视图组件)
|
||
- `KBChatUserMessageCell.h/m` - 用户消息 Cell(右侧气泡)
|
||
- `KBChatAssistantMessageCell.h/m` - AI 消息 Cell(左侧气泡 + 语音按钮)
|
||
- `KBChatTimeCell.h/m` - 时间戳 Cell(居中显示)
|
||
- `KBChatTableView.h/m` - 聊天列表视图(主容器)
|
||
|
||
### ViewController(测试页面)
|
||
- `KBChatTestVC.h/m` - 测试页面(可选,用于演示)
|
||
|
||
### 文档
|
||
- `KBChatTableView_Usage.md` - 使用说明
|
||
- `集成指南.md` - 本文档
|
||
|
||
---
|
||
|
||
## 🎯 核心特性
|
||
|
||
✅ **三种消息类型**
|
||
- 用户消息:右侧浅色气泡
|
||
- AI 消息:左侧深色气泡 + 语音播放按钮
|
||
- 时间戳:居中显示,自动插入(5 分钟间隔)
|
||
|
||
✅ **语音播放**
|
||
- 点击播放/暂停
|
||
- 显示语音时长(如 "6"")
|
||
- 播放状态图标切换
|
||
|
||
✅ **打字机效果**
|
||
- 支持实时更新 AI 消息文本
|
||
- 流式显示
|
||
|
||
✅ **自动滚动**
|
||
- 新消息自动滚动到底部
|
||
- 平滑动画
|
||
|
||
---
|
||
|
||
## 🔧 集成到 KBAiMainVC
|
||
|
||
### 步骤 1:修改 import
|
||
|
||
在 `KBAiMainVC.m` 顶部添加:
|
||
|
||
```objective-c
|
||
#import "KBChatTableView.h"
|
||
```
|
||
|
||
### 步骤 2:修改属性声明
|
||
|
||
将:
|
||
```objective-c
|
||
@property (nonatomic, strong) KBAiChatView *chatView;
|
||
```
|
||
|
||
改为:
|
||
```objective-c
|
||
@property (nonatomic, strong) KBChatTableView *chatView;
|
||
```
|
||
|
||
### 步骤 3:修改 setupUI 方法
|
||
|
||
将:
|
||
```objective-c
|
||
self.chatView = [[KBAiChatView alloc] init];
|
||
```
|
||
|
||
改为:
|
||
```objective-c
|
||
self.chatView = [[KBChatTableView alloc] init];
|
||
```
|
||
|
||
### 步骤 4:修改消息添加逻辑
|
||
|
||
#### 添加用户消息(保持不变)
|
||
```objective-c
|
||
[self.chatView addUserMessage:finalText];
|
||
```
|
||
|
||
#### 添加 AI 消息(需要修改)
|
||
|
||
**原来的代码:**
|
||
```objective-c
|
||
[self.chatView addAssistantMessage:polishedText];
|
||
[self.chatView markLastAssistantMessageComplete];
|
||
```
|
||
|
||
**新的代码:**
|
||
```objective-c
|
||
// 计算音频时长(如果有音频数据)
|
||
NSTimeInterval duration = 0;
|
||
if (audioData && audioData.length > 0) {
|
||
// 方法 1:从 AVAudioPlayer 获取准确时长
|
||
NSError *error = nil;
|
||
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithData:audioData error:&error];
|
||
if (!error && player) {
|
||
duration = player.duration;
|
||
}
|
||
|
||
// 方法 2:估算时长(如果知道采样率)
|
||
// duration = audioData.length / (sampleRate * channels * bytesPerSample);
|
||
}
|
||
|
||
[self.chatView addAssistantMessage:polishedText
|
||
audioDuration:duration
|
||
audioData:audioData];
|
||
```
|
||
|
||
### 步骤 5:修改打字机效果(如果使用)
|
||
|
||
**原来的代码:**
|
||
```objective-c
|
||
[self.chatView addAssistantMessage:@""];
|
||
[self.chatView updateLastAssistantMessage:token];
|
||
[self.chatView markLastAssistantMessageComplete];
|
||
```
|
||
|
||
**新的代码:**
|
||
```objective-c
|
||
// 1. 添加空消息占位
|
||
[self.chatView addAssistantMessage:@""
|
||
audioDuration:0
|
||
audioData:nil];
|
||
|
||
// 2. 逐步更新
|
||
[self.chatView updateLastAssistantMessage:token];
|
||
|
||
// 3. 完成后标记并添加音频
|
||
[self.chatView markLastAssistantMessageComplete];
|
||
|
||
// 如果有音频,需要更新最后一条消息的音频数据
|
||
// 注意:当前实现不支持后续添加音频,建议在完成时重新添加消息
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 完整示例:修改 deepgramStreamingManagerDidReceiveFinalTranscript
|
||
|
||
```objective-c
|
||
- (void)deepgramStreamingManagerDidReceiveFinalTranscript:(NSString *)text {
|
||
if (text.length > 0) {
|
||
if (self.deepgramFullText.length > 0) {
|
||
[self.deepgramFullText appendString:@" "];
|
||
}
|
||
[self.deepgramFullText appendString:text];
|
||
}
|
||
|
||
self.transcriptLabel.text = self.deepgramFullText;
|
||
self.statusLabel.text = @"识别完成";
|
||
self.recordButton.state = KBAiRecordButtonStateNormal;
|
||
|
||
NSString *finalText = [self.deepgramFullText copy];
|
||
if (finalText.length == 0) {
|
||
return;
|
||
}
|
||
|
||
// 添加用户消息
|
||
[self.chatView addUserMessage:finalText];
|
||
|
||
if (self.elevenLabsApiKey.length == 0 || self.elevenLabsVoiceId.length == 0) {
|
||
[KBHUD showError:@"请先配置 ElevenLabs API Key/VoiceId"];
|
||
return;
|
||
}
|
||
|
||
__weak typeof(self) weakSelf = self;
|
||
[KBHUD showWithStatus:@"润色中..."];
|
||
|
||
[self.aiVM requestChatMessageWithContent:finalText
|
||
completion:^(KBAiMessageResponse *_Nullable response,
|
||
NSError *_Nullable error) {
|
||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||
if (!strongSelf) return;
|
||
|
||
dispatch_async(dispatch_get_main_queue(), ^{
|
||
if (error) {
|
||
[KBHUD dismiss];
|
||
[KBHUD showError:error.localizedDescription ?: @"润色失败"];
|
||
return;
|
||
}
|
||
|
||
NSString *polishedText = response.data.content ?: response.data.text ?: response.data.message ?: @"";
|
||
|
||
if (polishedText.length == 0) {
|
||
[KBHUD dismiss];
|
||
[KBHUD showError:@"润色结果为空"];
|
||
return;
|
||
}
|
||
|
||
[KBHUD showWithStatus:@"生成语音..."];
|
||
|
||
[strongSelf.aiVM requestElevenLabsSpeechWithText:polishedText
|
||
voiceId:strongSelf.elevenLabsVoiceId
|
||
apiKey:strongSelf.elevenLabsApiKey
|
||
outputFormat:nil
|
||
modelId:nil
|
||
completion:^(NSData *_Nullable audioData,
|
||
NSError *_Nullable ttsError) {
|
||
dispatch_async(dispatch_get_main_queue(), ^{
|
||
[KBHUD dismiss];
|
||
|
||
if (ttsError) {
|
||
[KBHUD showError:ttsError.localizedDescription ?: @"语音生成失败"];
|
||
// 即使语音失败,也添加文本消息
|
||
[strongSelf.chatView addAssistantMessage:polishedText
|
||
audioDuration:0
|
||
audioData:nil];
|
||
return;
|
||
}
|
||
|
||
// 计算音频时长
|
||
NSTimeInterval duration = 0;
|
||
if (audioData && audioData.length > 0) {
|
||
NSError *playerError = nil;
|
||
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithData:audioData
|
||
error:&playerError];
|
||
if (!playerError && player) {
|
||
duration = player.duration;
|
||
}
|
||
}
|
||
|
||
// 添加 AI 消息(带语音)
|
||
[strongSelf.chatView addAssistantMessage:polishedText
|
||
audioDuration:duration
|
||
audioData:audioData];
|
||
});
|
||
}];
|
||
});
|
||
}];
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🧪 测试步骤
|
||
|
||
### 1. 测试新 UI(使用测试页面)
|
||
|
||
在任意地方跳转到测试页面:
|
||
|
||
```objective-c
|
||
KBChatTestVC *testVC = [[KBChatTestVC alloc] init];
|
||
[self.navigationController pushViewController:testVC animated:YES];
|
||
```
|
||
|
||
### 2. 集成到 KBAiMainVC
|
||
|
||
按照上面的步骤修改 `KBAiMainVC.m`
|
||
|
||
### 3. 验证功能
|
||
|
||
- ✅ 用户消息显示在右侧
|
||
- ✅ AI 消息显示在左侧
|
||
- ✅ 时间戳自动插入
|
||
- ✅ 语音按钮可点击播放
|
||
- ✅ 语音时长正确显示
|
||
- ✅ 消息自动滚动到底部
|
||
|
||
---
|
||
|
||
## 🎨 自定义样式
|
||
|
||
### 修改气泡颜色
|
||
|
||
**用户消息**(`KBChatUserMessageCell.m`):
|
||
```objective-c
|
||
self.bubbleView.backgroundColor = [UIColor colorWithRed:0.94 green:0.94 blue:0.94 alpha:1.0];
|
||
self.messageLabel.textColor = [UIColor blackColor];
|
||
```
|
||
|
||
**AI 消息**(`KBChatAssistantMessageCell.m`):
|
||
```objective-c
|
||
self.bubbleView.backgroundColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:0.7];
|
||
self.messageLabel.textColor = [UIColor whiteColor];
|
||
```
|
||
|
||
### 修改时间戳间隔
|
||
|
||
在 `KBChatTableView.m` 中:
|
||
```objective-c
|
||
static const NSTimeInterval kTimestampInterval = 5 * 60; // 改为你想要的秒数
|
||
```
|
||
|
||
### 修改气泡圆角
|
||
|
||
在对应的 Cell 中:
|
||
```objective-c
|
||
self.bubbleView.layer.cornerRadius = 16; // 改为你想要的值
|
||
```
|
||
|
||
---
|
||
|
||
## ⚠️ 注意事项
|
||
|
||
1. **音频格式**:确保 `audioData` 是 AVAudioPlayer 支持的格式(MP3、AAC、M4A 等)
|
||
|
||
2. **音频会话**:播放音频前确保配置了 AVAudioSession(已在 `AudioSessionManager` 中处理)
|
||
|
||
3. **内存管理**:如果消息量很大,考虑:
|
||
- 限制消息数量(如只保留最近 100 条)
|
||
- 清除旧消息的音频数据
|
||
- 实现分页加载
|
||
|
||
4. **线程安全**:所有 UI 更新必须在主线程执行
|
||
|
||
5. **音频时长计算**:
|
||
- 方法 1:使用 AVAudioPlayer 获取准确时长(推荐)
|
||
- 方法 2:根据采样率估算(不够准确)
|
||
|
||
---
|
||
|
||
## 🐛 常见问题
|
||
|
||
### Q1: 语音按钮不显示?
|
||
**A:** 检查 `audioData` 是否为 nil 或长度为 0
|
||
|
||
### Q2: 点击语音按钮没反应?
|
||
**A:** 检查音频数据格式是否正确,查看控制台日志
|
||
|
||
### Q3: 时间戳不显示?
|
||
**A:** 检查消息的 `timestamp` 是否正确设置
|
||
|
||
### Q4: 消息不自动滚动?
|
||
**A:** 确保在主线程调用 `scrollToBottom`
|
||
|
||
### Q5: 气泡宽度不对?
|
||
**A:** 检查 Masonry 约束,确保 `multipliedBy(0.75)` 生效
|
||
|
||
---
|
||
|
||
## 📚 相关文件
|
||
|
||
- 使用说明:`KBChatTableView_Usage.md`
|
||
- 测试页面:`KBChatTestVC.h/m`
|
||
- 原有实现:`KBAiChatView.h/m`(可保留作为备份)
|
||
|
||
---
|
||
|
||
## ✅ 完成清单
|
||
|
||
- [ ] 创建所有新文件
|
||
- [ ] 修改 `KBAiMainVC.m` 的 import
|
||
- [ ] 修改属性声明
|
||
- [ ] 修改 setupUI 方法
|
||
- [ ] 修改消息添加逻辑
|
||
- [ ] 测试用户消息显示
|
||
- [ ] 测试 AI 消息显示
|
||
- [ ] 测试语音播放功能
|
||
- [ ] 测试时间戳显示
|
||
- [ ] 测试打字机效果
|
||
- [ ] 自定义样式(可选)
|
||
- [ ] 删除旧的 `KBAiChatView`(可选)
|
||
|
||
---
|
||
|
||
## 🎉 完成!
|
||
|
||
按照以上步骤完成集成后,你将拥有一个功能完整、美观的聊天 UI 界面!
|