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>
205 lines
6.3 KiB
TypeScript
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>
|
|
);
|
|
}
|