Files
ttstd-infinite-canvas/web/src/services/api/image-models.ts
2026-09-14 01:11:56 +08:00

69 lines
3.5 KiB
TypeScript

import { sanitizeChannelModelCatalogItem, type ChannelModelCatalogItem } from "@/lib/channel-model-catalog";
import { createChannelTransport } from "@/services/api/channel-transport";
import { readAxiosError, validateGeminiPayload } from "@/services/api/image-response";
import { geminiApiUrl, geminiHeaders } from "@/services/api/image-transport";
import { http } from "@/services/api/request";
import { buildApiUrl, type AiConfig, type ModelChannel } from "@/stores/use-config-store";
const defaultGeminiConfig: Pick<AiConfig, "baseUrl" | "apiKey" | "apiFormat" | "model" | "systemPrompt"> = {
baseUrl: "https://generativelanguage.googleapis.com",
apiKey: "",
apiFormat: "gemini",
model: "",
systemPrompt: "",
};
type GeminiModelPayload = { models?: Array<{ name?: string }> };
type OpenAIModelPayload = { data?: Array<{ id?: string }>; error?: { message?: string } };
export async function fetchImageModels(config: Pick<AiConfig, "baseUrl" | "apiKey" | "apiFormat">) {
try {
if (config.apiFormat === "gemini") {
const requestConfig = { ...defaultGeminiConfig, ...config };
const payload = await createChannelTransport(requestConfig, "image").get<GeminiModelPayload>(geminiApiUrl(requestConfig), { headers: geminiHeaders(requestConfig) });
validateGeminiPayload(payload);
return (payload.models || [])
.map((model) => model.name?.replace(/^models\//, ""))
.filter((id): id is string => Boolean(id))
.sort((a, b) => a.localeCompare(b));
}
const payload = await createChannelTransport(config, "image").get<OpenAIModelPayload>(buildApiUrl(config.baseUrl, "/models"));
return (payload.data || [])
.map((model) => model.id)
.filter((id): id is string => Boolean(id))
.sort((a, b) => a.localeCompare(b));
} catch (error) {
throw new Error(readAxiosError(error, "读取模型失败"));
}
}
export type ChannelModelFetchResult = { models: string[]; catalog: ChannelModelCatalogItem[] };
export async function fetchChannelModels(channel: ModelChannel, viaBackend = false): Promise<ChannelModelFetchResult> {
if (!viaBackend) {
const models = await fetchImageModels({ baseUrl: channel.baseUrl, apiKey: channel.apiKey, apiFormat: channel.apiFormat });
return { models, catalog: models.map((id) => ({ id })) };
}
try {
// 登录态由同源后端代取模型目录,避免每个 OpenAI 兼容服务分别维护浏览器 CORS 白名单。
const result = await http.post<{ models?: Array<string | ChannelModelCatalogItem> }>("/ai/models", {
baseUrl: channel.baseUrl,
apiKey: channel.apiKey,
apiFormat: channel.apiFormat,
headers: channel.headers,
});
const catalog = new Map<string, ChannelModelCatalogItem>();
for (const item of result.models || []) {
const entry = typeof item === "string" ? sanitizeChannelModelCatalogItem({ id: item }) : sanitizeChannelModelCatalogItem(item);
if (!entry) continue;
const existing = catalog.get(entry.id);
catalog.set(entry.id, existing || entry);
}
const models = Array.from(catalog.keys()).sort((a, b) => a.localeCompare(b));
const sortedCatalog = Array.from(catalog.values()).sort((a, b) => a.id.localeCompare(b.id));
return { models, catalog: sortedCatalog };
} catch (error) {
throw new Error(readAxiosError(error, "读取模型失败"));
}
}