diff --git a/app/globals.css b/app/globals.css index ad79ed694..1c6012bcb 100644 --- a/app/globals.css +++ b/app/globals.css @@ -262,11 +262,19 @@ } .chat-card-x-scroll { + min-width: 0; + max-width: 100%; + -webkit-overflow-scrolling: touch; scrollbar-gutter: stable; scrollbar-width: thin; scrollbar-color: rgba(148, 163, 184, 0.42) rgba(148, 163, 184, 0.14); } + .chat-card-x-scroll > table, + .chat-card-x-scroll > svg { + max-width: none; + } + .chat-card-x-scroll::-webkit-scrollbar { width: 10px; height: 10px; diff --git a/components/elements/openclaw-chart-card.tsx b/components/elements/openclaw-chart-card.tsx index e758b20f2..6d2848d2f 100644 --- a/components/elements/openclaw-chart-card.tsx +++ b/components/elements/openclaw-chart-card.tsx @@ -290,7 +290,7 @@ export function OpenClawChartCard({ chart }: { chart: OpenClawChartSpec }) { }; return ( -
+
@@ -314,7 +314,7 @@ export function OpenClawChartCard({ chart }: { chart: OpenClawChartSpec }) { 下载数据
-
+
& { node?: unknown; }; +type MarkdownTableCellProps = ComponentProps<"td"> & { + node?: unknown; +}; + +type MarkdownTableHeaderCellProps = ComponentProps<"th"> & { + node?: unknown; +}; + +function HorizontalDragScrollArea({ + children, + className, + ...props +}: ComponentProps<"div">) { + const scrollRef = useRef(null); + const dragStateRef = useRef<{ + pointerId: number; + startX: number; + scrollLeft: number; + hasDragged: boolean; + } | null>(null); + const suppressClickRef = useRef(false); + + const endDrag = (event: ReactPointerEvent) => { + const state = dragStateRef.current; + const element = scrollRef.current; + if (!state || !element || state.pointerId !== event.pointerId) return; + + if (state.hasDragged) { + suppressClickRef.current = true; + window.setTimeout(() => { + suppressClickRef.current = false; + }, 0); + } + + if (element.hasPointerCapture(event.pointerId)) { + element.releasePointerCapture(event.pointerId); + } + dragStateRef.current = null; + }; + + return ( +
{ + if (!suppressClickRef.current) return; + event.preventDefault(); + event.stopPropagation(); + }} + onPointerCancel={endDrag} + onPointerDown={(event) => { + if (event.button !== 0) return; + const element = scrollRef.current; + if (!element || element.scrollWidth <= element.clientWidth) return; + + dragStateRef.current = { + pointerId: event.pointerId, + startX: event.clientX, + scrollLeft: element.scrollLeft, + hasDragged: false, + }; + element.setPointerCapture(event.pointerId); + }} + onPointerMove={(event) => { + const state = dragStateRef.current; + const element = scrollRef.current; + if (!state || !element || state.pointerId !== event.pointerId) return; + + const deltaX = event.clientX - state.startX; + if (Math.abs(deltaX) > 2) { + state.hasDragged = true; + event.preventDefault(); + } + element.scrollLeft = state.scrollLeft - deltaX; + }} + onPointerUp={endDrag} + {...props} + > + {children} +
+ ); +} + const OPENCLAW_CHART_BLOCK_REGEX = /```(?:openclaw-chart|openclaw_chart|claw-chart|claw_chart|chart)\s*\n[\s\S]*?```/i; const MARKDOWN_TABLE_REGEX = @@ -313,7 +404,7 @@ function DownloadableMarkdownTable({ }; return ( -
+
下载表格
-
+ {children}
-
+
); } +function MarkdownTableCell({ + className, + node: _node, + ...props +}: MarkdownTableCellProps) { + return ( + + ); +} + +function MarkdownTableHeaderCell({ + className, + node: _node, + ...props +}: MarkdownTableHeaderCellProps) { + return ( + + ); +} + // 注意:移除了 memo 的自定义比较函数,确保 isProcessing* 状态变化时组件能正确重新渲染 // 之前的 memo 比较函数可能导致 Streamdown 内部的 ImagePreview 组件没有正确更新 export const Response = ({ @@ -414,6 +531,8 @@ export const Response = ({ return ; }, table: DownloadableMarkdownTable, + td: MarkdownTableCell, + th: MarkdownTableHeaderCell, }; const renderImagePreview = (src: string, alt: string | undefined, key: string) => ( @@ -515,8 +634,9 @@ export const Response = ({ return (
+
{content}
-
{content}
); };