| 1 | import { useEffect, useState } from 'react';
|
| 2 | import {
|
| 3 | BarChart3,
|
| 4 | Bot,
|
| 5 | History,
|
| 6 | Inbox,
|
| 7 | LayoutDashboard,
|
| 8 | LogOut,
|
| 9 | Moon,
|
| 10 | Plus,
|
| 11 | Search,
|
| 12 | Settings,
|
| 13 | Store,
|
| 14 | Sun,
|
| 15 | } from 'lucide-react';
|
| 16 | import { Button } from '@/components/ui/button';
|
| 17 | import {
|
| 18 | CommandDialog,
|
| 19 | CommandEmpty,
|
| 20 | CommandGroup,
|
| 21 | CommandInput,
|
| 22 | CommandItem,
|
| 23 | CommandList,
|
| 24 | CommandSeparator,
|
| 25 | CommandShortcut,
|
| 26 | } from '@/components/ui/command';
|
| 27 | import { pageMeta, pages, type Page } from '@/lib/navigation';
|
| 28 |
|
| 29 | const pageIcons = {
|
| 30 | dashboard: LayoutDashboard,
|
| 31 | history: History,
|
| 32 | conversations: Inbox,
|
| 33 | agents: Bot,
|
| 34 | insights: BarChart3,
|
| 35 | connect: Plus,
|
| 36 | marketplace: Store,
|
| 37 | settings: Settings,
|
| 38 | } as const;
|
| 39 |
|
| 40 | export function CommandPalette({
|
| 41 | open,
|
| 42 | onOpenChange,
|
| 43 | navigate,
|
| 44 | theme,
|
| 45 | onToggleTheme,
|
| 46 | onSignOut,
|
| 47 | agents,
|
| 48 | attention,
|
| 49 | }: {
|
| 50 | open: boolean;
|
| 51 | onOpenChange: (open: boolean) => void;
|
| 52 | navigate: (page: Page) => void;
|
| 53 | theme: 'dark' | 'light';
|
| 54 | onToggleTheme: () => void;
|
| 55 | onSignOut: () => void;
|
| 56 | agents: { agentId: string; name?: string }[];
|
| 57 | attention: { setup: number; invites: number; inbox: number };
|
| 58 | }) {
|
| 59 | const go = (page: Page) => {
|
| 60 | onOpenChange(false);
|
| 61 | navigate(page);
|
| 62 | };
|
| 63 |
|
| 64 | return (
|
| 65 | <CommandDialog
|
| 66 | open={open}
|
| 67 | onOpenChange={onOpenChange}
|
| 68 | title="Jump to"
|
| 69 | description="Search pages, agents, and actions"
|
| 70 | className="bg-popover"
|
| 71 | >
|
| 72 | <CommandInput placeholder="Go to a page, agent, or action…" />
|
| 73 | <CommandList>
|
| 74 | <CommandEmpty>Nothing matches.</CommandEmpty>
|
| 75 | {attention.setup + attention.invites + attention.inbox > 0 ? (
|
| 76 | <CommandGroup heading="Needs you">
|
| 77 | {attention.setup > 0 ? (
|
| 78 | <CommandItem onSelect={() => go('connect')}>
|
| 79 | <Plus />
|
| 80 | Pending setup
|
| 81 | <CommandShortcut>{attention.setup}</CommandShortcut>
|
| 82 | </CommandItem>
|
| 83 | ) : null}
|
| 84 | {attention.invites > 0 ? (
|
| 85 | <CommandItem onSelect={() => go('dashboard')}>
|
| 86 | <LayoutDashboard />
|
| 87 | Pending invites
|
| 88 | <CommandShortcut>{attention.invites}</CommandShortcut>
|
| 89 | </CommandItem>
|
| 90 | ) : null}
|
| 91 | {attention.inbox > 0 ? (
|
| 92 | <CommandItem onSelect={() => go('conversations')}>
|
| 93 | <Inbox />
|
| 94 | Unread inbox
|
| 95 | <CommandShortcut>{attention.inbox}</CommandShortcut>
|
| 96 | </CommandItem>
|
| 97 | ) : null}
|
| 98 | </CommandGroup>
|
| 99 | ) : null}
|
| 100 | <CommandGroup heading="Pages">
|
| 101 | {pages.map((page) => {
|
| 102 | const Icon = pageIcons[page];
|
| 103 | return (
|
| 104 | <CommandItem key={page} value={pageMeta[page].label} onSelect={() => go(page)}>
|
| 105 | <Icon />
|
| 106 | {pageMeta[page].label}
|
| 107 | </CommandItem>
|
| 108 | );
|
| 109 | })}
|
| 110 | </CommandGroup>
|
| 111 | {agents.length ? (
|
| 112 | <CommandGroup heading="Agents">
|
| 113 | {agents.map((agent) => (
|
| 114 | <CommandItem
|
| 115 | key={agent.agentId}
|
| 116 | value={`${agent.name ?? ''} ${agent.agentId}`}
|
| 117 | onSelect={() => go('agents')}
|
| 118 | >
|
| 119 | <Bot />
|
| 120 | {agent.name || agent.agentId}
|
| 121 | </CommandItem>
|
| 122 | ))}
|
| 123 | </CommandGroup>
|
| 124 | ) : null}
|
| 125 | <CommandSeparator />
|
| 126 | <CommandGroup heading="Account">
|
| 127 | <CommandItem
|
| 128 | onSelect={() => {
|
| 129 | onToggleTheme();
|
| 130 | onOpenChange(false);
|
| 131 | }}
|
| 132 | >
|
| 133 | {theme === 'dark' ? <Sun /> : <Moon />}
|
| 134 | {theme === 'dark' ? 'Switch to light' : 'Switch to dark'}
|
| 135 | </CommandItem>
|
| 136 | <CommandItem
|
| 137 | onSelect={() => {
|
| 138 | onOpenChange(false);
|
| 139 | onSignOut();
|
| 140 | }}
|
| 141 | >
|
| 142 | <LogOut />
|
| 143 | Sign out
|
| 144 | </CommandItem>
|
| 145 | </CommandGroup>
|
| 146 | </CommandList>
|
| 147 | </CommandDialog>
|
| 148 | );
|
| 149 | }
|
| 150 |
|
| 151 | export function CommandTrigger({ onOpen }: { onOpen: () => void }) {
|
| 152 | const [mod, setMod] = useState('Ctrl');
|
| 153 | useEffect(() => {
|
| 154 | setMod(navigator.platform.toLowerCase().includes('mac') ? '⌘' : 'Ctrl');
|
| 155 | }, []);
|
| 156 | return (
|
| 157 | <Button
|
| 158 | type="button"
|
| 159 | variant="outline"
|
| 160 | size="sm"
|
| 161 | onClick={onOpen}
|
| 162 | className="commandTrigger text-muted-foreground h-8 max-w-xs flex-1 justify-start border-border bg-transparent px-2 shadow-none hover:bg-sidebar-accent sm:min-w-[220px]"
|
| 163 | >
|
| 164 | <Search />
|
| 165 | <span className="flex-1 text-left">search</span>
|
| 166 | <kbd className="commandKbd">
|
| 167 | {mod}
|
| 168 | {mod === '⌘' ? '' : '+'}K
|
| 169 | </kbd>
|
| 170 | </Button>
|
| 171 | );
|
| 172 | }
|