Files
keyboard/keyBoard/Class/AiTalk/V/KBPersonaChatCell.m

361 lines
11 KiB
Mathematica
Raw Normal View History

2026-01-26 16:53:41 +08:00
//
// KBPersonaChatCell.m
// keyBoard
//
// Created by Kiro on 2026/1/26.
//
#import "KBPersonaChatCell.h"
#import "KBChatTableView.h"
#import "KBAiChatMessage.h"
2026-01-26 18:17:02 +08:00
#import "KBChatHistoryPageModel.h"
#import "AiVM.h"
2026-01-26 16:53:41 +08:00
#import <Masonry/Masonry.h>
#import <SDWebImage/SDWebImage.h>
2026-01-26 20:36:51 +08:00
@interface KBPersonaChatCell () <KBChatTableViewDelegate>
2026-01-26 16:53:41 +08:00
///
@property (nonatomic, strong) UIImageView *backgroundImageView;
///
@property (nonatomic, strong) UIImageView *avatarImageView;
///
@property (nonatomic, strong) UILabel *nameLabel;
///
@property (nonatomic, strong) UILabel *openingLabel;
///
2026-01-26 20:36:51 +08:00
@property (nonatomic, strong) KBChatTableView *chatView;
2026-01-26 16:53:41 +08:00
///
@property (nonatomic, strong) NSMutableArray<KBAiChatMessage *> *messages;
///
@property (nonatomic, assign) BOOL hasLoadedData;
2026-01-26 18:17:02 +08:00
///
@property (nonatomic, assign) BOOL isLoading;
///
@property (nonatomic, assign) NSInteger currentPage;
///
@property (nonatomic, assign) BOOL hasMoreHistory;
/// AiVM
@property (nonatomic, strong) AiVM *aiVM;
2026-01-26 16:53:41 +08:00
@end
@implementation KBPersonaChatCell
#pragma mark - Lifecycle
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setupUI];
}
return self;
}
#pragma mark - 1
- (void)setupUI {
//
[self.contentView addSubview:self.backgroundImageView];
[self.backgroundImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.contentView);
}];
//
UIView *maskView = [[UIView alloc] init];
maskView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.3];
[self.contentView addSubview:maskView];
[maskView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.contentView);
}];
//
[self.contentView addSubview:self.avatarImageView];
[self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.contentView).offset(80);
make.centerX.equalTo(self.contentView);
make.size.mas_equalTo(CGSizeMake(80, 80));
}];
//
[self.contentView addSubview:self.nameLabel];
[self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.avatarImageView.mas_bottom).offset(12);
make.centerX.equalTo(self.contentView);
}];
//
[self.contentView addSubview:self.openingLabel];
[self.openingLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.nameLabel.mas_bottom).offset(8);
make.left.equalTo(self.contentView).offset(40);
make.right.equalTo(self.contentView).offset(-40);
}];
//
2026-01-26 20:36:51 +08:00
[self.contentView addSubview:self.chatView];
[self.chatView mas_makeConstraints:^(MASConstraintMaker *make) {
2026-01-26 16:53:41 +08:00
make.top.equalTo(self.openingLabel.mas_bottom).offset(30);
make.left.right.bottom.equalTo(self.contentView);
}];
}
#pragma mark - Setter
- (void)setPersona:(KBPersonaModel *)persona {
_persona = persona;
//
self.hasLoadedData = NO;
2026-01-26 18:17:02 +08:00
self.isLoading = NO;
self.currentPage = 1;
self.hasMoreHistory = YES;
2026-01-26 16:53:41 +08:00
self.messages = [NSMutableArray array];
2026-01-26 18:17:02 +08:00
self.aiVM = [[AiVM alloc] init];
2026-01-26 16:53:41 +08:00
// UI
[self.backgroundImageView sd_setImageWithURL:[NSURL URLWithString:persona.coverImageUrl]
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.introText;
2026-01-26 20:36:51 +08:00
[self.chatView clearMessages];
2026-01-26 16:53:41 +08:00
}
#pragma mark - 2
- (void)preloadDataIfNeeded {
2026-01-26 18:17:02 +08:00
if (self.hasLoadedData || self.isLoading) {
2026-01-26 16:53:41 +08:00
return;
}
2026-01-26 18:17:02 +08:00
[self loadChatHistory];
}
- (void)loadChatHistory {
if (self.isLoading || !self.hasMoreHistory) {
2026-01-26 20:36:51 +08:00
[self.chatView endLoadMoreWithHasMoreData:self.hasMoreHistory];
2026-01-26 18:17:02 +08:00
return;
}
2026-01-26 16:53:41 +08:00
2026-01-26 18:17:02 +08:00
self.isLoading = YES;
2026-01-26 20:36:51 +08:00
if (self.currentPage == 1) {
[self.chatView resetNoMoreData];
}
2026-01-26 18:17:02 +08:00
// 使 persona.personaId companionId
NSInteger companionId = self.persona.personaId;
__weak typeof(self) weakSelf = self;
[self.aiVM fetchChatHistoryWithCompanionId:companionId
pageNum:self.currentPage
pageSize:20
completion:^(KBChatHistoryPageModel *pageModel, NSError *error) {
weakSelf.isLoading = NO;
if (error) {
NSLog(@"[KBPersonaChatCell] 加载聊天记录失败:%@", error.localizedDescription);
2026-01-26 20:36:51 +08:00
[weakSelf.chatView endLoadMoreWithHasMoreData:weakSelf.hasMoreHistory];
2026-01-26 18:17:02 +08:00
//
if (weakSelf.currentPage == 1 && weakSelf.persona.introText.length > 0) {
[weakSelf showOpeningMessage];
}
return;
}
weakSelf.hasLoadedData = YES;
weakSelf.hasMoreHistory = pageModel.hasMore;
// KBAiChatMessage
NSMutableArray *newMessages = [NSMutableArray array];
for (KBChatHistoryModel *item in pageModel.records) {
KBAiChatMessage *message;
2026-01-26 20:36:51 +08:00
// sender
// sender = 1:
// sender = 2: AI
if (item.sender == KBChatSenderUser) {
//
2026-01-26 18:17:02 +08:00
message = [KBAiChatMessage userMessageWithText:item.content];
2026-01-26 20:36:51 +08:00
} else if (item.sender == KBChatSenderAssistant) {
// AI
message = [KBAiChatMessage assistantMessageWithText:item.content];
2026-01-26 18:17:02 +08:00
} else {
2026-01-26 20:36:51 +08:00
// AI
NSLog(@"[KBPersonaChatCell] 未知的 sender 类型:%ld", (long)item.sender);
2026-01-26 18:17:02 +08:00
message = [KBAiChatMessage assistantMessageWithText:item.content];
}
2026-01-26 20:36:51 +08:00
2026-01-26 18:17:02 +08:00
message.isComplete = YES;
2026-01-26 20:36:51 +08:00
message.needsTypewriterEffect = NO;
2026-01-26 18:17:02 +08:00
[newMessages addObject:message];
}
//
if (weakSelf.currentPage == 1) {
//
weakSelf.messages = newMessages;
} else {
//
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newMessages.count)];
[weakSelf.messages insertObjects:newMessages atIndexes:indexSet];
}
// UI
dispatch_async(dispatch_get_main_queue(), ^{
2026-01-26 20:36:51 +08:00
BOOL keepOffset = (weakSelf.currentPage != 1);
BOOL scrollToBottom = (weakSelf.currentPage == 1);
[weakSelf.chatView reloadWithMessages:weakSelf.messages
keepOffset:keepOffset
scrollToBottom:scrollToBottom];
[weakSelf.chatView endLoadMoreWithHasMoreData:weakSelf.hasMoreHistory];
2026-01-26 18:17:02 +08:00
});
NSLog(@"[KBPersonaChatCell] 加载成功:第 %ld 页,%ld 条消息,还有更多:%@",
(long)weakSelf.currentPage,
(long)newMessages.count,
pageModel.hasMore ? @"是" : @"否");
}];
}
- (void)loadMoreHistory {
if (!self.hasMoreHistory || self.isLoading) {
2026-01-26 20:36:51 +08:00
[self.chatView endLoadMoreWithHasMoreData:self.hasMoreHistory];
2026-01-26 18:17:02 +08:00
return;
2026-01-26 16:53:41 +08:00
}
2026-01-26 18:17:02 +08:00
self.currentPage++;
[self loadChatHistory];
}
- (void)showOpeningMessage {
//
KBAiChatMessage *openingMsg = [KBAiChatMessage assistantMessageWithText:self.persona.introText];
openingMsg.isComplete = YES;
2026-01-26 20:36:51 +08:00
openingMsg.needsTypewriterEffect = NO;
2026-01-26 18:17:02 +08:00
[self.messages addObject:openingMsg];
dispatch_async(dispatch_get_main_queue(), ^{
2026-01-26 20:36:51 +08:00
[self.chatView reloadWithMessages:self.messages
keepOffset:NO
scrollToBottom:YES];
2026-01-26 18:17:02 +08:00
});
2026-01-26 16:53:41 +08:00
}
2026-01-26 20:36:51 +08:00
#pragma mark - 3
2026-01-26 16:53:41 +08:00
2026-01-26 20:36:51 +08:00
- (void)appendUserMessage:(NSString *)text {
if (text.length == 0) {
return;
2026-01-26 16:53:41 +08:00
}
2026-01-26 20:36:51 +08:00
if (!self.messages) {
self.messages = [NSMutableArray array];
}
2026-01-26 16:53:41 +08:00
2026-01-26 20:36:51 +08:00
KBAiChatMessage *message = [KBAiChatMessage userMessageWithText:text];
[self.messages addObject:message];
[self.chatView addMessage:message autoScroll:YES];
2026-01-26 16:53:41 +08:00
}
2026-01-26 20:36:51 +08:00
- (void)appendAssistantMessage:(NSString *)text
audioId:(NSString *)audioId {
if (text.length == 0) {
return;
}
if (!self.messages) {
self.messages = [NSMutableArray array];
}
KBAiChatMessage *message = [KBAiChatMessage assistantMessageWithText:text
audioId:audioId];
message.needsTypewriterEffect = YES;
[self.messages addObject:message];
[self.chatView addMessage:message autoScroll:YES];
2026-01-26 16:53:41 +08:00
}
2026-01-26 20:36:51 +08:00
#pragma mark - KBChatTableViewDelegate
2026-01-26 18:17:02 +08:00
2026-01-26 20:36:51 +08:00
- (void)chatTableViewDidScroll:(KBChatTableView *)chatView
scrollView:(UIScrollView *)scrollView {
2026-01-26 18:17:02 +08:00
CGFloat offsetY = scrollView.contentOffset.y;
//
if (offsetY <= -50 && !self.isLoading) {
[self loadMoreHistory];
}
}
2026-01-26 20:36:51 +08:00
- (void)chatTableViewDidTriggerLoadMore:(KBChatTableView *)chatView {
[self loadMoreHistory];
}
2026-01-26 16:53:41 +08:00
#pragma mark - Lazy Load
- (UIImageView *)backgroundImageView {
if (!_backgroundImageView) {
_backgroundImageView = [[UIImageView alloc] init];
_backgroundImageView.contentMode = UIViewContentModeScaleAspectFill;
_backgroundImageView.clipsToBounds = YES;
}
return _backgroundImageView;
}
- (UIImageView *)avatarImageView {
if (!_avatarImageView) {
_avatarImageView = [[UIImageView alloc] init];
_avatarImageView.contentMode = UIViewContentModeScaleAspectFill;
_avatarImageView.layer.cornerRadius = 40;
_avatarImageView.layer.borderWidth = 3;
_avatarImageView.layer.borderColor = [UIColor whiteColor].CGColor;
_avatarImageView.clipsToBounds = YES;
}
return _avatarImageView;
}
- (UILabel *)nameLabel {
if (!_nameLabel) {
_nameLabel = [[UILabel alloc] init];
_nameLabel.font = [UIFont boldSystemFontOfSize:20];
_nameLabel.textColor = [UIColor whiteColor];
_nameLabel.textAlignment = NSTextAlignmentCenter;
}
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;
}
2026-01-26 20:36:51 +08:00
- (KBChatTableView *)chatView {
if (!_chatView) {
_chatView = [[KBChatTableView alloc] init];
_chatView.backgroundColor = [UIColor clearColor];
_chatView.delegate = self;
2026-01-26 16:53:41 +08:00
}
2026-01-26 20:36:51 +08:00
return _chatView;
2026-01-26 16:53:41 +08:00
}
@end