use serde_json::{Map, Value, json}; use crate::{ AudioError, BackgroundMusicTaskRequest, EditorBackgroundMusicTaskRequest, SUNO_DEFAULT_MODEL, SUNO_PROMPT_MAX_CHARS, SUNO_TAGS_MAX_CHARS, SUNO_TITLE_MAX_CHARS, }; pub fn build_background_music_task_body( request: BackgroundMusicTaskRequest, ) -> Result { let prompt = normalize_limited_text_allow_empty(&request.prompt, "prompt", SUNO_PROMPT_MAX_CHARS)?; let title = normalize_limited_text(&request.title, "title", SUNO_TITLE_MAX_CHARS)?; let tags = request .tags .as_deref() .map(|value| normalize_limited_text(value, "tags", SUNO_TAGS_MAX_CHARS)) .transpose()?; let model = normalize_optional_text(request.model.as_deref()) .unwrap_or_else(|| SUNO_DEFAULT_MODEL.to_string()); let mut body = Map::from_iter([ ("prompt".to_string(), Value::String(prompt)), ("mv".to_string(), Value::String(model)), ("title".to_string(), Value::String(title)), ("task".to_string(), Value::String("generate".to_string())), ( "make_instrumental".to_string(), Value::Bool(request.instrumental), ), ]); if let Some(tags) = tags { body.insert("tags".to_string(), Value::String(tags)); } Ok(Value::Object(body)) } pub fn build_editor_background_music_task_body( request: EditorBackgroundMusicTaskRequest, ) -> Result { let prompt = crate::validate_background_music_generation_prompt(&request.gpt_description_prompt)?; let model = normalize_optional_text(request.model.as_deref()) .unwrap_or_else(|| SUNO_DEFAULT_MODEL.to_string()); Ok(json!({ "mv": model, "gpt_description_prompt": prompt.prompt, "make_instrumental": request.make_instrumental, })) } pub fn normalize_limited_text( value: &str, field: &'static str, max_chars: usize, ) -> Result { let normalized = value.trim().to_string(); if normalized.is_empty() { return Err(AudioError::invalid_request(format!("{field} 不能为空"))); } if normalized.chars().count() > max_chars { return Err(AudioError::invalid_request(format!( "{field} 超过 {} 字符", max_chars ))); } Ok(normalized) } pub fn normalize_limited_text_allow_empty( value: &str, field: &'static str, max_chars: usize, ) -> Result { let normalized = value.trim().to_string(); if normalized.chars().count() > max_chars { return Err(AudioError::invalid_request(format!( "{field} 超过 {} 字符", max_chars ))); } Ok(normalized) } pub fn normalize_optional_text(value: Option<&str>) -> Option { value .map(str::trim) .filter(|value| !value.is_empty()) .map(ToOwned::to_owned) }