10 KiB
10 KiB
聊天记录 Model 说明
📦 Model 结构
1. KBChatHistoryModel(聊天记录模型)
@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(分页数据模型)
@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
请求参数
{
"companionId": 0, // AI 陪聊角色 ID(使用 KBPersonaModel.personaId)
"pageNum": 1, // 页码
"pageSize": 20 // 每页大小
}
参数说明
- companionId:使用
fetchPersonasWithPageNum接口返回的KBPersonaModel.personaId - pageNum:页码,从 1 开始
- pageSize:每页大小,建议 20
响应格式
{
"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 中调用接口
#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 中使用
@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
// 将 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 中加载聊天记录
// 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 |
创建时间 |
枚举值说明
typedef NS_ENUM(NSInteger, KBChatSender) {
KBChatSenderUser = 1, // 用户(右侧显示)
KBChatSenderAssistant = 2 // AI 助手(左侧显示)
};
与 KBAiChatMessage 的映射关系
// 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];
}
✅ 已完成功能
- ✅ KBChatHistoryModel:聊天记录模型
- ✅ KBChatHistoryPageModel:分页数据模型
- ✅ AiVM 新增接口:fetchChatHistoryWithCompanionId
- ✅ MJExtension 配置:JSON 自动转 Model
- ✅ 扩展属性:isUserMessage、isAssistantMessage、hasMore
- ✅ 容错处理:字段为 null 时设置默认值
🚧 待实现功能
- 在 KBPersonaChatCell 中集成聊天记录加载
- 实现下拉加载更多历史消息
- 实现消息时间戳显示
- 实现消息发送功能
- 实现消息缓存(避免重复请求)
📌 注意事项
- companionId:直接使用
KBPersonaModel.personaId(人设列表接口返回的 ID) - 时间格式:createdAt 是字符串,需要根据实际格式解析
- 分页方向:默认从最新消息开始加载,下拉加载历史消息
- 消息顺序:需要根据实际需求决定是正序还是倒序
- 容错处理:已做 null 值容错,确保不会崩溃
数据关联关系
KBPersonaModel (人设列表)
└─ personaId (人设 ID)
↓
用作 companionId 参数
↓
KBChatHistoryPageModel (聊天记录)
└─ records (消息列表)