291 lines
12 KiB
JavaScript
291 lines
12 KiB
JavaScript
import { CopilotChatDefaultLabels, useCopilotChatConfiguration } from "../../providers/CopilotChatConfigurationProvider.mjs";
|
|
import { cn } from "../../lib/utils.mjs";
|
|
import { Button } from "../ui/button.mjs";
|
|
import { renderSlot } from "../../lib/slots.mjs";
|
|
import CopilotChatInput_default from "./CopilotChatInput.mjs";
|
|
import CopilotChatSuggestionView from "./CopilotChatSuggestionView.mjs";
|
|
import CopilotChatMessageView from "./CopilotChatMessageView.mjs";
|
|
import { useKeyboardHeight } from "../../hooks/use-keyboard-height.mjs";
|
|
import React, { useEffect, useRef, useState } from "react";
|
|
import { twMerge } from "tailwind-merge";
|
|
import { ChevronDown } from "lucide-react";
|
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
import { StickToBottom, useStickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
|
|
|
|
//#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 = useRef(null);
|
|
const [inputContainerHeight, setInputContainerHeight] = useState(0);
|
|
const [isResizing, setIsResizing] = useState(false);
|
|
const resizeTimeoutRef = useRef(null);
|
|
const { isKeyboardOpen, keyboardHeight, availableHeight } = useKeyboardHeight();
|
|
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__ */ jsx("div", {
|
|
style: { paddingBottom: `${inputContainerHeight + FEATHER_HEIGHT + (hasSuggestions ? 4 : 32)}px` },
|
|
children: /* @__PURE__ */ jsxs("div", {
|
|
className: "cpk:max-w-3xl cpk:mx-auto",
|
|
children: [BoundMessageView, hasSuggestions ? /* @__PURE__ */ 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 ?? /* @__PURE__ */ jsx(Fragment, {})
|
|
});
|
|
return /* @__PURE__ */ jsx("div", {
|
|
"data-copilotkit": true,
|
|
"data-testid": "copilot-chat",
|
|
"data-copilot-running": isRunning ? "true" : "false",
|
|
className: twMerge("copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
|
|
...props,
|
|
children: BoundWelcomeScreen
|
|
});
|
|
}
|
|
if (children) return /* @__PURE__ */ jsx("div", {
|
|
"data-copilotkit": true,
|
|
style: { display: "contents" },
|
|
children: children({
|
|
messageView: BoundMessageView,
|
|
input: BoundInput,
|
|
scrollView: BoundScrollView,
|
|
suggestionView: BoundSuggestionView ?? /* @__PURE__ */ jsx(Fragment, {})
|
|
})
|
|
});
|
|
return /* @__PURE__ */ jsxs("div", {
|
|
"data-copilotkit": true,
|
|
"data-testid": "copilot-chat",
|
|
"data-copilot-running": isRunning ? "true" : "false",
|
|
className: twMerge("copilotKitChat cpk:relative cpk:h-full", className),
|
|
...props,
|
|
children: [BoundScrollView, BoundInput]
|
|
});
|
|
}
|
|
(function(_CopilotChatView) {
|
|
const ScrollContent = ({ children, scrollToBottomButton, feather, inputContainerHeight, isResizing }) => {
|
|
const { isAtBottom, scrollToBottom } = useStickToBottomContext();
|
|
const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
|
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
/* @__PURE__ */ jsx(StickToBottom.Content, {
|
|
className: "cpk:overflow-y-scroll cpk:overflow-x-hidden",
|
|
style: {
|
|
flex: "1 1 0%",
|
|
minHeight: 0
|
|
},
|
|
children: /* @__PURE__ */ 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__ */ 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] = useState(false);
|
|
const { scrollRef, contentRef, scrollToBottom } = useStickToBottom();
|
|
const [showScrollButton, setShowScrollButton] = useState(false);
|
|
useEffect(() => {
|
|
setHasMounted(true);
|
|
}, []);
|
|
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__ */ 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__ */ 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__ */ 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__ */ 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__ */ 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__ */ jsx(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__ */ jsx(ScrollContent, {
|
|
scrollToBottomButton,
|
|
feather,
|
|
inputContainerHeight,
|
|
isResizing,
|
|
children
|
|
})
|
|
});
|
|
};
|
|
_CopilotChatView.ScrollToBottomButton = ({ className, ...props }) => /* @__PURE__ */ jsx(Button, {
|
|
"data-testid": "copilot-scroll-to-bottom",
|
|
variant: "outline",
|
|
size: "sm",
|
|
className: 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__ */ jsx(ChevronDown, { className: "cpk:w-4 cpk:h-4 cpk:text-gray-600 cpk:dark:text-white" })
|
|
});
|
|
_CopilotChatView.Feather = ({ className, style, ...props }) => /* @__PURE__ */ 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 }) => {
|
|
const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
|
|
return /* @__PURE__ */ 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__ */ jsx("div", {
|
|
"data-copilotkit": true,
|
|
style: { display: "contents" },
|
|
children: children({
|
|
welcomeMessage: BoundWelcomeMessage,
|
|
input,
|
|
suggestionView,
|
|
className,
|
|
...props
|
|
})
|
|
});
|
|
return /* @__PURE__ */ 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__ */ jsxs("div", {
|
|
className: "cpk:w-full cpk:max-w-3xl cpk:flex cpk:flex-col cpk:items-center",
|
|
children: [
|
|
/* @__PURE__ */ jsx("div", {
|
|
className: "cpk:mb-6",
|
|
children: BoundWelcomeMessage
|
|
}),
|
|
/* @__PURE__ */ jsx("div", {
|
|
className: "cpk:w-full",
|
|
children: input
|
|
}),
|
|
/* @__PURE__ */ jsx("div", {
|
|
className: "cpk:mt-4 cpk:flex cpk:justify-center",
|
|
children: suggestionView
|
|
})
|
|
]
|
|
})
|
|
});
|
|
};
|
|
})(CopilotChatView || (CopilotChatView = {}));
|
|
var CopilotChatView_default = CopilotChatView;
|
|
|
|
//#endregion
|
|
export { CopilotChatView, CopilotChatView_default as default };
|
|
//# sourceMappingURL=CopilotChatView.mjs.map
|