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

Viewing files at commit 874239cc591fCommit detailsView latest

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