//! 拼图创意 Agent 模板协议。 //! //! 这里只保存拼图模块自己的模板事实,HTTP / SSE 展示字段由 api-server //! 再映射到 shared-contracts,避免通用 Agent 复制拼图模板规则。 use serde::{Deserialize, Serialize}; pub const PUZZLE_PHASE1_TEMPLATE_ID: &str = "puzzle.default-creative"; pub const PUZZLE_PHASE1_TEMPLATE_TITLE: &str = "创意拼图"; pub const PUZZLE_FAMILY_KEEPSTAKE_TEMPLATE_ID: &str = "puzzle.family-keepsake"; pub const PUZZLE_TRAVEL_MEMORY_TEMPLATE_ID: &str = "puzzle.travel-memory"; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum PuzzleCreativePricingUnit { Point, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum PuzzleCreativeSupportedLevelMode { Single, Multi, SingleOrMulti, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum PuzzleCreativeLevelGenerationMode { SingleLevel, MultiLevel, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PuzzleCreativeCostRange { pub min_points: u32, pub max_points: u32, pub pricing_unit: PuzzleCreativePricingUnit, pub reason: String, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum PuzzleCreativeDraftEditableFieldPath { #[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, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PuzzleCreativeImageGenerationPolicy { 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, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PuzzleCreativeTemplateProtocol { pub template_id: String, pub title: String, pub summary: String, pub preview_image_src: Option, pub supported_level_mode: PuzzleCreativeSupportedLevelMode, pub min_level_count: u32, pub max_level_count: u32, pub default_level_count: u32, pub cost_range: PuzzleCreativeCostRange, pub required_draft_fields: Vec, pub image_policy: PuzzleCreativeImageGenerationPolicy, } fn shared_required_draft_fields() -> Vec { vec![ PuzzleCreativeDraftEditableFieldPath::WorkTitle, PuzzleCreativeDraftEditableFieldPath::WorkDescription, PuzzleCreativeDraftEditableFieldPath::WorkTags, PuzzleCreativeDraftEditableFieldPath::LevelName, PuzzleCreativeDraftEditableFieldPath::LevelPictureDescription, PuzzleCreativeDraftEditableFieldPath::LevelPictureReference, ] } fn shared_image_policy() -> PuzzleCreativeImageGenerationPolicy { PuzzleCreativeImageGenerationPolicy { allow_uploaded_image_directly: true, allow_generated_images: true, allow_per_level_reference_image: true, default_candidate_count_per_level: 1, } } fn build_template( template_id: &str, title: &str, summary: &str, default_level_count: u32, min_points: u32, max_points: u32, reason: &str, ) -> PuzzleCreativeTemplateProtocol { PuzzleCreativeTemplateProtocol { template_id: template_id.to_string(), title: title.to_string(), summary: summary.to_string(), preview_image_src: None, supported_level_mode: PuzzleCreativeSupportedLevelMode::SingleOrMulti, min_level_count: 1, max_level_count: 6, default_level_count, cost_range: PuzzleCreativeCostRange { min_points, max_points, pricing_unit: PuzzleCreativePricingUnit::Point, reason: reason.to_string(), }, required_draft_fields: shared_required_draft_fields(), image_policy: shared_image_policy(), } } pub fn retrieve_puzzle_template_catalog() -> Vec { vec![ build_template( PUZZLE_PHASE1_TEMPLATE_ID, PUZZLE_PHASE1_TEMPLATE_TITLE, "把图文灵感整理成可编辑、可试玩的拼图草稿。", 1, 2, 12, "按关卡数和每关图片生成次数估算,实际扣费以后端任务结算为准", ), build_template( PUZZLE_FAMILY_KEEPSTAKE_TEMPLATE_ID, "家庭纪念拼图", "把合影、节日或成长瞬间做成温暖的纪念拼图。", 3, 4, 14, "按纪念主题多关卡和图片候选估算,实际扣费以后端任务结算为准", ), build_template( PUZZLE_TRAVEL_MEMORY_TEMPLATE_ID, "旅行记忆拼图", "把一次出行拆成地点、风景和故事节点拼图。", 3, 4, 16, "按旅行节点和每关图片生成次数估算,实际扣费以后端任务结算为准", ), ] } #[cfg(test)] mod tests { use super::*; #[test] fn phase1_template_contains_cost_range_and_editable_fields() { let template = retrieve_puzzle_template_catalog() .into_iter() .find(|template| template.template_id == PUZZLE_PHASE1_TEMPLATE_ID) .expect("template should exist"); assert_eq!(template.template_id, PUZZLE_PHASE1_TEMPLATE_ID); assert_eq!(template.cost_range.min_points, 2); assert_eq!(template.cost_range.max_points, 12); assert!( template .required_draft_fields .contains(&PuzzleCreativeDraftEditableFieldPath::LevelPictureReference) ); } #[test] fn catalog_exposes_multiple_phase1_puzzle_subtemplates() { let catalog = retrieve_puzzle_template_catalog(); assert!(catalog.len() >= 3); assert!( catalog .iter() .any(|template| template.template_id == PUZZLE_FAMILY_KEEPSTAKE_TEMPLATE_ID) ); assert!( catalog .iter() .any(|template| template.template_id == PUZZLE_TRAVEL_MEMORY_TEMPLATE_ID) ); assert!(catalog.iter().all(|template| template.supported_level_mode == PuzzleCreativeSupportedLevelMode::SingleOrMulti)); } }