处理语音
This commit is contained in:
@@ -66,6 +66,9 @@ typedef NS_ENUM(NSInteger, KBAiChatMessageType) {
|
|||||||
/// 创建 AI 消息(仅文本,无音频)
|
/// 创建 AI 消息(仅文本,无音频)
|
||||||
+ (instancetype)assistantMessageWithText:(NSString *)text;
|
+ (instancetype)assistantMessageWithText:(NSString *)text;
|
||||||
|
|
||||||
|
/// 创建加载中的 AI 消息
|
||||||
|
+ (instancetype)loadingAssistantMessage;
|
||||||
|
|
||||||
/// 创建时间戳消息
|
/// 创建时间戳消息
|
||||||
+ (instancetype)timeMessageWithTimestamp:(NSDate *)timestamp;
|
+ (instancetype)timeMessageWithTimestamp:(NSDate *)timestamp;
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,20 @@
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (instancetype)loadingAssistantMessage {
|
||||||
|
KBAiChatMessage *message = [[KBAiChatMessage alloc] init];
|
||||||
|
message.type = KBAiChatMessageTypeAssistant;
|
||||||
|
message.text = @"";
|
||||||
|
message.timestamp = [NSDate date];
|
||||||
|
message.audioId = nil;
|
||||||
|
message.audioDuration = 0;
|
||||||
|
message.audioData = nil;
|
||||||
|
message.isComplete = NO;
|
||||||
|
message.isLoading = YES;
|
||||||
|
message.needsTypewriterEffect = NO;
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
+ (instancetype)timeMessageWithTimestamp:(NSDate *)timestamp {
|
+ (instancetype)timeMessageWithTimestamp:(NSDate *)timestamp {
|
||||||
KBAiChatMessage *message = [[KBAiChatMessage alloc] init];
|
KBAiChatMessage *message = [[KBAiChatMessage alloc] init];
|
||||||
message.type = KBAiChatMessageTypeTime;
|
message.type = KBAiChatMessageTypeTime;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
@property (nonatomic, strong) UIView *bubbleView;
|
@property (nonatomic, strong) UIView *bubbleView;
|
||||||
@property (nonatomic, strong, readwrite) UILabel *messageLabel; // readwrite 允许内部修改
|
@property (nonatomic, strong, readwrite) UILabel *messageLabel; // readwrite 允许内部修改
|
||||||
@property (nonatomic, strong) UIActivityIndicatorView *loadingIndicator;
|
@property (nonatomic, strong) UIActivityIndicatorView *loadingIndicator;
|
||||||
|
@property (nonatomic, strong) UIActivityIndicatorView *messageLoadingIndicator; // AI 消息加载指示器
|
||||||
@property (nonatomic, strong) KBAiChatMessage *currentMessage;
|
@property (nonatomic, strong) KBAiChatMessage *currentMessage;
|
||||||
|
|
||||||
// 打字机效果
|
// 打字机效果
|
||||||
@@ -61,6 +62,12 @@
|
|||||||
self.loadingIndicator.hidesWhenStopped = YES;
|
self.loadingIndicator.hidesWhenStopped = YES;
|
||||||
[self.contentView addSubview:self.loadingIndicator];
|
[self.contentView addSubview:self.loadingIndicator];
|
||||||
|
|
||||||
|
// AI 消息加载指示器(用于等待 AI 回复)
|
||||||
|
self.messageLoadingIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
|
||||||
|
self.messageLoadingIndicator.color = [UIColor whiteColor];
|
||||||
|
self.messageLoadingIndicator.hidesWhenStopped = YES;
|
||||||
|
[self.contentView addSubview:self.messageLoadingIndicator];
|
||||||
|
|
||||||
// 气泡视图
|
// 气泡视图
|
||||||
self.bubbleView = [[UIView alloc] init];
|
self.bubbleView = [[UIView alloc] init];
|
||||||
self.bubbleView.backgroundColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:0.7];
|
self.bubbleView.backgroundColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:0.7];
|
||||||
@@ -83,8 +90,7 @@
|
|||||||
[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) {
|
||||||
@@ -96,6 +102,12 @@
|
|||||||
make.center.equalTo(self.voiceButton);
|
make.center.equalTo(self.voiceButton);
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
// AI 消息加载指示器约束(与 bubbleView 位置一致)
|
||||||
|
[self.messageLoadingIndicator mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.left.equalTo(self.contentView).offset(16);
|
||||||
|
make.top.equalTo(self.voiceButton.mas_bottom).offset(12);
|
||||||
|
}];
|
||||||
|
|
||||||
// 关键修复:bubbleView 必须有明确的高度约束链
|
// 关键修复:bubbleView 必须有明确的高度约束链
|
||||||
[self.bubbleView mas_makeConstraints:^(MASConstraintMaker *make) {
|
[self.bubbleView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
make.top.equalTo(self.voiceButton.mas_bottom).offset(4);
|
make.top.equalTo(self.voiceButton.mas_bottom).offset(4);
|
||||||
@@ -116,8 +128,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)configureWithMessage:(KBAiChatMessage *)message {
|
- (void)configureWithMessage:(KBAiChatMessage *)message {
|
||||||
NSLog(@"[KBChatAssistantMessageCell] 配置消息 - 文本长度: %lu, isComplete: %d, needsTypewriter: %d, 打字机运行中: %d",
|
NSLog(@"[KBChatAssistantMessageCell] 配置消息 - 文本长度: %lu, isComplete: %d, needsTypewriter: %d, isLoading: %d, 打字机运行中: %d",
|
||||||
(unsigned long)message.text.length, message.isComplete, message.needsTypewriterEffect,
|
(unsigned long)message.text.length, message.isComplete, message.needsTypewriterEffect, message.isLoading,
|
||||||
(self.typewriterTimer && self.typewriterTimer.isValid));
|
(self.typewriterTimer && self.typewriterTimer.isValid));
|
||||||
|
|
||||||
// 先停止之前的打字机效果(无论是否是同一条消息)
|
// 先停止之前的打字机效果(无论是否是同一条消息)
|
||||||
@@ -125,6 +137,21 @@
|
|||||||
|
|
||||||
self.currentMessage = message;
|
self.currentMessage = message;
|
||||||
|
|
||||||
|
// 处理 loading 状态
|
||||||
|
if (message.isLoading) {
|
||||||
|
self.messageLabel.attributedText = nil;
|
||||||
|
self.messageLabel.text = @"";
|
||||||
|
self.bubbleView.hidden = YES;
|
||||||
|
self.voiceButton.hidden = YES;
|
||||||
|
self.durationLabel.hidden = YES;
|
||||||
|
[self.messageLoadingIndicator startAnimating];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非 loading 状态,隐藏 loading 指示器,显示气泡
|
||||||
|
[self.messageLoadingIndicator stopAnimating];
|
||||||
|
self.bubbleView.hidden = NO;
|
||||||
|
|
||||||
// 只有明确需要打字机效果的消息才使用打字机
|
// 只有明确需要打字机效果的消息才使用打字机
|
||||||
if (message.needsTypewriterEffect && !message.isComplete && message.text.length > 0) {
|
if (message.needsTypewriterEffect && !message.isComplete && message.text.length > 0) {
|
||||||
// 启动新的打字机效果
|
// 启动新的打字机效果
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
/// 清空所有消息
|
/// 清空所有消息
|
||||||
- (void)clearMessages;
|
- (void)clearMessages;
|
||||||
|
|
||||||
|
/// 移除 loading AI 消息
|
||||||
|
- (void)removeLoadingAssistantMessage;
|
||||||
|
|
||||||
/// 滚动到底部
|
/// 滚动到底部
|
||||||
- (void)scrollToBottom;
|
- (void)scrollToBottom;
|
||||||
|
|
||||||
|
|||||||
@@ -236,6 +236,19 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
|||||||
[self updateFooterVisibility];
|
[self updateFooterVisibility];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)removeLoadingAssistantMessage {
|
||||||
|
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];
|
||||||
|
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||||
|
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
|
||||||
|
NSLog(@"[KBChatTableView] 移除 loading AI 消息,索引: %ld", (long)i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (void)scrollToBottom {
|
- (void)scrollToBottom {
|
||||||
[self scrollToBottomAnimated:YES];
|
[self scrollToBottomAnimated:YES];
|
||||||
}
|
}
|
||||||
@@ -339,6 +352,11 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 自动预加载音频(不自动播放)
|
||||||
|
if (message.type == KBAiChatMessageTypeAssistant && message.audioId.length > 0) {
|
||||||
|
[self preloadAudioForMessage:message];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)reloadWithMessages:(NSArray<KBAiChatMessage *> *)messages
|
- (void)reloadWithMessages:(NSArray<KBAiChatMessage *> *)messages
|
||||||
@@ -620,8 +638,8 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
|||||||
[cell showLoadingAnimation];
|
[cell showLoadingAnimation];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始轮询请求(最多5次,每次间隔0.5秒)
|
// 开始轮询请求(最多10次,每次间隔1秒,共10秒)
|
||||||
[self pollAudioForMessage:message atIndexPath:indexPath retryCount:0 maxRetries:5];
|
[self pollAudioForMessage:message atIndexPath:indexPath retryCount:0 maxRetries:10];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)pollAudioForMessage:(KBAiChatMessage *)message
|
- (void)pollAudioForMessage:(KBAiChatMessage *)message
|
||||||
@@ -638,6 +656,7 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
|||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
// 如果成功获取到 audioURL
|
// 如果成功获取到 audioURL
|
||||||
if (!error && audioURL.length > 0) {
|
if (!error && audioURL.length > 0) {
|
||||||
|
NSLog(@"[KBChatTableView] 音频 URL 获取成功(第 %ld 次)", (long)(retryCount + 1));
|
||||||
// 下载并播放音频
|
// 下载并播放音频
|
||||||
[strongSelf downloadAndPlayAudioFromURL:audioURL
|
[strongSelf downloadAndPlayAudioFromURL:audioURL
|
||||||
forMessage:message
|
forMessage:message
|
||||||
@@ -647,11 +666,11 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
|||||||
|
|
||||||
// 如果还没达到最大重试次数,继续轮询
|
// 如果还没达到最大重试次数,继续轮询
|
||||||
if (retryCount < maxRetries - 1) {
|
if (retryCount < maxRetries - 1) {
|
||||||
NSLog(@"[KBChatTableView] 音频未就绪,0.5秒后重试 (%ld/%ld)",
|
NSLog(@"[KBChatTableView] 音频未就绪,1秒后重试 (%ld/%ld)",
|
||||||
(long)(retryCount + 1), (long)maxRetries);
|
(long)(retryCount + 1), (long)maxRetries);
|
||||||
|
|
||||||
// 0.5秒后重试
|
// 1秒后重试
|
||||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)),
|
||||||
dispatch_get_main_queue(), ^{
|
dispatch_get_main_queue(), ^{
|
||||||
[strongSelf pollAudioForMessage:message
|
[strongSelf pollAudioForMessage:message
|
||||||
atIndexPath:indexPath
|
atIndexPath:indexPath
|
||||||
@@ -786,6 +805,115 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - Audio Preload (自动预加载,不播放)
|
||||||
|
|
||||||
|
/// 预加载音频(不自动播放)
|
||||||
|
- (void)preloadAudioForMessage:(KBAiChatMessage *)message {
|
||||||
|
if (!message || message.audioId.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已经有音频数据,不需要预加载
|
||||||
|
if (message.audioData && message.audioData.length > 0) {
|
||||||
|
NSLog(@"[KBChatTableView] 音频已缓存,跳过预加载");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSLog(@"[KBChatTableView] 开始预加载音频,audioId: %@", message.audioId);
|
||||||
|
|
||||||
|
// 记录开始时间
|
||||||
|
NSDate *startTime = [NSDate date];
|
||||||
|
|
||||||
|
// 开始轮询请求(最多10次,每次间隔1秒,共10秒)
|
||||||
|
[self pollPreloadAudioForMessage:message retryCount:0 maxRetries:10 startTime:startTime];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 轮询预加载音频
|
||||||
|
- (void)pollPreloadAudioForMessage:(KBAiChatMessage *)message
|
||||||
|
retryCount:(NSInteger)retryCount
|
||||||
|
maxRetries:(NSInteger)maxRetries
|
||||||
|
startTime:(NSDate *)startTime {
|
||||||
|
|
||||||
|
__weak typeof(self) weakSelf = self;
|
||||||
|
[self.aiVM requestAudioWithAudioId:message.audioId
|
||||||
|
completion:^(NSString *_Nullable audioURL, NSError *_Nullable error) {
|
||||||
|
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||||
|
if (!strongSelf) return;
|
||||||
|
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
// 如果成功获取到 audioURL
|
||||||
|
if (!error && audioURL.length > 0) {
|
||||||
|
// 计算获取 audioURL 的耗时
|
||||||
|
NSTimeInterval elapsed = [[NSDate date] timeIntervalSinceDate:startTime];
|
||||||
|
NSLog(@"[KBChatTableView] ✅ 预加载音频 URL 获取成功(第 %ld 次),耗时: %.2f 秒", (long)(retryCount + 1), elapsed);
|
||||||
|
// 下载音频(不播放)
|
||||||
|
[strongSelf downloadAudioFromURL:audioURL forMessage:message startTime:startTime];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果还没达到最大重试次数,继续轮询
|
||||||
|
if (retryCount < maxRetries - 1) {
|
||||||
|
NSLog(@"[KBChatTableView] 预加载音频未就绪,1秒后重试 (%ld/%ld)",
|
||||||
|
(long)(retryCount + 1), (long)maxRetries);
|
||||||
|
|
||||||
|
// 1秒后重试(给后端更多时间生成音频)
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)),
|
||||||
|
dispatch_get_main_queue(), ^{
|
||||||
|
[strongSelf pollPreloadAudioForMessage:message
|
||||||
|
retryCount:retryCount + 1
|
||||||
|
maxRetries:maxRetries
|
||||||
|
startTime:startTime];
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
NSTimeInterval elapsed = [[NSDate date] timeIntervalSinceDate:startTime];
|
||||||
|
NSLog(@"[KBChatTableView] ❌ 预加载音频失败,已重试 %ld 次,总耗时: %.2f 秒", (long)maxRetries, elapsed);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 下载音频(不播放)
|
||||||
|
- (void)downloadAudioFromURL:(NSString *)urlString
|
||||||
|
forMessage:(KBAiChatMessage *)message
|
||||||
|
startTime:(NSDate *)startTime {
|
||||||
|
NSURL *url = [NSURL URLWithString:urlString];
|
||||||
|
if (!url) {
|
||||||
|
NSLog(@"[KBChatTableView] 预加载:无效的音频 URL: %@", urlString);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||||
|
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
|
||||||
|
|
||||||
|
NSURLSessionDataTask *task = [session dataTaskWithURL:url
|
||||||
|
completionHandler:^(NSData *_Nullable data,
|
||||||
|
NSURLResponse *_Nullable response,
|
||||||
|
NSError *_Nullable error) {
|
||||||
|
if (error || !data || data.length == 0) {
|
||||||
|
NSLog(@"[KBChatTableView] 预加载:下载音频失败: %@", error.localizedDescription ?: @"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
// 缓存音频数据到消息对象
|
||||||
|
message.audioData = data;
|
||||||
|
|
||||||
|
// 计算音频时长
|
||||||
|
NSError *playerError = nil;
|
||||||
|
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithData:data error:&playerError];
|
||||||
|
if (!playerError && player) {
|
||||||
|
message.audioDuration = player.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算总耗时(从开始请求到下载完成)
|
||||||
|
NSTimeInterval totalElapsed = [[NSDate date] timeIntervalSinceDate:startTime];
|
||||||
|
NSLog(@"[KBChatTableView] ✅ 预加载音频完成,音频时长: %.2f秒,总耗时: %.2f 秒", message.audioDuration, totalElapsed);
|
||||||
|
});
|
||||||
|
}];
|
||||||
|
|
||||||
|
[task resume];
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - AVAudioPlayerDelegate
|
#pragma mark - AVAudioPlayerDelegate
|
||||||
|
|
||||||
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
|
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
- (void)appendAssistantMessage:(NSString *)text
|
- (void)appendAssistantMessage:(NSString *)text
|
||||||
audioId:(nullable NSString *)audioId;
|
audioId:(nullable NSString *)audioId;
|
||||||
|
|
||||||
|
/// 添加 loading AI 消息
|
||||||
|
- (void)appendLoadingAssistantMessage;
|
||||||
|
|
||||||
|
/// 移除 loading AI 消息
|
||||||
|
- (void)removeLoadingAssistantMessage;
|
||||||
|
|
||||||
/// 更新聊天列表底部 inset
|
/// 更新聊天列表底部 inset
|
||||||
- (void)updateChatViewBottomInset:(CGFloat)bottomInset;
|
- (void)updateChatViewBottomInset:(CGFloat)bottomInset;
|
||||||
|
|
||||||
|
|||||||
@@ -503,6 +503,10 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
[self ensureOpeningMessageAtTop];
|
[self ensureOpeningMessageAtTop];
|
||||||
|
|
||||||
|
// 查找并移除 loading 消息
|
||||||
|
[self removeLoadingAssistantMessage];
|
||||||
|
|
||||||
KBAiChatMessage *message = [KBAiChatMessage assistantMessageWithText:text
|
KBAiChatMessage *message = [KBAiChatMessage assistantMessageWithText:text
|
||||||
audioId:audioId];
|
audioId:audioId];
|
||||||
message.needsTypewriterEffect = YES;
|
message.needsTypewriterEffect = YES;
|
||||||
@@ -510,6 +514,33 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
|||||||
[self.chatView addMessage:message autoScroll:YES];
|
[self.chatView addMessage:message autoScroll:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 添加 loading AI 消息
|
||||||
|
- (void)appendLoadingAssistantMessage {
|
||||||
|
if (!self.messages) {
|
||||||
|
self.messages = [NSMutableArray array];
|
||||||
|
}
|
||||||
|
|
||||||
|
[self ensureOpeningMessageAtTop];
|
||||||
|
KBAiChatMessage *message = [KBAiChatMessage loadingAssistantMessage];
|
||||||
|
[self.messages addObject:message];
|
||||||
|
[self.chatView addMessage:message autoScroll:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 移除 loading AI 消息
|
||||||
|
- (void)removeLoadingAssistantMessage {
|
||||||
|
// 从数据源中移除
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 chatView 中移除
|
||||||
|
[self.chatView removeLoadingAssistantMessage];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)updateChatViewBottomInset:(CGFloat)bottomInset {
|
- (void)updateChatViewBottomInset:(CGFloat)bottomInset {
|
||||||
[self.chatView updateContentBottomInset:bottomInset];
|
[self.chatView updateContentBottomInset:bottomInset];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -964,6 +964,8 @@
|
|||||||
KBPersonaChatCell *currentCell = [self currentPersonaCell];
|
KBPersonaChatCell *currentCell = [self currentPersonaCell];
|
||||||
if (currentCell && appendToUI) {
|
if (currentCell && appendToUI) {
|
||||||
[currentCell appendUserMessage:text];
|
[currentCell appendUserMessage:text];
|
||||||
|
// 添加 loading AI 消息
|
||||||
|
[currentCell appendLoadingAssistantMessage];
|
||||||
}
|
}
|
||||||
|
|
||||||
self.isWaitingForAIResponse = YES;
|
self.isWaitingForAIResponse = YES;
|
||||||
@@ -990,12 +992,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response.code == 50030) {
|
if (response.code == 50030) {
|
||||||
|
// 移除 loading 消息
|
||||||
|
if (cell) {
|
||||||
|
[cell removeLoadingAssistantMessage];
|
||||||
|
}
|
||||||
NSString *message = response.message ?: @"";
|
NSString *message = response.message ?: @"";
|
||||||
[strongSelf showChatLimitPopWithMessage:message];
|
[strongSelf showChatLimitPopWithMessage:message];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response || !response.data) {
|
if (!response || !response.data) {
|
||||||
|
// 移除 loading 消息
|
||||||
|
if (cell) {
|
||||||
|
[cell removeLoadingAssistantMessage];
|
||||||
|
}
|
||||||
NSString *message = response.message ?: @"聊天响应为空";
|
NSString *message = response.message ?: @"聊天响应为空";
|
||||||
NSLog(@"[KBAIHomeVC] 聊天响应为空:%@", message);
|
NSLog(@"[KBAIHomeVC] 聊天响应为空:%@", message);
|
||||||
if (message.length > 0) {
|
if (message.length > 0) {
|
||||||
@@ -1007,11 +1017,16 @@
|
|||||||
NSString *aiResponse = response.data.aiResponse ?: response.data.content ?: response.data.text ?: response.data.message ?: @"";
|
NSString *aiResponse = response.data.aiResponse ?: response.data.content ?: response.data.text ?: response.data.message ?: @"";
|
||||||
NSString *audioId = response.data.audioId;
|
NSString *audioId = response.data.audioId;
|
||||||
if (aiResponse.length == 0) {
|
if (aiResponse.length == 0) {
|
||||||
|
// 移除 loading 消息
|
||||||
|
if (cell) {
|
||||||
|
[cell removeLoadingAssistantMessage];
|
||||||
|
}
|
||||||
NSLog(@"[KBAIHomeVC] AI 回复为空");
|
NSLog(@"[KBAIHomeVC] AI 回复为空");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cell) {
|
if (cell) {
|
||||||
|
// appendAssistantMessage 内部会自动移除 loading 消息
|
||||||
[cell appendAssistantMessage:aiResponse audioId:audioId];
|
[cell appendAssistantMessage:aiResponse audioId:audioId];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user