fix(canvas): 图像参数 - 移除透明背景误拦截并支持 21:9 尺寸

This commit is contained in:
ddCat committed 2026-07-23 19:28:07 +08:00
1 parent 43fcdd8692
commit 624c1923a0
4 files changed
+6 -32

No files matched your search

-9
View File
@@ -327,9 +327,6 @@ func systemChannelIDFromBaseURL(baseURL string) string {
} }
func runImageTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { func runImageTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) {
if input.Config.TransparentBackground == "true" && !supportsTransparentImageBackground(input.Config) {
return nil, errors.New("当前模型不支持透明背景,请关闭透明背景或切换至 GPT Image 1 系列模型")
}
var payload imageResponse var payload imageResponse
if len(input.ReferenceImages) > 0 || input.Mask != nil { if len(input.ReferenceImages) > 0 || input.Mask != nil {
body := &bytes.Buffer{} body := &bytes.Buffer{}
@@ -1781,12 +1778,6 @@ func isArkPlanVideoConfig(config providerConfig) bool {
return strings.Contains(strings.ToLower(config.BaseURL), "/api/plan/v3") return strings.Contains(strings.ToLower(config.BaseURL), "/api/plan/v3")
} }
// 透明背景必须按模型能力显式开放,避免上游静默忽略参数后返回不透明图片。
func supportsTransparentImageBackground(config providerConfig) bool {
model := strings.ToLower(strings.TrimSpace(config.Model))
return strings.EqualFold(strings.TrimSpace(config.APIFormat), "openai") && (model == "gpt-image-1" || strings.HasPrefix(model, "gpt-image-1-") || strings.HasPrefix(model, "gpt-image-1."))
}
func normalizeImageQuality(value string) string { func normalizeImageQuality(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) { switch strings.ToLower(strings.TrimSpace(value)) {
case "1k": case "1k":
+5 -6
View File
@@ -2,7 +2,7 @@ import { type ReactNode, useState } from "react";
import { ConfigProvider, Switch } from "antd"; import { ConfigProvider, Switch } from "antd";
import { type CanvasTheme } from "@/lib/canvas-theme"; import { type CanvasTheme } from "@/lib/canvas-theme";
import { supportsTransparentImageBackground, type AiConfig } from "@/stores/use-config-store"; import { type AiConfig } from "@/stores/use-config-store";
const qualityOptions = [ const qualityOptions = [
{ value: "auto", label: "自动" }, { value: "auto", label: "自动" },
@@ -19,6 +19,7 @@ const aspectOptions = [
{ value: "4:3", label: "4:3", width: 1360, height: 1024, icon: "landscape" }, { value: "4:3", label: "4:3", width: 1360, height: 1024, icon: "landscape" },
{ value: "3:4", label: "3:4", width: 1024, height: 1360, icon: "portrait" }, { value: "3:4", label: "3:4", width: 1024, height: 1360, icon: "portrait" },
{ value: "16:9", label: "16:9", width: 1824, height: 1024, icon: "landscape" }, { value: "16:9", label: "16:9", width: 1824, height: 1024, icon: "landscape" },
{ value: "21:9", label: "21:9", size: "2352x1008", width: 2352, height: 1008, icon: "landscape" },
{ value: "9:16", label: "9:16", width: 1024, height: 1824, icon: "portrait" }, { value: "9:16", label: "9:16", width: 1024, height: 1824, icon: "portrait" },
{ value: "1:1-2k", label: "1:1(2k)", size: "2048x2048", width: 2048, height: 2048, icon: "square" }, { value: "1:1-2k", label: "1:1(2k)", size: "2048x2048", width: 2048, height: 2048, icon: "square" },
{ value: "16:9-2k", label: "16:9(2k)", size: "2048x1152", width: 2048, height: 1152, icon: "landscape" }, { value: "16:9-2k", label: "16:9(2k)", size: "2048x1152", width: 2048, height: 1152, icon: "landscape" },
@@ -43,7 +44,6 @@ export function ImageSettingsPanel({ config, onConfigChange, theme, showTitle =
const [snapDimensionToStep, setSnapDimensionToStep] = useState(true); const [snapDimensionToStep, setSnapDimensionToStep] = useState(true);
const quality = config.quality || "auto"; const quality = config.quality || "auto";
const transparentBackground = config.transparentBackground === "true"; const transparentBackground = config.transparentBackground === "true";
const transparentBackgroundSupported = supportsTransparentImageBackground(config, config.model || config.imageModel);
const count = Math.max(1, Math.min(maxCount, Math.floor(Math.abs(Number(config.count)) || 1))); const count = Math.max(1, Math.min(maxCount, Math.floor(Math.abs(Number(config.count)) || 1)));
const activeSize = config.size || "auto"; const activeSize = config.size || "auto";
const selectedAspect = aspectOptions.find((item) => (item.size || item.value) === activeSize || item.value === activeSize); const selectedAspect = aspectOptions.find((item) => (item.size || item.value) === activeSize || item.value === activeSize);
@@ -84,15 +84,14 @@ export function ImageSettingsPanel({ config, onConfigChange, theme, showTitle =
<div className="flex items-center justify-between gap-3"> <div className="flex items-center justify-between gap-3">
<div className="min-w-0"> <div className="min-w-0">
<SettingTitle color={theme.node.muted}>透明背景</SettingTitle> <SettingTitle color={theme.node.muted}>透明背景</SettingTitle>
<div className="mt-1 text-[11px]" style={{ color: transparentBackgroundSupported ? theme.node.muted : theme.accent.danger }}> <div className="mt-1 text-[11px]" style={{ color: theme.node.muted }}>
{transparentBackgroundSupported ? "输出保留 Alpha 通道的 PNG" : transparentBackground ? "当前模型不支持,生成会被阻止" : "当前模型不支持透明背景"} 请求模型输出保留 Alpha 通道的 PNG
</div> </div>
</div> </div>
<span title={transparentBackgroundSupported ? "使用透明背景生成 PNG 图片" : "仅 GPT Image 1 系列模型支持透明背景"} onMouseDown={(event) => event.stopPropagation()}> <span title="是否支持透明背景由当前模型接口决定" onMouseDown={(event) => event.stopPropagation()}>
<Switch <Switch
size="small" size="small"
checked={transparentBackground} checked={transparentBackground}
disabled={!transparentBackgroundSupported && !transparentBackground}
onChange={(checked) => onConfigChange("transparentBackground", checked ? "true" : "false")} onChange={(checked) => onConfigChange("transparentBackground", checked ? "true" : "false")}
/> />
</span> </span>
+1 -10
View File
@@ -1,6 +1,6 @@
import axios from "axios"; import axios from "axios";
import { buildApiUrl, isSystemProxyBaseUrl, resolveBackendApiUrl, resolveModelRequestConfig, supportsTransparentImageBackground, type AiConfig, type ModelChannel } from "@/stores/use-config-store"; import { buildApiUrl, isSystemProxyBaseUrl, resolveBackendApiUrl, resolveModelRequestConfig, type AiConfig, type ModelChannel } from "@/stores/use-config-store";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { dataUrlToFile } from "@/lib/image-utils"; import { dataUrlToFile } from "@/lib/image-utils";
import { buildImageReferencePromptText } from "@/lib/image-reference-prompt"; import { buildImageReferencePromptText } from "@/lib/image-reference-prompt";
@@ -233,13 +233,6 @@ function readStatusError(status: number | undefined, fallback: string) {
return status ? `${fallback}:${status}` : fallback; return status ? `${fallback}:${status}` : fallback;
} }
function assertTransparentBackgroundSupported(config: AiConfig, model: string) {
if (config.transparentBackground !== "true") return;
if (!supportsTransparentImageBackground(config, model)) {
throw new Error("当前模型不支持透明背景,请关闭“透明背景”或切换至 GPT Image 1 系列模型");
}
}
function withSystemPrompt(config: AiConfig, prompt: string) { function withSystemPrompt(config: AiConfig, prompt: string) {
const systemPrompt = config.systemPrompt.trim(); const systemPrompt = config.systemPrompt.trim();
return systemPrompt ? `${systemPrompt}\n\n${prompt}` : prompt; return systemPrompt ? `${systemPrompt}\n\n${prompt}` : prompt;
@@ -764,7 +757,6 @@ function parseGeminiImagePayload(payload: GeminiPayload) {
export async function requestGeneration(config: AiConfig, prompt: string, options?: RequestOptions) { export async function requestGeneration(config: AiConfig, prompt: string, options?: RequestOptions) {
const selectedModel = config.model || config.imageModel; const selectedModel = config.model || config.imageModel;
assertTransparentBackgroundSupported(config, selectedModel);
const requestConfig = resolveModelRequestConfig(config, selectedModel); const requestConfig = resolveModelRequestConfig(config, selectedModel);
const n = Math.max(1, Math.min(15, Math.floor(Math.abs(Number(config.count)) || 1))); const n = Math.max(1, Math.min(15, Math.floor(Math.abs(Number(config.count)) || 1)));
if (requestConfig.apiFormat === "gemini") { if (requestConfig.apiFormat === "gemini") {
@@ -803,7 +795,6 @@ export async function requestGeneration(config: AiConfig, prompt: string, option
export async function requestEdit(config: AiConfig, prompt: string, references: ReferenceImage[], mask?: ReferenceImage, options?: RequestOptions) { export async function requestEdit(config: AiConfig, prompt: string, references: ReferenceImage[], mask?: ReferenceImage, options?: RequestOptions) {
const selectedModel = config.model || config.imageModel; const selectedModel = config.model || config.imageModel;
assertTransparentBackgroundSupported(config, selectedModel);
const requestConfig = resolveModelRequestConfig(config, selectedModel); const requestConfig = resolveModelRequestConfig(config, selectedModel);
const n = Math.max(1, Math.min(15, Math.floor(Math.abs(Number(config.count)) || 1))); const n = Math.max(1, Math.min(15, Math.floor(Math.abs(Number(config.count)) || 1)));
const requestPrompt = buildImageReferencePromptText(prompt, references); const requestPrompt = buildImageReferencePromptText(prompt, references);
-7
View File
@@ -383,13 +383,6 @@ export function resolveModelChannel(config: AiConfig, value: string) {
return matched || config.channels[0] || createModelChannel({ id: "default", name: "默认渠道", baseUrl: config.baseUrl, apiKey: config.apiKey, apiFormat: config.apiFormat, models: config.models.map(modelOptionName) }); return matched || config.channels[0] || createModelChannel({ id: "default", name: "默认渠道", baseUrl: config.baseUrl, apiKey: config.apiKey, apiFormat: config.apiFormat, models: config.models.map(modelOptionName) });
} }
// 透明背景必须按模型能力显式开放,避免代理接口静默忽略参数后返回不透明图片。
export function supportsTransparentImageBackground(config: AiConfig, value: string) {
const channel = resolveModelChannel(config, value);
const model = modelOptionName(value).trim().toLowerCase();
return channel.apiFormat === "openai" && (model === "gpt-image-1" || model.startsWith("gpt-image-1-") || model.startsWith("gpt-image-1."));
}
export function resolveModelRequestConfig(config: AiConfig, value: string) { export function resolveModelRequestConfig(config: AiConfig, value: string) {
const channel = resolveModelChannel(config, value); const channel = resolveModelChannel(config, value);
return { return {