fix(ui): panel toggle in header bar + message spacing
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

- Move side panel toggle from floating button to chat header right side
  (Trae Solo style) via new PanelToggleButton component
- Add px-6 py-4 padding to message list container
- Add mb-5 gap between messages for readable vertical spacing
This commit is contained in:
iven
2026-04-10 12:03:29 +08:00
parent 99262efca4
commit 34043de685
2 changed files with 41 additions and 18 deletions

View File

@@ -12,7 +12,7 @@ import { type UnlistenFn } from '@tauri-apps/api/event';
import { safeListenEvent } from '../lib/safe-tauri'; import { safeListenEvent } from '../lib/safe-tauri';
import { Paperclip, ArrowUp, MessageSquare, Download, X, FileText, Image as ImageIcon, Search, ClipboardList, Square } from 'lucide-react'; import { Paperclip, ArrowUp, MessageSquare, Download, X, FileText, Image as ImageIcon, Search, ClipboardList, Square } from 'lucide-react';
import { Button, EmptyState, MessageListSkeleton, LoadingDots } from './ui'; import { Button, EmptyState, MessageListSkeleton, LoadingDots } from './ui';
import { ResizableChatLayout } from './ai/ResizableChatLayout'; import { ResizableChatLayout, PanelToggleButton } from './ai/ResizableChatLayout';
import { ArtifactPanel } from './ai/ArtifactPanel'; import { ArtifactPanel } from './ai/ArtifactPanel';
import { ToolCallChain, type ToolCallStep } from './ai/ToolCallChain'; import { ToolCallChain, type ToolCallStep } from './ai/ToolCallChain';
import { TaskProgress, type Subtask } from './ai/TaskProgress'; import { TaskProgress, type Subtask } from './ai/TaskProgress';
@@ -361,6 +361,13 @@ export function ChatArea({ compact, onOpenDetail }: { compact?: boolean; onOpenD
<Search className="w-3.5 h-3.5" /> <Search className="w-3.5 h-3.5" />
</Button> </Button>
)} )}
{/* Side panel toggle — Trae Solo style, in header */}
{!compact && (
<PanelToggleButton
panelOpen={artifactPanelOpen}
onToggle={() => setArtifactPanelOpen(!artifactPanelOpen)}
/>
)}
{/* 详情按钮 (简洁模式) */} {/* 详情按钮 (简洁模式) */}
{compact && onOpenDetail && ( {compact && onOpenDetail && (
<Button <Button
@@ -398,7 +405,7 @@ export function ChatArea({ compact, onOpenDetail }: { compact?: boolean; onOpenD
</AnimatePresence> </AnimatePresence>
{/* Messages */} {/* Messages */}
<Conversation className="flex-1 bg-white dark:bg-gray-900"> <Conversation className="flex-1 bg-white dark:bg-gray-900 px-6 py-4">
<AnimatePresence mode="popLayout"> <AnimatePresence mode="popLayout">
{/* Loading skeleton */} {/* Loading skeleton */}
{isLoading && messages.length === 0 && ( {isLoading && messages.length === 0 && (
@@ -467,6 +474,7 @@ export function ChatArea({ compact, onOpenDetail }: { compact?: boolean; onOpenD
animate="visible" animate="visible"
layout layout
transition={defaultTransition} transition={defaultTransition}
className="mb-5"
> >
<MessageBubble message={message} onRetry={createRetryHandler(message.id)} /> <MessageBubble message={message} onRetry={createRetryHandler(message.id)} />
</motion.div> </motion.div>

View File

@@ -10,6 +10,9 @@ import { X, PanelRightOpen, PanelRightClose } from 'lucide-react';
* - Right panel: Artifact/detail viewer (collapsible) * - Right panel: Artifact/detail viewer (collapsible)
* - Draggable resize handle between panels * - Draggable resize handle between panels
* - Persisted panel sizes via localStorage * - Persisted panel sizes via localStorage
*
* Side-panel toggle is injected into the chat header via `headerAction`
* so it sits cleanly in the top-right corner (Trae Solo style).
*/ */
interface ResizableChatLayoutProps { interface ResizableChatLayoutProps {
@@ -61,16 +64,10 @@ export function ResizableChatLayout({
}, [rightPanelOpen, onRightPanelToggle]); }, [rightPanelOpen, onRightPanelToggle]);
if (!rightPanelOpen || !rightPanel) { if (!rightPanelOpen || !rightPanel) {
// Panel closed: just render chat panel, no floating button
return ( return (
<div className="h-full flex flex-col overflow-hidden relative"> <div className="h-full flex flex-col overflow-hidden">
{chatPanel} {chatPanel}
<button
onClick={handleToggle}
className="absolute top-20 right-4 z-10 p-1.5 rounded-md bg-white/80 dark:bg-gray-800/80 border border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-white dark:hover:bg-gray-800 transition-colors shadow-sm"
title="打开侧面板"
>
<PanelRightOpen className="w-4 h-4" />
</button>
</div> </div>
); );
} }
@@ -87,15 +84,8 @@ export function ResizableChatLayout({
defaultSize={sizes.left} defaultSize={sizes.left}
minSize="40%" minSize="40%"
> >
<div className="h-full flex flex-col relative"> <div className="h-full flex flex-col">
{chatPanel} {chatPanel}
<button
onClick={handleToggle}
className="absolute top-20 right-4 z-10 p-1.5 rounded-md bg-white/80 dark:bg-gray-800/80 border border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-white dark:hover:bg-gray-800 transition-colors shadow-sm"
title="关闭侧面板"
>
<PanelRightClose className="w-4 h-4" />
</button>
</div> </div>
</Panel> </Panel>
@@ -134,3 +124,28 @@ export function ResizableChatLayout({
</div> </div>
); );
} }
/**
* Toggle button for embedding in chat header.
* Renders PanelRightOpen/PanelRightClose icon — Trae Solo style.
*/
export function PanelToggleButton({
panelOpen,
onToggle,
}: {
panelOpen: boolean;
onToggle: () => void;
}) {
return (
<button
onClick={onToggle}
className="p-1.5 rounded-md text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
title={panelOpen ? '关闭侧面板' : '打开侧面板'}
>
{panelOpen
? <PanelRightClose className="w-4 h-4" />
: <PanelRightOpen className="w-4 h-4" />
}
</button>
);
}