// // KBAiMainVC.m // keyBoard // // Created by Mac on 2026/1/15. // #import "KBAiMainVC.h" #import "ConversationOrchestrator.h" #import "KBAiChatView.h" #import "KBAiRecordButton.h" #import "KBAICommentView.h" #import "LSTPopView.h" @interface KBAiMainVC () @property (nonatomic,weak) LSTPopView *popView; // UI @property(nonatomic, strong) KBAiChatView *chatView; @property(nonatomic, strong) KBAiRecordButton *recordButton; @property(nonatomic, strong) UILabel *statusLabel; // 核心模块 @property(nonatomic, strong) ConversationOrchestrator *orchestrator; @end @implementation KBAiMainVC #pragma mark - Lifecycle - (void)viewDidLoad { [super viewDidLoad]; [self setupUI]; [self setupOrchestrator]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // 页面消失时停止对话 [self.orchestrator stop]; } #pragma mark - UI Setup - (void)setupUI { self.view.backgroundColor = [UIColor systemBackgroundColor]; self.title = @"AI 助手"; // 安全区域 UILayoutGuide *safeArea = self.view.safeAreaLayoutGuide; // 状态标签 self.statusLabel = [[UILabel alloc] init]; self.statusLabel.text = @"按住按钮开始对话"; self.statusLabel.font = [UIFont systemFontOfSize:14]; self.statusLabel.textColor = [UIColor secondaryLabelColor]; self.statusLabel.textAlignment = NSTextAlignmentCenter; self.statusLabel.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:self.statusLabel]; // 聊天视图 self.chatView = [[KBAiChatView alloc] init]; self.chatView.backgroundColor = [UIColor systemBackgroundColor]; self.chatView.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:self.chatView]; // 录音按钮 self.recordButton = [[KBAiRecordButton alloc] init]; self.recordButton.delegate = self; self.recordButton.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:self.recordButton]; // 布局约束 [NSLayoutConstraint activateConstraints:@[ // 状态标签 [self.statusLabel.topAnchor constraintEqualToAnchor:safeArea.topAnchor constant:8], [self.statusLabel.leadingAnchor constraintEqualToAnchor:safeArea.leadingAnchor constant:16], [self.statusLabel.trailingAnchor constraintEqualToAnchor:safeArea.trailingAnchor constant:-16], // 聊天视图 [self.chatView.topAnchor constraintEqualToAnchor:self.statusLabel.bottomAnchor constant:8], [self.chatView.leadingAnchor constraintEqualToAnchor:safeArea.leadingAnchor], [self.chatView.trailingAnchor constraintEqualToAnchor:safeArea.trailingAnchor], [self.chatView.bottomAnchor constraintEqualToAnchor:self.recordButton.topAnchor constant:-16], // 录音按钮 [self.recordButton.leadingAnchor constraintEqualToAnchor:safeArea.leadingAnchor constant:20], [self.recordButton.trailingAnchor constraintEqualToAnchor:safeArea.trailingAnchor constant:-20], [self.recordButton.bottomAnchor constraintEqualToAnchor:safeArea.bottomAnchor constant:-16], [self.recordButton.heightAnchor constraintEqualToConstant:50], ]]; } #pragma mark - Orchestrator Setup - (void)setupOrchestrator { self.orchestrator = [[ConversationOrchestrator alloc] init]; // 配置服务器地址(TODO: 替换为实际地址) // self.orchestrator.asrServerURL = @"wss://your-asr-server.com/ws/asr"; // self.orchestrator.llmServerURL = // @"https://your-llm-server.com/api/chat/stream"; // self.orchestrator.ttsServerURL = @"https://your-tts-server.com/api/tts"; __weak typeof(self) weakSelf = self; // 状态变化回调 self.orchestrator.onStateChange = ^(ConversationState state) { __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) return; [strongSelf updateStatusForState:state]; }; // 实时识别文本回调 self.orchestrator.onPartialText = ^(NSString *text) { __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) return; strongSelf.statusLabel.text = text.length > 0 ? text : @"正在识别..."; }; // 用户最终文本回调 self.orchestrator.onUserFinalText = ^(NSString *text) { __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) return; if (text.length > 0) { [strongSelf.chatView addUserMessage:text]; } }; // AI 可见文本回调(打字机效果) self.orchestrator.onAssistantVisibleText = ^(NSString *text) { __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) return; [strongSelf.chatView updateLastAssistantMessage:text]; }; // AI 完整回复回调 self.orchestrator.onAssistantFullText = ^(NSString *text) { __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) return; [strongSelf.chatView updateLastAssistantMessage:text]; [strongSelf.chatView markLastAssistantMessageComplete]; }; // 音量更新回调 self.orchestrator.onVolumeUpdate = ^(float rms) { __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) return; [strongSelf.recordButton updateVolumeRMS:rms]; }; // AI 开始说话 self.orchestrator.onSpeakingStart = ^{ __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) return; // 添加空的 AI 消息占位 [strongSelf.chatView addAssistantMessage:@""]; }; // AI 说话结束 self.orchestrator.onSpeakingEnd = ^{ __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) return; [strongSelf.chatView markLastAssistantMessageComplete]; }; // 错误回调 self.orchestrator.onError = ^(NSError *error) { __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) return; [strongSelf showError:error]; }; } #pragma mark - 事件 - (void)showComment{ CGFloat customViewHeight = KB_SCREEN_HEIGHT*(0.8); KBAICommentView *customView = [[KBAICommentView alloc] initWithFrame:CGRectMake(0, 0, KB_SCREEN_WIDTH, customViewHeight)]; LSTPopView *popView = [LSTPopView initWithCustomView:customView parentView:self.view popStyle:LSTPopStyleSmoothFromBottom dismissStyle:LSTDismissStyleSmoothToBottom]; self.popView = popView; popView.priority = 1000; popView.hemStyle = LSTHemStyleBottom; popView.dragStyle = LSTDragStyleY_Positive; popView.dragDistance = customViewHeight*0.5; popView.sweepStyle = LSTSweepStyleY_Positive; popView.swipeVelocity = 1600; popView.sweepDismissStyle = LSTSweepDismissStyleSmooth; [popView pop]; } #pragma mark - UI Updates - (void)updateStatusForState:(ConversationState)state { switch (state) { case ConversationStateIdle: self.statusLabel.text = @"按住按钮开始对话"; self.recordButton.state = KBAiRecordButtonStateNormal; break; case ConversationStateListening: self.statusLabel.text = @"正在聆听..."; self.recordButton.state = KBAiRecordButtonStateRecording; break; case ConversationStateRecognizing: self.statusLabel.text = @"正在识别..."; self.recordButton.state = KBAiRecordButtonStateNormal; break; case ConversationStateThinking: self.statusLabel.text = @"AI 正在思考..."; self.recordButton.state = KBAiRecordButtonStateNormal; break; case ConversationStateSpeaking: self.statusLabel.text = @"AI 正在回复..."; self.recordButton.state = KBAiRecordButtonStateNormal; break; } } - (void)showError:(NSError *)error { UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"错误" message:error.localizedDescription preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]]; [self presentViewController:alert animated:YES completion:nil]; } #pragma mark - KBAiRecordButtonDelegate - (void)recordButtonDidBeginPress:(KBAiRecordButton *)button { [self.orchestrator userDidPressRecord]; } - (void)recordButtonDidEndPress:(KBAiRecordButton *)button { [self.orchestrator userDidReleaseRecord]; } - (void)recordButtonDidCancelPress:(KBAiRecordButton *)button { // 取消录音(同样调用 release,ASR 会返回空或部分结果) [self.orchestrator userDidReleaseRecord]; } @end