import { Command as CommandPrimitive } from 'cmdk' import * as React from 'react' import { ErrorBoundary } from 'react-error-boundary' import { cn } from 'ui/src/lib/utils' import { AlertTriangle } from 'lucide-react' import { DetailedHTMLProps, HTMLAttributes, KeyboardEventHandler } from 'react' import { DialogContent, Dialog } from 'ui' import { Button } from 'ui/src/components/Button' import { LoadingLine } from 'ui/src/components/LoadingLine/LoadingLine' import { useCommandMenu } from './CommandMenuProvider' type CommandPrimitiveElement = React.ElementRef type CommandPrimitiveProps = React.ComponentPropsWithoutRef export const copyToClipboard = (str: string, callback = () => {}) => { const focused = window.document.hasFocus() if (focused) { window.navigator?.clipboard?.writeText(str).then(callback) } else { console.warn('Unable to copy to clipboard') } } export function escapeDoubleQuotes(str: string) { return str.replaceAll('"', '\\"') } export const Command = React.forwardRef( ({ className, ...props }, ref) => ( ) ) Command.displayName = CommandPrimitive.displayName export function CommandError({ resetErrorBoundary }: { resetErrorBoundary: () => void }) { return (

Sorry, looks like we're having some issues with the command menu!

Please try again in a bit.

) } interface CommandDialogProps extends React.ComponentProps { onKeyDown?: KeyboardEventHandler page?: number | string visible?: boolean setIsOpen: (open: boolean) => void } export const CommandDialog = ({ children, onKeyDown, page, setIsOpen, ...props }: CommandDialogProps) => { const [animateBounce, setAnimateBounce] = React.useState(false) React.useEffect(() => { setAnimateBounce(true) setTimeout(() => setAnimateBounce(false), 126) }, [page]) return ( { // Only hide menu when clicking outside, not focusing outside // Prevents Firefox dropdown issue that immediately closes menu after opening if (e.type === 'dismissableLayer.pointerDownOutside') { setIsOpen(!open) } }} hideClose size={'xlarge'} className={cn( '!bg-overlay/90 backdrop-filter backdrop-blur-sm', '!border-overlay/90', 'transition ease-out', 'place-self-start mx-auto top-24', animateBounce ? 'scale-[101.5%]' : 'scale-100' )} > {children} ) } CommandDialog.displayName = 'CommandDialog' type CommandPrimitiveInputElement = React.ElementRef type CommandPrimitiveInputProps = React.ComponentPropsWithoutRef export const CommandInput = React.forwardRef< CommandPrimitiveInputElement, CommandPrimitiveInputProps >(({ className, value, onValueChange, ...props }, ref) => { const { isLoading } = useCommandMenu() return (
) }) CommandInput.displayName = CommandPrimitive.Input.displayName type CommandPrimitiveListElement = React.ElementRef type CommandPrimitiveListProps = React.ComponentPropsWithoutRef export const CommandList = React.forwardRef( ({ className, ...props }, ref) => ( ) ) CommandList.displayName = CommandPrimitive.List.displayName type CommandPrimitiveEmptyElement = React.ElementRef type CommandPrimitiveEmptyProps = React.ComponentPropsWithoutRef export const CommandEmpty = React.forwardRef< CommandPrimitiveEmptyElement, CommandPrimitiveEmptyProps >((props, ref) => ( )) CommandEmpty.displayName = CommandPrimitive.Empty.displayName type CommandPrimitiveGroupElement = React.ElementRef type CommandPrimitiveGroupProps = React.ComponentPropsWithoutRef export const CommandGroup = React.forwardRef< CommandPrimitiveGroupElement, CommandPrimitiveGroupProps >(({ className, ...props }, ref) => ( )) CommandGroup.displayName = CommandPrimitive.Group.displayName type CommandPrimitiveSeparatorElement = React.ElementRef type CommandPrimitiveSeparatorProps = React.ComponentPropsWithoutRef< typeof CommandPrimitive.Separator > export const CommandSeparator = React.forwardRef< CommandPrimitiveSeparatorElement, CommandPrimitiveSeparatorProps >(({ className, ...props }, ref) => ( )) CommandSeparator.displayName = CommandPrimitive.Separator.displayName type CommandPrimitiveItemElement = React.ElementRef type CommandPrimitiveItemProps = React.ComponentPropsWithoutRef export interface CommandItemProps extends CommandPrimitiveItemProps { type: 'link' | 'block-link' | 'command' badge?: React.ReactNode } export const CommandItem = React.forwardRef( ({ className, type, children, badge, ...props }, ref) => (
{children}
{badge}
) ) CommandItem.displayName = CommandPrimitive.Item.displayName export const CommandItemStale = React.forwardRef( ({ className, ...props }, ref) => ( ) ) CommandItemStale.displayName = 'CommandItemStale' interface CommandShortcutProps { className?: string children?: React.ReactNode onClick?: () => void type?: 'default' | 'breadcrumb' } export const CommandShortcut = ({ className, children, onClick, type = 'default', }: CommandShortcutProps) => { return ( ) } CommandShortcut.displayName = 'CommandShortcut' export const CommandLabel = ({ className, ...props }: React.HTMLAttributes) => { return } CommandShortcut.displayName = 'CommandLabel' export interface TextHighlighterProps extends DetailedHTMLProps, HTMLSpanElement> { text: string query: string } export const TextHighlighter = ({ text, query, ...props }: TextHighlighterProps) => { // Wrap all instances of `query` in a span to make them bold const elements = text.split(query).flatMap((part, index, parts) => { const returnValue = [<>{part}] // Add back the wrapped `query` (if it's not the last element) if (index !== parts.length - 1) { returnValue.push({query}) } return returnValue }) return {elements} } TextHighlighter.displayName = 'TextHighlighter' export interface UseHistoryKeysOptions { enable: boolean messages: string[] setPrompt: (prompt: string) => void } /** * Enables a shell-style message history when hitting * up/down on the keyboard */ export function useHistoryKeys({ enable, messages, setPrompt }: UseHistoryKeysOptions) { // Message index when hitting up/down on the keyboard (shell style) const [, setMessageSelectionIndex] = React.useState(0) React.useEffect(() => { if (enable) { return } // Note: intentionally setting index to 1 greater than max index setMessageSelectionIndex(messages.length) }, [messages, enable]) React.useEffect(() => { function onKeyDown(e: KeyboardEvent) { switch (e.key) { case 'ArrowUp': setMessageSelectionIndex((index) => { const newIndex = Math.max(index - 1, 0) const newMessage = messages[newIndex] if (newMessage) { setPrompt(newMessage) } return newIndex }) return case 'ArrowDown': setMessageSelectionIndex((index) => { const newIndex = Math.min(index + 1, messages.length) const newMessage = messages[newIndex] if (newMessage) { setPrompt(newMessage) } return newIndex }) return default: return } } window.addEventListener('keydown', onKeyDown) return () => { window.removeEventListener('keydown', onKeyDown) } }, [messages]) } /** * Automatically focuses an input on key press * and on load (after the call stack) * * @returns An input ref for the input to focus */ export function useAutoInputFocus() { const [input, setInput] = React.useState() // Use a callback-style ref to access the element when it mounts const inputRef = React.useCallback((inputElement: HTMLInputElement) => { if (inputElement) { setInput(inputElement) // We need to delay the focus until the end of the call stack // due to order of operations setTimeout(() => { inputElement.focus() }, 0) } }, []) // Focus the input when typing from anywhere React.useEffect(() => { function onKeyDown(e: KeyboardEvent) { if (!e.ctrlKey && !e.altKey && !e.metaKey && e.key !== 'Tab') { input?.focus() } } window.addEventListener('keydown', onKeyDown) return () => { window.removeEventListener('keydown', onKeyDown) } }, [input]) return inputRef }