205 lines
6.7 KiB
Rust
205 lines
6.7 KiB
Rust
//! 拼图创意 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));
|
||
}
|
||
}
|