rdesign/frontend/node_modules/@copilotkitnext/react/dist/components/chat/CopilotChatInput.mjs

680 lines
28 KiB
JavaScript

import { CopilotChatDefaultLabels, useCopilotChatConfiguration } from "../../providers/CopilotChatConfigurationProvider.mjs";
import { cn } from "../../lib/utils.mjs";
import { Button } from "../ui/button.mjs";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip.mjs";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "../ui/dropdown-menu.mjs";
import { CopilotChatAudioRecorder } from "./CopilotChatAudioRecorder.mjs";
import { renderSlot } from "../../lib/slots.mjs";
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
import { twMerge } from "tailwind-merge";
import { ArrowUp, Check, Loader2, Mic, Plus, Square, X } from "lucide-react";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
//#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 }) {
const isControlled = value !== void 0;
const [internalValue, setInternalValue] = useState(() => value ?? "");
useEffect(() => {
if (!isControlled && value !== void 0) setInternalValue(value);
}, [isControlled, value]);
const resolvedValue = isControlled ? value ?? "" : internalValue;
const [layout, setLayout] = useState("compact");
const ignoreResizeRef = useRef(false);
const resizeEvaluationRafRef = useRef(null);
const isExpanded = mode === "input" && layout === "expanded";
const [commandQuery, setCommandQuery] = useState(null);
const [slashHighlightIndex, setSlashHighlightIndex] = useState(0);
const inputRef = useRef(null);
const gridRef = useRef(null);
const addButtonContainerRef = useRef(null);
const actionsContainerRef = useRef(null);
const audioRecorderRef = useRef(null);
const slashMenuRef = useRef(null);
const config = useCopilotChatConfiguration();
const labels = config?.labels ?? CopilotChatDefaultLabels;
const previousModalStateRef = useRef(void 0);
const measurementCanvasRef = useRef(null);
const measurementsRef = useRef({
singleLineHeight: 0,
maxHeight: 0,
paddingLeft: 0,
paddingRight: 0
});
const commandItems = 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 = 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]);
useEffect(() => {
if (!autoFocus) {
previousModalStateRef.current = config?.isModalOpen;
return;
}
if (config?.isModalOpen && !previousModalStateRef.current) inputRef.current?.focus();
previousModalStateRef.current = config?.isModalOpen;
}, [config?.isModalOpen, autoFocus]);
useEffect(() => {
if (commandItems.length === 0 && commandQuery !== null) setCommandQuery(null);
}, [commandItems.length, commandQuery]);
const previousCommandQueryRef = useRef(null);
useEffect(() => {
if (commandQuery !== null && commandQuery !== previousCommandQueryRef.current && filteredCommands.length > 0) setSlashHighlightIndex(0);
previousCommandQueryRef.current = commandQuery;
}, [commandQuery, filteredCommands.length]);
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
]);
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]);
useEffect(() => {
if (mode !== "input") {
setLayout("compact");
setCommandQuery(null);
}
}, [mode]);
const updateSlashState = useCallback((value) => {
if (commandItems.length === 0) {
setCommandQuery((prev) => prev === null ? prev : null);
return;
}
if (value.startsWith("/")) {
const query = (value.split(/\r?\n/, 1)[0] ?? "").slice(1);
setCommandQuery((prev) => prev === query ? prev : query);
} else setCommandQuery((prev) => prev === null ? prev : null);
}, [commandItems.length]);
useEffect(() => {
updateSlashState(resolvedValue);
}, [resolvedValue, updateSlashState]);
const handleChange = (e) => {
const nextValue = e.target.value;
if (!isControlled) setInternalValue(nextValue);
onChange?.(nextValue);
updateSlashState(nextValue);
};
const clearInputValue = useCallback(() => {
if (!isControlled) setInternalValue("");
if (onChange) onChange("");
}, [isControlled, onChange]);
const runCommand = useCallback((item) => {
clearInputValue();
item.action?.();
setCommandQuery(null);
setSlashHighlightIndex(0);
requestAnimationFrame(() => {
inputRef.current?.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?.();
else send();
}
};
const send = () => {
if (!onSubmitMessage) return;
const trimmed = resolvedValue.trim();
if (!trimmed) return;
onSubmitMessage(trimmed);
if (!isControlled) {
setInternalValue("");
onChange?.("");
}
if (inputRef.current) inputRef.current.focus();
};
const BoundTextArea = renderSlot(textArea, CopilotChatInput.TextArea, {
ref: inputRef,
value: resolvedValue,
onChange: handleChange,
onKeyDown: handleKeyDown,
autoFocus,
className: twMerge("cpk:w-full cpk:py-3", isExpanded ? "cpk:px-5" : "cpk:pr-5")
});
const isProcessing = mode !== "transcribe" && isRunning;
const canSend = resolvedValue.trim().length > 0 && !!onSubmitMessage;
const canStop = !!onStop;
const handleSendButtonClick = () => {
if (isProcessing) {
onStop?.();
return;
}
send();
};
const BoundAudioRecorder = renderSlot(audioRecorder, CopilotChatAudioRecorder, { ref: audioRecorderRef });
const BoundSendButton = renderSlot(sendButton, CopilotChatInput.SendButton, {
onClick: handleSendButtonClick,
disabled: isProcessing ? !canStop : !canSend,
children: isProcessing && canStop ? /* @__PURE__ */ jsx(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 = useCallback(async () => {
const recorder = audioRecorderRef.current;
if (recorder && recorder.state === "recording") try {
const audioBlob = await recorder.stop();
if (onFinishTranscribeWithAudio) await onFinishTranscribeWithAudio(audioBlob);
} catch (error) {
console.error("Failed to stop recording:", error);
}
onFinishTranscribe?.();
}, [onFinishTranscribe, onFinishTranscribeWithAudio]);
const BoundFinishTranscribeButton = renderSlot(finishTranscribeButton, CopilotChatInput.FinishTranscribeButton, { onClick: handleFinishTranscribe });
const BoundAddMenuButton = renderSlot(addMenuButton, CopilotChatInput.AddMenuButton, {
disabled: mode === "transcribe",
onAddFile,
toolsMenu
});
const BoundDisclaimer = renderSlot(disclaimer, CopilotChatInput.Disclaimer, {});
const shouldShowDisclaimer = showDisclaimer ?? positioning === "absolute";
if (children) {
const childProps = {
textArea: BoundTextArea,
audioRecorder: BoundAudioRecorder,
sendButton: BoundSendButton,
startTranscribeButton: BoundStartTranscribeButton,
cancelTranscribeButton: BoundCancelTranscribeButton,
finishTranscribeButton: BoundFinishTranscribeButton,
addMenuButton: BoundAddMenuButton,
disclaimer: BoundDisclaimer,
onSubmitMessage,
onStop,
isRunning,
onStartTranscribe,
onCancelTranscribe,
onFinishTranscribe,
onAddFile,
mode,
toolsMenu,
autoFocus,
positioning,
keyboardHeight,
showDisclaimer: shouldShowDisclaimer
};
return /* @__PURE__ */ 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 = 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 = 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 = useCallback((nextLayout) => {
setLayout((prev) => {
if (prev === nextLayout) return prev;
ignoreResizeRef.current = true;
return nextLayout;
});
}, []);
const evaluateLayout = 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) {
const addWidth = addContainer.getBoundingClientRect().width;
const actionsWidth = actionsContainer.getBoundingClientRect().width;
const compactWidth = Math.max(gridAvailableWidth - addWidth - actionsWidth - columnGap * 2, 0);
const canvas = measurementCanvasRef.current ?? 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
]);
useLayoutEffect(() => {
evaluateLayout();
}, [evaluateLayout]);
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;
useEffect(() => {
if (!slashMenuVisible || slashHighlightIndex < 0) return;
(slashMenuRef.current?.querySelector(`[data-slash-index="${slashHighlightIndex}"]`))?.scrollIntoView({ block: "nearest" });
}, [slashMenuVisible, slashHighlightIndex]);
const slashMenu = slashMenuVisible ? /* @__PURE__ */ 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__ */ 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__ */ jsx("button", {
type: "button",
role: "option",
"aria-selected": isActive,
"data-active": isActive ? "true" : void 0,
"data-slash-index": index,
className: 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__ */ jsx("div", {
"data-testid": "copilot-chat-input",
className: 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__ */ jsxs("div", {
ref: gridRef,
className: 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__ */ jsx("div", {
ref: addButtonContainerRef,
className: twMerge("cpk:flex cpk:items-center", isExpanded ? "cpk:row-start-2" : "cpk:row-start-1", "cpk:col-start-1"),
children: BoundAddMenuButton
}),
/* @__PURE__ */ jsx("div", {
className: 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__ */ jsx("div", {
className: "cpk:flex cpk:w-full cpk:items-center cpk:justify-center cpk:py-3 cpk:px-5",
children: /* @__PURE__ */ jsx(Loader2, { className: "cpk:size-[26px] cpk:animate-spin cpk:text-muted-foreground" })
}) : /* @__PURE__ */ jsxs(Fragment, { children: [BoundTextArea, slashMenu] })
}),
/* @__PURE__ */ jsx("div", {
ref: actionsContainerRef,
className: 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__ */ jsxs(Fragment, { children: [onCancelTranscribe && BoundCancelTranscribeButton, onFinishTranscribe && BoundFinishTranscribeButton] }) : /* @__PURE__ */ jsxs(Fragment, { children: [onStartTranscribe && BoundStartTranscribeButton, BoundSendButton] })
})
]
})
});
return /* @__PURE__ */ 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__ */ 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__ */ jsx("div", {
className: "cpk:mr-[10px]",
children: /* @__PURE__ */ jsx(Button, {
type: "button",
"data-testid": "copilot-send-button",
variant: "chatInputToolbarPrimary",
size: "chatInputToolbarIcon",
className,
...props,
children: children ?? /* @__PURE__ */ jsx(ArrowUp, { className: "cpk:size-[18px]" })
})
});
const ToolbarButton = _CopilotChatInput.ToolbarButton = ({ icon, labelKey, defaultClassName, className, ...props }) => {
const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
return /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
asChild: true,
children: /* @__PURE__ */ jsx(Button, {
type: "button",
variant: "chatInputToolbarSecondary",
size: "chatInputToolbarIcon",
className: twMerge(defaultClassName, className),
...props,
children: icon
})
}), /* @__PURE__ */ jsx(TooltipContent, {
side: "bottom",
children: /* @__PURE__ */ jsx("p", { children: labels[labelKey] })
})] });
};
_CopilotChatInput.StartTranscribeButton = (props) => /* @__PURE__ */ jsx(ToolbarButton, {
"data-testid": "copilot-start-transcribe-button",
icon: /* @__PURE__ */ jsx(Mic, { className: "cpk:size-[18px]" }),
labelKey: "chatInputToolbarStartTranscribeButtonLabel",
defaultClassName: "cpk:mr-2",
...props
});
_CopilotChatInput.CancelTranscribeButton = (props) => /* @__PURE__ */ jsx(ToolbarButton, {
"data-testid": "copilot-cancel-transcribe-button",
icon: /* @__PURE__ */ jsx(X, { className: "cpk:size-[18px]" }),
labelKey: "chatInputToolbarCancelTranscribeButtonLabel",
defaultClassName: "cpk:mr-2",
...props
});
_CopilotChatInput.FinishTranscribeButton = (props) => /* @__PURE__ */ jsx(ToolbarButton, {
"data-testid": "copilot-finish-transcribe-button",
icon: /* @__PURE__ */ jsx(Check, { className: "cpk:size-[18px]" }),
labelKey: "chatInputToolbarFinishTranscribeButtonLabel",
defaultClassName: "cpk:mr-[10px]",
...props
});
_CopilotChatInput.AddMenuButton = ({ className, toolsMenu, onAddFile, disabled, ...props }) => {
const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
const menuItems = 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 = useCallback((items) => items.map((item, index) => {
if (item === "-") return /* @__PURE__ */ jsx(DropdownMenuSeparator, {}, `separator-${index}`);
if (item.items && item.items.length > 0) return /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [/* @__PURE__ */ jsx(DropdownMenuSubTrigger, { children: item.label }), /* @__PURE__ */ jsx(DropdownMenuSubContent, { children: renderMenuItems(item.items) })] }, `group-${index}`);
return /* @__PURE__ */ jsx(DropdownMenuItem, {
onClick: item.action,
children: item.label
}, `item-${index}`);
}), []);
const hasMenuItems = menuItems.length > 0;
const isDisabled = disabled || !hasMenuItems;
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
asChild: true,
children: /* @__PURE__ */ jsx(DropdownMenuTrigger, {
asChild: true,
children: /* @__PURE__ */ jsx(Button, {
type: "button",
"data-testid": "copilot-add-menu-button",
variant: "chatInputToolbarSecondary",
size: "chatInputToolbarIcon",
className: twMerge("cpk:ml-1", className),
disabled: isDisabled,
...props,
children: /* @__PURE__ */ jsx(Plus, { className: "cpk:size-[20px]" })
})
})
}), /* @__PURE__ */ jsx(TooltipContent, {
side: "bottom",
children: /* @__PURE__ */ jsxs("p", {
className: "cpk:flex cpk:items-center cpk:gap-1 cpk:text-xs cpk:font-medium",
children: [/* @__PURE__ */ jsx("span", { children: "Add files and more" }), /* @__PURE__ */ 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__ */ jsx(DropdownMenuContent, {
side: "top",
align: "start",
children: renderMenuItems(menuItems)
})] });
};
_CopilotChatInput.TextArea = forwardRef(function TextArea({ style, className, autoFocus, placeholder, ...props }, ref) {
const internalTextareaRef = useRef(null);
const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
useImperativeHandle(ref, () => internalTextareaRef.current);
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);
}, []);
useEffect(() => {
if (autoFocus) internalTextareaRef.current?.focus();
}, [autoFocus]);
return /* @__PURE__ */ jsx("textarea", {
ref: internalTextareaRef,
"data-testid": "copilot-chat-textarea",
placeholder: placeholder ?? labels.chatInputPlaceholder,
className: 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 }) => {
const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
return /* @__PURE__ */ 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
export { CopilotChatInput_default as default };
//# sourceMappingURL=CopilotChatInput.mjs.map