682 lines
31 KiB
JavaScript
682 lines
31 KiB
JavaScript
const require_runtime = require('../../_virtual/_rolldown/runtime.cjs');
|
|
const require_CopilotChatConfigurationProvider = require('../../providers/CopilotChatConfigurationProvider.cjs');
|
|
const require_utils = require('../../lib/utils.cjs');
|
|
const require_button = require('../ui/button.cjs');
|
|
const require_tooltip = require('../ui/tooltip.cjs');
|
|
const require_dropdown_menu = require('../ui/dropdown-menu.cjs');
|
|
const require_CopilotChatAudioRecorder = require('./CopilotChatAudioRecorder.cjs');
|
|
const require_slots = require('../../lib/slots.cjs');
|
|
let react = require("react");
|
|
react = require_runtime.__toESM(react);
|
|
let tailwind_merge = require("tailwind-merge");
|
|
let lucide_react = require("lucide-react");
|
|
let react_jsx_runtime = require("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] = (0, react.useState)(() => value ?? "");
|
|
(0, react.useEffect)(() => {
|
|
if (!isControlled && value !== void 0) setInternalValue(value);
|
|
}, [isControlled, value]);
|
|
const resolvedValue = isControlled ? value ?? "" : internalValue;
|
|
const [layout, setLayout] = (0, react.useState)("compact");
|
|
const ignoreResizeRef = (0, react.useRef)(false);
|
|
const resizeEvaluationRafRef = (0, react.useRef)(null);
|
|
const isExpanded = mode === "input" && layout === "expanded";
|
|
const [commandQuery, setCommandQuery] = (0, react.useState)(null);
|
|
const [slashHighlightIndex, setSlashHighlightIndex] = (0, react.useState)(0);
|
|
const inputRef = (0, react.useRef)(null);
|
|
const gridRef = (0, react.useRef)(null);
|
|
const addButtonContainerRef = (0, react.useRef)(null);
|
|
const actionsContainerRef = (0, react.useRef)(null);
|
|
const audioRecorderRef = (0, react.useRef)(null);
|
|
const slashMenuRef = (0, react.useRef)(null);
|
|
const config = require_CopilotChatConfigurationProvider.useCopilotChatConfiguration();
|
|
const labels = config?.labels ?? require_CopilotChatConfigurationProvider.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?.isModalOpen;
|
|
return;
|
|
}
|
|
if (config?.isModalOpen && !previousModalStateRef.current) inputRef.current?.focus();
|
|
previousModalStateRef.current = config?.isModalOpen;
|
|
}, [config?.isModalOpen, autoFocus]);
|
|
(0, react.useEffect)(() => {
|
|
if (commandItems.length === 0 && commandQuery !== null) setCommandQuery(null);
|
|
}, [commandItems.length, commandQuery]);
|
|
const previousCommandQueryRef = (0, react.useRef)(null);
|
|
(0, react.useEffect)(() => {
|
|
if (commandQuery !== null && commandQuery !== previousCommandQueryRef.current && filteredCommands.length > 0) setSlashHighlightIndex(0);
|
|
previousCommandQueryRef.current = commandQuery;
|
|
}, [commandQuery, filteredCommands.length]);
|
|
(0, react.useEffect)(() => {
|
|
if (commandQuery === null) {
|
|
setSlashHighlightIndex(0);
|
|
return;
|
|
}
|
|
if (filteredCommands.length === 0) setSlashHighlightIndex(-1);
|
|
else if (slashHighlightIndex < 0 || slashHighlightIndex >= filteredCommands.length) setSlashHighlightIndex(0);
|
|
}, [
|
|
commandQuery,
|
|
filteredCommands,
|
|
slashHighlightIndex
|
|
]);
|
|
(0, react.useEffect)(() => {
|
|
const recorder = audioRecorderRef.current;
|
|
if (!recorder) return;
|
|
if (mode === "transcribe") recorder.start().catch(console.error);
|
|
else if (recorder.state === "recording") recorder.stop().catch(console.error);
|
|
}, [mode]);
|
|
(0, react.useEffect)(() => {
|
|
if (mode !== "input") {
|
|
setLayout("compact");
|
|
setCommandQuery(null);
|
|
}
|
|
}, [mode]);
|
|
const updateSlashState = (0, react.useCallback)((value) => {
|
|
if (commandItems.length === 0) {
|
|
setCommandQuery((prev) => prev === null ? prev : null);
|
|
return;
|
|
}
|
|
if (value.startsWith("/")) {
|
|
const query = (value.split(/\r?\n/, 1)[0] ?? "").slice(1);
|
|
setCommandQuery((prev) => prev === query ? prev : query);
|
|
} else setCommandQuery((prev) => prev === null ? prev : null);
|
|
}, [commandItems.length]);
|
|
(0, react.useEffect)(() => {
|
|
updateSlashState(resolvedValue);
|
|
}, [resolvedValue, updateSlashState]);
|
|
const handleChange = (e) => {
|
|
const nextValue = e.target.value;
|
|
if (!isControlled) setInternalValue(nextValue);
|
|
onChange?.(nextValue);
|
|
updateSlashState(nextValue);
|
|
};
|
|
const clearInputValue = (0, react.useCallback)(() => {
|
|
if (!isControlled) setInternalValue("");
|
|
if (onChange) onChange("");
|
|
}, [isControlled, onChange]);
|
|
const runCommand = (0, react.useCallback)((item) => {
|
|
clearInputValue();
|
|
item.action?.();
|
|
setCommandQuery(null);
|
|
setSlashHighlightIndex(0);
|
|
requestAnimationFrame(() => {
|
|
inputRef.current?.focus();
|
|
});
|
|
}, [clearInputValue]);
|
|
const handleKeyDown = (e) => {
|
|
if (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 = require_slots.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?.();
|
|
return;
|
|
}
|
|
send();
|
|
};
|
|
const BoundAudioRecorder = require_slots.renderSlot(audioRecorder, require_CopilotChatAudioRecorder.CopilotChatAudioRecorder, { ref: audioRecorderRef });
|
|
const BoundSendButton = require_slots.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 = require_slots.renderSlot(startTranscribeButton, CopilotChatInput.StartTranscribeButton, { onClick: onStartTranscribe });
|
|
const BoundCancelTranscribeButton = require_slots.renderSlot(cancelTranscribeButton, CopilotChatInput.CancelTranscribeButton, { onClick: onCancelTranscribe });
|
|
const handleFinishTranscribe = (0, react.useCallback)(async () => {
|
|
const recorder = audioRecorderRef.current;
|
|
if (recorder && recorder.state === "recording") try {
|
|
const audioBlob = await recorder.stop();
|
|
if (onFinishTranscribeWithAudio) await onFinishTranscribeWithAudio(audioBlob);
|
|
} catch (error) {
|
|
console.error("Failed to stop recording:", error);
|
|
}
|
|
onFinishTranscribe?.();
|
|
}, [onFinishTranscribe, onFinishTranscribeWithAudio]);
|
|
const BoundFinishTranscribeButton = require_slots.renderSlot(finishTranscribeButton, CopilotChatInput.FinishTranscribeButton, { onClick: handleFinishTranscribe });
|
|
const BoundAddMenuButton = require_slots.renderSlot(addMenuButton, CopilotChatInput.AddMenuButton, {
|
|
disabled: mode === "transcribe",
|
|
onAddFile,
|
|
toolsMenu
|
|
});
|
|
const BoundDisclaimer = require_slots.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__ */ (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) {
|
|
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
|
|
]);
|
|
(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)(() => {
|
|
if (!slashMenuVisible || slashHighlightIndex < 0) return;
|
|
(slashMenuRef.current?.querySelector(`[data-slash-index="${slashHighlightIndex}"]`))?.scrollIntoView({ block: "nearest" });
|
|
}, [slashMenuVisible, slashHighlightIndex]);
|
|
const slashMenu = slashMenuVisible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-testid": "copilot-slash-menu",
|
|
role: "listbox",
|
|
"aria-label": "Slash commands",
|
|
ref: slashMenuRef,
|
|
className: "cpk:absolute cpk:bottom-full cpk:left-0 cpk:right-0 cpk:z-30 cpk:mb-2 cpk:max-h-64 cpk:overflow-y-auto cpk:rounded-lg cpk:border cpk:border-border cpk:bg-white cpk:shadow-lg cpk:dark:border-[#3a3a3a] cpk:dark:bg-[#1f1f1f]",
|
|
style: { maxHeight: `${SLASH_MENU_MAX_VISIBLE_ITEMS * SLASH_MENU_ITEM_HEIGHT_PX}px` },
|
|
children: filteredCommands.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: "cpk:px-3 cpk:py-2 cpk:text-sm cpk:text-muted-foreground",
|
|
children: "No commands found"
|
|
}) : filteredCommands.map((item, index) => {
|
|
const isActive = index === slashHighlightIndex;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
type: "button",
|
|
role: "option",
|
|
"aria-selected": isActive,
|
|
"data-active": isActive ? "true" : void 0,
|
|
"data-slash-index": index,
|
|
className: (0, tailwind_merge.twMerge)("cpk:w-full cpk:px-3 cpk:py-2 cpk:text-left cpk:text-sm cpk:transition-colors", "cpk:hover:bg-muted cpk:dark:hover:bg-[#2f2f2f]", isActive ? "cpk:bg-muted cpk:dark:bg-[#2f2f2f]" : "cpk:bg-transparent"),
|
|
onMouseEnter: () => setSlashHighlightIndex(index),
|
|
onMouseDown: (event) => {
|
|
event.preventDefault();
|
|
runCommand(item);
|
|
},
|
|
children: item.label
|
|
}, `${item.label}-${index}`);
|
|
})
|
|
}) : null;
|
|
const inputPill = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
"data-testid": "copilot-chat-input",
|
|
className: (0, tailwind_merge.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: require_utils.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)(require_button.Button, {
|
|
type: "button",
|
|
"data-testid": "copilot-send-button",
|
|
variant: "chatInputToolbarPrimary",
|
|
size: "chatInputToolbarIcon",
|
|
className,
|
|
...props,
|
|
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ArrowUp, { className: "cpk:size-[18px]" })
|
|
})
|
|
});
|
|
const ToolbarButton = _CopilotChatInput.ToolbarButton = ({ icon, labelKey, defaultClassName, className, ...props }) => {
|
|
const labels = require_CopilotChatConfigurationProvider.useCopilotChatConfiguration()?.labels ?? require_CopilotChatConfigurationProvider.CopilotChatDefaultLabels;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_tooltip.Tooltip, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_tooltip.TooltipTrigger, {
|
|
asChild: true,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_button.Button, {
|
|
type: "button",
|
|
variant: "chatInputToolbarSecondary",
|
|
size: "chatInputToolbarIcon",
|
|
className: (0, tailwind_merge.twMerge)(defaultClassName, className),
|
|
...props,
|
|
children: icon
|
|
})
|
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_tooltip.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 }) => {
|
|
const labels = require_CopilotChatConfigurationProvider.useCopilotChatConfiguration()?.labels ?? require_CopilotChatConfigurationProvider.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)(require_dropdown_menu.DropdownMenuSeparator, {}, `separator-${index}`);
|
|
if (item.items && item.items.length > 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_dropdown_menu.DropdownMenuSub, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_dropdown_menu.DropdownMenuSubTrigger, { children: item.label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_dropdown_menu.DropdownMenuSubContent, { children: renderMenuItems(item.items) })] }, `group-${index}`);
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_dropdown_menu.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)(require_dropdown_menu.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_tooltip.Tooltip, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_tooltip.TooltipTrigger, {
|
|
asChild: true,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_dropdown_menu.DropdownMenuTrigger, {
|
|
asChild: true,
|
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_button.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)(require_tooltip.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)(require_dropdown_menu.DropdownMenuContent, {
|
|
side: "top",
|
|
align: "start",
|
|
children: renderMenuItems(menuItems)
|
|
})] });
|
|
};
|
|
_CopilotChatInput.TextArea = (0, react.forwardRef)(function TextArea({ style, className, autoFocus, placeholder, ...props }, ref) {
|
|
const internalTextareaRef = (0, react.useRef)(null);
|
|
const labels = require_CopilotChatConfigurationProvider.useCopilotChatConfiguration()?.labels ?? require_CopilotChatConfigurationProvider.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) internalTextareaRef.current?.focus();
|
|
}, [autoFocus]);
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
ref: internalTextareaRef,
|
|
"data-testid": "copilot-chat-textarea",
|
|
placeholder: 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 = require_CopilotChatAudioRecorder.CopilotChatAudioRecorder;
|
|
_CopilotChatInput.Disclaimer = ({ className, ...props }) => {
|
|
const labels = require_CopilotChatConfigurationProvider.useCopilotChatConfiguration()?.labels ?? require_CopilotChatConfigurationProvider.CopilotChatDefaultLabels;
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
className: require_utils.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
|
|
exports.default = CopilotChatInput_default;
|
|
//# sourceMappingURL=CopilotChatInput.cjs.map
|