1
This commit is contained in:
@@ -3,6 +3,7 @@ use crate::*;
|
||||
const ASSET_HISTORY_MAX_LIMIT: usize = 120;
|
||||
const ASSET_HISTORY_CHARACTER_VISUAL_KIND: &str = "character_visual";
|
||||
const ASSET_HISTORY_SCENE_IMAGE_KIND: &str = "scene_image";
|
||||
const ASSET_HISTORY_PUZZLE_COVER_IMAGE_KIND: &str = "puzzle_cover_image";
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = asset_object,
|
||||
@@ -199,8 +200,11 @@ fn list_asset_history(
|
||||
let asset_kind = input.asset_kind.trim();
|
||||
if asset_kind != ASSET_HISTORY_CHARACTER_VISUAL_KIND
|
||||
&& asset_kind != ASSET_HISTORY_SCENE_IMAGE_KIND
|
||||
&& asset_kind != ASSET_HISTORY_PUZZLE_COVER_IMAGE_KIND
|
||||
{
|
||||
return Err("历史素材类型只支持 character_visual 或 scene_image".to_string());
|
||||
return Err(
|
||||
"历史素材类型只支持 character_visual、scene_image 或 puzzle_cover_image".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let limit = usize::try_from(input.limit)
|
||||
|
||||
@@ -685,7 +685,7 @@ fn save_puzzle_generated_images_tx(
|
||||
if candidates.is_empty() {
|
||||
return Err("拼图候选图不能为空".to_string());
|
||||
}
|
||||
append_generated_candidates(&mut draft, candidates);
|
||||
replace_generated_candidate(&mut draft, candidates);
|
||||
draft.generation_status = "ready".to_string();
|
||||
if let Some(selected) = draft
|
||||
.candidates
|
||||
@@ -724,7 +724,7 @@ fn save_puzzle_generated_images_tx(
|
||||
stage: next_stage,
|
||||
anchor_pack_json: row.anchor_pack_json.clone(),
|
||||
draft_json: Some(serialize_json(&draft)),
|
||||
last_assistant_reply: Some("候选图已经生成,请选择正式拼图图片。".to_string()),
|
||||
last_assistant_reply: Some("拼图图片已经生成,并已替换当前正式图。".to_string()),
|
||||
published_profile_id: row.published_profile_id.clone(),
|
||||
created_at: row.created_at,
|
||||
updated_at: saved_at,
|
||||
@@ -1510,21 +1510,19 @@ fn increment_puzzle_profile_play_count(
|
||||
);
|
||||
}
|
||||
|
||||
fn append_generated_candidates(
|
||||
fn replace_generated_candidate(
|
||||
draft: &mut PuzzleResultDraft,
|
||||
candidates: Vec<PuzzleGeneratedImageCandidate>,
|
||||
) {
|
||||
let has_selected_candidate = draft.candidates.iter().any(|entry| entry.selected);
|
||||
// 再次生成图片是扩充候选池,不覆盖创作者已经看到或已经选择的候选图。
|
||||
// 若已有正式选择,新追加候选图保持未选中,避免同一草稿出现多个 selected。
|
||||
draft
|
||||
.candidates
|
||||
.extend(candidates.into_iter().map(|mut candidate| {
|
||||
if has_selected_candidate {
|
||||
candidate.selected = false;
|
||||
}
|
||||
// 结果页生图采用单图替换:每次只保留最新图片,并立即作为正式图。
|
||||
draft.candidates = candidates
|
||||
.into_iter()
|
||||
.take(1)
|
||||
.map(|mut candidate| {
|
||||
candidate.selected = true;
|
||||
candidate
|
||||
}));
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
fn list_published_puzzle_profiles(ctx: &TxContext) -> Result<Vec<PuzzleWorkProfile>, String> {
|
||||
@@ -1634,7 +1632,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn puzzle_generated_images_are_appended_without_clearing_existing_candidates() {
|
||||
fn puzzle_generated_images_replace_existing_candidate() {
|
||||
let anchor_pack = infer_anchor_pack("蒸汽城市雨夜猫咪", Some("蒸汽城市雨夜猫咪"));
|
||||
let mut draft = compile_result_draft(&anchor_pack, &[]);
|
||||
draft.candidates = vec![PuzzleGeneratedImageCandidate {
|
||||
@@ -1647,7 +1645,7 @@ mod tests {
|
||||
selected: true,
|
||||
}];
|
||||
|
||||
append_generated_candidates(
|
||||
replace_generated_candidate(
|
||||
&mut draft,
|
||||
vec![PuzzleGeneratedImageCandidate {
|
||||
candidate_id: "session-1-candidate-2".to_string(),
|
||||
@@ -1660,11 +1658,9 @@ mod tests {
|
||||
}],
|
||||
);
|
||||
|
||||
assert_eq!(draft.candidates.len(), 2);
|
||||
assert_eq!(draft.candidates[0].candidate_id, "session-1-candidate-1");
|
||||
assert_eq!(draft.candidates.len(), 1);
|
||||
assert_eq!(draft.candidates[0].candidate_id, "session-1-candidate-2");
|
||||
assert!(draft.candidates[0].selected);
|
||||
assert_eq!(draft.candidates[1].candidate_id, "session-1-candidate-2");
|
||||
assert!(!draft.candidates[1].selected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -434,6 +434,10 @@ pub(crate) fn sync_profile_projections_from_snapshot(
|
||||
let game_state_object = game_state.as_object();
|
||||
let saved_at = Timestamp::from_micros_since_unix_epoch(snapshot.saved_at_micros);
|
||||
|
||||
if is_non_persistent_runtime_snapshot(&game_state) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
sync_profile_dashboard_from_snapshot(ctx, snapshot, game_state_object, saved_at);
|
||||
sync_profile_save_archive_from_snapshot(ctx, snapshot, &game_state, saved_at)?;
|
||||
|
||||
@@ -740,6 +744,10 @@ fn resolve_profile_save_archive_meta(
|
||||
game_state: &JsonValue,
|
||||
current_story_json: Option<&str>,
|
||||
) -> Option<ProfileSaveArchiveMeta> {
|
||||
if is_non_persistent_runtime_snapshot(game_state) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let game_state_object = game_state.as_object();
|
||||
let world_meta = resolve_profile_world_snapshot_meta(game_state_object)?;
|
||||
let story_engine_memory = game_state_object
|
||||
@@ -813,6 +821,25 @@ fn resolve_profile_save_archive_meta(
|
||||
})
|
||||
}
|
||||
|
||||
fn is_non_persistent_runtime_snapshot(game_state: &JsonValue) -> bool {
|
||||
let Some(game_state) = game_state.as_object() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if game_state
|
||||
.get("runtimePersistenceDisabled")
|
||||
.and_then(JsonValue::as_bool)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
matches!(
|
||||
read_string_from_json(game_state.get("runtimeMode")).as_deref(),
|
||||
Some("preview") | Some("test")
|
||||
)
|
||||
}
|
||||
|
||||
fn build_builtin_world_title(world_type: &str) -> String {
|
||||
match world_type {
|
||||
"WUXIA" => "武侠世界".to_string(),
|
||||
|
||||
Reference in New Issue
Block a user