11import {
22 defineCommand ,
3- imagePath ,
4- imageSyncPath ,
53 taskPath ,
64 detectOutputFormat ,
75 resolveOutputDir ,
@@ -20,6 +18,7 @@ import {
2018 BailianError ,
2119 resolveBooleanFlag ,
2220 resolveWatermark ,
21+ resolveImageEditApi ,
2322 ASYNC_FLAG ,
2423 CONCURRENT_FLAG ,
2524 redactDataUri ,
@@ -32,13 +31,8 @@ import { resolveImageSize } from "bailian-cli-runtime";
3231import { join } from "path" ;
3332import { BOOL_FLAG_PROMPT_EXTEND_CLI_TRUE , BOOL_FLAG_WATERMARK } from "bailian-cli-runtime" ;
3433
35- const SYNC_MODEL_PREFIXES = [ "qwen-image-2.0" , "qwen-image-max" , "wan2.7-image" ] ;
3634const PROMPT_EXTEND_DEFAULT_PREFIXES = [ "qwen-image-2.0" , "qwen-image-max" ] ;
3735
38- function isSyncModel ( model : string ) : boolean {
39- return SYNC_MODEL_PREFIXES . some ( ( prefix ) => model . startsWith ( prefix ) ) ;
40- }
41-
4236function enablesPromptExtendByDefault ( model : string ) : boolean {
4337 return PROMPT_EXTEND_DEFAULT_PREFIXES . some ( ( prefix ) => model . startsWith ( prefix ) ) ;
4438}
@@ -114,6 +108,7 @@ export default defineCommand({
114108 '--image ./a.png --image ./b.png --prompt "Merge two images into one collage"' ,
115109 '--image https://example.com/photo.png --prompt "Remove the person" --model qwen-image-2.0-pro' ,
116110 '--image ./photo.png --prompt "Change the style" --model wan2.7-image' ,
111+ '--image ./photo.png --prompt "Place the subject on a table" --model wan2.5-i2i-preview' ,
117112 '--image ./photo.png --prompt "Replace the background with a beach" --watermark false' ,
118113 ] ,
119114 async run ( ctx ) {
@@ -128,7 +123,7 @@ export default defineCommand({
128123 const prompt = flags . prompt ;
129124
130125 const model = flags . model || settings . defaultImageModel || "qwen-image-2.0" ;
131- const useSync = isSyncModel ( model ) ;
126+ const route = resolveImageEditApi ( model ) ;
132127
133128 // Auto-upload local files (resolve all images in parallel)
134129 const resolvedImages = await Promise . all (
@@ -142,90 +137,118 @@ export default defineCommand({
142137 "prompt-extend" ,
143138 ) ;
144139
145- // Build content: all images first, then text prompt
146- const contentItems : Array < { image ?: string ; text ?: string } > = resolvedImages . map (
147- ( u : string ) => ( { image : u } ) ,
148- ) ;
149- contentItems . push ( { text : prompt } ) ;
150-
151140 const watermark = resolveWatermark ( flags . watermark ) ;
152141
153- const body : DashScopeImageRequest = {
154- model,
155- input : {
156- messages : [
157- {
158- role : "user" ,
159- content : contentItems ,
160- } ,
161- ] ,
162- } ,
163- parameters : {
164- size : resolveImageSize ( flags . size , useSync ) ,
165- n,
166- seed : flags . seed ,
167- prompt_extend : promptExtend ,
168- watermark,
169- negative_prompt : flags . negativePrompt || undefined ,
170- } ,
142+ const parameters : NonNullable < DashScopeImageRequest [ "parameters" ] > = {
143+ size : resolveImageSize ( flags . size , route . useSync ) ,
144+ n,
145+ seed : flags . seed ,
146+ prompt_extend : promptExtend ,
147+ watermark,
171148 } ;
172149
150+ let body : DashScopeImageRequest ;
151+ if ( route . inputStyle === "prompt-images" ) {
152+ body = {
153+ model,
154+ input : {
155+ prompt,
156+ images : resolvedImages ,
157+ negative_prompt : flags . negativePrompt || undefined ,
158+ } ,
159+ parameters,
160+ } ;
161+ } else {
162+ const contentItems : Array < { image ?: string ; text ?: string } > = resolvedImages . map (
163+ ( imageUrl : string ) => ( { image : imageUrl } ) ,
164+ ) ;
165+ contentItems . push ( { text : prompt } ) ;
166+ body = {
167+ model,
168+ input : {
169+ messages : [
170+ {
171+ role : "user" ,
172+ content : contentItems ,
173+ } ,
174+ ] ,
175+ } ,
176+ parameters : {
177+ ...parameters ,
178+ negative_prompt : flags . negativePrompt || undefined ,
179+ } ,
180+ } ;
181+ }
182+
173183 // Remove undefined parameters
174184 stripUndefined ( body . parameters as Record < string , unknown > ) ;
175185
176186 const format = detectOutputFormat ( settings . output ) ;
177187
178188 if ( settings . dryRun ) {
179- const previewBody = {
180- ...body ,
181- input : {
182- messages : body . input . messages . map ( ( message ) => ( {
183- ...message ,
184- content : message . content . map ( ( item ) =>
185- item . image ? { ...item , image : redactDataUri ( item . image ) } : item ,
186- ) ,
187- } ) ) ,
188- } ,
189- } ;
190- emitResult ( { request : previewBody , mode : useSync ? "sync" : "async" } , format ) ;
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+ } ;
209+ emitResult (
210+ { request : previewBody , mode : route . useSync ? "sync" : "async" , path : route . path } ,
211+ format ,
212+ ) ;
191213 return ;
192214 }
193215
194216 if ( ! settings . quiet ) {
195217 process . stderr . write (
196- `[Model: ${ model } ] [Mode: ${ useSync ? "sync" : "async" } ] [Images: ${ resolvedImages . length } ]\n` ,
218+ `[Model: ${ model } ] [Mode: ${ route . useSync ? "sync" : "async" } ] [Images: ${ resolvedImages . length } ]\n` ,
197219 ) ;
198220 }
199221
200222 const concurrent = getConcurrency ( flags ) ;
201223
202- if ( useSync ) {
203- await handleSyncMode ( ctx . client , settings , body , flags , format , concurrent ) ;
224+ if ( route . useSync ) {
225+ await handleSyncMode ( ctx . client , settings , route . path , body , flags , format , concurrent ) ;
204226 } else {
205- await handleAsyncMode ( ctx . client , settings , body , flags , format , concurrent ) ;
227+ await handleAsyncMode ( ctx . client , settings , route . path , body , flags , format , concurrent ) ;
206228 }
207229 } ,
208230} ) ;
209231
210232async function handleSyncMode (
211233 client : Client ,
212234 settings : Settings ,
235+ path : string ,
213236 body : DashScopeImageRequest ,
214237 flags : EditFlags ,
215238 format : OutputFormat ,
216239 concurrent : number ,
217240) : Promise < void > {
218241 const results = await runConcurrent ( concurrent , settings , ( ) =>
219242 client . requestJson < DashScopeImageSyncResponse > ( {
220- path : imageSyncPath ( ) ,
243+ path,
221244 method : "POST" ,
222245 body,
223246 } ) ,
224247 ) ;
225248
226249 const imageUrls = results
227- . flatMap ( ( r ) => r . output . choices || [ ] )
228- . flatMap ( ( c ) => c . message ?. content || [ ] )
250+ . flatMap ( ( result ) => result . output . choices || [ ] )
251+ . flatMap ( ( choice ) => choice . message ?. content || [ ] )
229252 . map ( ( item ) => item . image )
230253 . filter ( Boolean ) ;
231254
@@ -239,6 +262,7 @@ async function handleSyncMode(
239262async function handleAsyncMode (
240263 client : Client ,
241264 settings : Settings ,
265+ path : string ,
242266 body : DashScopeImageRequest ,
243267 flags : EditFlags ,
244268 format : OutputFormat ,
@@ -249,14 +273,14 @@ async function handleAsyncMode(
249273 settings ,
250274 ( ) =>
251275 client . requestJson < DashScopeAsyncResponse > ( {
252- path : imagePath ( ) ,
276+ path,
253277 method : "POST" ,
254278 body,
255279 async : true ,
256280 } ) ,
257281 "tasks" ,
258282 ) ;
259- const taskIds = responses . map ( ( r ) => r . output . task_id ) ;
283+ const taskIds = responses . map ( ( response ) => response . output . task_id ) ;
260284
261285 if ( flags . async ) {
262286 emitResult ( { task_ids : taskIds } , format ) ;
@@ -269,12 +293,12 @@ async function handleAsyncMode(
269293 url : client . url ( taskPath ( taskId ) ) ,
270294 intervalSec : pollInterval ,
271295 timeoutSec : settings . timeout ,
272- isComplete : ( d ) => ( d as DashScopeTaskResponse ) . output . task_status === "SUCCEEDED" ,
273- isFailed : ( d ) => ( d as DashScopeTaskResponse ) . output . task_status === "FAILED" ,
274- getStatus : ( d ) => ( d as DashScopeTaskResponse ) . output . task_status ,
275- getErrorMessage : ( d ) => {
276- const o = ( d as DashScopeTaskResponse ) . output ;
277- return o . message || o . code || undefined ;
296+ isComplete : ( data ) => ( data as DashScopeTaskResponse ) . output . task_status === "SUCCEEDED" ,
297+ isFailed : ( data ) => ( data as DashScopeTaskResponse ) . output . task_status === "FAILED" ,
298+ getStatus : ( data ) => ( data as DashScopeTaskResponse ) . output . task_status ,
299+ getErrorMessage : ( data ) => {
300+ const output = ( data as DashScopeTaskResponse ) . output ;
301+ return output . message || output . code || undefined ;
278302 } ,
279303 } ) ,
280304 ) ;
@@ -285,13 +309,13 @@ async function handleAsyncMode(
285309 for ( const result of results ) {
286310 if ( result . output . choices ) {
287311 const urls = result . output . choices
288- . flatMap ( ( c ) => c . message ?. content || [ ] )
312+ . flatMap ( ( choice ) => choice . message ?. content || [ ] )
289313 . map ( ( item ) => item . image )
290314 . filter ( Boolean ) ;
291315 imageUrls . push ( ...urls ) ;
292316 }
293317 if ( result . output . results ) {
294- const urls = result . output . results . map ( ( r ) => r . url ) . filter ( Boolean ) ;
318+ const urls = result . output . results . map ( ( item ) => item . url ) . filter ( Boolean ) ;
295319 if ( urls . length > 0 && imageUrls . length === 0 ) {
296320 imageUrls . push ( ...urls ) ;
297321 }
@@ -321,8 +345,8 @@ async function saveImages(
321345 // Parallel download all images
322346 const items =
323347 imageUrls . length > 1
324- ? imageUrls . map ( ( url , i ) => {
325- const filename = `${ prefix } _${ String ( i + 1 ) . padStart ( 3 , "0" ) } .png` ;
348+ ? imageUrls . map ( ( url , index ) => {
349+ const filename = `${ prefix } _${ String ( index + 1 ) . padStart ( 3 , "0" ) } .png` ;
326350 return { url, destPath : join ( outDir , filename ) } ;
327351 } )
328352 : [ { url : imageUrls [ 0 ] , destPath : join ( outDir , `${ prefix } .png` ) } ] ;
0 commit comments