feat: v3 robustness upgrade — EasyOCR, failure recovery, minimum content check
- OCR: EasyOCR (primary, ch_sim+en) with PaddleOCR fallback for Windows compatibility - Validation: _check_minimum_content() rejects empty-shell JRXML (no band/textField) - Retry: MAX_RETRY 3→5, exhaustion records pending_failure_context for next-turn auto-injection - Finalize: only saves jrxml_versions on pass, preserves last good final_jrxml on fail - Extract JRXML: improved empty markdown block handling and XML fragment fallback - UI: real-time node progress via placeholder updates, initial "analyzing" feedback - UI: use agent_state (full) instead of node_state (partial) for summary card routing - UI: unknown template_type now gives LLM meaningful image context instead of metadata - Docs: updated CLAUDE.md and CODE_GUIDE.md to reflect all v3 changes Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -138,18 +138,30 @@ def run_agent(user_input: str):
|
||||
agent_state["user_input"] = user_input
|
||||
agent_state["retry_count"] = 0
|
||||
|
||||
# ---- UI 容器 ----
|
||||
streaming_placeholder = st.empty() # 流式文本
|
||||
nodes_container = st.container() # 节点进度区
|
||||
# ---- UI 占位 ----
|
||||
progress_placeholder = st.empty() # 实时节点进度
|
||||
streaming_placeholder = st.empty() # 流式文本
|
||||
summary_placeholder = st.empty() # 总结卡片
|
||||
|
||||
# 节点追踪
|
||||
executed_nodes: list[dict] = [] # {name, label, status, detail}
|
||||
# 初始状态提示
|
||||
progress_placeholder.info("⏳ 正在分析您的需求...")
|
||||
|
||||
executed_nodes: list[dict] = []
|
||||
stream_text = ""
|
||||
stream_active = False
|
||||
current_stream_node = ""
|
||||
final_state = None
|
||||
|
||||
def _render_progress(nodes: list[dict]):
|
||||
"""渲染实时节点进度到占位符。"""
|
||||
if not nodes:
|
||||
return
|
||||
lines = []
|
||||
for i, node in enumerate(nodes):
|
||||
icon = "●" if i == len(nodes) - 1 else "✓"
|
||||
detail = f" — {node['detail']}" if node.get("detail") else ""
|
||||
lines.append(f"{icon} {node['label']}{detail}")
|
||||
progress_placeholder.markdown("\n\n".join(lines))
|
||||
|
||||
try:
|
||||
for event in st.session_state.graph.stream(
|
||||
agent_state, stream_mode=["updates", "custom"]
|
||||
@@ -177,7 +189,6 @@ def run_agent(user_input: str):
|
||||
)
|
||||
|
||||
elif node_name in ("generate", "modify_jrxml", "correct_jrxml"):
|
||||
# 流式文本已在上面的 custom 事件中展示
|
||||
jrxml = node_state.get("current_jrxml", "")
|
||||
executed_nodes[-1]["detail"] = f"生成 {len(jrxml)} 字符 JRXML"
|
||||
|
||||
@@ -199,31 +210,29 @@ def run_agent(user_input: str):
|
||||
|
||||
final_state = node_state
|
||||
|
||||
# 每个节点完成后立即更新进度
|
||||
_render_progress(executed_nodes)
|
||||
|
||||
elif mode == "custom":
|
||||
cd = data
|
||||
if cd.get("type") == "stream":
|
||||
stream_text += cd.get("text", "")
|
||||
stream_active = True
|
||||
current_stream_node = cd.get("node", "")
|
||||
streaming_placeholder.code(stream_text, language="xml")
|
||||
|
||||
except Exception as e:
|
||||
progress_placeholder.empty()
|
||||
st.error(f"工作流异常: {e}")
|
||||
return
|
||||
|
||||
# ---- 渲染节点进度区 ----
|
||||
with nodes_container:
|
||||
with st.expander("处理过程", expanded=False):
|
||||
for i, node in enumerate(executed_nodes):
|
||||
icon = "✓" if i < len(executed_nodes) - 1 else "●"
|
||||
detail_str = f" — {node['detail']}" if node.get("detail") else ""
|
||||
st.caption(f"{icon} {node['label']}{detail_str}")
|
||||
|
||||
# ---- 清除流式占位 ----
|
||||
# ---- 清理临时占位 ----
|
||||
progress_placeholder.empty()
|
||||
if stream_active:
|
||||
streaming_placeholder.empty()
|
||||
|
||||
# ---- 总结卡片 ----
|
||||
# 注:node_state 只含变更字段,用 agent_state(被所有节点就地修改)获取完整状态
|
||||
final_state = agent_state
|
||||
if final_state:
|
||||
st.session_state.agent_state = final_state
|
||||
intent = final_state.get("intent", "")
|
||||
@@ -239,7 +248,6 @@ def run_agent(user_input: str):
|
||||
|
||||
elif intent in ("undo_modification", "reset_session"):
|
||||
st.success("操作已完成")
|
||||
# 消息已在节点中添加
|
||||
|
||||
elif intent in ("preview_report", "export_pdf", "export_jrxml"):
|
||||
jrxml = final_state.get("current_jrxml", "")
|
||||
@@ -279,10 +287,10 @@ def run_agent(user_input: str):
|
||||
if jrxml:
|
||||
with st.expander("查看当前 JRXML"):
|
||||
_render_jrxml(jrxml, max_lines=80)
|
||||
st.caption("请简化报表结构后重试。")
|
||||
st.caption("💡 下次输入修改需求时,系统会自动加载失败上下文继续修复。")
|
||||
st.session_state.messages.append({
|
||||
"role": "assistant",
|
||||
"content": f"❌ 经过 {retries} 次重试后仍无法生成有效的 JRXML。\n\n**错误:** {error_msg}",
|
||||
"content": f"❌ 经过 {retries} 次重试后仍无法生成有效的 JRXML。\n\n**错误:** {error_msg}\n\n💡 请直接描述修改需求,系统会自动加载失败上下文。",
|
||||
"type": "error_explanation",
|
||||
})
|
||||
else:
|
||||
@@ -431,6 +439,25 @@ with st.sidebar:
|
||||
else:
|
||||
# 新建模式:按 A4 模板处理
|
||||
parsed_text = layout["description"]
|
||||
else:
|
||||
# tt == "unknown": OCR 不可用或未检测到文字元素
|
||||
has_ocr = result.get("method") not in ("metadata_only", None)
|
||||
img_w, img_h = layout["image_size"]
|
||||
ratio = layout["aspect_ratio"]
|
||||
if has_ocr:
|
||||
parsed_text = (
|
||||
f"[图片上传] 尺寸 {img_w}x{img_h}px, 比例 {ratio}。"
|
||||
f"未检测到 A4 报表结构,图片将被视为参考样式。\n"
|
||||
f"请根据用户的文字描述生成报表。"
|
||||
)
|
||||
else:
|
||||
parsed_text = (
|
||||
f"[图片上传] 尺寸 {img_w}x{img_h}px, 比例 {ratio}。\n"
|
||||
f"⚠ OCR 引擎未安装,无法识别图片中的文字内容。\n"
|
||||
f"请严格根据用户的文字描述来推断图片中的报表需求。\n"
|
||||
f"(提示:如需图片文字识别,请运行 pip install paddleocr)"
|
||||
)
|
||||
parsed_type = "image_reference"
|
||||
|
||||
Path(tmp_path).unlink(missing_ok=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user