'use strict'; const axios = require('axios'); const { uploadBufferToDefaultS3 } = require('./objectStorage'); const { getSelectionFissionImageGenerationConfig } = require('./selectionFissionAiSettings'); const QWEN_GENERATION_PATH = '/api/v1/services/aigc/multimodal-generation/generation'; function normalizeText(value, maxLength = 1000) { return String(value ?? '').trim().slice(0, maxLength); } function stripTrailingSlash(value) { return normalizeText(value, 1000).replace(/\/+$/, ''); } function getProviderConfig(config) { const provider = config?.provider === 'qwen' ? 'qwen' : 'external'; return { provider, ...(config?.[provider] || {}), timeoutMs: config?.timeoutMs }; } function resolveQwenImageDimensions() { return '800*800'; } function getRequestErrorMessage(error, fallback = '生图请求失败') { return normalizeText( error?.response?.data?.error?.message || error?.response?.data?.message || error?.response?.data?.error || error?.message || fallback, 1000 ) || fallback; } async function persistQwenImage(imageUrl, timeoutMs) { const response = await axios.get(imageUrl, { responseType: 'arraybuffer', timeout: Math.min(Number(timeoutMs) || 120000, 120000), maxContentLength: 20 * 1024 * 1024, }); const mimeType = normalizeText(response.headers?.['content-type'], 120).split(';')[0] || 'image/png'; const extension = mimeType.includes('jpeg') ? 'jpg' : (mimeType.split('/')[1] || 'png'); const uploaded = await uploadBufferToDefaultS3( Buffer.from(response.data), mimeType, 'selection-fission/generated-images', extension ); return uploaded.url; } async function requestExternalImage(providerConfig, input) { const response = await axios.post(`${stripTrailingSlash(providerConfig.baseUrl)}/api/external/image/generate`, { prompt: input.prompt, negativePrompt: input.negativePrompt, imageCount: 1, aspectRatio: '1:1', imageSize: '1K', width: 800, height: 800, referenceImages: input.referenceImages, metadata: input.metadata, }, { timeout: providerConfig.timeoutMs, headers: { Authorization: `Bearer ${providerConfig.apiKey}`, 'x-api-key': providerConfig.apiKey, 'Content-Type': 'application/json', }, maxBodyLength: 15 * 1024 * 1024, }); const responseImages = response.data?.data?.images || response.data?.images || []; const first = Array.isArray(responseImages) ? responseImages[0] : null; return { imageUrl: normalizeText(first?.originalUrl || first?.url || first?.thumbnailUrl, 4000), requestId: normalizeText(response.data?.data?.requestId || response.data?.requestId, 160), provider: 'external', model: providerConfig.model || 'provider-default', raw: response.data, }; } async function requestQwenImage(providerConfig, input, dependencies = {}) { const post = dependencies.post || axios.post; const persistImage = dependencies.persistImage || persistQwenImage; const content = input.referenceImages.slice(0, 3).map((imageUrl) => ({ image: imageUrl })); content.push({ text: input.prompt }); const response = await post( `${stripTrailingSlash(providerConfig.baseUrl)}${QWEN_GENERATION_PATH}`, { model: providerConfig.model, input: { messages: [{ role: 'user', content }] }, parameters: { n: 1, negative_prompt: normalizeText(input.negativePrompt, 500), prompt_extend: input.metadata?.disablePromptExtend === true ? false : providerConfig.promptExtend !== false, watermark: Boolean(providerConfig.watermark), size: resolveQwenImageDimensions(input.aspectRatio, providerConfig.imageSize), }, }, { timeout: providerConfig.timeoutMs, headers: { Authorization: `Bearer ${providerConfig.apiKey}`, 'Content-Type': 'application/json', }, maxBodyLength: 30 * 1024 * 1024, } ); const contentRows = response.data?.output?.choices?.[0]?.message?.content || []; const temporaryImageUrl = Array.isArray(contentRows) ? normalizeText(contentRows.find((row) => row?.image)?.image, 4000) : ''; if (!temporaryImageUrl) { const error = new Error(response.data?.message || '千问生图接口未返回图片'); error.code = response.data?.code || 'QWEN_IMAGE_EMPTY'; throw error; } const imageUrl = await persistImage(temporaryImageUrl, providerConfig.timeoutMs); return { imageUrl, requestId: normalizeText(response.data?.request_id, 160), provider: 'qwen', model: providerConfig.model, usage: response.data?.usage || null, raw: response.data, }; } async function generateSelectionImage(input = {}) { const config = await getSelectionFissionImageGenerationConfig(); const providerConfig = { ...getProviderConfig(config), imageSize: '1K' }; if (!providerConfig.baseUrl || !providerConfig.apiKey) { const error = new Error( providerConfig.provider === 'qwen' ? '千问生图服务尚未配置 API Key' : '现有生图服务尚未配置 API Key' ); error.status = 409; error.code = 'SELECTION_IMAGE_API_KEY_MISSING'; throw error; } try { return providerConfig.provider === 'qwen' ? await requestQwenImage(providerConfig, input) : await requestExternalImage(providerConfig, input); } catch (error) { if (error.code === 'SELECTION_IMAGE_API_KEY_MISSING') throw error; const wrapped = new Error( `${providerConfig.provider === 'qwen' ? '千问官方' : '现有第三方'}生图失败:${getRequestErrorMessage(error)}` ); wrapped.status = error.response?.status || error.status || 502; wrapped.code = error.response?.data?.code || error.code || 'SELECTION_IMAGE_REQUEST_FAILED'; throw wrapped; } } async function testSelectionImageGenerationConfig() { const config = await getSelectionFissionImageGenerationConfig(); const providerConfig = getProviderConfig(config); if (!providerConfig.apiKey) { const error = new Error(providerConfig.provider === 'qwen' ? '千问生图服务尚未配置 API Key' : '现有生图服务尚未配置 API Key'); error.status = 409; throw error; } const input = { prompt: '生成一张简洁的白色摄影棚背景测试图,不要文字、图标或水印。', negativePrompt: '文字,水印,标志,复杂物体', aspectRatio: '1:1', referenceImages: providerConfig.provider === 'qwen' ? [] : ['https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20260310/jiydyi/image+%2818%29-2026-03-10-16-39-59.webp'], metadata: { task: 'settings_connection_test' }, }; const result = providerConfig.provider === 'qwen' ? await requestQwenImage({ ...providerConfig, imageSize: '1K' }, input) : await requestExternalImage({ ...providerConfig, imageSize: '1K' }, input); return { provider: result.provider, model: result.model, imageUrl: result.imageUrl, requestId: result.requestId, message: '生图服务连接成功', }; } module.exports = { QWEN_GENERATION_PATH, generateSelectionImage, getProviderConfig, requestExternalImage, requestQwenImage, resolveQwenImageDimensions, testSelectionImageGenerationConfig, };