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