feat: add adaptive agent workspace windows

This commit is contained in:
2026-08-19 12:57:37 +08:00
parent d08cf2abc1
commit f58e0a70f4
18 changed files with 1797 additions and 309 deletions
+97 -62
View File
@@ -35,7 +35,6 @@ import { MapDevPanel } from "./components/map-dev-panel";
import { MobileWorkbenchSheet } from "./components/mobile-workbench-sheet";
import { FeaturePopover } from "./components/feature-popover";
import { ScheduledConditionFeed } from "./components/scheduled-condition-feed";
import { WorkbenchAgentPanels } from "./components/workbench-agent-panels";
import {
ToolbarPanel,
type ExportViewPreset,
@@ -43,6 +42,10 @@ import {
type ToolbarToolId
} from "./components/toolbar-panel";
import { WorkbenchTopBar } from "./components/workbench-top-bar";
import {
WorkbenchMainFrame,
type WorkbenchNavigationSection
} from "./workspace/workbench-main-frame";
import { WORKBENCH_SCENARIOS, WORKBENCH_USER } from "./data/workbench-session";
import { useWorkbenchAgent } from "./hooks/use-workbench-agent";
import { useWorkbenchDrawing, type WorkbenchDrawMode } from "./hooks/use-workbench-drawing";
@@ -101,6 +104,8 @@ export function MapWorkbenchPage({
const devPanelEnabled = env.TJWATER_ENABLE_DEV_PANEL;
const mapContainerRef = useRef<HTMLDivElement | null>(null);
const [detailFeature, setDetailFeature] = useState<DetailFeature | null>(null);
const [mainFrameSection, setMainFrameSection] = useState<WorkbenchNavigationSection>("agent");
const [mainFrameNavigationCollapsed, setMainFrameNavigationCollapsed] = useState(false);
const [devPanelOpen, setDevPanelOpen] = useState(false);
const [impactVisible, setImpactVisible] = useState(false);
const [activeToolId, setActiveToolId] = useState<ToolbarToolId | null>(null);
@@ -149,7 +154,6 @@ export function MapWorkbenchPage({
agentPanelWidth,
closeMobileSheet,
conditionFeedExpanded,
conditionFeedMounted,
handleConditionExpandedChange,
isLargeScreen,
leftPanelOpen,
@@ -159,11 +163,8 @@ export function MapWorkbenchPage({
openConditionFeedForViewport,
rightPanelExpanded,
rightPanelOpen,
setAgentPanelWidth,
setMobileSheetSnap,
shouldShowConditionFeed,
toggleConditionFeedForViewport,
viewportWidth
toggleConditionFeedForViewport
} = useWorkbenchResponsiveLayout({
activeToolOpen: activeToolId !== null,
agentPanelOpen: agent.panelOpen,
@@ -173,6 +174,31 @@ export function MapWorkbenchPage({
onClearActiveTool: clearActiveTool
});
function openAgentWorkspaceForViewport() {
if (isLargeScreen) {
setMainFrameSection("agent");
setMainFrameNavigationCollapsed(false);
}
openAgentPanelForViewport();
}
function openConditionWorkspaceForViewport() {
if (isLargeScreen) {
setMainFrameSection("conditions");
setMainFrameNavigationCollapsed(false);
}
openConditionFeedForViewport();
}
function toggleConditionWorkspaceForViewport() {
if (isLargeScreen) {
setMainFrameSection((current) => current === "conditions" ? "agent" : "conditions");
setMainFrameNavigationCollapsed(false);
return;
}
toggleConditionFeedForViewport();
}
const { mapRef, mapReady, mapError, sourceStatuses, fitNetworkBounds } = useWorkbenchMap({
containerRef: mapContainerRef,
impactVisible,
@@ -182,9 +208,9 @@ export function MapWorkbenchPage({
const { controller: mapController, state: mapControllerState } = useWorkbenchMapController({
mapRef,
mapReady,
leftPanelOpen,
rightPanelOpen,
conditionPanelExpanded: rightPanelExpanded,
leftPanelOpen: isLargeScreen ? false : leftPanelOpen,
rightPanelOpen: isLargeScreen ? devPanelOpen : rightPanelOpen,
conditionPanelExpanded: isLargeScreen ? false : rightPanelExpanded,
agentPanelWidth
});
@@ -442,7 +468,7 @@ export function MapWorkbenchPage({
return;
}
openAgentPanelForViewport();
openAgentWorkspaceForViewport();
try {
await agent.submitPrompt(
@@ -479,7 +505,7 @@ export function MapWorkbenchPage({
return;
}
openConditionFeedForViewport();
openConditionWorkspaceForViewport();
setConditionFocusRequest((current) => ({
conditionId,
requestId: (current?.requestId ?? 0) + 1
@@ -601,9 +627,9 @@ export function MapWorkbenchPage({
zoom: trustedAction.zoom ?? Math.max(map.getZoom(), 14),
padding: getResponsiveWorkbenchPadding(
map,
leftPanelOpen,
rightPanelOpen,
rightPanelExpanded,
isLargeScreen ? false : leftPanelOpen,
isLargeScreen ? devPanelOpen : rightPanelOpen,
isLargeScreen ? false : rightPanelExpanded,
agentPanelWidth
),
duration: trustedAction.durationMs ?? 500
@@ -764,13 +790,6 @@ export function MapWorkbenchPage({
} as CSSProperties
}
>
<div
ref={mapContainerRef}
className="map-grid"
style={{ position: "absolute", inset: 0 }}
aria-label="供水管网地图"
/>
<WorkbenchTopBar
dataTime={headerDataTime}
modelName="AquaDispatch v1.0"
@@ -778,14 +797,14 @@ export function MapWorkbenchPage({
activeScenarioId={activeScenarioId}
alerts={workbenchAlerts}
user={user}
conditionFeedVisible={isLargeScreen ? shouldShowConditionFeed : mobileSheet === "condition"}
conditionFeedVisible={isLargeScreen ? mainFrameSection === "conditions" && !mainFrameNavigationCollapsed : mobileSheet === "condition"}
taskTickerAvailable={taskTickerAvailable}
taskTickerVisible={taskTickerVisible}
devPanelEnabled={devPanelEnabled}
devPanelOpen={devPanelOpen}
onSelectScenario={handleSelectScenario}
onSelectAlert={handleSelectAlert}
onToggleConditionFeed={toggleConditionFeedForViewport}
onToggleConditionFeed={toggleConditionWorkspaceForViewport}
onToggleTaskTicker={() => setTaskTickerVisible((current) => !current)}
onToggleDevPanel={() => setDevPanelOpen((current) => !current)}
onPreviewScenario={handlePreviewScenario}
@@ -799,50 +818,73 @@ export function MapWorkbenchPage({
onLogout={onLogout}
/>
<WorkbenchAgentPanels
panelProps={agentPanelProps}
panelOpen={agent.panelOpen}
panelCollapsing={agent.panelCollapsing}
conditionExpanded={shouldShowConditionFeed && conditionFeedExpanded}
personaState={agent.personaState}
statusLabel={agent.statusLabel}
viewportWidth={viewportWidth}
onCollapsePanel={agent.collapsePanel}
onExpandPanel={agent.expandPanel}
onWidthCommit={setAgentPanelWidth}
/>
{!devPanelOpen ? (
<div className="absolute right-2 top-24 z-20 hidden lg:block">
<MapToolbar items={toolbarItems} className="hidden lg:block" />
</div>
) : null}
{conditionFeedMounted && !devPanelOpen ? (
<div className="absolute right-16 top-24 z-20 hidden lg:block 2xl:[--workbench-condition-width:var(--workbench-condition-width-wide)]">
<WorkbenchMainFrame
testEnabled={devPanelEnabled}
activeSection={mainFrameSection}
navigationCollapsed={mainFrameNavigationCollapsed}
onActiveSectionChange={setMainFrameSection}
onNavigationCollapsedChange={setMainFrameNavigationCollapsed}
renderAgentPanel={(onCollapse) => (
<AgentCommandPanel
{...agentPanelProps}
presentation="desktop-dock"
onCollapse={onCollapse}
/>
)}
conditionsPanel={(
<ScheduledConditionFeed
presentation="desktop-floating"
presentation="desktop-dock"
conditions={scheduledConditions}
expanded={conditionFeedExpanded}
focusRequest={conditionFocusRequest}
loading={scheduledConditionsLoading}
visible={shouldShowConditionFeed}
visible
selectedConditionId={selectedConditionId}
onExpandedChange={handleConditionExpandedChange}
onFocusRequestHandled={handleConditionFocusRequestHandled}
onSelectedConditionChange={setSelectedConditionId}
onExpandAgent={openAgentPanelForViewport}
onExpandAgent={openAgentWorkspaceForViewport}
onLoadHistorySession={handleLoadAgentHistorySession}
onSubmitPrompt={agent.submitPrompt}
/>
</div>
) : null}
{!devPanelOpen ? (
<div className="absolute right-16 top-24 z-20 hidden lg:block">
<ToolbarPanel {...toolbarPanelProps} />
</div>
) : null}
)}
layersPanel={(
<div className="flex h-full min-h-0 flex-col gap-3 overflow-y-auto p-3">
<MapToolbar items={toolbarItems} orientation="horizontal" className="w-full flex-wrap" />
<ToolbarPanel {...toolbarPanelProps} panelWidthClassName="w-full" />
</div>
)}
toolsPanel={(
<div className="flex h-full min-h-0 flex-col gap-3 overflow-y-auto p-3">
<MapToolbar items={toolbarItems} orientation="horizontal" className="w-full flex-wrap" />
<ToolbarPanel {...toolbarPanelProps} panelWidthClassName="w-full" />
</div>
)}
mapContent={(
<div
ref={mapContainerRef}
className="map-grid absolute inset-0"
style={{ position: "absolute", inset: 0 }}
aria-label="供水管网地图"
/>
)}
mapOverlay={(
<div className="pointer-events-none absolute inset-0">
<div className="pointer-events-auto absolute right-3 top-3 hidden lg:block">
<MapToolbar items={toolbarItems} />
</div>
<div className="pointer-events-auto absolute right-16 top-3 hidden lg:block">
<ToolbarPanel {...toolbarPanelProps} />
</div>
<div className="absolute bottom-0 right-0 flex flex-col items-end gap-2">
<div className="flex items-end gap-2 pr-2">
<MapZoom mapRef={mapRef} mapReady={mapReady} onHome={fitNetworkBounds} />
</div>
<MapScaleLine mapRef={mapRef} mapReady={mapReady} />
</div>
</div>
)}
/>
{devPanelEnabled && devPanelOpen ? (
<MapDevPanel
@@ -862,13 +904,6 @@ export function MapWorkbenchPage({
</div>
) : null}
<div className="pointer-events-none absolute bottom-0 right-0 z-30 hidden flex-col items-end gap-2 md:flex">
<div className="flex items-end gap-2 pr-2">
<MapZoom mapRef={mapRef} mapReady={mapReady} onHome={fitNetworkBounds} />
</div>
<MapScaleLine mapRef={mapRef} mapReady={mapReady} />
</div>
<AnimatePresence initial={false}>
{shouldShowTaskTicker ? (
<motion.div