@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.
317 lines (314 loc) • 10.9 kB
JavaScript
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
import { useUrlMetadata } from '@liveblocks/react';
import { useMemo, useState, useCallback, memo, forwardRef, useRef, useEffect } from 'react';
import { useComponents } from '../../components.js';
import { ChevronRightIcon } from '../../icons/ChevronRight.js';
import { WarningIcon } from '../../icons/Warning.js';
import { useOverrides, OverridesProvider } from '../../overrides.js';
import { Content as AiMessageContent } from '../../primitives/AiMessage/index.js';
import { AiMessageToolInvocation } from '../../primitives/AiMessage/tool-invocation.js';
import { Root as CollapsibleRoot, Trigger as CollapsibleTrigger, Content as CollapsibleContent } from '../../primitives/Collapsible/index.js';
import { cn } from '../../utils/cn.js';
import { ErrorBoundary } from '../../utils/ErrorBoundary.js';
import { Favicon } from './Favicon.js';
import { Prose } from './Prose.js';
function getUrlDomain(url) {
return new URL(url).hostname;
}
function AiChatSource({
source,
components,
className,
...props
}) {
const { Anchor } = useComponents(components);
const { metadata } = useUrlMetadata(source.url);
const label = useMemo(() => {
return source.title ?? metadata?.title ?? getUrlDomain(source.url);
}, [source.title, source.url, metadata?.title]);
return /* @__PURE__ */ jsxs(
Anchor,
{
href: source.url,
target: "_blank",
rel: "noopener noreferrer",
className: cn("lb-ai-chat-source", className),
...props,
children: [
/* @__PURE__ */ jsx(Favicon, { url: source.url, className: "lb-ai-chat-source-favicon" }),
/* @__PURE__ */ jsx("span", { className: "lb-ai-chat-source-label", children: label })
]
}
);
}
function AiChatSources({
sources: allSources,
maxSources,
components,
className,
...props
}) {
const $ = useOverrides();
const [isOpen, setOpen] = useState(false);
const visibleSources = typeof maxSources === "number" && !isOpen ? allSources.slice(0, maxSources) : allSources;
const handleToggle = useCallback(() => {
setOpen((isOpen2) => !isOpen2);
}, []);
return /* @__PURE__ */ jsxs("ol", { className: cn("lb-ai-chat-sources", className), ...props, children: [
visibleSources.map((source, index) => {
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(AiChatSource, { source, components }) }, `${index}-${source.url}`);
}),
visibleSources.length !== allSources.length ? /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", { className: "lb-ai-chat-sources-more", onClick: handleToggle, children: /* @__PURE__ */ jsxs("span", { className: "lb-ai-chat-sources-more-label", children: [
"+ ",
$.LIST_REMAINING(allSources.length - visibleSources.length)
] }) }) }) : null
] });
}
const AiChatAssistantMessage = memo(
forwardRef(
({
message,
className,
overrides,
components,
showReasoning,
showRetrievals,
showSources,
...props
}, forwardedRef) => {
const $ = useOverrides(overrides);
let children = null;
const messageContent = /* @__PURE__ */ jsx(
AssistantMessageContent,
{
message,
components,
showReasoning,
showRetrievals,
showSources
}
);
if (message.deletedAt !== void 0) {
children = /* @__PURE__ */ jsx("div", { className: "lb-ai-chat-message-deleted", children: $.AI_CHAT_MESSAGE_DELETED });
} else if (message.status === "generating" || message.status === "awaiting-tool") {
if (message.contentSoFar.length === 0) {
children = /* @__PURE__ */ jsx("div", { className: "lb-ai-chat-message-thinking lb-ai-chat-pending", children: $.AI_CHAT_MESSAGE_THINKING });
} else {
children = messageContent;
}
} else if (message.status === "completed") {
children = messageContent;
} else if (message.status === "failed") {
if (message.errorReason === "Aborted by user") {
children = messageContent;
} else {
children = /* @__PURE__ */ jsxs(Fragment, { children: [
messageContent,
/* @__PURE__ */ jsxs("div", { className: "lb-ai-chat-message-error", children: [
/* @__PURE__ */ jsx("span", { className: "lb-icon-container", children: /* @__PURE__ */ jsx(WarningIcon, {}) }),
message.errorReason
] })
] });
}
}
return /* @__PURE__ */ jsx(
"div",
{
className: cn(
"lb-ai-chat-message lb-ai-chat-assistant-message",
className
),
...props,
ref: forwardedRef,
children: /* @__PURE__ */ jsx(OverridesProvider, { overrides, children })
}
);
}
)
);
const NoopComponent = () => null;
function AssistantMessageContent({
message,
components,
showReasoning = true,
showRetrievals = true,
showSources = true
}) {
const componentsRef = useRef(components);
let showKnowledgeRetrievals = typeof showRetrievals === "object" ? showRetrievals.knowledge : showRetrievals;
let showWebRetrievals = typeof showRetrievals === "object" ? showRetrievals.web : showRetrievals;
showKnowledgeRetrievals ??= true;
showWebRetrievals ??= true;
const BoundTextPart = useMemo(
() => (props) => /* @__PURE__ */ jsx(TextPart, { ...props, components: componentsRef.current }),
[]
);
const BoundReasoningPart = useMemo(
() => (props) => {
if (!showReasoning || showReasoning === "during" && !props.isStreaming) {
return null;
}
return /* @__PURE__ */ jsx(ReasoningPart, { ...props, components: componentsRef.current });
},
[showReasoning]
);
const BoundRetrievalPart = useMemo(
() => (props) => {
if (props.part.kind === "knowledge") {
if (!showKnowledgeRetrievals || showKnowledgeRetrievals === "during" && !props.isStreaming) {
return null;
}
} else if (props.part.kind === "web") {
if (!showWebRetrievals || showWebRetrievals === "during" && !props.isStreaming) {
return null;
}
}
return /* @__PURE__ */ jsx(RetrievalPart, { ...props });
},
[showKnowledgeRetrievals, showWebRetrievals]
);
return /* @__PURE__ */ jsx(
AiMessageContent,
{
message,
components: {
TextPart: BoundTextPart,
ReasoningPart: BoundReasoningPart,
RetrievalPart: BoundRetrievalPart,
SourcesPart: showSources ? SourcesPart : NoopComponent,
ToolInvocationPart
},
className: "lb-ai-chat-message-content"
}
);
}
function TextPart({ part, components, isStreaming }) {
return /* @__PURE__ */ jsx(
Prose,
{
content: part.text,
className: "lb-ai-chat-message-text",
components,
partial: isStreaming
}
);
}
function ReasoningPart({ part, isStreaming, components }) {
const [isOpen, setIsOpen] = useState(isStreaming);
const $ = useOverrides();
useEffect(() => {
if (!isStreaming) {
setIsOpen(false);
}
}, [isStreaming]);
return /* @__PURE__ */ jsxs(
CollapsibleRoot,
{
className: "lb-collapsible lb-ai-chat-message-reasoning",
open: isOpen,
onOpenChange: setIsOpen,
children: [
/* @__PURE__ */ jsxs(
CollapsibleTrigger,
{
className: cn(
"lb-collapsible-trigger",
isStreaming && "lb-ai-chat-pending"
),
children: [
$.AI_CHAT_MESSAGE_REASONING(isStreaming, part),
/* @__PURE__ */ jsx("span", { className: "lb-collapsible-chevron lb-icon-container", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
]
}
),
/* @__PURE__ */ jsx(CollapsibleContent, { className: "lb-collapsible-content", children: /* @__PURE__ */ jsx(
Prose,
{
content: part.text,
partial: isStreaming,
components
}
) })
]
}
);
}
function RetrievalPartFavicons({
sources,
maxSources
}) {
if (!sources) {
return null;
}
const visibleSources = typeof maxSources === "number" ? sources.slice(0, maxSources) : sources;
return /* @__PURE__ */ jsx("div", { className: "lb-ai-chat-message-retrieval-favicons", children: visibleSources.map((source) => /* @__PURE__ */ jsx(Favicon, { url: source.url }, source.url)) });
}
function RetrievalPart({ part, isStreaming }) {
const $ = useOverrides();
let content = null;
if (part.kind === "web" && part.sources && part.sources.length > 0) {
content = /* @__PURE__ */ jsx(
AiChatSources,
{
className: "lb-ai-chat-message-retrieval-sources",
sources: part.sources
}
);
}
return /* @__PURE__ */ jsxs(
CollapsibleRoot,
{
className: "lb-collapsible lb-ai-chat-message-retrieval",
defaultOpen: false,
disabled: !content,
children: [
/* @__PURE__ */ jsxs(
CollapsibleTrigger,
{
className: cn(
"lb-collapsible-trigger",
isStreaming && "lb-ai-chat-pending"
),
children: [
$.AI_CHAT_MESSAGE_RETRIEVAL(isStreaming, part),
part.kind === "web" ? /* @__PURE__ */ jsx(RetrievalPartFavicons, { sources: part.sources, maxSources: 3 }) : null,
content ? /* @__PURE__ */ jsx("span", { className: "lb-collapsible-chevron lb-icon-container", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) }) : null
]
}
),
content ? /* @__PURE__ */ jsx(CollapsibleContent, { className: "lb-collapsible-content", children: content }) : null
]
}
);
}
function ToolInvocationPart({
part,
message
}) {
return /* @__PURE__ */ jsx("div", { className: "lb-ai-chat-message-tool-invocation", children: /* @__PURE__ */ jsx(
ErrorBoundary,
{
fallback: /* @__PURE__ */ jsxs("div", { className: "lb-ai-chat-message-error", children: [
/* @__PURE__ */ jsx("span", { className: "lb-icon-container", children: /* @__PURE__ */ jsx(WarningIcon, {}) }),
/* @__PURE__ */ jsxs("p", { children: [
"Failed to render tool call result for ",
/* @__PURE__ */ jsx("code", { children: part.name }),
". See console for details."
] })
] }),
children: /* @__PURE__ */ jsx(AiMessageToolInvocation, { part, message })
}
) });
}
function SourcesPart({ part }) {
return /* @__PURE__ */ jsx(
AiChatSources,
{
className: "lb-ai-chat-message-sources",
sources: part.sources,
maxSources: 5
}
);
}
export { AiChatAssistantMessage };
//# sourceMappingURL=AiChatAssistantMessage.js.map