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).
306 lines
9.9 KiB
Rust
306 lines
9.9 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::creation_audio::CreationAudioAsset;
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct CreateMatch3DAgentSessionRequest {
|
|
#[serde(default)]
|
|
pub seed_text: Option<String>,
|
|
#[serde(default)]
|
|
pub theme_text: Option<String>,
|
|
#[serde(default)]
|
|
pub reference_image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub clear_count: Option<u32>,
|
|
#[serde(default)]
|
|
pub difficulty: Option<u32>,
|
|
#[serde(default)]
|
|
pub asset_style_id: Option<String>,
|
|
#[serde(default)]
|
|
pub asset_style_label: Option<String>,
|
|
#[serde(default)]
|
|
pub asset_style_prompt: Option<String>,
|
|
#[serde(default)]
|
|
pub generate_click_sound: Option<bool>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct SendMatch3DAgentMessageRequest {
|
|
pub client_message_id: String,
|
|
pub text: String,
|
|
#[serde(default)]
|
|
pub quick_fill_requested: Option<bool>,
|
|
#[serde(default)]
|
|
pub reference_image_src: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ExecuteMatch3DAgentActionRequest {
|
|
pub action: String,
|
|
#[serde(default)]
|
|
pub game_name: Option<String>,
|
|
#[serde(default)]
|
|
pub summary: Option<String>,
|
|
#[serde(default)]
|
|
pub tags: Option<Vec<String>>,
|
|
#[serde(default)]
|
|
pub cover_image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub clear_count: Option<u32>,
|
|
#[serde(default)]
|
|
pub difficulty: Option<u32>,
|
|
#[serde(default)]
|
|
pub generate_click_sound: Option<bool>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DCreatorConfigResponse {
|
|
pub theme_text: String,
|
|
#[serde(default)]
|
|
pub reference_image_src: Option<String>,
|
|
pub clear_count: u32,
|
|
pub difficulty: u32,
|
|
#[serde(default)]
|
|
pub asset_style_id: Option<String>,
|
|
#[serde(default)]
|
|
pub asset_style_label: Option<String>,
|
|
#[serde(default)]
|
|
pub asset_style_prompt: Option<String>,
|
|
#[serde(default)]
|
|
pub generate_click_sound: bool,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DResultDraftResponse {
|
|
pub profile_id: String,
|
|
pub game_name: String,
|
|
pub theme_text: String,
|
|
#[serde(default)]
|
|
pub summary_text: Option<String>,
|
|
pub summary: String,
|
|
pub tags: Vec<String>,
|
|
#[serde(default)]
|
|
pub cover_image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub reference_image_src: Option<String>,
|
|
pub clear_count: u32,
|
|
pub difficulty: u32,
|
|
pub total_item_count: u32,
|
|
pub publish_ready: bool,
|
|
pub blockers: Vec<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub background_prompt: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub background_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub background_image_object_key: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub generated_background_asset: Option<Match3DGeneratedBackgroundAssetResponse>,
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
pub generated_item_assets: Vec<Match3DGeneratedItemAssetResponse>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DGeneratedBackgroundAssetResponse {
|
|
pub prompt: String,
|
|
#[serde(default)]
|
|
pub level_scene_prompt: Option<String>,
|
|
#[serde(default)]
|
|
pub level_scene_image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub level_scene_image_object_key: Option<String>,
|
|
#[serde(default)]
|
|
pub image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub image_object_key: Option<String>,
|
|
#[serde(default)]
|
|
pub ui_spritesheet_prompt: Option<String>,
|
|
#[serde(default)]
|
|
pub ui_spritesheet_image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub ui_spritesheet_image_object_key: Option<String>,
|
|
#[serde(default)]
|
|
pub item_spritesheet_prompt: Option<String>,
|
|
#[serde(default)]
|
|
pub item_spritesheet_image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub item_spritesheet_image_object_key: Option<String>,
|
|
#[serde(default)]
|
|
pub container_prompt: Option<String>,
|
|
#[serde(default)]
|
|
pub container_image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub container_image_object_key: Option<String>,
|
|
pub status: String,
|
|
#[serde(default)]
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DGeneratedItemImageViewResponse {
|
|
pub view_id: String,
|
|
pub view_index: u32,
|
|
#[serde(default)]
|
|
pub image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub image_object_key: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DGeneratedItemAssetResponse {
|
|
pub item_id: String,
|
|
pub item_name: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub item_size: Option<String>,
|
|
#[serde(default)]
|
|
pub image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub image_object_key: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
pub image_views: Vec<Match3DGeneratedItemImageViewResponse>,
|
|
#[serde(default)]
|
|
pub model_src: Option<String>,
|
|
#[serde(default)]
|
|
pub model_object_key: Option<String>,
|
|
#[serde(default)]
|
|
pub model_file_name: Option<String>,
|
|
#[serde(default)]
|
|
pub task_uuid: Option<String>,
|
|
#[serde(default)]
|
|
pub subscription_key: Option<String>,
|
|
#[serde(default)]
|
|
pub sound_prompt: Option<String>,
|
|
#[serde(default)]
|
|
pub background_music_title: Option<String>,
|
|
#[serde(default)]
|
|
pub background_music_style: Option<String>,
|
|
#[serde(default)]
|
|
pub background_music_prompt: Option<String>,
|
|
#[serde(default)]
|
|
pub background_music: Option<CreationAudioAsset>,
|
|
#[serde(default)]
|
|
pub click_sound: Option<CreationAudioAsset>,
|
|
#[serde(default)]
|
|
pub background_asset: Option<Match3DGeneratedBackgroundAssetResponse>,
|
|
pub status: String,
|
|
#[serde(default)]
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DAnchorItemResponse {
|
|
pub key: String,
|
|
pub label: String,
|
|
pub value: String,
|
|
pub status: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DAnchorPackResponse {
|
|
pub theme: Match3DAnchorItemResponse,
|
|
pub clear_count: Match3DAnchorItemResponse,
|
|
pub difficulty: Match3DAnchorItemResponse,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DAgentMessageResponse {
|
|
pub id: String,
|
|
pub role: String,
|
|
pub kind: String,
|
|
pub text: String,
|
|
pub created_at: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DAgentSessionSnapshotResponse {
|
|
pub session_id: String,
|
|
pub current_turn: u32,
|
|
pub progress_percent: u32,
|
|
pub stage: String,
|
|
pub anchor_pack: Match3DAnchorPackResponse,
|
|
#[serde(default)]
|
|
pub config: Option<Match3DCreatorConfigResponse>,
|
|
#[serde(default)]
|
|
pub draft: Option<Match3DResultDraftResponse>,
|
|
pub messages: Vec<Match3DAgentMessageResponse>,
|
|
#[serde(default)]
|
|
pub last_assistant_reply: Option<String>,
|
|
#[serde(default)]
|
|
pub published_profile_id: Option<String>,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DAgentSessionResponse {
|
|
pub session: Match3DAgentSessionSnapshotResponse,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DAgentActionResponse {
|
|
pub session: Match3DAgentSessionSnapshotResponse,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use serde_json::json;
|
|
|
|
#[test]
|
|
fn create_match3d_session_request_uses_camel_case() {
|
|
let payload = serde_json::to_value(CreateMatch3DAgentSessionRequest {
|
|
seed_text: Some("水果消除".to_string()),
|
|
theme_text: Some("水果".to_string()),
|
|
reference_image_src: Some("data:image/png;base64,abc".to_string()),
|
|
clear_count: Some(4),
|
|
difficulty: Some(3),
|
|
asset_style_id: Some("flat-icon".to_string()),
|
|
asset_style_label: Some("扁平图标".to_string()),
|
|
asset_style_prompt: Some("干净扁平 2D 游戏道具图标风格".to_string()),
|
|
generate_click_sound: Some(true),
|
|
})
|
|
.expect("payload should serialize");
|
|
|
|
assert_eq!(payload["seedText"], json!("水果消除"));
|
|
assert_eq!(payload["themeText"], json!("水果"));
|
|
assert_eq!(
|
|
payload["referenceImageSrc"],
|
|
json!("data:image/png;base64,abc")
|
|
);
|
|
assert_eq!(payload["clearCount"], json!(4));
|
|
assert_eq!(payload["assetStyleId"], json!("flat-icon"));
|
|
assert_eq!(payload["generateClickSound"], json!(true));
|
|
}
|
|
|
|
#[test]
|
|
fn execute_match3d_action_request_serializes_click_sound_toggle() {
|
|
let payload = serde_json::to_value(ExecuteMatch3DAgentActionRequest {
|
|
action: "match3d_compile_draft".to_string(),
|
|
game_name: None,
|
|
summary: None,
|
|
tags: None,
|
|
cover_image_src: None,
|
|
clear_count: None,
|
|
difficulty: None,
|
|
generate_click_sound: Some(true),
|
|
})
|
|
.expect("payload should serialize");
|
|
|
|
assert_eq!(payload["action"], json!("match3d_compile_draft"));
|
|
assert_eq!(payload["generateClickSound"], json!(true));
|
|
}
|
|
}
|