feat: unify phase one creation flow
This commit is contained in:
@@ -100,6 +100,7 @@ fn map_wooden_fish_session_snapshot(
|
||||
fn map_wooden_fish_work_snapshot(
|
||||
snapshot: WoodenFishWorkSnapshot,
|
||||
) -> Result<WoodenFishWorkProfileResponse, SpacetimeClientError> {
|
||||
let generation_status = parse_generation_status(&snapshot.generation_status);
|
||||
let draft = WoodenFishDraftResponse {
|
||||
template_id: "wooden-fish".to_string(),
|
||||
template_name: "敲木鱼".to_string(),
|
||||
@@ -116,15 +117,23 @@ fn map_wooden_fish_work_snapshot(
|
||||
back_button_asset: snapshot.back_button_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),
|
||||
generation_status: generation_status.clone(),
|
||||
};
|
||||
let hit_object_asset = draft
|
||||
.hit_object_asset
|
||||
.clone()
|
||||
.or_else(|| {
|
||||
matches!(generation_status, WoodenFishGenerationStatus::Failed)
|
||||
.then(default_failed_hit_object_asset)
|
||||
})
|
||||
.ok_or_else(|| SpacetimeClientError::missing_snapshot("wooden fish hit object asset"))?;
|
||||
let hit_sound_asset = draft
|
||||
.hit_sound_asset
|
||||
.clone()
|
||||
.or_else(|| {
|
||||
matches!(generation_status, WoodenFishGenerationStatus::Failed)
|
||||
.then(default_failed_hit_sound_asset)
|
||||
})
|
||||
.ok_or_else(|| SpacetimeClientError::missing_snapshot("wooden fish hit sound asset"))?;
|
||||
Ok(WoodenFishWorkProfileResponse {
|
||||
summary: WoodenFishWorkSummaryResponse {
|
||||
@@ -143,7 +152,7 @@ fn map_wooden_fish_work_snapshot(
|
||||
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
|
||||
published_at: snapshot.published_at_micros.map(format_timestamp_micros),
|
||||
publish_ready: snapshot.publish_ready,
|
||||
generation_status: parse_generation_status(&snapshot.generation_status),
|
||||
generation_status,
|
||||
},
|
||||
draft,
|
||||
hit_object_asset,
|
||||
@@ -154,6 +163,31 @@ fn map_wooden_fish_work_snapshot(
|
||||
})
|
||||
}
|
||||
|
||||
fn default_failed_hit_object_asset() -> WoodenFishImageAsset {
|
||||
WoodenFishImageAsset {
|
||||
asset_id: "wooden-fish-failed-hit-object".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-failed-hit-object".to_string(),
|
||||
generation_provider: "failed-fallback".to_string(),
|
||||
prompt: "生成失败占位图".to_string(),
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
}
|
||||
}
|
||||
|
||||
fn default_failed_hit_sound_asset() -> WoodenFishAudioAsset {
|
||||
WoodenFishAudioAsset {
|
||||
asset_id: "wooden-fish-failed-hit-sound".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-failed-hit-sound".to_string(),
|
||||
source: "failed-fallback".to_string(),
|
||||
prompt: Some("生成失败占位音效".to_string()),
|
||||
duration_ms: Some(3_000),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_wooden_fish_draft_snapshot(snapshot: WoodenFishDraftSnapshot) -> WoodenFishDraftResponse {
|
||||
WoodenFishDraftResponse {
|
||||
template_id: snapshot.template_id,
|
||||
|
||||
@@ -122,6 +122,35 @@ impl SpacetimeClient {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn mark_wooden_fish_generation_failed(
|
||||
&self,
|
||||
session_id: String,
|
||||
owner_user_id: String,
|
||||
author_display_name: String,
|
||||
) -> Result<WoodenFishSessionSnapshotResponse, SpacetimeClientError> {
|
||||
let current = self
|
||||
.get_wooden_fish_session(session_id.clone(), owner_user_id.clone())
|
||||
.await?;
|
||||
let mut draft = current.draft.clone().unwrap_or_else(default_draft);
|
||||
let profile_id = resolve_wooden_fish_profile_id(
|
||||
&draft,
|
||||
&WoodenFishActionType::CompileDraft,
|
||||
draft.profile_id.as_deref(),
|
||||
)?;
|
||||
draft.profile_id = Some(profile_id.clone());
|
||||
draft.generation_status = WoodenFishGenerationStatus::Failed;
|
||||
let now_micros = current_unix_micros();
|
||||
self.compile_wooden_fish_draft(build_failed_compile_input(
|
||||
¤t,
|
||||
&owner_user_id,
|
||||
&author_display_name,
|
||||
&profile_id,
|
||||
&draft,
|
||||
now_micros,
|
||||
)?)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn compile_wooden_fish_draft(
|
||||
&self,
|
||||
procedure_input: WoodenFishDraftCompileInput,
|
||||
@@ -636,6 +665,52 @@ fn build_compile_input(
|
||||
})
|
||||
}
|
||||
|
||||
fn build_failed_compile_input(
|
||||
current: &WoodenFishSessionSnapshotResponse,
|
||||
owner_user_id: &str,
|
||||
author_display_name: &str,
|
||||
profile_id: &str,
|
||||
draft: &WoodenFishDraftResponse,
|
||||
now_micros: i64,
|
||||
) -> Result<WoodenFishDraftCompileInput, SpacetimeClientError> {
|
||||
Ok(WoodenFishDraftCompileInput {
|
||||
session_id: current.session_id.clone(),
|
||||
owner_user_id: owner_user_id.to_string(),
|
||||
profile_id: profile_id.to_string(),
|
||||
author_display_name: author_display_name.trim().to_string(),
|
||||
work_title: draft.work_title.clone(),
|
||||
work_description: draft.work_description.clone(),
|
||||
theme_tags_json: Some(json_string(&draft.theme_tags)?),
|
||||
hit_object_prompt: draft.hit_object_prompt.clone(),
|
||||
hit_object_reference_image_src: draft.hit_object_reference_image_src.clone(),
|
||||
hit_sound_prompt: draft.hit_sound_prompt.clone(),
|
||||
hit_object_asset_json: draft
|
||||
.hit_object_asset
|
||||
.as_ref()
|
||||
.map(json_string)
|
||||
.transpose()?,
|
||||
background_asset_json: draft
|
||||
.background_asset
|
||||
.as_ref()
|
||||
.map(json_string)
|
||||
.transpose()?,
|
||||
hit_sound_asset_json: draft
|
||||
.hit_sound_asset
|
||||
.as_ref()
|
||||
.map(json_string)
|
||||
.transpose()?,
|
||||
back_button_asset_json: draft
|
||||
.back_button_asset
|
||||
.as_ref()
|
||||
.map(json_string)
|
||||
.transpose()?,
|
||||
floating_words_json: Some(json_string(&draft.floating_words)?),
|
||||
cover_image_src: draft.cover_image_src.clone(),
|
||||
generation_status: Some("failed".to_string()),
|
||||
compiled_at_micros: now_micros,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_update_input(
|
||||
owner_user_id: &str,
|
||||
profile_id: &str,
|
||||
@@ -801,6 +876,7 @@ mod tests {
|
||||
|
||||
const SESSION_ID: &str = "wooden-fish-session-test";
|
||||
const OWNER_USER_ID: &str = "user-test";
|
||||
const AUTHOR_DISPLAY_NAME: &str = "测试玩家";
|
||||
const PROFILE_ID: &str = "wooden-fish-profile-test";
|
||||
const NOW_MICROS: i64 = 1_763_456_789_000_000;
|
||||
|
||||
@@ -813,9 +889,14 @@ mod tests {
|
||||
payload.back_button_asset = Some(generated_back_button_asset("generated-compile-back"));
|
||||
payload.hit_sound_asset = Some(generated_hit_sound_asset("generated-compile-sound"));
|
||||
|
||||
let (plan, draft) =
|
||||
build_wooden_fish_action_plan(&session, OWNER_USER_ID, &payload, NOW_MICROS)
|
||||
.expect("compile-draft should build plan");
|
||||
let (plan, draft) = build_wooden_fish_action_plan(
|
||||
&session,
|
||||
OWNER_USER_ID,
|
||||
AUTHOR_DISPLAY_NAME,
|
||||
&payload,
|
||||
NOW_MICROS,
|
||||
)
|
||||
.expect("compile-draft should build plan");
|
||||
|
||||
let WoodenFishActionProcedure::Compile(input) = plan else {
|
||||
panic!("compile-draft should call compile_wooden_fish_draft");
|
||||
@@ -862,11 +943,16 @@ mod tests {
|
||||
payload.background_asset = Some(generated_background_asset("generated-compile-background"));
|
||||
payload.back_button_asset = Some(generated_back_button_asset("generated-compile-back"));
|
||||
|
||||
let error =
|
||||
match build_wooden_fish_action_plan(&session, OWNER_USER_ID, &payload, NOW_MICROS) {
|
||||
Ok(_) => panic!("compile-draft should not synthesize fake hit sound assets"),
|
||||
Err(error) => error,
|
||||
};
|
||||
let error = match build_wooden_fish_action_plan(
|
||||
&session,
|
||||
OWNER_USER_ID,
|
||||
AUTHOR_DISPLAY_NAME,
|
||||
&payload,
|
||||
NOW_MICROS,
|
||||
) {
|
||||
Ok(_) => panic!("compile-draft should not synthesize fake hit sound assets"),
|
||||
Err(error) => error,
|
||||
};
|
||||
|
||||
assert!(
|
||||
error
|
||||
@@ -883,11 +969,16 @@ mod tests {
|
||||
payload.hit_sound_asset = Some(generated_hit_sound_asset("generated-compile-sound"));
|
||||
payload.back_button_asset = Some(generated_back_button_asset("generated-compile-back"));
|
||||
|
||||
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,
|
||||
};
|
||||
let error = match build_wooden_fish_action_plan(
|
||||
&session,
|
||||
OWNER_USER_ID,
|
||||
AUTHOR_DISPLAY_NAME,
|
||||
&payload,
|
||||
NOW_MICROS,
|
||||
) {
|
||||
Ok(_) => panic!("compile-draft should not publish without background asset"),
|
||||
Err(error) => error,
|
||||
};
|
||||
|
||||
assert!(
|
||||
error
|
||||
@@ -904,11 +995,16 @@ mod tests {
|
||||
payload.background_asset = Some(generated_background_asset("generated-compile-background"));
|
||||
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 back button asset"),
|
||||
Err(error) => error,
|
||||
};
|
||||
let error = match build_wooden_fish_action_plan(
|
||||
&session,
|
||||
OWNER_USER_ID,
|
||||
AUTHOR_DISPLAY_NAME,
|
||||
&payload,
|
||||
NOW_MICROS,
|
||||
) {
|
||||
Ok(_) => panic!("compile-draft should not publish without back button asset"),
|
||||
Err(error) => error,
|
||||
};
|
||||
|
||||
assert!(
|
||||
error
|
||||
@@ -926,9 +1022,14 @@ mod tests {
|
||||
payload.background_asset = Some(generated_background_asset("generated-background"));
|
||||
payload.back_button_asset = Some(generated_back_button_asset("generated-back"));
|
||||
|
||||
let (plan, _draft) =
|
||||
build_wooden_fish_action_plan(&session, OWNER_USER_ID, &payload, NOW_MICROS)
|
||||
.expect("regenerate-hit-object should build plan");
|
||||
let (plan, _draft) = build_wooden_fish_action_plan(
|
||||
&session,
|
||||
OWNER_USER_ID,
|
||||
AUTHOR_DISPLAY_NAME,
|
||||
&payload,
|
||||
NOW_MICROS,
|
||||
)
|
||||
.expect("regenerate-hit-object should build plan");
|
||||
|
||||
let WoodenFishActionProcedure::Compile(input) = plan else {
|
||||
panic!("regenerate-hit-object should call compile_wooden_fish_draft");
|
||||
@@ -987,9 +1088,14 @@ mod tests {
|
||||
"健康+1".to_string(),
|
||||
]);
|
||||
|
||||
let (plan, draft) =
|
||||
build_wooden_fish_action_plan(&session, OWNER_USER_ID, &payload, NOW_MICROS)
|
||||
.expect("update-floating-words should build plan");
|
||||
let (plan, draft) = build_wooden_fish_action_plan(
|
||||
&session,
|
||||
OWNER_USER_ID,
|
||||
AUTHOR_DISPLAY_NAME,
|
||||
&payload,
|
||||
NOW_MICROS,
|
||||
)
|
||||
.expect("update-floating-words should build plan");
|
||||
|
||||
let WoodenFishActionProcedure::Update(input) = plan else {
|
||||
panic!("update-floating-words should call update_wooden_fish_work");
|
||||
@@ -1016,6 +1122,31 @@ mod tests {
|
||||
assert!(draft.hit_sound_prompt.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wooden_fish_failed_compile_input_preserves_session_and_marks_failed() {
|
||||
let session = session_with_draft(draft_without_assets());
|
||||
let mut draft = session.draft.clone().expect("draft should exist");
|
||||
draft.profile_id = Some(PROFILE_ID.to_string());
|
||||
draft.generation_status = WoodenFishGenerationStatus::Failed;
|
||||
|
||||
let input = build_failed_compile_input(
|
||||
&session,
|
||||
OWNER_USER_ID,
|
||||
"测试玩家",
|
||||
PROFILE_ID,
|
||||
&draft,
|
||||
NOW_MICROS,
|
||||
)
|
||||
.expect("failed compile input should build");
|
||||
|
||||
assert_eq!(input.session_id, SESSION_ID);
|
||||
assert_eq!(input.profile_id, PROFILE_ID);
|
||||
assert_eq!(input.generation_status.as_deref(), Some("failed"));
|
||||
assert!(input.hit_object_asset_json.is_none());
|
||||
assert!(input.background_asset_json.is_none());
|
||||
assert!(input.back_button_asset_json.is_none());
|
||||
}
|
||||
|
||||
fn action(action_type: WoodenFishActionType) -> WoodenFishActionRequest {
|
||||
WoodenFishActionRequest {
|
||||
action_type,
|
||||
|
||||
Reference in New Issue
Block a user