workflow_visualization.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. '''
  4. 知识获取工作流可视化
  5. 1. 读取知识获取工作流详细过程数据文件(参考 .cache/9f510b2a8348/execution_record.json)
  6. 2. 文件路径可设置,每个文件表示一个输入信息执行过程,可能有多个。多个文件路径硬编码在代码中,用list表示
  7. 3. 将知识获取工作流用html页面展示出来
  8. 4. HTML 文件输出路径在当前目录下,文件名称为 workflow_visualization_datetime.html
  9. '''
  10. import json
  11. import os
  12. from datetime import datetime
  13. from pathlib import Path
  14. # 硬编码的文件路径列表(相对于项目根目录)
  15. DATA_FILE_PATHS = [
  16. "../.cache/9f510b2a8348/execution_record.json",
  17. # 可以在这里添加更多文件路径
  18. ]
  19. def load_data_files(file_paths):
  20. """读取并解析JSON文件"""
  21. data_list = []
  22. script_dir = Path(__file__).parent
  23. for file_path in file_paths:
  24. # 将相对路径转换为绝对路径
  25. abs_path = (script_dir / file_path).resolve()
  26. if not abs_path.exists():
  27. print(f"警告: 文件不存在: {abs_path}")
  28. continue
  29. try:
  30. with open(abs_path, 'r', encoding='utf-8') as f:
  31. data = json.load(f)
  32. data_list.append(data)
  33. except (json.JSONDecodeError, IOError) as e:
  34. print(f"错误: 读取文件失败 {abs_path}: {e}")
  35. return data_list
  36. def parse_workflow_data(data):
  37. """解析工作流数据,提取关键信息"""
  38. workflow = {
  39. 'input': {},
  40. 'steps': [],
  41. 'output': {}
  42. }
  43. # 提取输入信息
  44. if 'input' in data:
  45. workflow['input'] = {
  46. 'question': data['input'].get('question', ''),
  47. 'post_info': data['input'].get('post_info', ''),
  48. 'persona_info': data['input'].get('persona_info', '')
  49. }
  50. # 提取执行流程
  51. if 'execution' in data and 'modules' in data['execution']:
  52. function_knowledge = data['execution']['modules'].get('function_knowledge', {})
  53. # 步骤1: 生成query
  54. if 'generate_query' in function_knowledge:
  55. generate_query = function_knowledge['generate_query']
  56. workflow['steps'].append({
  57. 'step': 'generate_query',
  58. 'name': '生成查询',
  59. 'query': generate_query.get('query', ''),
  60. 'prompt': generate_query.get('prompt', '')
  61. })
  62. # 步骤2: 选择工具
  63. if 'select_tool' in function_knowledge:
  64. select_tool = function_knowledge['select_tool']
  65. response = select_tool.get('response', {})
  66. workflow['steps'].append({
  67. 'step': 'select_tool',
  68. 'name': '选择工具',
  69. 'prompt': select_tool.get('prompt', ''),
  70. 'tool_name': response.get('工具名', ''),
  71. 'tool_id': response.get('工具调用ID', ''),
  72. 'tool_usage': response.get('使用方法', '')
  73. })
  74. # 步骤3: 提取参数
  75. if 'extract_params' in function_knowledge:
  76. extract_params = function_knowledge['extract_params']
  77. workflow['steps'].append({
  78. 'step': 'extract_params',
  79. 'name': '提取参数',
  80. 'prompt': extract_params.get('prompt', ''),
  81. 'params': extract_params.get('params', {})
  82. })
  83. # 步骤4: 执行工具
  84. if 'execute_tool' in function_knowledge:
  85. execute_tool = function_knowledge['execute_tool']
  86. workflow['steps'].append({
  87. 'step': 'execute_tool',
  88. 'name': '执行工具',
  89. 'response': execute_tool.get('response', '')
  90. })
  91. # 提取输出信息
  92. if 'output' in data:
  93. workflow['output'] = {
  94. 'result': data['output'].get('result', '')
  95. }
  96. return workflow
  97. def escape_html(text):
  98. """转义HTML特殊字符"""
  99. if not isinstance(text, str):
  100. text = str(text)
  101. return (text.replace('&', '&')
  102. .replace('<', '&lt;')
  103. .replace('>', '&gt;')
  104. .replace('"', '&quot;')
  105. .replace("'", '&#39;'))
  106. def format_json_for_display(obj):
  107. """格式化JSON对象用于显示"""
  108. if isinstance(obj, dict):
  109. return json.dumps(obj, ensure_ascii=False, indent=2)
  110. elif isinstance(obj, str):
  111. try:
  112. parsed = json.loads(obj)
  113. return json.dumps(parsed, ensure_ascii=False, indent=2)
  114. except (json.JSONDecodeError, ValueError):
  115. return obj
  116. return str(obj)
  117. def generate_html(workflows):
  118. """生成HTML页面"""
  119. html = '''<!DOCTYPE html>
  120. <html lang="zh-CN">
  121. <head>
  122. <meta charset="UTF-8">
  123. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  124. <title>知识获取工作流可视化</title>
  125. <style>
  126. * {
  127. margin: 0;
  128. padding: 0;
  129. box-sizing: border-box;
  130. }
  131. body {
  132. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
  133. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  134. color: #333;
  135. line-height: 1.6;
  136. min-height: 100vh;
  137. }
  138. .container {
  139. max-width: 1400px;
  140. margin: 0 auto;
  141. padding: 30px 20px;
  142. }
  143. h1 {
  144. text-align: center;
  145. color: white;
  146. margin-bottom: 40px;
  147. font-size: 32px;
  148. font-weight: 600;
  149. text-shadow: 0 2px 10px rgba(0,0,0,0.2);
  150. letter-spacing: 1px;
  151. }
  152. .tabs {
  153. display: flex;
  154. background: white;
  155. border-radius: 12px 12px 0 0;
  156. box-shadow: 0 4px 20px rgba(0,0,0,0.15);
  157. overflow-x: auto;
  158. padding: 5px;
  159. }
  160. .tab {
  161. padding: 16px 28px;
  162. cursor: pointer;
  163. border: none;
  164. background: transparent;
  165. color: #666;
  166. font-size: 14px;
  167. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  168. white-space: nowrap;
  169. border-radius: 8px;
  170. margin: 0 4px;
  171. position: relative;
  172. font-weight: 500;
  173. }
  174. .tab:hover {
  175. background: #f0f0f0;
  176. color: #333;
  177. }
  178. .tab.active {
  179. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  180. color: white;
  181. box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
  182. }
  183. .tab-content {
  184. display: none;
  185. background: white;
  186. padding: 40px;
  187. border-radius: 0 0 12px 12px;
  188. box-shadow: 0 4px 20px rgba(0,0,0,0.15);
  189. margin-bottom: 20px;
  190. animation: fadeIn 0.3s ease-in;
  191. }
  192. @keyframes fadeIn {
  193. from { opacity: 0; transform: translateY(10px); }
  194. to { opacity: 1; transform: translateY(0); }
  195. }
  196. .tab-content.active {
  197. display: block;
  198. }
  199. .input-section {
  200. background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
  201. padding: 28px;
  202. border-radius: 12px;
  203. margin-bottom: 35px;
  204. box-shadow: 0 4px 15px rgba(0,0,0,0.1);
  205. border: 1px solid rgba(255,255,255,0.5);
  206. }
  207. .input-section h3 {
  208. color: #2c3e50;
  209. margin-bottom: 20px;
  210. font-size: 20px;
  211. font-weight: 600;
  212. display: flex;
  213. align-items: center;
  214. gap: 10px;
  215. }
  216. .input-section h3::before {
  217. content: '📋';
  218. font-size: 24px;
  219. }
  220. .input-item {
  221. margin-bottom: 16px;
  222. padding: 12px;
  223. background: rgba(255,255,255,0.7);
  224. border-radius: 8px;
  225. transition: all 0.3s;
  226. }
  227. .input-item:hover {
  228. background: rgba(255,255,255,0.9);
  229. transform: translateX(5px);
  230. }
  231. .input-item strong {
  232. color: #495057;
  233. display: inline-block;
  234. width: 110px;
  235. font-weight: 600;
  236. }
  237. .input-item .placeholder {
  238. color: #999;
  239. font-style: italic;
  240. }
  241. .workflow {
  242. position: relative;
  243. }
  244. .workflow-step {
  245. background: white;
  246. border: 2px solid #e0e0e0;
  247. border-radius: 12px;
  248. margin-bottom: 25px;
  249. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  250. overflow: hidden;
  251. box-shadow: 0 2px 8px rgba(0,0,0,0.08);
  252. }
  253. .workflow-step.active {
  254. border-color: #667eea;
  255. box-shadow: 0 8px 24px rgba(102, 126, 234, 0.25);
  256. transform: translateY(-2px);
  257. }
  258. .step-header {
  259. padding: 20px 24px;
  260. background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
  261. cursor: pointer;
  262. display: flex;
  263. justify-content: space-between;
  264. align-items: center;
  265. user-select: none;
  266. transition: all 0.3s;
  267. }
  268. .step-header:hover {
  269. background: linear-gradient(135deg, #e9ecef 0%, #dee2e6 100%);
  270. }
  271. .workflow-step.active .step-header {
  272. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  273. color: white;
  274. }
  275. .workflow-step.active .step-name {
  276. color: white;
  277. }
  278. .workflow-step.active .step-toggle {
  279. color: white;
  280. }
  281. .step-title {
  282. display: flex;
  283. align-items: center;
  284. gap: 15px;
  285. }
  286. .step-number {
  287. display: inline-flex;
  288. align-items: center;
  289. justify-content: center;
  290. width: 36px;
  291. height: 36px;
  292. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  293. color: white;
  294. border-radius: 50%;
  295. font-size: 16px;
  296. font-weight: bold;
  297. box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
  298. }
  299. .workflow-step.active .step-number {
  300. background: white;
  301. color: #667eea;
  302. box-shadow: 0 4px 12px rgba(255,255,255,0.3);
  303. }
  304. .step-name {
  305. font-size: 18px;
  306. font-weight: 600;
  307. color: #2c3e50;
  308. }
  309. .step-toggle {
  310. color: #6c757d;
  311. font-size: 20px;
  312. transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  313. }
  314. .step-toggle.expanded {
  315. transform: rotate(180deg);
  316. }
  317. .step-content {
  318. padding: 0 20px;
  319. max-height: 0;
  320. overflow: hidden;
  321. transition: max-height 0.3s ease-out, padding 0.3s;
  322. }
  323. .step-content.expanded {
  324. max-height: 5000px;
  325. padding: 20px;
  326. }
  327. .step-detail {
  328. margin-bottom: 20px;
  329. }
  330. .step-detail-label {
  331. font-weight: 600;
  332. color: #495057;
  333. margin-bottom: 10px;
  334. display: block;
  335. font-size: 14px;
  336. text-transform: uppercase;
  337. letter-spacing: 0.5px;
  338. }
  339. .step-detail-content {
  340. background: #f8f9fa;
  341. padding: 16px;
  342. border-radius: 8px;
  343. border-left: 4px solid #667eea;
  344. font-size: 14px;
  345. line-height: 1.8;
  346. white-space: pre-wrap;
  347. word-wrap: break-word;
  348. max-height: 400px;
  349. overflow-y: auto;
  350. box-shadow: 0 2px 8px rgba(0,0,0,0.05);
  351. }
  352. .json-content {
  353. font-family: 'SF Mono', 'Monaco', 'Courier New', monospace;
  354. background: #1e1e1e;
  355. color: #d4d4d4;
  356. padding: 20px;
  357. border-radius: 8px;
  358. overflow-x: auto;
  359. border-left: 4px solid #667eea;
  360. box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  361. }
  362. .prompt-toggle-btn {
  363. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  364. color: white;
  365. border: none;
  366. padding: 10px 20px;
  367. border-radius: 6px;
  368. cursor: pointer;
  369. font-size: 13px;
  370. font-weight: 500;
  371. margin-top: 15px;
  372. transition: all 0.3s;
  373. box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
  374. }
  375. .prompt-toggle-btn:hover {
  376. transform: translateY(-2px);
  377. box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
  378. }
  379. .prompt-content {
  380. display: none;
  381. margin-top: 15px;
  382. padding: 16px;
  383. background: #fff3cd;
  384. border-radius: 8px;
  385. border-left: 4px solid #ffc107;
  386. font-size: 13px;
  387. line-height: 1.8;
  388. white-space: pre-wrap;
  389. word-wrap: break-word;
  390. max-height: 500px;
  391. overflow-y: auto;
  392. }
  393. .prompt-content.show {
  394. display: block;
  395. animation: slideDown 0.3s ease-out;
  396. }
  397. @keyframes slideDown {
  398. from {
  399. opacity: 0;
  400. max-height: 0;
  401. }
  402. to {
  403. opacity: 1;
  404. max-height: 500px;
  405. }
  406. }
  407. .output-section {
  408. background: linear-gradient(135deg, #e0f2fe 0%, #bae6fd 100%);
  409. padding: 28px;
  410. border-radius: 12px;
  411. margin-top: 35px;
  412. border-left: 4px solid #0ea5e9;
  413. box-shadow: 0 4px 15px rgba(14, 165, 233, 0.2);
  414. }
  415. .output-section h3 {
  416. color: #0369a1;
  417. margin-bottom: 20px;
  418. font-size: 20px;
  419. font-weight: 600;
  420. display: flex;
  421. align-items: center;
  422. gap: 10px;
  423. }
  424. .output-section h3::before {
  425. content: '✨';
  426. font-size: 24px;
  427. }
  428. .arrow {
  429. text-align: center;
  430. color: #667eea;
  431. font-size: 32px;
  432. margin: -15px 0;
  433. position: relative;
  434. z-index: 1;
  435. filter: drop-shadow(0 2px 4px rgba(102, 126, 234, 0.3));
  436. }
  437. .arrow::before {
  438. content: '↓';
  439. }
  440. @media (max-width: 768px) {
  441. .container {
  442. padding: 10px;
  443. }
  444. .tab {
  445. padding: 12px 15px;
  446. font-size: 13px;
  447. }
  448. .tab-content {
  449. padding: 20px;
  450. }
  451. }
  452. </style>
  453. </head>
  454. <body>
  455. <div class="container">
  456. <h1>知识获取工作流可视化</h1>
  457. <div class="tabs" id="tabs">
  458. '''
  459. # 生成Tab标签
  460. for i, workflow in enumerate(workflows):
  461. question = workflow['input'].get('question', f'问题 {i+1}')
  462. active_class = 'active' if i == 0 else ''
  463. html += f' <button class="tab {active_class}" onclick="switchTab({i})">{escape_html(question)}</button>\n'
  464. html += ' </div>\n'
  465. # 生成Tab内容
  466. for i, workflow in enumerate(workflows):
  467. active_class = 'active' if i == 0 else ''
  468. html += f' <div class="tab-content {active_class}" id="tab-{i}">\n'
  469. # 输入信息
  470. html += ' <div class="input-section">\n'
  471. html += ' <h3>输入信息</h3>\n'
  472. html += f' <div class="input-item"><strong>问题:</strong> {escape_html(workflow["input"].get("question", ""))}</div>\n'
  473. post_info = workflow['input'].get('post_info', '')
  474. post_info_display = escape_html(post_info) if post_info else '<span class="placeholder">(无)</span>'
  475. html += f' <div class="input-item"><strong>帖子信息:</strong> {post_info_display}</div>\n'
  476. persona_info = workflow['input'].get('persona_info', '')
  477. persona_info_display = escape_html(persona_info) if persona_info else '<span class="placeholder">(无)</span>'
  478. html += f' <div class="input-item"><strong>人设信息:</strong> {persona_info_display}</div>\n'
  479. html += ' </div>\n'
  480. # 工作流程
  481. html += ' <div class="workflow">\n'
  482. for j, step in enumerate(workflow['steps']):
  483. step_id = f"step-{i}-{j}"
  484. html += f' <div class="workflow-step" id="{step_id}">\n'
  485. html += ' <div class="step-header" onclick="toggleStep(\'' + step_id + '\')">\n'
  486. html += ' <div class="step-title">\n'
  487. html += f' <span class="step-number">{j+1}</span>\n'
  488. html += f' <span class="step-name">{escape_html(step["name"])}</span>\n'
  489. html += ' </div>\n'
  490. html += ' <span class="step-toggle">▼</span>\n'
  491. html += ' </div>\n'
  492. html += ' <div class="step-content" id="content-' + step_id + '">\n'
  493. # 根据步骤类型显示不同内容(prompt放在最后,默认隐藏)
  494. prompt_id = f"prompt-{step_id}"
  495. if step['step'] == 'generate_query':
  496. if step.get('query'):
  497. html += ' <div class="step-detail">\n'
  498. html += ' <span class="step-detail-label">生成的Query:</span>\n'
  499. html += f' <div class="step-detail-content">{escape_html(step["query"])}</div>\n'
  500. html += ' </div>\n'
  501. elif step['step'] == 'select_tool':
  502. if step.get('tool_name'):
  503. html += ' <div class="step-detail">\n'
  504. html += ' <span class="step-detail-label">工具名称:</span>\n'
  505. html += f' <div class="step-detail-content">{escape_html(step["tool_name"])}</div>\n'
  506. html += ' </div>\n'
  507. if step.get('tool_id'):
  508. html += ' <div class="step-detail">\n'
  509. html += ' <span class="step-detail-label">工具调用ID:</span>\n'
  510. html += f' <div class="step-detail-content">{escape_html(step["tool_id"])}</div>\n'
  511. html += ' </div>\n'
  512. if step.get('tool_usage'):
  513. html += ' <div class="step-detail">\n'
  514. html += ' <span class="step-detail-label">使用方法:</span>\n'
  515. html += f' <div class="step-detail-content">{escape_html(step["tool_usage"])}</div>\n'
  516. html += ' </div>\n'
  517. elif step['step'] == 'extract_params':
  518. if step.get('params'):
  519. html += ' <div class="step-detail">\n'
  520. html += ' <span class="step-detail-label">提取的参数:</span>\n'
  521. params_str = format_json_for_display(step['params'])
  522. html += f' <div class="step-detail-content json-content">{escape_html(params_str)}</div>\n'
  523. html += ' </div>\n'
  524. elif step['step'] == 'execute_tool':
  525. if step.get('response'):
  526. html += ' <div class="step-detail">\n'
  527. html += ' <span class="step-detail-label">执行结果:</span>\n'
  528. response_str = format_json_for_display(step['response'])
  529. html += f' <div class="step-detail-content json-content">{escape_html(response_str)}</div>\n'
  530. html += ' </div>\n'
  531. # Prompt放在最后,默认隐藏
  532. if step.get('prompt'):
  533. html += f' <button class="prompt-toggle-btn" onclick="togglePrompt(\'{prompt_id}\')">显示 Prompt</button>\n'
  534. html += f' <div class="prompt-content" id="{prompt_id}">{escape_html(step["prompt"])}</div>\n'
  535. html += ' </div>\n'
  536. html += ' </div>\n'
  537. # 添加箭头(除了最后一步)
  538. if j < len(workflow['steps']) - 1:
  539. html += ' <div class="arrow"></div>\n'
  540. html += ' </div>\n'
  541. # 输出信息
  542. if workflow['output'].get('result'):
  543. html += ' <div class="output-section">\n'
  544. html += ' <h3>最终输出</h3>\n'
  545. result_str = format_json_for_display(workflow['output']['result'])
  546. html += f' <div class="step-detail-content json-content">{escape_html(result_str)}</div>\n'
  547. html += ' </div>\n'
  548. html += ' </div>\n'
  549. html += ''' </div>
  550. <script>
  551. function switchTab(index) {
  552. // 隐藏所有tab内容
  553. const contents = document.querySelectorAll('.tab-content');
  554. contents.forEach(content => content.classList.remove('active'));
  555. // 移除所有tab的active状态
  556. const tabs = document.querySelectorAll('.tab');
  557. tabs.forEach(tab => tab.classList.remove('active'));
  558. // 显示选中的tab内容
  559. document.getElementById('tab-' + index).classList.add('active');
  560. tabs[index].classList.add('active');
  561. }
  562. function toggleStep(stepId) {
  563. const step = document.getElementById(stepId);
  564. const content = document.getElementById('content-' + stepId);
  565. const toggle = step.querySelector('.step-toggle');
  566. if (content.classList.contains('expanded')) {
  567. content.classList.remove('expanded');
  568. toggle.classList.remove('expanded');
  569. step.classList.remove('active');
  570. } else {
  571. content.classList.add('expanded');
  572. toggle.classList.add('expanded');
  573. step.classList.add('active');
  574. }
  575. }
  576. function togglePrompt(promptId) {
  577. const promptContent = document.getElementById(promptId);
  578. const btn = promptContent.previousElementSibling;
  579. if (promptContent.classList.contains('show')) {
  580. promptContent.classList.remove('show');
  581. btn.textContent = '显示 Prompt';
  582. } else {
  583. promptContent.classList.add('show');
  584. btn.textContent = '隐藏 Prompt';
  585. }
  586. }
  587. // 页面加载时高亮第一个步骤
  588. window.addEventListener('load', function() {
  589. const firstSteps = document.querySelectorAll('.workflow-step');
  590. firstSteps.forEach((step, index) => {
  591. if (index === 0 || index % (firstSteps.length / document.querySelectorAll('.tab-content').length) === 0) {
  592. step.classList.add('active');
  593. const content = step.querySelector('.step-content');
  594. const toggle = step.querySelector('.step-toggle');
  595. if (content) {
  596. content.classList.add('expanded');
  597. toggle.classList.add('expanded');
  598. }
  599. }
  600. });
  601. });
  602. </script>
  603. </body>
  604. </html>'''
  605. return html
  606. def main():
  607. """主函数"""
  608. # 获取当前脚本所在目录
  609. script_dir = Path(__file__).parent
  610. os.chdir(script_dir)
  611. # 读取数据文件
  612. print("正在读取数据文件...")
  613. data_list = load_data_files(DATA_FILE_PATHS)
  614. if not data_list:
  615. print("错误: 没有成功读取任何数据文件")
  616. return
  617. print(f"成功读取 {len(data_list)} 个数据文件")
  618. # 解析工作流数据
  619. print("正在解析工作流数据...")
  620. workflows = [parse_workflow_data(data) for data in data_list]
  621. # 生成HTML
  622. print("正在生成HTML页面...")
  623. html = generate_html(workflows)
  624. # 保存HTML文件
  625. timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  626. output_filename = f"workflow_visualization_{timestamp}.html"
  627. with open(output_filename, 'w', encoding='utf-8') as f:
  628. f.write(html)
  629. print(f"HTML页面已生成: {output_filename}")
  630. print(f"文件路径: {os.path.abspath(output_filename)}")
  631. if __name__ == '__main__':
  632. main()