diff --git a/web/src/App.tsx b/web/src/App.tsx index 07739f1..33148cc 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState, type MouseEvent as ReactMouseEvent } from 'react'; import { Zap, ArrowLeft, @@ -11,6 +11,8 @@ import { Brain, Settings as SettingsIcon, ChevronRight, + MessageSquare, + Calendar, } from 'lucide-react'; import { ChatContainer } from './components/Chat/ChatContainer'; import { TopicList } from './components/Sidebar/TopicList'; @@ -200,6 +202,75 @@ function App() { // 设置弹窗关闭计数器:每次关闭时递增,用于通知 ExpertSelector 刷新已选专家状态 const [settingsClosedTick, setSettingsClosedTick] = useState(0); + // ---- 三栏列宽(拖拽调宽,对齐 harness AppFrame)---- + const [sidebarWidth, setSidebarWidth] = useState(() => { + const parsed = parseInt(localStorage.getItem('picobot-sidebar-width') ?? '', 10); + return Number.isFinite(parsed) ? Math.min(Math.max(parsed, 216), 400) : 288; + }); + const [rightPanelWidth, setRightPanelWidth] = useState(() => { + const parsed = parseInt(localStorage.getItem('picobot-right-panel-width') ?? '', 10); + return Number.isFinite(parsed) ? Math.min(Math.max(parsed, 240), 420) : 320; + }); + + // 拖拽期间禁用 width transition,避免面板"追鼠标"延迟感 + const [isResizing, setIsResizing] = useState(false); + + const startSidebarDrag = useCallback( + (e: ReactMouseEvent) => { + e.preventDefault(); + const startX = e.clientX; + const startWidth = sidebarWidth; + setIsResizing(true); + const onMove = (ev: MouseEvent) => { + setSidebarWidth(Math.min(Math.max(startWidth + ev.clientX - startX, 216), 400)); + }; + const onUp = (ev: MouseEvent) => { + document.removeEventListener('mousemove', onMove); + document.removeEventListener('mouseup', onUp); + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + setIsResizing(false); + localStorage.setItem( + 'picobot-sidebar-width', + String(Math.min(Math.max(startWidth + ev.clientX - startX, 216), 400)), + ); + }; + document.addEventListener('mousemove', onMove); + document.addEventListener('mouseup', onUp); + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + }, + [sidebarWidth], + ); + + const startRightPanelDrag = useCallback( + (e: ReactMouseEvent) => { + e.preventDefault(); + const startX = e.clientX; + const startWidth = rightPanelWidth; + setIsResizing(true); + const onMove = (ev: MouseEvent) => { + setRightPanelWidth(Math.min(Math.max(startWidth - (ev.clientX - startX), 240), 420)); + }; + const onUp = (ev: MouseEvent) => { + document.removeEventListener('mousemove', onMove); + document.removeEventListener('mouseup', onUp); + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + setIsResizing(false); + localStorage.setItem( + 'picobot-right-panel-width', + String(Math.min(Math.max(startWidth - (ev.clientX - startX), 240), 420)), + ); + }; + document.addEventListener('mousemove', onMove); + document.addEventListener('mouseup', onUp); + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + }, + [rightPanelWidth], + ); + const handleSaveConnection = useCallback((host: string, port: number) => { setGatewaySettings({ host, port }); }, []); @@ -615,189 +686,222 @@ function App() { }, [schedulerView, subAgentView, selectedTopic]); return ( -