feat(desktop): DeerFlow visual redesign + stream hang fix + intelligence client
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
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
DeerFlow frontend visual overhaul: - Card-style input box (white rounded card, textarea top, actions bottom) - Dropdown mode selector (闪速/思考/Pro/Ultra with icons+descriptions) - Colored quick-action chips (小惊喜/写作/研究/收集/学习) - Minimal top bar (title + token count + export) - Warm gray color system (#faf9f6 bg, #f5f4f1 sidebar, #e8e6e1 border) - DeerFlow-style sidebar (新对话/对话/智能体 nav) - Reasoning block, tool call chain, task progress visualization - Streaming text, model selector, suggestion chips components - Resizable artifact panel with drag handle - Virtualized message list for 100+ messages Bug fixes: - Stream hang: GatewayClient onclose code 1000 now calls onComplete - WebView2 textarea border: CSS !important override for UA styles - Gateway stream event handling (response/phase/tool_call types) Intelligence client: - Unified client with fallback drivers (compactor/heartbeat/identity/memory/reflection) - Gateway API types and type conversions
This commit is contained in:
136
desktop/src/components/ai/ResizableChatLayout.tsx
Normal file
136
desktop/src/components/ai/ResizableChatLayout.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import { useCallback, type ReactNode } from 'react';
|
||||
import { Group, Panel, Separator } from 'react-resizable-panels';
|
||||
import { X, PanelRightOpen, PanelRightClose } from 'lucide-react';
|
||||
|
||||
/**
|
||||
* Resizable dual-panel layout for chat + artifact/detail panel.
|
||||
*
|
||||
* Uses react-resizable-panels v4 API:
|
||||
* - Left panel: Chat area (always visible)
|
||||
* - Right panel: Artifact/detail viewer (collapsible)
|
||||
* - Draggable resize handle between panels
|
||||
* - Persisted panel sizes via localStorage
|
||||
*/
|
||||
|
||||
interface ResizableChatLayoutProps {
|
||||
chatPanel: ReactNode;
|
||||
rightPanel?: ReactNode;
|
||||
rightPanelTitle?: string;
|
||||
rightPanelOpen?: boolean;
|
||||
onRightPanelToggle?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'zclaw-layout-panels';
|
||||
const LEFT_PANEL_ID = 'chat-panel';
|
||||
const RIGHT_PANEL_ID = 'detail-panel';
|
||||
|
||||
function loadPanelSizes(): { left: string; right: string } {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored);
|
||||
if (parsed.left && parsed.right) {
|
||||
return { left: parsed.left, right: parsed.right };
|
||||
}
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
return { left: '65%', right: '35%' };
|
||||
}
|
||||
|
||||
function savePanelSizes(layout: Record<string, number>) {
|
||||
try {
|
||||
const left = layout[LEFT_PANEL_ID];
|
||||
const right = layout[RIGHT_PANEL_ID];
|
||||
if (left !== undefined && right !== undefined) {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify({ left, right }));
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
export function ResizableChatLayout({
|
||||
chatPanel,
|
||||
rightPanel,
|
||||
rightPanelTitle = '详情',
|
||||
rightPanelOpen = false,
|
||||
onRightPanelToggle,
|
||||
}: ResizableChatLayoutProps) {
|
||||
const sizes = loadPanelSizes();
|
||||
|
||||
const handleToggle = useCallback(() => {
|
||||
onRightPanelToggle?.(!rightPanelOpen);
|
||||
}, [rightPanelOpen, onRightPanelToggle]);
|
||||
|
||||
if (!rightPanelOpen || !rightPanel) {
|
||||
return (
|
||||
<div className="flex-1 flex flex-col overflow-hidden relative">
|
||||
{chatPanel}
|
||||
<button
|
||||
onClick={handleToggle}
|
||||
className="absolute top-3 right-3 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>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<Group
|
||||
orientation="horizontal"
|
||||
onLayoutChanged={(layout) => savePanelSizes(layout)}
|
||||
>
|
||||
{/* Left panel: Chat */}
|
||||
<Panel
|
||||
id={LEFT_PANEL_ID}
|
||||
defaultSize={sizes.left}
|
||||
minSize="40%"
|
||||
>
|
||||
<div className="h-full flex flex-col relative">
|
||||
{chatPanel}
|
||||
<button
|
||||
onClick={handleToggle}
|
||||
className="absolute top-3 right-3 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>
|
||||
</Panel>
|
||||
|
||||
{/* Resize handle */}
|
||||
<Separator className="w-1.5 flex items-center justify-center group cursor-col-resize hover:bg-orange-100 dark:hover:bg-orange-900/20 transition-colors">
|
||||
<div className="w-0.5 h-8 rounded-full bg-gray-300 dark:bg-gray-600 group-hover:bg-orange-400 dark:group-hover:bg-orange-500 transition-colors" />
|
||||
</Separator>
|
||||
|
||||
{/* Right panel: Artifact/Detail */}
|
||||
<Panel
|
||||
id={RIGHT_PANEL_ID}
|
||||
defaultSize={sizes.right}
|
||||
minSize="25%"
|
||||
>
|
||||
<div className="h-full flex flex-col bg-gray-50 dark:bg-gray-900 border-l border-gray-200 dark:border-gray-800">
|
||||
{/* Panel header */}
|
||||
<div className="h-12 flex items-center justify-between px-4 border-b border-gray-200 dark:border-gray-800 flex-shrink-0">
|
||||
<span className="text-xs font-medium text-gray-600 dark:text-gray-400 uppercase tracking-wide">
|
||||
{rightPanelTitle}
|
||||
</span>
|
||||
<button
|
||||
onClick={handleToggle}
|
||||
className="p-1 rounded text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
|
||||
title="关闭面板"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
{/* Panel content */}
|
||||
<div className="flex-1 overflow-y-auto custom-scrollbar">
|
||||
{rightPanel}
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</Group>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user