Files
keyboard/keyBoard/Class/AiTalk/VM/AiVM.m
2026-01-26 18:51:37 +08:00

342 lines
11 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// AiVM.m
// keyBoard
//
// Created by Mac on 2026/1/22.
//
#import "AiVM.h"
#import "KBAPI.h"
#import "KBNetworkManager.h"
#import <MJExtension/MJExtension.h>
@implementation KBAiSyncData
- (void)setAudioBase64:(NSString *)audioBase64 {
if (![audioBase64 isKindOfClass:[NSString class]]) {
_audioBase64 = nil;
self.audioData = nil;
return;
}
_audioBase64 = [audioBase64 copy];
if (_audioBase64.length == 0) {
self.audioData = nil;
return;
}
NSString *cleanBase64 = _audioBase64;
NSRange commaRange = [cleanBase64 rangeOfString:@","];
if ([cleanBase64 hasPrefix:@"data:"] && commaRange.location != NSNotFound) {
cleanBase64 = [cleanBase64 substringFromIndex:commaRange.location + 1];
}
self.audioData = [[NSData alloc]
initWithBase64EncodedString:cleanBase64
options:NSDataBase64DecodingIgnoreUnknownCharacters];
}
@end
@implementation KBAiSyncResponse
@end
@implementation KBAiMessageData
@end
@implementation KBAiMessageResponse
@end
@implementation AiVM
- (void)syncChatWithTranscript:(NSString *)transcript
completion:(AiVMSyncCompletion)completion {
if (transcript.length == 0) {
NSError *error = [NSError
errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey : @"transcript is empty"}];
if (completion) {
completion(nil, error);
}
return;
}
NSDictionary *params = @{ @"transcript" : transcript ?: @"" };
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
NSLog(@"[AiVM] /chat/sync request: %@", params);
[[KBNetworkManager shared]
POST:API_AI_CHAT_SYNC
jsonBody:params
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
CFAbsoluteTime elapsed =
(CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;
if (error) {
NSLog(@"[AiVM] /chat/sync failed: %@",
error.localizedDescription ?: @"");
NSLog(@"[AiVM] /chat/sync duration: %.0f ms", elapsed);
if (completion) {
completion(nil, error);
}
return;
}
NSLog(@"[AiVM] /chat/sync response received");
NSLog(@"[AiVM] /chat/sync duration: %.0f ms", elapsed);
KBAiSyncResponse *model =
[KBAiSyncResponse mj_objectWithKeyValues:json];
if (completion) {
completion(model, nil);
}
}];
}
- (void)requestChatMessageWithContent:(NSString *)content
companionId:(NSInteger)companionId
completion:(AiVMMessageCompletion)completion {
if (content.length == 0) {
NSError *error = [NSError
errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey : @"content is empty"}];
if (completion) {
completion(nil, error);
}
return;
}
NSString *encodedContent =
[content stringByAddingPercentEncodingWithAllowedCharacters:
[NSCharacterSet URLQueryAllowedCharacterSet]];
NSString *path = [NSString
stringWithFormat:@"%@?content=%@&companionId=%ld", API_AI_CHAT_MESSAGE,
encodedContent ?: @"", (long)companionId];
NSDictionary *params = @{
@"content" : content ?: @"",
@"companionId" : @(companionId)
};
[[KBNetworkManager shared]
POST:path
jsonBody:params
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
if (completion) {
completion(nil, error);
}
return;
}
KBAiMessageResponse *model =
[KBAiMessageResponse mj_objectWithKeyValues:json];
id dataObj = json[@"data"];
if (!model.data && [dataObj isKindOfClass:[NSString class]]) {
KBAiMessageData *data = [[KBAiMessageData alloc] init];
data.content = (NSString *)dataObj;
model.data = data;
}
if (completion) {
completion(model, nil);
}
}];
}
- (void)requestAudioWithAudioId:(NSString *)audioId
completion:(AiVMAudioURLCompletion)completion {
if (audioId.length == 0) {
NSError *error = [NSError
errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey : @"audioId is empty"}];
if (completion) {
completion(nil, error);
}
return;
}
NSString *path = [NSString stringWithFormat:@"/chat/audio/%@", audioId];
[[KBNetworkManager shared]
GET:path
parameters:nil
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
if (completion) {
completion(nil, error);
}
return;
}
// 解析返回的 URL
NSString *audioURL = nil;
if ([json isKindOfClass:[NSDictionary class]]) {
// 返回格式:{"code": 0, "data": {"audioUrl": "http://...", "url": "http://..."}}
id dataObj = json[@"data"];
if ([dataObj isKindOfClass:[NSDictionary class]]) {
NSDictionary *dataDict = (NSDictionary *)dataObj;
// 优先使用 audioUrl兼容 url
id audioUrlObj = dataDict[@"audioUrl"] ?: dataDict[@"url"];
// 检查是否为 NSNull
if (audioUrlObj && ![audioUrlObj isKindOfClass:[NSNull class]] && [audioUrlObj isKindOfClass:[NSString class]]) {
audioURL = (NSString *)audioUrlObj;
}
} else if ([dataObj isKindOfClass:[NSString class]]) {
audioURL = (NSString *)dataObj;
}
// 或者直接返回 URL 字符串
if (!audioURL) {
id audioUrlObj = json[@"audioUrl"] ?: json[@"url"];
if (audioUrlObj && ![audioUrlObj isKindOfClass:[NSNull class]] && [audioUrlObj isKindOfClass:[NSString class]]) {
audioURL = (NSString *)audioUrlObj;
}
}
}
// 如果 audioURL 为空或 nil返回 nil不是错误表示音频还未生成
if (!audioURL || audioURL.length == 0) {
if (completion) {
completion(nil, nil); // 返回 nil 表示音频未就绪,需要重试
}
return;
}
if (completion) {
completion(audioURL, nil);
}
}];
}
#pragma mark - 人设相关接口
- (void)fetchPersonasWithPageNum:(NSInteger)pageNum
pageSize:(NSInteger)pageSize
completion:(void (^)(KBPersonaPageModel * _Nullable, NSError * _Nullable))completion {
NSDictionary *params = @{
@"pageNum": @(pageNum),
@"pageSize": @(pageSize)
};
NSLog(@"[AiVM] /ai-companion/page request: %@", params);
[[KBNetworkManager shared]
POST:@"/ai-companion/page"
jsonBody:params
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
NSLog(@"[AiVM] /ai-companion/page failed: %@", error.localizedDescription ?: @"");
if (completion) {
completion(nil, error);
}
return;
}
NSLog(@"[AiVM] /ai-companion/page response: %@", json);
// 解析响应
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
if (completion) {
completion(nil, bizError);
}
return;
}
// 转换为模型
id dataObj = json[@"data"];
if ([dataObj isKindOfClass:[NSDictionary class]]) {
KBPersonaPageModel *pageModel = [KBPersonaPageModel mj_objectWithKeyValues:dataObj];
if (completion) {
completion(pageModel, nil);
}
} else {
NSError *parseError = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"数据格式错误"}];
if (completion) {
completion(nil, parseError);
}
}
}];
}
#pragma mark - 聊天记录相关接口
- (void)fetchChatHistoryWithCompanionId:(NSInteger)companionId
pageNum:(NSInteger)pageNum
pageSize:(NSInteger)pageSize
completion:(void (^)(KBChatHistoryPageModel * _Nullable, NSError * _Nullable))completion {
NSDictionary *params = @{
@"companionId": @(companionId),
@"pageNum": @(pageNum),
@"pageSize": @(pageSize)
};
NSLog(@"[AiVM] /chat/history request: %@", params);
[[KBNetworkManager shared]
POST:@"/chat/history"
jsonBody:params
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
NSLog(@"[AiVM] /chat/history failed: %@", error.localizedDescription ?: @"");
if (completion) {
completion(nil, error);
}
return;
}
NSLog(@"[AiVM] /chat/history response: %@", json);
// 解析响应
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
if (completion) {
completion(nil, bizError);
}
return;
}
// 转换为模型
id dataObj = json[@"data"];
if ([dataObj isKindOfClass:[NSDictionary class]]) {
KBChatHistoryPageModel *pageModel = [KBChatHistoryPageModel mj_objectWithKeyValues:dataObj];
if (completion) {
completion(pageModel, nil);
}
} else {
NSError *parseError = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"数据格式错误"}];
if (completion) {
completion(nil, parseError);
}
}
}];
}
@end