This commit is contained in:
2026-02-02 20:36:38 +08:00
parent f1b52151be
commit 6e50cdcd2a
3 changed files with 143 additions and 29 deletions

View File

@@ -63,6 +63,14 @@ NS_ASSUME_NONNULL_BEGIN
/// 移除 loading 用户消息 /// 移除 loading 用户消息
- (void)removeLoadingUserMessage; - (void)removeLoadingUserMessage;
/// 顶部加载中提示
- (void)showTopLoading;
- (void)hideTopLoading;
/// 顶部“无更多数据”提示
- (void)showNoMoreData;
- (void)hideNoMoreData;
/// 滚动到底部 /// 滚动到底部
- (void)scrollToBottom; - (void)scrollToBottom;

View File

@@ -37,6 +37,11 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
@property (nonatomic, assign) CGSize lastIntroFooterTableSize; @property (nonatomic, assign) CGSize lastIntroFooterTableSize;
@property (nonatomic, assign) BOOL applyingIntroFooter; @property (nonatomic, assign) BOOL applyingIntroFooter;
@property (nonatomic, copy) NSString *remoteAudioToken; @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 @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 { - (void)scrollToBottom {
[self scrollToBottomAnimated:YES]; [self scrollToBottomAnimated:YES];
} }

View File

@@ -30,11 +30,6 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
/// ///
@property (nonatomic, strong) UILabel *nameLabel; @property (nonatomic, strong) UILabel *nameLabel;
///
@property (nonatomic, strong) UILabel *openingLabel;
/// ///
@property (nonatomic, strong) NSMutableArray<KBAiChatMessage *> *messages; @property (nonatomic, strong) NSMutableArray<KBAiChatMessage *> *messages;
@@ -69,6 +64,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
@property (nonatomic, assign) BOOL isCurrentPersonaCell; @property (nonatomic, assign) BOOL isCurrentPersonaCell;
@property (nonatomic, assign) BOOL shouldAutoPlayPrologueAudio; @property (nonatomic, assign) BOOL shouldAutoPlayPrologueAudio;
@property (nonatomic, assign) BOOL hasPlayedPrologueAudio; @property (nonatomic, assign) BOOL hasPlayedPrologueAudio;
@property (nonatomic, assign) BOOL shouldShowOpeningMessage;
@end @end
@@ -107,6 +103,8 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
self.isCurrentPersonaCell = NO; self.isCurrentPersonaCell = NO;
self.shouldAutoPlayPrologueAudio = NO; self.shouldAutoPlayPrologueAudio = NO;
self.hasPlayedPrologueAudio = NO; self.hasPlayedPrologueAudio = NO;
self.shouldShowOpeningMessage = NO;
self.shouldShowOpeningMessage = NO;
// self.hasLoadedData = NO; // self.hasLoadedData = NO;
// Cell // Cell
@@ -129,14 +127,6 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
make.edges.equalTo(self.contentView); 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.contentView addSubview:self.avatarImageView];
[self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) { [self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) {
@@ -172,8 +162,9 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
// //
[self.contentView addSubview:self.chatView]; [self.contentView addSubview:self.chatView];
CGFloat topY = KB_STATUSBAR_HEIGHT + 15;
[self.chatView mas_makeConstraints:^(MASConstraintMaker *make) { [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.left.right.equalTo(self.contentView);
make.bottom.equalTo(self.avatarImageView.mas_top).offset(-10); make.bottom.equalTo(self.avatarImageView.mas_top).offset(-10);
}]; }];
@@ -220,8 +211,6 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
[self.avatarImageView sd_setImageWithURL:[NSURL URLWithString:persona.avatarUrl] [self.avatarImageView sd_setImageWithURL:[NSURL URLWithString:persona.avatarUrl]
placeholderImage:[UIImage imageNamed:@"placeholder_avatar"]]; placeholderImage:[UIImage imageNamed:@"placeholder_avatar"]];
self.nameLabel.text = persona.name; self.nameLabel.text = persona.name;
self.openingLabel.text = persona.shortDesc.length > 0 ? persona.shortDesc : persona.prologue;
// //
[self.chatView stopPlayingAudio]; [self.chatView stopPlayingAudio];
@@ -232,6 +221,8 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
NSLog(@"[KBPersonaChatCell] contentView.frame: %@", NSStringFromCGRect(self.contentView.frame)); NSLog(@"[KBPersonaChatCell] contentView.frame: %@", NSStringFromCGRect(self.contentView.frame));
if (self.messages.count > 0) { if (self.messages.count > 0) {
self.shouldShowOpeningMessage = NO;
[self removeOpeningMessageIfNeeded];
[self.chatView updateIntroFooterText:nil]; [self.chatView updateIntroFooterText:nil];
[self ensureOpeningMessageAtTop]; [self ensureOpeningMessageAtTop];
// //
@@ -241,8 +232,9 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
keepOffset:NO keepOffset:NO
scrollToBottom:YES]; scrollToBottom:YES];
} else { } else {
self.shouldShowOpeningMessage = NO;
[self.chatView clearMessages]; [self.chatView clearMessages];
[self.chatView updateIntroFooterText:persona.prologue]; [self.chatView updateIntroFooterText:nil];
} }
NSLog(@"[KBPersonaChatCell] ========== setPersona 结束 =========="); NSLog(@"[KBPersonaChatCell] ========== setPersona 结束 ==========");
@@ -269,6 +261,10 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
} }
self.isLoading = YES; self.isLoading = YES;
BOOL isLoadMore = (self.currentPage > 1);
if (isLoadMore) {
[self.chatView showTopLoading];
}
if (self.currentPage == 1) { if (self.currentPage == 1) {
[self.chatView resetNoMoreData]; [self.chatView resetNoMoreData];
@@ -291,8 +287,12 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
NSLog(@"[KBPersonaChatCell] 加载聊天记录失败:%@", error.localizedDescription); NSLog(@"[KBPersonaChatCell] 加载聊天记录失败:%@", error.localizedDescription);
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
strongSelf.isLoading = NO; strongSelf.isLoading = NO;
if (isLoadMore) {
[strongSelf.chatView hideTopLoading];
}
[strongSelf.chatView endLoadMoreWithHasMoreData:strongSelf.hasMoreHistory]; [strongSelf.chatView endLoadMoreWithHasMoreData:strongSelf.hasMoreHistory];
if (strongSelf.currentPage == 1 && strongSelf.persona.prologue.length > 0) { if (strongSelf.currentPage == 1 && strongSelf.persona.prologue.length > 0) {
strongSelf.shouldShowOpeningMessage = YES;
[strongSelf showOpeningMessage]; [strongSelf showOpeningMessage];
} }
}); });
@@ -306,6 +306,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
if (loadedPage == 1) { if (loadedPage == 1) {
BOOL isEmpty = (pageModel.total == 0); BOOL isEmpty = (pageModel.total == 0);
strongSelf.shouldAutoPlayPrologueAudio = isEmpty && (strongSelf.persona.prologueAudio.length > 0); strongSelf.shouldAutoPlayPrologueAudio = isEmpty && (strongSelf.persona.prologueAudio.length > 0);
strongSelf.shouldShowOpeningMessage = isEmpty;
if (!strongSelf.shouldAutoPlayPrologueAudio) { if (!strongSelf.shouldAutoPlayPrologueAudio) {
[strongSelf.chatView stopPlayingAudio]; [strongSelf.chatView stopPlayingAudio];
} else { } else {
@@ -317,6 +318,8 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
[strongSelf.chatView clearMessages]; [strongSelf.chatView clearMessages];
[strongSelf.chatView updateIntroFooterText:strongSelf.persona.prologue]; [strongSelf.chatView updateIntroFooterText:strongSelf.persona.prologue];
[strongSelf.chatView endLoadMoreWithHasMoreData:strongSelf.hasMoreHistory]; [strongSelf.chatView endLoadMoreWithHasMoreData:strongSelf.hasMoreHistory];
[strongSelf.chatView hideTopLoading];
[strongSelf.chatView hideNoMoreData];
strongSelf.isLoading = NO; strongSelf.isLoading = NO;
}); });
strongSelf.currentPage++; strongSelf.currentPage++;
@@ -355,6 +358,9 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
if (loadedPage == 1) { if (loadedPage == 1) {
// //
strongSelf.messages = newMessages; strongSelf.messages = newMessages;
if (!strongSelf.shouldShowOpeningMessage) {
[strongSelf removeOpeningMessageIfNeeded];
}
[strongSelf ensureOpeningMessageAtTop]; [strongSelf ensureOpeningMessageAtTop];
} else { } else {
// //
@@ -375,6 +381,9 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
// UI // UI
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if (isLoadMore) {
[strongSelf.chatView hideTopLoading];
}
if (loadedPage == 1) { if (loadedPage == 1) {
NSLog(@"[KBPersonaChatCell] 刷新 UI - loadedPage: %ld, keepOffset: 0, scrollToBottom: 1", NSLog(@"[KBPersonaChatCell] 刷新 UI - loadedPage: %ld, keepOffset: 0, scrollToBottom: 1",
(long)loadedPage); (long)loadedPage);
@@ -394,6 +403,11 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
} }
} }
[strongSelf.chatView endLoadMoreWithHasMoreData:strongSelf.hasMoreHistory]; [strongSelf.chatView endLoadMoreWithHasMoreData:strongSelf.hasMoreHistory];
if (!strongSelf.hasMoreHistory && strongSelf.messages.count > 0) {
[strongSelf.chatView showNoMoreData];
} else {
[strongSelf.chatView hideNoMoreData];
}
// //
[[KBAIChatMessageCacheManager shared] saveMessages:strongSelf.messages [[KBAIChatMessageCacheManager shared] saveMessages:strongSelf.messages
@@ -449,6 +463,9 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
} }
- (void)showOpeningMessage { - (void)showOpeningMessage {
if (!self.shouldShowOpeningMessage) {
return;
}
if (self.messages.count == 0) { if (self.messages.count == 0) {
[self.chatView clearMessages]; [self.chatView clearMessages];
[self.chatView updateIntroFooterText:self.persona.prologue]; [self.chatView updateIntroFooterText:self.persona.prologue];
@@ -487,6 +504,9 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
} }
- (void)ensureOpeningMessageAtTop { - (void)ensureOpeningMessageAtTop {
if (!self.shouldShowOpeningMessage) {
return;
}
NSString *prologue = [self currentPrologueText]; NSString *prologue = [self currentPrologueText];
if (prologue.length == 0) { if (prologue.length == 0) {
return; 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 { - (nullable KBAiChatMessage *)openingMessageInMessages {
NSInteger index = [self openingMessageIndexInMessages]; NSInteger index = [self openingMessageIndexInMessages];
if (index == NSNotFound) { if (index == NSNotFound) {
@@ -548,6 +576,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
self.shouldAutoPlayPrologueAudio = NO; self.shouldAutoPlayPrologueAudio = NO;
self.hasPlayedPrologueAudio = NO; self.hasPlayedPrologueAudio = NO;
self.shouldShowOpeningMessage = YES;
[self.chatView stopPlayingAudio]; [self.chatView stopPlayingAudio];
// //
@@ -907,17 +936,6 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
return _nameLabel; 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 { - (KBChatTableView *)chatView {
if (!_chatView) { if (!_chatView) {
_chatView = [[KBChatTableView alloc] init]; _chatView = [[KBChatTableView alloc] init];