2026-01-22 13:47:34 +08:00
|
|
|
|
//
|
|
|
|
|
|
// AiVM.m
|
|
|
|
|
|
// keyBoard
|
|
|
|
|
|
//
|
|
|
|
|
|
// Created by Mac on 2026/1/22.
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
#import "AiVM.h"
|
2026-01-22 22:03:56 +08:00
|
|
|
|
#import "KBAPI.h"
|
|
|
|
|
|
#import "KBNetworkManager.h"
|
2026-01-27 21:32:52 +08:00
|
|
|
|
#import "KBCommentModel.h"
|
2026-01-28 16:35:47 +08:00
|
|
|
|
#import "KBLikedCompanionModel.h"
|
|
|
|
|
|
#import "KBChattedCompanionModel.h"
|
2026-01-28 18:58:30 +08:00
|
|
|
|
#import "KBChatSessionResetModel.h"
|
2026-01-22 22:03:56 +08:00
|
|
|
|
#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
|
2026-01-22 13:47:34 +08:00
|
|
|
|
|
2026-01-27 16:28:17 +08:00
|
|
|
|
@implementation KBAiSpeechTranscribeData
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
@implementation KBAiSpeechTranscribeResponse
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
2026-01-22 13:47:34 +08:00
|
|
|
|
@implementation AiVM
|
|
|
|
|
|
|
2026-01-22 22:03:56 +08:00
|
|
|
|
- (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
|
2026-01-26 18:51:37 +08:00
|
|
|
|
companionId:(NSInteger)companionId
|
2026-01-22 22:03:56 +08:00
|
|
|
|
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
|
2026-01-26 18:51:37 +08:00
|
|
|
|
stringWithFormat:@"%@?content=%@&companionId=%ld", API_AI_CHAT_MESSAGE,
|
|
|
|
|
|
encodedContent ?: @"", (long)companionId];
|
|
|
|
|
|
NSDictionary *params = @{
|
|
|
|
|
|
@"content" : content ?: @"",
|
|
|
|
|
|
@"companionId" : @(companionId)
|
|
|
|
|
|
};
|
2026-01-22 22:03:56 +08:00
|
|
|
|
[[KBNetworkManager shared]
|
|
|
|
|
|
POST:path
|
|
|
|
|
|
jsonBody:params
|
|
|
|
|
|
headers:nil
|
|
|
|
|
|
autoShowBusinessError:NO
|
|
|
|
|
|
completion:^(NSDictionary *_Nullable json,
|
|
|
|
|
|
NSURLResponse *_Nullable response,
|
|
|
|
|
|
NSError *_Nullable error) {
|
2026-01-27 16:28:17 +08:00
|
|
|
|
KBAiMessageResponse *model =
|
|
|
|
|
|
[KBAiMessageResponse mj_objectWithKeyValues:json];
|
2026-01-22 22:03:56 +08:00
|
|
|
|
if (error) {
|
|
|
|
|
|
if (completion) {
|
2026-01-27 16:28:17 +08:00
|
|
|
|
completion(model, error);
|
2026-01-22 22:03:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-27 16:28:17 +08:00
|
|
|
|
|
2026-01-23 21:51:37 +08:00
|
|
|
|
id dataObj = json[@"data"];
|
|
|
|
|
|
if (!model.data && [dataObj isKindOfClass:[NSString class]]) {
|
|
|
|
|
|
KBAiMessageData *data = [[KBAiMessageData alloc] init];
|
|
|
|
|
|
data.content = (NSString *)dataObj;
|
|
|
|
|
|
model.data = data;
|
|
|
|
|
|
}
|
2026-01-22 22:03:56 +08:00
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(model, nil);
|
|
|
|
|
|
}
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 21:51:37 +08:00
|
|
|
|
- (void)requestAudioWithAudioId:(NSString *)audioId
|
|
|
|
|
|
completion:(AiVMAudioURLCompletion)completion {
|
|
|
|
|
|
if (audioId.length == 0) {
|
2026-01-22 22:03:56 +08:00
|
|
|
|
NSError *error = [NSError
|
|
|
|
|
|
errorWithDomain:@"AiVM"
|
|
|
|
|
|
code:-1
|
2026-01-23 21:51:37 +08:00
|
|
|
|
userInfo:@{NSLocalizedDescriptionKey : @"audioId is empty"}];
|
2026-01-22 22:03:56 +08:00
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(nil, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 21:51:37 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}];
|
2026-01-22 22:03:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-27 13:57:32 +08:00
|
|
|
|
- (void)uploadAudioFileAtURL:(NSURL *)fileURL
|
|
|
|
|
|
completion:(AiVMUploadAudioCompletion)completion {
|
|
|
|
|
|
if (!fileURL || !fileURL.isFileURL) {
|
|
|
|
|
|
NSError *error = [NSError errorWithDomain:@"AiVM"
|
|
|
|
|
|
code:-1
|
|
|
|
|
|
userInfo:@{NSLocalizedDescriptionKey : @"invalid fileURL"}];
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(nil, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[[KBNetworkManager shared] uploadFile:API_AI_AUDIO_UPLOAD
|
|
|
|
|
|
fileURL:fileURL
|
|
|
|
|
|
name:@"file"
|
2026-01-28 20:18:18 +08:00
|
|
|
|
mimeType:@"audio/m4a"
|
2026-01-27 13:57:32 +08:00
|
|
|
|
parameters:nil
|
|
|
|
|
|
headers:nil
|
|
|
|
|
|
completion:^(NSDictionary *_Nullable json,
|
|
|
|
|
|
NSURLResponse *_Nullable response,
|
|
|
|
|
|
NSError *_Nullable error) {
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(nil, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSString *fileURLString = nil;
|
|
|
|
|
|
id dataObj = json[@"data"];
|
|
|
|
|
|
if ([dataObj isKindOfClass:[NSString class]]) {
|
|
|
|
|
|
fileURLString = (NSString *)dataObj;
|
|
|
|
|
|
} else if ([dataObj isKindOfClass:[NSDictionary class]]) {
|
|
|
|
|
|
id urlObj = dataObj[@"url"] ?: dataObj[@"audioUrl"];
|
|
|
|
|
|
if ([urlObj isKindOfClass:[NSString class]]) {
|
|
|
|
|
|
fileURLString = (NSString *)urlObj;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(fileURLString, nil);
|
|
|
|
|
|
}
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-27 16:28:17 +08:00
|
|
|
|
- (void)transcribeAudioFileAtURL:(NSURL *)fileURL
|
|
|
|
|
|
completion:(AiVMSpeechTranscribeCompletion)completion {
|
|
|
|
|
|
if (!fileURL || !fileURL.isFileURL) {
|
|
|
|
|
|
NSError *error = [NSError errorWithDomain:@"AiVM"
|
|
|
|
|
|
code:-1
|
|
|
|
|
|
userInfo:@{NSLocalizedDescriptionKey : @"invalid fileURL"}];
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(nil, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[[KBNetworkManager shared] uploadFile:API_AI_SPEECH_TRANSCRIBE
|
|
|
|
|
|
fileURL:fileURL
|
|
|
|
|
|
name:@"file"
|
|
|
|
|
|
mimeType:@"audio/m4a"
|
|
|
|
|
|
parameters:nil
|
|
|
|
|
|
headers:nil
|
|
|
|
|
|
completion:^(NSDictionary *_Nullable json,
|
|
|
|
|
|
NSURLResponse *_Nullable response,
|
|
|
|
|
|
NSError *_Nullable error) {
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(nil, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
KBAiSpeechTranscribeResponse *model =
|
|
|
|
|
|
[KBAiSpeechTranscribeResponse mj_objectWithKeyValues:json];
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(model, nil);
|
|
|
|
|
|
}
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-26 16:53:41 +08:00
|
|
|
|
#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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-26 18:17:02 +08:00
|
|
|
|
#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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-27 21:32:52 +08:00
|
|
|
|
#pragma mark - 评论相关接口
|
|
|
|
|
|
|
|
|
|
|
|
- (void)addCommentWithCompanionId:(NSInteger)companionId
|
|
|
|
|
|
content:(NSString *)content
|
|
|
|
|
|
parentId:(nullable NSNumber *)parentId
|
|
|
|
|
|
rootId:(NSInteger)rootId
|
|
|
|
|
|
completion:(void (^)(NSInteger, NSError * _Nullable))completion {
|
|
|
|
|
|
if (content.length == 0) {
|
|
|
|
|
|
NSError *error = [NSError errorWithDomain:@"AiVM"
|
|
|
|
|
|
code:-1
|
|
|
|
|
|
userInfo:@{NSLocalizedDescriptionKey: @"评论内容不能为空"}];
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(-1, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSMutableDictionary *params = [NSMutableDictionary dictionary];
|
|
|
|
|
|
params[@"companionId"] = @(companionId);
|
|
|
|
|
|
params[@"content"] = content;
|
|
|
|
|
|
params[@"rootId"] = @(rootId);
|
|
|
|
|
|
if (parentId) {
|
|
|
|
|
|
params[@"parentId"] = parentId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/comment/add request: %@", params);
|
|
|
|
|
|
[[KBNetworkManager shared]
|
|
|
|
|
|
POST:@"/ai-companion/comment/add"
|
|
|
|
|
|
jsonBody:[params copy]
|
|
|
|
|
|
headers:nil
|
|
|
|
|
|
autoShowBusinessError:NO
|
|
|
|
|
|
completion:^(NSDictionary *_Nullable json,
|
|
|
|
|
|
NSURLResponse *_Nullable response,
|
|
|
|
|
|
NSError *_Nullable error) {
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/comment/add failed: %@", error.localizedDescription ?: @"");
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(-1, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/comment/add response: %@", json);
|
|
|
|
|
|
NSInteger code = [json[@"code"] integerValue];
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(code, nil);
|
|
|
|
|
|
}
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)fetchCommentsWithCompanionId:(NSInteger)companionId
|
|
|
|
|
|
pageNum:(NSInteger)pageNum
|
|
|
|
|
|
pageSize:(NSInteger)pageSize
|
|
|
|
|
|
completion:(void (^)(KBCommentPageModel * _Nullable, NSError * _Nullable))completion {
|
|
|
|
|
|
NSDictionary *params = @{
|
|
|
|
|
|
@"companionId": @(companionId),
|
|
|
|
|
|
@"pageNum": @(pageNum > 0 ? pageNum : 1),
|
|
|
|
|
|
@"pageSize": @(pageSize > 0 ? pageSize : 20)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/comment/page request: %@", params);
|
|
|
|
|
|
[[KBNetworkManager shared]
|
|
|
|
|
|
POST:@"/ai-companion/comment/page"
|
|
|
|
|
|
jsonBody:params
|
|
|
|
|
|
headers:nil
|
|
|
|
|
|
autoShowBusinessError:NO
|
|
|
|
|
|
completion:^(NSDictionary *_Nullable json,
|
|
|
|
|
|
NSURLResponse *_Nullable response,
|
|
|
|
|
|
NSError *_Nullable error) {
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/comment/page failed: %@", error.localizedDescription ?: @"");
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(nil, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/comment/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]]) {
|
|
|
|
|
|
KBCommentPageModel *pageModel = [KBCommentPageModel mj_objectWithKeyValues:dataObj];
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(pageModel, nil);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
NSError *parseError = [NSError errorWithDomain:@"AiVM"
|
|
|
|
|
|
code:-1
|
|
|
|
|
|
userInfo:@{NSLocalizedDescriptionKey: @"数据格式错误"}];
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(nil, parseError);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)likeCommentWithCommentId:(NSInteger)commentId
|
|
|
|
|
|
completion:(void (^)(KBCommentLikeResponse * _Nullable, NSError * _Nullable))completion {
|
|
|
|
|
|
NSDictionary *params = @{
|
|
|
|
|
|
@"commentId": @(commentId)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/comment/like request: %@", params);
|
|
|
|
|
|
[[KBNetworkManager shared]
|
|
|
|
|
|
POST:@"/ai-companion/comment/like"
|
|
|
|
|
|
jsonBody:params
|
|
|
|
|
|
headers:nil
|
|
|
|
|
|
autoShowBusinessError:NO
|
|
|
|
|
|
completion:^(NSDictionary *_Nullable json,
|
|
|
|
|
|
NSURLResponse *_Nullable response,
|
|
|
|
|
|
NSError *_Nullable error) {
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/comment/like failed: %@", error.localizedDescription ?: @"");
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(nil, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/comment/like response: %@", json);
|
|
|
|
|
|
|
|
|
|
|
|
KBCommentLikeResponse *likeResponse = [KBCommentLikeResponse mj_objectWithKeyValues:json];
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(likeResponse, nil);
|
|
|
|
|
|
}
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)likeCompanionWithCompanionId:(NSInteger)companionId
|
|
|
|
|
|
completion:(void (^)(KBCommentLikeResponse * _Nullable, NSError * _Nullable))completion {
|
|
|
|
|
|
NSDictionary *params = @{
|
|
|
|
|
|
@"companionId": @(companionId)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/like request: %@", params);
|
|
|
|
|
|
[[KBNetworkManager shared]
|
|
|
|
|
|
POST:@"/ai-companion/like"
|
|
|
|
|
|
jsonBody:params
|
|
|
|
|
|
headers:nil
|
|
|
|
|
|
autoShowBusinessError:NO
|
|
|
|
|
|
completion:^(NSDictionary *_Nullable json,
|
|
|
|
|
|
NSURLResponse *_Nullable response,
|
|
|
|
|
|
NSError *_Nullable error) {
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/like failed: %@", error.localizedDescription ?: @"");
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(nil, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/like response: %@", json);
|
|
|
|
|
|
|
|
|
|
|
|
KBCommentLikeResponse *likeResponse = [KBCommentLikeResponse mj_objectWithKeyValues:json];
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(likeResponse, nil);
|
|
|
|
|
|
}
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-28 16:35:47 +08:00
|
|
|
|
#pragma mark - 点赞列表接口
|
|
|
|
|
|
|
|
|
|
|
|
- (void)fetchLikedCompanionsWithCompletion:(void (^)(NSArray<KBLikedCompanionModel *> * _Nullable, NSError * _Nullable))completion {
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/liked request");
|
|
|
|
|
|
[[KBNetworkManager shared]
|
|
|
|
|
|
GET:@"/ai-companion/liked"
|
|
|
|
|
|
parameters:nil
|
|
|
|
|
|
headers:nil
|
|
|
|
|
|
autoShowBusinessError:NO
|
|
|
|
|
|
completion:^(NSDictionary *_Nullable json,
|
|
|
|
|
|
NSURLResponse *_Nullable response,
|
|
|
|
|
|
NSError *_Nullable error) {
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/liked failed: %@", error.localizedDescription ?: @"");
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(nil, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/liked 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSArray *dataArray = json[@"data"];
|
|
|
|
|
|
if (![dataArray isKindOfClass:[NSArray class]]) {
|
|
|
|
|
|
dataArray = @[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSArray<KBLikedCompanionModel *> *list = [KBLikedCompanionModel mj_objectArrayWithKeyValuesArray:dataArray];
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(list, nil);
|
|
|
|
|
|
}
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)fetchChattedCompanionsWithCompletion:(void (^)(NSArray<KBChattedCompanionModel *> * _Nullable, NSError * _Nullable))completion {
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/chatted request");
|
|
|
|
|
|
[[KBNetworkManager shared]
|
|
|
|
|
|
GET:@"/ai-companion/chatted"
|
|
|
|
|
|
parameters:nil
|
|
|
|
|
|
headers:nil
|
|
|
|
|
|
autoShowBusinessError:NO
|
|
|
|
|
|
completion:^(NSDictionary *_Nullable json,
|
|
|
|
|
|
NSURLResponse *_Nullable response,
|
|
|
|
|
|
NSError *_Nullable error) {
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/chatted failed: %@", error.localizedDescription ?: @"");
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(nil, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSLog(@"[AiVM] /ai-companion/chatted 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSArray *dataArray = json[@"data"];
|
|
|
|
|
|
if (![dataArray isKindOfClass:[NSArray class]]) {
|
|
|
|
|
|
dataArray = @[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSArray<KBChattedCompanionModel *> *list = [KBChattedCompanionModel mj_objectArrayWithKeyValuesArray:dataArray];
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(list, nil);
|
|
|
|
|
|
}
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-28 18:58:30 +08:00
|
|
|
|
#pragma mark - 会话管理接口
|
|
|
|
|
|
|
|
|
|
|
|
- (void)resetChatSessionWithCompanionId:(NSInteger)companionId
|
|
|
|
|
|
completion:(void (^)(KBChatSessionResetResponse * _Nullable, NSError * _Nullable))completion {
|
|
|
|
|
|
NSDictionary *params = @{
|
|
|
|
|
|
@"companionId": @(companionId)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
NSLog(@"[AiVM] /chat/session/reset request: %@", params);
|
|
|
|
|
|
[[KBNetworkManager shared]
|
|
|
|
|
|
POST:@"/chat/session/reset"
|
|
|
|
|
|
jsonBody:params
|
|
|
|
|
|
headers:nil
|
|
|
|
|
|
autoShowBusinessError:NO
|
|
|
|
|
|
completion:^(NSDictionary *_Nullable json,
|
|
|
|
|
|
NSURLResponse *_Nullable response,
|
|
|
|
|
|
NSError *_Nullable error) {
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
NSLog(@"[AiVM] /chat/session/reset failed: %@", error.localizedDescription ?: @"");
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(nil, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSLog(@"[AiVM] /chat/session/reset response: %@", json);
|
|
|
|
|
|
|
|
|
|
|
|
KBChatSessionResetResponse *resetResponse = [KBChatSessionResetResponse mj_objectWithKeyValues:json];
|
|
|
|
|
|
if (completion) {
|
|
|
|
|
|
completion(resetResponse, nil);
|
|
|
|
|
|
}
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 13:47:34 +08:00
|
|
|
|
@end
|