fix: crash 'list' object has no attribute 'keys' on image upload, output disappearing on error
Root cause: layout_schema.regions is a list of region dicts, not a dict. _log_ocr_layers() was calling .keys() on it, causing agent_error. Also fixed: ProcessSection now stays visible after streaming ends (error or completion), so generated content is not lost. Header shows ✓/✕/pulse indicators. Error handler now refreshes session state for partial JRXML download.
This commit is contained in:
+3
-2
@@ -565,8 +565,9 @@ def _log_ocr_layers(state: AgentState) -> None:
|
|||||||
# ── 位置层:布局 schema(行/列/区域)──
|
# ── 位置层:布局 schema(行/列/区域)──
|
||||||
layout = state.get("layout_schema")
|
layout = state.get("layout_schema")
|
||||||
if isinstance(layout, dict) and layout.get("total_rows", 0) > 0:
|
if isinstance(layout, dict) and layout.get("total_rows", 0) > 0:
|
||||||
regions = layout.get("regions", {})
|
region_list = layout.get("regions", [])
|
||||||
region_names = list(regions.keys()) if regions else []
|
_rn = {"title": "标题", "header": "表头", "data": "数据", "footer": "表尾"}
|
||||||
|
region_names = [_rn.get(r["type"], r["type"]) for r in region_list] if isinstance(region_list, list) else []
|
||||||
cols = layout.get("total_columns", 0)
|
cols = layout.get("total_columns", 0)
|
||||||
rows = layout.get("total_rows", 0)
|
rows = layout.get("total_rows", 0)
|
||||||
regions_label = ", ".join(region_names) if region_names else "标题/表头/数据/表尾"
|
regions_label = ", ".join(region_names) if region_names else "标题/表头/数据/表尾"
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ async function handleSend(text: string, files: File[]) {
|
|||||||
onAgentError(data) {
|
onAgentError(data) {
|
||||||
chat.setError(data.error)
|
chat.setError(data.error)
|
||||||
chat.addMessage({ role: 'assistant', content: `执行异常: ${data.error}`, type: 'error' })
|
chat.addMessage({ role: 'assistant', content: `执行异常: ${data.error}`, type: 'error' })
|
||||||
|
setTimeout(() => session.refreshFromState({}), 500)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|||||||
@@ -15,10 +15,20 @@ function isXmlLike(text: string): boolean {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="chat.streaming && chat.sections.length > 0" class="process-sections">
|
<div v-if="chat.sections.length > 0" class="process-sections">
|
||||||
<div class="sections-header">
|
<div class="sections-header">
|
||||||
<span class="pulse-dot"></span>
|
<template v-if="chat.streaming">
|
||||||
处理中 · {{ chat.formatDuration(chat.totalDurationMs) }}
|
<span class="pulse-dot"></span>
|
||||||
|
处理中 · {{ chat.formatDuration(chat.totalDurationMs) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="chat.error">
|
||||||
|
<span class="error-icon">✕</span>
|
||||||
|
执行异常 · {{ chat.formatDuration(chat.totalDurationMs) }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class="done-icon">✓</span>
|
||||||
|
完成 · {{ chat.formatDuration(chat.totalDurationMs) }}
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<details
|
<details
|
||||||
@@ -76,6 +86,18 @@ function isXmlLike(text: string): boolean {
|
|||||||
animation: pulse 1.5s infinite;
|
animation: pulse 1.5s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-icon {
|
||||||
|
color: #f38ba8;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.done-icon {
|
||||||
|
color: #a6e3a1;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%, 100% { opacity: 1; }
|
0%, 100% { opacity: 1; }
|
||||||
50% { opacity: 0.3; }
|
50% { opacity: 0.3; }
|
||||||
|
|||||||
Reference in New Issue
Block a user