You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
558 lines
22 KiB
558 lines
22 KiB
/**
|
|
* ⚙️ AI 模型配置管理组件
|
|
* 展示模型配置列表、新增/编辑/激活/删除配置
|
|
*/
|
|
import { ref, onMounted, watch } from 'vue'
|
|
import * as api from '../js/api.js'
|
|
import { toast, formatDate } from '../js/utils.js'
|
|
|
|
// 应用类型选项
|
|
const APP_TYPE_OPTIONS = [
|
|
{ value: '', label: '全部类型' },
|
|
{ value: 'CHAT', label: '智能客服对话' },
|
|
{ value: 'EMBEDDING', label: '文本向量化' },
|
|
{ value: 'RAG_REWRITE', label: 'RAG查询重写' }
|
|
]
|
|
|
|
// 提供商选项
|
|
const PROVIDER_OPTIONS = [
|
|
{ value: 'dashscope', label: '通义千问 (DashScope)' },
|
|
{ value: 'deepseek', label: 'DeepSeek (深度求索)' },
|
|
{ value: 'volcengine', label: '豆包 (字节跳动)' },
|
|
{ value: 'moonshot', label: 'Kimi (月之暗面)' },
|
|
{ value: 'zhipu', label: '智谱 AI (GLM)' },
|
|
{ value: 'openai', label: 'OpenAI' },
|
|
{ value: 'other', label: '其他' }
|
|
]
|
|
|
|
// 提供商默认配置(切换时自动填充)
|
|
// chatModels / embeddingModels 按 app_type 分别展示,models 作为向后兼容兜底
|
|
const PROVIDER_DEFAULTS = {
|
|
dashscope: {
|
|
baseUrl: '',
|
|
chatModels: ['qwen-turbo', 'qwen-plus', 'qwen-max', 'qwen-long'],
|
|
embeddingModels: ['text-embedding-v2'],
|
|
defaultChatModel: 'qwen-turbo',
|
|
defaultEmbeddingModel: 'text-embedding-v2',
|
|
tip: '通义千问使用 DashScope 自动配置,Base URL 留空即可'
|
|
},
|
|
deepseek: {
|
|
baseUrl: 'https://api.deepseek.com',
|
|
chatModels: ['deepseek-chat', 'deepseek-reasoner'],
|
|
embeddingModels: [],
|
|
defaultChatModel: 'deepseek-chat',
|
|
defaultEmbeddingModel: '',
|
|
tip: ''
|
|
},
|
|
volcengine: {
|
|
baseUrl: 'https://ark.cn-beijing.volces.com/api/v3',
|
|
chatModels: ['doubao-1-5-pro-32k', 'doubao-1-5-lite-32k', 'doubao-pro-32k', 'doubao-1-5-thinking-pro', 'doubao-seed-1.6'],
|
|
embeddingModels: ['doubao-embedding-text-240515', 'doubao-embedding-large', 'doubao-embedding-vision-251215'],
|
|
defaultChatModel: 'doubao-1-5-pro-32k',
|
|
defaultEmbeddingModel: 'doubao-embedding-text-240515',
|
|
tip: '豆包对话推荐 doubao-1-5-pro-32k;向量化推荐 doubao-embedding-text-240515(2048维)。模型名称可在火山引擎 ARK 控制台获取'
|
|
},
|
|
moonshot: {
|
|
baseUrl: 'https://api.moonshot.cn/v1',
|
|
chatModels: ['moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k'],
|
|
embeddingModels: [],
|
|
defaultChatModel: 'moonshot-v1-8k',
|
|
defaultEmbeddingModel: '',
|
|
tip: ''
|
|
},
|
|
zhipu: {
|
|
baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
|
|
chatModels: ['glm-4-plus', 'glm-4-flash', 'glm-4-long', 'glm-4'],
|
|
embeddingModels: [],
|
|
defaultChatModel: 'glm-4-flash',
|
|
defaultEmbeddingModel: '',
|
|
tip: ''
|
|
},
|
|
openai: {
|
|
baseUrl: 'https://api.openai.com',
|
|
chatModels: ['gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo'],
|
|
embeddingModels: ['text-embedding-3-small', 'text-embedding-3-large', 'text-embedding-ada-002'],
|
|
defaultChatModel: 'gpt-4o-mini',
|
|
defaultEmbeddingModel: 'text-embedding-3-small',
|
|
tip: ''
|
|
},
|
|
other: {
|
|
baseUrl: '',
|
|
chatModels: [],
|
|
embeddingModels: [],
|
|
defaultChatModel: '',
|
|
defaultEmbeddingModel: '',
|
|
tip: '使用 OpenAI 兼容 API 的其他提供商,请填写 Base URL 和模型名称'
|
|
}
|
|
}
|
|
|
|
export default {
|
|
template: `
|
|
<div class="card">
|
|
<h2>⚙️ AI 大模型配置管理</h2>
|
|
|
|
<!-- 筛选栏 -->
|
|
<div class="input-row">
|
|
<select class="input" v-model="filterAppType" @change="load(1)" style="max-width:200px;">
|
|
<option v-for="opt in appTypeOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</option>
|
|
</select>
|
|
<button class="btn btn-primary btn-sm" @click="openAddModal">➕ 新建配置</button>
|
|
<button class="btn btn-outline btn-sm" @click="load()">🔄 刷新</button>
|
|
</div>
|
|
|
|
<!-- 配置列表表格 -->
|
|
<div style="overflow-x:auto;">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>配置名称</th>
|
|
<th>应用类型</th>
|
|
<th>模型名称</th>
|
|
<th>提供商</th>
|
|
<th>温度</th>
|
|
<th>API Key</th>
|
|
<th>状态</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-if="configs.length === 0">
|
|
<td colspan="8" style="text-align:center;color:var(--sub);">暂无配置记录</td>
|
|
</tr>
|
|
<tr v-for="c in configs" :key="c.id">
|
|
<td>
|
|
<strong>{{ c.name || '-' }}</strong>
|
|
<div v-if="c.description" style="font-size:11px;color:var(--sub);margin-top:2px;">{{ c.description }}</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge" :class="getAppTypeBadgeClass(c.app_type)">{{ getAppTypeLabel(c.app_type) }}</span>
|
|
</td>
|
|
<td><code style="font-size:12px;background:#f3f4f6;padding:2px 6px;border-radius:4px;">{{ c.model_name }}</code></td>
|
|
<td>{{ getProviderLabel(c.provider) }}</td>
|
|
<td>{{ c.temperature != null ? c.temperature : '-' }}</td>
|
|
<td><code style="font-size:12px;background:#f3f4f6;padding:2px 6px;border-radius:4px;">{{ c.api_key || '-' }}</code></td>
|
|
<td>
|
|
<span v-if="c.is_active" style="color:#16a34a;font-weight:600;">
|
|
🟢 活跃
|
|
</span>
|
|
<span v-else style="color:var(--sub);">
|
|
⚫ 未激活
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline" @click="openEditModal(c)">编辑</button>
|
|
<button v-if="!c.is_active" class="btn btn-sm btn-primary" @click="activate(c.id)">激活</button>
|
|
<button v-if="!c.is_active" class="btn btn-sm btn-danger" @click="remove(c.id, c.name)">删除</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- 分页 -->
|
|
<div class="pagination" v-if="totalPages > 1">
|
|
<button :disabled="currentPage <= 1" @click="load(currentPage - 1)">上一页</button>
|
|
<template v-for="i in totalPages" :key="i">
|
|
<button v-if="i === 1 || i === totalPages || (i >= currentPage - 2 && i <= currentPage + 2)"
|
|
:class="{ active: i === currentPage }" @click="load(i)">{{ i }}</button>
|
|
<span v-else-if="i === currentPage - 3 || i === currentPage + 3" style="padding:6px;">...</span>
|
|
</template>
|
|
<button :disabled="currentPage >= totalPages" @click="load(currentPage + 1)">下一页</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 编辑/新建弹窗 -->
|
|
<div class="modal-overlay" :class="{ active: editModal.visible }">
|
|
<div class="modal-box" style="max-width:860px;">
|
|
<button class="modal-close" @click="closeEditModal">×</button>
|
|
<h2>{{ editModal.mode === 'add' ? '➕ 新建模型配置' : '✏️ 编辑模型配置' }}</h2>
|
|
|
|
<div class="model-form">
|
|
<!-- 配置名称(跨列) -->
|
|
<div class="model-form-full">
|
|
<label style="font-size:13px;font-weight:600;display:block;margin-bottom:4px;">配置名称 <span style="color:#dc2626;">*</span></label>
|
|
<input type="text" class="input" v-model="editModal.form.name" placeholder="如:生产环境-DeepSeek对话">
|
|
</div>
|
|
|
|
<!-- 应用类型 + 提供商(双列) -->
|
|
<div>
|
|
<label style="font-size:13px;font-weight:600;display:block;margin-bottom:4px;">应用类型 <span style="color:#dc2626;">*</span></label>
|
|
<select class="input" v-model="editModal.form.app_type" :disabled="editModal.mode === 'edit'">
|
|
<option v-for="opt in appTypeOptions.slice(1)" :key="opt.value" :value="opt.value">{{ opt.label }}</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label style="font-size:13px;font-weight:600;display:block;margin-bottom:4px;">提供商 <span style="color:#dc2626;">*</span></label>
|
|
<select class="input" v-model="editModal.form.provider" @change="onProviderChange">
|
|
<option v-for="opt in providerOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- 提供商提示(跨列) -->
|
|
<div v-if="providerTip" class="model-form-full" style="padding:8px 12px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:6px;font-size:12px;color:#1e40af;">
|
|
💡 {{ providerTip }}
|
|
</div>
|
|
|
|
<!-- API Key(跨列) -->
|
|
<div class="model-form-full">
|
|
<label style="font-size:13px;font-weight:600;display:block;margin-bottom:4px;">API Key <span style="color:#dc2626;">*</span></label>
|
|
<div style="display:flex;gap:8px;">
|
|
<input :type="showApiKey ? 'text' : 'password'" class="input" v-model="editModal.form.api_key"
|
|
:placeholder="editModal.mode === 'edit' ? '留空则不修改' : '请输入 API Key'" style="flex:1;">
|
|
<button class="btn btn-sm btn-outline" @click="showApiKey = !showApiKey" style="white-space:nowrap;">
|
|
{{ showApiKey ? '🙈 隐藏' : '👁️ 显示' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 模型名称(跨列) -->
|
|
<div class="model-form-full">
|
|
<label style="font-size:13px;font-weight:600;display:block;margin-bottom:4px;">模型名称 <span style="color:#dc2626;">*</span></label>
|
|
<input v-if="!currentProviderModels.length" type="text" class="input" v-model="editModal.form.model_name"
|
|
:placeholder="currentProviderDefaultModel || '请输入模型名称'">
|
|
<select v-else class="input" v-model="editModal.form.model_name">
|
|
<option v-for="m in currentProviderModels" :key="m" :value="m">{{ m }}</option>
|
|
<option value="__custom__">自定义输入...</option>
|
|
</select>
|
|
<input v-if="editModal.form.model_name === '__custom__'" type="text" class="input"
|
|
v-model="customModelName" placeholder="输入自定义模型名称" style="margin-top:6px;">
|
|
</div>
|
|
|
|
<!-- 温度 + 最大 Token(双列) -->
|
|
<div>
|
|
<label style="font-size:13px;font-weight:600;display:block;margin-bottom:4px;">温度 (Temperature)</label>
|
|
<input type="number" class="input" v-model.number="editModal.form.temperature" step="0.1" min="0" max="2" placeholder="0.7">
|
|
</div>
|
|
<div>
|
|
<label style="font-size:13px;font-weight:600;display:block;margin-bottom:4px;">最大 Token 数</label>
|
|
<input type="number" class="input" v-model.number="editModal.form.max_tokens" min="1" max="128000" placeholder="2000">
|
|
</div>
|
|
|
|
<!-- Base URL(跨列) -->
|
|
<div class="model-form-full">
|
|
<label style="font-size:13px;font-weight:600;display:block;margin-bottom:4px;">API 基础地址</label>
|
|
<input type="text" class="input" v-model="editModal.form.base_url" :placeholder="providerBaseUrlPlaceholder">
|
|
</div>
|
|
|
|
<!-- 向量维度(仅 EMBEDDING 类型,跨列) -->
|
|
<div v-if="editModal.form.app_type === 'EMBEDDING'" class="model-form-full" style="padding:10px 14px;background:#fefce8;border:1px solid #fde68a;border-radius:8px;">
|
|
<label style="font-size:13px;font-weight:600;display:block;margin-bottom:6px;">📐 向量维度 <span style="color:#dc2626;">*</span></label>
|
|
<input type="number" class="input" v-model.number="editModal.form.embeddingDimensions" min="1" max="8192" placeholder="1024" style="max-width:240px;">
|
|
<div style="font-size:11px;color:#92400e;margin-top:4px;">
|
|
⚠️ 修改维度后需重建向量表:<code style="background:#fde68a;padding:1px 4px;border-radius:3px;">DROP TABLE IF EXISTS vector_store CASCADE</code>,然后重启服务并重新上传知识库文档。
|
|
常用维度:千问 text-embedding-v2=1024 | 豆包 doubao-embedding-text=2048 | OpenAI text-embedding-3-small=1536
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 优先级 + 是否激活(双列) -->
|
|
<div>
|
|
<label style="font-size:13px;font-weight:600;display:block;margin-bottom:4px;">优先级</label>
|
|
<input type="number" class="input" v-model.number="editModal.form.priority" min="0" placeholder="0(数值越大越优先)">
|
|
</div>
|
|
<div style="display:flex;align-items:flex-end;padding-bottom:4px;">
|
|
<label style="display:flex;align-items:center;gap:8px;font-size:13px;cursor:pointer;">
|
|
<input type="checkbox" v-model="editModal.form.is_active">
|
|
设为活跃配置(同类型只能有一个活跃)
|
|
</label>
|
|
</div>
|
|
|
|
<!-- 描述(跨列) -->
|
|
<div class="model-form-full">
|
|
<label style="font-size:13px;font-weight:600;display:block;margin-bottom:4px;">描述说明</label>
|
|
<textarea class="input" v-model="editModal.form.description" rows="2" placeholder="可选,填写配置用途说明"></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 按钮 -->
|
|
<div style="display:flex;gap:10px;margin-top:20px;justify-content:flex-end;">
|
|
<button class="btn btn-outline" @click="closeEditModal">取消</button>
|
|
<button class="btn btn-primary" @click="saveConfig">💾 保存</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`,
|
|
setup() {
|
|
const configs = ref([])
|
|
const currentPage = ref(1)
|
|
const totalPages = ref(1)
|
|
const total = ref(0)
|
|
const filterAppType = ref('')
|
|
const showApiKey = ref(false)
|
|
const customModelName = ref('')
|
|
const providerTip = ref('')
|
|
|
|
// 计算当前提供商的模型列表和默认值
|
|
const currentProviderModels = ref([])
|
|
const currentProviderDefaultModel = ref('')
|
|
const providerBaseUrlPlaceholder = ref('')
|
|
|
|
// 编辑弹窗状态
|
|
const editModal = ref({
|
|
visible: false,
|
|
mode: 'add',
|
|
editId: null,
|
|
form: createEmptyForm()
|
|
})
|
|
|
|
function createEmptyForm() {
|
|
return {
|
|
name: '',
|
|
app_type: 'CHAT',
|
|
provider: 'dashscope',
|
|
api_key: '',
|
|
model_name: '',
|
|
temperature: 0.7,
|
|
max_tokens: 2000,
|
|
base_url: '',
|
|
embeddingDimensions: 1024,
|
|
priority: 0,
|
|
is_active: false,
|
|
description: ''
|
|
}
|
|
}
|
|
|
|
// 提供商切换时自动填充(按 app_type 区分对话/向量模型)
|
|
function onProviderChange() {
|
|
const provider = editModal.value.form.provider
|
|
const defaults = PROVIDER_DEFAULTS[provider] || PROVIDER_DEFAULTS.other
|
|
const isEmbedding = editModal.value.form.app_type === 'EMBEDDING'
|
|
|
|
// 自动填充 Base URL
|
|
editModal.value.form.base_url = defaults.baseUrl
|
|
|
|
// 按 app_type 选择模型列表和默认模型
|
|
const models = isEmbedding ? (defaults.embeddingModels || []) : (defaults.chatModels || [])
|
|
const defaultModel = isEmbedding ? (defaults.defaultEmbeddingModel || '') : (defaults.defaultChatModel || '')
|
|
|
|
// 仅新建模式且模型名为空时自动填充默认模型
|
|
if (editModal.value.mode === 'add' && !editModal.value.form.model_name) {
|
|
editModal.value.form.model_name = defaultModel
|
|
}
|
|
|
|
currentProviderModels.value = models
|
|
currentProviderDefaultModel.value = defaultModel
|
|
providerTip.value = defaults.tip || ''
|
|
providerBaseUrlPlaceholder.value = defaults.baseUrl || '私有化部署时可填写自定义地址'
|
|
customModelName.value = ''
|
|
}
|
|
|
|
// 监听 app_type 变化,刷新模型列表
|
|
watch(() => editModal.value.form.app_type, () => {
|
|
if (editModal.value.visible) {
|
|
onProviderChange()
|
|
}
|
|
})
|
|
|
|
// ==================== 数据加载 ====================
|
|
|
|
async function load(p = 1) {
|
|
currentPage.value = p
|
|
try {
|
|
const json = await api.listModelConfigs(p, 10, filterAppType.value || undefined)
|
|
if (json.success) {
|
|
configs.value = json.data || []
|
|
total.value = json.total || 0
|
|
totalPages.value = json.pages || 1
|
|
} else {
|
|
toast(json.message || '查询失败', 'error')
|
|
}
|
|
} catch (e) {
|
|
toast('加载配置列表失败:' + e.message, 'error')
|
|
}
|
|
}
|
|
|
|
// ==================== 弹窗操作 ====================
|
|
|
|
function openAddModal() {
|
|
editModal.value = {
|
|
visible: true,
|
|
mode: 'add',
|
|
editId: null,
|
|
form: createEmptyForm()
|
|
}
|
|
showApiKey.value = false
|
|
onProviderChange()
|
|
}
|
|
|
|
function openEditModal(config) {
|
|
// 从 extraConfig 中读取 embeddingDimensions
|
|
let extraConfig = {}
|
|
if (config.extraConfig) {
|
|
try {
|
|
extraConfig = typeof config.extraConfig === 'string' ? JSON.parse(config.extraConfig) : config.extraConfig
|
|
} catch (e) {}
|
|
}
|
|
editModal.value = {
|
|
visible: true,
|
|
mode: 'edit',
|
|
editId: config.id,
|
|
form: {
|
|
name: config.name || '',
|
|
app_type: config.app_type || 'CHAT',
|
|
provider: config.provider || 'dashscope',
|
|
api_key: '',
|
|
model_name: config.model_name || '',
|
|
temperature: config.temperature,
|
|
max_tokens: config.max_tokens,
|
|
base_url: config.base_url || '',
|
|
embeddingDimensions: extraConfig.dimensions || 1024,
|
|
priority: config.priority || 0,
|
|
is_active: config.is_active || false,
|
|
description: config.description || ''
|
|
}
|
|
}
|
|
showApiKey.value = false
|
|
onProviderChange()
|
|
}
|
|
|
|
function closeEditModal() {
|
|
editModal.value.visible = false
|
|
}
|
|
|
|
// ==================== 保存配置 ====================
|
|
|
|
/**
|
|
* 将前端表单字段名(snake_case)转换为后端 Java 实体字段名(camelCase)
|
|
* 因为 Jackson 默认使用 camelCase 反序列化,前端发送 snake_case 会导致字段无法映射
|
|
*/
|
|
function toCamelCase(form) {
|
|
const data = {
|
|
name: form.name,
|
|
appType: form.app_type,
|
|
provider: form.provider,
|
|
apiKey: form.api_key,
|
|
modelName: form.model_name,
|
|
temperature: form.temperature,
|
|
maxTokens: form.max_tokens,
|
|
baseUrl: form.base_url,
|
|
priority: form.priority,
|
|
isActive: form.is_active,
|
|
description: form.description
|
|
}
|
|
// EMBEDDING 类型:将向量维度写入 extraConfig
|
|
if (form.app_type === 'EMBEDDING') {
|
|
data.extraConfig = {
|
|
dimensions: form.embeddingDimensions || 1024
|
|
}
|
|
}
|
|
return data
|
|
}
|
|
|
|
async function saveConfig() {
|
|
const form = editModal.value.form
|
|
|
|
// 如果选了"自定义输入",取自定义名称
|
|
if (form.model_name === '__custom__') {
|
|
if (!customModelName.value.trim()) {
|
|
toast('请输入自定义模型名称', 'error')
|
|
return
|
|
}
|
|
form.model_name = customModelName.value.trim()
|
|
}
|
|
|
|
if (!form.name || !form.name.trim()) {
|
|
toast('请填写配置名称', 'error')
|
|
return
|
|
}
|
|
if (!form.app_type) {
|
|
toast('请选择应用类型', 'error')
|
|
return
|
|
}
|
|
if (!form.model_name || !form.model_name.trim()) {
|
|
toast('请填写模型名称', 'error')
|
|
return
|
|
}
|
|
if (editModal.value.mode === 'add' && (!form.api_key || !form.api_key.trim())) {
|
|
toast('请填写 API Key', 'error')
|
|
return
|
|
}
|
|
|
|
try {
|
|
let json
|
|
// 转换为 camelCase 后再发送,确保 Jackson 正确反序列化
|
|
const camelData = toCamelCase(form)
|
|
if (editModal.value.mode === 'add') {
|
|
json = await api.createModelConfig(camelData)
|
|
} else {
|
|
// 编辑模式:API Key 留空则不发送(不覆盖原值)
|
|
if (!camelData.apiKey || !camelData.apiKey.trim()) {
|
|
delete camelData.apiKey
|
|
}
|
|
json = await api.updateModelConfig(editModal.value.editId, camelData)
|
|
}
|
|
|
|
if (json.success) {
|
|
toast(editModal.value.mode === 'add' ? '配置创建成功,已切换生效' : '配置更新成功,已切换生效', 'success')
|
|
closeEditModal()
|
|
load(currentPage.value)
|
|
} else {
|
|
toast(json.message || '操作失败', 'error')
|
|
}
|
|
} catch (e) {
|
|
toast('保存失败:' + e.message, 'error')
|
|
}
|
|
}
|
|
|
|
// ==================== 激活配置 ====================
|
|
|
|
async function activate(id) {
|
|
if (!confirm('确定激活此配置?同类型的其他配置将被自动停用,新配置将立即生效。')) return
|
|
try {
|
|
const json = await api.activateModelConfig(id)
|
|
if (json.success) {
|
|
toast('配置已激活,立即生效', 'success')
|
|
load(currentPage.value)
|
|
} else {
|
|
toast(json.message || '激活失败', 'error')
|
|
}
|
|
} catch (e) {
|
|
toast('激活失败:' + e.message, 'error')
|
|
}
|
|
}
|
|
|
|
// ==================== 删除配置 ====================
|
|
|
|
async function remove(id, name) {
|
|
if (!confirm('确定删除配置「' + (name || id) + '」?')) return
|
|
try {
|
|
const json = await api.deleteModelConfig(id)
|
|
if (json.success) {
|
|
toast('配置删除成功', 'success')
|
|
load(currentPage.value)
|
|
} else {
|
|
toast(json.message || '删除失败', 'error')
|
|
}
|
|
} catch (e) {
|
|
toast('删除失败:' + e.message, 'error')
|
|
}
|
|
}
|
|
|
|
// ==================== 工具函数 ====================
|
|
|
|
function getAppTypeLabel(appType) {
|
|
const map = { CHAT: '智能客服对话', EMBEDDING: '文本向量化', RAG_REWRITE: 'RAG查询重写' }
|
|
return map[appType] || appType
|
|
}
|
|
|
|
function getAppTypeBadgeClass(appType) {
|
|
const map = { CHAT: 'badge-get', EMBEDDING: '', RAG_REWRITE: '' }
|
|
return map[appType] || ''
|
|
}
|
|
|
|
function getProviderLabel(provider) {
|
|
const found = PROVIDER_OPTIONS.find(o => o.value === provider)
|
|
return found ? found.label : provider
|
|
}
|
|
|
|
// 初始加载
|
|
onMounted(() => { load() })
|
|
|
|
return {
|
|
configs, currentPage, totalPages, total, filterAppType, showApiKey, editModal,
|
|
appTypeOptions: APP_TYPE_OPTIONS, providerOptions: PROVIDER_OPTIONS,
|
|
providerTip, currentProviderModels, currentProviderDefaultModel, providerBaseUrlPlaceholder, customModelName,
|
|
load, openAddModal, openEditModal, closeEditModal, saveConfig, activate, remove,
|
|
onProviderChange, getAppTypeLabel, getAppTypeBadgeClass, getProviderLabel, formatDate
|
|
}
|
|
}
|
|
}
|