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 20:24:40 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
b86e7032a193c75d1cbebe23da960a1367c747c1
b86e7032
1 parent
4f2d767b
Fixed the issue of GraphRAG not being read in full-screen mode
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
94 additions
and
25 deletions
ReportEngine/agent.py
ReportEngine/flask_interface.py
ReportEngine/graphrag/graph_storage.py
templates/graph_viewer.html
templates/index.html
ReportEngine/agent.py
View file @
b86e703
...
...
@@ -420,9 +420,16 @@ class ReportAgent:
error_log_dir
=
self
.
config
.
JSON_ERROR_LOG_DIR
,
)
def
generate_report
(
self
,
query
:
str
,
reports
:
List
[
Any
],
forum_logs
:
str
=
""
,
custom_template
:
str
=
""
,
save_report
:
bool
=
True
,
stream_handler
:
Optional
[
Callable
[[
str
,
Dict
[
str
,
Any
]],
None
]]
=
None
)
->
str
:
def
generate_report
(
self
,
query
:
str
,
reports
:
List
[
Any
],
forum_logs
:
str
=
""
,
custom_template
:
str
=
""
,
save_report
:
bool
=
True
,
stream_handler
:
Optional
[
Callable
[[
str
,
Dict
[
str
,
Any
]],
None
]]
=
None
,
report_id
:
Optional
[
str
]
=
None
)
->
str
:
"""
生成综合报告(章节JSON → IR → HTML)。
...
...
@@ -440,6 +447,7 @@ class ReportAgent:
custom_template: 用户指定的Markdown模板,如为空则交由模板节点自动挑选。
save_report: 是否在生成后自动将HTML、IR与状态写入磁盘。
stream_handler: 可选的流式事件回调,接收阶段标签与payload,用于UI实时展示。
report_id: 外部透传的任务ID,用于与前端/SSE保持一致并复用同一个目录。
返回:
dict: 包含 `html_content` 以及HTML/IR/状态文件路径的字典;若 `save_report=False` 则仅返回HTML字符串。
...
...
@@ -448,7 +456,16 @@ class ReportAgent:
Exception: 任一子节点或渲染阶段失败时抛出,外层调用方负责兜底。
"""
start_time
=
datetime
.
now
()
report_id
=
f
"report-{uuid4().hex[:8]}"
report_id_value
=
(
report_id
or
""
)
.
strip
()
if
report_id_value
:
# 仅保留易读且安全的字符,确保可直接作为目录名复用
report_id_value
=
""
.
join
(
c
if
c
.
isalnum
()
or
c
in
(
"-"
,
"_"
)
else
"_"
for
c
in
report_id_value
)
or
f
"report-{uuid4().hex[:8]}"
else
:
report_id_value
=
f
"report-{uuid4().hex[:8]}"
report_id
=
report_id_value
self
.
state
.
task_id
=
report_id
self
.
state
.
query
=
query
self
.
state
.
metadata
.
query
=
query
...
...
ReportEngine/flask_interface.py
View file @
b86e703
...
...
@@ -489,7 +489,8 @@ def run_report_generation(task: ReportTask, query: str, custom_template: str = "
forum_logs
=
content
[
'forum_logs'
],
custom_template
=
custom_template
,
save_report
=
True
,
stream_handler
=
stream_handler
stream_handler
=
stream_handler
,
report_id
=
task
.
task_id
)
break
except
ChapterJsonParseError
as
err
:
...
...
ReportEngine/graphrag/graph_storage.py
View file @
b86e703
...
...
@@ -293,6 +293,28 @@ class GraphStorage:
"""图谱存储管理器"""
FILENAME
=
"graphrag.json"
@staticmethod
def
_normalize_identifier
(
value
:
str
)
->
str
:
"""统一规约ID,去除分隔符便于模糊匹配。"""
return
re
.
sub
(
r'[^a-zA-Z0-9]'
,
''
,
str
(
value
or
''
))
.
lower
()
def
_graph_file_matches
(
self
,
graph_path
:
Path
,
normalized_target
:
str
)
->
bool
:
"""检查图文件中的 task_id/report_id 是否与目标匹配。"""
try
:
with
open
(
graph_path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
data
=
json
.
load
(
f
)
candidates
=
[
data
.
get
(
'task_id'
),
data
.
get
(
'report_id'
),
data
.
get
(
'metadata'
,
{})
.
get
(
'report_id'
)
if
isinstance
(
data
.
get
(
'metadata'
),
dict
)
else
None
]
for
candidate
in
candidates
:
if
candidate
and
self
.
_normalize_identifier
(
candidate
)
==
normalized_target
:
return
True
except
Exception
:
return
False
return
False
@property
def
chapters_dir
(
self
)
->
Path
:
...
...
@@ -377,17 +399,17 @@ class GraphStorage:
chapters_dir
=
self
.
chapters_dir
if
not
chapters_dir
.
exists
():
return
None
# 兼容不同分隔符(report-xxx 与 report_xxx)以及简化匹配
if
not
report_id
:
return
None
normalized_target
=
re
.
sub
(
r'[-_]'
,
''
,
str
(
report_id
))
.
lower
()
# 兼容不同分隔符(report-xxx 与 report_xxx)以及简化匹配
normalized_target
=
self
.
_normalize_identifier
(
report_id
)
alt_targets
=
{
report_id
,
str
(
report_id
)
,
str
(
report_id
)
.
replace
(
'_'
,
'-'
),
str
(
report_id
)
.
replace
(
'-'
,
'_'
),
normalized_target
,
}
fallback_match
:
Optional
[
Path
]
=
None
# 查找匹配报告ID的目录
for
run_dir
in
chapters_dir
.
iterdir
():
...
...
@@ -395,7 +417,7 @@ class GraphStorage:
continue
name
=
run_dir
.
name
normalized_name
=
re
.
sub
(
r'[-_]'
,
''
,
name
)
.
lower
(
)
normalized_name
=
self
.
_normalize_identifier
(
name
)
# 检查目录名是否包含报告ID或归一化后相等
if
(
...
...
@@ -405,8 +427,14 @@ class GraphStorage:
graph_path
=
run_dir
/
self
.
FILENAME
if
graph_path
.
exists
():
return
graph_path
# 若目录名不匹配,则尝试读取文件内容比对 task_id/report_id
graph_path
=
run_dir
/
self
.
FILENAME
if
graph_path
.
exists
()
and
not
fallback_match
:
if
self
.
_graph_file_matches
(
graph_path
,
normalized_target
):
fallback_match
=
graph_path
return
None
return
fallback_match
def
find_latest_graph
(
self
)
->
Optional
[
Path
]:
"""
...
...
templates/graph_viewer.html
View file @
b86e703
...
...
@@ -633,21 +633,34 @@
// 加载图谱数据
async
function
loadGraphData
(
options
=
{})
{
const
{
fromPoll
=
false
,
fromManual
=
false
}
=
options
;
const
{
fromPoll
=
false
,
fromManual
=
false
,
allowFallback
=
true
}
=
options
;
// 仅在首次或未加载成功时展示大遮罩
if
(
!
graphReady
||
!
fromPoll
)
{
showLoading
(
true
);
}
try
{
const
url
=
reportId
?
`
/
api
/
graph
/
$
{
reportId
}
`
:
'/api/graph/latest'
;
const
response
=
await
fetch
(
url
,
{
cache
:
'no-store'
});
const
data
=
await
response
.
json
();
const
fetchGraph
=
async
(
id
)
=>
{
const
url
=
id
?
`
/
api
/
graph
/
$
{
id
}
`
:
'/api/graph/latest'
;
const
response
=
await
fetch
(
url
,
{
cache
:
'no-store'
});
const
data
=
await
response
.
json
();
if
(
!
response
.
ok
||
!
data
.
success
||
!
data
.
graph
)
{
return
null
;
}
return
data
;
};
let
data
=
await
fetchGraph
(
reportId
);
let
usedFallback
=
false
;
if
((
!
data
||
!
data
.
graph
)
&&
allowFallback
)
{
data
=
await
fetchGraph
(
null
);
usedFallback
=
!!
(
data
&&
data
.
graph
);
}
if
(
data
.
success
&&
data
.
graph
)
{
if
(
data
&&
data
.
graph
)
{
if
(
data
.
report_id
)
{
reportId
=
data
.
report_id
;
}
allNodes
=
data
.
graph
.
nodes
;
allEdges
=
data
.
graph
.
edges
;
...
...
@@ -664,7 +677,7 @@
graphReady
=
true
;
stopGraphPolling
();
if
(
fromManual
)
{
showToast
(
'已刷新最新图谱'
);
showToast
(
usedFallback
?
'未找到指定图谱,已切换至最新版本'
:
'已刷新最新图谱'
);
}
}
else
{
if
(
!
graphReady
)
{
...
...
templates/index.html
View file @
b86e703
...
...
@@ -5586,14 +5586,24 @@ function getConsoleContainer() {
try {
const targetTaskId = graphPanelTaskId || (lastCompletedReportTask ? lastCompletedReportTask.task_id : null);
let data = await fetchGraphData(targetTaskId);
if (!data && allowFallback && targetTaskId) {
let data = null;
let usedFallback = false;
if (targetTaskId) {
data = await fetchGraphData(targetTaskId);
}
if ((!data || !data.graph) && allowFallback) {
data = await fetchGraphData(null);
usedFallback = !!(data && data.graph);
}
if (data && data.graph) {
graphPanelTaskId = targetTaskId || data.report_id || graphPanelTaskId;
const resolvedId = data.report_id || targetTaskId || graphPanelTaskId;
if (resolvedId) {
graphPanelTaskId = resolvedId;
}
renderGraphPanel(data.graph);
setGraphPanelState('
ready
');
setGraphPanelState('
ready
'
, usedFallback ? '
已切换到最新可用的知识图谱
' : ''
);
} else {
updateGraphStats({ nodes: [], edges: [] });
setGraphPanelState('
idle
', '
暂未找到知识图谱,请生成报告后刷新
');
...
...
Please
register
or
login
to post a comment