Add quick LLM provider setup

This commit is contained in:
2026-07-07 13:12:27 +08:00
parent bcdf91c85d
commit dada4aafcd
5 changed files with 208 additions and 88 deletions

View File

@@ -43,7 +43,7 @@ const knowledgeForm = reactive({
});
const modelForm = reactive({
provider: "openai",
provider: "openai-compatible",
displayName: "",
apiType: "openai_compatible",
modelName: "",
@@ -66,58 +66,65 @@ const modelForm = reactive({
timeoutSecond: 30,
});
const modelPresets = [
const quickModelForm = reactive({
provider: "deepseek",
modelName: "deepseek-v4-pro",
apiKey: "",
});
const quickModelProviders = [
{
label: "OpenAI",
provider: "openai",
label: "DeepSeek",
provider: "deepseek",
apiType: "openai_compatible",
baseUrl: "https://api.deepseek.com",
authType: "bearer",
apiVersion: "",
temperature: 0.2,
topP: null,
maxToken: 1024,
contextWindow: 128000,
models: [
{ label: "deepseek-v4-pro", value: "deepseek-v4-pro" },
{ label: "deepseek-v4-flash", value: "deepseek-v4-flash" },
],
},
{
label: "MiniMax",
provider: "minimax",
apiType: "anthropic_messages",
baseUrl: "https://api.minimax.io/anthropic",
authType: "bearer",
apiVersion: "",
temperature: null,
topP: null,
maxToken: 1024,
contextWindow: 1000000,
models: [
{ label: "MiniMax-M3", value: "MiniMax-M3" },
{ label: "MiniMax-M2.7", value: "MiniMax-M2.7" },
{ label: "MiniMax-M2.7-highspeed", value: "MiniMax-M2.7-highspeed" },
{ label: "MiniMax-M2.5", value: "MiniMax-M2.5" },
{ label: "MiniMax-M2.5-highspeed", value: "MiniMax-M2.5-highspeed" },
{ label: "MiniMax-M2.1", value: "MiniMax-M2.1" },
{ label: "MiniMax-M2.1-highspeed", value: "MiniMax-M2.1-highspeed" },
{ label: "MiniMax-M2", value: "MiniMax-M2" },
],
},
] as const;
const protocolPresets = [
{
label: "OpenAI 兼容协议",
provider: "openai-compatible",
apiType: "openai_compatible",
baseUrl: "https://api.openai.com/v1",
apiUrl: "",
authType: "bearer",
},
{
label: "DeepSeek",
provider: "deepseek",
apiType: "openai_compatible",
baseUrl: "https://api.deepseek.com/v1",
apiUrl: "",
authType: "bearer",
},
{
label: "通义千问 DashScope",
provider: "qwen",
apiType: "openai_compatible",
baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
apiUrl: "",
authType: "bearer",
},
{
label: "Kimi / Moonshot",
provider: "moonshot",
apiType: "openai_compatible",
baseUrl: "https://api.moonshot.cn/v1",
apiUrl: "",
authType: "bearer",
},
{
label: "硅基流动 SiliconFlow",
provider: "siliconflow",
apiType: "openai_compatible",
baseUrl: "https://api.siliconflow.cn/v1",
apiUrl: "",
authType: "bearer",
},
{
label: "火山方舟 Ark",
provider: "volcengine-ark",
apiType: "openai_compatible",
baseUrl: "https://ark.cn-beijing.volces.com/api/v3",
apiUrl: "",
authType: "bearer",
},
{
label: "Anthropic Claude",
provider: "anthropic",
label: "Anthropic Messages 协议",
provider: "anthropic-compatible",
apiType: "anthropic_messages",
baseUrl: "https://api.anthropic.com",
apiUrl: "",
@@ -127,17 +134,6 @@ const modelPresets = [
topP: null,
topK: null,
},
{
label: "Google Gemini",
provider: "gemini",
apiType: "gemini_generate_content",
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
apiUrl: "",
authType: "api_key",
temperature: null,
topP: null,
topK: null,
},
];
const nullableModelFields = [
@@ -267,6 +263,52 @@ function buildModelPayload() {
return payload;
}
async function quickAddModel() {
const provider = selectedQuickProvider();
if (!provider) return;
if (!quickModelForm.apiKey) {
ElMessage.error("请填写 API Key");
return;
}
await api.createModel({
provider: provider.provider,
displayName: `${provider.label} ${quickModelForm.modelName}`,
apiType: provider.apiType,
modelName: quickModelForm.modelName,
baseUrl: provider.baseUrl,
apiUrl: "",
apiKey: quickModelForm.apiKey,
authType: provider.authType,
apiVersion: provider.apiVersion,
temperature: provider.temperature,
topP: provider.topP,
topK: null,
presencePenalty: null,
frequencyPenalty: null,
maxToken: provider.maxToken,
contextWindow: provider.contextWindow,
streamEnabled: 1,
responseFormat: null,
extraParams: null,
remark: "快速添加",
timeoutSecond: 30,
});
ElMessage.success(`${provider.label} 模型已新增`);
quickModelForm.apiKey = "";
await loadCurrentMenu();
}
function selectedQuickProvider() {
return quickModelProviders.find((item) => item.provider === quickModelForm.provider) ?? quickModelProviders[0];
}
function applyQuickProvider(provider: string) {
const target = quickModelProviders.find((item) => item.provider === provider);
if (!target) return;
quickModelForm.modelName = target.models[0]?.value ?? "";
}
async function enableModel(id: number) {
await api.enableModel(id);
ElMessage.success("模型已启用");
@@ -283,7 +325,7 @@ async function testModel(id: number) {
}
function applyModelPreset(label: string) {
const preset = modelPresets.find((item) => item.label === label);
const preset = protocolPresets.find((item) => item.label === label);
if (!preset) return;
Object.assign(
modelForm,
@@ -332,7 +374,7 @@ function editModel(row: ModelItem) {
function resetModelForm() {
editingModelId.value = null;
Object.assign(modelForm, {
provider: "openai",
provider: "openai-compatible",
displayName: "",
apiType: "openai_compatible",
modelName: "",
@@ -516,27 +558,64 @@ function buildChatQuery() {
<template v-if="activeMenu === 'models'">
<div class="page-head inline">
<div><h2>模型管理</h2><p>支持 OpenAI 兼容Anthropic MessagesGemini generateContent 和常见第三方 LLM</p></div>
<div><h2>模型管理</h2><p>DeepSeekMiniMax 可快速添加其他供应商按 OpenAI 兼容 Anthropic Messages 协议添加</p></div>
<el-button @click="resetModelForm">清空表单</el-button>
</div>
<section class="quick-model-panel">
<div class="quick-model-head">
<div>
<h3>快速添加</h3>
<p>适用于 DeepSeek MiniMax只需要填写 API Key 并选择模型</p>
</div>
</div>
<div class="quick-model-grid">
<el-form-item label="厂商">
<el-select v-model="quickModelForm.provider" @change="applyQuickProvider">
<el-option
v-for="provider in quickModelProviders"
:key="provider.provider"
:label="provider.label"
:value="provider.provider"
/>
</el-select>
</el-form-item>
<el-form-item label="模型">
<el-select v-model="quickModelForm.modelName">
<el-option
v-for="model in selectedQuickProvider()?.models"
:key="model.value"
:label="model.label"
:value="model.value"
/>
</el-select>
</el-form-item>
<el-form-item label="API Key">
<el-input v-model="quickModelForm.apiKey" placeholder="填写后保存,列表中只展示掩码" show-password />
</el-form-item>
<el-form-item label=" ">
<el-button type="primary" class="full-btn" @click="quickAddModel">快速新增</el-button>
</el-form-item>
</div>
</section>
<el-form class="model-config-panel" label-position="top" :model="modelForm">
<div class="model-config-grid">
<el-form-item label="供应商预设">
<el-select placeholder="选择预设" clearable @change="applyModelPreset">
<el-option v-for="preset in modelPresets" :key="preset.label" :label="preset.label" :value="preset.label" />
<el-form-item label="协议模板">
<el-select placeholder="选择协议模板" clearable @change="applyModelPreset">
<el-option v-for="preset in protocolPresets" :key="preset.label" :label="preset.label" :value="preset.label" />
</el-select>
</el-form-item>
<el-form-item label="供应商标识">
<el-input v-model="modelForm.provider" placeholder="openai / deepseek / qwen" />
<el-input v-model="modelForm.provider" placeholder="custom-openai / custom-anthropic" />
</el-form-item>
<el-form-item label="显示名称">
<el-input v-model="modelForm.displayName" placeholder="例如:DeepSeek V3 生产" />
<el-input v-model="modelForm.displayName" placeholder="例如:自建网关生产模型" />
</el-form-item>
<el-form-item label="协议类型">
<el-select v-model="modelForm.apiType">
<el-option label="OpenAI 兼容" value="openai_compatible" />
<el-option label="Anthropic Messages" value="anthropic_messages" />
<el-option label="Gemini generateContent" value="gemini_generate_content" />
</el-select>
</el-form-item>
<el-form-item label="模型名">

View File

@@ -193,16 +193,48 @@ textarea {
background: #ffffff;
}
.quick-model-panel {
margin-bottom: 18px;
padding: 18px;
border: 1px solid #cde7df;
border-radius: 8px;
background: #f8fcfb;
}
.quick-model-head {
margin-bottom: 12px;
}
.quick-model-head h3 {
margin: 0;
font-size: 17px;
}
.quick-model-head p {
margin: 6px 0 0;
color: #667a73;
}
.quick-model-grid {
display: grid;
grid-template-columns: 180px 240px minmax(280px, 1fr) 130px;
gap: 12px;
align-items: end;
}
.model-config-grid {
display: grid;
grid-template-columns: repeat(4, minmax(180px, 1fr));
gap: 12px 14px;
}
.quick-model-panel .el-form-item,
.model-config-panel .el-form-item {
margin-bottom: 12px;
}
.quick-model-panel .el-select,
.quick-model-panel .el-input,
.model-config-panel .el-select,
.model-config-panel .el-input-number,
.model-config-panel .el-input {

View File

@@ -138,11 +138,7 @@ def _call_anthropic_messages(model: ModelConfig, rag_result: RagResult) -> str:
_put_if_not_none(payload, "top_k", model.top_k)
payload.update(_load_extra_params(model.extra_params))
headers = {
"x-api-key": model.api_key,
"anthropic-version": model.api_version or "2023-06-01",
"Content-Type": "application/json",
}
headers = _anthropic_headers(model)
try:
response = httpx.post(
_resolve_anthropic_endpoint(model),
@@ -263,6 +259,19 @@ def _auth_headers(model: ModelConfig) -> dict[str, str]:
return headers
def _anthropic_headers(model: ModelConfig) -> dict[str, str]:
headers = {"Content-Type": "application/json"}
if model.auth_type == "bearer":
headers["Authorization"] = f"Bearer {model.api_key}"
else:
headers["x-api-key"] = model.api_key
if model.api_version:
headers["anthropic-version"] = model.api_version
elif model.provider == "anthropic":
headers["anthropic-version"] = "2023-06-01"
return headers
def _decimal_to_float(value: Any) -> float | None:
return float(value) if value is not None else None