This commit is contained in:
2026-01-23 21:51:37 +08:00
parent 6ad9783bcb
commit 77fd46aa34
26 changed files with 3681 additions and 199 deletions

View File

@@ -11,7 +11,7 @@
#import "AudioSessionManager.h"
#import "DeepgramStreamingManager.h"
#import "KBAICommentView.h"
#import "KBAiChatView.h"
#import "KBChatTableView.h"
#import "KBAiRecordButton.h"
#import "KBHUD.h"
#import "LSTPopView.h"
@@ -26,7 +26,7 @@
@property(nonatomic, weak) LSTPopView *popView;
// UI
@property(nonatomic, strong) KBAiChatView *chatView;
@property(nonatomic, strong) KBChatTableView *chatView;
@property(nonatomic, strong) KBAiRecordButton *recordButton;
@property(nonatomic, strong) UILabel *statusLabel;
@property(nonatomic, strong) UILabel *transcriptLabel;
@@ -68,8 +68,7 @@
[self setupUI];
[self setupOrchestrator];
[self setupStreamingManager];
// websocket-api Deepgram
// [self setupDeepgramManager];
[self setupDeepgramManager];
}
- (void)viewWillAppear:(BOOL)animated {
@@ -159,7 +158,7 @@
[self.view addSubview:self.transcriptLabel];
//
self.chatView = [[KBAiChatView alloc] init];
self.chatView = [[KBChatTableView alloc] init];
self.chatView.backgroundColor = [UIColor clearColor];
self.chatView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.chatView];
@@ -208,13 +207,20 @@
make.left.equalTo(self.view).offset(16);
make.right.equalTo(self.view).offset(-16);
}];
//
[self.transcriptLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
forAxis:UILayoutConstraintAxisVertical];
[self.chatView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.view);
make.bottom.equalTo(self.tabbarBackgroundView.mas_top).offset(-8);
make.top.equalTo(self.transcriptLabel.mas_bottom).offset(8);
make.left.equalTo(self.view).offset(16);
make.right.equalTo(self.view).offset(-16);
make.bottom.lessThanOrEqualTo(self.recordButton.mas_top).offset(-16);
// 0
make.height.greaterThanOrEqualTo(@100).priority(MASLayoutPriorityDefaultHigh);
}];
// chatView
[self.chatView setContentCompressionResistancePriority:UILayoutPriorityRequired
forAxis:UILayoutConstraintAxisVertical];
[self.recordButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.mas_safeAreaLayoutGuideLeft).offset(20);
@@ -311,7 +317,7 @@
return;
// AI
[strongSelf.chatView addAssistantMessage:@""];
[strongSelf.chatView addAssistantMessage:@"" audioDuration:0 audioData:nil];
};
// AI
@@ -455,6 +461,10 @@
- (void)recordButtonDidBeginPress:(KBAiRecordButton *)button {
NSLog(@"[KBAiMainVC] Record button began press");
//
[self.chatView stopPlayingAudio];
NSString *token = [[KBUserSessionManager shared] accessToken] ?: @"";
if (token.length == 0) {
[[KBUserSessionManager shared] goLoginVC];
@@ -463,20 +473,19 @@
self.statusLabel.text = @"正在连接...";
self.recordButton.state = KBAiRecordButtonStateRecording;
[self.deepgramFullText setString:@""];
self.transcriptLabel.text = @"";
[self.voiceChatAudioBuffer setLength:0];
[self.streamingManager startWithToken:token language:@"en" voiceId:nil];
[self.deepgramManager start];
}
- (void)recordButtonDidEndPress:(KBAiRecordButton *)button {
NSLog(@"[KBAiMainVC] Record button end press");
[self.streamingManager stopAndFinalize];
[self.deepgramManager stopAndFinalize];
}
- (void)recordButtonDidCancelPress:(KBAiRecordButton *)button {
NSLog(@"[KBAiMainVC] Record button cancel press");
[self.voiceChatAudioBuffer setLength:0];
[self.streamingManager cancel];
[self.deepgramManager cancel];
}
#pragma mark - VoiceChatStreamingManagerDelegate
@@ -537,7 +546,7 @@
- (void)voiceChatStreamingManagerDidReceiveLLMStart {
self.statusLabel.text = @"AI 正在思考...";
[self.assistantVisibleText setString:@""];
[self.chatView addAssistantMessage:@""];
[self.chatView addAssistantMessage:@"" audioDuration:0 audioData:nil];
[self.voiceChatAudioBuffer setLength:0];
}
@@ -559,22 +568,36 @@
- (void)voiceChatStreamingManagerDidCompleteWithTranscript:(NSString *)transcript
aiResponse:(NSString *)aiResponse {
NSString *finalText = aiResponse.length > 0 ? aiResponse
: self.assistantVisibleText;
NSString *finalText = aiResponse.length > 0 ? aiResponse : self.assistantVisibleText;
if (aiResponse.length > 0) {
[self.assistantVisibleText setString:aiResponse];
}
//
NSTimeInterval duration = 0;
if (self.voiceChatAudioBuffer.length > 0) {
NSError *error = nil;
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithData:self.voiceChatAudioBuffer
error:&error];
if (!error && player) {
duration = player.duration;
}
}
if (finalText.length > 0) {
[self.chatView updateLastAssistantMessage:finalText];
[self.chatView markLastAssistantMessageComplete];
} else if (transcript.length > 0) {
[self.chatView addAssistantMessage:transcript];
[self.chatView markLastAssistantMessageComplete];
[self.chatView addAssistantMessage:transcript
audioDuration:duration
audioData:self.voiceChatAudioBuffer.length > 0 ? self.voiceChatAudioBuffer : nil];
}
if (self.voiceChatAudioBuffer.length > 0) {
[self playAiAudioData:self.voiceChatAudioBuffer];
[self.voiceChatAudioBuffer setLength:0];
}
self.recordButton.state = KBAiRecordButtonStateNormal;
self.statusLabel.text = @"完成";
}
@@ -629,39 +652,50 @@
self.statusLabel.text = @"识别完成";
self.recordButton.state = KBAiRecordButtonStateNormal;
// NSString *finalText = [self.deepgramFullText copy];
// if (finalText.length > 0) {
// __weak typeof(self) weakSelf = self;
// [KBHUD show];
// [self.aiVM syncChatWithTranscript:finalText
// completion:^(KBAiSyncResponse *_Nullable response,
// NSError *_Nullable error) {
// __strong typeof(weakSelf) strongSelf = weakSelf;
// if (!strongSelf) {
// return;
// }
// dispatch_async(dispatch_get_main_queue(), ^{
// [KBHUD dismiss];
// if (error) {
// [KBHUD showError:error.localizedDescription ?: @"请求失败"];
// return;
// }
//
// NSString *aiResponse = response.data.aiResponse ?: @"";
// if (aiResponse.length > 0) {
// NSLog(@"[KBAiMainVC] /chat/sync aiResponse: %@", aiResponse);
// }
//
// NSData *audioData = response.data.audioData;
// if (audioData.length > 0) {
// NSLog(@"[KBAiMainVC] /chat/sync audio ready, start play");
// [strongSelf playAiAudioData:audioData];
// } else {
// NSLog(@"[KBAiMainVC] /chat/sync audioData empty");
// }
// });
// }];
// }
NSString *finalText = [self.deepgramFullText copy];
if (finalText.length == 0) {
return;
}
//
[self.chatView addUserMessage:finalText];
__weak typeof(self) weakSelf = self;
[KBHUD showWithStatus:@"AI 思考中..."];
// chat/message
[self.aiVM requestChatMessageWithContent:finalText
completion:^(KBAiMessageResponse *_Nullable response,
NSError *_Nullable error) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD dismiss];
if (error) {
[KBHUD showError:error.localizedDescription ?: @"请求失败"];
return;
}
// AI
NSString *aiResponse = response.data.aiResponse ?: response.data.content ?: response.data.text ?: response.data.message ?: @"";
if (aiResponse.length == 0) {
[KBHUD showError:@"AI 回复为空"];
return;
}
// audioId
NSString *audioId = response.data.audioId;
// AI audioId
[strongSelf.chatView addAssistantMessage:aiResponse
audioId:audioId];
});
}];
}
- (void)deepgramStreamingManagerDidFail:(NSError *)error {