@@ -110,12 +110,79 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
110110 return { error : `API 返回错误: HTTP ${ conversationResponse . status ( ) } ` } ;
111111 }
112112
113+ // 5.5 解析 conversation 响应,检查是否是纯文本回复(拒绝/限流场景)
114+ let conversationText = '' ;
115+ let isImageGenerationStarted = false ;
116+ let conversationBody = '' ;
117+ try {
118+ conversationBody = await conversationResponse . text ( ) ;
119+
120+ // 检查是否有图片生成相关的内容 (dalle 工具调用或 file_ 文件引用)
121+ // 注意:不使用 'image' 关键词,因为拒绝消息也会包含这个词
122+ isImageGenerationStarted = conversationBody . includes ( 'dalle' ) || conversationBody . includes ( 'file_' ) ;
123+ logger . debug ( '适配器' , `isImageGenerationStarted: ${ isImageGenerationStarted } ` , meta ) ;
124+
125+ // 提取文本内容
126+ const lines = conversationBody . split ( '\n' ) ;
127+ for ( const line of lines ) {
128+ if ( ! line . startsWith ( 'data: ' ) ) continue ;
129+ const dataStr = line . slice ( 6 ) . trim ( ) ;
130+ if ( dataStr === '[DONE]' ) continue ;
131+ try {
132+ const data = JSON . parse ( dataStr ) ;
133+ // 提取初始文本 (channel=final 的 assistant 消息)
134+ if ( data . v ?. message ?. channel === 'final' &&
135+ data . v ?. message ?. author ?. role === 'assistant' &&
136+ data . v ?. message ?. content ?. parts ?. length > 0 ) {
137+ const part = data . v . message . content . parts [ 0 ] ;
138+ if ( typeof part === 'string' ) {
139+ conversationText = part ;
140+ }
141+ }
142+ // patch 格式累加 (data.v 是 patch 操作数组)
143+ if ( Array . isArray ( data . v ) ) {
144+ for ( const patch of data . v ) {
145+ if ( patch . o === 'append' && patch . p === '/message/content/parts/0' && patch . v ) {
146+ conversationText += patch . v ;
147+ }
148+ }
149+ }
150+ } catch { }
151+ }
152+ logger . debug ( '适配器' , `提取到文本 (${ conversationText . length } 字符): ${ conversationText . substring ( 0 , 200 ) } ...` , meta ) ;
153+ } catch ( e ) {
154+ logger . warn ( '适配器' , `解析 conversation 响应失败: ${ e . message } ` , meta ) ;
155+ }
156+
157+ // 早期检测:如果文本表明是拒绝/限流,立即返回,不等待图片超时
158+ if ( conversationText ) {
159+ // 检查是否是速率限制错误 (不重试,同账号重试也没用)
160+ const isRateLimit = conversationBody . includes ( 'RateLimitException' ) ||
161+ conversationBody . includes ( 'rate limit' ) ||
162+ / l i m i t .* r e s e t / i. test ( conversationText ) ;
163+
164+ if ( isRateLimit ) {
165+ logger . warn ( '适配器' , `早期检测到速率限制: ${ conversationText . substring ( 0 , 200 ) } ...` , meta ) ;
166+ return { error : `触发速率限制: ${ conversationText . substring ( 0 , 200 ) } ` , retryable : false } ;
167+ }
168+
169+ // 如果没有图片生成迹象,检查是否是内容被拒���
170+ if ( ! isImageGenerationStarted ) {
171+ const isContentRejection = / c a n n o t | c a n ' t | u n a b l e | s o r r y | p o l i c y | v i o l a t / i. test ( conversationText ) ;
172+ if ( isContentRejection ) {
173+ logger . warn ( '适配器' , `早期检测到内容拒绝: ${ conversationText . substring ( 0 , 200 ) } ...` , meta ) ;
174+ return { error : `内容被拒绝: ${ conversationText . substring ( 0 , 200 ) } ` , retryable : false } ;
175+ }
176+ }
177+ }
178+
113179 logger . info ( '适配器' , '生成中,等待图片就绪...' , meta ) ;
114180
115181 // 6. 监听文件状态接口,等待图片生成完成
116- // 通过 file_name 是否包含 .part 判断是否生成完成
182+ // 如果 conversation 响应中没有图片生成迹象,使用较短超时
117183 let downloadUrl = null ;
118184 let fileName = null ;
185+ const imageTimeout = isImageGenerationStarted ? 120000 : 30000 ;
119186
120187 try {
121188 await page . waitForResponse ( async ( response ) => {
@@ -128,11 +195,6 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
128195 const fn = json . file_name ;
129196 const dl = json . download_url ;
130197
131- // 检查是否生成完成:
132- // 1. 必须有 file_name
133- // 2. file_name 开头必须是 user- (生成的图片)
134- // 3. file_name 不能包含 .part(表示中间状态)
135- // 4. 必须有 download_url
136198 if ( fn && fn . startsWith ( 'user-' ) && ! fn . includes ( '.part' ) && dl ) {
137199 fileName = fn ;
138200 downloadUrl = dl ;
@@ -145,8 +207,34 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
145207 } catch {
146208 return false ;
147209 }
148- } , { timeout : waitTimeout } ) ;
210+ } , { timeout : imageTimeout } ) ;
149211 } catch ( e ) {
212+ logger . debug ( '适配器' , `等待图片超时, conversationText长度: ${ conversationText . length } , downloadUrl: ${ downloadUrl } ` , meta ) ;
213+
214+ // 超时时检查是否有 conversation 中的文本内容
215+ if ( conversationText && ! downloadUrl ) {
216+ const isRateLimit = conversationBody . includes ( 'RateLimitException' ) ||
217+ conversationBody . includes ( 'rate limit' ) ||
218+ / l i m i t .* r e s e t / i. test ( conversationText ) ;
219+
220+ if ( isRateLimit ) {
221+ logger . warn ( '适配器' , `触发速率限制: ${ conversationText . substring ( 0 , 200 ) } ...` , meta ) ;
222+ return { error : `触发速率限制: ${ conversationText . substring ( 0 , 200 ) } ` , retryable : false } ;
223+ }
224+
225+ logger . warn ( '适配器' , `模型返回文本而非图片: ${ conversationText . substring ( 0 , 200 ) } ...` , meta ) ;
226+ return { error : `模型返回文本而非图片: ${ conversationText . substring ( 0 , 200 ) } ` , retryable : false } ;
227+ }
228+
229+ // 如果没有提取到文本,但有原始响应体,尝试用简单方式提取
230+ if ( ! conversationText && conversationBody ) {
231+ const partsMatch = conversationBody . match ( / " p a r t s " : \s * \[ " ( [ ^ " ] + ) " \] / ) ;
232+ if ( partsMatch && partsMatch [ 1 ] ) {
233+ logger . warn ( '适配器' , `通过正则提取到文本: ${ partsMatch [ 1 ] . substring ( 0 , 200 ) } ...` , meta ) ;
234+ return { error : `模型返回文本而非图片: ${ partsMatch [ 1 ] . substring ( 0 , 200 ) } ` , retryable : false } ;
235+ }
236+ }
237+
150238 const pageError = normalizePageError ( e , meta ) ;
151239 if ( pageError ) return pageError ;
152240 throw e ;
0 commit comments