import type { AiProviderKind, AiProviderResponse, AiProviderStatus, AiRelayMode } from "../api/types"; export type AiProviderFilter = AiProviderStatus | "all"; export type AiProviderListState = "loading" | "ready" | "error"; export type AiProviderViewState = "api" | "local-development" | "saving" | "error"; export interface AiProviderFormState { id: string; name: string; kind: AiProviderKind; baseUrl: string; apiKeyRef: string; modelsText: string; defaultModel: string; relayMode: AiRelayMode; timeoutMs: string; redactionPolicy: string; } export interface AiProviderKindDefaults { id: string; name: string; kind: AiProviderKind; baseUrl: string; apiKeyRef: string; modelsText: string; defaultModel: string; relayMode: AiRelayMode; timeoutMs: string; redactionPolicy: string; requirement: string; advancedNote: string; } export const aiProviderKindDefaults: Record = { "openai-compatible": { id: "ai.openai-compatible", name: "OpenAI Compatible", kind: "openai-compatible", baseUrl: "https://relay.example.test/v1", apiKeyRef: "secret://providers/openai-compatible", modelsText: "gpt-5.6-luna", defaultModel: "gpt-5.6-luna", relayMode: "relay", timeoutMs: "30000", redactionPolicy: "default", requirement: "需要平台侧 API key / secret 引用;Base URL 可按网关修改。", advancedNote: "OpenAI 兼容网关通常只差 Base URL,模型可以保存后再发现。" }, openai: { id: "ai.openai", name: "OpenAI", kind: "openai", baseUrl: "https://api.openai.com/v1", apiKeyRef: "secret://providers/openai", modelsText: "gpt-5.6-terra, gpt-5.6-luna", defaultModel: "gpt-5.6-terra", relayMode: "relay", timeoutMs: "30000", redactionPolicy: "default", requirement: "官方 SDK/API 使用 API key;平台保存 secret 引用,不在页面保存真实密钥。", advancedNote: "官方 OpenAI API 使用 Bearer API key 与 /v1 endpoint;模型列表可按账号权限调整。" }, claude: { id: "ai.claude", name: "Claude", kind: "claude", baseUrl: "https://api.anthropic.com/v1", apiKeyRef: "secret://providers/anthropic", modelsText: "claude-sonnet-5, claude-haiku-4-5-20251001", defaultModel: "claude-sonnet-5", relayMode: "relay", timeoutMs: "30000", redactionPolicy: "default", requirement: "Anthropic 请求使用 API key;版本头由平台适配层处理。", advancedNote: "保持平台中介调用,避免插件或前端接触 Anthropic key。" }, gemini: { id: "ai.gemini", name: "Gemini", kind: "gemini", baseUrl: "https://generativelanguage.googleapis.com/v1beta", apiKeyRef: "secret://providers/gemini", modelsText: "gemini-3.5-flash, gemini-2.5-flash", defaultModel: "gemini-3.5-flash", relayMode: "relay", timeoutMs: "30000", redactionPolicy: "default", requirement: "Google Gemini 入门使用 API key;平台保存 secret 引用。", advancedNote: "Base URL 使用 Google Generative Language API;模型名按项目可用模型调整。" }, ollama: { id: "ai.ollama", name: "Ollama Local", kind: "ollama", baseUrl: "http://127.0.0.1:11434/v1", apiKeyRef: "", modelsText: "gpt-oss:20b", defaultModel: "gpt-oss:20b", relayMode: "local", timeoutMs: "30000", redactionPolicy: "default", requirement: "本地 Ollama 默认不需要 API key;重点是本机/内网 Base URL 和模型名。", advancedNote: "Ollama OpenAI 兼容接口会忽略 dummy key;平台本地模式不要求用户填写密钥引用。" }, custom: { id: "ai.custom", name: "Custom Provider", kind: "custom", baseUrl: "https://provider.example.test/v1", apiKeyRef: "secret://providers/custom", modelsText: "custom-model", defaultModel: "custom-model", relayMode: "relay", timeoutMs: "30000", redactionPolicy: "default", requirement: "自定义服务至少需要平台 secret 引用、Base URL 和一个模型名。", advancedNote: "用于非标准协议或私有网关;保存前请确认服务兼容平台适配层。" } }; export interface AiProviderMetrics { total: number; active: number; disabled: number; models: number; } export interface AiProviderActionState { providerId: string; label: string; success: boolean; message: string; } export interface AiProviderPageInitialState { providers?: AiProviderResponse[]; selectedId?: string; listState?: AiProviderListState; listError?: string; source?: AiProviderViewState; action?: AiProviderActionState | null; } export function emptyAiProviderForm(): AiProviderFormState { return aiProviderFormFromDefaults("openai"); } export function aiProviderToForm(provider?: AiProviderResponse): AiProviderFormState { if (!provider) { return emptyAiProviderForm(); } return { id: provider.id, name: provider.name, kind: provider.kind, baseUrl: provider.baseUrl, apiKeyRef: provider.apiKeyRef, modelsText: provider.models.join(", "), defaultModel: provider.defaultModel ?? "", relayMode: provider.relayMode, timeoutMs: String(provider.timeoutMs), redactionPolicy: provider.redactionPolicy }; } export function aiProviderFormFromDefaults(kind: AiProviderKind): AiProviderFormState { const defaults = aiProviderKindDefaults[kind]; return { id: "", name: defaults.name, kind: defaults.kind, baseUrl: defaults.baseUrl, apiKeyRef: defaults.apiKeyRef, modelsText: defaults.modelsText, defaultModel: defaults.defaultModel, relayMode: defaults.relayMode, timeoutMs: defaults.timeoutMs, redactionPolicy: defaults.redactionPolicy }; } export function applyAiProviderKindDefaults(current: AiProviderFormState, kind: AiProviderKind): AiProviderFormState { const defaults = aiProviderKindDefaults[kind]; return { ...current, id: current.id, name: defaults.name, kind: defaults.kind, baseUrl: defaults.baseUrl, apiKeyRef: defaults.apiKeyRef, modelsText: defaults.modelsText, defaultModel: defaults.defaultModel, relayMode: defaults.relayMode, timeoutMs: defaults.timeoutMs, redactionPolicy: defaults.redactionPolicy }; } export function completeAiProviderForm(form: AiProviderFormState): AiProviderFormState { const defaults = aiProviderKindDefaults[form.kind]; const modelsText = form.modelsText.trim() || defaults.modelsText; const models = modelsText .split(",") .map((model) => model.trim()) .filter(Boolean); return { ...form, id: generatedAiProviderId(form), name: form.name.trim() || defaults.name, baseUrl: form.baseUrl.trim() || defaults.baseUrl, apiKeyRef: form.apiKeyRef.trim() || defaults.apiKeyRef, modelsText, defaultModel: form.defaultModel.trim() || models[0] || defaults.defaultModel, relayMode: form.relayMode || defaults.relayMode, timeoutMs: form.timeoutMs.trim() || defaults.timeoutMs, redactionPolicy: form.redactionPolicy.trim() || defaults.redactionPolicy }; } export function generatedAiProviderId(form: AiProviderFormState): string { if (form.id.trim()) { return form.id.trim(); } return aiProviderKindDefaults[form.kind].id; } export function summarizeAiProviders(providers: AiProviderResponse[]): AiProviderMetrics { return providers.reduce( (metrics, provider) => ({ total: metrics.total + 1, active: metrics.active + (provider.status === "active" ? 1 : 0), disabled: metrics.disabled + (provider.status === "disabled" ? 1 : 0), models: metrics.models + provider.models.length }), { total: 0, active: 0, disabled: 0, models: 0 } ); }