Files
paperclip/ui/src/components/CommandPalette.tsx
Forgotten 8f17b6fb52 Build out agent management UI: detail page, create dialog, list view
Add NewAgentDialog for creating agents with adapter config. Expand
AgentDetail page with tabbed view (overview, runs, config, logs),
run history timeline, and live status. Enhance Agents list page with
richer cards and filtering. Update AgentProperties panel, API client,
query keys, and utility helpers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 12:33:04 -06:00

205 lines
6.3 KiB
TypeScript

import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { useCompany } from "../context/CompanyContext";
import { useDialog } from "../context/DialogContext";
import { issuesApi } from "../api/issues";
import { agentsApi } from "../api/agents";
import { projectsApi } from "../api/projects";
import { queryKeys } from "../lib/queryKeys";
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "@/components/ui/command";
import {
CircleDot,
Bot,
Hexagon,
Target,
LayoutDashboard,
Inbox,
DollarSign,
History,
GitBranch,
SquarePen,
Plus,
} from "lucide-react";
export function CommandPalette() {
const [open, setOpen] = useState(false);
const navigate = useNavigate();
const { selectedCompanyId } = useCompany();
const { openNewIssue, openNewAgent } = useDialog();
useEffect(() => {
function handleKeyDown(e: KeyboardEvent) {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen(true);
}
}
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, []);
const { data: issues = [] } = useQuery({
queryKey: queryKeys.issues.list(selectedCompanyId!),
queryFn: () => issuesApi.list(selectedCompanyId!),
enabled: !!selectedCompanyId && open,
});
const { data: agents = [] } = useQuery({
queryKey: queryKeys.agents.list(selectedCompanyId!),
queryFn: () => agentsApi.list(selectedCompanyId!),
enabled: !!selectedCompanyId && open,
});
const { data: projects = [] } = useQuery({
queryKey: queryKeys.projects.list(selectedCompanyId!),
queryFn: () => projectsApi.list(selectedCompanyId!),
enabled: !!selectedCompanyId && open,
});
function go(path: string) {
setOpen(false);
navigate(path);
}
const agentName = (id: string | null) => {
if (!id) return null;
return agents.find((a) => a.id === id)?.name ?? null;
};
return (
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Search issues, agents, projects..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Pages">
<CommandItem onSelect={() => go("/dashboard")}>
<LayoutDashboard className="mr-2 h-4 w-4" />
Dashboard
</CommandItem>
<CommandItem onSelect={() => go("/inbox")}>
<Inbox className="mr-2 h-4 w-4" />
Inbox
</CommandItem>
<CommandItem onSelect={() => go("/issues")}>
<CircleDot className="mr-2 h-4 w-4" />
Issues
</CommandItem>
<CommandItem onSelect={() => go("/projects")}>
<Hexagon className="mr-2 h-4 w-4" />
Projects
</CommandItem>
<CommandItem onSelect={() => go("/goals")}>
<Target className="mr-2 h-4 w-4" />
Goals
</CommandItem>
<CommandItem onSelect={() => go("/agents")}>
<Bot className="mr-2 h-4 w-4" />
Agents
</CommandItem>
<CommandItem onSelect={() => go("/costs")}>
<DollarSign className="mr-2 h-4 w-4" />
Costs
</CommandItem>
<CommandItem onSelect={() => go("/activity")}>
<History className="mr-2 h-4 w-4" />
Activity
</CommandItem>
<CommandItem onSelect={() => go("/org")}>
<GitBranch className="mr-2 h-4 w-4" />
Org Chart
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Actions">
<CommandItem
onSelect={() => {
setOpen(false);
openNewIssue();
}}
>
<SquarePen className="mr-2 h-4 w-4" />
Create new issue
<span className="ml-auto text-xs text-muted-foreground">C</span>
</CommandItem>
<CommandItem
onSelect={() => {
setOpen(false);
openNewAgent();
}}
>
<Plus className="mr-2 h-4 w-4" />
Create new agent
</CommandItem>
<CommandItem onSelect={() => go("/projects")}>
<Plus className="mr-2 h-4 w-4" />
Create new project
</CommandItem>
</CommandGroup>
{issues.length > 0 && (
<>
<CommandSeparator />
<CommandGroup heading="Issues">
{issues.slice(0, 10).map((issue) => (
<CommandItem key={issue.id} onSelect={() => go(`/issues/${issue.id}`)}>
<CircleDot className="mr-2 h-4 w-4" />
<span className="text-muted-foreground mr-2 font-mono text-xs">
{issue.id.slice(0, 8)}
</span>
<span className="flex-1 truncate">{issue.title}</span>
{issue.assigneeAgentId && (
<span className="text-xs text-muted-foreground ml-2">
{agentName(issue.assigneeAgentId)}
</span>
)}
</CommandItem>
))}
</CommandGroup>
</>
)}
{agents.length > 0 && (
<>
<CommandSeparator />
<CommandGroup heading="Agents">
{agents.slice(0, 10).map((agent) => (
<CommandItem key={agent.id} onSelect={() => go(`/agents/${agent.id}`)}>
<Bot className="mr-2 h-4 w-4" />
{agent.name}
<span className="text-xs text-muted-foreground ml-2">{agent.role}</span>
</CommandItem>
))}
</CommandGroup>
</>
)}
{projects.length > 0 && (
<>
<CommandSeparator />
<CommandGroup heading="Projects">
{projects.slice(0, 10).map((project) => (
<CommandItem key={project.id} onSelect={() => go(`/projects/${project.id}`)}>
<Hexagon className="mr-2 h-4 w-4" />
{project.name}
</CommandItem>
))}
</CommandGroup>
</>
)}
</CommandList>
</CommandDialog>
);
}