feat: add project-aware agent workspace
Generic Container CI/CD / test-build-publish (push) Successful in 2m9s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 2m39s

This commit is contained in:
2026-10-22 18:52:18 +08:00
parent c6a0e74e72
commit 2d8506fea0
191 changed files with 12373 additions and 3556 deletions
@@ -1,5 +1,6 @@
import {
Activity,
ArrowRightLeft,
Bell,
Bot,
CheckCircle2,
@@ -17,7 +18,9 @@ import {
UserRound,
type LucideIcon
} from "lucide-react";
import type { ReactNode } from "react";
import { useRef, useState, type ReactNode } from "react";
import { AnimatePresence } from "motion/react";
import { ProjectSwitchDialog } from "./project-switch-dialog";
import {
MAP_COMPACT_RADIUS_CLASS_NAME,
MAP_ICON_CELL_RADIUS_CLASS_NAME,
@@ -39,6 +42,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger
} from "@/shared/ui/dropdown-menu";
import type { WorkbenchProject } from "../api/project-client";
import type { WorkbenchAlert, WorkbenchUser } from "../types";
import {
compactHeaderControlButtonClassName,
@@ -259,10 +263,22 @@ export function AlertMenu({
);
}
export type WorkbenchProjectSelection = {
projects: WorkbenchProject[];
selectedProjectId: string;
pendingProjectId: string | null;
error: string | null;
onSelectProject: (projectId: string) => void;
onOpen?: () => void;
onRefresh?: () => void;
refreshing?: boolean;
};
export function UserMenu({
open,
onOpenChange,
user,
projectSelection,
onRefreshTiles,
onShowDataStatus,
onShowShortcuts,
@@ -272,16 +288,23 @@ export function UserMenu({
open: boolean;
onOpenChange: (open: boolean) => void;
user: WorkbenchUser;
projectSelection?: WorkbenchProjectSelection;
onRefreshTiles: () => void;
onShowDataStatus: () => void;
onShowShortcuts: () => void;
onExportConfig: () => void;
onLogout?: () => Promise<void>;
}) {
const [projectDialogOpen, setProjectDialogOpen] = useState(false);
const [projectDialogKeyboard, setProjectDialogKeyboard] = useState(false);
const triggerRef = useRef<HTMLButtonElement>(null);
return (
<>
<DropdownMenu modal={false} open={open} onOpenChange={onOpenChange}>
<DropdownMenuTrigger asChild>
<button
ref={triggerRef}
type="button"
className={cn(
"group lg:h-8 lg:w-auto lg:justify-start lg:px-2",
@@ -297,8 +320,18 @@ export function UserMenu({
<ChevronDown size={14} className="hidden shrink-0 text-[var(--ink-muted)] transition group-hover:text-[var(--action-blue-hover)] group-data-[state=open]:rotate-180 group-data-[state=open]:text-[var(--action-blue-hover)] 2xl:block" aria-hidden="true" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className={cn("w-72", menuSurfaceClassName)}>
<DropdownMenuContent align="end" className={cn("z-[90] w-72", menuSurfaceClassName)}
onKeyDownCapture={() => setProjectDialogKeyboard(true)}
onPointerDownCapture={() => setProjectDialogKeyboard(false)}
onCloseAutoFocus={(event) => { if (projectDialogOpen) event.preventDefault(); }}>
<MenuHeader title={user.name} meta={user.role} icon={ShieldCheck} />
{projectSelection ? (
<>
<MenuAction icon={ArrowRightLeft} label="切换项目" description="选择要进入的供水项目"
onSelect={() => { onOpenChange(false); setProjectDialogOpen(true); projectSelection.onOpen?.(); }} />
<MenuSeparator />
</>
) : null}
<MenuSectionLabel>会话控制</MenuSectionLabel>
<MenuAction icon={Activity} label="查看运行状态" description="检查数据源与运行健康度" onSelect={onShowDataStatus} />
<MenuAction icon={RefreshCw} label="刷新地图瓦片" description="请求地图重新渲染业务图层" onSelect={onRefreshTiles} />
@@ -319,6 +352,12 @@ export function UserMenu({
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<AnimatePresence>
{projectDialogOpen && projectSelection ? <ProjectSwitchDialog selection={projectSelection} keyboardOpened={projectDialogKeyboard}
onClose={() => setProjectDialogOpen(false)}
onRestoreFocus={() => triggerRef.current?.focus()} /> : null}
</AnimatePresence>
</>
);
}