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:
2026-05-22 03:06:41 +08:00
parent 321e1ea33a
commit ae014ac881
90 changed files with 7078 additions and 3389 deletions

View File

@@ -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,