feat: add puzzle and big fish draft generation progress

This commit is contained in:
2026-04-25 15:17:01 +08:00
parent 1b2daf4796
commit 9cb3c6a27e
10 changed files with 898 additions and 33 deletions

View File

@@ -434,10 +434,13 @@ pub async fn execute_big_fish_action(
let now = current_utc_micros();
let session = match payload.action.trim() {
"big_fish_compile_draft" => {
state
.spacetime_client()
.compile_big_fish_draft(session_id, owner_user_id, now)
.await
compile_big_fish_draft_with_all_assets(
&state,
session_id,
owner_user_id,
now,
)
.await
}
"big_fish_generate_level_main_image" => {
let asset_url = generate_big_fish_formal_asset(
@@ -766,6 +769,98 @@ fn map_big_fish_asset_coverage_response(
}
}
async fn compile_big_fish_draft_with_all_assets(
state: &AppState,
session_id: String,
owner_user_id: String,
now: i64,
) -> Result<BigFishSessionRecord, SpacetimeClientError> {
let session = state
.spacetime_client()
.compile_big_fish_draft(session_id.clone(), owner_user_id.clone(), now)
.await?;
let draft = session
.draft
.clone()
.ok_or_else(|| SpacetimeClientError::Runtime("大鱼吃小鱼玩法草稿尚未生成".to_string()))?;
// 点击生成草稿时一次性生成所有首版玩法资产,前端只负责展示进度和最终 session。
for level in &draft.levels {
let asset_url = generate_big_fish_formal_asset(
state,
&owner_user_id,
&session_id,
"level_main_image",
Some(level.level),
None,
current_utc_micros(),
)
.await
.map_err(|error| SpacetimeClientError::Runtime(error.message().to_string()))?;
state
.spacetime_client()
.generate_big_fish_asset(BigFishAssetGenerateRecordInput {
session_id: session_id.clone(),
owner_user_id: owner_user_id.clone(),
asset_kind: "level_main_image".to_string(),
level: Some(level.level),
motion_key: None,
asset_url: Some(asset_url),
generated_at_micros: current_utc_micros(),
})
.await?;
}
for level in &draft.levels {
for motion_key in ["idle_float", "move_swim"] {
let asset_url = generate_big_fish_formal_asset(
state,
&owner_user_id,
&session_id,
"level_motion",
Some(level.level),
Some(motion_key),
current_utc_micros(),
)
.await
.map_err(|error| SpacetimeClientError::Runtime(error.message().to_string()))?;
state
.spacetime_client()
.generate_big_fish_asset(BigFishAssetGenerateRecordInput {
session_id: session_id.clone(),
owner_user_id: owner_user_id.clone(),
asset_kind: "level_motion".to_string(),
level: Some(level.level),
motion_key: Some(motion_key.to_string()),
asset_url: Some(asset_url),
generated_at_micros: current_utc_micros(),
})
.await?;
}
}
let asset_url = generate_big_fish_formal_asset(
state,
&owner_user_id,
&session_id,
"stage_background",
None,
None,
current_utc_micros(),
)
.await
.map_err(|error| SpacetimeClientError::Runtime(error.message().to_string()))?;
state
.spacetime_client()
.generate_big_fish_asset(BigFishAssetGenerateRecordInput {
session_id,
owner_user_id,
asset_kind: "stage_background".to_string(),
level: None,
motion_key: None,
asset_url: Some(asset_url),
generated_at_micros: current_utc_micros(),
})
.await
}
fn map_big_fish_agent_message_response(
message: BigFishAgentMessageRecord,
) -> BigFishAgentMessageResponse {

View File

@@ -435,14 +435,17 @@ pub async fn execute_puzzle_agent_action(
let (operation_type, phase_label, phase_detail, session) = match payload.action.trim() {
"compile_puzzle_draft" => {
let session = state
.spacetime_client()
.compile_puzzle_agent_draft(session_id, owner_user_id, now)
.await;
let session = compile_puzzle_draft_with_initial_cover(
&state,
session_id.clone(),
owner_user_id.clone(),
now,
)
.await;
(
"compile_puzzle_draft",
"结果页草稿",
"根据当前锚点编译结果页草稿",
"完整拼图草稿",
"编译草稿、生成候选图并应用正式图片",
session,
)
}
@@ -572,6 +575,18 @@ pub async fn execute_puzzle_agent_action(
)
})?;
let session = state
.spacetime_client()
.get_puzzle_agent_session(session_id.clone(), owner_user_id.clone())
.await
.map_err(|error| {
puzzle_error_response(
&request_context,
PUZZLE_AGENT_API_BASE_PROVIDER,
map_puzzle_client_error(error),
)
})?;
return Ok(json_success_body(
Some(&request_context),
PuzzleAgentActionResponse {
@@ -584,6 +599,7 @@ pub async fn execute_puzzle_agent_action(
progress: 100,
error: None,
},
session: map_puzzle_agent_session_response(session),
},
));
}
@@ -616,6 +632,7 @@ pub async fn execute_puzzle_agent_action(
progress: 100,
error: None,
},
session: map_puzzle_agent_session_response(session),
},
))
}
@@ -1336,6 +1353,64 @@ fn build_stable_puzzle_work_ids(session_id: &str) -> (String, String) {
)
}
async fn compile_puzzle_draft_with_initial_cover(
state: &AppState,
session_id: String,
owner_user_id: String,
now: i64,
) -> Result<PuzzleAgentSessionRecord, SpacetimeClientError> {
let compiled_session = state
.spacetime_client()
.compile_puzzle_agent_draft(session_id.clone(), owner_user_id.clone(), now)
.await?;
let draft = compiled_session
.draft
.clone()
.ok_or_else(|| SpacetimeClientError::Runtime("拼图结果页草稿尚未生成".to_string()))?;
// 点击生成草稿时一次性完成首图生成与正式图选定,前端只展示进度,不再承担业务编排。
let candidates = generate_puzzle_image_candidates(
state,
owner_user_id.as_str(),
&compiled_session.session_id,
&draft.level_name,
&draft.summary,
2,
)
.await
.map_err(SpacetimeClientError::Runtime)?;
let selected_candidate_id = candidates
.iter()
.find(|candidate| candidate.selected)
.or_else(|| candidates.first())
.map(|candidate| candidate.candidate_id.clone())
.ok_or_else(|| SpacetimeClientError::Runtime("拼图候选图生成结果为空".to_string()))?;
let candidates_json = serde_json::to_string(
&candidates
.iter()
.map(to_puzzle_generated_image_candidate)
.collect::<Vec<_>>(),
)
.map_err(|error| SpacetimeClientError::Runtime(format!("拼图候选图序列化失败:{error}")))?;
state
.spacetime_client()
.save_puzzle_generated_images(PuzzleGeneratedImagesSaveRecordInput {
session_id: compiled_session.session_id.clone(),
owner_user_id: owner_user_id.clone(),
candidates_json,
saved_at_micros: current_utc_micros(),
})
.await?;
state
.spacetime_client()
.select_puzzle_cover_image(PuzzleSelectCoverImageRecordInput {
session_id,
owner_user_id,
candidate_id: selected_candidate_id,
selected_at_micros: current_utc_micros(),
})
.await
}
fn ensure_non_empty(
request_context: &RequestContext,
provider: &str,

View File

@@ -187,4 +187,6 @@ pub struct PuzzleAgentOperationResponse {
#[serde(rename_all = "camelCase")]
pub struct PuzzleAgentActionResponse {
pub operation: PuzzleAgentOperationResponse,
/// 操作完成后的最新会话快照,供前端直接更新界面,避免重复拉取完整 session。
pub session: PuzzleAgentSessionSnapshotResponse,
}