From 7733202bb33557aad3d984540d981f4e03743b82 Mon Sep 17 00:00:00 2001 From: Linghong Date: Fri, 7 Aug 2026 08:12:03 +0000 Subject: [PATCH] =?UTF-8?q?=E9=9F=B3=E6=95=88=E5=A4=B1=E8=B4=A5=E5=AF=B9?= =?UTF-8?q?=E5=A4=96=E5=8F=AA=E8=BF=94=E5=9B=9E=E7=A8=B3=E5=AE=9A=E6=96=87?= =?UTF-8?q?=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把音效任务加入用户可见失败文案白名单,不再透出 ElevenLabs 端点与传输层诊断。 新增音效失败文案脱敏测试,原始诊断仍保留在任务记录、tracing 与审计事件中。 --- .../api-server/src/external_generation.rs | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/server-rs/crates/api-server/src/external_generation.rs b/server-rs/crates/api-server/src/external_generation.rs index 1fba1ce65..679e06c5a 100644 --- a/server-rs/crates/api-server/src/external_generation.rs +++ b/server-rs/crates/api-server/src/external_generation.rs @@ -21,6 +21,7 @@ use spacetime_client::{ use crate::editor_generation_queue::{ EDITOR_BACKGROUND_REMOVAL_JOB_KIND, EDITOR_CHARACTER_ANIMATION_GENERATION_JOB_KIND, + EDITOR_SOUND_EFFECT_GENERATION_JOB_KIND, }; use crate::{ api_response::json_success_body, auth::AuthenticatedAccessToken, http_error::AppError, @@ -202,9 +203,9 @@ fn map_external_generation_job_status( } } -/// 外部任务表保存的失败文本同时服务于 worker 诊断和用户通知。抠图及角色动作透明化任务的 -/// 原始失败文本可能包含内部 provider、分割模型或实现细节,因此只能在普通用户读取边界 -/// 替换成稳定文案。 +/// 外部任务表保存的失败文本同时服务于 worker 诊断和用户通知。抠图、角色动作透明化和音效 +/// 任务的原始失败文本可能包含内部 provider、分割模型、请求端点或 HTTP 传输层细节,因此 +/// 只能在普通用户读取边界替换成稳定文案。 /// 原始值仍留在任务记录、tracing 和后台审计路径中。 fn user_visible_external_generation_error(job_kind: &str, error: Option) -> Option { if job_kind == EDITOR_BACKGROUND_REMOVAL_JOB_KIND && error.is_some() { @@ -213,6 +214,10 @@ fn user_visible_external_generation_error(job_kind: &str, error: Option) if job_kind == EDITOR_CHARACTER_ANIMATION_GENERATION_JOB_KIND && error.is_some() { return Some("角色动作生成失败,请稍后重试。".to_string()); } + // 音效失败文本来自 ElevenLabs / reqwest,会带上请求端点、底层错误链和上游状态码。 + if job_kind == EDITOR_SOUND_EFFECT_GENERATION_JOB_KIND && error.is_some() { + return Some("音效生成失败,请稍后重试。".to_string()); + } error } @@ -470,6 +475,50 @@ mod tests { assert!(!lower.contains("provider")); } + #[test] + fn sound_effect_failure_hides_provider_and_transport_details_from_owner() { + let job = ExternalGenerationJobSummaryRecord { + job_id: "task-sound-effect-failed".to_string(), + job_kind: EDITOR_SOUND_EFFECT_GENERATION_JOB_KIND.to_string(), + owner_user_id: "user-1".to_string(), + source_module: "editor-canvas".to_string(), + source_entity_id: "project-1".to_string(), + request_label: "音效生成".to_string(), + request_prompt: None, + status: "failed".to_string(), + last_error_message: Some( + "请求 ElevenLabs 音效生成失败:error sending request for url \ + (https://api.elevenlabs.io/v1/sound-generation?output_format=mp3_44100_128): \ + connection error: Connection refused (os error 111)" + .to_string(), + ), + created_at: "2026-08-07T08:00:00Z".to_string(), + started_at: Some("2026-08-07T08:00:01Z".to_string()), + completed_at: Some("2026-08-07T08:00:10Z".to_string()), + updated_at: "2026-08-07T08:00:10Z".to_string(), + updated_at_micros: 1_000, + price_mud_points: 5, + refund_ledger_id: None, + notification_acknowledged_at: None, + notification_acknowledged_at_micros: None, + phase: None, + warning_message: None, + }; + + let status = map_external_generation_job_status(job.clone()); + let task = map_external_generation_task_record(job); + + assert_eq!(status.error.as_deref(), Some("音效生成失败,请稍后重试。")); + assert_eq!(task.error.as_deref(), Some("音效生成失败,请稍后重试。")); + for value in [status.error, task.error].into_iter().flatten() { + let lower = value.to_ascii_lowercase(); + assert!(!lower.contains("elevenlabs")); + assert!(!lower.contains("api.elevenlabs.io")); + assert!(!lower.contains("sound-generation")); + assert!(!lower.contains("os error")); + } + } + #[test] fn unrelated_generation_failure_keeps_its_user_visible_message() { let message = user_visible_external_generation_error(