Files
Genarrative/server-rs/crates/shared-contracts/src/match3d_agent.rs
高物 ae014ac881 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).
2026-05-22 03:06:41 +08:00

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));
}
}