马一丁

Display GraphRAG on the front-end page

... ... @@ -8,6 +8,7 @@
<!-- PDF导出依赖 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
<style>
* {
margin: 0;
... ... @@ -1485,6 +1486,91 @@
color: #666;
font-size: 14px;
}
/* 知识图谱迷你面板 */
.graph-mini-panel {
border: 2px solid #000000;
background-color: #f7f7f7;
padding: 10px 12px;
width: 280px;
align-self: flex-start;
display: none;
box-shadow: 4px 4px 0 #0000001a;
}
.graph-mini-panel.collapsed .graph-mini-body {
display: none;
}
.graph-mini-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 8px;
}
.graph-mini-title {
font-weight: bold;
font-size: 14px;
}
.graph-mini-subtitle {
font-size: 12px;
color: #555555;
line-height: 1.2;
}
.graph-mini-actions {
display: flex;
gap: 6px;
}
.graph-mini-button {
border: 1px solid #000000;
background-color: #ffffff;
padding: 6px 10px;
cursor: pointer;
font-size: 12px;
font-weight: bold;
transition: background-color 0.2s ease, color 0.2s ease;
}
.graph-mini-button:hover {
background-color: #000000;
color: #ffffff;
}
.graph-mini-body {
position: relative;
height: 240px;
width: 240px;
border: 1px dashed #000000;
background-color: #ffffff;
overflow: hidden;
}
.graph-mini-placeholder {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 8px;
font-size: 12px;
color: #666666;
}
.graph-mini-placeholder.error {
color: #b42318;
}
.graph-mini-canvas {
height: 100%;
width: 100%;
display: none;
}
</style>
<!-- PDF中文字体数据 (优化后的子集字体,包含6763个GB2312汉字) -->
... ... @@ -1648,6 +1734,11 @@
let customTemplate = ''; // 存储用户上传的自定义模板内容
let configValues = {};
let configDirty = false;
let graphragEnabled = false;
let graphragSettingLoaded = false;
let graphMiniNetwork = null;
let graphMiniPreferredTaskId = null;
let graphMiniLoading = false;
let configAutoRefreshTimer = null;
let systemStarted = false;
let systemStarting = false;
... ... @@ -2038,6 +2129,30 @@
query: 8503
};
function syncGraphragFlag(config) {
if (!config || !Object.prototype.hasOwnProperty.call(config, 'GRAPHRAG_ENABLED')) {
return;
}
graphragEnabled = String(config.GRAPHRAG_ENABLED).toLowerCase() === 'true';
graphragSettingLoaded = true;
}
async function ensureGraphragSetting(force = false) {
if (!force && graphragSettingLoaded) {
return graphragEnabled;
}
try {
const response = await fetch(CONFIG_ENDPOINT, { cache: 'no-store' });
const data = await response.json();
if (data && data.success && data.config) {
syncGraphragFlag(data.config);
}
} catch (error) {
console.warn('读取GraphRAG配置失败:', error);
}
return graphragEnabled;
}
const configFieldGroups = [
{
title: '数据库连接',
... ... @@ -2420,6 +2535,7 @@
throw new Error(data.message || '读取配置失败');
}
configValues = data.config || {};
syncGraphragFlag(configValues);
renderConfigForm(configValues);
configDirty = false;
if (messageOverride) {
... ... @@ -2713,6 +2829,7 @@
throw new Error(data.message || '保存失败');
}
configValues = data.config || {};
syncGraphragFlag(configValues);
renderConfigForm(configValues);
configDirty = false;
if (silent) {
... ... @@ -4765,6 +4882,24 @@ function getConsoleContainer() {
<!-- 任务进度区域 -->
<div id="taskProgressArea"></div>
<!-- 知识图谱迷你预览(GraphRAG 开启时显示) -->
<div class="graph-mini-panel" id="graphMiniPanel">
<div class="graph-mini-header">
<div>
<div class="graph-mini-title">知识图谱</div>
<div class="graph-mini-subtitle">GraphRAG 生成概览</div>
</div>
<div class="graph-mini-actions">
<button class="graph-mini-button" id="graphMiniRefresh" title="刷新知识图谱">刷新</button>
<button class="graph-mini-button" id="graphMiniToggle" title="折叠/展开">折叠</button>
</div>
</div>
<div class="graph-mini-body" id="graphMiniBody">
<div class="graph-mini-placeholder" id="graphMiniPlaceholder">等待图谱生成...</div>
<div class="graph-mini-canvas" id="graphMiniCanvas"></div>
</div>
</div>
<!-- 报告预览区域 -->
<div class="report-preview" id="reportPreview">
<div class="report-loading">
... ... @@ -4775,6 +4910,7 @@ function getConsoleContainer() {
reportContent.innerHTML = interfaceHTML;
initializeReportControls();
initializeGraphMiniPanel(statusData);
resetReportStreamOutput('等待新的Report任务启动...');
updateReportStreamStatus('idle');
... ... @@ -4806,6 +4942,215 @@ function getConsoleContainer() {
}
}
async function initializeGraphMiniPanel(statusData) {
const panel = document.getElementById('graphMiniPanel');
if (!panel) return;
const enabled = await ensureGraphragSetting();
if (!enabled) {
panel.style.display = 'none';
return;
}
panel.style.display = 'block';
bindGraphMiniEvents();
const currentTaskId = statusData && statusData.current_task
? statusData.current_task.task_id
: (lastCompletedReportTask ? lastCompletedReportTask.task_id : null);
const currentTaskStatus = statusData && statusData.current_task
? statusData.current_task.status
: '';
if (currentTaskId) {
graphMiniPreferredTaskId = currentTaskId;
}
if (currentTaskStatus === 'running') {
setGraphMiniWaiting(currentTaskId);
return;
}
if (panel.classList.contains('collapsed')) {
setGraphMiniPlaceholder('展开以查看知识图谱');
return;
}
refreshGraphMini(graphMiniPreferredTaskId, true);
}
function bindGraphMiniEvents() {
const toggleBtn = document.getElementById('graphMiniToggle');
const refreshBtn = document.getElementById('graphMiniRefresh');
if (toggleBtn && !toggleBtn.dataset.bound) {
toggleBtn.dataset.bound = 'true';
toggleBtn.addEventListener('click', toggleGraphMiniPanel);
}
if (refreshBtn && !refreshBtn.dataset.bound) {
refreshBtn.dataset.bound = 'true';
refreshBtn.addEventListener('click', () => refreshGraphMini(graphMiniPreferredTaskId, true));
}
}
function toggleGraphMiniPanel() {
const panel = document.getElementById('graphMiniPanel');
const toggleBtn = document.getElementById('graphMiniToggle');
if (!panel) return;
const collapsed = panel.classList.toggle('collapsed');
if (toggleBtn) {
toggleBtn.textContent = collapsed ? '展开' : '折叠';
}
if (!collapsed) {
refreshGraphMini(graphMiniPreferredTaskId, true);
} else {
setGraphMiniPlaceholder('已折叠,展开以查看知识图谱');
}
}
function setGraphMiniPlaceholder(message, type = '') {
const placeholder = document.getElementById('graphMiniPlaceholder');
const canvas = document.getElementById('graphMiniCanvas');
if (!placeholder || !canvas) return;
placeholder.textContent = message || '';
if (type === 'error') {
placeholder.classList.add('error');
} else {
placeholder.classList.remove('error');
}
placeholder.style.display = 'flex';
canvas.style.display = 'none';
}
async function fetchGraphData(taskId = null) {
const url = taskId ? `/api/graph/${taskId}` : '/api/graph/latest';
try {
const response = await fetch(url, { cache: 'no-store' });
const data = await response.json();
if (!response.ok || !data.success || !data.graph) {
return null;
}
return data;
} catch (error) {
console.warn('获取知识图谱失败:', error);
return null;
}
}
function renderGraphMini(graph) {
const canvas = document.getElementById('graphMiniCanvas');
const placeholder = document.getElementById('graphMiniPlaceholder');
if (!canvas || !placeholder) return;
if (!(window.vis && window.vis.Network)) {
setGraphMiniPlaceholder('图谱组件未加载,请检查网络后重试', 'error');
return;
}
const nodes = new vis.DataSet((graph.nodes || []).map(node => ({
id: node.id,
label: node.label || node.id,
group: node.group || node.type,
title: node.title || '',
size: 12
})));
const edges = new vis.DataSet((graph.edges || []).map(edge => ({
from: edge.from,
to: edge.to,
label: edge.label || '',
arrows: 'to',
color: '#444444',
font: { align: 'top', size: 10 }
})));
const options = {
height: '100%',
width: '100%',
interaction: { hover: true, dragView: true, zoomView: true },
nodes: {
shape: 'dot',
borderWidth: 1,
font: { size: 12 },
scaling: { min: 8, max: 18 }
},
edges: {
smooth: true,
color: '#999999',
width: 1
},
physics: {
stabilization: true,
barnesHut: { avoidOverlap: 0.5, springLength: 80, springConstant: 0.02 }
}
};
if (graphMiniNetwork) {
graphMiniNetwork.destroy();
}
placeholder.style.display = 'none';
canvas.style.display = 'block';
graphMiniNetwork = new vis.Network(canvas, { nodes, edges }, options);
const fitOnce = () => {
graphMiniNetwork.fit({ animation: { duration: 300, easing: 'easeInOutQuad' } });
graphMiniNetwork.off('stabilizationIterationsDone', fitOnce);
};
graphMiniNetwork.on('stabilizationIterationsDone', fitOnce);
}
async function refreshGraphMini(taskId = null, allowFallback = true) {
const panel = document.getElementById('graphMiniPanel');
if (!panel) return;
const enabled = await ensureGraphragSetting();
if (!enabled) {
panel.style.display = 'none';
return;
}
if (panel.classList.contains('collapsed')) {
return;
}
bindGraphMiniEvents();
panel.style.display = 'block';
if (taskId) {
graphMiniPreferredTaskId = taskId;
}
if (graphMiniLoading) {
return;
}
const targetTaskId = taskId || graphMiniPreferredTaskId || (lastCompletedReportTask ? lastCompletedReportTask.task_id : null);
setGraphMiniPlaceholder('加载知识图谱...');
graphMiniLoading = true;
try {
let data = await fetchGraphData(targetTaskId);
if (!data && allowFallback && targetTaskId) {
data = await fetchGraphData(null);
}
if (data && data.graph) {
renderGraphMini(data.graph);
} else {
setGraphMiniPlaceholder('暂未找到知识图谱,请稍后点击刷新重试', 'error');
}
} finally {
graphMiniLoading = false;
}
}
function setGraphMiniWaiting(taskId) {
graphMiniPreferredTaskId = taskId || graphMiniPreferredTaskId;
ensureGraphragSetting().then(enabled => {
if (!enabled) return;
const panel = document.getElementById('graphMiniPanel');
if (!panel) return;
panel.style.display = 'block';
bindGraphMiniEvents();
setGraphMiniPlaceholder('等待图谱生成...');
});
}
function initializeReportControls() {
const generateButton = document.getElementById('generateReportButton');
if (generateButton && !generateButton.dataset.bound) {
... ... @@ -5203,6 +5548,7 @@ function getConsoleContainer() {
if (data.success) {
reportTaskId = data.task_id;
showMessage('报告生成已启动', 'success');
setGraphMiniWaiting(reportTaskId);
// 更新任务状态显示
updateTaskProgressStatus({
... ... @@ -5292,6 +5638,8 @@ function getConsoleContainer() {
if (data.task.status === 'completed') {
stopProgressPolling();
showMessage('报告生成完成!', 'success');
graphMiniPreferredTaskId = data.task.task_id;
refreshGraphMini(data.task.task_id, true);
// 自动显示报告
viewReport(taskId);
... ... @@ -5581,11 +5929,13 @@ function getConsoleContainer() {
if (eventType === 'status' && task) {
if (task.status === 'running') {
resetReportLogsForNewTask(task.task_id, '收到流式状态事件,已重置日志');
setGraphMiniWaiting(task.task_id);
}
updateTaskProgressStatus(task);
reportTaskId = task.status === 'running' ? task.task_id : null;
if (task.status === 'completed') {
lastCompletedReportTask = task;
graphMiniPreferredTaskId = task.task_id;
setGenerateButtonState(false);
} else if (task.status === 'running') {
setGenerateButtonState(true);
... ... @@ -5677,6 +6027,8 @@ function getConsoleContainer() {
if (task) {
lastCompletedReportTask = task;
updateDownloadButtonState(task);
graphMiniPreferredTaskId = task.task_id;
refreshGraphMini(task.task_id, true);
}
if (eventData.task_id && !reportAutoPreviewLoaded) {
viewReport(eventData.task_id);
... ...