feat: 5-issue fix — OCR image parse bug + Vue frontend feature parity + streaming UX

Fix 1 (CRITICAL): file_parser.py suffix normalization ".jpg", api_server.py Path.suffix
Fix 2: Sidebar version history download, ProcessSection replaces old components
Fix 3: OCR content/position layer structured logging in agent/nodes.py
Fix 4: collapsible process sections with per-section stream routing + auto-fold
Fix 5: agent_complete total_duration_ms, SummaryCard duration display

- backend/file_parser.py: normalize suffix to always include leading dot
- api_server.py: step_index in node_start, total_duration_ms in agent_complete
- agent/nodes.py: _log_ocr_layers() for [内容层]/[位置层]/[合并] logging
- frontend: ProcessSection.vue (NEW), chat.ts sections model, Sidebar versions
- CLAUDE.md: updated component list and v6 changelog
This commit is contained in:
2026-05-21 23:43:21 +08:00
parent 60e2f520ba
commit a364e1de81
9 changed files with 492 additions and 21 deletions
+9 -1
View File
@@ -103,15 +103,19 @@ UPLOADS_DIR = Path(os.getenv("UPLOADS_DIR", "./uploads"))
# 当前请求的事件队列(单个用户桌面应用,无并发问题)
_current_event_queue: Optional[queue.Queue] = None
_step_counter: int = 0
def _on_node_start(node_name: str):
"""全局 node_start 回调 — 将事件推入当前请求的事件队列。"""
global _step_counter
q = _current_event_queue
if q is not None:
_step_counter += 1
q.put(("node_start", {
"node": node_name,
"label": NODE_LABELS.get(node_name, node_name),
"step_index": _step_counter,
}))
@@ -176,8 +180,10 @@ def _run_graph_sync(agent_state: AgentState, event_q: queue.Queue):
async def _sse_generator(agent_state: AgentState) -> str:
"""SSE 事件生成器 —— 在后台线程运行图,异步产出 SSE 字符串。"""
global _current_event_queue
global _current_event_queue, _step_counter
_step_counter = 0
t_start = time.time()
event_q: queue.Queue = queue.Queue()
_current_event_queue = event_q
@@ -198,6 +204,7 @@ async def _sse_generator(agent_state: AgentState) -> str:
kind = item[0]
if kind == "done":
_current_event_queue = None
total_ms = round((time.time() - t_start) * 1000)
yield _sse_line("agent_complete", {
"reason": "done",
"intent": agent_state.get("intent", ""),
@@ -206,6 +213,7 @@ async def _sse_generator(agent_state: AgentState) -> str:
"error_msg": agent_state.get("error_msg", ""),
"natural_explanation": agent_state.get("natural_explanation", ""),
"retry_count": agent_state.get("retry_count", 0),
"total_duration_ms": total_ms,
"ocr_extraction_result": agent_state.get("ocr_extraction_result", {}),
})
await future