背景色决策挪到预扣泥点之后,避免余额不足/失败时白发决策
生图 / 动作生视频 / 图标 spritesheet / UI 提取四条链路此前都在扣费前跑 gpt-5-mini 背景色决策,导致用户余额不足或生成注定失败时仍白发一次决策,平台白付 token。 改为:预扣前只做颜色无关的算价 / 校验 / settings(动画用默认色占位算价,与队列 路径一致),把决策及颜色相关的 prompt / 合成 / generation_inputs 搬进 execute_billable_asset_operation_with_cost 的闭包,闭包把决策结果带出供后续抠图 与落库使用。语义:余额不足则决策不跑(平台零成本);决策失败则闭包返 Err 走失败 退款(用户不损失泥点)。四条 _for_owner 函数同时覆盖 worker / inline / agent / external-API 所有入口。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -648,43 +648,19 @@ pub(crate) async fn generate_editor_character_animation_for_owner(
|
||||
)
|
||||
.await
|
||||
.map_err(|error| character_animation_error_response(&request_context, error))?;
|
||||
// 背景色决策:显式传入 → 视觉 LLM 按源图主体配色自动决策 → 默认色。
|
||||
let screen_background_decision = resolve_editor_screen_background_color(
|
||||
state.llm_client(),
|
||||
state.creative_agent_gpt5_client(),
|
||||
EditorScreenBackgroundDecisionInput {
|
||||
kind: EditorScreenBackgroundDecisionKind::CharacterAnimation,
|
||||
screen_color: payload.screen_color.clone(),
|
||||
prompt: payload.prompt_text.clone(),
|
||||
icon_descriptions: Vec::new(),
|
||||
reference_count: 1,
|
||||
source_image_data_url: Some(source_data_url.clone()),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(|error| character_animation_error_response(&request_context, error))?;
|
||||
// 把实际选定的背景色落进资产记录,供后续以该资产为参考图时反查旧背景色。
|
||||
let generation_inputs = apply_editor_screen_background_decision_to_generation_inputs(
|
||||
generation_inputs,
|
||||
Some(&screen_background_decision),
|
||||
);
|
||||
let normalized = normalize_editor_character_animation_request_with_pricing(
|
||||
payload,
|
||||
// 定价 / 上游设置先于扣费,且都不依赖背景色:用默认色占位归一化(与队列路径一致),
|
||||
// 把背景色决策挪到预扣泥点之后,避免余额不足 / 生成注定失败时仍白发一次 gpt-5-mini 决策。
|
||||
let priced = normalize_editor_character_animation_request_with_pricing(
|
||||
payload.clone(),
|
||||
&pricing,
|
||||
screen_background_decision.color,
|
||||
crate::editor_green_screen::default_editor_screen_background_color(),
|
||||
)
|
||||
.map_err(|error| character_animation_error_response(&request_context, error))?;
|
||||
let settings = require_editor_character_animation_settings(&state, &normalized)
|
||||
let settings = require_editor_character_animation_settings(&state, &priced)
|
||||
.map_err(|error| character_animation_error_response(&request_context, error))?;
|
||||
// 透明源图先合成到选定背景色:让视频背景确定性地等于抠图键色,而不是赌模型服从提示词。
|
||||
let source_data_url = composite_source_image_onto_screen_color(
|
||||
source_data_url.as_str(),
|
||||
screen_background_decision.color,
|
||||
)
|
||||
.unwrap_or(source_data_url);
|
||||
let extraction_settings = resolve_backend_frame_extraction_settings(&state);
|
||||
let http_client = build_upstream_http_client(settings.ark.request_timeout_ms)
|
||||
.map_err(|error| character_animation_error_response(&request_context, error))?;
|
||||
let extraction_settings = resolve_backend_frame_extraction_settings(&state);
|
||||
let task_id = generate_ai_task_id(current_utc_micros());
|
||||
// 抠图供应商失败审计上下文:逐帧阿里云抠图失败即使兜底成功,也要进 OTLP + tracking_event。
|
||||
let matting_audit = crate::external_api_audit::ExternalApiAuditContext {
|
||||
@@ -698,8 +674,40 @@ pub(crate) async fn generate_editor_character_animation_for_owner(
|
||||
owner_user_id.as_str(),
|
||||
EDITOR_CHARACTER_ANIMATION_ASSET_KIND,
|
||||
task_id.as_str(),
|
||||
u64::from(normalized.price_mud_points),
|
||||
u64::from(priced.price_mud_points),
|
||||
async {
|
||||
// 背景色决策:预扣泥点之后才发起(显式传入 → 视觉 LLM 按源图主体配色自动决策 → 默认色)。
|
||||
// 决策失败会让本 future 返回 Err,走 execute_billable 的失败退款,用户不损失泥点。
|
||||
let screen_background_decision = resolve_editor_screen_background_color(
|
||||
state.llm_client(),
|
||||
state.creative_agent_gpt5_client(),
|
||||
EditorScreenBackgroundDecisionInput {
|
||||
kind: EditorScreenBackgroundDecisionKind::CharacterAnimation,
|
||||
screen_color: payload.screen_color.clone(),
|
||||
prompt: payload.prompt_text.clone(),
|
||||
icon_descriptions: Vec::new(),
|
||||
reference_count: 1,
|
||||
source_image_data_url: Some(source_data_url.clone()),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
// 把实际选定的背景色落进资产记录,供后续以该资产为参考图时反查旧背景色。
|
||||
let generation_inputs = apply_editor_screen_background_decision_to_generation_inputs(
|
||||
generation_inputs,
|
||||
Some(&screen_background_decision),
|
||||
);
|
||||
// 用决策色重新归一化以拿到最终提示词(价格 / 校验已在预扣前用占位色完成)。
|
||||
let normalized = normalize_editor_character_animation_request_with_pricing(
|
||||
payload,
|
||||
&pricing,
|
||||
screen_background_decision.color,
|
||||
)?;
|
||||
// 透明源图先合成到选定背景色:让视频背景确定性地等于抠图键色,而不是赌模型服从提示词。
|
||||
let source_data_url = composite_source_image_onto_screen_color(
|
||||
source_data_url.as_str(),
|
||||
screen_background_decision.color,
|
||||
)
|
||||
.unwrap_or(source_data_url);
|
||||
let generated = request_editor_character_animation_preview(
|
||||
&state,
|
||||
&http_client,
|
||||
@@ -723,12 +731,12 @@ pub(crate) async fn generate_editor_character_animation_for_owner(
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok::<_, AppError>((generated, frames))
|
||||
Ok::<_, AppError>((generated, frames, normalized, generation_inputs))
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let (generated, frames) =
|
||||
let (generated, frames, normalized, generation_inputs) =
|
||||
result.map_err(|error| character_animation_error_response(&request_context, error))?;
|
||||
let completed_project = if let Some(completion) = canvas_completion.as_ref() {
|
||||
let frame_payload_json = serde_json::to_value(&frames).map_err(|error| {
|
||||
|
||||
@@ -1357,28 +1357,10 @@ pub(crate) async fn generate_editor_image_for_owner(
|
||||
|
||||
let normalized_kind = payload.kind.as_deref().map(str::trim);
|
||||
let is_character_generation = matches!(normalized_kind, Some("character"));
|
||||
let screen_background_decision = if is_character_generation {
|
||||
Some(
|
||||
resolve_editor_screen_background_color(
|
||||
state.llm_client(),
|
||||
state.creative_agent_gpt5_client(),
|
||||
EditorScreenBackgroundDecisionInput {
|
||||
kind: EditorScreenBackgroundDecisionKind::Character,
|
||||
screen_color: payload.screen_color.clone(),
|
||||
prompt: role_setting.clone(),
|
||||
icon_descriptions: Vec::new(),
|
||||
reference_count: payload.reference_image_srcs.as_ref().map_or(0, Vec::len),
|
||||
source_image_data_url: None,
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let screen_color = screen_background_decision
|
||||
.as_ref()
|
||||
.map(|decision| decision.color);
|
||||
// 背景色决策挪到预扣泥点之后(见下方 execute_billable 闭包),避免余额不足 / 生成注定失败时
|
||||
// 仍白发一次 gpt-5-mini 决策。这里先固化决策需要、但随后会被 payload 消费掉的输入。
|
||||
let requested_screen_color = payload.screen_color.clone();
|
||||
let reference_count = payload.reference_image_srcs.as_ref().map_or(0, Vec::len);
|
||||
let seg_model = if is_character_generation {
|
||||
Some(parse_editor_bgfilter_seg_model(
|
||||
payload.seg_model.as_deref(),
|
||||
@@ -1405,24 +1387,6 @@ pub(crate) async fn generate_editor_image_for_owner(
|
||||
has_dimension_options,
|
||||
&generation_options,
|
||||
);
|
||||
let submitted_prompt = if is_character_generation {
|
||||
build_editor_character_image_prompt(
|
||||
role_setting.as_str(),
|
||||
screen_color.expect("character generation should have screen color"),
|
||||
)
|
||||
} else if is_ui_design_generation {
|
||||
build_editor_ui_design_prompt(
|
||||
role_setting.as_str(),
|
||||
payload
|
||||
.reference_image_srcs
|
||||
.as_ref()
|
||||
.is_some_and(|references| {
|
||||
references.iter().any(|source| !source.trim().is_empty())
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
role_setting.clone()
|
||||
};
|
||||
let failure_context = match normalized_kind {
|
||||
Some("character") => "图片画布生成角色形象",
|
||||
Some("spec") => "图片画布生成规范",
|
||||
@@ -1444,6 +1408,9 @@ pub(crate) async fn generate_editor_image_for_owner(
|
||||
.filter(|source| !source.is_empty())
|
||||
.take(reference_limit)
|
||||
.collect::<Vec<_>>();
|
||||
// 决策移入闭包后 payload.reference_image_srcs 已被 reference_sources 消费,
|
||||
// 这里从过滤后的 reference_sources 预先固化 ui-design 的「是否带参考图」布尔。
|
||||
let ui_has_references = !reference_sources.is_empty();
|
||||
let settings = require_openai_image_settings(state)?.with_external_api_audit_context(
|
||||
request_context,
|
||||
caller.audit_subject_user_id.clone(),
|
||||
@@ -1485,7 +1452,41 @@ pub(crate) async fn generate_editor_image_for_owner(
|
||||
Some(generation_options.image_size),
|
||||
);
|
||||
let generate_operation = async {
|
||||
if generation_options.model == EDITOR_IMAGE_MODEL_NANOBANANA2 {
|
||||
// 背景色决策:预扣泥点之后才发起,仅角色生图需要(决策失败→future 返回 Err→失败退款,
|
||||
// 用户不损失泥点,平台也只在用户确有余额时才付这次 gpt-5-mini 决策)。
|
||||
let screen_background_decision = if is_character_generation {
|
||||
Some(
|
||||
resolve_editor_screen_background_color(
|
||||
state.llm_client(),
|
||||
state.creative_agent_gpt5_client(),
|
||||
EditorScreenBackgroundDecisionInput {
|
||||
kind: EditorScreenBackgroundDecisionKind::Character,
|
||||
screen_color: requested_screen_color.clone(),
|
||||
prompt: role_setting.clone(),
|
||||
icon_descriptions: Vec::new(),
|
||||
reference_count,
|
||||
source_image_data_url: None,
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let screen_color = screen_background_decision
|
||||
.as_ref()
|
||||
.map(|decision| decision.color);
|
||||
let submitted_prompt = if is_character_generation {
|
||||
build_editor_character_image_prompt(
|
||||
role_setting.as_str(),
|
||||
screen_color.expect("character generation should have screen color"),
|
||||
)
|
||||
} else if is_ui_design_generation {
|
||||
build_editor_ui_design_prompt(role_setting.as_str(), ui_has_references)
|
||||
} else {
|
||||
role_setting.clone()
|
||||
};
|
||||
let generated = if generation_options.model == EDITOR_IMAGE_MODEL_NANOBANANA2 {
|
||||
create_openai_nanobanana_generate_content(
|
||||
&http_client,
|
||||
&settings,
|
||||
@@ -1497,7 +1498,7 @@ pub(crate) async fn generate_editor_image_for_owner(
|
||||
reference_images.as_slice(),
|
||||
failure_context,
|
||||
)
|
||||
.await
|
||||
.await?
|
||||
} else if reference_images.is_empty() {
|
||||
create_openai_image_generation_with_model(
|
||||
&http_client,
|
||||
@@ -1510,7 +1511,7 @@ pub(crate) async fn generate_editor_image_for_owner(
|
||||
&[],
|
||||
failure_context,
|
||||
)
|
||||
.await
|
||||
.await?
|
||||
} else {
|
||||
create_openai_image_edit_with_references_and_model(
|
||||
&http_client,
|
||||
@@ -1523,18 +1524,23 @@ pub(crate) async fn generate_editor_image_for_owner(
|
||||
reference_images.as_slice(),
|
||||
failure_context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
.await?
|
||||
};
|
||||
Ok::<_, AppError>((generated, submitted_prompt, screen_background_decision))
|
||||
};
|
||||
let generated = execute_billable_asset_operation_with_cost(
|
||||
state,
|
||||
caller.owner_user_id.as_str(),
|
||||
editor_image_generation_billing_asset_kind(normalized_kind),
|
||||
request_context.request_id(),
|
||||
u64::from(configured_price_mud_points),
|
||||
generate_operation,
|
||||
)
|
||||
.await?;
|
||||
let (generated, submitted_prompt, screen_background_decision) =
|
||||
execute_billable_asset_operation_with_cost(
|
||||
state,
|
||||
caller.owner_user_id.as_str(),
|
||||
editor_image_generation_billing_asset_kind(normalized_kind),
|
||||
request_context.request_id(),
|
||||
u64::from(configured_price_mud_points),
|
||||
generate_operation,
|
||||
)
|
||||
.await?;
|
||||
let screen_color = screen_background_decision
|
||||
.as_ref()
|
||||
.map(|decision| decision.color);
|
||||
let mut image = generated.images.into_iter().next().ok_or_else(|| {
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": "vector-engine",
|
||||
@@ -2896,20 +2902,10 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
|
||||
payload: EditorIconSpritesheetGenerationRequest,
|
||||
) -> Result<Json<Value>, AppError> {
|
||||
let icon_descriptions = normalize_icon_descriptions(payload.icon_descriptions)?;
|
||||
let screen_background_decision = resolve_editor_screen_background_color(
|
||||
state.llm_client(),
|
||||
state.creative_agent_gpt5_client(),
|
||||
EditorScreenBackgroundDecisionInput {
|
||||
kind: EditorScreenBackgroundDecisionKind::IconSpritesheet,
|
||||
screen_color: payload.screen_color.clone(),
|
||||
prompt: icon_descriptions.join("\n"),
|
||||
icon_descriptions: icon_descriptions.clone(),
|
||||
reference_count: 1 + payload.reference_image_srcs.as_ref().map_or(0, Vec::len),
|
||||
source_image_data_url: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let screen_color = screen_background_decision.color;
|
||||
// 背景色决策挪到预扣泥点之后(见下方 execute_billable 闭包),避免余额不足 / 生成注定失败时
|
||||
// 仍白发一次 gpt-5-mini 决策。这里先固化决策需要的输入。
|
||||
let requested_screen_color = payload.screen_color.clone();
|
||||
let reference_count = 1 + payload.reference_image_srcs.as_ref().map_or(0, Vec::len);
|
||||
let seg_model = parse_editor_bgfilter_seg_model(payload.seg_model.as_deref())?;
|
||||
let mut reference_images =
|
||||
Vec::with_capacity(1 + payload.reference_image_srcs.as_ref().map_or(0, Vec::len));
|
||||
@@ -2954,7 +2950,6 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
|
||||
Some(generation_options.image_size),
|
||||
)?;
|
||||
let size = generation_options.size.as_str();
|
||||
let prompt = build_editor_icon_spritesheet_prompt(&icon_descriptions, screen_color);
|
||||
|
||||
let settings = require_openai_image_settings(state)?.with_external_api_audit_context(
|
||||
request_context,
|
||||
@@ -2965,43 +2960,62 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
|
||||
.or_else(|| payload.project_id.clone()),
|
||||
);
|
||||
let http_client = build_openai_image_http_client(&settings)?;
|
||||
let generated = execute_billable_asset_operation_with_cost(
|
||||
state,
|
||||
caller.owner_user_id.as_str(),
|
||||
"editor_icon_spritesheet",
|
||||
request_context.request_id(),
|
||||
u64::from(expected_price_mud_points),
|
||||
async {
|
||||
if generation_options.model == EDITOR_IMAGE_MODEL_NANOBANANA2 {
|
||||
create_openai_nanobanana_generate_content(
|
||||
&http_client,
|
||||
&settings,
|
||||
generation_options.model,
|
||||
prompt.as_str(),
|
||||
None,
|
||||
generation_options.aspect_ratio,
|
||||
generation_options.provider_image_size,
|
||||
reference_images.as_slice(),
|
||||
"图片画布生成图标素材 spritesheet",
|
||||
let (generated, prompt, screen_background_decision) =
|
||||
execute_billable_asset_operation_with_cost(
|
||||
state,
|
||||
caller.owner_user_id.as_str(),
|
||||
"editor_icon_spritesheet",
|
||||
request_context.request_id(),
|
||||
u64::from(expected_price_mud_points),
|
||||
async {
|
||||
// 背景色决策:预扣泥点之后才发起(决策失败→future 返回 Err→失败退款)。
|
||||
let screen_background_decision = resolve_editor_screen_background_color(
|
||||
state.llm_client(),
|
||||
state.creative_agent_gpt5_client(),
|
||||
EditorScreenBackgroundDecisionInput {
|
||||
kind: EditorScreenBackgroundDecisionKind::IconSpritesheet,
|
||||
screen_color: requested_screen_color.clone(),
|
||||
prompt: icon_descriptions.join("\n"),
|
||||
icon_descriptions: icon_descriptions.clone(),
|
||||
reference_count,
|
||||
source_image_data_url: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
create_openai_image_edit_with_references_and_model(
|
||||
&http_client,
|
||||
&settings,
|
||||
generation_options.model,
|
||||
prompt.as_str(),
|
||||
None,
|
||||
size,
|
||||
1,
|
||||
reference_images.as_slice(),
|
||||
"图片画布生成图标素材 spritesheet",
|
||||
)
|
||||
.await
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
.await?;
|
||||
let screen_color = screen_background_decision.color;
|
||||
let prompt = build_editor_icon_spritesheet_prompt(&icon_descriptions, screen_color);
|
||||
let generated = if generation_options.model == EDITOR_IMAGE_MODEL_NANOBANANA2 {
|
||||
create_openai_nanobanana_generate_content(
|
||||
&http_client,
|
||||
&settings,
|
||||
generation_options.model,
|
||||
prompt.as_str(),
|
||||
None,
|
||||
generation_options.aspect_ratio,
|
||||
generation_options.provider_image_size,
|
||||
reference_images.as_slice(),
|
||||
"图片画布生成图标素材 spritesheet",
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
create_openai_image_edit_with_references_and_model(
|
||||
&http_client,
|
||||
&settings,
|
||||
generation_options.model,
|
||||
prompt.as_str(),
|
||||
None,
|
||||
size,
|
||||
1,
|
||||
reference_images.as_slice(),
|
||||
"图片画布生成图标素材 spritesheet",
|
||||
)
|
||||
.await?
|
||||
};
|
||||
Ok::<_, AppError>((generated, prompt, screen_background_decision))
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let screen_color = screen_background_decision.color;
|
||||
let image = generated.images.into_iter().next().ok_or_else(|| {
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": "vector-engine",
|
||||
@@ -3174,23 +3188,14 @@ pub(crate) async fn extract_editor_ui_design_assets_for_owner(
|
||||
caller: EditorGenerationCaller,
|
||||
payload: EditorUiDesignAssetExtractionRequest,
|
||||
) -> Result<Json<Value>, AppError> {
|
||||
let screen_background_decision = resolve_editor_screen_background_color(
|
||||
state.llm_client(),
|
||||
state.creative_agent_gpt5_client(),
|
||||
EditorScreenBackgroundDecisionInput {
|
||||
kind: EditorScreenBackgroundDecisionKind::UiDesignAssetExtraction,
|
||||
screen_color: payload.screen_color.clone(),
|
||||
prompt: format!(
|
||||
"从 UI 设计图提取被红色框选的素材,输出 spritesheet。目标规格 {} {}。",
|
||||
payload.aspect_ratio, payload.image_size
|
||||
),
|
||||
icon_descriptions: Vec::new(),
|
||||
reference_count: 1 + payload.reference_image_srcs.as_ref().map_or(0, Vec::len),
|
||||
source_image_data_url: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let screen_color = screen_background_decision.color;
|
||||
// 背景色决策挪到预扣泥点之后(见下方 execute_billable 闭包),避免余额不足 / 生成注定失败时
|
||||
// 仍白发一次 gpt-5-mini 决策。这里先固化决策需要的输入。
|
||||
let requested_screen_color = payload.screen_color.clone();
|
||||
let reference_count = 1 + payload.reference_image_srcs.as_ref().map_or(0, Vec::len);
|
||||
let decision_prompt = format!(
|
||||
"从 UI 设计图提取被红色框选的素材,输出 spritesheet。目标规格 {} {}。",
|
||||
payload.aspect_ratio, payload.image_size
|
||||
);
|
||||
let seg_model = parse_editor_bgfilter_seg_model(payload.seg_model.as_deref())?;
|
||||
let reference_image = parse_editor_reference_image(
|
||||
state,
|
||||
@@ -3241,44 +3246,62 @@ pub(crate) async fn extract_editor_ui_design_assets_for_owner(
|
||||
.or_else(|| payload.project_id.clone()),
|
||||
);
|
||||
let http_client = build_openai_image_http_client(&settings)?;
|
||||
let prompt = editor_ui_design_asset_extraction_prompt(screen_color);
|
||||
let generated = execute_billable_asset_operation_with_cost(
|
||||
state,
|
||||
caller.owner_user_id.as_str(),
|
||||
"editor_ui_design_asset_extraction",
|
||||
request_context.request_id(),
|
||||
u64::from(expected_price_mud_points),
|
||||
async {
|
||||
if generation_options.model == EDITOR_IMAGE_MODEL_NANOBANANA2 {
|
||||
create_openai_nanobanana_generate_content(
|
||||
&http_client,
|
||||
&settings,
|
||||
generation_options.model,
|
||||
prompt.as_str(),
|
||||
None,
|
||||
generation_options.aspect_ratio,
|
||||
generation_options.provider_image_size,
|
||||
reference_images.as_slice(),
|
||||
"图片画布提取UI设计图素材 spritesheet",
|
||||
let (generated, prompt, screen_background_decision) =
|
||||
execute_billable_asset_operation_with_cost(
|
||||
state,
|
||||
caller.owner_user_id.as_str(),
|
||||
"editor_ui_design_asset_extraction",
|
||||
request_context.request_id(),
|
||||
u64::from(expected_price_mud_points),
|
||||
async {
|
||||
// 背景色决策:预扣泥点之后才发起(决策失败→future 返回 Err→失败退款)。
|
||||
let screen_background_decision = resolve_editor_screen_background_color(
|
||||
state.llm_client(),
|
||||
state.creative_agent_gpt5_client(),
|
||||
EditorScreenBackgroundDecisionInput {
|
||||
kind: EditorScreenBackgroundDecisionKind::UiDesignAssetExtraction,
|
||||
screen_color: requested_screen_color.clone(),
|
||||
prompt: decision_prompt.clone(),
|
||||
icon_descriptions: Vec::new(),
|
||||
reference_count,
|
||||
source_image_data_url: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
create_openai_image_edit_with_references_and_model(
|
||||
&http_client,
|
||||
&settings,
|
||||
generation_options.model,
|
||||
prompt.as_str(),
|
||||
None,
|
||||
generation_options.size.as_str(),
|
||||
1,
|
||||
reference_images.as_slice(),
|
||||
"图片画布提取UI设计图素材 spritesheet",
|
||||
)
|
||||
.await
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
.await?;
|
||||
let screen_color = screen_background_decision.color;
|
||||
let prompt = editor_ui_design_asset_extraction_prompt(screen_color);
|
||||
let generated = if generation_options.model == EDITOR_IMAGE_MODEL_NANOBANANA2 {
|
||||
create_openai_nanobanana_generate_content(
|
||||
&http_client,
|
||||
&settings,
|
||||
generation_options.model,
|
||||
prompt.as_str(),
|
||||
None,
|
||||
generation_options.aspect_ratio,
|
||||
generation_options.provider_image_size,
|
||||
reference_images.as_slice(),
|
||||
"图片画布提取UI设计图素材 spritesheet",
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
create_openai_image_edit_with_references_and_model(
|
||||
&http_client,
|
||||
&settings,
|
||||
generation_options.model,
|
||||
prompt.as_str(),
|
||||
None,
|
||||
generation_options.size.as_str(),
|
||||
1,
|
||||
reference_images.as_slice(),
|
||||
"图片画布提取UI设计图素材 spritesheet",
|
||||
)
|
||||
.await?
|
||||
};
|
||||
Ok::<_, AppError>((generated, prompt, screen_background_decision))
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let screen_color = screen_background_decision.color;
|
||||
let image = generated.images.into_iter().next().ok_or_else(|| {
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": "vector-engine",
|
||||
|
||||
Reference in New Issue
Block a user