This commit is contained in:
2026-01-26 18:43:07 +08:00
parent 6a177ceebc
commit a22599feda
4 changed files with 241 additions and 27 deletions

View File

@@ -9,10 +9,11 @@
#import "KBPersonaChatCell.h"
#import "KBPersonaModel.h"
#import "KBVoiceInputBar.h"
#import "KBVoiceToTextManager.h"
#import "AiVM.h"
#import <Masonry/Masonry.h>
@interface KBAIHomeVC () <UICollectionViewDelegate, UICollectionViewDataSource, KBVoiceInputBarDelegate>
@interface KBAIHomeVC () <UICollectionViewDelegate, UICollectionViewDataSource, KBVoiceToTextManagerDelegate>
///
@property (nonatomic, strong) UICollectionView *collectionView;
@@ -20,6 +21,9 @@
///
@property (nonatomic, strong) KBVoiceInputBar *voiceInputBar;
///
@property (nonatomic, strong) KBVoiceToTextManager *voiceToTextManager;
///
@property (nonatomic, strong) NSMutableArray<KBPersonaModel *> *personas;
@@ -61,6 +65,7 @@
self.aiVM = [[AiVM alloc] init];
[self setupUI];
[self setupVoiceToTextManager];
[self loadPersonas];
}
@@ -235,6 +240,14 @@
}
}
#pragma mark - 4
- (void)setupVoiceToTextManager {
self.voiceToTextManager = [[KBVoiceToTextManager alloc] initWithInputBar:self.voiceInputBar];
self.voiceToTextManager.delegate = self;
[self.voiceToTextManager prepareConnection];
}
#pragma mark - Lazy Load
- (UICollectionView *)collectionView {
@@ -263,41 +276,26 @@
- (KBVoiceInputBar *)voiceInputBar {
if (!_voiceInputBar) {
_voiceInputBar = [[KBVoiceInputBar alloc] init];
_voiceInputBar.delegate = self;
_voiceInputBar.statusText = @"按住按钮开始对话";
}
return _voiceInputBar;
}
#pragma mark - KBVoiceInputBarDelegate
#pragma mark - KBVoiceToTextManagerDelegate
- (void)voiceInputBarDidBeginRecording:(KBVoiceInputBar *)inputBar {
NSLog(@"[KBAIHomeVC] 开始录音");
inputBar.statusText = @"正在聆听...";
- (void)voiceToTextManager:(KBVoiceToTextManager *)manager
didReceiveFinalText:(NSString *)text {
if (text.length == 0) {
return;
}
NSLog(@"[KBAIHomeVC] 语音识别结果:%@", text);
// TODO:
// 1.
// 2.
// 3.
// TODO: 使
}
- (void)voiceInputBarDidEndRecording:(KBVoiceInputBar *)inputBar {
NSLog(@"[KBAIHomeVC] 结束录音");
inputBar.statusText = @"正在识别...";
// TODO:
// 1.
// 2.
// 3.
}
- (void)voiceInputBarDidCancelRecording:(KBVoiceInputBar *)inputBar {
NSLog(@"[KBAIHomeVC] 取消录音");
inputBar.statusText = @"已取消";
// TODO:
// 1.
// 2.
- (void)voiceToTextManager:(KBVoiceToTextManager *)manager
didFailWithError:(NSError *)error {
NSLog(@"[KBAIHomeVC] 语音识别失败:%@", error.localizedDescription);
}
@end

View File

@@ -0,0 +1,40 @@
//
// KBVoiceToTextManager.h
// keyBoard
//
// Created by Mac on 2026/1/26.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class KBVoiceInputBar;
@class KBVoiceToTextManager;
@protocol KBVoiceToTextManagerDelegate <NSObject>
@optional
- (void)voiceToTextManagerDidBeginRecording:(KBVoiceToTextManager *)manager;
- (void)voiceToTextManagerDidEndRecording:(KBVoiceToTextManager *)manager;
- (void)voiceToTextManagerDidCancelRecording:(KBVoiceToTextManager *)manager;
- (void)voiceToTextManager:(KBVoiceToTextManager *)manager
didUpdateInterimText:(NSString *)text;
- (void)voiceToTextManager:(KBVoiceToTextManager *)manager
didReceiveFinalText:(NSString *)text;
- (void)voiceToTextManager:(KBVoiceToTextManager *)manager
didFailWithError:(NSError *)error;
@end
/// Voice-to-text manager (binds KBVoiceInputBar and uses Deepgram).
@interface KBVoiceToTextManager : NSObject
@property(nonatomic, weak) id<KBVoiceToTextManagerDelegate> delegate;
@property(nonatomic, weak, readonly) KBVoiceInputBar *inputBar;
- (instancetype)initWithInputBar:(KBVoiceInputBar *)inputBar;
- (void)prepareConnection;
- (void)disconnect;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,170 @@
//
// KBVoiceToTextManager.m
// keyBoard
//
// Created by Mac on 2026/1/26.
//
#import "KBVoiceToTextManager.h"
#import "DeepgramStreamingManager.h"
#import "KBVoiceInputBar.h"
@interface KBVoiceToTextManager () <KBVoiceInputBarDelegate,
DeepgramStreamingManagerDelegate>
@property(nonatomic, strong) DeepgramStreamingManager *deepgramManager;
@property(nonatomic, weak) KBVoiceInputBar *inputBar;
@property(nonatomic, strong) NSMutableString *fullText;
@end
@implementation KBVoiceToTextManager
- (instancetype)initWithInputBar:(KBVoiceInputBar *)inputBar {
self = [super init];
if (self) {
_inputBar = inputBar;
_inputBar.delegate = self;
_fullText = [[NSMutableString alloc] init];
[self setupDeepgram];
}
return self;
}
- (void)dealloc {
[self.deepgramManager disconnect];
}
#pragma mark - Public Methods
- (void)prepareConnection {
[self.deepgramManager prepareConnection];
}
- (void)disconnect {
[self.deepgramManager disconnect];
}
#pragma mark - Private Methods
- (void)setupDeepgram {
self.deepgramManager = [[DeepgramStreamingManager alloc] init];
self.deepgramManager.delegate = self;
self.deepgramManager.serverURL = @"wss://api.deepgram.com/v1/listen";
self.deepgramManager.apiKey = @"9c792eb63a65d644cbc95785155754cd1e84f8cf";
self.deepgramManager.language = @"en";
self.deepgramManager.model = @"nova-3";
self.deepgramManager.punctuate = YES;
self.deepgramManager.smartFormat = YES;
self.deepgramManager.interimResults = YES;
self.deepgramManager.encoding = @"linear16";
self.deepgramManager.sampleRate = 16000.0;
self.deepgramManager.channels = 1;
}
- (void)resetTranscript {
[self.fullText setString:@""];
}
#pragma mark - KBVoiceInputBarDelegate
- (void)voiceInputBarDidBeginRecording:(KBVoiceInputBar *)inputBar {
[self resetTranscript];
inputBar.statusText = @"正在连接...";
[self.deepgramManager start];
if ([self.delegate respondsToSelector:@selector
(voiceToTextManagerDidBeginRecording:)]) {
[self.delegate voiceToTextManagerDidBeginRecording:self];
}
}
- (void)voiceInputBarDidEndRecording:(KBVoiceInputBar *)inputBar {
inputBar.statusText = @"正在识别...";
[self.deepgramManager stopAndFinalize];
if ([self.delegate respondsToSelector:@selector
(voiceToTextManagerDidEndRecording:)]) {
[self.delegate voiceToTextManagerDidEndRecording:self];
}
}
- (void)voiceInputBarDidCancelRecording:(KBVoiceInputBar *)inputBar {
inputBar.statusText = @"已取消";
[self resetTranscript];
[self.deepgramManager cancel];
if ([self.delegate respondsToSelector:@selector
(voiceToTextManagerDidCancelRecording:)]) {
[self.delegate voiceToTextManagerDidCancelRecording:self];
}
}
#pragma mark - DeepgramStreamingManagerDelegate
- (void)deepgramStreamingManagerDidConnect {
self.inputBar.statusText = @"正在聆听...";
}
- (void)deepgramStreamingManagerDidDisconnect:(NSError *_Nullable)error {
if (!error) {
return;
}
self.inputBar.statusText = @"识别失败";
if ([self.delegate respondsToSelector:@selector
(voiceToTextManager:didFailWithError:)]) {
[self.delegate voiceToTextManager:self didFailWithError:error];
}
}
- (void)deepgramStreamingManagerDidUpdateRMS:(float)rms {
[self.inputBar updateVolumeRMS:rms];
}
- (void)deepgramStreamingManagerDidReceiveInterimTranscript:(NSString *)text {
NSString *displayText = text ?: @"";
if (self.fullText.length > 0 && displayText.length > 0) {
displayText =
[NSString stringWithFormat:@"%@ %@", self.fullText, displayText];
} else if (self.fullText.length > 0) {
displayText = [self.fullText copy];
}
self.inputBar.statusText =
displayText.length > 0 ? displayText : @"正在识别...";
if ([self.delegate respondsToSelector:@selector
(voiceToTextManager:didUpdateInterimText:)]) {
[self.delegate voiceToTextManager:self didUpdateInterimText:displayText];
}
}
- (void)deepgramStreamingManagerDidReceiveFinalTranscript:(NSString *)text {
if (text.length > 0) {
if (self.fullText.length > 0) {
[self.fullText appendString:@" "];
}
[self.fullText appendString:text];
}
NSString *finalText = [self.fullText copy];
self.inputBar.statusText =
finalText.length > 0 ? finalText : @"识别完成";
if (finalText.length > 0 &&
[self.delegate respondsToSelector:@selector
(voiceToTextManager:didReceiveFinalText:)]) {
[self.delegate voiceToTextManager:self didReceiveFinalText:finalText];
}
}
- (void)deepgramStreamingManagerDidFail:(NSError *)error {
self.inputBar.statusText = @"识别失败";
if ([self.delegate respondsToSelector:@selector
(voiceToTextManager:didFailWithError:)]) {
[self.delegate voiceToTextManager:self didFailWithError:error];
}
}
@end