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

Viewing files at commit d2f1913e8c83Commit detailsView latest

apps/dashboard/src/components/command-palette.tsx 172 lines · 4.6 KB · tsx
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
}