1
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user