Refine creation progress and wooden fish runtime
This commit is contained in:
@@ -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("不得把主题物品画在画面中央"));
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user