修复图标图集自动拆分降级
图集带背景原图和透明整图先写入项目资源与账号素材库并回填画布 自动拆分识别或持久化失败时保留图集并返回结构化警告 手动拆分限制图片尺寸、总像素和单次切片数量 同步共享契约、外部 OpenAPI、前端类型、设计文档和回归测试
This commit is contained in:
@@ -103,6 +103,9 @@ const EDITOR_IMAGE_MODEL_NANOBANANA2: &str = "gemini-3.1-flash-image-preview";
|
||||
const EDITOR_IMAGE_MODEL_NANOBANANA2_DISPLAY_ALIAS: &str = "nanobanana2";
|
||||
const EDITOR_IMAGE_MODEL_NANOBANANA_LEGACY_ALIAS: &str = "nano-banana";
|
||||
const EDITOR_ICON_DESCRIPTION_LIMIT: usize = 100;
|
||||
const EDITOR_ICON_SPRITESHEET_MANUAL_MAX_DIMENSION: u32 = 4096;
|
||||
const EDITOR_ICON_SPRITESHEET_MANUAL_MAX_PIXELS: u64 = 2048 * 2048;
|
||||
const EDITOR_ICON_SPRITESHEET_MANUAL_MAX_SLICES: usize = 64;
|
||||
const EDITOR_UI_DESIGN_ASSET_EXTRACTION_REFERENCE_LIMIT: usize = 5;
|
||||
const EDITOR_CHARACTER_IMAGE_ASSET_KIND: &str = "editor_character_image";
|
||||
const EDITOR_CHARACTER_IMAGE_ENTITY_KIND: &str = "editor_project";
|
||||
@@ -114,6 +117,8 @@ const EDITOR_UI_DESIGN_IMAGE_ASSET_KIND: &str = "editor_ui_design_image";
|
||||
const EDITOR_IMAGE_EDIT_ASSET_KIND: &str = "editor_image_edit";
|
||||
const EDITOR_ICON_SPRITESHEET_ASSET_KIND: &str = "editor_icon_spritesheet";
|
||||
const EDITOR_ICON_SPRITESHEET_SLICE_ASSET_KIND: &str = "editor_icon_spritesheet_slice";
|
||||
const EDITOR_ICON_SPRITESHEET_SLICE_WARNING_COMPONENTS: &str = "insufficient-connected-components";
|
||||
const EDITOR_ICON_SPRITESHEET_SLICE_WARNING_PERSISTENCE: &str = "slice-persistence-failed";
|
||||
const EDITOR_UI_DESIGN_SPRITESHEET_ASSET_KIND: &str = "editor_ui_design_spritesheet";
|
||||
const EDITOR_UI_DESIGN_ASSET_IMAGE_KIND: &str = "editor_ui_design_asset";
|
||||
const EDITOR_GREEN_SCREEN_SOURCE_ASSET_KIND: &str = "editor_green_screen_source";
|
||||
@@ -564,6 +569,13 @@ pub struct EditorIconSpritesheetIconResponse {
|
||||
asset: Option<EditorAssetPayload>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorIconSpritesheetSliceWarningResponse {
|
||||
code: &'static str,
|
||||
reason: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorIconSpritesheetGenerationResponse {
|
||||
@@ -571,6 +583,8 @@ pub struct EditorIconSpritesheetGenerationResponse {
|
||||
spritesheet_width: u32,
|
||||
spritesheet_height: u32,
|
||||
icon_image_srcs: Vec<EditorIconSpritesheetIconResponse>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
slice_warning: Option<EditorIconSpritesheetSliceWarningResponse>,
|
||||
prompt: String,
|
||||
actual_prompt: Option<String>,
|
||||
model: String,
|
||||
@@ -1629,6 +1643,7 @@ pub(crate) async fn generate_editor_image_for_owner(
|
||||
PersistEditorGreenScreenSourceResourceInput {
|
||||
project_id: payload.project_id.clone(),
|
||||
owner_user_id: caller.owner_user_id.clone(),
|
||||
folder_id: None,
|
||||
label: format!("{}(原图)", role_setting),
|
||||
width: source_width,
|
||||
height: source_height,
|
||||
@@ -3481,12 +3496,17 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
|
||||
&matting_audit,
|
||||
)
|
||||
.await?;
|
||||
let asset_folder_id = payload
|
||||
.asset_folder_id
|
||||
.clone()
|
||||
.or_else(|| Some(EDITOR_ASSET_DEFAULT_FOLDER_ID.to_string()));
|
||||
let source_record = persist_editor_green_screen_source_resource(
|
||||
state,
|
||||
source_persisted,
|
||||
PersistEditorGreenScreenSourceResourceInput {
|
||||
project_id: payload.project_id.clone(),
|
||||
owner_user_id: caller.owner_user_id.clone(),
|
||||
folder_id: asset_folder_id.clone(),
|
||||
label: "图标素材图集(原图)".to_string(),
|
||||
width: source_width,
|
||||
height: source_height,
|
||||
@@ -3502,19 +3522,6 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
|
||||
let (spritesheet_width, spritesheet_height) = image::load_from_memory(image.bytes.as_slice())
|
||||
.map(|image| (image.width(), image.height()))
|
||||
.unwrap_or((512, 512));
|
||||
let source = DownloadedImage {
|
||||
bytes: image.bytes.clone(),
|
||||
mime_type: image.mime_type.clone(),
|
||||
extension: image.extension.clone(),
|
||||
};
|
||||
let icon_slices =
|
||||
slice_generated_icon_spritesheet_by_connected_components(&source, &icon_descriptions)
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": "editor-icon-spritesheet",
|
||||
"message": error.to_string(),
|
||||
}))
|
||||
})?;
|
||||
let owner_user_id = caller.owner_user_id;
|
||||
let spritesheet_persisted = persist_editor_generated_image(
|
||||
state,
|
||||
@@ -3537,7 +3544,7 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
|
||||
PersistEditorGeneratedAssetInput {
|
||||
project_id: payload.project_id.clone(),
|
||||
owner_user_id: owner_user_id.clone(),
|
||||
folder_id: payload.asset_folder_id.clone(),
|
||||
folder_id: asset_folder_id.clone(),
|
||||
label: "图标素材图集".to_string(),
|
||||
image_src: spritesheet_image_src.clone(),
|
||||
object_key: Some(spritesheet_persisted.object_key.clone()),
|
||||
@@ -3557,29 +3564,79 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let icon_image_srcs = persist_editor_spritesheet_slices(
|
||||
state,
|
||||
icon_slices,
|
||||
PersistEditorSpritesheetSlicesInput {
|
||||
owner_user_id: owner_user_id.clone(),
|
||||
project_id: payload.project_id.clone(),
|
||||
asset_folder_id: payload.asset_folder_id.clone(),
|
||||
source_resource_id: spritesheet_record
|
||||
.resource
|
||||
.as_ref()
|
||||
.map(|resource| resource.resource_id.clone()),
|
||||
task_id: generated.task_id.clone(),
|
||||
prompt: prompt.clone(),
|
||||
actual_prompt: generated.actual_prompt.clone(),
|
||||
model: generation_options.model.to_string(),
|
||||
provider: "VectorEngine".to_string(),
|
||||
generation_inputs: generation_inputs.clone(),
|
||||
path_kind: "icon-spritesheet-assets",
|
||||
asset_kind: EDITOR_ICON_SPRITESHEET_SLICE_ASSET_KIND,
|
||||
persistence_provider: "vector-engine",
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let slice_source = DownloadedImage {
|
||||
bytes: image.bytes.clone(),
|
||||
mime_type: image.mime_type.clone(),
|
||||
extension: image.extension.clone(),
|
||||
};
|
||||
let (icon_image_srcs, slice_warning) =
|
||||
match slice_generated_icon_spritesheet_by_connected_components(
|
||||
&slice_source,
|
||||
&icon_descriptions,
|
||||
) {
|
||||
Ok(icon_slices) => {
|
||||
match persist_editor_spritesheet_slices(
|
||||
state,
|
||||
icon_slices,
|
||||
PersistEditorSpritesheetSlicesInput {
|
||||
owner_user_id: owner_user_id.clone(),
|
||||
project_id: payload.project_id.clone(),
|
||||
asset_folder_id: asset_folder_id.clone(),
|
||||
source_resource_id: spritesheet_record
|
||||
.resource
|
||||
.as_ref()
|
||||
.map(|resource| resource.resource_id.clone()),
|
||||
task_id: generated.task_id.clone(),
|
||||
prompt: prompt.clone(),
|
||||
actual_prompt: generated.actual_prompt.clone(),
|
||||
model: generation_options.model.to_string(),
|
||||
provider: "VectorEngine".to_string(),
|
||||
generation_inputs: generation_inputs.clone(),
|
||||
path_kind: "icon-spritesheet-assets",
|
||||
asset_kind: EDITOR_ICON_SPRITESHEET_SLICE_ASSET_KIND,
|
||||
persistence_provider: "vector-engine",
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(icon_image_srcs) => (icon_image_srcs, None),
|
||||
Err(error) => {
|
||||
let reason = error.body_text();
|
||||
tracing::warn!(
|
||||
provider = "editor-icon-spritesheet",
|
||||
operation = "persist_automatic_slices",
|
||||
task_id = %generated.task_id,
|
||||
reason = %reason,
|
||||
"图标图集已持久化,但自动拆分素材持久化失败"
|
||||
);
|
||||
(
|
||||
Vec::new(),
|
||||
Some(EditorIconSpritesheetSliceWarningResponse {
|
||||
code: EDITOR_ICON_SPRITESHEET_SLICE_WARNING_PERSISTENCE,
|
||||
reason,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
let reason = error.to_string();
|
||||
tracing::warn!(
|
||||
provider = "editor-icon-spritesheet",
|
||||
operation = "detect_automatic_slices",
|
||||
task_id = %generated.task_id,
|
||||
reason = %reason,
|
||||
"图标图集已持久化,但自动拆分未识别到全部素材"
|
||||
);
|
||||
(
|
||||
Vec::new(),
|
||||
Some(EditorIconSpritesheetSliceWarningResponse {
|
||||
code: EDITOR_ICON_SPRITESHEET_SLICE_WARNING_COMPONENTS,
|
||||
reason,
|
||||
}),
|
||||
)
|
||||
}
|
||||
};
|
||||
let (canvas_items, primary_layer_id) =
|
||||
if let Some(completion) = payload.canvas_completion.as_ref() {
|
||||
build_icon_spritesheet_canvas_layer_items(
|
||||
@@ -3608,6 +3665,7 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
|
||||
spritesheet_width,
|
||||
spritesheet_height,
|
||||
icon_image_srcs,
|
||||
slice_warning,
|
||||
prompt,
|
||||
actual_prompt: generated.actual_prompt,
|
||||
model: generation_options.model.to_string(),
|
||||
@@ -3674,6 +3732,7 @@ pub async fn split_editor_icon_spritesheet(
|
||||
mime_type: reference.mime_type,
|
||||
extension: "png".to_string(),
|
||||
};
|
||||
validate_editor_icon_spritesheet_manual_source(&source)?;
|
||||
let slices =
|
||||
slice_generated_icon_spritesheet_all_by_connected_components(&source).map_err(|error| {
|
||||
AppError::from_status(StatusCode::UNPROCESSABLE_ENTITY).with_details(json!({
|
||||
@@ -3681,6 +3740,7 @@ pub async fn split_editor_icon_spritesheet(
|
||||
"message": error.to_string(),
|
||||
}))
|
||||
})?;
|
||||
validate_editor_icon_spritesheet_manual_slice_count(slices.len())?;
|
||||
let task_id = build_prefixed_uuid_id("editor-atlas-split-");
|
||||
let prompt = source_resource
|
||||
.prompt
|
||||
@@ -3750,6 +3810,65 @@ pub async fn split_editor_icon_spritesheet(
|
||||
))
|
||||
}
|
||||
|
||||
fn validate_editor_icon_spritesheet_manual_source(
|
||||
source: &DownloadedImage,
|
||||
) -> Result<(), AppError> {
|
||||
let reader = image::ImageReader::new(Cursor::new(source.bytes.as_slice()))
|
||||
.with_guessed_format()
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::UNPROCESSABLE_ENTITY).with_details(json!({
|
||||
"provider": "editor-icon-spritesheet-slicing",
|
||||
"message": format!("无法识别图集图片格式:{error}"),
|
||||
}))
|
||||
})?;
|
||||
let (width, height) = reader.into_dimensions().map_err(|error| {
|
||||
AppError::from_status(StatusCode::UNPROCESSABLE_ENTITY).with_details(json!({
|
||||
"provider": "editor-icon-spritesheet-slicing",
|
||||
"message": format!("无法读取图集图片尺寸:{error}"),
|
||||
}))
|
||||
})?;
|
||||
validate_editor_icon_spritesheet_manual_dimensions(width, height)
|
||||
}
|
||||
|
||||
fn validate_editor_icon_spritesheet_manual_dimensions(
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<(), AppError> {
|
||||
let pixel_count = u64::from(width).saturating_mul(u64::from(height));
|
||||
if width == 0
|
||||
|| height == 0
|
||||
|| width > EDITOR_ICON_SPRITESHEET_MANUAL_MAX_DIMENSION
|
||||
|| height > EDITOR_ICON_SPRITESHEET_MANUAL_MAX_DIMENSION
|
||||
|| pixel_count > EDITOR_ICON_SPRITESHEET_MANUAL_MAX_PIXELS
|
||||
{
|
||||
return Err(
|
||||
AppError::from_status(StatusCode::UNPROCESSABLE_ENTITY).with_details(json!({
|
||||
"provider": "editor-icon-spritesheet-slicing",
|
||||
"message": "图集尺寸超过手动拆分限制。",
|
||||
"width": width,
|
||||
"height": height,
|
||||
"maxDimension": EDITOR_ICON_SPRITESHEET_MANUAL_MAX_DIMENSION,
|
||||
"maxPixels": EDITOR_ICON_SPRITESHEET_MANUAL_MAX_PIXELS,
|
||||
})),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_editor_icon_spritesheet_manual_slice_count(slice_count: usize) -> Result<(), AppError> {
|
||||
if slice_count > EDITOR_ICON_SPRITESHEET_MANUAL_MAX_SLICES {
|
||||
return Err(
|
||||
AppError::from_status(StatusCode::UNPROCESSABLE_ENTITY).with_details(json!({
|
||||
"provider": "editor-icon-spritesheet-slicing",
|
||||
"message": "图集识别出的素材数量超过手动拆分限制。",
|
||||
"sliceCount": slice_count,
|
||||
"maxSliceCount": EDITOR_ICON_SPRITESHEET_MANUAL_MAX_SLICES,
|
||||
})),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn extract_editor_ui_design_assets(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
@@ -3976,6 +4095,7 @@ pub(crate) async fn extract_editor_ui_design_assets_for_owner(
|
||||
PersistEditorGreenScreenSourceResourceInput {
|
||||
project_id: payload.project_id.clone(),
|
||||
owner_user_id: caller.owner_user_id.clone(),
|
||||
folder_id: None,
|
||||
label: format!("{}(原图)", source_label),
|
||||
width: source_width,
|
||||
height: source_height,
|
||||
@@ -4135,6 +4255,7 @@ pub(crate) async fn extract_editor_ui_design_assets_for_owner(
|
||||
spritesheet_width,
|
||||
spritesheet_height,
|
||||
icon_image_srcs,
|
||||
slice_warning: None,
|
||||
prompt,
|
||||
actual_prompt: generated.actual_prompt,
|
||||
model: generation_options.model.to_string(),
|
||||
@@ -5907,6 +6028,7 @@ pub(crate) struct PersistedEditorGeneratedImage {
|
||||
struct PersistEditorGreenScreenSourceResourceInput {
|
||||
project_id: Option<String>,
|
||||
owner_user_id: String,
|
||||
folder_id: Option<String>,
|
||||
label: String,
|
||||
width: u32,
|
||||
height: u32,
|
||||
@@ -6057,7 +6179,7 @@ async fn persist_editor_green_screen_source_resource(
|
||||
PersistEditorGeneratedAssetInput {
|
||||
project_id: input.project_id,
|
||||
owner_user_id: input.owner_user_id,
|
||||
folder_id: None,
|
||||
folder_id: input.folder_id,
|
||||
label: input.label,
|
||||
image_src: editor_media_src_from_object_key(persisted.object_key.as_str()),
|
||||
object_key: Some(persisted.object_key),
|
||||
@@ -8041,6 +8163,59 @@ mod tests {
|
||||
assert!(items[2]["x"].as_f64().unwrap() > source_right);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_canvas_spritesheet_items_keep_sheet_and_source_when_slices_are_empty() {
|
||||
let completion = EditorCanvasGenerationCompletionRequest {
|
||||
dialog_id: Some("dialog-icon-sheet".to_string()),
|
||||
title: "图标素材图集".to_string(),
|
||||
placeholder: EditorCanvasGenerationPlaceholderPayload {
|
||||
x: 100.0,
|
||||
y: 80.0,
|
||||
width: 256.0,
|
||||
height: 256.0,
|
||||
original_width: 512.0,
|
||||
original_height: 512.0,
|
||||
},
|
||||
};
|
||||
let processed = editor_project_resource_for_canvas_test(
|
||||
"resource-icon-sheet",
|
||||
"icon-spritesheet",
|
||||
512,
|
||||
512,
|
||||
);
|
||||
let source = editor_project_resource_for_canvas_test(
|
||||
"resource-icon-source",
|
||||
EDITOR_GREEN_SCREEN_SOURCE_ASSET_KIND,
|
||||
512,
|
||||
512,
|
||||
);
|
||||
|
||||
let (items, primary_layer_id) = build_icon_spritesheet_canvas_layer_items(
|
||||
&completion,
|
||||
Some(&processed),
|
||||
Some(&source),
|
||||
&[],
|
||||
);
|
||||
|
||||
assert_eq!(items.len(), 2);
|
||||
assert_eq!(items[0]["resourceId"], json!("resource-icon-sheet"));
|
||||
assert_eq!(items[1]["resourceId"], json!("resource-icon-source"));
|
||||
assert_eq!(
|
||||
primary_layer_id.as_deref(),
|
||||
Some("layer-editor-resource-resource-icon-sheet")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manual_icon_spritesheet_limits_reject_large_images_and_excessive_slices() {
|
||||
assert!(validate_editor_icon_spritesheet_manual_dimensions(2048, 2048).is_ok());
|
||||
assert!(validate_editor_icon_spritesheet_manual_dimensions(4096, 1024).is_ok());
|
||||
assert!(validate_editor_icon_spritesheet_manual_dimensions(2049, 2048).is_err());
|
||||
assert!(validate_editor_icon_spritesheet_manual_dimensions(4097, 1).is_err());
|
||||
assert!(validate_editor_icon_spritesheet_manual_slice_count(64).is_ok());
|
||||
assert!(validate_editor_icon_spritesheet_manual_slice_count(65).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_canvas_generation_completion_skips_when_dialog_was_removed() {
|
||||
let layers = json!([
|
||||
@@ -8386,6 +8561,10 @@ mod tests {
|
||||
spritesheet_width: 512,
|
||||
spritesheet_height: 512,
|
||||
icon_image_srcs: Vec::new(),
|
||||
slice_warning: Some(EditorIconSpritesheetSliceWarningResponse {
|
||||
code: EDITOR_ICON_SPRITESHEET_SLICE_WARNING_COMPONENTS,
|
||||
reason: "图标 spritesheet 连通域数量不足:需要 2 个,实际 1 个。".to_string(),
|
||||
}),
|
||||
prompt: "图标 prompt".to_string(),
|
||||
actual_prompt: Some("图标 prompt".to_string()),
|
||||
model: "gemini-3.1-flash-image-preview".to_string(),
|
||||
@@ -8404,6 +8583,15 @@ mod tests {
|
||||
|
||||
assert_eq!(payload["priceMudPoints"], json!(12));
|
||||
assert_eq!(payload["iconImageSrcs"], json!([]));
|
||||
assert_eq!(
|
||||
payload["sliceWarning"]["code"],
|
||||
json!("insufficient-connected-components")
|
||||
);
|
||||
assert!(
|
||||
payload["sliceWarning"]["reason"]
|
||||
.as_str()
|
||||
.is_some_and(|reason| reason.contains("连通域数量不足"))
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -875,6 +875,11 @@ mod tests {
|
||||
.get("/api/external/v1/editor/icon-spritesheets/generations")
|
||||
.is_some()
|
||||
);
|
||||
assert_eq!(
|
||||
parsed["components"]["schemas"]["EditorIconSpritesheetGenerationResponse"]["properties"]
|
||||
["sliceWarning"]["anyOf"][0]["$ref"],
|
||||
"#/components/schemas/EditorIconSpritesheetSliceWarning"
|
||||
);
|
||||
assert!(
|
||||
parsed["paths"]
|
||||
.get("/api/external/v1/editor/ui-designs/assets/extractions")
|
||||
|
||||
@@ -477,6 +477,13 @@ pub struct EditorIconSpritesheetIconPayload {
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorIconSpritesheetSliceWarningPayload {
|
||||
pub code: String,
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorIconSpritesheetGenerateResponse {
|
||||
@@ -484,6 +491,8 @@ pub struct EditorIconSpritesheetGenerateResponse {
|
||||
pub spritesheet_width: u32,
|
||||
pub spritesheet_height: u32,
|
||||
pub icon_image_srcs: Vec<EditorIconSpritesheetIconPayload>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub slice_warning: Option<EditorIconSpritesheetSliceWarningPayload>,
|
||||
pub prompt: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub actual_prompt: Option<String>,
|
||||
@@ -1293,6 +1302,10 @@ mod tests {
|
||||
spritesheet_width: 512,
|
||||
spritesheet_height: 512,
|
||||
icon_image_srcs: Vec::new(),
|
||||
slice_warning: Some(EditorIconSpritesheetSliceWarningPayload {
|
||||
code: "insufficient-connected-components".to_string(),
|
||||
reason: "连通域数量不足".to_string(),
|
||||
}),
|
||||
prompt: "图标 prompt".to_string(),
|
||||
actual_prompt: Some("图标 prompt".to_string()),
|
||||
model: "gemini-3.1-flash-image-preview".to_string(),
|
||||
@@ -1304,6 +1317,13 @@ mod tests {
|
||||
|
||||
assert_eq!(response_payload["priceMudPoints"], json!(12));
|
||||
assert_eq!(response_payload["iconImageSrcs"], json!([]));
|
||||
assert_eq!(
|
||||
response_payload["sliceWarning"],
|
||||
json!({
|
||||
"code": "insufficient-connected-components",
|
||||
"reason": "连通域数量不足",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user