This commit is contained in:
2026-05-08 11:44:42 +08:00
parent b08127031c
commit abf1f1ebea
249 changed files with 39411 additions and 887 deletions

View 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)
);
}
}