feat: refine wooden fish runtime generation

This commit is contained in:
2026-05-22 03:49:35 +08:00
parent d81cc49549
commit 5f1128540e
30 changed files with 804 additions and 126 deletions

View File

@@ -112,6 +112,7 @@ fn map_wooden_fish_work_snapshot(
hit_sound_prompt: snapshot.hit_sound_prompt.clone(),
floating_words: snapshot.floating_words.clone(),
hit_object_asset: snapshot.hit_object_asset.clone().map(map_image_asset),
background_asset: snapshot.background_asset.clone().map(map_image_asset),
hit_sound_asset: snapshot.hit_sound_asset.clone().map(map_audio_asset),
cover_image_src: empty_string_to_none(snapshot.cover_image_src.clone()),
generation_status: parse_generation_status(&snapshot.generation_status),
@@ -145,6 +146,7 @@ fn map_wooden_fish_work_snapshot(
},
draft,
hit_object_asset,
background_asset: snapshot.background_asset.map(map_image_asset),
hit_sound_asset,
floating_words: snapshot.floating_words,
})
@@ -163,6 +165,7 @@ fn map_wooden_fish_draft_snapshot(snapshot: WoodenFishDraftSnapshot) -> WoodenFi
hit_sound_prompt: snapshot.hit_sound_prompt,
floating_words: snapshot.floating_words,
hit_object_asset: snapshot.hit_object_asset.map(map_image_asset),
background_asset: snapshot.background_asset.map(map_image_asset),
hit_sound_asset: snapshot.hit_sound_asset.map(map_audio_asset),
cover_image_src: snapshot.cover_image_src,
generation_status: parse_generation_status(&snapshot.generation_status),

View File

@@ -18,6 +18,7 @@ pub struct WoodenFishDraftCompileInput {
pub hit_object_reference_image_src: Option<String>,
pub hit_sound_prompt: Option<String>,
pub hit_object_asset_json: Option<String>,
pub background_asset_json: Option<String>,
pub hit_sound_asset_json: Option<String>,
pub floating_words_json: Option<String>,
pub cover_image_src: Option<String>,

View File

@@ -21,6 +21,7 @@ pub struct WoodenFishDraftSnapshot {
pub hit_sound_prompt: Option<String>,
pub floating_words: Vec<String>,
pub hit_object_asset: Option<WoodenFishImageAssetSnapshot>,
pub background_asset: Option<WoodenFishImageAssetSnapshot>,
pub hit_sound_asset: Option<WoodenFishAudioAssetSnapshot>,
pub cover_image_src: Option<String>,
pub generation_status: String,

View File

@@ -23,6 +23,7 @@ pub struct WoodenFishGalleryViewRow {
pub hit_object_reference_image_src: Option<String>,
pub hit_sound_prompt: Option<String>,
pub hit_object_asset: Option<WoodenFishImageAssetSnapshot>,
pub background_asset: Option<WoodenFishImageAssetSnapshot>,
pub hit_sound_asset: Option<WoodenFishAudioAssetSnapshot>,
pub floating_words: Vec<String>,
pub cover_image_src: String,
@@ -57,6 +58,8 @@ pub struct WoodenFishGalleryViewRowCols {
pub hit_sound_prompt: __sdk::__query_builder::Col<WoodenFishGalleryViewRow, Option<String>>,
pub hit_object_asset:
__sdk::__query_builder::Col<WoodenFishGalleryViewRow, Option<WoodenFishImageAssetSnapshot>>,
pub background_asset:
__sdk::__query_builder::Col<WoodenFishGalleryViewRow, Option<WoodenFishImageAssetSnapshot>>,
pub hit_sound_asset:
__sdk::__query_builder::Col<WoodenFishGalleryViewRow, Option<WoodenFishAudioAssetSnapshot>>,
pub floating_words: __sdk::__query_builder::Col<WoodenFishGalleryViewRow, Vec<String>>,
@@ -92,6 +95,7 @@ impl __sdk::__query_builder::HasCols for WoodenFishGalleryViewRow {
),
hit_sound_prompt: __sdk::__query_builder::Col::new(table_name, "hit_sound_prompt"),
hit_object_asset: __sdk::__query_builder::Col::new(table_name, "hit_object_asset"),
background_asset: __sdk::__query_builder::Col::new(table_name, "background_asset"),
hit_sound_asset: __sdk::__query_builder::Col::new(table_name, "hit_sound_asset"),
floating_words: __sdk::__query_builder::Col::new(table_name, "floating_words"),
cover_image_src: __sdk::__query_builder::Col::new(table_name, "cover_image_src"),

View File

@@ -27,6 +27,7 @@ pub struct WoodenFishWorkProfileRow {
pub play_count: u32,
pub updated_at: __sdk::Timestamp,
pub published_at: Option<__sdk::Timestamp>,
pub background_asset_json: Option<String>,
}
impl __sdk::InModule for WoodenFishWorkProfileRow {
@@ -59,6 +60,8 @@ pub struct WoodenFishWorkProfileRowCols {
pub updated_at: __sdk::__query_builder::Col<WoodenFishWorkProfileRow, __sdk::Timestamp>,
pub published_at:
__sdk::__query_builder::Col<WoodenFishWorkProfileRow, Option<__sdk::Timestamp>>,
pub background_asset_json:
__sdk::__query_builder::Col<WoodenFishWorkProfileRow, Option<String>>,
}
impl __sdk::__query_builder::HasCols for WoodenFishWorkProfileRow {
@@ -100,6 +103,10 @@ impl __sdk::__query_builder::HasCols for WoodenFishWorkProfileRow {
play_count: __sdk::__query_builder::Col::new(table_name, "play_count"),
updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"),
published_at: __sdk::__query_builder::Col::new(table_name, "published_at"),
background_asset_json: __sdk::__query_builder::Col::new(
table_name,
"background_asset_json",
),
}
}
}

View File

@@ -22,6 +22,7 @@ pub struct WoodenFishWorkSnapshot {
pub hit_object_reference_image_src: Option<String>,
pub hit_sound_prompt: Option<String>,
pub hit_object_asset: Option<WoodenFishImageAssetSnapshot>,
pub background_asset: Option<WoodenFishImageAssetSnapshot>,
pub hit_sound_asset: Option<WoodenFishAudioAssetSnapshot>,
pub floating_words: Vec<String>,
pub cover_image_src: String,

View File

@@ -16,6 +16,7 @@ pub struct WoodenFishWorkUpdateInput {
pub hit_object_reference_image_src: Option<String>,
pub hit_sound_prompt: Option<String>,
pub hit_object_asset_json: Option<String>,
pub background_asset_json: Option<String>,
pub hit_sound_asset_json: Option<String>,
pub floating_words_json: Option<String>,
pub cover_image_src: Option<String>,

View File

@@ -529,6 +529,9 @@ fn merge_action_into_draft(
if let Some(asset) = payload.hit_object_asset.clone() {
draft.hit_object_asset = Some(asset);
}
if let Some(asset) = payload.background_asset.clone() {
draft.background_asset = Some(asset);
}
}
if matches!(
scope,
@@ -573,6 +576,7 @@ fn merge_action_into_draft(
&& payload.hit_object_asset.is_none()
{
draft.hit_object_asset = None;
draft.background_asset = None;
}
if draft.floating_words.is_empty() {
draft.floating_words = default_floating_words();
@@ -606,6 +610,9 @@ fn build_compile_input(
let hit_sound_asset = draft.hit_sound_asset.clone().ok_or_else(|| {
SpacetimeClientError::validation_failed("wooden fish hit sound asset 缺少真实生成资产")
})?;
let background_asset = draft.background_asset.clone().ok_or_else(|| {
SpacetimeClientError::validation_failed("wooden fish background asset 缺少真实生成资产")
})?;
Ok(WoodenFishDraftCompileInput {
session_id: current.session_id.clone(),
@@ -619,6 +626,7 @@ fn build_compile_input(
hit_object_reference_image_src: draft.hit_object_reference_image_src.clone(),
hit_sound_prompt: draft.hit_sound_prompt.clone(),
hit_object_asset_json: Some(json_string(&hit_object_asset)?),
background_asset_json: Some(json_string(&background_asset)?),
hit_sound_asset_json: Some(json_string(&hit_sound_asset)?),
floating_words_json: Some(json_string(&draft.floating_words)?),
cover_image_src: draft.cover_image_src.clone(),
@@ -644,6 +652,7 @@ fn build_update_input(
hit_object_reference_image_src: draft.hit_object_reference_image_src.clone(),
hit_sound_prompt: draft.hit_sound_prompt.clone(),
hit_object_asset_json: None,
background_asset_json: None,
hit_sound_asset_json: if include_hit_sound_asset {
draft
.hit_sound_asset
@@ -710,6 +719,7 @@ fn default_draft() -> WoodenFishDraftResponse {
hit_sound_prompt: Some(DEFAULT_HIT_SOUND_PROMPT.to_string()),
floating_words: default_floating_words(),
hit_object_asset: None,
background_asset: None,
hit_sound_asset: None,
cover_image_src: None,
generation_status: WoodenFishGenerationStatus::Draft,
@@ -796,6 +806,7 @@ mod tests {
let session = session_with_draft(draft_without_assets());
let mut payload = action(WoodenFishActionType::CompileDraft);
payload.hit_object_asset = Some(generated_hit_object_asset("generated-compile-object"));
payload.background_asset = Some(generated_background_asset("generated-compile-background"));
payload.hit_sound_asset = Some(generated_hit_sound_asset("generated-compile-sound"));
let (plan, draft) =
@@ -822,6 +833,13 @@ mod tests {
.unwrap_or("")
.contains("generated-compile-sound")
);
assert!(
input
.background_asset_json
.as_deref()
.unwrap_or("")
.contains("generated-compile-background")
);
assert_eq!(draft.generation_status, WoodenFishGenerationStatus::Ready);
}
@@ -830,6 +848,7 @@ mod tests {
let session = session_with_draft(draft_without_assets());
let mut payload = action(WoodenFishActionType::CompileDraft);
payload.hit_object_asset = Some(generated_hit_object_asset("generated-compile-object"));
payload.background_asset = Some(generated_background_asset("generated-compile-background"));
let error =
match build_wooden_fish_action_plan(&session, OWNER_USER_ID, &payload, NOW_MICROS) {
@@ -844,12 +863,33 @@ mod tests {
);
}
#[test]
fn wooden_fish_compile_requires_real_background_asset_from_api_server() {
let session = session_with_draft(draft_without_assets());
let mut payload = action(WoodenFishActionType::CompileDraft);
payload.hit_object_asset = Some(generated_hit_object_asset("generated-compile-object"));
payload.hit_sound_asset = Some(generated_hit_sound_asset("generated-compile-sound"));
let error =
match build_wooden_fish_action_plan(&session, OWNER_USER_ID, &payload, NOW_MICROS) {
Ok(_) => panic!("compile-draft should not publish without background asset"),
Err(error) => error,
};
assert!(
error
.to_string()
.contains("background asset 缺少真实生成资产")
);
}
#[test]
fn wooden_fish_action_regenerate_hit_object_replaces_only_object_asset() {
let session = session_with_draft(draft_with_assets());
let mut payload = action(WoodenFishActionType::RegenerateHitObject);
payload.hit_object_prompt = Some("新的敲击物".to_string());
payload.hit_object_asset = Some(generated_hit_object_asset("generated-object"));
payload.background_asset = Some(generated_background_asset("generated-background"));
let (plan, _draft) =
build_wooden_fish_action_plan(&session, OWNER_USER_ID, &payload, NOW_MICROS)
@@ -886,6 +926,13 @@ mod tests {
.unwrap_or("")
.contains("old-sound")
);
assert!(
input
.background_asset_json
.as_deref()
.unwrap_or("")
.contains("generated-background")
);
}
#[test]
@@ -930,6 +977,7 @@ mod tests {
hit_object_prompt: None,
hit_object_reference_image_src: None,
hit_object_asset: None,
background_asset: None,
hit_sound_prompt: None,
hit_sound_asset: None,
floating_words: None,
@@ -969,6 +1017,21 @@ mod tests {
}
}
fn generated_background_asset(asset_id: &str) -> WoodenFishImageAsset {
WoodenFishImageAsset {
asset_id: asset_id.to_string(),
image_src: "/generated-wooden-fish-assets/real-profile/background/image.png"
.to_string(),
image_object_key: "generated-wooden-fish-assets/real-profile/background/image.png"
.to_string(),
asset_object_id: format!("{asset_id}-asset"),
generation_provider: "image2".to_string(),
prompt: "新的敲击背景".to_string(),
width: 1024,
height: 1536,
}
}
fn generated_hit_sound_asset(asset_id: &str) -> WoodenFishAudioAsset {
WoodenFishAudioAsset {
asset_id: asset_id.to_string(),
@@ -995,6 +1058,16 @@ mod tests {
width: 1024,
height: 1024,
}),
background_asset: Some(WoodenFishImageAsset {
asset_id: "old-background".to_string(),
image_src: "/generated-wooden-fish-assets/old-background.png".to_string(),
image_object_key: "generated-wooden-fish-assets/old-background.png".to_string(),
asset_object_id: "old-background-asset".to_string(),
generation_provider: "image2".to_string(),
prompt: "旧背景".to_string(),
width: 1024,
height: 1536,
}),
hit_sound_asset: Some(WoodenFishAudioAsset {
asset_id: "old-sound".to_string(),
audio_src: "/generated-wooden-fish-assets/old-sound.mp3".to_string(),
@@ -1023,6 +1096,7 @@ mod tests {
hit_sound_prompt: Some("旧音效".to_string()),
floating_words: default_floating_words(),
hit_object_asset: None,
background_asset: None,
hit_sound_asset: None,
cover_image_src: None,
generation_status: WoodenFishGenerationStatus::Draft,