UNPKG

@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
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