1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::client::Client;
6use crate::error::Result;
7
8#[derive(Debug, Clone, Serialize, Default)]
10pub struct TextToSpeechRequest {
11 pub model: String,
13
14 pub text: String,
16
17 #[serde(skip_serializing_if = "Option::is_none")]
19 pub voice: Option<String>,
20
21 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
23 pub output_format: Option<String>,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub speed: Option<f64>,
28}
29
30pub type TtsRequest = TextToSpeechRequest;
32
33#[derive(Debug, Clone, Deserialize)]
35pub struct TextToSpeechResponse {
36 pub audio_base64: String,
38
39 pub format: String,
41
42 pub size_bytes: i64,
44
45 pub model: String,
47
48 #[serde(default)]
50 pub cost_ticks: i64,
51
52 #[serde(default)]
54 pub balance_after: i64,
55
56 #[serde(default)]
58 pub request_id: String,
59}
60
61pub type TtsResponse = TextToSpeechResponse;
63
64#[derive(Debug, Clone, Serialize, Default)]
66pub struct SpeechToTextRequest {
67 pub model: String,
69
70 pub audio_base64: String,
72
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub filename: Option<String>,
76
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub language: Option<String>,
80}
81
82pub type SttRequest = SpeechToTextRequest;
84
85#[derive(Debug, Clone, Deserialize)]
87pub struct SpeechToTextResponse {
88 pub text: String,
90
91 pub model: String,
93
94 #[serde(default)]
96 pub cost_ticks: i64,
97
98 #[serde(default)]
100 pub balance_after: i64,
101
102 #[serde(default)]
104 pub request_id: String,
105}
106
107pub type SttResponse = SpeechToTextResponse;
109
110#[derive(Debug, Clone, Serialize, Default)]
112pub struct MusicRequest {
113 pub model: String,
115
116 pub prompt: String,
118
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub duration_seconds: Option<i32>,
122}
123
124#[derive(Debug, Clone, Deserialize)]
126pub struct MusicResponse {
127 #[serde(default)]
129 pub audio_clips: Vec<MusicClip>,
130
131 #[serde(default)]
133 pub model: String,
134
135 #[serde(default)]
137 pub cost_ticks: i64,
138
139 #[serde(default)]
141 pub balance_after: i64,
142
143 #[serde(default)]
145 pub request_id: String,
146}
147
148#[derive(Debug, Clone, Deserialize)]
150pub struct MusicClip {
151 pub base64: String,
153
154 #[serde(default)]
156 pub format: String,
157
158 #[serde(default)]
160 pub size_bytes: i64,
161
162 #[serde(default)]
164 pub index: i32,
165}
166
167#[derive(Debug, Clone, Serialize, Default)]
169pub struct SoundEffectRequest {
170 pub prompt: String,
172
173 #[serde(skip_serializing_if = "Option::is_none")]
175 pub duration_seconds: Option<f64>,
176}
177
178#[derive(Debug, Clone, Deserialize)]
180pub struct SoundEffectResponse {
181 pub audio_base64: String,
183
184 pub format: String,
186
187 #[serde(default)]
189 pub size_bytes: i64,
190
191 #[serde(default)]
193 pub model: String,
194
195 #[serde(default)]
197 pub cost_ticks: i64,
198
199 #[serde(default)]
201 pub request_id: String,
202}
203
204#[derive(Debug, Clone, Deserialize)]
210pub struct AudioResponse {
211 #[serde(default)]
213 pub audio_base64: Option<String>,
214
215 #[serde(default)]
217 pub format: Option<String>,
218
219 #[serde(default)]
221 pub size_bytes: Option<i64>,
222
223 #[serde(default)]
225 pub model: Option<String>,
226
227 #[serde(default)]
229 pub cost_ticks: i64,
230
231 #[serde(default)]
233 pub request_id: String,
234
235 #[serde(flatten)]
237 pub extra: HashMap<String, serde_json::Value>,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize, Default)]
242pub struct DialogueTurn {
243 pub speaker: String,
245
246 pub text: String,
248
249 #[serde(skip_serializing_if = "Option::is_none")]
251 pub voice: Option<String>,
252}
253
254#[derive(Debug, Clone, Serialize)]
256pub struct DialogueVoice {
257 pub voice_id: String,
258 pub name: String,
259}
260
261#[derive(Debug, Clone, Serialize, Default)]
264pub struct DialogueRequest {
265 pub text: String,
267
268 pub voices: Vec<DialogueVoice>,
270
271 #[serde(skip_serializing_if = "Option::is_none")]
273 pub model: Option<String>,
274
275 #[serde(rename = "output_format", skip_serializing_if = "Option::is_none")]
277 pub output_format: Option<String>,
278
279 #[serde(skip_serializing_if = "Option::is_none")]
281 pub seed: Option<i32>,
282}
283
284impl DialogueRequest {
285 pub fn from_turns(turns: Vec<DialogueTurn>, model: Option<String>) -> Self {
288 let text = turns.iter()
290 .map(|t| format!("{}: {}", t.speaker, t.text))
291 .collect::<Vec<_>>()
292 .join("\n");
293
294 let mut seen = std::collections::HashSet::new();
296 let voices: Vec<DialogueVoice> = turns.iter()
297 .filter(|t| t.voice.is_some() && seen.insert(t.speaker.clone()))
298 .map(|t| DialogueVoice {
299 voice_id: t.voice.clone().unwrap_or_default(),
300 name: t.speaker.clone(),
301 })
302 .collect();
303
304 Self {
305 text,
306 voices,
307 model,
308 ..Default::default()
309 }
310 }
311}
312
313#[derive(Debug, Clone, Serialize, Default)]
315pub struct SpeechToSpeechRequest {
316 #[serde(skip_serializing_if = "Option::is_none")]
318 pub model: Option<String>,
319
320 pub audio_base64: String,
322
323 #[serde(skip_serializing_if = "Option::is_none")]
325 pub voice_id: Option<String>,
326
327 #[serde(skip_serializing_if = "Option::is_none")]
329 pub voice: Option<String>,
330
331 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
333 pub output_format: Option<String>,
334}
335
336#[derive(Debug, Clone, Serialize, Default)]
338pub struct IsolateVoiceRequest {
339 pub audio_base64: String,
341
342 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
344 pub output_format: Option<String>,
345}
346
347pub type IsolateRequest = IsolateVoiceRequest;
349
350#[derive(Debug, Clone, Serialize, Default)]
352pub struct RemixVoiceRequest {
353 pub audio_base64: String,
355
356 #[serde(skip_serializing_if = "Option::is_none")]
358 pub voice: Option<String>,
359
360 #[serde(skip_serializing_if = "Option::is_none")]
362 pub model: Option<String>,
363
364 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
366 pub output_format: Option<String>,
367}
368
369pub type RemixRequest = RemixVoiceRequest;
371
372#[derive(Debug, Clone, Serialize, Default)]
374pub struct DubRequest {
375 #[serde(skip_serializing_if = "Option::is_none")]
377 pub audio_base64: Option<String>,
378
379 #[serde(skip_serializing_if = "Option::is_none")]
381 pub filename: Option<String>,
382
383 #[serde(skip_serializing_if = "Option::is_none")]
385 pub source_url: Option<String>,
386
387 pub target_lang: String,
389
390 #[serde(skip_serializing_if = "Option::is_none")]
392 pub source_lang: Option<String>,
393
394 #[serde(skip_serializing_if = "Option::is_none")]
396 pub num_speakers: Option<i32>,
397
398 #[serde(skip_serializing_if = "Option::is_none")]
400 pub highest_resolution: Option<bool>,
401}
402
403#[derive(Debug, Clone, Serialize, Default)]
405pub struct AlignRequest {
406 pub audio_base64: String,
408
409 pub text: String,
411
412 #[serde(skip_serializing_if = "Option::is_none")]
414 pub language: Option<String>,
415}
416
417#[derive(Debug, Clone, Deserialize)]
419pub struct AlignmentSegment {
420 pub text: String,
422
423 pub start: f64,
425
426 pub end: f64,
428}
429
430#[derive(Debug, Clone, Deserialize)]
432pub struct AlignedWord {
433 pub text: String,
435
436 pub start_time: f64,
438
439 pub end_time: f64,
441
442 #[serde(default)]
444 pub confidence: f64,
445}
446
447#[derive(Debug, Clone, Deserialize)]
449pub struct AlignResponse {
450 #[serde(default)]
452 pub segments: Vec<AlignmentSegment>,
453
454 #[serde(default)]
456 pub alignment: Vec<AlignedWord>,
457
458 #[serde(default)]
460 pub model: String,
461
462 #[serde(default)]
464 pub cost_ticks: i64,
465
466 #[serde(default)]
468 pub request_id: String,
469}
470
471#[derive(Debug, Clone, Deserialize)]
477pub struct DialogueResponse {
478 pub audio_base64: String,
479 pub format: String,
480 #[serde(default)]
481 pub size_bytes: i64,
482 #[serde(default)]
483 pub model: String,
484 #[serde(default)]
485 pub cost_ticks: i64,
486 #[serde(default)]
487 pub request_id: String,
488}
489
490#[derive(Debug, Clone, Deserialize)]
492pub struct SpeechToSpeechResponse {
493 pub audio_base64: String,
494 pub format: String,
495 #[serde(default)]
496 pub size_bytes: i64,
497 #[serde(default)]
498 pub model: String,
499 #[serde(default)]
500 pub cost_ticks: i64,
501 #[serde(default)]
502 pub request_id: String,
503}
504
505#[derive(Debug, Clone, Deserialize)]
507pub struct IsolateVoiceResponse {
508 pub audio_base64: String,
509 pub format: String,
510 #[serde(default)]
511 pub size_bytes: i64,
512 #[serde(default)]
513 pub cost_ticks: i64,
514 #[serde(default)]
515 pub request_id: String,
516}
517
518#[derive(Debug, Clone, Deserialize)]
520pub struct RemixVoiceResponse {
521 #[serde(default)]
522 pub audio_base64: Option<String>,
523 pub format: String,
524 #[serde(default)]
525 pub size_bytes: i64,
526 #[serde(default)]
527 pub voice_id: Option<String>,
528 #[serde(default)]
529 pub cost_ticks: i64,
530 #[serde(default)]
531 pub request_id: String,
532}
533
534#[derive(Debug, Clone, Deserialize)]
536pub struct DubResponse {
537 pub dubbing_id: String,
538 pub audio_base64: String,
539 pub format: String,
540 #[serde(default)]
541 pub target_lang: String,
542 #[serde(default)]
543 pub status: String,
544 #[serde(default)]
545 pub processing_time_seconds: f64,
546 #[serde(default)]
547 pub cost_ticks: i64,
548 #[serde(default)]
549 pub request_id: String,
550}
551
552#[derive(Debug, Clone, Deserialize)]
554pub struct VoiceDesignResponse {
555 pub previews: Vec<VoicePreview>,
556 #[serde(default)]
557 pub cost_ticks: i64,
558 #[serde(default)]
559 pub request_id: String,
560}
561
562#[derive(Debug, Clone, Deserialize)]
564pub struct VoicePreview {
565 pub generated_voice_id: String,
566 pub audio_base64: String,
567 pub format: String,
568}
569
570#[derive(Debug, Clone, Deserialize)]
572pub struct StarfishTTSResponse {
573 #[serde(default)]
574 pub audio_base64: Option<String>,
575 #[serde(default)]
576 pub url: Option<String>,
577 pub format: String,
578 #[serde(default)]
579 pub size_bytes: i64,
580 #[serde(default)]
581 pub duration: f64,
582 #[serde(default)]
583 pub model: String,
584 #[serde(default)]
585 pub cost_ticks: i64,
586 #[serde(default)]
587 pub request_id: String,
588}
589
590#[derive(Debug, Clone, Serialize, Default)]
592pub struct MusicAdvancedRequest {
593 pub prompt: String,
594 #[serde(skip_serializing_if = "Option::is_none")]
595 pub duration_seconds: Option<i32>,
596 #[serde(skip_serializing_if = "Option::is_none")]
597 pub model: Option<String>,
598 #[serde(skip_serializing_if = "Option::is_none")]
599 pub finetune_id: Option<String>,
600}
601
602#[derive(Debug, Clone, Deserialize)]
604pub struct MusicAdvancedClip {
605 #[serde(default)]
606 pub base64: String,
607 #[serde(default)]
608 pub format: String,
609 #[serde(default)]
610 pub size: i64,
611}
612
613#[derive(Debug, Clone, Deserialize)]
615pub struct MusicAdvancedResponse {
616 #[serde(default)]
617 pub clips: Vec<MusicAdvancedClip>,
618 #[serde(default)]
619 pub model: String,
620 #[serde(default)]
621 pub cost_ticks: i64,
622 #[serde(default)]
623 pub request_id: String,
624}
625
626#[derive(Debug, Clone, Serialize, Deserialize)]
628pub struct MusicFinetuneInfo {
629 pub finetune_id: String,
630 pub name: String,
631 #[serde(default)]
632 pub description: Option<String>,
633 #[serde(default)]
634 pub status: String,
635 #[serde(default)]
636 pub model_id: Option<String>,
637 #[serde(default)]
638 pub created_at: Option<String>,
639}
640
641#[derive(Debug, Clone, Deserialize)]
643pub struct MusicFinetuneListResponse {
644 pub finetunes: Vec<MusicFinetuneInfo>,
645}
646
647#[derive(Debug, Clone, Serialize)]
649pub struct MusicFinetuneCreateRequest {
650 pub name: String,
651 #[serde(skip_serializing_if = "Option::is_none")]
652 pub description: Option<String>,
653 pub samples: Vec<String>,
654}
655
656#[derive(Debug, Clone, Serialize, Default)]
658pub struct VoiceDesignRequest {
659 #[serde(rename = "voice_description")]
661 pub description: String,
662
663 #[serde(rename = "sample_text")]
665 pub text: String,
666
667 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
669 pub output_format: Option<String>,
670}
671
672#[derive(Debug, Clone, Serialize, Default)]
674pub struct StarfishTTSRequest {
675 pub text: String,
677
678 #[serde(skip_serializing_if = "Option::is_none")]
680 pub voice_id: Option<String>,
681
682 #[serde(skip_serializing_if = "Option::is_none")]
684 pub voice: Option<String>,
685
686 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
688 pub output_format: Option<String>,
689
690 #[serde(skip_serializing_if = "Option::is_none")]
692 pub input_type: Option<String>,
693
694 #[serde(skip_serializing_if = "Option::is_none")]
696 pub speed: Option<f64>,
697
698 #[serde(skip_serializing_if = "Option::is_none")]
700 pub language: Option<String>,
701
702 #[serde(skip_serializing_if = "Option::is_none")]
704 pub locale: Option<String>,
705}
706
707#[derive(Debug, Clone, Serialize, Deserialize, Default)]
713pub struct MusicSection {
714 pub section_type: String,
715 #[serde(skip_serializing_if = "Option::is_none")]
716 pub lyrics: Option<String>,
717 #[serde(skip_serializing_if = "Option::is_none")]
718 pub style: Option<String>,
719 #[serde(skip_serializing_if = "Option::is_none")]
720 pub style_exclude: Option<String>,
721}
722
723#[derive(Debug, Clone, Serialize, Default)]
725pub struct ElevenMusicRequest {
726 pub model: String,
727 pub prompt: String,
728 #[serde(skip_serializing_if = "Option::is_none")]
729 pub sections: Option<Vec<MusicSection>>,
730 #[serde(skip_serializing_if = "Option::is_none")]
731 pub duration_seconds: Option<i32>,
732 #[serde(skip_serializing_if = "Option::is_none")]
733 pub language: Option<String>,
734 #[serde(skip_serializing_if = "Option::is_none")]
735 pub vocals: Option<bool>,
736 #[serde(skip_serializing_if = "Option::is_none")]
737 pub style: Option<String>,
738 #[serde(skip_serializing_if = "Option::is_none")]
739 pub style_exclude: Option<String>,
740 #[serde(skip_serializing_if = "Option::is_none")]
741 pub finetune_id: Option<String>,
742 #[serde(skip_serializing_if = "Option::is_none")]
743 pub edit_reference_id: Option<String>,
744 #[serde(skip_serializing_if = "Option::is_none")]
745 pub edit_instruction: Option<String>,
746}
747
748#[derive(Debug, Clone, Deserialize)]
750pub struct ElevenMusicClip {
751 #[serde(default)]
753 pub base64: String,
754 #[serde(default)]
756 pub format: String,
757 #[serde(default)]
759 pub size: i64,
760}
761
762#[derive(Debug, Clone, Deserialize)]
765pub struct ElevenMusicResponse {
766 #[serde(default)]
768 pub clips: Vec<ElevenMusicClip>,
769 #[serde(default)]
771 pub model: String,
772 #[serde(default)]
774 pub cost_ticks: i64,
775 #[serde(default)]
777 pub request_id: String,
778}
779
780#[derive(Debug, Clone, Serialize, Deserialize)]
782pub struct FinetuneInfo {
783 pub finetune_id: String,
784 pub name: String,
785 #[serde(default)]
786 pub status: String,
787 #[serde(default)]
788 pub created_at: Option<String>,
789}
790
791#[derive(Debug, Clone, Deserialize)]
793pub struct ListFinetunesResponse {
794 pub finetunes: Vec<FinetuneInfo>,
795}
796
797impl Client {
802 pub async fn speak(&self, req: &TextToSpeechRequest) -> Result<TextToSpeechResponse> {
804 let (mut resp, meta) = self
805 .post_json::<TextToSpeechRequest, TextToSpeechResponse>("/qai/v1/audio/tts", req)
806 .await?;
807 if resp.cost_ticks == 0 {
808 resp.cost_ticks = meta.cost_ticks;
809 }
810 if resp.request_id.is_empty() {
811 resp.request_id = meta.request_id;
812 }
813 Ok(resp)
814 }
815
816 pub async fn transcribe(&self, req: &SpeechToTextRequest) -> Result<SpeechToTextResponse> {
818 let (mut resp, meta) = self
819 .post_json::<SpeechToTextRequest, SpeechToTextResponse>("/qai/v1/audio/stt", req)
820 .await?;
821 if resp.cost_ticks == 0 {
822 resp.cost_ticks = meta.cost_ticks;
823 }
824 if resp.request_id.is_empty() {
825 resp.request_id = meta.request_id;
826 }
827 Ok(resp)
828 }
829
830 pub async fn sound_effects(&self, req: &SoundEffectRequest) -> Result<SoundEffectResponse> {
832 let (mut resp, meta) = self
833 .post_json::<SoundEffectRequest, SoundEffectResponse>(
834 "/qai/v1/audio/sound-effects",
835 req,
836 )
837 .await?;
838 if resp.cost_ticks == 0 {
839 resp.cost_ticks = meta.cost_ticks;
840 }
841 if resp.request_id.is_empty() {
842 resp.request_id = meta.request_id;
843 }
844 Ok(resp)
845 }
846
847 pub async fn generate_music(&self, req: &MusicRequest) -> Result<MusicResponse> {
849 let (mut resp, meta) = self
850 .post_json::<MusicRequest, MusicResponse>("/qai/v1/audio/music", req)
851 .await?;
852 if resp.cost_ticks == 0 {
853 resp.cost_ticks = meta.cost_ticks;
854 }
855 if resp.request_id.is_empty() {
856 resp.request_id = meta.request_id;
857 }
858 Ok(resp)
859 }
860
861 pub async fn dialogue(&self, req: &DialogueRequest) -> Result<AudioResponse> {
863 let (mut resp, meta) = self
864 .post_json::<DialogueRequest, AudioResponse>("/qai/v1/audio/dialogue", req)
865 .await?;
866 if resp.cost_ticks == 0 {
867 resp.cost_ticks = meta.cost_ticks;
868 }
869 if resp.request_id.is_empty() {
870 resp.request_id = meta.request_id;
871 }
872 Ok(resp)
873 }
874
875 pub async fn speech_to_speech(
877 &self,
878 req: &SpeechToSpeechRequest,
879 ) -> Result<AudioResponse> {
880 let (mut resp, meta) = self
881 .post_json::<SpeechToSpeechRequest, AudioResponse>(
882 "/qai/v1/audio/speech-to-speech",
883 req,
884 )
885 .await?;
886 if resp.cost_ticks == 0 {
887 resp.cost_ticks = meta.cost_ticks;
888 }
889 if resp.request_id.is_empty() {
890 resp.request_id = meta.request_id;
891 }
892 Ok(resp)
893 }
894
895 pub async fn isolate_voice(&self, req: &IsolateVoiceRequest) -> Result<AudioResponse> {
897 let (mut resp, meta) = self
898 .post_json::<IsolateVoiceRequest, AudioResponse>("/qai/v1/audio/isolate", req)
899 .await?;
900 if resp.cost_ticks == 0 {
901 resp.cost_ticks = meta.cost_ticks;
902 }
903 if resp.request_id.is_empty() {
904 resp.request_id = meta.request_id;
905 }
906 Ok(resp)
907 }
908
909 pub async fn remix_voice(&self, req: &RemixVoiceRequest) -> Result<AudioResponse> {
911 let (mut resp, meta) = self
912 .post_json::<RemixVoiceRequest, AudioResponse>("/qai/v1/audio/remix", req)
913 .await?;
914 if resp.cost_ticks == 0 {
915 resp.cost_ticks = meta.cost_ticks;
916 }
917 if resp.request_id.is_empty() {
918 resp.request_id = meta.request_id;
919 }
920 Ok(resp)
921 }
922
923 pub async fn dub(&self, req: &DubRequest) -> Result<AudioResponse> {
925 let (mut resp, meta) = self
926 .post_json::<DubRequest, AudioResponse>("/qai/v1/audio/dub", req)
927 .await?;
928 if resp.cost_ticks == 0 {
929 resp.cost_ticks = meta.cost_ticks;
930 }
931 if resp.request_id.is_empty() {
932 resp.request_id = meta.request_id;
933 }
934 Ok(resp)
935 }
936
937 pub async fn align(&self, req: &AlignRequest) -> Result<AlignResponse> {
939 let (mut resp, meta) = self
940 .post_json::<AlignRequest, AlignResponse>("/qai/v1/audio/align", req)
941 .await?;
942 if resp.cost_ticks == 0 {
943 resp.cost_ticks = meta.cost_ticks;
944 }
945 if resp.request_id.is_empty() {
946 resp.request_id = meta.request_id;
947 }
948 Ok(resp)
949 }
950
951 pub async fn voice_design(&self, req: &VoiceDesignRequest) -> Result<AudioResponse> {
953 let (mut resp, meta) = self
954 .post_json::<VoiceDesignRequest, AudioResponse>("/qai/v1/audio/voice-design", req)
955 .await?;
956 if resp.cost_ticks == 0 {
957 resp.cost_ticks = meta.cost_ticks;
958 }
959 if resp.request_id.is_empty() {
960 resp.request_id = meta.request_id;
961 }
962 Ok(resp)
963 }
964
965 pub async fn starfish_tts(&self, req: &StarfishTTSRequest) -> Result<AudioResponse> {
967 let (mut resp, meta) = self
968 .post_json::<StarfishTTSRequest, AudioResponse>("/qai/v1/audio/starfish-tts", req)
969 .await?;
970 if resp.cost_ticks == 0 {
971 resp.cost_ticks = meta.cost_ticks;
972 }
973 if resp.request_id.is_empty() {
974 resp.request_id = meta.request_id;
975 }
976 Ok(resp)
977 }
978
979 pub async fn generate_music_advanced(
981 &self,
982 req: &ElevenMusicRequest,
983 ) -> Result<ElevenMusicResponse> {
984 let (mut resp, meta) = self
985 .post_json::<ElevenMusicRequest, ElevenMusicResponse>(
986 "/qai/v1/audio/music/advanced",
987 req,
988 )
989 .await?;
990 if resp.cost_ticks == 0 {
991 resp.cost_ticks = meta.cost_ticks;
992 }
993 if resp.request_id.is_empty() {
994 resp.request_id = meta.request_id;
995 }
996 Ok(resp)
997 }
998
999 pub async fn list_finetunes(&self) -> Result<ListFinetunesResponse> {
1001 let (resp, _) = self
1002 .get_json::<ListFinetunesResponse>("/qai/v1/audio/finetunes")
1003 .await?;
1004 Ok(resp)
1005 }
1006
1007 pub async fn create_finetune(
1009 &self,
1010 name: &str,
1011 files: Vec<crate::voices::CloneVoiceFile>,
1012 ) -> Result<FinetuneInfo> {
1013 let mut form = reqwest::multipart::Form::new().text("name", name.to_string());
1014
1015 for file in files {
1016 let part = reqwest::multipart::Part::bytes(file.data)
1017 .file_name(file.filename)
1018 .mime_str(&file.mime_type)
1019 .map_err(|e| crate::error::Error::Http(e.into()))?;
1020 form = form.part("files", part);
1021 }
1022
1023 let (resp, _) = self
1024 .post_multipart::<FinetuneInfo>("/qai/v1/audio/finetunes", form)
1025 .await?;
1026 Ok(resp)
1027 }
1028
1029 pub async fn delete_finetune(&self, id: &str) -> Result<serde_json::Value> {
1031 let path = format!("/qai/v1/audio/finetunes/{id}");
1032 let (resp, _) = self.delete_json::<serde_json::Value>(&path).await?;
1033 Ok(resp)
1034 }
1035}