收紧编辑器内部处理元数据边界
普通用户与公开响应统一移除生成 provider、内部处理模型和抠图审计字段。 普通生成请求移除 segModel,由后端固定抠图策略并清理客户端伪造的保留字段。 手动去背景继承可信源模型,角色动作与去背景失败统一使用稳定用户文案。 后台原始审计继续保留真实 provider 与抠图模型,并补齐 Owner、Public、Admin 和紧凑结果测试。 同步更新前端响应契约、外部 OpenAPI、后端架构文档和项目决策记录。
This commit is contained in:
@@ -4043,10 +4043,11 @@ mod tests {
|
||||
use super::{
|
||||
AdminDashboardGranularity, AdminDisplayNameDirectory, EditorShowcaseAssetRecord,
|
||||
admin_dashboard_user_stats_from_record, admin_dashboard_user_stats_from_result,
|
||||
admin_editor_asset_group_payload, admin_editor_showcase_asset_payload_from_record,
|
||||
append_spacetime_sql_response_chunk, apply_admin_database_table_filters,
|
||||
build_admin_asset_read_url_audit, build_admin_dashboard_chart,
|
||||
build_admin_database_table_row, build_admin_editor_showcase_campaign_image_confirm_request,
|
||||
admin_editor_asset_group_payload, admin_editor_asset_payload_from_record,
|
||||
admin_editor_showcase_asset_payload_from_record, append_spacetime_sql_response_chunk,
|
||||
apply_admin_database_table_filters, build_admin_asset_read_url_audit,
|
||||
build_admin_dashboard_chart, build_admin_database_table_row,
|
||||
build_admin_editor_showcase_campaign_image_confirm_request,
|
||||
build_admin_tracking_event_keys_sql, build_admin_tracking_events_sql, build_body_preview,
|
||||
build_debug_base_url, build_spacetime_schema_url, clamp_admin_database_table_limit,
|
||||
clamp_admin_tracking_event_limit, enforce_admin_request_permission,
|
||||
@@ -4154,6 +4155,42 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_editor_asset_payload_keeps_internal_matting_audit_metadata() {
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
let mut record = test_admin_editor_asset_record(
|
||||
"asset-matting",
|
||||
"task-matting",
|
||||
"抠图结果",
|
||||
"generated-character-drafts/editor/matting.png",
|
||||
0,
|
||||
);
|
||||
record.model = Some("BgFilter complex".to_string());
|
||||
record.provider = Some("BgFilter".to_string());
|
||||
record.generation_inputs = Some(json!({
|
||||
"fields": [],
|
||||
"references": [],
|
||||
"screenColorHex": "#CFEFFF",
|
||||
"mattingProvider": "BgFilter",
|
||||
"mattingModel": "birefnet"
|
||||
}));
|
||||
|
||||
let payload = admin_editor_asset_payload_from_record(&state, record);
|
||||
let value = serde_json::to_value(payload).expect("admin payload should serialize");
|
||||
|
||||
assert_eq!(value["model"], json!("BgFilter complex"));
|
||||
assert_eq!(value["provider"], json!("BgFilter"));
|
||||
assert_eq!(
|
||||
value["generationInputs"]["screenColorHex"],
|
||||
json!("#CFEFFF")
|
||||
);
|
||||
assert_eq!(
|
||||
value["generationInputs"]["mattingProvider"],
|
||||
json!("BgFilter")
|
||||
);
|
||||
assert_eq!(value["generationInputs"]["mattingModel"], json!("birefnet"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_asset_read_url_audit_keeps_actor_and_target_without_signed_url() {
|
||||
let draft = build_admin_asset_read_url_audit(
|
||||
|
||||
@@ -1164,7 +1164,6 @@ pub(crate) async fn generate_editor_video_for_owner(
|
||||
prompt: normalized.prompt,
|
||||
actual_prompt: Some(generated.submitted_prompt),
|
||||
model: normalized.model,
|
||||
provider: "VectorEngine".to_string(),
|
||||
task_id,
|
||||
duration_seconds: normalized.duration_seconds,
|
||||
resolution: normalized.resolution,
|
||||
@@ -5827,7 +5826,22 @@ fn character_animation_error_response(
|
||||
request_context: &RequestContext,
|
||||
error: AppError,
|
||||
) -> Response {
|
||||
error.into_response_with_context(Some(request_context))
|
||||
user_visible_character_animation_error(error).into_response_with_context(Some(request_context))
|
||||
}
|
||||
|
||||
fn user_visible_character_animation_error(error: AppError) -> AppError {
|
||||
if !error.status_code().is_server_error() {
|
||||
return error;
|
||||
}
|
||||
|
||||
tracing::error!(
|
||||
status = %error.status_code(),
|
||||
error = %error,
|
||||
"editor_character_animation_internal_error_hidden_from_user"
|
||||
);
|
||||
AppError::from_status(error.status_code()).with_details(json!({
|
||||
"message": "角色动作生成失败,请稍后重试。",
|
||||
}))
|
||||
}
|
||||
|
||||
fn editor_video_error_response(request_context: &RequestContext, error: AppError) -> Response {
|
||||
@@ -5985,6 +5999,23 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::AppConfig;
|
||||
|
||||
#[test]
|
||||
fn character_animation_server_error_hides_internal_matting_details() {
|
||||
let error = AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": "BgFilter",
|
||||
"message": "BgFilter birefnet provider 调用失败",
|
||||
}));
|
||||
let sanitized = user_visible_character_animation_error(error);
|
||||
|
||||
assert_eq!(sanitized.status_code(), StatusCode::BAD_GATEWAY);
|
||||
assert_eq!(sanitized.body_text(), "角色动作生成失败,请稍后重试。");
|
||||
let details = sanitized.details().expect("稳定错误应保留用户文案");
|
||||
assert!(details.get("provider").is_none());
|
||||
let lower = details.to_string().to_ascii_lowercase();
|
||||
assert!(!lower.contains("bgfilter"));
|
||||
assert!(!lower.contains("birefnet"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_video_data_url_accepts_mp4_payload() {
|
||||
let parsed =
|
||||
|
||||
@@ -333,7 +333,6 @@ impl EditorAgentTool for GenerateImageTool {
|
||||
style: None,
|
||||
model: Some(args.model),
|
||||
screen_color: None,
|
||||
seg_model: None,
|
||||
aspect_ratio: Some(args.aspect_ratio),
|
||||
image_size: Some(args.image_size),
|
||||
reference_image_srcs: Some(reference_image_srcs),
|
||||
@@ -441,7 +440,6 @@ impl EditorAgentTool for GenerateCharacterTool {
|
||||
style: None,
|
||||
model: Some(args.model),
|
||||
screen_color: Some("auto".to_string()),
|
||||
seg_model: Some("birefnet".to_string()),
|
||||
aspect_ratio: Some(args.aspect_ratio),
|
||||
image_size: Some(args.image_size),
|
||||
reference_image_srcs: Some(reference_image_srcs),
|
||||
@@ -551,7 +549,6 @@ impl EditorAgentTool for GenerateUiDesignTool {
|
||||
style: None,
|
||||
model: Some(args.model),
|
||||
screen_color: None,
|
||||
seg_model: None,
|
||||
aspect_ratio: Some(args.aspect_ratio),
|
||||
image_size: Some(args.image_size),
|
||||
reference_image_srcs: Some(reference_image_srcs),
|
||||
@@ -805,7 +802,6 @@ impl EditorAgentTool for GenerateIconSpritesheetTool {
|
||||
style: None,
|
||||
model: Some(args.model),
|
||||
screen_color: Some("auto".to_string()),
|
||||
seg_model: Some("birefnet".to_string()),
|
||||
aspect_ratio: Some(args.aspect_ratio),
|
||||
image_size: Some(args.image_size),
|
||||
project_id: Some(context.conversation.project_id.clone()),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -32,8 +32,8 @@ use crate::{
|
||||
editor_project_resource_payload_from_record, extract_editor_ui_design_assets_for_owner,
|
||||
generate_editor_icon_spritesheet_for_owner, generate_editor_image_for_owner,
|
||||
map_editor_project_error, normalize_editor_persisted_media_src, normalize_optional_string,
|
||||
parse_editor_generation_json_payload, save_editor_project_layout_with_revision_and_get,
|
||||
serialize_editor_asset_metadata,
|
||||
parse_editor_generation_json_payload, sanitize_editor_client_generation_inputs,
|
||||
save_editor_project_layout_with_revision_and_get, serialize_editor_asset_metadata,
|
||||
},
|
||||
external_api_auth::ExternalApiPrincipal,
|
||||
http_error::AppError,
|
||||
@@ -467,7 +467,7 @@ pub async fn create_external_editor_asset(
|
||||
) -> Result<Json<Value>, AppError> {
|
||||
require_scope(&principal, SCOPE_EDITOR_ASSET)?;
|
||||
let generation_inputs_json =
|
||||
serialize_editor_asset_metadata(payload.generation_inputs.clone())?;
|
||||
serialize_external_editor_generation_inputs(payload.generation_inputs.clone())?;
|
||||
let object_key = normalize_optional_string(payload.object_key);
|
||||
let image_src = normalize_editor_persisted_media_src(payload.image_src, object_key.as_deref())?;
|
||||
let asset = state
|
||||
@@ -569,7 +569,7 @@ pub async fn create_external_editor_project_resource(
|
||||
) -> Result<Json<Value>, AppError> {
|
||||
require_scope(&principal, SCOPE_EDITOR_ASSET)?;
|
||||
let generation_inputs_json =
|
||||
serialize_editor_asset_metadata(payload.generation_inputs.clone())?;
|
||||
serialize_external_editor_generation_inputs(payload.generation_inputs.clone())?;
|
||||
let object_key = normalize_optional_string(payload.object_key);
|
||||
let image_src = normalize_editor_persisted_media_src(payload.image_src, object_key.as_deref())?;
|
||||
let resource = state
|
||||
@@ -787,6 +787,12 @@ fn normalize_project_title(title: Option<String>) -> String {
|
||||
.unwrap_or_else(|| EDITOR_PROJECT_DEFAULT_TITLE.to_string())
|
||||
}
|
||||
|
||||
fn serialize_external_editor_generation_inputs(
|
||||
generation_inputs: Option<Value>,
|
||||
) -> Result<Option<String>, AppError> {
|
||||
serialize_editor_asset_metadata(sanitize_editor_client_generation_inputs(generation_inputs))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -809,6 +815,26 @@ mod tests {
|
||||
assert_eq!(request.expected_revision, 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_editor_generation_inputs_cannot_forge_internal_audit_fields() {
|
||||
let serialized = serialize_external_editor_generation_inputs(Some(json!({
|
||||
"fields": [{"label": "角色设定", "value": "骑士"}],
|
||||
"screenColorHex": "#00FF00",
|
||||
"mattingProvider": "forged-provider",
|
||||
"mattingModel": "forged-model",
|
||||
"characterAnimation": {"durationSeconds": 4}
|
||||
})))
|
||||
.expect("外部编辑器生成输入应可序列化")
|
||||
.expect("非空生成输入应保留");
|
||||
let parsed: Value = serde_json::from_str(&serialized).expect("生成输入应为合法 JSON");
|
||||
|
||||
assert_eq!(parsed["fields"][0]["value"], "骑士");
|
||||
assert_eq!(parsed["characterAnimation"]["durationSeconds"], 4);
|
||||
assert!(parsed.get("screenColorHex").is_none());
|
||||
assert!(parsed.get("mattingProvider").is_none());
|
||||
assert!(parsed.get("mattingModel").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exported_openapi_json_contains_external_editor_routes_and_security() {
|
||||
let parsed: Value = serde_json::from_str(OPENAPI_JSON).expect("openapi json should parse");
|
||||
@@ -1025,5 +1051,25 @@ mod tests {
|
||||
parsed["components"]["securitySchemes"]["ExternalApiKey"]["scheme"],
|
||||
"bearer"
|
||||
);
|
||||
for schema_name in [
|
||||
"EditorProjectResource",
|
||||
"EditorAsset",
|
||||
"EditorImageGenerationResponse",
|
||||
"EditorIconSpritesheetGenerationResponse",
|
||||
"EditorVideoGenerationResponse",
|
||||
"EditorAudioGenerationResponse",
|
||||
] {
|
||||
let schema = &parsed["components"]["schemas"][schema_name];
|
||||
assert!(
|
||||
schema["properties"].get("provider").is_none(),
|
||||
"{schema_name} 不应向普通用户公开 provider"
|
||||
);
|
||||
assert!(
|
||||
schema["required"]
|
||||
.as_array()
|
||||
.is_some_and(|required| !required.contains(&json!("provider"))),
|
||||
"{schema_name} 不应要求 provider"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ use spacetime_client::{
|
||||
ExternalGenerationJobSummaryRecord, SpacetimeClientError,
|
||||
};
|
||||
|
||||
use crate::editor_generation_queue::{
|
||||
EDITOR_BACKGROUND_REMOVAL_JOB_KIND, EDITOR_CHARACTER_ANIMATION_GENERATION_JOB_KIND,
|
||||
};
|
||||
use crate::{
|
||||
api_response::json_success_body, auth::AuthenticatedAccessToken, http_error::AppError,
|
||||
request_context::RequestContext, state::AppState,
|
||||
@@ -180,7 +183,10 @@ fn map_external_generation_job_status(
|
||||
ExternalGenerationJobStatus::Failed,
|
||||
"生成失败。",
|
||||
0,
|
||||
job.last_error_message.clone(),
|
||||
user_visible_external_generation_error(
|
||||
job.job_kind.as_str(),
|
||||
job.last_error_message.clone(),
|
||||
),
|
||||
),
|
||||
_ => (ExternalGenerationJobStatus::Queued, "排队中。", 8, None),
|
||||
};
|
||||
@@ -196,6 +202,21 @@ fn map_external_generation_job_status(
|
||||
}
|
||||
}
|
||||
|
||||
/// 外部任务表保存的失败文本同时服务于 worker 诊断和用户通知。抠图及角色动作透明化任务的
|
||||
/// 原始失败文本可能包含内部 provider、分割模型或实现细节,因此只能在普通用户读取边界
|
||||
/// 替换成稳定文案。
|
||||
/// 原始值仍留在任务记录、tracing 和后台审计路径中。
|
||||
fn user_visible_external_generation_error(job_kind: &str, error: Option<String>) -> Option<String> {
|
||||
if job_kind == EDITOR_BACKGROUND_REMOVAL_JOB_KIND && error.is_some() {
|
||||
return Some("去除背景失败,请稍后重试。".to_string());
|
||||
}
|
||||
if job_kind == EDITOR_CHARACTER_ANIMATION_GENERATION_JOB_KIND && error.is_some() {
|
||||
return Some("角色动作生成失败,请稍后重试。".to_string());
|
||||
}
|
||||
|
||||
error
|
||||
}
|
||||
|
||||
fn map_external_generation_job_status_detail(
|
||||
job: ExternalGenerationJobSummaryRecord,
|
||||
) -> ExternalGenerationJobStatusDetailRecord {
|
||||
@@ -392,4 +413,70 @@ mod tests {
|
||||
let generating = map_external_generation_job_status(job);
|
||||
assert_eq!(generating.phase_detail, "正在生成。");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn background_removal_failure_hides_internal_processing_details_from_owner() {
|
||||
let job = ExternalGenerationJobSummaryRecord {
|
||||
job_id: "task-matting-failed".to_string(),
|
||||
job_kind: EDITOR_BACKGROUND_REMOVAL_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(
|
||||
"BgFilter birefnet provider 返回失败,model=segment-common-image".to_string(),
|
||||
),
|
||||
created_at: "2026-07-31T08:00:00Z".to_string(),
|
||||
started_at: Some("2026-07-31T08:00:01Z".to_string()),
|
||||
completed_at: Some("2026-07-31T08:00:10Z".to_string()),
|
||||
updated_at: "2026-07-31T08:00:10Z".to_string(),
|
||||
updated_at_micros: 1_000,
|
||||
price_mud_points: 0,
|
||||
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("bgfilter"));
|
||||
assert!(!lower.contains("birefnet"));
|
||||
assert!(!lower.contains("segment-common-image"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn character_animation_failure_hides_internal_matting_details_from_owner() {
|
||||
let message = user_visible_external_generation_error(
|
||||
EDITOR_CHARACTER_ANIMATION_GENERATION_JOB_KIND,
|
||||
Some("解析 BgFilter 输入动作帧失败,seg_model=birefnet,provider=BgFilter".to_string()),
|
||||
);
|
||||
|
||||
assert_eq!(message.as_deref(), Some("角色动作生成失败,请稍后重试。"));
|
||||
let lower = message
|
||||
.expect("失败任务应返回稳定文案")
|
||||
.to_ascii_lowercase();
|
||||
assert!(!lower.contains("bgfilter"));
|
||||
assert!(!lower.contains("birefnet"));
|
||||
assert!(!lower.contains("provider"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unrelated_generation_failure_keeps_its_user_visible_message() {
|
||||
let message = user_visible_external_generation_error(
|
||||
"editor_image_generation",
|
||||
Some("生成服务暂时不可用。".to_string()),
|
||||
);
|
||||
|
||||
assert_eq!(message.as_deref(), Some("生成服务暂时不可用。"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1237,6 +1237,24 @@ fn compact_editor_generation_result(mut result: Value) -> Value {
|
||||
let Some(object) = result.as_object_mut() else {
|
||||
return result;
|
||||
};
|
||||
// 紧凑结果会回传给普通用户的 Agent 工具调用卡片,不能把供应商或内部后处理实现
|
||||
// 当作可见生成信息下发。正常的用户可见模型仍然保留,以便卡片恢复原有展示。
|
||||
object.remove("provider");
|
||||
if object
|
||||
.get("model")
|
||||
.and_then(Value::as_str)
|
||||
.is_some_and(is_editor_internal_processing_model)
|
||||
{
|
||||
object.remove("model");
|
||||
}
|
||||
if let Some(generation_inputs) = object
|
||||
.get_mut("generationInputs")
|
||||
.and_then(Value::as_object_mut)
|
||||
{
|
||||
for field in ["screenColorHex", "mattingProvider", "mattingModel"] {
|
||||
generation_inputs.remove(field);
|
||||
}
|
||||
}
|
||||
object.remove("project");
|
||||
object.remove("asset");
|
||||
object.remove("spritesheetAsset");
|
||||
@@ -1271,6 +1289,18 @@ fn compact_editor_generation_result(mut result: Value) -> Value {
|
||||
result
|
||||
}
|
||||
|
||||
fn is_editor_internal_processing_model(model: &str) -> bool {
|
||||
matches!(
|
||||
model.trim().to_ascii_lowercase().as_str(),
|
||||
"anime-seg"
|
||||
| "bgfilter complex"
|
||||
| "birefnet"
|
||||
| "connected-components"
|
||||
| "screen-color-keying"
|
||||
| "segment-common-image"
|
||||
)
|
||||
}
|
||||
|
||||
fn extract_editor_generation_warning_fields(
|
||||
warning: Option<&Value>,
|
||||
is_slice_warning: bool,
|
||||
@@ -1757,6 +1787,13 @@ mod tests {
|
||||
"actualPrompt": null,
|
||||
"model": "gpt-image-2",
|
||||
"provider": "VectorEngine",
|
||||
"generationInputs": {
|
||||
"fields": { "prompt": "castle" },
|
||||
"characterAnimation": { "frameCount": 8 },
|
||||
"screenColorHex": "#CFEFFF",
|
||||
"mattingProvider": "BgFilter",
|
||||
"mattingModel": "birefnet",
|
||||
},
|
||||
"taskId": "provider-task-1",
|
||||
"resource": {
|
||||
"resourceId": "resource-1",
|
||||
@@ -1794,9 +1831,45 @@ mod tests {
|
||||
.get("project")
|
||||
.is_none()
|
||||
);
|
||||
assert_eq!(
|
||||
payload["editor-agent-tool-call-result"]["model"],
|
||||
json!("gpt-image-2")
|
||||
);
|
||||
assert!(
|
||||
payload["editor-agent-tool-call-result"]
|
||||
.get("provider")
|
||||
.is_none()
|
||||
);
|
||||
assert_eq!(
|
||||
payload["editor-agent-tool-call-result"]["generationInputs"],
|
||||
json!({
|
||||
"fields": { "prompt": "castle" },
|
||||
"characterAnimation": { "frameCount": 8 },
|
||||
})
|
||||
);
|
||||
assert!(!payload.to_string().contains("data:image"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compact_editor_generation_result_removes_internal_models_case_insensitively() {
|
||||
for model in [
|
||||
"anime-seg",
|
||||
" BgFilter Complex ",
|
||||
"BIREFNET",
|
||||
"connected-components",
|
||||
"screen-color-keying",
|
||||
"segment-common-image",
|
||||
] {
|
||||
let compact = compact_editor_generation_result(json!({
|
||||
"model": model,
|
||||
"provider": "internal-provider",
|
||||
}));
|
||||
|
||||
assert!(compact.get("model").is_none(), "model={model}");
|
||||
assert!(compact.get("provider").is_none(), "model={model}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_agent_spritesheet_result_keeps_all_persisted_slices() {
|
||||
let mut job = external_generation_job_record_fixture(Some("lease-1"));
|
||||
|
||||
@@ -338,7 +338,6 @@ pub(crate) async fn generate_editor_sound_effect_for_owner(
|
||||
prompt: normalized.prompt.clone(),
|
||||
actual_prompt: Some(normalized.prompt),
|
||||
model: normalized.model,
|
||||
provider: generated.provider,
|
||||
task_id: generated.task_id,
|
||||
price_mud_points: normalized.price_mud_points,
|
||||
audio_kind: "sound-effect".to_string(),
|
||||
@@ -562,7 +561,6 @@ pub(crate) async fn generate_editor_background_music_for_owner(
|
||||
prompt: normalized.gpt_description_prompt.clone(),
|
||||
actual_prompt: Some(normalized.gpt_description_prompt),
|
||||
model: platform_audio::SUNO_DEFAULT_MODEL.to_string(),
|
||||
provider: generated.provider,
|
||||
task_id: generated.task_id,
|
||||
price_mud_points: normalized.price_mud_points,
|
||||
audio_kind: "background-music".to_string(),
|
||||
|
||||
@@ -424,7 +424,6 @@ pub struct EditorVideoGenerateResponse {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub actual_prompt: Option<String>,
|
||||
pub model: String,
|
||||
pub provider: String,
|
||||
pub task_id: String,
|
||||
pub duration_seconds: u32,
|
||||
pub resolution: String,
|
||||
@@ -503,7 +502,6 @@ pub struct EditorIconSpritesheetGenerateResponse {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub actual_prompt: Option<String>,
|
||||
pub model: String,
|
||||
pub provider: String,
|
||||
pub task_id: String,
|
||||
pub price_mud_points: u32,
|
||||
}
|
||||
@@ -560,7 +558,6 @@ pub struct EditorAudioGenerateResponse {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub actual_prompt: Option<String>,
|
||||
pub model: String,
|
||||
pub provider: String,
|
||||
pub task_id: String,
|
||||
pub price_mud_points: u32,
|
||||
pub audio_kind: String,
|
||||
@@ -1260,7 +1257,6 @@ mod tests {
|
||||
prompt: "让角色向镜头挥手".to_string(),
|
||||
actual_prompt: Some("让角色向镜头挥手".to_string()),
|
||||
model: "kling3.0-omni".to_string(),
|
||||
provider: "VectorEngine".to_string(),
|
||||
task_id: "task-1".to_string(),
|
||||
duration_seconds: 5,
|
||||
resolution: "480p".to_string(),
|
||||
@@ -1287,6 +1283,7 @@ mod tests {
|
||||
assert_eq!(payload["assetObjectId"], json!("assetobj_video_1"));
|
||||
assert_eq!(payload["sourceType"], json!("generated"));
|
||||
assert_eq!(payload["durationSeconds"], json!(5));
|
||||
assert!(payload.get("provider").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1323,7 +1320,6 @@ mod tests {
|
||||
prompt: "图标 prompt".to_string(),
|
||||
actual_prompt: Some("图标 prompt".to_string()),
|
||||
model: "gemini-3.1-flash-image-preview".to_string(),
|
||||
provider: "VectorEngine".to_string(),
|
||||
task_id: "task-icons".to_string(),
|
||||
price_mud_points: 12,
|
||||
})
|
||||
@@ -1331,6 +1327,7 @@ mod tests {
|
||||
|
||||
assert_eq!(response_payload["priceMudPoints"], json!(12));
|
||||
assert_eq!(response_payload["iconImageSrcs"], json!([]));
|
||||
assert!(response_payload.get("provider").is_none());
|
||||
assert_eq!(
|
||||
response_payload["sliceWarning"],
|
||||
json!({
|
||||
@@ -1410,7 +1407,6 @@ mod tests {
|
||||
prompt: "金币掉落叮当声".to_string(),
|
||||
actual_prompt: Some("金币掉落叮当声".to_string()),
|
||||
model: "audio1.0".to_string(),
|
||||
provider: "VectorEngine".to_string(),
|
||||
task_id: "sound-task-1".to_string(),
|
||||
price_mud_points: 10,
|
||||
audio_kind: "sound-effect".to_string(),
|
||||
@@ -1439,6 +1435,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(response_payload["assetObjectId"], json!("assetobj_audio_1"));
|
||||
assert_eq!(response_payload["audioKind"], json!("sound-effect"));
|
||||
assert!(response_payload.get("provider").is_none());
|
||||
assert_eq!(
|
||||
response_payload["asset"]["assetKind"],
|
||||
json!("sound-effect")
|
||||
|
||||
Reference in New Issue
Block a user