fix(match3d): embed container reference image
This commit is contained in:
@@ -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,你要创作的关卡是难度几";
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user