feat: 完善知识库版本管理与检索审计后台
This commit is contained in:
@@ -3,6 +3,7 @@ import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
|
||||
import AdminLoginView from "./components/AdminLoginView.vue";
|
||||
import KnowledgeManagementView from "./components/KnowledgeManagementView.vue";
|
||||
import { systemSettingDefinitions, systemSettingSections, type SystemSettingValue } from "./config/systemSettings";
|
||||
import { api, clearToken, getToken, saveToken } from "./services/api";
|
||||
import type {
|
||||
@@ -14,6 +15,8 @@ import type {
|
||||
ChatRecord,
|
||||
DashboardStats,
|
||||
KnowledgeItem,
|
||||
RetrievalLogItem,
|
||||
AttentionItem,
|
||||
ModelItem,
|
||||
SystemConfigItem,
|
||||
UserImportResult,
|
||||
@@ -36,6 +39,10 @@ const configs = ref<SystemConfigItem[]>([]);
|
||||
const chats = ref<ChatRecord[]>([]);
|
||||
const aiLogs = ref<AiLogRecord[]>([]);
|
||||
const operationLogs = ref<Record<string, unknown>[]>([]);
|
||||
const retrievalLogs = ref<RetrievalLogItem[]>([]);
|
||||
const attentionRecords = ref<AttentionItem[]>([]);
|
||||
const selectedRetrievalLog = ref<Record<string, unknown> | null>(null);
|
||||
const retrievalDetailOpen = ref(false);
|
||||
const promptContent = ref("");
|
||||
const chatDetail = ref<ChatDetail | null>(null);
|
||||
const chatDetailOpen = ref(false);
|
||||
@@ -47,6 +54,7 @@ const editingKnowledgeId = ref<number | null>(null);
|
||||
const batchNodeToken = ref("");
|
||||
const batchImporting = ref(false);
|
||||
const agentDebugging = ref(false);
|
||||
const agentDebugTrace = ref<Record<string, unknown>[]>([]);
|
||||
const agentPreviewMessages = ref<{ role: "user" | "assistant" | "system"; content: string }[]>([
|
||||
{ role: "assistant", content: "选择模型和知识库后,可以在这里调试 Agent 的真实问答效果。" },
|
||||
]);
|
||||
@@ -263,6 +271,8 @@ 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 === "attention") attentionRecords.value = await api.attentionList();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -711,6 +721,7 @@ async function debugAgent() {
|
||||
return;
|
||||
}
|
||||
agentPreviewMessages.value.push({ role: "assistant", content: result.answer });
|
||||
agentDebugTrace.value = result.retrievalTrace || [];
|
||||
ElMessage.success(result.message);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Agent 调试失败";
|
||||
@@ -725,6 +736,35 @@ function clearAgentPreview() {
|
||||
agentPreviewMessages.value = [
|
||||
{ role: "assistant", content: "预览已清空,可以继续发起新的 Agent 调试。" },
|
||||
];
|
||||
agentDebugTrace.value = [];
|
||||
}
|
||||
|
||||
async function openRetrievalDetail(row: RetrievalLogItem) {
|
||||
selectedRetrievalLog.value = await api.retrievalLogDetail(row.id);
|
||||
retrievalDetailOpen.value = true;
|
||||
}
|
||||
|
||||
async function updateAttentionStatus(row: AttentionItem, status: string) {
|
||||
await api.updateAttention(row.id, status);
|
||||
ElMessage.success("人工关注状态已更新");
|
||||
attentionRecords.value = await api.attentionList();
|
||||
}
|
||||
|
||||
async function deleteAttentionRecord(row: AttentionItem) {
|
||||
try {
|
||||
await ElMessageBox.confirm("确定删除这条人工关注记录吗?操作将写入审计日志。", "删除人工关注记录", {
|
||||
confirmButtonText: "确认删除", cancelButtonText: "取消", type: "warning",
|
||||
});
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
await api.deleteAttention(row.id);
|
||||
ElMessage.success("人工关注记录已删除");
|
||||
attentionRecords.value = await api.attentionList();
|
||||
}
|
||||
|
||||
function attentionPriorityLabel(priority: string) {
|
||||
return { urgent: "紧急", important: "重要", normal: "普通" }[priority as "urgent" | "important" | "normal"] || priority;
|
||||
}
|
||||
|
||||
async function clearFeishuCache() {
|
||||
@@ -750,6 +790,11 @@ async function openChatDetail(row: ChatRecord) {
|
||||
chatDetailOpen.value = true;
|
||||
}
|
||||
|
||||
async function openChatSession(sessionId: number) {
|
||||
chatDetail.value = await api.chatDetail(sessionId);
|
||||
chatDetailOpen.value = true;
|
||||
}
|
||||
|
||||
async function openAiLogDetail(row: AiLogRecord) {
|
||||
selectedAiLog.value = await api.aiLogDetail(row.id);
|
||||
aiLogDetailOpen.value = true;
|
||||
@@ -792,6 +837,8 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
<button :class="{ active: activeMenu === 'models' }" @click="switchMenu('models')">模型管理</button>
|
||||
<button :class="{ active: activeMenu === 'configs' }" @click="switchMenu('configs')">系统配置</button>
|
||||
<button :class="{ active: activeMenu === 'records' }" @click="switchMenu('records')">记录审计</button>
|
||||
<button :class="{ active: activeMenu === 'retrievals' }" @click="switchMenu('retrievals')">检索日志</button>
|
||||
<button :class="{ active: activeMenu === 'attention' }" @click="switchMenu('attention')">人工关注</button>
|
||||
</aside>
|
||||
|
||||
<section class="workspace">
|
||||
@@ -908,53 +955,7 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
</template>
|
||||
|
||||
<template v-if="activeMenu === 'knowledge'">
|
||||
<div class="page-head inline">
|
||||
<div><h2>知识库管理</h2><p>维护飞书知识库 SpaceID 和 NodeID。</p></div>
|
||||
<el-button type="warning" @click="clearFeishuCache">刷新飞书缓存</el-button>
|
||||
</div>
|
||||
<el-form class="knowledge-form" :model="knowledgeForm">
|
||||
<el-input v-model="knowledgeForm.name" placeholder="知识库名称" />
|
||||
<el-input v-model="knowledgeForm.feishuSpaceId" placeholder="SpaceID" />
|
||||
<el-input v-model="knowledgeForm.feishuNodeId" placeholder="NodeID" />
|
||||
<el-input v-model="knowledgeForm.remark" placeholder="备注" />
|
||||
<div class="form-switch-row">
|
||||
<span>启用</span>
|
||||
<el-switch v-model="knowledgeForm.status" :active-value="1" :inactive-value="0" />
|
||||
</div>
|
||||
<div class="form-actions-inline">
|
||||
<el-button type="primary" @click="saveKnowledge">{{ editingKnowledgeId ? "保存" : "新增" }}</el-button>
|
||||
<el-button @click="resetKnowledgeForm">清空</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<el-divider />
|
||||
<div class="batch-import-row">
|
||||
<el-input v-model="batchNodeToken" placeholder="输入 NodeToken(如 wikcnXXX),自动获取标题与 SpaceID" style="max-width: 400px" />
|
||||
<el-button type="primary" @click="batchImportKnowledge" :loading="batchImporting">导入</el-button>
|
||||
</div>
|
||||
<el-table :data="knowledge" stripe>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="名称" min-width="160" />
|
||||
<el-table-column prop="feishuSpaceId" label="SpaceID" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="feishuNodeId" label="NodeID" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="状态" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.status"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
@change="saveKnowledgeRow(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="editKnowledge(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteKnowledge(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<KnowledgeManagementView />
|
||||
</template>
|
||||
|
||||
<template v-if="activeMenu === 'prompt'">
|
||||
@@ -1054,6 +1055,19 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
<el-button type="primary" :loading="agentDebugging" @click="debugAgent">发送</el-button>
|
||||
</div>
|
||||
</aside>
|
||||
<aside class="agent-trace-panel">
|
||||
<div class="agent-preview-head">
|
||||
<h3>检索追踪</h3>
|
||||
<span>本轮工具调用</span>
|
||||
</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>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -1212,6 +1226,36 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-if="activeMenu === 'retrievals'">
|
||||
<div class="page-head"><h2>检索日志</h2><p>查看每条回答使用的知识目录、Agent 决策、搜索候选、重排和章节回读。</p></div>
|
||||
<el-table :data="retrievalLogs" stripe>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="sessionId" label="会话" width="90" />
|
||||
<el-table-column prop="question" label="用户问题" min-width="280" show-overflow-tooltip />
|
||||
<el-table-column label="调用知识" width="100"><template #default="{ row }"><el-tag :type="row.knowledgeCalled ? 'success' : 'info'">{{ row.knowledgeCalled ? '已调用' : '未调用' }}</el-tag></template></el-table-column>
|
||||
<el-table-column prop="selectedKnowledgeIds" label="知识库" width="120" />
|
||||
<el-table-column prop="status" label="状态" width="100" />
|
||||
<el-table-column prop="toolCostMs" label="工具耗时" width="110" />
|
||||
<el-table-column prop="totalCostMs" label="总耗时" width="100" />
|
||||
<el-table-column prop="createdAt" label="时间" width="180" />
|
||||
<el-table-column label="操作" width="90" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="openRetrievalDetail(row)">详情</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<template v-if="activeMenu === 'attention'">
|
||||
<div class="page-head"><h2>人工关注</h2><p>处理安全风险、复杂卡住、知识缺失和用户主动求助记录。</p></div>
|
||||
<el-table :data="attentionRecords" stripe>
|
||||
<el-table-column label="优先级" width="100"><template #default="{ row }"><el-tag :type="row.priority === 'urgent' ? 'danger' : row.priority === 'important' ? 'warning' : 'info'">{{ attentionPriorityLabel(row.priority) }}</el-tag></template></el-table-column>
|
||||
<el-table-column prop="problemSummary" label="问题摘要" min-width="220" />
|
||||
<el-table-column prop="triggerReason" label="触发原因" min-width="220" />
|
||||
<el-table-column prop="userId" label="用户" width="80" />
|
||||
<el-table-column prop="sessionId" label="会话" width="80" />
|
||||
<el-table-column label="状态" width="150"><template #default="{ row }"><el-select :model-value="row.status" size="small" @change="updateAttentionStatus(row, $event)"><el-option label="待处理" value="pending" /><el-option label="处理中" value="processing" /><el-option label="已处理" value="resolved" /><el-option label="无需处理" value="ignored" /></el-select></template></el-table-column>
|
||||
<el-table-column prop="createdAt" label="创建时间" width="180" />
|
||||
<el-table-column label="操作" width="130" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="openChatSession(row.sessionId)">原会话</el-button><el-button link type="danger" @click="deleteAttentionRecord(row)">删除</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<template v-if="activeMenu === 'records'">
|
||||
<div class="page-head"><h2>记录审计</h2><p>聊天、AI 请求和后台操作记录。</p></div>
|
||||
<el-tabs v-model="recordTab">
|
||||
@@ -1292,6 +1336,21 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<el-drawer v-model="retrievalDetailOpen" size="820px" title="检索日志详情">
|
||||
<template v-if="selectedRetrievalLog">
|
||||
<dl class="knowledge-meta">
|
||||
<dt>问题</dt><dd>{{ selectedRetrievalLog.question }}</dd>
|
||||
<dt>安全规则</dt><dd>{{ selectedRetrievalLog.safetyRuleVersion }}</dd>
|
||||
<dt>目录哈希</dt><dd>{{ selectedRetrievalLog.catalogHash }}</dd>
|
||||
<dt>最终章节</dt><dd>{{ selectedRetrievalLog.finalSectionIds || '未读取知识章节' }}</dd>
|
||||
</dl>
|
||||
<h3>本轮 KnowledgeList</h3><pre class="trace-json">{{ JSON.stringify(selectedRetrievalLog.catalogSnapshot, null, 2) }}</pre>
|
||||
<h3>Agent 工具轨迹</h3><pre class="trace-json">{{ JSON.stringify(selectedRetrievalLog.toolTrace, null, 2) }}</pre>
|
||||
<h3>候选切片与取舍</h3><el-table :data="selectedRetrievalLog.candidates"><el-table-column prop="knowledgeId" label="知识库" width="90" /><el-table-column prop="versionId" label="版本" width="80" /><el-table-column prop="sectionId" label="章节" width="80" /><el-table-column prop="lexicalScore" label="词法分" width="100" /><el-table-column prop="rerankScore" label="重排分" width="100" /><el-table-column label="采用" width="80"><template #default="{ row }">{{ row.selected ? '是' : '否' }}</template></el-table-column><el-table-column prop="discardReason" label="舍弃原因" min-width="180" /></el-table>
|
||||
<h3>最终回答</h3><pre class="trace-json">{{ selectedRetrievalLog.finalAnswer || '尚未生成回答' }}</pre>
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer v-model="chatDetailOpen" size="760px" title="聊天详情">
|
||||
<template v-if="chatDetail">
|
||||
<section class="chat-summary">
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
|
||||
import { api } from "../services/api";
|
||||
import type {
|
||||
KnowledgeDetail,
|
||||
KnowledgeItem,
|
||||
KnowledgeSyncJob,
|
||||
KnowledgeVersion,
|
||||
KnowledgeVersionContent,
|
||||
MigrationSummary,
|
||||
} from "../types/api";
|
||||
|
||||
const rows = ref<KnowledgeItem[]>([]);
|
||||
const migration = ref<MigrationSummary | null>(null);
|
||||
const loading = ref(false);
|
||||
const operatingId = ref<number | null>(null);
|
||||
const detailOpen = ref(false);
|
||||
const detail = ref<KnowledgeDetail | null>(null);
|
||||
const versions = ref<KnowledgeVersion[]>([]);
|
||||
const jobs = ref<KnowledgeSyncJob[]>([]);
|
||||
const versionContent = ref<KnowledgeVersionContent | null>(null);
|
||||
const activeTab = ref("overview");
|
||||
const filter = reactive({ keyword: "", type: "", open: "", source: "", pending: "" });
|
||||
const form = reactive({
|
||||
name: "",
|
||||
feishuSpaceId: "",
|
||||
feishuNodeId: "",
|
||||
knowledgeType: "general",
|
||||
reviewMode: "manual",
|
||||
status: 0,
|
||||
remark: "",
|
||||
});
|
||||
|
||||
const filteredRows = computed(() => rows.value.filter((item) => {
|
||||
if (filter.keyword && !`${item.name}${item.sourceTitle || ""}`.includes(filter.keyword)) return false;
|
||||
if (filter.type && item.knowledgeType !== filter.type) return false;
|
||||
if (filter.open && String(item.status) !== filter.open) return false;
|
||||
if (filter.source && item.sourceStatus !== filter.source) return false;
|
||||
if (filter.pending === "yes" && !item.pendingVersionId) return false;
|
||||
if (filter.pending === "no" && item.pendingVersionId) return false;
|
||||
return true;
|
||||
}));
|
||||
|
||||
onMounted(load);
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
try {
|
||||
[rows.value, migration.value] = await Promise.all([api.knowledge(), api.migrationSummary()]);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function createKnowledge() {
|
||||
if (!form.name || !form.feishuSpaceId || !form.feishuNodeId) {
|
||||
ElMessage.warning("请填写名称、SpaceID 和 NodeID");
|
||||
return;
|
||||
}
|
||||
await api.createKnowledge({ ...form });
|
||||
Object.assign(form, { name: "", feishuSpaceId: "", feishuNodeId: "", knowledgeType: "general", reviewMode: "manual", status: 0, remark: "" });
|
||||
ElMessage.success("知识库已新增,请进行首次同步");
|
||||
await load();
|
||||
}
|
||||
|
||||
async function openDetail(item: KnowledgeItem) {
|
||||
detailOpen.value = true;
|
||||
activeTab.value = "overview";
|
||||
versionContent.value = null;
|
||||
await refreshDetail(item.id);
|
||||
}
|
||||
|
||||
async function refreshDetail(id: number) {
|
||||
[detail.value, versions.value, jobs.value] = await Promise.all([
|
||||
api.knowledgeDetail(id), api.knowledgeVersions(id), api.knowledgeSyncJobs(id),
|
||||
]);
|
||||
}
|
||||
|
||||
async function sync(item: KnowledgeItem, mode: "review" | "replace") {
|
||||
operatingId.value = item.id;
|
||||
try {
|
||||
await api.syncKnowledge(item.id, mode);
|
||||
ElMessage.success("同步处理完成,已生成待审核版本");
|
||||
await load();
|
||||
if (detailOpen.value) await refreshDetail(item.id);
|
||||
} finally {
|
||||
operatingId.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function runMigration() {
|
||||
await ElMessageBox.confirm(
|
||||
"将依次重新读取全部现有飞书文章并生成待审核版本。迁移审核完成前,新检索链路不会使用这些知识库。",
|
||||
"开始知识迁移",
|
||||
{ confirmButtonText: "开始迁移", cancelButtonText: "取消", type: "warning" },
|
||||
);
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = await api.runKnowledgeMigration();
|
||||
ElMessage.success(`迁移处理完成:${result.success}/${result.total} 成功`);
|
||||
await load();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmManifest() {
|
||||
if (!detail.value?.pendingVersion) return;
|
||||
await api.confirmKnowledgeManifest(detail.value.knowledge.id, detail.value.pendingVersion.id);
|
||||
ElMessage.success("知识库说明卡已确认");
|
||||
await refreshDetail(detail.value.knowledge.id);
|
||||
}
|
||||
|
||||
async function review(action: "approve" | "reject") {
|
||||
if (!detail.value?.pendingVersion) return;
|
||||
let reason = "";
|
||||
if (action === "reject") {
|
||||
const result = await ElMessageBox.prompt(
|
||||
"请记录拒绝原因。知识内容不能在后台修改,请前往飞书源文章修改后重新同步。",
|
||||
"拒绝版本",
|
||||
{ confirmButtonText: "确认拒绝", cancelButtonText: "取消", inputValidator: (value) => Boolean(value?.trim()) || "请填写拒绝原因" },
|
||||
);
|
||||
reason = result.value;
|
||||
}
|
||||
await api.reviewKnowledgeVersion(detail.value.knowledge.id, detail.value.pendingVersion.id, action, reason);
|
||||
ElMessage.success(action === "approve" ? "版本已审核通过" : "版本已拒绝");
|
||||
await refreshDetail(detail.value.knowledge.id);
|
||||
}
|
||||
|
||||
async function publish(keepOpen: boolean) {
|
||||
if (!detail.value?.pendingVersion) return;
|
||||
await ElMessageBox.confirm(
|
||||
`将发布 V${detail.value.pendingVersion.versionNo},从用户下一条消息开始生效;发布后知识库将${keepOpen ? "保持开放" : "处于关闭状态"}。`,
|
||||
"发布知识版本",
|
||||
{ confirmButtonText: "确认发布", cancelButtonText: "取消", type: "warning" },
|
||||
);
|
||||
await api.publishKnowledgeVersion(detail.value.knowledge.id, detail.value.pendingVersion.id, keepOpen);
|
||||
ElMessage.success("正式版本已发布");
|
||||
await load();
|
||||
await refreshDetail(detail.value.knowledge.id);
|
||||
}
|
||||
|
||||
async function rollback(version: KnowledgeVersion) {
|
||||
if (!detail.value) return;
|
||||
await ElMessageBox.confirm(
|
||||
`将从当前版本回滚到 V${version.versionNo},从用户下一条消息开始生效。`,
|
||||
"回滚知识版本",
|
||||
{ confirmButtonText: "确认回滚", cancelButtonText: "取消", type: "warning" },
|
||||
);
|
||||
await api.rollbackKnowledgeVersion(detail.value.knowledge.id, version.id, detail.value.knowledge.status === 1);
|
||||
ElMessage.success("知识版本已回滚");
|
||||
await load();
|
||||
await refreshDetail(detail.value.knowledge.id);
|
||||
}
|
||||
|
||||
async function toggleOpen(item: KnowledgeItem) {
|
||||
const next = item.status !== 1;
|
||||
await ElMessageBox.confirm(
|
||||
next ? "开放后,该知识库将从用户下一条消息开始进入 Agent 动态知识目录。" : "关闭后,当前生成不受影响,从用户下一条消息开始无法检索该知识库。",
|
||||
next ? "开放知识库" : "关闭知识库",
|
||||
{ confirmButtonText: next ? "确认开放" : "确认关闭", cancelButtonText: "取消", type: "warning" },
|
||||
);
|
||||
await api.setKnowledgeOpen(item.id, next);
|
||||
ElMessage.success(next ? "知识库已开放" : "知识库已关闭");
|
||||
await load();
|
||||
}
|
||||
|
||||
async function archive(item: KnowledgeItem) {
|
||||
await ElMessageBox.confirm("归档后知识库会立即关闭,但历史正式版本和检索记录将保留。", "归档知识库", {
|
||||
confirmButtonText: "确认归档", cancelButtonText: "取消", type: "warning",
|
||||
});
|
||||
await api.setKnowledgeLifecycle(item.id, "archived");
|
||||
ElMessage.success("知识库已归档");
|
||||
await load();
|
||||
}
|
||||
|
||||
async function remove(item: KnowledgeItem) {
|
||||
await ElMessageBox.confirm(
|
||||
`将物理删除“${item.name}”及关联数据。该操作不可恢复,并可能影响历史检索记录定位。`,
|
||||
"物理删除知识库",
|
||||
{ confirmButtonText: "确认删除", cancelButtonText: "取消", type: "error" },
|
||||
);
|
||||
await api.deleteKnowledge(item.id);
|
||||
ElMessage.success("知识库已删除");
|
||||
await load();
|
||||
}
|
||||
|
||||
async function showVersion(version: KnowledgeVersion) {
|
||||
versionContent.value = await api.knowledgeVersionContent(version.id);
|
||||
activeTab.value = "content";
|
||||
}
|
||||
|
||||
function statusType(status: string) {
|
||||
if (["published", "passed", "normal", "completed"].includes(status)) return "success";
|
||||
if (["failed", "blocked", "error", "rejected"].includes(status)) return "danger";
|
||||
if (["pending_review", "processing", "pending"].includes(status)) return "warning";
|
||||
return "info";
|
||||
}
|
||||
|
||||
function typeLabel(value: string) {
|
||||
return { course: "课程知识库", qa: "答疑知识库", general: "通用知识库" }[value] || value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-head inline">
|
||||
<div><h2>知识库管理</h2><p>一篇飞书文章对应一个知识库;同步、审核和发布相互独立。</p></div>
|
||||
<el-button type="primary" :loading="loading" @click="runMigration">迁移现有知识库</el-button>
|
||||
</div>
|
||||
|
||||
<section class="migration-overview">
|
||||
<div><span>知识库总数</span><strong>{{ migration?.total ?? 0 }}</strong></div>
|
||||
<div><span>待迁移</span><strong>{{ migration?.counts.pending ?? 0 }}</strong></div>
|
||||
<div><span>待审核</span><strong>{{ migration?.counts.pending_review ?? 0 }}</strong></div>
|
||||
<div><span>迁移完成</span><strong>{{ migration?.counts.completed ?? 0 }}</strong></div>
|
||||
<div><span>失败</span><strong>{{ migration?.counts.failed ?? 0 }}</strong></div>
|
||||
</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" />
|
||||
<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" />
|
||||
</el-select>
|
||||
<el-select v-model="form.reviewMode">
|
||||
<el-option label="必须人工审核" value="manual" /><el-option label="允许自动发布" value="automatic" />
|
||||
</el-select>
|
||||
<el-button type="primary" @click="createKnowledge">保存并准备同步</el-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="knowledge-filters">
|
||||
<el-input v-model="filter.keyword" placeholder="搜索后台名称或飞书标题" clearable />
|
||||
<el-select v-model="filter.type" placeholder="知识库类型" clearable><el-option label="课程" value="course" /><el-option label="答疑" value="qa" /><el-option label="通用" value="general" /></el-select>
|
||||
<el-select v-model="filter.open" placeholder="开放状态" clearable><el-option label="已开放" value="1" /><el-option label="已关闭" value="0" /></el-select>
|
||||
<el-select v-model="filter.source" placeholder="源状态" clearable><el-option label="正常" value="normal" /><el-option label="源异常" value="error" /></el-select>
|
||||
<el-select v-model="filter.pending" placeholder="待发布版本" clearable><el-option label="有待发布版本" value="yes" /><el-option label="无待发布版本" value="no" /></el-select>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" :data="filteredRows" stripe>
|
||||
<el-table-column label="知识库" min-width="240"><template #default="{ row }"><strong>{{ row.name }}</strong><small class="table-subline">{{ row.sourceTitle || "尚未同步源标题" }}</small></template></el-table-column>
|
||||
<el-table-column label="类型/审核" width="150"><template #default="{ row }">{{ typeLabel(row.knowledgeType) }}<small class="table-subline">{{ row.reviewMode === 'manual' ? '人工审核' : '允许自动发布' }}</small></template></el-table-column>
|
||||
<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="160"><template #default="{ row }"><el-tag :type="row.status === 1 ? 'success' : 'info'">{{ row.status === 1 ? '已开放' : '已关闭' }}</el-tag><el-tag class="status-gap" :type="statusType(row.sourceStatus)">{{ row.sourceStatus === 'normal' ? '源正常' : '源异常' }}</el-tag></template></el-table-column>
|
||||
<el-table-column prop="lastSyncedAt" label="最近同步" width="180" />
|
||||
<el-table-column label="操作" width="270" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="openDetail(row)">详情</el-button><el-button link type="primary" :loading="operatingId === row.id" @click="sync(row, 'review')">同步</el-button><el-button link @click="toggleOpen(row)">{{ row.status === 1 ? '关闭' : '开放' }}</el-button><el-dropdown trigger="click"><el-button link>更多</el-button><template #dropdown><el-dropdown-menu><el-dropdown-item @click="sync(row, 'replace')">同步并准备替换</el-dropdown-item><el-dropdown-item @click="archive(row)">归档</el-dropdown-item><el-dropdown-item divided @click="remove(row)">物理删除</el-dropdown-item></el-dropdown-menu></template></el-dropdown></template></el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-drawer v-model="detailOpen" size="76%" title="知识库详情" destroy-on-close>
|
||||
<template v-if="detail">
|
||||
<div class="knowledge-detail-head">
|
||||
<div><h3>{{ detail.knowledge.name }}</h3><p>{{ typeLabel(detail.knowledge.knowledgeType) }} · {{ detail.knowledge.reviewMode === 'manual' ? '必须人工审核' : '允许自动发布' }}</p></div>
|
||||
<div><el-tag :type="detail.knowledge.status === 1 ? 'success' : 'info'">{{ detail.knowledge.status === 1 ? '已开放' : '已关闭' }}</el-tag><el-tag :type="statusType(detail.knowledge.sourceStatus)">{{ detail.knowledge.sourceStatus === 'normal' ? '源文章正常' : '源文章异常' }}</el-tag></div>
|
||||
</div>
|
||||
<el-alert v-if="detail.knowledge.sourceError" :title="detail.knowledge.sourceError" type="error" :closable="false" />
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="基本信息" name="overview">
|
||||
<dl class="knowledge-meta"><dt>SpaceID</dt><dd>{{ detail.knowledge.feishuSpaceId }}</dd><dt>NodeID</dt><dd>{{ detail.knowledge.feishuNodeId }}</dd><dt>正式版本</dt><dd>{{ detail.currentVersion ? `V${detail.currentVersion.versionNo}` : '暂无' }}</dd><dt>待发布版本</dt><dd>{{ detail.pendingVersion ? `V${detail.pendingVersion.versionNo}` : '暂无' }}</dd></dl>
|
||||
<section v-if="detail.manifest" class="manifest-panel"><h4>Knowledge Manifest</h4><p><strong>用途:</strong>{{ detail.manifest.purpose }}</p><p><strong>适用:</strong>{{ detail.manifest.applicableQuestions }}</p><p><strong>不适用:</strong>{{ detail.manifest.inapplicableQuestions }}</p><p><strong>核心主题:</strong>{{ detail.manifest.coreTopics }}</p><p><strong>边界:</strong>{{ detail.manifest.boundaries }}</p><el-button v-if="!detail.manifest.confirmed && detail.pendingVersion" type="primary" @click="confirmManifest">确认说明卡</el-button></section>
|
||||
<div v-if="detail.pendingVersion" class="version-actions"><el-button type="success" @click="review('approve')">审核通过</el-button><el-button type="danger" @click="review('reject')">审核拒绝</el-button><el-button type="primary" @click="publish(true)">发布并保持开放</el-button><el-button @click="publish(false)">发布后关闭</el-button></div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="内容结构" name="content">
|
||||
<el-empty v-if="!versionContent" description="请从版本记录中选择一个版本查看" />
|
||||
<template v-else><el-collapse><el-collapse-item v-for="section in versionContent.sections" :key="section.id" :title="`${section.sectionKey} ${section.title}`"><pre class="source-content">{{ section.content }}</pre></el-collapse-item></el-collapse><h4>知识卡</h4><el-table :data="versionContent.cards"><el-table-column prop="title" label="标题" /><el-table-column prop="summary" label="摘要" min-width="300" /><el-table-column prop="riskLevel" label="风险" width="100" /><el-table-column prop="reviewStatus" label="审核" width="110" /></el-table></template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="版本记录" name="versions"><el-table :data="versions"><el-table-column label="版本" width="90"><template #default="{ row }">V{{ row.versionNo }}</template></el-table-column><el-table-column prop="status" label="状态" width="130" /><el-table-column prop="qualityStatus" label="质检" width="120" /><el-table-column prop="createdAt" label="生成时间" /><el-table-column prop="publishedAt" label="发布时间" /><el-table-column label="操作" width="180"><template #default="{ row }"><el-button link type="primary" @click="showVersion(row)">查看内容</el-button><el-button v-if="['published','superseded'].includes(row.status) && row.id !== detail?.knowledge.currentVersionId" link @click="rollback(row)">回滚</el-button></template></el-table-column></el-table></el-tab-pane>
|
||||
<el-tab-pane label="同步记录" name="jobs"><el-table :data="jobs"><el-table-column prop="id" label="任务" width="80" /><el-table-column prop="mode" label="模式" width="110" /><el-table-column prop="status" label="状态" width="130" /><el-table-column prop="stage" label="阶段" width="130" /><el-table-column label="进度" width="180"><template #default="{ row }"><el-progress :percentage="row.progress" /></template></el-table-column><el-table-column prop="sourceError" label="失败原因" min-width="260" /></el-table></el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
@@ -9,6 +9,13 @@ import type {
|
||||
ChatRecordQuery,
|
||||
DashboardStats,
|
||||
KnowledgeItem,
|
||||
KnowledgeDetail,
|
||||
KnowledgeSyncJob,
|
||||
KnowledgeVersion,
|
||||
KnowledgeVersionContent,
|
||||
MigrationSummary,
|
||||
RetrievalLogItem,
|
||||
AttentionItem,
|
||||
ModelItem,
|
||||
SystemConfigItem,
|
||||
UserImportResult,
|
||||
@@ -118,6 +125,27 @@ export const api = {
|
||||
"/admin/knowledge/import",
|
||||
{ method: "POST", body: JSON.stringify(body) },
|
||||
),
|
||||
knowledgeDetail: (id: number) => request<KnowledgeDetail>(`/admin/knowledge/${id}/detail`),
|
||||
knowledgeVersions: (id: number) => request<KnowledgeVersion[]>(`/admin/knowledge/${id}/versions`),
|
||||
knowledgeVersionContent: (versionId: number) => request<KnowledgeVersionContent>(`/admin/knowledge/version/${versionId}/content`),
|
||||
knowledgeSyncJobs: (id: number) => request<KnowledgeSyncJob[]>(`/admin/knowledge/${id}/sync-jobs`),
|
||||
syncKnowledge: (id: number, mode: "review" | "replace") =>
|
||||
request<KnowledgeSyncJob>(`/admin/knowledge/${id}/sync`, { method: "POST", body: JSON.stringify({ mode }) }),
|
||||
confirmKnowledgeManifest: (id: number, versionId: number) =>
|
||||
request<null>(`/admin/knowledge/${id}/manifest/${versionId}/confirm`, { method: "POST", body: "{}" }),
|
||||
reviewKnowledgeVersion: (id: number, versionId: number, action: "approve" | "reject", reason?: string) =>
|
||||
request<null>(`/admin/knowledge/${id}/version/${versionId}/review`, { method: "POST", body: JSON.stringify({ action, reason }) }),
|
||||
publishKnowledgeVersion: (id: number, versionId: number, keepOpen: boolean) =>
|
||||
request<KnowledgeItem>(`/admin/knowledge/${id}/version/${versionId}/publish`, { method: "POST", body: JSON.stringify({ keepOpen }) }),
|
||||
rollbackKnowledgeVersion: (id: number, versionId: number, keepOpen: boolean) =>
|
||||
request<KnowledgeItem>(`/admin/knowledge/${id}/version/${versionId}/rollback`, { method: "POST", body: JSON.stringify({ keepOpen }) }),
|
||||
setKnowledgeOpen: (id: number, open: boolean) =>
|
||||
request<KnowledgeItem>(`/admin/knowledge/${id}/open-status`, { method: "PUT", body: JSON.stringify({ open }) }),
|
||||
setKnowledgeLifecycle: (id: number, status: "active" | "archived") =>
|
||||
request<KnowledgeItem>(`/admin/knowledge/${id}/lifecycle`, { method: "PUT", body: JSON.stringify({ status }) }),
|
||||
migrationSummary: () => request<MigrationSummary>("/admin/knowledge-migration/summary"),
|
||||
runKnowledgeMigration: (knowledgeId?: number) =>
|
||||
request<{ total: number; success: number; results: Record<string, unknown>[] }>(`/admin/knowledge-migration/run${knowledgeId ? `?knowledgeId=${knowledgeId}` : ""}`, { method: "POST", body: "{}" }),
|
||||
prompt: () => request<{ promptContent: string }>("/admin/prompt"),
|
||||
savePrompt: (promptContent: string) =>
|
||||
request<{ promptContent: string }>("/admin/prompt", { method: "PUT", body: JSON.stringify({ promptContent }) }),
|
||||
@@ -148,6 +176,12 @@ export const api = {
|
||||
request<AiLogRecord[]>(`/admin/ai-log/list${queryString(query)}`),
|
||||
aiLogDetail: (id: number) => request<AiLogRecord>(`/admin/ai-log/${id}`),
|
||||
operationLogs: () => request<Record<string, unknown>[]>("/admin/log/list"),
|
||||
retrievalLogs: () => request<RetrievalLogItem[]>("/admin/retrieval-log/list"),
|
||||
retrievalLogDetail: (id: number) => request<Record<string, unknown>>(`/admin/retrieval-log/${id}`),
|
||||
attentionList: () => request<AttentionItem[]>("/admin/attention/list"),
|
||||
updateAttention: (id: number, status: string, note?: string) =>
|
||||
request<AttentionItem>(`/admin/attention/${id}`, { method: "PUT", body: JSON.stringify({ status, note }) }),
|
||||
deleteAttention: (id: number) => request<null>(`/admin/attention/${id}`, { method: "DELETE" }),
|
||||
clearFeishuCache: () =>
|
||||
request<{ cleared: number; message: string }>("/admin/feishu/cache/clear", { method: "POST", body: "{}" }),
|
||||
};
|
||||
|
||||
@@ -349,6 +349,145 @@ textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.migration-overview {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.migration-overview div,
|
||||
.knowledge-work-panel {
|
||||
padding: 14px;
|
||||
border: 1px solid #dfe8e5;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.migration-overview span,
|
||||
.table-subline {
|
||||
display: block;
|
||||
color: #71817b;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.migration-overview strong {
|
||||
display: block;
|
||||
margin-top: 7px;
|
||||
color: #223631;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.knowledge-work-panel {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.knowledge-work-panel h3 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.knowledge-create-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.knowledge-filters {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(240px, 1.6fr) repeat(4, minmax(130px, 0.8fr));
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.status-gap {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.knowledge-detail-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.knowledge-detail-head h3,
|
||||
.knowledge-detail-head p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.knowledge-detail-head p {
|
||||
margin-top: 6px;
|
||||
color: #667a73;
|
||||
}
|
||||
|
||||
.knowledge-detail-head .el-tag + .el-tag {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.knowledge-meta {
|
||||
display: grid;
|
||||
grid-template-columns: 120px minmax(0, 1fr);
|
||||
gap: 8px 14px;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
.knowledge-meta dt {
|
||||
color: #667a73;
|
||||
}
|
||||
|
||||
.knowledge-meta dd {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.manifest-panel {
|
||||
padding: 16px;
|
||||
border: 1px solid #dfe8e5;
|
||||
border-radius: 8px;
|
||||
background: #f7faf9;
|
||||
}
|
||||
|
||||
.manifest-panel h4 {
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
.manifest-panel p {
|
||||
margin: 8px 0;
|
||||
color: #40524b;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.version-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.source-content {
|
||||
max-height: 420px;
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
color: #34453f;
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.knowledge-create-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.knowledge-filters {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.actions.compact {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -483,13 +622,14 @@ textarea {
|
||||
|
||||
.agent-workbench {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(520px, 1fr) minmax(360px, 420px);
|
||||
grid-template-columns: minmax(380px, 0.9fr) minmax(420px, 1.1fr) minmax(280px, 0.7fr);
|
||||
gap: 18px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.agent-config-panel,
|
||||
.agent-preview-panel {
|
||||
.agent-preview-panel,
|
||||
.agent-trace-panel {
|
||||
padding: 18px;
|
||||
border: 1px solid #dfe8e5;
|
||||
border-radius: 8px;
|
||||
@@ -504,6 +644,49 @@ textarea {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.agent-trace-panel {
|
||||
height: clamp(560px, calc(100vh - 170px), 760px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.agent-trace-list {
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 12px;
|
||||
background: #f6f8f7;
|
||||
}
|
||||
|
||||
.agent-trace-list article {
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
border: 1px solid #dfe8e5;
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.agent-trace-list pre,
|
||||
.trace-json {
|
||||
overflow: auto;
|
||||
margin: 8px 0 0;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
color: #40524b;
|
||||
font-family: "SFMono-Regular", Consolas, monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.trace-json {
|
||||
max-height: 360px;
|
||||
padding: 12px;
|
||||
border: 1px solid #dfe8e5;
|
||||
border-radius: 6px;
|
||||
background: #f7faf9;
|
||||
}
|
||||
|
||||
.agent-config-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@@ -955,6 +1138,17 @@ textarea {
|
||||
color: #b42318;
|
||||
}
|
||||
|
||||
@media (max-width: 1450px) {
|
||||
.agent-workbench {
|
||||
grid-template-columns: minmax(420px, 1fr) minmax(420px, 1fr);
|
||||
}
|
||||
|
||||
.agent-trace-panel {
|
||||
grid-column: 1 / -1;
|
||||
height: 420px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1120px) {
|
||||
.student-tools,
|
||||
.agent-workbench,
|
||||
@@ -969,6 +1163,10 @@ textarea {
|
||||
.agent-preview-panel {
|
||||
height: 620px;
|
||||
}
|
||||
|
||||
.agent-trace-panel {
|
||||
grid-column: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
|
||||
@@ -63,6 +63,18 @@ export interface KnowledgeItem {
|
||||
feishuNodeId: string;
|
||||
status: number;
|
||||
remark?: string | null;
|
||||
sourceTitle?: string | null;
|
||||
knowledgeType: "course" | "qa" | "general";
|
||||
reviewMode: "manual" | "automatic";
|
||||
lifecycleStatus: "active" | "archived";
|
||||
sourceStatus: "normal" | "error";
|
||||
sourceError?: string | null;
|
||||
manifestConfirmed: boolean;
|
||||
currentVersionId?: number | null;
|
||||
pendingVersionId?: number | null;
|
||||
lastSyncedAt?: string | null;
|
||||
lastPublishedAt?: string | null;
|
||||
migrationStatus: string;
|
||||
createdAt?: string | null;
|
||||
updatedAt?: string | null;
|
||||
}
|
||||
@@ -101,6 +113,97 @@ export interface AgentDebugResult {
|
||||
modelName?: string;
|
||||
retrieveCount?: number;
|
||||
knowledgeIds?: string;
|
||||
retrievalTrace?: Record<string, unknown>[];
|
||||
retrievalLogId?: number;
|
||||
}
|
||||
|
||||
export interface KnowledgeVersion {
|
||||
id: number;
|
||||
knowledgeId: number;
|
||||
snapshotId: number;
|
||||
versionNo: number;
|
||||
status: string;
|
||||
qualityStatus: string;
|
||||
changeSummary?: Record<string, unknown> | null;
|
||||
qualityReport?: Record<string, unknown> | null;
|
||||
processingRuleVersion: string;
|
||||
generationModel?: string | null;
|
||||
createdAt: string;
|
||||
publishedAt?: string | null;
|
||||
}
|
||||
|
||||
export interface KnowledgeManifest {
|
||||
id: number;
|
||||
versionId: number;
|
||||
purpose: string;
|
||||
applicableQuestions: string;
|
||||
inapplicableQuestions: string;
|
||||
coreTopics: string;
|
||||
entities?: string | null;
|
||||
boundaries?: string | null;
|
||||
changeLevel: string;
|
||||
confirmed: boolean;
|
||||
}
|
||||
|
||||
export interface KnowledgeDetail {
|
||||
knowledge: KnowledgeItem;
|
||||
currentVersion?: KnowledgeVersion | null;
|
||||
pendingVersion?: KnowledgeVersion | null;
|
||||
manifest?: KnowledgeManifest | null;
|
||||
}
|
||||
|
||||
export interface KnowledgeVersionContent {
|
||||
version: KnowledgeVersion;
|
||||
manifest?: KnowledgeManifest | null;
|
||||
sections: Array<{ id: number; sectionKey: string; title: string; content: string; sourceStart: number; sourceEnd: number; contentHash: string }>;
|
||||
cards: Array<Record<string, unknown>>;
|
||||
}
|
||||
|
||||
export interface KnowledgeSyncJob {
|
||||
id: number;
|
||||
knowledgeId: number;
|
||||
mode: string;
|
||||
status: string;
|
||||
stage: string;
|
||||
progress: number;
|
||||
sourceError?: string | null;
|
||||
resultSummary?: Record<string, unknown> | null;
|
||||
createdAt: string;
|
||||
finishedAt?: string | null;
|
||||
}
|
||||
|
||||
export interface MigrationSummary {
|
||||
total: number;
|
||||
counts: Record<string, number>;
|
||||
items: KnowledgeItem[];
|
||||
}
|
||||
|
||||
export interface RetrievalLogItem {
|
||||
id: number;
|
||||
sessionId?: number | null;
|
||||
question: string;
|
||||
knowledgeCalled: boolean;
|
||||
selectedKnowledgeIds?: string | null;
|
||||
queryTerms?: string[] | null;
|
||||
status: string;
|
||||
toolCostMs?: number | null;
|
||||
totalCostMs?: number | null;
|
||||
attentionCreated: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface AttentionItem {
|
||||
id: number;
|
||||
sessionId: number;
|
||||
messageId: number;
|
||||
userId: number;
|
||||
triggerMessage: string;
|
||||
problemSummary: string;
|
||||
triggerReason: string;
|
||||
priority: string;
|
||||
status: string;
|
||||
handlerNote?: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface ChatRecord {
|
||||
|
||||
Reference in New Issue
Block a user