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:
@@ -5,11 +5,11 @@ use crate::mapper::{
|
||||
map_jump_hop_works_procedure_result,
|
||||
};
|
||||
use shared_contracts::jump_hop::{
|
||||
JumpHopActionRequest, JumpHopActionResponse, JumpHopActionType, JumpHopCharacterAsset, JumpHopDifficulty,
|
||||
JumpHopDraftResponse, JumpHopGalleryResponse, JumpHopGenerationStatus, JumpHopJumpRequest,
|
||||
JumpHopRestartRunRequest, JumpHopRuntimeRunSnapshotResponse, JumpHopSessionSnapshotResponse,
|
||||
JumpHopStartRunRequest, JumpHopStylePreset, JumpHopTileAsset, JumpHopTileType,
|
||||
JumpHopWorkProfileResponse,
|
||||
JumpHopActionRequest, JumpHopActionResponse, JumpHopActionType, JumpHopCharacterAsset,
|
||||
JumpHopDifficulty, JumpHopDraftResponse, JumpHopGalleryResponse, JumpHopGenerationStatus,
|
||||
JumpHopJumpRequest, JumpHopRestartRunRequest, JumpHopRuntimeRunSnapshotResponse,
|
||||
JumpHopSessionSnapshotResponse, JumpHopStartRunRequest, JumpHopStylePreset, JumpHopTileAsset,
|
||||
JumpHopTileType, JumpHopWorkProfileResponse,
|
||||
};
|
||||
use shared_kernel::build_prefixed_uuid_id;
|
||||
|
||||
@@ -21,10 +21,9 @@ impl SpacetimeClient {
|
||||
&self,
|
||||
session: JumpHopSessionSnapshotResponse,
|
||||
) -> Result<JumpHopSessionSnapshotResponse, SpacetimeClientError> {
|
||||
let draft = session
|
||||
.draft
|
||||
.clone()
|
||||
.ok_or_else(|| SpacetimeClientError::validation_failed("jump-hop session 缺少 draft"))?;
|
||||
let draft = session.draft.clone().ok_or_else(|| {
|
||||
SpacetimeClientError::validation_failed("jump-hop session 缺少 draft")
|
||||
})?;
|
||||
let theme_tags_json = Some(json_string(&draft.theme_tags)?);
|
||||
let config_json = Some(build_config_json(&draft)?);
|
||||
let work_title = draft.work_title.clone();
|
||||
@@ -164,15 +163,14 @@ impl SpacetimeClient {
|
||||
procedure_input: JumpHopWorkUpdateInput,
|
||||
) -> Result<JumpHopWorkProfileResponse, SpacetimeClientError> {
|
||||
self.call_after_connect("update_jump_hop_work", move |connection, sender| {
|
||||
connection.procedures().update_jump_hop_work_then(
|
||||
procedure_input,
|
||||
move |_, result| {
|
||||
connection
|
||||
.procedures()
|
||||
.update_jump_hop_work_then(procedure_input, move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_jump_hop_work_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
},
|
||||
);
|
||||
});
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -212,15 +210,14 @@ impl SpacetimeClient {
|
||||
};
|
||||
|
||||
self.call_after_connect("list_jump_hop_works", move |connection, sender| {
|
||||
connection.procedures().list_jump_hop_works_then(
|
||||
procedure_input,
|
||||
move |_, result| {
|
||||
connection
|
||||
.procedures()
|
||||
.list_jump_hop_works_then(procedure_input, move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_jump_hop_works_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
},
|
||||
);
|
||||
});
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -229,7 +226,8 @@ impl SpacetimeClient {
|
||||
&self,
|
||||
profile_id: String,
|
||||
) -> Result<JumpHopWorkProfileResponse, SpacetimeClientError> {
|
||||
self.get_jump_hop_work_profile(profile_id, String::new()).await
|
||||
self.get_jump_hop_work_profile(profile_id, String::new())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn start_jump_hop_run(
|
||||
@@ -253,15 +251,14 @@ impl SpacetimeClient {
|
||||
procedure_input: JumpHopRunStartInput,
|
||||
) -> Result<JumpHopRuntimeRunSnapshotResponse, SpacetimeClientError> {
|
||||
self.call_after_connect("start_jump_hop_run", move |connection, sender| {
|
||||
connection.procedures().start_jump_hop_run_then(
|
||||
procedure_input,
|
||||
move |_, result| {
|
||||
connection
|
||||
.procedures()
|
||||
.start_jump_hop_run_then(procedure_input, move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_jump_hop_run_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
},
|
||||
);
|
||||
});
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -277,15 +274,14 @@ impl SpacetimeClient {
|
||||
};
|
||||
|
||||
self.call_after_connect("get_jump_hop_run", move |connection, sender| {
|
||||
connection.procedures().get_jump_hop_run_then(
|
||||
procedure_input,
|
||||
move |_, result| {
|
||||
connection
|
||||
.procedures()
|
||||
.get_jump_hop_run_then(procedure_input, move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_jump_hop_run_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
},
|
||||
);
|
||||
});
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -305,15 +301,14 @@ impl SpacetimeClient {
|
||||
};
|
||||
|
||||
self.call_after_connect("jump_hop_jump", move |connection, sender| {
|
||||
connection.procedures().jump_hop_jump_then(
|
||||
procedure_input,
|
||||
move |_, result| {
|
||||
connection
|
||||
.procedures()
|
||||
.jump_hop_jump_then(procedure_input, move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_jump_hop_run_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
},
|
||||
);
|
||||
});
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -333,15 +328,14 @@ impl SpacetimeClient {
|
||||
};
|
||||
|
||||
self.call_after_connect("restart_jump_hop_run", move |connection, sender| {
|
||||
connection.procedures().restart_jump_hop_run_then(
|
||||
procedure_input,
|
||||
move |_, result| {
|
||||
connection
|
||||
.procedures()
|
||||
.restart_jump_hop_run_then(procedure_input, move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_jump_hop_run_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
},
|
||||
);
|
||||
});
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -430,16 +424,16 @@ fn build_jump_hop_action_plan(
|
||||
JumpHopAssetRefresh::Preserve,
|
||||
now_micros,
|
||||
)?),
|
||||
JumpHopActionType::RegenerateCharacter => JumpHopActionProcedure::Compile(
|
||||
build_compile_input(
|
||||
JumpHopActionType::RegenerateCharacter => {
|
||||
JumpHopActionProcedure::Compile(build_compile_input(
|
||||
current,
|
||||
owner_user_id,
|
||||
&profile_id,
|
||||
&mut draft,
|
||||
JumpHopAssetRefresh::Character,
|
||||
now_micros,
|
||||
)?,
|
||||
),
|
||||
)?)
|
||||
}
|
||||
JumpHopActionType::RegenerateTiles => JumpHopActionProcedure::Compile(build_compile_input(
|
||||
current,
|
||||
owner_user_id,
|
||||
@@ -472,7 +466,11 @@ fn merge_action_into_draft(
|
||||
scope,
|
||||
JumpHopDraftMergeScope::CompileDraft | JumpHopDraftMergeScope::UpdateWorkMeta
|
||||
) {
|
||||
if let Some(value) = payload.work_title.as_ref().filter(|value| !value.trim().is_empty()) {
|
||||
if let Some(value) = payload
|
||||
.work_title
|
||||
.as_ref()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
{
|
||||
draft.work_title = value.trim().to_string();
|
||||
}
|
||||
if let Some(value) = payload.work_description.as_ref() {
|
||||
@@ -523,7 +521,9 @@ fn merge_action_into_draft(
|
||||
draft.tile_prompt = value.trim().to_string();
|
||||
}
|
||||
if draft.work_title.trim().is_empty() {
|
||||
return Err(SpacetimeClientError::validation_failed("jump-hop work_title 不能为空"));
|
||||
return Err(SpacetimeClientError::validation_failed(
|
||||
"jump-hop work_title 不能为空",
|
||||
));
|
||||
}
|
||||
Ok(draft)
|
||||
}
|
||||
@@ -762,7 +762,9 @@ fn ensure_tile_assets(
|
||||
.map(|(index, tile_type)| JumpHopTileAsset {
|
||||
tile_type,
|
||||
image_src: format!("/generated-jump-hop-assets/{profile_id}/tiles/{index}{suffix}.png"),
|
||||
image_object_key: format!("generated-jump-hop-assets/{profile_id}/tiles/{index}{suffix}.png"),
|
||||
image_object_key: format!(
|
||||
"generated-jump-hop-assets/{profile_id}/tiles/{index}{suffix}.png"
|
||||
),
|
||||
asset_object_id: format!("{profile_id}-tile-{index}{suffix}-object"),
|
||||
source_atlas_cell: format!("cell-{index}{suffix}"),
|
||||
visual_width: 256,
|
||||
@@ -788,7 +790,9 @@ fn resolve_cover_composite(
|
||||
{
|
||||
return Some(value.to_string());
|
||||
}
|
||||
let suffix = asset_revision_suffix((!matches!(refresh, JumpHopAssetRefresh::Preserve)).then_some(now_micros));
|
||||
let suffix = asset_revision_suffix(
|
||||
(!matches!(refresh, JumpHopAssetRefresh::Preserve)).then_some(now_micros),
|
||||
);
|
||||
Some(format!(
|
||||
"/generated-jump-hop-assets/{profile_id}/cover-composite{suffix}.png"
|
||||
))
|
||||
@@ -850,9 +854,27 @@ mod tests {
|
||||
assert_eq!(input.session_id, SESSION_ID);
|
||||
assert_eq!(input.owner_user_id, OWNER_USER_ID);
|
||||
assert_eq!(input.generation_status.as_deref(), Some("ready"));
|
||||
assert!(input.character_asset_json.as_deref().unwrap_or("").contains("-character"));
|
||||
assert!(input.tile_atlas_asset_json.as_deref().unwrap_or("").contains("-tile-atlas"));
|
||||
assert!(input.tile_assets_json.as_deref().unwrap_or("").contains("tile-0-object"));
|
||||
assert!(
|
||||
input
|
||||
.character_asset_json
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.contains("-character")
|
||||
);
|
||||
assert!(
|
||||
input
|
||||
.tile_atlas_asset_json
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.contains("-tile-atlas")
|
||||
);
|
||||
assert!(
|
||||
input
|
||||
.tile_assets_json
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.contains("tile-0-object")
|
||||
);
|
||||
assert_eq!(draft.generation_status, JumpHopGenerationStatus::Ready);
|
||||
}
|
||||
|
||||
@@ -869,10 +891,34 @@ mod tests {
|
||||
let JumpHopActionProcedure::Compile(input) = plan else {
|
||||
panic!("regenerate-character should call compile_jump_hop_draft");
|
||||
};
|
||||
assert!(!input.character_asset_json.as_deref().unwrap_or("").contains("old-character"));
|
||||
assert!(input.character_asset_json.as_deref().unwrap_or("").contains(&NOW_MICROS.to_string()));
|
||||
assert!(input.tile_atlas_asset_json.as_deref().unwrap_or("").contains("old-tile-atlas"));
|
||||
assert!(input.tile_assets_json.as_deref().unwrap_or("").contains("old-normal-tile"));
|
||||
assert!(
|
||||
!input
|
||||
.character_asset_json
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.contains("old-character")
|
||||
);
|
||||
assert!(
|
||||
input
|
||||
.character_asset_json
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.contains(&NOW_MICROS.to_string())
|
||||
);
|
||||
assert!(
|
||||
input
|
||||
.tile_atlas_asset_json
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.contains("old-tile-atlas")
|
||||
);
|
||||
assert!(
|
||||
input
|
||||
.tile_assets_json
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.contains("old-normal-tile")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -888,11 +934,41 @@ mod tests {
|
||||
let JumpHopActionProcedure::Compile(input) = plan else {
|
||||
panic!("regenerate-tiles should call compile_jump_hop_draft");
|
||||
};
|
||||
assert!(input.character_asset_json.as_deref().unwrap_or("").contains("old-character"));
|
||||
assert!(!input.tile_atlas_asset_json.as_deref().unwrap_or("").contains("old-tile-atlas"));
|
||||
assert!(!input.tile_assets_json.as_deref().unwrap_or("").contains("old-normal-tile"));
|
||||
assert!(input.tile_atlas_asset_json.as_deref().unwrap_or("").contains(&NOW_MICROS.to_string()));
|
||||
assert!(input.tile_assets_json.as_deref().unwrap_or("").contains(&NOW_MICROS.to_string()));
|
||||
assert!(
|
||||
input
|
||||
.character_asset_json
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.contains("old-character")
|
||||
);
|
||||
assert!(
|
||||
!input
|
||||
.tile_atlas_asset_json
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.contains("old-tile-atlas")
|
||||
);
|
||||
assert!(
|
||||
!input
|
||||
.tile_assets_json
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.contains("old-normal-tile")
|
||||
);
|
||||
assert!(
|
||||
input
|
||||
.tile_atlas_asset_json
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.contains(&NOW_MICROS.to_string())
|
||||
);
|
||||
assert!(
|
||||
input
|
||||
.tile_assets_json
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.contains(&NOW_MICROS.to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -934,8 +1010,20 @@ mod tests {
|
||||
};
|
||||
assert_eq!(input.difficulty.as_deref(), Some("challenge"));
|
||||
assert!(input.style_preset.is_none());
|
||||
assert_eq!(draft.character_asset.as_ref().map(|asset| asset.asset_id.as_str()), Some("old-character"));
|
||||
assert_eq!(draft.tile_assets.first().map(|asset| asset.asset_object_id.as_str()), Some("old-normal-tile-object"));
|
||||
assert_eq!(
|
||||
draft
|
||||
.character_asset
|
||||
.as_ref()
|
||||
.map(|asset| asset.asset_id.as_str()),
|
||||
Some("old-character")
|
||||
);
|
||||
assert_eq!(
|
||||
draft
|
||||
.tile_assets
|
||||
.first()
|
||||
.map(|asset| asset.asset_object_id.as_str()),
|
||||
Some("old-normal-tile-object")
|
||||
);
|
||||
}
|
||||
|
||||
fn action(action_type: JumpHopActionType) -> JumpHopActionRequest {
|
||||
|
||||
Reference in New Issue
Block a user