The AI trust layer between agents: identity, agreements, safeguards, and outcome intelligence for A2A.

Viewing files at commit c2393f876b9dCommit detailsView latest

apps/dashboard/src/components/command-palette.tsx 165 lines · 4.3 KB · tsx
1
import { useEffect, useState } from 'react';
2
import {
3
  BarChart3,
4
  History,
5
  LayoutDashboard,
6
  LogOut,
7
  Moon,
8
  Plus,
9
  ScanLine,
10
  Search,
11
  Settings,
12
  Shield,
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
  agents: ScanLine,
33
  insights: BarChart3,
34
  shield: Shield,
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 };
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 > 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
          </CommandGroup>
92
        ) : null}
93
        <CommandGroup heading="Pages">
94
          {pages.map((page) => {
95
            const Icon = pageIcons[page];
96
            return (
97
              <CommandItem key={page} value={pageMeta[page].label} onSelect={() => go(page)}>
98
                <Icon />
99
                {pageMeta[page].label}
100
              </CommandItem>
101
            );
102
          })}
103
        </CommandGroup>
104
        {agents.length ? (
105
          <CommandGroup heading="Agents">
106
            {agents.map((agent) => (
107
              <CommandItem
108
                key={agent.agentId}
109
                value={`${agent.name ?? ''} ${agent.agentId}`}
110
                onSelect={() => go('agents')}
111
              >
112
                <ScanLine />
113
                {agent.name || agent.agentId}
114
              </CommandItem>
115
            ))}
116
          </CommandGroup>
117
        ) : null}
118
        <CommandSeparator />
119
        <CommandGroup heading="Account">
120
          <CommandItem
121
            onSelect={() => {
122
              onToggleTheme();
123
              onOpenChange(false);
124
            }}
125
          >
126
            {theme === 'dark' ? <Sun /> : <Moon />}
127
            {theme === 'dark' ? 'Switch to light' : 'Switch to dark'}
128
          </CommandItem>
129
          <CommandItem
130
            onSelect={() => {
131
              onOpenChange(false);
132
              onSignOut();
133
            }}
134
          >
135
            <LogOut />
136
            Sign out
137
          </CommandItem>
138
        </CommandGroup>
139
      </CommandList>
140
    </CommandDialog>
141
  );
142
}
143
144
export function CommandTrigger({ onOpen }: { onOpen: () => void }) {
145
  const [mod, setMod] = useState('Ctrl');
146
  useEffect(() => {
147
    setMod(navigator.platform.toLowerCase().includes('mac') ? '⌘' : 'Ctrl');
148
  }, []);
149
  return (
150
    <Button
151
      type="button"
152
      variant="outline"
153
      size="sm"
154
      onClick={onOpen}
155
      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]"
156
    >
157
      <Search />
158
      <span className="flex-1 text-left">search</span>
159
      <kbd className="commandKbd">
160
        {mod}
161
        {mod === '⌘' ? '' : '+'}K
162
      </kbd>
163
    </Button>
164
  );
165
}