处理键盘ai功能

This commit is contained in:
2026-02-05 16:01:21 +08:00
parent 750b391100
commit 3cb02d5b76
5 changed files with 116 additions and 42 deletions

View File

@@ -1073,7 +1073,7 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
__strong typeof(weakSelf) self = weakSelf;
if (!self) return;
if (!response.success) {
if (response.code != 0) {
if (response.code == 50030) {
NSLog(@"[KB] ⚠️ 次数用尽: %@", response.message);
[self.chatPanelView kb_removeLoadingAssistantMessage];
@@ -1086,9 +1086,9 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
return;
}
NSLog(@"[KB] ✅ 收到回复: %@", response.text);
NSLog(@"[KB] ✅ 收到回复: %@", response.data.aiResponse);
if (response.text.length == 0) {
if (response.data.aiResponse.length == 0) {
[self.chatPanelView kb_removeLoadingAssistantMessage];
[KBHUD showInfo:KBLocalized(@"未获取到回复内容")];
return;
@@ -1096,12 +1096,12 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
// AI
NSLog(@"[KB] 准备添加 AI 消息");
[self.chatPanelView kb_addAssistantMessage:response.text audioId:response.audioId];
[self.chatPanelView kb_addAssistantMessage:response.data.aiResponse audioId:response.data.audioId];
NSLog(@"[KB] AI 消息添加完成");
// audioId
if (response.audioId.length > 0) {
[self kb_preloadAudioWithAudioId:response.audioId];
if (response.data.audioId.length > 0) {
[self kb_preloadAudioWithAudioId:response.data.audioId];
}
}];
}

View File

@@ -7,17 +7,25 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class KBChatDataModel;
NS_ASSUME_NONNULL_BEGIN
/// 聊天响应模型
@interface KBChatResponse : NSObject
@property (nonatomic, copy, nullable) NSString *text;
@property (nonatomic, copy, nullable) NSString *audioId;
@property (nonatomic, strong, nullable) KBChatDataModel *data;
//@property (nonatomic, copy, nullable) NSString *audioId;
@property (nonatomic, copy, nullable) NSString *message;
@property (nonatomic, assign) BOOL success;
@property (nonatomic, assign) NSInteger code;
@end
@interface KBChatDataModel : NSObject
@property (nonatomic, copy, nullable) NSString *aiResponse;
@property (nonatomic, copy, nullable) NSString *audioId;
@property (nonatomic, copy, nullable) NSString *llmDuration;
@end
/// 音频响应模型

View File

@@ -12,6 +12,9 @@
@implementation KBChatResponse
@end
@implementation KBChatDataModel
@end
@implementation KBAudioResponse
@end
@@ -68,8 +71,7 @@
completion:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
KBChatResponse *chatResponse = [KBChatResponse mj_objectWithKeyValues:json];
if (error) {
if (chatResponse.code != 0) {
chatResponse.success = NO;
// chatResponse.errorMessage = error.localizedDescription ?: @"请求失败";
if (completion) completion(chatResponse);

View File

@@ -26,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, weak, nullable) id<KBToolBarDelegate> delegate;
/// 左侧按钮的标题(数量由数组决定)。默认值:@[@"AI", @"语音"]。
/// 左侧按钮的标题(数量由数组决定)。默认值:@[@"AI"]。
@property (nonatomic, copy) NSArray<NSString *> *leftButtonTitles;
/// 暴露按钮以便外部定制(只读;首次访问时懒加载创建)

View File

@@ -15,9 +15,11 @@
@property (nonatomic, strong) NSArray<UIButton *> *leftButtonsInternal;
//@property (nonatomic, strong) UIButton *settingsButtonInternal;
@property (nonatomic, strong) UIButton *globeButtonInternal; //
@property (nonatomic, strong) UIImageView *avatarImageView; // AppGroup persona_cover.jpg
@property (nonatomic, strong) UIButton *undoButtonInternal; //
@property (nonatomic, assign) BOOL kbNeedsInputModeSwitchKey;
@property (nonatomic, assign) BOOL kbUndoVisible;
@property (nonatomic, assign) BOOL kbAvatarVisible;
@end
@implementation KBToolBar
@@ -26,12 +28,12 @@ static NSString * const kKBAIKeyIdentifier = @"ai";
static NSString * const kKBUndoKeyIdentifier = @"key_revoke";
static const CGFloat kKBAIButtonWidth = 40;
static const CGFloat kKBAIButtonHeight = 40;
static const NSInteger kKBVoiceButtonIndex = 1;
static const CGFloat kKBAvatarSize = 40;
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
_leftButtonTitles = @[@"AI", KBLocalized(@"语音")]; //
_leftButtonTitles = @[@"AI"]; //
[self setupUI];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(kb_undoStateChanged)
@@ -69,7 +71,6 @@ static const NSInteger kKBVoiceButtonIndex = 1;
}
}];
[self kb_updateAIButtonAppearance];
[self kb_updateVoiceButtonAppearance];
}
#pragma mark -
@@ -79,6 +80,7 @@ static const NSInteger kKBVoiceButtonIndex = 1;
// [self addSubview:self.settingsButtonInternal];
[self addSubview:self.globeButtonInternal];
[self addSubview:self.undoButtonInternal];
[self addSubview:self.avatarImageView];
//
// [self.settingsButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) {
@@ -94,13 +96,7 @@ static const NSInteger kKBVoiceButtonIndex = 1;
make.width.height.mas_equalTo(32);
}];
//
[self.undoButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.mas_right).offset(-12);
make.centerY.equalTo(self.mas_centerY);
make.height.mas_equalTo(32);
make.width.mas_equalTo(84);
}];
[self kb_updateRightControlsConstraints];
[self kb_updateLeftContainerConstraints];
@@ -173,8 +169,8 @@ static const NSInteger kKBVoiceButtonIndex = 1;
- (void)kb_applyTheme {
[self kb_updateAIButtonAppearance];
[self kb_updateVoiceButtonAppearance];
[self kb_updateUndoButtonAppearance];
[self kb_updateAvatarAppearance];
}
- (void)kb_updateAIButtonAppearance {
@@ -211,16 +207,6 @@ static const NSInteger kKBVoiceButtonIndex = 1;
}
}
- (void)kb_updateVoiceButtonAppearance {
UIButton *voiceButton = [self kb_voiceButton];
if (!voiceButton) { return; }
voiceButton.backgroundColor = [UIColor colorWithHex:0xE53935];
[voiceButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
voiceButton.layer.cornerRadius = 16;
voiceButton.layer.masksToBounds = YES;
}
- (void)kb_updateUndoButtonAppearance {
if (!self.undoButtonInternal) { return; }
@@ -241,6 +227,41 @@ static const NSInteger kKBVoiceButtonIndex = 1;
}
}
#pragma mark - Avatar
- (void)kb_updateAvatarAppearance {
UIImage *img = [self kb_personaCoverImageFromAppGroup];
BOOL shouldShow = (img != nil);
self.avatarImageView.image = img;
if (self.kbAvatarVisible == shouldShow) {
self.avatarImageView.hidden = !shouldShow;
return;
}
self.kbAvatarVisible = shouldShow;
self.avatarImageView.hidden = !shouldShow;
[self kb_updateRightControlsConstraints];
[self kb_updateLeftContainerConstraints];
[self setNeedsLayout];
[self layoutIfNeeded];
}
- (nullable UIImage *)kb_personaCoverImageFromAppGroup {
NSURL *containerURL = [[NSFileManager defaultManager]
containerURLForSecurityApplicationGroupIdentifier:AppGroup];
if (!containerURL) {
return nil;
}
NSString *imagePath =
[[containerURL path] stringByAppendingPathComponent:@"persona_cover.jpg"];
if (imagePath.length == 0 ||
![[NSFileManager defaultManager] fileExistsAtPath:imagePath]) {
return nil;
}
return [UIImage imageWithContentsOfFile:imagePath];
}
#pragma mark - Actions
- (void)onLeftAction:(UIButton *)sender {
@@ -261,6 +282,16 @@ static const NSInteger kKBVoiceButtonIndex = 1;
}
}
- (void)onAvatarTap {
if (!self.kbAvatarVisible || self.avatarImageView.hidden) {
return;
}
// index=1 index
if ([self.delegate respondsToSelector:@selector(toolBar:didTapActionAtIndex:)]) {
[self.delegate toolBar:self didTapActionAtIndex:1];
}
}
#pragma mark - Lazy
- (UIView *)leftContainer {
@@ -271,6 +302,23 @@ static const NSInteger kKBVoiceButtonIndex = 1;
return _leftContainer;
}
- (UIImageView *)avatarImageView {
if (!_avatarImageView) {
_avatarImageView = [[UIImageView alloc] init];
_avatarImageView.hidden = YES;
_avatarImageView.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9];
_avatarImageView.contentMode = UIViewContentModeScaleAspectFill;
_avatarImageView.layer.cornerRadius = kKBAvatarSize * 0.5;
_avatarImageView.layer.masksToBounds = YES;
_avatarImageView.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:@selector(onAvatarTap)];
[_avatarImageView addGestureRecognizer:tap];
}
return _avatarImageView;
}
//- (UIButton *)settingsButtonInternal {
// if (!_settingsButtonInternal) {
// _settingsButtonInternal = [UIButton buttonWithType:UIButtonTypeSystem];
@@ -319,11 +367,6 @@ static const NSInteger kKBVoiceButtonIndex = 1;
return self.leftButtonsInternal[0];
}
- (UIButton *)kb_voiceButton {
if (self.leftButtonsInternal.count <= kKBVoiceButtonIndex) { return nil; }
return self.leftButtonsInternal[kKBVoiceButtonIndex];
}
#pragma mark - Globe (Input Mode Switch)
// 宿
@@ -362,6 +405,8 @@ static const NSInteger kKBVoiceButtonIndex = 1;
}
if (self.kbUndoVisible) {
make.right.equalTo(self.undoButtonInternal.mas_left).offset(-8);
} else if (self.kbAvatarVisible) {
make.right.equalTo(self.avatarImageView.mas_left).offset(-8);
} else {
make.right.equalTo(self).offset(-12);
}
@@ -371,6 +416,24 @@ static const NSInteger kKBVoiceButtonIndex = 1;
}];
}
- (void)kb_updateRightControlsConstraints {
[self.avatarImageView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self).offset(-12);
make.centerY.equalTo(self).offset(5);
make.width.height.mas_equalTo(kKBAvatarSize);
}];
[self.undoButtonInternal mas_remakeConstraints:^(MASConstraintMaker *make) {
if (self.kbAvatarVisible) {
make.right.equalTo(self.avatarImageView.mas_left).offset(-8);
} else {
make.right.equalTo(self).offset(-12);
}
make.centerY.equalTo(self.mas_centerY);
make.height.mas_equalTo(32);
make.width.mas_equalTo(84);
}];
}
- (void)kb_undoStateChanged {
[self kb_updateUndoVisibilityAnimated:YES];
}
@@ -405,6 +468,7 @@ static const NSInteger kKBVoiceButtonIndex = 1;
- (void)didMoveToWindow {
[super didMoveToWindow];
[self kb_refreshGlobeVisibility];
[self kb_updateAvatarAppearance];
}
@end