@@ -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 ( i d: number ) {
await api . enableModel ( i d) ;
ElMessage . success ( "模型已启 用") ;
async function setModelAvailability ( row : ModelItem , enable d: number ) {
await api . setModelAvailability ( row . id , enable d) ;
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 = " 16 0" show -overflow -tooltip / >
< el-table-column label = "分流能力 " min -width = " 22 0" >
< 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 = "9 0" / >
< el-table-column label = "运行状态 " width = "15 0" >
< 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 >