From 6e50cdcd2a6e3d847152640363b0190c4261f15e Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Mon, 2 Feb 2026 20:36:38 +0800 Subject: [PATCH] 1 --- .../Class/AiTalk/V/Chat/KBChatTableView.h | 8 ++ .../Class/AiTalk/V/Chat/KBChatTableView.m | 88 +++++++++++++++++++ .../Class/AiTalk/V/Chat/KBPersonaChatCell.m | 76 ++++++++++------ 3 files changed, 143 insertions(+), 29 deletions(-) diff --git a/keyBoard/Class/AiTalk/V/Chat/KBChatTableView.h b/keyBoard/Class/AiTalk/V/Chat/KBChatTableView.h index 80fe4f4..5aff434 100644 --- a/keyBoard/Class/AiTalk/V/Chat/KBChatTableView.h +++ b/keyBoard/Class/AiTalk/V/Chat/KBChatTableView.h @@ -63,6 +63,14 @@ NS_ASSUME_NONNULL_BEGIN /// 移除 loading 用户消息 - (void)removeLoadingUserMessage; +/// 顶部加载中提示 +- (void)showTopLoading; +- (void)hideTopLoading; + +/// 顶部“无更多数据”提示 +- (void)showNoMoreData; +- (void)hideNoMoreData; + /// 滚动到底部 - (void)scrollToBottom; diff --git a/keyBoard/Class/AiTalk/V/Chat/KBChatTableView.m b/keyBoard/Class/AiTalk/V/Chat/KBChatTableView.m index ec12066..2e83e07 100644 --- a/keyBoard/Class/AiTalk/V/Chat/KBChatTableView.m +++ b/keyBoard/Class/AiTalk/V/Chat/KBChatTableView.m @@ -37,6 +37,11 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟 @property (nonatomic, assign) CGSize lastIntroFooterTableSize; @property (nonatomic, assign) BOOL applyingIntroFooter; @property (nonatomic, copy) NSString *remoteAudioToken; +@property (nonatomic, strong) UIView *topStatusView; +@property (nonatomic, strong) UIActivityIndicatorView *topLoadingIndicator; +@property (nonatomic, strong) UILabel *topStatusLabel; +@property (nonatomic, assign) BOOL isTopLoading; +@property (nonatomic, assign) BOOL isTopNoMore; @end @@ -427,6 +432,89 @@ static inline CGFloat KBChatAbsTimeInterval(NSTimeInterval interval) { } } +#pragma mark - Top Status + +- (void)showTopLoading { + self.isTopLoading = YES; + self.isTopNoMore = NO; + [self updateTopStatusView]; +} + +- (void)hideTopLoading { + self.isTopLoading = NO; + [self updateTopStatusView]; +} + +- (void)showNoMoreData { + self.isTopNoMore = YES; + self.isTopLoading = NO; + [self updateTopStatusView]; +} + +- (void)hideNoMoreData { + self.isTopNoMore = NO; + [self updateTopStatusView]; +} + +- (void)updateTopStatusView { + BOOL shouldShow = self.isTopLoading || self.isTopNoMore; + if (!shouldShow) { + self.topStatusView.hidden = YES; + return; + } + + if (!self.topStatusView) { + self.topStatusView = [[UIView alloc] initWithFrame:CGRectZero]; + self.topStatusView.backgroundColor = [UIColor clearColor]; + self.topStatusView.userInteractionEnabled = NO; + + self.topLoadingIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium]; + self.topLoadingIndicator.hidesWhenStopped = YES; + [self.topStatusView addSubview:self.topLoadingIndicator]; + + self.topStatusLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + self.topStatusLabel.font = [UIFont systemFontOfSize:12]; + self.topStatusLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.8]; + [self.topStatusView addSubview:self.topStatusLabel]; + + [self addSubview:self.topStatusView]; + } + + if (self.isTopLoading) { + self.topStatusLabel.text = KBLocalized(@"Loading..."); + [self.topLoadingIndicator startAnimating]; + } else if (self.isTopNoMore) { + self.topStatusLabel.text = KBLocalized(@"No more data"); + [self.topLoadingIndicator stopAnimating]; + } + + CGFloat width = CGRectGetWidth(self.tableView.bounds); + if (width <= 0) { + width = CGRectGetWidth(self.bounds); + } + CGFloat height = 32; + self.topStatusView.frame = CGRectMake(0, 0, width, height); + self.topStatusView.hidden = NO; + [self bringSubviewToFront:self.topStatusView]; + + CGSize labelSize = [self.topStatusLabel sizeThatFits:CGSizeMake(width - 40, height)]; + CGFloat totalWidth = labelSize.width + (self.isTopLoading ? 20 + 6 : 0); + CGFloat startX = (width - totalWidth) / 2.0; + if (self.isTopLoading) { + self.topLoadingIndicator.frame = CGRectMake(startX, (height - 20) / 2.0, 20, 20); + self.topStatusLabel.frame = CGRectMake(CGRectGetMaxX(self.topLoadingIndicator.frame) + 6, + (height - labelSize.height) / 2.0, + labelSize.width, + labelSize.height); + } else { + self.topStatusLabel.frame = CGRectMake((width - labelSize.width) / 2.0, + (height - labelSize.height) / 2.0, + labelSize.width, + labelSize.height); + } + +} + - (void)scrollToBottom { [self scrollToBottomAnimated:YES]; } diff --git a/keyBoard/Class/AiTalk/V/Chat/KBPersonaChatCell.m b/keyBoard/Class/AiTalk/V/Chat/KBPersonaChatCell.m index a25f209..bc9dc2a 100644 --- a/keyBoard/Class/AiTalk/V/Chat/KBPersonaChatCell.m +++ b/keyBoard/Class/AiTalk/V/Chat/KBPersonaChatCell.m @@ -30,11 +30,6 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe /// 人设名称 @property (nonatomic, strong) UILabel *nameLabel; -/// 开场白 -@property (nonatomic, strong) UILabel *openingLabel; - - - /// 聊天消息 @property (nonatomic, strong) NSMutableArray *messages; @@ -69,6 +64,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe @property (nonatomic, assign) BOOL isCurrentPersonaCell; @property (nonatomic, assign) BOOL shouldAutoPlayPrologueAudio; @property (nonatomic, assign) BOOL hasPlayedPrologueAudio; +@property (nonatomic, assign) BOOL shouldShowOpeningMessage; @end @@ -107,6 +103,8 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe self.isCurrentPersonaCell = NO; self.shouldAutoPlayPrologueAudio = NO; self.hasPlayedPrologueAudio = NO; + self.shouldShowOpeningMessage = NO; + self.shouldShowOpeningMessage = NO; // ✅ 移除了 self.hasLoadedData = NO; // 这样 Cell 复用时不会重复请求数据 @@ -129,14 +127,6 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe make.edges.equalTo(self.contentView); }]; - // 开场白 - [self.contentView addSubview:self.openingLabel]; - [self.openingLabel mas_makeConstraints:^(MASConstraintMaker *make) { - make.top.equalTo(self.contentView).offset(KB_NAV_TOTAL_HEIGHT); - make.left.equalTo(self.contentView).offset(40); - make.right.equalTo(self.contentView).offset(-40); - }]; - // 头像 [self.contentView addSubview:self.avatarImageView]; [self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) { @@ -172,8 +162,9 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe // 聊天列表 [self.contentView addSubview:self.chatView]; + CGFloat topY = KB_STATUSBAR_HEIGHT + 15; [self.chatView mas_makeConstraints:^(MASConstraintMaker *make) { - make.top.equalTo(self.contentView).offset(KB_NAV_TOTAL_HEIGHT); + make.top.equalTo(self.contentView).offset(topY); make.left.right.equalTo(self.contentView); make.bottom.equalTo(self.avatarImageView.mas_top).offset(-10); }]; @@ -219,9 +210,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe placeholderImage:[UIImage imageNamed:@"placeholder_bg"]]; [self.avatarImageView sd_setImageWithURL:[NSURL URLWithString:persona.avatarUrl] placeholderImage:[UIImage imageNamed:@"placeholder_avatar"]]; - self.nameLabel.text = persona.name; - self.openingLabel.text = persona.shortDesc.length > 0 ? persona.shortDesc : persona.prologue; - + self.nameLabel.text = persona.name; // 关键修复:清空消息时停止音频播放,避免状态混乱 [self.chatView stopPlayingAudio]; @@ -232,6 +221,8 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe NSLog(@"[KBPersonaChatCell] contentView.frame: %@", NSStringFromCGRect(self.contentView.frame)); if (self.messages.count > 0) { + self.shouldShowOpeningMessage = NO; + [self removeOpeningMessageIfNeeded]; [self.chatView updateIntroFooterText:nil]; [self ensureOpeningMessageAtTop]; // 同步缓存,避免下次从缓存缺少开场白 @@ -241,8 +232,9 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe keepOffset:NO scrollToBottom:YES]; } else { + self.shouldShowOpeningMessage = NO; [self.chatView clearMessages]; - [self.chatView updateIntroFooterText:persona.prologue]; + [self.chatView updateIntroFooterText:nil]; } NSLog(@"[KBPersonaChatCell] ========== setPersona 结束 =========="); @@ -269,6 +261,10 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe } self.isLoading = YES; + BOOL isLoadMore = (self.currentPage > 1); + if (isLoadMore) { + [self.chatView showTopLoading]; + } if (self.currentPage == 1) { [self.chatView resetNoMoreData]; @@ -291,8 +287,12 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe NSLog(@"[KBPersonaChatCell] 加载聊天记录失败:%@", error.localizedDescription); dispatch_async(dispatch_get_main_queue(), ^{ strongSelf.isLoading = NO; + if (isLoadMore) { + [strongSelf.chatView hideTopLoading]; + } [strongSelf.chatView endLoadMoreWithHasMoreData:strongSelf.hasMoreHistory]; if (strongSelf.currentPage == 1 && strongSelf.persona.prologue.length > 0) { + strongSelf.shouldShowOpeningMessage = YES; [strongSelf showOpeningMessage]; } }); @@ -306,6 +306,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe if (loadedPage == 1) { BOOL isEmpty = (pageModel.total == 0); strongSelf.shouldAutoPlayPrologueAudio = isEmpty && (strongSelf.persona.prologueAudio.length > 0); + strongSelf.shouldShowOpeningMessage = isEmpty; if (!strongSelf.shouldAutoPlayPrologueAudio) { [strongSelf.chatView stopPlayingAudio]; } else { @@ -317,6 +318,8 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe [strongSelf.chatView clearMessages]; [strongSelf.chatView updateIntroFooterText:strongSelf.persona.prologue]; [strongSelf.chatView endLoadMoreWithHasMoreData:strongSelf.hasMoreHistory]; + [strongSelf.chatView hideTopLoading]; + [strongSelf.chatView hideNoMoreData]; strongSelf.isLoading = NO; }); strongSelf.currentPage++; @@ -355,6 +358,9 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe if (loadedPage == 1) { // 第一页,直接赋值 strongSelf.messages = newMessages; + if (!strongSelf.shouldShowOpeningMessage) { + [strongSelf removeOpeningMessageIfNeeded]; + } [strongSelf ensureOpeningMessageAtTop]; } else { // 后续页,继续加载历史 @@ -375,6 +381,9 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe // 刷新 UI dispatch_async(dispatch_get_main_queue(), ^{ + if (isLoadMore) { + [strongSelf.chatView hideTopLoading]; + } if (loadedPage == 1) { NSLog(@"[KBPersonaChatCell] 刷新 UI - loadedPage: %ld, keepOffset: 0, scrollToBottom: 1", (long)loadedPage); @@ -394,6 +403,11 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe } } [strongSelf.chatView endLoadMoreWithHasMoreData:strongSelf.hasMoreHistory]; + if (!strongSelf.hasMoreHistory && strongSelf.messages.count > 0) { + [strongSelf.chatView showNoMoreData]; + } else { + [strongSelf.chatView hideNoMoreData]; + } // ✅ 保存到缓存(包含开场白) [[KBAIChatMessageCacheManager shared] saveMessages:strongSelf.messages @@ -449,6 +463,9 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe } - (void)showOpeningMessage { + if (!self.shouldShowOpeningMessage) { + return; + } if (self.messages.count == 0) { [self.chatView clearMessages]; [self.chatView updateIntroFooterText:self.persona.prologue]; @@ -487,6 +504,9 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe } - (void)ensureOpeningMessageAtTop { + if (!self.shouldShowOpeningMessage) { + return; + } NSString *prologue = [self currentPrologueText]; if (prologue.length == 0) { return; @@ -507,6 +527,14 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe } } +- (void)removeOpeningMessageIfNeeded { + NSInteger index = [self openingMessageIndexInMessages]; + if (index == NSNotFound) { + return; + } + [self.messages removeObjectAtIndex:index]; +} + - (nullable KBAiChatMessage *)openingMessageInMessages { NSInteger index = [self openingMessageIndexInMessages]; if (index == NSNotFound) { @@ -548,6 +576,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe self.shouldAutoPlayPrologueAudio = NO; self.hasPlayedPrologueAudio = NO; + self.shouldShowOpeningMessage = YES; [self.chatView stopPlayingAudio]; // 清空消息数组 @@ -907,17 +936,6 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe return _nameLabel; } -- (UILabel *)openingLabel { - if (!_openingLabel) { - _openingLabel = [[UILabel alloc] init]; - _openingLabel.font = [UIFont systemFontOfSize:14]; - _openingLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.9]; - _openingLabel.textAlignment = NSTextAlignmentCenter; - _openingLabel.numberOfLines = 2; - } - return _openingLabel; -} - - (KBChatTableView *)chatView { if (!_chatView) { _chatView = [[KBChatTableView alloc] init];