恢复画布抠图模型默认请求契约

恢复角色、图标和 UI 素材提取自动提交默认 segModel。
后端重新接收并校验 segModel,非法值不回显内部 provider 或模型。
保持普通用户响应、搜索、详情、导出与错误文本的内部信息脱敏。
修正 Owner/Public、External OpenAPI 与站内 BFF 请求边界文档。
补齐前后端请求序列化和安全回归测试。
This commit is contained in:
2026-07-31 05:45:21 +00:00
parent 56fe1cb85d
commit 41327a528c
14 changed files with 94 additions and 34 deletions
@@ -19,9 +19,9 @@
## 2026-07-30 抠图实际后端作为 generationInputs 顶层内部元数据保存
- 背景:角色、图标图集和 UI 图集抠图派生资产需要保留最终实际执行的处理后端,供后台诊断 BgFilter、阿里云通用抠图和本地键色的降级结果;把抠图模型写成 `generationInputs.fields` 的“处理模型”会进入图片信息,与用户可见输入快照语义冲突,而覆盖正式资产 `model` 又会丢失源生图模型。
- 决策:继续使用现有 `generation_inputs_json` JSON 包络,不修改 SpacetimeDB schema。`fields` / `references` 只保存用户可见生成输入;仿照顶层 `screenColorHex`,抠图派生资产在顶层写入 `mattingProvider` / `mattingModel`。BgFilter 记录本次实际 `seg_model`;阿里云记录 `Aliyun Matting / segment-common-image`;本地键色记录 `Genarrative Local / screen-color-keying`。三条链路的正式资产 `model` 继续继承源生图模型,图片信息不读取顶层内部字段。`screenColorHex``mattingProvider``mattingModel` 和素材顶层 `provider` 属于内部执行信息:素材 owner、精选提交响应和后台管理接口仍按既有权限读取原始值,匿名 `GET /api/editor/showcase/resources` 必须在 public payload 边界删除三个 `generationInputs` 顶层内部键并省略 `provider`,历史精选快照不做数据迁移。角色动作逐帧可能混用多个 fallback,本次不把单帧结果提升为整组动画模型。
- 决策:继续使用现有 `generation_inputs_json` JSON 包络,不修改 SpacetimeDB schema。`fields` / `references` 只保存用户可见生成输入;仿照顶层 `screenColorHex`,抠图派生资产在顶层写入 `mattingProvider` / `mattingModel`。BgFilter 记录本次实际 `seg_model`;阿里云记录 `Aliyun Matting / segment-common-image`;本地键色记录 `Genarrative Local / screen-color-keying`。三条链路的正式资产 `model` 继续继承源生图模型,图片信息不读取顶层内部字段。`screenColorHex``mattingProvider``mattingModel` 和素材顶层 `provider` 属于内部执行信息:普通用户(包括素材 owner、精选提交 / 点赞响应与匿名公开读取统一省略,只有后台管理和服务端 raw 审计读取原始值;历史数据不迁移,在 User/Public mapper 边界清理。角色动作逐帧可能混用多个 fallback,本次不把单帧结果提升为整组动画模型。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs` 的抠图结果和角色 / 图标 / UI 派生资产持久化、图片信息兼容测试、后端数据契约与图片画布 MVP 文档;不修改前端生产展示逻辑、请求 DTO、SpacetimeDB 表、迁移或生成绑定。
- 验证方式:后端单测覆盖三种实际结果映射、顶层元数据不改写 `fields`,结构断言覆盖三条派生资产持久化链路仍保留源生图模型;public mapper 测试同时证明匿名响应省略 `provider` 和三个内部键、owner 精选快照仍保留原始值;前端测试覆盖顶层字段不在图片信息出现。运行 `cargo test -p api-server editor_project::tests --manifest-path server-rs/Cargo.toml`、图片信息定向前端测试、`cargo check -p api-server --manifest-path server-rs/Cargo.toml``npm run typecheck``npm run check:encoding``git diff --check`
- 验证方式:后端单测覆盖三种实际结果映射、顶层元数据不改写 `fields`,结构断言覆盖三条派生资产持久化链路仍保留源生图模型;User/Public mapper 测试同时证明素材 owner、精选提交 / 点赞与匿名响应省略 `provider` 和三个内部键Admin raw payload 保留原始值;前端测试覆盖顶层字段不在图片信息或搜索索引出现。运行 `cargo test -p api-server editor_project::tests --manifest-path server-rs/Cargo.toml`、图片信息定向前端测试、`cargo check -p api-server --manifest-path server-rs/Cargo.toml``npm run typecheck``npm run check:encoding``git diff --check`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`
---
@@ -31,7 +31,7 @@
- 背景:2026-07-30 的记录误把素材 owner 与后台审计并列为原始抠图执行信息的读取方。owner 是普通用户,前端不展示字段不能阻止其从项目资源、素材库、精选提交 / 点赞回包、画布布局或任务完成响应的网络 payload 读取 BgFilter、阿里云、本地键色、具体分割模型或背景色。
- 决策:本条取代 2026-07-30 决策中“素材 owner、精选提交响应仍可读取原始值”的表述。普通用户(包括素材 owner)和匿名公开读取必须共同过滤素材顶层 `provider`、内部处理 `model`,以及 `generationInputs` 顶层 `screenColorHex``mattingProvider``mattingModel`;正常用户可见生成 `model` 和其他合法功能性顶层字段(例如 `characterAnimation`)保持不变。User/Owner mapper 先完成该清理,public mapper 在其基础上叠加公开字段规则;后台管理与服务端审计继续使用 raw mapper 和持久化原值。历史数据不迁移,统一在读取边界清理。
- 入站与持久化:客户端提交的 `generationInputs` 不得伪造上述内部键,服务端在实际处理完成后才写入可信值。手动去背景的正式素材 `model` 必须继承经服务端验证的正常源生图模型;若来源或祖先链不存在正常模型则为 `null`,不得写入 `BgFilter complex` 等内部处理模型。内部抠图 provider / model 可继续持久化供后台审计,普通用户完成响应和用户可见错误文本均不得暴露它们;手动去背景与角色动作透明化失败在 Owner HTTP / 任务状态边界统一替换为稳定业务文案,原始错误只留在任务记录、tracing 和后台审计。
- 影响范围:项目资源、素材库、精选提交 / 点赞、图片 / 图标 / 视频 / 音频 / 角色动画生成完成、Agent 紧凑结果和识别出的画布资源 / 图层快照的 User/Public mapper;手动去背景持久化和完成响应;External Editor API 创建素材 / 资源时的保留键入站清理;相应 API / OpenAPI 契约、前端搜索 / 详情 / ZIP 过滤测试与后台 raw 审计测试。不修改 SpacetimeDB schema、迁移或 bindings。
- 影响范围:项目资源、素材库、精选提交 / 点赞、图片 / 图标 / 视频 / 音频 / 角色动画生成完成、Agent 紧凑结果和识别出的画布资源 / 图层快照的 User/Public mapper;手动去背景持久化和完成响应;External Editor API 创建素材 / 资源时的保留键入站清理;相应响应 / OpenAPI 契约、前端搜索 / 详情 / ZIP 过滤测试与后台 raw 审计测试。同源画布 BFF 的角色、图标和 UI 请求继续由前端自动提交默认 `segModel=birefnet`,后端继续校验并在缺失时回落默认值;该请求控制字段不进入 `generationInputs`、普通用户响应、搜索、详情、导出或错误详情。角色动作的 `seg_model` 继续由后端固定。不修改 SpacetimeDB schema、迁移或 bindings。
- 验证方式:Owner 和匿名响应覆盖无 `provider`、无内部 `model`、无三个内部 `generationInputs` 键,且正常 `model``characterAnimation` 仍保留;Admin raw payload 保持完整。覆盖历史 `BgFilter complex`、三类入站伪造键、手动去背景源模型回溯和用户错误文本过滤;运行 api-server 定向测试、前端定向测试、`cargo check -p api-server --manifest-path server-rs/Cargo.toml``npm run typecheck``npm run check:encoding``git diff --check`
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -116,7 +116,7 @@ SpacetimeDB procedure
1. 通过 OSS / asset object adapter 持久化媒体文件。
2. 写入 `editor_asset`,让生成素材进入账号级素材库。
3. 如果请求带 `projectId`,写入 `editor_project_resource`
4. 返回图片读取地址、素材 ID、资源 ID、尺寸、prompt、model、provider 和 taskId。
4. 返回图片读取地址、素材 ID、资源 ID、尺寸、prompt、model 和 taskId;普通 External API 响应、项目资源与素材 read model 不返回生成 provider 或内部抠图审计字段。同源画布前端自动提交默认 `segModel` 属于站内 BFF 请求契约,不因此向 External OpenAPI 开放该字段
如果请求未带 `projectId`,只生成并写入素材库;调用方可随后创建项目或自行保存画板布局。
@@ -333,6 +333,7 @@ impl EditorAgentTool for GenerateImageTool {
style: None,
model: Some(args.model),
screen_color: None,
seg_model: None,
aspect_ratio: Some(args.aspect_ratio),
image_size: Some(args.image_size),
reference_image_srcs: Some(reference_image_srcs),
@@ -440,6 +441,7 @@ impl EditorAgentTool for GenerateCharacterTool {
style: None,
model: Some(args.model),
screen_color: Some("auto".to_string()),
seg_model: Some("birefnet".to_string()),
aspect_ratio: Some(args.aspect_ratio),
image_size: Some(args.image_size),
reference_image_srcs: Some(reference_image_srcs),
@@ -549,6 +551,7 @@ impl EditorAgentTool for GenerateUiDesignTool {
style: None,
model: Some(args.model),
screen_color: None,
seg_model: None,
aspect_ratio: Some(args.aspect_ratio),
image_size: Some(args.image_size),
reference_image_srcs: Some(reference_image_srcs),
@@ -802,6 +805,7 @@ impl EditorAgentTool for GenerateIconSpritesheetTool {
style: None,
model: Some(args.model),
screen_color: Some("auto".to_string()),
seg_model: Some("birefnet".to_string()),
aspect_ratio: Some(args.aspect_ratio),
image_size: Some(args.image_size),
project_id: Some(context.conversation.project_id.clone()),
@@ -151,7 +151,6 @@ const EDITOR_UI_DESIGN_ASSET_IMAGE_KIND: &str = "editor_ui_design_asset";
const EDITOR_LEGACY_GREEN_SCREEN_SOURCE_ASSET_KIND: &str = "editor_green_screen_source";
const EDITOR_PROVIDER_SOURCE_SLOT: &str = "provider_source";
pub(crate) const EDITOR_BGFILTER_DEFAULT_SEG_MODEL: &str = "birefnet";
#[cfg(test)]
const EDITOR_BGFILTER_SEG_MODEL_ANIME_SEG: &str = "anime-seg";
const EDITOR_BGFILTER_MATTING_PROVIDER: &str = "BgFilter";
const EDITOR_ALIYUN_MATTING_PROVIDER: &str = "Aliyun Matting";
@@ -270,6 +269,7 @@ pub struct EditorImageGenerationRequest {
pub(crate) style: Option<String>,
pub(crate) model: Option<String>,
pub(crate) screen_color: Option<String>,
pub(crate) seg_model: Option<String>,
pub(crate) aspect_ratio: Option<String>,
pub(crate) image_size: Option<String>,
pub(crate) reference_image_srcs: Option<Vec<String>>,
@@ -327,6 +327,7 @@ pub struct EditorIconSpritesheetGenerationRequest {
pub(crate) style: Option<String>,
pub(crate) model: Option<String>,
pub(crate) screen_color: Option<String>,
pub(crate) seg_model: Option<String>,
pub(crate) aspect_ratio: Option<String>,
pub(crate) image_size: Option<String>,
pub(crate) project_id: Option<String>,
@@ -351,6 +352,7 @@ pub struct EditorUiDesignAssetExtractionRequest {
pub(crate) source_image_src: String,
pub(crate) model: Option<String>,
pub(crate) screen_color: Option<String>,
pub(crate) seg_model: Option<String>,
pub(crate) reference_image_srcs: Option<Vec<String>>,
pub(crate) aspect_ratio: String,
pub(crate) image_size: String,
@@ -1673,7 +1675,13 @@ pub(crate) async fn generate_editor_image_for_owner(
// 仍白发一次 gpt-5-mini 决策。这里先固化决策需要、但随后会被 payload 消费掉的输入。
let requested_screen_color = payload.screen_color.clone();
let reference_count = payload.reference_image_srcs.as_ref().map_or(0, Vec::len);
let seg_model = is_character_generation.then_some(EDITOR_BGFILTER_DEFAULT_SEG_MODEL);
let seg_model = if is_character_generation {
Some(parse_editor_bgfilter_seg_model(
payload.seg_model.as_deref(),
)?)
} else {
None
};
let is_ui_design_generation = matches!(normalized_kind, Some("ui-design"));
let is_publication_material_generation =
matches!(normalized_kind, Some("publication-material"));
@@ -4380,6 +4388,22 @@ fn sign_editor_private_object_read_url(
.map_err(|error| map_oss_error(error, "aliyun-oss"))
}
fn parse_editor_bgfilter_seg_model(value: Option<&str>) -> Result<&'static str, AppError> {
let Some(value) = value.map(str::trim).filter(|value| !value.is_empty()) else {
return Ok(EDITOR_BGFILTER_DEFAULT_SEG_MODEL);
};
match value.to_ascii_lowercase().as_str() {
EDITOR_BGFILTER_DEFAULT_SEG_MODEL => Ok(EDITOR_BGFILTER_DEFAULT_SEG_MODEL),
EDITOR_BGFILTER_SEG_MODEL_ANIME_SEG => Ok(EDITOR_BGFILTER_SEG_MODEL_ANIME_SEG),
_ => Err(
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
"field": "segModel",
"message": "抠图参数无效。",
})),
),
}
}
struct PersistEditorSpritesheetSlicesInput {
owner_user_id: String,
project_id: Option<String>,
@@ -4588,7 +4612,7 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
// 仍白发一次 gpt-5-mini 决策。这里先固化决策需要的输入。
let requested_screen_color = payload.screen_color.clone();
let reference_count = 1 + payload.reference_image_srcs.as_ref().map_or(0, Vec::len);
let seg_model = EDITOR_BGFILTER_DEFAULT_SEG_MODEL;
let seg_model = parse_editor_bgfilter_seg_model(payload.seg_model.as_deref())?;
let mut reference_images =
Vec::with_capacity(1 + payload.reference_image_srcs.as_ref().map_or(0, Vec::len));
reference_images.push(
@@ -5519,7 +5543,7 @@ pub(crate) async fn extract_editor_ui_design_assets_for_owner(
"从 UI 设计图提取被红色框选的素材,输出 spritesheet。目标规格 {} {}。",
payload.aspect_ratio, payload.image_size
);
let seg_model = EDITOR_BGFILTER_DEFAULT_SEG_MODEL;
let seg_model = parse_editor_bgfilter_seg_model(payload.seg_model.as_deref())?;
let reference_image = parse_editor_reference_image(
state,
caller.owner_user_id.as_str(),
@@ -12355,12 +12379,7 @@ mod tests {
}))
.expect("UI extraction request should deserialize dimensions");
assert_eq!(request.screen_color.as_deref(), Some("#E6D8FF"));
assert!(
serde_json::to_value(&request)
.expect("request should serialize")
.get("segModel")
.is_none()
);
assert_eq!(request.seg_model.as_deref(), Some("anime-seg"));
let generation_options = normalize_editor_ui_design_asset_extraction_options(
request.model.as_deref(),
request.aspect_ratio.as_str(),
@@ -12426,12 +12445,7 @@ mod tests {
assert_eq!(request.prompt, "生成图片");
assert_eq!(request.screen_color.as_deref(), Some("#CFEFFF"));
assert!(
serde_json::to_value(&request)
.expect("request should serialize")
.get("segModel")
.is_none()
);
assert_eq!(request.seg_model.as_deref(), Some("anime-seg"));
assert_eq!(request.style, None);
}
@@ -12698,12 +12712,7 @@ mod tests {
Some(vec!["/generated-images/editor/style.png".to_string()])
);
assert_eq!(request.screen_color.as_deref(), Some("#7FB3FF"));
assert!(
serde_json::to_value(&request)
.expect("request should serialize")
.get("segModel")
.is_none()
);
assert_eq!(request.seg_model.as_deref(), Some("anime-seg"));
assert_eq!(request.asset_label.as_deref(), Some(" 冒险界面图标 "));
assert_eq!(request.style, None);
@@ -12811,6 +12820,26 @@ mod tests {
);
}
#[test]
fn editor_bgfilter_seg_model_parser_defaults_and_rejects_unknown_without_internal_details() {
assert_eq!(
parse_editor_bgfilter_seg_model(None).expect("default should parse"),
EDITOR_BGFILTER_DEFAULT_SEG_MODEL
);
assert_eq!(
parse_editor_bgfilter_seg_model(Some("Anime-Seg")).expect("anime seg should parse"),
EDITOR_BGFILTER_SEG_MODEL_ANIME_SEG
);
let error =
parse_editor_bgfilter_seg_model(Some("u2net")).expect_err("unknown model should fail");
let body = error.body_text();
assert!(body.contains("抠图参数无效"));
assert!(!body.to_ascii_lowercase().contains("bgfilter"));
assert!(!body.contains(EDITOR_BGFILTER_DEFAULT_SEG_MODEL));
assert!(!body.contains(EDITOR_BGFILTER_SEG_MODEL_ANIME_SEG));
}
#[test]
fn editor_bgfilter_worker_budget_covers_two_attempts_and_parent_reserves() {
let config = AppConfig::default();
@@ -3529,6 +3529,7 @@ describe('ImageCanvasEditorView generation integration', () => {
sourceImageSrc:
'generated-character-drafts/editor/generation-references/marked-ui-design.png',
screenColor: 'auto',
segModel: 'birefnet',
aspectRatio: '1:1',
imageSize: '1K',
projectId: 'editor-project-ui-extract',
@@ -100,6 +100,7 @@ export const DEFAULT_ICON_DESCRIPTIONS = [
'冻结按钮',
];
export const DEFAULT_EDITOR_GENERATION_BACKGROUND_COLOR = 'auto';
export const DEFAULT_EDITOR_BGFILTER_SEG_MODEL = 'birefnet';
export const UI_DESIGN_ASSET_EXTRACTION_PROMPT =
'仅提取被红色框框选的素材并整理成spritesheet,图集背景必须使用所选单一纯色抠图背景。纯色背景必须平整无纹理、无渐变、无阴影、无地面、无环境、无道具,方便后续扣除背景;素材自身不要出现与背景色相同或相近的描边、底板、投影或反光。';
export const QUICK_EDIT_SIZE_PRESETS = [
@@ -343,6 +343,7 @@ describe('ImageCanvasGenerationSubmissionModel', () => {
kind: 'character',
model: 'gpt-image-2',
screenColor: 'auto',
segModel: 'birefnet',
style: 'pixelArt',
aspectRatio: '2:3',
imageSize: '2K',
@@ -716,6 +717,7 @@ describe('ImageCanvasGenerationSubmissionModel', () => {
iconDescriptions: ['返回按钮\n\n设置按钮'],
model: 'gpt-image-2',
screenColor: 'auto',
segModel: 'birefnet',
style: 'pixelArt',
aspectRatio: '3:2',
imageSize: '2K',
@@ -29,6 +29,7 @@ import {
buildUiDesignGenerationInputs,
buildVideoGenerationInputs,
CHARACTER_ANIMATION_MODEL,
DEFAULT_EDITOR_BGFILTER_SEG_MODEL,
DEFAULT_EDITOR_GENERATION_BACKGROUND_COLOR,
DEFAULT_SOUND_EFFECT_DURATION_SECONDS,
DEFAULT_SOUND_EFFECT_MODEL,
@@ -342,6 +343,7 @@ export function buildImageGenerationSubmissionPlan({
].filter((src): src is string => Boolean(src));
const imageModel = normalizeEditorImageModel(dialog.imageModel);
const screenColor = DEFAULT_EDITOR_GENERATION_BACKGROUND_COLOR;
const segModel = DEFAULT_EDITOR_BGFILTER_SEG_MODEL;
return {
kind: 'image',
normalizedPrompt,
@@ -350,6 +352,7 @@ export function buildImageGenerationSubmissionPlan({
kind: 'character',
model: imageModel,
screenColor,
segModel,
style: dialog.style === 'pixelArt' ? 'pixelArt' : 'none',
aspectRatio: dialog.aspectRatio ?? '1:1',
imageSize: dialog.imageSize ?? '1K',
@@ -616,6 +619,7 @@ export function buildIconSpritesheetGenerationSubmissionPlan(
const rememberImageModel = normalizeEditorImageModel(dialog.imageModel);
const screenColor = DEFAULT_EDITOR_GENERATION_BACKGROUND_COLOR;
const segModel = DEFAULT_EDITOR_BGFILTER_SEG_MODEL;
return {
ok: true,
iconDescriptions,
@@ -633,6 +637,7 @@ export function buildIconSpritesheetGenerationSubmissionPlan(
iconDescriptions,
model: rememberImageModel,
screenColor,
segModel,
style: dialog.style === 'pixelArt' ? 'pixelArt' : 'none',
aspectRatio: dialog.aspectRatio ?? '1:1',
imageSize: dialog.imageSize ?? '1K',
@@ -2573,6 +2573,7 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
sourceImageSrc: 'generated/ui-design.png',
model: 'gemini-3.1-flash-image-preview',
screenColor: 'auto',
segModel: 'birefnet',
}),
);
});
@@ -2709,6 +2710,7 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
'generated-character-drafts/editor/generation-references/reference.png',
model: 'gemini-3.1-flash-image-preview',
screenColor: 'auto',
segModel: 'birefnet',
aspectRatio: '1:1',
imageSize: '2K',
canvasCompletion: expect.objectContaining({
@@ -2770,6 +2772,7 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
expect(extractEditorUiDesignAssetsMock).toHaveBeenCalledWith(
expect.objectContaining({
screenColor: 'auto',
segModel: 'birefnet',
referenceImageSrcs: ['generated/ref-style.png'],
generationInputs: expect.objectContaining({
references: expect.arrayContaining([
@@ -58,6 +58,7 @@ import {
buildQuickEditGenerationInputs,
buildUiDesignAssetExtractionGenerationInputs,
buildVideoGenerationInputs,
DEFAULT_EDITOR_BGFILTER_SEG_MODEL,
DEFAULT_EDITOR_GENERATION_BACKGROUND_COLOR,
DEFAULT_IMAGE_MODEL,
DEFAULT_VIDEO_MODEL,
@@ -1085,6 +1086,7 @@ export function useImageCanvasGenerationSubmissionWorkflow({
);
}
const screenColor = DEFAULT_EDITOR_GENERATION_BACKGROUND_COLOR;
const segModel = DEFAULT_EDITOR_BGFILTER_SEG_MODEL;
const generationInputs = buildUiDesignAssetExtractionGenerationInputs(
sourceLayer,
options.references,
@@ -1121,6 +1123,7 @@ export function useImageCanvasGenerationSubmissionWorkflow({
options.model ?? DEFAULT_IMAGE_MODEL,
),
screenColor,
segModel,
...(referenceImageSrcs.length ? { referenceImageSrcs } : {}),
projectId,
generationInputs,
@@ -837,6 +837,7 @@ describe('editorProjectClient', () => {
kind: 'character',
model: 'gpt-image-2',
screenColor: '#FFD6C2',
segModel: 'anime-seg',
style: 'pixelArt',
aspectRatio: '2:3',
imageSize: '1K',
@@ -861,6 +862,7 @@ describe('editorProjectClient', () => {
kind: 'character',
model: 'gpt-image-2',
screenColor: '#FFD6C2',
segModel: 'anime-seg',
style: 'pixelArt',
aspectRatio: '2:3',
imageSize: '1K',
@@ -1014,6 +1016,7 @@ describe('editorProjectClient', () => {
iconDescriptions: ['返回按钮'],
model: 'gpt-image-2',
screenColor: '#E6D8FF',
segModel: 'anime-seg',
style: 'none',
aspectRatio: '1:1',
imageSize: '2K',
@@ -1035,6 +1038,7 @@ describe('editorProjectClient', () => {
iconDescriptions: ['返回按钮'],
model: 'gpt-image-2',
screenColor: '#E6D8FF',
segModel: 'anime-seg',
style: 'none',
aspectRatio: '1:1',
imageSize: '2K',
@@ -1153,6 +1157,7 @@ describe('editorProjectClient', () => {
sourceImageSrc: '/generated-images/editor/ui-design.png',
model: 'gpt-image-2',
screenColor: '#7FB3FF',
segModel: 'anime-seg',
referenceImageSrcs: ['/generated-images/editor/ui-ref.png'],
projectId: 'editor-project-1',
assetFolderId: 'project',
@@ -1175,6 +1180,7 @@ describe('editorProjectClient', () => {
sourceImageSrc: '/generated-images/editor/ui-design.png',
model: 'gpt-image-2',
screenColor: '#7FB3FF',
segModel: 'anime-seg',
referenceImageSrcs: ['/generated-images/editor/ui-ref.png'],
projectId: 'editor-project-1',
generationInputs: {
@@ -197,6 +197,7 @@ export type EditorImageGenerationInput = {
| 'publication-material';
model?: string;
screenColor?: string;
segModel?: string;
style?: EditorImageGenerationStyle;
aspectRatio?: string;
imageSize?: string;
@@ -229,6 +230,7 @@ export type EditorIconSpritesheetGenerationInput = {
iconDescriptions: string[];
model?: string;
screenColor?: string;
segModel?: string;
style?: EditorImageGenerationStyle;
aspectRatio?: string;
imageSize?: string;
@@ -250,6 +252,7 @@ export type EditorUiDesignAssetExtractionInput = {
sourceImageSrc: string;
model?: string;
screenColor?: string;
segModel?: string;
referenceImageSrcs?: string[];
aspectRatio?: string;
imageSize?: string;
@@ -916,6 +919,7 @@ export async function generateEditorImage(input: EditorImageGenerationInput) {
...(input.kind ? { kind: input.kind } : {}),
...(input.model ? { model: input.model } : {}),
...(input.screenColor ? { screenColor: input.screenColor } : {}),
...(input.segModel ? { segModel: input.segModel } : {}),
...(input.style ? { style: input.style } : {}),
...(input.aspectRatio ? { aspectRatio: input.aspectRatio } : {}),
...(input.imageSize ? { imageSize: input.imageSize } : {}),
@@ -962,6 +966,7 @@ export async function generateEditorIconSpritesheet(
iconDescriptions: input.iconDescriptions,
model: input.model?.trim() || EDITOR_IMAGE_MODEL_NANOBANANA2,
...(input.screenColor ? { screenColor: input.screenColor } : {}),
...(input.segModel ? { segModel: input.segModel } : {}),
...(input.style ? { style: input.style } : {}),
...(input.aspectRatio ? { aspectRatio: input.aspectRatio } : {}),
...(input.imageSize ? { imageSize: input.imageSize } : {}),
@@ -1009,6 +1014,7 @@ export async function extractEditorUiDesignAssets(
sourceImageSrc: input.sourceImageSrc,
...(input.model ? { model: input.model } : {}),
...(input.screenColor ? { screenColor: input.screenColor } : {}),
...(input.segModel ? { segModel: input.segModel } : {}),
...(input.referenceImageSrcs?.length
? { referenceImageSrcs: input.referenceImageSrcs }
: {}),