mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-26 06:24:32 +08:00
73bc4eb65d
- Add `get_codex_oauth_models` Tauri command reusing the managed OAuth access token to hit `chatgpt.com/backend-api/codex/models`; HTTP and multi-shape JSON parsing live in `services::codex_oauth_models` so the command stays thin. - Unify the Claude form's "fetch models" button across normal / Copilot / Codex OAuth presets, drop the auto-load effect for Copilot in favor of explicit clicks, and guard against stale responses with a requestId ref. - Add Vitest coverage for both Copilot and Codex OAuth paths asserting no request on mount and the correct account id on click; add Rust unit tests for the four model-list payload shapes.
93 lines
2.5 KiB
TypeScript
93 lines
2.5 KiB
TypeScript
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<FetchedModel[]> {
|
|
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<FetchedModel[]> {
|
|
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"));
|
|
}
|