Files
keyboard/keyBoard/Class/AiTalk/M/聊天记录Model说明.md
2026-01-26 20:36:51 +08:00

426 lines
10 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.

# 聊天记录 Model 说明
## 📦 Model 结构
### 1. KBChatHistoryModel聊天记录模型
```objc
@interface KBChatHistoryModel : NSObject
/// 消息 ID
@property (nonatomic, assign) NSInteger messageId;
/// 发送者0-用户1-AI
@property (nonatomic, assign) KBChatSender sender;
/// 消息内容
@property (nonatomic, copy) NSString *content;
/// 创建时间
@property (nonatomic, copy) NSString *createdAt;
/// 是否是用户消息
@property (nonatomic, assign, readonly) BOOL isUserMessage;
/// 是否是 AI 消息
@property (nonatomic, assign, readonly) BOOL isAssistantMessage;
@end
```
### 2. KBChatHistoryPageModel分页数据模型
```objc
@interface KBChatHistoryPageModel : NSObject
/// 聊天记录列表
@property (nonatomic, strong) NSArray<KBChatHistoryModel *> *records;
/// 总记录数
@property (nonatomic, assign) NSInteger total;
/// 每页大小
@property (nonatomic, assign) NSInteger size;
/// 当前页码
@property (nonatomic, assign) NSInteger current;
/// 总页数
@property (nonatomic, assign) NSInteger pages;
/// 是否还有更多数据
@property (nonatomic, assign, readonly) BOOL hasMore;
@end
```
---
## 🌐 接口说明
### 接口地址
```
POST /chat/history
```
### 请求参数
```json
{
"companionId": 0, // AI 陪聊角色 ID使用 KBPersonaModel.personaId
"pageNum": 1, // 页码
"pageSize": 20 // 每页大小
}
```
### 参数说明
- **companionId**:使用 `fetchPersonasWithPageNum` 接口返回的 `KBPersonaModel.personaId`
- **pageNum**:页码,从 1 开始
- **pageSize**:每页大小,建议 20
### 响应格式
```json
{
"code": 0,
"message": "success",
"data": {
"records": [
{
"id": 1,
"sender": 1, // 1-用户右侧2-AI左侧
"content": "你好",
"createdAt": "2026-01-26 10:00:00"
},
{
"id": 2,
"sender": 2,
"content": "你好!有什么可以帮助你的吗?",
"createdAt": "2026-01-26 10:00:05"
}
],
"total": 100,
"current": 1,
"pages": 5
}
}
```
### sender 字段说明
- **sender = 1**:用户消息(显示在右侧)
- **sender = 2**AI 消息(显示在左侧)
---
## 📝 使用示例
### 1. 在 AiVM 中调用接口
```objc
#import "AiVM.h"
AiVM *aiVM = [[AiVM alloc] init];
[aiVM fetchChatHistoryWithCompanionId:123
pageNum:1
pageSize:20
completion:^(KBChatHistoryPageModel *pageModel, NSError *error) {
if (error) {
NSLog(@"加载失败:%@", error.localizedDescription);
return;
}
NSLog(@"加载成功:%ld 条消息", pageModel.records.count);
for (KBChatHistoryModel *message in pageModel.records) {
if (message.isUserMessage) {
NSLog(@"用户:%@", message.content);
} else {
NSLog(@"AI%@", message.content);
}
}
if (pageModel.hasMore) {
NSLog(@"还有更多数据");
}
}];
```
### 2. 在 VC 中使用
```objc
@interface YourViewController ()
@property (nonatomic, strong) AiVM *aiVM;
@property (nonatomic, strong) NSMutableArray<KBChatHistoryModel *> *messages;
@property (nonatomic, assign) NSInteger currentPage;
@property (nonatomic, assign) BOOL hasMore;
@end
@implementation YourViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.aiVM = [[AiVM alloc] init];
self.messages = [NSMutableArray array];
self.currentPage = 1;
self.hasMore = YES;
[self loadChatHistory];
}
- (void)loadChatHistory {
NSInteger companionId = 123; // 当前人设 ID
__weak typeof(self) weakSelf = self;
[self.aiVM fetchChatHistoryWithCompanionId:companionId
pageNum:self.currentPage
pageSize:20
completion:^(KBChatHistoryPageModel *pageModel, NSError *error) {
if (error) {
NSLog(@"加载失败:%@", error.localizedDescription);
return;
}
// 追加数据
[weakSelf.messages addObjectsFromArray:pageModel.records];
weakSelf.hasMore = pageModel.hasMore;
// 刷新 UI
[weakSelf.tableView reloadData];
}];
}
- (void)loadMoreHistory {
if (!self.hasMore) {
return;
}
self.currentPage++;
[self loadChatHistory];
}
@end
```
### 3. 转换为 KBAiChatMessage
```objc
// 将 KBChatHistoryModel 转换为 KBAiChatMessage用于聊天界面展示
- (KBAiChatMessage *)convertToAiChatMessage:(KBChatHistoryModel *)historyModel {
if (historyModel.isUserMessage) {
return [KBAiChatMessage userMessageWithText:historyModel.content];
} else {
return [KBAiChatMessage assistantMessageWithText:historyModel.content];
}
}
// 批量转换
- (NSArray<KBAiChatMessage *> *)convertHistoryToMessages:(NSArray<KBChatHistoryModel *> *)history {
NSMutableArray *messages = [NSMutableArray array];
for (KBChatHistoryModel *item in history) {
[messages addObject:[self convertToAiChatMessage:item]];
}
return messages;
}
```
---
## 🔄 与 KBPersonaChatCell 集成
### 在 KBPersonaChatCell 中加载聊天记录
```objc
// KBPersonaChatCell.m
- (void)setPersona:(KBPersonaModel *)persona {
_persona = persona;
// 重置状态
self.hasLoadedData = NO;
self.isLoading = NO;
self.currentPage = 1;
self.hasMoreHistory = YES;
self.messages = [NSMutableArray array];
self.aiVM = [[AiVM alloc] init];
// 设置 UI
[self updateUI];
}
- (void)preloadDataIfNeeded {
if (self.hasLoadedData || self.isLoading) {
return;
}
[self loadChatHistory];
}
- (void)loadChatHistory {
if (self.isLoading || !self.hasMoreHistory) {
return;
}
self.isLoading = YES;
// 使用 persona.personaId 作为 companionId
NSInteger companionId = self.persona.personaId;
__weak typeof(self) weakSelf = self;
[self.aiVM fetchChatHistoryWithCompanionId:companionId
pageNum:self.currentPage
pageSize:20
completion:^(KBChatHistoryPageModel *pageModel, NSError *error) {
weakSelf.isLoading = NO;
if (error) {
NSLog(@"加载聊天记录失败:%@", error.localizedDescription);
return;
}
weakSelf.hasLoadedData = YES;
weakSelf.hasMoreHistory = pageModel.hasMore;
// 转换为 KBAiChatMessage
NSMutableArray *newMessages = [NSMutableArray array];
for (KBChatHistoryModel *item in pageModel.records) {
KBAiChatMessage *message;
if (item.isUserMessage) {
message = [KBAiChatMessage userMessageWithText:item.content];
} else {
message = [KBAiChatMessage assistantMessageWithText:item.content];
}
message.isComplete = YES;
[newMessages addObject:message];
}
// 插入到顶部(历史消息)
if (weakSelf.currentPage == 1) {
weakSelf.messages = newMessages;
} else {
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newMessages.count)];
[weakSelf.messages insertObjects:newMessages atIndexes:indexSet];
}
// 刷新 UI
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.tableView reloadData];
});
}];
}
- (void)loadMoreHistory {
if (!self.hasMoreHistory || self.isLoading) {
return;
}
self.currentPage++;
[self loadChatHistory];
}
// 下拉加载更多
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat offsetY = scrollView.contentOffset.y;
if (offsetY <= -50 && !self.isLoading) {
[self loadMoreHistory];
}
}
```
---
## 📊 数据流程
```
1. 用户滑动到某个人设
2. KBPersonaChatCell 触发预加载
3. 调用 AiVM.fetchChatHistoryWithCompanionId
4. 请求 POST /chat/history
5. 返回 KBChatHistoryPageModel
6. 转换为 KBAiChatMessage
7. 在 TableView 中展示
```
---
## 🎯 字段映射
### MJExtension 自动映射
| JSON 字段 | Model 属性 | 说明 |
|-----------|-----------|------|
| `id` | `messageId` | 消息 ID |
| `sender` | `sender` | 发送者0-用户1-AI |
| `content` | `content` | 消息内容 |
| `createdAt` | `createdAt` | 创建时间 |
### 枚举值说明
```objc
typedef NS_ENUM(NSInteger, KBChatSender) {
KBChatSenderUser = 1, // 用户(右侧显示)
KBChatSenderAssistant = 2 // AI 助手(左侧显示)
};
```
### 与 KBAiChatMessage 的映射关系
```objc
// KBChatHistoryModel → KBAiChatMessage
if (historyModel.sender == KBChatSenderUser) {
// sender = 1 → KBAiChatMessageTypeUser右侧
message = [KBAiChatMessage userMessageWithText:historyModel.content];
} else if (historyModel.sender == KBChatSenderAssistant) {
// sender = 2 → KBAiChatMessageTypeAssistant左侧
message = [KBAiChatMessage assistantMessageWithText:historyModel.content];
}
```
---
## ✅ 已完成功能
1. ✅ KBChatHistoryModel聊天记录模型
2. ✅ KBChatHistoryPageModel分页数据模型
3. ✅ AiVM 新增接口fetchChatHistoryWithCompanionId
4. ✅ MJExtension 配置JSON 自动转 Model
5. ✅ 扩展属性isUserMessage、isAssistantMessage、hasMore
6. ✅ 容错处理:字段为 null 时设置默认值
---
## 🚧 待实现功能
1. 在 KBPersonaChatCell 中集成聊天记录加载
2. 实现下拉加载更多历史消息
3. 实现消息时间戳显示
4. 实现消息发送功能
5. 实现消息缓存(避免重复请求)
---
## 📌 注意事项
1. **companionId**:直接使用 `KBPersonaModel.personaId`(人设列表接口返回的 ID
2. **时间格式**createdAt 是字符串,需要根据实际格式解析
3. **分页方向**:默认从最新消息开始加载,下拉加载历史消息
4. **消息顺序**:需要根据实际需求决定是正序还是倒序
5. **容错处理**:已做 null 值容错,确保不会崩溃
### 数据关联关系
```
KBPersonaModel (人设列表)
└─ personaId (人设 ID)
用作 companionId 参数
KBChatHistoryPageModel (聊天记录)
└─ records (消息列表)
```