# 聊天记录 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 *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 *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 *)convertHistoryToMessages:(NSArray *)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 (消息列表) ```