Docs / docs (push) Successful in 22s
Playwright Tests / test-playwright (1, 2) (push) Successful in 3m14s
Playwright Tests / test-playwright (2, 2) (push) Successful in 1m53s
pre-commit / pre-commit (push) Failing after 2m13s
Test Backend / test-backend (push) Successful in 2m38s
Compose Smoke Test / test-compose (push) Successful in 38s
Playwright Tests / merge-reports (push) Successful in 1m8s
`GET /api/v1/search/` hands the client one flat index of everything worth jumping to — flows and the nodes inside them, dashboards and the widgets on them, panels, secrets, modules, workers and alert channels — and cmdk matches it in the browser, so results narrow while typing without a round trip per keystroke. A node hit is the one thing no list endpoint could answer: it opens its flow with that node in focus. The panel is reached from **Search** above Documentation in the sidebar, or ⌘K anywhere. The flow canvas palette moves to ⌘P, being the narrower of the two. The panels dialog gains an address (`/dashboards?panels`) so a panel hit has somewhere to land, and the sidebar logo gets `shrink-0`: the rail's width animates while the logo is already back, and a flex item short of room is squeezed rather than clipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016vGH7jqcXxWKP9wZFPyVdU
134 lines
3.8 KiB
TypeScript
134 lines
3.8 KiB
TypeScript
import { useQuery } from "@tanstack/react-query"
|
|
import { useNavigate } from "@tanstack/react-router"
|
|
|
|
import type { FlowSummary, NodeTypeInfo } from "@/client"
|
|
import {
|
|
CommandDialog,
|
|
CommandEmpty,
|
|
CommandGroup,
|
|
CommandInput,
|
|
CommandItem,
|
|
CommandList,
|
|
} from "@/components/ui/command"
|
|
import { libraryQueryOptions } from "./queries"
|
|
|
|
/**
|
|
* ⌘P: add a node, jump to another flow, or run the current one, without
|
|
* reaching for the dock.
|
|
*/
|
|
export function CommandPalette({
|
|
open,
|
|
onOpenChange,
|
|
nodeTypes,
|
|
flows,
|
|
onAddNode,
|
|
onAddSharedNode,
|
|
onRun,
|
|
}: {
|
|
open: boolean
|
|
onOpenChange: (open: boolean) => void
|
|
nodeTypes: NodeTypeInfo[]
|
|
flows: FlowSummary[]
|
|
onAddNode: (type: string) => void
|
|
onAddSharedNode: (libName: string) => void
|
|
onRun: () => void
|
|
}) {
|
|
const navigate = useNavigate()
|
|
const { data: libraryData } = useQuery({
|
|
...libraryQueryOptions(),
|
|
enabled: open,
|
|
})
|
|
|
|
// Picking an item re-renders the canvas, which can interrupt the dialog's
|
|
// exit animation and leave its overlay swallowing clicks. Unmounting the
|
|
// dialog outright is deterministic; the palette does not need to fade out.
|
|
if (!open) return null
|
|
|
|
const library = libraryData ?? []
|
|
|
|
const close = (action: () => void) => {
|
|
onOpenChange(false)
|
|
action()
|
|
}
|
|
|
|
return (
|
|
<CommandDialog
|
|
open={open}
|
|
onOpenChange={onOpenChange}
|
|
title="Commands"
|
|
description="Add a node, switch flow, or run"
|
|
>
|
|
<CommandInput placeholder="Add a node, switch flow, run…" />
|
|
<CommandList>
|
|
<CommandEmpty>Nothing matches that.</CommandEmpty>
|
|
|
|
<CommandGroup heading="Add a node">
|
|
{nodeTypes.map((nodeType) => (
|
|
<CommandItem
|
|
key={nodeType.type}
|
|
value={`${nodeType.title} ${nodeType.description}`}
|
|
onSelect={() => close(() => onAddNode(nodeType.type))}
|
|
>
|
|
<span className="flex flex-col">
|
|
<span>{nodeType.title}</span>
|
|
<span className="text-xs text-muted-foreground">
|
|
{nodeType.description}
|
|
</span>
|
|
</span>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
|
|
{library.length > 0 ? (
|
|
<CommandGroup heading="Reusable nodes">
|
|
{library.map((entry) => (
|
|
<CommandItem
|
|
key={entry.name}
|
|
value={`shared ${entry.name}`}
|
|
onSelect={() => close(() => onAddSharedNode(entry.name))}
|
|
>
|
|
<span className="flex flex-col">
|
|
<span className="font-mono">{entry.name}</span>
|
|
<span className="text-xs text-muted-foreground">
|
|
{(entry.used_by ?? []).length === 1
|
|
? "Used once"
|
|
: `Used ${(entry.used_by ?? []).length} times`}
|
|
, code shared with every flow using it
|
|
</span>
|
|
</span>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
) : null}
|
|
|
|
{flows.length > 0 ? (
|
|
<CommandGroup heading="Flows">
|
|
{flows.map((flow) => (
|
|
<CommandItem
|
|
key={flow.name}
|
|
value={`flow ${flow.name} ${flow.title}`}
|
|
onSelect={() =>
|
|
close(() =>
|
|
navigate({
|
|
to: "/flows/$flowName",
|
|
params: { flowName: flow.name },
|
|
}),
|
|
)
|
|
}
|
|
>
|
|
{flow.title || flow.name}
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
) : null}
|
|
|
|
<CommandGroup heading="Actions">
|
|
<CommandItem value="run flow" onSelect={() => close(onRun)}>
|
|
Run this flow
|
|
</CommandItem>
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</CommandDialog>
|
|
)
|
|
}
|