UNPKG

@copilotkit/react-core

Version:

<img src="https://github.com/user-attachments/assets/0a6b64d9-e193-4940-a3f6-60334ac34084" alt="banner" style="border-radius: 12px; border: 2px solid #d6d4fa;" />

1,092 lines (1,082 loc) 405 kB
"use client"; (function(global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./index.css'), require('@copilotkit/core'), require('@ag-ui/client'), require('react'), require('tailwind-merge'), require('lucide-react'), require('@copilotkit/shared'), require('react/jsx-runtime'), require('@radix-ui/react-slot'), require('class-variance-authority'), require('clsx'), require('@radix-ui/react-tooltip'), require('@radix-ui/react-dropdown-menu'), require('streamdown'), require('zod'), require('@lit-labs/react'), require('@copilotkit/a2ui-renderer'), require('zod-to-json-schema'), require('react-dom'), require('@tanstack/react-virtual'), require('use-stick-to-bottom')) : typeof define === 'function' && define.amd ? define(['exports', './index.css', '@copilotkit/core', '@ag-ui/client', 'react', 'tailwind-merge', 'lucide-react', '@copilotkit/shared', 'react/jsx-runtime', '@radix-ui/react-slot', 'class-variance-authority', 'clsx', '@radix-ui/react-tooltip', '@radix-ui/react-dropdown-menu', 'streamdown', 'zod', '@lit-labs/react', '@copilotkit/a2ui-renderer', 'zod-to-json-schema', 'react-dom', '@tanstack/react-virtual', 'use-stick-to-bottom'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.CopilotKitReactCoreV2 = {}), global.src_v2_index_css,global.CopilotKitCore,global.AgUIClient,global.React,global.tailwindMerge,global.lucideReact,global.CopilotKitShared,global.ReactJsxRuntime,global.RadixReactSlot,global.classVarianceAuthority,global.clsx,global.RadixReactTooltip,global.RadixReactDropdownMenu,global.streamdown,global.Zod,global.LitLabsReact,global.CopilotKitA2UIRenderer,global.zod_to_json_schema,global.ReactDOM,global._tanstack_react_virtual,global.useStickToBottom)); })(this, function(exports, src_v2_index_css, _copilotkit_core, _ag_ui_client, react, tailwind_merge, lucide_react, _copilotkit_shared, react_jsx_runtime, _radix_ui_react_slot, class_variance_authority, clsx, _radix_ui_react_tooltip, _radix_ui_react_dropdown_menu, streamdown, zod, _lit_labs_react, _copilotkit_a2ui_renderer, zod_to_json_schema, react_dom, _tanstack_react_virtual, use_stick_to_bottom) { Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); //#region \0rolldown/runtime.js var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) { __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } } } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion react = __toESM(react); _radix_ui_react_tooltip = __toESM(_radix_ui_react_tooltip); _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu); //#region src/v2/lib/slots.tsx /** * Shallow equality comparison for objects. */ function shallowEqual(obj1, obj2) { const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); if (keys1.length !== keys2.length) return false; for (const key of keys1) if (obj1[key] !== obj2[key]) return false; return true; } /** * Returns true only for plain JS objects (`{}`), excluding arrays, Dates, * class instances, and other exotic objects that happen to have typeof "object". */ function isPlainObject(obj) { return obj !== null && typeof obj === "object" && Object.prototype.toString.call(obj) === "[object Object]"; } /** * Returns the same reference as long as the value is shallowly equal to the * previous render's value. * * - Identical references bail out immediately (O(1)). * - Plain objects ({}) are shallow-compared key-by-key. * - Arrays, Dates, class instances, functions, and primitives are compared by * reference only — shallowEqual is never called on non-plain objects, which * avoids incorrect equality for e.g. [1,2] vs [1,2] (different arrays). * * Typical use: stabilize inline slot props so MemoizedSlotWrapper's shallow * equality check isn't defeated by a new object reference on every render. */ function useShallowStableRef(value) { const ref = (0, react.useRef)(value); if (ref.current === value) return ref.current; if (isPlainObject(ref.current) && isPlainObject(value)) { if (shallowEqual(ref.current, value)) return ref.current; } ref.current = value; return ref.current; } /** * Check if a value is a React component type (function, class, forwardRef, memo, etc.) */ function isReactComponentType(value) { if (typeof value === "function") return true; if (value && typeof value === "object" && "$$typeof" in value && !react.default.isValidElement(value)) return true; return false; } /** * Internal function to render a slot value as a React element (non-memoized). */ function renderSlotElement(slot, DefaultComponent, props) { if (typeof slot === "string") { const existingClassName = props.className; return react.default.createElement(DefaultComponent, { ...props, className: (0, tailwind_merge.twMerge)(existingClassName, slot) }); } if (isReactComponentType(slot)) return react.default.createElement(slot, props); if (slot && typeof slot === "object" && !react.default.isValidElement(slot)) return react.default.createElement(DefaultComponent, { ...props, ...slot }); return react.default.createElement(DefaultComponent, props); } /** * Internal memoized wrapper component for renderSlot. * Uses forwardRef to support ref forwarding. */ const MemoizedSlotWrapper = react.default.memo(react.default.forwardRef(function MemoizedSlotWrapper(props, ref) { const { $slot, $component, ...rest } = props; return renderSlotElement($slot, $component, ref !== null ? { ...rest, ref } : rest); }), (prev, next) => { if (prev.$slot !== next.$slot) return false; if (prev.$component !== next.$component) return false; const { $slot: _ps, $component: _pc, ...prevRest } = prev; const { $slot: _ns, $component: _nc, ...nextRest } = next; return shallowEqual(prevRest, nextRest); }); /** * Renders a slot value as a memoized React element. * Automatically prevents unnecessary re-renders using shallow prop comparison. * Supports ref forwarding. * * @example * renderSlot(customInput, CopilotChatInput, { onSubmit: handleSubmit }) */ function renderSlot(slot, DefaultComponent, props) { return react.default.createElement(MemoizedSlotWrapper, { ...props, $slot: slot, $component: DefaultComponent }); } //#endregion //#region src/v2/providers/CopilotChatConfigurationProvider.tsx const CopilotChatDefaultLabels = { chatInputPlaceholder: "Type a message...", chatInputToolbarStartTranscribeButtonLabel: "Transcribe", chatInputToolbarCancelTranscribeButtonLabel: "Cancel", chatInputToolbarFinishTranscribeButtonLabel: "Finish", chatInputToolbarAddButtonLabel: "Add attachments", chatInputToolbarToolsButtonLabel: "Tools", assistantMessageToolbarCopyCodeLabel: "Copy", assistantMessageToolbarCopyCodeCopiedLabel: "Copied", assistantMessageToolbarCopyMessageLabel: "Copy", assistantMessageToolbarThumbsUpLabel: "Good response", assistantMessageToolbarThumbsDownLabel: "Bad response", assistantMessageToolbarReadAloudLabel: "Read aloud", assistantMessageToolbarRegenerateLabel: "Regenerate", userMessageToolbarCopyMessageLabel: "Copy", userMessageToolbarEditMessageLabel: "Edit", chatDisclaimerText: "AI can make mistakes. Please verify important information.", chatToggleOpenLabel: "Open chat", chatToggleCloseLabel: "Close chat", modalHeaderTitle: "CopilotKit Chat", welcomeMessageText: "How can I help you today?" }; const CopilotChatConfiguration = (0, react.createContext)(null); const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId, hasExplicitThreadId, isModalDefaultOpen }) => { const parentConfig = (0, react.useContext)(CopilotChatConfiguration); const stableLabels = useShallowStableRef(labels); const mergedLabels = (0, react.useMemo)(() => ({ ...CopilotChatDefaultLabels, ...parentConfig?.labels, ...stableLabels }), [stableLabels, parentConfig?.labels]); const resolvedAgentId = agentId ?? parentConfig?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID; const resolvedThreadId = (0, react.useMemo)(() => { if (threadId) return threadId; if (parentConfig?.threadId) return parentConfig.threadId; return (0, _copilotkit_shared.randomUUID)(); }, [threadId, parentConfig?.threadId]); const resolvedHasExplicitThreadId = (hasExplicitThreadId !== void 0 ? hasExplicitThreadId : !!threadId) || !!parentConfig?.hasExplicitThreadId; const [internalModalOpen, setInternalModalOpen] = (0, react.useState)(isModalDefaultOpen ?? true); const hasExplicitDefault = isModalDefaultOpen !== void 0; const setAndSync = (0, react.useCallback)((open) => { setInternalModalOpen(open); parentConfig?.setModalOpen(open); }, [parentConfig?.setModalOpen]); const isMounted = (0, react.useRef)(false); (0, react.useEffect)(() => { if (!hasExplicitDefault) return; if (!isMounted.current) { isMounted.current = true; return; } if (parentConfig?.isModalOpen === void 0) return; setInternalModalOpen(parentConfig.isModalOpen); }, [parentConfig?.isModalOpen, hasExplicitDefault]); const resolvedIsModalOpen = hasExplicitDefault ? internalModalOpen : parentConfig?.isModalOpen ?? internalModalOpen; const resolvedSetModalOpen = hasExplicitDefault ? setAndSync : parentConfig?.setModalOpen ?? setInternalModalOpen; const configurationValue = (0, react.useMemo)(() => ({ labels: mergedLabels, agentId: resolvedAgentId, threadId: resolvedThreadId, hasExplicitThreadId: resolvedHasExplicitThreadId, isModalOpen: resolvedIsModalOpen, setModalOpen: resolvedSetModalOpen }), [ mergedLabels, resolvedAgentId, resolvedThreadId, resolvedHasExplicitThreadId, resolvedIsModalOpen, resolvedSetModalOpen ]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfiguration.Provider, { value: configurationValue, children }); }; const useCopilotChatConfiguration = () => { return (0, react.useContext)(CopilotChatConfiguration); }; //#endregion //#region src/v2/lib/utils.ts const twMerge$7 = (0, tailwind_merge.extendTailwindMerge)({ prefix: "cpk" }); function cn(...inputs) { return twMerge$7((0, clsx.clsx)(inputs)); } //#endregion //#region src/v2/components/ui/button.tsx const buttonVariants = (0, class_variance_authority.cva)("cpk:inline-flex cpk:items-center cpk:justify-center cpk:gap-2 cpk:whitespace-nowrap cpk:rounded-md cpk:text-sm cpk:font-medium cpk:transition-all cpk:disabled:pointer-events-none cpk:disabled:opacity-50 cpk:[&_svg]:pointer-events-none cpk:[&_svg:not([class*='size-'])]:size-4 cpk:shrink-0 cpk:[&_svg]:shrink-0 cpk:outline-none cpk:focus-visible:border-ring cpk:focus-visible:ring-ring/50 cpk:focus-visible:ring-[3px] cpk:aria-invalid:ring-destructive/20 cpk:dark:aria-invalid:ring-destructive/40 cpk:aria-invalid:border-destructive", { variants: { variant: { default: "cpk:bg-primary cpk:text-primary-foreground cpk:shadow-xs cpk:hover:bg-primary/90", destructive: "cpk:bg-destructive cpk:text-white cpk:shadow-xs cpk:hover:bg-destructive/90 cpk:focus-visible:ring-destructive/20 cpk:dark:focus-visible:ring-destructive/40 cpk:dark:bg-destructive/60", outline: "cpk:border cpk:bg-background cpk:shadow-xs cpk:hover:bg-accent cpk:hover:text-accent-foreground cpk:dark:bg-input/30 cpk:dark:border-input cpk:dark:hover:bg-input/50", secondary: "cpk:bg-secondary cpk:text-secondary-foreground cpk:shadow-xs cpk:hover:bg-secondary/80", ghost: "cpk:hover:bg-accent cpk:hover:text-accent-foreground cpk:dark:hover:bg-accent/50 cpk:cursor-pointer", link: "cpk:text-primary cpk:underline-offset-4 cpk:hover:underline", assistantMessageToolbarButton: [ "cpk:cursor-pointer", "cpk:p-0 cpk:text-[rgb(93,93,93)] cpk:hover:bg-[#E8E8E8]", "cpk:dark:text-[rgb(243,243,243)] cpk:dark:hover:bg-[#303030]", "cpk:h-8 cpk:w-8", "cpk:transition-colors", "cpk:hover:text-[rgb(93,93,93)]", "cpk:dark:hover:text-[rgb(243,243,243)]" ], chatInputToolbarPrimary: [ "cpk:cursor-pointer", "cpk:bg-black cpk:text-white", "cpk:dark:bg-white cpk:dark:text-black cpk:dark:focus-visible:outline-white", "cpk:rounded-full", "cpk:transition-colors", "cpk:focus:outline-none", "cpk:hover:opacity-70 cpk:disabled:hover:opacity-100", "cpk:disabled:cursor-not-allowed cpk:disabled:bg-[#00000014] cpk:disabled:text-[rgb(13,13,13)]", "cpk:dark:disabled:bg-[#454545] cpk:dark:disabled:text-white " ], chatInputToolbarSecondary: [ "cpk:cursor-pointer", "cpk:bg-transparent cpk:text-[#444444]", "cpk:dark:text-white cpk:dark:border-[#404040]", "cpk:rounded-full", "cpk:transition-colors", "cpk:focus:outline-none", "cpk:hover:bg-[#f8f8f8] cpk:hover:text-[#333333]", "cpk:dark:hover:bg-[#404040] cpk:dark:hover:text-[#FFFFFF]", "cpk:disabled:cursor-not-allowed cpk:disabled:opacity-50", "cpk:disabled:hover:bg-transparent cpk:disabled:hover:text-[#444444]", "cpk:dark:disabled:hover:bg-transparent cpk:dark:disabled:hover:text-[#CCCCCC]" ] }, size: { default: "cpk:h-9 cpk:px-4 cpk:py-2 cpk:has-[>svg]:px-3", sm: "cpk:h-8 cpk:rounded-md cpk:gap-1.5 cpk:px-3 cpk:has-[>svg]:px-2.5", lg: "cpk:h-10 cpk:rounded-md cpk:px-6 cpk:has-[>svg]:px-4", icon: "cpk:size-9", chatInputToolbarIcon: ["cpk:h-9 cpk:w-9 cpk:rounded-full"], chatInputToolbarIconLabel: [ "cpk:h-9 cpk:px-3 cpk:rounded-full", "cpk:gap-2", "cpk:font-normal" ] } }, defaultVariants: { variant: "default", size: "default" } }); const Button = react.forwardRef(function Button({ className, variant, size, asChild = false, ...props }, ref) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(asChild ? _radix_ui_react_slot.Slot : "button", { ref, "data-slot": "button", className: cn(buttonVariants({ variant, size, className })), ...props }); }); //#endregion //#region src/v2/components/ui/tooltip.tsx function TooltipProvider({ delayDuration = 0, ...props }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_tooltip.Provider, { "data-slot": "tooltip-provider", delayDuration, ...props }); } function Tooltip({ ...props }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_tooltip.Root, { "data-slot": "tooltip", ...props }) }); } function TooltipTrigger({ ...props }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_tooltip.Trigger, { "data-slot": "tooltip-trigger", ...props }); } function TooltipContent({ className, sideOffset = 0, children, ...props }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_tooltip.Portal, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_radix_ui_react_tooltip.Content, { "data-copilotkit": true, "data-slot": "tooltip-content", sideOffset, className: cn("cpk:bg-primary cpk:text-primary-foreground cpk:animate-in cpk:fade-in-0 cpk:zoom-in-95 cpk:data-[state=closed]:animate-out cpk:data-[state=closed]:fade-out-0 cpk:data-[state=closed]:zoom-out-95 cpk:data-[side=bottom]:slide-in-from-top-2 cpk:data-[side=left]:slide-in-from-right-2 cpk:data-[side=right]:slide-in-from-left-2 cpk:data-[side=top]:slide-in-from-bottom-2 cpk:z-50 cpk:w-fit cpk:origin-(--radix-tooltip-content-transform-origin) cpk:rounded-md cpk:px-3 cpk:py-1.5 cpk:text-xs cpk:text-balance", className), ...props, children: [children, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_tooltip.Arrow, { className: "cpk:bg-primary cpk:fill-primary cpk:z-50 cpk:size-2.5 cpk:translate-y-[calc(-50%_-_2px)] cpk:rotate-45 cpk:rounded-[2px]" })] }) }); } //#endregion //#region src/v2/components/ui/dropdown-menu.tsx function DropdownMenu({ ...props }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_dropdown_menu.Root, { "data-slot": "dropdown-menu", ...props }); } function DropdownMenuTrigger({ ...props }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_dropdown_menu.Trigger, { "data-slot": "dropdown-menu-trigger", ...props }); } function DropdownMenuContent({ className, sideOffset = 4, ...props }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_dropdown_menu.Portal, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_dropdown_menu.Content, { "data-copilotkit": true, "data-slot": "dropdown-menu-content", sideOffset, className: cn("cpk:bg-popover cpk:text-popover-foreground cpk:data-[state=open]:animate-in cpk:data-[state=closed]:animate-out cpk:data-[state=closed]:fade-out-0 cpk:data-[state=open]:fade-in-0 cpk:data-[state=closed]:zoom-out-95 cpk:data-[state=open]:zoom-in-95 cpk:data-[side=bottom]:slide-in-from-top-2 cpk:data-[side=left]:slide-in-from-right-2 cpk:data-[side=right]:slide-in-from-left-2 cpk:data-[side=top]:slide-in-from-bottom-2 cpk:z-50 cpk:max-h-(--radix-dropdown-menu-content-available-height) cpk:min-w-[8rem] cpk:origin-(--radix-dropdown-menu-content-transform-origin) cpk:overflow-x-hidden cpk:overflow-y-auto cpk:rounded-md cpk:border cpk:p-1 cpk:shadow-md", className), ...props }) }); } function DropdownMenuItem({ className, inset, variant = "default", ...props }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_dropdown_menu.Item, { "data-slot": "dropdown-menu-item", "data-inset": inset, "data-variant": variant, className: cn("cpk:focus:bg-accent cpk:focus:text-accent-foreground cpk:data-[variant=destructive]:text-destructive cpk:data-[variant=destructive]:focus:bg-destructive/10 cpk:dark:data-[variant=destructive]:focus:bg-destructive/20 cpk:data-[variant=destructive]:focus:text-destructive cpk:data-[variant=destructive]:*:[svg]:!text-destructive cpk:[&_svg:not([class*='text-'])]:text-muted-foreground cpk:relative cpk:flex cpk:cursor-default cpk:items-center cpk:gap-2 cpk:rounded-sm cpk:px-2 cpk:py-1.5 cpk:text-sm cpk:outline-hidden cpk:select-none cpk:data-[disabled]:pointer-events-none cpk:data-[disabled]:opacity-50 cpk:data-[inset]:pl-8 cpk:[&_svg]:pointer-events-none cpk:[&_svg]:shrink-0 cpk:[&_svg:not([class*='size-'])]:size-4", className), ...props }); } function DropdownMenuSeparator({ className, ...props }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_dropdown_menu.Separator, { "data-slot": "dropdown-menu-separator", className: cn("cpk:bg-border cpk:-mx-1 cpk:my-1 cpk:h-px", className), ...props }); } function DropdownMenuSub({ ...props }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_dropdown_menu.Sub, { "data-slot": "dropdown-menu-sub", ...props }); } function DropdownMenuSubTrigger({ className, inset, children, ...props }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_radix_ui_react_dropdown_menu.SubTrigger, { "data-slot": "dropdown-menu-sub-trigger", "data-inset": inset, className: cn("cpk:focus:bg-accent cpk:focus:text-accent-foreground cpk:data-[state=open]:bg-accent cpk:data-[state=open]:text-accent-foreground cpk:flex cpk:cursor-default cpk:items-center cpk:rounded-sm cpk:px-2 cpk:py-1.5 cpk:text-sm cpk:outline-hidden cpk:select-none cpk:data-[inset]:pl-8", className), ...props, children: [children, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRightIcon, { className: "cpk:ml-auto cpk:size-4" })] }); } function DropdownMenuSubContent({ className, ...props }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_dropdown_menu.SubContent, { "data-slot": "dropdown-menu-sub-content", className: cn("cpk:bg-popover cpk:text-popover-foreground cpk:data-[state=open]:animate-in cpk:data-[state=closed]:animate-out cpk:data-[state=closed]:fade-out-0 cpk:data-[state=open]:fade-in-0 cpk:data-[state=closed]:zoom-out-95 cpk:data-[state=open]:zoom-in-95 cpk:data-[side=bottom]:slide-in-from-top-2 cpk:data-[side=left]:slide-in-from-right-2 cpk:data-[side=right]:slide-in-from-left-2 cpk:data-[side=top]:slide-in-from-bottom-2 cpk:z-50 cpk:min-w-[8rem] cpk:origin-(--radix-dropdown-menu-content-transform-origin) cpk:overflow-hidden cpk:rounded-md cpk:border cpk:p-1 cpk:shadow-lg", className), ...props }); } //#endregion //#region src/v2/components/chat/CopilotChatAudioRecorder.tsx /** Error subclass so callers can `instanceof`-guard recorder failures */ var AudioRecorderError = class extends Error { constructor(message) { super(message); this.name = "AudioRecorderError"; } }; const CopilotChatAudioRecorder = (0, react.forwardRef)((props, ref) => { const { className, ...divProps } = props; const canvasRef = (0, react.useRef)(null); const [recorderState, setRecorderState] = (0, react.useState)("idle"); const mediaRecorderRef = (0, react.useRef)(null); const audioChunksRef = (0, react.useRef)([]); const streamRef = (0, react.useRef)(null); const analyserRef = (0, react.useRef)(null); const audioContextRef = (0, react.useRef)(null); const animationIdRef = (0, react.useRef)(null); const amplitudeHistoryRef = (0, react.useRef)([]); const frameCountRef = (0, react.useRef)(0); const scrollOffsetRef = (0, react.useRef)(0); const smoothedAmplitudeRef = (0, react.useRef)(0); const fadeOpacityRef = (0, react.useRef)(0); const cleanup = (0, react.useCallback)(() => { if (animationIdRef.current) { cancelAnimationFrame(animationIdRef.current); animationIdRef.current = null; } if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") try { mediaRecorderRef.current.stop(); } catch {} if (streamRef.current) { streamRef.current.getTracks().forEach((track) => track.stop()); streamRef.current = null; } if (audioContextRef.current && audioContextRef.current.state !== "closed") { audioContextRef.current.close().catch(() => {}); audioContextRef.current = null; } mediaRecorderRef.current = null; analyserRef.current = null; audioChunksRef.current = []; amplitudeHistoryRef.current = []; frameCountRef.current = 0; scrollOffsetRef.current = 0; smoothedAmplitudeRef.current = 0; fadeOpacityRef.current = 0; }, []); const start = (0, react.useCallback)(async () => { if (recorderState !== "idle") throw new AudioRecorderError("Recorder is already active"); try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); streamRef.current = stream; const audioContext = new AudioContext(); audioContextRef.current = audioContext; const source = audioContext.createMediaStreamSource(stream); const analyser = audioContext.createAnalyser(); analyser.fftSize = 2048; source.connect(analyser); analyserRef.current = analyser; const mimeType = MediaRecorder.isTypeSupported("audio/webm;codecs=opus") ? "audio/webm;codecs=opus" : MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : MediaRecorder.isTypeSupported("audio/mp4") ? "audio/mp4" : ""; const options = mimeType ? { mimeType } : {}; const mediaRecorder = new MediaRecorder(stream, options); mediaRecorderRef.current = mediaRecorder; audioChunksRef.current = []; mediaRecorder.ondataavailable = (event) => { if (event.data.size > 0) audioChunksRef.current.push(event.data); }; mediaRecorder.start(100); setRecorderState("recording"); } catch (error) { cleanup(); if (error instanceof Error && error.name === "NotAllowedError") throw new AudioRecorderError("Microphone permission denied"); if (error instanceof Error && error.name === "NotFoundError") throw new AudioRecorderError("No microphone found"); throw new AudioRecorderError(error instanceof Error ? error.message : "Failed to start recording"); } }, [recorderState, cleanup]); const stop = (0, react.useCallback)(() => { return new Promise((resolve, reject) => { const mediaRecorder = mediaRecorderRef.current; if (!mediaRecorder || recorderState !== "recording") { reject(new AudioRecorderError("No active recording")); return; } setRecorderState("processing"); mediaRecorder.onstop = () => { const mimeType = mediaRecorder.mimeType || "audio/webm"; const audioBlob = new Blob(audioChunksRef.current, { type: mimeType }); cleanup(); setRecorderState("idle"); resolve(audioBlob); }; mediaRecorder.onerror = () => { cleanup(); setRecorderState("idle"); reject(new AudioRecorderError("Recording failed")); }; mediaRecorder.stop(); }); }, [recorderState, cleanup]); const calculateAmplitude = (dataArray) => { let sum = 0; for (let i = 0; i < dataArray.length; i++) { const sample = (dataArray[i] ?? 128) / 128 - 1; sum += sample * sample; } return Math.sqrt(sum / dataArray.length); }; (0, react.useEffect)(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; const barWidth = 2; const barSpacing = barWidth + 1; const scrollSpeed = 1 / 3; const draw = () => { const rect = canvas.getBoundingClientRect(); const dpr = window.devicePixelRatio || 1; if (canvas.width !== rect.width * dpr || canvas.height !== rect.height * dpr) { canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; ctx.scale(dpr, dpr); } const maxBars = Math.floor(rect.width / barSpacing) + 2; if (analyserRef.current && recorderState === "recording") { if (amplitudeHistoryRef.current.length === 0) amplitudeHistoryRef.current = new Array(maxBars).fill(0); if (fadeOpacityRef.current < 1) fadeOpacityRef.current = Math.min(1, fadeOpacityRef.current + .03); scrollOffsetRef.current += scrollSpeed; const bufferLength = analyserRef.current.fftSize; const dataArray = new Uint8Array(bufferLength); analyserRef.current.getByteTimeDomainData(dataArray); const rawAmplitude = calculateAmplitude(dataArray); const speed = rawAmplitude > smoothedAmplitudeRef.current ? .12 : .08; smoothedAmplitudeRef.current += (rawAmplitude - smoothedAmplitudeRef.current) * speed; if (scrollOffsetRef.current >= barSpacing) { scrollOffsetRef.current -= barSpacing; amplitudeHistoryRef.current.push(smoothedAmplitudeRef.current); if (amplitudeHistoryRef.current.length > maxBars) amplitudeHistoryRef.current = amplitudeHistoryRef.current.slice(-maxBars); } } ctx.clearRect(0, 0, rect.width, rect.height); ctx.fillStyle = getComputedStyle(canvas).color; ctx.globalAlpha = fadeOpacityRef.current; const centerY = rect.height / 2; const maxAmplitude = rect.height / 2 - 2; const history = amplitudeHistoryRef.current; if (history.length > 0) { const offset = scrollOffsetRef.current; const edgeFadeWidth = 12; for (let i = 0; i < history.length; i++) { const amplitude = history[i] ?? 0; const scaledAmplitude = Math.min(amplitude * 4, 1); const barHeight = Math.max(2, scaledAmplitude * maxAmplitude * 2); const x = rect.width - (history.length - i) * barSpacing - offset; const y = centerY - barHeight / 2; if (x + barWidth > 0 && x < rect.width) { let edgeOpacity = 1; if (x < edgeFadeWidth) edgeOpacity = Math.max(0, x / edgeFadeWidth); else if (x > rect.width - edgeFadeWidth) edgeOpacity = Math.max(0, (rect.width - x) / edgeFadeWidth); ctx.globalAlpha = fadeOpacityRef.current * edgeOpacity; ctx.fillRect(x, y, barWidth, barHeight); } } } animationIdRef.current = requestAnimationFrame(draw); }; draw(); return () => { if (animationIdRef.current) cancelAnimationFrame(animationIdRef.current); }; }, [recorderState]); (0, react.useEffect)(() => { return cleanup; }, [cleanup]); (0, react.useImperativeHandle)(ref, () => ({ get state() { return recorderState; }, start, stop, dispose: cleanup }), [ recorderState, start, stop, cleanup ]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: (0, tailwind_merge.twMerge)("cpk:w-full cpk:py-3 cpk:px-5", className), ...divProps, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("canvas", { ref: canvasRef, className: "cpk:block cpk:w-full cpk:h-[26px]" }) }); }); CopilotChatAudioRecorder.displayName = "CopilotChatAudioRecorder"; //#endregion //#region src/v2/components/chat/CopilotChatInput.tsx const SLASH_MENU_MAX_VISIBLE_ITEMS = 5; const SLASH_MENU_ITEM_HEIGHT_PX = 40; function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning = false, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, onAddFile, onChange, value, toolsMenu, autoFocus = false, positioning = "static", keyboardHeight = 0, containerRef, showDisclaimer, bottomAnchored = false, textArea, sendButton, startTranscribeButton, cancelTranscribeButton, finishTranscribeButton, addMenuButton, audioRecorder, disclaimer, children, className, ...props }) { const isControlled = value !== void 0; const [internalValue, setInternalValue] = (0, react.useState)(() => value ?? ""); (0, react.useEffect)(() => { if (!isControlled && value !== void 0) setInternalValue(value); }, [isControlled, value]); const resolvedValue = isControlled ? value ?? "" : internalValue; const [layout, setLayout] = (0, react.useState)("compact"); const ignoreResizeRef = (0, react.useRef)(false); const resizeEvaluationRafRef = (0, react.useRef)(null); const isExpanded = mode === "input" && layout === "expanded"; const [commandQuery, setCommandQuery] = (0, react.useState)(null); const [slashHighlightIndex, setSlashHighlightIndex] = (0, react.useState)(0); const inputRef = (0, react.useRef)(null); const gridRef = (0, react.useRef)(null); const addButtonContainerRef = (0, react.useRef)(null); const actionsContainerRef = (0, react.useRef)(null); const audioRecorderRef = (0, react.useRef)(null); const slashMenuRef = (0, react.useRef)(null); const config = useCopilotChatConfiguration(); const labels = config?.labels ?? CopilotChatDefaultLabels; const previousModalStateRef = (0, react.useRef)(void 0); const measurementCanvasRef = (0, react.useRef)(null); const measurementsRef = (0, react.useRef)({ singleLineHeight: 0, maxHeight: 0, paddingLeft: 0, paddingRight: 0 }); const containerCacheRef = (0, react.useRef)(null); const commandItems = (0, react.useMemo)(() => { const entries = []; const seen = /* @__PURE__ */ new Set(); const pushItem = (item) => { if (item === "-") return; if (item.items && item.items.length > 0) { for (const nested of item.items) pushItem(nested); return; } if (!seen.has(item.label)) { seen.add(item.label); entries.push(item); } }; if (onAddFile) pushItem({ label: labels.chatInputToolbarAddButtonLabel, action: onAddFile }); if (toolsMenu && toolsMenu.length > 0) for (const item of toolsMenu) pushItem(item); return entries; }, [ labels.chatInputToolbarAddButtonLabel, onAddFile, toolsMenu ]); const filteredCommands = (0, react.useMemo)(() => { if (commandQuery === null) return []; if (commandItems.length === 0) return []; const query = commandQuery.trim().toLowerCase(); if (query.length === 0) return commandItems; const startsWith = []; const contains = []; for (const item of commandItems) { const label = item.label.toLowerCase(); if (label.startsWith(query)) startsWith.push(item); else if (label.includes(query)) contains.push(item); } return [...startsWith, ...contains]; }, [commandItems, commandQuery]); (0, react.useEffect)(() => { if (!autoFocus) { previousModalStateRef.current = config?.isModalOpen; return; } if (config?.isModalOpen && !previousModalStateRef.current) inputRef.current?.focus({ preventScroll: true }); previousModalStateRef.current = config?.isModalOpen; }, [config?.isModalOpen, autoFocus]); (0, react.useEffect)(() => { if (commandItems.length === 0 && commandQuery !== null) setCommandQuery(null); }, [commandItems.length, commandQuery]); const previousCommandQueryRef = (0, react.useRef)(null); (0, react.useEffect)(() => { if (commandQuery !== null && commandQuery !== previousCommandQueryRef.current && filteredCommands.length > 0) setSlashHighlightIndex(0); previousCommandQueryRef.current = commandQuery; }, [commandQuery, filteredCommands.length]); (0, react.useEffect)(() => { if (commandQuery === null) { setSlashHighlightIndex(0); return; } if (filteredCommands.length === 0) setSlashHighlightIndex(-1); else if (slashHighlightIndex < 0 || slashHighlightIndex >= filteredCommands.length) setSlashHighlightIndex(0); }, [ commandQuery, filteredCommands, slashHighlightIndex ]); (0, react.useEffect)(() => { const recorder = audioRecorderRef.current; if (!recorder) return; if (mode === "transcribe") recorder.start().catch(console.error); else if (recorder.state === "recording") recorder.stop().catch(console.error); }, [mode]); (0, react.useEffect)(() => { if (mode !== "input") { setLayout("compact"); setCommandQuery(null); } }, [mode]); const updateSlashState = (0, react.useCallback)((value) => { if (commandItems.length === 0) { setCommandQuery((prev) => prev === null ? prev : null); return; } if (value.startsWith("/")) { const query = (value.split(/\r?\n/, 1)[0] ?? "").slice(1); setCommandQuery((prev) => prev === query ? prev : query); } else setCommandQuery((prev) => prev === null ? prev : null); }, [commandItems.length]); (0, react.useEffect)(() => { updateSlashState(resolvedValue); }, [resolvedValue, updateSlashState]); const handleChange = (e) => { const nextValue = e.target.value; if (!isControlled) setInternalValue(nextValue); onChange?.(nextValue); updateSlashState(nextValue); }; const clearInputValue = (0, react.useCallback)(() => { if (!isControlled) setInternalValue(""); if (onChange) onChange(""); }, [isControlled, onChange]); const runCommand = (0, react.useCallback)((item) => { clearInputValue(); item.action?.(); setCommandQuery(null); setSlashHighlightIndex(0); requestAnimationFrame(() => { inputRef.current?.focus(); }); }, [clearInputValue]); const handleKeyDown = (e) => { if (e.nativeEvent.isComposing || e.keyCode === 229) return; if (commandQuery !== null && mode === "input") { if (e.key === "ArrowDown") { if (filteredCommands.length > 0) { e.preventDefault(); setSlashHighlightIndex((prev) => { if (filteredCommands.length === 0) return prev; return prev === -1 ? 0 : (prev + 1) % filteredCommands.length; }); } return; } if (e.key === "ArrowUp") { if (filteredCommands.length > 0) { e.preventDefault(); setSlashHighlightIndex((prev) => { if (filteredCommands.length === 0) return prev; if (prev === -1) return filteredCommands.length - 1; return prev <= 0 ? filteredCommands.length - 1 : prev - 1; }); } return; } if (e.key === "Enter") { const selected = slashHighlightIndex >= 0 ? filteredCommands[slashHighlightIndex] : void 0; if (selected) { e.preventDefault(); runCommand(selected); return; } } if (e.key === "Escape") { e.preventDefault(); setCommandQuery(null); return; } } if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); if (isProcessing) onStop?.(); else send(); } }; const send = () => { if (!onSubmitMessage) return; const trimmed = resolvedValue.trim(); if (!trimmed) return; onSubmitMessage(trimmed); if (!isControlled) setInternalValue(""); onChange?.(""); if (inputRef.current) inputRef.current.focus(); }; const BoundTextArea = renderSlot(textArea, CopilotChatInput.TextArea, { ref: inputRef, value: resolvedValue, onChange: handleChange, onKeyDown: handleKeyDown, onCompositionStart: () => { isComposingRef.current = true; }, onCompositionEnd: () => { isComposingRef.current = false; }, autoFocus, className: (0, tailwind_merge.twMerge)("cpk:w-full cpk:py-3", isExpanded ? "cpk:px-5" : "cpk:pr-5") }); const isProcessing = mode !== "transcribe" && isRunning; const canSend = resolvedValue.trim().length > 0 && !!onSubmitMessage; const canStop = !!onStop; const handleSendButtonClick = () => { if (isProcessing) { onStop?.(); return; } send(); }; const BoundAudioRecorder = renderSlot(audioRecorder, CopilotChatAudioRecorder, { ref: audioRecorderRef }); const BoundSendButton = renderSlot(sendButton, CopilotChatInput.SendButton, { onClick: handleSendButtonClick, disabled: isProcessing ? !canStop : !canSend, children: isProcessing && canStop ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Square, { className: "cpk:size-[18px] cpk:fill-current" }) : void 0 }); const BoundStartTranscribeButton = renderSlot(startTranscribeButton, CopilotChatInput.StartTranscribeButton, { onClick: onStartTranscribe }); const BoundCancelTranscribeButton = renderSlot(cancelTranscribeButton, CopilotChatInput.CancelTranscribeButton, { onClick: onCancelTranscribe }); const handleFinishTranscribe = (0, react.useCallback)(async () => { const recorder = audioRecorderRef.current; if (recorder && recorder.state === "recording") try { const audioBlob = await recorder.stop(); if (onFinishTranscribeWithAudio) await onFinishTranscribeWithAudio(audioBlob); } catch (error) { console.error("Failed to stop recording:", error); } onFinishTranscribe?.(); }, [onFinishTranscribe, onFinishTranscribeWithAudio]); const BoundFinishTranscribeButton = renderSlot(finishTranscribeButton, CopilotChatInput.FinishTranscribeButton, { onClick: handleFinishTranscribe }); const BoundAddMenuButton = renderSlot(addMenuButton, CopilotChatInput.AddMenuButton, { disabled: mode === "transcribe", onAddFile, toolsMenu }); const BoundDisclaimer = renderSlot(disclaimer, CopilotChatInput.Disclaimer, {}); const shouldShowDisclaimer = showDisclaimer ?? positioning === "absolute"; if (children) { const childProps = { textArea: BoundTextArea, audioRecorder: BoundAudioRecorder, sendButton: BoundSendButton, startTranscribeButton: BoundStartTranscribeButton, cancelTranscribeButton: BoundCancelTranscribeButton, finishTranscribeButton: BoundFinishTranscribeButton, addMenuButton: BoundAddMenuButton, disclaimer: BoundDisclaimer, onSubmitMessage, onStop, isRunning, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onAddFile, mode, toolsMenu, autoFocus, positioning, keyboardHeight, showDisclaimer: shouldShowDisclaimer, containerRef }; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { "data-copilotkit": true, style: { display: "contents" }, children: children(childProps) }); } const handleContainerClick = (e) => { const target = e.target; if (target.tagName !== "BUTTON" && !target.closest("button") && inputRef.current && mode === "input") inputRef.current.focus(); }; const isComposingRef = (0, react.useRef)(false); const ensureMeasurements = (0, react.useCallback)(() => { const textarea = inputRef.current; if (!textarea || isComposingRef.current) return; const previousValue = textarea.value; const previousHeight = textarea.style.height; textarea.style.height = "auto"; const computedStyle = window.getComputedStyle(textarea); const paddingLeft = parseFloat(computedStyle.paddingLeft) || 0; const paddingRight = parseFloat(computedStyle.paddingRight) || 0; const paddingTop = parseFloat(computedStyle.paddingTop) || 0; const paddingBottom = parseFloat(computedStyle.paddingBottom) || 0; textarea.value = ""; const singleLineHeight = textarea.scrollHeight; textarea.value = previousValue; const maxHeight = (singleLineHeight - paddingTop - paddingBottom) * 5 + paddingTop + paddingBottom; measurementsRef.current = { singleLineHeight, maxHeight, paddingLeft, paddingRight }; textarea.style.height = previousHeight; textarea.style.maxHeight = `${maxHeight}px`; }, []); const adjustTextareaHeight = (0, react.useCallback)(() => { const textarea = inputRef.current; if (!textarea) return 0; if (measurementsRef.current.singleLineHeight === 0) ensureMeasurements(); const { maxHeight } = measurementsRef.current; if (maxHeight) textarea.style.maxHeight = `${maxHeight}px`; textarea.style.height = "auto"; const scrollHeight = textarea.scrollHeight; if (maxHeight) textarea.style.height = `${Math.min(scrollHeight, maxHeight)}px`; else textarea.style.height = `${scrollHeight}px`; return scrollHeight; }, [ensureMeasurements]); const updateLayout = (0, react.useCallback)((nextLayout) => { setLayout((prev) => { if (prev === nextLayout) return prev; ignoreResizeRef.current = true; return nextLayout; }); }, []); const updateContainerCache = (0, react.useCallback)(() => { const grid = gridRef.current; const addContainer = addButtonContainerRef.current; const actionsContainer = actionsContainerRef.current; if (!grid || !addContainer || !actionsContainer) return null; const gridStyles = window.getComputedStyle(grid); const paddingLeft = parseFloat(gridStyles.paddingLeft) || 0; const paddingRight = parseFloat(gridStyles.paddingRight) || 0; const columnGap = parseFloat(gridStyles.columnGap) || 0; const gridAvailableWidth = grid.clientWidth - paddingLeft - paddingRight; if (gridAvailableWidth <= 0) return null; const addWidth = addContainer.getBoundingClientRect().width; const actionsWidth = actionsContainer.getBoundingClientRect().width; const compactWidth = Math.max(gridAvailableWidth - addWidth - actionsWidth - columnGap * 2, 0); if (compactWidth <= 0) return null; const result = { compactWidth }; containerCacheRef.current = result; return result; }, []); const evaluateLayout = (0, react.useCallback)(() => { if (mode !== "input") { updateLayout("compact"); return; } if (typeof window !== "undefined" && typeof window.matchMedia === "function") { if (window.matchMedia("(max-width: 767px)").matches) { ensureMeasurements(); adjustTextareaHeight(); updateLayout("expanded"); return; } } const textarea = inputRef.current; const grid = gridRef.current; const addContainer = addButtonContainerRef.current; const actionsContainer = actionsContainerRef.current; if (!textarea || !grid || !addContainer || !actionsContainer) return; if (measurementsRef.current.singleLineHeight === 0) ensureMeasurements(); const scrollHeight = adjustTextareaHeight(); const baseline = measurementsRef.current.singleLineHeight; const hasExplicitBreak = resolvedValue.includes("\n"); const renderedMultiline = baseline > 0 ? scrollHeight > baseline + 1 : false; let shouldExpand = hasExplicitBreak || renderedMultiline; if (!shouldExpand) { const cache = containerCacheRef.current ?? updateContainerCache(); if (cache && cache.compactWidth > 0) { const compactInnerWidth = Math.max(cache.compactWidth - (measurementsRef.current.paddingLeft || 0) - (measurementsRef.current.paddingRight || 0), 0); if (compactInnerWidth > 0) { const textareaStyles = window.getComputedStyle(textarea); let font = textareaStyles.font; if (!font) { const { fontStyle, fontVariant, fontWeight, fontSize, lineHeight, fontFamily } = textareaStyles; if (fontSize && fontFamily) font = `${fontStyle} ${fontVariant} ${fontWeight} ${fontSize}/${lineHeight} ${fontFamily}`; } if (font?.trim()) { const canvas = measurementCanvasRef.current ?? document.createElement("canvas"); if (!measurementCanvasRef.current) measurementCanvasRef.current = canvas; const context = canvas.getContext("2d"); if (context) { context.font = font; const lines = resolvedValue.length > 0 ? resolvedValue.split("\n") : [""]; let longestWidth = 0; for (const line of lines) { const metrics = context.measureText(line || " "); if (metrics.width > longestWidth) longestWidth = metrics.width; } if (longestWidth > compactInnerWidth) shouldExpand = true; } else if (process.env.NODE_ENV !== "production") console.warn("[CopilotChatInput] canvas.getContext('2d') returned null. Text-width-based expansion will be unavailable."); } else if (process.env.NODE_ENV !== "production") console.warn("[CopilotChatInput] Could not resolve textarea font for layout measurement. Text-width-based expansion will be skipped until the next evaluation."); } } } updateLayout(shouldExpand ? "expanded" : "compact"); }, [ adjustTextareaHeight, ensureMeasurements, mode, resolvedValue, updateContainerCache, updateLayout ]); (0, react.useLayoutEffect)(() => { evaluateLayout(); }, [evaluateLayout]); (0, react.useEffect)(() => { if (typeof ResizeObserver === "undefined") return; const textarea = inputRef.current; const grid = gridRef.current; const addContainer = addButtonContainerRef.current; const actionsContainer = actionsContainerRef.current; if (!textarea || !grid || !addContainer || !actionsContainer) return; const containerTargets = new Set([ grid, addContainer, actionsContainer ]); const scheduleEvaluation = (invalidateCache) => { if (ignoreResizeRef.current) { ignoreResizeRef.current = false; return; } if (invalidateCache) containerCacheRef.current = null; if (typeof window === "undefined") { evaluateLayout(); return; } if (resizeEvaluationRafRef.current !== null) cancelAnimationFrame(resizeEvaluationRafRef.current); resizeEvaluationRafRef.current = window.requestAnimationFrame(() => { resizeEvaluationRafRef.current = null; evaluateLayout(); }); }; const observer = new ResizeObserver((entries) => { let shouldInvalidate = false; for (const entry of entries) if (containerTargets.has(entry.target)) { shouldInvalidate = true; break; } scheduleEvaluation(shouldInvalidate); }); observer.observe(grid); observer.observe(addContainer); observer.observe(actionsContainer); observer.observe(textarea); return () => { observer.disconnect(); if (typeof window !== "undefined" && resizeEvaluationRafRef.current !== null) { cancelAnimationFrame(resizeEvaluationRafRef.current); resizeEvaluationRafRef.current = null; } }; }, [evaluateLayout]); const slashMenuVisible = commandQuery !== null && commandItems.length > 0; (0, react.useEffect)(() => { if (!slashMenuVisible || slashHighlightIndex < 0) return; (slashMenuRef.current?.querySelector(`[data-slash-index="${slashHighlightIndex}"]`))?.scrollIntoView({ block: "nearest" }); }, [slashMenuVisible, slashHighlightIndex]); const slashMenu = slashMenuVisible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { "data-testid": "copilot-slash-menu", role: "listbox", "aria-label": "Slash commands", ref: slashMenuRef, className: "cpk:absolute cpk:bottom-full cpk:left-0 cpk:right-0 cpk:z-30 cpk:mb-2 cpk:max-h-64 cpk:overflow-y-auto cpk:rounded-lg cpk:border cpk:border-border cpk:bg-white cpk:shadow-lg cpk:dark:border-[#3a3a3a] cpk:dark:bg-[#1f1f1f]", style: { maxHeight: `${SLASH_MENU_MAX_VISIBLE_ITEMS * SLASH_MENU_ITEM_HEIGHT_PX}px` }, children: filteredCommands.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "cpk:px-3 cpk:py-2 cpk:text-sm cpk:text-muted-foreground", children: "No commands found" }) : filteredCommands.map((item, index) => { const isActive = index === slashHighlightIndex; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button", role: "option", "aria-selected": isActive, "data-active": isActive ? "true" : void 0, "data-slash-index": index, className: (0, tailwind_merge.twMerge)("cpk:w-full cpk:px-3 cpk:py-2 cpk:text-left cpk:text-sm cpk:transition-colors", "cpk:hover:bg-muted cpk:dark:hover:bg-[#2f2f2f]", isActive ? "cpk:bg-muted cpk:dark:bg-[#2f2f2f]" : "cpk:bg-transparent"), onMouseEnter: () => setSlashHighlightIndex(index), onMouseDown: (event) => { event.preventDefault(); runCommand(item); }, children: item.label }, `${item.label}-${index}`); }) }) : null; const inputPill = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { "data-testid": "copilot-chat-input", className: (0, tailwind_merge.tw