Removed: - app.py (deprecated Streamlit UI, replaced by api_server.py + frontend/) - start_agent_jrxml.py (old launcher, replaced by start.py) - test_reorder.py, e2e_test.py (ad-hoc/outdated test scripts) - ocr_raw_positions.json (debug output) - ARCHITECTURE.md, CODE_GUIDE.md, RAG_INTEGRATION.md, ROADMAP.md (superseded by CLAUDE.md) - EVALUATION_REPORT.md (auto-generated) - scripts/init_kb.py (replaced by init_default_kb.py) - validation_service/validate.bat (redundant, start.py covers it) - sessions/*.json (34 test session files, already gitignored) Updated: - CLAUDE.md: removed stale file entries from key mapping table - README.md: updated init script reference and removed validate.bat - .gitignore: removed EVALUATION_REPORT.md entry
26 KiB
对话场景遍历文档
从
agent/graph.py状态图递归遍历生成,覆盖所有用户意图 → 节点路径 → 退出条件。 最后更新: 2026-05-24
状态图总览
┌──────────────────────────────────────────────────┐
│ 修正循环 (最多 MAX_RETRY=5 次) │
│ ┌─────────┐ ┌──────────────┐ ┌────────┐ │
│ │ validate │───→│ explain_error│───→│correct │ │
│ └────┬─────┘ └──────────────┘ │_jrxml │ │
│ │ pass └───┬────┘ │
│ ▼ │ │
│ ┌─────────┐ retry<5 │
│ │finalize │◄────────────────────────────────┘ │
│ └─────────┘ retry>=5 │
└──────────────────────────────────────────────────┘
load_session ──→ process_input ──→ manage_context ──→ save_state_snapshot
│
▼
classify_intent
│
┌────────────┬──────────┬────────┬───────────┼───────────┬──────────┐
▼ ▼ ▼ ▼ ▼ ▼ ▼
retrieve modify_jrxml save_ handle_ handle_ handle_ (兜底)
(新建报表) (修改报表) session consult undo reset
│ │ (预览) (咨询) (撤销) (重置)
┌────────┴────┐ │ │ │ │ │
▼ ▼ │ │ │ │ │
generate generate_ │ │ │ │ │
(1-shot) skeleton │ │ │ │ │
│ │ │ │ │ │ │
│ refine_ │ │ │ │ │
│ layout │ │ │ │ │
│ │ │ │ │ │ │
│ map_fields │ │ │ │ │
│ │ │ │ │ │ │
└──────┬──────┘ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
save_session ◄─────┴──────────┘ finalize ◄─── finalize ◄── finalize
│ ▲
│ (预览/导出跳过验证) │
├───────────────────────────────────────┘
│ (其他意图走验证)
▼
validate ──→ explain_error ──→ correct_jrxml ──→ validate (循环)
│ pass │ retry>=MAX
▼ ▼
finalize ────────────────────────────────→ finalize
节点详细清单
每个节点标注了 代码行号 (agent/nodes.py 或 agent/graph.py)、前驱节点 (predecessors)、后继节点 (successors)。
1. load_session — 加载会话
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:77 |
| 前驱 | (入口节点, graph entry_point) |
| 后继 | process_input (固定边 graph.py:198) |
| 功能 | 从 sessions/{session_id}.json 磁盘加载状态,注入 agent_state。不从磁盘覆盖 session_id。 |
| LLM | 否 |
2. process_input — 处理用户输入
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:98 |
| 前驱 | load_session (graph.py:198) |
| 后继 | manage_context (graph.py:199) |
| 功能 | 文件解析(PDF/DOCX/XLSX/图片/文本)→ OCR 字段提取 → 批注检测 → 模板 JRXML 解析。注入 ocr_extraction_result、layout_schema、ocr_elements、uploaded_template_jrxml。 |
| LLM | 否(OCR 用 PaddleOCR/EasyOCR) |
3. manage_context — 上下文管理
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:143 |
| 前驱 | process_input (graph.py:199) |
| 后继 | save_state_snapshot (graph.py:200) |
| 功能 | Token 计数 → 对话压缩(超限时 LLM 压缩为摘要)→ compressed_history。 |
| LLM | 是(压缩时调 LLM) |
4. save_state_snapshot — 状态快照
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:191 |
| 前驱 | manage_context (graph.py:200) |
| 后继 | classify_intent (graph.py:201) |
| 功能 | 深拷贝当前状态 → 推入 history_states 列表。最多保留 5 个快照。撤销时恢复到最新快照。 |
| LLM | 否 |
5. classify_intent — 意图分类
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:200 |
| 前驱 | save_state_snapshot (graph.py:201) |
| 后继 | 6 路条件分发 (graph.py:204-215) |
| 功能 | LLM 分类用户意图为 8 种之一。prompt: prompts/intent_classify.md。 |
| LLM | 是 |
| 路由函数 | route_by_intent (graph.py:67) |
分类逻辑与路由目标:
| 意图值 | 路由目标 | 说明 |
|---|---|---|
initial_generation |
→ retrieve |
新建报表 |
modify_report |
→ modify_jrxml |
修改现有报表 |
preview_report |
→ save_session |
预览(跳过生成) |
export_pdf |
→ save_session |
导出 PDF(跳过生成) |
export_jrxml |
→ save_session |
下载 JRXML(跳过生成) |
consult_question |
→ handle_consult |
咨询问答 |
undo_modification |
→ handle_undo |
撤销 |
reset_session |
→ handle_reset |
重置 |
| 未知/兜底 | 有 current_jrxml → modify_jrxml; 无 → retrieve |
6. retrieve — RAG/知识库检索
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:442 |
| 前驱 | classify_intent (graph.py:204-215, intent=initial_generation) |
| 后继 | 条件分发: generate_skeleton 或 generate (graph.py:218-224) |
| 功能 | ① ErrorKB 检索历史修正案例 → ② KB 模板检索 → ③ KB 字段定义检索。注入 retrieved_context、kb_template_jrxml、kb_fields。 |
| LLM | 否(向量搜索 + 字段匹配) |
| 路由函数 | route_after_retrieve (graph.py:94) |
路由逻辑 (route_after_retrieve, graph.py:94-99):
layout_schema.total_rows > 0→generate_skeleton(3 阶段)- 否则 →
generate(1-shot)
7. generate — 1-shot 生成
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:578 |
| 前驱 | retrieve (graph.py:218-224, 无 layout_schema 时) |
| 后继 | save_session (graph.py:227-231) |
| 功能 | LLM 一次生成完整 JRXML。注入 OCR 上下文 + 模板上下文。流式输出。截断时续写(最多 3 轮)。 |
| LLM | 是 |
| Prompt | prompts/initial_generation.md |
8. generate_skeleton — 骨架生成(3 阶段-1)
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:657 |
| 前驱 | retrieve (graph.py:218-224, 有 layout_schema 时) |
| 后继 | refine_layout (固定边 graph.py:233) |
| 功能 | 压缩布局 schema → LLM 生成骨架 JRXML。字段用 $F{field_N} 占位。流式输出 + 续写。 |
| LLM | 是 |
| Prompt | prompts/skeleton_generation.md |
9. refine_layout — 坐标精调(3 阶段-2)
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:879 |
| 前驱 | generate_skeleton (graph.py:233) |
| 后继 | map_fields (固定边 graph.py:234) |
| 功能 | ① decompose_jrxml() 拆解为 header + bands → ② 每个 band 窗口化(>4000 字符切分)→ ③ 逐窗口 LLM 精调坐标 → ④ reassemble_jrxml() 重组 → ⑤ validate_element_count() 校验(>10% 回退)。header 完全不发给 LLM。 |
| LLM | 是(N 次,N = band 窗口数) |
| Prompt | prompts/refine_layout.md |
10. map_fields — 字段映射(3 阶段-3)
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:978 |
| 前驱 | refine_layout (graph.py:234) |
| 后继 | save_session (graph.py:235-239) |
| 功能 | 纯程序化正则替换 $F{field_N} → OCR 真实字段名。_sanitize_field_name() 净化非 ASCII 字符。零 LLM 调用。 |
| LLM | 否 |
11. modify_jrxml — 修改报表
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:1022 |
| 前驱 | classify_intent (graph.py:204-215, intent=modify_report) |
| 后继 | save_session (graph.py:242-246) |
| 功能 | 基于现有 JRXML + 用户修改描述 + OCR 上下文 + 模板上下文 → LLM 修改。流式输出 + 续写。空响应守卫。 |
| LLM | 是 |
| Prompt | prompts/modification.md |
12. handle_consult — 咨询解答
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:261 |
| 前驱 | classify_intent (graph.py:204-215, intent=consult_question) |
| 后继 | finalize (固定边 graph.py:280) |
| 功能 | LLM 回答 JasperReports 相关知识问题。回答写入 conversation_history。 |
| LLM | 是 |
| Prompt | prompts/consult.md |
13. handle_undo — 撤销
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:281 |
| 前驱 | classify_intent (graph.py:204-215, intent=undo_modification) |
| 后继 | save_session (graph.py:249-253) |
| 功能 | 从 history_states 弹出最近快照,恢复 current_jrxml、conversation_history、status。无快照时提示"无可撤销状态"。 |
| LLM | 否 |
14. handle_reset — 重置
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:309 |
| 前驱 | classify_intent (graph.py:204-215, intent=reset_session) |
| 后继 | finalize (固定边 graph.py:281) |
| 功能 | 清空所有状态到 create_initial_state() 默认值(保留 session_id、session_name)。 |
| LLM | 否 |
15. save_session — 保存会话
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:325 |
| 前驱 | generate、map_fields、modify_jrxml、handle_undo、classify_intent(预览/导出) |
| 后继 | 条件分发: validate 或 finalize (graph.py:256-260) |
| 功能 | 原子持久化会话 JSON (tempfile + os.replace)。序列化 agent_state 到 sessions/{session_id}.json。 |
| LLM | 否 |
| 路由函数 | route_after_save (graph.py:118) |
路由逻辑 (route_after_save, graph.py:118-123):
intent in (preview_report, export_pdf, export_jrxml)→finalize(跳过验证)- 其他 →
validate
16. validate — 验证
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:1235 |
| 前驱 | save_session (graph.py:256-260)、correct_jrxml (graph.py:273-277) |
| 后继 | 条件分发: finalize 或 explain_error (graph.py:263-267) |
| 功能 | ① 结构检查(字段引用一致性/SQL 存在/pageWidth/pageHeight/name)→ ② XSD 校验(可选)→ ③ 像素对比(有上传图片时 Java 渲染 JRXML→PNG + OpenCV SSIM)。 |
| LLM | 否 |
| 路由函数 | route_after_validate (graph.py:127) |
路由逻辑 (route_after_validate, graph.py:127-131):
status == "pass"→finalizestatus == "fail"→explain_error
17. explain_error — 错误解释
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:1310 |
| 前驱 | validate (graph.py:263-267, status=fail) |
| 后继 | correct_jrxml (graph.py:268-272) |
| 功能 | LLM 将编译错误翻译为自然语言解释。注入 natural_explanation。 |
| LLM | 是 |
| Prompt | prompts/explain_error.md |
18. correct_jrxml — 自动修正
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:1355 |
| 前驱 | explain_error (graph.py:268-272) |
| 后继 | 条件分发: validate 或 finalize (graph.py:273-277) |
| 功能 | 基于错误解释 + OCR 上下文 + 模板上下文 → LLM 修正 JRXML。注入 last_error_case。去重检测(输入输出相同则 retry_count+=2)。 |
| LLM | 是 |
| Prompt | prompts/correction.md |
| 路由函数 | route_after_correct (graph.py:139) |
路由逻辑 (route_after_correct, graph.py:139-143):
retry_count >= MAX_RETRY(默认5) →finalize(放弃修正)retry_count < MAX_RETRY→validate(重新验证)
19. finalize — 最终处理
| 属性 | 值 |
|---|---|
| 代码位置 | agent/nodes.py:1452 |
| 前驱 | validate(pass)、correct_jrxml(retry>=MAX)、handle_consult、handle_reset、save_session(预览/导出) |
| 后继 | END (graph.py:284) |
| 功能 | 记录 jrxml_versions 版本历史。验证通过时设置 final_jrxml。失败时记录 pending_failure_context 供下次输入自动注入。 |
| LLM | 否 |
路由函数索引
| # | 路由函数 | 代码位置 | 条件 | 分支 |
|---|---|---|---|---|
| R1 | route_by_intent |
graph.py:67 |
state.intent |
6 路: retrieve / modify_jrxml / save_session / handle_consult / handle_undo / handle_reset |
| R2 | route_after_retrieve |
graph.py:94 |
layout_schema.total_rows > 0 |
2 路: generate_skeleton / generate |
| R3 | route_after_generate |
graph.py:103 |
无条件 | save_session |
| R4 | route_after_modify |
graph.py:108 |
无条件 | save_session |
| R5 | route_after_undo |
graph.py:113 |
无条件 | save_session |
| R6 | route_after_save |
graph.py:118 |
intent in (preview, export) |
2 路: finalize / validate |
| R7 | route_after_validate |
graph.py:127 |
status == "pass" |
2 路: finalize / explain_error |
| R8 | route_after_explain |
graph.py:133 |
无条件 | correct_jrxml |
| R9 | route_after_correct |
graph.py:139 |
retry_count >= MAX_RETRY |
2 路: finalize / validate |
完整对话场景
场景 1: 新建报表 — 1-shot(无布局 schema)
触发: intent=initial_generation + 无图片/无结构化布局
用户示例: "帮我生成一个销售报表"、"生成一个包含客户名和金额的表格"
load_session nodes.py:77
→ process_input nodes.py:98
→ manage_context nodes.py:143
→ save_state_snapshot nodes.py:191
→ classify_intent nodes.py:200 意图=initial_generation
└─ R1: route_by_intent graph.py:67 → retrieve
→ retrieve nodes.py:442
└─ R2: route_after_retrieve graph.py:94 layout_schema 为空 → generate
→ generate nodes.py:578 LLM 1-shot 生成完整 JRXML
└─ R3: route_after_generate graph.py:103 → save_session
→ save_session nodes.py:325 持久化到磁盘
└─ R6: route_after_save graph.py:118 intent=initial_generation → validate
→ validate nodes.py:1235 结构检查 + XSD + 像素对比
└─ R7: route_after_validate graph.py:127
├─ status=pass → finalize nodes.py:1452 → END ✓
└─ status=fail → explain_error nodes.py:1310
└─ R8 → correct_jrxml nodes.py:1355
└─ R9:
retry<5 → validate (循环)
retry>=5 → finalize → END ✗
LLM 调用: classify_intent + generate + 最多 5× (explain_error + correct_jrxml)
退出好结局: final_jrxml 有值, status=pass
退出坏结局: pending_failure_context 有值, retry_count=5
场景 2: 新建报表 — 3 阶段分层生成(有布局 schema)
触发: intent=initial_generation + 上传图片 + OCR 提取到 layout_schema.total_rows > 0
用户示例: 上传销售单图片 → "根据这个模板生成报表"
load_session nodes.py:77
→ process_input nodes.py:98 OCR提取 + 布局分析
→ manage_context nodes.py:143
→ save_state_snapshot nodes.py:191
→ classify_intent nodes.py:200 意图=initial_generation
└─ R1: route_by_intent graph.py:67 → retrieve
→ retrieve nodes.py:442 KB检索模板+字段
└─ R2: route_after_retrieve graph.py:94 layout_schema.total_rows>0 → generate_skeleton
→ generate_skeleton nodes.py:657 阶段1: 骨架JRXML ($F{field_N}占位)
→ refine_layout nodes.py:879 阶段2: Band级窗口化坐标精调
→ map_fields nodes.py:978 阶段3: 程序化字段映射
└─ R3: route_after_generate graph.py:103 → save_session
→ save_session nodes.py:325
└─ R6: route_after_save graph.py:118 → validate
→ validate nodes.py:1235
└─ R7 同场景1的验证循环
内容保护:
refine_layout: header (field/param/queryString) 完全不发给 LLMrefine_layout: 每窗口 ~4000 字符, LLM 无法重写整个报表map_fields: 纯正则替换, 零 LLM, 100% 确定性validate_element_count(): 每阶段后校验, >10% 变化回退
LLM 调用: classify_intent + generate_skeleton + N×refine_layout(N=band窗口数) + 可能的修正循环
场景 3: 修改已有报表
触发: intent=modify_report(已有 current_jrxml)
用户示例: "把标题字体改大"、"在底部加合计行"、"删除第三列"
load_session → process_input → manage_context → save_state_snapshot
→ classify_intent nodes.py:200 意图=modify_report
└─ R1: route_by_intent graph.py:67 → modify_jrxml
→ modify_jrxml nodes.py:1022 LLM修改现有JRXML
└─ R4: route_after_modify graph.py:108 → save_session
→ save_session nodes.py:325
└─ R6: route_after_save graph.py:118 → validate
→ (同场景1的验证循环)
特殊逻辑: correct_jrxml 去重检测: 输入输出相同 → retry_count += 2
场景 4: 预览 / 导出(跳过验证)
触发: intent in (preview_report, export_pdf, export_jrxml)
用户示例: "预览报表"、"导出 PDF"、"下载 JRXML"
load_session → process_input → manage_context → save_state_snapshot
→ classify_intent nodes.py:200 意图=preview/export
└─ R1: route_by_intent graph.py:67 → save_session
→ save_session nodes.py:325
└─ R6: route_after_save graph.py:118 intent=preview/export → finalize
→ finalize nodes.py:1452 → END ✓
LLM 调用: 仅 classify_intent (1次)
跳过: generate / modify_jrxml / validate / correct_jrxml
场景 5: 咨询问答
触发: intent=consult_question
用户示例: "JasperReports 里 $F 和 $P 有什么区别?"、"怎么设置页脚?"
load_session → process_input → manage_context → save_state_snapshot
→ classify_intent nodes.py:200 意图=consult_question
└─ R1: route_by_intent graph.py:67 → handle_consult
→ handle_consult nodes.py:261 LLM回答
→ finalize nodes.py:1452 → END ✓
LLM 调用: classify_intent + handle_consult (2次)
场景 6: 撤销
触发: intent=undo_modification
用户示例: "撤销"、"回退"、"恢复到修改前"
load_session → process_input → manage_context → save_state_snapshot
→ classify_intent nodes.py:200 意图=undo_modification
└─ R1: route_by_intent graph.py:67 → handle_undo
→ handle_undo nodes.py:281 恢复history_states快照
└─ R5: route_after_undo graph.py:113 → save_session
→ save_session nodes.py:325
└─ R6 → validate → (验证循环)
LLM 调用: 仅 classify_intent (1次)
特殊: 无快照时提示"无可撤销状态",不改变当前状态
场景 7: 重置
触发: intent=reset_session
用户示例: "重置"、"重新开始"、"清空对话"
load_session → process_input → manage_context → save_state_snapshot
→ classify_intent nodes.py:200 意图=reset_session
└─ R1: route_by_intent graph.py:67 → handle_reset
→ handle_reset nodes.py:309 清空到初始状态
→ finalize nodes.py:1452 → END ✓
LLM 调用: 仅 classify_intent (1次)
场景 8: 兜底路由(未知意图)
触发: LLM 分类返回非标准意图
load_session → ... → classify_intent → [未知意图]
└─ R1 fallback (graph.py:87-90):
├─ state有current_jrxml → modify_jrxml (走修改路径, →场景3)
└─ state无current_jrxml → retrieve (走生成路径, →场景1/2)
AgentState 字段速查
| 字段 | 类型 | 写节点 | 读节点 |
|---|---|---|---|
intent |
str |
classify_intent | R1 route_by_intent, R6 route_after_save |
current_jrxml |
str |
generate, generate_skeleton, refine_layout, map_fields, modify_jrxml, correct_jrxml, handle_undo | validate, save_session, finalize |
user_input |
str |
process_input | classify_intent, manage_context |
user_modification_request |
str |
process_input | modify_jrxml |
conversation_history |
list |
process_input, finalize, handle_consult | manage_context, classify_intent, modify_jrxml |
full_conversation_history |
list |
process_input | manage_context |
compressed_history |
str |
manage_context | modify_jrxml, handle_consult |
retry_count |
int |
correct_jrxml, validate | R7 route_after_correct |
status |
str |
validate | R7 route_after_validate, finalize |
error_msg |
str |
validate | explain_error, finalize |
natural_explanation |
str |
explain_error | correct_jrxml |
final_jrxml |
str |
finalize | (用户下载) |
jrxml_versions |
list |
finalize | (前端展示) |
last_error_case |
dict |
correct_jrxml | retrieve |
pending_failure_context |
dict |
finalize | process_input (下次) |
layout_schema |
dict |
process_input | R2 route_after_retrieve, generate_skeleton |
ocr_elements |
list |
process_input | refine_layout, generate_skeleton |
ocr_extraction_result |
dict |
process_input | map_fields, modify_jrxml, correct_jrxml |
history_states |
list |
save_state_snapshot | handle_undo |
kb_id |
str |
process_input | retrieve |
kb_fields |
list |
retrieve | generate_skeleton |
uploaded_template_jrxml |
str |
process_input | generate, generate_skeleton, modify_jrxml, correct_jrxml |
LLM 调用统计
| 场景 | classify | 生成节点 | 窗口数 | 修正循环 | 总计(最小~最大) |
|---|---|---|---|---|---|
| 1-shot 生成 | 1 | generate=1 | - | 0~5×2 | 2 ~ 12 |
| 3 阶段生成 | 1 | skeleton+refine×N | N | 0~5×2 | 2+N ~ 12+N |
| 修改报表 | 1 | modify=1 | - | 0~5×2 | 2 ~ 12 |
| 预览/导出 | 1 | - | - | - | 1 |
| 咨询 | 1 | consult=1 | - | - | 2 |
| 撤销 | 1 | - | - | - | 1 |
| 重置 | 1 | - | - | - | 1 |
N = band 窗口数。
销售单.jrxml(73k 字符) 拆解后 N≈17。
修正循环流程
validate ──fail──→ explain_error ──→ correct_jrxml
▲ │
│ retry_count < MAX_RETRY(5) │
└──────────────────────────────────────┘
│
│ retry_count >= 5
▼
finalize (放弃, 记录pending_failure_context)
修正轮次推进:
validate失败 →status="fail",error_msg有值explain_error→ LLM 翻译错误 →natural_explanation有值correct_jrxml→ LLM 修正 →retry_count += 1。去重检测:输入输出相同 →retry_count += 2route_after_correct→ retry<5 → 回到validate; retry>=5 →finalize
失败上下文 (pending_failure_context): 重试耗尽后记录 {error_msg, bad_jrxml, retry_count, ts},下次用户消息时 process_input 自动注入到 prompt。
边定义索引(graph.py 全部边)
| 类型 | 源节点 | 目标节点 | 位置 |
|---|---|---|---|
| 固定边 | load_session | process_input | line 198 |
| 固定边 | process_input | manage_context | line 199 |
| 固定边 | manage_context | save_state_snapshot | line 200 |
| 固定边 | save_state_snapshot | classify_intent | line 201 |
| 条件边 | classify_intent | retrieve / modify_jrxml / save_session / handle_consult / handle_undo / handle_reset | lines 204-215 |
| 条件边 | retrieve | generate / generate_skeleton | lines 218-224 |
| 条件边 | generate | save_session | lines 227-231 |
| 固定边 | generate_skeleton | refine_layout | line 233 |
| 固定边 | refine_layout | map_fields | line 234 |
| 条件边 | map_fields | save_session | lines 235-239 |
| 条件边 | modify_jrxml | save_session | lines 242-246 |
| 条件边 | handle_undo | save_session | lines 249-253 |
| 条件边 | save_session | validate / finalize | lines 256-260 |
| 条件边 | validate | finalize / explain_error | lines 263-267 |
| 条件边 | explain_error | correct_jrxml | lines 268-272 |
| 条件边 | correct_jrxml | validate / finalize | lines 273-277 |
| 固定边 | handle_consult | finalize | line 280 |
| 固定边 | handle_reset | finalize | line 281 |
| 固定边 | finalize | END | line 284 |