import { useCallback, useEffect, useMemo, useRef, useState, type ComponentProps, type CSSProperties } from "react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { CalendarClock } from "lucide-react"; import { AgentCommandPanel, AgentPersona, toTrustedMapAction, type FrontendActionRequest, type UIEnvelopePayload, type AgentUiResult } from "@/features/agent"; import { MapErrorNotice, MapLoadingNotice, MapScaleLine, MapSourceStatusNotice, MapToolbar, MapZoom, showMapNotice, type MapToolbarItem, type MapSourceStatus } from "@/features/map/core"; import { env } from "@/shared/config/env"; import { AgentTaskTicker } from "./components/agent-task-ticker"; import { MapDevPanel } from "./components/map-dev-panel"; import { MobileWorkbenchSheet, type MobileWorkbenchSheetSnap } 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, type ToolbarPanelProps, type ToolbarToolId } from "./components/toolbar-panel"; import { WorkbenchTopBar } from "./components/workbench-top-bar"; 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"; import { useWorkbenchMap } from "./hooks/use-workbench-map"; import { useWorkbenchMapController } from "./hooks/use-workbench-map-controller"; import { useWorkbenchRuntimeData } from "./hooks/use-workbench-runtime-data"; import { toMapFeatureReference } from "./hooks/use-map-interactions"; import { useWorkbenchMeasurement, type WorkbenchMeasureMode, type WorkbenchMeasureUnit } from "./hooks/use-workbench-measurement"; import { WORKBENCH_LAYOUT, getWorkbenchBasemapTone, getWorkbenchLayoutCssVariables } from "./layout/workbench-layout"; import { getResponsiveWorkbenchPadding } from "./map/camera"; import { exportMapViewImage } from "./map/export-view"; import { parseScadaAnalysisItems } from "./map/scada-analysis"; import { BASE_LAYER_OPTIONS, INITIAL_LAYER_VISIBILITY, MAP_LEGEND_ITEMS, WORKBENCH_LAYER_GROUPS, applyBaseLayerVisibility, createLayerControlItems, getWorkbenchLayerIds } from "./map/map-control-config"; import { waterNetworkToolbarItems } from "./map/toolbar-config"; import type { DetailFeature, ScheduledConditionItem, ScheduledConditionRecord, WorkbenchAlert } from "./types"; import { createScheduledConditionAlerts, getScheduledConditionIdFromAlertId } from "./utils/scheduled-condition-alerts"; import { createAlertQueueConversationPrompt } from "./utils/scheduled-condition-prompts"; const WORKBENCH_LAYOUT_CSS_VARIABLES = getWorkbenchLayoutCssVariables(); export function MapWorkbenchPage() { const hasMapboxToken = Boolean(env.DRAINAGE_MAPBOX_ACCESS_TOKEN); const devPanelEnabled = env.DRAINAGE_ENABLE_DEV_PANEL; const mapContainerRef = useRef(null); const [detailFeature, setDetailFeature] = useState(null); const [devPanelOpen, setDevPanelOpen] = useState(false); const [impactVisible, setImpactVisible] = useState(false); const [isLargeScreen, setIsLargeScreen] = useState(false); const [viewportWidth, setViewportWidth] = useState(WORKBENCH_LAYOUT.desktopMinWidth); const [agentPanelWidth, setAgentPanelWidth] = useState( WORKBENCH_LAYOUT.desktop.agentWidth ); const [activeToolId, setActiveToolId] = useState(null); const [conditionFeedVisible, setConditionFeedVisible] = useState(false); const [conditionFeedMounted, setConditionFeedMounted] = useState(true); const [conditionFeedExpanded, setConditionFeedExpanded] = useState(false); const [mobileSheet, setMobileSheet] = useState<"agent" | "condition" | null>(null); const [mobileSheetSnap, setMobileSheetSnap] = useState("half"); const [agentCollapsedForCondition, setAgentCollapsedForCondition] = useState(false); const prefersReducedMotion = useReducedMotion(); const [taskTickerVisible, setTaskTickerVisible] = useState(true); const [selectedConditionId, setSelectedConditionId] = useState(null); const [conditionFocusRequest, setConditionFocusRequest] = useState<{ conditionId: string; requestId: number; } | null>(null); const [activeBaseLayerId, setActiveBaseLayerId] = useState( hasMapboxToken ? "mapbox-light" : "none" ); const basemapTone = getWorkbenchBasemapTone(activeBaseLayerId); const [activeScenarioId, setActiveScenarioId] = useState(WORKBENCH_SCENARIOS[0]?.id ?? ""); const [activeMeasureModeId, setActiveMeasureModeId] = useState("distance"); const [activeMeasureUnitId, setActiveMeasureUnitId] = useState("km"); const [activeDrawToolId, setActiveDrawToolId] = useState(null); const [layerVisibility, setLayerVisibility] = useState>(INITIAL_LAYER_VISIBILITY); const [agentUiResults, setAgentUiResults] = useState([]); const { headerDataTime, scheduledConditions, scheduledConditionsLoading } = useWorkbenchRuntimeData(); const baseLayerOptions = useMemo( () => BASE_LAYER_OPTIONS.map((option) => ({ ...option, disabled: option.id !== "none" && !hasMapboxToken })), [hasMapboxToken] ); const handleSelectFeature = useCallback((feature: DetailFeature) => { setDetailFeature(feature); }, []); useEffect(() => { const mediaQuery = window.matchMedia("(min-width: 1024px)"); const handleChange = () => { setIsLargeScreen(mediaQuery.matches); setViewportWidth(window.innerWidth); if (mediaQuery.matches) { setMobileSheet(null); } }; handleChange(); setConditionFeedVisible(window.innerWidth >= 1280); mediaQuery.addEventListener("change", handleChange); window.addEventListener("resize", handleChange); return () => { mediaQuery.removeEventListener("change", handleChange); window.removeEventListener("resize", handleChange); }; }, []); const shouldShowConditionFeed = conditionFeedVisible && !activeToolId; useEffect(() => { if (shouldShowConditionFeed) { setConditionFeedMounted(true); return; } const timeoutId = window.setTimeout(() => { setConditionFeedMounted(false); }, 170); return () => window.clearTimeout(timeoutId); }, [shouldShowConditionFeed]); const agent = useWorkbenchAgent({ onUiEnvelope: handleAgentUiEnvelope, onFrontendAction: handleFrontendAction }); function openAgentPanelForViewport() { if (isLargeScreen) { if (conditionFeedExpanded && viewportWidth < 1440) { setConditionFeedExpanded(false); setAgentCollapsedForCondition(false); } agent.expandPanel(); return; } setMobileSheet("agent"); setMobileSheetSnap("half"); } const closeMobileSheet = useCallback(() => { setMobileSheet(null); }, []); function handleConditionExpandedChange(nextExpanded: boolean) { if (nextExpanded && isLargeScreen && viewportWidth < 1440) { setAgentCollapsedForCondition(agent.panelOpen); agent.collapsePanel(); } else if (!nextExpanded && agentCollapsedForCondition) { setAgentCollapsedForCondition(false); agent.expandPanel(); } setConditionFeedExpanded(nextExpanded); } useEffect(() => { if (!isLargeScreen || shouldShowConditionFeed || !conditionFeedExpanded) { return; } setConditionFeedExpanded(false); if (agentCollapsedForCondition) { setAgentCollapsedForCondition(false); agent.expandPanel(); } }, [ agent, agentCollapsedForCondition, conditionFeedExpanded, isLargeScreen, shouldShowConditionFeed ]); function toggleConditionFeedForViewport() { setActiveToolId(null); if (isLargeScreen) { setConditionFeedVisible((current) => !current); return; } if (mobileSheet === "condition") { closeMobileSheet(); return; } setConditionFeedExpanded(false); setMobileSheetSnap("half"); setMobileSheet("condition"); } const leftPanelOpen = isLargeScreen && agent.panelOpen; const rightPanelOpen = isLargeScreen && (devPanelOpen || shouldShowConditionFeed); const rightPanelExpanded = isLargeScreen && shouldShowConditionFeed && conditionFeedExpanded && !devPanelOpen; const { mapRef, mapReady, mapError, sourceStatuses, fitNetworkBounds } = useWorkbenchMap({ containerRef: mapContainerRef, leftPanelOpen, rightPanelOpen, impactVisible, onSelectFeature: handleSelectFeature, selectedFeature: detailFeature }); const { controller: mapController, state: mapControllerState } = useWorkbenchMapController({ mapRef, mapReady, leftPanelOpen, rightPanelOpen, conditionPanelExpanded: rightPanelExpanded, agentPanelWidth }); const activeAgentUiResults = useMemo( () => agentUiResults.filter((result) => result.sessionId === agent.sessionId), [agent.sessionId, agentUiResults] ); const conditionAlerts = useMemo( () => createScheduledConditionAlerts(scheduledConditions), [scheduledConditions] ); const runningTickerConditions = useMemo( () => scheduledConditions.filter( (condition): condition is ScheduledConditionRecord => condition.kind === "condition" && condition.status === "running" ), [scheduledConditions] ); const workbenchAlerts = conditionAlerts; const drawing = useWorkbenchDrawing({ mapRef, mapReady, activeMode: activeDrawToolId, onModeChange: setActiveDrawToolId }); const measurement = useWorkbenchMeasurement({ mapRef, mapReady, mode: activeMeasureModeId, unit: activeMeasureUnitId }); const taskTickerAvailable = runningTickerConditions.length > 0; const shouldShowTaskTicker = mobileSheet === null && !conditionFeedExpanded && taskTickerVisible && taskTickerAvailable; const taskTickerEnterState = prefersReducedMotion ? { opacity: 1, scale: 1, y: 0 } : { opacity: 1, scale: 1, y: 0, transition: { type: "spring" as const, stiffness: 420, damping: 30, mass: 0.75 } }; const taskTickerExitState = prefersReducedMotion ? { opacity: 0, scale: 1, y: 0, transition: { duration: 0 } } : { opacity: 0, scale: 0.96, y: -8, transition: { duration: 0.18, ease: [0.5, 0, 0.2, 1] as const } }; useEffect(() => { if (!mapReady || !mapRef.current) { return; } applyBaseLayerVisibility(mapRef.current, activeBaseLayerId); }, [activeBaseLayerId, mapReady, mapRef]); const toggleToolbarTool = useCallback((toolId: ToolbarToolId) => { setActiveToolId((current) => { return current === toolId ? null : toolId; }); }, []); const handleConditionFocusRequestHandled = useCallback((requestId: number) => { setConditionFocusRequest((current) => (current?.requestId === requestId ? null : current)); }, []); const toolbarItems = useMemo( () => waterNetworkToolbarItems.map((item) => ({ ...item, active: activeToolId === item.id, disabled: !mapReady, onClick: () => toggleToolbarTool(item.id as ToolbarToolId) })), [activeToolId, mapReady, toggleToolbarTool] ); const layerControlItems = useMemo( () => createLayerControlItems(layerVisibility), [layerVisibility] ); function handleToggleLayer(layerControlId: string, visible: boolean) { const map = mapRef.current; setLayerVisibility((current) => ({ ...current, [layerControlId]: visible })); if (layerControlId === "simulation") { setImpactVisible(visible); } if (!map || !mapReady) { return; } getWorkbenchLayerIds(map, layerControlId).forEach((layerId) => { if (map.getLayer(layerId)) { map.setLayoutProperty(layerId, "visibility", visible ? "visible" : "none"); } }); } async function handleExportView(preset: ExportViewPreset) { const map = mapRef.current; if (!map || !mapReady) { showMapNotice({ tone: "warning", message: "地图尚未就绪,无法导出视图。" }); return; } const targetLongEdge = getExportTargetLongEdge(preset); const exportLabel = getExportPresetLabel(preset); showMapNotice({ id: "map-view-export", tone: "loading", title: "正在导出", message: `正在生成${exportLabel}地图截图...`, duration: Infinity }); try { const result = await exportMapViewImage(map, { scale: preset === "current" ? 1 : undefined, targetLongEdge, selectedFeature: toMapFeatureReference(detailFeature) }); showMapNotice({ id: "map-view-export", tone: "success", message: `已导出 ${result.width} × ${result.height} PNG。` }); } catch { showMapNotice({ id: "map-view-export", tone: "error", title: "导出失败", message: "当前底图或瓦片可能限制了画布导出。" }); } } function handleRefreshTiles() { const map = mapRef.current; if (!map || !mapReady) { showMapNotice({ tone: "warning", message: "地图尚未就绪,无法刷新瓦片。" }); return; } map.triggerRepaint(); showMapNotice({ tone: "success", message: "已请求地图重新渲染,业务瓦片将在视图变化时刷新。" }); } function handleShowDataStatus() { if (sourceStatuses.length === 0) { showMapNotice({ tone: "info", message: "数据源状态正在初始化。" }); return; } showMapNotice({ tone: sourceStatuses.some((sourceStatus) => sourceStatus.status === "offline") ? "warning" : "info", title: "数据状态", message: sourceStatuses .map((sourceStatus) => `${sourceStatus.sourceName}:${sourceStatus.status}`) .join(" · "), duration: 5000 }); } function handleShowShortcuts() { showMapNotice({ tone: "info", title: "快捷键", message: "双击完成线/面绘制;圆形标注先点圆心,再点半径;Esc 可作为后续快捷键扩展。", duration: 6000 }); } function handleSelectScenario(scenarioId: string) { const scenario = WORKBENCH_SCENARIOS.find((item) => item.id === scenarioId); setActiveScenarioId(scenarioId); showMapNotice({ tone: "success", title: "方案已切换", message: `当前方案:${scenario?.name ?? scenarioId}。地图模拟结果暂未自动切换。` }); } function handlePreviewScenario() { setImpactVisible(true); setLayerVisibility((current) => ({ ...current, simulation: true })); if (mapRef.current && mapReady) { WORKBENCH_LAYER_GROUPS.simulation.forEach((layerId) => { if (mapRef.current?.getLayer(layerId)) { mapRef.current.setLayoutProperty(layerId, "visibility", "visible"); } }); } showMapNotice({ tone: "info", title: "预览影响范围", message: "已打开模拟结果图层,可在右侧图层面板继续调整显示。" }); } function handleCompareScenario() { showMapNotice({ tone: "info", title: "方案比较", message: "候选方案比较将汇总影响范围、管渠处置与输排风险。" }); } function handleExportScenarioReport() { showMapNotice({ tone: "success", title: "场景报告", message: "已生成当前场景报告任务,可继续导出地图截图或配置。" }); } async function handleAnalyzeAlertsWithAgent() { if (workbenchAlerts.length === 0) { showMapNotice({ tone: "info", title: "当前无待复核工况", message: "当前没有需要汇总的工况提醒。" }); return; } if (agent.streaming) { showMapNotice({ tone: "warning", message: "Agent 正在处理上一条指令。" }); return; } openAgentPanelForViewport(); try { await agent.submitPrompt( createAlertQueueConversationPrompt({ alerts: workbenchAlerts, conditions: scheduledConditions }) ); showMapNotice({ tone: "success", title: "已发起工况汇总", message: "已将待复核工况发送到 Agent。" }); } catch (error) { showMapNotice({ tone: "error", title: "工况汇总发起失败", message: error instanceof Error ? error.message : "无法将待复核工况发送到 Agent。" }); } } function handleSelectAlert(alert: WorkbenchAlert) { const conditionId = getScheduledConditionIdFromAlertId(alert.id); if (conditionId) { const condition = scheduledConditions.find((item) => item.id === conditionId); if (!condition) { showMapNotice({ tone: "warning", title: "工况任务未找到", message: `未找到提醒关联的工况任务:${conditionId}。` }); return; } setConditionFeedVisible(true); setActiveToolId(null); if (!isLargeScreen) { setMobileSheet("condition"); setMobileSheetSnap("half"); } setConditionFocusRequest((current) => ({ conditionId, requestId: (current?.requestId ?? 0) + 1 })); return; } showMapNotice({ tone: "warning", title: alert.title, message: alert.description }); } async function handleAgentUiEnvelope(payload: UIEnvelopePayload, sessionId: string) { const { envelope } = payload; if (envelope.kind === "map_action") { await handleAgentMapAction( envelope.action, envelope.params, sessionId, envelope.fallbackText ); return; } setAgentUiResults((current) => [ ...current.slice(-5), { id: payload.envelope_id || `agent-ui-${Date.now().toString(36)}`, sessionId, createdAt: payload.created_at, envelope } ]); showMapNotice({ tone: "info", title: envelope.kind === "chart" ? "Agent 图表已渲染" : "Agent 面板已渲染", message: envelope.fallbackText ?? (envelope.kind === "chart" ? "已在 Agent 面板显示可信图表。" : "已在 Agent 面板显示可信组件。") }); } async function handleFrontendAction(request: FrontendActionRequest, signal: AbortSignal) { if (request.name === "render_scada_analysis") { const items = parseScadaAnalysisItems(request.params.items); if (!items) throw new Error("INVALID_SCADA_ANALYSIS"); const result = await mapController.renderScadaAnalysis(items, signal); const counts = result.level_counts; showMapNotice({ tone: "success", title: "Agent SCADA 分析已渲染", message: `已渲染 ${result.rendered_ids.length} 个,高 ${counts.high}、中 ${counts.medium}、低 ${counts.low}${result.missing_ids.length ? `,缺失 ${result.missing_ids.length} 个` : ""}。` }); return result; } if (request.name === "clear_scada_analysis") { const result = mapController.clearScadaAnalysis(); showMapNotice({ tone: "info", title: "Agent SCADA 分析已清除", message: "地图已移除当前会话的 SCADA 分析结果。" }); return result; } throw new Error("UNSUPPORTED_FRONTEND_ACTION"); } async function handleStartNewAgentSession() { mapController.clearScadaAnalysis(); await agent.startNewSession(); } async function handleLoadAgentHistorySession(sessionId: string) { if (sessionId !== agent.sessionId) { mapController.clearScadaAnalysis(); } await agent.loadHistorySession(sessionId); } async function handleDeleteAgentHistorySession(sessionId: string) { const clearsActiveAnalysis = sessionId === agent.sessionId; const deleted = await agent.deleteHistorySession(sessionId); if (deleted && clearsActiveAnalysis) { mapController.clearScadaAnalysis(); } } async function handleAgentMapAction( action: string, params: unknown, sessionId: string, fallbackText?: string ) { const trustedAction = toTrustedMapAction(action, params, fallbackText); if (!trustedAction) { showMapNotice({ tone: "warning", title: "地图动作已降级", message: fallbackText ?? "Agent 地图动作参数未通过前端校验。" }); return; } if (trustedAction.type === "zoom_to_map") { const map = mapRef.current; if (!map || !mapReady) { showMapNotice({ tone: "warning", message: "地图尚未就绪,无法执行 Agent 缩放动作。" }); return; } map.easeTo({ center: trustedAction.center, zoom: trustedAction.zoom ?? Math.max(map.getZoom(), 14), padding: getResponsiveWorkbenchPadding(map, leftPanelOpen, rightPanelOpen), duration: trustedAction.durationMs ?? 500 }); showMapNotice({ tone: "info", title: "Agent 地图定位", message: trustedAction.fallbackText ?? "已根据 Agent 建议缩放到目标位置。" }); return; } if (trustedAction.type === "locate_features") { fitNetworkBounds(); showMapNotice({ tone: "info", title: "Agent 资产定位", message: trustedAction.fallbackText ?? (trustedAction.featureIds.length > 0 ? `已接收 ${trustedAction.featureIds.length} 个目标资产,当前先回到全网视图。` : "已根据 Agent 建议回到全网视图。") }); return; } if (trustedAction.type === "apply_layer_style") { if (trustedAction.layerGroupId && typeof trustedAction.visible === "boolean") { handleToggleLayer(trustedAction.layerGroupId, trustedAction.visible); } showMapNotice({ tone: "info", title: "Agent 图层样式", message: trustedAction.fallbackText ?? "已接收 Agent 图层样式建议,当前仅执行受控图层显隐。" }); return; } try { await agent.resolveRenderRef(trustedAction.renderRef, sessionId); showMapNotice({ tone: "info", title: "Agent 节点渲染", message: trustedAction.fallbackText ?? "已验证 render_ref,节点分类渲染器将在后续接入。" }); } catch (error) { showMapNotice({ tone: "error", title: "render_ref 解析失败", message: error instanceof Error ? error.message : "无法读取 Agent 渲染结果。" }); } } function handleExportConfig() { const config = { exportedAt: new Date().toISOString(), activeBaseLayerId, layerVisibility, activeToolId, activeMeasureModeId, activeMeasureUnitId }; const blob = new Blob([JSON.stringify(config, null, 2)], { type: "application/json" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = "drainage-network-map-config.json"; document.body.appendChild(link); link.click(); link.remove(); URL.revokeObjectURL(url); showMapNotice({ tone: "success", message: "当前地图配置已导出。" }); } const toolbarPanelProps: ToolbarPanelProps = { activeToolId, activeMeasureModeId, activeMeasureUnitId, measuring: measurement.measuring, measureResult: measurement.result, measureEnabled: mapReady, hasMeasurePoints: measurement.hasPoints, activeDrawToolId, drawingEnabled: mapReady, drawingAnnotations: drawing.annotations, canUndoDrawing: drawing.canUndo, canRedoDrawing: drawing.canRedo, canDeleteSelectedDrawing: drawing.canDeleteSelected, hasDrawingFeatures: drawing.hasFeatures, layerItems: layerControlItems, legendItems: MAP_LEGEND_ITEMS, baseLayerOptions, activeBaseLayerId, onSelectMeasureMode: setActiveMeasureModeId, onSelectMeasureUnit: setActiveMeasureUnitId, onStartMeasure: measurement.start, onStopMeasure: measurement.stop, onClearMeasure: measurement.clear, onCopyMeasure: measurement.copyResult, onSelectDrawTool: setActiveDrawToolId, onUndoDrawing: drawing.undo, onRedoDrawing: drawing.redo, onDeleteSelectedDrawing: drawing.deleteSelected, onClearDrawing: drawing.clear, onExportAnnotations: drawing.exportGeoJSON, onSelectBaseLayer: setActiveBaseLayerId, onToggleLayer: handleToggleLayer, onExportView: handleExportView, onRefreshTiles: handleRefreshTiles, onShowDataStatus: handleShowDataStatus, onShowShortcuts: handleShowShortcuts, onExportConfig: handleExportConfig }; const agentPanelProps: Omit< ComponentProps, "collapsing" | "onCollapse" > = { personaState: agent.personaState, sessionTitle: agent.sessionTitle, sessionHistory: agent.sessionHistory, sessionHistoryLoading: agent.sessionHistoryLoading, activeSessionId: agent.sessionId, statusLabel: agent.statusLabel, connectionStatus: agent.connectionStatus, streaming: agent.streaming, messages: agent.messages, streamRenderState: agent.streamRenderState, modelOptions: agent.modelOptions, selectedModel: agent.selectedModel, approvalMode: agent.approvalMode, uiResults: activeAgentUiResults, onRefreshHistory: agent.refreshSessionHistory, onStartNewSession: handleStartNewAgentSession, onLoadHistorySession: handleLoadAgentHistorySession, onRenameHistorySession: agent.renameHistorySession, onDeleteHistorySession: handleDeleteAgentHistorySession, onSelectModel: agent.setSelectedModel, onApprovalModeChange: agent.setApprovalMode, onSubmitPrompt: agent.submitPrompt, onStopPrompt: agent.stopPrompt, onReplyPermission: agent.replyPermission, onReplyQuestion: agent.replyQuestion, onRejectQuestion: agent.rejectQuestion }; return (
setTaskTickerVisible((current) => !current)} onToggleDevPanel={() => setDevPanelOpen((current) => !current)} onPreviewScenario={handlePreviewScenario} onCompareScenario={handleCompareScenario} onExportScenarioReport={handleExportScenarioReport} onAnalyzeAlertsWithAgent={handleAnalyzeAlertsWithAgent} onShowDataStatus={handleShowDataStatus} onRefreshTiles={handleRefreshTiles} onShowShortcuts={handleShowShortcuts} onExportConfig={handleExportConfig} /> {!devPanelOpen ? (
) : null} {conditionFeedMounted && !devPanelOpen ? (
) : null} {!devPanelOpen ? (
) : null} {devPanelEnabled && devPanelOpen ? ( setDevPanelOpen(false)} /> ) : null} {mobileSheet === null ? (
) : null}
{shouldShowTaskTicker ? ( ) : null}
{mobileSheet === null ? (
) : null}
{mobileSheet ? ( {mobileSheet === "agent" ? ( ) : ( )} ) : null} setDetailFeature(null)} /> {!mapReady && !mapError ? ( ) : null} {!hasMapboxToken ? ( ) : null} {sourceStatuses.filter(isNoticeSourceStatus).map((sourceStatus) => ( ))} {mapError ? : null}
); } function isNoticeSourceStatus(sourceStatus: { status: string; }): sourceStatus is { id: string; sourceName: string; status: MapSourceStatus; message: string } { return ( sourceStatus.status === "online" || sourceStatus.status === "degraded" || sourceStatus.status === "offline" ); } function getExportTargetLongEdge(preset: ExportViewPreset) { if (preset === "4k") { return 3840; } return undefined; } function getExportPresetLabel(preset: ExportViewPreset) { if (preset === "4k") { return " 4K "; } return "当前分辨率"; }