This commit is contained in:
2026-01-31 23:17:58 +08:00
parent 6ae504823b
commit 81bc50ce17
5 changed files with 133 additions and 17 deletions

View File

@@ -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

View File

@@ -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 {
//

View File

@@ -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

View File

@@ -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];
}

View File

@@ -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];
}
});
}];