新增聊天记录
This commit is contained in:
43
keyBoard/Class/AiTalk/M/KBChatHistoryModel.h
Normal file
43
keyBoard/Class/AiTalk/M/KBChatHistoryModel.h
Normal file
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// KBChatHistoryModel.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Kiro on 2026/1/26.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 消息发送者类型
|
||||
typedef NS_ENUM(NSInteger, KBChatSender) {
|
||||
KBChatSenderUser = 0, // 用户
|
||||
KBChatSenderAssistant = 1 // AI 助手
|
||||
};
|
||||
|
||||
/// 聊天记录模型
|
||||
@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;
|
||||
|
||||
#pragma mark - 扩展属性
|
||||
|
||||
/// 是否是用户消息
|
||||
@property (nonatomic, assign, readonly) BOOL isUserMessage;
|
||||
|
||||
/// 是否是 AI 消息
|
||||
@property (nonatomic, assign, readonly) BOOL isAssistantMessage;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
43
keyBoard/Class/AiTalk/M/KBChatHistoryModel.m
Normal file
43
keyBoard/Class/AiTalk/M/KBChatHistoryModel.m
Normal file
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// KBChatHistoryModel.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Kiro on 2026/1/26.
|
||||
//
|
||||
|
||||
#import "KBChatHistoryModel.h"
|
||||
#import <MJExtension/MJExtension.h>
|
||||
|
||||
@implementation KBChatHistoryModel
|
||||
|
||||
#pragma mark - MJExtension 配置
|
||||
|
||||
// 字段映射
|
||||
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
|
||||
return @{
|
||||
@"messageId": @"id"
|
||||
};
|
||||
}
|
||||
|
||||
// 转换完成后的处理
|
||||
- (void)mj_keyValuesDidFinishConvertingToObject {
|
||||
// 容错处理
|
||||
if (!self.content) {
|
||||
self.content = @"";
|
||||
}
|
||||
if (!self.createdAt) {
|
||||
self.createdAt = @"";
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 扩展属性
|
||||
|
||||
- (BOOL)isUserMessage {
|
||||
return self.sender == KBChatSenderUser;
|
||||
}
|
||||
|
||||
- (BOOL)isAssistantMessage {
|
||||
return self.sender == KBChatSenderAssistant;
|
||||
}
|
||||
|
||||
@end
|
||||
45
keyBoard/Class/AiTalk/M/KBChatHistoryPageModel.h
Normal file
45
keyBoard/Class/AiTalk/M/KBChatHistoryPageModel.h
Normal file
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// KBChatHistoryPageModel.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Kiro on 2026/1/26.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "KBChatHistoryModel.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 排序规则
|
||||
@interface KBChatOrderRule : NSObject
|
||||
@property (nonatomic, copy) NSString *column; // 排序字段
|
||||
@property (nonatomic, assign) BOOL asc; // 是否升序
|
||||
@end
|
||||
|
||||
/// 聊天记录分页数据模型
|
||||
@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, strong, nullable) NSArray<KBChatOrderRule *> *orders;
|
||||
|
||||
/// 总页数
|
||||
@property (nonatomic, assign) NSInteger pages;
|
||||
|
||||
/// 是否还有更多数据
|
||||
@property (nonatomic, assign, readonly) BOOL hasMore;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
39
keyBoard/Class/AiTalk/M/KBChatHistoryPageModel.m
Normal file
39
keyBoard/Class/AiTalk/M/KBChatHistoryPageModel.m
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// KBChatHistoryPageModel.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Kiro on 2026/1/26.
|
||||
//
|
||||
|
||||
#import "KBChatHistoryPageModel.h"
|
||||
#import <MJExtension/MJExtension.h>
|
||||
|
||||
@implementation KBChatOrderRule
|
||||
@end
|
||||
|
||||
@implementation KBChatHistoryPageModel
|
||||
|
||||
#pragma mark - MJExtension 配置
|
||||
|
||||
// 数组内元素类型
|
||||
+ (NSDictionary *)mj_objectClassInArray {
|
||||
return @{
|
||||
@"records": [KBChatHistoryModel class],
|
||||
@"orders": [KBChatOrderRule class]
|
||||
};
|
||||
}
|
||||
|
||||
// 转换完成后的处理
|
||||
- (void)mj_keyValuesDidFinishConvertingToObject {
|
||||
if (!self.records) {
|
||||
self.records = @[];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 扩展属性
|
||||
|
||||
- (BOOL)hasMore {
|
||||
return self.current < self.pages;
|
||||
}
|
||||
|
||||
@end
|
||||
408
keyBoard/Class/AiTalk/M/聊天记录Model说明.md
Normal file
408
keyBoard/Class/AiTalk/M/聊天记录Model说明.md
Normal file
@@ -0,0 +1,408 @@
|
||||
# 聊天记录 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": 0, // 0-用户,1-AI
|
||||
"content": "你好",
|
||||
"createdAt": "2026-01-26 10:00:00"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"sender": 1,
|
||||
"content": "你好!有什么可以帮助你的吗?",
|
||||
"createdAt": "2026-01-26 10:00:05"
|
||||
}
|
||||
],
|
||||
"total": 100,
|
||||
"current": 1,
|
||||
"pages": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 使用示例
|
||||
|
||||
### 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 = 0, // 用户
|
||||
KBChatSenderAssistant = 1 // AI 助手
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 已完成功能
|
||||
|
||||
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 (消息列表)
|
||||
```
|
||||
Reference in New Issue
Block a user