fix(match3d): embed container reference image

This commit is contained in:
kdletters
2026-05-20 15:37:44 +08:00
parent 83e92fc3c4
commit 0eed942ce5
6 changed files with 67 additions and 55 deletions

View File

@@ -109,8 +109,10 @@ const MATCH3D_WORK_METADATA_LLM_MODEL: &str = "gpt-4o";
const MATCH3D_ITEM_SIZE_LARGE: &str = "";
const MATCH3D_ITEM_SIZE_MEDIUM: &str = "";
const MATCH3D_ITEM_SIZE_SMALL: &str = "";
const MATCH3D_CONTAINER_REFERENCE_IMAGE_PATH: &str =
"public/match3d-background-references/pot-fused-reference.png";
const MATCH3D_CONTAINER_REFERENCE_IMAGE_BYTES: &[u8] = include_bytes!(
"../../../../public/match3d-background-references/pot-fused-reference.png"
);
const MATCH3D_PUBLIC_REFERENCE_IMAGE_PATH_PREFIX: &str = "public/";
const MATCH3D_QUESTION_THEME: &str = "你想创作什么题材";
const MATCH3D_QUESTION_CLEAR_COUNT: &str = "需要消除多少次才能通关";
const MATCH3D_QUESTION_DIFFICULTY: &str = "如果难度是从1-10你要创作的关卡是难度几";

View File

@@ -1156,6 +1156,19 @@ fn match3d_public_reference_image_paths_are_limited_to_known_assets() {
);
}
#[test]
fn match3d_container_reference_image_is_embedded_for_api_only_deploy() {
let reference = load_match3d_container_reference_image()
.expect("container reference image should be compiled into api-server");
assert_eq!(reference.mime_type, "image/png");
assert_eq!(reference.file_name, "match3d-container-reference.png");
assert!(
reference.bytes.starts_with(&[137, 80, 78, 71, 13, 10, 26, 10]),
"container reference image should be PNG bytes"
);
}
#[test]
fn match3d_cover_reference_prompt_marks_reference_images() {
let prompt = build_match3d_cover_reference_generation_prompt("水果封面", true);
@@ -1684,44 +1697,44 @@ fn match3d_required_item_images_require_five_views() {
assert!(!has_match3d_required_item_images(&assets, 3));
let five_view_assets = (1..=3)
.map(|index| Match3DGeneratedItemAsset {
item_id: format!("match3d-item-{index}"),
item_name: format!("物品{index}"),
item_size: Some(MATCH3D_ITEM_SIZE_LARGE.to_string()),
image_src: Some(format!(
"/generated-match3d-assets/s/p/items/i{index}/views/view-01.png"
)),
image_object_key: Some(format!(
"generated-match3d-assets/s/p/items/i{index}/views/view-01.png"
)),
image_views: (1..=MATCH3D_ITEM_VIEW_COUNT)
.map(|view_index| Match3DGeneratedItemImageView {
view_id: format!("view-{view_index:02}"),
view_index: view_index as u32,
image_src: Some(format!(
"/generated-match3d-assets/s/p/items/i{index}/views/view-{view_index:02}.png"
)),
image_object_key: Some(format!(
"generated-match3d-assets/s/p/items/i{index}/views/view-{view_index:02}.png"
)),
})
.collect(),
model_src: None,
model_object_key: None,
model_file_name: None,
task_uuid: None,
subscription_key: None,
sound_prompt: None,
background_music_title: None,
background_music_style: None,
background_music_prompt: None,
background_music: None,
click_sound: None,
background_asset: None,
status: "image_ready".to_string(),
error: None,
})
.collect::<Vec<_>>();
.map(|index| Match3DGeneratedItemAsset {
item_id: format!("match3d-item-{index}"),
item_name: format!("物品{index}"),
item_size: Some(MATCH3D_ITEM_SIZE_LARGE.to_string()),
image_src: Some(format!(
"/generated-match3d-assets/s/p/items/i{index}/views/view-01.png"
)),
image_object_key: Some(format!(
"generated-match3d-assets/s/p/items/i{index}/views/view-01.png"
)),
image_views: (1..=MATCH3D_ITEM_VIEW_COUNT)
.map(|view_index| Match3DGeneratedItemImageView {
view_id: format!("view-{view_index:02}"),
view_index: view_index as u32,
image_src: Some(format!(
"/generated-match3d-assets/s/p/items/i{index}/views/view-{view_index:02}.png"
)),
image_object_key: Some(format!(
"generated-match3d-assets/s/p/items/i{index}/views/view-{view_index:02}.png"
)),
})
.collect(),
model_src: None,
model_object_key: None,
model_file_name: None,
task_uuid: None,
subscription_key: None,
sound_prompt: None,
background_music_title: None,
background_music_style: None,
background_music_prompt: None,
background_music: None,
click_sound: None,
background_asset: None,
status: "image_ready".to_string(),
error: None,
})
.collect::<Vec<_>>();
assert!(has_match3d_required_item_images(&five_view_assets, 3));
}

View File

@@ -386,7 +386,7 @@ pub(super) async fn generate_match3d_background_image(
require_match3d_oss_client(state)?;
let settings = require_openai_image_settings(state)?;
let http_client = build_openai_image_http_client(&settings)?;
let reference_image = load_match3d_container_reference_image().await?;
let reference_image = load_match3d_container_reference_image()?;
let generated_background = create_openai_image_generation(
&http_client,
&settings,
@@ -486,7 +486,7 @@ pub(super) async fn generate_match3d_container_image(
require_match3d_oss_client(state)?;
let settings = require_openai_image_settings(state)?;
let http_client = build_openai_image_http_client(&settings)?;
let reference_image = load_match3d_container_reference_image().await?;
let reference_image = load_match3d_container_reference_image()?;
let container_prompt = build_match3d_container_generation_prompt(config, prompt);
let generated_container = create_openai_image_edit(
&http_client,
@@ -563,15 +563,10 @@ pub(super) fn merge_match3d_container_image_into_background_asset(
}
}
async fn load_match3d_container_reference_image() -> Result<OpenAiReferenceImage, AppError> {
let bytes = tokio::fs::read(MATCH3D_CONTAINER_REFERENCE_IMAGE_PATH)
.await
.map_err(|error| {
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({
"provider": MATCH3D_AGENT_PROVIDER,
"message": format!("读取抓大鹅容器参考图失败:{error}"),
}))
})?;
pub(super) fn load_match3d_container_reference_image() -> Result<OpenAiReferenceImage, AppError> {
// 中文注释:生产 API 单独发布二进制Web 静态资源可能在另一轮流水线发布。
// 容器参考图属于后端生图协议输入,必须随 api-server 编译进二进制,不能依赖运行时 cwd 下存在 public/。
let bytes = MATCH3D_CONTAINER_REFERENCE_IMAGE_BYTES.to_vec();
if bytes.is_empty() {
return Err(
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({
@@ -992,7 +987,9 @@ pub(super) fn normalize_match3d_public_reference_image_path(source: &str) -> Opt
) {
return None;
}
Some(format!("public/{source}"))
Some(format!(
"{MATCH3D_PUBLIC_REFERENCE_IMAGE_PATH_PREFIX}{source}"
))
}
pub(super) fn collect_match3d_cover_reference_image_sources(