This commit is contained in:
2026-01-23 21:51:37 +08:00
parent 6ad9783bcb
commit 77fd46aa34
26 changed files with 3681 additions and 199 deletions

View File

@@ -131,125 +131,85 @@ autoShowBusinessError:NO
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)requestElevenLabsSpeechWithText:(NSString *)text
voiceId:(NSString *)voiceId
apiKey:(NSString *)apiKey
outputFormat:(NSString *)outputFormat
modelId:(NSString *)modelId
completion:(AiVMElevenLabsCompletion)completion {
if (text.length == 0 || voiceId.length == 0 || apiKey.length == 0) {
- (void)requestAudioWithAudioId:(NSString *)audioId
completion:(AiVMAudioURLCompletion)completion {
if (audioId.length == 0) {
NSError *error = [NSError
errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey : @"invalid parameters"}];
userInfo:@{NSLocalizedDescriptionKey : @"audioId is empty"}];
if (completion) {
completion(nil, error);
}
return;
}
NSString *format = outputFormat.length > 0 ? outputFormat : @"mp3_44100_128";
NSString *model = modelId.length > 0 ? modelId : @"eleven_multilingual_v2";
NSString *escapedVoiceId =
[voiceId stringByAddingPercentEncodingWithAllowedCharacters:
[NSCharacterSet URLPathAllowedCharacterSet]];
NSString *escapedFormat =
[format stringByAddingPercentEncodingWithAllowedCharacters:
[NSCharacterSet URLQueryAllowedCharacterSet]];
NSString *urlString =
[NSString stringWithFormat:@"https://api.elevenlabs.io/v1/text-to-speech/%@/stream?output_format=%@",
escapedVoiceId ?: @"",
escapedFormat ?: @""];
NSURL *url = [NSURL URLWithString:urlString];
if (!url) {
NSError *error = [NSError
errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey : @"invalid URL"}];
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;
}
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"audio/mpeg" forHTTPHeaderField:@"Accept"];
[request setValue:apiKey forHTTPHeaderField:@"xi-api-key"];
// 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;
}
}
}
NSDictionary *body = @{
@"text" : text ?: @"",
@"model_id" : model ?: @""
};
NSError *jsonError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:body
options:0
error:&jsonError];
if (jsonError) {
if (completion) {
completion(nil, jsonError);
}
return;
}
request.HTTPBody = jsonData;
// audioURL nil nil
if (!audioURL || audioURL.length == 0) {
if (completion) {
completion(nil, nil); // nil
}
return;
}
NSURLSessionConfiguration *config =
[NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSURLSessionDataTask *task =
[session dataTaskWithRequest:request
completionHandler:^(NSData *_Nullable data,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
if (completion) {
completion(nil, error);
}
return;
}
if (![response isKindOfClass:[NSHTTPURLResponse class]]) {
NSError *respError = [NSError
errorWithDomain:@"AiVM"
code:-1
userInfo:@{
NSLocalizedDescriptionKey :
@"invalid response"
}];
if (completion) {
completion(nil, respError);
}
return;
}
NSInteger status =
((NSHTTPURLResponse *)response).statusCode;
if (status < 200 || status >= 300 || data.length == 0) {
NSError *respError = [NSError
errorWithDomain:@"AiVM"
code:status
userInfo:@{
NSLocalizedDescriptionKey :
@"request failed"
}];
if (completion) {
completion(nil, respError);
}
return;
}
if (completion) {
completion(data, nil);
}
}];
[task resume];
if (completion) {
completion(audioURL, nil);
}
}];
}
@end