Skip to content

Commit c4f5bb0

Browse files
committed
fix(image): resolve size and prompt_extend by model profile
Stop inferring size/prompt_extend from sync vs async; use per-family sizeProfile. wanx*-imageedit uses function+base_image_url; bare qwen-image uses the fixed resolution table.
1 parent 9a13700 commit c4f5bb0

12 files changed

Lines changed: 602 additions & 162 deletions

File tree

packages/commands/src/commands/image/edit.ts

Lines changed: 60 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,6 @@ import { resolveImageSize } from "bailian-cli-runtime";
3131
import { join } from "path";
3232
import { BOOL_FLAG_PROMPT_EXTEND_CLI_TRUE, BOOL_FLAG_WATERMARK } from "bailian-cli-runtime";
3333

34-
const PROMPT_EXTEND_DEFAULT_PREFIXES = ["qwen-image-2.0", "qwen-image-max"];
35-
36-
function enablesPromptExtendByDefault(model: string): boolean {
37-
return PROMPT_EXTEND_DEFAULT_PREFIXES.some((prefix) => model.startsWith(prefix));
38-
}
39-
4034
const EDIT_FLAGS = {
4135
image: {
4236
type: "array",
@@ -71,6 +65,12 @@ const EDIT_FLAGS = {
7165
valueHint: "<text>",
7266
description: "Negative prompt to exclude unwanted content",
7367
},
68+
function: {
69+
type: "string",
70+
valueHint: "<name>",
71+
description:
72+
"wanx*-imageedit function (default: description_edit). Examples: stylization_all, description_edit",
73+
},
7474
promptExtend: {
7575
type: "boolean",
7676
valueHint: "<bool>",
@@ -109,6 +109,7 @@ export default defineCommand({
109109
'--image https://example.com/photo.png --prompt "Remove the person" --model qwen-image-2.0-pro',
110110
'--image ./photo.png --prompt "Change the style" --model wan2.7-image',
111111
'--image ./photo.png --prompt "Place the subject on a table" --model wan2.5-i2i-preview',
112+
'--image ./photo.png --prompt "转换成绘本风格" --model wanx2.1-imageedit --function stylization_all',
112113
'--image ./photo.png --prompt "Replace the background with a beach" --watermark false',
113114
],
114115
async run(ctx) {
@@ -133,22 +134,39 @@ export default defineCommand({
133134

134135
const promptExtend = resolveBooleanFlag(
135136
flags.promptExtend,
136-
enablesPromptExtendByDefault(model) ? true : undefined,
137+
route.promptExtendDefault,
137138
"prompt-extend",
138139
);
139140

140141
const watermark = resolveWatermark(flags.watermark);
141142

142143
const parameters: NonNullable<DashScopeImageRequest["parameters"]> = {
143-
size: resolveImageSize(flags.size, route.useSync),
144+
size: resolveImageSize(flags.size, route.sizeProfile),
144145
n,
145146
seed: flags.seed,
146147
prompt_extend: promptExtend,
147148
watermark,
148149
};
149150

150151
let body: DashScopeImageRequest;
151-
if (route.inputStyle === "prompt-images") {
152+
if (route.inputStyle === "function-base-image") {
153+
const baseImageUrl = resolvedImages[0];
154+
if (!baseImageUrl) {
155+
throw new BailianError(
156+
"wanx*-imageedit requires at least one --image as base_image_url.",
157+
ExitCode.USAGE,
158+
);
159+
}
160+
body = {
161+
model,
162+
input: {
163+
function: flags.function || "description_edit",
164+
prompt,
165+
base_image_url: baseImageUrl,
166+
},
167+
parameters,
168+
};
169+
} else if (route.inputStyle === "prompt-images") {
152170
body = {
153171
model,
154172
input: {
@@ -186,26 +204,39 @@ export default defineCommand({
186204
const format = detectOutputFormat(settings.output);
187205

188206
if (settings.dryRun) {
189-
const previewBody =
190-
"messages" in body.input
191-
? {
192-
...body,
193-
input: {
194-
messages: body.input.messages.map((message) => ({
195-
...message,
196-
content: message.content.map((item) =>
197-
item.image ? { ...item, image: redactDataUri(item.image) } : item,
198-
),
199-
})),
200-
},
201-
}
202-
: {
203-
...body,
204-
input: {
205-
...body.input,
206-
images: body.input.images?.map((imageUrl) => redactDataUri(imageUrl)),
207-
},
208-
};
207+
let previewBody: DashScopeImageRequest = body;
208+
if ("messages" in body.input) {
209+
previewBody = {
210+
...body,
211+
input: {
212+
messages: body.input.messages.map((message) => ({
213+
...message,
214+
content: message.content.map((item) =>
215+
item.image ? { ...item, image: redactDataUri(item.image) } : item,
216+
),
217+
})),
218+
},
219+
};
220+
} else if ("images" in body.input) {
221+
previewBody = {
222+
...body,
223+
input: {
224+
...body.input,
225+
images: body.input.images?.map((imageUrl) => redactDataUri(imageUrl)),
226+
},
227+
};
228+
} else if ("base_image_url" in body.input) {
229+
previewBody = {
230+
...body,
231+
input: {
232+
...body.input,
233+
base_image_url: redactDataUri(body.input.base_image_url),
234+
mask_image_url: body.input.mask_image_url
235+
? redactDataUri(body.input.mask_image_url)
236+
: undefined,
237+
},
238+
};
239+
}
209240
emitResult(
210241
{ request: previewBody, mode: route.useSync ? "sync" : "async", path: route.path },
211242
format,

packages/commands/src/commands/image/generate.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,6 @@ import { BOOL_FLAG_PROMPT_EXTEND_IMAGE_GENERATE, BOOL_FLAG_WATERMARK } from "bai
3030

3131
import { join } from "path";
3232

33-
const PROMPT_EXTEND_DEFAULT_PREFIXES = ["qwen-image-2.0", "qwen-image-max"];
34-
35-
function enablesPromptExtendByDefault(model: string): boolean {
36-
return PROMPT_EXTEND_DEFAULT_PREFIXES.some((prefix) => model.startsWith(prefix));
37-
}
38-
3933
const GENERATE_FLAGS = {
4034
prompt: { type: "string", valueHint: "<text>", description: "Image description", required: true },
4135
model: {
@@ -115,13 +109,13 @@ export default defineCommand({
115109
const route = resolveImageGenerateApi(model);
116110
const defaultSize = "1:1";
117111
const sizeInput = flags.size || defaultSize;
118-
const size = resolveImageSize(sizeInput, route.useSync);
112+
const size = resolveImageSize(sizeInput, route.sizeProfile);
119113
const n = flags.n ?? 1;
120114
const concurrent = getConcurrency(flags);
121115

122116
const promptExtend = resolveBooleanFlag(
123117
flags.promptExtend,
124-
enablesPromptExtendByDefault(model) ? true : undefined,
118+
route.promptExtendDefault,
125119
"prompt-extend",
126120
);
127121

packages/commands/tests/e2e/image-edit.e2e.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,78 @@ describe("e2e: image edit", () => {
9696
"data:image/png;base64,<omitted>",
9797
);
9898
});
99+
100+
test("wan2.5-i2i-preview dry-run 走 prompt+images", async () => {
101+
const { stdout, stderr, exitCode } = await runCommandE2e(IMAGE_ROUTES, [
102+
"image",
103+
"edit",
104+
"--model",
105+
"wan2.5-i2i-preview",
106+
"--image",
107+
"https://example.com/source.png",
108+
"--prompt",
109+
"Place on a table",
110+
"--size",
111+
"1:1",
112+
"--dry-run",
113+
"--output",
114+
"json",
115+
]);
116+
expect(exitCode, stderr).toBe(0);
117+
const data = parseStdoutJson<{
118+
mode?: string;
119+
path?: string;
120+
request?: {
121+
input?: { prompt?: string; images?: string[]; messages?: unknown };
122+
parameters?: { size?: string };
123+
};
124+
}>(stdout);
125+
expect(data.mode).toBe("async");
126+
expect(data.path).toBe("/api/v1/services/aigc/image2image/image-synthesis");
127+
expect(data.request?.input?.prompt).toBe("Place on a table");
128+
expect(data.request?.input?.images).toEqual(["https://example.com/source.png"]);
129+
expect(data.request?.input?.messages).toBeUndefined();
130+
expect(data.request?.parameters?.size).toBe("1280*1280");
131+
});
132+
133+
test("wanx2.1-imageedit dry-run 走 function + base_image_url", async () => {
134+
const { stdout, stderr, exitCode } = await runCommandE2e(IMAGE_ROUTES, [
135+
"image",
136+
"edit",
137+
"--model",
138+
"wanx2.1-imageedit",
139+
"--image",
140+
"https://example.com/source.png",
141+
"--prompt",
142+
"转换成绘本风格",
143+
"--function",
144+
"stylization_all",
145+
"--dry-run",
146+
"--output",
147+
"json",
148+
]);
149+
expect(exitCode, stderr).toBe(0);
150+
const data = parseStdoutJson<{
151+
mode?: string;
152+
path?: string;
153+
request?: {
154+
input?: {
155+
function?: string;
156+
prompt?: string;
157+
base_image_url?: string;
158+
images?: unknown;
159+
messages?: unknown;
160+
};
161+
};
162+
}>(stdout);
163+
expect(data.mode).toBe("async");
164+
expect(data.path).toBe("/api/v1/services/aigc/image2image/image-synthesis");
165+
expect(data.request?.input?.function).toBe("stylization_all");
166+
expect(data.request?.input?.prompt).toBe("转换成绘本风格");
167+
expect(data.request?.input?.base_image_url).toBe("https://example.com/source.png");
168+
expect(data.request?.input?.images).toBeUndefined();
169+
expect(data.request?.input?.messages).toBeUndefined();
170+
});
99171
});
100172

101173
describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())("e2e: image edit", () => {

packages/commands/tests/e2e/image-generate.e2e.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,65 @@ describe("e2e: image generate", () => {
107107
expect(data.request?.model).toBe("qwen-image-plus");
108108
});
109109

110+
test("qwen-image-plus 默认 1:1 映射为 1328*1328", async () => {
111+
const { stdout, stderr, exitCode } = await runCommandE2e(IMAGE_ROUTES, [
112+
"image",
113+
"generate",
114+
"--model",
115+
"qwen-image-plus",
116+
"--prompt",
117+
"一只猫",
118+
"--dry-run",
119+
"--output",
120+
"json",
121+
]);
122+
expect(exitCode, stderr).toBe(0);
123+
const data = parseStdoutJson<{
124+
request?: { parameters?: { size?: string; prompt_extend?: boolean } };
125+
}>(stdout);
126+
expect(data.request?.parameters?.size).toBe("1328*1328");
127+
});
128+
129+
test("z-image-turbo 默认 prompt_extend 为 false", async () => {
130+
const { stdout, stderr, exitCode } = await runCommandE2e(IMAGE_ROUTES, [
131+
"image",
132+
"generate",
133+
"--model",
134+
"z-image-turbo",
135+
"--prompt",
136+
"一只猫",
137+
"--dry-run",
138+
"--output",
139+
"json",
140+
]);
141+
expect(exitCode, stderr).toBe(0);
142+
const data = parseStdoutJson<{
143+
request?: { parameters?: { size?: string; prompt_extend?: boolean } };
144+
}>(stdout);
145+
expect(data.request?.parameters?.prompt_extend).toBe(false);
146+
expect(data.request?.parameters?.size).toBe("1024*1024");
147+
});
148+
149+
test("wanx-v1 默认 1:1 映射为 1024*1024", async () => {
150+
const { stdout, stderr, exitCode } = await runCommandE2e(IMAGE_ROUTES, [
151+
"image",
152+
"generate",
153+
"--model",
154+
"wanx-v1",
155+
"--prompt",
156+
"一只猫",
157+
"--dry-run",
158+
"--output",
159+
"json",
160+
]);
161+
expect(exitCode, stderr).toBe(0);
162+
const data = parseStdoutJson<{
163+
request?: { parameters?: { size?: string }; input?: { prompt?: string } };
164+
}>(stdout);
165+
expect(data.request?.parameters?.size).toBe("1024*1024");
166+
expect(data.request?.input?.prompt).toBe("一只猫");
167+
});
168+
110169
test("wanx2.0-t2i-turbo dry-run 走 text2image prompt 路径", async () => {
111170
const { stdout, stderr, exitCode } = await runCommandE2e(IMAGE_ROUTES, [
112171
"image",

0 commit comments

Comments
 (0)