Rename Agents to Applications, remove Exchanges, implement Routes search
- Rename "Agents" scope/labels to "Applications" throughout command palette - Remove "Exchanges" scope (was disabled placeholder) - Implement "Routes" scope: derives routes from agents' routeIds, filterable by route ID or owning application name - Selecting a route filters executions by routeId - Route results show purple icon, route ID, and owning application(s) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -279,6 +279,12 @@
|
||||
color: var(--rose);
|
||||
}
|
||||
|
||||
.iconRoute {
|
||||
composes: resultIcon;
|
||||
background: rgba(168, 85, 247, 0.12);
|
||||
color: var(--purple);
|
||||
}
|
||||
|
||||
/* ── Result Body ── */
|
||||
.resultBody {
|
||||
flex: 1;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ExecutionSummary, AgentInstance } from '../../api/schema';
|
||||
import type { PaletteResult } from './use-palette-search';
|
||||
import type { PaletteResult, RouteInfo } from './use-palette-search';
|
||||
import { highlightMatch, formatRelativeTime } from './utils';
|
||||
import styles from './CommandPalette.module.css';
|
||||
|
||||
@@ -80,7 +80,7 @@ function ExecutionResult({ data, query }: { data: ExecutionSummary; query: strin
|
||||
);
|
||||
}
|
||||
|
||||
function AgentResult({ data, query }: { data: AgentInstance; query: string }) {
|
||||
function ApplicationResult({ data, query }: { data: AgentInstance; query: string }) {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.iconAgent}>
|
||||
@@ -101,7 +101,34 @@ function AgentResult({ data, query }: { data: AgentInstance; query: string }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.resultRight}>
|
||||
<span className={styles.resultTime}>Agent</span>
|
||||
<span className={styles.resultTime}>Application</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function RouteResult({ data, query }: { data: RouteInfo; query: string }) {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.iconRoute}>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="6" cy="19" r="3" />
|
||||
<path d="M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15" />
|
||||
<circle cx="18" cy="5" r="3" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className={styles.resultBody}>
|
||||
<div className={styles.resultTitle}>
|
||||
<HighlightedText text={data.routeId} query={query} />
|
||||
</div>
|
||||
<div className={styles.resultMeta}>
|
||||
<span>{data.agentIds.length} {data.agentIds.length === 1 ? 'application' : 'applications'}</span>
|
||||
<span className={styles.sep} />
|
||||
<span>{data.agentIds.join(', ')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.resultRight}>
|
||||
<span className={styles.resultTime}>Route</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -117,8 +144,11 @@ export function ResultItem({ result, selected, query, onClick }: ResultItemProps
|
||||
{result.type === 'execution' && (
|
||||
<ExecutionResult data={result.data as ExecutionSummary} query={query} />
|
||||
)}
|
||||
{result.type === 'agent' && (
|
||||
<AgentResult data={result.data as AgentInstance} query={query} />
|
||||
{result.type === 'application' && (
|
||||
<ApplicationResult data={result.data as AgentInstance} query={query} />
|
||||
)}
|
||||
{result.type === 'route' && (
|
||||
<RouteResult data={result.data as RouteInfo} query={query} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -11,34 +11,14 @@ interface ResultsListProps {
|
||||
}
|
||||
|
||||
export function ResultsList({ results, isLoading, onSelect }: ResultsListProps) {
|
||||
const { selectedIndex, query, scope } = useCommandPalette();
|
||||
const { selectedIndex, query } = useCommandPalette();
|
||||
const listRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const el = listRef.current?.querySelector('[data-palette-item].selected, [data-palette-item]:nth-child(' + (selectedIndex + 1) + ')');
|
||||
if (!el) return;
|
||||
const items = listRef.current?.querySelectorAll('[data-palette-item]');
|
||||
items?.[selectedIndex]?.scrollIntoView({ block: 'nearest' });
|
||||
}, [selectedIndex]);
|
||||
|
||||
if (scope === 'routes' || scope === 'exchanges') {
|
||||
const label = scope === 'routes' ? 'Route' : 'Exchange';
|
||||
return (
|
||||
<div className={styles.results}>
|
||||
<div className={styles.emptyState}>
|
||||
<svg className={styles.emptyIcon} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
|
||||
<path d="M12 6v6l4 2" />
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
</svg>
|
||||
<span className={styles.emptyText}>{label} search coming soon</span>
|
||||
<span className={styles.emptyHint}>
|
||||
This feature is planned for a future release
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading && results.length === 0) {
|
||||
return (
|
||||
<div className={styles.results}>
|
||||
@@ -70,7 +50,8 @@ export function ResultsList({ results, isLoading, onSelect }: ResultsListProps)
|
||||
|
||||
// Group results by type
|
||||
const executions = results.filter((r) => r.type === 'execution');
|
||||
const agents = results.filter((r) => r.type === 'agent');
|
||||
const applications = results.filter((r) => r.type === 'application');
|
||||
const routes = results.filter((r) => r.type === 'route');
|
||||
|
||||
let globalIndex = 0;
|
||||
|
||||
@@ -93,10 +74,27 @@ export function ResultsList({ results, isLoading, onSelect }: ResultsListProps)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
{agents.length > 0 && (
|
||||
{applications.length > 0 && (
|
||||
<>
|
||||
<div className={styles.groupLabel}>Agents</div>
|
||||
{agents.map((r) => {
|
||||
<div className={styles.groupLabel}>Applications</div>
|
||||
{applications.map((r) => {
|
||||
const idx = globalIndex++;
|
||||
return (
|
||||
<ResultItem
|
||||
key={r.id}
|
||||
result={r}
|
||||
selected={idx === selectedIndex}
|
||||
query={query}
|
||||
onClick={() => onSelect(r)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
{routes.length > 0 && (
|
||||
<>
|
||||
<div className={styles.groupLabel}>Routes</div>
|
||||
{routes.map((r) => {
|
||||
const idx = globalIndex++;
|
||||
return (
|
||||
<ResultItem
|
||||
|
||||
@@ -3,25 +3,26 @@ import styles from './CommandPalette.module.css';
|
||||
|
||||
interface ScopeTabsProps {
|
||||
executionCount: number;
|
||||
agentCount: number;
|
||||
applicationCount: number;
|
||||
routeCount: number;
|
||||
}
|
||||
|
||||
const SCOPES: { key: PaletteScope; label: string; disabled?: boolean }[] = [
|
||||
const SCOPES: { key: PaletteScope; label: string }[] = [
|
||||
{ key: 'all', label: 'All' },
|
||||
{ key: 'executions', label: 'Executions' },
|
||||
{ key: 'agents', label: 'Agents' },
|
||||
{ key: 'routes', label: 'Routes', disabled: true },
|
||||
{ key: 'exchanges', label: 'Exchanges', disabled: true },
|
||||
{ key: 'applications', label: 'Applications' },
|
||||
{ key: 'routes', label: 'Routes' },
|
||||
];
|
||||
|
||||
export function ScopeTabs({ executionCount, agentCount }: ScopeTabsProps) {
|
||||
export function ScopeTabs({ executionCount, applicationCount, routeCount }: ScopeTabsProps) {
|
||||
const { scope, setScope } = useCommandPalette();
|
||||
|
||||
function getCount(key: PaletteScope): string | number {
|
||||
if (key === 'all') return executionCount + agentCount;
|
||||
function getCount(key: PaletteScope): number {
|
||||
if (key === 'all') return executionCount + applicationCount + routeCount;
|
||||
if (key === 'executions') return executionCount;
|
||||
if (key === 'agents') return agentCount;
|
||||
return '\u2014';
|
||||
if (key === 'applications') return applicationCount;
|
||||
if (key === 'routes') return routeCount;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -29,14 +30,8 @@ export function ScopeTabs({ executionCount, agentCount }: ScopeTabsProps) {
|
||||
{SCOPES.map((s) => (
|
||||
<button
|
||||
key={s.key}
|
||||
className={
|
||||
s.disabled
|
||||
? styles.scopeTabDisabled
|
||||
: scope === s.key
|
||||
? styles.scopeTabActive
|
||||
: styles.scopeTab
|
||||
}
|
||||
onClick={() => !s.disabled && setScope(s.key)}
|
||||
className={scope === s.key ? styles.scopeTabActive : styles.scopeTab}
|
||||
onClick={() => setScope(s.key)}
|
||||
>
|
||||
{s.label}
|
||||
<span className={styles.scopeCount}>{getCount(s.key)}</span>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
export type PaletteScope = 'all' | 'executions' | 'agents' | 'routes' | 'exchanges';
|
||||
export type PaletteScope = 'all' | 'executions' | 'applications' | 'routes';
|
||||
|
||||
export interface PaletteFilter {
|
||||
key: 'status' | 'route' | 'agent' | 'processor';
|
||||
|
||||
@@ -4,18 +4,27 @@ import type { ExecutionSummary, AgentInstance } from '../../api/schema';
|
||||
import { useCommandPalette, type PaletteScope } from './use-command-palette';
|
||||
import { useDebouncedValue } from './utils';
|
||||
|
||||
export interface RouteInfo {
|
||||
routeId: string;
|
||||
agentIds: string[];
|
||||
}
|
||||
|
||||
export interface PaletteResult {
|
||||
type: 'execution' | 'agent';
|
||||
type: 'execution' | 'application' | 'route';
|
||||
id: string;
|
||||
data: ExecutionSummary | AgentInstance;
|
||||
data: ExecutionSummary | AgentInstance | RouteInfo;
|
||||
}
|
||||
|
||||
function isExecutionScope(scope: PaletteScope) {
|
||||
return scope === 'all' || scope === 'executions';
|
||||
}
|
||||
|
||||
function isAgentScope(scope: PaletteScope) {
|
||||
return scope === 'all' || scope === 'agents';
|
||||
function isApplicationScope(scope: PaletteScope) {
|
||||
return scope === 'all' || scope === 'applications';
|
||||
}
|
||||
|
||||
function isRouteScope(scope: PaletteScope) {
|
||||
return scope === 'all' || scope === 'routes';
|
||||
}
|
||||
|
||||
export function usePaletteSearch() {
|
||||
@@ -57,7 +66,7 @@ export function usePaletteSearch() {
|
||||
if (error) throw new Error('Failed to load agents');
|
||||
return data!;
|
||||
},
|
||||
enabled: isOpen && isAgentScope(scope),
|
||||
enabled: isOpen && (isApplicationScope(scope) || isRouteScope(scope)),
|
||||
staleTime: 30_000,
|
||||
});
|
||||
|
||||
@@ -73,21 +82,53 @@ export function usePaletteSearch() {
|
||||
return a.id.toLowerCase().includes(q) || a.group.toLowerCase().includes(q);
|
||||
});
|
||||
|
||||
const agentResults: PaletteResult[] = filteredAgents.slice(0, 10).map((a) => ({
|
||||
type: 'agent' as const,
|
||||
const applicationResults: PaletteResult[] = filteredAgents.slice(0, 10).map((a) => ({
|
||||
type: 'application' as const,
|
||||
id: a.id,
|
||||
data: a,
|
||||
}));
|
||||
|
||||
// Derive unique routes from all agents
|
||||
const routeMap = new Map<string, string[]>();
|
||||
for (const agent of agentsQuery.data ?? []) {
|
||||
for (const routeId of agent.routeIds ?? []) {
|
||||
const existing = routeMap.get(routeId);
|
||||
if (existing) {
|
||||
if (!existing.includes(agent.id)) existing.push(agent.id);
|
||||
} else {
|
||||
routeMap.set(routeId, [agent.id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const allRoutes: RouteInfo[] = Array.from(routeMap.entries()).map(([routeId, agentIds]) => ({
|
||||
routeId,
|
||||
agentIds,
|
||||
}));
|
||||
|
||||
const filteredRoutes = allRoutes.filter((r) => {
|
||||
if (!debouncedQuery) return true;
|
||||
const q = debouncedQuery.toLowerCase();
|
||||
return r.routeId.toLowerCase().includes(q) || r.agentIds.some((a) => a.toLowerCase().includes(q));
|
||||
});
|
||||
|
||||
const routeResults: PaletteResult[] = filteredRoutes.slice(0, 10).map((r) => ({
|
||||
type: 'route' as const,
|
||||
id: r.routeId,
|
||||
data: r,
|
||||
}));
|
||||
|
||||
let results: PaletteResult[] = [];
|
||||
if (scope === 'all') results = [...executionResults, ...agentResults];
|
||||
if (scope === 'all') results = [...executionResults, ...applicationResults, ...routeResults];
|
||||
else if (scope === 'executions') results = executionResults;
|
||||
else if (scope === 'agents') results = agentResults;
|
||||
else if (scope === 'applications') results = applicationResults;
|
||||
else if (scope === 'routes') results = routeResults;
|
||||
|
||||
return {
|
||||
results,
|
||||
executionCount: executionsQuery.data?.total ?? 0,
|
||||
agentCount: filteredAgents.length,
|
||||
applicationCount: filteredAgents.length,
|
||||
routeCount: filteredRoutes.length,
|
||||
isLoading: executionsQuery.isFetching || agentsQuery.isFetching,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user