diff --git a/web/src/components/Chat/MessageBubble.tsx b/web/src/components/Chat/MessageBubble.tsx
index 54214a3..5f7bc7d 100644
--- a/web/src/components/Chat/MessageBubble.tsx
+++ b/web/src/components/Chat/MessageBubble.tsx
@@ -1,4 +1,5 @@
import { useState, useEffect, memo } from 'react';
+import { createPortal } from 'react-dom';
import {
CheckCircle,
ChevronRight,
@@ -319,9 +320,14 @@ function ImageLightbox({
URL.revokeObjectURL(url);
};
- return (
+ // 必须 portal 到 body:本组件渲染在虚拟化消息行内,行元素带
+ // transform(translateY),transformed 祖先会成为 fixed 后代的包含块,
+ // 导致遮罩只覆盖该行区域、工具栏定位到行盒而非视口、其他行内容叠在
+ // 遮罩之上。portal 后 fixed/z-index 相对视口全局生效。
+ // z-[10000]:高于下拉选择器(z-[9999])等全局浮层。
+ return createPortal(
{/* 顶部工具栏 */}
@@ -349,7 +355,8 @@ function ImageLightbox({
onClick={(e) => e.stopPropagation()}
alt="图片预览"
/>
-
+ ,
+ document.body,
);
}
diff --git a/web/src/components/Chat/ToolDetailModal.tsx b/web/src/components/Chat/ToolDetailModal.tsx
index 01b8048..fbb089e 100644
--- a/web/src/components/Chat/ToolDetailModal.tsx
+++ b/web/src/components/Chat/ToolDetailModal.tsx
@@ -1,4 +1,5 @@
import { useEffect } from 'react';
+import { createPortal } from 'react-dom';
import { X, Terminal, Clock, Maximize2 } from 'lucide-react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
@@ -65,9 +66,12 @@ export function ToolDetailModal({
? 'var(--accent-amber)'
: 'var(--text-muted)';
- return (
+ // portal 到 body:在虚拟化消息行内打开时,行元素的 transform 会成为
+ // fixed 后代的包含块,遮罩/定位全部失效(同 ImageLightbox)。
+ // z-[10000]:高于下拉选择器(z-[9999])等全局浮层。
+ return createPortal(
{/* Modal container */}
@@ -145,6 +149,7 @@ export function ToolDetailModal({
-
+ ,
+ document.body,
);
}