2026-01-26 16:53:41 +08:00
|
|
|
|
//
|
|
|
|
|
|
// KBPersonaChatCell.m
|
|
|
|
|
|
// keyBoard
|
|
|
|
|
|
//
|
|
|
|
|
|
// Created by Kiro on 2026/1/26.
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
#import "KBPersonaChatCell.h"
|
|
|
|
|
|
#import "KBAiChatMessage.h"
|
2026-01-26 18:17:02 +08:00
|
|
|
|
#import "KBChatHistoryPageModel.h"
|
|
|
|
|
|
#import "AiVM.h"
|
2026-01-27 18:53:19 +08:00
|
|
|
|
#import "KBImagePositionButton.h"
|
2026-01-27 21:32:52 +08:00
|
|
|
|
#import "KBAICommentView.h"
|
2026-01-28 18:58:30 +08:00
|
|
|
|
#import "KBAIChatMessageCacheManager.h"
|
2026-01-26 16:53:41 +08:00
|
|
|
|
#import <Masonry/Masonry.h>
|
|
|
|
|
|
#import <SDWebImage/SDWebImage.h>
|
2026-01-27 21:32:52 +08:00
|
|
|
|
#import <LSTPopView/LSTPopView.h>
|
2026-01-29 16:03:21 +08:00
|
|
|
|
#import "AIPersonInfoVC.h"
|
2026-01-28 19:31:27 +08:00
|
|
|
|
/// 聊天会话被重置的通知
|
|
|
|
|
|
static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidResetNotification";
|
|
|
|
|
|
|
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-28 17:21:19 +08:00
|
|
|
|
|
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;
|
|
|
|
|
|
|
2026-01-30 21:24:17 +08:00
|
|
|
|
@property (nonatomic, assign) BOOL canTriggerLoadMore;
|
|
|
|
|
|
|
2026-01-26 18:17:02 +08:00
|
|
|
|
/// 当前页码
|
|
|
|
|
|
@property (nonatomic, assign) NSInteger currentPage;
|
|
|
|
|
|
|
|
|
|
|
|
/// 是否还有更多历史消息
|
|
|
|
|
|
@property (nonatomic, assign) BOOL hasMoreHistory;
|
|
|
|
|
|
|
|
|
|
|
|
/// AiVM 实例
|
|
|
|
|
|
@property (nonatomic, strong) AiVM *aiVM;
|
|
|
|
|
|
|
2026-01-27 18:53:19 +08:00
|
|
|
|
/// 评论按钮
|
|
|
|
|
|
@property (nonatomic, strong) KBImagePositionButton *commentButton;
|
|
|
|
|
|
|
|
|
|
|
|
/// 喜欢按钮
|
|
|
|
|
|
@property (nonatomic, strong) KBImagePositionButton *likeButton;
|
|
|
|
|
|
|
2026-01-27 21:32:52 +08:00
|
|
|
|
/// 评论弹窗
|
|
|
|
|
|
@property (nonatomic, weak) LSTPopView *popView;
|
|
|
|
|
|
|
2026-01-31 23:17:58 +08:00
|
|
|
|
@property (nonatomic, strong) NSMutableDictionary<NSString *, KBAiChatMessage *> *pendingAssistantMessages;
|
|
|
|
|
|
|
2026-02-02 14:29:42 +08:00
|
|
|
|
@property (nonatomic, assign) BOOL isCurrentPersonaCell;
|
|
|
|
|
|
@property (nonatomic, assign) BOOL shouldAutoPlayPrologueAudio;
|
|
|
|
|
|
@property (nonatomic, assign) BOOL hasPlayedPrologueAudio;
|
|
|
|
|
|
|
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];
|
2026-01-28 19:31:27 +08:00
|
|
|
|
|
|
|
|
|
|
// 监听聊天会话重置通知
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
|
|
selector:@selector(handleChatSessionReset:)
|
|
|
|
|
|
name:KBChatSessionDidResetNotification
|
|
|
|
|
|
object:nil];
|
2026-01-26 16:53:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
return self;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-28 19:31:27 +08:00
|
|
|
|
- (void)dealloc {
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-28 18:58:30 +08:00
|
|
|
|
/// 关键修复:Cell 复用时不清空数据,避免重复请求
|
2026-01-27 17:03:16 +08:00
|
|
|
|
- (void)prepareForReuse {
|
|
|
|
|
|
[super prepareForReuse];
|
|
|
|
|
|
|
|
|
|
|
|
// 停止音频播放
|
|
|
|
|
|
[self.chatView stopPlayingAudio];
|
|
|
|
|
|
|
2026-01-28 18:58:30 +08:00
|
|
|
|
// 重置加载状态标志(但不清空 hasLoadedData)
|
2026-01-27 17:03:16 +08:00
|
|
|
|
self.isLoading = NO;
|
2026-01-30 21:24:17 +08:00
|
|
|
|
self.canTriggerLoadMore = YES;
|
2026-01-31 23:17:58 +08:00
|
|
|
|
[self.pendingAssistantMessages removeAllObjects];
|
2026-02-02 14:29:42 +08:00
|
|
|
|
self.isCurrentPersonaCell = NO;
|
|
|
|
|
|
self.shouldAutoPlayPrologueAudio = NO;
|
|
|
|
|
|
self.hasPlayedPrologueAudio = NO;
|
2026-01-28 18:58:30 +08:00
|
|
|
|
|
|
|
|
|
|
// ✅ 移除了 self.hasLoadedData = NO;
|
|
|
|
|
|
// 这样 Cell 复用时不会重复请求数据
|
2026-01-27 17:03:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-26 16:53:41 +08:00
|
|
|
|
#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);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
2026-01-27 17:49:45 +08:00
|
|
|
|
// 开场白
|
|
|
|
|
|
[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);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
2026-01-26 16:53:41 +08:00
|
|
|
|
// 头像
|
|
|
|
|
|
[self.contentView addSubview:self.avatarImageView];
|
|
|
|
|
|
[self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
2026-01-27 17:49:45 +08:00
|
|
|
|
make.bottom.equalTo(self.contentView).offset(-KB_TABBAR_HEIGHT - 50 - 20);
|
|
|
|
|
|
make.left.equalTo(self.contentView).offset(20);
|
|
|
|
|
|
make.size.mas_equalTo(CGSizeMake(54, 54));
|
2026-01-26 16:53:41 +08:00
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
// 人设名称
|
|
|
|
|
|
[self.contentView addSubview:self.nameLabel];
|
|
|
|
|
|
[self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
2026-01-27 17:49:45 +08:00
|
|
|
|
make.left.equalTo(self.avatarImageView.mas_right).offset(5);
|
|
|
|
|
|
make.centerY.equalTo(self.avatarImageView);
|
2026-01-26 16:53:41 +08:00
|
|
|
|
}];
|
|
|
|
|
|
|
2026-01-27 18:53:19 +08:00
|
|
|
|
// 评论按钮(最右侧)
|
|
|
|
|
|
[self.contentView addSubview:self.commentButton];
|
|
|
|
|
|
[self.commentButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.right.equalTo(self.contentView).offset(-20);
|
|
|
|
|
|
make.centerY.equalTo(self.avatarImageView);
|
|
|
|
|
|
make.width.mas_equalTo(40);
|
|
|
|
|
|
make.height.mas_equalTo(50);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
// 喜欢按钮(评论按钮左侧,间距20px)
|
|
|
|
|
|
[self.contentView addSubview:self.likeButton];
|
|
|
|
|
|
[self.likeButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.right.equalTo(self.commentButton.mas_left).offset(-20);
|
|
|
|
|
|
make.centerY.equalTo(self.avatarImageView);
|
|
|
|
|
|
make.width.mas_equalTo(40);
|
|
|
|
|
|
make.height.mas_equalTo(50);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
2026-01-26 16:53:41 +08:00
|
|
|
|
// 聊天列表
|
2026-01-26 20:36:51 +08:00
|
|
|
|
[self.contentView addSubview:self.chatView];
|
|
|
|
|
|
[self.chatView mas_makeConstraints:^(MASConstraintMaker *make) {
|
2026-01-27 17:49:45 +08:00
|
|
|
|
make.top.equalTo(self.contentView).offset(KB_NAV_TOTAL_HEIGHT);
|
|
|
|
|
|
make.left.right.equalTo(self.contentView);
|
|
|
|
|
|
make.bottom.equalTo(self.avatarImageView.mas_top).offset(-10);
|
2026-01-26 16:53:41 +08:00
|
|
|
|
}];
|
2026-01-27 17:49:45 +08:00
|
|
|
|
|
2026-01-26 16:53:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Setter
|
|
|
|
|
|
|
2026-02-02 13:26:38 +08:00
|
|
|
|
- (NSString *)currentPrologueText {
|
|
|
|
|
|
if (self.persona.prologue.length > 0) {
|
|
|
|
|
|
return self.persona.prologue;
|
|
|
|
|
|
}
|
|
|
|
|
|
return self.persona.introText ?: @"";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-26 16:53:41 +08:00
|
|
|
|
- (void)setPersona:(KBPersonaModel *)persona {
|
|
|
|
|
|
_persona = persona;
|
|
|
|
|
|
|
|
|
|
|
|
// 重置状态
|
2026-01-26 18:17:02 +08:00
|
|
|
|
self.isLoading = NO;
|
2026-01-30 21:24:17 +08:00
|
|
|
|
self.canTriggerLoadMore = YES;
|
2026-01-26 18:17:02 +08:00
|
|
|
|
self.currentPage = 1;
|
|
|
|
|
|
self.hasMoreHistory = YES;
|
2026-01-31 23:17:58 +08:00
|
|
|
|
[self.pendingAssistantMessages removeAllObjects];
|
2026-02-02 14:29:42 +08:00
|
|
|
|
self.isCurrentPersonaCell = NO;
|
|
|
|
|
|
self.shouldAutoPlayPrologueAudio = NO;
|
|
|
|
|
|
self.hasPlayedPrologueAudio = NO;
|
2026-01-28 18:58:30 +08:00
|
|
|
|
|
2026-01-29 13:13:42 +08:00
|
|
|
|
// ⚠️ 临时禁用缓存,排查问题
|
|
|
|
|
|
// NSArray *cachedMessages = [[KBAIChatMessageCacheManager shared] messagesForCompanionId:persona.personaId];
|
|
|
|
|
|
// if (cachedMessages.count > 0) {
|
|
|
|
|
|
// self.messages = [cachedMessages mutableCopy];
|
|
|
|
|
|
// self.hasLoadedData = YES;
|
|
|
|
|
|
// NSLog(@"[Cell] ✅ 从缓存加载:personaId=%ld, 消息数=%ld", (long)persona.personaId, (long)cachedMessages.count);
|
|
|
|
|
|
// } else {
|
2026-01-28 18:58:30 +08:00
|
|
|
|
self.messages = [NSMutableArray array];
|
|
|
|
|
|
self.hasLoadedData = NO;
|
2026-01-29 13:13:42 +08:00
|
|
|
|
NSLog(@"[Cell] ⚠️ 缓存已禁用:personaId=%ld, 需要请求数据", (long)persona.personaId);
|
|
|
|
|
|
// }
|
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;
|
2026-02-02 13:26:38 +08:00
|
|
|
|
self.openingLabel.text = persona.shortDesc.length > 0 ? persona.shortDesc : persona.prologue;
|
2026-01-26 16:53:41 +08:00
|
|
|
|
|
2026-01-27 17:03:16 +08:00
|
|
|
|
// 关键修复:清空消息时停止音频播放,避免状态混乱
|
|
|
|
|
|
[self.chatView stopPlayingAudio];
|
2026-01-28 20:18:18 +08:00
|
|
|
|
|
2026-01-29 13:13:42 +08:00
|
|
|
|
NSLog(@"[KBPersonaChatCell] ========== setPersona 调试 ==========");
|
|
|
|
|
|
NSLog(@"[KBPersonaChatCell] personaId: %ld", (long)persona.personaId);
|
|
|
|
|
|
NSLog(@"[KBPersonaChatCell] messages.count: %ld", (long)self.messages.count);
|
|
|
|
|
|
NSLog(@"[KBPersonaChatCell] chatView.frame: %@", NSStringFromCGRect(self.chatView.frame));
|
|
|
|
|
|
NSLog(@"[KBPersonaChatCell] contentView.frame: %@", NSStringFromCGRect(self.contentView.frame));
|
|
|
|
|
|
|
2026-01-28 18:58:30 +08:00
|
|
|
|
if (self.messages.count > 0) {
|
2026-02-02 13:26:38 +08:00
|
|
|
|
[self.chatView updateIntroFooterText:nil];
|
|
|
|
|
|
[self ensureOpeningMessageAtTop];
|
2026-01-28 20:18:18 +08:00
|
|
|
|
// 同步缓存,避免下次从缓存缺少开场白
|
|
|
|
|
|
[[KBAIChatMessageCacheManager shared] saveMessages:self.messages
|
|
|
|
|
|
forCompanionId:persona.personaId];
|
2026-01-28 18:58:30 +08:00
|
|
|
|
[self.chatView reloadWithMessages:self.messages
|
2026-01-28 19:31:27 +08:00
|
|
|
|
keepOffset:NO
|
|
|
|
|
|
scrollToBottom:YES];
|
2026-01-28 18:58:30 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
[self.chatView clearMessages];
|
2026-02-02 13:26:38 +08:00
|
|
|
|
[self.chatView updateIntroFooterText:persona.prologue];
|
2026-01-28 18:58:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-29 13:13:42 +08:00
|
|
|
|
NSLog(@"[KBPersonaChatCell] ========== setPersona 结束 ==========");
|
|
|
|
|
|
|
2026-01-27 18:53:19 +08:00
|
|
|
|
[self.commentButton setTitle:persona.commentCount forState:UIControlStateNormal];
|
|
|
|
|
|
[self.likeButton setTitle:persona.likeCount forState:UIControlStateNormal];
|
|
|
|
|
|
self.likeButton.selected = persona.liked;
|
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
|
2026-01-31 22:40:50 +08:00
|
|
|
|
pageSize:10
|
2026-01-26 18:17:02 +08:00
|
|
|
|
completion:^(KBChatHistoryPageModel *pageModel, NSError *error) {
|
2026-01-30 21:24:17 +08:00
|
|
|
|
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
|
|
|
|
if (!strongSelf) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-01-26 18:17:02 +08:00
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
NSLog(@"[KBPersonaChatCell] 加载聊天记录失败:%@", error.localizedDescription);
|
2026-01-30 21:24:17 +08:00
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
|
strongSelf.isLoading = NO;
|
|
|
|
|
|
[strongSelf.chatView endLoadMoreWithHasMoreData:strongSelf.hasMoreHistory];
|
2026-02-02 13:26:38 +08:00
|
|
|
|
if (strongSelf.currentPage == 1 && strongSelf.persona.prologue.length > 0) {
|
2026-01-30 21:24:17 +08:00
|
|
|
|
[strongSelf showOpeningMessage];
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-01-26 18:17:02 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-30 21:24:17 +08:00
|
|
|
|
strongSelf.hasLoadedData = YES;
|
|
|
|
|
|
strongSelf.hasMoreHistory = pageModel.hasMore;
|
2026-02-02 14:29:42 +08:00
|
|
|
|
|
2026-02-02 13:26:38 +08:00
|
|
|
|
NSInteger loadedPage = strongSelf.currentPage;
|
2026-02-02 14:29:42 +08:00
|
|
|
|
if (loadedPage == 1) {
|
|
|
|
|
|
BOOL isEmpty = (pageModel.total == 0);
|
|
|
|
|
|
strongSelf.shouldAutoPlayPrologueAudio = isEmpty && (strongSelf.persona.prologueAudio.length > 0);
|
|
|
|
|
|
if (!strongSelf.shouldAutoPlayPrologueAudio) {
|
|
|
|
|
|
[strongSelf.chatView stopPlayingAudio];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
[strongSelf tryPlayPrologueAudioIfNeeded];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-02 13:26:38 +08:00
|
|
|
|
if (loadedPage == 1 && pageModel.total == 0) {
|
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
|
[strongSelf.chatView clearMessages];
|
|
|
|
|
|
[strongSelf.chatView updateIntroFooterText:strongSelf.persona.prologue];
|
|
|
|
|
|
[strongSelf.chatView endLoadMoreWithHasMoreData:strongSelf.hasMoreHistory];
|
|
|
|
|
|
strongSelf.isLoading = NO;
|
|
|
|
|
|
});
|
|
|
|
|
|
strongSelf.currentPage++;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-26 18:17:02 +08:00
|
|
|
|
// 转换为 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-31 22:40:50 +08:00
|
|
|
|
[newMessages addObject:message];
|
|
|
|
|
|
// [newMessages insertObject:message atIndex:0];
|
2026-01-26 18:17:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-28 20:18:18 +08:00
|
|
|
|
// 插入历史消息(确保开场白始终是第一条)
|
2026-02-02 13:26:38 +08:00
|
|
|
|
[strongSelf.chatView updateIntroFooterText:nil];
|
2026-01-29 13:13:42 +08:00
|
|
|
|
|
|
|
|
|
|
if (loadedPage == 1) {
|
2026-01-26 18:17:02 +08:00
|
|
|
|
// 第一页,直接赋值
|
2026-01-30 21:24:17 +08:00
|
|
|
|
strongSelf.messages = newMessages;
|
|
|
|
|
|
[strongSelf ensureOpeningMessageAtTop];
|
2026-01-26 18:17:02 +08:00
|
|
|
|
} else {
|
2026-01-31 22:40:50 +08:00
|
|
|
|
// 后续页,继续加载历史
|
2026-01-30 21:24:17 +08:00
|
|
|
|
[strongSelf ensureOpeningMessageAtTop];
|
2026-01-28 20:18:18 +08:00
|
|
|
|
if (newMessages.count > 0) {
|
2026-01-31 22:40:50 +08:00
|
|
|
|
if (strongSelf.chatView.inverted) {
|
|
|
|
|
|
NSInteger openingIndex = [strongSelf openingMessageIndexInMessages];
|
|
|
|
|
|
NSUInteger insertIndex = (openingIndex != NSNotFound) ? (NSUInteger)openingIndex : strongSelf.messages.count;
|
|
|
|
|
|
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(insertIndex, newMessages.count)];
|
|
|
|
|
|
[strongSelf.messages insertObjects:newMessages atIndexes:indexSet];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
NSUInteger insertIndex = [strongSelf hasOpeningMessageAtTop] ? 1 : 0;
|
|
|
|
|
|
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(insertIndex, newMessages.count)];
|
|
|
|
|
|
[strongSelf.messages insertObjects:newMessages atIndexes:indexSet];
|
|
|
|
|
|
}
|
2026-01-28 20:18:18 +08:00
|
|
|
|
}
|
2026-01-26 18:17:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新 UI
|
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
2026-01-30 21:24:17 +08:00
|
|
|
|
if (loadedPage == 1) {
|
|
|
|
|
|
NSLog(@"[KBPersonaChatCell] 刷新 UI - loadedPage: %ld, keepOffset: 0, scrollToBottom: 1",
|
|
|
|
|
|
(long)loadedPage);
|
|
|
|
|
|
[strongSelf.chatView reloadWithMessages:strongSelf.messages
|
|
|
|
|
|
keepOffset:NO
|
|
|
|
|
|
scrollToBottom:YES];
|
|
|
|
|
|
} else {
|
2026-01-31 22:40:50 +08:00
|
|
|
|
if (strongSelf.chatView.inverted) {
|
|
|
|
|
|
NSLog(@"[KBPersonaChatCell] 刷新 UI - loadedPage: %ld, appendHistory", (long)loadedPage);
|
|
|
|
|
|
KBAiChatMessage *openingMessage = [strongSelf openingMessageInMessages];
|
|
|
|
|
|
[strongSelf.chatView appendHistoryMessages:newMessages openingMessage:openingMessage];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
NSLog(@"[KBPersonaChatCell] 刷新 UI - loadedPage: %ld, prependHistory",
|
|
|
|
|
|
(long)loadedPage);
|
|
|
|
|
|
KBAiChatMessage *openingMessage = [strongSelf hasOpeningMessageAtTop] ? strongSelf.messages.firstObject : nil;
|
|
|
|
|
|
[strongSelf.chatView prependHistoryMessages:newMessages openingMessage:openingMessage];
|
|
|
|
|
|
}
|
2026-01-29 13:13:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
[strongSelf.chatView endLoadMoreWithHasMoreData:strongSelf.hasMoreHistory];
|
2026-01-28 18:58:30 +08:00
|
|
|
|
|
2026-01-28 20:18:18 +08:00
|
|
|
|
// ✅ 保存到缓存(包含开场白)
|
2026-01-29 13:13:42 +08:00
|
|
|
|
[[KBAIChatMessageCacheManager shared] saveMessages:strongSelf.messages
|
2026-01-28 18:58:30 +08:00
|
|
|
|
forCompanionId:companionId];
|
2026-01-30 21:24:17 +08:00
|
|
|
|
strongSelf.isLoading = NO;
|
2026-01-26 18:17:02 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-01-30 21:24:17 +08:00
|
|
|
|
strongSelf.currentPage++;
|
2026-01-28 18:58:30 +08:00
|
|
|
|
|
2026-01-26 18:17:02 +08:00
|
|
|
|
NSLog(@"[KBPersonaChatCell] 加载成功:第 %ld 页,%ld 条消息,还有更多:%@",
|
2026-01-30 21:24:17 +08:00
|
|
|
|
(long)strongSelf.currentPage - 1,
|
2026-01-26 18:17:02 +08:00
|
|
|
|
(long)newMessages.count,
|
|
|
|
|
|
pageModel.hasMore ? @"是" : @"否");
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 14:29:42 +08:00
|
|
|
|
#pragma mark - Prologue Audio
|
|
|
|
|
|
|
|
|
|
|
|
- (void)tryPlayPrologueAudioIfNeeded {
|
|
|
|
|
|
if (!self.isCurrentPersonaCell) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!self.shouldAutoPlayPrologueAudio) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (self.hasPlayedPrologueAudio) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (self.persona.prologueAudio.length == 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
self.hasPlayedPrologueAudio = YES;
|
|
|
|
|
|
[self.chatView playRemoteAudioWithURLString:self.persona.prologueAudio];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)onBecameCurrentPersonaCell {
|
|
|
|
|
|
self.isCurrentPersonaCell = YES;
|
|
|
|
|
|
[self tryPlayPrologueAudioIfNeeded];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)onResignedCurrentPersonaCell {
|
|
|
|
|
|
self.isCurrentPersonaCell = NO;
|
|
|
|
|
|
[self.chatView stopPlayingAudio];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-26 18:17:02 +08:00
|
|
|
|
- (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 loadChatHistory];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)showOpeningMessage {
|
2026-02-02 13:26:38 +08:00
|
|
|
|
if (self.messages.count == 0) {
|
|
|
|
|
|
[self.chatView clearMessages];
|
|
|
|
|
|
[self.chatView updateIntroFooterText:self.persona.prologue];
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[self.chatView updateIntroFooterText:nil];
|
2026-01-28 20:18:18 +08:00
|
|
|
|
[self ensureOpeningMessageAtTop];
|
2026-01-26 18:17:02 +08:00
|
|
|
|
|
|
|
|
|
|
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-28 20:18:18 +08:00
|
|
|
|
- (BOOL)hasOpeningMessageAtTop {
|
|
|
|
|
|
if (self.messages.count == 0) {
|
|
|
|
|
|
return NO;
|
|
|
|
|
|
}
|
2026-01-31 22:40:50 +08:00
|
|
|
|
if (self.chatView.inverted) {
|
|
|
|
|
|
return [self isOpeningMessage:self.messages.lastObject];
|
|
|
|
|
|
}
|
2026-01-28 20:18:18 +08:00
|
|
|
|
return [self isOpeningMessage:self.messages.firstObject];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (BOOL)isOpeningMessage:(KBAiChatMessage *)message {
|
|
|
|
|
|
if (!message) {
|
|
|
|
|
|
return NO;
|
|
|
|
|
|
}
|
2026-02-02 13:26:38 +08:00
|
|
|
|
NSString *prologue = [self currentPrologueText];
|
|
|
|
|
|
if (prologue.length == 0) {
|
2026-01-28 20:18:18 +08:00
|
|
|
|
return NO;
|
|
|
|
|
|
}
|
2026-02-02 13:26:38 +08:00
|
|
|
|
return (message.type == KBAiChatMessageTypeAssistant) && [message.text isEqualToString:prologue];
|
2026-01-28 20:18:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)ensureOpeningMessageAtTop {
|
2026-02-02 13:26:38 +08:00
|
|
|
|
NSString *prologue = [self currentPrologueText];
|
|
|
|
|
|
if (prologue.length == 0) {
|
2026-01-28 20:18:18 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!self.messages) {
|
|
|
|
|
|
self.messages = [NSMutableArray array];
|
|
|
|
|
|
}
|
|
|
|
|
|
if ([self hasOpeningMessageAtTop]) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-02-02 13:26:38 +08:00
|
|
|
|
KBAiChatMessage *openingMsg = [KBAiChatMessage assistantMessageWithText:prologue];
|
2026-01-28 20:18:18 +08:00
|
|
|
|
openingMsg.isComplete = YES;
|
|
|
|
|
|
openingMsg.needsTypewriterEffect = NO;
|
2026-01-31 22:40:50 +08:00
|
|
|
|
if (self.chatView.inverted) {
|
|
|
|
|
|
[self.messages addObject:openingMsg];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
[self.messages insertObject:openingMsg atIndex:0];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (nullable KBAiChatMessage *)openingMessageInMessages {
|
|
|
|
|
|
NSInteger index = [self openingMessageIndexInMessages];
|
|
|
|
|
|
if (index == NSNotFound) {
|
|
|
|
|
|
return nil;
|
|
|
|
|
|
}
|
|
|
|
|
|
return self.messages[index];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (NSInteger)openingMessageIndexInMessages {
|
2026-02-02 13:26:38 +08:00
|
|
|
|
NSString *prologue = [self currentPrologueText];
|
|
|
|
|
|
if (prologue.length == 0 || self.messages.count == 0) {
|
2026-01-31 22:40:50 +08:00
|
|
|
|
return NSNotFound;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (self.chatView.inverted) {
|
|
|
|
|
|
NSInteger lastIndex = self.messages.count - 1;
|
|
|
|
|
|
KBAiChatMessage *msg = self.messages[lastIndex];
|
|
|
|
|
|
return [self isOpeningMessage:msg] ? lastIndex : NSNotFound;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
KBAiChatMessage *first = self.messages.firstObject;
|
|
|
|
|
|
return [self isOpeningMessage:first] ? 0 : NSNotFound;
|
2026-01-28 20:18:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-28 19:31:27 +08:00
|
|
|
|
#pragma mark - 通知处理
|
|
|
|
|
|
|
|
|
|
|
|
/// 处理聊天会话被重置的通知
|
|
|
|
|
|
- (void)handleChatSessionReset:(NSNotification *)notification {
|
|
|
|
|
|
NSNumber *companionIdObj = notification.userInfo[@"companionId"];
|
|
|
|
|
|
if (!companionIdObj) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSInteger companionId = [companionIdObj integerValue];
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是当前显示的人设,清空聊天记录
|
|
|
|
|
|
if (self.persona && self.persona.personaId == companionId) {
|
|
|
|
|
|
NSLog(@"[KBPersonaChatCell] 收到聊天重置通知:companionId=%ld, 清空聊天记录", (long)companionId);
|
|
|
|
|
|
|
2026-02-02 15:28:00 +08:00
|
|
|
|
self.shouldAutoPlayPrologueAudio = NO;
|
|
|
|
|
|
self.hasPlayedPrologueAudio = NO;
|
|
|
|
|
|
[self.chatView stopPlayingAudio];
|
|
|
|
|
|
|
2026-01-28 19:31:27 +08:00
|
|
|
|
// 清空消息数组
|
|
|
|
|
|
self.messages = [NSMutableArray array];
|
|
|
|
|
|
self.hasLoadedData = NO;
|
|
|
|
|
|
self.currentPage = 1;
|
|
|
|
|
|
self.hasMoreHistory = YES;
|
|
|
|
|
|
|
|
|
|
|
|
// 清空聊天视图
|
|
|
|
|
|
[self.chatView clearMessages];
|
|
|
|
|
|
|
2026-01-28 20:18:18 +08:00
|
|
|
|
// 显示开场白(始终保持第一条)
|
|
|
|
|
|
[self showOpeningMessage];
|
2026-01-28 19:31:27 +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-02-02 14:29:42 +08:00
|
|
|
|
self.shouldAutoPlayPrologueAudio = NO;
|
|
|
|
|
|
[self.chatView stopPlayingAudio];
|
2026-02-02 13:26:38 +08:00
|
|
|
|
[self.chatView updateIntroFooterText:nil];
|
2026-01-28 20:18:18 +08:00
|
|
|
|
[self ensureOpeningMessageAtTop];
|
2026-01-26 20:36:51 +08:00
|
|
|
|
KBAiChatMessage *message = [KBAiChatMessage userMessageWithText:text];
|
2026-01-31 22:40:50 +08:00
|
|
|
|
if (self.chatView.inverted) {
|
|
|
|
|
|
[self.messages insertObject:message atIndex:0];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
[self.messages addObject:message];
|
|
|
|
|
|
}
|
2026-01-26 20:36:51 +08:00
|
|
|
|
[self.chatView addMessage:message autoScroll:YES];
|
2026-01-26 16:53:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-31 23:17:58 +08:00
|
|
|
|
- (void)appendUserMessage:(NSString *)text requestId:(NSString *)requestId {
|
|
|
|
|
|
[self appendUserMessage:text];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-29 14:42:49 +08:00
|
|
|
|
- (void)appendLoadingUserMessage {
|
|
|
|
|
|
if (!self.messages) {
|
|
|
|
|
|
self.messages = [NSMutableArray array];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 14:29:42 +08:00
|
|
|
|
self.shouldAutoPlayPrologueAudio = NO;
|
|
|
|
|
|
[self.chatView stopPlayingAudio];
|
2026-02-02 13:26:38 +08:00
|
|
|
|
[self.chatView updateIntroFooterText:nil];
|
2026-01-29 14:42:49 +08:00
|
|
|
|
[self ensureOpeningMessageAtTop];
|
|
|
|
|
|
KBAiChatMessage *message = [KBAiChatMessage loadingUserMessage];
|
2026-01-31 22:40:50 +08:00
|
|
|
|
if (self.chatView.inverted) {
|
|
|
|
|
|
[self.messages insertObject:message atIndex:0];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
[self.messages addObject:message];
|
|
|
|
|
|
}
|
2026-01-29 14:42:49 +08:00
|
|
|
|
[self.chatView addMessage:message autoScroll:YES];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)updateLastUserMessage:(NSString *)text {
|
|
|
|
|
|
[self.chatView updateLastUserMessage:text];
|
|
|
|
|
|
|
|
|
|
|
|
// 更新数据源中的消息
|
2026-01-31 22:40:50 +08:00
|
|
|
|
if (self.chatView.inverted) {
|
|
|
|
|
|
for (NSInteger i = 0; i < self.messages.count; i++) {
|
|
|
|
|
|
KBAiChatMessage *message = self.messages[i];
|
|
|
|
|
|
if (message.type == KBAiChatMessageTypeUser && message.isLoading) {
|
|
|
|
|
|
message.text = text;
|
|
|
|
|
|
message.isLoading = NO;
|
|
|
|
|
|
message.isComplete = YES;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
|
|
|
|
|
KBAiChatMessage *message = self.messages[i];
|
|
|
|
|
|
if (message.type == KBAiChatMessageTypeUser && message.isLoading) {
|
|
|
|
|
|
message.text = text;
|
|
|
|
|
|
message.isLoading = NO;
|
|
|
|
|
|
message.isComplete = YES;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-01-29 14:42:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-29 13:44:52 +08:00
|
|
|
|
- (void)markLastUserMessageLoadingComplete {
|
2026-01-29 14:42:49 +08:00
|
|
|
|
[self.chatView markLastUserMessageLoadingComplete];
|
|
|
|
|
|
|
|
|
|
|
|
// 同步更新数据源
|
2026-01-31 22:40:50 +08:00
|
|
|
|
if (self.chatView.inverted) {
|
|
|
|
|
|
for (NSInteger i = 0; i < self.messages.count; i++) {
|
|
|
|
|
|
KBAiChatMessage *message = self.messages[i];
|
|
|
|
|
|
if (message.type == KBAiChatMessageTypeUser && message.isLoading) {
|
|
|
|
|
|
message.isLoading = NO;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
|
|
|
|
|
KBAiChatMessage *message = self.messages[i];
|
|
|
|
|
|
if (message.type == KBAiChatMessageTypeUser && message.isLoading) {
|
|
|
|
|
|
message.isLoading = NO;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-01-29 14:42:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-29 13:44:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-26 20:36:51 +08:00
|
|
|
|
- (void)appendAssistantMessage:(NSString *)text
|
2026-01-28 20:18:18 +08:00
|
|
|
|
audioId:(NSString *)audioId {
|
2026-01-26 20:36:51 +08:00
|
|
|
|
if (text.length == 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!self.messages) {
|
|
|
|
|
|
self.messages = [NSMutableArray array];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 14:29:42 +08:00
|
|
|
|
self.shouldAutoPlayPrologueAudio = NO;
|
2026-02-02 13:26:38 +08:00
|
|
|
|
[self.chatView updateIntroFooterText:nil];
|
2026-01-28 20:18:18 +08:00
|
|
|
|
[self ensureOpeningMessageAtTop];
|
2026-01-29 20:56:24 +08:00
|
|
|
|
|
|
|
|
|
|
// 查找并移除 loading 消息
|
|
|
|
|
|
[self removeLoadingAssistantMessage];
|
|
|
|
|
|
|
2026-01-26 20:36:51 +08:00
|
|
|
|
KBAiChatMessage *message = [KBAiChatMessage assistantMessageWithText:text
|
|
|
|
|
|
audioId:audioId];
|
|
|
|
|
|
message.needsTypewriterEffect = YES;
|
2026-01-31 22:40:50 +08:00
|
|
|
|
if (self.chatView.inverted) {
|
|
|
|
|
|
[self.messages insertObject:message atIndex:0];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
[self.messages addObject:message];
|
|
|
|
|
|
}
|
2026-01-26 20:36:51 +08:00
|
|
|
|
[self.chatView addMessage:message autoScroll:YES];
|
2026-01-26 16:53:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-29 20:56:24 +08:00
|
|
|
|
/// 添加 loading AI 消息
|
|
|
|
|
|
- (void)appendLoadingAssistantMessage {
|
|
|
|
|
|
if (!self.messages) {
|
|
|
|
|
|
self.messages = [NSMutableArray array];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 14:29:42 +08:00
|
|
|
|
self.shouldAutoPlayPrologueAudio = NO;
|
2026-02-02 13:26:38 +08:00
|
|
|
|
[self.chatView updateIntroFooterText:nil];
|
2026-01-29 20:56:24 +08:00
|
|
|
|
[self ensureOpeningMessageAtTop];
|
|
|
|
|
|
KBAiChatMessage *message = [KBAiChatMessage loadingAssistantMessage];
|
2026-01-31 22:40:50 +08:00
|
|
|
|
if (self.chatView.inverted) {
|
|
|
|
|
|
[self.messages insertObject:message atIndex:0];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
[self.messages addObject:message];
|
|
|
|
|
|
}
|
2026-01-29 20:56:24 +08:00
|
|
|
|
[self.chatView addMessage:message autoScroll:YES];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-31 23:17:58 +08:00
|
|
|
|
- (void)appendLoadingAssistantMessageWithRequestId:(NSString *)requestId {
|
|
|
|
|
|
if (requestId.length == 0) {
|
|
|
|
|
|
[self appendLoadingAssistantMessage];
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!self.pendingAssistantMessages) {
|
|
|
|
|
|
self.pendingAssistantMessages = [NSMutableDictionary dictionary];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!self.messages) {
|
|
|
|
|
|
self.messages = [NSMutableArray array];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 14:29:42 +08:00
|
|
|
|
self.shouldAutoPlayPrologueAudio = NO;
|
2026-02-02 13:26:38 +08:00
|
|
|
|
[self.chatView updateIntroFooterText:nil];
|
2026-01-31 23:17:58 +08:00
|
|
|
|
[self ensureOpeningMessageAtTop];
|
|
|
|
|
|
KBAiChatMessage *message = [KBAiChatMessage loadingAssistantMessage];
|
|
|
|
|
|
self.pendingAssistantMessages[requestId] = message;
|
|
|
|
|
|
|
|
|
|
|
|
if (self.chatView.inverted) {
|
|
|
|
|
|
[self.messages insertObject:message atIndex:0];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
[self.messages addObject:message];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[self.chatView addMessage:message autoScroll:YES];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-29 20:56:24 +08:00
|
|
|
|
/// 移除 loading AI 消息
|
|
|
|
|
|
- (void)removeLoadingAssistantMessage {
|
|
|
|
|
|
// 从数据源中移除
|
2026-01-31 22:40:50 +08:00
|
|
|
|
if (self.chatView.inverted) {
|
|
|
|
|
|
for (NSInteger i = 0; i < self.messages.count; i++) {
|
|
|
|
|
|
KBAiChatMessage *message = self.messages[i];
|
|
|
|
|
|
if (message.type == KBAiChatMessageTypeAssistant && message.isLoading) {
|
|
|
|
|
|
[self.messages removeObjectAtIndex:i];
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
|
|
|
|
|
KBAiChatMessage *message = self.messages[i];
|
|
|
|
|
|
if (message.type == KBAiChatMessageTypeAssistant && message.isLoading) {
|
|
|
|
|
|
[self.messages removeObjectAtIndex:i];
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-01-29 20:56:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从 chatView 中移除
|
|
|
|
|
|
[self.chatView removeLoadingAssistantMessage];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-31 23:17:58 +08:00
|
|
|
|
- (void)removeLoadingAssistantMessageWithRequestId:(NSString *)requestId {
|
|
|
|
|
|
if (requestId.length == 0) {
|
|
|
|
|
|
[self removeLoadingAssistantMessage];
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
KBAiChatMessage *target = self.pendingAssistantMessages[requestId];
|
|
|
|
|
|
if (!target) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[self.pendingAssistantMessages removeObjectForKey:requestId];
|
|
|
|
|
|
|
|
|
|
|
|
NSInteger idx = [self.messages indexOfObjectIdenticalTo:target];
|
|
|
|
|
|
if (idx != NSNotFound) {
|
|
|
|
|
|
[self.messages removeObjectAtIndex:idx];
|
|
|
|
|
|
[self.chatView reloadWithMessages:self.messages keepOffset:NO scrollToBottom:NO];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)updateAssistantMessageWithRequestId:(NSString *)requestId
|
|
|
|
|
|
text:(NSString *)text
|
|
|
|
|
|
audioId:(nullable NSString *)audioId {
|
|
|
|
|
|
if (requestId.length == 0) {
|
|
|
|
|
|
[self appendAssistantMessage:text audioId:audioId];
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
KBAiChatMessage *target = self.pendingAssistantMessages[requestId];
|
|
|
|
|
|
[self.pendingAssistantMessages removeObjectForKey:requestId];
|
|
|
|
|
|
|
|
|
|
|
|
if (!target) {
|
|
|
|
|
|
[self appendAssistantMessage:text audioId:audioId];
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
target.isLoading = NO;
|
|
|
|
|
|
target.text = text ?: @"";
|
|
|
|
|
|
target.audioId = audioId;
|
|
|
|
|
|
target.needsTypewriterEffect = YES;
|
|
|
|
|
|
target.isComplete = NO;
|
|
|
|
|
|
|
|
|
|
|
|
[self.chatView reloadMessage:target];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-27 16:28:17 +08:00
|
|
|
|
- (void)updateChatViewBottomInset:(CGFloat)bottomInset {
|
|
|
|
|
|
[self.chatView updateContentBottomInset:bottomInset];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2026-01-31 22:40:50 +08:00
|
|
|
|
if (chatView.inverted) {
|
|
|
|
|
|
CGFloat contentHeight = scrollView.contentSize.height;
|
|
|
|
|
|
CGFloat scrollViewHeight = scrollView.bounds.size.height;
|
|
|
|
|
|
CGFloat maxOffsetY = contentHeight - scrollViewHeight + scrollView.contentInset.bottom;
|
|
|
|
|
|
if (maxOffsetY < 0) {
|
|
|
|
|
|
maxOffsetY = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (offsetY >= maxOffsetY - 50 && !self.isLoading && self.canTriggerLoadMore && self.hasMoreHistory) {
|
|
|
|
|
|
self.canTriggerLoadMore = NO;
|
|
|
|
|
|
[self loadMoreHistory];
|
|
|
|
|
|
} else if (offsetY < maxOffsetY - 100) {
|
|
|
|
|
|
self.canTriggerLoadMore = YES;
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-30 21:24:17 +08:00
|
|
|
|
if (offsetY <= 50 && !self.isLoading && self.canTriggerLoadMore && self.hasMoreHistory) {
|
|
|
|
|
|
self.canTriggerLoadMore = NO;
|
2026-01-26 18:17:02 +08:00
|
|
|
|
[self loadMoreHistory];
|
2026-01-30 21:24:17 +08:00
|
|
|
|
} else if (offsetY > -20) {
|
|
|
|
|
|
self.canTriggerLoadMore = YES;
|
2026-01-26 18:17:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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;
|
2026-01-27 17:49:45 +08:00
|
|
|
|
_avatarImageView.layer.cornerRadius = 27;
|
2026-01-26 16:53:41 +08:00
|
|
|
|
_avatarImageView.layer.borderWidth = 3;
|
|
|
|
|
|
_avatarImageView.layer.borderColor = [UIColor whiteColor].CGColor;
|
|
|
|
|
|
_avatarImageView.clipsToBounds = YES;
|
2026-01-29 16:03:21 +08:00
|
|
|
|
_avatarImageView.userInteractionEnabled = YES;
|
|
|
|
|
|
|
|
|
|
|
|
// 添加点击手势
|
|
|
|
|
|
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(avatarTapped)];
|
|
|
|
|
|
[_avatarImageView addGestureRecognizer:tap];
|
2026-01-26 16:53:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
return _avatarImageView;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (UILabel *)nameLabel {
|
|
|
|
|
|
if (!_nameLabel) {
|
|
|
|
|
|
_nameLabel = [[UILabel alloc] init];
|
2026-01-27 18:53:19 +08:00
|
|
|
|
_nameLabel.font = [UIFont boldSystemFontOfSize:12];
|
2026-01-26 16:53:41 +08:00
|
|
|
|
_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];
|
2026-01-31 22:40:50 +08:00
|
|
|
|
_chatView.inverted = YES;
|
2026-01-26 20:36:51 +08:00
|
|
|
|
_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
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-27 18:53:19 +08:00
|
|
|
|
- (KBImagePositionButton *)commentButton {
|
|
|
|
|
|
if (!_commentButton) {
|
|
|
|
|
|
// 创建上图下文的按钮
|
|
|
|
|
|
_commentButton = [[KBImagePositionButton alloc] initWithImagePosition:KBImagePositionTop spacing:4];
|
|
|
|
|
|
|
|
|
|
|
|
// 关键修复:先设置字体,再设置文字,避免循环调用
|
|
|
|
|
|
_commentButton.titleLabel.font = [UIFont systemFontOfSize:10];
|
|
|
|
|
|
|
|
|
|
|
|
// 设置图片
|
|
|
|
|
|
[_commentButton setImage:[UIImage imageNamed:@"ai_comment_icon"] forState:UIControlStateNormal];
|
|
|
|
|
|
|
|
|
|
|
|
// 设置文字
|
|
|
|
|
|
[_commentButton setTitle:@"0" forState:UIControlStateNormal];
|
|
|
|
|
|
[_commentButton setTitleColor:[[UIColor whiteColor] colorWithAlphaComponent:0.8] forState:UIControlStateNormal];
|
|
|
|
|
|
|
|
|
|
|
|
// 添加点击事件
|
|
|
|
|
|
[_commentButton addTarget:self action:@selector(commentButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
|
|
}
|
|
|
|
|
|
return _commentButton;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (KBImagePositionButton *)likeButton {
|
|
|
|
|
|
if (!_likeButton) {
|
|
|
|
|
|
// 创建上图下文的按钮
|
|
|
|
|
|
_likeButton = [[KBImagePositionButton alloc] initWithImagePosition:KBImagePositionTop spacing:4];
|
|
|
|
|
|
|
|
|
|
|
|
// 关键修复:先设置字体,再设置文字,避免循环调用
|
|
|
|
|
|
_likeButton.titleLabel.font = [UIFont systemFontOfSize:10];
|
|
|
|
|
|
|
|
|
|
|
|
// 设置图片
|
|
|
|
|
|
[_likeButton setImage:[UIImage imageNamed:@"ai_live_icon"] forState:UIControlStateNormal];
|
|
|
|
|
|
[_likeButton setImage:[UIImage imageNamed:@"ai_livesel_icon"] forState:UIControlStateSelected];
|
|
|
|
|
|
|
|
|
|
|
|
// 设置文字
|
|
|
|
|
|
[_likeButton setTitle:@"0" forState:UIControlStateNormal];
|
|
|
|
|
|
[_likeButton setTitleColor:[[UIColor whiteColor] colorWithAlphaComponent:0.8] forState:UIControlStateNormal];
|
|
|
|
|
|
|
|
|
|
|
|
// 添加点击事件
|
|
|
|
|
|
[_likeButton addTarget:self action:@selector(likeButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
|
|
}
|
|
|
|
|
|
return _likeButton;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Button Actions
|
|
|
|
|
|
|
2026-01-29 16:03:21 +08:00
|
|
|
|
- (void)avatarTapped {
|
|
|
|
|
|
NSLog(@"[KBPersonaChatCell] 头像点击,跳转到人设详情页");
|
|
|
|
|
|
|
|
|
|
|
|
if (self.persona.personaId <= 0) {
|
|
|
|
|
|
NSLog(@"[KBPersonaChatCell] personaId 无效,取消跳转");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
AIPersonInfoVC *vc = [[AIPersonInfoVC alloc] init];
|
|
|
|
|
|
vc.companionId = self.persona.personaId;
|
|
|
|
|
|
[KB_CURRENT_NAV pushViewController:vc animated:YES];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-27 18:53:19 +08:00
|
|
|
|
- (void)commentButtonTapped:(KBImagePositionButton *)sender {
|
2026-01-27 21:32:52 +08:00
|
|
|
|
NSLog(@"[KBPersonaChatCell] 评论按钮点击");
|
2026-01-27 18:53:19 +08:00
|
|
|
|
|
2026-01-27 21:32:52 +08:00
|
|
|
|
// 弹出评论视图
|
|
|
|
|
|
[self showComment];
|
2026-01-27 18:53:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)likeButtonTapped:(KBImagePositionButton *)sender {
|
2026-01-27 21:32:52 +08:00
|
|
|
|
NSLog(@"[KBPersonaChatCell] 喜欢按钮点击");
|
|
|
|
|
|
|
|
|
|
|
|
NSInteger personaId = self.persona.personaId;
|
2026-01-27 18:53:19 +08:00
|
|
|
|
|
2026-01-27 21:32:52 +08:00
|
|
|
|
// 禁用按钮,防止重复点击
|
|
|
|
|
|
sender.enabled = NO;
|
|
|
|
|
|
|
|
|
|
|
|
__weak typeof(self) weakSelf = self;
|
|
|
|
|
|
[self.aiVM likeCompanionWithCompanionId:personaId completion:^(KBCommentLikeResponse * _Nullable response, NSError * _Nullable error) {
|
|
|
|
|
|
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
|
|
|
|
if (!strongSelf) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
|
// 恢复按钮可用状态
|
|
|
|
|
|
sender.enabled = YES;
|
|
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
NSLog(@"[KBPersonaChatCell] 点赞失败:%@", error.localizedDescription);
|
|
|
|
|
|
// TODO: 显示错误提示
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response && response.code == 0) {
|
|
|
|
|
|
// 获取当前喜欢数
|
|
|
|
|
|
NSInteger currentLikeCount = [strongSelf.persona.likeCount integerValue];
|
|
|
|
|
|
|
|
|
|
|
|
// response.data: true 表示点赞成功,false 表示取消点赞成功
|
|
|
|
|
|
if (response.data) {
|
|
|
|
|
|
// 点赞成功,喜欢数加1
|
|
|
|
|
|
currentLikeCount += 1;
|
|
|
|
|
|
sender.selected = YES;
|
|
|
|
|
|
NSLog(@"[KBPersonaChatCell] 点赞成功,新喜欢数:%ld", (long)currentLikeCount);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 取消点赞成功,喜欢数减1(但不能小于0)
|
|
|
|
|
|
currentLikeCount = MAX(0, currentLikeCount - 1);
|
|
|
|
|
|
sender.selected = NO;
|
|
|
|
|
|
NSLog(@"[KBPersonaChatCell] 取消点赞成功,新喜欢数:%ld", (long)currentLikeCount);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新模型数据
|
|
|
|
|
|
strongSelf.persona.likeCount = [NSString stringWithFormat:@"%ld", (long)currentLikeCount];
|
|
|
|
|
|
strongSelf.persona.liked = sender.selected;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新按钮显示文字
|
|
|
|
|
|
[sender setTitle:strongSelf.persona.likeCount forState:UIControlStateNormal];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
NSLog(@"[KBPersonaChatCell] 点赞失败:%@", response.message ?: @"未知错误");
|
|
|
|
|
|
// TODO: 显示错误提示
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}];
|
2026-01-27 18:53:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-27 21:32:52 +08:00
|
|
|
|
#pragma mark - Comment View
|
|
|
|
|
|
|
|
|
|
|
|
- (void)showComment {
|
|
|
|
|
|
// 关闭之前的弹窗
|
|
|
|
|
|
if (self.popView) {
|
|
|
|
|
|
[self.popView dismiss];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-29 16:42:43 +08:00
|
|
|
|
CGFloat customViewHeight = KB_SCREEN_HEIGHT * 0.7;
|
2026-01-27 21:32:52 +08:00
|
|
|
|
KBAICommentView *customView = [[KBAICommentView alloc]
|
|
|
|
|
|
initWithFrame:CGRectMake(0, 0, KB_SCREEN_WIDTH, customViewHeight)];
|
|
|
|
|
|
|
2026-01-29 14:51:42 +08:00
|
|
|
|
NSString *commentCount = self.persona.commentCount;
|
|
|
|
|
|
NSInteger totalCommentCount = [commentCount integerValue];;
|
|
|
|
|
|
customView.totalCommentCount = totalCommentCount;
|
2026-01-27 21:32:52 +08:00
|
|
|
|
// 设置评论视图的人设 ID
|
2026-01-28 12:04:31 +08:00
|
|
|
|
customView.companionId = self.persona.personaId;
|
|
|
|
|
|
|
|
|
|
|
|
// 加载评论数据
|
|
|
|
|
|
[customView loadComments];
|
2026-01-27 21:32:52 +08:00
|
|
|
|
|
|
|
|
|
|
LSTPopView *popView = [LSTPopView initWithCustomView:customView
|
|
|
|
|
|
parentView:nil
|
|
|
|
|
|
popStyle:LSTPopStyleSmoothFromBottom
|
|
|
|
|
|
dismissStyle:LSTDismissStyleSmoothToBottom];
|
2026-01-28 12:04:31 +08:00
|
|
|
|
customView.popView = popView;
|
2026-01-28 13:43:36 +08:00
|
|
|
|
popView.bgColor = [UIColor clearColor];
|
2026-01-27 21:32:52 +08:00
|
|
|
|
self.popView = popView;
|
|
|
|
|
|
popView.priority = 1000;
|
|
|
|
|
|
popView.isAvoidKeyboard = NO;
|
|
|
|
|
|
popView.hemStyle = LSTHemStyleBottom;
|
|
|
|
|
|
popView.dragStyle = LSTDragStyleY_Positive;
|
|
|
|
|
|
popView.dragDistance = customViewHeight * 0.5;
|
|
|
|
|
|
popView.sweepStyle = LSTSweepStyleY_Positive;
|
|
|
|
|
|
popView.swipeVelocity = 1600;
|
|
|
|
|
|
popView.sweepDismissStyle = LSTSweepDismissStyleSmooth;
|
2026-01-29 16:42:43 +08:00
|
|
|
|
popView.bgClickBlock = ^{
|
|
|
|
|
|
[KB_CURRENT_NAV.view endEditing:true];
|
|
|
|
|
|
};
|
2026-01-27 21:32:52 +08:00
|
|
|
|
[popView pop];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (AiVM *)aiVM{
|
|
|
|
|
|
if (!_aiVM) {
|
|
|
|
|
|
_aiVM = [[AiVM alloc] init];
|
|
|
|
|
|
}
|
|
|
|
|
|
return _aiVM;
|
|
|
|
|
|
}
|
2026-01-26 16:53:41 +08:00
|
|
|
|
@end
|