@liveblocks/react-ui
Version:
A set of React pre-built components for the Liveblocks products. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.
177 lines (174 loc) • 6.49 kB
JavaScript
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { useAiChatMessages, RegisterAiKnowledge, RegisterAiTool } from '@liveblocks/react';
import { useLayoutEffect } from '@liveblocks/react/_private';
import { forwardRef, useRef, useState, useImperativeHandle, useEffect } from 'react';
import { ArrowDownIcon } from '../icons/ArrowDown.js';
import { SpinnerIcon } from '../icons/Spinner.js';
import { useOverrides } from '../overrides.js';
import { cn } from '../utils/cn.js';
import { useVisible } from '../utils/use-visible.js';
import { AiChatAssistantMessage } from './internal/AiChatAssistantMessage.js';
import { AiChatComposer } from './internal/AiChatComposer.js';
import { AiChatUserMessage } from './internal/AiChatUserMessage.js';
const MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR = 50;
const defaultComponents = {
Empty: () => null,
Loading: () => /* @__PURE__ */ jsx("div", {
className: "lb-loading lb-ai-chat-loading",
children: /* @__PURE__ */ jsx(SpinnerIcon, {})
})
};
const AiChat = forwardRef(
({
chatId,
copilotId,
autoFocus,
overrides,
knowledge,
tools = {},
layout = "inset",
components,
className,
...props
}, forwardedRef) => {
const { messages, isLoading, error } = useAiChatMessages(chatId);
const $ = useOverrides(overrides);
const Empty = components?.Empty ?? defaultComponents.Empty;
const Loading = components?.Loading ?? defaultComponents.Loading;
const containerRef = useRef(null);
const containerBottomRef = useRef(null);
const isScrollIndicatorEnabled = !isLoading && !error;
const isScrollAtBottom = useVisible(containerBottomRef, {
enabled: isScrollIndicatorEnabled,
root: containerRef,
rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR,
initialValue: null
});
const isScrollIndicatorVisible = isScrollIndicatorEnabled && isScrollAtBottom !== null ? !isScrollAtBottom : false;
const [lastSentMessageId, setLastSentMessageId] = useState(null);
useImperativeHandle(
forwardedRef,
() => containerRef.current,
[]
);
const scrollToBottomCallbackRef = useRef(void 0);
if (scrollToBottomCallbackRef.current === void 0) {
scrollToBottomCallbackRef.current = function(behavior) {
const container = containerRef.current;
if (container === null)
return;
container.scrollTo({
top: container.scrollHeight,
behavior
});
};
}
const scrollToBottom = scrollToBottomCallbackRef.current;
return /* @__PURE__ */ jsxs("div", {
ref: containerRef,
...props,
className: cn(
"lb-root lb-ai-chat",
layout === "compact" ? "lb-ai-chat:layout-compact" : "lb-ai-chat:layout-inset",
className
),
children: [
knowledge ? knowledge.map((source, index) => /* @__PURE__ */ jsx(RegisterAiKnowledge, {
description: source.description,
value: source.value
}, index)) : null,
Object.entries(tools).map(([name, tool]) => /* @__PURE__ */ jsx(RegisterAiTool, {
chatId,
name,
tool
}, name)),
/* @__PURE__ */ jsx("div", {
className: "lb-ai-chat-content",
children: isLoading ? /* @__PURE__ */ jsx(Loading, {}) : error !== void 0 ? /* @__PURE__ */ jsx("div", {
className: "lb-error lb-ai-chat-error",
children: $.AI_CHAT_MESSAGES_ERROR(error)
}) : messages.length === 0 ? /* @__PURE__ */ jsx(Empty, {
chatId,
copilotId
}) : /* @__PURE__ */ jsxs(Fragment, {
children: [
/* @__PURE__ */ jsx(AutoScrollHandler, {
lastSentMessageId,
scrollToBottom
}),
/* @__PURE__ */ jsx("div", {
className: "lb-ai-chat-messages",
children: messages.map((message) => {
if (message.role === "user") {
return /* @__PURE__ */ jsx(AiChatUserMessage, {
message,
overrides
}, message.id);
} else if (message.role === "assistant") {
return /* @__PURE__ */ jsx(AiChatAssistantMessage, {
message,
overrides,
components
}, message.id);
} else {
return null;
}
})
})
]
})
}),
/* @__PURE__ */ jsxs("div", {
className: "lb-ai-chat-footer",
children: [
/* @__PURE__ */ jsx("div", {
className: "lb-ai-chat-footer-actions",
children: /* @__PURE__ */ jsx("div", {
className: "lb-root lb-elevation lb-elevation-moderate lb-ai-chat-scroll-indicator",
"data-visible": isScrollIndicatorVisible ? "" : void 0,
children: /* @__PURE__ */ jsx("button", {
className: "lb-ai-chat-scroll-indicator-button",
tabIndex: isScrollIndicatorVisible ? 0 : -1,
"aria-hidden": !isScrollIndicatorVisible,
onClick: () => scrollToBottom("smooth"),
children: /* @__PURE__ */ jsx("span", {
className: "lb-icon-container",
children: /* @__PURE__ */ jsx(ArrowDownIcon, {})
})
})
})
}),
/* @__PURE__ */ jsx(AiChatComposer, {
chatId,
copilotId,
overrides,
autoFocus,
onUserMessageCreate: ({ id }) => setLastSentMessageId(id),
className: layout === "inset" ? "lb-elevation lb-elevation-moderate" : void 0
}, chatId)
]
}),
/* @__PURE__ */ jsx("div", {
ref: containerBottomRef,
style: { position: "sticky", height: 0 },
"aria-hidden": true,
"data-scroll-at-bottom": isScrollAtBottom ? "" : void 0
})
]
});
}
);
function AutoScrollHandler({
lastSentMessageId,
scrollToBottom
}) {
useLayoutEffect(() => {
scrollToBottom("instant");
}, [scrollToBottom]);
useEffect(() => {
scrollToBottom("smooth");
}, [lastSentMessageId, scrollToBottom]);
return null;
}
export { AiChat };
//# sourceMappingURL=AiChat.js.map