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);
}];
[self.lineView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.contentView).offset(16);
make.right.equalTo(self.contentView).offset(-16);
make.bottom.equalTo(self.contentView);
make.height.mas_equalTo(0.5);
}];
// [self.lineView mas_makeConstraints:^(MASConstraintMaker *make) {
// make.left.equalTo(self.contentView).offset(16);
// make.right.equalTo(self.contentView).offset(-16);
// make.bottom.equalTo(self.contentView);
// make.height.mas_equalTo(0.5);
// }];
}
#pragma mark - Configuration

View File

@@ -16,6 +16,7 @@
#import "AiVM.h"
#import <MJExtension/MJExtension.h>
#import <Masonry/Masonry.h>
#import <MJRefresh/MJRefresh.h>
static NSString *const kCommentHeaderIdentifier = @"CommentHeader";
static NSString *const kReplyCellIdentifier = @"ReplyCell";
@@ -33,6 +34,12 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
@property(nonatomic, strong) NSMutableArray<KBAICommentModel *> *comments;
@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;
///
@@ -56,6 +63,9 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
self = [super initWithFrame:frame];
if (self) {
self.comments = [NSMutableArray array];
self.currentPage = 1;
self.pageSize = 20;
self.hasMoreData = YES;
[self setupUI];
[self setupKeyboardObservers];
}
@@ -114,6 +124,20 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
make.left.right.equalTo(self);
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
@@ -174,49 +198,87 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
#pragma mark - Data Loading
- (void)loadComments {
if (self.isLoading) {
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;
[self.aiVM fetchCommentsWithCompanionId:self.companionId
pageNum:1
pageSize:20
pageNum:page
pageSize:self.pageSize
completion:^(KBCommentPageModel *pageModel, NSError *error) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
strongSelf.isLoading = NO;
if (error) {
NSLog(@"[KBAICommentView] 加载评论失败:%@", error.localizedDescription);
//
dispatch_async(dispatch_get_main_queue(), ^{
[strongSelf showEmptyStateWithError];
if (append) {
[strongSelf.tableView.mj_footer endRefreshing];
} else {
[strongSelf showEmptyStateWithError];
}
});
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[strongSelf updateCommentsWithPageModel:pageModel];
[strongSelf updateCommentsWithPageModel:pageModel append:append];
});
}];
}
/// KBCommentPageModel UI KBAICommentModel
- (void)updateCommentsWithPageModel:(KBCommentPageModel *)pageModel {
- (void)updateCommentsWithPageModel:(KBCommentPageModel *)pageModel append:(BOOL)append {
if (!pageModel) {
NSLog(@"[KBAICommentView] pageModel 为空");
//
[self showEmptyState];
[self.tableView.mj_footer endRefreshing];
return;
}
self.totalCommentCount = pageModel.total;
[self.comments removeAllObjects];
if (!append) {
[self.comments removeAllObjects];
}
// tableView
CGFloat tableWidth = self.tableView.bounds.size.width;
@@ -224,7 +286,7 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
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) {
// KBAICommentModel使 MJExtension
@@ -243,6 +305,20 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
[self updateTitle];
[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) {
@@ -641,6 +717,7 @@ static NSInteger const kRepliesLoadCount = 5;
_tableView.backgroundColor = [UIColor clearColor];
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
_tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1, 0.01)];
// "暂无数据"
_tableView.useEmptyDataSet = NO;

View File

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

View File

@@ -83,7 +83,8 @@
[self.voiceButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.contentView).offset(16);
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) {
@@ -105,8 +106,8 @@
// messageLabel AutoLayout bubbleView
[self.messageLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.bubbleView).offset(10);
make.bottom.equalTo(self.bubbleView).offset(-10).priority(999); //
make.top.equalTo(self.bubbleView).offset(5);
make.bottom.equalTo(self.bubbleView).offset(-5).priority(999); //
make.left.equalTo(self.bubbleView).offset(12);
make.right.equalTo(self.bubbleView).offset(-12);
// messageLabel 0

View File

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

View File

@@ -202,8 +202,14 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
//
[self.chatView stopPlayingAudio];
//
//
[self ensureOpeningMessageAtTop];
//
if (self.messages.count > 0) {
//
[[KBAIChatMessageCacheManager shared] saveMessages:self.messages
forCompanionId:persona.personaId];
[self.chatView reloadWithMessages:self.messages
keepOffset:NO
scrollToBottom:YES];
@@ -287,14 +293,19 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
[newMessages addObject:message];
}
//
//
if (weakSelf.currentPage == 1) {
//
weakSelf.messages = newMessages;
[weakSelf ensureOpeningMessageAtTop];
} else {
//
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newMessages.count)];
[weakSelf.messages insertObjects:newMessages atIndexes:indexSet];
//
[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];
}
}
// UI
@@ -306,8 +317,8 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
scrollToBottom:scrollToBottom];
[weakSelf.chatView endLoadMoreWithHasMoreData:weakSelf.hasMoreHistory];
//
[[KBAIChatMessageCacheManager shared] saveMessages:weakSelf.messages
//
[[KBAIChatMessageCacheManager shared] saveMessages:weakSelf.messages
forCompanionId:companionId];
});
@@ -332,10 +343,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
- (void)showOpeningMessage {
//
KBAiChatMessage *openingMsg = [KBAiChatMessage assistantMessageWithText:self.persona.introText];
openingMsg.isComplete = YES;
openingMsg.needsTypewriterEffect = NO;
[self.messages addObject:openingMsg];
[self ensureOpeningMessageAtTop];
dispatch_async(dispatch_get_main_queue(), ^{
[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 -
///
@@ -368,10 +411,8 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
//
[self.chatView clearMessages];
//
if (self.persona.introText.length > 0) {
[self showOpeningMessage];
}
//
[self showOpeningMessage];
}
}
@@ -386,13 +427,14 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
self.messages = [NSMutableArray array];
}
[self ensureOpeningMessageAtTop];
KBAiChatMessage *message = [KBAiChatMessage userMessageWithText:text];
[self.messages addObject:message];
[self.chatView addMessage:message autoScroll:YES];
}
- (void)appendAssistantMessage:(NSString *)text
audioId:(NSString *)audioId {
audioId:(NSString *)audioId {
if (text.length == 0) {
return;
}
@@ -401,6 +443,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
self.messages = [NSMutableArray array];
}
[self ensureOpeningMessageAtTop];
KBAiChatMessage *message = [KBAiChatMessage assistantMessageWithText:text
audioId:audioId];
message.needsTypewriterEffect = YES;

View File

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