Refine creation progress and wooden fish runtime

This commit is contained in:
2026-05-27 21:56:19 +08:00
parent 95e702d670
commit 9cc36ea99c
9 changed files with 209 additions and 32 deletions

View File

@@ -758,7 +758,7 @@ fn build_wooden_fish_hit_object_prompt(prompt: &str) -> String {
fn build_wooden_fish_background_prompt(prompt: &str) -> String {
format!(
"生成敲木鱼背景,要求主题画风与参考图保持高度一致,背景元素和颜色搭配与主题对应,木鱼预设在屏幕中央位置,木鱼主体周围元素保持干净,背景氛围围绕外围设计,背景环境图中不包含新木鱼物品,背景氛围中不增加木槌互动物品。尺寸竖屏9:16。参考图必须是第一步敲击物抠图完成后的透明图不继承任何绿色底色、绿幕底色或纯绿色画布并要求最终输出完整不透明的背景环境图。中央主体预留区必须保持干净画面中央 40% 区域禁止出现主题主体、主体局部特写、主体轮廓影子、重复元素或主题主体的局部碎片;主题元素只允许出现在外围氛围,不得把主题物品画在画面中央,也不要把主题物品作为背景中心装饰。\n主题为:{}",
"生成敲木鱼背景,要求主题画风与参考图保持高度一致,背景元素和颜色搭配与主题对应,只生成竖屏背景环境图,不生成、不描绘、不暗示新木鱼物品本体,也不要出现木槌互动物品。尺寸竖屏9:16。参考图必须是第一步敲击物抠图完成后的透明图不继承任何绿色底色、绿幕底色或纯绿色画布并要求最终输出完整不透明的背景环境图。中央主体预留区必须保持干净中央区域是运行态叠放敲击物的留白区域,画面中央 40% 区域禁止出现主题主体、主体局部特写、主体轮廓影子、重复元素或主题主体的局部碎片;主题元素只允许出现在外围氛围,不得把主题物品画在画面中央,也不要把主题物品作为背景中心装饰。\n主题为:{}",
clean_string(prompt, DEFAULT_HIT_OBJECT_PROMPT)
)
}
@@ -1228,14 +1228,17 @@ mod tests {
fn wooden_fish_background_prompt_uses_hidden_image2_flow() {
let prompt = build_wooden_fish_background_prompt("苹果");
assert!(prompt.contains(
"生成敲木鱼背景,要求主题,画风与参考图保持高度一致,背景元素和颜色搭配与主题对应,木鱼预设在屏幕中央位置,木鱼主体周围元素保持干净,背景氛围围绕外围设计,背景环境图中不包含新木鱼物品,背景氛围中不增加木槌互动物品。"
));
assert!(prompt.contains("只生成竖屏背景环境图"));
assert!(prompt.contains("生成、不描绘、不暗示新木鱼物品本体"));
assert!(prompt.contains("不要出现木槌互动物品"));
assert!(!prompt.contains("木鱼预设在屏幕中央位置"));
assert!(!prompt.contains("木鱼主体周围元素保持干净"));
assert!(prompt.contains("尺寸竖屏9:16"));
assert!(prompt.contains("抠图完成后的透明图"));
assert!(prompt.contains("不继承任何绿色底色"));
assert!(prompt.contains("完整不透明的背景环境图"));
assert!(prompt.contains("中央主体预留区"));
assert!(prompt.contains("中央区域是运行态叠放敲击物的留白区域"));
assert!(prompt.contains("禁止出现主题主体"));
assert!(prompt.contains("苹果"));
assert!(prompt.contains("不得把主题物品画在画面中央"));

View File

@@ -13,6 +13,10 @@ use serde::Serialize;
use serde::de::DeserializeOwned;
use spacetimedb::AnonymousViewContext;
const DEFAULT_WOODEN_FISH_BACK_BUTTON_ASSET_ID: &str = "wooden-fish-default-back-button";
const DEFAULT_WOODEN_FISH_BACK_BUTTON_IMAGE_SRC: &str = "/UI/11_left_arrow.png";
const DEFAULT_WOODEN_FISH_BACK_BUTTON_IMAGE_OBJECT_KEY: &str = "public/UI/11_left_arrow.png";
#[spacetimedb::view(accessor = wooden_fish_gallery_view, public)]
pub fn wooden_fish_gallery_view(ctx: &AnonymousViewContext) -> Vec<WoodenFishGalleryViewRow> {
let mut items = ctx
@@ -593,10 +597,14 @@ fn start_wooden_fish_run_tx(
input: WoodenFishRunStartInput,
) -> Result<WoodenFishRunSnapshot, String> {
require_non_empty(&input.run_id, "wooden_fish run_id")?;
let work = find_work(ctx, &input.profile_id)?;
let stored_work = find_work(ctx, &input.profile_id)?;
let work = backfill_historical_runtime_content(&stored_work);
if !is_publish_ready(&work) {
return Err("敲木鱼运行态需要完整作品配置".to_string());
}
if work.back_button_asset_json != stored_work.back_button_asset_json {
replace_work(ctx, &stored_work, clone_work(&work));
}
let snapshot = WoodenFishRunSnapshot {
run_id: input.run_id.clone(),
profile_id: input.profile_id.clone(),
@@ -740,6 +748,7 @@ fn build_session_snapshot(
}
fn build_work_snapshot(row: &WoodenFishWorkProfileRow) -> Result<WoodenFishWorkSnapshot, String> {
let row = backfill_historical_runtime_content(row);
Ok(WoodenFishWorkSnapshot {
work_id: row.work_id.clone(),
profile_id: row.profile_id.clone(),
@@ -775,7 +784,7 @@ fn build_work_snapshot(row: &WoodenFishWorkProfileRow) -> Result<WoodenFishWorkS
)),
cover_image_src: row.cover_image_src.clone(),
publication_status: row.publication_status.clone(),
publish_ready: is_publish_ready(row),
publish_ready: is_publish_ready(&row),
play_count: row.play_count,
generation_status: row.generation_status.clone(),
updated_at_micros: row.updated_at.to_micros_since_unix_epoch(),
@@ -1009,6 +1018,15 @@ fn insert_event(
}
fn is_publish_ready(row: &WoodenFishWorkProfileRow) -> bool {
is_publish_ready_except_back_button(row)
&& row
.back_button_asset_json
.as_deref()
.and_then(clean_optional)
.is_some()
}
fn is_publish_ready_except_back_button(row: &WoodenFishWorkProfileRow) -> bool {
!row.work_title.trim().is_empty()
&& !row.hit_object_asset_json.trim().is_empty()
&& row
@@ -1016,14 +1034,40 @@ fn is_publish_ready(row: &WoodenFishWorkProfileRow) -> bool {
.as_deref()
.and_then(clean_optional)
.is_some()
&& row
&& !row.hit_sound_asset_json.trim().is_empty()
&& !row.floating_words_json.trim().is_empty()
&& row.generation_status == WOODEN_FISH_GENERATION_READY
}
fn backfill_historical_runtime_content(row: &WoodenFishWorkProfileRow) -> WoodenFishWorkProfileRow {
if row.publication_status != WOODEN_FISH_PUBLICATION_PUBLISHED
|| !is_publish_ready_except_back_button(row)
|| row
.back_button_asset_json
.as_deref()
.and_then(clean_optional)
.is_some()
&& !row.hit_sound_asset_json.trim().is_empty()
&& !row.floating_words_json.trim().is_empty()
&& row.generation_status == WOODEN_FISH_GENERATION_READY
{
return clone_work(row);
}
WoodenFishWorkProfileRow {
back_button_asset_json: Some(to_json_string(&default_wooden_fish_back_button_asset())),
..clone_work(row)
}
}
fn default_wooden_fish_back_button_asset() -> WoodenFishImageAssetSnapshot {
WoodenFishImageAssetSnapshot {
asset_id: DEFAULT_WOODEN_FISH_BACK_BUTTON_ASSET_ID.to_string(),
image_src: DEFAULT_WOODEN_FISH_BACK_BUTTON_IMAGE_SRC.to_string(),
image_object_key: DEFAULT_WOODEN_FISH_BACK_BUTTON_IMAGE_OBJECT_KEY.to_string(),
asset_object_id: DEFAULT_WOODEN_FISH_BACK_BUTTON_ASSET_ID.to_string(),
generation_provider: "bundled-default".to_string(),
prompt: "历史敲木鱼默认返回按钮".to_string(),
width: 28,
height: 28,
}
}
fn default_config_from_input(
@@ -1288,3 +1332,82 @@ fn clone_run(row: &WoodenFishRuntimeRunRow) -> WoodenFishRuntimeRunRow {
updated_at: row.updated_at,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn historical_published_work_without_back_button_gets_runtime_backfill() {
let row = published_ready_work_without_back_button();
assert!(!is_publish_ready(&row));
let repaired = backfill_historical_runtime_content(&row);
let snapshot = build_work_snapshot(&repaired).expect("历史作品补齐后应可映射运行态快照");
assert!(is_publish_ready(&repaired));
assert!(snapshot.publish_ready);
assert_eq!(
snapshot
.back_button_asset
.as_ref()
.map(|asset| asset.image_src.as_str()),
Some("/UI/11_left_arrow.png")
);
}
fn published_ready_work_without_back_button() -> WoodenFishWorkProfileRow {
let now = Timestamp::from_micros_since_unix_epoch(1_770_000_000_000_000);
WoodenFishWorkProfileRow {
profile_id: "wooden-fish-profile-history".to_string(),
work_id: "wooden-fish-profile-history".to_string(),
owner_user_id: "user-history".to_string(),
source_session_id: "wooden-fish-session-history".to_string(),
author_display_name: "敲木鱼玩家".to_string(),
work_title: "今日敲木鱼".to_string(),
work_description: String::new(),
theme_tags_json: to_json_string(&vec!["敲木鱼".to_string(), "解压".to_string()]),
hit_object_prompt: "默认敲击物图案,圆润木质质感,透明背景".to_string(),
hit_object_reference_image_src: String::new(),
hit_sound_prompt: String::new(),
hit_object_asset_json: to_json_string(&WoodenFishImageAssetSnapshot {
asset_id: "wooden-fish-hit-object-history".to_string(),
image_src: "/wooden-fish/default-hit-object.png".to_string(),
image_object_key: "public/wooden-fish/default-hit-object.png".to_string(),
asset_object_id: "wooden-fish-hit-object-history".to_string(),
generation_provider: "bundled-default".to_string(),
prompt: "默认敲击物图案".to_string(),
width: 1024,
height: 1024,
}),
hit_sound_asset_json: to_json_string(&WoodenFishAudioAssetSnapshot {
asset_id: "wooden-fish-hit-sound-history".to_string(),
audio_src: "/wooden-fish/default-hit-sound.mp3".to_string(),
audio_object_key: "public/wooden-fish/default-hit-sound.mp3".to_string(),
asset_object_id: "wooden-fish-hit-sound-history".to_string(),
source: "bundled-default".to_string(),
prompt: Some("默认木鱼音".to_string()),
duration_ms: Some(3_000),
}),
floating_words_json: to_json_string(&default_floating_words()),
cover_image_src: "/wooden-fish/default-hit-object.png".to_string(),
generation_status: WOODEN_FISH_GENERATION_READY.to_string(),
publication_status: WOODEN_FISH_PUBLICATION_PUBLISHED.to_string(),
play_count: 0,
updated_at: now,
published_at: Some(now),
background_asset_json: Some(to_json_string(&WoodenFishImageAssetSnapshot {
asset_id: "wooden-fish-background-history".to_string(),
image_src: "/generated-wooden-fish-assets/history/background/image.png".to_string(),
image_object_key: "generated-wooden-fish-assets/history/background/image.png"
.to_string(),
asset_object_id: "wooden-fish-background-history".to_string(),
generation_provider: "image2".to_string(),
prompt: "历史背景".to_string(),
width: 1024,
height: 1536,
})),
back_button_asset_json: None,
}
}
}