合并 origin/master:保留 3D 契约与 provider checkpoint,旧玩法表随主线退役

- server-rs/crates/shared-contracts/src/lib.rs:采用主线的模块裁剪(删掉 cfg(any()) 遗留模块、补上 agc_analytics 与 game_distribution),同时保留本分支的 editor_canvas、model3d 模块与 EDITOR_GENERATION_OPERATION_KINDS 导出
- server-rs/crates/spacetime-module/src/migration.rs:主线删除的 410 行旧玩法表 normalize 段保持删除,保留本分支的 provider_kind / provider_task_id 兼容段与对应用例
- docs/【开发运维】本地开发验证与生产运维-2026-05-15.md:校验清单同时保留 Tripo 3D 生成任务与 editor_background_music_generation / model3d_text_to_model / model3d_image_to_model
- .gitignore:补回本分支新增的 3D 模型文件忽略规则(*.glb / *.gltf 等 12 行),压测数据段随主线一并删除
- 共享记忆:本分支的 3D 决策与踩坑条目保留在主线重排后的 decision-log.md 与 pitfalls.md 中
This commit is contained in:
2026-09-23 19:39:30 +08:00
997 changed files with 56264 additions and 80302 deletions
+46 -1
View File
@@ -10,12 +10,13 @@ use crate::creation_entry_config::{
};
/// 后台 member 可被授予的一级 Tab 权限;账号管理仅 owner 可见,不进入该集合。
pub const ADMIN_TAB_PERMISSIONS: [&str; 18] = [
pub const ADMIN_TAB_PERMISSIONS: [&str; 19] = [
"dashboard",
"overview",
"tables",
"debug",
"tracking",
"agc-tracking",
"gray-release",
"redeem",
"invite",
@@ -1495,3 +1496,47 @@ pub struct AdminAgcModelCatalog {
pub default_model_id: String,
pub models: Vec<AdminAgcModel>,
}
// 客户端埋点明细;时间范围按发生时间,按 (event_time, event_id) 倒序分页。
// received_at 仅用于固定分页快照的入库时间上界。
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AdminAgcTrackingEventListQuery {
pub user_id: Option<String>,
pub project_id: Option<String>,
pub creative_task_id: Option<String>,
pub agent_run_id: Option<String>,
pub event_name: Option<String>,
pub client_version: Option<String>,
pub start_time: Option<String>,
pub end_time: Option<String>,
pub cursor: Option<String>,
pub limit: Option<u32>,
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AdminAgcTrackingEventEntry {
pub event_id: String,
pub schema_version: u32,
pub event_name: String,
pub event_time: String,
pub user_id: String,
pub editor_session_id: String,
pub project_id: Option<String>,
pub creative_task_id: Option<String>,
pub agent_run_id: Option<String>,
pub agent_turn_id: Option<String>,
pub status: Option<String>,
pub error_code: Option<String>,
pub source: String,
pub client_version: String,
pub properties: serde_json::Value,
pub batch_id: String,
pub received_at: String,
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AdminAgcTrackingEventListPayload {
pub entries: Vec<AdminAgcTrackingEventEntry>,
pub next_cursor: Option<String>,
}
@@ -0,0 +1,282 @@
//! 客户端埋点上传合同;字段与本地 schema v1 保持一致。
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
fn required_nullable<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
Option::<T>::deserialize(deserializer)
}
macro_rules! values {
($name:ident { $($variant:ident),+ $(,)? }) => {
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum $name { $($variant),+ }
};
}
values!(Source {
Editor,
Direct,
DesignAgent,
AssetCanvas,
ResourceEditor,
UiEditor,
Manual,
System
});
values!(Status { Success, Failed });
values!(EntrySource {
DirectLaunch,
ProjectAssociation,
AppRestore
});
values!(SessionEndReason {
UserExit,
AppRestart
});
values!(FocusReason {
InitialFocus,
WindowFocus,
Restore,
AccountChange
});
values!(BlurReason {
WindowBlur,
Minimized,
AppExit,
SystemSuspend,
AccountChange
});
values!(CreationSource {
HomeGame,
HomeDesign,
Template,
SelectedDirectory
});
values!(OpenSource {
Create,
Picker,
Recent,
AppRestore,
ProjectAssociation
});
values!(AgentType {
GameAgent,
DesignAgent
});
values!(RunSource {
UserSubmit,
UserContinue,
Clarification,
Approval,
UserRetry
});
values!(RunEndReason {
Finished,
WaitingForUser,
WaitingForApproval,
Failed
});
values!(ErrorCode {
ProviderAuthFailed,
ProviderRateLimited,
ProviderUnavailable,
ProviderTimeout,
ProviderInvalidResponse,
LocalIoFailed,
RuntimeFailed,
RuntimeErrorUnclassified
});
values!(RevisionSource {
Agent,
AssetCanvas,
ResourceEditor,
UiEditor,
ManualEdit,
SystemProjection
});
values!(ChangeKind {
Code,
Asset,
Ui,
DesignDocument,
Mixed
});
values!(PreviewSource {
User,
Agent,
AutoRestore
});
values!(SaveSource {
Manual,
Auto,
Checkpoint
});
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct SessionStart {
pub entry_source: EntrySource,
#[serde(deserialize_with = "required_nullable")]
pub first_project_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct SessionEnd {
pub end_reason: SessionEndReason,
#[serde(deserialize_with = "required_nullable")]
pub session_duration_ms: Option<u64>,
#[serde(deserialize_with = "required_nullable")]
pub last_project_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FocusStart {
pub focus_interval_id: String,
pub focus_reason: FocusReason,
#[serde(deserialize_with = "required_nullable")]
pub active_project_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FocusEnd {
pub focus_interval_id: String,
pub blur_reason: BlurReason,
#[serde(deserialize_with = "required_nullable")]
pub focus_duration_ms: Option<u64>,
#[serde(deserialize_with = "required_nullable")]
pub active_project_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ProjectCreated {
pub creation_source: CreationSource,
#[serde(skip_serializing_if = "Option::is_none")]
pub project_template_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ProjectOpened {
pub open_source: OpenSource,
#[serde(deserialize_with = "required_nullable")]
pub is_first_open: Option<bool>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EmptyProperties {}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RunFinished {
pub agent_type: AgentType,
pub run_source: RunSource,
#[serde(deserialize_with = "required_nullable")]
pub duration_ms: Option<u64>,
pub retry_index: u64,
#[serde(deserialize_with = "required_nullable")]
pub output_change_detected: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub revision_id: Option<String>,
pub end_reason: RunEndReason,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RevisionCreated {
pub revision_id: String,
pub revision_source: RevisionSource,
pub change_kind: ChangeKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub files_changed_count: Option<u64>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct PreviewReady {
pub preview_source: PreviewSource,
pub preview_version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub ready_duration_ms: Option<u64>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ProjectSaved {
pub save_source: SaveSource,
#[serde(skip_serializing_if = "Option::is_none")]
pub revision_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(
tag = "event_name",
content = "properties",
rename_all = "snake_case",
deny_unknown_fields
)]
pub enum EventData {
EditorSessionStart(SessionStart),
EditorSessionEnd(SessionEnd),
EditorFocusStart(FocusStart),
EditorFocusEnd(FocusEnd),
ProjectCreateSuccess(ProjectCreated),
ProjectOpen(ProjectOpened),
CreativeTaskSubmit(EmptyProperties),
AgentRunCompleted(RunFinished),
AgentRunFailed(RunFinished),
ProjectRevisionCreated(RevisionCreated),
PreviewReady(PreviewReady),
ProjectSave(ProjectSaved),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Event {
pub schema_version: u32,
pub event_id: String,
pub event_name: String,
pub event_time: String,
#[serde(deserialize_with = "required_nullable")]
pub user_id: Option<String>,
pub editor_session_id: String,
#[serde(deserialize_with = "required_nullable")]
pub project_id: Option<String>,
#[serde(deserialize_with = "required_nullable")]
pub creative_task_id: Option<String>,
#[serde(deserialize_with = "required_nullable")]
pub agent_run_id: Option<String>,
#[serde(deserialize_with = "required_nullable")]
pub agent_turn_id: Option<String>,
#[serde(deserialize_with = "required_nullable")]
pub status: Option<Status>,
#[serde(deserialize_with = "required_nullable")]
pub error_code: Option<ErrorCode>,
pub source: Source,
pub client_version: String,
pub properties: Value,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AgcAnalyticsBatch {
pub schema_version: u32,
pub batch_id: String,
pub destination_origin: String,
pub user_id: String,
pub events: Vec<Event>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AgcAnalyticsAcknowledgement {
pub acknowledged_batch_ids: Vec<String>,
pub event_count: u32,
}
File diff suppressed because it is too large Load Diff
@@ -1,261 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CreateBigFishSessionRequest {
#[serde(default)]
pub seed_text: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SendBigFishMessageRequest {
pub client_message_id: String,
pub text: String,
#[serde(default)]
pub quick_fill_requested: Option<bool>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ExecuteBigFishActionRequest {
pub action: String,
#[serde(default)]
pub level: Option<u32>,
#[serde(default)]
pub motion_key: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RecordBigFishPlayRequest {
#[serde(default)]
pub elapsed_ms: Option<u64>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SubmitBigFishInputRequest {
pub x: f32,
pub y: f32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishAnchorItemResponse {
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 BigFishAnchorPackResponse {
pub gameplay_promise: BigFishAnchorItemResponse,
pub ecology_visual_theme: BigFishAnchorItemResponse,
pub growth_ladder: BigFishAnchorItemResponse,
pub risk_tempo: BigFishAnchorItemResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishLevelBlueprintResponse {
pub level: u32,
pub name: String,
pub one_line_fantasy: String,
pub text_description: String,
pub silhouette_direction: String,
pub size_ratio: f32,
pub visual_description: String,
pub visual_prompt_seed: String,
pub idle_motion_description: String,
pub move_motion_description: String,
pub motion_prompt_seed: String,
pub merge_source_level: Option<u32>,
pub prey_window: Vec<u32>,
pub threat_window: Vec<u32>,
pub is_final_level: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishBackgroundBlueprintResponse {
pub theme: String,
pub color_mood: String,
pub foreground_hints: String,
pub midground_composition: String,
pub background_depth: String,
pub safe_play_area_hint: String,
pub spawn_edge_hint: String,
pub background_prompt_seed: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishRuntimeParamsResponse {
pub level_count: u32,
pub merge_count_per_upgrade: u32,
pub spawn_target_count: u32,
pub leader_move_speed: f32,
pub follower_catch_up_speed: f32,
pub offscreen_cull_seconds: f32,
pub prey_spawn_delta_levels: Vec<u32>,
pub threat_spawn_delta_levels: Vec<u32>,
pub win_level: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishGameDraftResponse {
pub title: String,
pub subtitle: String,
pub core_fun: String,
pub ecology_theme: String,
pub levels: Vec<BigFishLevelBlueprintResponse>,
pub background: BigFishBackgroundBlueprintResponse,
pub runtime_params: BigFishRuntimeParamsResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishAgentMessageResponse {
pub id: String,
pub role: String,
pub kind: String,
pub text: String,
pub created_at: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishAssetSlotResponse {
pub slot_id: String,
pub asset_kind: String,
pub level: Option<u32>,
pub motion_key: Option<String>,
pub status: String,
pub asset_url: Option<String>,
pub prompt_snapshot: String,
pub updated_at: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishAssetCoverageResponse {
pub level_main_image_ready_count: u32,
pub level_motion_ready_count: u32,
pub background_ready: bool,
pub required_level_count: u32,
pub publish_ready: bool,
pub blockers: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishSessionSnapshotResponse {
pub session_id: String,
pub current_turn: u32,
pub progress_percent: u32,
pub stage: String,
pub anchor_pack: BigFishAnchorPackResponse,
pub draft: Option<BigFishGameDraftResponse>,
pub asset_slots: Vec<BigFishAssetSlotResponse>,
pub asset_coverage: BigFishAssetCoverageResponse,
pub messages: Vec<BigFishAgentMessageResponse>,
pub last_assistant_reply: Option<String>,
pub publish_ready: bool,
pub updated_at: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishSessionResponse {
pub session: BigFishSessionSnapshotResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishActionResponse {
pub session: BigFishSessionSnapshotResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishVector2Response {
pub x: f32,
pub y: f32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishRuntimeEntityResponse {
pub entity_id: String,
pub level: u32,
pub position: BigFishVector2Response,
pub radius: f32,
pub offscreen_seconds: f32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishRuntimeSnapshotResponse {
pub run_id: String,
pub session_id: String,
pub status: String,
pub tick: u64,
pub player_level: u32,
pub win_level: u32,
pub leader_entity_id: Option<String>,
pub owned_entities: Vec<BigFishRuntimeEntityResponse>,
pub wild_entities: Vec<BigFishRuntimeEntityResponse>,
pub camera_center: BigFishVector2Response,
pub last_input: BigFishVector2Response,
pub event_log: Vec<String>,
pub updated_at: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishRunResponse {
pub run: BigFishRuntimeSnapshotResponse,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn big_fish_session_request_uses_camel_case() {
let payload = serde_json::to_value(CreateBigFishSessionRequest {
seed_text: Some("深海机械鱼".to_string()),
})
.expect("payload should serialize");
assert_eq!(payload, json!({ "seedText": "深海机械鱼" }));
}
#[test]
fn big_fish_action_request_uses_camel_case() {
let payload = serde_json::to_value(ExecuteBigFishActionRequest {
action: "big_fish_generate_level_motion".to_string(),
level: Some(3),
motion_key: Some("move_swim".to_string()),
})
.expect("payload should serialize");
assert_eq!(payload["motionKey"], json!("move_swim"));
assert_eq!(payload["level"], json!(3));
}
#[test]
fn record_big_fish_play_request_uses_camel_case() {
let payload = serde_json::to_value(RecordBigFishPlayRequest {
elapsed_ms: Some(12_345),
})
.expect("payload should serialize");
assert_eq!(payload, json!({ "elapsedMs": 12_345 }));
}
}
@@ -1,38 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishWorkSummaryResponse {
pub work_id: String,
pub source_session_id: String,
pub owner_user_id: String,
pub author_display_name: String,
pub title: String,
pub subtitle: String,
pub summary: String,
#[serde(default)]
pub cover_image_src: Option<String>,
pub status: String,
pub updated_at: String,
#[serde(default)]
pub published_at: Option<String>,
pub publish_ready: bool,
pub level_count: u32,
pub level_main_image_ready_count: u32,
pub level_motion_ready_count: u32,
pub background_ready: bool,
#[serde(default)]
pub play_count: u32,
#[serde(default)]
pub remix_count: u32,
#[serde(default)]
pub like_count: u32,
#[serde(default)]
pub recent_play_count_7d: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BigFishWorksResponse {
pub items: Vec<BigFishWorkSummaryResponse>,
}
@@ -1,28 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ParseCreationAgentDocumentInputRequest {
pub file_name: String,
#[serde(default)]
pub content_type: Option<String>,
pub content_base64: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CreationAgentDocumentInputPayload {
pub file_name: String,
#[serde(default)]
pub content_type: Option<String>,
pub size_bytes: usize,
pub text: String,
#[serde(default)]
pub source_asset_id: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ParseCreationAgentDocumentInputResponse {
pub document: CreationAgentDocumentInputPayload,
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,236 @@
//! 游戏分发 API 的跨端 DTO。
//!
//! 领域状态机位于 `module-game-distribution`;本文件只描述 HTTP/客户端可见的
//! 数据形状,不能承载 owner、审核或公开切换规则。
use serde::{Deserialize, Serialize};
pub const GAME_DISTRIBUTION_CATEGORIES: [&str; 7] =
["休闲", "益智", "动作", "冒险", "模拟", "策略", "其他"];
/// 发布页免费生成简介与分类的输入。
///
/// 只传经过裁剪的项目摘要,不传本地绝对路径、聊天记录、凭据或完整 manifest。
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionPublishMetadataSuggestionRequest {
pub name: String,
#[serde(default)]
pub goal: Option<String>,
#[serde(default)]
pub context: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionPublishMetadataSuggestion {
pub summary: String,
pub category: String,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum GameDistributionVersionStatus {
AwaitingUpload,
Uploaded,
Validating,
PendingReview,
Published,
UploadFailed,
ValidationFailed,
Rejected,
Cancelled,
Revoked,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum GameDistributionVisibility {
Unpublished,
Published,
Suspended,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum GameDistributionInputMode {
Keyboard,
Mouse,
Touch,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum GameDistributionOrientation {
Landscape,
Portrait,
Responsive,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionDeviceSupport {
pub desktop: bool,
pub mobile: bool,
pub touch: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionAuthor {
pub id: String,
pub name: String,
#[serde(default)]
pub avatar_url: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionVersionSummary {
pub id: String,
pub version: String,
pub entry_url: String,
pub sha256: String,
pub published_at: String,
pub controls: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionGameSummary {
pub id: String,
pub title: String,
pub summary: String,
pub description: String,
pub category: String,
pub tags: Vec<String>,
pub cover_color: String,
pub icon: String,
pub author: GameDistributionAuthor,
pub device_support: GameDistributionDeviceSupport,
#[serde(default)]
pub input_modes: Vec<GameDistributionInputMode>,
#[serde(default)]
pub orientation: Option<GameDistributionOrientation>,
pub status: GameDistributionVisibility,
pub current_version: Option<GameDistributionVersionSummary>,
pub play_count: u64,
pub created_at: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionListResponse {
pub games: Vec<GameDistributionGameSummary>,
#[serde(default)]
pub next_cursor: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionCreateGameRequest {
#[serde(default)]
pub local_project_id: Option<String>,
pub title: String,
pub summary: String,
#[serde(default)]
pub description: Option<String>,
pub category: String,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub cover_asset_id: Option<String>,
/// 截图素材 ID(最多 6 张,复用平台图片上传与归属校验)。
#[serde(default)]
pub screenshots: Vec<String>,
pub device_support: GameDistributionDeviceSupport,
pub input_modes: Vec<GameDistributionInputMode>,
pub orientation: GameDistributionOrientation,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionCreateVersionRequest {
#[serde(default)]
pub local_project_id: Option<String>,
pub package_sha256: String,
pub package_bytes: u64,
pub package_file_count: u32,
pub package_entry_path: String,
pub game_metadata: GameDistributionCreateGameRequest,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionPrivateVersion {
pub version_id: String,
pub game_id: String,
pub version_number: u64,
pub package_sha256: String,
pub package_bytes: u64,
pub status: GameDistributionVersionStatus,
#[serde(default)]
pub review_reason: Option<String>,
pub created_at: String,
pub updated_at: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_game_request_round_trips_local_project_id() {
let payload: GameDistributionCreateGameRequest =
serde_json::from_value(serde_json::json!({
"localProjectId": "proj-1",
"title": "游戏",
"summary": "简介",
"category": "益智",
"deviceSupport": { "desktop": true, "mobile": false, "touch": false },
"inputModes": ["keyboard"],
"orientation": "responsive",
}))
.expect("请求应可解析");
assert_eq!(payload.local_project_id.as_deref(), Some("proj-1"));
let serialized = serde_json::to_value(&payload).expect("请求应可序列化");
assert_eq!(serialized["localProjectId"], "proj-1");
}
#[test]
fn legacy_create_game_request_without_local_project_id_stays_compatible() {
let payload: GameDistributionCreateGameRequest =
serde_json::from_value(serde_json::json!({
"title": "游戏",
"summary": "简介",
"category": "益智",
"deviceSupport": { "desktop": true, "mobile": false, "touch": false },
"inputModes": ["keyboard"],
"orientation": "responsive",
}))
.expect("旧请求应继续可解析");
assert_eq!(payload.local_project_id, None);
}
#[test]
fn create_version_request_accepts_local_project_id() {
let payload: GameDistributionCreateVersionRequest =
serde_json::from_value(serde_json::json!({
"localProjectId": "proj-1",
"packageSha256": "a".repeat(64),
"packageBytes": 10,
"packageFileCount": 1,
"packageEntryPath": "index.html",
"gameMetadata": {
"title": "游戏",
"summary": "简介",
"category": "益智",
"deviceSupport": { "desktop": true, "mobile": false, "touch": false },
"inputModes": ["keyboard"],
"orientation": "responsive",
},
}))
.expect("版本请求应可解析");
assert_eq!(payload.local_project_id.as_deref(), Some("proj-1"));
}
}
File diff suppressed because it is too large Load Diff
+2 -40
View File
@@ -1,24 +1,13 @@
pub mod admin;
pub mod agc_analytics;
pub mod agc_project_snapshots;
pub mod ai;
pub mod api;
#[cfg(feature = "oss-contracts")]
pub mod assets;
pub mod auth;
#[cfg(any())]
pub mod bark_battle;
#[cfg(any())]
pub mod big_fish;
#[cfg(any())]
pub mod big_fish_works;
pub mod client_downloads;
#[cfg(any())]
pub mod creation_agent_document_input;
pub mod creation_audio;
#[cfg(any())]
pub mod creation_entry_config;
#[cfg(any())]
pub mod creative_agent;
pub mod editor_agent;
pub mod editor_canvas;
pub mod editor_generation;
@@ -32,35 +21,8 @@ pub use editor_generation::{
};
pub mod external_generation;
pub mod game_creation_app;
pub mod game_distribution;
pub mod hyper3d;
pub mod llm;
pub mod model3d;
#[cfg(any())]
pub mod public_work;
#[cfg(any())]
pub mod puzzle_agent;
#[cfg(any())]
pub mod puzzle_clear;
#[cfg(any())]
pub mod puzzle_creative_template;
#[cfg(any())]
pub mod puzzle_gallery;
#[cfg(any())]
pub mod puzzle_runtime;
#[cfg(any())]
pub mod puzzle_works;
pub mod runtime;
#[cfg(any())]
pub mod runtime_story;
#[cfg(any())]
pub mod square_hole_agent;
#[cfg(any())]
pub mod square_hole_runtime;
#[cfg(any())]
pub mod square_hole_works;
#[cfg(any())]
pub mod story;
#[cfg(any())]
pub mod visual_novel;
#[cfg(any())]
pub mod wooden_fish;
@@ -1,305 +0,0 @@
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));
}
}
@@ -1,127 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct StartMatch3DRunRequest {
pub profile_id: String,
#[serde(default)]
pub item_type_count_override: Option<u32>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ClickMatch3DItemRequest {
#[serde(default)]
pub run_id: Option<String>,
pub item_instance_id: String,
pub client_snapshot_version: u64,
pub client_event_id: String,
pub clicked_at_ms: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct StopMatch3DRunRequest {
pub client_action_id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Match3DItemSnapshotResponse {
pub item_instance_id: String,
pub item_type_id: String,
pub visual_key: String,
pub x: f32,
pub y: f32,
pub radius: f32,
pub layer: u32,
pub state: String,
pub clickable: bool,
#[serde(default)]
pub tray_slot_index: Option<u32>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Match3DTraySlotResponse {
pub slot_index: u32,
#[serde(default)]
pub item_instance_id: Option<String>,
#[serde(default)]
pub item_type_id: Option<String>,
#[serde(default)]
pub visual_key: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Match3DRunSnapshotResponse {
pub run_id: String,
pub profile_id: String,
pub owner_user_id: String,
pub status: String,
/// 对外 HTTP 快照版本。领域层内部字段名为 board_version,facade 需要在这里完成映射。
pub snapshot_version: u64,
pub started_at_ms: u64,
pub duration_limit_ms: u64,
#[serde(default)]
pub server_now_ms: Option<u64>,
pub remaining_ms: u64,
pub clear_count: u32,
pub total_item_count: u32,
pub cleared_item_count: u32,
pub items: Vec<Match3DItemSnapshotResponse>,
pub tray_slots: Vec<Match3DTraySlotResponse>,
#[serde(default)]
pub failure_reason: Option<String>,
#[serde(default)]
pub last_confirmed_action_id: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Match3DClickConfirmationResponse {
pub accepted: bool,
#[serde(default)]
pub reject_reason: Option<String>,
#[serde(default)]
pub entered_slot_index: Option<u32>,
pub cleared_item_instance_ids: Vec<String>,
pub run: Match3DRunSnapshotResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Match3DRunResponse {
pub run: Match3DRunSnapshotResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Match3DClickResponse {
pub confirmation: Match3DClickConfirmationResponse,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn click_match3d_item_request_uses_camel_case() {
let payload = serde_json::to_value(ClickMatch3DItemRequest {
run_id: Some("run-1".to_string()),
item_instance_id: "item-1".to_string(),
client_snapshot_version: 7,
client_event_id: "event-1".to_string(),
clicked_at_ms: 12_345,
})
.expect("payload should serialize");
assert_eq!(payload["runId"], json!("run-1"));
assert_eq!(payload["itemInstanceId"], json!("item-1"));
assert_eq!(payload["clientSnapshotVersion"], json!(7));
assert_eq!(payload["clientEventId"], json!("event-1"));
assert_eq!(payload["clickedAtMs"], json!(12_345));
}
}
@@ -1,337 +0,0 @@
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)]
pub generation_status: Option<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)]
#[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)]
#[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, 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)]
#[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));
}
#[test]
fn match3d_work_summary_uses_camel_case_generation_status() {
let payload = serde_json::to_value(Match3DWorkSummaryResponse {
work_id: "work-1".to_string(),
profile_id: "profile-1".to_string(),
owner_user_id: "user-1".to_string(),
source_session_id: Some("session-1".to_string()),
game_name: "水果抓大鹅".to_string(),
theme_text: "水果".to_string(),
summary: "水果主题".to_string(),
tags: vec!["水果".to_string()],
cover_image_src: None,
reference_image_src: None,
clear_count: 4,
difficulty: 5,
publication_status: "draft".to_string(),
play_count: 0,
updated_at: "2026-05-01T00:00:00Z".to_string(),
published_at: None,
publish_ready: false,
generation_status: Some("generating".to_string()),
background_prompt: None,
background_image_src: None,
background_image_object_key: None,
generated_background_asset: None,
generated_item_assets: Vec::new(),
})
.expect("payload should serialize");
assert_eq!(payload["generationStatus"], json!("generating"));
}
}
@@ -1,63 +0,0 @@
use serde::{Deserialize, Serialize};
/// 公开作品列表统一契约。
///
/// 该契约面向 BFF 和后续可选前端直连订阅,字段只包含平台公开卡片需要的摘要信息。
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PublicWorkGalleryEntryResponse {
pub source_type: String,
pub work_id: String,
pub profile_id: String,
#[serde(default)]
pub source_session_id: Option<String>,
pub public_work_code: String,
pub owner_user_id: String,
pub author_display_name: String,
pub world_name: String,
pub subtitle: String,
pub summary_text: String,
#[serde(default)]
pub cover_image_src: Option<String>,
#[serde(default)]
pub cover_asset_id: Option<String>,
pub theme_tags: Vec<String>,
pub play_count: u32,
pub remix_count: u32,
pub like_count: u32,
#[serde(default)]
pub recent_play_count_7d: u32,
#[serde(default)]
pub published_at: Option<String>,
pub updated_at: String,
pub sort_time_micros: i64,
}
/// 公开作品详情统一摘要契约。
///
/// `detail_payload_json` 只承载详情页展示扩展字段,不承载正式 runtime 配置。
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PublicWorkDetailEntryResponse {
#[serde(flatten)]
pub entry: PublicWorkGalleryEntryResponse,
pub detail_payload_json: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PublicWorkGalleryResponse {
pub items: Vec<PublicWorkGalleryEntryResponse>,
#[serde(default)]
pub has_more: bool,
#[serde(default)]
pub next_cursor: Option<String>,
#[serde(default)]
pub total_count: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PublicWorkDetailResponse {
pub item: PublicWorkDetailEntryResponse,
}
@@ -1,294 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::creation_audio::CreationAudioAsset;
use crate::external_generation::ExternalGenerationJobStatusRecord;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CreatePuzzleAgentSessionRequest {
#[serde(default)]
pub seed_text: Option<String>,
#[serde(default)]
pub work_title: Option<String>,
#[serde(default)]
pub work_description: Option<String>,
#[serde(default)]
pub picture_description: Option<String>,
#[serde(default)]
pub reference_image_src: Option<String>,
#[serde(default)]
pub reference_image_srcs: Vec<String>,
#[serde(default)]
pub reference_image_asset_object_id: Option<String>,
#[serde(default)]
pub reference_image_asset_object_ids: Vec<String>,
#[serde(default)]
pub image_model: Option<String>,
#[serde(default)]
pub ai_redraw: Option<bool>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SendPuzzleAgentMessageRequest {
pub client_message_id: String,
pub text: String,
#[serde(default)]
pub quick_fill_requested: Option<bool>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ExecutePuzzleAgentActionRequest {
pub action: String,
#[serde(default)]
pub prompt_text: Option<String>,
#[serde(default)]
pub reference_image_src: Option<String>,
#[serde(default)]
pub reference_image_srcs: Vec<String>,
#[serde(default)]
pub reference_image_asset_object_id: Option<String>,
#[serde(default)]
pub reference_image_asset_object_ids: Vec<String>,
#[serde(default)]
pub image_model: Option<String>,
#[serde(default)]
pub ai_redraw: Option<bool>,
#[serde(default)]
pub candidate_count: Option<u32>,
#[serde(default)]
pub should_auto_name_level: Option<bool>,
#[serde(default)]
pub candidate_id: Option<String>,
#[serde(default)]
pub level_id: Option<String>,
#[serde(default)]
pub work_title: Option<String>,
#[serde(default)]
pub work_description: Option<String>,
#[serde(default)]
pub picture_description: Option<String>,
#[serde(default)]
pub level_name: Option<String>,
#[serde(default)]
pub summary: Option<String>,
#[serde(default)]
pub theme_tags: Option<Vec<String>>,
#[serde(default)]
pub levels_json: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleFormDraftResponse {
#[serde(default)]
pub work_title: Option<String>,
#[serde(default)]
pub work_description: Option<String>,
#[serde(default)]
pub picture_description: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleAnchorItemResponse {
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 PuzzleAnchorPackResponse {
pub theme_promise: PuzzleAnchorItemResponse,
pub visual_subject: PuzzleAnchorItemResponse,
pub visual_mood: PuzzleAnchorItemResponse,
pub composition_hooks: PuzzleAnchorItemResponse,
pub tags_and_forbidden: PuzzleAnchorItemResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleGeneratedImageCandidateResponse {
pub candidate_id: String,
pub image_src: String,
pub asset_id: String,
pub prompt: String,
#[serde(default)]
pub actual_prompt: Option<String>,
pub source_type: String,
pub selected: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleCreatorIntentResponse {
pub source_mode: String,
pub raw_messages_summary: String,
pub theme_promise: String,
pub visual_subject: String,
pub visual_mood: Vec<String>,
pub composition_hooks: Vec<String>,
pub theme_tags: Vec<String>,
pub forbidden_directives: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleResultDraftResponse {
pub work_title: String,
pub work_description: String,
pub level_name: String,
pub summary: String,
pub theme_tags: Vec<String>,
pub forbidden_directives: Vec<String>,
#[serde(default)]
pub creator_intent: Option<PuzzleCreatorIntentResponse>,
pub anchor_pack: PuzzleAnchorPackResponse,
pub candidates: Vec<PuzzleGeneratedImageCandidateResponse>,
#[serde(default)]
pub selected_candidate_id: Option<String>,
#[serde(default)]
pub cover_image_src: Option<String>,
#[serde(default)]
pub cover_asset_id: Option<String>,
pub generation_status: String,
pub levels: Vec<PuzzleDraftLevelResponse>,
#[serde(default)]
pub form_draft: Option<PuzzleFormDraftResponse>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleDraftLevelResponse {
pub level_id: String,
pub level_name: String,
pub picture_description: String,
#[serde(default)]
pub picture_reference: Option<String>,
#[serde(default)]
pub ui_background_prompt: Option<String>,
#[serde(default)]
pub ui_background_image_src: Option<String>,
#[serde(default)]
pub ui_background_image_object_key: Option<String>,
#[serde(default)]
pub level_scene_image_src: Option<String>,
#[serde(default)]
pub level_scene_image_object_key: Option<String>,
#[serde(default)]
pub ui_spritesheet_image_src: Option<String>,
#[serde(default)]
pub ui_spritesheet_image_object_key: Option<String>,
#[serde(default)]
pub level_background_image_src: Option<String>,
#[serde(default)]
pub level_background_image_object_key: Option<String>,
#[serde(default)]
pub background_music: Option<CreationAudioAsset>,
pub candidates: Vec<PuzzleGeneratedImageCandidateResponse>,
#[serde(default)]
pub selected_candidate_id: Option<String>,
#[serde(default)]
pub cover_image_src: Option<String>,
#[serde(default)]
pub cover_asset_id: Option<String>,
pub generation_status: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleAgentMessageResponse {
pub id: String,
pub role: String,
pub kind: String,
pub text: String,
pub created_at: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleAgentSuggestedActionResponse {
pub id: String,
pub action_type: String,
pub label: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleResultPreviewBlockerResponse {
pub id: String,
pub code: String,
pub message: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleResultPreviewFindingResponse {
pub id: String,
pub severity: String,
pub code: String,
pub message: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleResultPreviewEnvelopeResponse {
pub draft: PuzzleResultDraftResponse,
pub blockers: Vec<PuzzleResultPreviewBlockerResponse>,
pub quality_findings: Vec<PuzzleResultPreviewFindingResponse>,
pub publish_ready: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleAgentSessionSnapshotResponse {
pub session_id: String,
pub seed_text: String,
pub current_turn: u32,
pub progress_percent: u32,
pub stage: String,
pub anchor_pack: PuzzleAnchorPackResponse,
#[serde(default)]
pub draft: Option<PuzzleResultDraftResponse>,
pub messages: Vec<PuzzleAgentMessageResponse>,
#[serde(default)]
pub last_assistant_reply: Option<String>,
#[serde(default)]
pub published_profile_id: Option<String>,
pub suggested_actions: Vec<PuzzleAgentSuggestedActionResponse>,
#[serde(default)]
pub result_preview: Option<PuzzleResultPreviewEnvelopeResponse>,
pub updated_at: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleAgentSessionResponse {
pub session: PuzzleAgentSessionSnapshotResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleAgentOperationResponse {
pub operation_id: String,
pub operation_type: String,
pub status: String,
pub phase_label: String,
pub phase_detail: String,
pub progress: u32,
#[serde(default)]
pub error: Option<String>,
#[serde(default)]
pub queue_state: Option<ExternalGenerationJobStatusRecord>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleAgentActionResponse {
pub operation: PuzzleAgentOperationResponse,
/// 操作完成后的最新会话快照,供前端直接更新界面,避免重复拉取完整 session。
pub session: PuzzleAgentSessionSnapshotResponse,
}
@@ -1,317 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::external_generation::ExternalGenerationJobStatusRecord;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PuzzleClearGenerationStatus {
Draft,
Generating,
Ready,
Failed,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum PuzzleClearActionType {
CompileDraft,
RegenerateAtlas,
UpdateWorkMeta,
UpdateBoardBackground,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PuzzleClearRunStatus {
Playing,
LevelFailed,
LevelCleared,
Finished,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearImageAsset {
pub asset_id: String,
pub image_src: String,
pub image_object_key: String,
pub asset_object_id: String,
pub generation_provider: String,
pub prompt: String,
pub width: u32,
pub height: u32,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearPatternGroup {
pub group_id: String,
pub shape: String,
pub width: u32,
pub height: u32,
pub atlas_x: u32,
pub atlas_y: u32,
pub atlas_width: u32,
pub atlas_height: u32,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearCardAsset {
pub card_id: String,
pub group_id: String,
pub shape: String,
pub orientation: String,
pub part_x: u32,
pub part_y: u32,
pub image_src: String,
pub image_object_key: String,
pub asset_object_id: String,
pub source_atlas_cell: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearWorkspaceCreateRequest {
pub template_id: String,
pub work_title: String,
pub work_description: String,
pub theme_prompt: String,
#[serde(default)]
pub board_background_prompt: String,
pub generate_board_background: bool,
pub board_background_asset: Option<PuzzleClearImageAsset>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearActionRequest {
pub action_type: PuzzleClearActionType,
pub profile_id: Option<String>,
pub work_title: Option<String>,
pub work_description: Option<String>,
pub theme_prompt: Option<String>,
#[serde(default)]
pub board_background_prompt: Option<String>,
pub generate_board_background: Option<bool>,
pub board_background_asset: Option<PuzzleClearImageAsset>,
pub atlas_asset: Option<PuzzleClearImageAsset>,
pub pattern_groups: Option<Vec<PuzzleClearPatternGroup>>,
pub card_assets: Option<Vec<PuzzleClearCardAsset>>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearDraftResponse {
pub template_id: String,
pub template_name: String,
pub profile_id: Option<String>,
pub work_title: String,
pub work_description: String,
pub theme_prompt: String,
#[serde(default)]
pub board_background_prompt: String,
pub generate_board_background: bool,
pub board_background_asset: Option<PuzzleClearImageAsset>,
pub card_back_image_src: Option<String>,
pub atlas_asset: Option<PuzzleClearImageAsset>,
pub pattern_groups: Vec<PuzzleClearPatternGroup>,
pub card_assets: Vec<PuzzleClearCardAsset>,
pub generation_status: PuzzleClearGenerationStatus,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearSessionSnapshotResponse {
pub session_id: String,
pub owner_user_id: String,
pub status: PuzzleClearGenerationStatus,
pub draft: Option<PuzzleClearDraftResponse>,
pub created_at: String,
pub updated_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearSessionResponse {
pub session: PuzzleClearSessionSnapshotResponse,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearActionResponse {
pub action_type: PuzzleClearActionType,
pub session: PuzzleClearSessionSnapshotResponse,
pub work: Option<PuzzleClearWorkProfileResponse>,
#[serde(default)]
pub queue_state: Option<ExternalGenerationJobStatusRecord>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearWorkSummaryResponse {
pub runtime_kind: String,
pub work_id: String,
pub profile_id: String,
pub owner_user_id: String,
pub source_session_id: Option<String>,
pub work_title: String,
pub work_description: String,
pub theme_prompt: String,
pub cover_image_src: Option<String>,
pub publication_status: String,
pub play_count: u32,
pub updated_at: String,
pub published_at: Option<String>,
pub publish_ready: bool,
pub generation_status: PuzzleClearGenerationStatus,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearWorkProfileResponse {
pub summary: PuzzleClearWorkSummaryResponse,
pub draft: PuzzleClearDraftResponse,
pub board_background_asset: Option<PuzzleClearImageAsset>,
pub atlas_asset: PuzzleClearImageAsset,
pub pattern_groups: Vec<PuzzleClearPatternGroup>,
pub card_assets: Vec<PuzzleClearCardAsset>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearWorksResponse {
pub items: Vec<PuzzleClearWorkSummaryResponse>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearWorkDetailResponse {
pub item: PuzzleClearWorkProfileResponse,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearWorkMutationResponse {
pub item: PuzzleClearWorkProfileResponse,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearBoardCell {
pub row: u32,
pub col: u32,
pub card: Option<PuzzleClearCardAsset>,
pub locked_group_id: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearBoardSnapshot {
pub rows: u32,
pub cols: u32,
pub cells: Vec<PuzzleClearBoardCell>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearRuntimeSnapshotResponse {
pub run_id: String,
pub profile_id: String,
pub owner_user_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub runtime_mode: Option<String>,
pub status: PuzzleClearRunStatus,
pub level_index: u32,
pub clears_done: u32,
pub target_clears: u32,
pub level_duration_seconds: u32,
pub level_started_at_ms: u64,
pub board: PuzzleClearBoardSnapshot,
pub ready_columns: Vec<Vec<PuzzleClearCardAsset>>,
pub started_at_ms: u64,
pub finished_at_ms: Option<u64>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearRunResponse {
pub run: PuzzleClearRuntimeSnapshotResponse,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearStartRunRequest {
pub profile_id: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearSwapRequest {
pub from_row: u32,
pub from_col: u32,
pub to_row: u32,
pub to_col: u32,
pub client_action_id: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearRetryLevelRequest {
pub client_action_id: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearNextLevelRequest {
pub client_action_id: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleClearTimeUpRequest {
pub client_action_id: String,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn workspace_create_request_uses_camel_case() {
let payload = PuzzleClearWorkspaceCreateRequest {
template_id: "puzzle-clear".to_string(),
work_title: "花园拼消消".to_string(),
work_description: "轻松消除".to_string(),
theme_prompt: "春日花园".to_string(),
board_background_prompt: "樱花庭院".to_string(),
generate_board_background: true,
board_background_asset: None,
};
let value = serde_json::to_value(payload).expect("request should serialize");
assert_eq!(value["templateId"], json!("puzzle-clear"));
assert_eq!(value["themePrompt"], json!("春日花园"));
assert_eq!(value["boardBackgroundPrompt"], json!("樱花庭院"));
assert_eq!(value["generateBoardBackground"], json!(true));
}
#[test]
fn runtime_swap_request_uses_camel_case() {
let payload = PuzzleClearSwapRequest {
from_row: 1,
from_col: 2,
to_row: 1,
to_col: 3,
client_action_id: "swap-1".to_string(),
};
let value = serde_json::to_value(payload).expect("request should serialize");
assert_eq!(value["fromRow"], json!(1));
assert_eq!(value["toCol"], json!(3));
assert_eq!(value["clientActionId"], json!("swap-1"));
}
}
@@ -1,230 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum PuzzleTemplatePricingUnit {
Point,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum PuzzleSupportedLevelMode {
Single,
Multi,
SingleOrMulti,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum PuzzleLevelGenerationMode {
SingleLevel,
MultiLevel,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleTemplateCostRange {
pub min_points: u32,
pub max_points: u32,
pub pricing_unit: PuzzleTemplatePricingUnit,
pub reason: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum PuzzleDraftEditableFieldPath {
#[serde(rename = "workTitle")]
WorkTitle,
#[serde(rename = "workDescription")]
WorkDescription,
#[serde(rename = "workTags")]
WorkTags,
#[serde(rename = "levels[].levelName")]
LevelName,
#[serde(rename = "levels[].pictureDescription")]
LevelPictureDescription,
#[serde(rename = "levels[].pictureReference")]
LevelPictureReference,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleTemplateImageGenerationPolicy {
pub allow_uploaded_image_directly: bool,
pub allow_generated_images: bool,
pub allow_per_level_reference_image: bool,
pub default_candidate_count_per_level: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleCreativeTemplateProtocol {
pub template_id: String,
pub title: String,
pub summary: String,
#[serde(default)]
pub preview_image_src: Option<String>,
pub supported_level_mode: PuzzleSupportedLevelMode,
pub min_level_count: u32,
pub max_level_count: u32,
pub default_level_count: u32,
pub cost_range: PuzzleTemplateCostRange,
pub required_draft_fields: Vec<PuzzleDraftEditableFieldPath>,
pub image_policy: PuzzleTemplateImageGenerationPolicy,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleCreativeTemplateSelection {
pub template_id: String,
pub title: String,
pub reason: String,
pub cost_range: PuzzleTemplateCostRange,
pub supported_level_mode: PuzzleSupportedLevelMode,
pub selected_level_mode: PuzzleLevelGenerationMode,
pub planned_level_count: u32,
pub requires_user_confirmation: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CreativePuzzleLevelDraftInput {
pub level_name: String,
pub picture_description: String,
/// 任务 A 冻结:Phase 1 采用正式字段方案,后续拼图草稿落地需补正式 pictureReference 字段。
#[serde(default)]
pub picture_reference: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CreativePuzzleDraftToolInput {
pub template_id: String,
pub template_cost_range: PuzzleTemplateCostRange,
pub work_title: String,
pub work_description: String,
pub work_tags: Vec<String>,
pub levels: Vec<CreativePuzzleLevelDraftInput>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleImageGenerationPlanLevel {
pub level_id: String,
pub level_name: String,
pub picture_description: String,
pub image_prompt: String,
#[serde(default)]
pub picture_reference: Option<String>,
pub candidate_count: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleImageGenerationPlan {
pub mode: PuzzleLevelGenerationMode,
pub template_id: String,
pub estimated_cost_range: PuzzleTemplateCostRange,
pub levels: Vec<PuzzleImageGenerationPlanLevel>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum PuzzleDraftFieldPatchOperation {
Set,
Append,
Replace,
Remove,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleDraftFieldPatch {
pub field_path: PuzzleDraftEditableFieldPath,
pub operation: PuzzleDraftFieldPatchOperation,
#[serde(default)]
pub level_id: Option<String>,
pub value: serde_json::Value,
pub rationale: String,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn cost_range() -> PuzzleTemplateCostRange {
PuzzleTemplateCostRange {
min_points: 2,
max_points: 12,
pricing_unit: PuzzleTemplatePricingUnit::Point,
reason: "按关卡数和每关图片生成次数估算".to_string(),
}
}
#[test]
fn creative_agent_puzzle_template_protocol_uses_camel_case() {
let payload = serde_json::to_value(PuzzleCreativeTemplateProtocol {
template_id: "puzzle.default-creative".to_string(),
title: "创意拼图".to_string(),
summary: "把图文灵感做成拼图".to_string(),
preview_image_src: None,
supported_level_mode: PuzzleSupportedLevelMode::SingleOrMulti,
min_level_count: 1,
max_level_count: 6,
default_level_count: 1,
cost_range: cost_range(),
required_draft_fields: vec![
PuzzleDraftEditableFieldPath::WorkTitle,
PuzzleDraftEditableFieldPath::LevelPictureReference,
],
image_policy: PuzzleTemplateImageGenerationPolicy {
allow_uploaded_image_directly: true,
allow_generated_images: true,
allow_per_level_reference_image: true,
default_candidate_count_per_level: 1,
},
})
.expect("template should serialize");
assert_eq!(payload["templateId"], json!("puzzle.default-creative"));
assert_eq!(payload["previewImageSrc"], json!(null));
assert_eq!(payload["supportedLevelMode"], json!("single_or_multi"));
assert_eq!(payload["costRange"]["pricingUnit"], json!("point"));
assert_eq!(
payload["requiredDraftFields"],
json!(["workTitle", "levels[].pictureReference"])
);
assert_eq!(
payload["imagePolicy"]["allowPerLevelReferenceImage"],
json!(true)
);
}
#[test]
fn creative_agent_puzzle_image_plan_roundtrips() {
let plan = PuzzleImageGenerationPlan {
mode: PuzzleLevelGenerationMode::MultiLevel,
template_id: "puzzle.default-creative".to_string(),
estimated_cost_range: cost_range(),
levels: vec![PuzzleImageGenerationPlanLevel {
level_id: "level-1".to_string(),
level_name: "第一关".to_string(),
picture_description: "温暖的家庭照片".to_string(),
image_prompt: "pixel puzzle, warm family photo".to_string(),
picture_reference: Some("asset-ref-1".to_string()),
candidate_count: 1,
}],
};
let payload = serde_json::to_value(&plan).expect("plan should serialize");
assert_eq!(payload["mode"], json!("multi_level"));
assert_eq!(
payload["levels"][0]["pictureReference"],
json!("asset-ref-1")
);
let decoded: PuzzleImageGenerationPlan =
serde_json::from_value(payload).expect("plan should deserialize");
assert_eq!(decoded, plan);
}
}
@@ -1,30 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::puzzle_works::{PuzzleWorkProfileResponse, PuzzleWorkSummaryResponse};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleGalleryResponse {
pub items: Vec<PuzzleWorkSummaryResponse>,
#[serde(default)]
pub preview_refs: Vec<PuzzleGalleryWorkRefResponse>,
#[serde(default)]
pub has_more: bool,
#[serde(default)]
pub next_cursor: Option<String>,
#[serde(default)]
pub total_count: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleGalleryWorkRefResponse {
pub work_id: String,
pub profile_id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleGalleryDetailResponse {
pub item: PuzzleWorkProfileResponse,
}
@@ -1,204 +0,0 @@
use crate::creation_audio::CreationAudioAsset;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct StartPuzzleRunRequest {
pub profile_id: String,
#[serde(default)]
pub level_id: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SwapPuzzlePiecesRequest {
pub first_piece_id: String,
pub second_piece_id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct DragPuzzlePieceRequest {
pub piece_id: String,
pub target_row: u32,
pub target_col: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct AdvancePuzzleNextLevelRequest {
#[serde(default)]
pub target_profile_id: Option<String>,
#[serde(default)]
pub prefer_similar_work: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UsePuzzleRuntimePropRequest {
pub prop_kind: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UpdatePuzzleRuntimePauseRequest {
pub paused: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SubmitPuzzleLeaderboardRequest {
pub profile_id: String,
pub grid_size: u32,
pub elapsed_ms: u64,
pub nickname: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleCellPositionResponse {
pub row: u32,
pub col: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzlePieceStateResponse {
pub piece_id: String,
pub correct_row: u32,
pub correct_col: u32,
pub current_row: u32,
pub current_col: u32,
#[serde(default)]
pub merged_group_id: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleMergedGroupStateResponse {
pub group_id: String,
pub piece_ids: Vec<String>,
pub occupied_cells: Vec<PuzzleCellPositionResponse>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleLeaderboardEntryResponse {
pub rank: u32,
pub nickname: String,
pub elapsed_ms: u64,
#[serde(default)]
pub visible_tags: Vec<String>,
#[serde(default)]
pub is_current_player: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleBoardSnapshotResponse {
pub rows: u32,
pub cols: u32,
pub pieces: Vec<PuzzlePieceStateResponse>,
pub merged_groups: Vec<PuzzleMergedGroupStateResponse>,
#[serde(default)]
pub selected_piece_id: Option<String>,
pub all_tiles_resolved: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleRuntimeLevelSnapshotResponse {
pub run_id: String,
pub level_index: u32,
#[serde(default)]
pub level_id: Option<String>,
pub grid_size: u32,
pub profile_id: String,
pub level_name: String,
pub author_display_name: String,
pub theme_tags: Vec<String>,
#[serde(default)]
pub cover_image_src: Option<String>,
#[serde(default)]
pub ui_background_image_src: Option<String>,
#[serde(default)]
pub ui_background_image_object_key: Option<String>,
#[serde(default)]
pub level_background_image_src: Option<String>,
#[serde(default)]
pub level_background_image_object_key: Option<String>,
#[serde(default)]
pub ui_spritesheet_image_src: Option<String>,
#[serde(default)]
pub ui_spritesheet_image_object_key: Option<String>,
#[serde(default)]
pub background_music: Option<CreationAudioAsset>,
pub board: PuzzleBoardSnapshotResponse,
pub status: String,
#[serde(default)]
pub started_at_ms: u64,
#[serde(default)]
pub cleared_at_ms: Option<u64>,
#[serde(default)]
pub elapsed_ms: Option<u64>,
#[serde(default)]
pub time_limit_ms: u64,
#[serde(default)]
pub remaining_ms: u64,
#[serde(default)]
pub paused_accumulated_ms: u64,
#[serde(default)]
pub pause_started_at_ms: Option<u64>,
#[serde(default)]
pub freeze_accumulated_ms: u64,
#[serde(default)]
pub freeze_started_at_ms: Option<u64>,
#[serde(default)]
pub freeze_until_ms: Option<u64>,
#[serde(default)]
pub leaderboard_entries: Vec<PuzzleLeaderboardEntryResponse>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleRecommendedNextWorkResponse {
pub profile_id: String,
pub level_name: String,
pub author_display_name: String,
pub theme_tags: Vec<String>,
#[serde(default)]
pub cover_image_src: Option<String>,
pub similarity_score: f32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleRunSnapshotResponse {
pub run_id: String,
pub entry_profile_id: String,
pub cleared_level_count: u32,
pub current_level_index: u32,
pub current_grid_size: u32,
pub played_profile_ids: Vec<String>,
pub previous_level_tags: Vec<String>,
#[serde(default)]
pub current_level: Option<PuzzleRuntimeLevelSnapshotResponse>,
#[serde(default)]
pub recommended_next_profile_id: Option<String>,
#[serde(default)]
pub next_level_mode: String,
#[serde(default)]
pub next_level_profile_id: Option<String>,
#[serde(default)]
pub next_level_id: Option<String>,
#[serde(default)]
pub recommended_next_works: Vec<PuzzleRecommendedNextWorkResponse>,
#[serde(default)]
pub leaderboard_entries: Vec<PuzzleLeaderboardEntryResponse>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleRunResponse {
pub run: PuzzleRunSnapshotResponse,
}
@@ -1,153 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::puzzle_agent::{PuzzleAnchorPackResponse, PuzzleDraftLevelResponse};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PutPuzzleWorkRequest {
pub work_title: String,
pub work_description: String,
pub level_name: String,
pub summary: String,
pub theme_tags: Vec<String>,
#[serde(default)]
pub cover_image_src: Option<String>,
#[serde(default)]
pub cover_asset_id: Option<String>,
#[serde(default)]
pub levels: Vec<PuzzleDraftLevelResponse>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleWorkSummaryResponse {
pub work_id: String,
pub profile_id: String,
pub owner_user_id: String,
#[serde(default)]
pub source_session_id: Option<String>,
pub author_display_name: String,
pub work_title: String,
pub work_description: String,
pub level_name: String,
pub summary: String,
pub theme_tags: Vec<String>,
#[serde(default)]
pub cover_image_src: Option<String>,
#[serde(default)]
pub cover_asset_id: Option<String>,
pub publication_status: String,
pub updated_at: String,
#[serde(default)]
pub published_at: Option<String>,
pub play_count: u32,
#[serde(default)]
pub remix_count: u32,
#[serde(default)]
pub like_count: u32,
#[serde(default)]
pub recent_play_count_7d: u32,
#[serde(default)]
pub point_incentive_total_half_points: u64,
#[serde(default)]
pub point_incentive_claimed_points: u64,
#[serde(default)]
pub point_incentive_total_points: f64,
#[serde(default)]
pub point_incentive_claimable_points: u64,
pub publish_ready: bool,
#[serde(default)]
pub generation_status: Option<String>,
#[serde(default)]
pub levels: Vec<PuzzleDraftLevelResponse>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn puzzle_work_summary_response_uses_point_incentive_fields() {
let payload = serde_json::to_value(PuzzleWorkSummaryResponse {
work_id: "work-1".to_string(),
profile_id: "profile-1".to_string(),
owner_user_id: "user-1".to_string(),
source_session_id: None,
author_display_name: "作者".to_string(),
work_title: "作品".to_string(),
work_description: "描述".to_string(),
level_name: "第一关".to_string(),
summary: "画面".to_string(),
theme_tags: vec!["拼图".to_string(), "夜色".to_string(), "灯光".to_string()],
cover_image_src: None,
cover_asset_id: None,
publication_status: "published".to_string(),
updated_at: "2026-05-01T00:00:00Z".to_string(),
published_at: Some("2026-05-01T00:00:00Z".to_string()),
play_count: 1,
remix_count: 0,
like_count: 0,
recent_play_count_7d: 1,
point_incentive_total_half_points: 3,
point_incentive_claimed_points: 1,
point_incentive_total_points: 1.5,
point_incentive_claimable_points: 0,
publish_ready: true,
generation_status: Some("ready".to_string()),
levels: Vec::new(),
})
.expect("payload should serialize");
assert_eq!(payload["pointIncentiveTotalHalfPoints"], 3);
assert_eq!(payload["pointIncentiveClaimedPoints"], 1);
assert_eq!(payload["pointIncentiveTotalPoints"], 1.5);
assert_eq!(payload["pointIncentiveClaimablePoints"], 0);
assert_eq!(payload["generationStatus"], "ready");
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleWorkProfileResponse {
#[serde(flatten)]
pub summary: PuzzleWorkSummaryResponse,
pub anchor_pack: PuzzleAnchorPackResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleWorksResponse {
pub items: Vec<PuzzleWorkSummaryResponse>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleWorkDetailResponse {
pub item: PuzzleWorkProfileResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleWorkMutationResponse {
pub item: PuzzleWorkProfileResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleOnboardingGenerateRequest {
pub prompt_text: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleOnboardingGenerateResponse {
pub item: PuzzleWorkSummaryResponse,
pub level: PuzzleDraftLevelResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PuzzleOnboardingSaveRequest {
pub prompt_text: String,
pub item: PuzzleWorkSummaryResponse,
}
File diff suppressed because it is too large Load Diff
@@ -1,171 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CreateSquareHoleSessionRequest {
#[serde(default)]
pub seed_text: Option<String>,
#[serde(default)]
pub theme_text: Option<String>,
#[serde(default)]
pub twist_rule: Option<String>,
#[serde(default)]
pub shape_count: Option<u32>,
#[serde(default)]
pub difficulty: Option<u32>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SendSquareHoleMessageRequest {
pub client_message_id: String,
pub text: String,
#[serde(default)]
pub quick_fill_requested: Option<bool>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ExecuteSquareHoleActionRequest {
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 regenerate_visual_assets: Option<bool>,
#[serde(default)]
pub visual_asset_slot: Option<String>,
#[serde(default)]
pub visual_asset_option_id: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleShapeOptionResponse {
pub option_id: String,
pub shape_kind: String,
pub label: String,
pub target_hole_id: String,
pub image_prompt: String,
#[serde(default)]
pub image_src: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleHoleOptionResponse {
pub hole_id: String,
pub hole_kind: String,
pub label: String,
pub image_prompt: String,
#[serde(default)]
pub image_src: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleAnchorItemResponse {
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 SquareHoleAnchorPackResponse {
pub theme: SquareHoleAnchorItemResponse,
pub twist_rule: SquareHoleAnchorItemResponse,
pub shape_count: SquareHoleAnchorItemResponse,
pub difficulty: SquareHoleAnchorItemResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleCreatorConfigResponse {
pub theme_text: String,
pub twist_rule: String,
pub shape_count: u32,
pub difficulty: u32,
#[serde(default)]
pub shape_options: Vec<SquareHoleShapeOptionResponse>,
#[serde(default)]
pub hole_options: Vec<SquareHoleHoleOptionResponse>,
#[serde(default)]
pub background_prompt: String,
#[serde(default)]
pub cover_image_src: Option<String>,
#[serde(default)]
pub background_image_src: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleResultDraftResponse {
pub profile_id: String,
pub game_name: String,
pub theme_text: String,
pub twist_rule: String,
pub summary: String,
pub tags: Vec<String>,
#[serde(default)]
pub cover_image_src: Option<String>,
#[serde(default)]
pub background_prompt: String,
#[serde(default)]
pub background_image_src: Option<String>,
#[serde(default)]
pub shape_options: Vec<SquareHoleShapeOptionResponse>,
#[serde(default)]
pub hole_options: Vec<SquareHoleHoleOptionResponse>,
pub shape_count: u32,
pub difficulty: u32,
pub publish_ready: bool,
pub blockers: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleAgentMessageResponse {
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 SquareHoleSessionSnapshotResponse {
pub session_id: String,
pub current_turn: u32,
pub progress_percent: u32,
pub stage: String,
pub anchor_pack: SquareHoleAnchorPackResponse,
pub config: SquareHoleCreatorConfigResponse,
#[serde(default)]
pub draft: Option<SquareHoleResultDraftResponse>,
pub messages: Vec<SquareHoleAgentMessageResponse>,
#[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 SquareHoleSessionResponse {
pub session: SquareHoleSessionSnapshotResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleActionResponse {
pub session: SquareHoleSessionSnapshotResponse,
}
@@ -1,96 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct StartSquareHoleRunRequest {
pub profile_id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct DropSquareHoleShapeRequest {
#[serde(default)]
pub run_id: Option<String>,
pub hole_id: String,
pub client_snapshot_version: u64,
pub client_event_id: String,
pub dropped_at_ms: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct StopSquareHoleRunRequest {
pub client_action_id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleShapeSnapshotResponse {
pub shape_id: String,
pub shape_kind: String,
pub label: String,
pub target_hole_id: String,
pub color: String,
#[serde(default)]
pub image_src: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleHoleSnapshotResponse {
pub hole_id: String,
pub hole_kind: String,
pub label: String,
pub x: f32,
pub y: f32,
#[serde(default)]
pub image_src: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleDropFeedbackResponse {
pub accepted: bool,
#[serde(default)]
pub reject_reason: Option<String>,
pub message: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleRunSnapshotResponse {
pub run_id: String,
pub profile_id: String,
pub owner_user_id: String,
pub status: String,
pub snapshot_version: u64,
pub started_at_ms: u64,
pub duration_limit_ms: u64,
pub remaining_ms: u64,
pub total_shape_count: u32,
pub completed_shape_count: u32,
pub combo: u32,
pub best_combo: u32,
pub score: u32,
pub rule_label: String,
#[serde(default)]
pub background_image_src: Option<String>,
#[serde(default)]
pub current_shape: Option<SquareHoleShapeSnapshotResponse>,
pub holes: Vec<SquareHoleHoleSnapshotResponse>,
#[serde(default)]
pub last_feedback: Option<SquareHoleDropFeedbackResponse>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleRunResponse {
pub run: SquareHoleRunSnapshotResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleDropResponse {
pub feedback: SquareHoleDropFeedbackResponse,
pub run: SquareHoleRunSnapshotResponse,
}
@@ -1,113 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleShapeOptionResponse {
pub option_id: String,
pub shape_kind: String,
pub label: String,
pub target_hole_id: String,
pub image_prompt: String,
#[serde(default)]
pub image_src: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleHoleOptionResponse {
pub hole_id: String,
pub hole_kind: String,
pub label: String,
pub image_prompt: String,
#[serde(default)]
pub image_src: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PutSquareHoleWorkRequest {
pub game_name: String,
#[serde(default)]
pub theme_text: Option<String>,
pub twist_rule: String,
pub summary: String,
pub tags: Vec<String>,
#[serde(default)]
pub cover_image_src: Option<String>,
#[serde(default)]
pub background_prompt: Option<String>,
#[serde(default)]
pub background_image_src: Option<String>,
#[serde(default)]
pub shape_options: Option<Vec<SquareHoleShapeOptionResponse>>,
#[serde(default)]
pub hole_options: Option<Vec<SquareHoleHoleOptionResponse>>,
pub shape_count: u32,
pub difficulty: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RegenerateSquareHoleWorkImageRequest {
pub visual_asset_slot: String,
#[serde(default)]
pub visual_asset_option_id: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleWorkSummaryResponse {
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 twist_rule: String,
pub summary: String,
pub tags: Vec<String>,
#[serde(default)]
pub cover_image_src: Option<String>,
#[serde(default)]
pub background_prompt: String,
#[serde(default)]
pub background_image_src: Option<String>,
#[serde(default)]
pub shape_options: Vec<SquareHoleShapeOptionResponse>,
#[serde(default)]
pub hole_options: Vec<SquareHoleHoleOptionResponse>,
pub shape_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,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleWorkProfileResponse {
#[serde(flatten)]
pub summary: SquareHoleWorkSummaryResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleWorksResponse {
pub items: Vec<SquareHoleWorkSummaryResponse>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleWorkDetailResponse {
pub item: SquareHoleWorkProfileResponse,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SquareHoleWorkMutationResponse {
pub item: SquareHoleWorkProfileResponse,
}

Some files were not shown because too many files have changed in this diff Show More