Switch to VectorEngine gpt-image-2 and edits
Replace uses of the legacy `gpt-image-2-all` model with `gpt-image-2` and standardize image workflows: no-reference generation uses POST /v1/images/generations, any-reference flows use POST /v1/images/edits with multipart `image` parts. Update SKILLs, generation scripts, decision logs, and docs to reflect the contract change and edits-vs-generations guidance. Apply corresponding changes across backend (api-server match3d/puzzle modules, openai image adapter, mappers, telemetry, spacetime client/module), frontend components and services (Match3D, Puzzle, CreativeImageInputPanel, runtime shells), and add new spritesheet/parser files and tests. Also add media/logo.png. These changes align repository code and documentation with the VectorEngine image API contract and update generation/upload handling (green-screen -> alpha processing, spritesheet handling, and related tests).
This commit is contained in:
@@ -184,6 +184,12 @@ pub(crate) fn parse_puzzle_level_records_from_module_json(
|
||||
ui_background_prompt: level.ui_background_prompt,
|
||||
ui_background_image_src: level.ui_background_image_src,
|
||||
ui_background_image_object_key: level.ui_background_image_object_key,
|
||||
level_scene_image_src: level.level_scene_image_src,
|
||||
level_scene_image_object_key: level.level_scene_image_object_key,
|
||||
ui_spritesheet_image_src: level.ui_spritesheet_image_src,
|
||||
ui_spritesheet_image_object_key: level.ui_spritesheet_image_object_key,
|
||||
level_background_image_src: level.level_background_image_src,
|
||||
level_background_image_object_key: level.level_background_image_object_key,
|
||||
background_music: level
|
||||
.background_music
|
||||
.map(map_puzzle_audio_asset_domain_record),
|
||||
@@ -357,6 +363,12 @@ pub(crate) fn serialize_puzzle_levels_response(
|
||||
"ui_background_prompt": level.ui_background_prompt,
|
||||
"ui_background_image_src": level.ui_background_image_src,
|
||||
"ui_background_image_object_key": level.ui_background_image_object_key,
|
||||
"level_scene_image_src": level.level_scene_image_src,
|
||||
"level_scene_image_object_key": level.level_scene_image_object_key,
|
||||
"ui_spritesheet_image_src": level.ui_spritesheet_image_src,
|
||||
"ui_spritesheet_image_object_key": level.ui_spritesheet_image_object_key,
|
||||
"level_background_image_src": level.level_background_image_src,
|
||||
"level_background_image_object_key": level.level_background_image_object_key,
|
||||
"background_music": puzzle_audio_asset_response_module_json(&level.background_music),
|
||||
"candidates": level
|
||||
.candidates
|
||||
@@ -411,6 +423,12 @@ pub(crate) fn normalize_puzzle_levels_json_for_module(
|
||||
"ui_background_prompt": level.ui_background_prompt,
|
||||
"ui_background_image_src": level.ui_background_image_src,
|
||||
"ui_background_image_object_key": level.ui_background_image_object_key,
|
||||
"level_scene_image_src": level.level_scene_image_src,
|
||||
"level_scene_image_object_key": level.level_scene_image_object_key,
|
||||
"ui_spritesheet_image_src": level.ui_spritesheet_image_src,
|
||||
"ui_spritesheet_image_object_key": level.ui_spritesheet_image_object_key,
|
||||
"level_background_image_src": level.level_background_image_src,
|
||||
"level_background_image_object_key": level.level_background_image_object_key,
|
||||
"background_music": puzzle_audio_asset_response_module_json(&level.background_music),
|
||||
"candidates": level
|
||||
.candidates
|
||||
@@ -918,6 +936,15 @@ pub(crate) fn build_puzzle_levels_with_primary_update(
|
||||
levels[index].ui_background_image_src = target_level.ui_background_image_src.clone();
|
||||
levels[index].ui_background_image_object_key =
|
||||
target_level.ui_background_image_object_key.clone();
|
||||
levels[index].level_scene_image_src = target_level.level_scene_image_src.clone();
|
||||
levels[index].level_scene_image_object_key =
|
||||
target_level.level_scene_image_object_key.clone();
|
||||
levels[index].ui_spritesheet_image_src = target_level.ui_spritesheet_image_src.clone();
|
||||
levels[index].ui_spritesheet_image_object_key =
|
||||
target_level.ui_spritesheet_image_object_key.clone();
|
||||
levels[index].level_background_image_src = target_level.level_background_image_src.clone();
|
||||
levels[index].level_background_image_object_key =
|
||||
target_level.level_background_image_object_key.clone();
|
||||
if let Some(picture_reference) = picture_reference
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
@@ -1033,6 +1060,29 @@ pub(crate) fn attach_puzzle_level_ui_background(
|
||||
levels[index].ui_background_image_object_key = Some(generated.object_key);
|
||||
}
|
||||
|
||||
pub(crate) fn attach_puzzle_level_asset_bundle(
|
||||
levels: &mut [PuzzleDraftLevelRecord],
|
||||
level_id: &str,
|
||||
generated: GeneratedPuzzleLevelAssetBundle,
|
||||
) {
|
||||
let Some(index) = levels
|
||||
.iter()
|
||||
.position(|level| level.level_id == level_id)
|
||||
.or_else(|| (!levels.is_empty()).then_some(0))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let level = &mut levels[index];
|
||||
level.level_scene_image_src = Some(generated.level_scene.image_src);
|
||||
level.level_scene_image_object_key = Some(generated.level_scene.object_key);
|
||||
level.ui_spritesheet_image_src = Some(generated.ui_spritesheet.image_src);
|
||||
level.ui_spritesheet_image_object_key = Some(generated.ui_spritesheet.object_key);
|
||||
level.level_background_image_src = Some(generated.level_background.image_src.clone());
|
||||
level.level_background_image_object_key = Some(generated.level_background.object_key.clone());
|
||||
level.ui_background_image_src = Some(generated.level_background.image_src);
|
||||
level.ui_background_image_object_key = Some(generated.level_background.object_key);
|
||||
}
|
||||
|
||||
pub(crate) async fn generate_puzzle_initial_ui_background_required(
|
||||
state: &PuzzleApiState,
|
||||
owner_user_id: &str,
|
||||
@@ -1052,26 +1102,56 @@ pub(crate) async fn generate_puzzle_initial_ui_background_required(
|
||||
Ok((prompt, generated))
|
||||
}
|
||||
|
||||
pub(crate) async fn generate_puzzle_level_asset_bundle_required(
|
||||
state: &PuzzleApiState,
|
||||
owner_user_id: &str,
|
||||
session_id: &str,
|
||||
target_level: &PuzzleDraftLevelRecord,
|
||||
puzzle_image: &PuzzleDownloadedImage,
|
||||
) -> Result<GeneratedPuzzleLevelAssetBundle, AppError> {
|
||||
generate_puzzle_level_asset_bundle(
|
||||
state,
|
||||
owner_user_id,
|
||||
session_id,
|
||||
target_level.level_name.as_str(),
|
||||
puzzle_image,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_puzzle_initial_level_assets_ready(
|
||||
level: &PuzzleDraftLevelRecord,
|
||||
) -> Result<(), AppError> {
|
||||
let has_ui_background = level
|
||||
.ui_background_image_src
|
||||
let has_level_background = level
|
||||
.level_background_image_src
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.is_some_and(|value| !value.is_empty())
|
||||
|| level
|
||||
.ui_background_image_object_key
|
||||
.level_background_image_object_key
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.is_some_and(|value| !value.is_empty());
|
||||
if has_ui_background {
|
||||
let has_ui_spritesheet = level
|
||||
.ui_spritesheet_image_src
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.is_some_and(|value| !value.is_empty())
|
||||
|| level
|
||||
.ui_spritesheet_image_object_key
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.is_some_and(|value| !value.is_empty());
|
||||
if has_level_background && has_ui_spritesheet {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut missing = Vec::new();
|
||||
if !has_ui_background {
|
||||
missing.push("UI背景图");
|
||||
if !has_level_background {
|
||||
missing.push("关卡背景图");
|
||||
}
|
||||
if !has_ui_spritesheet {
|
||||
missing.push("UI spritesheet");
|
||||
}
|
||||
|
||||
Err(
|
||||
@@ -1125,8 +1205,8 @@ pub(crate) async fn compile_puzzle_draft_with_initial_cover(
|
||||
target_level.level_name = generated_naming.level_name.clone();
|
||||
target_level.ui_background_prompt = generated_naming.ui_background_prompt.clone();
|
||||
let mut generated_metadata = generated_naming;
|
||||
// 点击生成草稿时一次性完成首图生成、UI 背景生成与正式图选定,前端只展示进度,不再承担业务编排。
|
||||
let candidates_future = generate_puzzle_image_candidates(
|
||||
// 点击生成草稿时一次性完成拼图主图和运行态资产包,前端只展示进度,不再承担业务编排。
|
||||
let mut candidates = generate_puzzle_image_candidates(
|
||||
state,
|
||||
owner_user_id.as_str(),
|
||||
&compiled_session.session_id,
|
||||
@@ -1137,18 +1217,8 @@ pub(crate) async fn compile_puzzle_draft_with_initial_cover(
|
||||
image_model,
|
||||
1,
|
||||
target_level.candidates.len(),
|
||||
);
|
||||
let ui_background_future = generate_puzzle_initial_ui_background_required(
|
||||
state,
|
||||
owner_user_id.as_str(),
|
||||
compiled_session.session_id.as_str(),
|
||||
&draft,
|
||||
&target_level,
|
||||
);
|
||||
// 中文注释:命名稳定后并行发起首关图与 UI 背景,避免两次外部生图串行等待。
|
||||
let (candidates_result, ui_background_result) =
|
||||
tokio::join!(candidates_future, ui_background_future);
|
||||
let mut candidates = candidates_result?;
|
||||
)
|
||||
.await?;
|
||||
if let Some(first_candidate) = candidates.first()
|
||||
&& let Some(refined_naming) = generate_puzzle_first_level_name_from_image(
|
||||
state,
|
||||
@@ -1184,19 +1254,25 @@ pub(crate) async fn compile_puzzle_draft_with_initial_cover(
|
||||
"message": "拼图候选图生成结果为空",
|
||||
}))
|
||||
})?;
|
||||
// 中文注释:拼图草稿音频生成临时关闭,首版生成只补首图与 UI 背景。
|
||||
let (ui_prompt, ui_background) = ui_background_result?;
|
||||
attach_puzzle_level_ui_background(
|
||||
&mut updated_levels,
|
||||
target_level.level_id.as_str(),
|
||||
ui_prompt,
|
||||
ui_background,
|
||||
);
|
||||
// 中文注释:拼图草稿音频生成临时关闭,首版生成只补首图、关卡背景和 UI spritesheet。
|
||||
if let Some(selected_candidate) = candidates
|
||||
.iter()
|
||||
.find(|candidate| candidate.record.selected)
|
||||
.or_else(|| candidates.first())
|
||||
{
|
||||
let asset_bundle = generate_puzzle_level_asset_bundle_required(
|
||||
state,
|
||||
owner_user_id.as_str(),
|
||||
compiled_session.session_id.as_str(),
|
||||
&target_level,
|
||||
&selected_candidate.downloaded_image,
|
||||
)
|
||||
.await?;
|
||||
attach_puzzle_level_asset_bundle(
|
||||
&mut updated_levels,
|
||||
target_level.level_id.as_str(),
|
||||
asset_bundle,
|
||||
);
|
||||
attach_selected_puzzle_candidate_to_levels(
|
||||
&mut updated_levels,
|
||||
target_level.level_id.as_str(),
|
||||
@@ -1455,7 +1531,7 @@ pub(crate) async fn compile_puzzle_draft_with_uploaded_cover(
|
||||
let generated_level_name = target_level.level_name.clone();
|
||||
let mut updated_levels =
|
||||
build_puzzle_levels_with_primary_update(&draft, &target_level, reference_image_src);
|
||||
let persist_upload_future = persist_puzzle_generated_asset(
|
||||
let persisted_upload = persist_puzzle_generated_asset(
|
||||
state,
|
||||
owner_user_id.as_str(),
|
||||
&compiled_session.session_id,
|
||||
@@ -1464,24 +1540,20 @@ pub(crate) async fn compile_puzzle_draft_with_uploaded_cover(
|
||||
"uploaded-direct",
|
||||
uploaded_downloaded_image.clone(),
|
||||
current_utc_micros(),
|
||||
);
|
||||
let ui_background_future = generate_puzzle_initial_ui_background_required(
|
||||
)
|
||||
.await?;
|
||||
let asset_bundle = generate_puzzle_level_asset_bundle_required(
|
||||
state,
|
||||
owner_user_id.as_str(),
|
||||
compiled_session.session_id.as_str(),
|
||||
&draft,
|
||||
&target_level,
|
||||
);
|
||||
// 中文注释:直用上传图时并行完成上传图持久化与 UI 背景生成;音频生成入口临时关闭。
|
||||
let (persisted_upload_result, ui_background_result) =
|
||||
tokio::join!(persist_upload_future, ui_background_future);
|
||||
let persisted_upload = persisted_upload_result?;
|
||||
let (ui_prompt, ui_background) = ui_background_result?;
|
||||
attach_puzzle_level_ui_background(
|
||||
&uploaded_downloaded_image,
|
||||
)
|
||||
.await?;
|
||||
attach_puzzle_level_asset_bundle(
|
||||
&mut updated_levels,
|
||||
target_level.level_id.as_str(),
|
||||
ui_prompt,
|
||||
ui_background,
|
||||
asset_bundle,
|
||||
);
|
||||
attach_selected_puzzle_candidate_to_levels(
|
||||
&mut updated_levels,
|
||||
|
||||
Reference in New Issue
Block a user