Merge branch 'master' of http://82.157.175.59:3000/GenarrativeAI/Genarrative
This commit is contained in:
@@ -57,8 +57,8 @@ use spacetime_client::{
|
||||
PuzzleAgentMessageRecord, PuzzleAgentMessageSubmitRecordInput,
|
||||
PuzzleAgentSessionCreateRecordInput, PuzzleAgentSessionRecord,
|
||||
PuzzleAgentSuggestedActionRecord, PuzzleAnchorItemRecord, PuzzleAnchorPackRecord,
|
||||
PuzzleAudioAssetRecord, PuzzleCreatorIntentRecord, PuzzleDraftLevelRecord, PuzzleFormDraftRecord,
|
||||
PuzzleFormDraftSaveRecordInput, PuzzleGeneratedImageCandidateRecord,
|
||||
PuzzleAudioAssetRecord, PuzzleCreatorIntentRecord, PuzzleDraftLevelRecord,
|
||||
PuzzleFormDraftRecord, PuzzleFormDraftSaveRecordInput, PuzzleGeneratedImageCandidateRecord,
|
||||
PuzzleGeneratedImagesSaveRecordInput, PuzzleLeaderboardEntryRecord,
|
||||
PuzzleLeaderboardSubmitRecordInput, PuzzlePublishRecordInput, PuzzleRecommendedNextWorkRecord,
|
||||
PuzzleResultDraftRecord, PuzzleResultPreviewBlockerRecord, PuzzleResultPreviewFindingRecord,
|
||||
@@ -2189,7 +2189,9 @@ fn map_puzzle_draft_level_response(level: PuzzleDraftLevelRecord) -> PuzzleDraft
|
||||
ui_background_prompt: level.ui_background_prompt,
|
||||
ui_background_image_src: level.ui_background_image_src,
|
||||
ui_background_image_object_key: level.ui_background_image_object_key,
|
||||
background_music: level.background_music.map(map_puzzle_audio_asset_record_response),
|
||||
background_music: level
|
||||
.background_music
|
||||
.map(map_puzzle_audio_asset_record_response),
|
||||
candidates: level
|
||||
.candidates
|
||||
.into_iter()
|
||||
@@ -2506,7 +2508,9 @@ fn map_puzzle_runtime_level_response(
|
||||
theme_tags: level.theme_tags,
|
||||
cover_image_src: level.cover_image_src,
|
||||
ui_background_image_src: level.ui_background_image_src,
|
||||
background_music: level.background_music.map(map_puzzle_audio_asset_record_response),
|
||||
background_music: level
|
||||
.background_music
|
||||
.map(map_puzzle_audio_asset_record_response),
|
||||
board: map_puzzle_board_response(level.board),
|
||||
status: level.status,
|
||||
started_at_ms: level.started_at_ms,
|
||||
@@ -2800,7 +2804,9 @@ fn parse_puzzle_level_records_from_module_json(
|
||||
ui_background_prompt: level.ui_background_prompt,
|
||||
ui_background_image_src: level.ui_background_image_src,
|
||||
ui_background_image_object_key: level.ui_background_image_object_key,
|
||||
background_music: level.background_music.map(map_puzzle_audio_asset_domain_record),
|
||||
background_music: level
|
||||
.background_music
|
||||
.map(map_puzzle_audio_asset_domain_record),
|
||||
candidates: level
|
||||
.candidates
|
||||
.into_iter()
|
||||
@@ -3413,6 +3419,24 @@ fn attach_puzzle_level_background_music(
|
||||
});
|
||||
}
|
||||
|
||||
fn attach_puzzle_level_ui_background(
|
||||
levels: &mut [PuzzleDraftLevelRecord],
|
||||
level_id: &str,
|
||||
prompt: String,
|
||||
generated: GeneratedPuzzleUiBackgroundResponse,
|
||||
) {
|
||||
let Some(index) = levels
|
||||
.iter()
|
||||
.position(|level| level.level_id == level_id)
|
||||
.or_else(|| (!levels.is_empty()).then_some(0))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
levels[index].ui_background_prompt = Some(prompt);
|
||||
levels[index].ui_background_image_src = Some(generated.image_src);
|
||||
levels[index].ui_background_image_object_key = Some(generated.object_key);
|
||||
}
|
||||
|
||||
async fn try_generate_puzzle_background_music(
|
||||
state: &AppState,
|
||||
owner_user_id: &str,
|
||||
@@ -3456,6 +3480,37 @@ async fn try_generate_puzzle_background_music(
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_generate_puzzle_initial_ui_background(
|
||||
state: &AppState,
|
||||
owner_user_id: &str,
|
||||
session_id: &str,
|
||||
draft: &PuzzleResultDraftRecord,
|
||||
target_level: &PuzzleDraftLevelRecord,
|
||||
) -> Option<(String, GeneratedPuzzleUiBackgroundResponse)> {
|
||||
let prompt = normalize_puzzle_ui_background_prompt("", draft, target_level);
|
||||
match generate_puzzle_ui_background_image(
|
||||
state,
|
||||
owner_user_id,
|
||||
session_id,
|
||||
target_level.level_name.as_str(),
|
||||
prompt.as_str(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(generated) => Some((prompt, generated)),
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
provider = PUZZLE_AGENT_API_BASE_PROVIDER,
|
||||
session_id,
|
||||
level_id = %target_level.level_id,
|
||||
error = %error,
|
||||
"拼图草稿 UI 背景图自动生成失败,保留草稿并允许结果页重试"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn compile_puzzle_draft_with_initial_cover(
|
||||
state: &AppState,
|
||||
session_id: String,
|
||||
@@ -3540,9 +3595,24 @@ async fn compile_puzzle_draft_with_initial_cover(
|
||||
music,
|
||||
);
|
||||
}
|
||||
let levels_json_with_generated_name = Some(serialize_puzzle_level_records_for_module(
|
||||
&updated_levels,
|
||||
)?);
|
||||
if let Some((ui_prompt, ui_background)) = try_generate_puzzle_initial_ui_background(
|
||||
state,
|
||||
owner_user_id.as_str(),
|
||||
compiled_session.session_id.as_str(),
|
||||
&draft,
|
||||
&target_level,
|
||||
)
|
||||
.await
|
||||
{
|
||||
attach_puzzle_level_ui_background(
|
||||
&mut updated_levels,
|
||||
target_level.level_id.as_str(),
|
||||
ui_prompt,
|
||||
ui_background,
|
||||
);
|
||||
}
|
||||
let levels_json_with_generated_name =
|
||||
Some(serialize_puzzle_level_records_for_module(&updated_levels)?);
|
||||
let candidates_json = serde_json::to_string(
|
||||
&candidates
|
||||
.iter()
|
||||
@@ -3674,7 +3744,7 @@ async fn compile_puzzle_draft_with_uploaded_cover(
|
||||
&target_level.picture_description,
|
||||
&draft.summary,
|
||||
);
|
||||
// 中文注释:关闭 AI 重绘时不请求 VectorEngine,也不进入光点扣费流程;上传图直接成为首关正式图候选。
|
||||
// 中文注释:关闭 AI 重绘时首关图不请求 VectorEngine;上传图直接成为首关正式图候选。
|
||||
let candidate_id = format!(
|
||||
"{}-candidate-{}",
|
||||
compiled_session.session_id,
|
||||
@@ -3714,9 +3784,24 @@ async fn compile_puzzle_draft_with_uploaded_cover(
|
||||
music,
|
||||
);
|
||||
}
|
||||
let levels_json_with_generated_name = Some(serialize_puzzle_level_records_for_module(
|
||||
&updated_levels,
|
||||
)?);
|
||||
if let Some((ui_prompt, ui_background)) = try_generate_puzzle_initial_ui_background(
|
||||
state,
|
||||
owner_user_id.as_str(),
|
||||
compiled_session.session_id.as_str(),
|
||||
&draft,
|
||||
&target_level,
|
||||
)
|
||||
.await
|
||||
{
|
||||
attach_puzzle_level_ui_background(
|
||||
&mut updated_levels,
|
||||
target_level.level_id.as_str(),
|
||||
ui_prompt,
|
||||
ui_background,
|
||||
);
|
||||
}
|
||||
let levels_json_with_generated_name =
|
||||
Some(serialize_puzzle_level_records_for_module(&updated_levels)?);
|
||||
let persisted_upload = persist_puzzle_generated_asset(
|
||||
state,
|
||||
owner_user_id.as_str(),
|
||||
@@ -4728,12 +4813,12 @@ async fn load_puzzle_ui_background_reference_data_url() -> Result<String, AppErr
|
||||
}))
|
||||
})?;
|
||||
if bytes.is_empty() {
|
||||
return Err(AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(
|
||||
json!({
|
||||
return Err(
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({
|
||||
"provider": PUZZLE_AGENT_API_BASE_PROVIDER,
|
||||
"message": "拼图 UI 背景参考图为空",
|
||||
}),
|
||||
));
|
||||
})),
|
||||
);
|
||||
}
|
||||
Ok(format!(
|
||||
"data:image/png;base64,{}",
|
||||
@@ -5047,8 +5132,7 @@ mod tests {
|
||||
|
||||
let levels_json = serialize_puzzle_levels_response(&request_context, &[level])
|
||||
.expect("levels should serialize");
|
||||
let payload: Value =
|
||||
serde_json::from_str(&levels_json).expect("levels json should parse");
|
||||
let payload: Value = serde_json::from_str(&levels_json).expect("levels json should parse");
|
||||
assert_eq!(
|
||||
payload[0]["background_music"]["audio_src"],
|
||||
Value::String("/generated-puzzle-assets/audio.mp3".to_string())
|
||||
@@ -5104,8 +5188,7 @@ mod tests {
|
||||
|
||||
let levels_json = serialize_puzzle_levels_response(&request_context, &[level])
|
||||
.expect("levels should serialize");
|
||||
let payload: Value =
|
||||
serde_json::from_str(&levels_json).expect("levels json should parse");
|
||||
let payload: Value = serde_json::from_str(&levels_json).expect("levels json should parse");
|
||||
assert_eq!(
|
||||
payload[0]["ui_background_prompt"],
|
||||
Value::String("雨夜猫街竖屏拼图UI背景".to_string())
|
||||
@@ -5128,10 +5211,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn puzzle_ui_background_prompt_keeps_square_boundary_constraint() {
|
||||
let prompt = build_puzzle_ui_background_request_prompt_for_test(
|
||||
"雨夜猫街",
|
||||
"雨夜猫街主题背景",
|
||||
);
|
||||
let prompt =
|
||||
build_puzzle_ui_background_request_prompt_for_test("雨夜猫街", "雨夜猫街主题背景");
|
||||
|
||||
assert!(prompt.contains("9:16"));
|
||||
assert!(prompt.contains("中央必须预留清晰正方形拼图区"));
|
||||
@@ -5139,6 +5220,36 @@ mod tests {
|
||||
assert!(prompt.contains("不要画文字"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn puzzle_ui_background_initial_attach_updates_first_level_fields() {
|
||||
let draft = test_puzzle_draft_record();
|
||||
let generated = GeneratedPuzzleUiBackgroundResponse {
|
||||
image_src: "/generated-puzzle-assets/session/ui/background.png".to_string(),
|
||||
object_key: "generated-puzzle-assets/session/ui/background.png".to_string(),
|
||||
};
|
||||
let mut levels = draft.levels.clone();
|
||||
|
||||
attach_puzzle_level_ui_background(
|
||||
&mut levels,
|
||||
"puzzle-level-1",
|
||||
"雨夜猫街移动端拼图UI背景".to_string(),
|
||||
generated,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
levels[0].ui_background_prompt.as_deref(),
|
||||
Some("雨夜猫街移动端拼图UI背景")
|
||||
);
|
||||
assert_eq!(
|
||||
levels[0].ui_background_image_src.as_deref(),
|
||||
Some("/generated-puzzle-assets/session/ui/background.png")
|
||||
);
|
||||
assert_eq!(
|
||||
levels[0].ui_background_image_object_key.as_deref(),
|
||||
Some("generated-puzzle-assets/session/ui/background.png")
|
||||
);
|
||||
}
|
||||
|
||||
fn test_puzzle_anchor_pack_record() -> PuzzleAnchorPackRecord {
|
||||
let item = PuzzleAnchorItemRecord {
|
||||
key: "visualSubject".to_string(),
|
||||
|
||||
Reference in New Issue
Block a user