马一丁

Optimize Rendering Logic with New IR Binding

@@ -709,10 +709,36 @@ class HTMLRenderer: @@ -709,10 +709,36 @@ class HTMLRenderer:
709 } 709 }
710 handler = handlers.get(block_type) 710 handler = handlers.get(block_type)
711 if handler: 711 if handler:
712 - return handler(block) 712 + html_fragment = handler(block)
  713 + return self._wrap_error_block(html_fragment, block)
713 if isinstance(block.get("blocks"), list): 714 if isinstance(block.get("blocks"), list):
714 - return self._render_blocks(block["blocks"])  
715 - return f'<pre class="unknown-block">{self._escape_html(json.dumps(block, ensure_ascii=False, indent=2))}</pre>' 715 + html_fragment = self._render_blocks(block["blocks"])
  716 + return self._wrap_error_block(html_fragment, block)
  717 + fallback = f'<pre class="unknown-block">{self._escape_html(json.dumps(block, ensure_ascii=False, indent=2))}</pre>'
  718 + return self._wrap_error_block(fallback, block)
  719 +
  720 + def _wrap_error_block(self, html_fragment: str, block: Dict[str, Any]) -> str:
  721 + """若block标记了error元数据,则包裹提示容器并注入tooltip。"""
  722 + if not html_fragment:
  723 + return html_fragment
  724 + meta = block.get("meta") or {}
  725 + log_ref = meta.get("errorLogRef")
  726 + if not isinstance(log_ref, dict):
  727 + return html_fragment
  728 + raw_preview = (meta.get("rawJsonPreview") or "")[:1200]
  729 + error_message = meta.get("errorMessage") or "LLM返回块解析错误"
  730 + importance = meta.get("importance") or "standard"
  731 + ref_label = ""
  732 + if log_ref.get("relativeFile") and log_ref.get("entryId"):
  733 + ref_label = f"{log_ref['relativeFile']}#{log_ref['entryId']}"
  734 + tooltip = f"{error_message} | {ref_label}".strip()
  735 + attr_raw = self._escape_attr(raw_preview or tooltip)
  736 + attr_title = self._escape_attr(tooltip)
  737 + class_suffix = self._escape_attr(importance)
  738 + return (
  739 + f'<div class="llm-error-block importance-{class_suffix}" '
  740 + f'data-raw="{attr_raw}" title="{attr_title}">{html_fragment}</div>'
  741 + )
716 742
717 def _render_heading(self, block: Dict[str, Any]) -> str: 743 def _render_heading(self, block: Dict[str, Any]) -> str:
718 """渲染heading block,确保锚点存在""" 744 """渲染heading block,确保锚点存在"""
@@ -1498,6 +1524,41 @@ body {{ @@ -1498,6 +1524,41 @@ body {{
1498 font-weight: 500; 1524 font-weight: 500;
1499 margin-top: 0; 1525 margin-top: 0;
1500 }} 1526 }}
  1527 +.llm-error-block {{
  1528 + border: 1px dashed var(--secondary-color);
  1529 + border-radius: 12px;
  1530 + padding: 12px;
  1531 + margin: 12px 0;
  1532 + background: rgba(229,62,62,0.06);
  1533 + position: relative;
  1534 +}}
  1535 +.llm-error-block.importance-critical {{
  1536 + border-color: var(--secondary-color-dark);
  1537 + background: rgba(229,62,62,0.12);
  1538 +}}
  1539 +.llm-error-block::after {{
  1540 + content: attr(data-raw);
  1541 + white-space: pre-wrap;
  1542 + position: absolute;
  1543 + left: 0;
  1544 + right: 0;
  1545 + bottom: 100%;
  1546 + max-height: 240px;
  1547 + overflow: auto;
  1548 + background: rgba(0,0,0,0.85);
  1549 + color: #fff;
  1550 + font-size: 0.85rem;
  1551 + padding: 12px;
  1552 + border-radius: 10px;
  1553 + margin-bottom: 8px;
  1554 + opacity: 0;
  1555 + pointer-events: none;
  1556 + transition: opacity 0.2s ease;
  1557 + z-index: 20;
  1558 +}}
  1559 +.llm-error-block:hover::after {{
  1560 + opacity: 1;
  1561 +}}
1501 .report-header h1 {{ 1562 .report-header h1 {{
1502 margin: 0; 1563 margin: 0;
1503 font-size: 1.6rem; 1564 font-size: 1.6rem;