fix: prevent unsupported seedream size and lock size when model disallows
This commit is contained in:
parent
b3ce87ca2b
commit
b28f1013da
@ -102,13 +102,18 @@ export function ImageParamsBar({
|
|||||||
// Gemini:1~2 张
|
// Gemini:1~2 张
|
||||||
return [1, 2];
|
return [1, 2];
|
||||||
};
|
};
|
||||||
|
const supportsSize = imageGenConfig?.supportsSize !== false;
|
||||||
|
|
||||||
const clampParamsByProvider = (raw: ImageGenerationParams): ImageGenerationParams => {
|
const clampParamsByProvider = (raw: ImageGenerationParams): ImageGenerationParams => {
|
||||||
const options = getImageCountOptions();
|
const options = getImageCountOptions();
|
||||||
const fallbackCount = options[0] ?? 1;
|
const fallbackCount = options[0] ?? 1;
|
||||||
const imageCount = options.includes(raw.imageCount || fallbackCount) ? (raw.imageCount || fallbackCount) : fallbackCount;
|
const imageCount = options.includes(raw.imageCount || fallbackCount) ? (raw.imageCount || fallbackCount) : fallbackCount;
|
||||||
|
const imageSize = supportsSize
|
||||||
|
? (raw.imageSize || "2K")
|
||||||
|
: "2K";
|
||||||
return {
|
return {
|
||||||
...raw,
|
...raw,
|
||||||
|
imageSize,
|
||||||
imageCount,
|
imageCount,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -204,26 +209,37 @@ export function ImageParamsBar({
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
||||||
{/* 分辨率 */}
|
{/* 分辨率 */}
|
||||||
<DropdownMenu>
|
{supportsSize ? (
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenu>
|
||||||
<Button variant="ghost" className="h-8 px-2 gap-1 focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0">
|
<DropdownMenuTrigger asChild>
|
||||||
<Maximize2 size={14} />
|
<Button variant="ghost" className="h-8 px-2 gap-1 focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0">
|
||||||
<span className="font-medium text-xs">{params.imageSize}</span>
|
<Maximize2 size={14} />
|
||||||
</Button>
|
<span className="font-medium text-xs">{params.imageSize}</span>
|
||||||
</DropdownMenuTrigger>
|
</Button>
|
||||||
<DropdownMenuContent align="center" className="min-w-[100px]">
|
</DropdownMenuTrigger>
|
||||||
{IMAGE_SIZE_OPTIONS.map((option) => (
|
<DropdownMenuContent align="center" className="min-w-[100px]">
|
||||||
<DropdownMenuItem
|
{IMAGE_SIZE_OPTIONS.map((option) => (
|
||||||
key={option.value}
|
<DropdownMenuItem
|
||||||
onClick={() => updateParam("imageSize", option.value)}
|
key={option.value}
|
||||||
className="focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0"
|
onClick={() => updateParam("imageSize", option.value)}
|
||||||
>
|
className="focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0"
|
||||||
<Check className={cn("mr-2 h-4 w-4", params.imageSize === option.value ? "opacity-100" : "opacity-0")} />
|
>
|
||||||
{option.label}
|
<Check className={cn("mr-2 h-4 w-4", params.imageSize === option.value ? "opacity-100" : "opacity-0")} />
|
||||||
</DropdownMenuItem>
|
{option.label}
|
||||||
))}
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
))}
|
||||||
</DropdownMenu>
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="h-8 px-2 gap-1 text-muted-foreground focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<Maximize2 size={14} />
|
||||||
|
<span className="font-medium text-xs">2K</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
{showImageCountSelector && (
|
{showImageCountSelector && (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
|
|||||||
@ -18,6 +18,7 @@ let cachedRewriteConfig: VolcanoUrlRewriteConfig | null | undefined;
|
|||||||
const VOLCANO_IMAGE_FETCH_TIMEOUT_MS = 20_000;
|
const VOLCANO_IMAGE_FETCH_TIMEOUT_MS = 20_000;
|
||||||
const VOLCANO_INLINE_MAX_SIDE = 1024;
|
const VOLCANO_INLINE_MAX_SIDE = 1024;
|
||||||
const DEFAULT_VOLCANO_IMAGE_SIZE = "2K";
|
const DEFAULT_VOLCANO_IMAGE_SIZE = "2K";
|
||||||
|
const SEEDREAM_MODEL_ID_PATTERN = /doubao-seedream-4-5/i;
|
||||||
|
|
||||||
function normalizeVolcanoImageSize(imageSize?: string): "1K" | "2K" | "4K" {
|
function normalizeVolcanoImageSize(imageSize?: string): "1K" | "2K" | "4K" {
|
||||||
const normalized = String(imageSize || "").trim().toUpperCase();
|
const normalized = String(imageSize || "").trim().toUpperCase();
|
||||||
@ -27,6 +28,23 @@ function normalizeVolcanoImageSize(imageSize?: string): "1K" | "2K" | "4K" {
|
|||||||
return DEFAULT_VOLCANO_IMAGE_SIZE;
|
return DEFAULT_VOLCANO_IMAGE_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveVolcanoImageSizeForModel(
|
||||||
|
modelId: string,
|
||||||
|
imageSize?: string
|
||||||
|
): "1K" | "2K" | "4K" {
|
||||||
|
const normalized = normalizeVolcanoImageSize(imageSize);
|
||||||
|
if (SEEDREAM_MODEL_ID_PATTERN.test(modelId)) {
|
||||||
|
// doubao-seedream-4-5 在当前链路下 1K 会返回 InvalidParameter,强制回落到 2K
|
||||||
|
if (normalized === "1K") {
|
||||||
|
console.log(
|
||||||
|
`[Volcano Vision] Size normalized for ${modelId}: 1K -> 2K (model constraint)`
|
||||||
|
);
|
||||||
|
return "2K";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
function guessImageMimeTypeFromUrl(url: string): string {
|
function guessImageMimeTypeFromUrl(url: string): string {
|
||||||
const cleanUrl = url.split("?")[0]?.toLowerCase() || "";
|
const cleanUrl = url.split("?")[0]?.toLowerCase() || "";
|
||||||
if (cleanUrl.endsWith(".png")) return "image/png";
|
if (cleanUrl.endsWith(".png")) return "image/png";
|
||||||
@ -478,7 +496,7 @@ async function sendVolcanoRequest(
|
|||||||
console.log("[Volcano Vision] Image count:", image ? (Array.isArray(image) ? image.length : 1) : 0);
|
console.log("[Volcano Vision] Image count:", image ? (Array.isArray(image) ? image.length : 1) : 0);
|
||||||
|
|
||||||
// 构建请求体
|
// 构建请求体
|
||||||
const normalizedSize = normalizeVolcanoImageSize(imageSize);
|
const normalizedSize = resolveVolcanoImageSizeForModel(modelId, imageSize);
|
||||||
const requestBody: any = {
|
const requestBody: any = {
|
||||||
model: modelId,
|
model: modelId,
|
||||||
prompt,
|
prompt,
|
||||||
@ -711,7 +729,10 @@ export function createVolcanoVision({ apiKey, baseURL }: VolcanoVisionConfig) {
|
|||||||
const activeModelId = options.overrideModelId || modelId;
|
const activeModelId = options.overrideModelId || modelId;
|
||||||
console.log("[Volcano Vision] Using modelId:", activeModelId);
|
console.log("[Volcano Vision] Using modelId:", activeModelId);
|
||||||
console.log("[Volcano Vision] Image params from options:", options.imageParams);
|
console.log("[Volcano Vision] Image params from options:", options.imageParams);
|
||||||
const imageSize = normalizeVolcanoImageSize(options.imageParams?.imageSize);
|
const imageSize = resolveVolcanoImageSizeForModel(
|
||||||
|
activeModelId,
|
||||||
|
options.imageParams?.imageSize
|
||||||
|
);
|
||||||
|
|
||||||
// 🆕 优先使用用户选择的图片数量,其次是 maxImages 参数,默认 1 张
|
// 🆕 优先使用用户选择的图片数量,其次是 maxImages 参数,默认 1 张
|
||||||
const imageCount = options.imageParams?.imageCount || options.maxImages || 1;
|
const imageCount = options.imageParams?.imageCount || options.maxImages || 1;
|
||||||
@ -772,7 +793,10 @@ export function createVolcanoVision({ apiKey, baseURL }: VolcanoVisionConfig) {
|
|||||||
const allImages: Array<{ thumbnail: string; original: string }> = [];
|
const allImages: Array<{ thumbnail: string; original: string }> = [];
|
||||||
|
|
||||||
// 使用流式请求
|
// 使用流式请求
|
||||||
const imageSize = normalizeVolcanoImageSize(options.imageParams?.imageSize);
|
const imageSize = resolveVolcanoImageSizeForModel(
|
||||||
|
modelId,
|
||||||
|
options.imageParams?.imageSize
|
||||||
|
);
|
||||||
for await (const event of sendVolcanoStreamRequest(baseURL, apiKey, options.messages, modelId, imageSize)) {
|
for await (const event of sendVolcanoStreamRequest(baseURL, apiKey, options.messages, modelId, imageSize)) {
|
||||||
if (event.type === "image_generation.partial_succeeded" && event.url) {
|
if (event.type === "image_generation.partial_succeeded" && event.url) {
|
||||||
// 每张图片生成完成后立即上传并发送
|
// 每张图片生成完成后立即上传并发送
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user