feat(*): 图片模型渠道 - 新增 Grok JSON 生图与单图编辑协议
This commit is contained in:
@@ -146,7 +146,7 @@ function ProtocolBrandMark({ protocol, compact = false }: { protocol: ModelProto
|
||||
if (protocol.value.startsWith("volcengine-jimeng-")) return <span className={cn("grid shrink-0 place-items-center rounded-md bg-muted text-foreground/65", iconSize)}><Sparkles className={compact ? "size-3" : "size-4"} /></span>;
|
||||
if (protocol.value.startsWith("volcengine-")) return <span className={cn("grid shrink-0 place-items-center rounded-md bg-muted text-foreground/65", iconSize)}><Flame className={compact ? "size-3" : "size-4"} /></span>;
|
||||
if (protocol.value.startsWith("newapi-channel-")) return <span className={cn("grid shrink-0 place-items-center rounded-md bg-muted text-foreground/65", iconSize)}><Network className={compact ? "size-3" : "size-4"} /></span>;
|
||||
const brand = protocol.value === "gemini-veo" ? "gemini" : protocol.value === "xai-video" ? "grok" : "openai";
|
||||
const brand = protocol.value === "gemini-veo" ? "gemini" : protocol.value === "grok-image" || protocol.value === "xai-video" ? "grok" : "openai";
|
||||
return <BrandIconRow models={[brand]} compact={compact} />;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,16 @@ export function defaultImageCapabilityConfig(protocol?: ModelProtocol, model = "
|
||||
outputFormat: { supported: true },
|
||||
maxOutputs: 15,
|
||||
};
|
||||
if (protocol === "volcengine-ark-image") {
|
||||
if (protocol === "grok-image") {
|
||||
image.references.maxImages = 1;
|
||||
image.references.maskSupported = false;
|
||||
image.size = { parameter: "none", values: [], default: "auto", allowCustom: false };
|
||||
image.quality = { supported: false, values: [], default: "auto" };
|
||||
image.transparentBackground = { supported: false, default: false };
|
||||
image.responseFormat = { supported: true };
|
||||
image.outputFormat = { supported: false };
|
||||
image.maxOutputs = 1;
|
||||
} else if (protocol === "volcengine-ark-image") {
|
||||
image.references.maskSupported = false;
|
||||
image.quality.supported = false;
|
||||
image.transparentBackground.supported = false;
|
||||
@@ -89,7 +98,7 @@ export function defaultImageCapabilityConfig(protocol?: ModelProtocol, model = "
|
||||
image.responseFormat.supported = false;
|
||||
image.outputFormat.supported = false;
|
||||
}
|
||||
if (model.trim().toLowerCase().startsWith("grok-imagine-image")) {
|
||||
if (protocol !== "grok-image" && model.trim().toLowerCase().startsWith("grok-imagine-image")) {
|
||||
image.references.maxImages = 0;
|
||||
image.references.maskSupported = false;
|
||||
image.size = { parameter: "none", values: [], default: "auto", allowCustom: false };
|
||||
|
||||
@@ -2,6 +2,7 @@ export type ModelProtocol =
|
||||
| "chat-completion"
|
||||
| "openai-response"
|
||||
| "openai-image"
|
||||
| "grok-image"
|
||||
| "volcengine-ark-image"
|
||||
| "volcengine-jimeng-image"
|
||||
| "openai-audio"
|
||||
@@ -30,6 +31,7 @@ export const MODEL_PROTOCOLS: ModelProtocolDefinition[] = [
|
||||
{ value: "chat-completion", label: "OpenAI Chat Completions", capability: "text", create: "POST /v1/chat/completions", contentType: "application/json", media: "文本与多模态消息" },
|
||||
{ value: "openai-response", label: "OpenAI Responses", capability: "text", create: "POST /v1/responses", contentType: "application/json", media: "文本与多模态输入" },
|
||||
{ value: "openai-image", label: "OpenAI Images", capability: "image", create: "POST /v1/images/generations", contentType: "application/json / multipart", media: "生成、编辑与参考图" },
|
||||
{ value: "grok-image", label: "Grok Images", capability: "image", create: "POST /v1/images/generations / edits", contentType: "application/json", media: "文生图与单张 URL 参考图,不支持蒙版" },
|
||||
{ value: "volcengine-ark-image", label: "火山方舟图片", capability: "image", create: "POST /api/v3/images/generations", contentType: "application/json", media: "文生图与 image 参考图,不支持蒙版" },
|
||||
{ value: "volcengine-jimeng-image", label: "即梦官方图片", capability: "image", create: "POST CVSync2AsyncSubmitTask", poll: "POST CVSync2AsyncGetResult", contentType: "application/json + AK/SK 签名", media: "0-14 张参考图,模型标识填写 req_key" },
|
||||
{ value: "openai-audio", label: "OpenAI Audio", capability: "audio", create: "POST /v1/audio/speech", contentType: "application/json", media: "文本转语音" },
|
||||
|
||||
@@ -181,6 +181,7 @@ function defaultProtocolForModel(channel: ModelChannel, model: string): ModelPro
|
||||
if (channel.interfaceType) return channel.interfaceType;
|
||||
if (channel.apiFormat === "gemini" && modelMatchesCapability(model, "video")) return "gemini-veo";
|
||||
if (modelMatchesCapability(model, "video")) return "newapi";
|
||||
if (modelOptionName(model).trim().toLowerCase().startsWith("grok-imagine-image")) return "grok-image";
|
||||
if (modelMatchesCapability(model, "image")) return "openai-image";
|
||||
return "chat-completion";
|
||||
}
|
||||
|
||||
@@ -812,6 +812,24 @@ export async function requestGeneration(config: AiConfig, prompt: string, option
|
||||
throw new Error(readAxiosError(error, "请求失败"));
|
||||
}
|
||||
}
|
||||
if (requestConfig.interfaceType === "grok-image") {
|
||||
try {
|
||||
const responseData = await postChannelJSON<ImageApiResponse>(
|
||||
requestConfig,
|
||||
aiApiUrl(requestConfig, "/images/generations"),
|
||||
{
|
||||
model: requestConfig.model,
|
||||
prompt: withSystemPrompt(requestConfig, prompt),
|
||||
n,
|
||||
response_format: "url",
|
||||
},
|
||||
options,
|
||||
);
|
||||
return parseImagePayload(responseData);
|
||||
} catch (error) {
|
||||
throw new Error(readAxiosError(error, "Grok 图片生成失败"));
|
||||
}
|
||||
}
|
||||
const quality = imageProfile.quality.supported && normalizedImage.quality !== "auto" ? normalizeQuality(normalizedImage.quality) || normalizedImage.quality : undefined;
|
||||
const requestSize = resolveImageRequestSize(imageProfile, quality, normalizedImage.size);
|
||||
const isVolcengineArk = requestConfig.interfaceType === "volcengine-ark-image";
|
||||
@@ -853,6 +871,12 @@ async function postChannelJSON<T>(config: ReturnType<typeof resolveModelRequestC
|
||||
).data;
|
||||
}
|
||||
|
||||
async function grokImageInputURL(image: ReferenceImage) {
|
||||
const candidate = image.url?.trim() || "";
|
||||
if (/^https?:\/\//i.test(candidate)) return candidate;
|
||||
return imageToDataUrl(image);
|
||||
}
|
||||
|
||||
export async function requestEdit(config: AiConfig, prompt: string, references: ReferenceImage[], mask?: ReferenceImage, options?: RequestOptions) {
|
||||
const selectedModel = config.model || config.imageModel;
|
||||
const requestConfig = resolveModelRequestConfig(config, selectedModel);
|
||||
@@ -869,6 +893,28 @@ export async function requestEdit(config: AiConfig, prompt: string, references:
|
||||
throw new Error(readAxiosError(error, "请求失败"));
|
||||
}
|
||||
}
|
||||
if (requestConfig.interfaceType === "grok-image") {
|
||||
if (mask) throw new Error("Grok 图片协议不支持蒙版编辑,请移除蒙版后重试");
|
||||
if (references.length !== 1) throw new Error("Grok 图片编辑必须提供且仅支持 1 张参考图");
|
||||
try {
|
||||
const imageUrl = await grokImageInputURL(references[0]);
|
||||
const response = await postChannelJSON<ImageApiResponse>(
|
||||
requestConfig,
|
||||
aiApiUrl(requestConfig, "/images/edits"),
|
||||
{
|
||||
model: requestConfig.model,
|
||||
prompt: withSystemPrompt(requestConfig, requestPrompt),
|
||||
image: { url: imageUrl },
|
||||
n,
|
||||
response_format: "url",
|
||||
},
|
||||
options,
|
||||
);
|
||||
return parseImagePayload(response);
|
||||
} catch (error) {
|
||||
throw new Error(readAxiosError(error, "Grok 图片编辑失败"));
|
||||
}
|
||||
}
|
||||
if (requestConfig.interfaceType === "volcengine-ark-image") {
|
||||
if (mask) throw new Error("火山方舟图片协议不支持蒙版编辑,请移除蒙版后重试");
|
||||
const quality = imageProfile.quality.supported && normalizedImage.quality !== "auto" ? normalizeQuality(normalizedImage.quality) || normalizedImage.quality : undefined;
|
||||
|
||||
@@ -491,7 +491,7 @@ export function defaultBaseUrlForChannelInterface(interfaceType?: ChannelInterfa
|
||||
if (interfaceType === "gemini-veo") return GEMINI_BASE_URL;
|
||||
if (interfaceType === "volcengine-ark-image" || interfaceType === "volcengine-ark-video") return "https://ark.cn-beijing.volces.com/api/v3";
|
||||
if (interfaceType === "volcengine-jimeng-image" || interfaceType === "volcengine-jimeng-video") return "https://visual.volcengineapi.com";
|
||||
if (interfaceType === "newapi" || interfaceType === "newapi-channel-1" || interfaceType === "newapi-channel-2" || interfaceType === "xai-video") return "";
|
||||
if (interfaceType === "grok-image" || interfaceType === "newapi" || interfaceType === "newapi-channel-1" || interfaceType === "newapi-channel-2" || interfaceType === "xai-video") return "";
|
||||
return OPENAI_BASE_URL;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user