Files
keyboard/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m
2026-01-26 18:17:02 +08:00

304 lines
8.7 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// KBAIHomeVC.m
// keyBoard
//
// Created by Mac on 2026/1/26.
//
#import "KBAIHomeVC.h"
#import "KBPersonaChatCell.h"
#import "KBPersonaModel.h"
#import "KBVoiceInputBar.h"
#import "AiVM.h"
#import <Masonry/Masonry.h>
@interface KBAIHomeVC () <UICollectionViewDelegate, UICollectionViewDataSource, KBVoiceInputBarDelegate>
/// 人设列表容器
@property (nonatomic, strong) UICollectionView *collectionView;
/// 底部语音输入栏
@property (nonatomic, strong) KBVoiceInputBar *voiceInputBar;
/// 人设数据
@property (nonatomic, strong) NSMutableArray<KBPersonaModel *> *personas;
/// 当前页码
@property (nonatomic, assign) NSInteger currentPage;
/// 是否还有更多数据
@property (nonatomic, assign) BOOL hasMore;
/// 是否正在加载
@property (nonatomic, assign) BOOL isLoading;
/// 当前显示的索引
@property (nonatomic, assign) NSInteger currentIndex;
/// 已预加载的索引集合
@property (nonatomic, strong) NSMutableSet<NSNumber *> *preloadedIndexes;
/// AiVM 实例
@property (nonatomic, strong) AiVM *aiVM;
@end
@implementation KBAIHomeVC
#pragma mark - Lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.kb_navView.hidden = true;
// 初始化数据
self.personas = [NSMutableArray array];
self.currentPage = 1;
self.hasMore = YES;
self.isLoading = NO;
self.currentIndex = 0;
self.preloadedIndexes = [NSMutableSet set];
self.aiVM = [[AiVM alloc] init];
[self setupUI];
[self loadPersonas];
}
#pragma mark - 1控件初始化
- (void)setupUI {
[self.view addSubview:self.collectionView];
[self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
// 底部语音输入栏
[self.view addSubview:self.voiceInputBar];
[self.voiceInputBar mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.view);
make.bottom.equalTo(self.view);
make.height.mas_equalTo(150); // 根据实际需要调整高度
}];
}
#pragma mark - 2数据加载
- (void)loadPersonas {
if (self.isLoading) {
return;
}
self.isLoading = YES;
__weak typeof(self) weakSelf = self;
[self.aiVM fetchPersonasWithPageNum:self.currentPage
pageSize:10
completion:^(KBPersonaPageModel * _Nullable pageModel, NSError * _Nullable error) {
weakSelf.isLoading = NO;
if (error) {
NSLog(@"加载人设列表失败:%@", error.localizedDescription);
// TODO: 显示错误提示
return;
}
if (!pageModel || !pageModel.records) {
NSLog(@"人设列表数据为空");
return;
}
// 追加数据
[weakSelf.personas addObjectsFromArray:pageModel.records];
weakSelf.hasMore = pageModel.hasMore;
// 刷新 UI
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.collectionView reloadData];
// 首次加载,预加载前 3 个
if (weakSelf.currentPage == 1) {
[weakSelf preloadDataForIndexes:@[@0, @1, @2]];
}
});
NSLog(@"加载成功:当前 %ld 条,总共 %ld 条,还有更多:%@",
weakSelf.personas.count,
pageModel.total,
pageModel.hasMore ? @"" : @"");
}];
}
- (void)loadMorePersonas {
if (!self.hasMore || self.isLoading) {
return;
}
self.currentPage++;
[self loadPersonas];
}
#pragma mark - 3预加载逻辑
- (void)preloadAdjacentCellsForIndex:(NSInteger)index {
if (index < 0 || index >= self.personas.count) {
return;
}
NSMutableArray *indexesToPreload = [NSMutableArray array];
// 上一个
if (index > 0) {
[indexesToPreload addObject:@(index - 1)];
}
// 当前
[indexesToPreload addObject:@(index)];
// 下一个
if (index < self.personas.count - 1) {
[indexesToPreload addObject:@(index + 1)];
}
[self preloadDataForIndexes:indexesToPreload];
}
- (void)preloadDataForIndexes:(NSArray<NSNumber *> *)indexes {
for (NSNumber *indexNum in indexes) {
if ([self.preloadedIndexes containsObject:indexNum]) {
continue;
}
[self.preloadedIndexes addObject:indexNum];
NSInteger index = [indexNum integerValue];
if (index >= self.personas.count) {
continue;
}
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
KBPersonaChatCell *cell = (KBPersonaChatCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
if (cell) {
[cell preloadDataIfNeeded];
}
NSLog(@"预加载第 %ld 个人设", (long)index);
}
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.personas.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
KBPersonaChatCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"KBPersonaChatCell" forIndexPath:indexPath];
cell.persona = self.personas[indexPath.item];
// 标记为已预加载
[self.preloadedIndexes addObject:@(indexPath.item)];
// 直接触发预加载
[cell preloadDataIfNeeded];
return cell;
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat pageHeight = scrollView.bounds.size.height;
CGFloat offsetY = scrollView.contentOffset.y;
NSInteger currentPage = offsetY / pageHeight;
// 滑动超过 30% 就预加载
if (fmod(offsetY, pageHeight) > pageHeight * 0.3) {
[self preloadAdjacentCellsForIndex:currentPage + 1];
} else {
[self preloadAdjacentCellsForIndex:currentPage];
}
// 接近底部时加载更多
if (offsetY + scrollView.bounds.size.height >= scrollView.contentSize.height - pageHeight) {
[self loadMorePersonas];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
CGFloat pageHeight = scrollView.bounds.size.height;
NSInteger currentPage = scrollView.contentOffset.y / pageHeight;
self.currentIndex = currentPage;
if (currentPage < self.personas.count) {
NSLog(@"当前在第 %ld 个人设:%@", (long)currentPage, self.personas[currentPage].name);
}
}
#pragma mark - Lazy Load
- (UICollectionView *)collectionView {
if (!_collectionView) {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
layout.minimumLineSpacing = 0;
layout.minimumInteritemSpacing = 0;
layout.itemSize = [UIScreen mainScreen].bounds.size;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
_collectionView.pagingEnabled = YES;
_collectionView.showsVerticalScrollIndicator = NO;
_collectionView.backgroundColor = [UIColor whiteColor];
_collectionView.delegate = self;
_collectionView.dataSource = self;
[_collectionView registerClass:[KBPersonaChatCell class] forCellWithReuseIdentifier:@"KBPersonaChatCell"];
if (@available(iOS 11.0, *)) {
_collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
}
return _collectionView;
}
- (KBVoiceInputBar *)voiceInputBar {
if (!_voiceInputBar) {
_voiceInputBar = [[KBVoiceInputBar alloc] init];
_voiceInputBar.delegate = self;
_voiceInputBar.statusText = @"按住按钮开始对话";
}
return _voiceInputBar;
}
#pragma mark - KBVoiceInputBarDelegate
- (void)voiceInputBarDidBeginRecording:(KBVoiceInputBar *)inputBar {
NSLog(@"[KBAIHomeVC] 开始录音");
inputBar.statusText = @"正在聆听...";
// TODO: 开始录音逻辑
// 1. 检查登录状态
// 2. 连接语音识别服务
// 3. 开始录音
}
- (void)voiceInputBarDidEndRecording:(KBVoiceInputBar *)inputBar {
NSLog(@"[KBAIHomeVC] 结束录音");
inputBar.statusText = @"正在识别...";
// TODO: 结束录音逻辑
// 1. 停止录音
// 2. 发送音频数据
// 3. 等待识别结果
}
- (void)voiceInputBarDidCancelRecording:(KBVoiceInputBar *)inputBar {
NSLog(@"[KBAIHomeVC] 取消录音");
inputBar.statusText = @"已取消";
// TODO: 取消录音逻辑
// 1. 停止录音
// 2. 清理资源
}
@end