diff --git a/packages/webui/src/components/ChatViewer/ChatViewer.css b/packages/webui/src/components/ChatViewer/ChatViewer.css index 5cc71011a..c3a1a38bf 100644 --- a/packages/webui/src/components/ChatViewer/ChatViewer.css +++ b/packages/webui/src/components/ChatViewer/ChatViewer.css @@ -89,7 +89,8 @@ } .chat-viewer-messages > *:not(:last-child) { - padding-bottom: 8px; + padding-bottom: 0; + padding-top: 0; } /* Disable overflow anchoring on individual items for manual scroll control */ @@ -102,6 +103,15 @@ margin-top: 0; } +/* =========================== + Timeline Wrapper Styles + =========================== */ + +/* Tool call wrapper - transparent container for timeline continuity */ +.chat-viewer-toolcall-wrapper { + position: relative; +} + /* =========================== Animations =========================== */ @@ -150,3 +160,24 @@ height: 1px; overflow-anchor: auto; } + +/* =========================== + ChatViewer-specific Styles + =========================== */ + +/* Better spacing between message groups */ +.chat-viewer-messages .user-message-container { + margin-top: 16px; +} + +/* Ensure proper stacking context */ +.chat-viewer-messages > * { + isolation: isolate; +} + +/* Responsive adjustments for ChatViewer */ +@media (max-width: 600px) { + .chat-viewer-messages { + padding: 12px; + } +} diff --git a/packages/webui/src/components/ChatViewer/ChatViewer.stories.tsx b/packages/webui/src/components/ChatViewer/ChatViewer.stories.tsx index 866380399..4673003f9 100644 --- a/packages/webui/src/components/ChatViewer/ChatViewer.stories.tsx +++ b/packages/webui/src/components/ChatViewer/ChatViewer.stories.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { useRef } from 'react'; +import { useRef, useState, useCallback, useEffect } from 'react'; import type { Meta, StoryObj } from '@storybook/react-vite'; import { ChatViewer, @@ -1212,3 +1212,416 @@ export const WithRefControl: Story = { }, }, }; + +// Comprehensive sample data for playground with all tool types +const PLAYGROUND_SAMPLE = `[ + { + "uuid": "1", + "timestamp": "2026-01-15T14:00:00.000Z", + "type": "user", + "message": { + "role": "user", + "parts": [{ "text": "帮我创建一个 React 组件,并添加到项目中" }] + } + }, + { + "uuid": "2", + "timestamp": "2026-01-15T14:00:05.000Z", + "type": "assistant", + "message": { + "role": "assistant", + "content": "好的,我来帮你创建一个 React 组件。首先让我搜索一下项目结构。" + } + }, + { + "uuid": "3", + "timestamp": "2026-01-15T14:00:06.000Z", + "type": "tool_call", + "toolCall": { + "toolCallId": "search-1", + "kind": "grep", + "title": "Searching for component patterns", + "status": "completed", + "rawInput": "export.*Component", + "content": [{ + "type": "content", + "content": { + "type": "text", + "text": "src/components/Button.tsx:export const Button: FC = () => {\\nsrc/components/Card.tsx:export const Card: FC = () => {" + } + }], + "locations": [ + { "path": "src/components/Button.tsx", "line": 5 }, + { "path": "src/components/Card.tsx", "line": 8 } + ] + } + }, + { + "uuid": "4", + "timestamp": "2026-01-15T14:00:08.000Z", + "type": "tool_call", + "toolCall": { + "toolCallId": "read-1", + "kind": "read", + "title": "src/components/Button.tsx", + "status": "completed", + "content": [{ + "type": "content", + "content": { + "type": "text", + "text": "import type { FC } from 'react';\\n\\nexport interface ButtonProps {\\n label: string;\\n onClick?: () => void;\\n}\\n\\nexport const Button: FC = ({ label, onClick }) => (\\n \\n);" + } + }], + "locations": [{ "path": "src/components/Button.tsx" }] + } + }, + { + "uuid": "5", + "timestamp": "2026-01-15T14:00:10.000Z", + "type": "assistant", + "message": { + "role": "assistant", + "content": "我找到了项目的组件结构。现在我来创建新的组件文件。" + } + }, + { + "uuid": "6", + "timestamp": "2026-01-15T14:00:12.000Z", + "type": "tool_call", + "toolCall": { + "toolCallId": "write-1", + "kind": "write", + "title": "Creating src/components/Modal.tsx", + "status": "completed", + "content": [{ + "type": "diff", + "path": "src/components/Modal.tsx", + "oldText": null, + "newText": "import type { FC, ReactNode } from 'react';\\nimport './Modal.css';\\n\\nexport interface ModalProps {\\n isOpen: boolean;\\n onClose: () => void;\\n title: string;\\n children: ReactNode;\\n}\\n\\nexport const Modal: FC = ({\\n isOpen,\\n onClose,\\n title,\\n children,\\n}) => {\\n if (!isOpen) return null;\\n\\n return (\\n
\\n
\\n
\\n

{title}

\\n \\n
\\n
\\n {children}\\n
\\n
\\n
\\n );\\n};" + }], + "locations": [{ "path": "src/components/Modal.tsx" }] + } + }, + { + "uuid": "7", + "timestamp": "2026-01-15T14:00:15.000Z", + "type": "tool_call", + "toolCall": { + "toolCallId": "edit-1", + "kind": "edit", + "title": "Updating src/components/index.ts", + "status": "completed", + "content": [{ + "type": "diff", + "path": "src/components/index.ts", + "oldText": "export { Button } from './Button';\\nexport { Card } from './Card';", + "newText": "export { Button } from './Button';\\nexport { Card } from './Card';\\nexport { Modal } from './Modal';" + }], + "locations": [{ "path": "src/components/index.ts", "line": 3 }] + } + }, + { + "uuid": "8", + "timestamp": "2026-01-15T14:00:18.000Z", + "type": "assistant", + "message": { + "role": "assistant", + "content": "组件已创建。让我运行测试确保没有问题。" + } + }, + { + "uuid": "9", + "timestamp": "2026-01-15T14:00:20.000Z", + "type": "tool_call", + "toolCall": { + "toolCallId": "bash-1", + "kind": "bash", + "title": "Running tests", + "status": "completed", + "rawInput": "npm run test -- --coverage", + "content": [{ + "type": "content", + "content": { + "type": "text", + "text": "PASS src/components/Modal.test.tsx\\n Modal Component\\n ✓ renders when isOpen is true (15ms)\\n ✓ does not render when isOpen is false (3ms)\\n ✓ calls onClose when close button clicked (8ms)\\n\\nTest Suites: 1 passed, 1 total\\nTests: 3 passed, 3 total\\nCoverage: 92.5%" + } + }] + } + }, + { + "uuid": "10", + "timestamp": "2026-01-15T14:00:25.000Z", + "type": "tool_call", + "toolCall": { + "toolCallId": "plan-1", + "kind": "todowrite", + "title": "Updating task progress", + "status": "completed", + "content": [{ + "type": "content", + "content": { + "type": "plan", + "entries": [ + { "content": "Search project structure", "status": "completed" }, + { "content": "Create Modal component", "status": "completed" }, + { "content": "Update exports", "status": "completed" }, + { "content": "Run tests", "status": "completed" }, + { "content": "Add documentation", "status": "pending" } + ] + } + }] + } + }, + { + "uuid": "11", + "timestamp": "2026-01-15T14:00:30.000Z", + "type": "assistant", + "message": { + "role": "assistant", + "content": "Modal 组件已成功创建并通过所有测试!\\n\\n**创建的文件:**\\n- \`src/components/Modal.tsx\` - 主组件文件\\n- \`src/components/Modal.css\` - 样式文件\\n\\n**功能特性:**\\n- 支持打开/关闭状态控制\\n- 可自定义标题和内容\\n- 点击关闭按钮触发回调\\n\\n还需要我添加文档吗?" + } + } +]`; + +// Playground component for testing JSON input with auto-render +const PlaygroundTemplate = () => { + const [jsonInput, setJsonInput] = useState(PLAYGROUND_SAMPLE); + const [messages, setMessages] = useState([]); + const [error, setError] = useState(null); + const [autoRender, setAutoRender] = useState(true); + const debounceRef = useRef | null>(null); + + const parseAndRender = useCallback((input: string) => { + try { + const parsed = JSON.parse(input); + if (!Array.isArray(parsed)) { + throw new Error('JSON must be an array of messages'); + } + setMessages(parsed); + setError(null); + } catch (e) { + setError(e instanceof Error ? e.message : 'Invalid JSON'); + setMessages([]); + } + }, []); + + // Auto-render with debounce when JSON input changes + useEffect(() => { + if (!autoRender) return; + + if (debounceRef.current) { + clearTimeout(debounceRef.current); + } + + debounceRef.current = setTimeout(() => { + parseAndRender(jsonInput); + }, 300); + + return () => { + if (debounceRef.current) { + clearTimeout(debounceRef.current); + } + }; + }, [jsonInput, autoRender, parseAndRender]); + + // Parse on initial load + useEffect(() => { + parseAndRender(jsonInput); + }, [parseAndRender, jsonInput]); + + return ( +
+ {/* Left Panel - JSON Input */} +
+
+

+ JSON Input (Messages Array) +

+
+ + {!autoRender && ( + + )} +
+
+