"use client"; import { scopeProjectRef, scopeThreadRef } from "@t3tools/client-runtime";import { DEFAULT_MODEL, type EnvironmentId, type FilesystemBrowseResult, type ProjectId, ProviderInstanceId, type SourceControlDiscoveryResult, type SourceControlProviderKind, type SourceControlRepositoryInfo,} from "@t3tools/contracts";import { useQuery, useQueryClient } from "@tanstack/react-query";import { useNavigate, useParams } from "@tanstack/react-router";import * as Option from "effect/Option";import { ArrowDownIcon, ArrowLeftIcon, ArrowUpIcon, CornerLeftUpIcon, FolderIcon, FolderPlusIcon, LinkIcon, MessageSquareIcon, SettingsIcon, SquarePenIcon,} from "lucide-react";import { useCallback, useDeferredValue, useEffect, useLayoutEffect, useMemo, useRef, useState, type KeyboardEvent, type ReactNode,} from "react";import { useShallow } from "zustand/react/shallow";import { useCommandPaletteStore } from "../commandPaletteStore";import { readEnvironmentApi } from "../environmentApi";import { readPrimaryEnvironmentDescriptor, usePrimaryEnvironmentId } from "../environments/primary";import { useSavedEnvironmentRegistryStore, useSavedEnvironmentRuntimeStore,} from "../environments/runtime";import { useHandleNewThread } from "../hooks/useHandleNewThread";import { useSettings } from "../hooks/useSettings";import { readLocalApi } from "../localApi";import { getSourceControlDiscoverySnapshot, refreshSourceControlDiscovery,} from "../lib/sourceControlDiscoveryState";import { startNewThreadInProjectFromContext, startNewThreadFromContext,} from "../lib/chatThreadActions";import { appendBrowsePathSegment, canNavigateUp, ensureBrowseDirectoryPath, findProjectByPath, getBrowseDirectoryPath, getBrowseLeafPathSegment, getBrowseParentPath, hasTrailingPathSeparator, inferProjectTitleFromPath, isExplicitRelativeProjectPath, isFilesystemBrowseQuery, isUnsupportedWindowsProjectPath, resolveProjectPathForDispatch,} from "../lib/projectPaths";import { isTerminalFocused } from "../lib/terminalFocus";import { getLatestThreadForProject } from "../lib/threadSort";import { cn, isMacPlatform, isWindowsPlatform, newCommandId, newProjectId } from "../lib/utils";import { selectProjectsAcrossEnvironments, selectSidebarThreadsAcrossEnvironments, useStore,} from "../store";import { selectThreadTerminalUiState, useTerminalUiStateStore } from "../terminalUiStateStore";import { buildThreadRouteParams, resolveThreadRouteTarget } from "../threadRoutes";import { ADDON_ICON_CLASS, buildBrowseGroups, buildProjectActionItems, buildRootGroups, buildThreadActionItems, type CommandPaletteActionItem, type CommandPaletteSubmenuItem, type CommandPaletteView, filterBrowseEntries, filterCommandPaletteGroups, getCommandPaletteInputPlaceholder, getCommandPaletteMode, ITEM_ICON_CLASS, RECENT_THREAD_LIMIT,} from "./CommandPalette.logic";import { resolveEnvironmentOptionLabel } from "./BranchToolbar.logic";import { CommandPaletteResults } from "./CommandPaletteResults";import { AzureDevOpsIcon, BitbucketIcon, GitHubIcon, GitLabIcon } from "./Icons";import { ProjectFavicon } from "./ProjectFavicon";import { ThreadRowLeadingStatus, ThreadRowTrailingStatus } from "./ThreadStatusIndicators";import { useServerKeybindings } from "../rpc/serverState";import { resolveShortcutCommand } from "../keybindings";import { Command, CommandDialog, CommandDialogPopup, CommandFooter, CommandInput, CommandPanel,} from "./ui/command";import { Button } from "./ui/button";import { Kbd, KbdGroup } from "./ui/kbd";import { stackedThreadToast, toastManager } from "./ui/toast";import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";import { ComposerHandleContext, useComposerHandleContext } from "../composerHandleContext";import type { ChatComposerHandle } from "./chat/ChatComposer"; const EMPTY_BROWSE_ENTRIES: FilesystemBrowseResult["entries"] = [];const BROWSE_STALE_TIME_MS = 30_000; function getLocalFileManagerName(platform: string): string { if (isMacPlatform(platform)) { return "Finder"; } if (isWindowsPlatform(platform)) { return "Explorer"; } return "Files";} function getEnvironmentBrowsePlatform(os: string | null | undefined): string { if (os === "windows") { return "Win32"; } if (os === "darwin") { return "MacIntel"; } if (os === "linux") { return "Linux"; } return typeof navigator === "undefined" ? "" : navigator.platform;} interface AddProjectEnvironmentOption { readonly environmentId: EnvironmentId; readonly label: string; readonly isPrimary: boolean;} type AddProjectRemoteProviderKind = Extract< SourceControlProviderKind, "github" | "gitlab" | "bitbucket" | "azure-devops">;type AddProjectRemoteSource = AddProjectRemoteProviderKind | "url"; type AddProjectCloneFlow = | { readonly step: "repository"; readonly environmentId: EnvironmentId; readonly source: AddProjectRemoteSource; } | { readonly step: "confirm"; readonly environmentId: EnvironmentId; readonly source: AddProjectRemoteSource; readonly repositoryInput: string; readonly repository: SourceControlRepositoryInfo | null; readonly remoteUrl: string; }; const REMOTE_PROJECT_SOURCES: ReadonlyArray<AddProjectRemoteSource> = [ "url", "github", "gitlab", "bitbucket", "azure-devops",];const REMOTE_PROJECT_PROVIDER_SOURCES: ReadonlyArray<AddProjectRemoteProviderKind> = [ "github", "gitlab", "bitbucket", "azure-devops",]; function remoteProjectSourceLabel(source: AddProjectRemoteSource): string { switch (source) { case "github": return "GitHub"; case "gitlab": return "GitLab"; case "bitbucket": return "Bitbucket"; case "azure-devops": return "Azure DevOps"; case "url": return "Git URL"; }} function remoteProjectSourcePathHint(source: AddProjectRemoteSource): string { switch (source) { case "github": return "owner/repo"; case "gitlab": return "group/project"; case "bitbucket": return "workspace/repository"; case "azure-devops": return "project/repository"; case "url": return "URL"; }} function remoteProjectSourceProvider( source: AddProjectRemoteSource,): AddProjectRemoteProviderKind | null { return source === "url" ? null : source;} function remoteProjectSourceIcon(source: AddProjectRemoteSource, className: string): ReactNode { switch (source) { case "github": return <GitHubIcon className={className} />; case "gitlab": return <GitLabIcon className={className} />; case "bitbucket": return <BitbucketIcon className={className} />; case "azure-devops": return <AzureDevOpsIcon className={className} />; case "url": return <LinkIcon className={className} />; }} function remoteProjectInputPlaceholder(flow: AddProjectCloneFlow | null): string | null { if (!flow) return null; if (flow.step === "confirm") return null; if (flow.source === "url") { return "Enter Git clone URL"; } return `Enter ${remoteProjectSourceLabel(flow.source)} repository (${remoteProjectSourcePathHint(flow.source)})`;} function sourceProviderKind(source: AddProjectRemoteSource): AddProjectRemoteProviderKind | null { return source === "url" ? null : source;} function sortAddProjectProviderSources( readinessBySource: AddProjectRemoteSourceReadiness,): ReadonlyArray<AddProjectRemoteProviderKind> { return REMOTE_PROJECT_PROVIDER_SOURCES.toSorted((left, right) => { const leftReady = readinessBySource[left].ready; const rightReady = readinessBySource[right].ready; if (leftReady !== rightReady) { return leftReady ? -1 : 1; } return remoteProjectSourceLabel(left).localeCompare(remoteProjectSourceLabel(right)); });} type AddProjectRemoteSourceReadiness = Record< AddProjectRemoteSource, { readonly ready: boolean; readonly hint: string | null }>; function buildAddProjectRemoteSourceReadiness( discovery: SourceControlDiscoveryResult | null,): AddProjectRemoteSourceReadiness { const unavailable = { ready: false, hint: "Provider status unavailable. Open Settings -> Source Control and rescan.", } as const; const defaultReadiness: AddProjectRemoteSourceReadiness = { url: { ready: true, hint: null }, github: unavailable, gitlab: unavailable, bitbucket: unavailable, "azure-devops": unavailable, }; if (!discovery) { return defaultReadiness; } const providerByKind = new Map( discovery.sourceControlProviders.map((provider) => [provider.kind, provider]), ); const readiness = { ...defaultReadiness }; for (const source of REMOTE_PROJECT_SOURCES) { const kind = sourceProviderKind(source); if (!kind) continue; const provider = providerByKind.get(kind); if (!provider) { readiness[source] = unavailable; continue; } if (provider.status !== "available") { readiness[source] = { ready: false, hint: provider.installHint }; continue; } if (provider.auth.status === "unauthenticated") { readiness[source] = { ready: false, hint: Option.getOrNull(provider.auth.detail) ?? `${provider.label} is not authenticated. Open Settings -> Source Control for setup guidance.`, }; continue; } readiness[source] = { ready: true, hint: null }; } return readiness;} function errorMessage(error: unknown): string { if (error instanceof Error && error.message.trim().length > 0) { return error.message; } return "An error occurred.";} export function CommandPalette({ children }: { children: ReactNode }) { const open = useCommandPaletteStore((store) => store.open); const setOpen = useCommandPaletteStore((store) => store.setOpen); const toggleOpen = useCommandPaletteStore((store) => store.toggleOpen); const keybindings = useServerKeybindings(); const composerHandleRef = useRef<ChatComposerHandle | null>(null); const routeTarget = useParams({ strict: false, select: (params) => resolveThreadRouteTarget(params), }); const routeThreadRef = routeTarget?.kind === "server" ? routeTarget.threadRef : null; const terminalOpen = useTerminalUiStateStore((state) => routeThreadRef ? selectThreadTerminalUiState(state.terminalUiStateByThreadKey, routeThreadRef).terminalOpen : false, ); useEffect(() => { const onKeyDown = (event: globalThis.KeyboardEvent) => { if (event.defaultPrevented) return; const command = resolveShortcutCommand(event, keybindings, { context: { terminalFocus: isTerminalFocused(), terminalOpen, }, }); if (command !== "commandPalette.toggle") { return; } event.preventDefault(); event.stopPropagation(); toggleOpen(); }; window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); }, [keybindings, terminalOpen, toggleOpen]); return ( <ComposerHandleContext value={composerHandleRef}> <CommandDialog open={open} onOpenChange={setOpen}> {children} <CommandPaletteDialog /> </CommandDialog> </ComposerHandleContext> );} function CommandPaletteDialog() { const open = useCommandPaletteStore((store) => store.open); const setOpen = useCommandPaletteStore((store) => store.setOpen); useEffect(() => { return () => { setOpen(false); }; }, [setOpen]); if (!open) { return null; } return <OpenCommandPaletteDialog />;} function OpenCommandPaletteDialog() { const navigate = useNavigate(); const setOpen = useCommandPaletteStore((store) => store.setOpen); const openIntent = useCommandPaletteStore((store) => store.openIntent); const clearOpenIntent = useCommandPaletteStore((store) => store.clearOpenIntent); const composerHandleRef = useComposerHandleContext(); const [query, setQuery] = useState(""); const deferredQuery = useDeferredValue(query); const isActionsOnly = deferredQuery.startsWith(">"); const queryClient = useQueryClient(); const [highlightedItemValue, setHighlightedItemValue] = useState<string | null>(null); const settings = useSettings(); const { activeDraftThread, activeThread, defaultProjectRef, handleNewThread } = useHandleNewThread(); const projects = useStore(useShallow(selectProjectsAcrossEnvironments)); const threads = useStore(useShallow(selectSidebarThreadsAcrossEnvironments)); const keybindings = useServerKeybindings(); const [viewStack, setViewStack] = useState<CommandPaletteView[]>([]); const currentView = viewStack.at(-1) ?? null; const [browseGeneration, setBrowseGeneration] = useState(0); const [addProjectEnvironmentId, setAddProjectEnvironmentId] = useState<EnvironmentId | null>( null, ); const [isPickingProjectFolder, setIsPickingProjectFolder] = useState(false); const [addProjectCloneFlow, setAddProjectCloneFlow] = useState<AddProjectCloneFlow | null>(null); const [isRemoteProjectLookingUp, setIsRemoteProjectLookingUp] = useState(false); const [isRemoteProjectCloning, setIsRemoteProjectCloning] = useState(false); const primaryEnvironmentId = usePrimaryEnvironmentId(); const primaryEnvironmentLabel = readPrimaryEnvironmentDescriptor()?.label ?? null; const savedEnvironmentRegistry = useSavedEnvironmentRegistryStore((state) => state.byId); const savedEnvironmentRuntimeById = useSavedEnvironmentRuntimeStore((state) => state.byId); const addProjectEnvironmentOptions = useMemo(() => { const options: AddProjectEnvironmentOption[] = []; const seenEnvironmentIds = new Set<EnvironmentId>(); if (primaryEnvironmentId) { seenEnvironmentIds.add(primaryEnvironmentId); options.push({ environmentId: primaryEnvironmentId, label: resolveEnvironmentOptionLabel({ isPrimary: true, environmentId: primaryEnvironmentId, runtimeLabel: primaryEnvironmentLabel, }), isPrimary: true, }); } for (const record of Object.values(savedEnvironmentRegistry)) { if (seenEnvironmentIds.has(record.environmentId)) { continue; } const runtimeState = savedEnvironmentRuntimeById[record.environmentId]; options.push({ environmentId: record.environmentId, label: resolveEnvironmentOptionLabel({ isPrimary: false, environmentId: record.environmentId, runtimeLabel: runtimeState?.descriptor?.label ?? null, savedLabel: record.label, }), isPrimary: false, }); } options.sort((left, right) => { if (left.isPrimary !== right.isPrimary) { return left.isPrimary ? -1 : 1; } return left.label.localeCompare(right.label); }); return options; }, [ primaryEnvironmentId, primaryEnvironmentLabel, savedEnvironmentRegistry, savedEnvironmentRuntimeById, ]); const defaultAddProjectEnvironmentId = addProjectEnvironmentOptions[0]?.environmentId ?? null; const browseEnvironmentId = addProjectEnvironmentId ?? defaultAddProjectEnvironmentId; const browseEnvironmentPlatform = useMemo(() => { const os = browseEnvironmentId && primaryEnvironmentId && browseEnvironmentId === primaryEnvironmentId ? (readPrimaryEnvironmentDescriptor()?.platform.os ?? null) : browseEnvironmentId ? (savedEnvironmentRuntimeById[browseEnvironmentId]?.descriptor?.platform.os ?? savedEnvironmentRuntimeById[browseEnvironmentId]?.serverConfig?.environment.platform .os ?? null) : null; return getEnvironmentBrowsePlatform(os); }, [browseEnvironmentId, primaryEnvironmentId, savedEnvironmentRuntimeById]); const isRemoteProjectCloneFlow = addProjectCloneFlow !== null; const isRemoteProjectRepositoryStep = addProjectCloneFlow?.step === "repository"; const isBrowsing = !isRemoteProjectRepositoryStep && isFilesystemBrowseQuery(query, browseEnvironmentPlatform); const paletteMode = getCommandPaletteMode({ currentView, isBrowsing }); const getAddProjectInitialQueryForEnvironment = useCallback( (environmentId: EnvironmentId | null): string => { const environmentSettings = environmentId && primaryEnvironmentId && environmentId === primaryEnvironmentId ? settings : environmentId ? savedEnvironmentRuntimeById[environmentId]?.serverConfig?.settings : null;Lines 1–500 of 1744. Load more
"use client"; import { scopeProjectRef, scopeThreadRef } from "@t3tools/client-runtime"; import { DEFAULT_MODEL, type EnvironmentId, type FilesystemBrowseResult, type ProjectId, ProviderInstanceId, type SourceControlDiscoveryResult, type SourceControlProviderKind, type SourceControlRepositoryInfo, } from "@t3tools/contracts"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useNavigate, useParams } from "@tanstack/react-router"; import * as Option from "effect/Option"; import { ArrowDownIcon, ArrowLeftIcon, ArrowUpIcon, CornerLeftUpIcon, FolderIcon, FolderPlusIcon, LinkIcon, MessageSquareIcon, SettingsIcon, SquarePenIcon, } from "lucide-react"; import { useCallback, useDeferredValue, useEffect, useLayoutEffect, useMemo, useRef, useState, type KeyboardEvent, type ReactNode, } from "react"; import { useShallow } from "zustand/react/shallow"; import { useCommandPaletteStore } from "../commandPaletteStore"; import { readEnvironmentApi } from "../environmentApi"; import { readPrimaryEnvironmentDescriptor, usePrimaryEnvironmentId } from "../environments/primary"; import { useSavedEnvironmentRegistryStore, useSavedEnvironmentRuntimeStore, } from "../environments/runtime"; import { useHandleNewThread } from "../hooks/useHandleNewThread"; import { useSettings } from "../hooks/useSettings"; import { readLocalApi } from "../localApi"; import { getSourceControlDiscoverySnapshot, refreshSourceControlDiscovery, } from "../lib/sourceControlDiscoveryState"; import { startNewThreadInProjectFromContext, startNewThreadFromContext, } from "../lib/chatThreadActions"; import { appendBrowsePathSegment, canNavigateUp, ensureBrowseDirectoryPath, findProjectByPath, getBrowseDirectoryPath, getBrowseLeafPathSegment, getBrowseParentPath, hasTrailingPathSeparator, inferProjectTitleFromPath, isExplicitRelativeProjectPath, isFilesystemBrowseQuery, isUnsupportedWindowsProjectPath, resolveProjectPathForDispatch, } from "../lib/projectPaths"; import { isTerminalFocused } from "../lib/terminalFocus"; import { getLatestThreadForProject } from "../lib/threadSort"; import { cn, isMacPlatform, isWindowsPlatform, newCommandId, newProjectId } from "../lib/utils"; import { selectProjectsAcrossEnvironments, selectSidebarThreadsAcrossEnvironments, useStore, } from "../store"; import { selectThreadTerminalUiState, useTerminalUiStateStore } from "../terminalUiStateStore"; import { buildThreadRouteParams, resolveThreadRouteTarget } from "../threadRoutes"; import { ADDON_ICON_CLASS, buildBrowseGroups, buildProjectActionItems, buildRootGroups, buildThreadActionItems, type CommandPaletteActionItem, type CommandPaletteSubmenuItem, type CommandPaletteView, filterBrowseEntries, filterCommandPaletteGroups, getCommandPaletteInputPlaceholder, getCommandPaletteMode, ITEM_ICON_CLASS, RECENT_THREAD_LIMIT, } from "./CommandPalette.logic"; import { resolveEnvironmentOptionLabel } from "./BranchToolbar.logic"; import { CommandPaletteResults } from "./CommandPaletteResults"; import { AzureDevOpsIcon, BitbucketIcon, GitHubIcon, GitLabIcon } from "./Icons"; import { ProjectFavicon } from "./ProjectFavicon"; import { ThreadRowLeadingStatus, ThreadRowTrailingStatus } from "./ThreadStatusIndicators"; import { useServerKeybindings } from "../rpc/serverState"; import { resolveShortcutCommand } from "../keybindings"; import { Command, CommandDialog, CommandDialogPopup, CommandFooter, CommandInput, CommandPanel, } from "./ui/command"; import { Button } from "./ui/button"; import { Kbd, KbdGroup } from "./ui/kbd"; import { stackedThreadToast, toastManager } from "./ui/toast"; import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; import { ComposerHandleContext, useComposerHandleContext } from "../composerHandleContext"; import type { ChatComposerHandle } from "./chat/ChatComposer"; const EMPTY_BROWSE_ENTRIES: FilesystemBrowseResult["entries"] = []; const BROWSE_STALE_TIME_MS = 30_000; function getLocalFileManagerName(platform: string): string { if (isMacPlatform(platform)) { return "Finder"; } if (isWindowsPlatform(platform)) { return "Explorer"; } return "Files"; } function getEnvironmentBrowsePlatform(os: string | null | undefined): string { if (os === "windows") { return "Win32"; } if (os === "darwin") { return "MacIntel"; } if (os === "linux") { return "Linux"; } return typeof navigator === "undefined" ? "" : navigator.platform; } interface AddProjectEnvironmentOption { readonly environmentId: EnvironmentId; readonly label: string; readonly isPrimary: boolean; } type AddProjectRemoteProviderKind = Extract< SourceControlProviderKind, "github" | "gitlab" | "bitbucket" | "azure-devops" >; type AddProjectRemoteSource = AddProjectRemoteProviderKind | "url"; type AddProjectCloneFlow = | { readonly step: "repository"; readonly environmentId: EnvironmentId; readonly source: AddProjectRemoteSource; } | { readonly step: "confirm"; readonly environmentId: EnvironmentId; readonly source: AddProjectRemoteSource; readonly repositoryInput: string; readonly repository: SourceControlRepositoryInfo | null; readonly remoteUrl: string; }; const REMOTE_PROJECT_SOURCES: ReadonlyArray<AddProjectRemoteSource> = [ "url", "github", "gitlab", "bitbucket", "azure-devops", ]; const REMOTE_PROJECT_PROVIDER_SOURCES: ReadonlyArray<AddProjectRemoteProviderKind> = [ "github", "gitlab", "bitbucket", "azure-devops", ]; function remoteProjectSourceLabel(source: AddProjectRemoteSource): string { switch (source) { case "github": return "GitHub"; case "gitlab": return "GitLab"; case "bitbucket": return "Bitbucket"; case "azure-devops": return "Azure DevOps"; case "url": return "Git URL"; } } function remoteProjectSourcePathHint(source: AddProjectRemoteSource): string { switch (source) { case "github": return "owner/repo"; case "gitlab": return "group/project"; case "bitbucket": return "workspace/repository"; case "azure-devops": return "project/repository"; case "url": return "URL"; } } function remoteProjectSourceProvider( source: AddProjectRemoteSource, ): AddProjectRemoteProviderKind | null { return source === "url" ? null : source; } function remoteProjectSourceIcon(source: AddProjectRemoteSource, className: string): ReactNode { switch (source) { case "github": return <GitHubIcon className={className} />; case "gitlab": return <GitLabIcon className={className} />; case "bitbucket": return <BitbucketIcon className={className} />; case "azure-devops": return <AzureDevOpsIcon className={className} />; case "url": return <LinkIcon className={className} />; } } function remoteProjectInputPlaceholder(flow: AddProjectCloneFlow | null): string | null { if (!flow) return null; if (flow.step === "confirm") return null; if (flow.source === "url") { return "Enter Git clone URL"; } return `Enter ${remoteProjectSourceLabel(flow.source)} repository (${remoteProjectSourcePathHint(flow.source)})`; } function sourceProviderKind(source: AddProjectRemoteSource): AddProjectRemoteProviderKind | null { return source === "url" ? null : source; } function sortAddProjectProviderSources( readinessBySource: AddProjectRemoteSourceReadiness, ): ReadonlyArray<AddProjectRemoteProviderKind> { return REMOTE_PROJECT_PROVIDER_SOURCES.toSorted((left, right) => { const leftReady = readinessBySource[left].ready; const rightReady = readinessBySource[right].ready; if (leftReady !== rightReady) { return leftReady ? -1 : 1; } return remoteProjectSourceLabel(left).localeCompare(remoteProjectSourceLabel(right)); }); } type AddProjectRemoteSourceReadiness = Record< AddProjectRemoteSource, { readonly ready: boolean; readonly hint: string | null } >; function buildAddProjectRemoteSourceReadiness( discovery: SourceControlDiscoveryResult | null, ): AddProjectRemoteSourceReadiness { const unavailable = { ready: false, hint: "Provider status unavailable. Open Settings -> Source Control and rescan.", } as const; const defaultReadiness: AddProjectRemoteSourceReadiness = { url: { ready: true, hint: null }, github: unavailable, gitlab: unavailable, bitbucket: unavailable, "azure-devops": unavailable, }; if (!discovery) { return defaultReadiness; } const providerByKind = new Map( discovery.sourceControlProviders.map((provider) => [provider.kind, provider]), ); const readiness = { ...defaultReadiness }; for (const source of REMOTE_PROJECT_SOURCES) { const kind = sourceProviderKind(source); if (!kind) continue; const provider = providerByKind.get(kind); if (!provider) { readiness[source] = unavailable; continue; } if (provider.status !== "available") { readiness[source] = { ready: false, hint: provider.installHint }; continue; } if (provider.auth.status === "unauthenticated") { readiness[source] = { ready: false, hint: Option.getOrNull(provider.auth.detail) ?? `${provider.label} is not authenticated. Open Settings -> Source Control for setup guidance.`, }; continue; } readiness[source] = { ready: true, hint: null }; } return readiness; } function errorMessage(error: unknown): string { if (error instanceof Error && error.message.trim().length > 0) { return error.message; } return "An error occurred."; } export function CommandPalette({ children }: { children: ReactNode }) { const open = useCommandPaletteStore((store) => store.open); const setOpen = useCommandPaletteStore((store) => store.setOpen); const toggleOpen = useCommandPaletteStore((store) => store.toggleOpen); const keybindings = useServerKeybindings(); const composerHandleRef = useRef<ChatComposerHandle | null>(null); const routeTarget = useParams({ strict: false, select: (params) => resolveThreadRouteTarget(params), }); const routeThreadRef = routeTarget?.kind === "server" ? routeTarget.threadRef : null; const terminalOpen = useTerminalUiStateStore((state) => routeThreadRef ? selectThreadTerminalUiState(state.terminalUiStateByThreadKey, routeThreadRef).terminalOpen : false, ); useEffect(() => { const onKeyDown = (event: globalThis.KeyboardEvent) => { if (event.defaultPrevented) return; const command = resolveShortcutCommand(event, keybindings, { context: { terminalFocus: isTerminalFocused(), terminalOpen, }, }); if (command !== "commandPalette.toggle") { return; } event.preventDefault(); event.stopPropagation(); toggleOpen(); }; window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); }, [keybindings, terminalOpen, toggleOpen]); return ( <ComposerHandleContext value={composerHandleRef}> <CommandDialog open={open} onOpenChange={setOpen}> {children} <CommandPaletteDialog /> </CommandDialog> </ComposerHandleContext> ); } function CommandPaletteDialog() { const open = useCommandPaletteStore((store) => store.open); const setOpen = useCommandPaletteStore((store) => store.setOpen); useEffect(() => { return () => { setOpen(false); }; }, [setOpen]); if (!open) { return null; } return <OpenCommandPaletteDialog />; } function OpenCommandPaletteDialog() { const navigate = useNavigate(); const setOpen = useCommandPaletteStore((store) => store.setOpen); const openIntent = useCommandPaletteStore((store) => store.openIntent); const clearOpenIntent = useCommandPaletteStore((store) => store.clearOpenIntent); const composerHandleRef = useComposerHandleContext(); const [query, setQuery] = useState(""); const deferredQuery = useDeferredValue(query); const isActionsOnly = deferredQuery.startsWith(">"); const queryClient = useQueryClient(); const [highlightedItemValue, setHighlightedItemValue] = useState<string | null>(null); const settings = useSettings(); const { activeDraftThread, activeThread, defaultProjectRef, handleNewThread } = useHandleNewThread(); const projects = useStore(useShallow(selectProjectsAcrossEnvironments)); const threads = useStore(useShallow(selectSidebarThreadsAcrossEnvironments)); const keybindings = useServerKeybindings(); const [viewStack, setViewStack] = useState<CommandPaletteView[]>([]); const currentView = viewStack.at(-1) ?? null; const [browseGeneration, setBrowseGeneration] = useState(0); const [addProjectEnvironmentId, setAddProjectEnvironmentId] = useState<EnvironmentId | null>( null, ); const [isPickingProjectFolder, setIsPickingProjectFolder] = useState(false); const [addProjectCloneFlow, setAddProjectCloneFlow] = useState<AddProjectCloneFlow | null>(null); const [isRemoteProjectLookingUp, setIsRemoteProjectLookingUp] = useState(false); const [isRemoteProjectCloning, setIsRemoteProjectCloning] = useState(false); const primaryEnvironmentId = usePrimaryEnvironmentId(); const primaryEnvironmentLabel = readPrimaryEnvironmentDescriptor()?.label ?? null; const savedEnvironmentRegistry = useSavedEnvironmentRegistryStore((state) => state.byId); const savedEnvironmentRuntimeById = useSavedEnvironmentRuntimeStore((state) => state.byId); const addProjectEnvironmentOptions = useMemo(() => { const options: AddProjectEnvironmentOption[] = []; const seenEnvironmentIds = new Set<EnvironmentId>(); if (primaryEnvironmentId) { seenEnvironmentIds.add(primaryEnvironmentId); options.push({ environmentId: primaryEnvironmentId, label: resolveEnvironmentOptionLabel({ isPrimary: true, environmentId: primaryEnvironmentId, runtimeLabel: primaryEnvironmentLabel, }), isPrimary: true, }); } for (const record of Object.values(savedEnvironmentRegistry)) { if (seenEnvironmentIds.has(record.environmentId)) { continue; } const runtimeState = savedEnvironmentRuntimeById[record.environmentId]; options.push({ environmentId: record.environmentId, label: resolveEnvironmentOptionLabel({ isPrimary: false, environmentId: record.environmentId, runtimeLabel: runtimeState?.descriptor?.label ?? null, savedLabel: record.label, }), isPrimary: false, }); } options.sort((left, right) => { if (left.isPrimary !== right.isPrimary) { return left.isPrimary ? -1 : 1; } return left.label.localeCompare(right.label); }); return options; }, [ primaryEnvironmentId, primaryEnvironmentLabel, savedEnvironmentRegistry, savedEnvironmentRuntimeById, ]); const defaultAddProjectEnvironmentId = addProjectEnvironmentOptions[0]?.environmentId ?? null; const browseEnvironmentId = addProjectEnvironmentId ?? defaultAddProjectEnvironmentId; const browseEnvironmentPlatform = useMemo(() => { const os = browseEnvironmentId && primaryEnvironmentId && browseEnvironmentId === primaryEnvironmentId ? (readPrimaryEnvironmentDescriptor()?.platform.os ?? null) : browseEnvironmentId ? (savedEnvironmentRuntimeById[browseEnvironmentId]?.descriptor?.platform.os ?? savedEnvironmentRuntimeById[browseEnvironmentId]?.serverConfig?.environment.platform .os ?? null) : null; return getEnvironmentBrowsePlatform(os); }, [browseEnvironmentId, primaryEnvironmentId, savedEnvironmentRuntimeById]); const isRemoteProjectCloneFlow = addProjectCloneFlow !== null; const isRemoteProjectRepositoryStep = addProjectCloneFlow?.step === "repository"; const isBrowsing = !isRemoteProjectRepositoryStep && isFilesystemBrowseQuery(query, browseEnvironmentPlatform); const paletteMode = getCommandPaletteMode({ currentView, isBrowsing }); const getAddProjectInitialQueryForEnvironment = useCallback( (environmentId: EnvironmentId | null): string => { const environmentSettings = environmentId && primaryEnvironmentId && environmentId === primaryEnvironmentId ? settings : environmentId ? savedEnvironmentRuntimeById[environmentId]?.serverConfig?.settings : null;