"use client"; import { useMemo } from "react"; import { IconGitBranch, IconTerminal2 } from "@tabler/icons-react"; import { Badge } from "@kandev/ui/badge"; import { ScrollOnOverflow } from "@kandev/ui/scroll-on-overflow"; import type { LocalRepository, Repository, Branch, Executor, ExecutorProfile, } from "@/lib/types/http"; import type { AgentProfileOption } from "@/lib/state/slices"; import { formatUserHomePath, truncateRepoPath } from "@/lib/utils"; import { getExecutorIcon } from "@/lib/executor-icons"; import { AgentLogo } from "@/components/agent-logo"; import { getCapabilityWarning } from "@/lib/capability-warning"; type OptionItem = { value: string; label: string; renderLabel: () => React.ReactNode; }; export function useRepositoryOptions( repositories: Repository[], discoveredRepositories: LocalRepository[], ) { const repositoryOptions = useMemo(() => { const normalizeRepoPath = (path: string) => path.replace(/\\/g, "/").replace(/\/+$/g, ""); const workspaceRepoPaths = new Set( repositories .map((repo: Repository) => repo.local_path) .filter(Boolean) .map((path: string) => normalizeRepoPath(path)), ); const localRepoOptions = discoveredRepositories.filter( (repo: LocalRepository) => !workspaceRepoPaths.has(normalizeRepoPath(repo.path)), ); return [ ...repositories.map((repo: Repository) => ({ value: repo.id, label: repo.name, renderLabel: () => ( {repo.name} {truncateRepoPath(repo.local_path, 24)} ), })), ...localRepoOptions.map((repo: LocalRepository) => ({ value: repo.path, label: truncateRepoPath(repo.path, 24), renderLabel: () => ( {truncateRepoPath(repo.path, 28)} ), })), ]; }, [repositories, discoveredRepositories]); const headerRepositoryOptions = useMemo(() => { return repositoryOptions.map((opt) => ({ ...opt, renderLabel: () => {opt.label}, })); }, [repositoryOptions]); return { repositoryOptions, headerRepositoryOptions }; } export function useBranchOptions(branchOptionsRaw: Branch[]) { return useMemo(() => { return branchOptionsRaw.map((branchObj: Branch) => { const displayName = branchObj.type === "remote" && branchObj.remote ? `${branchObj.remote}/${branchObj.name}` : branchObj.name; return { value: displayName, label: displayName, renderLabel: () => ( {displayName} {branchObj.type === "local" ? "local" : branchObj.remote || "remote"} ), }; }); }, [branchOptionsRaw]); } export function useAgentProfileOptions(agentProfiles: AgentProfileOption[]): OptionItem[] { return useMemo(() => { return agentProfiles.map((profile: AgentProfileOption) => { const parts = profile.label.split(" \u2022 "); const agentLabel = parts[0] ?? profile.label; const profileLabel = parts[1] ?? ""; const isPassthrough = profile.cli_passthrough === true; const warning = getCapabilityWarning(profile.capability_status, profile.capability_error); return { value: profile.id, label: profile.label, renderLabel: () => ( {agentLabel} {warning && ( )} {isPassthrough && ( )} {profileLabel ? ( {profileLabel} ) : null} ), }; }); }, [agentProfiles]); } export function useExecutorOptions(executors: Executor[]): OptionItem[] { return useMemo(() => { return executors.map((executor: Executor) => { const Icon = getExecutorIcon(executor.type); return { value: executor.id, label: executor.name, renderLabel: () => ( {executor.name} ), }; }); }, [executors]); } export function useIsLocalExecutor(executors: Executor[], executorId: string): boolean { return useMemo(() => { const selected = executors.find((e: Executor) => e.id === executorId); return selected?.type === "local"; }, [executors, executorId]); } export function useExecutorHint(executors: Executor[], executorId: string): string | null { return useMemo(() => { const selectedExecutor = executors.find((e: Executor) => e.id === executorId); if (selectedExecutor?.type === "worktree") return "A git worktree will be created from the base branch."; if (selectedExecutor?.type === "local") return "The agent will run directly on the repository."; return null; }, [executors, executorId]); } export type ExecutorProfileOptionItem = OptionItem & { executorType?: string; executorName?: string; }; export function useExecutorProfileOptions( allProfiles: ExecutorProfile[], ): ExecutorProfileOptionItem[] { return useMemo(() => { return allProfiles.map((profile) => { const Icon = getExecutorIcon(profile.executor_type ?? "local"); return { value: profile.id, label: profile.name, executorType: profile.executor_type, executorName: profile.executor_name, renderLabel: () => ( {profile.name} {profile.executor_name && ( {profile.executor_name} )} ), }; }); }, [allProfiles]); }