From 3729372e1436fca5c3308c2467ccc31a80825eec Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:27:28 +0800 Subject: [PATCH] fix(web): v0.2 round-3 workbench: external right-rail toggle + drag-resize + command input own row + Code Agent result polling (#774) Fixes HWLAB #759 round-3 v0.2 WebUI issues introduced by the React migration: 1. device-pod right sidebar could not be re-expanded because the toggle button was nested inside the right sidebar which is fully hidden when collapsed. Move the toggle button to be a direct child of the right sidebar via a new anchor button + content wrapper. Add --right-collapsed-width and a vertical-rl text rotation so the toggle stays visible and accessible when the right sidebar is collapsed. 2. The Session sidebar and right sidebar resize handles had no event handlers after the React migration; dragging did nothing. Add a new useSidebarResize hook with pointer/keyboard handlers, dynamic bounds based on viewport and opposite sidebar width, localStorage persistence, and CSS variable injection on the workbench shell. 3. document.querySelector("#command-form > div") (the input shell) was on the same row as the timeout selects. Restore the original layout by switching the command-bar to grid-template-areas with the input shell on its own row and the three selects on a second row. 4. Code Agent was unusable from the React UI because the 202 response has status=running and no reply text; the React UI marked the message as completed with the fallback empty body. Add a polling loop in the workbench store that follows resultUrl until status reaches a terminal value, and surface assistantText / reply.content from the polled AgentChatResultResponse. Add AgentChatResultResponse, AgentChatReply and AgentRunProvenance types plus a getAgentChatResult API method. Verified locally on G14:web/hwlab-cloud-web: bun run check (12 fresh dist files), bun run scripts/tsc-check.ts (strict React TSX, 0 explicit any), bun test (2 pass / 0 fail). Co-authored-by: HWLAB --- web/hwlab-cloud-web/src/App.tsx | 99 +++++++++- .../device-pod/DevicePodSidebar.tsx | 6 +- .../src/hooks/useSidebarResize.ts | 186 ++++++++++++++++++ .../src/services/api/client.ts | 2 + web/hwlab-cloud-web/src/state/conversation.ts | 29 ++- web/hwlab-cloud-web/src/state/workbench.ts | 52 ++++- web/hwlab-cloud-web/src/styles/workbench.css | 35 +++- web/hwlab-cloud-web/src/types/domain.ts | 52 +++++ 8 files changed, 437 insertions(+), 24 deletions(-) create mode 100644 web/hwlab-cloud-web/src/hooks/useSidebarResize.ts diff --git a/web/hwlab-cloud-web/src/App.tsx b/web/hwlab-cloud-web/src/App.tsx index a77423a5..bd9e2e6b 100644 --- a/web/hwlab-cloud-web/src/App.tsx +++ b/web/hwlab-cloud-web/src/App.tsx @@ -1,7 +1,8 @@ import type { ReactElement } from "react"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useAuth } from "./hooks/useAuth"; +import { useSidebarResize } from "./hooks/useSidebarResize"; import { useWorkbenchStore } from "./state/workbench"; import type { RouteId } from "./types/domain"; import { LoginShell } from "./components/auth/LoginShell"; @@ -14,6 +15,15 @@ import { SettingsView } from "./components/settings/SettingsView"; import { SkillsView } from "./components/skills/SkillsView"; import { fetchText } from "./services/api/client"; +const SESSION_SIDEBAR_STORAGE_KEY = "hwlab.workbench.sessionSidebarWidth.v1"; +const SESSION_SIDEBAR_DEFAULT_WIDTH = 330; +const SESSION_SIDEBAR_MIN_WIDTH = 180; +const SESSION_SIDEBAR_MAX_WIDTH = 640; +const RIGHT_SIDEBAR_STORAGE_KEY = "hwlab.workbench.rightSidebarWidth.v1"; +const RIGHT_SIDEBAR_DEFAULT_WIDTH = 728; +const RIGHT_SIDEBAR_MIN_WIDTH = 560; +const RIGHT_SIDEBAR_MAX_WIDTH = 900; + export function App(): ReactElement { const auth = useAuth(); const store = useWorkbenchStore(auth.authState === "authenticated"); @@ -21,6 +31,28 @@ export function App(): ReactElement { const [leftCollapsed, setLeftCollapsed] = useState(false); const [rightCollapsed, setRightCollapsed] = useState(false); const [help, setHelp] = useState("加载中"); + const shellRef = useRef(null); + const narrow = useMatchMedia("(max-width: 1240px)"); + + const sessionResize = useSidebarResize({ + storageKey: SESSION_SIDEBAR_STORAGE_KEY, + defaultWidth: SESSION_SIDEBAR_DEFAULT_WIDTH, + bounds: { min: SESSION_SIDEBAR_MIN_WIDTH, max: SESSION_SIDEBAR_MAX_WIDTH }, + side: "left", + disabled: narrow || leftCollapsed, + shellElement: shellRef.current, + cssVariable: "--session-sidebar-width" + }); + + const rightResize = useSidebarResize({ + storageKey: RIGHT_SIDEBAR_STORAGE_KEY, + defaultWidth: RIGHT_SIDEBAR_DEFAULT_WIDTH, + bounds: { min: RIGHT_SIDEBAR_MIN_WIDTH, max: RIGHT_SIDEBAR_MAX_WIDTH }, + side: "right", + disabled: narrow || rightCollapsed, + shellElement: shellRef.current, + cssVariable: "--right-sidebar-width" + }); useEffect(() => { const listener = (): void => setRoute(routeFromLocation()); @@ -44,10 +76,36 @@ export function App(): ReactElement { if (auth.authState === "login") return ; return ( -
+
setLeftCollapsed((value) => !value)} /> void store.createSession()} onDelete={() => void store.deleteCurrentSession()} onSelect={(tab) => void store.selectConversation(tab)} /> -
); } +function useMatchMedia(query: string): boolean { + const [match, setMatch] = useState(() => typeof window !== "undefined" && window.matchMedia(query).matches); + useEffect(() => { + if (typeof window === "undefined") return; + const media = window.matchMedia(query); + const listener = (event: MediaQueryListEvent): void => setMatch(event.matches); + setMatch(media.matches); + media.addEventListener("change", listener); + return () => media.removeEventListener("change", listener); + }, [query]); + return match; +} + function AuthLoadingShell(): ReactElement { return (
diff --git a/web/hwlab-cloud-web/src/components/device-pod/DevicePodSidebar.tsx b/web/hwlab-cloud-web/src/components/device-pod/DevicePodSidebar.tsx index b74f379c..94bd29c1 100644 --- a/web/hwlab-cloud-web/src/components/device-pod/DevicePodSidebar.tsx +++ b/web/hwlab-cloud-web/src/components/device-pod/DevicePodSidebar.tsx @@ -23,13 +23,14 @@ export function DevicePodSidebar({ live, selectedDevicePodId, onSelect, collapse const summary = status?.summary ?? selected.summary ?? {}; return ( -