1use anyhow::Result;
3use reqwest::{Client, Response};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::time::Duration;
7use voirs_sdk::types::SynthesisConfig;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct CloudApiConfig {
11 pub base_url: String,
12 pub api_key: Option<String>,
13 pub timeout_seconds: u64,
14 pub retry_attempts: u32,
15 pub rate_limit_requests_per_minute: u32,
16 pub enabled_services: Vec<CloudService>,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
20pub enum CloudService {
21 Translation,
22 ContentManagement,
23 Analytics,
24 QualityAssurance,
25 VoiceTraining,
26 AudioProcessing,
27 SpeechRecognition,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct TranslationRequest {
32 pub text: String,
33 pub source_language: String,
34 pub target_language: String,
35 pub preserve_ssml: bool,
36 pub quality_level: TranslationQuality,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub enum TranslationQuality {
41 Fast,
42 Balanced,
43 HighQuality,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct TranslationResponse {
48 pub translated_text: String,
49 pub detected_language: Option<String>,
50 pub confidence_score: f32,
51 pub processing_time_ms: u32,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct ContentAnalysisRequest {
56 pub content: String,
57 pub analysis_types: Vec<AnalysisType>,
58 pub language: Option<String>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub enum AnalysisType {
63 Sentiment,
64 Entities,
65 Keywords,
66 Readability,
67 Appropriateness,
68 Complexity,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct ContentAnalysisResponse {
73 pub sentiment: Option<SentimentAnalysis>,
74 pub entities: Vec<EntityExtraction>,
75 pub keywords: Vec<KeywordExtraction>,
76 pub readability_score: Option<f32>,
77 pub appropriateness_rating: Option<AppropriattenessRating>,
78 pub complexity_level: Option<ComplexityLevel>,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct SentimentAnalysis {
83 pub sentiment: String, pub confidence: f32,
85 pub emotional_tone: Vec<EmotionScore>,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct EmotionScore {
90 pub emotion: String,
91 pub score: f32,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct EntityExtraction {
96 pub text: String,
97 pub entity_type: String,
98 pub confidence: f32,
99 pub start_offset: usize,
100 pub end_offset: usize,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct KeywordExtraction {
105 pub keyword: String,
106 pub relevance: f32,
107 pub frequency: u32,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub enum AppropriattenessRating {
112 Appropriate,
113 Questionable,
114 Inappropriate,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub enum ComplexityLevel {
119 Simple,
120 Moderate,
121 Complex,
122 Advanced,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct AnalyticsEvent {
127 pub event_type: String,
128 pub timestamp: u64,
129 pub user_id: Option<String>,
130 pub session_id: Option<String>,
131 pub properties: HashMap<String, serde_json::Value>,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct QualityAssessmentRequest {
136 pub audio_data: Vec<u8>,
137 pub text: String,
138 pub synthesis_config: SynthesisConfig,
139 pub assessment_types: Vec<QualityMetric>,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub enum QualityMetric {
144 Naturalness,
145 Intelligibility,
146 Prosody,
147 Pronunciation,
148 OverallQuality,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct QualityAssessmentResponse {
153 pub overall_score: f32,
154 pub metric_scores: HashMap<String, f32>,
155 pub detailed_feedback: Vec<QualityFeedback>,
156 pub improvement_suggestions: Vec<String>,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct QualityFeedback {
161 pub metric: String,
162 pub score: f32,
163 pub description: String,
164 pub timestamp_range: Option<(f32, f32)>, }
166
167pub struct CloudApiClient {
168 client: Client,
169 config: CloudApiConfig,
170 rate_limiter: RateLimiter,
171}
172
173struct RateLimiter {
174 requests_per_minute: u32,
175 request_times: Vec<std::time::Instant>,
176}
177
178impl CloudApiClient {
179 pub fn new(config: CloudApiConfig) -> Result<Self> {
180 let client = Client::builder()
181 .timeout(Duration::from_secs(config.timeout_seconds))
182 .build()?;
183
184 let rate_limiter = RateLimiter::new(config.rate_limit_requests_per_minute);
185
186 Ok(Self {
187 client,
188 config,
189 rate_limiter,
190 })
191 }
192
193 pub async fn translate_text(
195 &mut self,
196 request: TranslationRequest,
197 ) -> Result<TranslationResponse> {
198 self.rate_limiter.wait_if_needed().await;
199
200 if !self
201 .config
202 .enabled_services
203 .contains(&CloudService::Translation)
204 {
205 return Err(anyhow::anyhow!("Translation service not enabled"));
206 }
207
208 let url = format!("{}/v1/translate", self.config.base_url);
209 let response = self.make_request("POST", &url, Some(&request)).await?;
210
211 let translation_response: TranslationResponse = response.json().await?;
212 Ok(translation_response)
213 }
214
215 pub async fn analyze_content(
217 &mut self,
218 request: ContentAnalysisRequest,
219 ) -> Result<ContentAnalysisResponse> {
220 self.rate_limiter.wait_if_needed().await;
221
222 if !self
223 .config
224 .enabled_services
225 .contains(&CloudService::ContentManagement)
226 {
227 return Err(anyhow::anyhow!("Content analysis service not enabled"));
228 }
229
230 let url = format!("{}/v1/analyze", self.config.base_url);
231 let response = self.make_request("POST", &url, Some(&request)).await?;
232
233 let analysis_response: ContentAnalysisResponse = response.json().await?;
234 Ok(analysis_response)
235 }
236
237 pub async fn send_analytics_event(&mut self, event: AnalyticsEvent) -> Result<()> {
239 self.rate_limiter.wait_if_needed().await;
240
241 if !self
242 .config
243 .enabled_services
244 .contains(&CloudService::Analytics)
245 {
246 return Err(anyhow::anyhow!("Analytics service not enabled"));
247 }
248
249 let url = format!("{}/v1/analytics/events", self.config.base_url);
250 let _response = self.make_request("POST", &url, Some(&event)).await?;
251
252 Ok(())
253 }
254
255 pub async fn assess_quality(
257 &mut self,
258 request: QualityAssessmentRequest,
259 ) -> Result<QualityAssessmentResponse> {
260 self.rate_limiter.wait_if_needed().await;
261
262 if !self
263 .config
264 .enabled_services
265 .contains(&CloudService::QualityAssurance)
266 {
267 return Err(anyhow::anyhow!("Quality assessment service not enabled"));
268 }
269
270 let url = format!("{}/v1/quality/assess", self.config.base_url);
271
272 let payload = serde_json::json!({
274 "audio_data_base64": base64::encode(&request.audio_data),
275 "text": request.text,
276 "config": request.synthesis_config,
277 "metrics": request.assessment_types
278 });
279
280 let response = self.client.post(&url).json(&payload).send().await?;
281
282 if !response.status().is_success() {
283 return Err(anyhow::anyhow!(
284 "Quality assessment request failed: {}",
285 response.status()
286 ));
287 }
288
289 let quality_response: QualityAssessmentResponse = response.json().await?;
290 Ok(quality_response)
291 }
292
293 pub async fn get_service_health(&mut self) -> Result<ServiceHealth> {
295 let url = format!("{}/v1/health", self.config.base_url);
296 let response = self.make_request("GET", &url, None::<&()>).await?;
297
298 let health: ServiceHealth = response.json().await?;
299 Ok(health)
300 }
301
302 async fn make_request<T: Serialize>(
303 &self,
304 method: &str,
305 url: &str,
306 body: Option<&T>,
307 ) -> Result<Response> {
308 let mut request_builder = match method {
309 "GET" => self.client.get(url),
310 "POST" => self.client.post(url),
311 "PUT" => self.client.put(url),
312 "DELETE" => self.client.delete(url),
313 _ => return Err(anyhow::anyhow!("Unsupported HTTP method: {}", method)),
314 };
315
316 if let Some(api_key) = &self.config.api_key {
318 request_builder =
319 request_builder.header("Authorization", format!("Bearer {}", api_key));
320 }
321
322 if let Some(body) = body {
324 request_builder = request_builder.json(body);
325 }
326
327 let mut last_error = None;
329 for attempt in 0..=self.config.retry_attempts {
330 match request_builder.try_clone() {
331 Some(request) => match request.send().await {
332 Ok(response) => {
333 if response.status().is_success() {
334 return Ok(response);
335 } else {
336 last_error = Some(anyhow::anyhow!("HTTP error: {}", response.status()));
337 }
338 }
339 Err(e) => {
340 last_error = Some(anyhow::anyhow!("Request error: {}", e));
341 }
342 },
343 None => {
344 last_error = Some(anyhow::anyhow!("Failed to clone request"));
345 break;
346 }
347 }
348
349 if attempt < self.config.retry_attempts {
350 tokio::time::sleep(Duration::from_millis(1000 * (2_u64.pow(attempt)))).await;
351 }
353 }
354
355 Err(last_error.unwrap_or_else(|| anyhow::anyhow!("Request failed after all retries")))
356 }
357}
358
359#[derive(Debug, Clone, Serialize, Deserialize)]
360pub struct ServiceHealth {
361 pub status: String,
362 pub services: HashMap<String, ServiceStatus>,
363 pub response_time_ms: u32,
364 pub version: String,
365}
366
367#[derive(Debug, Clone, Serialize, Deserialize)]
368pub struct ServiceStatus {
369 pub healthy: bool,
370 pub response_time_ms: Option<u32>,
371 pub error_message: Option<String>,
372}
373
374impl RateLimiter {
375 fn new(requests_per_minute: u32) -> Self {
376 Self {
377 requests_per_minute,
378 request_times: Vec::new(),
379 }
380 }
381
382 async fn wait_if_needed(&mut self) {
383 let now = std::time::Instant::now();
384 let one_minute_ago = now - Duration::from_secs(60);
385
386 self.request_times.retain(|&time| time > one_minute_ago);
388
389 if self.request_times.len() >= self.requests_per_minute as usize {
391 if let Some(&oldest) = self.request_times.first() {
392 let wait_until = oldest + Duration::from_secs(60);
393 if now < wait_until {
394 let wait_duration = wait_until - now;
395 tokio::time::sleep(wait_duration).await;
396 }
397 }
398 }
399
400 self.request_times.push(now);
402 }
403}
404
405impl Default for CloudApiConfig {
406 fn default() -> Self {
407 Self {
408 base_url: "https://api.voirs.cloud".to_string(),
409 api_key: None,
410 timeout_seconds: 30,
411 retry_attempts: 3,
412 rate_limit_requests_per_minute: 60,
413 enabled_services: vec![
414 CloudService::Translation,
415 CloudService::ContentManagement,
416 CloudService::Analytics,
417 CloudService::QualityAssurance,
418 ],
419 }
420 }
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426
427 #[test]
428 fn test_cloud_api_config_default() {
429 let config = CloudApiConfig::default();
430 assert_eq!(config.timeout_seconds, 30);
431 assert_eq!(config.retry_attempts, 3);
432 assert!(config.enabled_services.len() > 0);
433 }
434
435 #[test]
436 fn test_translation_request_serialization() {
437 let request = TranslationRequest {
438 text: "Hello world".to_string(),
439 source_language: "en".to_string(),
440 target_language: "es".to_string(),
441 preserve_ssml: true,
442 quality_level: TranslationQuality::HighQuality,
443 };
444
445 let serialized = serde_json::to_string(&request);
446 assert!(serialized.is_ok());
447
448 let deserialized: Result<TranslationRequest, _> =
449 serde_json::from_str(&serialized.unwrap());
450 assert!(deserialized.is_ok());
451 }
452
453 #[test]
454 fn test_analytics_event_creation() {
455 let mut properties = HashMap::new();
456 properties.insert(
457 "voice_id".to_string(),
458 serde_json::Value::String("en-US-1".to_string()),
459 );
460 properties.insert(
461 "duration_ms".to_string(),
462 serde_json::Value::Number(serde_json::Number::from(5000)),
463 );
464
465 let event = AnalyticsEvent {
466 event_type: "synthesis_completed".to_string(),
467 timestamp: 1620000000,
468 user_id: Some("user123".to_string()),
469 session_id: Some("session456".to_string()),
470 properties,
471 };
472
473 assert_eq!(event.event_type, "synthesis_completed");
474 assert_eq!(event.properties.len(), 2);
475 }
476
477 #[tokio::test]
478 async fn test_rate_limiter() {
479 let mut limiter = RateLimiter::new(60); let start = std::time::Instant::now();
483 limiter.wait_if_needed().await;
484 let elapsed = start.elapsed();
485
486 assert!(elapsed < Duration::from_millis(100));
488 }
489
490 #[test]
491 fn test_quality_feedback_serialization() {
492 let feedback = QualityFeedback {
493 metric: "naturalness".to_string(),
494 score: 0.85,
495 description: "Speech sounds natural with minor robotic artifacts".to_string(),
496 timestamp_range: Some((1.5, 3.2)),
497 };
498
499 let serialized = serde_json::to_string(&feedback);
500 assert!(serialized.is_ok());
501
502 let deserialized: Result<QualityFeedback, _> = serde_json::from_str(&serialized.unwrap());
503 assert!(deserialized.is_ok());
504 }
505}