feat: route background ai workloads by model
This commit is contained in:
@@ -305,7 +305,15 @@ async function loadDashboard() {
|
||||
|
||||
async function refreshStorage() { storageLoading.value = true; try { storageStats.value = await api.storageStats(true); ElMessage.success("存储统计已刷新"); } finally { storageLoading.value = false; } }
|
||||
function formatBytes(value: unknown) { if (typeof value !== "number") return "无法统计"; const units = ["B", "KB", "MB", "GB", "TB"]; let size = value, i = 0; while (size >= 1024 && i < units.length - 1) { size /= 1024; i += 1; } return `${size.toFixed(i ? 2 : 0)} ${units[i]}`; }
|
||||
function formatMoney(value?: number | null, currency = "CNY") { if (typeof value !== "number") return "-"; return `${currency || "CNY"} ${value.toFixed(6)}`; }
|
||||
function formatMoney(value?: number | null, currency = "CNY") { if (typeof value !== "number") return "-"; if (currency === "MIXED") return "多币种,见明细"; return `${currency || "CNY"} ${value.toFixed(6)}`; }
|
||||
function modelSceneLabel(scene: string) {
|
||||
return ({
|
||||
background_report: "周期报告",
|
||||
background_summary: "摘要沉淀",
|
||||
knowledge_grounded: "知识问答",
|
||||
general_chat: "通用对话",
|
||||
} as Record<string, string>)[scene] || scene || "未分类";
|
||||
}
|
||||
async function cleanupRetrievalLogs() { if (!cleanupBefore.value) return ElMessage.warning("请选择清理日期"); const estimate = await api.estimateRetrievalCleanup(cleanupBefore.value); await ElMessageBox.confirm(`预计清理 ${estimate.estimatedCount} 条检索日志及关联明细,操作日志不会被删除。`, "确认清理", { type: "warning" }); const result = await api.cleanupRetrievalLogs(cleanupBefore.value); ElMessage.success(`已清理 ${result.deleted} 条日志`); await loadCurrentMenu(); }
|
||||
async function saveRetention() { await api.saveRetrievalRetention(retentionDays.value); ElMessage.success(retentionDays.value == null ? "已设为永久保留" : `已设为保留 ${retentionDays.value} 天`); await loadCurrentMenu(); }
|
||||
|
||||
@@ -711,12 +719,25 @@ function applyQuickProvider(provider: string) {
|
||||
quickModelForm.modelName = target.models[0]?.value ?? "";
|
||||
}
|
||||
|
||||
async function enableModel(id: number) {
|
||||
await api.enableModel(id);
|
||||
ElMessage.success("模型已启用");
|
||||
async function setModelAvailability(row: ModelItem, enabled: number) {
|
||||
await api.setModelAvailability(row.id, enabled);
|
||||
ElMessage.success(enabled === 1 ? "模型已加入可用池" : "模型已停用");
|
||||
await loadCurrentMenu();
|
||||
}
|
||||
|
||||
async function setDefaultModel(row: ModelItem) {
|
||||
await api.setDefaultModel(row.id);
|
||||
ElMessage.success("默认主模型已更新");
|
||||
await loadCurrentMenu();
|
||||
}
|
||||
|
||||
async function handleModelCommand(command: string, row: ModelItem) {
|
||||
if (command === "enable") return setModelAvailability(row, 1);
|
||||
if (command === "disable") return setModelAvailability(row, 0);
|
||||
if (command === "default") return setDefaultModel(row);
|
||||
return deleteModel(row.id);
|
||||
}
|
||||
|
||||
async function deleteModel(id: number) {
|
||||
try {
|
||||
await ElMessageBox.confirm("确认删除该模型?删除后不可恢复。", "删除模型", {
|
||||
@@ -1108,6 +1129,23 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
<div class="stat"><span>总 Token</span><strong>{{ stats?.totalToken ?? 0 }}</strong></div>
|
||||
<div class="stat"><span>估算成本</span><strong>{{ formatMoney(stats?.estimatedCost, stats?.costCurrency || 'CNY') }}</strong></div>
|
||||
</div>
|
||||
<section class="cost-breakdown-panel">
|
||||
<div class="storage-panel-head">
|
||||
<div><h3>模型使用与成本</h3><p>按实际调用场景和模型统计;报告、摘要分流是否生效可在这里核对。</p></div>
|
||||
</div>
|
||||
<el-table :data="stats?.costBreakdown || []" empty-text="当前日期范围内暂无模型调用">
|
||||
<el-table-column label="调用场景" min-width="130">
|
||||
<template #default="{ row }">{{ modelSceneLabel(row.scene) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="modelName" label="实际模型" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="requestCount" label="请求数" width="100" />
|
||||
<el-table-column prop="inputToken" label="输入 Token" width="130" />
|
||||
<el-table-column prop="outputToken" label="输出 Token" width="130" />
|
||||
<el-table-column label="估算成本" width="170">
|
||||
<template #default="{ row }">{{ formatMoney(row.estimatedCost, row.currency || 'CNY') }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
<section class="storage-panel" v-loading="storageLoading"><div class="storage-panel-head"><div><h3>项目存储占用</h3><p>数据库、Redis 与附件分别统计;无权限或不支持时显示“无法统计”。</p></div><el-button :loading="storageLoading" @click="refreshStorage">手动刷新</el-button></div><div class="storage-grid"><div><span>项目总量</span><strong>{{ formatBytes(storageStats?.totalBytes) }}</strong></div><div><span>数据库</span><strong>{{ formatBytes(storageStats?.detail?.database?.bytes) }}</strong></div><div><span>Redis</span><strong>{{ formatBytes(storageStats?.detail?.redis?.bytes) }}</strong></div><div><span>附件/文件</span><strong>{{ formatBytes(storageStats?.detail?.files?.bytes) }}</strong></div><div><span>近7天增长</span><strong>{{ formatBytes(storageStats?.growth7DaysBytes) }}</strong></div><div><span>近30天增长</span><strong>{{ formatBytes(storageStats?.growth30DaysBytes) }}</strong></div></div><small>最后统计:{{ storageStats?.createdAt || '尚未统计' }}</small></section>
|
||||
</template>
|
||||
|
||||
@@ -1322,7 +1360,7 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
|
||||
<template v-if="activeMenu === 'models'">
|
||||
<div class="page-head inline">
|
||||
<div><h2>模型管理</h2><p>DeepSeek、MiniMax 可快速添加;其他供应商按 OpenAI 兼容或 Anthropic Messages 协议添加。</p></div>
|
||||
<div><h2>模型管理</h2><p>可同时启用多个模型,但只保留一个默认主模型;正式聊天始终使用主模型,报告和摘要按能力标签分流并自动回退主模型。</p></div>
|
||||
<el-button @click="resetModelForm">清空表单</el-button>
|
||||
</div>
|
||||
|
||||
@@ -1444,13 +1482,27 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
<el-table-column label="估算单价" width="180">
|
||||
<template #default="{ row }">{{ row.currency || 'CNY' }} {{ row.inputPricePer1k ?? '-' }}/{{ row.outputPricePer1k ?? '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="usageScenarios" label="适用场景" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="分流能力" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<div class="model-capability-tags">
|
||||
<el-tag v-if="row.allowDeepChat === 1" size="small">深度对话</el-tag>
|
||||
<el-tag v-if="row.allowFixedInfo === 1" size="small">固定信息</el-tag>
|
||||
<el-tag v-if="row.allowSummary === 1" size="small" type="success">摘要</el-tag>
|
||||
<el-tag v-if="row.allowReport === 1" size="small" type="warning">报告</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="baseUrl" label="Base URL" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="authType" label="鉴权" width="110" />
|
||||
<el-table-column prop="enabled" label="启用" width="90" />
|
||||
<el-table-column label="运行状态" width="150">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.enabled === 1 ? 'success' : 'info'">{{ row.enabled === 1 ? '可用' : '停用' }}</el-tag>
|
||||
<el-tag v-if="row.isDefault === 1" class="model-default-tag">主模型</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="190" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<TableRowActions><el-button size="small" @click="editModel(row)">编辑</el-button><el-button size="small" @click="testModel(row.id)">测试</el-button><el-dropdown trigger="click" teleported @command="(command: string) => command === 'enable' ? enableModel(row.id) : deleteModel(row.id)"><el-button size="small">更多</el-button><template #dropdown><el-dropdown-menu><el-dropdown-item command="enable">启用</el-dropdown-item><el-dropdown-item command="delete" divided>删除</el-dropdown-item></el-dropdown-menu></template></el-dropdown></TableRowActions>
|
||||
<TableRowActions><el-button size="small" @click="editModel(row)">编辑</el-button><el-button size="small" @click="testModel(row.id)">测试</el-button><el-dropdown trigger="click" teleported @command="(command: string) => handleModelCommand(command, row)"><el-button size="small">更多</el-button><template #dropdown><el-dropdown-menu><el-dropdown-item v-if="row.enabled !== 1" command="enable">加入可用池</el-dropdown-item><el-dropdown-item v-if="row.enabled === 1 && row.isDefault !== 1" command="default">设为主模型</el-dropdown-item><el-dropdown-item v-if="row.enabled === 1 && row.isDefault !== 1" command="disable">停用</el-dropdown-item><el-dropdown-item v-if="row.isDefault !== 1" command="delete" divided>删除</el-dropdown-item></el-dropdown-menu></template></el-dropdown></TableRowActions>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
Reference in New Issue
Block a user