From 6c6718d72bfbeb05c607516493778ced18a94497 Mon Sep 17 00:00:00 2001 From: Hyu Date: Thu, 8 Oct 2026 15:39:30 +0800 Subject: [PATCH] feat(web): persist assistant size and draggable position --- .../home/components/WorkspaceAssistant.tsx | 1024 ++++++++++------- .../home-sidebar/SidebarDataContext.tsx | 95 +- .../home-sidebar/sidebarDataStore.ts | 78 ++ web/src/i18n/locales/en-US.ts | 2 + web/src/i18n/locales/es-ES.ts | 2 + web/src/i18n/locales/ja-JP.ts | 2 + web/src/i18n/locales/ru-RU.ts | 2 + web/src/i18n/locales/th-TH.ts | 2 + web/src/i18n/locales/vi-VN.ts | 2 + web/src/i18n/locales/zh-Hans.ts | 2 + web/src/i18n/locales/zh-Hant.ts | 2 + web/tests/e2e/workspace-assistant.spec.ts | 155 +++ 12 files changed, 894 insertions(+), 474 deletions(-) create mode 100644 web/src/app/home/components/home-sidebar/sidebarDataStore.ts diff --git a/web/src/app/home/components/WorkspaceAssistant.tsx b/web/src/app/home/components/WorkspaceAssistant.tsx index 31e2c4316..d7c42f3af 100644 --- a/web/src/app/home/components/WorkspaceAssistant.tsx +++ b/web/src/app/home/components/WorkspaceAssistant.tsx @@ -189,6 +189,9 @@ function AssistantSessions({ storageKey }: { storageKey: string }) { ); } +const ASSISTANT_HEIGHT_STORAGE_KEY = 'langbot-assistant-panel-height'; +const ASSISTANT_POSITION_STORAGE_KEY = 'langbot-assistant-panel-position'; + function AssistantPanel({ storageKey, onSelect, @@ -199,6 +202,61 @@ function AssistantPanel({ const { t } = useTranslation(); const [open, setOpen] = useState(true); const [closing, setClosing] = useState(false); + const [horizontalPosition, setHorizontalPosition] = useState(() => { + try { + const saved = Number( + localStorage.getItem(ASSISTANT_POSITION_STORAGE_KEY), + ); + return Number.isFinite(saved) ? Math.max(-1, Math.min(1, saved)) : 0; + } catch { + return 0; + } + }); + const [viewportWidth, setViewportWidth] = useState(window.innerWidth); + useEffect(() => { + const updateWidth = () => setViewportWidth(window.innerWidth); + window.addEventListener('resize', updateWidth); + return () => window.removeEventListener('resize', updateWidth); + }, []); + useEffect(() => { + try { + localStorage.setItem( + ASSISTANT_POSITION_STORAGE_KEY, + String(horizontalPosition), + ); + } catch { + // Positioning remains available when storage is blocked. + } + }, [horizontalPosition]); + // Store a relative position so it follows screen changes while keeping a + // 12px margin. The collapsed dock remains aligned with the expanded panel. + const horizontalRange = Math.max(0, (viewportWidth - 480 - 24) / 2); + const moveStart = useRef<{ x: number; offset: number } | null>(null); + const dockWasDragged = useRef(false); + const [panelHeight, setPanelHeight] = useState(() => { + try { + const saved = Number(localStorage.getItem(ASSISTANT_HEIGHT_STORAGE_KEY)); + return Number.isFinite(saved) && saved > 0 + ? Math.max(280, saved) + : undefined; + } catch { + return undefined; + } + }); + useEffect(() => { + if (panelHeight === undefined) return; + try { + localStorage.setItem(ASSISTANT_HEIGHT_STORAGE_KEY, String(panelHeight)); + } catch { + // Resizing still works when browser storage is unavailable. + } + }, [panelHeight]); + const panelRef = useRef(null); + const resizeStart = useRef<{ y: number; height: number } | null>(null); + const resizeHeight = (height: number) => { + const maximum = Math.max(160, window.innerHeight - 48); + setPanelHeight(Math.min(maximum, Math.max(Math.min(280, maximum), height))); + }; const closeTimer = useRef(null); const [conversation, setConversation] = useState(null); const [text, setText] = useState(''); @@ -376,7 +434,7 @@ function AssistantPanel({ } }); return () => window.cancelAnimationFrame(frame); - }, [open, conversation, busy, pendingText, liveText, liveTool]); + }, [open, conversation, busy, pendingText, liveText, liveTool, panelHeight]); async function submit(approved?: boolean) { if ( @@ -510,410 +568,592 @@ function AssistantPanel({ return createPortal(
- {!open && ( - - )} - {open && ( -