diff --git a/lib/ai/providers/alibaba-image.ts b/lib/ai/providers/alibaba-image.ts index 8181b4558..003ca12de 100644 --- a/lib/ai/providers/alibaba-image.ts +++ b/lib/ai/providers/alibaba-image.ts @@ -120,6 +120,79 @@ async function inferAspectRatioFromImageUrl(imageUrl: string): Promise { + if (!/^https?:\/\//i.test(imageUrl)) return null; + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), ALIBABA_IMAGE_FETCH_TIMEOUT_MS); + try { + const response = await fetch(imageUrl, { signal: controller.signal }); + if (!response.ok) { + console.warn("[Alibaba Image] Failed to fetch reference image dimensions:", response.status); + return null; + } + + const buffer = Buffer.from(await response.arrayBuffer()); + if (!buffer.length) return null; + + const metadata = await sharp(buffer).metadata(); + if (!metadata.width || !metadata.height || metadata.height <= 0) return null; + + return { + width: metadata.width, + height: metadata.height, + aspectRatio: metadata.width / metadata.height, + }; + } catch (error) { + console.warn("[Alibaba Image] Failed to infer image dimensions from reference image:", error); + return null; + } finally { + clearTimeout(timeoutId); + } +} + +function calculateAlibabaSizeFromDimensions( + width: number, + height: number, + baseSize: number, +): string { + const safeWidth = Math.max(1, Math.round(width)); + const safeHeight = Math.max(1, Math.round(height)); + const longestSide = Math.max(safeWidth, safeHeight); + const scale = baseSize / longestSide; + + let scaledWidth = Math.round(safeWidth * scale); + let scaledHeight = Math.round(safeHeight * scale); + + if (scaledWidth > ALIBABA_MAX_SIZE || scaledHeight > ALIBABA_MAX_SIZE) { + const clampScale = Math.min( + ALIBABA_MAX_SIZE / scaledWidth, + ALIBABA_MAX_SIZE / scaledHeight, + ); + scaledWidth = Math.round(scaledWidth * clampScale); + scaledHeight = Math.round(scaledHeight * clampScale); + } + + if (scaledWidth < ALIBABA_MIN_SIZE || scaledHeight < ALIBABA_MIN_SIZE) { + const boostScale = Math.max( + ALIBABA_MIN_SIZE / scaledWidth, + ALIBABA_MIN_SIZE / scaledHeight, + ); + scaledWidth = Math.round(scaledWidth * boostScale); + scaledHeight = Math.round(scaledHeight * boostScale); + } + + scaledWidth = Math.max(ALIBABA_MIN_SIZE, Math.min(ALIBABA_MAX_SIZE, scaledWidth)); + scaledHeight = Math.max(ALIBABA_MIN_SIZE, Math.min(ALIBABA_MAX_SIZE, scaledHeight)); + + scaledWidth = Math.round(scaledWidth / 2) * 2; + scaledHeight = Math.round(scaledHeight / 2) * 2; + + return `${scaledWidth}*${scaledHeight}`; +} + /** * 创建缩略图 */ @@ -501,17 +574,29 @@ export function createAlibabaImage({ apiKey, baseURL }: AlibabaImageConfig) { if (aspectRatio === "auto") { // 阿里文档在 auto 下默认接近 1024*1024,这会让 4K 选择失效。 // 这里改为根据参考图比例计算 size,确保 1K/2K/4K 档位真正生效。 - const referenceImageUrl = imageUrls[imageUrls.length - 1]; - const inferredAspectRatio = await inferAspectRatioFromImageUrl(referenceImageUrl); - if (inferredAspectRatio) { - params.size = calculateAlibabaSizeByAspect(inferredAspectRatio, baseSize); + const referenceImageUrl = imageUrls[0]; + const inferredDimensions = await inferImageDimensionsFromImageUrl(referenceImageUrl); + if (inferredDimensions) { + params.size = calculateAlibabaSizeFromDimensions( + inferredDimensions.width, + inferredDimensions.height, + baseSize, + ); console.log( - `[Alibaba Image] Auto mode resolved size: imageSize=${imageSize}, inferredAspect=${inferredAspectRatio} => size=${params.size}` + `[Alibaba Image] Auto mode resolved size: imageSize=${imageSize}, reference=${inferredDimensions.width}x${inferredDimensions.height}, inferredAspect=${inferredDimensions.aspectRatio} => size=${params.size}` ); } else { - console.log( - `[Alibaba Image] Auto mode keeps provider default size: imageSize=${imageSize}, inferredAspect=unavailable` - ); + const inferredAspectRatio = await inferAspectRatioFromImageUrl(referenceImageUrl); + if (inferredAspectRatio) { + params.size = calculateAlibabaSizeByAspect(inferredAspectRatio, baseSize); + console.log( + `[Alibaba Image] Auto mode resolved size via aspect fallback: imageSize=${imageSize}, inferredAspect=${inferredAspectRatio} => size=${params.size}` + ); + } else { + console.log( + `[Alibaba Image] Auto mode keeps provider default size: imageSize=${imageSize}, inferredAspect=unavailable` + ); + } } } else { const calculatedSize = calculateSize(aspectRatio, baseSize);