import { invoke } from "@tauri-apps/api/core"; import type { TFunction } from "i18next"; import { toast } from "sonner"; export interface FetchedModel { id: string; ownedBy: string | null; } /** * 从供应商获取可用模型列表 * * 使用 OpenAI 兼容的 GET /v1/models 端点。优先用 `modelsUrl` 精确覆写; * 否则后端会对 baseURL 生成候选列表并按序尝试(含"剥离 /anthropic 等兼容子路径"兜底)。 */ export async function fetchModelsForConfig( baseUrl: string, apiKey: string, isFullUrl?: boolean, modelsUrl?: string, ): Promise { return invoke("fetch_models_for_config", { baseUrl, apiKey, isFullUrl, modelsUrl, }); } /** * 获取 Codex OAuth (ChatGPT Plus/Pro 反代) 可用模型列表 * * Codex OAuth 使用 ChatGPT 的 backend-api/codex 端点,不兼容普通 /v1/models。 */ export async function fetchCodexOauthModels( accountId?: string | null, ): Promise { return invoke("get_codex_oauth_models", { accountId: accountId || null, }); } /** * 根据错误类型显示对应的 toast 提示 */ export function showFetchModelsError( err: unknown, t: TFunction, opts?: { hasApiKey: boolean; hasBaseUrl: boolean }, ): void { // 前端预检:缺少必填字段 if (opts && !opts.hasBaseUrl && !opts.hasApiKey) { toast.error(t("providerForm.fetchModelsNeedConfig")); return; } if (opts && !opts.hasApiKey) { toast.error(t("providerForm.fetchModelsNeedApiKey")); return; } if (opts && !opts.hasBaseUrl) { toast.error(t("providerForm.fetchModelsNeedEndpoint")); return; } // 解析后端错误字符串 const msg = String(err); if (msg.includes("HTTP 401") || msg.includes("HTTP 403")) { toast.error(t("providerForm.fetchModelsAuthFailed")); return; } // 所有候选端点均返回 404/405:供应商可能未开放 /models 接口,或 Base URL 有误 if (msg.includes("All candidates failed")) { toast.error(t("providerForm.fetchModelsEndpointNotFound")); return; } if (msg.includes("HTTP 404") || msg.includes("HTTP 405")) { toast.error(t("providerForm.fetchModelsEndpointNotFound")); return; } if (msg.includes("timeout") || msg.includes("timed out")) { toast.error(t("providerForm.fetchModelsTimeout")); return; } if (msg.includes("Failed to parse")) { toast.error(t("providerForm.fetchModelsNotSupported")); return; } // 通用兜底 toast.error(t("providerForm.fetchModelsFailed")); }