1
This commit is contained in:
@@ -91,6 +91,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
- (void)appendHistoryMessages:(NSArray<KBAiChatMessage *> *)messages
|
- (void)appendHistoryMessages:(NSArray<KBAiChatMessage *> *)messages
|
||||||
openingMessage:(nullable KBAiChatMessage *)openingMessage;
|
openingMessage:(nullable KBAiChatMessage *)openingMessage;
|
||||||
|
|
||||||
|
/// 刷新指定消息(按对象指针匹配)
|
||||||
|
- (void)reloadMessage:(KBAiChatMessage *)message;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -868,6 +868,18 @@ static inline CGFloat KBChatAbsTimeInterval(NSTimeInterval interval) {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)reloadMessage:(KBAiChatMessage *)message {
|
||||||
|
if (!message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NSInteger index = [self.messages indexOfObjectIdenticalTo:message];
|
||||||
|
if (index == NSNotFound) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
|
||||||
|
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
|
||||||
|
}
|
||||||
|
|
||||||
/// 判断是否需要插入时间戳
|
/// 判断是否需要插入时间戳
|
||||||
- (BOOL)shouldInsertTimestampForMessage:(KBAiChatMessage *)message {
|
- (BOOL)shouldInsertTimestampForMessage:(KBAiChatMessage *)message {
|
||||||
// 第一条消息总是显示时间
|
// 第一条消息总是显示时间
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
/// 添加用户消息
|
/// 添加用户消息
|
||||||
- (void)appendUserMessage:(NSString *)text;
|
- (void)appendUserMessage:(NSString *)text;
|
||||||
|
|
||||||
|
/// 添加用户消息(绑定本次请求 requestId)
|
||||||
|
- (void)appendUserMessage:(NSString *)text requestId:(NSString *)requestId;
|
||||||
|
|
||||||
/// 标记最后一条用户消息结束加载
|
/// 标记最后一条用户消息结束加载
|
||||||
- (void)markLastUserMessageLoadingComplete;
|
- (void)markLastUserMessageLoadingComplete;
|
||||||
|
|
||||||
@@ -40,12 +43,23 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
/// 添加 loading AI 消息
|
/// 添加 loading AI 消息
|
||||||
- (void)appendLoadingAssistantMessage;
|
- (void)appendLoadingAssistantMessage;
|
||||||
|
|
||||||
|
/// 添加 loading AI 消息(绑定本次请求 requestId)
|
||||||
|
- (void)appendLoadingAssistantMessageWithRequestId:(NSString *)requestId;
|
||||||
|
|
||||||
/// 移除 loading AI 消息
|
/// 移除 loading AI 消息
|
||||||
- (void)removeLoadingAssistantMessage;
|
- (void)removeLoadingAssistantMessage;
|
||||||
|
|
||||||
|
/// 移除 loading AI 消息(按 requestId 精确移除)
|
||||||
|
- (void)removeLoadingAssistantMessageWithRequestId:(NSString *)requestId;
|
||||||
|
|
||||||
/// 更新聊天列表底部 inset
|
/// 更新聊天列表底部 inset
|
||||||
- (void)updateChatViewBottomInset:(CGFloat)bottomInset;
|
- (void)updateChatViewBottomInset:(CGFloat)bottomInset;
|
||||||
|
|
||||||
|
/// 更新指定 requestId 对应的 AI 回复(替换 loading 占位消息)
|
||||||
|
- (void)updateAssistantMessageWithRequestId:(NSString *)requestId
|
||||||
|
text:(NSString *)text
|
||||||
|
audioId:(nullable NSString *)audioId;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
|||||||
/// 评论弹窗
|
/// 评论弹窗
|
||||||
@property (nonatomic, weak) LSTPopView *popView;
|
@property (nonatomic, weak) LSTPopView *popView;
|
||||||
|
|
||||||
|
@property (nonatomic, strong) NSMutableDictionary<NSString *, KBAiChatMessage *> *pendingAssistantMessages;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation KBPersonaChatCell
|
@implementation KBPersonaChatCell
|
||||||
@@ -97,6 +99,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
|||||||
// 重置加载状态标志(但不清空 hasLoadedData)
|
// 重置加载状态标志(但不清空 hasLoadedData)
|
||||||
self.isLoading = NO;
|
self.isLoading = NO;
|
||||||
self.canTriggerLoadMore = YES;
|
self.canTriggerLoadMore = YES;
|
||||||
|
[self.pendingAssistantMessages removeAllObjects];
|
||||||
|
|
||||||
// ✅ 移除了 self.hasLoadedData = NO;
|
// ✅ 移除了 self.hasLoadedData = NO;
|
||||||
// 这样 Cell 复用时不会重复请求数据
|
// 这样 Cell 复用时不会重复请求数据
|
||||||
@@ -180,6 +183,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
|||||||
self.canTriggerLoadMore = YES;
|
self.canTriggerLoadMore = YES;
|
||||||
self.currentPage = 1;
|
self.currentPage = 1;
|
||||||
self.hasMoreHistory = YES;
|
self.hasMoreHistory = YES;
|
||||||
|
[self.pendingAssistantMessages removeAllObjects];
|
||||||
|
|
||||||
// ⚠️ 临时禁用缓存,排查问题
|
// ⚠️ 临时禁用缓存,排查问题
|
||||||
// NSArray *cachedMessages = [[KBAIChatMessageCacheManager shared] messagesForCompanionId:persona.personaId];
|
// NSArray *cachedMessages = [[KBAIChatMessageCacheManager shared] messagesForCompanionId:persona.personaId];
|
||||||
@@ -506,6 +510,10 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
|||||||
[self.chatView addMessage:message autoScroll:YES];
|
[self.chatView addMessage:message autoScroll:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)appendUserMessage:(NSString *)text requestId:(NSString *)requestId {
|
||||||
|
[self appendUserMessage:text];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)appendLoadingUserMessage {
|
- (void)appendLoadingUserMessage {
|
||||||
if (!self.messages) {
|
if (!self.messages) {
|
||||||
self.messages = [NSMutableArray array];
|
self.messages = [NSMutableArray array];
|
||||||
@@ -613,6 +621,33 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
|||||||
[self.chatView addMessage:message autoScroll:YES];
|
[self.chatView addMessage:message autoScroll:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)appendLoadingAssistantMessageWithRequestId:(NSString *)requestId {
|
||||||
|
if (requestId.length == 0) {
|
||||||
|
[self appendLoadingAssistantMessage];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self.pendingAssistantMessages) {
|
||||||
|
self.pendingAssistantMessages = [NSMutableDictionary dictionary];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self.messages) {
|
||||||
|
self.messages = [NSMutableArray array];
|
||||||
|
}
|
||||||
|
|
||||||
|
[self ensureOpeningMessageAtTop];
|
||||||
|
KBAiChatMessage *message = [KBAiChatMessage loadingAssistantMessage];
|
||||||
|
self.pendingAssistantMessages[requestId] = message;
|
||||||
|
|
||||||
|
if (self.chatView.inverted) {
|
||||||
|
[self.messages insertObject:message atIndex:0];
|
||||||
|
} else {
|
||||||
|
[self.messages addObject:message];
|
||||||
|
}
|
||||||
|
|
||||||
|
[self.chatView addMessage:message autoScroll:YES];
|
||||||
|
}
|
||||||
|
|
||||||
/// 移除 loading AI 消息
|
/// 移除 loading AI 消息
|
||||||
- (void)removeLoadingAssistantMessage {
|
- (void)removeLoadingAssistantMessage {
|
||||||
// 从数据源中移除
|
// 从数据源中移除
|
||||||
@@ -638,6 +673,51 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
|||||||
[self.chatView removeLoadingAssistantMessage];
|
[self.chatView removeLoadingAssistantMessage];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)removeLoadingAssistantMessageWithRequestId:(NSString *)requestId {
|
||||||
|
if (requestId.length == 0) {
|
||||||
|
[self removeLoadingAssistantMessage];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KBAiChatMessage *target = self.pendingAssistantMessages[requestId];
|
||||||
|
if (!target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[self.pendingAssistantMessages removeObjectForKey:requestId];
|
||||||
|
|
||||||
|
NSInteger idx = [self.messages indexOfObjectIdenticalTo:target];
|
||||||
|
if (idx != NSNotFound) {
|
||||||
|
[self.messages removeObjectAtIndex:idx];
|
||||||
|
[self.chatView reloadWithMessages:self.messages keepOffset:NO scrollToBottom:NO];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateAssistantMessageWithRequestId:(NSString *)requestId
|
||||||
|
text:(NSString *)text
|
||||||
|
audioId:(nullable NSString *)audioId {
|
||||||
|
if (requestId.length == 0) {
|
||||||
|
[self appendAssistantMessage:text audioId:audioId];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KBAiChatMessage *target = self.pendingAssistantMessages[requestId];
|
||||||
|
[self.pendingAssistantMessages removeObjectForKey:requestId];
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
[self appendAssistantMessage:text audioId:audioId];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.isLoading = NO;
|
||||||
|
target.text = text ?: @"";
|
||||||
|
target.audioId = audioId;
|
||||||
|
target.needsTypewriterEffect = YES;
|
||||||
|
target.isComplete = NO;
|
||||||
|
|
||||||
|
[self.chatView reloadMessage:target];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)updateChatViewBottomInset:(CGFloat)bottomInset {
|
- (void)updateChatViewBottomInset:(CGFloat)bottomInset {
|
||||||
[self.chatView updateContentBottomInset:bottomInset];
|
[self.chatView updateContentBottomInset:bottomInset];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,8 @@
|
|||||||
/// 是否正在等待 AI 回复(用于禁止滚动)
|
/// 是否正在等待 AI 回复(用于禁止滚动)
|
||||||
@property (nonatomic, assign) BOOL isWaitingForAIResponse;
|
@property (nonatomic, assign) BOOL isWaitingForAIResponse;
|
||||||
|
|
||||||
|
@property (nonatomic, assign) NSInteger pendingAIRequestCount;
|
||||||
|
|
||||||
/// 右上角消息按钮
|
/// 右上角消息按钮
|
||||||
@property (nonatomic, strong) UIButton *messageButton;
|
@property (nonatomic, strong) UIButton *messageButton;
|
||||||
|
|
||||||
@@ -136,6 +138,7 @@
|
|||||||
self.preloadedIndexes = [NSMutableSet set];
|
self.preloadedIndexes = [NSMutableSet set];
|
||||||
self.aiVM = [[AiVM alloc] init];
|
self.aiVM = [[AiVM alloc] init];
|
||||||
self.isWaitingForAIResponse = NO;
|
self.isWaitingForAIResponse = NO;
|
||||||
|
self.pendingAIRequestCount = 0;
|
||||||
self.isTextInputMode = NO;
|
self.isTextInputMode = NO;
|
||||||
|
|
||||||
[self setupUI];
|
[self setupUI];
|
||||||
@@ -962,15 +965,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
KBPersonaChatCell *currentCell = [self currentPersonaCell];
|
KBPersonaChatCell *currentCell = [self currentPersonaCell];
|
||||||
|
NSString *requestId = [NSUUID UUID].UUIDString;
|
||||||
if (currentCell && appendToUI) {
|
if (currentCell && appendToUI) {
|
||||||
[currentCell appendUserMessage:text];
|
[currentCell appendUserMessage:text requestId:requestId];
|
||||||
// 添加 loading AI 消息
|
[currentCell appendLoadingAssistantMessageWithRequestId:requestId];
|
||||||
[currentCell appendLoadingAssistantMessage];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.isWaitingForAIResponse = YES;
|
self.pendingAIRequestCount += 1;
|
||||||
|
self.isWaitingForAIResponse = (self.pendingAIRequestCount > 0);
|
||||||
|
if (self.pendingAIRequestCount == 1) {
|
||||||
self.collectionView.scrollEnabled = NO;
|
self.collectionView.scrollEnabled = NO;
|
||||||
NSLog(@"[KBAIHomeVC] 开始等待 AI 回复,禁止 CollectionView 滚动");
|
NSLog(@"[KBAIHomeVC] 开始等待 AI 回复,禁止 CollectionView 滚动");
|
||||||
|
}
|
||||||
|
|
||||||
__weak typeof(self) weakSelf = self;
|
__weak typeof(self) weakSelf = self;
|
||||||
[self.aiVM requestChatMessageWithContent:text
|
[self.aiVM requestChatMessageWithContent:text
|
||||||
@@ -982,19 +988,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
strongSelf.isWaitingForAIResponse = NO;
|
if (strongSelf.pendingAIRequestCount > 0) {
|
||||||
|
strongSelf.pendingAIRequestCount -= 1;
|
||||||
|
}
|
||||||
|
strongSelf.isWaitingForAIResponse = (strongSelf.pendingAIRequestCount > 0);
|
||||||
|
if (strongSelf.pendingAIRequestCount == 0) {
|
||||||
strongSelf.collectionView.scrollEnabled = YES;
|
strongSelf.collectionView.scrollEnabled = YES;
|
||||||
NSLog(@"[KBAIHomeVC] AI 回复完成,恢复 CollectionView 滚动");
|
NSLog(@"[KBAIHomeVC] AI 回复完成,恢复 CollectionView 滚动");
|
||||||
|
}
|
||||||
|
|
||||||
KBPersonaChatCell *cell = [strongSelf currentPersonaCell];
|
KBPersonaChatCell *cell = [strongSelf currentPersonaCell];
|
||||||
if (cell) {
|
|
||||||
[cell markLastUserMessageLoadingComplete];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.code == 50030) {
|
if (response.code == 50030) {
|
||||||
// 移除 loading 消息
|
// 移除 loading 消息
|
||||||
if (cell) {
|
if (cell) {
|
||||||
[cell removeLoadingAssistantMessage];
|
[cell removeLoadingAssistantMessageWithRequestId:requestId];
|
||||||
}
|
}
|
||||||
NSString *message = response.message ?: @"";
|
NSString *message = response.message ?: @"";
|
||||||
[strongSelf showChatLimitPopWithMessage:message];
|
[strongSelf showChatLimitPopWithMessage:message];
|
||||||
@@ -1004,7 +1012,7 @@
|
|||||||
if (!response || !response.data) {
|
if (!response || !response.data) {
|
||||||
// 移除 loading 消息
|
// 移除 loading 消息
|
||||||
if (cell) {
|
if (cell) {
|
||||||
[cell removeLoadingAssistantMessage];
|
[cell removeLoadingAssistantMessageWithRequestId:requestId];
|
||||||
}
|
}
|
||||||
NSString *message = response.message ?: @"聊天响应为空";
|
NSString *message = response.message ?: @"聊天响应为空";
|
||||||
NSLog(@"[KBAIHomeVC] 聊天响应为空:%@", message);
|
NSLog(@"[KBAIHomeVC] 聊天响应为空:%@", message);
|
||||||
@@ -1019,15 +1027,14 @@
|
|||||||
if (aiResponse.length == 0) {
|
if (aiResponse.length == 0) {
|
||||||
// 移除 loading 消息
|
// 移除 loading 消息
|
||||||
if (cell) {
|
if (cell) {
|
||||||
[cell removeLoadingAssistantMessage];
|
[cell removeLoadingAssistantMessageWithRequestId:requestId];
|
||||||
}
|
}
|
||||||
NSLog(@"[KBAIHomeVC] AI 回复为空");
|
NSLog(@"[KBAIHomeVC] AI 回复为空");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cell) {
|
if (cell) {
|
||||||
// appendAssistantMessage 内部会自动移除 loading 消息
|
[cell updateAssistantMessageWithRequestId:requestId text:aiResponse audioId:audioId];
|
||||||
[cell appendAssistantMessage:aiResponse audioId:audioId];
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}];
|
}];
|
||||||
|
|||||||
Reference in New Issue
Block a user