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" />
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
|
||||
import { api } from "../services/api";
|
||||
import TableRowActions from "./TableRowActions.vue";
|
||||
import type {
|
||||
KnowledgeDetail,
|
||||
KnowledgeItem,
|
||||
@@ -29,6 +30,9 @@ const editOpen = ref(false);
|
||||
const editSaving = ref(false);
|
||||
const batchOpen = ref(false);
|
||||
const batchSaving = ref(false);
|
||||
const resolvingNode = ref(false);
|
||||
const nodeResolved = ref(false);
|
||||
const sourceTitle = ref("");
|
||||
const filter = reactive({ keyword: "", type: "", open: "", source: "", pending: "" });
|
||||
const form = reactive({
|
||||
name: "",
|
||||
@@ -159,13 +163,14 @@ async function restore(item: KnowledgeItem) {
|
||||
}
|
||||
|
||||
async function createKnowledge() {
|
||||
if (!form.name || !form.feishuSpaceId || !form.feishuNodeId) {
|
||||
ElMessage.warning("请填写名称、SpaceID 和 NodeID");
|
||||
if (!nodeResolved.value || !form.name || !form.feishuSpaceId || !form.feishuNodeId) {
|
||||
ElMessage.warning("请先通过 Node ID 获取并确认知识库信息");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await api.createKnowledge({ ...form });
|
||||
Object.assign(form, { name: "", feishuSpaceId: "", feishuNodeId: "", knowledgeType: "general", reviewMode: "manual", status: 0, remark: "" });
|
||||
nodeResolved.value = false; sourceTitle.value = "";
|
||||
ElMessage.success("知识库已新增,请进行首次同步");
|
||||
await load();
|
||||
} catch (error) {
|
||||
@@ -173,6 +178,21 @@ async function createKnowledge() {
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveNode() {
|
||||
const nodeId = form.feishuNodeId.trim();
|
||||
if (!nodeId) return ElMessage.warning("请输入 Node ID");
|
||||
resolvingNode.value = true;
|
||||
try {
|
||||
const info = await api.resolveKnowledgeNode(nodeId);
|
||||
Object.assign(form, { feishuNodeId: info.nodeId, feishuSpaceId: info.spaceId, name: info.name, remark: info.remark || "" });
|
||||
sourceTitle.value = info.sourceTitle; nodeResolved.value = true;
|
||||
ElMessage.success("已获取飞书知识库信息");
|
||||
} catch (error) {
|
||||
nodeResolved.value = false; sourceTitle.value = "";
|
||||
ElMessage.error(errorMessage(error, "Node ID 信息获取失败"));
|
||||
} finally { resolvingNode.value = false; }
|
||||
}
|
||||
|
||||
async function openDetail(item: KnowledgeItem) {
|
||||
detailOpen.value = true;
|
||||
activeTab.value = "overview";
|
||||
@@ -387,10 +407,15 @@ async function confirmAction(
|
||||
</section>
|
||||
|
||||
<section class="knowledge-work-panel">
|
||||
<h3>新增知识库</h3>
|
||||
<div class="knowledge-create-grid">
|
||||
<el-input v-model="form.feishuSpaceId" placeholder="SpaceID" />
|
||||
<el-input v-model="form.feishuNodeId" placeholder="NodeID" />
|
||||
<h3>通过 Node ID 快速新增</h3>
|
||||
<div class="node-resolve-row">
|
||||
<el-input v-model="form.feishuNodeId" placeholder="请输入飞书 Node ID" :disabled="resolvingNode" @input="nodeResolved = false" />
|
||||
<el-button type="primary" :loading="resolvingNode" @click="resolveNode">获取知识库信息</el-button>
|
||||
</div>
|
||||
<el-alert v-if="nodeResolved" :title="`已获取:${sourceTitle}`" type="success" :closable="false" show-icon />
|
||||
<div v-if="nodeResolved" class="knowledge-create-grid">
|
||||
<el-input v-model="form.feishuSpaceId" placeholder="Space ID" disabled />
|
||||
<el-input :model-value="sourceTitle" placeholder="飞书原标题" disabled />
|
||||
<el-input v-model="form.name" placeholder="后台显示名称" />
|
||||
<el-select v-model="form.knowledgeType">
|
||||
<el-option label="课程知识库" value="course" /><el-option label="答疑知识库" value="qa" /><el-option label="通用知识库" value="general" />
|
||||
@@ -398,6 +423,7 @@ async function confirmAction(
|
||||
<el-select v-model="form.reviewMode">
|
||||
<el-option label="必须人工审核" value="manual" /><el-option label="允许自动发布" value="automatic" />
|
||||
</el-select>
|
||||
<el-input v-model="form.remark" placeholder="备注(可修改)" />
|
||||
<el-button type="primary" @click="createKnowledge">保存并准备同步</el-button>
|
||||
</div>
|
||||
</section>
|
||||
@@ -422,7 +448,7 @@ async function confirmAction(
|
||||
<el-table-column label="版本" width="130"><template #default="{ row }"><span>正式 {{ row.currentVersionId ? `#${row.currentVersionId}` : '无' }}</span><small class="table-subline">待发布 {{ row.pendingVersionId ? `#${row.pendingVersionId}` : '无' }}</small></template></el-table-column>
|
||||
<el-table-column label="状态" width="210"><template #default="{ row }"><div class="knowledge-status-cell"><el-switch v-model="row.status" :active-value="1" :inactive-value="0" active-text="开放" inactive-text="关闭" inline-prompt :loading="operatingId === row.id" :disabled="row.lifecycleStatus === 'archived'" :before-change="() => toggleOpen(row)" /><el-tag :type="statusType(row.sourceStatus)">{{ row.sourceStatus === 'normal' ? '源正常' : '源异常' }}</el-tag></div></template></el-table-column>
|
||||
<el-table-column prop="lastSyncedAt" label="最近同步" width="180" />
|
||||
<el-table-column label="操作" width="220" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="openDetail(row)">详情</el-button><el-button link type="primary" :disabled="row.lifecycleStatus === 'archived'" :loading="operatingId === row.id" @click="sync(row, 'review')">同步</el-button><el-dropdown trigger="click" :teleported="true" @command="handleMore($event, row)"><el-button link type="primary">更多</el-button><template #dropdown><el-dropdown-menu><el-dropdown-item command="detail">查看详情</el-dropdown-item><el-dropdown-item command="preview">进入 Agent 预览</el-dropdown-item><el-dropdown-item command="versions">查看版本</el-dropdown-item><el-dropdown-item v-if="row.lifecycleStatus === 'active'" command="archive" divided>归档</el-dropdown-item><el-dropdown-item v-else command="restore" divided>恢复</el-dropdown-item><el-dropdown-item command="delete" :disabled="row.lifecycleStatus !== 'archived'" divided>删除</el-dropdown-item></el-dropdown-menu></template></el-dropdown></template></el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right"><template #default="{ row }"><TableRowActions><el-button link type="primary" @click="openDetail(row)">详情</el-button><el-button link type="primary" :disabled="row.lifecycleStatus === 'archived'" :loading="operatingId === row.id" @click="sync(row, 'review')">同步</el-button><el-dropdown trigger="click" :teleported="true" @command="handleMore($event, row)"><el-button link type="primary">更多</el-button><template #dropdown><el-dropdown-menu><el-dropdown-item command="detail">查看详情</el-dropdown-item><el-dropdown-item command="preview">进入 Agent 预览</el-dropdown-item><el-dropdown-item command="versions">查看版本</el-dropdown-item><el-dropdown-item v-if="row.lifecycleStatus === 'active'" command="archive" divided>归档</el-dropdown-item><el-dropdown-item v-else command="restore" divided>恢复</el-dropdown-item><el-dropdown-item command="delete" :disabled="row.lifecycleStatus !== 'archived'" divided>删除</el-dropdown-item></el-dropdown-menu></template></el-dropdown></TableRowActions></template></el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-drawer v-model="detailOpen" size="76%" title="知识库详情" destroy-on-close>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div class="table-row-actions"><slot /></div>
|
||||
</template>
|
||||
@@ -116,6 +116,7 @@ export const api = {
|
||||
request<AdminUser>(`/admin/user/${id}`, { method: "PUT", body: JSON.stringify(payload) }),
|
||||
deleteUser: (id: number) => request<null>(`/admin/user/${id}`, { method: "DELETE" }),
|
||||
knowledge: () => request<KnowledgeItem[]>("/admin/knowledge/list"),
|
||||
resolveKnowledgeNode: (nodeId: string) => request<{ nodeId: string; spaceId: string; sourceTitle: string; name: string; remark: string }>("/admin/knowledge/resolve-node", { method: "POST", body: JSON.stringify({ nodeId }) }),
|
||||
createKnowledge: (payload: Record<string, unknown>) =>
|
||||
request<KnowledgeItem>("/admin/knowledge", { method: "POST", body: JSON.stringify(payload) }),
|
||||
updateKnowledge: (id: number, payload: Record<string, unknown>) =>
|
||||
@@ -182,6 +183,11 @@ export const api = {
|
||||
aiLogDetail: (id: number) => request<AiLogRecord>(`/admin/ai-log/${id}`),
|
||||
operationLogs: () => request<Record<string, unknown>[]>("/admin/log/list"),
|
||||
retrievalLogs: () => request<RetrievalLogItem[]>("/admin/retrieval-log/list"),
|
||||
estimateRetrievalCleanup: (before: string) => request<{ before: string; estimatedCount: number }>("/admin/retrieval-log/cleanup/estimate", { method: "POST", body: JSON.stringify({ before }) }),
|
||||
cleanupRetrievalLogs: (before: string) => request<{ before: string; deleted: number }>("/admin/retrieval-log/cleanup", { method: "POST", body: JSON.stringify({ before }) }),
|
||||
retrievalRetention: () => request<{ enabled: boolean; retentionDays: number | null; lastRunAt: string | null; lastResult: string | null }>("/admin/retrieval-log/retention"),
|
||||
saveRetrievalRetention: (retentionDays: number | null) => request<{ enabled: boolean; retentionDays: number | null }>("/admin/retrieval-log/retention", { method: "PUT", body: JSON.stringify({ retentionDays }) }),
|
||||
storageStats: (refresh = false) => request<Record<string, any>>(`/admin/dashboard/storage${refresh ? "?refresh=true" : ""}`),
|
||||
retrievalLogDetail: (id: number) => request<Record<string, unknown>>(`/admin/retrieval-log/${id}`),
|
||||
attentionList: () => request<AttentionItem[]>("/admin/attention/list"),
|
||||
updateAttention: (id: number, status: string, note?: string) =>
|
||||
|
||||
@@ -275,6 +275,16 @@ textarea {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
.storage-panel { margin-top: 18px; padding: 18px; border: 1px solid #dfe8e5; border-radius: 8px; background: #fff; }
|
||||
.storage-panel-head { display: flex; justify-content: space-between; align-items: start; gap: 16px; }
|
||||
.storage-panel-head h3, .storage-panel-head p { margin: 0 0 6px; }
|
||||
.storage-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin: 14px 0; }
|
||||
.storage-grid > div { padding: 14px; background: #f6f8f7; border-radius: 8px; }
|
||||
.storage-grid span, .storage-grid strong { display: block; }
|
||||
.storage-grid strong { margin-top: 8px; font-size: 20px; }
|
||||
.cleanup-controls { display: flex; align-items: center; gap: 8px; white-space: nowrap; }
|
||||
.retention-panel { display: grid; grid-template-columns: minmax(260px, 1fr) 180px auto auto; align-items: center; gap: 12px; padding: 14px 16px; margin-bottom: 14px; border: 1px solid #dfe8e5; border-radius: 8px; background: #fff; }
|
||||
.retention-panel div span { display: block; margin-top: 4px; color: #667a73; font-size: 12px; }
|
||||
|
||||
.stat {
|
||||
padding: 18px;
|
||||
@@ -393,6 +403,9 @@ textarea {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.node-resolve-row { display: grid; grid-template-columns: minmax(280px, 1fr) auto; gap: 12px; margin-bottom: 12px; }
|
||||
.node-resolve-row .el-button { min-width: 170px; }
|
||||
|
||||
.knowledge-filters {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(240px, 1.6fr) repeat(4, minmax(130px, 0.8fr));
|
||||
@@ -699,6 +712,19 @@ textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.debug-knowledge-select { width: 100%; min-width: 0; }
|
||||
.debug-knowledge-select .el-select__wrapper { min-height: 40px; max-height: 76px; overflow-y: auto; }
|
||||
.debug-knowledge-popper { max-width: min(620px, 90vw); z-index: 4000 !important; }
|
||||
.debug-knowledge-popper .el-select-dropdown__item { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.table-row-actions { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; white-space: nowrap; }
|
||||
.table-row-actions .el-button + .el-button { margin-left: 0; }
|
||||
.table-row-actions .el-button { min-width: 48px; height: 32px; white-space: nowrap; }
|
||||
.tool-trace-node { border: 1px solid #dfe8e5; border-radius: 8px; background: #fff; overflow: hidden; }
|
||||
.tool-trace-node summary { cursor: pointer; display: flex; align-items: center; gap: 10px; padding: 12px; list-style: none; }
|
||||
.tool-trace-node summary span { margin-left: auto; color: #667a73; font-size: 12px; }
|
||||
.tool-trace-node h5 { margin: 10px 12px 4px; }
|
||||
.tool-trace-node pre { max-height: 320px; overflow: auto; }
|
||||
|
||||
.agent-preview-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user