1
This commit is contained in:
208
server-rs/crates/module-puzzle/src/creative_templates.rs
Normal file
208
server-rs/crates/module-puzzle/src/creative_templates.rs
Normal file
@@ -0,0 +1,208 @@
|
||||
//! 拼图创意 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<String>,
|
||||
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<PuzzleCreativeDraftEditableFieldPath>,
|
||||
pub image_policy: PuzzleCreativeImageGenerationPolicy,
|
||||
}
|
||||
|
||||
fn shared_required_draft_fields() -> Vec<PuzzleCreativeDraftEditableFieldPath> {
|
||||
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<PuzzleCreativeTemplateProtocol> {
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user