This commit is contained in:
2026-05-05 14:40:41 +08:00
parent e847fcea6f
commit 07e777fef8
76 changed files with 4246 additions and 444 deletions

View File

@@ -125,10 +125,7 @@ pub fn build_form_anchor_pack(title: &str, picture_description: &str) -> PuzzleA
pack.visual_mood.status = PuzzleAnchorStatus::Inferred;
pack.composition_hooks.value = "主体轮廓、色块分区、局部细节".to_string();
pack.composition_hooks.status = PuzzleAnchorStatus::Inferred;
pack.tags_and_forbidden.value = build_form_tags_and_forbidden(
normalized_title.as_deref().unwrap_or(""),
normalized_description.as_deref().unwrap_or(""),
);
pack.tags_and_forbidden.value = build_form_tags_and_forbidden(title, picture_description);
pack.tags_and_forbidden.status = PuzzleAnchorStatus::Inferred;
pack
@@ -178,12 +175,12 @@ pub fn compile_result_draft_from_seed(
seed_text: Option<&str>,
) -> PuzzleResultDraft {
let creator_intent = build_creator_intent(anchor_pack, messages);
let normalized_tags = normalize_theme_tags(creator_intent.theme_tags.clone());
let work_title = build_work_title(anchor_pack);
let normalized_tags = resolve_initial_theme_tags(seed_text, &creator_intent);
let work_description = resolve_work_description(seed_text, anchor_pack);
let picture_description = fallback_text(&anchor_pack.visual_subject.value, "画面主体");
let level_name =
build_level_name_from_picture(picture_description.as_str(), &normalized_tags, 1);
let work_title = resolve_work_title(seed_text, anchor_pack, &level_name);
let level = PuzzleDraftLevel {
level_id: "puzzle-level-1".to_string(),
level_name: level_name.clone(),
@@ -238,16 +235,6 @@ pub fn build_form_draft_from_parts(
let work_description = work_description.and_then(|value| normalize_required_string(&value));
let picture_description =
picture_description.and_then(|value| normalize_required_string(&value));
let title_for_tags = work_title.as_deref().unwrap_or("");
let picture_for_tags = picture_description.as_deref().unwrap_or("");
let mut tags = normalize_theme_tags(derive_form_theme_tags(title_for_tags, picture_for_tags));
if tags.is_empty() {
tags = vec![
"拼图".to_string(),
"插画".to_string(),
"清晰构图".to_string(),
];
}
let summary = work_description.clone().unwrap_or_default();
let level = PuzzleDraftLevel {
level_id: "puzzle-level-1".to_string(),
@@ -266,7 +253,7 @@ pub fn build_form_draft_from_parts(
work_description: summary.clone(),
level_name: String::new(),
summary,
theme_tags: tags,
theme_tags: Vec::new(),
forbidden_directives: Vec::new(),
creator_intent: None,
anchor_pack: anchor_pack.clone(),
@@ -349,12 +336,6 @@ pub fn apply_selected_candidate(
}
pub fn normalize_puzzle_draft(mut draft: PuzzleResultDraft) -> PuzzleResultDraft {
if draft.work_title.trim().is_empty() {
draft.work_title = fallback_text(&draft.anchor_pack.theme_promise.value, &draft.level_name);
}
if draft.work_description.trim().is_empty() {
draft.work_description = draft.summary.clone();
}
if draft.levels.is_empty() {
draft.levels = vec![PuzzleDraftLevel {
level_id: "puzzle-level-1".to_string(),
@@ -383,9 +364,6 @@ pub fn sync_primary_level_fields(draft: &mut PuzzleResultDraft) {
draft.cover_asset_id = primary_level.cover_asset_id.clone();
draft.generation_status = primary_level.generation_status.clone();
}
if draft.work_description.trim().is_empty() {
draft.work_description = draft.summary.clone();
}
draft.summary = draft.work_description.clone();
if draft.form_draft.is_some() {
draft.form_draft = Some(PuzzleFormDraft {
@@ -642,23 +620,19 @@ pub fn apply_publish_overrides_to_draft(
) -> Result<PuzzleResultDraft, PuzzleFieldError> {
let mut next_draft = normalize_puzzle_draft(draft.clone());
if let Some(next_work_title) = work_title
&& let Some(normalized_work_title) = normalize_required_string(&next_work_title)
{
next_draft.work_title = normalized_work_title;
if let Some(next_work_title) = work_title {
next_draft.work_title = normalize_required_string(&next_work_title).unwrap_or_default();
}
if let Some(next_work_description) = work_description
&& let Some(normalized_work_description) = normalize_required_string(&next_work_description)
{
next_draft.work_description = normalized_work_description;
if let Some(next_work_description) = work_description {
next_draft.work_description =
normalize_required_string(&next_work_description).unwrap_or_default();
}
if let Some(next_level_name) = level_name
&& let Some(normalized_level_name) = normalize_required_string(&next_level_name)
{
if let Some(next_level_name) = level_name {
if let Some(primary_level) = next_draft.levels.first_mut() {
primary_level.level_name = normalized_level_name;
primary_level.level_name =
normalize_required_string(&next_level_name).unwrap_or_default();
}
}
@@ -689,7 +663,7 @@ pub fn apply_publish_overrides_to_draft(
pub fn normalize_puzzle_levels(
levels: Vec<PuzzleDraftLevel>,
theme_tags: &[String],
_theme_tags: &[String],
) -> Result<Vec<PuzzleDraftLevel>, PuzzleFieldError> {
let mut normalized_levels = Vec::new();
for (index, mut level) in levels.into_iter().enumerate() {
@@ -697,9 +671,7 @@ pub fn normalize_puzzle_levels(
.unwrap_or_else(|| format!("puzzle-level-{}", index + 1));
let picture_description = normalize_required_string(&level.picture_description)
.unwrap_or_else(|| format!("{}关画面", index + 1));
let level_name = normalize_required_string(&level.level_name).unwrap_or_else(|| {
build_level_name_from_picture(picture_description.as_str(), theme_tags, index + 1)
});
let level_name = normalize_required_string(&level.level_name).unwrap_or_default();
level.level_id = level_id;
level.level_name = level_name;
level.picture_description = picture_description;
@@ -1959,21 +1931,67 @@ fn build_result_summary(anchor_pack: &PuzzleAnchorPack) -> String {
}
fn resolve_work_description(seed_text: Option<&str>, anchor_pack: &PuzzleAnchorPack) -> String {
seed_text
.and_then(parse_form_seed_text)
.and_then(|parts| {
parts
.work_description
.or(parts.picture_description)
.or(parts.work_title)
})
.unwrap_or_else(|| build_result_summary(anchor_pack))
if let Some(parts) = seed_text.and_then(parse_form_seed_text) {
if parts.picture_description.is_some()
&& parts.work_title.is_none()
&& parts.work_description.is_none()
{
return String::new();
}
return parts
.work_description
.unwrap_or_else(|| build_result_summary(anchor_pack));
}
build_result_summary(anchor_pack)
}
fn build_work_title(anchor_pack: &PuzzleAnchorPack) -> String {
fallback_text(&anchor_pack.theme_promise.value, "奇景拼图")
}
fn resolve_work_title(
seed_text: Option<&str>,
anchor_pack: &PuzzleAnchorPack,
level_name: &str,
) -> String {
seed_text
.and_then(parse_form_seed_text)
.and_then(|parts| {
parts
.work_title
.or_else(|| normalize_required_string(level_name))
})
.unwrap_or_else(|| build_work_title(anchor_pack))
}
fn resolve_initial_theme_tags(
seed_text: Option<&str>,
creator_intent: &PuzzleCreatorIntent,
) -> Vec<String> {
if let Some(parts) = seed_text.and_then(parse_form_seed_text) {
if parts.picture_description.is_some()
&& parts.work_title.is_none()
&& parts.work_description.is_none()
{
return Vec::new();
}
let derived_tags = normalize_theme_tags(derive_form_theme_tags(
parts
.work_title
.as_deref()
.unwrap_or(creator_intent.theme_promise.as_str()),
parts
.picture_description
.as_deref()
.unwrap_or(creator_intent.visual_subject.as_str()),
));
if !derived_tags.is_empty() {
return derived_tags;
}
}
normalize_theme_tags(creator_intent.theme_tags.clone())
}
fn extract_forbidden_directive(source: &str) -> String {
if let Some((_, tail)) = source.split_once('') {
return normalize_required_string(tail).unwrap_or_else(|| "禁止标题字".to_string());
@@ -1996,7 +2014,7 @@ fn build_level_name_from_picture(
}
}
if let Some(tag) = normalized_tags.first() {
return format!("{tag}{level_index}");
return format!("{tag}画面");
}
format!("{level_index}")
}
@@ -2912,6 +2930,23 @@ mod tests {
assert!(draft.theme_tags.len() >= PUZZLE_MIN_TAG_COUNT);
}
#[test]
fn picture_only_form_seed_uses_level_name_as_work_title_and_empty_metadata() {
let seed_text = "画面描述:一只猫在雨夜灯牌下回头。";
let anchor_pack = infer_anchor_pack(seed_text, None);
let draft = compile_result_draft_from_seed(&anchor_pack, &[], Some(seed_text));
assert_eq!(draft.level_name, "猫画面");
assert_eq!(draft.work_title, "猫画面");
assert_eq!(draft.work_description, "");
assert_eq!(draft.summary, "");
assert!(draft.theme_tags.is_empty());
assert_eq!(
draft.levels[0].picture_description,
"一只猫在雨夜灯牌下回头。"
);
}
#[test]
fn form_seed_keeps_multiline_picture_description() {
let anchor_pack = infer_anchor_pack(
@@ -3452,4 +3487,34 @@ mod tests {
assert_eq!(error, PuzzleFieldError::InvalidTagCount);
}
#[test]
fn apply_publish_overrides_preserves_empty_level_name_for_publish_gate() {
let anchor_pack = infer_anchor_pack("雨夜猫咪神庙", Some("雨夜猫咪神庙"));
let draft = compile_result_draft(&anchor_pack, &[]);
let mut levels = draft.levels.clone();
levels[0].level_name = " ".to_string();
let updated = apply_publish_overrides_to_draft(
&draft,
Some("雨夜猫塔作品".to_string()),
Some("作品描述。".to_string()),
Some("".to_string()),
Some("作品描述。".to_string()),
Some(vec![
"雨夜".to_string(),
"猫咪".to_string(),
"遗迹".to_string(),
]),
Some(levels),
)
.expect("empty level name should remain editable before publish gate");
assert_eq!(updated.levels[0].level_name, "");
assert!(
validate_publish_requirements(&updated, Some("玩家"))
.iter()
.any(|blocker| blocker.code == "MISSING_LEVEL_NAME")
);
}
}