//! 拼图领域模型。 //! //! 后续迁移拼图 Agent 会话、作品 profile 和运行态聚合时,只保留玩法规则; //! 图片生成、发布 HTTP shape 和排行榜适配留在外层。 use serde::{Deserialize, Serialize}; #[cfg(feature = "spacetime-types")] use spacetimedb::SpacetimeType; pub const PUZZLE_AGENT_SESSION_ID_PREFIX: &str = "puzzle-session-"; pub const PUZZLE_AGENT_MESSAGE_ID_PREFIX: &str = "puzzle-message-"; pub const PUZZLE_PROFILE_ID_PREFIX: &str = "puzzle-profile-"; pub const PUZZLE_RUN_ID_PREFIX: &str = "puzzle-run-"; pub const PUZZLE_MIN_TAG_COUNT: usize = 3; pub const PUZZLE_MAX_TAG_COUNT: usize = 6; pub const PUZZLE_FREEZE_TIME_DURATION_MS: u64 = 10_000; pub const PUZZLE_EXTEND_TIME_DURATION_MS: u64 = 60_000; pub const PUZZLE_NEXT_LEVEL_MODE_SAME_WORK: &str = "sameWork"; pub const PUZZLE_NEXT_LEVEL_MODE_SIMILAR_WORKS: &str = "similarWorks"; pub const PUZZLE_NEXT_LEVEL_MODE_NONE: &str = "none"; pub const PUZZLE_SUPPORTED_GRID_SIZES: [u32; 5] = [3, 4, 5, 6, 7]; pub(crate) const PUZZLE_INITIAL_SHUFFLE_ATTEMPTS: u64 = 64; // 中文注释:拼图难度只从关卡序号解析,避免切割规格和倒计时在不同入口各写一套。 #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct PuzzleLevelConfig { pub grid_size: u32, pub time_limit_ms: u64, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum PuzzleAgentStage { CollectingAnchors, DraftReady, ImageRefining, ReadyToPublish, Published, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum PuzzleAnchorStatus { Missing, Inferred, Confirmed, Locked, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum PuzzleAgentMessageRole { User, Assistant, System, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum PuzzleAgentMessageKind { Chat, Summary, ActionResult, Warning, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum PuzzlePublicationStatus { Draft, Published, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum PuzzleRuntimeLevelStatus { Playing, Cleared, Failed, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleAnchorItem { pub key: String, pub label: String, pub value: String, pub status: PuzzleAnchorStatus, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleAnchorPack { pub theme_promise: PuzzleAnchorItem, pub visual_subject: PuzzleAnchorItem, pub visual_mood: PuzzleAnchorItem, pub composition_hooks: PuzzleAnchorItem, pub tags_and_forbidden: PuzzleAnchorItem, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleCreatorIntent { pub source_mode: String, pub raw_messages_summary: String, pub theme_promise: String, pub visual_subject: String, pub visual_mood: Vec, pub composition_hooks: Vec, pub theme_tags: Vec, pub forbidden_directives: Vec, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleGeneratedImageCandidate { pub candidate_id: String, pub image_src: String, pub asset_id: String, pub prompt: String, pub actual_prompt: Option, pub source_type: String, pub selected: bool, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleDraftLevel { pub level_id: String, pub level_name: String, pub picture_description: String, #[serde(default)] pub picture_reference: Option, #[serde(default)] pub ui_background_prompt: Option, #[serde(default)] pub ui_background_image_src: Option, #[serde(default)] pub ui_background_image_object_key: Option, #[serde(default)] pub background_music: Option, pub candidates: Vec, pub selected_candidate_id: Option, pub cover_image_src: Option, pub cover_asset_id: Option, pub generation_status: String, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleAudioAsset { pub task_id: String, pub provider: String, #[serde(default)] pub asset_object_id: Option, #[serde(default)] pub asset_kind: Option, pub audio_src: String, #[serde(default)] pub prompt: Option, #[serde(default)] pub title: Option, #[serde(default)] pub updated_at: Option, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleResultDraft { #[serde(default)] pub work_title: String, #[serde(default)] pub work_description: String, pub level_name: String, pub summary: String, pub theme_tags: Vec, pub forbidden_directives: Vec, pub creator_intent: Option, pub anchor_pack: PuzzleAnchorPack, pub candidates: Vec, pub selected_candidate_id: Option, pub cover_image_src: Option, pub cover_asset_id: Option, pub generation_status: String, #[serde(default)] pub levels: Vec, #[serde(default)] pub form_draft: Option, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleFormDraft { pub work_title: Option, pub work_description: Option, pub picture_description: Option, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleResultPreviewBlocker { pub id: String, pub code: String, pub message: String, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleResultPreviewFinding { pub id: String, pub severity: String, pub code: String, pub message: String, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleResultPreviewEnvelope { pub draft: PuzzleResultDraft, pub blockers: Vec, pub quality_findings: Vec, pub publish_ready: bool, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleAgentMessageSnapshot { pub message_id: String, pub session_id: String, pub role: PuzzleAgentMessageRole, pub kind: PuzzleAgentMessageKind, pub text: String, pub created_at_micros: i64, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleAgentSuggestedAction { pub id: String, pub action_type: String, pub label: String, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleAgentSessionSnapshot { pub session_id: String, pub owner_user_id: String, pub seed_text: String, pub current_turn: u32, pub progress_percent: u32, pub stage: PuzzleAgentStage, pub anchor_pack: PuzzleAnchorPack, pub draft: Option, pub messages: Vec, pub last_assistant_reply: Option, pub published_profile_id: Option, pub suggested_actions: Vec, pub result_preview: Option, pub created_at_micros: i64, pub updated_at_micros: i64, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleWorkProfile { pub work_id: String, pub profile_id: String, pub owner_user_id: String, pub source_session_id: Option, pub author_display_name: String, #[serde(default)] pub work_title: String, #[serde(default)] pub work_description: String, pub level_name: String, pub summary: String, pub theme_tags: Vec, pub cover_image_src: Option, pub cover_asset_id: Option, #[serde(default)] pub levels: Vec, pub publication_status: PuzzlePublicationStatus, pub updated_at_micros: i64, pub published_at_micros: Option, #[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, #[serde(default)] pub point_incentive_total_half_points: u64, #[serde(default)] pub point_incentive_claimed_points: u64, pub publish_ready: bool, pub anchor_pack: PuzzleAnchorPack, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleCellPosition { pub row: u32, pub col: u32, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzlePieceState { pub piece_id: String, pub correct_row: u32, pub correct_col: u32, pub current_row: u32, pub current_col: u32, pub merged_group_id: Option, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleMergedGroupState { pub group_id: String, pub piece_ids: Vec, pub occupied_cells: Vec, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleLeaderboardEntry { pub rank: u32, pub nickname: String, pub elapsed_ms: u64, #[serde(default)] pub visible_tags: Vec, pub is_current_player: bool, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleBoardSnapshot { pub rows: u32, pub cols: u32, pub pieces: Vec, pub merged_groups: Vec, #[serde(default)] pub selected_piece_id: Option, #[serde(default)] pub all_tiles_resolved: bool, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PuzzleRuntimeLevelSnapshot { pub run_id: String, pub level_index: u32, #[serde(default)] pub level_id: Option, pub grid_size: u32, pub profile_id: String, pub level_name: String, pub author_display_name: String, pub theme_tags: Vec, pub cover_image_src: Option, #[serde(default)] pub ui_background_image_src: Option, #[serde(default)] pub background_music: Option, pub board: PuzzleBoardSnapshot, pub status: PuzzleRuntimeLevelStatus, #[serde(default)] pub started_at_ms: u64, #[serde(default)] pub cleared_at_ms: Option, #[serde(default)] pub elapsed_ms: Option, #[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, #[serde(default)] pub freeze_accumulated_ms: u64, #[serde(default)] pub freeze_started_at_ms: Option, #[serde(default)] pub freeze_until_ms: Option, #[serde(default)] pub leaderboard_entries: Vec, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PuzzleRunSnapshot { 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, pub previous_level_tags: Vec, pub current_level: Option, pub recommended_next_profile_id: Option, #[serde(default = "default_puzzle_next_level_mode")] pub next_level_mode: String, #[serde(default)] pub next_level_profile_id: Option, #[serde(default)] pub next_level_id: Option, #[serde(default)] pub recommended_next_works: Vec, #[serde(default)] pub leaderboard_entries: Vec, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PuzzleRecommendedNextWork { pub profile_id: String, pub level_name: String, pub author_display_name: String, pub theme_tags: Vec, pub cover_image_src: Option, pub similarity_score: f32, } pub(crate) fn default_puzzle_next_level_mode() -> String { PUZZLE_NEXT_LEVEL_MODE_NONE.to_string() } impl PuzzleAgentStage { pub fn as_str(self) -> &'static str { match self { Self::CollectingAnchors => "collecting_anchors", Self::DraftReady => "draft_ready", Self::ImageRefining => "image_refining", Self::ReadyToPublish => "ready_to_publish", Self::Published => "published", } } } impl PuzzleAnchorStatus { pub fn as_str(self) -> &'static str { match self { Self::Missing => "missing", Self::Inferred => "inferred", Self::Confirmed => "confirmed", Self::Locked => "locked", } } } impl PuzzleAgentMessageRole { pub fn as_str(self) -> &'static str { match self { Self::User => "user", Self::Assistant => "assistant", Self::System => "system", } } } impl PuzzleAgentMessageKind { pub fn as_str(self) -> &'static str { match self { Self::Chat => "chat", Self::Summary => "summary", Self::ActionResult => "action_result", Self::Warning => "warning", } } } impl PuzzlePublicationStatus { pub fn as_str(self) -> &'static str { match self { Self::Draft => "draft", Self::Published => "published", } } } impl PuzzleRuntimeLevelStatus { pub fn as_str(self) -> &'static str { match self { Self::Playing => "playing", Self::Cleared => "cleared", Self::Failed => "failed", } } }