Toggle navigation
Toggle navigation
This project
Loading...
Sign in
万朱浩
/
Venue-Ops
Go to a project
Toggle navigation
Projects
Groups
Snippets
Help
Toggle navigation pinning
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
马一丁
2025-12-19 12:24:14 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
c8980c88a09a25923651c7e2a610fa44252716ab
c8980c88
1 parent
b3b0ff16
Beautify the front-end page display of GraphRAG
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
492 additions
and
167 deletions
templates/index.html
templates/index.html
View file @
c8980c8
...
...
@@ -1486,47 +1486,50 @@
color
:
#666
;
font-size
:
14px
;
}
/* 知识图谱迷你面板 */
.graph-mini-panel
{
/* 知识图谱面板(与左侧内容同宽) */
.graph-panel
{
width
:
100%
;
border
:
2px
solid
#000000
;
background-color
:
#f7f7f7
;
padding
:
10px
12px
;
width
:
280px
;
align-self
:
flex-start
;
background-color
:
#ffffff
;
display
:
none
;
box-shadow
:
4px
4px
0
#0000001
a
;
flex-direction
:
column
;
gap
:
8px
;
}
.graph-
mini-panel.collapsed
.graph-mini
-body
{
.graph-
panel.collapsed
.graph-panel
-body
{
display
:
none
;
}
.graph-
mini
-header
{
.graph-
panel
-header
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
gap
:
8px
;
margin-bottom
:
8px
;
align-items
:
center
;
padding
:
12px
14px
;
border-bottom
:
2px
solid
#000000
;
background-color
:
#f8f9fa
;
gap
:
10px
;
}
.graph-
mini
-title
{
.graph-
panel
-title
{
font-weight
:
bold
;
font-size
:
1
4
px
;
font-size
:
1
5
px
;
}
.graph-
mini
-subtitle
{
.graph-
panel
-subtitle
{
font-size
:
12px
;
color
:
#555555
;
line-height
:
1.2
;
color
:
#555
;
margin-top
:
4px
;
line-height
:
1.3
;
}
.graph-
mini
-actions
{
.graph-
panel
-actions
{
display
:
flex
;
gap
:
6px
;
align-items
:
center
;
gap
:
8px
;
flex-wrap
:
wrap
;
}
.graph-
mini
-button
{
.graph-
panel
-button
{
border
:
1px
solid
#000000
;
background-color
:
#ffffff
;
padding
:
6px
10px
;
...
...
@@ -1536,40 +1539,143 @@
transition
:
background-color
0.2s
ease
,
color
0.2s
ease
;
}
.graph-
mini
-button
:hover
{
.graph-
panel
-button
:hover
{
background-color
:
#000000
;
color
:
#ffffff
;
}
.graph-mini-body
{
.graph-status-chip
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
6px
;
padding
:
6px
10px
;
border
:
1px
solid
#000000
;
font-size
:
12px
;
font-weight
:
bold
;
}
.graph-status-chip.idle
{
background-color
:
#fff7ed
;
color
:
#b45309
;
}
.graph-status-chip.loading
{
background-color
:
#fef3c7
;
color
:
#92400e
;
}
.graph-status-chip.ready
{
background-color
:
#e8f5e9
;
color
:
#2f855a
;
}
.graph-status-chip.error
{
background-color
:
#fef2f2
;
color
:
#b91c1c
;
}
.graph-panel-body
{
padding
:
10px
12px
14px
12px
;
display
:
flex
;
flex-direction
:
column
;
gap
:
10px
;
}
.graph-panel-toolbar
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
10px
;
align-items
:
center
;
justify-content
:
space-between
;
}
.graph-toolbar-left
{
display
:
flex
;
flex-wrap
:
wrap
;
align-items
:
center
;
gap
:
10px
;
}
.graph-stats
{
display
:
flex
;
gap
:
10px
;
font-size
:
12px
;
color
:
#333
;
}
.graph-stats
.stat-label
{
font-weight
:
bold
;
margin-right
:
4px
;
}
.graph-filter-group
{
display
:
flex
;
gap
:
8px
;
flex-wrap
:
wrap
;
}
.graph-filter-item
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
6px
;
padding
:
6px
8px
;
border
:
1px
solid
#000000
;
background-color
:
#ffffff
;
cursor
:
pointer
;
font-size
:
12px
;
user-select
:
none
;
}
.graph-filter-item
input
{
accent-color
:
#000000
;
}
.graph-filter-count
{
color
:
#666666
;
font-size
:
11px
;
}
.graph-search
{
display
:
flex
;
gap
:
8px
;
align-items
:
center
;
}
.graph-search
input
{
padding
:
8px
10px
;
border
:
1px
solid
#000000
;
min-width
:
200px
;
}
.graph-panel-canvas
{
position
:
relative
;
height
:
240px
;
width
:
240px
;
height
:
360px
;
width
:
100%
;
border
:
1px
dashed
#000000
;
background-color
:
#ffffff
;
overflow
:
hidden
;
}
.graph-
mini
-placeholder
{
.graph-
panel
-placeholder
{
position
:
absolute
;
inset
:
0
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
gap
:
6px
;
text-align
:
center
;
padding
:
10px
;
font-size
:
13px
;
color
:
#555
;
background-color
:
#ffffff
;
z-index
:
2
;
}
.graph-panel-placeholder.error
{
color
:
#b42318
;
}
.graph-panel-detail
{
border
:
1px
solid
#e5e5e5
;
padding
:
8px
;
background-color
:
#fafafa
;
font-size
:
12px
;
color
:
#666666
;
line-height
:
1.5
;
}
.graph-mini-placeholder.error
{
color
:
#b42318
;
.graph-panel-detail
.detail-title
{
font-weight
:
bold
;
margin-bottom
:
4px
;
}
.graph-mini-canvas
{
height
:
100%
;
width
:
100%
;
display
:
none
;
.graph-panel-detail
.detail-meta
{
color
:
#555
;
margin-bottom
:
6px
;
}
</style>
...
...
@@ -1736,9 +1842,12 @@
let
configDirty
=
false
;
let
graphragEnabled
=
false
;
let
graphragSettingLoaded
=
false
;
let
graphMiniNetwork
=
null
;
let
graphMiniPreferredTaskId
=
null
;
let
graphMiniLoading
=
false
;
let
graphPanelNetwork
=
null
;
let
graphPanelData
=
{
nodes
:
[],
edges
:
[]
};
let
graphPanelFilters
=
new
Set
([
'topic'
,
'engine'
,
'section'
,
'search_query'
,
'source'
]);
let
graphPanelTaskId
=
null
;
let
graphPanelState
=
'idle'
;
let
graphPanelLoading
=
false
;
let
configAutoRefreshTimer
=
null
;
let
systemStarted
=
false
;
let
systemStarting
=
false
;
...
...
@@ -4882,21 +4991,58 @@ function getConsoleContainer() {
<!--
任务进度区域
-->
<
div
id
=
"taskProgressArea"
><
/div
>
<!--
知识图谱迷你预览(
GraphRAG
开启时显示)
-->
<
div
class
=
"graph-mini-panel"
id
=
"graphMiniPanel"
>
<
div
class
=
"graph-mini-header"
>
<!--
知识图谱面板(
GraphRAG
开启时显示)
-->
<
div
class
=
"graph-panel"
id
=
"graphPanel"
>
<
div
class
=
"graph-panel-header"
>
<
div
>
<
div
class
=
"graph-mini-title"
>
知识图谱
<
/div
>
<
div
class
=
"graph-mini-subtitle"
>
GraphRAG
生成概览
<
/div
>
<
div
class
=
"graph-panel-title"
>
知识图谱
<
/div
>
<
div
class
=
"graph-panel-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
class
=
"graph-panel-actions"
>
<
span
class
=
"graph-status-chip idle"
id
=
"graphStatusChip"
>
未生成
<
/span
>
<
button
class
=
"graph-panel-button"
id
=
"graphFullBtn"
title
=
"在新标签页查看"
>
全屏
<
/button
>
<
button
class
=
"graph-panel-button"
id
=
"graphRefreshBtn"
title
=
"刷新知识图谱"
>
刷新
<
/button
>
<
button
class
=
"graph-panel-button"
id
=
"graphCollapseBtn"
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
class
=
"graph-panel-body"
id
=
"graphPanelBody"
>
<
div
class
=
"graph-panel-toolbar"
>
<
div
class
=
"graph-toolbar-left"
>
<
div
class
=
"graph-stats"
>
<
span
><
span
class
=
"stat-label"
>
节点
<
/span><span id="graphNodeCount">0</
span
><
/span
>
<
span
><
span
class
=
"stat-label"
>
关系
<
/span><span id="graphEdgeCount">0</
span
><
/span
>
<
/div
>
<
div
class
=
"graph-filter-group"
id
=
"graphFilterGroup"
>
<
label
class
=
"graph-filter-item"
>
<
input
type
=
"checkbox"
data
-
type
=
"topic"
checked
>
主题
<
span
class
=
"graph-filter-count"
data
-
type
-
count
=
"topic"
>
(
0
)
<
/span
>
<
/label
>
<
label
class
=
"graph-filter-item"
>
<
input
type
=
"checkbox"
data
-
type
=
"engine"
checked
>
引擎
<
span
class
=
"graph-filter-count"
data
-
type
-
count
=
"engine"
>
(
0
)
<
/span
>
<
/label
>
<
label
class
=
"graph-filter-item"
>
<
input
type
=
"checkbox"
data
-
type
=
"section"
checked
>
报告段落
<
span
class
=
"graph-filter-count"
data
-
type
-
count
=
"section"
>
(
0
)
<
/span
>
<
/label
>
<
label
class
=
"graph-filter-item"
>
<
input
type
=
"checkbox"
data
-
type
=
"search_query"
checked
>
搜索词
<
span
class
=
"graph-filter-count"
data
-
type
-
count
=
"search_query"
>
(
0
)
<
/span
>
<
/label
>
<
label
class
=
"graph-filter-item"
>
<
input
type
=
"checkbox"
data
-
type
=
"source"
checked
>
数据来源
<
span
class
=
"graph-filter-count"
data
-
type
-
count
=
"source"
>
(
0
)
<
/span
>
<
/label
>
<
/div
>
<
/div
>
<
div
class
=
"graph-search"
>
<
input
type
=
"text"
id
=
"graphSearchInput"
placeholder
=
"搜索节点..."
>
<
button
class
=
"graph-panel-button"
id
=
"graphFitBtn"
>
适应
<
/button
>
<
/div
>
<
/div
>
<
div
class
=
"graph-panel-canvas"
id
=
"graphPanelCanvas"
>
<
div
class
=
"graph-panel-placeholder"
id
=
"graphPanelPlaceholder"
>
等待图谱生成
...
<
/div
>
<
/div
>
<
div
class
=
"graph-panel-detail"
id
=
"graphPanelDetail"
style
=
"display: none;"
>
<
div
class
=
"detail-title"
id
=
"graphDetailTitle"
><
/div
>
<
div
class
=
"detail-meta"
id
=
"graphDetailMeta"
><
/div
>
<
div
class
=
"detail-props"
id
=
"graphDetailProps"
><
/div
>
<
/div
>
<
/div
>
<
/div
>
...
...
@@ -4910,7 +5056,7 @@ function getConsoleContainer() {
reportContent
.
innerHTML
=
interfaceHTML
;
initializeReportControls
();
initializeGraph
Mini
Panel
(
statusData
);
initializeGraphPanel
(
statusData
);
resetReportStreamOutput
(
'等待新的Report任务启动...'
);
updateReportStreamStatus
(
'idle'
);
...
...
@@ -4942,8 +5088,8 @@ function getConsoleContainer() {
}
}
async
function
initializeGraphMiniPanel
(
statusData
)
{
const
panel
=
document
.
getElementById
(
'graphMiniPanel'
);
async
function
initializeGraphPanel
(
statusData
)
{
const
panel
=
document
.
getElementById
(
'graphPanel'
);
if
(
!
panel
)
return
;
const
enabled
=
await
ensureGraphragSetting
();
...
...
@@ -4952,115 +5098,223 @@ function getConsoleContainer() {
return
;
}
panel
.
style
.
display
=
'block'
;
bindGraphMiniEvents
();
panel
.
style
.
display
=
'flex'
;
bindGraphPanelEvents
();
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
;
}
const
currentTask
=
statusData
&&
statusData
.
current_task
?
statusData
.
current_task
:
null
;
graphPanelTaskId
=
currentTask
?.
task_id
||
(
lastCompletedReportTask
?
lastCompletedReportTask
.
task_id
:
null
);
if
(
currentTaskStatus
===
'running'
)
{
setGraphMiniWaiting
(
currentTaskId
);
if
(
currentTask
&&
currentTask
.
status
===
'running'
)
{
setGraphPanelAwaiting
(
graphPanelTaskId
);
return
;
}
if
(
panel
.
classList
.
contains
(
'collapsed'
))
{
setGraphMiniPlaceholder
(
'展开以查看知识图谱'
);
return
;
refreshGraphPanel
(
graphPanelTaskId
,
true
);
}
function
bindGraphPanelEvents
()
{
const
refreshBtn
=
document
.
getElementById
(
'graphRefreshBtn'
);
const
collapseBtn
=
document
.
getElementById
(
'graphCollapseBtn'
);
const
fullBtn
=
document
.
getElementById
(
'graphFullBtn'
);
const
fitBtn
=
document
.
getElementById
(
'graphFitBtn'
);
const
searchInput
=
document
.
getElementById
(
'graphSearchInput'
);
const
filterGroup
=
document
.
getElementById
(
'graphFilterGroup'
);
if
(
refreshBtn
&&
!
refreshBtn
.
dataset
.
bound
)
{
refreshBtn
.
dataset
.
bound
=
'true'
;
refreshBtn
.
addEventListener
(
'click'
,
()
=>
refreshGraphPanel
(
graphPanelTaskId
,
true
));
}
if
(
collapseBtn
&&
!
collapseBtn
.
dataset
.
bound
)
{
collapseBtn
.
dataset
.
bound
=
'true'
;
collapseBtn
.
addEventListener
(
'click'
,
()
=>
{
const
panel
=
document
.
getElementById
(
'graphPanel'
);
if
(
!
panel
)
return
;
const
collapsed
=
panel
.
classList
.
toggle
(
'collapsed'
);
collapseBtn
.
textContent
=
collapsed
?
'展开'
:
'收起'
;
if
(
!
collapsed
)
{
refreshGraphPanel
(
graphPanelTaskId
,
false
);
}
else
{
setGraphPanelPlaceholder
(
'已折叠,展开后可查看知识图谱'
);
}
});
}
refreshGraphMini
(
graphMiniPreferredTaskId
,
true
);
}
if
(
fullBtn
&&
!
fullBtn
.
dataset
.
bound
)
{
fullBtn
.
dataset
.
bound
=
'true'
;
fullBtn
.
addEventListener
(
'click'
,
()
=>
{
const
target
=
graphPanelTaskId
||
(
lastCompletedReportTask
?
lastCompletedReportTask
.
task_id
:
null
);
const
url
=
target
?
`
/
graph
-
viewer
/
$
{
target
}
`
:
'/graph-viewer'
;
window
.
open
(
url
,
'_blank'
);
});
}
function
bindGraphMiniEvents
()
{
const
toggleBtn
=
document
.
getElementById
(
'graphMiniToggle'
);
const
refreshBtn
=
document
.
getElementById
(
'graphMiniRefresh'
);
if
(
fitBtn
&&
!
fitBtn
.
dataset
.
bound
)
{
fitBtn
.
dataset
.
bound
=
'true'
;
fitBtn
.
addEventListener
(
'click'
,
()
=>
{
if
(
graphPanelNetwork
)
{
graphPanelNetwork
.
fit
({
animation
:
{
duration
:
300
,
easing
:
'easeInOutQuad'
}
});
}
});
}
if
(
toggleBtn
&&
!
toggleBtn
.
dataset
.
bound
)
{
toggleBtn
.
dataset
.
bound
=
'true'
;
toggleBtn
.
addEventListener
(
'click'
,
toggleGraphMiniPanel
);
if
(
searchInput
&&
!
searchInput
.
dataset
.
bound
)
{
searchInput
.
dataset
.
bound
=
'true'
;
searchInput
.
addEventListener
(
'keydown'
,
(
e
)
=>
{
if
(
e
.
key
===
'Enter'
)
{
focusGraphByKeyword
(
searchInput
.
value
);
}
});
searchInput
.
addEventListener
(
'input'
,
()
=>
{
if
(
!
searchInput
.
value
)
{
clearGraphSelection
();
}
});
}
if
(
refreshBtn
&&
!
refreshBtn
.
dataset
.
bound
)
{
refreshBtn
.
dataset
.
bound
=
'true'
;
refreshBtn
.
addEventListener
(
'click'
,
()
=>
refreshGraphMini
(
graphMiniPreferredTaskId
,
true
));
if
(
filterGroup
&&
!
filterGroup
.
dataset
.
bound
)
{
filterGroup
.
dataset
.
bound
=
'true'
;
filterGroup
.
addEventListener
(
'change'
,
(
event
)
=>
{
const
checkbox
=
event
.
target
.
closest
(
'input[type="checkbox"][data-type]'
);
if
(
!
checkbox
)
return
;
const
type
=
(
checkbox
.
dataset
.
type
||
''
).
toLowerCase
();
if
(
checkbox
.
checked
)
{
graphPanelFilters
.
add
(
type
);
}
else
{
graphPanelFilters
.
delete
(
type
);
}
renderGraphPanel
(
graphPanelData
,
false
);
});
}
}
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
setGraphPanelState
(
state
,
message
=
''
)
{
graphPanelState
=
state
;
const
chip
=
document
.
getElementById
(
'graphStatusChip'
);
if
(
!
chip
)
return
;
chip
.
classList
.
remove
(
'idle'
,
'loading'
,
'ready'
,
'error'
);
chip
.
classList
.
add
(
state
);
const
textMap
=
{
idle
:
'未生成'
,
loading
:
'正在生成'
,
ready
:
'已生成'
,
error
:
'加载失败'
};
chip
.
textContent
=
textMap
[
state
]
||
state
;
if
(
message
)
{
setGraphPanelPlaceholder
(
message
,
state
===
'error'
?
'error'
:
''
);
}
}
function
setGraphMiniPlaceholder
(
message
,
type
=
''
)
{
const
placeholder
=
document
.
getElementById
(
'graphMiniPlaceholder'
);
const
canvas
=
document
.
getElementById
(
'graphMiniCanvas'
);
if
(
!
placeholder
||
!
canvas
)
return
;
function
setGraphPanelPlaceholder
(
message
,
type
=
''
)
{
const
placeholder
=
document
.
getElementById
(
'graphPanelPlaceholder'
);
if
(
!
placeholder
)
return
;
placeholder
.
textContent
=
message
||
''
;
if
(
type
===
'error'
)
{
placeholder
.
classList
.
add
(
'error'
);
}
else
{
placeholder
.
classList
.
remove
(
'error'
);
}
placeholder
.
style
.
display
=
'flex'
;
canvas
.
style
.
display
=
'none'
;
placeholder
.
classList
.
toggle
(
'error'
,
type
===
'error'
);
placeholder
.
style
.
display
=
message
?
'flex'
:
'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
;
function
updateGraphStats
(
allData
,
filteredNodes
=
null
,
filteredEdges
=
null
)
{
const
nodeCountEl
=
document
.
getElementById
(
'graphNodeCount'
);
const
edgeCountEl
=
document
.
getElementById
(
'graphEdgeCount'
);
if
(
nodeCountEl
)
nodeCountEl
.
textContent
=
(
filteredNodes
||
allData
.
nodes
||
[]).
length
;
if
(
edgeCountEl
)
edgeCountEl
.
textContent
=
(
filteredEdges
||
allData
.
edges
||
[]).
length
;
const
typeCounts
=
{};
(
allData
.
nodes
||
[]).
forEach
(
node
=>
{
const
key
=
(
node
.
group
||
node
.
type
||
'other'
).
toLowerCase
();
typeCounts
[
key
]
=
(
typeCounts
[
key
]
||
0
)
+
1
;
});
document
.
querySelectorAll
(
'.graph-filter-count'
).
forEach
(
el
=>
{
const
t
=
el
.
dataset
.
typeCount
;
el
.
textContent
=
`
(
$
{
typeCounts
[
t
]
||
0
})
`
;
});
}
function
renderGraphPanel
(
graph
,
resetPlaceholder
=
true
)
{
const
panel
=
document
.
getElementById
(
'graphPanel'
);
const
canvasWrapper
=
document
.
getElementById
(
'graphPanelCanvas'
);
if
(
!
panel
||
!
canvasWrapper
)
return
;
graphPanelData
=
graph
||
{
nodes
:
[],
edges
:
[]
};
const
allowed
=
graphPanelFilters
;
const
nodes
=
(
graphPanelData
.
nodes
||
[]).
filter
(
node
=>
allowed
.
has
((
node
.
group
||
node
.
type
||
''
).
toLowerCase
()));
const
nodeIdSet
=
new
Set
(
nodes
.
map
(
n
=>
n
.
id
));
const
edges
=
(
graphPanelData
.
edges
||
[]).
filter
(
edge
=>
nodeIdSet
.
has
(
edge
.
from
)
&&
nodeIdSet
.
has
(
edge
.
to
));
updateGraphStats
(
graphPanelData
,
nodes
,
edges
);
const
placeholder
=
document
.
getElementById
(
'graphPanelPlaceholder'
);
if
(
!
nodes
.
length
)
{
setGraphPanelState
(
'ready'
,
'未找到匹配的节点,请调整筛选或稍后再试'
);
if
(
placeholder
)
placeholder
.
style
.
display
=
'flex'
;
if
(
graphPanelNetwork
)
{
graphPanelNetwork
.
destroy
();
graphPanelNetwork
=
null
;
}
return
data
;
}
catch
(
error
)
{
console
.
warn
(
'获取知识图谱失败:'
,
error
);
return
null
;
return
;
}
if
(
resetPlaceholder
&&
placeholder
)
{
placeholder
.
style
.
display
=
'none'
;
}
}
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'
);
updateGraphStats
({
nodes
:
[],
edges
:
[]
});
setGraphPanelState
(
'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
container
=
canvasWrapper
;
let
canvas
=
document
.
getElementById
(
'graphPanelCanvasInner'
);
if
(
!
canvas
)
{
canvas
=
document
.
createElement
(
'div'
);
canvas
.
id
=
'graphPanelCanvasInner'
;
canvas
.
style
.
height
=
'100%'
;
canvas
.
style
.
width
=
'100%'
;
container
.
appendChild
(
canvas
);
}
else
{
canvas
.
innerHTML
=
''
;
}
setGraphPanelPlaceholder
(
''
);
const
edges
=
new
vis
.
DataSet
((
graph
.
edges
||
[]).
map
(
edge
=>
({
const
colorMap
=
{
topic
:
'#ef4444'
,
engine
:
'#f59e0b'
,
section
:
'#10b981'
,
search_query
:
'#3b82f6'
,
source
:
'#8b5cf6'
};
const
nodeData
=
new
vis
.
DataSet
(
nodes
.
map
(
node
=>
{
const
nodeType
=
(
node
.
group
||
node
.
type
||
''
).
toLowerCase
();
return
{
id
:
node
.
id
,
label
:
node
.
label
||
node
.
id
,
group
:
node
.
group
||
node
.
type
,
title
:
node
.
title
||
''
,
properties
:
node
.
properties
||
{},
size
:
14
,
color
:
{
background
:
colorMap
[
nodeType
]
||
'#ffffff'
,
border
:
'#000000'
,
highlight
:
{
background
:
colorMap
[
nodeType
]
||
'#e0e0e0'
,
border
:
'#000000'
}
}
};
}));
const
edgeData
=
new
vis
.
DataSet
(
edges
.
map
(
edge
=>
({
from
:
edge
.
from
,
to
:
edge
.
to
,
label
:
edge
.
label
||
''
,
arrows
:
'to'
,
color
:
'#444444'
,
font
:
{
align
:
'top'
,
size
:
10
}
font
:
{
align
:
'top'
,
size
:
10
},
color
:
'#555555'
,
smooth
:
true
})));
const
options
=
{
...
...
@@ -5071,83 +5325,154 @@ function getConsoleContainer() {
shape
:
'dot'
,
borderWidth
:
1
,
font
:
{
size
:
12
},
scaling
:
{
min
:
8
,
max
:
18
}
scaling
:
{
min
:
8
,
max
:
20
}
},
edges
:
{
smooth
:
true
,
color
:
'#999999'
,
width
:
1
},
physics
:
{
stabilization
:
true
,
barnesHut
:
{
avoidOverlap
:
0.
5
,
springLength
:
8
0
,
springConstant
:
0.02
}
barnesHut
:
{
avoidOverlap
:
0.
3
,
springLength
:
9
0
,
springConstant
:
0.02
}
}
};
if
(
graphMiniNetwork
)
{
graphMiniNetwork
.
destroy
();
if
(
graphPanelNetwork
)
{
graphPanelNetwork
.
destroy
();
}
graphPanelNetwork
=
new
vis
.
Network
(
canvas
,
{
nodes
:
nodeData
,
edges
:
edgeData
},
options
);
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
);
graphPanelNetwork
.
fit
({
animation
:
{
duration
:
300
,
easing
:
'easeInOutQuad'
}
});
graphPanelNetwork
.
off
(
'stabilizationIterationsDone'
,
fitOnce
);
};
graphMiniNetwork
.
on
(
'stabilizationIterationsDone'
,
fitOnce
);
graphPanelNetwork
.
on
(
'stabilizationIterationsDone'
,
fitOnce
);
graphPanelNetwork
.
on
(
'selectNode'
,
params
=>
{
const
selectedId
=
params
.
nodes
[
0
];
const
selectedNode
=
nodes
.
find
(
n
=>
n
.
id
===
selectedId
);
showGraphDetail
(
selectedNode
);
});
graphPanelNetwork
.
on
(
'deselectNode'
,
hideGraphDetail
);
}
function
showGraphDetail
(
node
)
{
const
detail
=
document
.
getElementById
(
'graphPanelDetail'
);
const
title
=
document
.
getElementById
(
'graphDetailTitle'
);
const
meta
=
document
.
getElementById
(
'graphDetailMeta'
);
const
props
=
document
.
getElementById
(
'graphDetailProps'
);
if
(
!
detail
||
!
title
||
!
meta
||
!
props
)
return
;
if
(
!
node
)
{
hideGraphDetail
();
return
;
}
detail
.
style
.
display
=
'block'
;
title
.
textContent
=
node
.
label
||
node
.
id
;
meta
.
textContent
=
`类型
:
$
{(
node
.
group
||
node
.
type
||
'未知'
)}
`
;
const
properties
=
node
.
properties
||
{};
const
lines
=
Object
.
entries
(
properties
).
map
(([
k
,
v
])
=>
`
<
div
><
strong
>
$
{
k
}:
<
/strong> ${v}</
div
>
`
);
props
.
innerHTML
=
lines
.
length
?
lines
.
join
(
''
)
:
'暂无更多属性'
;
}
function
hideGraphDetail
()
{
const
detail
=
document
.
getElementById
(
'graphPanelDetail'
);
if
(
detail
)
{
detail
.
style
.
display
=
'none'
;
}
}
async
function
refreshGraphMini
(
taskId
=
null
,
allowFallback
=
true
)
{
const
panel
=
document
.
getElementById
(
'graphMiniPanel'
);
function
focusGraphByKeyword
(
keyword
)
{
if
(
!
graphPanelNetwork
||
!
keyword
)
return
;
const
lower
=
keyword
.
toLowerCase
();
const
nodes
=
graphPanelNetwork
.
body
.
data
.
nodes
.
get
();
const
matched
=
nodes
.
filter
(
n
=>
(
n
.
label
||
''
).
toLowerCase
().
includes
(
lower
));
if
(
matched
.
length
)
{
const
ids
=
matched
.
map
(
n
=>
n
.
id
);
graphPanelNetwork
.
selectNodes
(
ids
);
graphPanelNetwork
.
focus
(
ids
[
0
],
{
scale
:
1
,
animation
:
{
duration
:
300
,
easing
:
'easeInOutQuad'
}
});
showGraphDetail
(
matched
[
0
]);
}
}
function
clearGraphSelection
()
{
if
(
graphPanelNetwork
)
{
graphPanelNetwork
.
unselectAll
();
}
hideGraphDetail
();
}
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
;
}
}
async
function
refreshGraphPanel
(
taskId
=
null
,
allowFallback
=
true
)
{
const
panel
=
document
.
getElementById
(
'graphPanel'
);
if
(
!
panel
)
return
;
const
enabled
=
await
ensureGraphragSetting
();
if
(
!
enabled
)
{
panel
.
style
.
display
=
'none'
;
return
;
}
panel
.
style
.
display
=
'flex'
;
bindGraphPanelEvents
();
if
(
panel
.
classList
.
contains
(
'collapsed'
))
{
return
;
}
bindGraphMiniEvents
();
panel
.
style
.
display
=
'block'
;
if
(
taskId
)
{
graph
MiniPreferred
TaskId
=
taskId
;
graph
Panel
TaskId
=
taskId
;
}
if
(
graphMiniLoading
)
{
return
;
}
if
(
graphPanelLoading
)
return
;
graphPanelLoading
=
true
;
setGraphPanelState
(
'loading'
,
'正在加载知识图谱...'
);
const
targetTaskId
=
taskId
||
graphMiniPreferredTaskId
||
(
lastCompletedReportTask
?
lastCompletedReportTask
.
task_id
:
null
);
setGraphMiniPlaceholder
(
'加载知识图谱...'
);
graphMiniLoading
=
true
;
try
{
const
targetTaskId
=
graphPanelTaskId
||
(
lastCompletedReportTask
?
lastCompletedReportTask
.
task_id
:
null
);
let
data
=
await
fetchGraphData
(
targetTaskId
);
if
(
!
data
&&
allowFallback
&&
targetTaskId
)
{
data
=
await
fetchGraphData
(
null
);
}
if
(
data
&&
data
.
graph
)
{
renderGraphMini
(
data
.
graph
);
graphPanelTaskId
=
targetTaskId
||
data
.
report_id
||
graphPanelTaskId
;
renderGraphPanel
(
data
.
graph
);
setGraphPanelState
(
'ready'
);
}
else
{
setGraphMiniPlaceholder
(
'暂未找到知识图谱,请稍后点击刷新重试'
,
'error'
);
updateGraphStats
({
nodes
:
[],
edges
:
[]
});
setGraphPanelState
(
'idle'
,
'暂未找到知识图谱,请生成报告后刷新'
);
}
}
catch
(
error
)
{
console
.
warn
(
'刷新知识图谱失败:'
,
error
);
updateGraphStats
({
nodes
:
[],
edges
:
[]
});
setGraphPanelState
(
'error'
,
'加载知识图谱失败,请稍后重试'
);
}
finally
{
graph
Mini
Loading
=
false
;
graph
Panel
Loading
=
false
;
}
}
function
setGraphMiniWaiting
(
taskId
)
{
graphMiniPreferredTaskId
=
taskId
||
graphMiniPreferredTaskId
;
function
setGraphPanelAwaiting
(
taskId
)
{
graphPanelTaskId
=
taskId
||
graphPanelTaskId
;
ensureGraphragSetting
().
then
(
enabled
=>
{
if
(
!
enabled
)
return
;
const
panel
=
document
.
getElementById
(
'graph
Mini
Panel'
);
const
panel
=
document
.
getElementById
(
'graphPanel'
);
if
(
!
panel
)
return
;
panel
.
style
.
display
=
'block'
;
bindGraphMiniEvents
();
setGraphMiniPlaceholder
(
'等待图谱生成...'
);
panel
.
style
.
display
=
'flex'
;
bindGraphPanelEvents
();
updateGraphStats
({
nodes
:
[],
edges
:
[]
});
setGraphPanelState
(
'loading'
,
'报告生成中,知识图谱生成后自动刷新'
);
setGraphPanelPlaceholder
(
'正在生成知识图谱...'
);
});
}
...
...
@@ -5548,7 +5873,7 @@ function getConsoleContainer() {
if
(
data
.
success
)
{
reportTaskId
=
data
.
task_id
;
showMessage
(
'报告生成已启动'
,
'success'
);
setGraph
MiniW
aiting
(
reportTaskId
);
setGraph
PanelAw
aiting
(
reportTaskId
);
// 更新任务状态显示
updateTaskProgressStatus
({
...
...
@@ -5638,8 +5963,8 @@ function getConsoleContainer() {
if
(
data
.
task
.
status
===
'completed'
)
{
stopProgressPolling
();
showMessage
(
'报告生成完成!'
,
'success'
);
graphMiniPreferredTaskId
=
data
.
task
.
task_id
;
refreshGraphMini
(
data
.
task
.
task_id
,
true
);
graphPanelTaskId
=
data
.
task
.
task_id
;
refreshGraphPanel
(
data
.
task
.
task_id
,
true
);
// 自动显示报告
viewReport
(
taskId
);
...
...
@@ -5929,13 +6254,13 @@ function getConsoleContainer() {
if
(
eventType
===
'status'
&&
task
)
{
if
(
task
.
status
===
'running'
)
{
resetReportLogsForNewTask
(
task
.
task_id
,
'收到流式状态事件,已重置日志'
);
setGraph
MiniW
aiting
(
task
.
task_id
);
setGraph
PanelAw
aiting
(
task
.
task_id
);
}
updateTaskProgressStatus
(
task
);
reportTaskId
=
task
.
status
===
'running'
?
task
.
task_id
:
null
;
if
(
task
.
status
===
'completed'
)
{
lastCompletedReportTask
=
task
;
graph
MiniPreferred
TaskId
=
task
.
task_id
;
graph
Panel
TaskId
=
task
.
task_id
;
setGenerateButtonState
(
false
);
}
else
if
(
task
.
status
===
'running'
)
{
setGenerateButtonState
(
true
);
...
...
@@ -6027,8 +6352,8 @@ function getConsoleContainer() {
if
(
task
)
{
lastCompletedReportTask
=
task
;
updateDownloadButtonState
(
task
);
graphMiniPreferredTaskId
=
task
.
task_id
;
refreshGraphMini
(
task
.
task_id
,
true
);
graphPanelTaskId
=
task
.
task_id
;
refreshGraphPanel
(
task
.
task_id
,
true
);
}
if
(
eventData
.
task_id
&&
!
reportAutoPreviewLoaded
)
{
viewReport
(
eventData
.
task_id
);
...
...
Please
register
or
login
to post a comment