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