"use client"; import { ChevronLeft, ChevronRight, LayoutGrid, Settings2, TriangleAlert, Unplug, Upload, Wrench, } from "lucide-react"; import Image from "next/image"; import { type ReactNode, useCallback, useRef, useState } from "react"; import type { ConnectorRow } from "@/components/assistant-ui/connector-popup/hooks/use-connector-rows"; import { Button } from "@/components/ui/button"; import { Drawer, DrawerContent, DrawerHandle, DrawerHeader, DrawerTitle, DrawerTrigger, } from "@/components/ui/drawer"; import { Separator } from "@/components/ui/separator"; import { Skeleton } from "@/components/ui/skeleton"; import { Spinner } from "@/components/ui/spinner"; import { Switch } from "@/components/ui/switch"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { CONNECTOR_TOOL_ICON_PATHS, getToolDisplayName, getToolIcon, } from "@/contracts/enums/toolIcons"; import { cn } from "@/lib/utils"; /** Minimal shape of a grouped agent-tool section (mirrors thread.tsx). */ export interface ToolGroupView { label: string; tools: { name: string }[]; connectorIcon?: string; } interface ComposerAddMenuDrawerProps { /** The `+` button; rendered as the drawer trigger. */ trigger: ReactNode; onUploadFiles: () => void; /** Connected connectors: one row per type, with live indexing health (`useConnectorRows`). */ connectorRows: ConnectorRow[]; /** Open a connector's manage view (deep-links via importConnectorRequestAtom). */ onSelectConnector: (row: ConnectorRow) => void; /** Navigate to the full connectors catalog. */ onBrowseConnectors: () => void; regularToolGroups: ToolGroupView[]; connectorToolGroups: ToolGroupView[]; otherToolGroup?: ToolGroupView; disabledToolsSet: Set; onToggleTool: (name: string) => void; onToggleToolGroup: (names: string[]) => void; /** True while the tool list is still loading (shows a skeleton). */ toolsLoading: boolean; } /** * Mobile "+" menu. A single vaul drawer that behaves like a flat list at the * root and drills into submenus in place (each level replaces the previous, * with a back button) rather than nesting overlays — the touch-friendly * equivalent of the desktop dropdown submenus. Screen state is a small push/pop * stack; closing the drawer resets it to the root. */ type Screen = | { kind: "root" } | { kind: "connectors" } | { kind: "tools" } | { kind: "toolGroup"; label: string }; const ROW = "flex w-full items-center gap-3 px-4 py-3 text-sm hover:bg-accent hover:text-accent-foreground transition-colors"; export function ComposerAddMenuDrawer({ trigger, onUploadFiles, connectorRows, onSelectConnector, onBrowseConnectors, regularToolGroups, connectorToolGroups, otherToolGroup, disabledToolsSet, onToggleTool, onToggleToolGroup, toolsLoading, }: ComposerAddMenuDrawerProps) { const [open, setOpen] = useState(false); const [stack, setStack] = useState([{ kind: "root" }]); // Slide direction: forward on push, back on pop — drives the enter animation. const dirRef = useRef<"forward" | "back">("forward"); const current = stack[stack.length - 1]; const push = useCallback((screen: Screen) => { dirRef.current = "forward"; setStack((prev) => [...prev, screen]); }, []); const pop = useCallback(() => { dirRef.current = "back"; setStack((prev) => (prev.length > 1 ? prev.slice(0, -1) : prev)); }, []); const handleOpenChange = useCallback((next: boolean) => { setOpen(next); // Reset to root when closed so the next open starts fresh. if (!next) { dirRef.current = "forward"; setStack([{ kind: "root" }]); } }, []); const close = useCallback(() => handleOpenChange(false), [handleOpenChange]); const title = current.kind === "connectors" ? "MCP Connectors" : current.kind === "tools" ? "Manage Tools" : current.kind === "toolGroup" ? current.label : "Add"; const renderToolRow = (name: string) => { const isDisabled = disabledToolsSet.has(name); const ToolIcon = getToolIcon(name); return (
{getToolDisplayName(name)} onToggleTool(name)} className="shrink-0" />
); }; const renderBody = () => { if (current.kind === "root") { return ( <> ); } if (current.kind === "connectors") { return ( <> {connectorRows.length === 0 ? (

No connectors yet.

) : ( connectorRows.map((row) => ( )) )} ); } if (current.kind === "toolGroup") { const group = connectorToolGroups.find((g) => g.label === current.label); return <>{group?.tools.map((t) => renderToolRow(t.name))}; } // current.kind === "tools" if (toolsLoading) { return (
{["t1", "t2", "t3", "t4"].map((k) => (
))}
); } return ( <> {regularToolGroups.map((group) => (
{group.label}
{group.tools.map((t) => renderToolRow(t.name))}
))} {connectorToolGroups.length > 0 && (
Connector Actions
{connectorToolGroups.map((group) => { const iconInfo = CONNECTOR_TOOL_ICON_PATHS[group.connectorIcon ?? ""]; const toolNames = group.tools.map((t) => t.name); const allDisabled = toolNames.every((n) => disabledToolsSet.has(n)); return (
onToggleToolGroup(toolNames)} className="shrink-0" />
); })}
)} {otherToolGroup && (
{otherToolGroup.label}
{otherToolGroup.tools.map((t) => renderToolRow(t.name))}
)} ); }; return ( {trigger} {stack.length > 1 ? ( ) : ( )} {title}
{renderBody()}
); }