fix: complete admin knowledge audit improvements
This commit is contained in:
@@ -4,6 +4,7 @@ import { computed, onMounted, reactive, ref } from "vue";
|
||||
|
||||
import AdminLoginView from "./components/AdminLoginView.vue";
|
||||
import KnowledgeManagementView from "./components/KnowledgeManagementView.vue";
|
||||
import TableRowActions from "./components/TableRowActions.vue";
|
||||
import { systemSettingDefinitions, systemSettingSections, type SystemSettingValue } from "./config/systemSettings";
|
||||
import { api, clearToken, getToken, saveToken } from "./services/api";
|
||||
import type {
|
||||
@@ -27,6 +28,11 @@ const activeMenu = ref("dashboard");
|
||||
const loading = ref(false);
|
||||
|
||||
const stats = ref<DashboardStats | null>(null);
|
||||
const storageStats = ref<Record<string, any> | null>(null);
|
||||
const storageLoading = ref(false);
|
||||
const cleanupBefore = ref("");
|
||||
const retentionDays = ref<number | null>(90);
|
||||
const retentionState = ref<{ lastRunAt: string | null; lastResult: string | null } | null>(null);
|
||||
const dashboardFilters = reactive({
|
||||
start: "",
|
||||
end: "",
|
||||
@@ -249,8 +255,15 @@ async function previewKnowledge(knowledgeId: number) {
|
||||
|
||||
async function loadDashboard() {
|
||||
stats.value = await api.dashboard(dashboardFilters.start, dashboardFilters.end);
|
||||
storageLoading.value = true;
|
||||
api.storageStats().then((value) => { storageStats.value = value; }).catch(() => { storageStats.value = null; }).finally(() => { storageLoading.value = false; });
|
||||
}
|
||||
|
||||
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]}`; }
|
||||
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(); }
|
||||
|
||||
async function loadCurrentMenu() {
|
||||
loading.value = true;
|
||||
try {
|
||||
@@ -262,6 +275,7 @@ async function loadCurrentMenu() {
|
||||
promptContent.value = prompt.promptContent;
|
||||
models.value = modelRows;
|
||||
knowledge.value = knowledgeRows;
|
||||
agentForm.knowledgeIds = knowledgeRows.filter((item) => item.status === 1 && item.lifecycleStatus === "active").map((item) => item.id);
|
||||
if (!agentForm.modelId && modelRows.length) {
|
||||
agentForm.modelId = modelRows.find((model) => model.enabled === 1)?.id ?? modelRows[0].id;
|
||||
}
|
||||
@@ -276,7 +290,7 @@ async function loadCurrentMenu() {
|
||||
aiLogs.value = await api.aiLogs();
|
||||
operationLogs.value = await api.operationLogs();
|
||||
}
|
||||
if (activeMenu.value === "retrievals") retrievalLogs.value = await api.retrievalLogs();
|
||||
if (activeMenu.value === "retrievals") { const [logs, retention] = await Promise.all([api.retrievalLogs(), api.retrievalRetention()]); retrievalLogs.value = logs; retentionDays.value = retention.retentionDays; retentionState.value = retention; }
|
||||
if (activeMenu.value === "attention") attentionRecords.value = await api.attentionList();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
@@ -937,6 +951,7 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
<el-table :data="users" stripe>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="phone" label="手机号" width="150" />
|
||||
@@ -986,8 +1001,8 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="调试知识库">
|
||||
<el-select v-model="agentForm.knowledgeIds" multiple collapse-tags placeholder="默认使用全部启用知识库">
|
||||
<el-option v-for="item in knowledge" :key="item.id" :label="item.name" :value="item.id" />
|
||||
<el-select v-model="agentForm.knowledgeIds" class="debug-knowledge-select" multiple filterable collapse-tags collapse-tags-tooltip :max-collapse-tags="2" popper-class="debug-knowledge-popper" placeholder="默认使用全部已开放知识库">
|
||||
<el-option v-for="item in knowledge" :key="item.id" :label="`${item.name}${item.status === 0 ? ' · 已关闭(仅本次预览)' : ''}`" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="Temperature">
|
||||
@@ -1067,10 +1082,12 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
</div>
|
||||
<div class="agent-trace-list">
|
||||
<el-empty v-if="agentDebugTrace.length === 0" description="发送问题后查看目录、检索与章节回读" :image-size="64" />
|
||||
<article v-for="(step, index) in agentDebugTrace" :key="index">
|
||||
<strong>{{ index + 1 }}. {{ step.tool }}</strong>
|
||||
<pre>{{ JSON.stringify(step, null, 2) }}</pre>
|
||||
</article>
|
||||
<details v-for="(step, index) in agentDebugTrace" :key="index" class="tool-trace-node">
|
||||
<summary><strong>{{ index + 1 }}. {{ step.tool }}</strong><el-tag size="small" :type="step.status === 'failed' ? 'danger' : 'success'">{{ step.status || '完成' }}</el-tag><span>{{ step.durationMs ?? 0 }} ms</span></summary>
|
||||
<h5>请求</h5><pre>{{ JSON.stringify(step.request ?? {}, null, 2) }}</pre>
|
||||
<h5>响应</h5><pre>{{ step.response == null ? '返回为空' : JSON.stringify(step.response, null, 2) }}</pre>
|
||||
<el-alert v-if="step.error" :title="step.error" type="error" :closable="false" />
|
||||
</details>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
@@ -1180,12 +1197,9 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
<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="210" fixed="right">
|
||||
<el-table-column label="操作" width="190" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="editModel(row)">编辑</el-button>
|
||||
<el-button size="small" @click="testModel(row.id)">测试</el-button>
|
||||
<el-button size="small" type="primary" @click="enableModel(row.id)">启用</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteModel(row.id)">删除</el-button>
|
||||
<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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -1232,7 +1246,8 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
</template>
|
||||
|
||||
<template v-if="activeMenu === 'retrievals'">
|
||||
<div class="page-head"><h2>检索日志</h2><p>查看每条回答使用的知识目录、Agent 决策、搜索候选、重排和章节回读。</p></div>
|
||||
<div class="page-head inline"><div><h2>检索日志</h2><p>查看每条回答使用的知识目录、Agent 决策、搜索候选、重排和章节回读。</p></div><div class="cleanup-controls"><el-date-picker v-model="cleanupBefore" value-format="YYYY-MM-DD" type="date" placeholder="清理此日期前" /><el-button type="danger" plain @click="cleanupRetrievalLogs">清理日志</el-button></div></div>
|
||||
<section class="retention-panel"><div><strong>定期清理</strong><span>每日自动分批删除过期检索日志和关联候选明细</span></div><el-select v-model="retentionDays" placeholder="保留时间"><el-option label="永久保留" :value="null" /><el-option label="30天" :value="30" /><el-option label="90天" :value="90" /><el-option label="180天" :value="180" /></el-select><el-button type="primary" @click="saveRetention">保存规则</el-button><small>最近执行:{{ retentionState?.lastRunAt || '尚未执行' }}</small></section>
|
||||
<el-table :data="retrievalLogs" stripe>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="sessionId" label="会话" width="90" />
|
||||
|
||||
Reference in New Issue
Block a user