This commit is contained in:
2026-01-28 20:18:18 +08:00
parent 70a8466d9f
commit ef52cd4872
7 changed files with 165 additions and 44 deletions

View File

@@ -40,12 +40,12 @@
make.height.mas_equalTo(24); make.height.mas_equalTo(24);
}]; }];
[self.lineView mas_makeConstraints:^(MASConstraintMaker *make) { // [self.lineView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.contentView).offset(16); // make.left.equalTo(self.contentView).offset(16);
make.right.equalTo(self.contentView).offset(-16); // make.right.equalTo(self.contentView).offset(-16);
make.bottom.equalTo(self.contentView); // make.bottom.equalTo(self.contentView);
make.height.mas_equalTo(0.5); // make.height.mas_equalTo(0.5);
}]; // }];
} }
#pragma mark - Configuration #pragma mark - Configuration

View File

@@ -16,6 +16,7 @@
#import "AiVM.h" #import "AiVM.h"
#import <MJExtension/MJExtension.h> #import <MJExtension/MJExtension.h>
#import <Masonry/Masonry.h> #import <Masonry/Masonry.h>
#import <MJRefresh/MJRefresh.h>
static NSString *const kCommentHeaderIdentifier = @"CommentHeader"; static NSString *const kCommentHeaderIdentifier = @"CommentHeader";
static NSString *const kReplyCellIdentifier = @"ReplyCell"; static NSString *const kReplyCellIdentifier = @"ReplyCell";
@@ -33,6 +34,12 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
@property(nonatomic, strong) NSMutableArray<KBAICommentModel *> *comments; @property(nonatomic, strong) NSMutableArray<KBAICommentModel *> *comments;
@property(nonatomic, assign) NSInteger totalCommentCount; @property(nonatomic, assign) NSInteger totalCommentCount;
///
@property(nonatomic, assign) NSInteger currentPage;
@property(nonatomic, assign) NSInteger pageSize;
@property(nonatomic, assign) BOOL isLoading;
@property(nonatomic, assign) BOOL hasMoreData;
/// ///
@property(nonatomic, assign) CGFloat keyboardHeight; @property(nonatomic, assign) CGFloat keyboardHeight;
/// ///
@@ -56,6 +63,9 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
self = [super initWithFrame:frame]; self = [super initWithFrame:frame];
if (self) { if (self) {
self.comments = [NSMutableArray array]; self.comments = [NSMutableArray array];
self.currentPage = 1;
self.pageSize = 20;
self.hasMoreData = YES;
[self setupUI]; [self setupUI];
[self setupKeyboardObservers]; [self setupKeyboardObservers];
} }
@@ -114,6 +124,20 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
make.left.right.equalTo(self); make.left.right.equalTo(self);
make.bottom.equalTo(self.inputView.mas_top); make.bottom.equalTo(self.inputView.mas_top);
}]; }];
//
__weak typeof(self) weakSelf = self;
MJRefreshAutoNormalFooter *footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
[strongSelf loadMoreComments];
}];
footer.stateLabel.hidden = YES;
footer.backgroundColor = [UIColor clearColor];
footer.automaticallyHidden = YES;
self.tableView.mj_footer = footer;
} }
#pragma mark - Keyboard Observers #pragma mark - Keyboard Observers
@@ -174,49 +198,87 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
#pragma mark - Data Loading #pragma mark - Data Loading
- (void)loadComments { - (void)loadComments {
if (self.companionId <= 0) { if (self.isLoading) {
NSLog(@"[KBAICommentView] companionId 未设置,无法加载评论");
[self showEmptyState];
return; return;
} }
self.currentPage = 1;
self.hasMoreData = YES;
[self.tableView.mj_footer resetNoMoreData];
[self fetchCommentsAtPage:self.currentPage append:NO];
}
- (void)loadMoreComments {
if (self.isLoading) {
[self.tableView.mj_footer endRefreshing];
return;
}
if (!self.hasMoreData) {
[self.tableView.mj_footer endRefreshingWithNoMoreData];
return;
}
NSInteger nextPage = self.currentPage + 1;
[self fetchCommentsAtPage:nextPage append:YES];
}
- (void)fetchCommentsAtPage:(NSInteger)page append:(BOOL)append {
if (self.companionId <= 0) {
NSLog(@"[KBAICommentView] companionId 未设置,无法加载评论");
[self showEmptyState];
[self.tableView.mj_footer endRefreshing];
return;
}
self.isLoading = YES;
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
[self.aiVM fetchCommentsWithCompanionId:self.companionId [self.aiVM fetchCommentsWithCompanionId:self.companionId
pageNum:1 pageNum:page
pageSize:20 pageSize:self.pageSize
completion:^(KBCommentPageModel *pageModel, NSError *error) { completion:^(KBCommentPageModel *pageModel, NSError *error) {
__strong typeof(weakSelf) strongSelf = weakSelf; __strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) { if (!strongSelf) {
return; return;
} }
strongSelf.isLoading = NO;
if (error) { if (error) {
NSLog(@"[KBAICommentView] 加载评论失败:%@", error.localizedDescription); NSLog(@"[KBAICommentView] 加载评论失败:%@", error.localizedDescription);
//
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if (append) {
[strongSelf.tableView.mj_footer endRefreshing];
} else {
[strongSelf showEmptyStateWithError]; [strongSelf showEmptyStateWithError];
}
}); });
return; return;
} }
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[strongSelf updateCommentsWithPageModel:pageModel]; [strongSelf updateCommentsWithPageModel:pageModel append:append];
}); });
}]; }];
} }
/// KBCommentPageModel UI KBAICommentModel /// KBCommentPageModel UI KBAICommentModel
- (void)updateCommentsWithPageModel:(KBCommentPageModel *)pageModel { - (void)updateCommentsWithPageModel:(KBCommentPageModel *)pageModel append:(BOOL)append {
if (!pageModel) { if (!pageModel) {
NSLog(@"[KBAICommentView] pageModel 为空"); NSLog(@"[KBAICommentView] pageModel 为空");
// //
[self showEmptyState]; [self showEmptyState];
[self.tableView.mj_footer endRefreshing];
return; return;
} }
self.totalCommentCount = pageModel.total; self.totalCommentCount = pageModel.total;
if (!append) {
[self.comments removeAllObjects]; [self.comments removeAllObjects];
}
// tableView // tableView
CGFloat tableWidth = self.tableView.bounds.size.width; CGFloat tableWidth = self.tableView.bounds.size.width;
@@ -224,7 +286,7 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
tableWidth = [UIScreen mainScreen].bounds.size.width; tableWidth = [UIScreen mainScreen].bounds.size.width;
} }
NSLog(@"[KBAICommentView] 加载到 %ld 条评论,共 %ld 条", (long)pageModel.records.count, (long)pageModel.total); NSLog(@"[KBAICommentView] 加载到 %ld 条评论,共 %ld 条,页码:%ld/%ld", (long)pageModel.records.count, (long)pageModel.total, (long)pageModel.current, (long)pageModel.pages);
for (KBCommentItem *item in pageModel.records) { for (KBCommentItem *item in pageModel.records) {
// KBAICommentModel使 MJExtension // KBAICommentModel使 MJExtension
@@ -244,6 +306,20 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
[self updateTitle]; [self updateTitle];
[self.tableView reloadData]; [self.tableView reloadData];
//
self.currentPage = pageModel.current > 0 ? pageModel.current : self.currentPage;
if (pageModel.pages > 0) {
self.hasMoreData = pageModel.current < pageModel.pages;
} else {
self.hasMoreData = pageModel.records.count >= self.pageSize;
}
if (self.hasMoreData) {
[self.tableView.mj_footer endRefreshing];
} else {
[self.tableView.mj_footer endRefreshingWithNoMoreData];
}
// //
if (self.comments.count == 0) { if (self.comments.count == 0) {
[self showEmptyState]; [self showEmptyState];
@@ -641,6 +717,7 @@ static NSInteger const kRepliesLoadCount = 5;
_tableView.backgroundColor = [UIColor clearColor]; _tableView.backgroundColor = [UIColor clearColor];
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone; _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag; _tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
_tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1, 0.01)];
// "暂无数据" // "暂无数据"
_tableView.useEmptyDataSet = NO; _tableView.useEmptyDataSet = NO;

View File

@@ -51,7 +51,7 @@
[self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) { [self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.contentView).offset(68); // 16 + 40 + 12 = 68 make.left.equalTo(self.contentView).offset(68); // 16 + 40 + 12 = 68
make.top.equalTo(self.contentView).offset(8); make.top.equalTo(self.contentView).offset(8);
make.width.height.mas_equalTo(28); make.width.height.mas_equalTo(26);
}]; }];
[self.userNameLabel mas_makeConstraints:^(MASConstraintMaker *make) { [self.userNameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
@@ -171,7 +171,7 @@
if (!_avatarImageView) { if (!_avatarImageView) {
_avatarImageView = [[UIImageView alloc] init]; _avatarImageView = [[UIImageView alloc] init];
_avatarImageView.contentMode = UIViewContentModeScaleAspectFill; _avatarImageView.contentMode = UIViewContentModeScaleAspectFill;
_avatarImageView.layer.cornerRadius = 14; _avatarImageView.layer.cornerRadius = 13;
_avatarImageView.layer.masksToBounds = YES; _avatarImageView.layer.masksToBounds = YES;
_avatarImageView.backgroundColor = [UIColor systemGray5Color]; _avatarImageView.backgroundColor = [UIColor systemGray5Color];
} }
@@ -182,7 +182,7 @@
if (!_userNameLabel) { if (!_userNameLabel) {
_userNameLabel = [[UILabel alloc] init]; _userNameLabel = [[UILabel alloc] init];
_userNameLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightMedium]; _userNameLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightMedium];
_userNameLabel.textColor = [UIColor secondaryLabelColor]; _userNameLabel.textColor = [UIColor colorWithHex:0x9F9F9F];
_userNameLabel.numberOfLines = 0; _userNameLabel.numberOfLines = 0;
} }
return _userNameLabel; return _userNameLabel;
@@ -191,8 +191,8 @@
- (UILabel *)contentLabel { - (UILabel *)contentLabel {
if (!_contentLabel) { if (!_contentLabel) {
_contentLabel = [[UILabel alloc] init]; _contentLabel = [[UILabel alloc] init];
_contentLabel.font = [UIFont systemFontOfSize:14]; _contentLabel.font = [UIFont systemFontOfSize:12];
_contentLabel.textColor = [UIColor labelColor]; _contentLabel.textColor = [UIColor whiteColor];
_contentLabel.numberOfLines = 0; _contentLabel.numberOfLines = 0;
} }
return _contentLabel; return _contentLabel;
@@ -201,8 +201,8 @@
- (UILabel *)timeLabel { - (UILabel *)timeLabel {
if (!_timeLabel) { if (!_timeLabel) {
_timeLabel = [[UILabel alloc] init]; _timeLabel = [[UILabel alloc] init];
_timeLabel.font = [UIFont systemFontOfSize:11]; _timeLabel.font = [UIFont systemFontOfSize:12];
_timeLabel.textColor = [UIColor secondaryLabelColor]; _timeLabel.textColor = [UIColor colorWithHex:0x9F9F9F];
} }
return _timeLabel; return _timeLabel;
} }

View File

@@ -83,7 +83,8 @@
[self.voiceButton mas_makeConstraints:^(MASConstraintMaker *make) { [self.voiceButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.contentView).offset(16); make.left.equalTo(self.contentView).offset(16);
make.top.equalTo(self.contentView).offset(8); make.top.equalTo(self.contentView).offset(8);
make.width.height.mas_equalTo(24); // make.width.height.mas_equalTo(24);
make.width.height.mas_equalTo(0);
}]; }];
[self.durationLabel mas_makeConstraints:^(MASConstraintMaker *make) { [self.durationLabel mas_makeConstraints:^(MASConstraintMaker *make) {
@@ -105,8 +106,8 @@
// messageLabel AutoLayout bubbleView // messageLabel AutoLayout bubbleView
[self.messageLabel mas_makeConstraints:^(MASConstraintMaker *make) { [self.messageLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.bubbleView).offset(10); make.top.equalTo(self.bubbleView).offset(5);
make.bottom.equalTo(self.bubbleView).offset(-10).priority(999); // make.bottom.equalTo(self.bubbleView).offset(-5).priority(999); //
make.left.equalTo(self.bubbleView).offset(12); make.left.equalTo(self.bubbleView).offset(12);
make.right.equalTo(self.bubbleView).offset(-12); make.right.equalTo(self.bubbleView).offset(-12);
// messageLabel 0 // messageLabel 0

View File

@@ -38,8 +38,8 @@
// //
[self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) { [self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.contentView).offset(8); make.top.equalTo(self.contentView).offset(1);
make.bottom.equalTo(self.contentView).offset(-8); make.bottom.equalTo(self.contentView).offset(-1);
make.centerX.equalTo(self.contentView); make.centerX.equalTo(self.contentView);
}]; }];
} }

View File

@@ -202,8 +202,14 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
// //
[self.chatView stopPlayingAudio]; [self.chatView stopPlayingAudio];
// //
[self ensureOpeningMessageAtTop];
//
if (self.messages.count > 0) { if (self.messages.count > 0) {
//
[[KBAIChatMessageCacheManager shared] saveMessages:self.messages
forCompanionId:persona.personaId];
[self.chatView reloadWithMessages:self.messages [self.chatView reloadWithMessages:self.messages
keepOffset:NO keepOffset:NO
scrollToBottom:YES]; scrollToBottom:YES];
@@ -287,15 +293,20 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
[newMessages addObject:message]; [newMessages addObject:message];
} }
// //
if (weakSelf.currentPage == 1) { if (weakSelf.currentPage == 1) {
// //
weakSelf.messages = newMessages; weakSelf.messages = newMessages;
[weakSelf ensureOpeningMessageAtTop];
} else { } else {
// //
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newMessages.count)]; [weakSelf ensureOpeningMessageAtTop];
if (newMessages.count > 0) {
NSUInteger insertIndex = [weakSelf hasOpeningMessageAtTop] ? 1 : 0;
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(insertIndex, newMessages.count)];
[weakSelf.messages insertObjects:newMessages atIndexes:indexSet]; [weakSelf.messages insertObjects:newMessages atIndexes:indexSet];
} }
}
// UI // UI
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
@@ -306,7 +317,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
scrollToBottom:scrollToBottom]; scrollToBottom:scrollToBottom];
[weakSelf.chatView endLoadMoreWithHasMoreData:weakSelf.hasMoreHistory]; [weakSelf.chatView endLoadMoreWithHasMoreData:weakSelf.hasMoreHistory];
// //
[[KBAIChatMessageCacheManager shared] saveMessages:weakSelf.messages [[KBAIChatMessageCacheManager shared] saveMessages:weakSelf.messages
forCompanionId:companionId]; forCompanionId:companionId];
}); });
@@ -332,10 +343,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
- (void)showOpeningMessage { - (void)showOpeningMessage {
// //
KBAiChatMessage *openingMsg = [KBAiChatMessage assistantMessageWithText:self.persona.introText]; [self ensureOpeningMessageAtTop];
openingMsg.isComplete = YES;
openingMsg.needsTypewriterEffect = NO;
[self.messages addObject:openingMsg];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.chatView reloadWithMessages:self.messages [self.chatView reloadWithMessages:self.messages
@@ -344,6 +352,41 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
}); });
} }
- (BOOL)hasOpeningMessageAtTop {
if (self.messages.count == 0) {
return NO;
}
return [self isOpeningMessage:self.messages.firstObject];
}
- (BOOL)isOpeningMessage:(KBAiChatMessage *)message {
if (!message) {
return NO;
}
NSString *introText = self.persona.introText ?: @"";
if (introText.length == 0) {
return NO;
}
return (message.type == KBAiChatMessageTypeAssistant) && [message.text isEqualToString:introText];
}
- (void)ensureOpeningMessageAtTop {
NSString *introText = self.persona.introText ?: @"";
if (introText.length == 0) {
return;
}
if (!self.messages) {
self.messages = [NSMutableArray array];
}
if ([self hasOpeningMessageAtTop]) {
return;
}
KBAiChatMessage *openingMsg = [KBAiChatMessage assistantMessageWithText:introText];
openingMsg.isComplete = YES;
openingMsg.needsTypewriterEffect = NO;
[self.messages insertObject:openingMsg atIndex:0];
}
#pragma mark - #pragma mark -
/// ///
@@ -368,11 +411,9 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
// //
[self.chatView clearMessages]; [self.chatView clearMessages];
// //
if (self.persona.introText.length > 0) {
[self showOpeningMessage]; [self showOpeningMessage];
} }
}
} }
#pragma mark - 3 #pragma mark - 3
@@ -386,6 +427,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
self.messages = [NSMutableArray array]; self.messages = [NSMutableArray array];
} }
[self ensureOpeningMessageAtTop];
KBAiChatMessage *message = [KBAiChatMessage userMessageWithText:text]; KBAiChatMessage *message = [KBAiChatMessage userMessageWithText:text];
[self.messages addObject:message]; [self.messages addObject:message];
[self.chatView addMessage:message autoScroll:YES]; [self.chatView addMessage:message autoScroll:YES];
@@ -401,6 +443,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
self.messages = [NSMutableArray array]; self.messages = [NSMutableArray array];
} }
[self ensureOpeningMessageAtTop];
KBAiChatMessage *message = [KBAiChatMessage assistantMessageWithText:text KBAiChatMessage *message = [KBAiChatMessage assistantMessageWithText:text
audioId:audioId]; audioId:audioId];
message.needsTypewriterEffect = YES; message.needsTypewriterEffect = YES;

View File

@@ -242,7 +242,7 @@ autoShowBusinessError:NO
[[KBNetworkManager shared] uploadFile:API_AI_AUDIO_UPLOAD [[KBNetworkManager shared] uploadFile:API_AI_AUDIO_UPLOAD
fileURL:fileURL fileURL:fileURL
name:@"file" name:@"file"
mimeType:@"audio/mp4" mimeType:@"audio/m4a"
parameters:nil parameters:nil
headers:nil headers:nil
completion:^(NSDictionary *_Nullable json, completion:^(NSDictionary *_Nullable json,