feat: 更新主流大模型供应商目录
- 按云服务、Coding Plan、API 网关和本地模型分组 - 新增兼容模型发现与供应商协议校验
This commit is contained in:
1 parent
01e4435f29
commit
d8b124fc7c
15 files changed
+838
-237
No files matched your search
@@ -12,153 +12,25 @@ import {
|
||||
import { toast } from 'sonner';
|
||||
import {
|
||||
Loader2, Save, Wifi, KeyRound, Brain, Sparkles, MessageSquare, AlertCircle,
|
||||
ExternalLink, CheckCircle2, ShoppingBag,
|
||||
ExternalLink, CheckCircle2, ShoppingBag, RefreshCw,
|
||||
} from 'lucide-react';
|
||||
import { aiApi, type LLMConfigData, type LLMTestResult } from '@/api';
|
||||
import { PageShell } from '@/components/layout/PageShell';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
// 单个提供商配置:默认 endpoint、候选模型、套餐/充值引导链接 // Logan.Li
|
||||
interface ProviderMeta {
|
||||
id: string;
|
||||
name: string;
|
||||
endpoint: string;
|
||||
models: string[];
|
||||
apiKeyOptional?: boolean; // 本地/自建模型 API Key 可留空 // Logan.Li
|
||||
planUrl?: string;
|
||||
planLabel?: string;
|
||||
planHint?: string;
|
||||
}
|
||||
import {
|
||||
CLOUD_CODING_PROVIDERS,
|
||||
CLOUD_GENERAL_PROVIDERS,
|
||||
endpointPlaceholder,
|
||||
findProvider,
|
||||
GATEWAY_PROVIDERS,
|
||||
LOCAL_PROVIDERS,
|
||||
needsManualModel,
|
||||
} from './llmProviders';
|
||||
|
||||
// 连接凭证快照(仅含影响连通的字段,切换后需重新验证) // Logan.Li
|
||||
const connectionSnapshot = (c: LLMConfigData) =>
|
||||
`${c.provider}|${c.model}|${c.endpoint}|${c.api_key}`;
|
||||
|
||||
// 云服务(按量 / 通用 API) // Logan.Li
|
||||
const CLOUD_GENERAL_PROVIDERS: ProviderMeta[] = [
|
||||
{
|
||||
id: 'deepseek', name: 'DeepSeek', endpoint: 'https://api.deepseek.com/v1',
|
||||
models: ['deepseek-v4-pro', 'deepseek-v4-flash', 'deepseek-reasoner', 'deepseek-chat'],
|
||||
planUrl: 'https://platform.deepseek.com',
|
||||
planLabel: i18n.t('aiops.getApiKey'),
|
||||
planHint: i18n.t('aiops.deepseekHint'),
|
||||
},
|
||||
{
|
||||
id: 'glm', name: i18n.t('aiops.zhipuGeneral'), endpoint: 'https://open.bigmodel.cn/api/paas/v4',
|
||||
models: ['glm-4.5', 'glm-4.6', 'glm-4-flash', 'glm-4-air', 'glm-4-plus'],
|
||||
planUrl: 'https://open.bigmodel.cn/',
|
||||
planLabel: i18n.t('aiops.zhipuOpenPlatform'),
|
||||
planHint: i18n.t('aiops.zhipuGetKey'),
|
||||
},
|
||||
{
|
||||
id: 'moonshot', name: 'Kimi (Moonshot)', endpoint: 'https://api.moonshot.cn/v1',
|
||||
models: ['kimi-latest', 'moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k'],
|
||||
planUrl: 'https://www.kimi.com/membership/pricing',
|
||||
planLabel: i18n.t('aiops.buyKimiPlan'),
|
||||
planHint: i18n.t('aiops.kimiGeneralHint'),
|
||||
},
|
||||
{
|
||||
id: 'minimax', name: i18n.t('aiops.minimaxPayg'), endpoint: 'https://api.minimaxi.com/v1',
|
||||
models: ['MiniMax-M3', 'MiniMax-M2.7', 'MiniMax-M2.7-highspeed', 'MiniMax-M2.5', 'MiniMax-M2.5-highspeed'],
|
||||
planUrl: 'https://platform.minimaxi.com/user-center/basic-information/interface-key',
|
||||
planLabel: i18n.t('aiops.getPaygApiKey'),
|
||||
planHint: i18n.t('aiops.byTokenBilling'),
|
||||
},
|
||||
{
|
||||
id: 'mimo', name: i18n.t('aiops.mimoXiaomi'), endpoint: 'https://api.mimo.mi.com/v1',
|
||||
models: ['mimo-v2', 'mimo-v2-flash'],
|
||||
planUrl: 'https://mimo.mi.com/',
|
||||
planLabel: i18n.t('aiops.mimoPlatform'),
|
||||
},
|
||||
{
|
||||
id: 'siliconflow', name: i18n.t('aiops.providerSiliconflow'), endpoint: 'https://api.siliconflow.cn/v1',
|
||||
models: ['deepseek-ai/DeepSeek-V3', 'Qwen/Qwen2.5-72B-Instruct', 'Pro/deepseek-ai/DeepSeek-R1'],
|
||||
planUrl: 'https://cloud.siliconflow.cn/',
|
||||
planLabel: i18n.t('aiops.siliconflowConsole'),
|
||||
planHint: i18n.t('aiops.siliconflowHint'),
|
||||
},
|
||||
{
|
||||
id: 'openai', name: 'OpenAI', endpoint: 'https://api.openai.com/v1',
|
||||
models: ['gpt-4o', 'gpt-4o-mini', 'gpt-4.1', 'gpt-4.1-mini', 'o3-mini', 'o4-mini', 'o1-mini'],
|
||||
planUrl: 'https://platform.openai.com/api-keys',
|
||||
planLabel: i18n.t('system.createApiKey'),
|
||||
planHint: i18n.t('aiops.openaiHint'),
|
||||
},
|
||||
{
|
||||
id: 'anthropic', name: 'Anthropic Claude', endpoint: 'https://api.anthropic.com',
|
||||
models: ['claude-sonnet-4-6', 'claude-opus-4-8', 'claude-haiku-4-5'],
|
||||
planUrl: 'https://console.anthropic.com/settings/billing',
|
||||
planLabel: i18n.t('aiops.anthropicBill'),
|
||||
planHint: i18n.t('aiops.customProxyHint'),
|
||||
},
|
||||
];
|
||||
|
||||
// 编码套餐 Coding Plan(固定月费,适合 Agent CLI 高频调用) // Logan.Li
|
||||
const CLOUD_CODING_PROVIDERS: ProviderMeta[] = [
|
||||
{
|
||||
id: 'glm-coding', name: i18n.t('aiops.zhipuCoding'), endpoint: 'https://open.bigmodel.cn/api/anthropic',
|
||||
models: ['GLM-5.2', 'GLM-5.1'],
|
||||
planUrl: 'https://bigmodel.cn/glm-coding',
|
||||
planLabel: i18n.t('aiops.buyGlmCoding'),
|
||||
planHint: i18n.t('aiops.zhipuCodingHint'),
|
||||
},
|
||||
{
|
||||
id: 'kimi', name: i18n.t('aiops.kimiCoding'), endpoint: 'https://api.kimi.com/coding/v1',
|
||||
models: ['kimi-for-coding', 'kimi-coding-latest'],
|
||||
planUrl: 'https://www.kimi.com/membership/pricing',
|
||||
planLabel: i18n.t('aiops.buyKimiPlan'),
|
||||
planHint: i18n.t('aiops.kimiCodingEndpoint'),
|
||||
},
|
||||
{
|
||||
id: 'minimax-coding', name: i18n.t('aiops.minimaxCoding'), endpoint: 'https://api.minimaxi.com/anthropic',
|
||||
models: ['MiniMax-M3', 'MiniMax-M3[1m]', 'MiniMax-M2.7'],
|
||||
planUrl: 'https://platform.minimaxi.com/user-center/payment/token-plan',
|
||||
planLabel: i18n.t('aiops.buyTokenPack'),
|
||||
planHint: i18n.t('aiops.anthropicSubHint'),
|
||||
},
|
||||
];
|
||||
|
||||
const CLOUD_PROVIDERS: ProviderMeta[] = [...CLOUD_GENERAL_PROVIDERS, ...CLOUD_CODING_PROVIDERS];
|
||||
|
||||
// 自定义接入:手动填请求地址 + API Key + 模型名(OpenAI 兼容) // Logan.Li
|
||||
const CUSTOM_PROVIDERS: ProviderMeta[] = [
|
||||
{
|
||||
id: 'custom', name: i18n.t('aiops.customProvider'), endpoint: '', models: [],
|
||||
apiKeyOptional: true,
|
||||
planHint: i18n.t('aiops.customProviderDesc'),
|
||||
},
|
||||
];
|
||||
|
||||
// 本地/自建模型(API Key 可选,置于列表底部) // Logan.Li
|
||||
const LOCAL_PROVIDERS: ProviderMeta[] = [
|
||||
{
|
||||
id: 'ollama', name: i18n.t('aiops.ollamaLocal'), endpoint: 'http://localhost:11434/v1',
|
||||
models: ['qwen2.5:7b', 'llama3.2', 'deepseek-r1:7b'],
|
||||
apiKeyOptional: true,
|
||||
},
|
||||
{
|
||||
id: 'vllm', name: i18n.t('aiops.vllmSelfHosted'), endpoint: 'http://localhost:8000/v1',
|
||||
models: [],
|
||||
apiKeyOptional: true,
|
||||
},
|
||||
];
|
||||
|
||||
const PROVIDERS: ProviderMeta[] = [...CLOUD_PROVIDERS, ...CUSTOM_PROVIDERS, ...LOCAL_PROVIDERS];
|
||||
|
||||
// 模型名需手动输入的提供商(空 models 或需填接入点 ID) // Logan.Li
|
||||
const needsManualModel = (provider: string, prov?: ProviderMeta) =>
|
||||
provider === 'custom' || provider === 'vllm' || !prov?.models.length;
|
||||
|
||||
const endpointPlaceholder = (provider: string) => {
|
||||
switch (provider) {
|
||||
case 'custom': return 'https://openrouter.ai/api/v1';
|
||||
case 'ollama': return 'http://localhost:11434/v1';
|
||||
case 'vllm': return 'http://localhost:8000/v1';
|
||||
default: return 'https://api.xxx.com/v1';
|
||||
}
|
||||
};
|
||||
|
||||
const defaultCfg: LLMConfigData = {
|
||||
provider: 'deepseek', model: 'deepseek-v4-pro', api_key: '',
|
||||
endpoint: 'https://api.deepseek.com/v1', temperature: 0.7, max_tokens: 8192,
|
||||
@@ -171,20 +43,36 @@ export default function LLMConfigPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [testing, setTesting] = useState(false);
|
||||
const [loadingModels, setLoadingModels] = useState(false);
|
||||
const [discoveredModels, setDiscoveredModels] = useState<string[]>([]);
|
||||
const [testResult, setTestResult] = useState<LLMTestResult | null>(null);
|
||||
const [modelMode, setModelMode] = useState<'select' | 'input'>('select');
|
||||
const [verifiedSnapshot, setVerifiedSnapshot] = useState<string | null>(null);
|
||||
|
||||
const prov = findProvider(cfg.provider);
|
||||
const currentSnapshot = useMemo(() => connectionSnapshot(cfg), [cfg]);
|
||||
const isVerified = verifiedSnapshot === currentSnapshot;
|
||||
const models = useMemo(() => Array.from(new Set([
|
||||
...(prov?.models ?? []),
|
||||
...discoveredModels,
|
||||
...(cfg.model?.trim() ? [cfg.model.trim()] : []),
|
||||
])), [cfg.model, discoveredModels, prov]);
|
||||
const manualModel = needsManualModel(cfg.provider, prov);
|
||||
const isCustom = cfg.provider === 'custom';
|
||||
const apiKeyOptional = prov?.apiKeyOptional ?? false;
|
||||
const endpointRequired = !prov?.endpoint;
|
||||
const canTest = Boolean(cfg.model?.trim())
|
||||
&& (endpointRequired ? Boolean(cfg.endpoint?.trim()) : true)
|
||||
&& (apiKeyOptional || Boolean(cfg.api_key?.trim()));
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const saved = await aiApi.getConfig();
|
||||
const merged = { ...defaultCfg, ...saved };
|
||||
const merged = saved.configured ? { ...defaultCfg, ...saved } : defaultCfg;
|
||||
setCfg(merged);
|
||||
const prov = PROVIDERS.find((x) => x.id === merged.provider);
|
||||
setDiscoveredModels([]);
|
||||
const prov = findProvider(merged.provider);
|
||||
const inList = prov ? prov.models.includes(merged.model) : false;
|
||||
setModelMode(needsManualModel(merged.provider, prov) || !inList ? 'input' : 'select');
|
||||
if (saved.configured) {
|
||||
@@ -244,14 +132,41 @@ export default function LLMConfigPage() {
|
||||
finally { setTesting(false); }
|
||||
};
|
||||
|
||||
const discoverModels = async () => {
|
||||
if (!cfg.endpoint?.trim()) {
|
||||
toast.error(t('system.fillEndpointFirst'));
|
||||
return;
|
||||
}
|
||||
if (!apiKeyOptional && !cfg.api_key?.trim()) {
|
||||
toast.error(t('system.fillApiKeyFirst'));
|
||||
return;
|
||||
}
|
||||
setLoadingModels(true);
|
||||
try {
|
||||
const result = await aiApi.listModels(cfg);
|
||||
if (!result.models.length) {
|
||||
toast.error(t('aiops.noModelsDiscovered'));
|
||||
return;
|
||||
}
|
||||
setDiscoveredModels(result.models);
|
||||
setModelMode('select');
|
||||
if (!cfg.model?.trim()) {
|
||||
set('model', result.models[0]);
|
||||
}
|
||||
toast.success(t('aiops.modelsDiscovered', { count: result.total }));
|
||||
}
|
||||
catch { toast.error(t('aiops.modelListFailed')); }
|
||||
finally { setLoadingModels(false); }
|
||||
};
|
||||
|
||||
const onProvider = (p: string) => {
|
||||
const next = PROVIDERS.find((x) => x.id === p);
|
||||
const next = findProvider(p);
|
||||
if (!next) return;
|
||||
const model = next.models[0] || '';
|
||||
setModelMode(needsManualModel(p, next) ? 'input' : 'select');
|
||||
setDiscoveredModels([]);
|
||||
invalidateVerify();
|
||||
const endpoint = p === 'custom' ? '' : (next.endpoint || cfg.endpoint);
|
||||
setCfg({ ...cfg, provider: p, endpoint, model });
|
||||
setCfg({ ...cfg, provider: p, endpoint: next.endpoint, model, api_key: '' });
|
||||
};
|
||||
|
||||
const onModel = (m: string) => {
|
||||
@@ -270,16 +185,8 @@ export default function LLMConfigPage() {
|
||||
setCfg({ ...cfg, [k]: v });
|
||||
};
|
||||
|
||||
const prov = PROVIDERS.find((x) => x.id === cfg.provider);
|
||||
const unknownProvider = Boolean(cfg.provider && !prov);
|
||||
const models = prov?.models ?? [];
|
||||
const manualModel = needsManualModel(cfg.provider, prov);
|
||||
const isCustom = cfg.provider === 'custom';
|
||||
const apiKeyOptional = prov?.apiKeyOptional ?? false;
|
||||
const endpointRequired = apiKeyOptional || cfg.provider === 'custom';
|
||||
const canTest = Boolean(cfg.model?.trim())
|
||||
&& (endpointRequired ? Boolean(cfg.endpoint?.trim()) : true)
|
||||
&& (apiKeyOptional || Boolean(cfg.api_key?.trim()));
|
||||
const showModelInput = modelMode === 'input' || (manualModel && discoveredModels.length === 0);
|
||||
|
||||
return (
|
||||
<PageShell
|
||||
@@ -294,7 +201,7 @@ export default function LLMConfigPage() {
|
||||
<Card className="dg-card">
|
||||
<CardHeader><CardTitle className="text-[var(--dg-text-primary)] text-base">{t('aiops.providerAndCredential')}</CardTitle></CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-[var(--dg-text-secondary)]">{t('aiops.providerLabel')}</Label>
|
||||
<Select value={cfg.provider} onValueChange={onProvider}>
|
||||
@@ -309,33 +216,44 @@ export default function LLMConfigPage() {
|
||||
<SelectGroup>
|
||||
<SelectLabel>{t('aiops.cloudService')}</SelectLabel>
|
||||
{CLOUD_GENERAL_PROVIDERS.map((p) => (
|
||||
<SelectItem key={p.id} value={p.id}>{p.name}</SelectItem>
|
||||
<SelectItem key={p.id} value={p.id}>{t(p.nameKey)}</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>{t('aiops.codingPlan')}</SelectLabel>
|
||||
{CLOUD_CODING_PROVIDERS.map((p) => (
|
||||
<SelectItem key={p.id} value={p.id}>{p.name}</SelectItem>
|
||||
<SelectItem key={p.id} value={p.id}>{t(p.nameKey)}</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>{t('aiops.customAccess')}</SelectLabel>
|
||||
{CUSTOM_PROVIDERS.map((p) => (
|
||||
<SelectItem key={p.id} value={p.id}>{p.name}</SelectItem>
|
||||
<SelectLabel>{t('aiops.apiGateway')}</SelectLabel>
|
||||
{GATEWAY_PROVIDERS.map((p) => (
|
||||
<SelectItem key={p.id} value={p.id}>{t(p.nameKey)}</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
<SelectGroup>
|
||||
<SelectLabel>{t('aiops.localSelfHosted')}</SelectLabel>
|
||||
{LOCAL_PROVIDERS.map((p) => (
|
||||
<SelectItem key={p.id} value={p.id}>{p.name}</SelectItem>
|
||||
<SelectItem key={p.id} value={p.id}>{t(p.nameKey)}</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-[var(--dg-text-secondary)]">{t('aiops.modelLabel')}</Label>
|
||||
{manualModel || modelMode === 'input' ? (
|
||||
<div className="flex min-h-5 items-center justify-between gap-2">
|
||||
<Label className="text-[var(--dg-text-secondary)]">{t('aiops.modelLabel')}</Label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={discoverModels}
|
||||
disabled={loadingModels}
|
||||
className="inline-flex items-center gap-1 text-xs dg-text-blue hover:underline disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<RefreshCw className={`h-3 w-3 ${loadingModels ? 'animate-spin' : ''}`} />
|
||||
{t('aiops.loadModels')}
|
||||
</button>
|
||||
</div>
|
||||
{showModelInput ? (
|
||||
<Input value={cfg.model} onChange={(e) => set('model', e.target.value)}
|
||||
placeholder={isCustom ? t('aiops.modelPlaceholder') : t('aiops.manualInputModel')}
|
||||
className="dg-input" />
|
||||
@@ -365,7 +283,7 @@ export default function LLMConfigPage() {
|
||||
<ShoppingBag className="w-4 h-4 shrink-0 mt-0.5 text-[var(--dg-orange)]" />
|
||||
<div className="flex-1 min-w-0 space-y-1">
|
||||
<div className="text-sm text-[var(--dg-text-secondary)]">
|
||||
{prov.planHint || t('aiops.providerNeedsKey', { name: prov.name })}
|
||||
{prov.planHintKey ? t(prov.planHintKey) : t('aiops.providerNeedsKey', { name: t(prov.nameKey) })}
|
||||
</div>
|
||||
<a
|
||||
href={prov.planUrl}
|
||||
@@ -373,17 +291,17 @@ export default function LLMConfigPage() {
|
||||
rel="noreferrer"
|
||||
className="inline-flex items-center gap-1 text-xs dg-text-blue hover:underline"
|
||||
>
|
||||
{prov.planLabel || t('aiops.goEnable')}
|
||||
{prov.planLabelKey ? t(prov.planLabelKey) : t('aiops.goEnable')}
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isCustom && prov?.planHint && (
|
||||
{isCustom && prov?.planHintKey && (
|
||||
<div className="flex items-start gap-3 rounded-lg bg-[var(--dg-bg-tertiary)] px-3 py-2.5">
|
||||
<KeyRound className="w-4 h-4 shrink-0 mt-0.5 text-[var(--dg-blue)]" />
|
||||
<div className="text-sm text-[var(--dg-text-secondary)]">{prov.planHint}</div>
|
||||
<div className="text-sm text-[var(--dg-text-secondary)]">{t(prov.planHintKey)}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -402,8 +320,8 @@ export default function LLMConfigPage() {
|
||||
<Label className="text-[var(--dg-text-secondary)]">{t('aiops.endpointLabel')}</Label>
|
||||
<Input value={cfg.endpoint} onChange={(e) => set('endpoint', e.target.value)}
|
||||
placeholder={endpointPlaceholder(cfg.provider)} className="dg-input" />
|
||||
{prov?.planHint && !prov.planUrl && (
|
||||
<p className="text-xs dg-text-muted">{prov.planHint}</p>
|
||||
{prov?.planHintKey && !prov.planUrl && !isCustom && (
|
||||
<p className="text-xs dg-text-muted">{t(prov.planHintKey)}</p>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -486,4 +404,4 @@ export default function LLMConfigPage() {
|
||||
</div>
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
// LLM 供应商目录参考 models.dev,兼容通道参考 New API;仅收录当前客户端可调用的 Chat/Messages 服务。 // Logan.Li
|
||||
export interface ProviderMeta {
|
||||
id: string;
|
||||
nameKey: string;
|
||||
endpoint: string;
|
||||
models: string[];
|
||||
apiKeyOptional?: boolean;
|
||||
planUrl?: string;
|
||||
planLabelKey?: string;
|
||||
planHintKey?: string;
|
||||
}
|
||||
|
||||
// 主流公有云模型服务,均使用 OpenAI 兼容协议,Anthropic 除外。 // Logan.Li
|
||||
export const CLOUD_GENERAL_PROVIDERS: ProviderMeta[] = [
|
||||
{
|
||||
id: 'deepseek', nameKey: 'aiops.providerDeepseek', endpoint: 'https://api.deepseek.com/v1',
|
||||
models: ['deepseek-v4-pro', 'deepseek-v4-flash', 'deepseek-reasoner', 'deepseek-chat'],
|
||||
planUrl: 'https://platform.deepseek.com', planLabelKey: 'aiops.getApiKey',
|
||||
planHintKey: 'aiops.deepseekHint',
|
||||
},
|
||||
{
|
||||
id: 'qwen', nameKey: 'aiops.providerQwen', endpoint: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||
models: ['qwen3.7-plus', 'qwen3.7-max', 'qwen3.6-plus', 'qwen3-coder-flash', 'qwq-plus'],
|
||||
planUrl: 'https://bailian.console.aliyun.com/', planLabelKey: 'aiops.getApiKey',
|
||||
},
|
||||
{
|
||||
id: 'glm', nameKey: 'aiops.providerZhipu', endpoint: 'https://open.bigmodel.cn/api/paas/v4',
|
||||
models: ['glm-5.2', 'glm-5.1', 'glm-5'],
|
||||
planUrl: 'https://open.bigmodel.cn/', planLabelKey: 'aiops.zhipuOpenPlatform',
|
||||
planHintKey: 'aiops.zhipuGetKey',
|
||||
},
|
||||
{
|
||||
id: 'moonshot', nameKey: 'aiops.providerMoonshot', endpoint: 'https://api.moonshot.cn/v1',
|
||||
models: ['kimi-k3', 'kimi-k2.7-code', 'kimi-k2.6', 'kimi-k2.5', 'kimi-k2-thinking-turbo'],
|
||||
planUrl: 'https://platform.moonshot.cn/console/api-keys', planLabelKey: 'aiops.getApiKey',
|
||||
planHintKey: 'aiops.kimiGeneralHint',
|
||||
},
|
||||
{
|
||||
id: 'minimax', nameKey: 'aiops.providerMinimax', endpoint: 'https://api.minimaxi.com/v1',
|
||||
models: ['MiniMax-M3', 'MiniMax-M2.7', 'MiniMax-M2.7-highspeed', 'MiniMax-M2.5'],
|
||||
planUrl: 'https://platform.minimaxi.com/user-center/basic-information/interface-key',
|
||||
planLabelKey: 'aiops.getPaygApiKey', planHintKey: 'aiops.byTokenBilling',
|
||||
},
|
||||
{
|
||||
id: 'mimo', nameKey: 'aiops.mimoXiaomi', endpoint: 'https://api.xiaomimimo.com/v1',
|
||||
models: ['mimo-v2.5-pro', 'mimo-v2.5', 'mimo-v2-pro', 'mimo-v2-flash'],
|
||||
planUrl: 'https://platform.xiaomimimo.com/', planLabelKey: 'aiops.mimoPlatform',
|
||||
},
|
||||
{
|
||||
id: 'hunyuan', nameKey: 'aiops.providerTencentTokenHub', endpoint: 'https://tokenhub.tencentmaas.com/v1',
|
||||
models: ['hy3', 'hy3-preview'],
|
||||
planUrl: 'https://console.cloud.tencent.com/lkeap', planLabelKey: 'aiops.getApiKey',
|
||||
},
|
||||
{
|
||||
id: 'volcengine', nameKey: 'aiops.providerVolcengine', endpoint: 'https://ark.cn-beijing.volces.com/api/v3',
|
||||
models: [], planUrl: 'https://console.volcengine.com/ark', planLabelKey: 'aiops.goEnable',
|
||||
planHintKey: 'aiops.endpointModelHint',
|
||||
},
|
||||
{
|
||||
id: 'baidu', nameKey: 'aiops.providerBaidu', endpoint: 'https://qianfan.baidubce.com/v2',
|
||||
models: [], planUrl: 'https://console.bce.baidu.com/qianfan/ais/console/applicationConsole/application',
|
||||
planLabelKey: 'aiops.getApiKey', planHintKey: 'aiops.manualModelHint',
|
||||
},
|
||||
{
|
||||
id: 'siliconflow', nameKey: 'aiops.providerSiliconflow', endpoint: 'https://api.siliconflow.cn/v1',
|
||||
models: ['deepseek-ai/DeepSeek-V4-Pro', 'deepseek-ai/DeepSeek-V4-Flash', 'Pro/deepseek-ai/DeepSeek-R1', 'Qwen/Qwen3.5-397B-A17B'],
|
||||
planUrl: 'https://cloud.siliconflow.cn/', planLabelKey: 'aiops.siliconflowConsole',
|
||||
planHintKey: 'aiops.siliconflowHint',
|
||||
},
|
||||
{
|
||||
id: 'openai', nameKey: 'aiops.providerOpenai', endpoint: 'https://api.openai.com/v1',
|
||||
models: ['gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-chat-latest', 'gpt-4.1', 'o4-mini'],
|
||||
planUrl: 'https://platform.openai.com/api-keys', planLabelKey: 'system.createApiKey',
|
||||
planHintKey: 'aiops.openaiHint',
|
||||
},
|
||||
{
|
||||
id: 'anthropic', nameKey: 'aiops.providerAnthropic', endpoint: 'https://api.anthropic.com',
|
||||
models: ['claude-sonnet-5', 'claude-fable-5', 'claude-opus-4-8', 'claude-sonnet-4-6', 'claude-haiku-4-5'],
|
||||
planUrl: 'https://console.anthropic.com/settings/keys', planLabelKey: 'aiops.getApiKey',
|
||||
},
|
||||
{
|
||||
id: 'google', nameKey: 'aiops.providerGemini', endpoint: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
||||
models: ['gemini-3.5-flash', 'gemini-3.1-pro-preview', 'gemini-3-flash-preview', 'gemini-2.5-pro', 'gemini-2.5-flash'],
|
||||
planUrl: 'https://aistudio.google.com/app/apikey', planLabelKey: 'aiops.getApiKey',
|
||||
},
|
||||
{
|
||||
id: 'xai', nameKey: 'aiops.providerXai', endpoint: 'https://api.x.ai/v1',
|
||||
models: ['grok-4.5', 'grok-4.3', 'grok-4.20-0309-reasoning', 'grok-4.20-0309-non-reasoning'],
|
||||
planUrl: 'https://console.x.ai/', planLabelKey: 'aiops.getApiKey',
|
||||
},
|
||||
{
|
||||
id: 'mistral', nameKey: 'aiops.providerMistral', endpoint: 'https://api.mistral.ai/v1',
|
||||
models: ['mistral-large-latest', 'mistral-medium-latest', 'mistral-small-latest', 'codestral-latest'],
|
||||
planUrl: 'https://console.mistral.ai/api-keys', planLabelKey: 'aiops.getApiKey',
|
||||
},
|
||||
{
|
||||
id: 'groq', nameKey: 'aiops.providerGroq', endpoint: 'https://api.groq.com/openai/v1',
|
||||
models: ['openai/gpt-oss-120b', 'llama-3.3-70b-versatile', 'qwen/qwen3-32b'],
|
||||
planUrl: 'https://console.groq.com/keys', planLabelKey: 'aiops.getApiKey',
|
||||
},
|
||||
{
|
||||
id: 'openrouter', nameKey: 'aiops.providerOpenrouter', endpoint: 'https://openrouter.ai/api/v1',
|
||||
models: ['openai/gpt-5.4', 'anthropic/claude-sonnet-4.6', 'google/gemini-3.1-pro-preview', 'deepseek/deepseek-v4-pro'],
|
||||
planUrl: 'https://openrouter.ai/settings/keys', planLabelKey: 'aiops.getApiKey',
|
||||
},
|
||||
{
|
||||
id: 'together', nameKey: 'aiops.providerTogether', endpoint: 'https://api.together.ai/v1',
|
||||
models: ['deepseek-ai/DeepSeek-V4-Pro', 'moonshotai/Kimi-K3', 'Qwen/Qwen3.7-Max', 'openai/gpt-oss-120b'],
|
||||
planUrl: 'https://api.together.ai/settings/api-keys', planLabelKey: 'aiops.getApiKey',
|
||||
},
|
||||
{
|
||||
id: 'cerebras', nameKey: 'aiops.providerCerebras', endpoint: 'https://api.cerebras.ai/v1',
|
||||
models: ['gpt-oss-120b', 'zai-glm-4.7', 'gemma-4-31b'],
|
||||
planUrl: 'https://cloud.cerebras.ai/platform/', planLabelKey: 'aiops.getApiKey',
|
||||
},
|
||||
{
|
||||
id: 'perplexity', nameKey: 'aiops.providerPerplexity', endpoint: 'https://api.perplexity.ai',
|
||||
models: ['sonar-pro', 'sonar-reasoning-pro', 'sonar'],
|
||||
planUrl: 'https://www.perplexity.ai/account/api/keys', planLabelKey: 'aiops.getApiKey',
|
||||
},
|
||||
];
|
||||
|
||||
// 编码套餐使用独立密钥或专用端点,不与通用 API 配置混用。 // Logan.Li
|
||||
export const CLOUD_CODING_PROVIDERS: ProviderMeta[] = [
|
||||
{
|
||||
id: 'glm-coding', nameKey: 'aiops.zhipuCoding', endpoint: 'https://open.bigmodel.cn/api/anthropic',
|
||||
models: ['GLM-5.2', 'GLM-5.1'], planUrl: 'https://bigmodel.cn/glm-coding',
|
||||
planLabelKey: 'aiops.buyGlmCoding', planHintKey: 'aiops.zhipuCodingHint',
|
||||
},
|
||||
{
|
||||
id: 'kimi', nameKey: 'aiops.kimiCoding', endpoint: 'https://api.kimi.com/coding/v1',
|
||||
models: ['kimi-for-coding', 'kimi-coding-latest'], planUrl: 'https://www.kimi.com/membership/pricing',
|
||||
planLabelKey: 'aiops.buyKimiPlan', planHintKey: 'aiops.kimiCodingEndpoint',
|
||||
},
|
||||
{
|
||||
id: 'minimax-coding', nameKey: 'aiops.minimaxCoding', endpoint: 'https://api.minimaxi.com/anthropic',
|
||||
models: ['MiniMax-M3', 'MiniMax-M3[1m]', 'MiniMax-M2.7'],
|
||||
planUrl: 'https://platform.minimaxi.com/user-center/payment/token-plan',
|
||||
planLabelKey: 'aiops.buyTokenPack', planHintKey: 'aiops.anthropicSubHint',
|
||||
},
|
||||
];
|
||||
|
||||
// New API/One API 是需要密钥的 OpenAI 兼容网关,自定义接入则允许无鉴权。 // Logan.Li
|
||||
export const GATEWAY_PROVIDERS: ProviderMeta[] = [
|
||||
{
|
||||
id: 'new-api', nameKey: 'aiops.providerNewApi', endpoint: '', models: [],
|
||||
planHintKey: 'aiops.newApiHint',
|
||||
},
|
||||
{
|
||||
id: 'custom', nameKey: 'aiops.customProvider', endpoint: '', models: [],
|
||||
apiKeyOptional: true, planHintKey: 'aiops.customProviderDesc',
|
||||
},
|
||||
];
|
||||
|
||||
// 本地与自建服务默认不要求 API Key。 // Logan.Li
|
||||
export const LOCAL_PROVIDERS: ProviderMeta[] = [
|
||||
{
|
||||
id: 'ollama', nameKey: 'aiops.ollamaLocal', endpoint: 'http://localhost:11434/v1',
|
||||
models: ['qwen3:8b', 'llama3.3', 'deepseek-r1:8b'], apiKeyOptional: true,
|
||||
},
|
||||
{
|
||||
id: 'vllm', nameKey: 'aiops.vllmSelfHosted', endpoint: 'http://localhost:8000/v1',
|
||||
models: [], apiKeyOptional: true,
|
||||
},
|
||||
{
|
||||
id: 'lmstudio', nameKey: 'aiops.providerLmStudio', endpoint: 'http://localhost:1234/v1',
|
||||
models: [], apiKeyOptional: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const PROVIDERS: ProviderMeta[] = [
|
||||
...CLOUD_GENERAL_PROVIDERS,
|
||||
...CLOUD_CODING_PROVIDERS,
|
||||
...GATEWAY_PROVIDERS,
|
||||
...LOCAL_PROVIDERS,
|
||||
];
|
||||
|
||||
export const findProvider = (provider: string) => PROVIDERS.find((item) => item.id === provider);
|
||||
|
||||
export const needsManualModel = (provider: string, meta?: ProviderMeta) =>
|
||||
provider === 'custom' || provider === 'new-api' || provider === 'vllm'
|
||||
|| provider === 'lmstudio' || !meta?.models.length;
|
||||
|
||||
export const endpointPlaceholder = (provider: string) => {
|
||||
switch (provider) {
|
||||
case 'new-api': return 'https://new-api.example.com/v1';
|
||||
case 'custom': return 'https://gateway.example.com/v1';
|
||||
case 'ollama': return 'http://localhost:11434/v1';
|
||||
case 'vllm': return 'http://localhost:8000/v1';
|
||||
case 'lmstudio': return 'http://localhost:1234/v1';
|
||||
default: return 'https://api.example.com/v1';
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user