|
|
@ -148,6 +148,10 @@ export default { |
|
|
<div style="font-size:22px;font-weight:700;color:#dc2626;">{{ healthSummary.offline }}</div> |
|
|
<div style="font-size:22px;font-weight:700;color:#dc2626;">{{ healthSummary.offline }}</div> |
|
|
<div style="font-size:11px;color:#991b1b;">🔴 离线</div> |
|
|
<div style="font-size:11px;color:#991b1b;">🔴 离线</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div v-if="healthSummary.unknown" style="padding:10px 16px;background:#fffbeb;border:1px solid #fde68a;border-radius:8px;min-width:100px;text-align:center;"> |
|
|
|
|
|
<div style="font-size:22px;font-weight:700;color:#d97706;">{{ healthSummary.unknown }}</div> |
|
|
|
|
|
<div style="font-size:11px;color:#92400e;">🟡 未检测</div> |
|
|
|
|
|
</div> |
|
|
<div style="padding:10px 16px;background:#f9fafb;border:1px solid #e5e7eb;border-radius:8px;min-width:100px;text-align:center;"> |
|
|
<div style="padding:10px 16px;background:#f9fafb;border:1px solid #e5e7eb;border-radius:8px;min-width:100px;text-align:center;"> |
|
|
<div style="font-size:22px;font-weight:700;color:#374151;">{{ healthSummary.total }}</div> |
|
|
<div style="font-size:22px;font-weight:700;color:#374151;">{{ healthSummary.total }}</div> |
|
|
<div style="font-size:11px;color:#6b7280;">📊 总计</div> |
|
|
<div style="font-size:11px;color:#6b7280;">📊 总计</div> |
|
|
@ -186,7 +190,7 @@ export default { |
|
|
<!-- F4: 健康状态指示 --> |
|
|
<!-- F4: 健康状态指示 --> |
|
|
<span v-if="healthMap[item.id]" :title="getHealthTooltip(item.id)" |
|
|
<span v-if="healthMap[item.id]" :title="getHealthTooltip(item.id)" |
|
|
:style="{ color: healthMap[item.id].status === 'ONLINE' ? '#16a34a' : '#dc2626' }"> |
|
|
:style="{ color: healthMap[item.id].status === 'ONLINE' ? '#16a34a' : '#dc2626' }"> |
|
|
{{ healthMap[item.id].status === 'ONLINE' ? '🟢' : '🔴' }} |
|
|
|
|
|
|
|
|
{{ healthMap[item.id].status === 'ONLINE' ? '🟢' : healthMap[item.id].status === 'UNKNOWN' ? '🟡' : '🔴' }} |
|
|
<span v-if="healthMap[item.id].latencyMs" style="font-size:10px;">{{ healthMap[item.id].latencyMs }}ms</span> |
|
|
<span v-if="healthMap[item.id].latencyMs" style="font-size:10px;">{{ healthMap[item.id].latencyMs }}ms</span> |
|
|
</span> |
|
|
</span> |
|
|
<span v-else style="color:#d1d5db;" title="未检测">🟡</span> |
|
|
<span v-else style="color:#d1d5db;" title="未检测">🟡</span> |
|
|
@ -245,8 +249,8 @@ export default { |
|
|
<!-- 健康指示器 --> |
|
|
<!-- 健康指示器 --> |
|
|
<span v-if="c.is_active && healthMap[c.id]" |
|
|
<span v-if="c.is_active && healthMap[c.id]" |
|
|
:title="getHealthTooltip(c.id)" |
|
|
:title="getHealthTooltip(c.id)" |
|
|
:style="{ cursor: 'help', color: healthMap[c.id].status === 'ONLINE' ? '#16a34a' : '#dc2626' }"> |
|
|
|
|
|
{{ healthMap[c.id].status === 'ONLINE' ? '●' : '●' }} |
|
|
|
|
|
|
|
|
:style="{ cursor: 'help', color: healthMap[c.id].status === 'ONLINE' ? '#16a34a' : healthMap[c.id].status === 'UNKNOWN' ? '#d97706' : '#dc2626' }"> |
|
|
|
|
|
{{ healthMap[c.id].status === 'ONLINE' ? '●' : healthMap[c.id].status === 'UNKNOWN' ? '●' : '●' }} |
|
|
<span v-if="healthMap[c.id].latencyMs" style="font-size:10px;">{{ healthMap[c.id].latencyMs }}ms</span> |
|
|
<span v-if="healthMap[c.id].latencyMs" style="font-size:10px;">{{ healthMap[c.id].latencyMs }}ms</span> |
|
|
</span> |
|
|
</span> |
|
|
<span v-else-if="c.is_active" style="color:#d1d5db;cursor:help;" title="未检测">●</span> |
|
|
<span v-else-if="c.is_active" style="color:#d1d5db;cursor:help;" title="未检测">●</span> |
|
|
@ -467,7 +471,7 @@ export default { |
|
|
<!-- F7: 导入弹窗 --> |
|
|
<!-- F7: 导入弹窗 --> |
|
|
<div class="modal-overlay" :class="{ active: showImportDialog }"> |
|
|
<div class="modal-overlay" :class="{ active: showImportDialog }"> |
|
|
<div class="modal-box" style="max-width:600px;"> |
|
|
<div class="modal-box" style="max-width:600px;"> |
|
|
<button class="modal-close" @click="showImportDialog = false">×</button> |
|
|
|
|
|
|
|
|
<button class="modal-close" @click="closeImportDialog">×</button> |
|
|
<h2>📥 导入模型配置</h2> |
|
|
<h2>📥 导入模型配置</h2> |
|
|
<div style="margin:16px 0;"> |
|
|
<div style="margin:16px 0;"> |
|
|
<label style="font-size:13px;font-weight:600;display:block;margin-bottom:8px;">选择 JSON 文件</label> |
|
|
<label style="font-size:13px;font-weight:600;display:block;margin-bottom:8px;">选择 JSON 文件</label> |
|
|
@ -499,7 +503,7 @@ export default { |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div style="display:flex;gap:10px;margin-top:20px;justify-content:flex-end;"> |
|
|
<div style="display:flex;gap:10px;margin-top:20px;justify-content:flex-end;"> |
|
|
<button class="btn btn-outline" @click="showImportDialog = false">关闭</button> |
|
|
|
|
|
|
|
|
<button class="btn btn-outline" @click="closeImportDialog">关闭</button> |
|
|
<button class="btn btn-primary" @click="doImport" :disabled="!importPreview">📥 确认导入</button> |
|
|
<button class="btn btn-primary" @click="doImport" :disabled="!importPreview">📥 确认导入</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
@ -593,8 +597,18 @@ export default { |
|
|
const models = isEmbedding ? (defaults.embeddingModels || []) : (defaults.chatModels || []) |
|
|
const models = isEmbedding ? (defaults.embeddingModels || []) : (defaults.chatModels || []) |
|
|
const defaultModel = isEmbedding ? (defaults.defaultEmbeddingModel || '') : (defaults.defaultChatModel || '') |
|
|
const defaultModel = isEmbedding ? (defaults.defaultEmbeddingModel || '') : (defaults.defaultChatModel || '') |
|
|
|
|
|
|
|
|
if (editModal.value.mode === 'add' && !editModal.value.form.model_name) { |
|
|
|
|
|
editModal.value.form.model_name = defaultModel |
|
|
|
|
|
|
|
|
// 新建模式:始终重置 model_name 为新提供商的默认模型
|
|
|
|
|
|
if (editModal.value.mode === 'add') { |
|
|
|
|
|
editModal.value.form.model_name = defaultModel || '' |
|
|
|
|
|
} else { |
|
|
|
|
|
// 编辑模式:如果当前 model_name 不在新提供商的模型列表中,重置为默认模型
|
|
|
|
|
|
const currentModel = editModal.value.form.model_name |
|
|
|
|
|
if (currentModel && currentModel !== '__custom__' && models.length > 0) { |
|
|
|
|
|
const found = models.find(m => m.name === currentModel) |
|
|
|
|
|
if (!found) { |
|
|
|
|
|
editModal.value.form.model_name = defaultModel || '' |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// F5: 使用对象格式的模型列表
|
|
|
// F5: 使用对象格式的模型列表
|
|
|
@ -671,7 +685,7 @@ export default { |
|
|
function getHealthTooltip(configId) { |
|
|
function getHealthTooltip(configId) { |
|
|
const h = healthMap.value[configId] |
|
|
const h = healthMap.value[configId] |
|
|
if (!h) return '未检测' |
|
|
if (!h) return '未检测' |
|
|
let tip = h.status === 'ONLINE' ? '在线' : '离线' |
|
|
|
|
|
|
|
|
let tip = h.status === 'ONLINE' ? '在线' : h.status === 'UNKNOWN' ? '未支持自动检测' : '离线' |
|
|
if (h.latencyMs) tip += ` · 延迟 ${h.latencyMs}ms` |
|
|
if (h.latencyMs) tip += ` · 延迟 ${h.latencyMs}ms` |
|
|
if (h.lastCheckTime) tip += ` · 最后检测 ${new Date(h.lastCheckTime).toLocaleTimeString()}` |
|
|
if (h.lastCheckTime) tip += ` · 最后检测 ${new Date(h.lastCheckTime).toLocaleTimeString()}` |
|
|
if (h.errorMessage) tip += ` · ${h.errorMessage}` |
|
|
if (h.errorMessage) tip += ` · ${h.errorMessage}` |
|
|
@ -793,7 +807,7 @@ export default { |
|
|
isActive: form.is_active, |
|
|
isActive: form.is_active, |
|
|
description: form.description |
|
|
description: form.description |
|
|
} |
|
|
} |
|
|
// 构建 extraConfig
|
|
|
|
|
|
|
|
|
// 构建 extraConfig(始终发送,以便后端清空已废弃的高级参数)
|
|
|
const extraConfig = {} |
|
|
const extraConfig = {} |
|
|
if (form.app_type === 'EMBEDDING') { |
|
|
if (form.app_type === 'EMBEDDING') { |
|
|
extraConfig.dimensions = form.embeddingDimensions || 1024 |
|
|
extraConfig.dimensions = form.embeddingDimensions || 1024 |
|
|
@ -805,9 +819,7 @@ export default { |
|
|
if (form.presencePenalty != null) extraConfig.presencePenalty = form.presencePenalty |
|
|
if (form.presencePenalty != null) extraConfig.presencePenalty = form.presencePenalty |
|
|
if (form.stopSequences && form.stopSequences.trim()) extraConfig.stopSequences = form.stopSequences.trim() |
|
|
if (form.stopSequences && form.stopSequences.trim()) extraConfig.stopSequences = form.stopSequences.trim() |
|
|
|
|
|
|
|
|
if (Object.keys(extraConfig).length > 0) { |
|
|
|
|
|
data.extraConfig = extraConfig |
|
|
data.extraConfig = extraConfig |
|
|
} |
|
|
|
|
|
return data |
|
|
return data |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -973,7 +985,7 @@ export default { |
|
|
|
|
|
|
|
|
async function exportConfigs() { |
|
|
async function exportConfigs() { |
|
|
try { |
|
|
try { |
|
|
const json = await api.exportModelConfigs(true) |
|
|
|
|
|
|
|
|
const json = await api.exportModelConfigs() |
|
|
if (json.success) { |
|
|
if (json.success) { |
|
|
const blob = new Blob([JSON.stringify(json.data, null, 2)], { type: 'application/json' }) |
|
|
const blob = new Blob([JSON.stringify(json.data, null, 2)], { type: 'application/json' }) |
|
|
const url = URL.createObjectURL(blob) |
|
|
const url = URL.createObjectURL(blob) |
|
|
@ -1029,6 +1041,12 @@ export default { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function closeImportDialog() { |
|
|
|
|
|
showImportDialog.value = false |
|
|
|
|
|
importPreview.value = null |
|
|
|
|
|
importResult.value = null |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// ==================== 工具函数 ====================
|
|
|
// ==================== 工具函数 ====================
|
|
|
|
|
|
|
|
|
function getAppTypeLabel(appType) { |
|
|
function getAppTypeLabel(appType) { |
|
|
@ -1076,7 +1094,7 @@ export default { |
|
|
fallbackChains, onDragStart, onDrop, deactivateConfig, |
|
|
fallbackChains, onDragStart, onDrop, deactivateConfig, |
|
|
// F7
|
|
|
// F7
|
|
|
showImportDialog, importPreview, importConflict, importResult, |
|
|
showImportDialog, importPreview, importConflict, importResult, |
|
|
onImportFileSelect, doImport, exportConfigs, |
|
|
|
|
|
|
|
|
onImportFileSelect, doImport, closeImportDialog, exportConfigs, |
|
|
// 基础
|
|
|
// 基础
|
|
|
load, openAddModal, openEditModal, closeEditModal, saveConfig, activate, remove, |
|
|
load, openAddModal, openEditModal, closeEditModal, saveConfig, activate, remove, |
|
|
onProviderChange, getAppTypeLabel, getAppTypeBadgeClass, getProviderLabel, formatDate |
|
|
onProviderChange, getAppTypeLabel, getAppTypeBadgeClass, getProviderLabel, formatDate |
|
|
|