Puzzle: support history images & partial generation
Allow history-generated image paths to be submitted where Data URLs were previously required and avoid treating partial/result-page generations as blocking the whole draft. Backend: resolve history /generated-* references via resolve_puzzle_reference_image_as_data_url and convert to PuzzleDownloadedImage; add PuzzleDownloadedImage::from_resolved_reference_image; extend draft handling to apply generated level metadata (auto-naming) and normalize generation_status to treat levels with images as ready. API: add shouldAutoNameLevel to action contracts and use it to request/refine generated level names. Spacetime/module and mappers: normalize completed level statuses when saving/reading so result-page background or per-level generation doesn't mask completed drafts. Frontend: expose resolver helpers, only mark a work as generating when no usable cover or ready level exists, keep level controls enabled during UI-background regeneration, and add tests covering history-image submission, auto-naming, and UI-background/partial-generation behaviors.
This commit is contained in:
@@ -253,6 +253,7 @@ fn puzzle_image_generation_builds_fallback_session_from_levels_snapshot() {
|
||||
image_model: Some(PUZZLE_IMAGE_MODEL_GPT_IMAGE_2.to_string()),
|
||||
ai_redraw: None,
|
||||
candidate_count: Some(1),
|
||||
should_auto_name_level: None,
|
||||
candidate_id: None,
|
||||
level_id: Some("puzzle-level-1".to_string()),
|
||||
work_title: Some("暖灯猫街作品".to_string()),
|
||||
@@ -376,6 +377,21 @@ fn puzzle_level_name_image_data_url_downsizes_generated_image() {
|
||||
assert!(data_url.len() > "data:image/png;base64,".len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn puzzle_uploaded_cover_can_reuse_resolved_history_image() {
|
||||
let resolved = PuzzleResolvedReferenceImage {
|
||||
mime_type: "image/png".to_string(),
|
||||
bytes_len: 8,
|
||||
bytes: b"pngbytes".to_vec(),
|
||||
};
|
||||
|
||||
let downloaded = PuzzleDownloadedImage::from_resolved_reference_image(resolved);
|
||||
|
||||
assert_eq!(downloaded.extension, "png");
|
||||
assert_eq!(downloaded.mime_type, "image/png");
|
||||
assert_eq!(downloaded.bytes, b"pngbytes");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn puzzle_first_level_name_snapshot_defaults_work_title() {
|
||||
let levels_json = serde_json::to_string(&vec![json!({
|
||||
@@ -397,6 +413,7 @@ fn puzzle_first_level_name_snapshot_defaults_work_title() {
|
||||
image_model: Some(PUZZLE_IMAGE_MODEL_GPT_IMAGE_2.to_string()),
|
||||
ai_redraw: None,
|
||||
candidate_count: Some(1),
|
||||
should_auto_name_level: None,
|
||||
candidate_id: None,
|
||||
level_id: Some("puzzle-level-1".to_string()),
|
||||
work_title: Some("猫画面".to_string()),
|
||||
@@ -619,7 +636,7 @@ fn puzzle_work_summary_response_keeps_levels_for_shelf_cover() {
|
||||
selected_candidate_id: Some("candidate-1".to_string()),
|
||||
cover_image_src: Some("/generated-puzzle-assets/session/cover.png".to_string()),
|
||||
cover_asset_id: Some("asset-1".to_string()),
|
||||
generation_status: "ready".to_string(),
|
||||
generation_status: "generating".to_string(),
|
||||
};
|
||||
|
||||
let response = map_puzzle_work_summary_response(
|
||||
@@ -654,6 +671,7 @@ fn puzzle_work_summary_response_keeps_levels_for_shelf_cover() {
|
||||
|
||||
assert_eq!(response.levels.len(), 1);
|
||||
assert_eq!(response.generation_status.as_deref(), Some("ready"));
|
||||
assert_eq!(response.levels[0].generation_status, "ready");
|
||||
assert_eq!(
|
||||
response.levels[0].cover_image_src.as_deref(),
|
||||
Some("/generated-puzzle-assets/session/cover.png")
|
||||
|
||||
Reference in New Issue
Block a user