5466 lines
234 KiB
JavaScript
5466 lines
234 KiB
JavaScript
"use client";
|
|
|
|
(function(global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@ag-ui/client'), require('react'), require('tailwind-merge'), require('lucide-react'), require('@copilotkitnext/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('@copilotkitnext/core'), require('zod'), require('@lit-labs/react'), require('@copilotkit/a2ui-renderer'), require('use-stick-to-bottom'), require('ts-deepmerge')) :
|
|
typeof define === 'function' && define.amd ? define(['exports', '@ag-ui/client', 'react', 'tailwind-merge', 'lucide-react', '@copilotkitnext/shared', 'react/jsx-runtime', '@radix-ui/react-slot', 'class-variance-authority', 'clsx', '@radix-ui/react-tooltip', '@radix-ui/react-dropdown-menu', 'streamdown', '@copilotkitnext/core', 'zod', '@lit-labs/react', '@copilotkit/a2ui-renderer', 'use-stick-to-bottom', 'ts-deepmerge'], factory) :
|
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.CopilotKitNextReact = {}), global.AgUIClient,global.React,global.tailwindMerge,global.lucideReact,global.CopilotKitNextShared,global.ReactJsxRuntime,global.RadixReactSlot,global.classVarianceAuthority,global.clsx,global.RadixReactTooltip,global.RadixReactDropdownMenu,global.streamdown,global.CopilotKitNextCore,global.Zod,global.LitLabsReact,global.CopilotKitA2UIRenderer,global.useStickToBottom,global.tsDeepmerge));
|
|
})(this, function(exports, _ag_ui_client, react, tailwind_merge, lucide_react, _copilotkitnext_shared, react_jsx_runtime, _radix_ui_react_slot, class_variance_authority, clsx, _radix_ui_react_tooltip, _radix_ui_react_dropdown_menu, streamdown, _copilotkitnext_core, zod, _lit_labs_react, _copilotkit_a2ui_renderer, use_stick_to_bottom, ts_deepmerge) {
|
|
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/providers/CopilotChatConfigurationProvider.tsx
|
|
const CopilotChatDefaultLabels = {
|
|
chatInputPlaceholder: "Type a message...",
|
|
chatInputToolbarStartTranscribeButtonLabel: "Transcribe",
|
|
chatInputToolbarCancelTranscribeButtonLabel: "Cancel",
|
|
chatInputToolbarFinishTranscribeButtonLabel: "Finish",
|
|
chatInputToolbarAddButtonLabel: "Add photos or files",
|
|
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, isModalDefaultOpen }) => {
|
|
var _ref, _parentConfig$isModal, _parentConfig$setModa;
|
|
const parentConfig = (0, react.useContext)(CopilotChatConfiguration);
|
|
const mergedLabels = (0, react.useMemo)(() => {
|
|
var _parentConfig$labels;
|
|
return {
|
|
...CopilotChatDefaultLabels,
|
|
...(_parentConfig$labels = parentConfig === null || parentConfig === void 0 ? void 0 : parentConfig.labels) !== null && _parentConfig$labels !== void 0 ? _parentConfig$labels : {},
|
|
...labels !== null && labels !== void 0 ? labels : {}
|
|
};
|
|
}, [labels, parentConfig === null || parentConfig === void 0 ? void 0 : parentConfig.labels]);
|
|
const resolvedAgentId = (_ref = agentId !== null && agentId !== void 0 ? agentId : parentConfig === null || parentConfig === void 0 ? void 0 : parentConfig.agentId) !== null && _ref !== void 0 ? _ref : _copilotkitnext_shared.DEFAULT_AGENT_ID;
|
|
const resolvedThreadId = (0, react.useMemo)(() => {
|
|
if (threadId) return threadId;
|
|
if (parentConfig === null || parentConfig === void 0 ? void 0 : parentConfig.threadId) return parentConfig.threadId;
|
|
return (0, _copilotkitnext_shared.randomUUID)();
|
|
}, [threadId, parentConfig === null || parentConfig === void 0 ? void 0 : parentConfig.threadId]);
|
|
const [internalModalOpen, setInternalModalOpen] = (0, react.useState)(isModalDefaultOpen !== null && isModalDefaultOpen !== void 0 ? isModalDefaultOpen : true);
|
|
const resolvedIsModalOpen = (_parentConfig$isModal = parentConfig === null || parentConfig === void 0 ? void 0 : parentConfig.isModalOpen) !== null && _parentConfig$isModal !== void 0 ? _parentConfig$isModal : internalModalOpen;
|
|
const resolvedSetModalOpen = (_parentConfig$setModa = parentConfig === null || parentConfig === void 0 ? void 0 : parentConfig.setModalOpen) !== null && _parentConfig$setModa !== void 0 ? _parentConfig$setModa : setInternalModalOpen;
|
|
const configurationValue = (0, react.useMemo)(() => ({
|
|
labels: mergedLabels,
|
|
agentId: resolvedAgentId,
|
|
threadId: resolvedThreadId,
|
|
isModalOpen: resolvedIsModalOpen,
|
|
setModalOpen: resolvedSetModalOpen
|
|
}), [
|
|
mergedLabels,
|
|
resolvedAgentId,
|
|
resolvedThreadId,
|
|
resolvedIsModalOpen,
|
|
resolvedSetModalOpen
|
|
]);
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfiguration.Provider, {
|
|
value: configurationValue,
|
|
children
|
|
});
|
|
};
|
|
const useCopilotChatConfiguration = () => {
|
|
return (0, react.useContext)(CopilotChatConfiguration);
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/lib/utils.ts
|
|
const twMerge$8 = (0, tailwind_merge.extendTailwindMerge)({ prefix: "cpk" });
|
|
function cn(...inputs) {
|
|
return twMerge$8((0, clsx.clsx)(inputs));
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/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"
|
|
}
|
|
});
|
|
function Button({ className, variant, size, asChild = false, ...props }) {
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(asChild ? _radix_ui_react_slot.Slot : "button", {
|
|
"data-slot": "button",
|
|
className: cn(buttonVariants({
|
|
variant,
|
|
size,
|
|
className
|
|
})),
|
|
...props
|
|
});
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/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/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/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 (_unused) {}
|
|
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++) {
|
|
var _dataArray$i;
|
|
const sample = ((_dataArray$i = dataArray[i]) !== null && _dataArray$i !== void 0 ? _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++) {
|
|
var _history$i;
|
|
const amplitude = (_history$i = history[i]) !== null && _history$i !== void 0 ? _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/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;
|
|
}
|
|
/**
|
|
* 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/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 = true, positioning = "static", keyboardHeight = 0, containerRef, showDisclaimer, textArea, sendButton, startTranscribeButton, cancelTranscribeButton, finishTranscribeButton, addMenuButton, audioRecorder, disclaimer, children, className, ...props }) {
|
|
var _config$labels;
|
|
const isControlled = value !== void 0;
|
|
const [internalValue, setInternalValue] = (0, react.useState)(() => value !== null && value !== void 0 ? value : "");
|
|
(0, react.useEffect)(() => {
|
|
if (!isControlled && value !== void 0) setInternalValue(value);
|
|
}, [isControlled, value]);
|
|
const resolvedValue = isControlled ? value !== null && value !== void 0 ? 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 = config === null || config === void 0 ? void 0 : config.labels) !== null && _config$labels !== void 0 ? _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 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 === null || config === void 0 ? void 0 : config.isModalOpen;
|
|
return;
|
|
}
|
|
if ((config === null || config === void 0 ? void 0 : config.isModalOpen) && !previousModalStateRef.current) {
|
|
var _inputRef$current;
|
|
(_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 || _inputRef$current.focus();
|
|
}
|
|
previousModalStateRef.current = config === null || config === void 0 ? void 0 : config.isModalOpen;
|
|
}, [config === null || config === void 0 ? void 0 : 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("/")) {
|
|
var _value$split$;
|
|
const query = ((_value$split$ = value.split(/\r?\n/, 1)[0]) !== null && _value$split$ !== void 0 ? _value$split$ : "").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 === null || onChange === void 0 || onChange(nextValue);
|
|
updateSlashState(nextValue);
|
|
};
|
|
const clearInputValue = (0, react.useCallback)(() => {
|
|
if (!isControlled) setInternalValue("");
|
|
if (onChange) onChange("");
|
|
}, [isControlled, onChange]);
|
|
const runCommand = (0, react.useCallback)((item) => {
|
|
var _item$action;
|
|
clearInputValue();
|
|
(_item$action = item.action) === null || _item$action === void 0 || _item$action.call(item);
|
|
setCommandQuery(null);
|
|
setSlashHighlightIndex(0);
|
|
requestAnimationFrame(() => {
|
|
var _inputRef$current2;
|
|
(_inputRef$current2 = inputRef.current) === null || _inputRef$current2 === void 0 || _inputRef$current2.focus();
|
|
});
|
|
}, [clearInputValue]);
|
|
const handleKeyDown = (e) => {
|
|
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 === null || onStop === void 0 || onStop();
|
|
else send();
|
|
}
|
|
};
|
|
const send = () => {
|
|
if (!onSubmitMessage) return;
|
|
const trimmed = resolvedValue.trim();
|
|
if (!trimmed) return;
|
|
onSubmitMessage(trimmed);
|
|
if (!isControlled) {
|
|
setInternalValue("");
|
|
onChange === null || onChange === void 0 || onChange("");
|
|
}
|
|
if (inputRef.current) inputRef.current.focus();
|
|
};
|
|
const BoundTextArea = renderSlot(textArea, CopilotChatInput.TextArea, {
|
|
ref: inputRef,
|
|
value: resolvedValue,
|
|
onChange: handleChange,
|
|
onKeyDown: handleKeyDown,
|
|
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 === null || onStop === void 0 || 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 === null || onFinishTranscribe === void 0 || 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 !== null && showDisclaimer !== void 0 ? 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
|
|
};
|
|
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 ensureMeasurements = (0, react.useCallback)(() => {
|
|
const textarea = inputRef.current;
|
|
if (!textarea) 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 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 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) {
|
|
var _measurementCanvasRef;
|
|
const addWidth = addContainer.getBoundingClientRect().width;
|
|
const actionsWidth = actionsContainer.getBoundingClientRect().width;
|
|
const compactWidth = Math.max(gridAvailableWidth - addWidth - actionsWidth - columnGap * 2, 0);
|
|
const canvas = (_measurementCanvasRef = measurementCanvasRef.current) !== null && _measurementCanvasRef !== void 0 ? _measurementCanvasRef : document.createElement("canvas");
|
|
if (!measurementCanvasRef.current) measurementCanvasRef.current = canvas;
|
|
const context = canvas.getContext("2d");
|
|
if (context) {
|
|
const textareaStyles = window.getComputedStyle(textarea);
|
|
context.font = textareaStyles.font || `${textareaStyles.fontStyle} ${textareaStyles.fontVariant} ${textareaStyles.fontWeight} ${textareaStyles.fontSize}/${textareaStyles.lineHeight} ${textareaStyles.fontFamily}`;
|
|
const compactInnerWidth = Math.max(compactWidth - (measurementsRef.current.paddingLeft || 0) - (measurementsRef.current.paddingRight || 0), 0);
|
|
if (compactInnerWidth > 0) {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
updateLayout(shouldExpand ? "expanded" : "compact");
|
|
}, [
|
|
adjustTextareaHeight,
|
|
ensureMeasurements,
|
|
mode,
|
|
resolvedValue,
|
|
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 scheduleEvaluation = () => {
|
|
if (ignoreResizeRef.current) {
|
|
ignoreResizeRef.current = false;
|
|
return;
|
|
}
|
|
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(() => {
|
|
scheduleEvaluation();
|
|
});
|
|
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)(() => {
|
|
var _slashMenuRef$current;
|
|
if (!slashMenuVisible || slashHighlightIndex < 0) return;
|
|
const active = (_slashMenuRef$current = slashMenuRef.current) === null || _slashMenuRef$current === void 0 ? void 0 : _slashMenuRef$current.querySelector(`[data-slash-index="${slashHighlightIndex}"]`);
|
|
active === null || active === void 0 || active.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.twMerge)("copilotKitInput", "cpk:flex cpk:w-full cpk:flex-col cpk:items-center cpk:justify-center", "cpk:cursor-text", "cpk:overflow-visible cpk:bg-clip-padding cpk:contain-inline-size", "cpk:bg-white cpk:dark:bg-[#303030]", "cpk:shadow-[0_4px_4px_0_#0000000a,0_0_1px_0_#0000009e] cpk:rounded-[28px]"),
|
|
onClick: handleContainerClick,
|
|
"data-layout": isExpanded ? "expanded" : "compact",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
ref: gridRef,
|
|
className: (0, tailwind_merge.twMerge)("cpk:grid cpk:w-full cpk:gap-x-3 cpk:gap-y-3 cpk:px-3 cpk:py-2", isExpanded ? "cpk:grid-cols-[auto_minmax(0,1fr)_auto] cpk:grid-rows-[auto_auto]" : "cpk:grid-cols-[auto_minmax(0,1fr)_auto] cpk:items-center"),
|
|
"data-layout": isExpanded ? "expanded" : "compact",
|
|
children: [
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
ref: addButtonContainerRef,
|
|
className: (0, tailwind_merge.twMerge)("cpk:flex cpk:items-center", isExpanded ? "cpk:row-start-2" : "cpk:row-start-1", "cpk:col-start-1"),
|
|
children: BoundAddMenuButton
|
|
}),
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: (0, tailwind_merge.twMerge)("cpk:relative cpk:flex cpk:min-w-0 cpk:flex-col cpk:min-h-[50px] cpk:justify-center", isExpanded ? "cpk:col-span-3 cpk:row-start-1" : "cpk:col-start-2 cpk:row-start-1"),
|
|
children: mode === "transcribe" ? BoundAudioRecorder : mode === "processing" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:flex cpk:w-full cpk:items-center cpk:justify-center cpk:py-3 cpk:px-5",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "cpk:size-[26px] cpk:animate-spin cpk:text-muted-foreground" })
|
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [BoundTextArea, slashMenu] })
|
|
}),
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
ref: actionsContainerRef,
|
|
className: (0, tailwind_merge.twMerge)("cpk:flex cpk:items-center cpk:justify-end cpk:gap-2", isExpanded ? "cpk:col-start-3 cpk:row-start-2" : "cpk:col-start-3 cpk:row-start-1"),
|
|
children: mode === "transcribe" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [onCancelTranscribe && BoundCancelTranscribeButton, onFinishTranscribe && BoundFinishTranscribeButton] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [onStartTranscribe && BoundStartTranscribeButton, BoundSendButton] })
|
|
})
|
|
]
|
|
})
|
|
});
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
"data-copilotkit": true,
|
|
ref: containerRef,
|
|
className: cn(positioning === "absolute" && "cpk:absolute cpk:bottom-0 cpk:left-0 cpk:right-0 cpk:z-20 cpk:pointer-events-none", className),
|
|
style: {
|
|
transform: keyboardHeight > 0 ? `translateY(-${keyboardHeight}px)` : void 0,
|
|
transition: "transform 0.2s ease-out"
|
|
},
|
|
...props,
|
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:max-w-3xl cpk:mx-auto cpk:py-0 cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-4 cpk:pointer-events-auto",
|
|
children: inputPill
|
|
}), shouldShowDisclaimer && BoundDisclaimer]
|
|
});
|
|
}
|
|
(function(_CopilotChatInput) {
|
|
_CopilotChatInput.SendButton = ({ className, children, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:mr-[10px]",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
type: "button",
|
|
"data-testid": "copilot-send-button",
|
|
variant: "chatInputToolbarPrimary",
|
|
size: "chatInputToolbarIcon",
|
|
className,
|
|
...props,
|
|
children: children !== null && children !== void 0 ? children : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ArrowUp, { className: "cpk:size-[18px]" })
|
|
})
|
|
});
|
|
const ToolbarButton = _CopilotChatInput.ToolbarButton = ({ icon, labelKey, defaultClassName, className, ...props }) => {
|
|
var _config$labels2;
|
|
const config = useCopilotChatConfiguration();
|
|
const labels = (_config$labels2 = config === null || config === void 0 ? void 0 : config.labels) !== null && _config$labels2 !== void 0 ? _config$labels2 : CopilotChatDefaultLabels;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Tooltip, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTrigger, {
|
|
asChild: true,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
type: "button",
|
|
variant: "chatInputToolbarSecondary",
|
|
size: "chatInputToolbarIcon",
|
|
className: (0, tailwind_merge.twMerge)(defaultClassName, className),
|
|
...props,
|
|
children: icon
|
|
})
|
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipContent, {
|
|
side: "bottom",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: labels[labelKey] })
|
|
})] });
|
|
};
|
|
_CopilotChatInput.StartTranscribeButton = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButton, {
|
|
"data-testid": "copilot-start-transcribe-button",
|
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Mic, { className: "cpk:size-[18px]" }),
|
|
labelKey: "chatInputToolbarStartTranscribeButtonLabel",
|
|
defaultClassName: "cpk:mr-2",
|
|
...props
|
|
});
|
|
_CopilotChatInput.CancelTranscribeButton = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButton, {
|
|
"data-testid": "copilot-cancel-transcribe-button",
|
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "cpk:size-[18px]" }),
|
|
labelKey: "chatInputToolbarCancelTranscribeButtonLabel",
|
|
defaultClassName: "cpk:mr-2",
|
|
...props
|
|
});
|
|
_CopilotChatInput.FinishTranscribeButton = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButton, {
|
|
"data-testid": "copilot-finish-transcribe-button",
|
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, { className: "cpk:size-[18px]" }),
|
|
labelKey: "chatInputToolbarFinishTranscribeButtonLabel",
|
|
defaultClassName: "cpk:mr-[10px]",
|
|
...props
|
|
});
|
|
_CopilotChatInput.AddMenuButton = ({ className, toolsMenu, onAddFile, disabled, ...props }) => {
|
|
var _config$labels3;
|
|
const config = useCopilotChatConfiguration();
|
|
const labels = (_config$labels3 = config === null || config === void 0 ? void 0 : config.labels) !== null && _config$labels3 !== void 0 ? _config$labels3 : CopilotChatDefaultLabels;
|
|
const menuItems = (0, react.useMemo)(() => {
|
|
const items = [];
|
|
if (onAddFile) items.push({
|
|
label: labels.chatInputToolbarAddButtonLabel,
|
|
action: onAddFile
|
|
});
|
|
if (toolsMenu && toolsMenu.length > 0) {
|
|
if (items.length > 0) items.push("-");
|
|
for (const item of toolsMenu) if (item === "-") {
|
|
if (items.length === 0 || items[items.length - 1] === "-") continue;
|
|
items.push(item);
|
|
} else items.push(item);
|
|
while (items.length > 0 && items[items.length - 1] === "-") items.pop();
|
|
}
|
|
return items;
|
|
}, [
|
|
onAddFile,
|
|
toolsMenu,
|
|
labels.chatInputToolbarAddButtonLabel
|
|
]);
|
|
const renderMenuItems = (0, react.useCallback)((items) => items.map((item, index) => {
|
|
if (item === "-") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownMenuSeparator, {}, `separator-${index}`);
|
|
if (item.items && item.items.length > 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DropdownMenuSub, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownMenuSubTrigger, { children: item.label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownMenuSubContent, { children: renderMenuItems(item.items) })] }, `group-${index}`);
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownMenuItem, {
|
|
onClick: item.action,
|
|
children: item.label
|
|
}, `item-${index}`);
|
|
}), []);
|
|
const hasMenuItems = menuItems.length > 0;
|
|
const isDisabled = disabled || !hasMenuItems;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Tooltip, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTrigger, {
|
|
asChild: true,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownMenuTrigger, {
|
|
asChild: true,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
type: "button",
|
|
"data-testid": "copilot-add-menu-button",
|
|
variant: "chatInputToolbarSecondary",
|
|
size: "chatInputToolbarIcon",
|
|
className: (0, tailwind_merge.twMerge)("cpk:ml-1", className),
|
|
disabled: isDisabled,
|
|
...props,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, { className: "cpk:size-[20px]" })
|
|
})
|
|
})
|
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipContent, {
|
|
side: "bottom",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
className: "cpk:flex cpk:items-center cpk:gap-1 cpk:text-xs cpk:font-medium",
|
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Add files and more" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
className: "cpk:rounded cpk:bg-[#4a4a4a] cpk:px-1 cpk:py-[1px] cpk:font-mono cpk:text-[11px] cpk:text-white cpk:dark:bg-[#e0e0e0] cpk:dark:text-black",
|
|
children: "/"
|
|
})]
|
|
})
|
|
})] }), hasMenuItems && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownMenuContent, {
|
|
side: "top",
|
|
align: "start",
|
|
children: renderMenuItems(menuItems)
|
|
})] });
|
|
};
|
|
_CopilotChatInput.TextArea = (0, react.forwardRef)(function TextArea({ style, className, autoFocus, placeholder, ...props }, ref) {
|
|
var _config$labels4;
|
|
const internalTextareaRef = (0, react.useRef)(null);
|
|
const config = useCopilotChatConfiguration();
|
|
const labels = (_config$labels4 = config === null || config === void 0 ? void 0 : config.labels) !== null && _config$labels4 !== void 0 ? _config$labels4 : CopilotChatDefaultLabels;
|
|
(0, react.useImperativeHandle)(ref, () => internalTextareaRef.current);
|
|
(0, react.useEffect)(() => {
|
|
const textarea = internalTextareaRef.current;
|
|
if (!textarea) return;
|
|
const handleFocus = () => {
|
|
setTimeout(() => {
|
|
textarea.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "nearest"
|
|
});
|
|
}, 300);
|
|
};
|
|
textarea.addEventListener("focus", handleFocus);
|
|
return () => textarea.removeEventListener("focus", handleFocus);
|
|
}, []);
|
|
(0, react.useEffect)(() => {
|
|
if (autoFocus) {
|
|
var _internalTextareaRef$;
|
|
(_internalTextareaRef$ = internalTextareaRef.current) === null || _internalTextareaRef$ === void 0 || _internalTextareaRef$.focus();
|
|
}
|
|
}, [autoFocus]);
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
ref: internalTextareaRef,
|
|
"data-testid": "copilot-chat-textarea",
|
|
placeholder: placeholder !== null && placeholder !== void 0 ? placeholder : labels.chatInputPlaceholder,
|
|
className: (0, tailwind_merge.twMerge)("cpk:bg-transparent cpk:outline-none cpk:antialiased cpk:font-regular cpk:leading-relaxed cpk:text-[16px] cpk:placeholder:text-[#00000077] cpk:dark:placeholder:text-[#fffc]", className),
|
|
style: {
|
|
overflow: "auto",
|
|
resize: "none",
|
|
...style
|
|
},
|
|
rows: 1,
|
|
...props
|
|
});
|
|
});
|
|
_CopilotChatInput.AudioRecorder = CopilotChatAudioRecorder;
|
|
_CopilotChatInput.Disclaimer = ({ className, ...props }) => {
|
|
var _config$labels5;
|
|
const config = useCopilotChatConfiguration();
|
|
const labels = (_config$labels5 = config === null || config === void 0 ? void 0 : config.labels) !== null && _config$labels5 !== void 0 ? _config$labels5 : CopilotChatDefaultLabels;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: cn("cpk:text-center cpk:text-xs cpk:text-muted-foreground cpk:py-3 cpk:px-4 cpk:max-w-3xl cpk:mx-auto", className),
|
|
...props,
|
|
children: labels.chatDisclaimerText
|
|
});
|
|
};
|
|
})(CopilotChatInput || (CopilotChatInput = {}));
|
|
CopilotChatInput.TextArea.displayName = "CopilotChatInput.TextArea";
|
|
CopilotChatInput.SendButton.displayName = "CopilotChatInput.SendButton";
|
|
CopilotChatInput.ToolbarButton.displayName = "CopilotChatInput.ToolbarButton";
|
|
CopilotChatInput.StartTranscribeButton.displayName = "CopilotChatInput.StartTranscribeButton";
|
|
CopilotChatInput.CancelTranscribeButton.displayName = "CopilotChatInput.CancelTranscribeButton";
|
|
CopilotChatInput.FinishTranscribeButton.displayName = "CopilotChatInput.FinishTranscribeButton";
|
|
CopilotChatInput.AddMenuButton.displayName = "CopilotChatInput.AddMenuButton";
|
|
CopilotChatInput.Disclaimer.displayName = "CopilotChatInput.Disclaimer";
|
|
var CopilotChatInput_default = CopilotChatInput;
|
|
|
|
//#endregion
|
|
//#region src/hooks/useKatexStyles.ts
|
|
let injected = false;
|
|
/**
|
|
* Dynamically injects KaTeX CSS at runtime to avoid the Next.js
|
|
* "Global CSS cannot be imported from within node_modules" build error.
|
|
*
|
|
* Uses a singleton flag so the stylesheet is only injected once.
|
|
*/
|
|
function useKatexStyles() {
|
|
(0, react.useEffect)(() => {
|
|
if (injected || typeof document === "undefined") return;
|
|
injected = true;
|
|
import("katex/dist/katex.min.css").catch(() => {});
|
|
}, []);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/components/CopilotKitInspector.tsx
|
|
const CopilotKitInspector = ({ core, ...rest }) => {
|
|
const [InspectorComponent, setInspectorComponent] = react.useState(null);
|
|
react.useEffect(() => {
|
|
let mounted = true;
|
|
import("@copilotkitnext/web-inspector").then((mod) => {
|
|
var _mod$defineWebInspect;
|
|
(_mod$defineWebInspect = mod.defineWebInspector) === null || _mod$defineWebInspect === void 0 || _mod$defineWebInspect.call(mod);
|
|
const Component = (0, _lit_labs_react.createComponent)({
|
|
tagName: mod.WEB_INSPECTOR_TAG,
|
|
elementClass: mod.WebInspectorElement,
|
|
react
|
|
});
|
|
if (mounted) setInspectorComponent(() => Component);
|
|
});
|
|
return () => {
|
|
mounted = false;
|
|
};
|
|
}, []);
|
|
if (!InspectorComponent) return null;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InspectorComponent, {
|
|
...rest,
|
|
core: core !== null && core !== void 0 ? core : null
|
|
});
|
|
};
|
|
CopilotKitInspector.displayName = "CopilotKitInspector";
|
|
|
|
//#endregion
|
|
//#region \0@oxc-project+runtime@0.112.0/helpers/typeof.js
|
|
function _typeof(o) {
|
|
"@babel/helpers - typeof";
|
|
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
|
|
return typeof o;
|
|
} : function(o) {
|
|
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
|
|
}, _typeof(o);
|
|
}
|
|
|
|
//#endregion
|
|
//#region \0@oxc-project+runtime@0.112.0/helpers/toPrimitive.js
|
|
function toPrimitive(t, r) {
|
|
if ("object" != _typeof(t) || !t) return t;
|
|
var e = t[Symbol.toPrimitive];
|
|
if (void 0 !== e) {
|
|
var i = e.call(t, r || "default");
|
|
if ("object" != _typeof(i)) return i;
|
|
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
}
|
|
return ("string" === r ? String : Number)(t);
|
|
}
|
|
|
|
//#endregion
|
|
//#region \0@oxc-project+runtime@0.112.0/helpers/toPropertyKey.js
|
|
function toPropertyKey(t) {
|
|
var i = toPrimitive(t, "string");
|
|
return "symbol" == _typeof(i) ? i : i + "";
|
|
}
|
|
|
|
//#endregion
|
|
//#region \0@oxc-project+runtime@0.112.0/helpers/defineProperty.js
|
|
function _defineProperty(e, r, t) {
|
|
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
value: t,
|
|
enumerable: !0,
|
|
configurable: !0,
|
|
writable: !0
|
|
}) : e[r] = t, e;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/components/MCPAppsActivityRenderer.tsx
|
|
const PROTOCOL_VERSION = "2025-06-18";
|
|
function buildSandboxHTML(extraCspDomains) {
|
|
const baseScriptSrc = "'self' 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval' blob: data: http://localhost:* https://localhost:*";
|
|
const baseFrameSrc = "* blob: data: http://localhost:* https://localhost:*";
|
|
const extra = (extraCspDomains === null || extraCspDomains === void 0 ? void 0 : extraCspDomains.length) ? " " + extraCspDomains.join(" ") : "";
|
|
return `<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src * data: blob: 'unsafe-inline'; media-src * blob: data:; font-src * blob: data:; script-src ${baseScriptSrc + extra}; style-src * blob: data: 'unsafe-inline'; connect-src *; frame-src ${baseFrameSrc + extra}; base-uri 'self';" />
|
|
<style>html,body{margin:0;padding:0;height:100%;width:100%;overflow:hidden}*{box-sizing:border-box}iframe{background-color:transparent;border:none;padding:0;overflow:hidden;width:100%;height:100%}</style>
|
|
</head>
|
|
<body>
|
|
<script>
|
|
if(window.self===window.top){throw new Error("This file must be used in an iframe.")}
|
|
const inner=document.createElement("iframe");
|
|
inner.style="width:100%;height:100%;border:none;";
|
|
inner.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms");
|
|
document.body.appendChild(inner);
|
|
window.addEventListener("message",async(event)=>{
|
|
if(event.source===window.parent){
|
|
if(event.data&&event.data.method==="ui/notifications/sandbox-resource-ready"){
|
|
const{html,sandbox}=event.data.params;
|
|
if(typeof sandbox==="string")inner.setAttribute("sandbox",sandbox);
|
|
if(typeof html==="string")inner.srcdoc=html;
|
|
}else if(inner&&inner.contentWindow){
|
|
inner.contentWindow.postMessage(event.data,"*");
|
|
}
|
|
}else if(event.source===inner.contentWindow){
|
|
window.parent.postMessage(event.data,"*");
|
|
}
|
|
});
|
|
window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-ready",params:{}},"*");
|
|
<\/script>
|
|
</body>
|
|
</html>`;
|
|
}
|
|
/**
|
|
* Queue for serializing MCP app requests to an agent.
|
|
* Ensures requests wait for the agent to stop running and are processed one at a time.
|
|
*/
|
|
var MCPAppsRequestQueue = class {
|
|
constructor() {
|
|
_defineProperty(this, "queues", /* @__PURE__ */ new Map());
|
|
_defineProperty(this, "processing", /* @__PURE__ */ new Map());
|
|
}
|
|
/**
|
|
* Add a request to the queue for a specific agent thread.
|
|
* Returns a promise that resolves when the request completes.
|
|
*/
|
|
async enqueue(agent, request) {
|
|
const threadId = agent.threadId || "default";
|
|
return new Promise((resolve, reject) => {
|
|
let queue = this.queues.get(threadId);
|
|
if (!queue) {
|
|
queue = [];
|
|
this.queues.set(threadId, queue);
|
|
}
|
|
queue.push({
|
|
execute: request,
|
|
resolve,
|
|
reject
|
|
});
|
|
this.processQueue(threadId, agent);
|
|
});
|
|
}
|
|
async processQueue(threadId, agent) {
|
|
if (this.processing.get(threadId)) return;
|
|
this.processing.set(threadId, true);
|
|
try {
|
|
const queue = this.queues.get(threadId);
|
|
if (!queue) return;
|
|
while (queue.length > 0) {
|
|
const item = queue[0];
|
|
try {
|
|
await this.waitForAgentIdle(agent);
|
|
const result = await item.execute();
|
|
item.resolve(result);
|
|
} catch (error) {
|
|
item.reject(error instanceof Error ? error : new Error(String(error)));
|
|
}
|
|
queue.shift();
|
|
}
|
|
} finally {
|
|
this.processing.set(threadId, false);
|
|
}
|
|
}
|
|
waitForAgentIdle(agent) {
|
|
return new Promise((resolve) => {
|
|
if (!agent.isRunning) {
|
|
resolve();
|
|
return;
|
|
}
|
|
let done = false;
|
|
const finish = () => {
|
|
if (done) return;
|
|
done = true;
|
|
clearInterval(checkInterval);
|
|
sub.unsubscribe();
|
|
resolve();
|
|
};
|
|
const sub = agent.subscribe({
|
|
onRunFinalized: finish,
|
|
onRunFailed: finish
|
|
});
|
|
const checkInterval = setInterval(() => {
|
|
if (!agent.isRunning) finish();
|
|
}, 500);
|
|
});
|
|
}
|
|
};
|
|
const mcpAppsRequestQueue = new MCPAppsRequestQueue();
|
|
/**
|
|
* Activity type for MCP Apps events - must match the middleware's MCPAppsActivityType
|
|
*/
|
|
const MCPAppsActivityType = "mcp-apps";
|
|
const MCPAppsActivityContentSchema = zod.z.object({
|
|
result: zod.z.object({
|
|
content: zod.z.array(zod.z.any()).optional(),
|
|
structuredContent: zod.z.any().optional(),
|
|
isError: zod.z.boolean().optional()
|
|
}),
|
|
resourceUri: zod.z.string(),
|
|
serverHash: zod.z.string(),
|
|
serverId: zod.z.string().optional(),
|
|
toolInput: zod.z.record(zod.z.unknown()).optional()
|
|
});
|
|
function isRequest(msg) {
|
|
return "id" in msg && "method" in msg;
|
|
}
|
|
function isNotification(msg) {
|
|
return !("id" in msg) && "method" in msg;
|
|
}
|
|
/**
|
|
* MCP Apps Extension Activity Renderer
|
|
*
|
|
* Renders MCP Apps UI in a sandboxed iframe with full protocol support.
|
|
* Fetches resource content on-demand via proxied MCP requests.
|
|
*/
|
|
const MCPAppsActivityRenderer = function MCPAppsActivityRenderer({ content, agent }) {
|
|
var _fetchedResource$_met2;
|
|
const containerRef = (0, react.useRef)(null);
|
|
const iframeRef = (0, react.useRef)(null);
|
|
const [iframeReady, setIframeReady] = (0, react.useState)(false);
|
|
const [error, setError] = (0, react.useState)(null);
|
|
const [isLoading, setIsLoading] = (0, react.useState)(true);
|
|
const [iframeSize, setIframeSize] = (0, react.useState)({});
|
|
const [fetchedResource, setFetchedResource] = (0, react.useState)(null);
|
|
const contentRef = (0, react.useRef)(content);
|
|
contentRef.current = content;
|
|
const agentRef = (0, react.useRef)(agent);
|
|
agentRef.current = agent;
|
|
const fetchStateRef = (0, react.useRef)({
|
|
inProgress: false,
|
|
promise: null,
|
|
resourceUri: null
|
|
});
|
|
const sendToIframe = (0, react.useCallback)((msg) => {
|
|
var _iframeRef$current;
|
|
if ((_iframeRef$current = iframeRef.current) === null || _iframeRef$current === void 0 ? void 0 : _iframeRef$current.contentWindow) {
|
|
console.log("[MCPAppsRenderer] Sending to iframe:", msg);
|
|
iframeRef.current.contentWindow.postMessage(msg, "*");
|
|
}
|
|
}, []);
|
|
const sendResponse = (0, react.useCallback)((id, result) => {
|
|
sendToIframe({
|
|
jsonrpc: "2.0",
|
|
id,
|
|
result
|
|
});
|
|
}, [sendToIframe]);
|
|
const sendErrorResponse = (0, react.useCallback)((id, code, message) => {
|
|
sendToIframe({
|
|
jsonrpc: "2.0",
|
|
id,
|
|
error: {
|
|
code,
|
|
message
|
|
}
|
|
});
|
|
}, [sendToIframe]);
|
|
const sendNotification = (0, react.useCallback)((method, params) => {
|
|
sendToIframe({
|
|
jsonrpc: "2.0",
|
|
method,
|
|
params: params || {}
|
|
});
|
|
}, [sendToIframe]);
|
|
(0, react.useEffect)(() => {
|
|
const { resourceUri, serverHash, serverId } = content;
|
|
if (fetchStateRef.current.inProgress && fetchStateRef.current.resourceUri === resourceUri) {
|
|
var _fetchStateRef$curren;
|
|
(_fetchStateRef$curren = fetchStateRef.current.promise) === null || _fetchStateRef$curren === void 0 || _fetchStateRef$curren.then((resource) => {
|
|
if (resource) {
|
|
setFetchedResource(resource);
|
|
setIsLoading(false);
|
|
}
|
|
}).catch((err) => {
|
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
setIsLoading(false);
|
|
});
|
|
return;
|
|
}
|
|
if (!agent) {
|
|
setError(/* @__PURE__ */ new Error("No agent available to fetch resource"));
|
|
setIsLoading(false);
|
|
return;
|
|
}
|
|
fetchStateRef.current.inProgress = true;
|
|
fetchStateRef.current.resourceUri = resourceUri;
|
|
const fetchPromise = (async () => {
|
|
try {
|
|
var _resultData$contents;
|
|
const resultData = (await mcpAppsRequestQueue.enqueue(agent, () => agent.runAgent({ forwardedProps: { __proxiedMCPRequest: {
|
|
serverHash,
|
|
serverId,
|
|
method: "resources/read",
|
|
params: { uri: resourceUri }
|
|
} } }))).result;
|
|
const resource = resultData === null || resultData === void 0 || (_resultData$contents = resultData.contents) === null || _resultData$contents === void 0 ? void 0 : _resultData$contents[0];
|
|
if (!resource) throw new Error("No resource content in response");
|
|
return resource;
|
|
} catch (err) {
|
|
console.error("[MCPAppsRenderer] Failed to fetch resource:", err);
|
|
throw err;
|
|
} finally {
|
|
fetchStateRef.current.inProgress = false;
|
|
}
|
|
})();
|
|
fetchStateRef.current.promise = fetchPromise;
|
|
fetchPromise.then((resource) => {
|
|
if (resource) {
|
|
setFetchedResource(resource);
|
|
setIsLoading(false);
|
|
}
|
|
}).catch((err) => {
|
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
setIsLoading(false);
|
|
});
|
|
}, [agent, content]);
|
|
(0, react.useEffect)(() => {
|
|
if (isLoading || !fetchedResource) return;
|
|
const container = containerRef.current;
|
|
if (!container) return;
|
|
let mounted = true;
|
|
let messageHandler = null;
|
|
let initialListener = null;
|
|
let createdIframe = null;
|
|
const setup = async () => {
|
|
try {
|
|
var _fetchedResource$_met;
|
|
const iframe = document.createElement("iframe");
|
|
createdIframe = iframe;
|
|
iframe.style.width = "100%";
|
|
iframe.style.height = "100px";
|
|
iframe.style.border = "none";
|
|
iframe.style.backgroundColor = "transparent";
|
|
iframe.style.display = "block";
|
|
iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms");
|
|
const sandboxReady = new Promise((resolve) => {
|
|
initialListener = (event) => {
|
|
if (event.source === iframe.contentWindow) {
|
|
var _event$data;
|
|
if (((_event$data = event.data) === null || _event$data === void 0 ? void 0 : _event$data.method) === "ui/notifications/sandbox-proxy-ready") {
|
|
if (initialListener) {
|
|
window.removeEventListener("message", initialListener);
|
|
initialListener = null;
|
|
}
|
|
resolve();
|
|
}
|
|
}
|
|
};
|
|
window.addEventListener("message", initialListener);
|
|
});
|
|
if (!mounted) {
|
|
if (initialListener) {
|
|
window.removeEventListener("message", initialListener);
|
|
initialListener = null;
|
|
}
|
|
return;
|
|
}
|
|
iframe.srcdoc = buildSandboxHTML((_fetchedResource$_met = fetchedResource._meta) === null || _fetchedResource$_met === void 0 || (_fetchedResource$_met = _fetchedResource$_met.ui) === null || _fetchedResource$_met === void 0 || (_fetchedResource$_met = _fetchedResource$_met.csp) === null || _fetchedResource$_met === void 0 ? void 0 : _fetchedResource$_met.resourceDomains);
|
|
iframeRef.current = iframe;
|
|
container.appendChild(iframe);
|
|
await sandboxReady;
|
|
if (!mounted) return;
|
|
console.log("[MCPAppsRenderer] Sandbox proxy ready");
|
|
messageHandler = async (event) => {
|
|
if (event.source !== iframe.contentWindow) return;
|
|
const msg = event.data;
|
|
if (!msg || typeof msg !== "object" || msg.jsonrpc !== "2.0") return;
|
|
console.log("[MCPAppsRenderer] Received from iframe:", msg);
|
|
if (isRequest(msg)) switch (msg.method) {
|
|
case "ui/initialize":
|
|
sendResponse(msg.id, {
|
|
protocolVersion: PROTOCOL_VERSION,
|
|
hostInfo: {
|
|
name: "CopilotKit MCP Apps Host",
|
|
version: "1.0.0"
|
|
},
|
|
hostCapabilities: {
|
|
openLinks: {},
|
|
logging: {}
|
|
},
|
|
hostContext: {
|
|
theme: "light",
|
|
platform: "web"
|
|
}
|
|
});
|
|
break;
|
|
case "ui/message": {
|
|
const currentAgent = agentRef.current;
|
|
if (!currentAgent) {
|
|
console.warn("[MCPAppsRenderer] ui/message: No agent available");
|
|
sendResponse(msg.id, { isError: false });
|
|
break;
|
|
}
|
|
try {
|
|
var _params$content;
|
|
const params = msg.params;
|
|
const textContent = ((_params$content = params.content) === null || _params$content === void 0 ? void 0 : _params$content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n")) || "";
|
|
if (textContent) currentAgent.addMessage({
|
|
id: crypto.randomUUID(),
|
|
role: params.role || "user",
|
|
content: textContent
|
|
});
|
|
sendResponse(msg.id, { isError: false });
|
|
} catch (err) {
|
|
console.error("[MCPAppsRenderer] ui/message error:", err);
|
|
sendResponse(msg.id, { isError: true });
|
|
}
|
|
break;
|
|
}
|
|
case "ui/open-link": {
|
|
var _msg$params;
|
|
const url = (_msg$params = msg.params) === null || _msg$params === void 0 ? void 0 : _msg$params.url;
|
|
if (url) {
|
|
window.open(url, "_blank", "noopener,noreferrer");
|
|
sendResponse(msg.id, { isError: false });
|
|
} else sendErrorResponse(msg.id, -32602, "Missing url parameter");
|
|
break;
|
|
}
|
|
case "tools/call": {
|
|
const { serverHash, serverId } = contentRef.current;
|
|
const currentAgent = agentRef.current;
|
|
if (!serverHash) {
|
|
sendErrorResponse(msg.id, -32603, "No server hash available for proxying");
|
|
break;
|
|
}
|
|
if (!currentAgent) {
|
|
sendErrorResponse(msg.id, -32603, "No agent available for proxying");
|
|
break;
|
|
}
|
|
try {
|
|
const runResult = await mcpAppsRequestQueue.enqueue(currentAgent, () => currentAgent.runAgent({ forwardedProps: { __proxiedMCPRequest: {
|
|
serverHash,
|
|
serverId,
|
|
method: "tools/call",
|
|
params: msg.params
|
|
} } }));
|
|
sendResponse(msg.id, runResult.result || {});
|
|
} catch (err) {
|
|
console.error("[MCPAppsRenderer] tools/call error:", err);
|
|
sendErrorResponse(msg.id, -32603, String(err));
|
|
}
|
|
break;
|
|
}
|
|
default: sendErrorResponse(msg.id, -32601, `Method not found: ${msg.method}`);
|
|
}
|
|
if (isNotification(msg)) switch (msg.method) {
|
|
case "ui/notifications/initialized":
|
|
console.log("[MCPAppsRenderer] Inner iframe initialized");
|
|
if (mounted) setIframeReady(true);
|
|
break;
|
|
case "ui/notifications/size-changed": {
|
|
const { width, height } = msg.params || {};
|
|
console.log("[MCPAppsRenderer] Size change:", {
|
|
width,
|
|
height
|
|
});
|
|
if (mounted) setIframeSize({
|
|
width: typeof width === "number" ? width : void 0,
|
|
height: typeof height === "number" ? height : void 0
|
|
});
|
|
break;
|
|
}
|
|
case "notifications/message":
|
|
console.log("[MCPAppsRenderer] App log:", msg.params);
|
|
break;
|
|
}
|
|
};
|
|
window.addEventListener("message", messageHandler);
|
|
let html;
|
|
if (fetchedResource.text) html = fetchedResource.text;
|
|
else if (fetchedResource.blob) html = atob(fetchedResource.blob);
|
|
else throw new Error("Resource has no text or blob content");
|
|
sendNotification("ui/notifications/sandbox-resource-ready", { html });
|
|
} catch (err) {
|
|
console.error("[MCPAppsRenderer] Setup error:", err);
|
|
if (mounted) setError(err instanceof Error ? err : new Error(String(err)));
|
|
}
|
|
};
|
|
setup();
|
|
return () => {
|
|
mounted = false;
|
|
if (initialListener) {
|
|
window.removeEventListener("message", initialListener);
|
|
initialListener = null;
|
|
}
|
|
if (messageHandler) window.removeEventListener("message", messageHandler);
|
|
if (createdIframe) {
|
|
createdIframe.remove();
|
|
createdIframe = null;
|
|
}
|
|
iframeRef.current = null;
|
|
};
|
|
}, [
|
|
isLoading,
|
|
fetchedResource,
|
|
sendNotification,
|
|
sendResponse,
|
|
sendErrorResponse
|
|
]);
|
|
(0, react.useEffect)(() => {
|
|
if (iframeRef.current) {
|
|
if (iframeSize.width !== void 0) {
|
|
iframeRef.current.style.minWidth = `min(${iframeSize.width}px, 100%)`;
|
|
iframeRef.current.style.width = "100%";
|
|
}
|
|
if (iframeSize.height !== void 0) iframeRef.current.style.height = `${iframeSize.height}px`;
|
|
}
|
|
}, [iframeSize]);
|
|
(0, react.useEffect)(() => {
|
|
if (iframeReady && content.toolInput) {
|
|
console.log("[MCPAppsRenderer] Sending tool input:", content.toolInput);
|
|
sendNotification("ui/notifications/tool-input", { arguments: content.toolInput });
|
|
}
|
|
}, [
|
|
iframeReady,
|
|
content.toolInput,
|
|
sendNotification
|
|
]);
|
|
(0, react.useEffect)(() => {
|
|
if (iframeReady && content.result) {
|
|
console.log("[MCPAppsRenderer] Sending tool result:", content.result);
|
|
sendNotification("ui/notifications/tool-result", content.result);
|
|
}
|
|
}, [
|
|
iframeReady,
|
|
content.result,
|
|
sendNotification
|
|
]);
|
|
const borderStyle = (fetchedResource === null || fetchedResource === void 0 || (_fetchedResource$_met2 = fetchedResource._meta) === null || _fetchedResource$_met2 === void 0 || (_fetchedResource$_met2 = _fetchedResource$_met2.ui) === null || _fetchedResource$_met2 === void 0 ? void 0 : _fetchedResource$_met2.prefersBorder) === true ? {
|
|
borderRadius: "8px",
|
|
backgroundColor: "#f9f9f9",
|
|
border: "1px solid #e0e0e0"
|
|
} : {};
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
ref: containerRef,
|
|
style: {
|
|
width: "100%",
|
|
height: iframeSize.height ? `${iframeSize.height}px` : "auto",
|
|
minHeight: "100px",
|
|
overflow: "hidden",
|
|
position: "relative",
|
|
...borderStyle
|
|
},
|
|
children: [isLoading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
style: {
|
|
padding: "1rem",
|
|
color: "#666"
|
|
},
|
|
children: "Loading..."
|
|
}), error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
style: {
|
|
color: "red",
|
|
padding: "1rem"
|
|
},
|
|
children: ["Error: ", error.message]
|
|
})]
|
|
});
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/a2ui/A2UIMessageRenderer.tsx
|
|
let initialized = false;
|
|
function ensureInitialized() {
|
|
if (!initialized) {
|
|
(0, _copilotkit_a2ui_renderer.initializeDefaultCatalog)();
|
|
(0, _copilotkit_a2ui_renderer.injectStyles)();
|
|
initialized = true;
|
|
}
|
|
}
|
|
function createA2UIMessageRenderer(options) {
|
|
const { theme } = options;
|
|
return {
|
|
activityType: "a2ui-surface",
|
|
content: zod.z.any(),
|
|
render: ({ content, agent }) => {
|
|
ensureInitialized();
|
|
const [operations, setOperations] = (0, react.useState)([]);
|
|
const lastSignatureRef = (0, react.useRef)(null);
|
|
const { copilotkit } = useCopilotKit();
|
|
(0, react.useEffect)(() => {
|
|
if (!content || !Array.isArray(content.operations)) {
|
|
lastSignatureRef.current = null;
|
|
setOperations([]);
|
|
return;
|
|
}
|
|
const incoming = content.operations;
|
|
const signature = stringifyOperations(incoming);
|
|
if (signature && signature === lastSignatureRef.current) return;
|
|
lastSignatureRef.current = signature;
|
|
setOperations(incoming);
|
|
}, [content]);
|
|
const groupedOperations = (0, react.useMemo)(() => {
|
|
const groups = /* @__PURE__ */ new Map();
|
|
for (const operation of operations) {
|
|
var _getOperationSurfaceI;
|
|
const surfaceId = (_getOperationSurfaceI = getOperationSurfaceId(operation)) !== null && _getOperationSurfaceI !== void 0 ? _getOperationSurfaceI : _copilotkit_a2ui_renderer.DEFAULT_SURFACE_ID;
|
|
if (!groups.has(surfaceId)) groups.set(surfaceId, []);
|
|
groups.get(surfaceId).push(operation);
|
|
}
|
|
return groups;
|
|
}, [operations]);
|
|
if (!groupedOperations.size) return null;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:flex cpk:min-h-0 cpk:flex-1 cpk:flex-col cpk:gap-6 cpk:overflow-auto cpk:py-6",
|
|
children: Array.from(groupedOperations.entries()).map(([surfaceId, ops]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReactSurfaceHost, {
|
|
surfaceId,
|
|
operations: ops,
|
|
theme,
|
|
agent,
|
|
copilotkit
|
|
}, surfaceId))
|
|
});
|
|
}
|
|
};
|
|
}
|
|
/**
|
|
* Renders a single A2UI surface using the React renderer.
|
|
* Wraps A2UIProvider + A2UIRenderer and bridges actions back to CopilotKit.
|
|
*/
|
|
function ReactSurfaceHost({ surfaceId, operations, theme, agent, copilotkit }) {
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:flex cpk:w-full cpk:flex-none cpk:flex-col cpk:gap-4",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_copilotkit_a2ui_renderer.A2UIProvider, {
|
|
onAction: (0, react.useCallback)(async (message) => {
|
|
if (!agent) return;
|
|
try {
|
|
var _copilotkit$propertie;
|
|
console.info("[A2UI] Action dispatched", message.userAction);
|
|
copilotkit.setProperties({
|
|
...(_copilotkit$propertie = copilotkit.properties) !== null && _copilotkit$propertie !== void 0 ? _copilotkit$propertie : {},
|
|
a2uiAction: message
|
|
});
|
|
await copilotkit.runAgent({ agent });
|
|
} finally {
|
|
if (copilotkit.properties) {
|
|
const { a2uiAction, ...rest } = copilotkit.properties;
|
|
copilotkit.setProperties(rest);
|
|
}
|
|
}
|
|
}, [agent, copilotkit]),
|
|
theme,
|
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SurfaceMessageProcessor, {
|
|
surfaceId,
|
|
operations
|
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_copilotkit_a2ui_renderer.A2UIRenderer, {
|
|
surfaceId,
|
|
className: "cpk:flex cpk:flex-1"
|
|
})]
|
|
})
|
|
});
|
|
}
|
|
/**
|
|
* Processes A2UI operations into the provider's message processor.
|
|
* Must be a child of A2UIProvider to access the actions context.
|
|
*/
|
|
function SurfaceMessageProcessor({ surfaceId, operations }) {
|
|
const { processMessages } = (0, _copilotkit_a2ui_renderer.useA2UIActions)();
|
|
const lastProcessedRef = (0, react.useRef)("");
|
|
(0, react.useEffect)(() => {
|
|
const key = `${surfaceId}-${JSON.stringify(operations)}`;
|
|
if (key === lastProcessedRef.current) return;
|
|
lastProcessedRef.current = key;
|
|
processMessages(operations);
|
|
}, [
|
|
processMessages,
|
|
surfaceId,
|
|
operations
|
|
]);
|
|
return null;
|
|
}
|
|
function getOperationSurfaceId(operation) {
|
|
var _ref, _ref2, _ref3, _operation$beginRende, _operation$beginRende2, _operation$surfaceUpd, _operation$dataModelU, _operation$deleteSurf;
|
|
if (!operation || typeof operation !== "object") return null;
|
|
if (typeof operation.surfaceId === "string") return operation.surfaceId;
|
|
return (_ref = (_ref2 = (_ref3 = (_operation$beginRende = operation === null || operation === void 0 || (_operation$beginRende2 = operation.beginRendering) === null || _operation$beginRende2 === void 0 ? void 0 : _operation$beginRende2.surfaceId) !== null && _operation$beginRende !== void 0 ? _operation$beginRende : operation === null || operation === void 0 || (_operation$surfaceUpd = operation.surfaceUpdate) === null || _operation$surfaceUpd === void 0 ? void 0 : _operation$surfaceUpd.surfaceId) !== null && _ref3 !== void 0 ? _ref3 : operation === null || operation === void 0 || (_operation$dataModelU = operation.dataModelUpdate) === null || _operation$dataModelU === void 0 ? void 0 : _operation$dataModelU.surfaceId) !== null && _ref2 !== void 0 ? _ref2 : operation === null || operation === void 0 || (_operation$deleteSurf = operation.deleteSurface) === null || _operation$deleteSurf === void 0 ? void 0 : _operation$deleteSurf.surfaceId) !== null && _ref !== void 0 ? _ref : null;
|
|
}
|
|
function stringifyOperations(ops) {
|
|
try {
|
|
return JSON.stringify(ops);
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/lib/react-core.ts
|
|
var CopilotKitCoreReact = class extends _copilotkitnext_core.CopilotKitCore {
|
|
constructor(config) {
|
|
var _config$renderToolCal, _config$renderCustomM, _config$renderActivit;
|
|
super(config);
|
|
_defineProperty(this, "_renderToolCalls", []);
|
|
_defineProperty(this, "_hookRenderToolCalls", /* @__PURE__ */ new Map());
|
|
_defineProperty(this, "_cachedMergedRenderToolCalls", null);
|
|
_defineProperty(this, "_renderCustomMessages", []);
|
|
_defineProperty(this, "_renderActivityMessages", []);
|
|
_defineProperty(this, "_interruptElement", null);
|
|
this._renderToolCalls = (_config$renderToolCal = config.renderToolCalls) !== null && _config$renderToolCal !== void 0 ? _config$renderToolCal : [];
|
|
this._renderCustomMessages = (_config$renderCustomM = config.renderCustomMessages) !== null && _config$renderCustomM !== void 0 ? _config$renderCustomM : [];
|
|
this._renderActivityMessages = (_config$renderActivit = config.renderActivityMessages) !== null && _config$renderActivit !== void 0 ? _config$renderActivit : [];
|
|
}
|
|
get renderCustomMessages() {
|
|
return this._renderCustomMessages;
|
|
}
|
|
get renderActivityMessages() {
|
|
return this._renderActivityMessages;
|
|
}
|
|
get renderToolCalls() {
|
|
if (this._hookRenderToolCalls.size === 0) return this._renderToolCalls;
|
|
if (this._cachedMergedRenderToolCalls) return this._cachedMergedRenderToolCalls;
|
|
const merged = /* @__PURE__ */ new Map();
|
|
for (const rc of this._renderToolCalls) {
|
|
var _rc$agentId;
|
|
merged.set(`${(_rc$agentId = rc.agentId) !== null && _rc$agentId !== void 0 ? _rc$agentId : ""}:${rc.name}`, rc);
|
|
}
|
|
for (const [key, rc] of this._hookRenderToolCalls) merged.set(key, rc);
|
|
this._cachedMergedRenderToolCalls = Array.from(merged.values());
|
|
return this._cachedMergedRenderToolCalls;
|
|
}
|
|
setRenderActivityMessages(renderers) {
|
|
this._renderActivityMessages = renderers;
|
|
}
|
|
setRenderCustomMessages(renderers) {
|
|
this._renderCustomMessages = renderers;
|
|
}
|
|
setRenderToolCalls(renderToolCalls) {
|
|
this._renderToolCalls = renderToolCalls;
|
|
this._cachedMergedRenderToolCalls = null;
|
|
this._notifyRenderToolCallsChanged();
|
|
}
|
|
addHookRenderToolCall(entry) {
|
|
var _entry$agentId;
|
|
const key = `${(_entry$agentId = entry.agentId) !== null && _entry$agentId !== void 0 ? _entry$agentId : ""}:${entry.name}`;
|
|
this._hookRenderToolCalls.set(key, entry);
|
|
this._cachedMergedRenderToolCalls = null;
|
|
this._notifyRenderToolCallsChanged();
|
|
}
|
|
removeHookRenderToolCall(name, agentId) {
|
|
const key = `${agentId !== null && agentId !== void 0 ? agentId : ""}:${name}`;
|
|
if (this._hookRenderToolCalls.delete(key)) {
|
|
this._cachedMergedRenderToolCalls = null;
|
|
this._notifyRenderToolCallsChanged();
|
|
}
|
|
}
|
|
_notifyRenderToolCallsChanged() {
|
|
this.notifySubscribers((subscriber) => {
|
|
const reactSubscriber = subscriber;
|
|
if (reactSubscriber.onRenderToolCallsChanged) reactSubscriber.onRenderToolCallsChanged({
|
|
copilotkit: this,
|
|
renderToolCalls: this.renderToolCalls
|
|
});
|
|
}, "Subscriber onRenderToolCallsChanged error:");
|
|
}
|
|
get interruptElement() {
|
|
return this._interruptElement;
|
|
}
|
|
setInterruptElement(element) {
|
|
this._interruptElement = element;
|
|
this.notifySubscribers((subscriber) => {
|
|
var _reactSubscriber$onIn;
|
|
const reactSubscriber = subscriber;
|
|
(_reactSubscriber$onIn = reactSubscriber.onInterruptElementChanged) === null || _reactSubscriber$onIn === void 0 || _reactSubscriber$onIn.call(reactSubscriber, {
|
|
copilotkit: this,
|
|
interruptElement: this._interruptElement
|
|
});
|
|
}, "Subscriber onInterruptElementChanged error:");
|
|
}
|
|
subscribe(subscriber) {
|
|
return super.subscribe(subscriber);
|
|
}
|
|
/**
|
|
* Wait for pending React state updates before the follow-up agent run.
|
|
*
|
|
* When a frontend tool handler calls setState(), React 18 batches the update
|
|
* and schedules a commit via its internal scheduler (MessageChannel). The
|
|
* useAgentContext hook registers context via useLayoutEffect, which runs
|
|
* synchronously after React commits that batch.
|
|
*
|
|
* Awaiting a zero-delay timeout yields to the macrotask queue. React's
|
|
* MessageChannel task runs first, committing the pending state and running
|
|
* useLayoutEffect (which updates the context store). The follow-up runAgent
|
|
* call then reads fresh context.
|
|
*/
|
|
async waitForPendingFrameworkUpdates() {
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
}
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/providers/CopilotKitProvider.tsx
|
|
const HEADER_NAME = "X-CopilotCloud-Public-Api-Key";
|
|
const COPILOT_CLOUD_CHAT_URL = "https://api.cloud.copilotkit.ai/copilotkit/v1";
|
|
const CopilotKitContext = (0, react.createContext)({
|
|
copilotkit: null,
|
|
executingToolCallIds: /* @__PURE__ */ new Set()
|
|
});
|
|
function useStableArrayProp(prop, warningMessage, isMeaningfulChange) {
|
|
const empty = (0, react.useMemo)(() => [], []);
|
|
const value = prop !== null && prop !== void 0 ? prop : empty;
|
|
const initial = (0, react.useRef)(value);
|
|
(0, react.useEffect)(() => {
|
|
if (warningMessage && value !== initial.current && (isMeaningfulChange ? isMeaningfulChange(initial.current, value) : true)) console.error(warningMessage);
|
|
}, [value, warningMessage]);
|
|
return value;
|
|
}
|
|
const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, publicApiKey, publicLicenseKey, properties = {}, agents__unsafe_dev_only: agents = {}, selfManagedAgents = {}, renderToolCalls, renderActivityMessages, renderCustomMessages, frontendTools, humanInTheLoop, showDevConsole = false, useSingleEndpoint = false, onError, a2ui }) => {
|
|
const [shouldRenderInspector, setShouldRenderInspector] = (0, react.useState)(false);
|
|
const [runtimeA2UIEnabled, setRuntimeA2UIEnabled] = (0, react.useState)(false);
|
|
(0, react.useEffect)(() => {
|
|
if (typeof window === "undefined") return;
|
|
if (showDevConsole === true) setShouldRenderInspector(true);
|
|
else if (showDevConsole === "auto") if (new Set(["localhost", "127.0.0.1"]).has(window.location.hostname)) setShouldRenderInspector(true);
|
|
else setShouldRenderInspector(false);
|
|
else setShouldRenderInspector(false);
|
|
}, [showDevConsole]);
|
|
const renderToolCallsList = useStableArrayProp(renderToolCalls, "renderToolCalls must be a stable array. If you want to dynamically add or remove tools, use `useFrontendTool` instead.", (initial, next) => {
|
|
const key = (rc) => {
|
|
var _rc$agentId, _rc$name;
|
|
return `${(_rc$agentId = rc === null || rc === void 0 ? void 0 : rc.agentId) !== null && _rc$agentId !== void 0 ? _rc$agentId : ""}:${(_rc$name = rc === null || rc === void 0 ? void 0 : rc.name) !== null && _rc$name !== void 0 ? _rc$name : ""}`;
|
|
};
|
|
const setFrom = (arr) => new Set(arr.map(key));
|
|
const a = setFrom(initial);
|
|
const b = setFrom(next);
|
|
if (a.size !== b.size) return true;
|
|
for (const k of a) if (!b.has(k)) return true;
|
|
return false;
|
|
});
|
|
const renderCustomMessagesList = useStableArrayProp(renderCustomMessages, "renderCustomMessages must be a stable array.");
|
|
const renderActivityMessagesList = useStableArrayProp(renderActivityMessages, "renderActivityMessages must be a stable array.");
|
|
const builtInActivityRenderers = (0, react.useMemo)(() => {
|
|
const renderers = [{
|
|
activityType: MCPAppsActivityType,
|
|
content: MCPAppsActivityContentSchema,
|
|
render: MCPAppsActivityRenderer
|
|
}];
|
|
if (runtimeA2UIEnabled) {
|
|
var _a2ui$theme;
|
|
renderers.unshift(createA2UIMessageRenderer({ theme: (_a2ui$theme = a2ui === null || a2ui === void 0 ? void 0 : a2ui.theme) !== null && _a2ui$theme !== void 0 ? _a2ui$theme : _copilotkit_a2ui_renderer.viewerTheme }));
|
|
}
|
|
return renderers;
|
|
}, [runtimeA2UIEnabled, a2ui]);
|
|
const allActivityRenderers = (0, react.useMemo)(() => {
|
|
return [...renderActivityMessagesList, ...builtInActivityRenderers];
|
|
}, [renderActivityMessagesList, builtInActivityRenderers]);
|
|
const resolvedPublicKey = publicApiKey !== null && publicApiKey !== void 0 ? publicApiKey : publicLicenseKey;
|
|
const mergedAgents = (0, react.useMemo)(() => ({
|
|
...agents,
|
|
...selfManagedAgents
|
|
}), [agents, selfManagedAgents]);
|
|
const hasLocalAgents = mergedAgents && Object.keys(mergedAgents).length > 0;
|
|
const mergedHeaders = (0, react.useMemo)(() => {
|
|
if (!resolvedPublicKey) return headers;
|
|
if (headers[HEADER_NAME]) return headers;
|
|
return {
|
|
...headers,
|
|
[HEADER_NAME]: resolvedPublicKey
|
|
};
|
|
}, [headers, resolvedPublicKey]);
|
|
if (!runtimeUrl && !resolvedPublicKey && !hasLocalAgents) {
|
|
const message = "Missing required prop: 'runtimeUrl' or 'publicApiKey' or 'publicLicenseKey'";
|
|
if (process.env.NODE_ENV === "production") throw new Error(message);
|
|
else console.warn(message);
|
|
}
|
|
const chatApiEndpoint = runtimeUrl !== null && runtimeUrl !== void 0 ? runtimeUrl : resolvedPublicKey ? COPILOT_CLOUD_CHAT_URL : void 0;
|
|
const frontendToolsList = useStableArrayProp(frontendTools, "frontendTools must be a stable array. If you want to dynamically add or remove tools, use `useFrontendTool` instead.");
|
|
const humanInTheLoopList = useStableArrayProp(humanInTheLoop, "humanInTheLoop must be a stable array. If you want to dynamically add or remove human-in-the-loop tools, use `useHumanInTheLoop` instead.");
|
|
const processedHumanInTheLoopTools = (0, react.useMemo)(() => {
|
|
const processedTools = [];
|
|
const processedRenderToolCalls = [];
|
|
humanInTheLoopList.forEach((tool) => {
|
|
const frontendTool = {
|
|
name: tool.name,
|
|
description: tool.description,
|
|
parameters: tool.parameters,
|
|
followUp: tool.followUp,
|
|
...tool.agentId && { agentId: tool.agentId },
|
|
handler: async () => {
|
|
return new Promise((resolve) => {
|
|
console.warn(`Human-in-the-loop tool '${tool.name}' called but no interactive handler is set up.`);
|
|
resolve(void 0);
|
|
});
|
|
}
|
|
};
|
|
processedTools.push(frontendTool);
|
|
if (tool.render) processedRenderToolCalls.push({
|
|
name: tool.name,
|
|
args: tool.parameters,
|
|
render: tool.render,
|
|
...tool.agentId && { agentId: tool.agentId }
|
|
});
|
|
});
|
|
return {
|
|
tools: processedTools,
|
|
renderToolCalls: processedRenderToolCalls
|
|
};
|
|
}, [humanInTheLoopList]);
|
|
const allTools = (0, react.useMemo)(() => {
|
|
const tools = [];
|
|
tools.push(...frontendToolsList);
|
|
tools.push(...processedHumanInTheLoopTools.tools);
|
|
return tools;
|
|
}, [frontendToolsList, processedHumanInTheLoopTools]);
|
|
const allRenderToolCalls = (0, react.useMemo)(() => {
|
|
const combined = [...renderToolCallsList];
|
|
frontendToolsList.forEach((tool) => {
|
|
if (tool.render) {
|
|
const args = tool.parameters || (tool.name === "*" ? zod.z.any() : void 0);
|
|
if (args) combined.push({
|
|
name: tool.name,
|
|
args,
|
|
render: tool.render
|
|
});
|
|
}
|
|
});
|
|
combined.push(...processedHumanInTheLoopTools.renderToolCalls);
|
|
return combined;
|
|
}, [
|
|
renderToolCallsList,
|
|
frontendToolsList,
|
|
processedHumanInTheLoopTools
|
|
]);
|
|
const copilotkitRef = (0, react.useRef)(null);
|
|
if (copilotkitRef.current === null) copilotkitRef.current = new CopilotKitCoreReact({
|
|
runtimeUrl: chatApiEndpoint,
|
|
runtimeTransport: useSingleEndpoint ? "single" : "rest",
|
|
headers: mergedHeaders,
|
|
credentials,
|
|
properties,
|
|
agents__unsafe_dev_only: mergedAgents,
|
|
tools: allTools,
|
|
renderToolCalls: allRenderToolCalls,
|
|
renderActivityMessages: allActivityRenderers,
|
|
renderCustomMessages: renderCustomMessagesList
|
|
});
|
|
const copilotkit = copilotkitRef.current;
|
|
(0, react.useEffect)(() => {
|
|
const subscription = copilotkit.subscribe({ onRuntimeConnectionStatusChanged: () => {
|
|
setRuntimeA2UIEnabled(copilotkit.a2uiEnabled);
|
|
} });
|
|
return () => {
|
|
subscription.unsubscribe();
|
|
};
|
|
}, [copilotkit]);
|
|
const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
|
|
(0, react.useEffect)(() => {
|
|
const subscription = copilotkit.subscribe({ onRenderToolCallsChanged: () => {
|
|
forceUpdate();
|
|
} });
|
|
return () => {
|
|
subscription.unsubscribe();
|
|
};
|
|
}, [copilotkit]);
|
|
const [executingToolCallIds, setExecutingToolCallIds] = (0, react.useState)(() => /* @__PURE__ */ new Set());
|
|
(0, react.useEffect)(() => {
|
|
const subscription = copilotkit.subscribe({
|
|
onToolExecutionStart: ({ toolCallId }) => {
|
|
setExecutingToolCallIds((prev) => {
|
|
if (prev.has(toolCallId)) return prev;
|
|
const next = new Set(prev);
|
|
next.add(toolCallId);
|
|
return next;
|
|
});
|
|
},
|
|
onToolExecutionEnd: ({ toolCallId }) => {
|
|
setExecutingToolCallIds((prev) => {
|
|
if (!prev.has(toolCallId)) return prev;
|
|
const next = new Set(prev);
|
|
next.delete(toolCallId);
|
|
return next;
|
|
});
|
|
}
|
|
});
|
|
return () => {
|
|
subscription.unsubscribe();
|
|
};
|
|
}, [copilotkit]);
|
|
const onErrorRef = (0, react.useRef)(onError);
|
|
(0, react.useEffect)(() => {
|
|
onErrorRef.current = onError;
|
|
}, [onError]);
|
|
(0, react.useEffect)(() => {
|
|
if (!onErrorRef.current) return;
|
|
const subscription = copilotkit.subscribe({ onError: (event) => {
|
|
var _onErrorRef$current;
|
|
(_onErrorRef$current = onErrorRef.current) === null || _onErrorRef$current === void 0 || _onErrorRef$current.call(onErrorRef, {
|
|
error: event.error,
|
|
code: event.code,
|
|
context: event.context
|
|
});
|
|
} });
|
|
return () => {
|
|
subscription.unsubscribe();
|
|
};
|
|
}, [copilotkit]);
|
|
(0, react.useEffect)(() => {
|
|
copilotkit.setRuntimeUrl(chatApiEndpoint);
|
|
copilotkit.setRuntimeTransport(useSingleEndpoint ? "single" : "rest");
|
|
copilotkit.setHeaders(mergedHeaders);
|
|
copilotkit.setCredentials(credentials);
|
|
copilotkit.setProperties(properties);
|
|
copilotkit.setAgents__unsafe_dev_only(mergedAgents);
|
|
}, [
|
|
copilotkit,
|
|
chatApiEndpoint,
|
|
mergedHeaders,
|
|
credentials,
|
|
properties,
|
|
mergedAgents,
|
|
useSingleEndpoint
|
|
]);
|
|
const didMountRef = (0, react.useRef)(false);
|
|
(0, react.useEffect)(() => {
|
|
if (!didMountRef.current) return;
|
|
copilotkit.setTools(allTools);
|
|
}, [copilotkit, allTools]);
|
|
(0, react.useEffect)(() => {
|
|
if (!didMountRef.current) return;
|
|
copilotkit.setRenderToolCalls(allRenderToolCalls);
|
|
}, [copilotkit, allRenderToolCalls]);
|
|
(0, react.useEffect)(() => {
|
|
if (!didMountRef.current) return;
|
|
copilotkit.setRenderActivityMessages(allActivityRenderers);
|
|
}, [copilotkit, allActivityRenderers]);
|
|
(0, react.useEffect)(() => {
|
|
if (!didMountRef.current) return;
|
|
copilotkit.setRenderCustomMessages(renderCustomMessagesList);
|
|
}, [copilotkit, renderCustomMessagesList]);
|
|
(0, react.useEffect)(() => {
|
|
didMountRef.current = true;
|
|
}, []);
|
|
const contextValue = (0, react.useMemo)(() => ({
|
|
copilotkit,
|
|
executingToolCallIds
|
|
}), [copilotkit, executingToolCallIds]);
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CopilotKitContext.Provider, {
|
|
value: contextValue,
|
|
children: [children, shouldRenderInspector ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotKitInspector, { core: copilotkit }) : null]
|
|
});
|
|
};
|
|
const useCopilotKit = () => {
|
|
const context = (0, react.useContext)(CopilotKitContext);
|
|
const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
|
|
if (!context) throw new Error("useCopilotKit must be used within CopilotKitProvider");
|
|
(0, react.useEffect)(() => {
|
|
const subscription = context.copilotkit.subscribe({ onRuntimeConnectionStatusChanged: () => {
|
|
forceUpdate();
|
|
} });
|
|
return () => {
|
|
subscription.unsubscribe();
|
|
};
|
|
}, []);
|
|
return context;
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/hooks/use-render-tool-call.tsx
|
|
/**
|
|
* Memoized component that renders a single tool call.
|
|
* This prevents unnecessary re-renders when parent components update
|
|
* but the tool call data hasn't changed.
|
|
*/
|
|
const ToolCallRenderer = react.default.memo(function ToolCallRenderer({ toolCall, toolMessage, RenderComponent, isExecuting }) {
|
|
const args = (0, react.useMemo)(() => (0, _copilotkitnext_shared.partialJSONParse)(toolCall.function.arguments), [toolCall.function.arguments]);
|
|
const toolName = toolCall.function.name;
|
|
if (toolMessage) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RenderComponent, {
|
|
name: toolName,
|
|
args,
|
|
status: _copilotkitnext_core.ToolCallStatus.Complete,
|
|
result: toolMessage.content
|
|
});
|
|
else if (isExecuting) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RenderComponent, {
|
|
name: toolName,
|
|
args,
|
|
status: _copilotkitnext_core.ToolCallStatus.Executing,
|
|
result: void 0
|
|
});
|
|
else return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RenderComponent, {
|
|
name: toolName,
|
|
args,
|
|
status: _copilotkitnext_core.ToolCallStatus.InProgress,
|
|
result: void 0
|
|
});
|
|
}, (prevProps, nextProps) => {
|
|
var _prevProps$toolMessag, _nextProps$toolMessag;
|
|
if (prevProps.toolCall.id !== nextProps.toolCall.id) return false;
|
|
if (prevProps.toolCall.function.name !== nextProps.toolCall.function.name) return false;
|
|
if (prevProps.toolCall.function.arguments !== nextProps.toolCall.function.arguments) return false;
|
|
if (((_prevProps$toolMessag = prevProps.toolMessage) === null || _prevProps$toolMessag === void 0 ? void 0 : _prevProps$toolMessag.content) !== ((_nextProps$toolMessag = nextProps.toolMessage) === null || _nextProps$toolMessag === void 0 ? void 0 : _nextProps$toolMessag.content)) return false;
|
|
if (prevProps.isExecuting !== nextProps.isExecuting) return false;
|
|
if (prevProps.RenderComponent !== nextProps.RenderComponent) return false;
|
|
return true;
|
|
});
|
|
/**
|
|
* Hook that returns a function to render tool calls based on the render functions
|
|
* defined in CopilotKitProvider.
|
|
*
|
|
* @returns A function that takes a tool call and optional tool message and returns the rendered component
|
|
*/
|
|
function useRenderToolCall() {
|
|
var _config$agentId;
|
|
const { copilotkit, executingToolCallIds } = useCopilotKit();
|
|
const config = useCopilotChatConfiguration();
|
|
const agentId = (_config$agentId = config === null || config === void 0 ? void 0 : config.agentId) !== null && _config$agentId !== void 0 ? _config$agentId : _copilotkitnext_shared.DEFAULT_AGENT_ID;
|
|
const renderToolCalls = (0, react.useSyncExternalStore)((callback) => {
|
|
return copilotkit.subscribe({ onRenderToolCallsChanged: callback }).unsubscribe;
|
|
}, () => copilotkit.renderToolCalls, () => copilotkit.renderToolCalls);
|
|
return (0, react.useCallback)(({ toolCall, toolMessage }) => {
|
|
const exactMatches = renderToolCalls.filter((rc) => rc.name === toolCall.function.name);
|
|
const renderConfig = exactMatches.find((rc) => rc.agentId === agentId) || exactMatches.find((rc) => !rc.agentId) || exactMatches[0] || renderToolCalls.find((rc) => rc.name === "*");
|
|
if (!renderConfig) return null;
|
|
const RenderComponent = renderConfig.render;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallRenderer, {
|
|
toolCall,
|
|
toolMessage,
|
|
RenderComponent,
|
|
isExecuting: executingToolCallIds.has(toolCall.id)
|
|
}, toolCall.id);
|
|
}, [
|
|
renderToolCalls,
|
|
executingToolCallIds,
|
|
agentId
|
|
]);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/hooks/use-render-custom-messages.tsx
|
|
function useRenderCustomMessages() {
|
|
const { copilotkit } = useCopilotKit();
|
|
const config = useCopilotChatConfiguration();
|
|
if (!config) return null;
|
|
const { agentId, threadId } = config;
|
|
const customMessageRenderers = copilotkit.renderCustomMessages.filter((renderer) => renderer.agentId === void 0 || renderer.agentId === agentId).sort((a, b) => {
|
|
const aHasAgent = a.agentId !== void 0;
|
|
if (aHasAgent === (b.agentId !== void 0)) return 0;
|
|
return aHasAgent ? -1 : 1;
|
|
});
|
|
return function(params) {
|
|
var _copilotkit$getRunIdF;
|
|
if (!customMessageRenderers.length) return null;
|
|
const { message, position } = params;
|
|
const resolvedRunId = (_copilotkit$getRunIdF = copilotkit.getRunIdForMessage(agentId, threadId, message.id)) !== null && _copilotkit$getRunIdF !== void 0 ? _copilotkit$getRunIdF : copilotkit.getRunIdsForThread(agentId, threadId).slice(-1)[0];
|
|
const runId = resolvedRunId !== null && resolvedRunId !== void 0 ? resolvedRunId : `missing-run-id:${message.id}`;
|
|
const agent = copilotkit.getAgent(agentId);
|
|
if (!agent) throw new Error("Agent not found");
|
|
const messagesIdsInRun = resolvedRunId ? agent.messages.filter((msg) => copilotkit.getRunIdForMessage(agentId, threadId, msg.id) === resolvedRunId).map((msg) => msg.id) : [message.id];
|
|
const rawMessageIndex = agent.messages.findIndex((msg) => msg.id === message.id);
|
|
const messageIndex = rawMessageIndex >= 0 ? rawMessageIndex : 0;
|
|
const messageIndexInRun = resolvedRunId ? Math.max(messagesIdsInRun.indexOf(message.id), 0) : 0;
|
|
const numberOfMessagesInRun = resolvedRunId ? messagesIdsInRun.length : 1;
|
|
const stateSnapshot = resolvedRunId ? copilotkit.getStateByRun(agentId, threadId, resolvedRunId) : void 0;
|
|
let result = null;
|
|
for (const renderer of customMessageRenderers) {
|
|
if (!renderer.render) continue;
|
|
const Component = renderer.render;
|
|
result = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
|
|
message,
|
|
position,
|
|
runId,
|
|
messageIndex,
|
|
messageIndexInRun,
|
|
numberOfMessagesInRun,
|
|
agentId,
|
|
stateSnapshot
|
|
}, `${runId}-${message.id}-${position}`);
|
|
if (result) break;
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/hooks/use-render-activity-message.tsx
|
|
function useRenderActivityMessage() {
|
|
var _config$agentId;
|
|
const { copilotkit } = useCopilotKit();
|
|
const config = useCopilotChatConfiguration();
|
|
const agentId = (_config$agentId = config === null || config === void 0 ? void 0 : config.agentId) !== null && _config$agentId !== void 0 ? _config$agentId : _copilotkitnext_shared.DEFAULT_AGENT_ID;
|
|
const renderers = copilotkit.renderActivityMessages;
|
|
const findRenderer = (0, react.useCallback)((activityType) => {
|
|
var _ref, _ref2, _matches$find;
|
|
if (!renderers.length) return null;
|
|
const matches = renderers.filter((renderer) => renderer.activityType === activityType);
|
|
return (_ref = (_ref2 = (_matches$find = matches.find((candidate) => candidate.agentId === agentId)) !== null && _matches$find !== void 0 ? _matches$find : matches.find((candidate) => candidate.agentId === void 0)) !== null && _ref2 !== void 0 ? _ref2 : renderers.find((candidate) => candidate.activityType === "*")) !== null && _ref !== void 0 ? _ref : null;
|
|
}, [agentId, renderers]);
|
|
const renderActivityMessage = (0, react.useCallback)((message) => {
|
|
const renderer = findRenderer(message.activityType);
|
|
if (!renderer) return null;
|
|
const parseResult = renderer.content.safeParse(message.content);
|
|
if (!parseResult.success) {
|
|
console.warn(`Failed to parse content for activity message '${message.activityType}':`, parseResult.error);
|
|
return null;
|
|
}
|
|
const Component = renderer.render;
|
|
const agent = copilotkit.getAgent(agentId);
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
|
|
activityType: message.activityType,
|
|
content: parseResult.data,
|
|
message,
|
|
agent
|
|
}, message.id);
|
|
}, [
|
|
agentId,
|
|
copilotkit,
|
|
findRenderer
|
|
]);
|
|
return (0, react.useMemo)(() => ({
|
|
renderActivityMessage,
|
|
findRenderer
|
|
}), [renderActivityMessage, findRenderer]);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/hooks/use-frontend-tool.tsx
|
|
const EMPTY_DEPS$1 = [];
|
|
function useFrontendTool(tool, deps) {
|
|
const { copilotkit } = useCopilotKit();
|
|
const extraDeps = deps !== null && deps !== void 0 ? deps : EMPTY_DEPS$1;
|
|
(0, react.useEffect)(() => {
|
|
const name = tool.name;
|
|
if (copilotkit.getTool({
|
|
toolName: name,
|
|
agentId: tool.agentId
|
|
})) {
|
|
console.warn(`Tool '${name}' already exists for agent '${tool.agentId || "global"}'. Overriding with latest registration.`);
|
|
copilotkit.removeTool(name, tool.agentId);
|
|
}
|
|
copilotkit.addTool(tool);
|
|
if (tool.render && tool.parameters) copilotkit.addHookRenderToolCall({
|
|
name,
|
|
args: tool.parameters,
|
|
agentId: tool.agentId,
|
|
render: tool.render
|
|
});
|
|
return () => {
|
|
copilotkit.removeTool(name, tool.agentId);
|
|
};
|
|
}, [
|
|
tool.name,
|
|
tool.available,
|
|
copilotkit,
|
|
extraDeps.length,
|
|
...extraDeps
|
|
]);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/hooks/use-component.tsx
|
|
/**
|
|
* Registers a React component as a frontend tool renderer in chat.
|
|
*
|
|
* This hook is a convenience wrapper around `useFrontendTool` that:
|
|
* - builds a model-facing tool description,
|
|
* - forwards optional schema parameters (any Standard Schema V1 compatible library),
|
|
* - renders your component with tool call parameters.
|
|
*
|
|
* Use this when you want to display a typed visual component for a tool call
|
|
* without manually wiring a full frontend tool object.
|
|
*
|
|
* When `parameters` is provided, render props are inferred from the schema.
|
|
* When omitted, the render component may accept any props.
|
|
*
|
|
* @typeParam TSchema - Schema describing tool parameters, or `undefined` when no schema is given.
|
|
* @param config - Tool registration config.
|
|
* @param deps - Optional dependencies to refresh registration (same semantics as `useEffect`).
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* // Without parameters — render accepts any props
|
|
* useComponent({
|
|
* name: "showGreeting",
|
|
* render: ({ message }: { message: string }) => <div>{message}</div>,
|
|
* });
|
|
* ```
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* // With parameters — render props inferred from schema
|
|
* useComponent({
|
|
* name: "showWeatherCard",
|
|
* parameters: z.object({ city: z.string() }),
|
|
* render: ({ city }) => <div>{city}</div>,
|
|
* });
|
|
* ```
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* useComponent(
|
|
* {
|
|
* name: "renderProfile",
|
|
* parameters: z.object({ userId: z.string() }),
|
|
* render: ProfileCard,
|
|
* agentId: "support-agent",
|
|
* },
|
|
* [selectedAgentId],
|
|
* );
|
|
* ```
|
|
*/
|
|
function useComponent(config, deps) {
|
|
const prefix = `Use this tool to display the "${config.name}" component in the chat. This tool renders a visual UI component for the user.`;
|
|
const fullDescription = config.description ? `${prefix}\n\n${config.description}` : prefix;
|
|
useFrontendTool({
|
|
name: config.name,
|
|
description: fullDescription,
|
|
parameters: config.parameters,
|
|
render: ({ args }) => {
|
|
const Component = config.render;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, { ...args });
|
|
},
|
|
agentId: config.agentId
|
|
}, deps);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/types/defineToolCallRenderer.ts
|
|
function defineToolCallRenderer(def) {
|
|
const argsSchema = def.name === "*" && !def.args ? zod.z.any() : def.args;
|
|
return {
|
|
name: def.name,
|
|
args: argsSchema,
|
|
render: def.render,
|
|
...def.agentId ? { agentId: def.agentId } : {}
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/hooks/use-render-tool.tsx
|
|
const EMPTY_DEPS = [];
|
|
/**
|
|
* Registers a renderer entry in CopilotKit's `renderToolCalls` registry.
|
|
*
|
|
* Key behavior:
|
|
* - deduplicates by `agentId:name` (latest registration wins),
|
|
* - keeps renderer entries on cleanup so historical chat tool calls can still render,
|
|
* - refreshes registration when `deps` change.
|
|
*
|
|
* @typeParam S - Schema type describing tool call parameters.
|
|
* @param config - Renderer config for wildcard or named tools.
|
|
* @param deps - Optional dependencies to refresh registration.
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* useRenderTool(
|
|
* {
|
|
* name: "searchDocs",
|
|
* parameters: z.object({ query: z.string() }),
|
|
* render: ({ status, parameters, result }) => {
|
|
* if (status === "executing") return <div>Searching {parameters.query}</div>;
|
|
* if (status === "complete") return <div>{result}</div>;
|
|
* return <div>Preparing...</div>;
|
|
* },
|
|
* },
|
|
* [],
|
|
* );
|
|
* ```
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* useRenderTool(
|
|
* {
|
|
* name: "summarize",
|
|
* parameters: z.object({ text: z.string() }),
|
|
* agentId: "research-agent",
|
|
* render: ({ name, status }) => <div>{name}: {status}</div>,
|
|
* },
|
|
* [selectedAgentId],
|
|
* );
|
|
* ```
|
|
*/
|
|
function useRenderTool(config, deps) {
|
|
const { copilotkit } = useCopilotKit();
|
|
const extraDeps = deps !== null && deps !== void 0 ? deps : EMPTY_DEPS;
|
|
(0, react.useEffect)(() => {
|
|
const renderer = config.name === "*" && !config.parameters ? defineToolCallRenderer({
|
|
name: "*",
|
|
render: (props) => config.render({
|
|
...props,
|
|
parameters: props.args
|
|
}),
|
|
...config.agentId ? { agentId: config.agentId } : {}
|
|
}) : defineToolCallRenderer({
|
|
name: config.name,
|
|
args: config.parameters,
|
|
render: (props) => config.render({
|
|
...props,
|
|
parameters: props.args
|
|
}),
|
|
...config.agentId ? { agentId: config.agentId } : {}
|
|
});
|
|
copilotkit.addHookRenderToolCall(renderer);
|
|
}, [
|
|
config.name,
|
|
copilotkit,
|
|
extraDeps.length,
|
|
...extraDeps
|
|
]);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/hooks/use-default-render-tool.tsx
|
|
/**
|
|
* Registers a wildcard (`"*"`) tool-call renderer via `useRenderTool`.
|
|
*
|
|
* - Call with no config to use CopilotKit's built-in default tool-call card.
|
|
* - Pass `config.render` to replace the default UI with your own fallback renderer.
|
|
*
|
|
* This is useful when you want a generic renderer for tools that do not have a
|
|
* dedicated `useRenderTool({ name: "..." })` registration.
|
|
*
|
|
* @param config - Optional custom wildcard render function.
|
|
* @param deps - Optional dependencies to refresh registration.
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* useDefaultRenderTool();
|
|
* ```
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* useDefaultRenderTool({
|
|
* render: ({ name, status }) => <div>{name}: {status}</div>,
|
|
* });
|
|
* ```
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* useDefaultRenderTool(
|
|
* {
|
|
* render: ({ name, result }) => (
|
|
* <ToolEventRow title={name} payload={result} compact={compactMode} />
|
|
* ),
|
|
* },
|
|
* [compactMode],
|
|
* );
|
|
* ```
|
|
*/
|
|
function useDefaultRenderTool(config, deps) {
|
|
var _config$render;
|
|
useRenderTool({
|
|
name: "*",
|
|
render: (_config$render = config === null || config === void 0 ? void 0 : config.render) !== null && _config$render !== void 0 ? _config$render : DefaultToolCallRenderer
|
|
}, deps);
|
|
}
|
|
function DefaultToolCallRenderer({ name, parameters, status, result }) {
|
|
const [isExpanded, setIsExpanded] = (0, react.useState)(false);
|
|
const statusString = String(status);
|
|
const isActive = statusString === "inProgress" || statusString === "executing";
|
|
const isComplete = statusString === "complete";
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
style: {
|
|
marginTop: "8px",
|
|
paddingBottom: "8px"
|
|
},
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
style: {
|
|
borderRadius: "12px",
|
|
border: "1px solid #e4e4e7",
|
|
backgroundColor: "#fafafa",
|
|
padding: "14px 16px"
|
|
},
|
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
onClick: () => setIsExpanded(!isExpanded),
|
|
style: {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
gap: "10px",
|
|
cursor: "pointer",
|
|
userSelect: "none"
|
|
},
|
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
style: {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "8px",
|
|
minWidth: 0
|
|
},
|
|
children: [
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
style: {
|
|
height: "14px",
|
|
width: "14px",
|
|
color: "#71717a",
|
|
transition: "transform 0.15s",
|
|
transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)",
|
|
flexShrink: 0
|
|
},
|
|
fill: "none",
|
|
viewBox: "0 0 24 24",
|
|
strokeWidth: 2,
|
|
stroke: "currentColor",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
strokeLinecap: "round",
|
|
strokeLinejoin: "round",
|
|
d: "M8.25 4.5l7.5 7.5-7.5 7.5"
|
|
})
|
|
}),
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
display: "inline-block",
|
|
height: "8px",
|
|
width: "8px",
|
|
borderRadius: "50%",
|
|
backgroundColor: isActive ? "#f59e0b" : isComplete ? "#10b981" : "#a1a1aa",
|
|
flexShrink: 0
|
|
} }),
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
style: {
|
|
fontSize: "13px",
|
|
fontWeight: 600,
|
|
color: "#18181b",
|
|
overflow: "hidden",
|
|
textOverflow: "ellipsis",
|
|
whiteSpace: "nowrap"
|
|
},
|
|
children: name
|
|
})
|
|
]
|
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
style: {
|
|
display: "inline-flex",
|
|
alignItems: "center",
|
|
borderRadius: "9999px",
|
|
padding: "2px 8px",
|
|
fontSize: "11px",
|
|
fontWeight: 500,
|
|
backgroundColor: isActive ? "#fef3c7" : isComplete ? "#d1fae5" : "#f4f4f5",
|
|
color: isActive ? "#92400e" : isComplete ? "#065f46" : "#3f3f46",
|
|
flexShrink: 0
|
|
},
|
|
children: isActive ? "Running" : isComplete ? "Done" : status
|
|
})]
|
|
}), isExpanded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
style: {
|
|
marginTop: "12px",
|
|
display: "grid",
|
|
gap: "12px"
|
|
},
|
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
style: {
|
|
fontSize: "10px",
|
|
textTransform: "uppercase",
|
|
letterSpacing: "0.05em",
|
|
color: "#71717a"
|
|
},
|
|
children: "Arguments"
|
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
style: {
|
|
marginTop: "6px",
|
|
maxHeight: "200px",
|
|
overflow: "auto",
|
|
borderRadius: "6px",
|
|
backgroundColor: "#f4f4f5",
|
|
padding: "10px",
|
|
fontSize: "11px",
|
|
lineHeight: 1.6,
|
|
color: "#27272a",
|
|
whiteSpace: "pre-wrap",
|
|
wordBreak: "break-word"
|
|
},
|
|
children: JSON.stringify(parameters !== null && parameters !== void 0 ? parameters : {}, null, 2)
|
|
})] }), result !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
style: {
|
|
fontSize: "10px",
|
|
textTransform: "uppercase",
|
|
letterSpacing: "0.05em",
|
|
color: "#71717a"
|
|
},
|
|
children: "Result"
|
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
style: {
|
|
marginTop: "6px",
|
|
maxHeight: "200px",
|
|
overflow: "auto",
|
|
borderRadius: "6px",
|
|
backgroundColor: "#f4f4f5",
|
|
padding: "10px",
|
|
fontSize: "11px",
|
|
lineHeight: 1.6,
|
|
color: "#27272a",
|
|
whiteSpace: "pre-wrap",
|
|
wordBreak: "break-word"
|
|
},
|
|
children: typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
})] })]
|
|
})]
|
|
})
|
|
});
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/hooks/use-human-in-the-loop.tsx
|
|
function useHumanInTheLoop(tool, deps) {
|
|
const { copilotkit } = useCopilotKit();
|
|
const resolvePromiseRef = (0, react.useRef)(null);
|
|
const respond = (0, react.useCallback)(async (result) => {
|
|
if (resolvePromiseRef.current) {
|
|
resolvePromiseRef.current(result);
|
|
resolvePromiseRef.current = null;
|
|
}
|
|
}, []);
|
|
const handler = (0, react.useCallback)(async () => {
|
|
return new Promise((resolve) => {
|
|
resolvePromiseRef.current = resolve;
|
|
});
|
|
}, []);
|
|
const RenderComponent = (0, react.useCallback)((props) => {
|
|
const ToolComponent = tool.render;
|
|
if (props.status === "inProgress") {
|
|
const enhancedProps = {
|
|
...props,
|
|
name: tool.name,
|
|
description: tool.description || "",
|
|
respond: void 0
|
|
};
|
|
return react.default.createElement(ToolComponent, enhancedProps);
|
|
} else if (props.status === "executing") {
|
|
const enhancedProps = {
|
|
...props,
|
|
name: tool.name,
|
|
description: tool.description || "",
|
|
respond
|
|
};
|
|
return react.default.createElement(ToolComponent, enhancedProps);
|
|
} else if (props.status === "complete") {
|
|
const enhancedProps = {
|
|
...props,
|
|
name: tool.name,
|
|
description: tool.description || "",
|
|
respond: void 0
|
|
};
|
|
return react.default.createElement(ToolComponent, enhancedProps);
|
|
}
|
|
return react.default.createElement(ToolComponent, props);
|
|
}, [
|
|
tool.render,
|
|
tool.name,
|
|
tool.description,
|
|
respond
|
|
]);
|
|
useFrontendTool({
|
|
...tool,
|
|
handler,
|
|
render: RenderComponent
|
|
}, deps);
|
|
(0, react.useEffect)(() => {
|
|
return () => {
|
|
copilotkit.removeHookRenderToolCall(tool.name, tool.agentId);
|
|
};
|
|
}, [
|
|
copilotkit,
|
|
tool.name,
|
|
tool.agentId
|
|
]);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/hooks/use-agent.tsx
|
|
let UseAgentUpdate = /* @__PURE__ */ function(UseAgentUpdate) {
|
|
UseAgentUpdate["OnMessagesChanged"] = "OnMessagesChanged";
|
|
UseAgentUpdate["OnStateChanged"] = "OnStateChanged";
|
|
UseAgentUpdate["OnRunStatusChanged"] = "OnRunStatusChanged";
|
|
return UseAgentUpdate;
|
|
}({});
|
|
const ALL_UPDATES = [
|
|
UseAgentUpdate.OnMessagesChanged,
|
|
UseAgentUpdate.OnStateChanged,
|
|
UseAgentUpdate.OnRunStatusChanged
|
|
];
|
|
function useAgent({ agentId, updates } = {}) {
|
|
var _agentId;
|
|
(_agentId = agentId) !== null && _agentId !== void 0 || (agentId = _copilotkitnext_shared.DEFAULT_AGENT_ID);
|
|
const { copilotkit } = useCopilotKit();
|
|
const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
|
|
const updateFlags = (0, react.useMemo)(() => updates !== null && updates !== void 0 ? updates : ALL_UPDATES, [JSON.stringify(updates)]);
|
|
const provisionalAgentCache = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
const agent = (0, react.useMemo)(() => {
|
|
var _copilotkit$agents;
|
|
const existing = copilotkit.getAgent(agentId);
|
|
if (existing) {
|
|
provisionalAgentCache.current.delete(agentId);
|
|
return existing;
|
|
}
|
|
const isRuntimeConfigured = copilotkit.runtimeUrl !== void 0;
|
|
const status = copilotkit.runtimeConnectionStatus;
|
|
if (isRuntimeConfigured && (status === _copilotkitnext_core.CopilotKitCoreRuntimeConnectionStatus.Disconnected || status === _copilotkitnext_core.CopilotKitCoreRuntimeConnectionStatus.Connecting)) {
|
|
const cached = provisionalAgentCache.current.get(agentId);
|
|
if (cached) {
|
|
cached.headers = { ...copilotkit.headers };
|
|
return cached;
|
|
}
|
|
const provisional = new _copilotkitnext_core.ProxiedCopilotRuntimeAgent({
|
|
runtimeUrl: copilotkit.runtimeUrl,
|
|
agentId,
|
|
transport: copilotkit.runtimeTransport
|
|
});
|
|
provisional.headers = { ...copilotkit.headers };
|
|
provisionalAgentCache.current.set(agentId, provisional);
|
|
return provisional;
|
|
}
|
|
if (isRuntimeConfigured && status === _copilotkitnext_core.CopilotKitCoreRuntimeConnectionStatus.Error) {
|
|
const provisional = new _copilotkitnext_core.ProxiedCopilotRuntimeAgent({
|
|
runtimeUrl: copilotkit.runtimeUrl,
|
|
agentId,
|
|
transport: copilotkit.runtimeTransport
|
|
});
|
|
provisional.headers = { ...copilotkit.headers };
|
|
return provisional;
|
|
}
|
|
const knownAgents = Object.keys((_copilotkit$agents = copilotkit.agents) !== null && _copilotkit$agents !== void 0 ? _copilotkit$agents : {});
|
|
const runtimePart = isRuntimeConfigured ? `runtimeUrl=${copilotkit.runtimeUrl}` : "no runtimeUrl";
|
|
throw new Error(`useAgent: Agent '${agentId}' not found after runtime sync (${runtimePart}). ` + (knownAgents.length ? `Known agents: [${knownAgents.join(", ")}]` : "No agents registered.") + " Verify your runtime /info and/or agents__unsafe_dev_only.");
|
|
}, [
|
|
agentId,
|
|
copilotkit.agents,
|
|
copilotkit.runtimeConnectionStatus,
|
|
copilotkit.runtimeUrl,
|
|
copilotkit.runtimeTransport,
|
|
JSON.stringify(copilotkit.headers)
|
|
]);
|
|
(0, react.useEffect)(() => {
|
|
if (updateFlags.length === 0) return;
|
|
const handlers = {};
|
|
if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) handlers.onMessagesChanged = () => {
|
|
forceUpdate();
|
|
};
|
|
if (updateFlags.includes(UseAgentUpdate.OnStateChanged)) handlers.onStateChanged = forceUpdate;
|
|
if (updateFlags.includes(UseAgentUpdate.OnRunStatusChanged)) {
|
|
handlers.onRunInitialized = forceUpdate;
|
|
handlers.onRunFinalized = forceUpdate;
|
|
handlers.onRunFailed = forceUpdate;
|
|
}
|
|
const subscription = agent.subscribe(handlers);
|
|
return () => subscription.unsubscribe();
|
|
}, [
|
|
agent,
|
|
forceUpdate,
|
|
JSON.stringify(updateFlags)
|
|
]);
|
|
return { agent };
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/hooks/use-agent-context.tsx
|
|
function useAgentContext(context) {
|
|
const { description, value } = context;
|
|
const { copilotkit } = useCopilotKit();
|
|
const stringValue = (0, react.useMemo)(() => {
|
|
if (typeof value === "string") return value;
|
|
return JSON.stringify(value);
|
|
}, [value]);
|
|
(0, react.useLayoutEffect)(() => {
|
|
if (!copilotkit) return;
|
|
const id = copilotkit.addContext({
|
|
description,
|
|
value: stringValue
|
|
});
|
|
return () => {
|
|
copilotkit.removeContext(id);
|
|
};
|
|
}, [
|
|
description,
|
|
stringValue,
|
|
copilotkit
|
|
]);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/hooks/use-suggestions.tsx
|
|
function useSuggestions({ agentId } = {}) {
|
|
const { copilotkit } = useCopilotKit();
|
|
const config = useCopilotChatConfiguration();
|
|
const resolvedAgentId = (0, react.useMemo)(() => {
|
|
var _ref;
|
|
return (_ref = agentId !== null && agentId !== void 0 ? agentId : config === null || config === void 0 ? void 0 : config.agentId) !== null && _ref !== void 0 ? _ref : _copilotkitnext_shared.DEFAULT_AGENT_ID;
|
|
}, [agentId, config === null || config === void 0 ? void 0 : config.agentId]);
|
|
const [suggestions, setSuggestions] = (0, react.useState)(() => {
|
|
return copilotkit.getSuggestions(resolvedAgentId).suggestions;
|
|
});
|
|
const [isLoading, setIsLoading] = (0, react.useState)(() => {
|
|
return copilotkit.getSuggestions(resolvedAgentId).isLoading;
|
|
});
|
|
(0, react.useEffect)(() => {
|
|
const result = copilotkit.getSuggestions(resolvedAgentId);
|
|
setSuggestions(result.suggestions);
|
|
setIsLoading(result.isLoading);
|
|
}, [copilotkit, resolvedAgentId]);
|
|
(0, react.useEffect)(() => {
|
|
const subscription = copilotkit.subscribe({
|
|
onSuggestionsChanged: ({ agentId: changedAgentId, suggestions }) => {
|
|
if (changedAgentId !== resolvedAgentId) return;
|
|
setSuggestions(suggestions);
|
|
},
|
|
onSuggestionsStartedLoading: ({ agentId: changedAgentId }) => {
|
|
if (changedAgentId !== resolvedAgentId) return;
|
|
setIsLoading(true);
|
|
},
|
|
onSuggestionsFinishedLoading: ({ agentId: changedAgentId }) => {
|
|
if (changedAgentId !== resolvedAgentId) return;
|
|
setIsLoading(false);
|
|
},
|
|
onSuggestionsConfigChanged: () => {
|
|
const result = copilotkit.getSuggestions(resolvedAgentId);
|
|
setSuggestions(result.suggestions);
|
|
setIsLoading(result.isLoading);
|
|
}
|
|
});
|
|
return () => {
|
|
subscription.unsubscribe();
|
|
};
|
|
}, [copilotkit, resolvedAgentId]);
|
|
return {
|
|
suggestions,
|
|
reloadSuggestions: (0, react.useCallback)(() => {
|
|
copilotkit.reloadSuggestions(resolvedAgentId);
|
|
}, [copilotkit, resolvedAgentId]),
|
|
clearSuggestions: (0, react.useCallback)(() => {
|
|
copilotkit.clearSuggestions(resolvedAgentId);
|
|
}, [copilotkit, resolvedAgentId]),
|
|
isLoading
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/hooks/use-configure-suggestions.tsx
|
|
function useConfigureSuggestions(config, deps) {
|
|
const { copilotkit } = useCopilotKit();
|
|
const chatConfig = useCopilotChatConfiguration();
|
|
const extraDeps = deps !== null && deps !== void 0 ? deps : [];
|
|
const resolvedConsumerAgentId = (0, react.useMemo)(() => {
|
|
var _chatConfig$agentId;
|
|
return (_chatConfig$agentId = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.agentId) !== null && _chatConfig$agentId !== void 0 ? _chatConfig$agentId : _copilotkitnext_shared.DEFAULT_AGENT_ID;
|
|
}, [chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.agentId]);
|
|
const rawConsumerAgentId = (0, react.useMemo)(() => config ? config.consumerAgentId : void 0, [config]);
|
|
const normalizationCacheRef = (0, react.useRef)({
|
|
serialized: null,
|
|
config: null
|
|
});
|
|
const { normalizedConfig, serializedConfig } = (0, react.useMemo)(() => {
|
|
if (!config) {
|
|
normalizationCacheRef.current = {
|
|
serialized: null,
|
|
config: null
|
|
};
|
|
return {
|
|
normalizedConfig: null,
|
|
serializedConfig: null
|
|
};
|
|
}
|
|
if (config.available === "disabled") {
|
|
normalizationCacheRef.current = {
|
|
serialized: null,
|
|
config: null
|
|
};
|
|
return {
|
|
normalizedConfig: null,
|
|
serializedConfig: null
|
|
};
|
|
}
|
|
let built;
|
|
if (isDynamicConfig(config)) built = { ...config };
|
|
else {
|
|
const normalizedSuggestions = normalizeStaticSuggestions(config.suggestions);
|
|
built = {
|
|
...config,
|
|
suggestions: normalizedSuggestions
|
|
};
|
|
}
|
|
const serialized = JSON.stringify(built);
|
|
const cache = normalizationCacheRef.current;
|
|
if (cache.serialized === serialized && cache.config) return {
|
|
normalizedConfig: cache.config,
|
|
serializedConfig: serialized
|
|
};
|
|
normalizationCacheRef.current = {
|
|
serialized,
|
|
config: built
|
|
};
|
|
return {
|
|
normalizedConfig: built,
|
|
serializedConfig: serialized
|
|
};
|
|
}, [
|
|
config,
|
|
resolvedConsumerAgentId,
|
|
...extraDeps
|
|
]);
|
|
const latestConfigRef = (0, react.useRef)(null);
|
|
latestConfigRef.current = normalizedConfig;
|
|
const previousSerializedConfigRef = (0, react.useRef)(null);
|
|
const targetAgentId = (0, react.useMemo)(() => {
|
|
if (!normalizedConfig) return resolvedConsumerAgentId;
|
|
const consumer = normalizedConfig.consumerAgentId;
|
|
if (!consumer || consumer === "*") return resolvedConsumerAgentId;
|
|
return consumer;
|
|
}, [normalizedConfig, resolvedConsumerAgentId]);
|
|
const isGlobalConfig = rawConsumerAgentId === void 0 || rawConsumerAgentId === "*";
|
|
const requestReload = (0, react.useCallback)(() => {
|
|
if (!normalizedConfig) return;
|
|
if (isGlobalConfig) {
|
|
var _copilotkit$agents;
|
|
const agents = Object.values((_copilotkit$agents = copilotkit.agents) !== null && _copilotkit$agents !== void 0 ? _copilotkit$agents : {});
|
|
for (const entry of agents) {
|
|
const agentId = entry.agentId;
|
|
if (!agentId) continue;
|
|
if (!entry.isRunning) copilotkit.reloadSuggestions(agentId);
|
|
}
|
|
return;
|
|
}
|
|
if (!targetAgentId) return;
|
|
copilotkit.reloadSuggestions(targetAgentId);
|
|
}, [
|
|
copilotkit,
|
|
isGlobalConfig,
|
|
normalizedConfig,
|
|
targetAgentId
|
|
]);
|
|
(0, react.useEffect)(() => {
|
|
if (!serializedConfig || !latestConfigRef.current) return;
|
|
const id = copilotkit.addSuggestionsConfig(latestConfigRef.current);
|
|
requestReload();
|
|
return () => {
|
|
copilotkit.removeSuggestionsConfig(id);
|
|
};
|
|
}, [
|
|
copilotkit,
|
|
serializedConfig,
|
|
requestReload
|
|
]);
|
|
(0, react.useEffect)(() => {
|
|
if (!normalizedConfig) {
|
|
previousSerializedConfigRef.current = null;
|
|
return;
|
|
}
|
|
if (serializedConfig && previousSerializedConfigRef.current === serializedConfig) return;
|
|
if (serializedConfig) previousSerializedConfigRef.current = serializedConfig;
|
|
requestReload();
|
|
}, [
|
|
normalizedConfig,
|
|
requestReload,
|
|
serializedConfig
|
|
]);
|
|
(0, react.useEffect)(() => {
|
|
if (!normalizedConfig || extraDeps.length === 0) return;
|
|
requestReload();
|
|
}, [
|
|
extraDeps.length,
|
|
normalizedConfig,
|
|
requestReload,
|
|
...extraDeps
|
|
]);
|
|
}
|
|
function isDynamicConfig(config) {
|
|
return "instructions" in config;
|
|
}
|
|
function normalizeStaticSuggestions(suggestions) {
|
|
return suggestions.map((suggestion) => {
|
|
var _suggestion$isLoading;
|
|
return {
|
|
...suggestion,
|
|
isLoading: (_suggestion$isLoading = suggestion.isLoading) !== null && _suggestion$isLoading !== void 0 ? _suggestion$isLoading : false
|
|
};
|
|
});
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/hooks/use-interrupt.tsx
|
|
const INTERRUPT_EVENT_NAME = "on_interrupt";
|
|
function isPromiseLike(value) {
|
|
return (typeof value === "object" || typeof value === "function") && value !== null && typeof Reflect.get(value, "then") === "function";
|
|
}
|
|
/**
|
|
* Handles agent interrupts (`on_interrupt`) with optional filtering, preprocessing, and resume behavior.
|
|
*
|
|
* The hook listens to custom events on the active agent, stores interrupt payloads per run,
|
|
* and surfaces a render callback once the run finalizes. Call `resolve` from your UI to resume
|
|
* execution with user-provided data.
|
|
*
|
|
* - `renderInChat: true` (default): the element is published into `<CopilotChat>` and this hook returns `void`.
|
|
* - `renderInChat: false`: the hook returns the interrupt element so you can place it anywhere in your component tree.
|
|
*
|
|
* `event.value` is typed as `any` since the interrupt payload shape depends on your agent.
|
|
* Type-narrow it in your callbacks (e.g. `handler`, `enabled`, `render`) as needed.
|
|
*
|
|
* @typeParam TResult - Inferred from `handler` return type. Exposed as `result` in `render`.
|
|
* @param config - Interrupt configuration (renderer, optional handler/filter, and render mode).
|
|
* @returns When `renderInChat` is `false`, returns the interrupt element (or `null` when idle).
|
|
* Otherwise returns `void` and publishes the element into chat. In `render`, `result` is always
|
|
* either the handler's resolved return value or `null` (including when no handler is provided,
|
|
* when filtering skips the interrupt, or when handler execution fails).
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* import { useInterrupt } from "@copilotkitnext/react";
|
|
*
|
|
* function InterruptUI() {
|
|
* useInterrupt({
|
|
* render: ({ event, resolve }) => (
|
|
* <div>
|
|
* <p>{event.value.question}</p>
|
|
* <button onClick={() => resolve({ approved: true })}>Approve</button>
|
|
* <button onClick={() => resolve({ approved: false })}>Reject</button>
|
|
* </div>
|
|
* ),
|
|
* });
|
|
*
|
|
* return null;
|
|
* }
|
|
* ```
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* import { useInterrupt } from "@copilotkitnext/react";
|
|
*
|
|
* function CustomPanel() {
|
|
* const interruptElement = useInterrupt({
|
|
* renderInChat: false,
|
|
* enabled: (event) => event.value.startsWith("approval:"),
|
|
* handler: async ({ event }) => ({ label: event.value.toUpperCase() }),
|
|
* render: ({ event, result, resolve }) => (
|
|
* <aside>
|
|
* <strong>{result?.label ?? ""}</strong>
|
|
* <button onClick={() => resolve({ value: event.value })}>Continue</button>
|
|
* </aside>
|
|
* ),
|
|
* });
|
|
*
|
|
* return <>{interruptElement}</>;
|
|
* }
|
|
* ```
|
|
*/
|
|
function useInterrupt(config) {
|
|
const { copilotkit } = useCopilotKit();
|
|
const { agent } = useAgent({ agentId: config.agentId });
|
|
const [pendingEvent, setPendingEvent] = (0, react.useState)(null);
|
|
const [handlerResult, setHandlerResult] = (0, react.useState)(null);
|
|
(0, react.useEffect)(() => {
|
|
let localInterrupt = null;
|
|
const subscription = agent.subscribe({
|
|
onCustomEvent: ({ event }) => {
|
|
if (event.name === INTERRUPT_EVENT_NAME) localInterrupt = {
|
|
name: event.name,
|
|
value: event.value
|
|
};
|
|
},
|
|
onRunStartedEvent: () => {
|
|
localInterrupt = null;
|
|
setPendingEvent(null);
|
|
},
|
|
onRunFinalized: () => {
|
|
if (localInterrupt) {
|
|
setPendingEvent(localInterrupt);
|
|
localInterrupt = null;
|
|
}
|
|
},
|
|
onRunFailed: () => {
|
|
localInterrupt = null;
|
|
}
|
|
});
|
|
return () => subscription.unsubscribe();
|
|
}, [agent]);
|
|
const resolve = (0, react.useCallback)((response) => {
|
|
setPendingEvent(null);
|
|
copilotkit.runAgent({
|
|
agent,
|
|
forwardedProps: { command: { resume: response } }
|
|
});
|
|
}, [agent, copilotkit]);
|
|
(0, react.useEffect)(() => {
|
|
if (!pendingEvent) {
|
|
setHandlerResult(null);
|
|
return;
|
|
}
|
|
if (config.enabled && !config.enabled(pendingEvent)) {
|
|
setHandlerResult(null);
|
|
return;
|
|
}
|
|
const handler = config.handler;
|
|
if (!handler) {
|
|
setHandlerResult(null);
|
|
return;
|
|
}
|
|
let cancelled = false;
|
|
const maybePromise = handler({
|
|
event: pendingEvent,
|
|
resolve
|
|
});
|
|
if (isPromiseLike(maybePromise)) Promise.resolve(maybePromise).then((resolved) => {
|
|
if (!cancelled) setHandlerResult(resolved);
|
|
}).catch(() => {
|
|
if (!cancelled) setHandlerResult(null);
|
|
});
|
|
else setHandlerResult(maybePromise);
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [
|
|
pendingEvent,
|
|
config.enabled,
|
|
config.handler,
|
|
resolve
|
|
]);
|
|
const element = (0, react.useMemo)(() => {
|
|
if (!pendingEvent) return null;
|
|
if (config.enabled && !config.enabled(pendingEvent)) return null;
|
|
return config.render({
|
|
event: pendingEvent,
|
|
result: handlerResult,
|
|
resolve
|
|
});
|
|
}, [
|
|
pendingEvent,
|
|
handlerResult,
|
|
config.enabled,
|
|
config.render,
|
|
resolve
|
|
]);
|
|
(0, react.useEffect)(() => {
|
|
if (config.renderInChat === false) return;
|
|
copilotkit.setInterruptElement(element);
|
|
return () => copilotkit.setInterruptElement(null);
|
|
}, [
|
|
element,
|
|
config.renderInChat,
|
|
copilotkit
|
|
]);
|
|
if (config.renderInChat === false) return element;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/components/chat/CopilotChatToolCallsView.tsx
|
|
function CopilotChatToolCallsView({ message, messages = [] }) {
|
|
const renderToolCall = useRenderToolCall();
|
|
if (!message.toolCalls || message.toolCalls.length === 0) return null;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: message.toolCalls.map((toolCall) => {
|
|
const toolMessage = messages.find((m) => m.role === "tool" && m.toolCallId === toolCall.id);
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderToolCall({
|
|
toolCall,
|
|
toolMessage
|
|
}) }, toolCall.id);
|
|
}) });
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/components/chat/CopilotChatAssistantMessage.tsx
|
|
function CopilotChatAssistantMessage({ message, messages, isRunning, onThumbsUp, onThumbsDown, onReadAloud, onRegenerate, additionalToolbarItems, toolbarVisible = true, markdownRenderer, toolbar, copyButton, thumbsUpButton, thumbsDownButton, readAloudButton, regenerateButton, toolCallsView, children, className, ...props }) {
|
|
var _messages;
|
|
useKatexStyles();
|
|
const boundMarkdownRenderer = renderSlot(markdownRenderer, CopilotChatAssistantMessage.MarkdownRenderer, { content: message.content || "" });
|
|
const boundCopyButton = renderSlot(copyButton, CopilotChatAssistantMessage.CopyButton, { onClick: async () => {
|
|
if (message.content) try {
|
|
await navigator.clipboard.writeText(message.content);
|
|
} catch (err) {
|
|
console.error("Failed to copy message:", err);
|
|
}
|
|
} });
|
|
const boundThumbsUpButton = renderSlot(thumbsUpButton, CopilotChatAssistantMessage.ThumbsUpButton, { onClick: onThumbsUp });
|
|
const boundThumbsDownButton = renderSlot(thumbsDownButton, CopilotChatAssistantMessage.ThumbsDownButton, { onClick: onThumbsDown });
|
|
const boundReadAloudButton = renderSlot(readAloudButton, CopilotChatAssistantMessage.ReadAloudButton, { onClick: onReadAloud });
|
|
const boundRegenerateButton = renderSlot(regenerateButton, CopilotChatAssistantMessage.RegenerateButton, { onClick: onRegenerate });
|
|
const boundToolbar = renderSlot(toolbar, CopilotChatAssistantMessage.Toolbar, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "cpk:flex cpk:items-center cpk:gap-1",
|
|
children: [
|
|
boundCopyButton,
|
|
(onThumbsUp || thumbsUpButton) && boundThumbsUpButton,
|
|
(onThumbsDown || thumbsDownButton) && boundThumbsDownButton,
|
|
(onReadAloud || readAloudButton) && boundReadAloudButton,
|
|
(onRegenerate || regenerateButton) && boundRegenerateButton,
|
|
additionalToolbarItems
|
|
]
|
|
}) });
|
|
const boundToolCallsView = renderSlot(toolCallsView, CopilotChatToolCallsView, {
|
|
message,
|
|
messages
|
|
});
|
|
const hasContent = !!(message.content && message.content.trim().length > 0);
|
|
const isLatestAssistantMessage = message.role === "assistant" && (messages === null || messages === void 0 || (_messages = messages[messages.length - 1]) === null || _messages === void 0 ? void 0 : _messages.id) === message.id;
|
|
const shouldShowToolbar = toolbarVisible && hasContent && !(isRunning && isLatestAssistantMessage);
|
|
if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-copilotkit": true,
|
|
style: { display: "contents" },
|
|
children: children({
|
|
markdownRenderer: boundMarkdownRenderer,
|
|
toolbar: boundToolbar,
|
|
toolCallsView: boundToolCallsView,
|
|
copyButton: boundCopyButton,
|
|
thumbsUpButton: boundThumbsUpButton,
|
|
thumbsDownButton: boundThumbsDownButton,
|
|
readAloudButton: boundReadAloudButton,
|
|
regenerateButton: boundRegenerateButton,
|
|
message,
|
|
messages,
|
|
isRunning,
|
|
onThumbsUp,
|
|
onThumbsDown,
|
|
onReadAloud,
|
|
onRegenerate,
|
|
additionalToolbarItems,
|
|
toolbarVisible: shouldShowToolbar
|
|
})
|
|
});
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
"data-copilotkit": true,
|
|
"data-testid": "copilot-assistant-message",
|
|
className: (0, tailwind_merge.twMerge)("copilotKitMessage copilotKitAssistantMessage", className),
|
|
...props,
|
|
"data-message-id": message.id,
|
|
children: [
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:prose cpk:max-w-full cpk:break-words cpk:dark:prose-invert",
|
|
children: boundMarkdownRenderer
|
|
}),
|
|
boundToolCallsView,
|
|
shouldShowToolbar && boundToolbar
|
|
]
|
|
});
|
|
}
|
|
(function(_CopilotChatAssistantMessage) {
|
|
_CopilotChatAssistantMessage.MarkdownRenderer = ({ content, className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(streamdown.Streamdown, {
|
|
className,
|
|
...props,
|
|
children: content !== null && content !== void 0 ? content : ""
|
|
});
|
|
_CopilotChatAssistantMessage.Toolbar = ({ className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-testid": "copilot-assistant-toolbar",
|
|
className: (0, tailwind_merge.twMerge)("cpk:w-full cpk:bg-transparent cpk:flex cpk:items-center cpk:-ml-[5px] cpk:-mt-[0px]", className),
|
|
...props
|
|
});
|
|
const ToolbarButton = _CopilotChatAssistantMessage.ToolbarButton = ({ title, children, ...props }) => {
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Tooltip, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTrigger, {
|
|
asChild: true,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
type: "button",
|
|
variant: "assistantMessageToolbarButton",
|
|
"aria-label": title,
|
|
...props,
|
|
children
|
|
})
|
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipContent, {
|
|
side: "bottom",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: title })
|
|
})] });
|
|
};
|
|
_CopilotChatAssistantMessage.CopyButton = ({ className, title, onClick, ...props }) => {
|
|
var _config$labels;
|
|
const config = useCopilotChatConfiguration();
|
|
const labels = (_config$labels = config === null || config === void 0 ? void 0 : config.labels) !== null && _config$labels !== void 0 ? _config$labels : CopilotChatDefaultLabels;
|
|
const [copied, setCopied] = (0, react.useState)(false);
|
|
const handleClick = (event) => {
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2e3);
|
|
if (onClick) onClick(event);
|
|
};
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButton, {
|
|
"data-testid": "copilot-copy-button",
|
|
title: title || labels.assistantMessageToolbarCopyMessageLabel,
|
|
onClick: handleClick,
|
|
className,
|
|
...props,
|
|
children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, { className: "cpk:size-[18px]" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Copy, { className: "cpk:size-[18px]" })
|
|
});
|
|
};
|
|
_CopilotChatAssistantMessage.ThumbsUpButton = ({ title, ...props }) => {
|
|
var _config$labels2;
|
|
const config = useCopilotChatConfiguration();
|
|
const labels = (_config$labels2 = config === null || config === void 0 ? void 0 : config.labels) !== null && _config$labels2 !== void 0 ? _config$labels2 : CopilotChatDefaultLabels;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButton, {
|
|
"data-testid": "copilot-thumbs-up-button",
|
|
title: title || labels.assistantMessageToolbarThumbsUpLabel,
|
|
...props,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ThumbsUp, { className: "cpk:size-[18px]" })
|
|
});
|
|
};
|
|
_CopilotChatAssistantMessage.ThumbsDownButton = ({ title, ...props }) => {
|
|
var _config$labels3;
|
|
const config = useCopilotChatConfiguration();
|
|
const labels = (_config$labels3 = config === null || config === void 0 ? void 0 : config.labels) !== null && _config$labels3 !== void 0 ? _config$labels3 : CopilotChatDefaultLabels;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButton, {
|
|
"data-testid": "copilot-thumbs-down-button",
|
|
title: title || labels.assistantMessageToolbarThumbsDownLabel,
|
|
...props,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ThumbsDown, { className: "cpk:size-[18px]" })
|
|
});
|
|
};
|
|
_CopilotChatAssistantMessage.ReadAloudButton = ({ title, ...props }) => {
|
|
var _config$labels4;
|
|
const config = useCopilotChatConfiguration();
|
|
const labels = (_config$labels4 = config === null || config === void 0 ? void 0 : config.labels) !== null && _config$labels4 !== void 0 ? _config$labels4 : CopilotChatDefaultLabels;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButton, {
|
|
"data-testid": "copilot-read-aloud-button",
|
|
title: title || labels.assistantMessageToolbarReadAloudLabel,
|
|
...props,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Volume2, { className: "cpk:size-[20px]" })
|
|
});
|
|
};
|
|
_CopilotChatAssistantMessage.RegenerateButton = ({ title, ...props }) => {
|
|
var _config$labels5;
|
|
const config = useCopilotChatConfiguration();
|
|
const labels = (_config$labels5 = config === null || config === void 0 ? void 0 : config.labels) !== null && _config$labels5 !== void 0 ? _config$labels5 : CopilotChatDefaultLabels;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButton, {
|
|
"data-testid": "copilot-regenerate-button",
|
|
title: title || labels.assistantMessageToolbarRegenerateLabel,
|
|
...props,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.RefreshCw, { className: "cpk:size-[18px]" })
|
|
});
|
|
};
|
|
})(CopilotChatAssistantMessage || (CopilotChatAssistantMessage = {}));
|
|
CopilotChatAssistantMessage.MarkdownRenderer.displayName = "CopilotChatAssistantMessage.MarkdownRenderer";
|
|
CopilotChatAssistantMessage.Toolbar.displayName = "CopilotChatAssistantMessage.Toolbar";
|
|
CopilotChatAssistantMessage.CopyButton.displayName = "CopilotChatAssistantMessage.CopyButton";
|
|
CopilotChatAssistantMessage.ThumbsUpButton.displayName = "CopilotChatAssistantMessage.ThumbsUpButton";
|
|
CopilotChatAssistantMessage.ThumbsDownButton.displayName = "CopilotChatAssistantMessage.ThumbsDownButton";
|
|
CopilotChatAssistantMessage.ReadAloudButton.displayName = "CopilotChatAssistantMessage.ReadAloudButton";
|
|
CopilotChatAssistantMessage.RegenerateButton.displayName = "CopilotChatAssistantMessage.RegenerateButton";
|
|
var CopilotChatAssistantMessage_default = CopilotChatAssistantMessage;
|
|
|
|
//#endregion
|
|
//#region src/components/chat/CopilotChatUserMessage.tsx
|
|
function flattenUserMessageContent(content) {
|
|
if (!content) return "";
|
|
if (typeof content === "string") return content;
|
|
return content.map((part) => {
|
|
if (part && typeof part === "object" && "type" in part && part.type === "text" && typeof part.text === "string") return part.text;
|
|
return "";
|
|
}).filter((text) => text.length > 0).join("\n");
|
|
}
|
|
function CopilotChatUserMessage({ message, onEditMessage, branchIndex, numberOfBranches, onSwitchToBranch, additionalToolbarItems, messageRenderer, toolbar, copyButton, editButton, branchNavigation, children, className, ...props }) {
|
|
const flattenedContent = (0, react.useMemo)(() => flattenUserMessageContent(message.content), [message.content]);
|
|
const BoundMessageRenderer = renderSlot(messageRenderer, CopilotChatUserMessage.MessageRenderer, { content: flattenedContent });
|
|
const BoundCopyButton = renderSlot(copyButton, CopilotChatUserMessage.CopyButton, { onClick: async () => {
|
|
if (flattenedContent) try {
|
|
await navigator.clipboard.writeText(flattenedContent);
|
|
} catch (err) {
|
|
console.error("Failed to copy message:", err);
|
|
}
|
|
} });
|
|
const BoundEditButton = renderSlot(editButton, CopilotChatUserMessage.EditButton, { onClick: () => onEditMessage === null || onEditMessage === void 0 ? void 0 : onEditMessage({ message }) });
|
|
const BoundBranchNavigation = renderSlot(branchNavigation, CopilotChatUserMessage.BranchNavigation, {
|
|
currentBranch: branchIndex,
|
|
numberOfBranches,
|
|
onSwitchToBranch,
|
|
message
|
|
});
|
|
const showBranchNavigation = numberOfBranches && numberOfBranches > 1 && onSwitchToBranch;
|
|
const BoundToolbar = renderSlot(toolbar, CopilotChatUserMessage.Toolbar, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "cpk:flex cpk:items-center cpk:gap-1 cpk:justify-end",
|
|
children: [
|
|
additionalToolbarItems,
|
|
BoundCopyButton,
|
|
onEditMessage && BoundEditButton,
|
|
showBranchNavigation && BoundBranchNavigation
|
|
]
|
|
}) });
|
|
if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-copilotkit": true,
|
|
style: { display: "contents" },
|
|
children: children({
|
|
messageRenderer: BoundMessageRenderer,
|
|
toolbar: BoundToolbar,
|
|
copyButton: BoundCopyButton,
|
|
editButton: BoundEditButton,
|
|
branchNavigation: BoundBranchNavigation,
|
|
message,
|
|
branchIndex,
|
|
numberOfBranches,
|
|
additionalToolbarItems
|
|
})
|
|
});
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
"data-copilotkit": true,
|
|
"data-testid": "copilot-user-message",
|
|
className: (0, tailwind_merge.twMerge)("copilotKitMessage copilotKitUserMessage cpk:flex cpk:flex-col cpk:items-end cpk:group cpk:pt-10", className),
|
|
"data-message-id": message.id,
|
|
...props,
|
|
children: [BoundMessageRenderer, BoundToolbar]
|
|
});
|
|
}
|
|
(function(_CopilotChatUserMessage) {
|
|
_CopilotChatUserMessage.Container = ({ children, className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: (0, tailwind_merge.twMerge)("cpk:flex cpk:flex-col cpk:items-end cpk:group", className),
|
|
...props,
|
|
children
|
|
});
|
|
_CopilotChatUserMessage.MessageRenderer = ({ content, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: (0, tailwind_merge.twMerge)("cpk:prose cpk:dark:prose-invert cpk:bg-muted cpk:relative cpk:max-w-[80%] cpk:rounded-[18px] cpk:px-4 cpk:py-1.5 cpk:data-[multiline]:py-3 cpk:inline-block cpk:whitespace-pre-wrap", className),
|
|
children: content
|
|
});
|
|
_CopilotChatUserMessage.Toolbar = ({ className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-testid": "copilot-user-toolbar",
|
|
className: (0, tailwind_merge.twMerge)("cpk:w-full cpk:bg-transparent cpk:flex cpk:items-center cpk:justify-end cpk:-mr-[5px] cpk:mt-[4px] cpk:invisible cpk:group-hover:visible", className),
|
|
...props
|
|
});
|
|
const ToolbarButton = _CopilotChatUserMessage.ToolbarButton = ({ title, children, className, ...props }) => {
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Tooltip, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTrigger, {
|
|
asChild: true,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
type: "button",
|
|
variant: "assistantMessageToolbarButton",
|
|
"aria-label": title,
|
|
className: (0, tailwind_merge.twMerge)(className),
|
|
...props,
|
|
children
|
|
})
|
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipContent, {
|
|
side: "bottom",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: title })
|
|
})] });
|
|
};
|
|
_CopilotChatUserMessage.CopyButton = ({ className, title, onClick, ...props }) => {
|
|
var _config$labels;
|
|
const config = useCopilotChatConfiguration();
|
|
const labels = (_config$labels = config === null || config === void 0 ? void 0 : config.labels) !== null && _config$labels !== void 0 ? _config$labels : CopilotChatDefaultLabels;
|
|
const [copied, setCopied] = (0, react.useState)(false);
|
|
const handleClick = (event) => {
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2e3);
|
|
if (onClick) onClick(event);
|
|
};
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButton, {
|
|
"data-testid": "copilot-user-copy-button",
|
|
title: title || labels.userMessageToolbarCopyMessageLabel,
|
|
onClick: handleClick,
|
|
className,
|
|
...props,
|
|
children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, { className: "cpk:size-[18px]" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Copy, { className: "cpk:size-[18px]" })
|
|
});
|
|
};
|
|
_CopilotChatUserMessage.EditButton = ({ className, title, ...props }) => {
|
|
var _config$labels2;
|
|
const config = useCopilotChatConfiguration();
|
|
const labels = (_config$labels2 = config === null || config === void 0 ? void 0 : config.labels) !== null && _config$labels2 !== void 0 ? _config$labels2 : CopilotChatDefaultLabels;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButton, {
|
|
"data-testid": "copilot-edit-button",
|
|
title: title || labels.userMessageToolbarEditMessageLabel,
|
|
className,
|
|
...props,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Edit, { className: "cpk:size-[18px]" })
|
|
});
|
|
};
|
|
_CopilotChatUserMessage.BranchNavigation = ({ className, currentBranch = 0, numberOfBranches = 1, onSwitchToBranch, message, ...props }) => {
|
|
if (!numberOfBranches || numberOfBranches <= 1 || !onSwitchToBranch) return null;
|
|
const canGoPrev = currentBranch > 0;
|
|
const canGoNext = currentBranch < numberOfBranches - 1;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
"data-testid": "copilot-branch-navigation",
|
|
className: (0, tailwind_merge.twMerge)("cpk:flex cpk:items-center cpk:gap-1", className),
|
|
...props,
|
|
children: [
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
type: "button",
|
|
variant: "assistantMessageToolbarButton",
|
|
onClick: () => onSwitchToBranch === null || onSwitchToBranch === void 0 ? void 0 : onSwitchToBranch({
|
|
branchIndex: currentBranch - 1,
|
|
numberOfBranches,
|
|
message
|
|
}),
|
|
disabled: !canGoPrev,
|
|
className: "cpk:h-6 cpk:w-6 cpk:p-0",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronLeft, { className: "cpk:size-[20px]" })
|
|
}),
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
className: "cpk:text-sm cpk:text-muted-foreground cpk:px-0 cpk:font-medium",
|
|
children: [
|
|
currentBranch + 1,
|
|
"/",
|
|
numberOfBranches
|
|
]
|
|
}),
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
type: "button",
|
|
variant: "assistantMessageToolbarButton",
|
|
onClick: () => onSwitchToBranch === null || onSwitchToBranch === void 0 ? void 0 : onSwitchToBranch({
|
|
branchIndex: currentBranch + 1,
|
|
numberOfBranches,
|
|
message
|
|
}),
|
|
disabled: !canGoNext,
|
|
className: "cpk:h-6 cpk:w-6 cpk:p-0",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, { className: "cpk:size-[20px]" })
|
|
})
|
|
]
|
|
});
|
|
};
|
|
})(CopilotChatUserMessage || (CopilotChatUserMessage = {}));
|
|
CopilotChatUserMessage.Container.displayName = "CopilotChatUserMessage.Container";
|
|
CopilotChatUserMessage.MessageRenderer.displayName = "CopilotChatUserMessage.MessageRenderer";
|
|
CopilotChatUserMessage.Toolbar.displayName = "CopilotChatUserMessage.Toolbar";
|
|
CopilotChatUserMessage.ToolbarButton.displayName = "CopilotChatUserMessage.ToolbarButton";
|
|
CopilotChatUserMessage.CopyButton.displayName = "CopilotChatUserMessage.CopyButton";
|
|
CopilotChatUserMessage.EditButton.displayName = "CopilotChatUserMessage.EditButton";
|
|
CopilotChatUserMessage.BranchNavigation.displayName = "CopilotChatUserMessage.BranchNavigation";
|
|
var CopilotChatUserMessage_default = CopilotChatUserMessage;
|
|
|
|
//#endregion
|
|
//#region src/components/chat/CopilotChatReasoningMessage.tsx
|
|
/**
|
|
* Formats an elapsed duration (in seconds) to a human-readable string.
|
|
*/
|
|
function formatDuration(seconds) {
|
|
if (seconds < 1) return "a few seconds";
|
|
if (seconds < 60) return `${Math.round(seconds)} seconds`;
|
|
const mins = Math.floor(seconds / 60);
|
|
const secs = Math.round(seconds % 60);
|
|
if (secs === 0) return `${mins} minute${mins > 1 ? "s" : ""}`;
|
|
return `${mins}m ${secs}s`;
|
|
}
|
|
function CopilotChatReasoningMessage({ message, messages, isRunning, header, contentView, toggle, children, className, ...props }) {
|
|
var _messages;
|
|
const isLatest = (messages === null || messages === void 0 || (_messages = messages[messages.length - 1]) === null || _messages === void 0 ? void 0 : _messages.id) === message.id;
|
|
const isStreaming = !!(isRunning && isLatest);
|
|
const hasContent = !!(message.content && message.content.length > 0);
|
|
const startTimeRef = (0, react.useRef)(null);
|
|
const [elapsed, setElapsed] = (0, react.useState)(0);
|
|
(0, react.useEffect)(() => {
|
|
if (isStreaming && startTimeRef.current === null) startTimeRef.current = Date.now();
|
|
if (!isStreaming && startTimeRef.current !== null) {
|
|
setElapsed((Date.now() - startTimeRef.current) / 1e3);
|
|
return;
|
|
}
|
|
if (!isStreaming) return;
|
|
const timer = setInterval(() => {
|
|
if (startTimeRef.current !== null) setElapsed((Date.now() - startTimeRef.current) / 1e3);
|
|
}, 1e3);
|
|
return () => clearInterval(timer);
|
|
}, [isStreaming]);
|
|
const [isOpen, setIsOpen] = (0, react.useState)(isStreaming);
|
|
(0, react.useEffect)(() => {
|
|
if (isStreaming) setIsOpen(true);
|
|
else setIsOpen(false);
|
|
}, [isStreaming]);
|
|
const label = isStreaming ? "Thinking…" : `Thought for ${formatDuration(elapsed)}`;
|
|
const boundHeader = renderSlot(header, CopilotChatReasoningMessage.Header, {
|
|
isOpen,
|
|
label,
|
|
hasContent,
|
|
isStreaming,
|
|
onClick: hasContent ? () => setIsOpen((prev) => !prev) : void 0
|
|
});
|
|
const boundContent = renderSlot(contentView, CopilotChatReasoningMessage.Content, {
|
|
isStreaming,
|
|
hasContent,
|
|
children: message.content
|
|
});
|
|
const boundToggle = renderSlot(toggle, CopilotChatReasoningMessage.Toggle, {
|
|
isOpen,
|
|
children: boundContent
|
|
});
|
|
if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-copilotkit": true,
|
|
style: { display: "contents" },
|
|
children: children({
|
|
header: boundHeader,
|
|
contentView: boundContent,
|
|
toggle: boundToggle,
|
|
message,
|
|
messages,
|
|
isRunning
|
|
})
|
|
});
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
className: (0, tailwind_merge.twMerge)("cpk:my-1", className),
|
|
"data-message-id": message.id,
|
|
...props,
|
|
children: [boundHeader, boundToggle]
|
|
});
|
|
}
|
|
(function(_CopilotChatReasoningMessage) {
|
|
_CopilotChatReasoningMessage.Header = ({ isOpen, label = "Thoughts", hasContent, isStreaming, className, children: headerChildren, ...headerProps }) => {
|
|
const isExpandable = !!hasContent;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
type: "button",
|
|
className: (0, tailwind_merge.twMerge)("cpk:inline-flex cpk:items-center cpk:gap-1 cpk:py-1 cpk:text-sm cpk:text-muted-foreground cpk:transition-colors cpk:select-none", isExpandable ? "cpk:hover:text-foreground cpk:cursor-pointer" : "cpk:cursor-default", className),
|
|
"aria-expanded": isExpandable ? isOpen : void 0,
|
|
...headerProps,
|
|
children: [
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
className: "cpk:font-medium",
|
|
children: label
|
|
}),
|
|
isStreaming && !hasContent && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
className: "cpk:inline-flex cpk:items-center cpk:ml-1",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cpk:w-1.5 cpk:h-1.5 cpk:rounded-full cpk:bg-muted-foreground cpk:animate-pulse" })
|
|
}),
|
|
headerChildren,
|
|
isExpandable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, { className: (0, tailwind_merge.twMerge)("cpk:size-3.5 cpk:shrink-0 cpk:transition-transform cpk:duration-200", isOpen && "cpk:rotate-90") })
|
|
]
|
|
});
|
|
};
|
|
_CopilotChatReasoningMessage.Content = ({ isStreaming, hasContent, className, children: contentChildren, ...contentProps }) => {
|
|
if (!hasContent && !isStreaming) return null;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: (0, tailwind_merge.twMerge)("cpk:pb-2 cpk:pt-1", className),
|
|
...contentProps,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "cpk:text-sm cpk:text-muted-foreground",
|
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(streamdown.Streamdown, { children: typeof contentChildren === "string" ? contentChildren : "" }), isStreaming && hasContent && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
className: "cpk:inline-flex cpk:items-center cpk:ml-1 cpk:align-middle",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cpk:w-2 cpk:h-2 cpk:rounded-full cpk:bg-muted-foreground cpk:animate-pulse-cursor" })
|
|
})]
|
|
})
|
|
});
|
|
};
|
|
_CopilotChatReasoningMessage.Toggle = ({ isOpen, className, children: toggleChildren, ...toggleProps }) => {
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: (0, tailwind_merge.twMerge)("cpk:grid cpk:transition-[grid-template-rows] cpk:duration-200 cpk:ease-in-out", className),
|
|
style: { gridTemplateRows: isOpen ? "1fr" : "0fr" },
|
|
...toggleProps,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:overflow-hidden",
|
|
children: toggleChildren
|
|
})
|
|
});
|
|
};
|
|
})(CopilotChatReasoningMessage || (CopilotChatReasoningMessage = {}));
|
|
CopilotChatReasoningMessage.Header.displayName = "CopilotChatReasoningMessage.Header";
|
|
CopilotChatReasoningMessage.Content.displayName = "CopilotChatReasoningMessage.Content";
|
|
CopilotChatReasoningMessage.Toggle.displayName = "CopilotChatReasoningMessage.Toggle";
|
|
var CopilotChatReasoningMessage_default = CopilotChatReasoningMessage;
|
|
|
|
//#endregion
|
|
//#region src/components/chat/CopilotChatSuggestionPill.tsx
|
|
const baseClasses = "group cpk:inline-flex cpk:h-7 cpk:sm:h-8 cpk:items-center cpk:gap-1 cpk:sm:gap-1.5 cpk:rounded-full cpk:border cpk:border-border/60 cpk:bg-background cpk:px-2.5 cpk:sm:px-3 cpk:text-[11px] cpk:sm:text-xs cpk:leading-none cpk:text-foreground cpk:transition-colors cpk:cursor-pointer cpk:hover:bg-accent/60 cpk:hover:text-foreground cpk:focus-visible:outline-none cpk:focus-visible:ring-2 cpk:focus-visible:ring-ring cpk:focus-visible:ring-offset-2 cpk:focus-visible:ring-offset-background cpk:disabled:cursor-not-allowed cpk:disabled:text-muted-foreground cpk:disabled:hover:bg-background cpk:disabled:hover:text-muted-foreground cpk:pointer-events-auto";
|
|
const labelClasses = "cpk:whitespace-nowrap cpk:font-medium cpk:leading-none";
|
|
const CopilotChatSuggestionPill = react.default.forwardRef(function CopilotChatSuggestionPill({ className, children, icon, isLoading, type, ...props }, ref) {
|
|
const showIcon = !isLoading && icon;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
ref,
|
|
"data-copilotkit": true,
|
|
"data-testid": "copilot-suggestion",
|
|
"data-slot": "suggestion-pill",
|
|
className: cn(baseClasses, className),
|
|
type: type !== null && type !== void 0 ? type : "button",
|
|
"aria-busy": isLoading || void 0,
|
|
disabled: isLoading || props.disabled,
|
|
...props,
|
|
children: [isLoading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
className: "cpk:flex cpk:h-3.5 cpk:sm:h-4 cpk:w-3.5 cpk:sm:w-4 cpk:items-center cpk:justify-center cpk:text-muted-foreground",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, {
|
|
className: "cpk:h-3.5 cpk:sm:h-4 cpk:w-3.5 cpk:sm:w-4 cpk:animate-spin",
|
|
"aria-hidden": "true"
|
|
})
|
|
}) : showIcon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
className: "cpk:flex cpk:h-3.5 cpk:sm:h-4 cpk:w-3.5 cpk:sm:w-4 cpk:items-center cpk:justify-center cpk:text-muted-foreground",
|
|
children: icon
|
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
className: labelClasses,
|
|
children
|
|
})]
|
|
});
|
|
});
|
|
CopilotChatSuggestionPill.displayName = "CopilotChatSuggestionPill";
|
|
|
|
//#endregion
|
|
//#region src/components/chat/CopilotChatSuggestionView.tsx
|
|
const DefaultContainer = react.default.forwardRef(function DefaultContainer({ className, ...props }, ref) {
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
ref,
|
|
"data-copilotkit": true,
|
|
"data-testid": "copilot-suggestions",
|
|
className: cn("cpk:flex cpk:flex-wrap cpk:items-center cpk:gap-1.5 cpk:sm:gap-2 cpk:pl-0 cpk:pr-4 cpk:sm:px-0 cpk:pointer-events-none", className),
|
|
...props
|
|
});
|
|
});
|
|
const CopilotChatSuggestionView = react.default.forwardRef(function CopilotChatSuggestionView({ suggestions, onSelectSuggestion, loadingIndexes, container, suggestion: suggestionSlot, className, children, ...restProps }, ref) {
|
|
const loadingSet = react.default.useMemo(() => {
|
|
if (!loadingIndexes || loadingIndexes.length === 0) return /* @__PURE__ */ new Set();
|
|
return new Set(loadingIndexes);
|
|
}, [loadingIndexes]);
|
|
const ContainerElement = renderSlot(container, DefaultContainer, {
|
|
ref,
|
|
className,
|
|
...restProps
|
|
});
|
|
const suggestionElements = suggestions.map((suggestion, index) => {
|
|
const isLoading = loadingSet.has(index) || suggestion.isLoading === true;
|
|
const pill = renderSlot(suggestionSlot, CopilotChatSuggestionPill, {
|
|
children: suggestion.title,
|
|
isLoading,
|
|
type: "button",
|
|
onClick: () => onSelectSuggestion === null || onSelectSuggestion === void 0 ? void 0 : onSelectSuggestion(suggestion, index)
|
|
});
|
|
return react.default.cloneElement(pill, { key: `${suggestion.title}-${index}` });
|
|
});
|
|
const boundContainer = react.default.cloneElement(ContainerElement, void 0, suggestionElements);
|
|
if (typeof children === "function") {
|
|
var _suggestions$0$title, _suggestions$, _suggestions$2;
|
|
const sampleSuggestion = renderSlot(suggestionSlot, CopilotChatSuggestionPill, {
|
|
children: (_suggestions$0$title = (_suggestions$ = suggestions[0]) === null || _suggestions$ === void 0 ? void 0 : _suggestions$.title) !== null && _suggestions$0$title !== void 0 ? _suggestions$0$title : "",
|
|
isLoading: suggestions.length > 0 ? loadingSet.has(0) || ((_suggestions$2 = suggestions[0]) === null || _suggestions$2 === void 0 ? void 0 : _suggestions$2.isLoading) === true : false,
|
|
type: "button"
|
|
});
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-copilotkit": true,
|
|
style: { display: "contents" },
|
|
children: children({
|
|
container: boundContainer,
|
|
suggestion: sampleSuggestion,
|
|
suggestions,
|
|
onSelectSuggestion,
|
|
loadingIndexes,
|
|
className,
|
|
...restProps
|
|
})
|
|
});
|
|
}
|
|
if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
"data-copilotkit": true,
|
|
style: { display: "contents" },
|
|
children: [boundContainer, children]
|
|
});
|
|
return boundContainer;
|
|
});
|
|
CopilotChatSuggestionView.displayName = "CopilotChatSuggestionView";
|
|
|
|
//#endregion
|
|
//#region src/components/chat/CopilotChatMessageView.tsx
|
|
/**
|
|
* Memoized wrapper for assistant messages to prevent re-renders when other messages change.
|
|
*/
|
|
const MemoizedAssistantMessage = react.default.memo(function MemoizedAssistantMessage({ message, messages, isRunning, AssistantMessageComponent, slotProps }) {
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AssistantMessageComponent, {
|
|
message,
|
|
messages,
|
|
isRunning,
|
|
...slotProps
|
|
});
|
|
}, (prevProps, nextProps) => {
|
|
var _nextProps$messages;
|
|
if (prevProps.message.id !== nextProps.message.id) return false;
|
|
if (prevProps.message.content !== nextProps.message.content) return false;
|
|
const prevToolCalls = prevProps.message.toolCalls;
|
|
const nextToolCalls = nextProps.message.toolCalls;
|
|
if ((prevToolCalls === null || prevToolCalls === void 0 ? void 0 : prevToolCalls.length) !== (nextToolCalls === null || nextToolCalls === void 0 ? void 0 : nextToolCalls.length)) return false;
|
|
if (prevToolCalls && nextToolCalls) for (let i = 0; i < prevToolCalls.length; i++) {
|
|
const prevTc = prevToolCalls[i];
|
|
const nextTc = nextToolCalls[i];
|
|
if (!prevTc || !nextTc) return false;
|
|
if (prevTc.id !== nextTc.id) return false;
|
|
if (prevTc.function.arguments !== nextTc.function.arguments) return false;
|
|
}
|
|
if (prevToolCalls && prevToolCalls.length > 0) {
|
|
const toolCallIds = new Set(prevToolCalls.map((tc) => tc.id));
|
|
const prevToolResults = prevProps.messages.filter((m) => m.role === "tool" && toolCallIds.has(m.toolCallId));
|
|
const nextToolResults = nextProps.messages.filter((m) => m.role === "tool" && toolCallIds.has(m.toolCallId));
|
|
if (prevToolResults.length !== nextToolResults.length) return false;
|
|
for (let i = 0; i < prevToolResults.length; i++) if (prevToolResults[i].content !== nextToolResults[i].content) return false;
|
|
}
|
|
if (((_nextProps$messages = nextProps.messages[nextProps.messages.length - 1]) === null || _nextProps$messages === void 0 ? void 0 : _nextProps$messages.id) === nextProps.message.id && prevProps.isRunning !== nextProps.isRunning) return false;
|
|
if (prevProps.AssistantMessageComponent !== nextProps.AssistantMessageComponent) return false;
|
|
if (prevProps.slotProps !== nextProps.slotProps) return false;
|
|
return true;
|
|
});
|
|
/**
|
|
* Memoized wrapper for user messages to prevent re-renders when other messages change.
|
|
*/
|
|
const MemoizedUserMessage = react.default.memo(function MemoizedUserMessage({ message, UserMessageComponent, slotProps }) {
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UserMessageComponent, {
|
|
message,
|
|
...slotProps
|
|
});
|
|
}, (prevProps, nextProps) => {
|
|
if (prevProps.message.id !== nextProps.message.id) return false;
|
|
if (prevProps.message.content !== nextProps.message.content) return false;
|
|
if (prevProps.UserMessageComponent !== nextProps.UserMessageComponent) return false;
|
|
if (prevProps.slotProps !== nextProps.slotProps) return false;
|
|
return true;
|
|
});
|
|
/**
|
|
* Memoized wrapper for activity messages to prevent re-renders when other messages change.
|
|
*/
|
|
const MemoizedActivityMessage = react.default.memo(function MemoizedActivityMessage({ message, renderActivityMessage }) {
|
|
return renderActivityMessage(message);
|
|
}, (prevProps, nextProps) => {
|
|
if (prevProps.message.id !== nextProps.message.id) return false;
|
|
if (prevProps.message.activityType !== nextProps.message.activityType) return false;
|
|
if (JSON.stringify(prevProps.message.content) !== JSON.stringify(nextProps.message.content)) return false;
|
|
return true;
|
|
});
|
|
/**
|
|
* Memoized wrapper for reasoning messages to prevent re-renders when other messages change.
|
|
*/
|
|
const MemoizedReasoningMessage = react.default.memo(function MemoizedReasoningMessage({ message, messages, isRunning, ReasoningMessageComponent, slotProps }) {
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReasoningMessageComponent, {
|
|
message,
|
|
messages,
|
|
isRunning,
|
|
...slotProps
|
|
});
|
|
}, (prevProps, nextProps) => {
|
|
var _prevProps$messages, _nextProps$messages2;
|
|
if (prevProps.message.id !== nextProps.message.id) return false;
|
|
if (prevProps.message.content !== nextProps.message.content) return false;
|
|
const prevIsLatest = ((_prevProps$messages = prevProps.messages[prevProps.messages.length - 1]) === null || _prevProps$messages === void 0 ? void 0 : _prevProps$messages.id) === prevProps.message.id;
|
|
const nextIsLatest = ((_nextProps$messages2 = nextProps.messages[nextProps.messages.length - 1]) === null || _nextProps$messages2 === void 0 ? void 0 : _nextProps$messages2.id) === nextProps.message.id;
|
|
if (prevIsLatest !== nextIsLatest) return false;
|
|
if (nextIsLatest && prevProps.isRunning !== nextProps.isRunning) return false;
|
|
if (prevProps.ReasoningMessageComponent !== nextProps.ReasoningMessageComponent) return false;
|
|
if (prevProps.slotProps !== nextProps.slotProps) return false;
|
|
return true;
|
|
});
|
|
/**
|
|
* Memoized wrapper for custom messages to prevent re-renders when other messages change.
|
|
*/
|
|
const MemoizedCustomMessage = react.default.memo(function MemoizedCustomMessage({ message, position, renderCustomMessage }) {
|
|
return renderCustomMessage({
|
|
message,
|
|
position
|
|
});
|
|
}, (prevProps, nextProps) => {
|
|
if (prevProps.message.id !== nextProps.message.id) return false;
|
|
if (prevProps.position !== nextProps.position) return false;
|
|
if (prevProps.message.content !== nextProps.message.content) return false;
|
|
if (prevProps.message.role !== nextProps.message.role) return false;
|
|
if (JSON.stringify(prevProps.stateSnapshot) !== JSON.stringify(nextProps.stateSnapshot)) return false;
|
|
return true;
|
|
});
|
|
function CopilotChatMessageView({ messages = [], assistantMessage, userMessage, reasoningMessage, cursor, isRunning = false, children, className, ...props }) {
|
|
const renderCustomMessage = useRenderCustomMessages();
|
|
const { renderActivityMessage } = useRenderActivityMessage();
|
|
const { copilotkit } = useCopilotKit();
|
|
const config = useCopilotChatConfiguration();
|
|
const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
|
|
(0, react.useEffect)(() => {
|
|
if (!(config === null || config === void 0 ? void 0 : config.agentId)) return;
|
|
const agent = copilotkit.getAgent(config.agentId);
|
|
if (!agent) return;
|
|
const subscription = agent.subscribe({ onStateChanged: forceUpdate });
|
|
return () => subscription.unsubscribe();
|
|
}, [
|
|
config === null || config === void 0 ? void 0 : config.agentId,
|
|
copilotkit,
|
|
forceUpdate
|
|
]);
|
|
const [interruptElement, setInterruptElement] = (0, react.useState)(null);
|
|
(0, react.useEffect)(() => {
|
|
setInterruptElement(copilotkit.interruptElement);
|
|
const subscription = copilotkit.subscribe({ onInterruptElementChanged: ({ interruptElement }) => {
|
|
setInterruptElement(interruptElement);
|
|
} });
|
|
return () => subscription.unsubscribe();
|
|
}, [copilotkit]);
|
|
const getStateSnapshotForMessage = (messageId) => {
|
|
var _copilotkit$getRunIdF;
|
|
if (!config) return void 0;
|
|
const resolvedRunId = (_copilotkit$getRunIdF = copilotkit.getRunIdForMessage(config.agentId, config.threadId, messageId)) !== null && _copilotkit$getRunIdF !== void 0 ? _copilotkit$getRunIdF : copilotkit.getRunIdsForThread(config.agentId, config.threadId).slice(-1)[0];
|
|
if (!resolvedRunId) return void 0;
|
|
return copilotkit.getStateByRun(config.agentId, config.threadId, resolvedRunId);
|
|
};
|
|
const deduplicatedMessages = [...new Map(messages.map((m) => [m.id, m])).values()];
|
|
if (process.env.NODE_ENV === "development" && deduplicatedMessages.length < messages.length) console.warn(`CopilotChatMessageView: Deduplicated ${messages.length - deduplicatedMessages.length} message(s) with duplicate IDs.`);
|
|
const messageElements = deduplicatedMessages.flatMap((message) => {
|
|
const elements = [];
|
|
const stateSnapshot = getStateSnapshotForMessage(message.id);
|
|
if (renderCustomMessage) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedCustomMessage, {
|
|
message,
|
|
position: "before",
|
|
renderCustomMessage,
|
|
stateSnapshot
|
|
}, `${message.id}-custom-before`));
|
|
if (message.role === "assistant") {
|
|
let AssistantComponent = CopilotChatAssistantMessage_default;
|
|
let assistantSlotProps;
|
|
if (isReactComponentType(assistantMessage)) AssistantComponent = assistantMessage;
|
|
else if (typeof assistantMessage === "string") assistantSlotProps = { className: assistantMessage };
|
|
else if (assistantMessage && typeof assistantMessage === "object") assistantSlotProps = assistantMessage;
|
|
elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedAssistantMessage, {
|
|
message,
|
|
messages,
|
|
isRunning,
|
|
AssistantMessageComponent: AssistantComponent,
|
|
slotProps: assistantSlotProps
|
|
}, message.id));
|
|
} else if (message.role === "user") {
|
|
let UserComponent = CopilotChatUserMessage_default;
|
|
let userSlotProps;
|
|
if (isReactComponentType(userMessage)) UserComponent = userMessage;
|
|
else if (typeof userMessage === "string") userSlotProps = { className: userMessage };
|
|
else if (userMessage && typeof userMessage === "object") userSlotProps = userMessage;
|
|
elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedUserMessage, {
|
|
message,
|
|
UserMessageComponent: UserComponent,
|
|
slotProps: userSlotProps
|
|
}, message.id));
|
|
} else if (message.role === "activity") {
|
|
const activityMsg = message;
|
|
elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedActivityMessage, {
|
|
message: activityMsg,
|
|
renderActivityMessage
|
|
}, message.id));
|
|
} else if (message.role === "reasoning") {
|
|
let ReasoningComponent = CopilotChatReasoningMessage_default;
|
|
let reasoningSlotProps;
|
|
if (isReactComponentType(reasoningMessage)) ReasoningComponent = reasoningMessage;
|
|
else if (typeof reasoningMessage === "string") reasoningSlotProps = { className: reasoningMessage };
|
|
else if (reasoningMessage && typeof reasoningMessage === "object") reasoningSlotProps = reasoningMessage;
|
|
elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedReasoningMessage, {
|
|
message,
|
|
messages,
|
|
isRunning,
|
|
ReasoningMessageComponent: ReasoningComponent,
|
|
slotProps: reasoningSlotProps
|
|
}, message.id));
|
|
}
|
|
if (renderCustomMessage) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedCustomMessage, {
|
|
message,
|
|
position: "after",
|
|
renderCustomMessage,
|
|
stateSnapshot
|
|
}, `${message.id}-custom-after`));
|
|
return elements;
|
|
}).filter(Boolean);
|
|
if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-copilotkit": true,
|
|
style: { display: "contents" },
|
|
children: children({
|
|
messageElements,
|
|
messages,
|
|
isRunning,
|
|
interruptElement
|
|
})
|
|
});
|
|
const lastMessage = messages[messages.length - 1];
|
|
const showCursor = isRunning && (lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.role) !== "reasoning";
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
"data-copilotkit": true,
|
|
"data-testid": "copilot-message-list",
|
|
className: (0, tailwind_merge.twMerge)("copilotKitMessages cpk:flex cpk:flex-col", className),
|
|
...props,
|
|
children: [
|
|
messageElements,
|
|
interruptElement,
|
|
showCursor && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:mt-2",
|
|
children: renderSlot(cursor, CopilotChatMessageView.Cursor, {})
|
|
})
|
|
]
|
|
});
|
|
}
|
|
CopilotChatMessageView.Cursor = function Cursor({ className, ...props }) {
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-testid": "copilot-loading-cursor",
|
|
className: (0, tailwind_merge.twMerge)("cpk:w-[11px] cpk:h-[11px] cpk:rounded-full cpk:bg-foreground cpk:animate-pulse-cursor cpk:ml-1", className),
|
|
...props
|
|
});
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/hooks/use-keyboard-height.tsx
|
|
/**
|
|
* Hook to detect mobile keyboard appearance and calculate available viewport height.
|
|
* Uses the Visual Viewport API to track keyboard state on mobile devices.
|
|
*
|
|
* @returns KeyboardState object with keyboard information
|
|
*/
|
|
function useKeyboardHeight() {
|
|
const [keyboardState, setKeyboardState] = (0, react.useState)({
|
|
isKeyboardOpen: false,
|
|
keyboardHeight: 0,
|
|
availableHeight: typeof window !== "undefined" ? window.innerHeight : 0,
|
|
viewportHeight: typeof window !== "undefined" ? window.innerHeight : 0
|
|
});
|
|
(0, react.useEffect)(() => {
|
|
if (typeof window === "undefined") return;
|
|
const visualViewport = window.visualViewport;
|
|
if (!visualViewport) return;
|
|
const updateKeyboardState = () => {
|
|
const layoutHeight = window.innerHeight;
|
|
const visualHeight = visualViewport.height;
|
|
const keyboardHeight = Math.max(0, layoutHeight - visualHeight);
|
|
setKeyboardState({
|
|
isKeyboardOpen: keyboardHeight > 150,
|
|
keyboardHeight,
|
|
availableHeight: visualHeight,
|
|
viewportHeight: layoutHeight
|
|
});
|
|
};
|
|
updateKeyboardState();
|
|
visualViewport.addEventListener("resize", updateKeyboardState);
|
|
visualViewport.addEventListener("scroll", updateKeyboardState);
|
|
return () => {
|
|
visualViewport.removeEventListener("resize", updateKeyboardState);
|
|
visualViewport.removeEventListener("scroll", updateKeyboardState);
|
|
};
|
|
}, []);
|
|
return keyboardState;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/components/chat/CopilotChatView.tsx
|
|
const FEATHER_HEIGHT = 96;
|
|
function CopilotChatView({ messageView, input, scrollView, suggestionView, welcomeScreen, messages = [], autoScroll = true, isRunning = false, suggestions, suggestionLoadingIndexes, onSelectSuggestion, onSubmitMessage, onStop, inputMode, inputValue, onInputChange, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, disclaimer, children, className, ...props }) {
|
|
const inputContainerRef = (0, react.useRef)(null);
|
|
const [inputContainerHeight, setInputContainerHeight] = (0, react.useState)(0);
|
|
const [isResizing, setIsResizing] = (0, react.useState)(false);
|
|
const resizeTimeoutRef = (0, react.useRef)(null);
|
|
const { isKeyboardOpen, keyboardHeight, availableHeight } = useKeyboardHeight();
|
|
(0, react.useEffect)(() => {
|
|
const element = inputContainerRef.current;
|
|
if (!element) return;
|
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
for (const entry of entries) {
|
|
const newHeight = entry.contentRect.height;
|
|
setInputContainerHeight((prevHeight) => {
|
|
if (newHeight !== prevHeight) {
|
|
setIsResizing(true);
|
|
if (resizeTimeoutRef.current) clearTimeout(resizeTimeoutRef.current);
|
|
resizeTimeoutRef.current = setTimeout(() => {
|
|
setIsResizing(false);
|
|
}, 250);
|
|
return newHeight;
|
|
}
|
|
return prevHeight;
|
|
});
|
|
}
|
|
});
|
|
resizeObserver.observe(element);
|
|
setInputContainerHeight(element.offsetHeight);
|
|
return () => {
|
|
resizeObserver.disconnect();
|
|
if (resizeTimeoutRef.current) clearTimeout(resizeTimeoutRef.current);
|
|
};
|
|
}, []);
|
|
const BoundMessageView = renderSlot(messageView, CopilotChatMessageView, {
|
|
messages,
|
|
isRunning
|
|
});
|
|
const BoundInput = renderSlot(input, CopilotChatInput_default, {
|
|
onSubmitMessage,
|
|
onStop,
|
|
mode: inputMode,
|
|
value: inputValue,
|
|
onChange: onInputChange,
|
|
isRunning,
|
|
onStartTranscribe,
|
|
onCancelTranscribe,
|
|
onFinishTranscribe,
|
|
onFinishTranscribeWithAudio,
|
|
positioning: "absolute",
|
|
keyboardHeight: isKeyboardOpen ? keyboardHeight : 0,
|
|
containerRef: inputContainerRef,
|
|
showDisclaimer: true,
|
|
...disclaimer !== void 0 ? { disclaimer } : {}
|
|
});
|
|
const hasSuggestions = Array.isArray(suggestions) && suggestions.length > 0;
|
|
const BoundSuggestionView = hasSuggestions ? renderSlot(suggestionView, CopilotChatSuggestionView, {
|
|
suggestions,
|
|
loadingIndexes: suggestionLoadingIndexes,
|
|
onSelectSuggestion,
|
|
className: "cpk:mb-3 cpk:lg:ml-4 cpk:lg:mr-4 cpk:ml-0 cpk:mr-0"
|
|
}) : null;
|
|
const BoundScrollView = renderSlot(scrollView, CopilotChatView.ScrollView, {
|
|
autoScroll,
|
|
inputContainerHeight,
|
|
isResizing,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
style: { paddingBottom: `${inputContainerHeight + FEATHER_HEIGHT + (hasSuggestions ? 4 : 32)}px` },
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "cpk:max-w-3xl cpk:mx-auto",
|
|
children: [BoundMessageView, hasSuggestions ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:pl-0 cpk:pr-4 cpk:sm:px-0 cpk:mt-4",
|
|
children: BoundSuggestionView
|
|
}) : null]
|
|
})
|
|
})
|
|
});
|
|
if (messages.length === 0 && !(welcomeScreen === false)) {
|
|
const BoundInputForWelcome = renderSlot(input, CopilotChatInput_default, {
|
|
onSubmitMessage,
|
|
onStop,
|
|
mode: inputMode,
|
|
value: inputValue,
|
|
onChange: onInputChange,
|
|
isRunning,
|
|
onStartTranscribe,
|
|
onCancelTranscribe,
|
|
onFinishTranscribe,
|
|
onFinishTranscribeWithAudio,
|
|
positioning: "static",
|
|
showDisclaimer: true,
|
|
...disclaimer !== void 0 ? { disclaimer } : {}
|
|
});
|
|
const BoundWelcomeScreen = renderSlot(welcomeScreen === true ? void 0 : welcomeScreen, CopilotChatView.WelcomeScreen, {
|
|
input: BoundInputForWelcome,
|
|
suggestionView: BoundSuggestionView !== null && BoundSuggestionView !== void 0 ? BoundSuggestionView : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {})
|
|
});
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-copilotkit": true,
|
|
"data-testid": "copilot-chat",
|
|
"data-copilot-running": isRunning ? "true" : "false",
|
|
className: (0, tailwind_merge.twMerge)("copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
|
|
...props,
|
|
children: BoundWelcomeScreen
|
|
});
|
|
}
|
|
if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-copilotkit": true,
|
|
style: { display: "contents" },
|
|
children: children({
|
|
messageView: BoundMessageView,
|
|
input: BoundInput,
|
|
scrollView: BoundScrollView,
|
|
suggestionView: BoundSuggestionView !== null && BoundSuggestionView !== void 0 ? BoundSuggestionView : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {})
|
|
})
|
|
});
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
"data-copilotkit": true,
|
|
"data-testid": "copilot-chat",
|
|
"data-copilot-running": isRunning ? "true" : "false",
|
|
className: (0, tailwind_merge.twMerge)("copilotKitChat cpk:relative cpk:h-full", className),
|
|
...props,
|
|
children: [BoundScrollView, BoundInput]
|
|
});
|
|
}
|
|
(function(_CopilotChatView) {
|
|
const ScrollContent = ({ children, scrollToBottomButton, feather, inputContainerHeight, isResizing }) => {
|
|
const { isAtBottom, scrollToBottom } = (0, use_stick_to_bottom.useStickToBottomContext)();
|
|
const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(use_stick_to_bottom.StickToBottom.Content, {
|
|
className: "cpk:overflow-y-scroll cpk:overflow-x-hidden",
|
|
style: {
|
|
flex: "1 1 0%",
|
|
minHeight: 0
|
|
},
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
|
|
children
|
|
})
|
|
}),
|
|
BoundFeather,
|
|
!isAtBottom && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
|
|
style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
|
|
children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
|
|
})
|
|
] });
|
|
};
|
|
_CopilotChatView.ScrollView = ({ children, autoScroll = true, scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
|
|
const [hasMounted, setHasMounted] = (0, react.useState)(false);
|
|
const { scrollRef, contentRef, scrollToBottom } = (0, use_stick_to_bottom.useStickToBottom)();
|
|
const [showScrollButton, setShowScrollButton] = (0, react.useState)(false);
|
|
(0, react.useEffect)(() => {
|
|
setHasMounted(true);
|
|
}, []);
|
|
(0, react.useEffect)(() => {
|
|
if (autoScroll) return;
|
|
const scrollElement = scrollRef.current;
|
|
if (!scrollElement) return;
|
|
const checkScroll = () => {
|
|
setShowScrollButton(!(scrollElement.scrollHeight - scrollElement.scrollTop - scrollElement.clientHeight < 10));
|
|
};
|
|
checkScroll();
|
|
scrollElement.addEventListener("scroll", checkScroll);
|
|
const resizeObserver = new ResizeObserver(checkScroll);
|
|
resizeObserver.observe(scrollElement);
|
|
return () => {
|
|
scrollElement.removeEventListener("scroll", checkScroll);
|
|
resizeObserver.disconnect();
|
|
};
|
|
}, [scrollRef, autoScroll]);
|
|
if (!hasMounted) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:overflow-y-scroll cpk:overflow-x-hidden",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
|
|
children
|
|
})
|
|
});
|
|
if (!autoScroll) {
|
|
const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
ref: scrollRef,
|
|
className: cn("cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:overflow-y-scroll cpk:overflow-x-hidden cpk:relative", className),
|
|
...props,
|
|
children: [
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
ref: contentRef,
|
|
className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
|
|
children
|
|
}),
|
|
BoundFeather,
|
|
showScrollButton && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
|
|
style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
|
|
children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
|
|
})
|
|
]
|
|
});
|
|
}
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(use_stick_to_bottom.StickToBottom, {
|
|
className: cn("cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:relative", className),
|
|
resize: "smooth",
|
|
initial: "smooth",
|
|
...props,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollContent, {
|
|
scrollToBottomButton,
|
|
feather,
|
|
inputContainerHeight,
|
|
isResizing,
|
|
children
|
|
})
|
|
});
|
|
};
|
|
_CopilotChatView.ScrollToBottomButton = ({ className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
"data-testid": "copilot-scroll-to-bottom",
|
|
variant: "outline",
|
|
size: "sm",
|
|
className: (0, tailwind_merge.twMerge)("cpk:rounded-full cpk:w-10 cpk:h-10 cpk:p-0 cpk:pointer-events-auto", "cpk:bg-white cpk:dark:bg-gray-900", "cpk:shadow-lg cpk:border cpk:border-gray-200 cpk:dark:border-gray-700", "cpk:hover:bg-gray-50 cpk:dark:hover:bg-gray-800", "cpk:flex cpk:items-center cpk:justify-center cpk:cursor-pointer", className),
|
|
...props,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, { className: "cpk:w-4 cpk:h-4 cpk:text-gray-600 cpk:dark:text-white" })
|
|
});
|
|
_CopilotChatView.Feather = ({ className, style, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: cn("cpk:absolute cpk:bottom-0 cpk:left-0 cpk:right-4 cpk:h-24 cpk:pointer-events-none cpk:z-10 cpk:bg-gradient-to-t", "cpk:from-white cpk:via-white cpk:to-transparent", "cpk:dark:from-[rgb(33,33,33)] cpk:dark:via-[rgb(33,33,33)]", className),
|
|
style,
|
|
...props
|
|
});
|
|
_CopilotChatView.WelcomeMessage = ({ className, ...props }) => {
|
|
var _config$labels;
|
|
const config = useCopilotChatConfiguration();
|
|
const labels = (_config$labels = config === null || config === void 0 ? void 0 : config.labels) !== null && _config$labels !== void 0 ? _config$labels : CopilotChatDefaultLabels;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
className: cn("cpk:text-xl cpk:sm:text-2xl cpk:font-medium cpk:text-foreground cpk:text-center", className),
|
|
...props,
|
|
children: labels.welcomeMessageText
|
|
});
|
|
};
|
|
_CopilotChatView.WelcomeScreen = ({ welcomeMessage, input, suggestionView, className, children, ...props }) => {
|
|
const BoundWelcomeMessage = renderSlot(welcomeMessage, CopilotChatView.WelcomeMessage, {});
|
|
if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-copilotkit": true,
|
|
style: { display: "contents" },
|
|
children: children({
|
|
welcomeMessage: BoundWelcomeMessage,
|
|
input,
|
|
suggestionView,
|
|
className,
|
|
...props
|
|
})
|
|
});
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-testid": "copilot-welcome-screen",
|
|
className: cn("cpk:flex-1 cpk:flex cpk:flex-col cpk:items-center cpk:justify-center cpk:px-4", className),
|
|
...props,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "cpk:w-full cpk:max-w-3xl cpk:flex cpk:flex-col cpk:items-center",
|
|
children: [
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:mb-6",
|
|
children: BoundWelcomeMessage
|
|
}),
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:w-full",
|
|
children: input
|
|
}),
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:mt-4 cpk:flex cpk:justify-center",
|
|
children: suggestionView
|
|
})
|
|
]
|
|
})
|
|
});
|
|
};
|
|
})(CopilotChatView || (CopilotChatView = {}));
|
|
var CopilotChatView_default = CopilotChatView;
|
|
|
|
//#endregion
|
|
//#region src/lib/transcription-client.ts
|
|
/**
|
|
* Convert a Blob to a base64 string
|
|
*/
|
|
async function blobToBase64(blob) {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onloadend = () => {
|
|
const base64 = reader.result.split(",")[1];
|
|
resolve(base64 !== null && base64 !== void 0 ? base64 : "");
|
|
};
|
|
reader.onerror = () => reject(/* @__PURE__ */ new Error("Failed to read audio data"));
|
|
reader.readAsDataURL(blob);
|
|
});
|
|
}
|
|
/**
|
|
* Check if an error response matches our expected format
|
|
*/
|
|
function isTranscriptionErrorResponse(data) {
|
|
return typeof data === "object" && data !== null && "error" in data && "message" in data && typeof data.error === "string" && typeof data.message === "string";
|
|
}
|
|
/**
|
|
* Parse error info from a transcription error response
|
|
*/
|
|
function parseTranscriptionError(response) {
|
|
var _response$retryable;
|
|
return {
|
|
code: response.error,
|
|
message: response.message,
|
|
retryable: (_response$retryable = response.retryable) !== null && _response$retryable !== void 0 ? _response$retryable : false
|
|
};
|
|
}
|
|
/**
|
|
* Custom error type for transcription failures.
|
|
* Extends Error with transcription-specific info for contextual error handling.
|
|
*/
|
|
var TranscriptionError = class extends Error {
|
|
constructor(info) {
|
|
super(info.message);
|
|
_defineProperty(this, "info", void 0);
|
|
this.name = "TranscriptionError";
|
|
this.info = info;
|
|
}
|
|
};
|
|
/**
|
|
* Transcribe an audio blob using the CopilotKit runtime
|
|
*
|
|
* Supports both REST mode (multipart/form-data) and single-endpoint mode (base64 JSON)
|
|
*
|
|
* @throws {TranscriptionError} When transcription fails with typed error information
|
|
*/
|
|
async function transcribeAudio(core, audioBlob, filename = "recording.webm") {
|
|
const runtimeUrl = core.runtimeUrl;
|
|
if (!runtimeUrl) throw new TranscriptionError({
|
|
code: _copilotkitnext_shared.TranscriptionErrorCode.INVALID_REQUEST,
|
|
message: "Runtime URL is not configured",
|
|
retryable: false
|
|
});
|
|
const headers = { ...core.headers };
|
|
let response;
|
|
try {
|
|
if (core.runtimeTransport === "single") {
|
|
const base64Audio = await blobToBase64(audioBlob);
|
|
headers["Content-Type"] = "application/json";
|
|
response = await fetch(runtimeUrl, {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify({
|
|
method: "transcribe",
|
|
body: {
|
|
audio: base64Audio,
|
|
mimeType: audioBlob.type || "audio/webm",
|
|
filename
|
|
}
|
|
})
|
|
});
|
|
} else {
|
|
delete headers["Content-Type"];
|
|
const formData = new FormData();
|
|
formData.append("audio", audioBlob, filename);
|
|
response = await fetch(`${runtimeUrl}/transcribe`, {
|
|
method: "POST",
|
|
headers,
|
|
body: formData
|
|
});
|
|
}
|
|
} catch (error) {
|
|
throw new TranscriptionError({
|
|
code: _copilotkitnext_shared.TranscriptionErrorCode.NETWORK_ERROR,
|
|
message: error instanceof Error ? error.message : "Network request failed",
|
|
retryable: true
|
|
});
|
|
}
|
|
if (!response.ok) {
|
|
let errorData;
|
|
try {
|
|
errorData = await response.json();
|
|
} catch (_unused) {
|
|
throw new TranscriptionError({
|
|
code: _copilotkitnext_shared.TranscriptionErrorCode.PROVIDER_ERROR,
|
|
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
retryable: response.status >= 500
|
|
});
|
|
}
|
|
if (isTranscriptionErrorResponse(errorData)) throw new TranscriptionError(parseTranscriptionError(errorData));
|
|
throw new TranscriptionError({
|
|
code: _copilotkitnext_shared.TranscriptionErrorCode.PROVIDER_ERROR,
|
|
message: typeof errorData === "object" && errorData !== null && "message" in errorData ? String(errorData.message) : "Transcription failed",
|
|
retryable: response.status >= 500
|
|
});
|
|
}
|
|
return await response.json();
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/components/chat/CopilotChat.tsx
|
|
function CopilotChat({ agentId, threadId, labels, chatView, onError, ...props }) {
|
|
var _ref;
|
|
const existingConfig = useCopilotChatConfiguration();
|
|
const resolvedAgentId = (_ref = agentId !== null && agentId !== void 0 ? agentId : existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.agentId) !== null && _ref !== void 0 ? _ref : _copilotkitnext_shared.DEFAULT_AGENT_ID;
|
|
const resolvedThreadId = (0, react.useMemo)(() => {
|
|
var _ref2;
|
|
return (_ref2 = threadId !== null && threadId !== void 0 ? threadId : existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.threadId) !== null && _ref2 !== void 0 ? _ref2 : (0, _copilotkitnext_shared.randomUUID)();
|
|
}, [threadId, existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.threadId]);
|
|
const { agent } = useAgent({ agentId: resolvedAgentId });
|
|
const { copilotkit } = useCopilotKit();
|
|
const { suggestions: autoSuggestions } = useSuggestions({ agentId: resolvedAgentId });
|
|
const onErrorRef = (0, react.useRef)(onError);
|
|
(0, react.useEffect)(() => {
|
|
onErrorRef.current = onError;
|
|
}, [onError]);
|
|
(0, react.useEffect)(() => {
|
|
if (!onErrorRef.current) return;
|
|
const subscription = copilotkit.subscribe({ onError: (event) => {
|
|
var _event$context, _event$context2;
|
|
if (((_event$context = event.context) === null || _event$context === void 0 ? void 0 : _event$context.agentId) === resolvedAgentId || !((_event$context2 = event.context) === null || _event$context2 === void 0 ? void 0 : _event$context2.agentId)) {
|
|
var _onErrorRef$current;
|
|
(_onErrorRef$current = onErrorRef.current) === null || _onErrorRef$current === void 0 || _onErrorRef$current.call(onErrorRef, {
|
|
error: event.error,
|
|
code: event.code,
|
|
context: event.context
|
|
});
|
|
}
|
|
} });
|
|
return () => {
|
|
subscription.unsubscribe();
|
|
};
|
|
}, [copilotkit, resolvedAgentId]);
|
|
const [transcribeMode, setTranscribeMode] = (0, react.useState)("input");
|
|
const [inputValue, setInputValue] = (0, react.useState)("");
|
|
const [transcriptionError, setTranscriptionError] = (0, react.useState)(null);
|
|
const [isTranscribing, setIsTranscribing] = (0, react.useState)(false);
|
|
const isTranscriptionEnabled = copilotkit.audioFileTranscriptionEnabled;
|
|
const isMediaRecorderSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined";
|
|
const { messageView: providedMessageView, suggestionView: providedSuggestionView, onStop: providedStopHandler, ...restProps } = props;
|
|
(0, react.useEffect)(() => {
|
|
let detached = false;
|
|
const connectAbortController = new AbortController();
|
|
if (agent instanceof _ag_ui_client.HttpAgent) agent.abortController = connectAbortController;
|
|
const connect = async (agent) => {
|
|
try {
|
|
await copilotkit.connectAgent({ agent });
|
|
} catch (error) {
|
|
if (detached) return;
|
|
console.error("CopilotChat: connectAgent failed", error);
|
|
}
|
|
};
|
|
agent.threadId = resolvedThreadId;
|
|
connect(agent);
|
|
return () => {
|
|
detached = true;
|
|
connectAbortController.abort();
|
|
agent.detachActiveRun();
|
|
};
|
|
}, [
|
|
resolvedThreadId,
|
|
agent,
|
|
resolvedAgentId
|
|
]);
|
|
const onSubmitInput = (0, react.useCallback)(async (value) => {
|
|
agent.addMessage({
|
|
id: (0, _copilotkitnext_shared.randomUUID)(),
|
|
role: "user",
|
|
content: value
|
|
});
|
|
setInputValue("");
|
|
try {
|
|
await copilotkit.runAgent({ agent });
|
|
} catch (error) {
|
|
console.error("CopilotChat: runAgent failed", error);
|
|
}
|
|
}, [agent]);
|
|
const handleSelectSuggestion = (0, react.useCallback)(async (suggestion) => {
|
|
agent.addMessage({
|
|
id: (0, _copilotkitnext_shared.randomUUID)(),
|
|
role: "user",
|
|
content: suggestion.message
|
|
});
|
|
try {
|
|
await copilotkit.runAgent({ agent });
|
|
} catch (error) {
|
|
console.error("CopilotChat: runAgent failed after selecting suggestion", error);
|
|
}
|
|
}, [agent]);
|
|
const stopCurrentRun = (0, react.useCallback)(() => {
|
|
try {
|
|
copilotkit.stopAgent({ agent });
|
|
} catch (error) {
|
|
console.error("CopilotChat: stopAgent failed", error);
|
|
try {
|
|
agent.abortRun();
|
|
} catch (abortError) {
|
|
console.error("CopilotChat: abortRun fallback failed", abortError);
|
|
}
|
|
}
|
|
}, [agent]);
|
|
const handleStartTranscribe = (0, react.useCallback)(() => {
|
|
setTranscriptionError(null);
|
|
setTranscribeMode("transcribe");
|
|
}, []);
|
|
const handleCancelTranscribe = (0, react.useCallback)(() => {
|
|
setTranscriptionError(null);
|
|
setTranscribeMode("input");
|
|
}, []);
|
|
const handleFinishTranscribe = (0, react.useCallback)(() => {
|
|
setTranscribeMode("input");
|
|
}, []);
|
|
const handleFinishTranscribeWithAudio = (0, react.useCallback)(async (audioBlob) => {
|
|
setIsTranscribing(true);
|
|
try {
|
|
setTranscriptionError(null);
|
|
const result = await transcribeAudio(copilotkit, audioBlob);
|
|
setInputValue((prev) => {
|
|
const trimmedPrev = prev.trim();
|
|
if (trimmedPrev) return `${trimmedPrev} ${result.text}`;
|
|
return result.text;
|
|
});
|
|
} catch (error) {
|
|
console.error("CopilotChat: Transcription failed", error);
|
|
if (error instanceof TranscriptionError) {
|
|
const { code, retryable, message } = error.info;
|
|
switch (code) {
|
|
case _copilotkitnext_shared.TranscriptionErrorCode.RATE_LIMITED:
|
|
setTranscriptionError("Too many requests. Please wait a moment.");
|
|
break;
|
|
case _copilotkitnext_shared.TranscriptionErrorCode.AUTH_FAILED:
|
|
setTranscriptionError("Authentication error. Please check your configuration.");
|
|
break;
|
|
case _copilotkitnext_shared.TranscriptionErrorCode.AUDIO_TOO_LONG:
|
|
setTranscriptionError("Recording is too long. Please try a shorter recording.");
|
|
break;
|
|
case _copilotkitnext_shared.TranscriptionErrorCode.AUDIO_TOO_SHORT:
|
|
setTranscriptionError("Recording is too short. Please try again.");
|
|
break;
|
|
case _copilotkitnext_shared.TranscriptionErrorCode.INVALID_AUDIO_FORMAT:
|
|
setTranscriptionError("Audio format not supported.");
|
|
break;
|
|
case _copilotkitnext_shared.TranscriptionErrorCode.SERVICE_NOT_CONFIGURED:
|
|
setTranscriptionError("Transcription service is not available.");
|
|
break;
|
|
case _copilotkitnext_shared.TranscriptionErrorCode.NETWORK_ERROR:
|
|
setTranscriptionError("Network error. Please check your connection.");
|
|
break;
|
|
default: setTranscriptionError(retryable ? "Transcription failed. Please try again." : message);
|
|
}
|
|
} else setTranscriptionError("Transcription failed. Please try again.");
|
|
} finally {
|
|
setIsTranscribing(false);
|
|
}
|
|
}, []);
|
|
(0, react.useEffect)(() => {
|
|
if (transcriptionError) {
|
|
const timer = setTimeout(() => {
|
|
setTranscriptionError(null);
|
|
}, 5e3);
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [transcriptionError]);
|
|
const mergedProps = (0, ts_deepmerge.merge)({
|
|
isRunning: agent.isRunning,
|
|
suggestions: autoSuggestions,
|
|
onSelectSuggestion: handleSelectSuggestion,
|
|
suggestionView: providedSuggestionView
|
|
}, {
|
|
...restProps,
|
|
...typeof providedMessageView === "string" ? { messageView: { className: providedMessageView } } : providedMessageView !== void 0 ? { messageView: providedMessageView } : {}
|
|
});
|
|
const hasMessages = agent.messages.length > 0;
|
|
const effectiveStopHandler = agent.isRunning && hasMessages ? providedStopHandler !== null && providedStopHandler !== void 0 ? providedStopHandler : stopCurrentRun : providedStopHandler;
|
|
const showTranscription = isTranscriptionEnabled && isMediaRecorderSupported;
|
|
const effectiveMode = isTranscribing ? "processing" : transcribeMode;
|
|
const RenderedChatView = renderSlot(chatView, CopilotChatView, (0, ts_deepmerge.merge)(mergedProps, {
|
|
messages: (0, react.useMemo)(() => [...agent.messages], [JSON.stringify(agent.messages)]),
|
|
onSubmitMessage: onSubmitInput,
|
|
onStop: effectiveStopHandler,
|
|
inputMode: effectiveMode,
|
|
inputValue,
|
|
onInputChange: setInputValue,
|
|
onStartTranscribe: showTranscription ? handleStartTranscribe : void 0,
|
|
onCancelTranscribe: showTranscription ? handleCancelTranscribe : void 0,
|
|
onFinishTranscribe: showTranscription ? handleFinishTranscribe : void 0,
|
|
onFinishTranscribeWithAudio: showTranscription ? handleFinishTranscribeWithAudio : void 0
|
|
}));
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CopilotChatConfigurationProvider, {
|
|
agentId: resolvedAgentId,
|
|
threadId: resolvedThreadId,
|
|
labels,
|
|
children: [transcriptionError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
style: {
|
|
position: "absolute",
|
|
bottom: "100px",
|
|
left: "50%",
|
|
transform: "translateX(-50%)",
|
|
backgroundColor: "#ef4444",
|
|
color: "white",
|
|
padding: "8px 16px",
|
|
borderRadius: "8px",
|
|
fontSize: "14px",
|
|
zIndex: 50
|
|
},
|
|
children: transcriptionError
|
|
}), RenderedChatView]
|
|
});
|
|
}
|
|
(function(_CopilotChat) {
|
|
_CopilotChat.View = CopilotChatView;
|
|
})(CopilotChat || (CopilotChat = {}));
|
|
|
|
//#endregion
|
|
//#region src/components/chat/CopilotChatToggleButton.tsx
|
|
const DefaultOpenIcon = ({ className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.MessageCircle, {
|
|
className: cn("cpk:h-6 cpk:w-6", className),
|
|
strokeWidth: 1.75,
|
|
fill: "currentColor",
|
|
...props
|
|
});
|
|
const DefaultCloseIcon = ({ className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, {
|
|
className: cn("cpk:h-6 cpk:w-6", className),
|
|
strokeWidth: 1.75,
|
|
...props
|
|
});
|
|
DefaultOpenIcon.displayName = "CopilotChatToggleButton.OpenIcon";
|
|
DefaultCloseIcon.displayName = "CopilotChatToggleButton.CloseIcon";
|
|
const ICON_TRANSITION_STYLE = Object.freeze({ transition: "opacity 120ms ease-out, transform 260ms cubic-bezier(0.22, 1, 0.36, 1)" });
|
|
const ICON_WRAPPER_BASE = "cpk:pointer-events-none cpk:absolute cpk:inset-0 cpk:flex cpk:items-center cpk:justify-center cpk:will-change-transform";
|
|
const BUTTON_BASE_CLASSES = cn("copilotKitButton", "cpk:fixed cpk:bottom-6 cpk:right-6 cpk:z-[1100] cpk:flex cpk:h-14 cpk:w-14 cpk:items-center cpk:justify-center", "cpk:rounded-full cpk:border cpk:border-primary cpk:bg-primary cpk:text-primary-foreground", "cpk:shadow-sm cpk:transition-all cpk:duration-200 cpk:ease-out", "cpk:hover:scale-[1.04] cpk:hover:shadow-md", "cpk:cursor-pointer", "cpk:active:scale-[0.96]", "cpk:focus-visible:outline-none cpk:focus-visible:ring-2 cpk:focus-visible:ring-primary/50 cpk:focus-visible:ring-offset-2 cpk:focus-visible:ring-offset-background", "cpk:disabled:pointer-events-none cpk:disabled:opacity-60");
|
|
const CopilotChatToggleButton = react.default.forwardRef(function CopilotChatToggleButton({ openIcon, closeIcon, className, ...buttonProps }, ref) {
|
|
var _configuration$labels, _configuration$isModa, _configuration$setMod;
|
|
const { onClick, type, disabled, ...restProps } = buttonProps;
|
|
const configuration = useCopilotChatConfiguration();
|
|
const labels = (_configuration$labels = configuration === null || configuration === void 0 ? void 0 : configuration.labels) !== null && _configuration$labels !== void 0 ? _configuration$labels : CopilotChatDefaultLabels;
|
|
const [fallbackOpen, setFallbackOpen] = (0, react.useState)(false);
|
|
const isOpen = (_configuration$isModa = configuration === null || configuration === void 0 ? void 0 : configuration.isModalOpen) !== null && _configuration$isModa !== void 0 ? _configuration$isModa : fallbackOpen;
|
|
const setModalOpen = (_configuration$setMod = configuration === null || configuration === void 0 ? void 0 : configuration.setModalOpen) !== null && _configuration$setMod !== void 0 ? _configuration$setMod : setFallbackOpen;
|
|
const handleClick = (event) => {
|
|
if (disabled) return;
|
|
if (onClick) onClick(event);
|
|
if (event.defaultPrevented) return;
|
|
setModalOpen(!isOpen);
|
|
};
|
|
const renderedOpenIcon = renderSlot(openIcon, DefaultOpenIcon, {
|
|
className: "cpk:h-6 cpk:w-6",
|
|
"aria-hidden": true,
|
|
focusable: false
|
|
});
|
|
const renderedCloseIcon = renderSlot(closeIcon, DefaultCloseIcon, {
|
|
className: "cpk:h-6 cpk:w-6",
|
|
"aria-hidden": true,
|
|
focusable: false
|
|
});
|
|
const openIconElement = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
"aria-hidden": "true",
|
|
"data-slot": "chat-toggle-button-open-icon",
|
|
className: ICON_WRAPPER_BASE,
|
|
style: {
|
|
...ICON_TRANSITION_STYLE,
|
|
opacity: isOpen ? 0 : 1,
|
|
transform: `scale(${isOpen ? .75 : 1}) rotate(${isOpen ? 90 : 0}deg)`
|
|
},
|
|
children: renderedOpenIcon
|
|
});
|
|
const closeIconElement = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
"aria-hidden": "true",
|
|
"data-slot": "chat-toggle-button-close-icon",
|
|
className: ICON_WRAPPER_BASE,
|
|
style: {
|
|
...ICON_TRANSITION_STYLE,
|
|
opacity: isOpen ? 1 : 0,
|
|
transform: `scale(${isOpen ? 1 : .75}) rotate(${isOpen ? 0 : -90}deg)`
|
|
},
|
|
children: renderedCloseIcon
|
|
});
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
ref,
|
|
type: type !== null && type !== void 0 ? type : "button",
|
|
"data-copilotkit": true,
|
|
"data-testid": "copilot-chat-toggle",
|
|
"data-slot": "chat-toggle-button",
|
|
"data-state": isOpen ? "open" : "closed",
|
|
className: cn(BUTTON_BASE_CLASSES, className),
|
|
"aria-label": isOpen ? labels.chatToggleCloseLabel : labels.chatToggleOpenLabel,
|
|
"aria-pressed": isOpen,
|
|
disabled,
|
|
onClick: handleClick,
|
|
...restProps,
|
|
children: [openIconElement, closeIconElement]
|
|
});
|
|
});
|
|
CopilotChatToggleButton.displayName = "CopilotChatToggleButton";
|
|
|
|
//#endregion
|
|
//#region src/components/chat/CopilotModalHeader.tsx
|
|
function CopilotModalHeader({ title, titleContent, closeButton, children, className, ...rest }) {
|
|
var _configuration$labels;
|
|
const configuration = useCopilotChatConfiguration();
|
|
const fallbackTitle = (_configuration$labels = configuration === null || configuration === void 0 ? void 0 : configuration.labels.modalHeaderTitle) !== null && _configuration$labels !== void 0 ? _configuration$labels : CopilotChatDefaultLabels.modalHeaderTitle;
|
|
const resolvedTitle = title !== null && title !== void 0 ? title : fallbackTitle;
|
|
const handleClose = (0, react.useCallback)(() => {
|
|
var _configuration$setMod;
|
|
configuration === null || configuration === void 0 || (_configuration$setMod = configuration.setModalOpen) === null || _configuration$setMod === void 0 || _configuration$setMod.call(configuration, false);
|
|
}, [configuration]);
|
|
const BoundTitle = renderSlot(titleContent, CopilotModalHeader.Title, { children: resolvedTitle });
|
|
const BoundCloseButton = renderSlot(closeButton, CopilotModalHeader.CloseButton, { onClick: handleClose });
|
|
if (children) return children({
|
|
titleContent: BoundTitle,
|
|
closeButton: BoundCloseButton,
|
|
title: resolvedTitle,
|
|
...rest
|
|
});
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
|
|
"data-testid": "copilot-modal-header",
|
|
"data-slot": "copilot-modal-header",
|
|
className: cn("copilotKitHeader", "cpk:flex cpk:items-center cpk:justify-between cpk:border-b cpk:border-border cpk:px-4 cpk:py-4", "cpk:bg-background/95 cpk:backdrop-blur cpk:supports-[backdrop-filter]:bg-background/80", className),
|
|
...rest,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "cpk:flex cpk:w-full cpk:items-center cpk:gap-2",
|
|
children: [
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:flex-1",
|
|
"aria-hidden": "true"
|
|
}),
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:flex cpk:flex-1 cpk:justify-center cpk:text-center",
|
|
children: BoundTitle
|
|
}),
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:flex cpk:flex-1 cpk:justify-end",
|
|
children: BoundCloseButton
|
|
})
|
|
]
|
|
})
|
|
});
|
|
}
|
|
CopilotModalHeader.displayName = "CopilotModalHeader";
|
|
(function(_CopilotModalHeader) {
|
|
_CopilotModalHeader.Title = ({ children, className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-testid": "copilot-header-title",
|
|
className: cn("cpk:w-full cpk:text-base cpk:font-medium cpk:leading-none cpk:tracking-tight cpk:text-foreground", className),
|
|
...props,
|
|
children
|
|
});
|
|
_CopilotModalHeader.CloseButton = ({ className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
type: "button",
|
|
"data-testid": "copilot-close-button",
|
|
className: cn("cpk:inline-flex cpk:size-8 cpk:items-center cpk:justify-center cpk:rounded-full cpk:text-muted-foreground cpk:transition cpk:cursor-pointer", "cpk:hover:bg-muted cpk:hover:text-foreground cpk:focus-visible:outline-none cpk:focus-visible:ring-2 cpk:focus-visible:ring-ring", className),
|
|
"aria-label": "Close",
|
|
...props,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, {
|
|
className: "cpk:h-4 cpk:w-4",
|
|
"aria-hidden": "true"
|
|
})
|
|
});
|
|
})(CopilotModalHeader || (CopilotModalHeader = {}));
|
|
CopilotModalHeader.Title.displayName = "CopilotModalHeader.Title";
|
|
CopilotModalHeader.CloseButton.displayName = "CopilotModalHeader.CloseButton";
|
|
|
|
//#endregion
|
|
//#region src/components/chat/CopilotSidebarView.tsx
|
|
const DEFAULT_SIDEBAR_WIDTH = 480;
|
|
const SIDEBAR_TRANSITION_MS = 260;
|
|
function CopilotSidebarView({ header, toggleButton, width, defaultOpen = true, ...props }) {
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfigurationProvider, {
|
|
isModalDefaultOpen: defaultOpen,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotSidebarViewInternal, {
|
|
header,
|
|
toggleButton,
|
|
width,
|
|
...props
|
|
})
|
|
});
|
|
}
|
|
function CopilotSidebarViewInternal({ header, toggleButton, width, ...props }) {
|
|
var _configuration$isModa;
|
|
const configuration = useCopilotChatConfiguration();
|
|
const isSidebarOpen = (_configuration$isModa = configuration === null || configuration === void 0 ? void 0 : configuration.isModalOpen) !== null && _configuration$isModa !== void 0 ? _configuration$isModa : false;
|
|
const sidebarRef = (0, react.useRef)(null);
|
|
const [sidebarWidth, setSidebarWidth] = (0, react.useState)(width !== null && width !== void 0 ? width : DEFAULT_SIDEBAR_WIDTH);
|
|
const widthToCss = (w) => {
|
|
return typeof w === "number" ? `${w}px` : w;
|
|
};
|
|
const widthToMargin = (w) => {
|
|
if (typeof w === "number") return `${w}px`;
|
|
return w;
|
|
};
|
|
(0, react.useEffect)(() => {
|
|
if (width !== void 0) return;
|
|
if (typeof window === "undefined") return;
|
|
const element = sidebarRef.current;
|
|
if (!element) return;
|
|
const updateWidth = () => {
|
|
const rect = element.getBoundingClientRect();
|
|
if (rect.width > 0) setSidebarWidth(rect.width);
|
|
};
|
|
updateWidth();
|
|
if (typeof ResizeObserver !== "undefined") {
|
|
const observer = new ResizeObserver(() => updateWidth());
|
|
observer.observe(element);
|
|
return () => observer.disconnect();
|
|
}
|
|
window.addEventListener("resize", updateWidth);
|
|
return () => window.removeEventListener("resize", updateWidth);
|
|
}, [width]);
|
|
const hasMounted = (0, react.useRef)(false);
|
|
(0, react.useLayoutEffect)(() => {
|
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
if (!window.matchMedia("(min-width: 768px)").matches) return;
|
|
if (isSidebarOpen) {
|
|
if (hasMounted.current) document.body.style.transition = `margin-inline-end ${SIDEBAR_TRANSITION_MS}ms ease`;
|
|
document.body.style.marginInlineEnd = widthToMargin(sidebarWidth);
|
|
} else if (hasMounted.current) {
|
|
document.body.style.transition = `margin-inline-end ${SIDEBAR_TRANSITION_MS}ms ease`;
|
|
document.body.style.marginInlineEnd = "";
|
|
}
|
|
hasMounted.current = true;
|
|
return () => {
|
|
document.body.style.marginInlineEnd = "";
|
|
document.body.style.transition = "";
|
|
};
|
|
}, [isSidebarOpen, sidebarWidth]);
|
|
const headerElement = renderSlot(header, CopilotModalHeader, {});
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [renderSlot(toggleButton, CopilotChatToggleButton, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("aside", {
|
|
ref: sidebarRef,
|
|
"data-copilotkit": true,
|
|
"data-testid": "copilot-sidebar",
|
|
"data-copilot-sidebar": true,
|
|
className: cn("copilotKitSidebar copilotKitWindow", "cpk:fixed cpk:right-0 cpk:top-0 cpk:z-[1200] cpk:flex", "cpk:h-[100vh] cpk:h-[100dvh] cpk:max-h-screen", "cpk:w-full", "cpk:border-l cpk:border-border cpk:bg-background cpk:text-foreground cpk:shadow-xl", "cpk:transition-transform cpk:duration-300 cpk:ease-out", isSidebarOpen ? "cpk:translate-x-0" : "cpk:translate-x-full cpk:pointer-events-none"),
|
|
style: {
|
|
["--sidebar-width"]: widthToCss(sidebarWidth),
|
|
paddingTop: "env(safe-area-inset-top)",
|
|
paddingBottom: "env(safe-area-inset-bottom)"
|
|
},
|
|
"aria-hidden": !isSidebarOpen,
|
|
"aria-label": "Copilot chat sidebar",
|
|
role: "complementary",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "cpk:flex cpk:h-full cpk:w-full cpk:flex-col cpk:overflow-hidden",
|
|
children: [headerElement, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:flex-1 cpk:overflow-hidden",
|
|
"data-sidebar-chat": true,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatView_default, { ...props })
|
|
})]
|
|
})
|
|
})] });
|
|
}
|
|
CopilotSidebarView.displayName = "CopilotSidebarView";
|
|
(function(_CopilotSidebarView) {
|
|
_CopilotSidebarView.WelcomeScreen = ({ welcomeMessage, input, suggestionView, className, children, ...props }) => {
|
|
const BoundWelcomeMessage = renderSlot(welcomeMessage, CopilotChatView_default.WelcomeMessage, {});
|
|
if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-copilotkit": true,
|
|
style: { display: "contents" },
|
|
children: children({
|
|
welcomeMessage: BoundWelcomeMessage,
|
|
input,
|
|
suggestionView,
|
|
className,
|
|
...props
|
|
})
|
|
});
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
className: cn("cpk:h-full cpk:flex cpk:flex-col", className),
|
|
...props,
|
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:flex-1 cpk:flex cpk:flex-col cpk:items-center cpk:justify-center cpk:px-4",
|
|
children: BoundWelcomeMessage
|
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:px-8 cpk:pb-4",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "cpk:max-w-3xl cpk:mx-auto",
|
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:mb-4 cpk:flex cpk:justify-center",
|
|
children: suggestionView
|
|
}), input]
|
|
})
|
|
})]
|
|
});
|
|
};
|
|
})(CopilotSidebarView || (CopilotSidebarView = {}));
|
|
|
|
//#endregion
|
|
//#region src/components/chat/CopilotPopupView.tsx
|
|
const DEFAULT_POPUP_WIDTH = 420;
|
|
const DEFAULT_POPUP_HEIGHT = 560;
|
|
const dimensionToCss = (value, fallback) => {
|
|
if (typeof value === "number" && Number.isFinite(value)) return `${value}px`;
|
|
if (typeof value === "string" && value.trim().length > 0) return value;
|
|
return `${fallback}px`;
|
|
};
|
|
function CopilotPopupView({ header, toggleButton, width, height, clickOutsideToClose, defaultOpen = true, className, ...restProps }) {
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfigurationProvider, {
|
|
isModalDefaultOpen: defaultOpen,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotPopupViewInternal, {
|
|
header,
|
|
toggleButton,
|
|
width,
|
|
height,
|
|
clickOutsideToClose,
|
|
className,
|
|
...restProps
|
|
})
|
|
});
|
|
}
|
|
function CopilotPopupViewInternal({ header, toggleButton, width, height, clickOutsideToClose, className, ...restProps }) {
|
|
var _configuration$isModa, _configuration$labels;
|
|
const configuration = useCopilotChatConfiguration();
|
|
const isPopupOpen = (_configuration$isModa = configuration === null || configuration === void 0 ? void 0 : configuration.isModalOpen) !== null && _configuration$isModa !== void 0 ? _configuration$isModa : false;
|
|
const setModalOpen = configuration === null || configuration === void 0 ? void 0 : configuration.setModalOpen;
|
|
const labels = (_configuration$labels = configuration === null || configuration === void 0 ? void 0 : configuration.labels) !== null && _configuration$labels !== void 0 ? _configuration$labels : CopilotChatDefaultLabels;
|
|
const containerRef = (0, react.useRef)(null);
|
|
const [isRendered, setIsRendered] = (0, react.useState)(isPopupOpen);
|
|
const [isAnimatingOut, setIsAnimatingOut] = (0, react.useState)(false);
|
|
(0, react.useEffect)(() => {
|
|
if (isPopupOpen) {
|
|
setIsRendered(true);
|
|
setIsAnimatingOut(false);
|
|
return;
|
|
}
|
|
if (!isRendered) return;
|
|
setIsAnimatingOut(true);
|
|
const timeout = setTimeout(() => {
|
|
setIsRendered(false);
|
|
setIsAnimatingOut(false);
|
|
}, 200);
|
|
return () => clearTimeout(timeout);
|
|
}, [isPopupOpen, isRendered]);
|
|
(0, react.useEffect)(() => {
|
|
if (!isPopupOpen) return;
|
|
if (typeof window === "undefined") return;
|
|
const handleKeyDown = (event) => {
|
|
if (event.key === "Escape") {
|
|
event.preventDefault();
|
|
setModalOpen === null || setModalOpen === void 0 || setModalOpen(false);
|
|
}
|
|
};
|
|
window.addEventListener("keydown", handleKeyDown);
|
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
}, [isPopupOpen, setModalOpen]);
|
|
(0, react.useEffect)(() => {
|
|
if (!isPopupOpen) return;
|
|
const focusTimer = setTimeout(() => {
|
|
const container = containerRef.current;
|
|
if (container && !container.contains(document.activeElement)) container.focus({ preventScroll: true });
|
|
}, 200);
|
|
return () => clearTimeout(focusTimer);
|
|
}, [isPopupOpen]);
|
|
(0, react.useEffect)(() => {
|
|
if (!isPopupOpen || !clickOutsideToClose) return;
|
|
if (typeof document === "undefined") return;
|
|
const handlePointerDown = (event) => {
|
|
const target = event.target;
|
|
if (!target) return;
|
|
const container = containerRef.current;
|
|
if (container === null || container === void 0 ? void 0 : container.contains(target)) return;
|
|
const toggleButton = document.querySelector("[data-slot='chat-toggle-button']");
|
|
if (toggleButton && toggleButton.contains(target)) return;
|
|
setModalOpen === null || setModalOpen === void 0 || setModalOpen(false);
|
|
};
|
|
document.addEventListener("pointerdown", handlePointerDown);
|
|
return () => document.removeEventListener("pointerdown", handlePointerDown);
|
|
}, [
|
|
isPopupOpen,
|
|
clickOutsideToClose,
|
|
setModalOpen
|
|
]);
|
|
const headerElement = (0, react.useMemo)(() => renderSlot(header, CopilotModalHeader, {}), [header]);
|
|
const toggleButtonElement = (0, react.useMemo)(() => renderSlot(toggleButton, CopilotChatToggleButton, {}), [toggleButton]);
|
|
const resolvedWidth = dimensionToCss(width, DEFAULT_POPUP_WIDTH);
|
|
const resolvedHeight = dimensionToCss(height, DEFAULT_POPUP_HEIGHT);
|
|
const popupStyle = (0, react.useMemo)(() => ({
|
|
"--copilot-popup-width": resolvedWidth,
|
|
"--copilot-popup-height": resolvedHeight,
|
|
"--copilot-popup-max-width": "calc(100vw - 3rem)",
|
|
"--copilot-popup-max-height": "calc(100dvh - 7.5rem)",
|
|
paddingTop: "env(safe-area-inset-top)",
|
|
paddingBottom: "env(safe-area-inset-bottom)",
|
|
paddingLeft: "env(safe-area-inset-left)",
|
|
paddingRight: "env(safe-area-inset-right)"
|
|
}), [resolvedHeight, resolvedWidth]);
|
|
const popupAnimationClass = isPopupOpen && !isAnimatingOut ? "cpk:pointer-events-auto cpk:translate-y-0 cpk:opacity-100 cpk:md:scale-100" : "cpk:pointer-events-none cpk:translate-y-4 cpk:opacity-0 cpk:md:translate-y-5 cpk:md:scale-[0.95]";
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [toggleButtonElement, isRendered ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-copilotkit": true,
|
|
className: cn("cpk:fixed cpk:inset-0 cpk:z-[1200] cpk:flex cpk:max-w-full cpk:flex-col cpk:items-stretch", "cpk:md:inset-auto cpk:md:bottom-24 cpk:md:right-6 cpk:md:items-end cpk:md:gap-4"),
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
ref: containerRef,
|
|
tabIndex: -1,
|
|
role: "dialog",
|
|
"aria-label": labels.modalHeaderTitle,
|
|
"data-testid": "copilot-popup",
|
|
"data-copilot-popup": true,
|
|
className: cn("copilotKitPopup copilotKitWindow", "cpk:relative cpk:flex cpk:h-full cpk:w-full cpk:flex-col cpk:overflow-hidden cpk:bg-background cpk:text-foreground", "cpk:origin-bottom cpk:focus:outline-none cpk:transform-gpu cpk:transition-transform cpk:transition-opacity cpk:duration-200 cpk:ease-out", "cpk:md:transition-transform cpk:md:transition-opacity", "cpk:rounded-none cpk:border cpk:border-border/0 cpk:shadow-none cpk:ring-0", "cpk:md:h-[var(--copilot-popup-height)] cpk:md:w-[var(--copilot-popup-width)]", "cpk:md:max-h-[var(--copilot-popup-max-height)] cpk:md:max-w-[var(--copilot-popup-max-width)]", "cpk:md:origin-bottom-right cpk:md:rounded-2xl cpk:md:border-border cpk:md:shadow-xl cpk:md:ring-1 cpk:md:ring-border/40", popupAnimationClass),
|
|
style: popupStyle,
|
|
children: [headerElement, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:flex-1 cpk:overflow-hidden",
|
|
"data-popup-chat": true,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatView_default, {
|
|
...restProps,
|
|
className: cn("cpk:h-full cpk:min-h-0", className)
|
|
})
|
|
})]
|
|
})
|
|
}) : null] });
|
|
}
|
|
CopilotPopupView.displayName = "CopilotPopupView";
|
|
(function(_CopilotPopupView) {
|
|
_CopilotPopupView.WelcomeScreen = ({ welcomeMessage, input, suggestionView, className, children, ...props }) => {
|
|
const BoundWelcomeMessage = renderSlot(welcomeMessage, CopilotChatView_default.WelcomeMessage, {});
|
|
if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-copilotkit": true,
|
|
style: { display: "contents" },
|
|
children: children({
|
|
welcomeMessage: BoundWelcomeMessage,
|
|
input,
|
|
suggestionView,
|
|
className,
|
|
...props
|
|
})
|
|
});
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
className: cn("cpk:h-full cpk:flex cpk:flex-col", className),
|
|
...props,
|
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:flex-1 cpk:flex cpk:flex-col cpk:items-center cpk:justify-center cpk:px-4",
|
|
children: BoundWelcomeMessage
|
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:mb-4 cpk:flex cpk:justify-center cpk:px-4",
|
|
children: suggestionView
|
|
}), input] })]
|
|
});
|
|
};
|
|
})(CopilotPopupView || (CopilotPopupView = {}));
|
|
var CopilotPopupView_default = CopilotPopupView;
|
|
|
|
//#endregion
|
|
//#region src/components/chat/CopilotSidebar.tsx
|
|
function CopilotSidebar({ header, toggleButton, defaultOpen, width, ...chatProps }) {
|
|
const SidebarViewOverride = (0, react.useMemo)(() => {
|
|
const Component = (viewProps) => {
|
|
const { header: viewHeader, toggleButton: viewToggleButton, width: viewWidth, defaultOpen: viewDefaultOpen, ...restProps } = viewProps;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotSidebarView, {
|
|
...restProps,
|
|
header: header !== null && header !== void 0 ? header : viewHeader,
|
|
toggleButton: toggleButton !== null && toggleButton !== void 0 ? toggleButton : viewToggleButton,
|
|
width: width !== null && width !== void 0 ? width : viewWidth,
|
|
defaultOpen: defaultOpen !== null && defaultOpen !== void 0 ? defaultOpen : viewDefaultOpen
|
|
});
|
|
};
|
|
return Object.assign(Component, CopilotChatView_default);
|
|
}, [
|
|
header,
|
|
toggleButton,
|
|
width,
|
|
defaultOpen
|
|
]);
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChat, {
|
|
welcomeScreen: CopilotSidebarView.WelcomeScreen,
|
|
...chatProps,
|
|
chatView: SidebarViewOverride
|
|
});
|
|
}
|
|
CopilotSidebar.displayName = "CopilotSidebar";
|
|
|
|
//#endregion
|
|
//#region src/components/chat/CopilotPopup.tsx
|
|
function CopilotPopup({ header, toggleButton, defaultOpen, width, height, clickOutsideToClose, ...chatProps }) {
|
|
const PopupViewOverride = (0, react.useMemo)(() => {
|
|
const Component = (viewProps) => {
|
|
const { header: viewHeader, toggleButton: viewToggleButton, width: viewWidth, height: viewHeight, clickOutsideToClose: viewClickOutsideToClose, defaultOpen: viewDefaultOpen, ...restProps } = viewProps;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotPopupView_default, {
|
|
...restProps,
|
|
header: header !== null && header !== void 0 ? header : viewHeader,
|
|
toggleButton: toggleButton !== null && toggleButton !== void 0 ? toggleButton : viewToggleButton,
|
|
width: width !== null && width !== void 0 ? width : viewWidth,
|
|
height: height !== null && height !== void 0 ? height : viewHeight,
|
|
clickOutsideToClose: clickOutsideToClose !== null && clickOutsideToClose !== void 0 ? clickOutsideToClose : viewClickOutsideToClose,
|
|
defaultOpen: defaultOpen !== null && defaultOpen !== void 0 ? defaultOpen : viewDefaultOpen
|
|
});
|
|
};
|
|
return Object.assign(Component, CopilotChatView_default);
|
|
}, [
|
|
clickOutsideToClose,
|
|
header,
|
|
toggleButton,
|
|
height,
|
|
width,
|
|
defaultOpen
|
|
]);
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChat, {
|
|
welcomeScreen: CopilotPopupView_default.WelcomeScreen,
|
|
...chatProps,
|
|
chatView: PopupViewOverride
|
|
});
|
|
}
|
|
CopilotPopup.displayName = "CopilotPopup";
|
|
|
|
//#endregion
|
|
//#region src/components/WildcardToolCallRender.tsx
|
|
const WildcardToolCallRender = defineToolCallRenderer({
|
|
name: "*",
|
|
render: ({ args, result, name, status }) => {
|
|
const [isExpanded, setIsExpanded] = (0, react.useState)(false);
|
|
const statusString = String(status);
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:mt-2 cpk:pb-2",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "cpk:rounded-xl cpk:border cpk:border-zinc-200/60 cpk:dark:border-zinc-800/60 cpk:bg-white/70 cpk:dark:bg-zinc-900/50 cpk:shadow-sm cpk:backdrop-blur cpk:p-4",
|
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "cpk:flex cpk:items-center cpk:justify-between cpk:gap-3 cpk:cursor-pointer",
|
|
onClick: () => setIsExpanded(!isExpanded),
|
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "cpk:flex cpk:items-center cpk:gap-2 cpk:min-w-0",
|
|
children: [
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
className: `cpk:h-4 cpk:w-4 cpk:text-zinc-500 cpk:dark:text-zinc-400 cpk:transition-transform ${isExpanded ? "cpk:rotate-90" : ""}`,
|
|
fill: "none",
|
|
viewBox: "0 0 24 24",
|
|
strokeWidth: 2,
|
|
stroke: "currentColor",
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
strokeLinecap: "round",
|
|
strokeLinejoin: "round",
|
|
d: "M8.25 4.5l7.5 7.5-7.5 7.5"
|
|
})
|
|
}),
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cpk:inline-block cpk:h-2 cpk:w-2 cpk:rounded-full cpk:bg-blue-500" }),
|
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
className: "cpk:truncate cpk:text-sm cpk:font-medium cpk:text-zinc-900 cpk:dark:text-zinc-100",
|
|
children: name
|
|
})
|
|
]
|
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
className: `cpk:inline-flex cpk:items-center cpk:rounded-full cpk:px-2 cpk:py-1 cpk:text-xs cpk:font-medium ${statusString === "inProgress" || statusString === "executing" ? "cpk:bg-amber-100 cpk:text-amber-800 cpk:dark:bg-amber-500/15 cpk:dark:text-amber-400" : statusString === "complete" ? "cpk:bg-emerald-100 cpk:text-emerald-800 cpk:dark:bg-emerald-500/15 cpk:dark:text-emerald-400" : "cpk:bg-zinc-100 cpk:text-zinc-800 cpk:dark:bg-zinc-700/40 cpk:dark:text-zinc-300"}`,
|
|
children: String(status)
|
|
})]
|
|
}), isExpanded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
className: "cpk:mt-3 cpk:grid cpk:gap-4",
|
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:text-xs cpk:uppercase cpk:tracking-wide cpk:text-zinc-500 cpk:dark:text-zinc-400",
|
|
children: "Arguments"
|
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
className: "cpk:mt-2 cpk:max-h-64 cpk:overflow-auto cpk:rounded-md cpk:bg-zinc-50 cpk:dark:bg-zinc-800/60 cpk:p-3 cpk:text-xs cpk:leading-relaxed cpk:text-zinc-800 cpk:dark:text-zinc-200 cpk:whitespace-pre-wrap cpk:break-words",
|
|
children: JSON.stringify(args !== null && args !== void 0 ? args : {}, null, 2)
|
|
})] }), result !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:text-xs cpk:uppercase cpk:tracking-wide cpk:text-zinc-500 cpk:dark:text-zinc-400",
|
|
children: "Result"
|
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
className: "cpk:mt-2 cpk:max-h-64 cpk:overflow-auto cpk:rounded-md cpk:bg-zinc-50 cpk:dark:bg-zinc-800/60 cpk:p-3 cpk:text-xs cpk:leading-relaxed cpk:text-zinc-800 cpk:dark:text-zinc-200 cpk:whitespace-pre-wrap cpk:break-words",
|
|
children: typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
})] })]
|
|
})]
|
|
})
|
|
});
|
|
}
|
|
});
|
|
|
|
//#endregion
|
|
exports.AudioRecorderError = AudioRecorderError;
|
|
Object.defineProperty(exports, 'CopilotChat', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return CopilotChat;
|
|
}
|
|
});
|
|
exports.CopilotChatAssistantMessage = CopilotChatAssistantMessage_default;
|
|
exports.CopilotChatAudioRecorder = CopilotChatAudioRecorder;
|
|
exports.CopilotChatConfigurationProvider = CopilotChatConfigurationProvider;
|
|
exports.CopilotChatInput = CopilotChatInput_default;
|
|
exports.CopilotChatMessageView = CopilotChatMessageView;
|
|
exports.CopilotChatReasoningMessage = CopilotChatReasoningMessage_default;
|
|
exports.CopilotChatSuggestionPill = CopilotChatSuggestionPill;
|
|
exports.CopilotChatSuggestionView = CopilotChatSuggestionView;
|
|
exports.CopilotChatToggleButton = CopilotChatToggleButton;
|
|
exports.CopilotChatToggleButtonCloseIcon = DefaultCloseIcon;
|
|
exports.CopilotChatToggleButtonOpenIcon = DefaultOpenIcon;
|
|
exports.CopilotChatToolCallsView = CopilotChatToolCallsView;
|
|
exports.CopilotChatUserMessage = CopilotChatUserMessage_default;
|
|
exports.CopilotChatView = CopilotChatView_default;
|
|
exports.CopilotKitCoreReact = CopilotKitCoreReact;
|
|
exports.CopilotKitInspector = CopilotKitInspector;
|
|
exports.CopilotKitProvider = CopilotKitProvider;
|
|
Object.defineProperty(exports, 'CopilotModalHeader', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return CopilotModalHeader;
|
|
}
|
|
});
|
|
exports.CopilotPopup = CopilotPopup;
|
|
Object.defineProperty(exports, 'CopilotPopupView', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return CopilotPopupView;
|
|
}
|
|
});
|
|
exports.CopilotSidebar = CopilotSidebar;
|
|
Object.defineProperty(exports, 'CopilotSidebarView', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return CopilotSidebarView;
|
|
}
|
|
});
|
|
exports.MCPAppsActivityContentSchema = MCPAppsActivityContentSchema;
|
|
exports.MCPAppsActivityRenderer = MCPAppsActivityRenderer;
|
|
exports.MCPAppsActivityType = MCPAppsActivityType;
|
|
exports.UseAgentUpdate = UseAgentUpdate;
|
|
exports.WildcardToolCallRender = WildcardToolCallRender;
|
|
exports.createA2UIMessageRenderer = createA2UIMessageRenderer;
|
|
exports.defineToolCallRenderer = defineToolCallRenderer;
|
|
exports.useAgent = useAgent;
|
|
exports.useAgentContext = useAgentContext;
|
|
exports.useComponent = useComponent;
|
|
exports.useConfigureSuggestions = useConfigureSuggestions;
|
|
exports.useCopilotChatConfiguration = useCopilotChatConfiguration;
|
|
exports.useCopilotKit = useCopilotKit;
|
|
exports.useDefaultRenderTool = useDefaultRenderTool;
|
|
exports.useFrontendTool = useFrontendTool;
|
|
exports.useHumanInTheLoop = useHumanInTheLoop;
|
|
exports.useInterrupt = useInterrupt;
|
|
exports.useRenderActivityMessage = useRenderActivityMessage;
|
|
exports.useRenderCustomMessages = useRenderCustomMessages;
|
|
exports.useRenderTool = useRenderTool;
|
|
exports.useRenderToolCall = useRenderToolCall;
|
|
exports.useSuggestions = useSuggestions;
|
|
Object.keys(_ag_ui_client).forEach(function (k) {
|
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
enumerable: true,
|
|
get: function () { return _ag_ui_client[k]; }
|
|
});
|
|
});
|
|
|
|
});
|
|
//# sourceMappingURL=index.umd.js.map
|