diff --git a/CustomKeyboard/KeyboardViewController.m b/CustomKeyboard/KeyboardViewController.m index 1e299b2..20ab610 100644 --- a/CustomKeyboard/KeyboardViewController.m +++ b/CustomKeyboard/KeyboardViewController.m @@ -855,8 +855,7 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, if (text.length == 0) { return; } - NSLog(@"[Keyboard] ========== kb_sendChatText =========="); - NSLog(@"[Keyboard] chatPanelView=%p", self.chatPanelView); + NSLog(@"[KB] 发送消息: %@", text); KBChatMessage *outgoing = [KBChatMessage userMessageWithText:text]; outgoing.avatarURL = [self kb_sharedUserAvatarURL]; @@ -869,7 +868,6 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, } // 添加 loading 消息 - NSLog(@"[Keyboard] 准备添加 loading 消息,chatPanelView=%p", self.chatPanelView); [self.chatPanelView kb_addLoadingAssistantMessage]; // 调用新的聊天接口 @@ -932,16 +930,9 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, } - (void)kb_reloadChatRowForMessage:(KBChatMessage *)message { - NSLog(@"[Keyboard] ========== kb_reloadChatRowForMessage =========="); - // 不再使用 self.chatMessages,直接刷新 tableView - UITableView *tableView = self.chatPanelView.tableView; - if (!tableView) { - NSLog(@"[Keyboard] tableView 为空,跳过"); - return; - } - // 刷新整个 tableView - NSLog(@"[Keyboard] 调用 tableView reloadData"); - [tableView reloadData]; + // 头像预加载完成后不需要刷新表格 + // 因为键盘扩展的聊天面板不显示头像,所以这里直接返回 + // 如果将来需要显示头像,可以只刷新特定行而不是整个表格 } - (void)kb_requestChatAudioForText:(NSString *)text { @@ -1011,51 +1002,40 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, /// 调用新的聊天接口(返回文本和 audioId) - (void)kb_requestChatMessageWithContent:(NSString *)content { - NSLog(@"[Keyboard] ========== kb_requestChatMessageWithContent =========="); - NSLog(@"[Keyboard] 请求内容: %@", content); - if (content.length == 0) { - NSLog(@"[Keyboard] ❌ 内容为空,移除 loading"); [self.chatPanelView kb_removeLoadingAssistantMessage]; return; } - // 从 AppGroup 获取选中的 persona companionId NSInteger companionId = [[KBVM shared] selectedCompanionIdFromAppGroup]; - NSLog(@"[Keyboard] 发送聊天请求: companionId=%ld", (long)companionId); + NSLog(@"[KB] 请求聊天: companionId=%ld", (long)companionId); __weak typeof(self) weakSelf = self; [[KBVM shared] sendChatMessageWithContent:content companionId:companionId completion:^(KBChatResponse *response) { __strong typeof(weakSelf) self = weakSelf; - if (!self) { - NSLog(@"[Keyboard] ❌ self 为空"); - return; - } - - NSLog(@"[Keyboard] 回调中 chatPanelView=%p", self.chatPanelView); + if (!self) return; if (!response.success) { - NSLog(@"[Keyboard] ❌ 请求失败: %@", response.errorMessage); + NSLog(@"[KB] ❌ 请求失败: %@", response.errorMessage); [self.chatPanelView kb_removeLoadingAssistantMessage]; [KBHUD showInfo:response.errorMessage ?: KBLocalized(@"请求失败")]; return; } - NSLog(@"[Keyboard] ✅ 解析结果: text=%@, audioId=%@", response.text, response.audioId); + NSLog(@"[KB] ✅ 收到回复: %@", response.text); if (response.text.length == 0) { - NSLog(@"[Keyboard] ❌ 文本为空,移除 loading"); [self.chatPanelView kb_removeLoadingAssistantMessage]; [KBHUD showInfo:KBLocalized(@"未获取到回复内容")]; return; } - NSLog(@"[Keyboard] 准备调用 kb_addAssistantMessage, chatPanelView=%p", self.chatPanelView); // 添加 AI 消息(带打字机效果) + NSLog(@"[KB] 准备添加 AI 消息"); [self.chatPanelView kb_addAssistantMessage:response.text audioId:response.audioId]; - NSLog(@"[Keyboard] kb_addAssistantMessage 调用完成"); + NSLog(@"[KB] AI 消息添加完成"); // 如果有 audioId,开始预加载音频 if (response.audioId.length > 0) { diff --git a/CustomKeyboard/View/Chat/KBChatPanelView.m b/CustomKeyboard/View/Chat/KBChatPanelView.m index f8d04b4..a44d077 100644 --- a/CustomKeyboard/View/Chat/KBChatPanelView.m +++ b/CustomKeyboard/View/Chat/KBChatPanelView.m @@ -25,7 +25,6 @@ static const NSUInteger kKBChatMessageLimit = 10; - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { - NSLog(@"[KBChatPanelView] ⚠️ initWithFrame 被调用,self=%p", self); self.backgroundColor = [UIColor clearColor]; self.messages = [NSMutableArray array]; @@ -50,9 +49,7 @@ static const NSUInteger kKBChatMessageLimit = 10; #pragma mark - Public - (void)kb_reloadWithMessages:(NSArray *)messages { - NSLog(@"[KBChatPanelView] ========== kb_reloadWithMessages =========="); - NSLog(@"[KBChatPanelView] self=%p, 传入消息数量: %lu", self, (unsigned long)messages.count); - NSLog(@"[KBChatPanelView] 调用堆栈: %@", [NSThread callStackSymbols]); + NSLog(@"[Panel] ⚠️ kb_reloadWithMessages 被调用,传入 %lu 条消息", (unsigned long)messages.count); [self.messages removeAllObjects]; if (messages.count > 0) { @@ -65,94 +62,95 @@ static const NSUInteger kKBChatMessageLimit = 10; - (void)kb_addUserMessage:(NSString *)text { if (text.length == 0) return; - NSLog(@"[KBChatPanelView] ========== kb_addUserMessage =========="); - NSLog(@"[KBChatPanelView] self=%p, messages=%p", self, self.messages); - NSLog(@"[KBChatPanelView] 添加用户消息: %@", text); - NSLog(@"[KBChatPanelView] 当前消息数量: %lu", (unsigned long)self.messages.count); + NSLog(@"[Panel] 添加用户消息: %@,当前消息数: %lu", text, (unsigned long)self.messages.count); KBChatMessage *msg = [KBChatMessage userMessageWithText:text]; - NSLog(@"[KBChatPanelView] 创建消息 - outgoing: %d, text: %@", msg.outgoing, msg.text); [self kb_appendMessage:msg]; - NSLog(@"[KBChatPanelView] 添加后消息数量: %lu", (unsigned long)self.messages.count); - for (NSInteger i = 0; i < self.messages.count; i++) { - KBChatMessage *m = self.messages[i]; - NSLog(@"[KBChatPanelView] 消息[%ld]: outgoing=%d, isLoading=%d, text=%@", (long)i, m.outgoing, m.isLoading, m.text); - } + NSLog(@"[Panel] 添加后消息数: %lu", (unsigned long)self.messages.count); } - (void)kb_addLoadingAssistantMessage { - NSLog(@"[KBChatPanelView] ========== kb_addLoadingAssistantMessage =========="); - NSLog(@"[KBChatPanelView] self=%p, messages=%p", self, self.messages); - NSLog(@"[KBChatPanelView] 当前消息数量: %lu", (unsigned long)self.messages.count); + NSLog(@"[Panel] 添加 loading 消息,当前消息数: %lu", (unsigned long)self.messages.count); KBChatMessage *msg = [KBChatMessage loadingAssistantMessage]; - NSLog(@"[KBChatPanelView] 创建 loading 消息 - outgoing: %d, isLoading: %d", msg.outgoing, msg.isLoading); [self kb_appendMessage:msg]; - NSLog(@"[KBChatPanelView] 添加后消息数量: %lu", (unsigned long)self.messages.count); - for (NSInteger i = 0; i < self.messages.count; i++) { - KBChatMessage *m = self.messages[i]; - NSLog(@"[KBChatPanelView] 消息[%ld]: outgoing=%d, isLoading=%d, text=%@", (long)i, m.outgoing, m.isLoading, m.text); - } + NSLog(@"[Panel] 添加后消息数: %lu", (unsigned long)self.messages.count); } - (void)kb_removeLoadingAssistantMessage { - NSLog(@"[KBChatPanelView] ========== kb_removeLoadingAssistantMessage =========="); - NSLog(@"[KBChatPanelView] 当前消息数量: %lu", (unsigned long)self.messages.count); - for (NSInteger i = 0; i < self.messages.count; i++) { - KBChatMessage *m = self.messages[i]; - NSLog(@"[KBChatPanelView] 消息[%ld]: outgoing=%d, isLoading=%d, text=%@", (long)i, m.outgoing, m.isLoading, m.text); - } + NSLog(@"[Panel] 移除 loading 消息,当前消息数: %lu", (unsigned long)self.messages.count); for (NSInteger i = self.messages.count - 1; i >= 0; i--) { KBChatMessage *msg = self.messages[i]; - NSLog(@"[KBChatPanelView] 检查消息[%ld]: outgoing=%d, isLoading=%d", (long)i, msg.outgoing, msg.isLoading); // 只移除 AI 消息(outgoing == NO)且是 loading 状态的 if (!msg.outgoing && msg.isLoading) { - NSLog(@"[KBChatPanelView] ✅ 找到 loading AI 消息,准备移除索引: %ld", (long)i); + NSLog(@"[Panel] ✅ 找到 loading 消息,移除索引: %ld", (long)i); [self.messages removeObjectAtIndex:i]; + + // 使用 beginUpdates/endUpdates 包裹删除操作 + [self.tableViewInternal beginUpdates]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0]; [self.tableViewInternal deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; - NSLog(@"[KBChatPanelView] 移除后消息数量: %lu", (unsigned long)self.messages.count); + [self.tableViewInternal endUpdates]; + + NSLog(@"[Panel] 移除后消息数: %lu", (unsigned long)self.messages.count); break; } } - - NSLog(@"[KBChatPanelView] 最终消息数量: %lu", (unsigned long)self.messages.count); - for (NSInteger i = 0; i < self.messages.count; i++) { - KBChatMessage *m = self.messages[i]; - NSLog(@"[KBChatPanelView] 最终消息[%ld]: outgoing=%d, isLoading=%d, text=%@", (long)i, m.outgoing, m.isLoading, m.text); - } } - (void)kb_addAssistantMessage:(NSString *)text audioId:(NSString *)audioId { - NSLog(@"[KBChatPanelView] ========== kb_addAssistantMessage =========="); - NSLog(@"[KBChatPanelView] self=%p, messages=%p", self, self.messages); - NSLog(@"[KBChatPanelView] AI 回复文本: %@", text); - NSLog(@"[KBChatPanelView] audioId: %@", audioId); - NSLog(@"[KBChatPanelView] 当前消息数量: %lu", (unsigned long)self.messages.count); + NSLog(@"[Panel] ========== kb_addAssistantMessage =========="); + NSLog(@"[Panel] 当前消息数: %lu", (unsigned long)self.messages.count); - // 先移除 loading 消息 - [self kb_removeLoadingAssistantMessage]; - - NSLog(@"[KBChatPanelView] 移除 loading 后消息数量: %lu", (unsigned long)self.messages.count); + // 查找 loading 消息的索引 + NSInteger loadingIndex = -1; + for (NSInteger i = self.messages.count - 1; i >= 0; i--) { + KBChatMessage *msg = self.messages[i]; + if (!msg.outgoing && msg.isLoading) { + loadingIndex = i; + break; + } + } + // 创建 AI 消息 KBChatMessage *msg = [KBChatMessage assistantMessageWithText:text audioId:audioId]; msg.displayName = KBLocalized(@"AI助手"); - NSLog(@"[KBChatPanelView] 创建 AI 消息 - outgoing: %d, isLoading: %d, needsTypewriter: %d, text: %@", - msg.outgoing, msg.isLoading, msg.needsTypewriterEffect, msg.text); - [self kb_appendMessage:msg]; + NSLog(@"[Panel] 创建 AI 消息,needsTypewriter: %d", msg.needsTypewriterEffect); - NSLog(@"[KBChatPanelView] 添加后消息数量: %lu", (unsigned long)self.messages.count); - for (NSInteger i = 0; i < self.messages.count; i++) { - KBChatMessage *m = self.messages[i]; - NSLog(@"[KBChatPanelView] 消息[%ld]: outgoing=%d, isLoading=%d, text=%@", (long)i, m.outgoing, m.isLoading, m.text); + // 使用批量更新,避免界面跳动 + [self.tableViewInternal beginUpdates]; + + if (loadingIndex >= 0) { + // 移除 loading 消息 + NSLog(@"[Panel] 移除 loading 索引: %ld", (long)loadingIndex); + [self.messages removeObjectAtIndex:loadingIndex]; + NSIndexPath *deleteIndexPath = [NSIndexPath indexPathForRow:loadingIndex inSection:0]; + [self.tableViewInternal deleteRowsAtIndexPaths:@[deleteIndexPath] + withRowAnimation:UITableViewRowAnimationNone]; } + + // 添加 AI 消息 + NSInteger insertIndex = self.messages.count; + [self.messages addObject:msg]; + NSLog(@"[Panel] 插入 AI 消息索引: %ld", (long)insertIndex); + NSIndexPath *insertIndexPath = [NSIndexPath indexPathForRow:insertIndex inSection:0]; + [self.tableViewInternal insertRowsAtIndexPaths:@[insertIndexPath] + withRowAnimation:UITableViewRowAnimationNone]; + + [self.tableViewInternal endUpdates]; + + // 滚动到底部 + [self kb_scrollToBottom]; + + NSLog(@"[Panel] 添加后消息数: %lu", (unsigned long)self.messages.count); } - (void)kb_updateLastAssistantMessageWithAudioData:(NSData *)audioData duration:(NSTimeInterval)duration { + NSLog(@"[Panel] 更新音频数据,duration: %.2f", duration); for (NSInteger i = self.messages.count - 1; i >= 0; i--) { KBChatMessage *msg = self.messages[i]; // 只更新 AI 消息(outgoing == NO)且非 loading 状态的 @@ -160,19 +158,12 @@ static const NSUInteger kKBChatMessageLimit = 10; msg.audioData = audioData; msg.audioDuration = duration; - // 刷新该行以更新语音时长显示 - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0]; - KBChatAssistantCell *cell = [self.tableViewInternal cellForRowAtIndexPath:indexPath]; - if ([cell isKindOfClass:[KBChatAssistantCell class]]) { - // 直接更新 Cell,不刷新整行(避免打断打字机效果) - if (duration > 0) { - // 通过重新配置来更新时长显示 - // 但不要触发打字机效果 - msg.needsTypewriterEffect = NO; - msg.isComplete = YES; - } + // 不刷新 Cell,避免打断打字机效果 + if (duration > 0) { + msg.needsTypewriterEffect = NO; + msg.isComplete = YES; } - NSLog(@"[KBChatPanelView] 更新 AI 消息音频数据,时长: %.2f秒", duration); + NSLog(@"[Panel] ✅ 音频数据已更新"); break; } } @@ -181,11 +172,13 @@ static const NSUInteger kKBChatMessageLimit = 10; - (void)kb_scrollToBottom { if (self.messages.count == 0) return; + NSLog(@"[Panel] 滚动到底部,消息数: %lu", (unsigned long)self.messages.count); + [self.tableViewInternal layoutIfNeeded]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.messages.count - 1 inSection:0]; [self.tableViewInternal scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom - animated:YES]; + animated:NO]; // 改为 NO,避免动画导致跳动 } #pragma mark - Private @@ -195,21 +188,25 @@ static const NSUInteger kKBChatMessageLimit = 10; NSInteger oldCount = self.messages.count; [self.messages addObject:message]; + NSLog(@"[Panel] kb_appendMessage: oldCount=%ld, newCount=%lu", (long)oldCount, (unsigned long)self.messages.count); // 限制消息数量 if (self.messages.count > kKBChatMessageLimit) { NSUInteger overflow = self.messages.count - kKBChatMessageLimit; [self.messages removeObjectsInRange:NSMakeRange(0, overflow)]; + NSLog(@"[Panel] 消息超限,reloadData"); [self.tableViewInternal reloadData]; } else { + NSLog(@"[Panel] 插入新行: %ld", (long)oldCount); + [self.tableViewInternal beginUpdates]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:oldCount inSection:0]; [self.tableViewInternal insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; + [self.tableViewInternal endUpdates]; } - dispatch_async(dispatch_get_main_queue(), ^{ - [self kb_scrollToBottom]; - }); + // 直接滚动,不用 dispatch_async + [self kb_scrollToBottom]; } #pragma mark - Actions @@ -227,26 +224,21 @@ static const NSUInteger kKBChatMessageLimit = 10; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - NSLog(@"[KBChatPanelView] ========== cellForRowAtIndexPath: %ld ==========", (long)indexPath.row); - if (indexPath.row >= self.messages.count) { - NSLog(@"[KBChatPanelView] ❌ 索引越界,返回空 Cell"); + NSLog(@"[Panel] ❌ cellForRow 索引越界: %ld >= %lu", (long)indexPath.row, (unsigned long)self.messages.count); return [[UITableViewCell alloc] init]; } KBChatMessage *msg = self.messages[indexPath.row]; - NSLog(@"[KBChatPanelView] 消息: outgoing=%d, isLoading=%d, needsTypewriter=%d, text=%@", - msg.outgoing, msg.isLoading, msg.needsTypewriterEffect, msg.text); + NSLog(@"[Panel] cellForRow[%ld]: outgoing=%d, isLoading=%d", (long)indexPath.row, msg.outgoing, msg.isLoading); if (msg.outgoing) { // 用户消息(右侧) - NSLog(@"[KBChatPanelView] 使用 KBChatUserCell"); KBChatUserCell *cell = [tableView dequeueReusableCellWithIdentifier:kUserCellIdentifier forIndexPath:indexPath]; [cell configureWithMessage:msg]; return cell; } else { // AI 消息(左侧) - NSLog(@"[KBChatPanelView] 使用 KBChatAssistantCell"); KBChatAssistantCell *cell = [tableView dequeueReusableCellWithIdentifier:kAssistantCellIdentifier forIndexPath:indexPath]; cell.delegate = self; [cell configureWithMessage:msg];