Add generationStatus and match3d/runtime fixes
Introduce persistent generationStatus to work summaries (puzzle & match3d) and propagate generation recovery rules across docs and frontend/backends so "generating" is restored from server-side work summary rather than ephemeral front-end notices. Update API server image/asset handling (improve match3d material sheet green/alpha decontamination and promote generatedItemAssets background fields) and add runtime improvements: alpha-based hotspot hit-testing, tray insertion/three-match animation behavior, and session re-read on client-side VectorEngine timeouts/lock-screen interruptions. Many docs, tests and related frontend modules updated/added to reflect these contract and behavior changes.
This commit is contained in:
@@ -870,7 +870,10 @@ fn compile_puzzle_agent_draft_tx(
|
||||
}
|
||||
let anchor_pack = infer_anchor_pack(&row.seed_text, Some(&row.seed_text));
|
||||
let messages = list_session_messages(ctx, &row.session_id);
|
||||
let draft = compile_result_draft_from_seed(&anchor_pack, &messages, Some(&row.seed_text));
|
||||
let draft = mark_puzzle_draft_generation_status(
|
||||
compile_result_draft_from_seed(&anchor_pack, &messages, Some(&row.seed_text)),
|
||||
"generating",
|
||||
);
|
||||
// 创作中心的拼图草稿卡只是 Agent session 的列表投影,
|
||||
// 每次编译结果页时同步 upsert,保证后续能按 source_session_id 恢复聊天。
|
||||
upsert_puzzle_draft_work_profile(
|
||||
@@ -2472,10 +2475,52 @@ fn profile_for_single_level(
|
||||
level: &module_puzzle::PuzzleDraftLevel,
|
||||
) -> PuzzleWorkProfile {
|
||||
let mut next_profile = profile.clone();
|
||||
let ui_background_carrier = profile.levels.iter().find(|candidate| {
|
||||
candidate
|
||||
.ui_background_image_src
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.map(|value| !value.is_empty())
|
||||
.unwrap_or(false)
|
||||
|| candidate
|
||||
.ui_background_image_object_key
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.map(|value| !value.is_empty())
|
||||
.unwrap_or(false)
|
||||
});
|
||||
let mut single_level = level.clone();
|
||||
if single_level
|
||||
.ui_background_image_src
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.unwrap_or("")
|
||||
.is_empty()
|
||||
&& single_level
|
||||
.ui_background_image_object_key
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.unwrap_or("")
|
||||
.is_empty()
|
||||
&& let Some(carrier) = ui_background_carrier
|
||||
{
|
||||
single_level.ui_background_image_src = carrier
|
||||
.ui_background_image_src
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(str::to_string);
|
||||
single_level.ui_background_image_object_key = carrier
|
||||
.ui_background_image_object_key
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(|value| value.trim_start_matches('/').to_string());
|
||||
}
|
||||
next_profile.level_name = level.level_name.clone();
|
||||
next_profile.cover_image_src = level.cover_image_src.clone();
|
||||
next_profile.cover_asset_id = level.cover_asset_id.clone();
|
||||
next_profile.levels = vec![level.clone()];
|
||||
next_profile.levels = vec![single_level];
|
||||
next_profile
|
||||
}
|
||||
|
||||
@@ -2496,6 +2541,17 @@ fn micros_to_millis(value: i64) -> u64 {
|
||||
(value as u64).saturating_div(1_000)
|
||||
}
|
||||
|
||||
fn mark_puzzle_draft_generation_status(
|
||||
mut draft: PuzzleResultDraft,
|
||||
generation_status: &str,
|
||||
) -> PuzzleResultDraft {
|
||||
draft.generation_status = generation_status.to_string();
|
||||
for level in &mut draft.levels {
|
||||
level.generation_status = generation_status.to_string();
|
||||
}
|
||||
draft
|
||||
}
|
||||
|
||||
fn upsert_puzzle_draft_work_profile(
|
||||
ctx: &TxContext,
|
||||
session_id: &str,
|
||||
@@ -3466,6 +3522,37 @@ mod tests {
|
||||
assert!(preview.publish_ready);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn puzzle_draft_generation_status_updates_all_levels() {
|
||||
let anchor_pack = infer_anchor_pack("蒸汽城市雨夜猫咪", Some("蒸汽城市雨夜猫咪"));
|
||||
let mut draft = compile_result_draft(&anchor_pack, &[]);
|
||||
draft.levels.push(module_puzzle::PuzzleDraftLevel {
|
||||
level_id: "puzzle-level-2".to_string(),
|
||||
level_name: "第二关".to_string(),
|
||||
picture_description: "第二关画面".to_string(),
|
||||
picture_reference: None,
|
||||
ui_background_prompt: None,
|
||||
ui_background_image_src: None,
|
||||
ui_background_image_object_key: None,
|
||||
background_music: None,
|
||||
candidates: Vec::new(),
|
||||
selected_candidate_id: None,
|
||||
cover_image_src: None,
|
||||
cover_asset_id: None,
|
||||
generation_status: "idle".to_string(),
|
||||
});
|
||||
|
||||
let draft = mark_puzzle_draft_generation_status(draft, "generating");
|
||||
|
||||
assert_eq!(draft.generation_status, "generating");
|
||||
assert!(
|
||||
draft
|
||||
.levels
|
||||
.iter()
|
||||
.all(|level| level.generation_status == "generating")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn puzzle_generated_images_replace_existing_candidate() {
|
||||
let anchor_pack = infer_anchor_pack("蒸汽城市雨夜猫咪", Some("蒸汽城市雨夜猫咪"));
|
||||
|
||||
Reference in New Issue
Block a user