Adds/updates documentation, assets and implementation for Match3D and puzzle image generation workflows. Key changes: decision logs and pitfalls updated to prefer VectorEngine Gemini for Match3D material sheets and to require edits (multipart) for 1:1 container reference images; guidance added for when to use APIMart vs VectorEngine. .env.example clarified APIMart/Responses config. Many new public assets and PPT visuals added. Code changes across frontend and backend: updated shared contracts, server-rs match3d/puzzle/image-generation handlers, VectorEngine/OpenAI image generation clients, and multiple React components/tests to handle UI/background/container image signing, edits workflow, and puzzle UI background resolution. Added src/services/puzzle-runtime/puzzleUiBackgroundSource.ts and related test updates. Includes notes about multipart HTTP/1.1 requirement and test/verification commands in docs.
284 lines
8.8 KiB
Rust
284 lines
8.8 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::creation_audio::CreationAudioAsset;
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct PutMatch3DWorkRequest {
|
|
pub game_name: String,
|
|
#[serde(default)]
|
|
pub theme_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,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct GenerateMatch3DWorkTagsRequest {
|
|
pub game_name: String,
|
|
pub theme_text: String,
|
|
#[serde(default)]
|
|
pub summary: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct GenerateMatch3DWorkTagsResponse {
|
|
pub tags: Vec<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct PutMatch3DAudioAssetsRequest {
|
|
pub generated_item_assets: Vec<Match3DGeneratedItemAssetResponse>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct PersistMatch3DGeneratedModelRequest {
|
|
pub item_id: String,
|
|
pub item_name: String,
|
|
pub source_url: String,
|
|
#[serde(default)]
|
|
pub file_name: Option<String>,
|
|
#[serde(default)]
|
|
pub task_uuid: Option<String>,
|
|
#[serde(default)]
|
|
pub subscription_key: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct PersistMatch3DGeneratedModelResponse {
|
|
pub asset: Match3DGeneratedItemAssetResponse,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct GenerateMatch3DCoverImageRequest {
|
|
pub prompt: String,
|
|
#[serde(default)]
|
|
pub uploaded_image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub reference_image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub reference_image_srcs: Vec<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct GenerateMatch3DCoverImageResponse {
|
|
pub item: Match3DWorkProfileResponse,
|
|
pub cover_image_src: String,
|
|
pub cover_image_object_key: String,
|
|
pub prompt: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct GenerateMatch3DBackgroundImageRequest {
|
|
pub prompt: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct GenerateMatch3DBackgroundImageResponse {
|
|
pub item: Match3DWorkProfileResponse,
|
|
pub background_image_src: String,
|
|
pub background_image_object_key: String,
|
|
pub generated_background_asset: Match3DGeneratedBackgroundAssetResponse,
|
|
pub prompt: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct GenerateMatch3DContainerImageRequest {
|
|
pub prompt: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct GenerateMatch3DContainerImageResponse {
|
|
pub item: Match3DWorkProfileResponse,
|
|
pub container_image_src: String,
|
|
pub container_image_object_key: String,
|
|
pub generated_background_asset: Match3DGeneratedBackgroundAssetResponse,
|
|
pub prompt: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct GenerateMatch3DItemAssetsRequest {
|
|
pub item_names: Vec<String>,
|
|
#[serde(default)]
|
|
pub mode: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct GenerateMatch3DItemAssetsResponse {
|
|
pub item: Match3DWorkProfileResponse,
|
|
pub generated_item_assets: Vec<Match3DGeneratedItemAssetResponse>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DWorkSummaryResponse {
|
|
pub work_id: String,
|
|
pub profile_id: String,
|
|
pub owner_user_id: String,
|
|
#[serde(default)]
|
|
pub source_session_id: Option<String>,
|
|
pub game_name: String,
|
|
pub theme_text: 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 publication_status: String,
|
|
pub play_count: u32,
|
|
pub updated_at: String,
|
|
#[serde(default)]
|
|
pub published_at: Option<String>,
|
|
pub publish_ready: bool,
|
|
#[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)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DGeneratedBackgroundAssetResponse {
|
|
pub prompt: String,
|
|
#[serde(default)]
|
|
pub image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub 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)]
|
|
#[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)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DGeneratedItemAssetResponse {
|
|
pub item_id: String,
|
|
pub item_name: 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)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DWorkProfileResponse {
|
|
#[serde(flatten)]
|
|
pub summary: Match3DWorkSummaryResponse,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DWorksResponse {
|
|
pub items: Vec<Match3DWorkSummaryResponse>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DWorkDetailResponse {
|
|
pub item: Match3DWorkProfileResponse,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Match3DWorkMutationResponse {
|
|
pub item: Match3DWorkProfileResponse,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use serde_json::json;
|
|
|
|
#[test]
|
|
fn match3d_work_request_uses_camel_case() {
|
|
let payload = serde_json::to_value(PutMatch3DWorkRequest {
|
|
game_name: "水果抓大鹅".to_string(),
|
|
theme_text: Some("水果".to_string()),
|
|
summary: "水果主题".to_string(),
|
|
tags: vec!["水果".to_string()],
|
|
cover_image_src: None,
|
|
reference_image_src: None,
|
|
clear_count: 4,
|
|
difficulty: 5,
|
|
})
|
|
.expect("payload should serialize");
|
|
|
|
assert_eq!(payload["gameName"], json!("水果抓大鹅"));
|
|
assert_eq!(payload["clearCount"], json!(4));
|
|
}
|
|
}
|