@liveblocks/react-ui 2.25.0-aiprivatebeta10 → 2.25.0-aiprivatebeta12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AiChat.cjs +11 -17
- package/dist/components/AiChat.cjs.map +1 -1
- package/dist/components/AiChat.js +12 -18
- package/dist/components/AiChat.js.map +1 -1
- package/dist/components/AiTool.cjs +36 -23
- package/dist/components/AiTool.cjs.map +1 -1
- package/dist/components/AiTool.js +37 -24
- package/dist/components/AiTool.js.map +1 -1
- package/dist/components/Composer.cjs +3 -3
- package/dist/components/Composer.cjs.map +1 -1
- package/dist/components/Composer.js +3 -3
- package/dist/components/Composer.js.map +1 -1
- package/dist/components/internal/AiChatAssistantMessage.cjs +8 -4
- package/dist/components/internal/AiChatAssistantMessage.cjs.map +1 -1
- package/dist/components/internal/AiChatAssistantMessage.js +9 -5
- package/dist/components/internal/AiChatAssistantMessage.js.map +1 -1
- package/dist/components/internal/AiChatComposer.cjs.map +1 -1
- package/dist/components/internal/AiChatComposer.js.map +1 -1
- package/dist/components/internal/AiChatUserMessage.cjs.map +1 -1
- package/dist/components/internal/AiChatUserMessage.js.map +1 -1
- package/dist/components/internal/CodeBlock.cjs +6 -3
- package/dist/components/internal/CodeBlock.cjs.map +1 -1
- package/dist/components/internal/CodeBlock.js +6 -3
- package/dist/components/internal/CodeBlock.js.map +1 -1
- package/dist/index.d.cts +20 -2
- package/dist/index.d.ts +20 -2
- package/dist/overrides.cjs +5 -1
- package/dist/overrides.cjs.map +1 -1
- package/dist/overrides.js +5 -1
- package/dist/overrides.js.map +1 -1
- package/dist/primitives/AiMessage/index.cjs +24 -19
- package/dist/primitives/AiMessage/index.cjs.map +1 -1
- package/dist/primitives/AiMessage/index.js +25 -20
- package/dist/primitives/AiMessage/index.js.map +1 -1
- package/dist/primitives/Collapsible/index.cjs +3 -3
- package/dist/primitives/Collapsible/index.cjs.map +1 -1
- package/dist/primitives/Collapsible/index.js +3 -3
- package/dist/primitives/Collapsible/index.js.map +1 -1
- package/dist/utils/use-controllable-state.cjs +25 -2
- package/dist/utils/use-controllable-state.cjs.map +1 -1
- package/dist/utils/use-controllable-state.js +25 -3
- package/dist/utils/use-controllable-state.js.map +1 -1
- package/dist/utils/use-visible.cjs +3 -1
- package/dist/utils/use-visible.cjs.map +1 -1
- package/dist/utils/use-visible.js +3 -1
- package/dist/utils/use-visible.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/src/styles/index.css +1 -1
- package/styles.css +1 -1
- package/styles.css.map +1 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
-
var core = require('@liveblocks/core');
|
|
5
4
|
var react$1 = require('@liveblocks/react');
|
|
6
5
|
var _private = require('@liveblocks/react/_private');
|
|
7
6
|
var react = require('react');
|
|
@@ -41,30 +40,20 @@ const AiChat = react.forwardRef(
|
|
|
41
40
|
const Loading = components?.Loading ?? defaultComponents.Loading;
|
|
42
41
|
const containerRef = react.useRef(null);
|
|
43
42
|
const containerBottomRef = react.useRef(null);
|
|
43
|
+
const isScrollIndicatorEnabled = !isLoading && !error;
|
|
44
44
|
const isScrollAtBottom = useVisible.useVisible(containerBottomRef, {
|
|
45
|
-
enabled:
|
|
45
|
+
enabled: isScrollIndicatorEnabled,
|
|
46
46
|
root: containerRef,
|
|
47
|
-
rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR
|
|
47
|
+
rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR,
|
|
48
|
+
initialValue: null
|
|
48
49
|
});
|
|
49
|
-
const isScrollIndicatorVisible =
|
|
50
|
-
const client = react$1.useClient();
|
|
51
|
-
const ai = client[core.kInternal].ai;
|
|
50
|
+
const isScrollIndicatorVisible = isScrollIndicatorEnabled && isScrollAtBottom !== null ? !isScrollAtBottom : false;
|
|
52
51
|
const [lastSentMessageId, setLastSentMessageId] = react.useState(null);
|
|
53
52
|
react.useImperativeHandle(
|
|
54
53
|
forwardedRef,
|
|
55
54
|
() => containerRef.current,
|
|
56
55
|
[]
|
|
57
56
|
);
|
|
58
|
-
react.useEffect(() => {
|
|
59
|
-
Object.entries(tools).map(
|
|
60
|
-
([key, value]) => ai.registerChatTool(chatId, key, value)
|
|
61
|
-
);
|
|
62
|
-
return () => {
|
|
63
|
-
Object.entries(tools).map(
|
|
64
|
-
([key]) => ai.unregisterChatTool(chatId, key)
|
|
65
|
-
);
|
|
66
|
-
};
|
|
67
|
-
}, [ai, chatId, tools]);
|
|
68
57
|
const scrollToBottomCallbackRef = react.useRef(void 0);
|
|
69
58
|
if (scrollToBottomCallbackRef.current === void 0) {
|
|
70
59
|
scrollToBottomCallbackRef.current = function(behavior) {
|
|
@@ -91,6 +80,11 @@ const AiChat = react.forwardRef(
|
|
|
91
80
|
description: source.description,
|
|
92
81
|
value: source.value
|
|
93
82
|
}, index)) : null,
|
|
83
|
+
Object.entries(tools).map(([name, tool]) => /* @__PURE__ */ jsxRuntime.jsx(react$1.RegisterAiTool, {
|
|
84
|
+
chatId,
|
|
85
|
+
name,
|
|
86
|
+
tool
|
|
87
|
+
}, name)),
|
|
94
88
|
/* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
95
89
|
className: "lb-ai-chat-content",
|
|
96
90
|
children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(Loading, {}) : error !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
@@ -151,7 +145,7 @@ const AiChat = react.forwardRef(
|
|
|
151
145
|
/* @__PURE__ */ jsxRuntime.jsx(AiChatComposer.AiChatComposer, {
|
|
152
146
|
chatId,
|
|
153
147
|
copilotId,
|
|
154
|
-
overrides:
|
|
148
|
+
overrides: overrides$1,
|
|
155
149
|
autoFocus,
|
|
156
150
|
onUserMessageCreate: ({ id }) => setLastSentMessageId(id),
|
|
157
151
|
className: layout === "inset" ? "lb-elevation lb-elevation-moderate" : void 0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiChat.cjs","sources":["../../src/components/AiChat.tsx"],"sourcesContent":["import type {\n AiKnowledgeSource,\n AiOpaqueToolDefinition,\n CopilotId,\n MessageId,\n} from \"@liveblocks/core\";\nimport { kInternal } from \"@liveblocks/core\";\nimport {\n RegisterAiKnowledge,\n useAiChatMessages,\n useClient,\n} from \"@liveblocks/react\";\nimport { useLayoutEffect } from \"@liveblocks/react/_private\";\nimport {\n type ComponentProps,\n type ComponentType,\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { SpinnerIcon } from \"../icons/Spinner\";\nimport {\n type AiChatComposerOverrides,\n type AiChatMessageOverrides,\n type AiChatOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../overrides\";\nimport { classNames } from \"../utils/class-names\";\nimport { useVisible } from \"../utils/use-visible\";\nimport { AiChatAssistantMessage } from \"./internal/AiChatAssistantMessage\";\nimport { AiChatComposer } from \"./internal/AiChatComposer\";\nimport { AiChatUserMessage } from \"./internal/AiChatUserMessage\";\n\nexport type AiChatComponentsEmptyProps = {\n /**\n * The chat id provided to the `AiChat` component.\n */\n chatId: string;\n /**\n * The copilot id provided to the `AiChat` component.\n */\n copilotId?: string;\n};\n\nexport type AiChatComponentsLoadingProps = Record<string, never>;\n\n// TODO: Add Markdown components\nexport type AiChatComponents = {\n /**\n * The component used to render the empty state of the chat.\n */\n Empty: ComponentType<AiChatComponentsEmptyProps>;\n /**\n * The component used to render the loading state of the chat.\n */\n Loading: ComponentType<AiChatComponentsLoadingProps>;\n};\n\n/**\n * The minimum number of pixels from the bottom of the scrollable area\n * before showing the scroll to bottom indicator.\n */\nconst MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR = 50;\n\nexport interface AiChatProps extends ComponentProps<\"div\"> {\n /**\n * The id of the chat the composer belongs to.\n */\n chatId: string;\n /**\n * Whether to focus the chat composer on mount.\n */\n autoFocus?: boolean;\n /**\n * The id of the copilot to use to send the message.\n */\n copilotId?: string;\n /**\n * The contextual knowledge to include in the chat. May be used by the assistant when generating responses.\n * Any knowledge you provide via this prop will be added to any already globally registered knowledge via <RegisterAiKnowledge />.\n */\n knowledge?: AiKnowledgeSource[];\n /**\n * Tool definitions to make available within this chat. May be used by the assistant when generating responses.\n */\n tools?: Record<string, AiOpaqueToolDefinition>;\n /**\n * The layout of the chat and its composer.\n */\n layout?: \"inset\" | \"compact\";\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides &\n AiChatMessageOverrides &\n AiChatComposerOverrides &\n AiChatOverrides\n >;\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents & AiChatComponents>;\n}\n\nconst defaultComponents: AiChatComponents = {\n Empty: () => null,\n Loading: () => (\n <div className=\"lb-loading lb-ai-chat-loading\">\n <SpinnerIcon />\n </div>\n ),\n};\n\nexport const AiChat = forwardRef<HTMLDivElement, AiChatProps>(\n (\n {\n chatId,\n copilotId,\n autoFocus,\n overrides,\n knowledge,\n tools = {},\n layout = \"inset\",\n components,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const { messages, isLoading, error } = useAiChatMessages(chatId);\n const $ = useOverrides(overrides);\n const Empty = components?.Empty ?? defaultComponents.Empty;\n const Loading = components?.Loading ?? defaultComponents.Loading;\n\n const containerRef = useRef<HTMLDivElement | null>(null);\n const containerBottomRef = useRef<HTMLDivElement | null>(null);\n const isScrollAtBottom = useVisible(containerBottomRef, {\n enabled: !isLoading && !error,\n root: containerRef,\n rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR,\n });\n const isScrollIndicatorVisible =\n isLoading || error ? false : !isScrollAtBottom;\n\n const client = useClient();\n const ai = client[kInternal].ai;\n\n const [lastSentMessageId, setLastSentMessageId] =\n useState<MessageId | null>(null);\n\n useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(\n forwardedRef,\n () => containerRef.current,\n []\n );\n\n // Register the provided tools to the chat on mount and unregister them on unmount\n useEffect(() => {\n Object.entries(tools).map(([key, value]) =>\n ai.registerChatTool(chatId, key, value)\n );\n return () => {\n Object.entries(tools).map(([key]) =>\n ai.unregisterChatTool(chatId, key)\n );\n };\n }, [ai, chatId, tools]);\n\n const scrollToBottomCallbackRef =\n useRef<(behavior: \"instant\" | \"smooth\") => void>(undefined);\n if (scrollToBottomCallbackRef.current === undefined) {\n scrollToBottomCallbackRef.current = function (\n behavior: \"instant\" | \"smooth\"\n ) {\n const container = containerRef.current;\n if (container === null) return;\n\n container.scrollTo({\n top: container.scrollHeight,\n behavior,\n });\n };\n }\n const scrollToBottom = scrollToBottomCallbackRef.current;\n\n return (\n <div\n ref={containerRef}\n {...props}\n className={classNames(\n \"lb-root lb-ai-chat\",\n layout === \"compact\"\n ? \"lb-ai-chat:layout-compact\"\n : \"lb-ai-chat:layout-inset\",\n className\n )}\n >\n {knowledge\n ? knowledge.map((source, index) => (\n <RegisterAiKnowledge\n key={index}\n description={source.description}\n value={source.value}\n // knowledgeKey={source.knowledgeKey}\n />\n ))\n : null}\n <div className=\"lb-ai-chat-content\">\n {isLoading ? (\n <Loading />\n ) : error !== undefined ? (\n <div className=\"lb-error lb-ai-chat-error\">\n {$.AI_CHAT_MESSAGES_ERROR(error)}\n </div>\n ) : messages.length === 0 ? (\n <Empty chatId={chatId} copilotId={copilotId} />\n ) : (\n <>\n <AutoScrollHandler\n lastSentMessageId={lastSentMessageId}\n scrollToBottom={scrollToBottom}\n />\n <div className=\"lb-ai-chat-messages\">\n {messages.map((message) => {\n if (message.role === \"user\") {\n return (\n <AiChatUserMessage\n key={message.id}\n message={message}\n overrides={overrides}\n />\n );\n } else if (message.role === \"assistant\") {\n return (\n <AiChatAssistantMessage\n key={message.id}\n message={message}\n overrides={overrides}\n components={components}\n />\n );\n } else {\n return null;\n }\n })}\n </div>\n </>\n )}\n </div>\n\n <div className=\"lb-ai-chat-footer\">\n <div className=\"lb-ai-chat-footer-actions\">\n <div\n className=\"lb-root lb-elevation lb-elevation-moderate lb-ai-chat-scroll-indicator\"\n data-visible={isScrollIndicatorVisible ? \"\" : undefined}\n >\n <button\n className=\"lb-ai-chat-scroll-indicator-button\"\n tabIndex={isScrollIndicatorVisible ? 0 : -1}\n aria-hidden={!isScrollIndicatorVisible}\n disabled={!isScrollIndicatorVisible}\n onClick={() => scrollToBottom(\"smooth\")}\n >\n <span className=\"lb-icon-container\">\n <ArrowDownIcon />\n </span>\n </button>\n </div>\n </div>\n <AiChatComposer\n key={chatId}\n chatId={chatId}\n copilotId={copilotId as CopilotId}\n overrides={$}\n autoFocus={autoFocus}\n onUserMessageCreate={({ id }) => setLastSentMessageId(id)}\n className={\n layout === \"inset\"\n ? \"lb-elevation lb-elevation-moderate\"\n : undefined\n }\n />\n </div>\n {/* This invisible element is a trick which allows us to use IntersectionObserver to detect when the\n * scrollable area is fully scrolled to the bottom instead of manually tracking the scroll position\n * and having to deal with resizes, etc.\n *\n * It's positioned at the bottom of the scrollable area and reliably only becomes \"visible\" to the\n * IntersectionObserver when the scrollable area is fully scrolled.\n */}\n <div\n ref={containerBottomRef}\n style={{ position: \"sticky\", height: 0 }}\n aria-hidden\n data-scroll-at-bottom={isScrollAtBottom ? \"\" : undefined}\n />\n </div>\n );\n }\n);\n\nfunction AutoScrollHandler({\n lastSentMessageId,\n scrollToBottom,\n}: {\n lastSentMessageId: MessageId | null;\n scrollToBottom: (behavior: \"instant\" | \"smooth\") => void;\n}) {\n // Scroll to bottom when the component first mounts\n useLayoutEffect(() => {\n scrollToBottom(\"instant\");\n }, [scrollToBottom]);\n\n // Scroll to bottom when sending a new message\n useEffect(() => {\n scrollToBottom(\"smooth\");\n }, [lastSentMessageId, scrollToBottom]);\n\n return null;\n}\n"],"names":["jsx","SpinnerIcon","forwardRef","overrides","useAiChatMessages","useOverrides","useRef","useVisible","useClient","kInternal","useState","useImperativeHandle","useEffect","jsxs","classNames","RegisterAiKnowledge","Fragment","AiChatUserMessage","AiChatAssistantMessage","ArrowDownIcon","AiChatComposer","useLayoutEffect"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AiChat.cjs","sources":["../../src/components/AiChat.tsx"],"sourcesContent":["import type {\n AiKnowledgeSource,\n AiOpaqueToolDefinition,\n CopilotId,\n MessageId,\n} from \"@liveblocks/core\";\nimport {\n RegisterAiKnowledge,\n RegisterAiTool,\n useAiChatMessages,\n} from \"@liveblocks/react\";\nimport { useLayoutEffect } from \"@liveblocks/react/_private\";\nimport {\n type ComponentProps,\n type ComponentType,\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { SpinnerIcon } from \"../icons/Spinner\";\nimport {\n type AiChatComposerOverrides,\n type AiChatMessageOverrides,\n type AiChatOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../overrides\";\nimport { classNames } from \"../utils/class-names\";\nimport { useVisible } from \"../utils/use-visible\";\nimport { AiChatAssistantMessage } from \"./internal/AiChatAssistantMessage\";\nimport { AiChatComposer } from \"./internal/AiChatComposer\";\nimport { AiChatUserMessage } from \"./internal/AiChatUserMessage\";\n\nexport type AiChatComponentsEmptyProps = {\n /**\n * The chat id provided to the `AiChat` component.\n */\n chatId: string;\n /**\n * The copilot id provided to the `AiChat` component.\n */\n copilotId?: string;\n};\n\nexport type AiChatComponentsLoadingProps = Record<string, never>;\n\n// TODO: Add Markdown components\nexport type AiChatComponents = {\n /**\n * The component used to render the empty state of the chat.\n */\n Empty: ComponentType<AiChatComponentsEmptyProps>;\n /**\n * The component used to render the loading state of the chat.\n */\n Loading: ComponentType<AiChatComponentsLoadingProps>;\n};\n\n/**\n * The minimum number of pixels from the bottom of the scrollable area\n * before showing the scroll to bottom indicator.\n */\nconst MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR = 50;\n\nexport interface AiChatProps extends ComponentProps<\"div\"> {\n /**\n * The id of the chat the composer belongs to.\n */\n chatId: string;\n /**\n * Whether to focus the chat composer on mount.\n */\n autoFocus?: boolean;\n /**\n * The id of the copilot to use to send the message.\n */\n copilotId?: string;\n /**\n * The contextual knowledge to include in the chat. May be used by the assistant when generating responses.\n * Any knowledge you provide via this prop will be added to any already globally registered knowledge via <RegisterAiKnowledge />.\n */\n knowledge?: AiKnowledgeSource[];\n /**\n * Tool definitions to make available within this chat. May be used by the assistant when generating responses.\n */\n tools?: Record<string, AiOpaqueToolDefinition>;\n /**\n * The layout of the chat and its composer.\n */\n layout?: \"inset\" | \"compact\";\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides &\n AiChatMessageOverrides &\n AiChatComposerOverrides &\n AiChatOverrides\n >;\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents & AiChatComponents>;\n}\n\nconst defaultComponents: AiChatComponents = {\n Empty: () => null,\n Loading: () => (\n <div className=\"lb-loading lb-ai-chat-loading\">\n <SpinnerIcon />\n </div>\n ),\n};\n\nexport const AiChat = forwardRef<HTMLDivElement, AiChatProps>(\n (\n {\n chatId,\n copilotId,\n autoFocus,\n overrides,\n knowledge,\n tools = {},\n layout = \"inset\",\n components,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const { messages, isLoading, error } = useAiChatMessages(chatId);\n const $ = useOverrides(overrides);\n const Empty = components?.Empty ?? defaultComponents.Empty;\n const Loading = components?.Loading ?? defaultComponents.Loading;\n\n const containerRef = useRef<HTMLDivElement | null>(null);\n const containerBottomRef = useRef<HTMLDivElement | null>(null);\n const isScrollIndicatorEnabled = !isLoading && !error;\n const isScrollAtBottom = useVisible(containerBottomRef, {\n enabled: isScrollIndicatorEnabled,\n root: containerRef,\n rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR,\n initialValue: null,\n });\n const isScrollIndicatorVisible =\n isScrollIndicatorEnabled && isScrollAtBottom !== null\n ? !isScrollAtBottom\n : false;\n\n const [lastSentMessageId, setLastSentMessageId] =\n useState<MessageId | null>(null);\n\n useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(\n forwardedRef,\n () => containerRef.current,\n []\n );\n\n const scrollToBottomCallbackRef =\n useRef<(behavior: \"instant\" | \"smooth\") => void>(undefined);\n if (scrollToBottomCallbackRef.current === undefined) {\n scrollToBottomCallbackRef.current = function (\n behavior: \"instant\" | \"smooth\"\n ) {\n const container = containerRef.current;\n if (container === null) return;\n\n container.scrollTo({\n top: container.scrollHeight,\n behavior,\n });\n };\n }\n const scrollToBottom = scrollToBottomCallbackRef.current;\n\n return (\n <div\n ref={containerRef}\n {...props}\n className={classNames(\n \"lb-root lb-ai-chat\",\n layout === \"compact\"\n ? \"lb-ai-chat:layout-compact\"\n : \"lb-ai-chat:layout-inset\",\n className\n )}\n >\n {knowledge\n ? knowledge.map((source, index) => (\n <RegisterAiKnowledge\n key={index}\n description={source.description}\n value={source.value}\n // knowledgeKey={source.knowledgeKey}\n />\n ))\n : null}\n\n {Object.entries(tools).map(([name, tool]) => (\n <RegisterAiTool key={name} chatId={chatId} name={name} tool={tool} />\n ))}\n\n <div className=\"lb-ai-chat-content\">\n {isLoading ? (\n <Loading />\n ) : error !== undefined ? (\n <div className=\"lb-error lb-ai-chat-error\">\n {$.AI_CHAT_MESSAGES_ERROR(error)}\n </div>\n ) : messages.length === 0 ? (\n <Empty chatId={chatId} copilotId={copilotId} />\n ) : (\n <>\n <AutoScrollHandler\n lastSentMessageId={lastSentMessageId}\n scrollToBottom={scrollToBottom}\n />\n <div className=\"lb-ai-chat-messages\">\n {messages.map((message) => {\n if (message.role === \"user\") {\n return (\n <AiChatUserMessage\n key={message.id}\n message={message}\n overrides={overrides}\n />\n );\n } else if (message.role === \"assistant\") {\n return (\n <AiChatAssistantMessage\n key={message.id}\n message={message}\n overrides={overrides}\n components={components}\n />\n );\n } else {\n return null;\n }\n })}\n </div>\n </>\n )}\n </div>\n\n <div className=\"lb-ai-chat-footer\">\n <div className=\"lb-ai-chat-footer-actions\">\n <div\n className=\"lb-root lb-elevation lb-elevation-moderate lb-ai-chat-scroll-indicator\"\n data-visible={isScrollIndicatorVisible ? \"\" : undefined}\n >\n <button\n className=\"lb-ai-chat-scroll-indicator-button\"\n tabIndex={isScrollIndicatorVisible ? 0 : -1}\n aria-hidden={!isScrollIndicatorVisible}\n disabled={!isScrollIndicatorVisible}\n onClick={() => scrollToBottom(\"smooth\")}\n >\n <span className=\"lb-icon-container\">\n <ArrowDownIcon />\n </span>\n </button>\n </div>\n </div>\n <AiChatComposer\n key={chatId}\n chatId={chatId}\n copilotId={copilotId as CopilotId}\n overrides={overrides}\n autoFocus={autoFocus}\n onUserMessageCreate={({ id }) => setLastSentMessageId(id)}\n className={\n layout === \"inset\"\n ? \"lb-elevation lb-elevation-moderate\"\n : undefined\n }\n />\n </div>\n {/* This invisible element is a trick which allows us to use IntersectionObserver to detect when the\n * scrollable area is fully scrolled to the bottom instead of manually tracking the scroll position\n * and having to deal with resizes, etc.\n *\n * It's positioned at the bottom of the scrollable area and reliably only becomes \"visible\" to the\n * IntersectionObserver when the scrollable area is fully scrolled.\n */}\n <div\n ref={containerBottomRef}\n style={{ position: \"sticky\", height: 0 }}\n aria-hidden\n data-scroll-at-bottom={isScrollAtBottom ? \"\" : undefined}\n />\n </div>\n );\n }\n);\n\nfunction AutoScrollHandler({\n lastSentMessageId,\n scrollToBottom,\n}: {\n lastSentMessageId: MessageId | null;\n scrollToBottom: (behavior: \"instant\" | \"smooth\") => void;\n}) {\n // Scroll to bottom when the component first mounts\n useLayoutEffect(() => {\n scrollToBottom(\"instant\");\n }, [scrollToBottom]);\n\n // Scroll to bottom when sending a new message\n useEffect(() => {\n scrollToBottom(\"smooth\");\n }, [lastSentMessageId, scrollToBottom]);\n\n return null;\n}\n"],"names":["jsx","SpinnerIcon","forwardRef","overrides","useAiChatMessages","useOverrides","useRef","useVisible","useState","useImperativeHandle","jsxs","classNames","RegisterAiKnowledge","RegisterAiTool","Fragment","AiChatUserMessage","AiChatAssistantMessage","ArrowDownIcon","AiChatComposer","useLayoutEffect","useEffect"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import {
|
|
3
|
-
import { useAiChatMessages, useClient, RegisterAiKnowledge } from '@liveblocks/react';
|
|
2
|
+
import { useAiChatMessages, RegisterAiKnowledge, RegisterAiTool } from '@liveblocks/react';
|
|
4
3
|
import { useLayoutEffect } from '@liveblocks/react/_private';
|
|
5
4
|
import { forwardRef, useRef, useState, useImperativeHandle, useEffect } from 'react';
|
|
6
5
|
import { ArrowDownIcon } from '../icons/ArrowDown.js';
|
|
@@ -39,30 +38,20 @@ const AiChat = forwardRef(
|
|
|
39
38
|
const Loading = components?.Loading ?? defaultComponents.Loading;
|
|
40
39
|
const containerRef = useRef(null);
|
|
41
40
|
const containerBottomRef = useRef(null);
|
|
41
|
+
const isScrollIndicatorEnabled = !isLoading && !error;
|
|
42
42
|
const isScrollAtBottom = useVisible(containerBottomRef, {
|
|
43
|
-
enabled:
|
|
43
|
+
enabled: isScrollIndicatorEnabled,
|
|
44
44
|
root: containerRef,
|
|
45
|
-
rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR
|
|
45
|
+
rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR,
|
|
46
|
+
initialValue: null
|
|
46
47
|
});
|
|
47
|
-
const isScrollIndicatorVisible =
|
|
48
|
-
const client = useClient();
|
|
49
|
-
const ai = client[kInternal].ai;
|
|
48
|
+
const isScrollIndicatorVisible = isScrollIndicatorEnabled && isScrollAtBottom !== null ? !isScrollAtBottom : false;
|
|
50
49
|
const [lastSentMessageId, setLastSentMessageId] = useState(null);
|
|
51
50
|
useImperativeHandle(
|
|
52
51
|
forwardedRef,
|
|
53
52
|
() => containerRef.current,
|
|
54
53
|
[]
|
|
55
54
|
);
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
Object.entries(tools).map(
|
|
58
|
-
([key, value]) => ai.registerChatTool(chatId, key, value)
|
|
59
|
-
);
|
|
60
|
-
return () => {
|
|
61
|
-
Object.entries(tools).map(
|
|
62
|
-
([key]) => ai.unregisterChatTool(chatId, key)
|
|
63
|
-
);
|
|
64
|
-
};
|
|
65
|
-
}, [ai, chatId, tools]);
|
|
66
55
|
const scrollToBottomCallbackRef = useRef(void 0);
|
|
67
56
|
if (scrollToBottomCallbackRef.current === void 0) {
|
|
68
57
|
scrollToBottomCallbackRef.current = function(behavior) {
|
|
@@ -89,6 +78,11 @@ const AiChat = forwardRef(
|
|
|
89
78
|
description: source.description,
|
|
90
79
|
value: source.value
|
|
91
80
|
}, index)) : null,
|
|
81
|
+
Object.entries(tools).map(([name, tool]) => /* @__PURE__ */ jsx(RegisterAiTool, {
|
|
82
|
+
chatId,
|
|
83
|
+
name,
|
|
84
|
+
tool
|
|
85
|
+
}, name)),
|
|
92
86
|
/* @__PURE__ */ jsx("div", {
|
|
93
87
|
className: "lb-ai-chat-content",
|
|
94
88
|
children: isLoading ? /* @__PURE__ */ jsx(Loading, {}) : error !== void 0 ? /* @__PURE__ */ jsx("div", {
|
|
@@ -149,7 +143,7 @@ const AiChat = forwardRef(
|
|
|
149
143
|
/* @__PURE__ */ jsx(AiChatComposer, {
|
|
150
144
|
chatId,
|
|
151
145
|
copilotId,
|
|
152
|
-
overrides
|
|
146
|
+
overrides,
|
|
153
147
|
autoFocus,
|
|
154
148
|
onUserMessageCreate: ({ id }) => setLastSentMessageId(id),
|
|
155
149
|
className: layout === "inset" ? "lb-elevation lb-elevation-moderate" : void 0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiChat.js","sources":["../../src/components/AiChat.tsx"],"sourcesContent":["import type {\n AiKnowledgeSource,\n AiOpaqueToolDefinition,\n CopilotId,\n MessageId,\n} from \"@liveblocks/core\";\nimport { kInternal } from \"@liveblocks/core\";\nimport {\n RegisterAiKnowledge,\n useAiChatMessages,\n useClient,\n} from \"@liveblocks/react\";\nimport { useLayoutEffect } from \"@liveblocks/react/_private\";\nimport {\n type ComponentProps,\n type ComponentType,\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { SpinnerIcon } from \"../icons/Spinner\";\nimport {\n type AiChatComposerOverrides,\n type AiChatMessageOverrides,\n type AiChatOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../overrides\";\nimport { classNames } from \"../utils/class-names\";\nimport { useVisible } from \"../utils/use-visible\";\nimport { AiChatAssistantMessage } from \"./internal/AiChatAssistantMessage\";\nimport { AiChatComposer } from \"./internal/AiChatComposer\";\nimport { AiChatUserMessage } from \"./internal/AiChatUserMessage\";\n\nexport type AiChatComponentsEmptyProps = {\n /**\n * The chat id provided to the `AiChat` component.\n */\n chatId: string;\n /**\n * The copilot id provided to the `AiChat` component.\n */\n copilotId?: string;\n};\n\nexport type AiChatComponentsLoadingProps = Record<string, never>;\n\n// TODO: Add Markdown components\nexport type AiChatComponents = {\n /**\n * The component used to render the empty state of the chat.\n */\n Empty: ComponentType<AiChatComponentsEmptyProps>;\n /**\n * The component used to render the loading state of the chat.\n */\n Loading: ComponentType<AiChatComponentsLoadingProps>;\n};\n\n/**\n * The minimum number of pixels from the bottom of the scrollable area\n * before showing the scroll to bottom indicator.\n */\nconst MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR = 50;\n\nexport interface AiChatProps extends ComponentProps<\"div\"> {\n /**\n * The id of the chat the composer belongs to.\n */\n chatId: string;\n /**\n * Whether to focus the chat composer on mount.\n */\n autoFocus?: boolean;\n /**\n * The id of the copilot to use to send the message.\n */\n copilotId?: string;\n /**\n * The contextual knowledge to include in the chat. May be used by the assistant when generating responses.\n * Any knowledge you provide via this prop will be added to any already globally registered knowledge via <RegisterAiKnowledge />.\n */\n knowledge?: AiKnowledgeSource[];\n /**\n * Tool definitions to make available within this chat. May be used by the assistant when generating responses.\n */\n tools?: Record<string, AiOpaqueToolDefinition>;\n /**\n * The layout of the chat and its composer.\n */\n layout?: \"inset\" | \"compact\";\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides &\n AiChatMessageOverrides &\n AiChatComposerOverrides &\n AiChatOverrides\n >;\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents & AiChatComponents>;\n}\n\nconst defaultComponents: AiChatComponents = {\n Empty: () => null,\n Loading: () => (\n <div className=\"lb-loading lb-ai-chat-loading\">\n <SpinnerIcon />\n </div>\n ),\n};\n\nexport const AiChat = forwardRef<HTMLDivElement, AiChatProps>(\n (\n {\n chatId,\n copilotId,\n autoFocus,\n overrides,\n knowledge,\n tools = {},\n layout = \"inset\",\n components,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const { messages, isLoading, error } = useAiChatMessages(chatId);\n const $ = useOverrides(overrides);\n const Empty = components?.Empty ?? defaultComponents.Empty;\n const Loading = components?.Loading ?? defaultComponents.Loading;\n\n const containerRef = useRef<HTMLDivElement | null>(null);\n const containerBottomRef = useRef<HTMLDivElement | null>(null);\n const isScrollAtBottom = useVisible(containerBottomRef, {\n enabled: !isLoading && !error,\n root: containerRef,\n rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR,\n });\n const isScrollIndicatorVisible =\n isLoading || error ? false : !isScrollAtBottom;\n\n const client = useClient();\n const ai = client[kInternal].ai;\n\n const [lastSentMessageId, setLastSentMessageId] =\n useState<MessageId | null>(null);\n\n useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(\n forwardedRef,\n () => containerRef.current,\n []\n );\n\n // Register the provided tools to the chat on mount and unregister them on unmount\n useEffect(() => {\n Object.entries(tools).map(([key, value]) =>\n ai.registerChatTool(chatId, key, value)\n );\n return () => {\n Object.entries(tools).map(([key]) =>\n ai.unregisterChatTool(chatId, key)\n );\n };\n }, [ai, chatId, tools]);\n\n const scrollToBottomCallbackRef =\n useRef<(behavior: \"instant\" | \"smooth\") => void>(undefined);\n if (scrollToBottomCallbackRef.current === undefined) {\n scrollToBottomCallbackRef.current = function (\n behavior: \"instant\" | \"smooth\"\n ) {\n const container = containerRef.current;\n if (container === null) return;\n\n container.scrollTo({\n top: container.scrollHeight,\n behavior,\n });\n };\n }\n const scrollToBottom = scrollToBottomCallbackRef.current;\n\n return (\n <div\n ref={containerRef}\n {...props}\n className={classNames(\n \"lb-root lb-ai-chat\",\n layout === \"compact\"\n ? \"lb-ai-chat:layout-compact\"\n : \"lb-ai-chat:layout-inset\",\n className\n )}\n >\n {knowledge\n ? knowledge.map((source, index) => (\n <RegisterAiKnowledge\n key={index}\n description={source.description}\n value={source.value}\n // knowledgeKey={source.knowledgeKey}\n />\n ))\n : null}\n <div className=\"lb-ai-chat-content\">\n {isLoading ? (\n <Loading />\n ) : error !== undefined ? (\n <div className=\"lb-error lb-ai-chat-error\">\n {$.AI_CHAT_MESSAGES_ERROR(error)}\n </div>\n ) : messages.length === 0 ? (\n <Empty chatId={chatId} copilotId={copilotId} />\n ) : (\n <>\n <AutoScrollHandler\n lastSentMessageId={lastSentMessageId}\n scrollToBottom={scrollToBottom}\n />\n <div className=\"lb-ai-chat-messages\">\n {messages.map((message) => {\n if (message.role === \"user\") {\n return (\n <AiChatUserMessage\n key={message.id}\n message={message}\n overrides={overrides}\n />\n );\n } else if (message.role === \"assistant\") {\n return (\n <AiChatAssistantMessage\n key={message.id}\n message={message}\n overrides={overrides}\n components={components}\n />\n );\n } else {\n return null;\n }\n })}\n </div>\n </>\n )}\n </div>\n\n <div className=\"lb-ai-chat-footer\">\n <div className=\"lb-ai-chat-footer-actions\">\n <div\n className=\"lb-root lb-elevation lb-elevation-moderate lb-ai-chat-scroll-indicator\"\n data-visible={isScrollIndicatorVisible ? \"\" : undefined}\n >\n <button\n className=\"lb-ai-chat-scroll-indicator-button\"\n tabIndex={isScrollIndicatorVisible ? 0 : -1}\n aria-hidden={!isScrollIndicatorVisible}\n disabled={!isScrollIndicatorVisible}\n onClick={() => scrollToBottom(\"smooth\")}\n >\n <span className=\"lb-icon-container\">\n <ArrowDownIcon />\n </span>\n </button>\n </div>\n </div>\n <AiChatComposer\n key={chatId}\n chatId={chatId}\n copilotId={copilotId as CopilotId}\n overrides={$}\n autoFocus={autoFocus}\n onUserMessageCreate={({ id }) => setLastSentMessageId(id)}\n className={\n layout === \"inset\"\n ? \"lb-elevation lb-elevation-moderate\"\n : undefined\n }\n />\n </div>\n {/* This invisible element is a trick which allows us to use IntersectionObserver to detect when the\n * scrollable area is fully scrolled to the bottom instead of manually tracking the scroll position\n * and having to deal with resizes, etc.\n *\n * It's positioned at the bottom of the scrollable area and reliably only becomes \"visible\" to the\n * IntersectionObserver when the scrollable area is fully scrolled.\n */}\n <div\n ref={containerBottomRef}\n style={{ position: \"sticky\", height: 0 }}\n aria-hidden\n data-scroll-at-bottom={isScrollAtBottom ? \"\" : undefined}\n />\n </div>\n );\n }\n);\n\nfunction AutoScrollHandler({\n lastSentMessageId,\n scrollToBottom,\n}: {\n lastSentMessageId: MessageId | null;\n scrollToBottom: (behavior: \"instant\" | \"smooth\") => void;\n}) {\n // Scroll to bottom when the component first mounts\n useLayoutEffect(() => {\n scrollToBottom(\"instant\");\n }, [scrollToBottom]);\n\n // Scroll to bottom when sending a new message\n useEffect(() => {\n scrollToBottom(\"smooth\");\n }, [lastSentMessageId, scrollToBottom]);\n\n return null;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AiChat.js","sources":["../../src/components/AiChat.tsx"],"sourcesContent":["import type {\n AiKnowledgeSource,\n AiOpaqueToolDefinition,\n CopilotId,\n MessageId,\n} from \"@liveblocks/core\";\nimport {\n RegisterAiKnowledge,\n RegisterAiTool,\n useAiChatMessages,\n} from \"@liveblocks/react\";\nimport { useLayoutEffect } from \"@liveblocks/react/_private\";\nimport {\n type ComponentProps,\n type ComponentType,\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { SpinnerIcon } from \"../icons/Spinner\";\nimport {\n type AiChatComposerOverrides,\n type AiChatMessageOverrides,\n type AiChatOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../overrides\";\nimport { classNames } from \"../utils/class-names\";\nimport { useVisible } from \"../utils/use-visible\";\nimport { AiChatAssistantMessage } from \"./internal/AiChatAssistantMessage\";\nimport { AiChatComposer } from \"./internal/AiChatComposer\";\nimport { AiChatUserMessage } from \"./internal/AiChatUserMessage\";\n\nexport type AiChatComponentsEmptyProps = {\n /**\n * The chat id provided to the `AiChat` component.\n */\n chatId: string;\n /**\n * The copilot id provided to the `AiChat` component.\n */\n copilotId?: string;\n};\n\nexport type AiChatComponentsLoadingProps = Record<string, never>;\n\n// TODO: Add Markdown components\nexport type AiChatComponents = {\n /**\n * The component used to render the empty state of the chat.\n */\n Empty: ComponentType<AiChatComponentsEmptyProps>;\n /**\n * The component used to render the loading state of the chat.\n */\n Loading: ComponentType<AiChatComponentsLoadingProps>;\n};\n\n/**\n * The minimum number of pixels from the bottom of the scrollable area\n * before showing the scroll to bottom indicator.\n */\nconst MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR = 50;\n\nexport interface AiChatProps extends ComponentProps<\"div\"> {\n /**\n * The id of the chat the composer belongs to.\n */\n chatId: string;\n /**\n * Whether to focus the chat composer on mount.\n */\n autoFocus?: boolean;\n /**\n * The id of the copilot to use to send the message.\n */\n copilotId?: string;\n /**\n * The contextual knowledge to include in the chat. May be used by the assistant when generating responses.\n * Any knowledge you provide via this prop will be added to any already globally registered knowledge via <RegisterAiKnowledge />.\n */\n knowledge?: AiKnowledgeSource[];\n /**\n * Tool definitions to make available within this chat. May be used by the assistant when generating responses.\n */\n tools?: Record<string, AiOpaqueToolDefinition>;\n /**\n * The layout of the chat and its composer.\n */\n layout?: \"inset\" | \"compact\";\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides &\n AiChatMessageOverrides &\n AiChatComposerOverrides &\n AiChatOverrides\n >;\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents & AiChatComponents>;\n}\n\nconst defaultComponents: AiChatComponents = {\n Empty: () => null,\n Loading: () => (\n <div className=\"lb-loading lb-ai-chat-loading\">\n <SpinnerIcon />\n </div>\n ),\n};\n\nexport const AiChat = forwardRef<HTMLDivElement, AiChatProps>(\n (\n {\n chatId,\n copilotId,\n autoFocus,\n overrides,\n knowledge,\n tools = {},\n layout = \"inset\",\n components,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const { messages, isLoading, error } = useAiChatMessages(chatId);\n const $ = useOverrides(overrides);\n const Empty = components?.Empty ?? defaultComponents.Empty;\n const Loading = components?.Loading ?? defaultComponents.Loading;\n\n const containerRef = useRef<HTMLDivElement | null>(null);\n const containerBottomRef = useRef<HTMLDivElement | null>(null);\n const isScrollIndicatorEnabled = !isLoading && !error;\n const isScrollAtBottom = useVisible(containerBottomRef, {\n enabled: isScrollIndicatorEnabled,\n root: containerRef,\n rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR,\n initialValue: null,\n });\n const isScrollIndicatorVisible =\n isScrollIndicatorEnabled && isScrollAtBottom !== null\n ? !isScrollAtBottom\n : false;\n\n const [lastSentMessageId, setLastSentMessageId] =\n useState<MessageId | null>(null);\n\n useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(\n forwardedRef,\n () => containerRef.current,\n []\n );\n\n const scrollToBottomCallbackRef =\n useRef<(behavior: \"instant\" | \"smooth\") => void>(undefined);\n if (scrollToBottomCallbackRef.current === undefined) {\n scrollToBottomCallbackRef.current = function (\n behavior: \"instant\" | \"smooth\"\n ) {\n const container = containerRef.current;\n if (container === null) return;\n\n container.scrollTo({\n top: container.scrollHeight,\n behavior,\n });\n };\n }\n const scrollToBottom = scrollToBottomCallbackRef.current;\n\n return (\n <div\n ref={containerRef}\n {...props}\n className={classNames(\n \"lb-root lb-ai-chat\",\n layout === \"compact\"\n ? \"lb-ai-chat:layout-compact\"\n : \"lb-ai-chat:layout-inset\",\n className\n )}\n >\n {knowledge\n ? knowledge.map((source, index) => (\n <RegisterAiKnowledge\n key={index}\n description={source.description}\n value={source.value}\n // knowledgeKey={source.knowledgeKey}\n />\n ))\n : null}\n\n {Object.entries(tools).map(([name, tool]) => (\n <RegisterAiTool key={name} chatId={chatId} name={name} tool={tool} />\n ))}\n\n <div className=\"lb-ai-chat-content\">\n {isLoading ? (\n <Loading />\n ) : error !== undefined ? (\n <div className=\"lb-error lb-ai-chat-error\">\n {$.AI_CHAT_MESSAGES_ERROR(error)}\n </div>\n ) : messages.length === 0 ? (\n <Empty chatId={chatId} copilotId={copilotId} />\n ) : (\n <>\n <AutoScrollHandler\n lastSentMessageId={lastSentMessageId}\n scrollToBottom={scrollToBottom}\n />\n <div className=\"lb-ai-chat-messages\">\n {messages.map((message) => {\n if (message.role === \"user\") {\n return (\n <AiChatUserMessage\n key={message.id}\n message={message}\n overrides={overrides}\n />\n );\n } else if (message.role === \"assistant\") {\n return (\n <AiChatAssistantMessage\n key={message.id}\n message={message}\n overrides={overrides}\n components={components}\n />\n );\n } else {\n return null;\n }\n })}\n </div>\n </>\n )}\n </div>\n\n <div className=\"lb-ai-chat-footer\">\n <div className=\"lb-ai-chat-footer-actions\">\n <div\n className=\"lb-root lb-elevation lb-elevation-moderate lb-ai-chat-scroll-indicator\"\n data-visible={isScrollIndicatorVisible ? \"\" : undefined}\n >\n <button\n className=\"lb-ai-chat-scroll-indicator-button\"\n tabIndex={isScrollIndicatorVisible ? 0 : -1}\n aria-hidden={!isScrollIndicatorVisible}\n disabled={!isScrollIndicatorVisible}\n onClick={() => scrollToBottom(\"smooth\")}\n >\n <span className=\"lb-icon-container\">\n <ArrowDownIcon />\n </span>\n </button>\n </div>\n </div>\n <AiChatComposer\n key={chatId}\n chatId={chatId}\n copilotId={copilotId as CopilotId}\n overrides={overrides}\n autoFocus={autoFocus}\n onUserMessageCreate={({ id }) => setLastSentMessageId(id)}\n className={\n layout === \"inset\"\n ? \"lb-elevation lb-elevation-moderate\"\n : undefined\n }\n />\n </div>\n {/* This invisible element is a trick which allows us to use IntersectionObserver to detect when the\n * scrollable area is fully scrolled to the bottom instead of manually tracking the scroll position\n * and having to deal with resizes, etc.\n *\n * It's positioned at the bottom of the scrollable area and reliably only becomes \"visible\" to the\n * IntersectionObserver when the scrollable area is fully scrolled.\n */}\n <div\n ref={containerBottomRef}\n style={{ position: \"sticky\", height: 0 }}\n aria-hidden\n data-scroll-at-bottom={isScrollAtBottom ? \"\" : undefined}\n />\n </div>\n );\n }\n);\n\nfunction AutoScrollHandler({\n lastSentMessageId,\n scrollToBottom,\n}: {\n lastSentMessageId: MessageId | null;\n scrollToBottom: (behavior: \"instant\" | \"smooth\") => void;\n}) {\n // Scroll to bottom when the component first mounts\n useLayoutEffect(() => {\n scrollToBottom(\"instant\");\n }, [scrollToBottom]);\n\n // Scroll to bottom when sending a new message\n useEffect(() => {\n scrollToBottom(\"smooth\");\n }, [lastSentMessageId, scrollToBottom]);\n\n return null;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -5,9 +5,11 @@ var core = require('@liveblocks/core');
|
|
|
5
5
|
var react = require('react');
|
|
6
6
|
require('../_private/index.cjs');
|
|
7
7
|
require('../icons/index.cjs');
|
|
8
|
+
var overrides = require('../overrides.cjs');
|
|
8
9
|
var contexts = require('../primitives/AiMessage/contexts.cjs');
|
|
9
10
|
var index = require('../primitives/Collapsible/index.cjs');
|
|
10
11
|
var classNames = require('../utils/class-names.cjs');
|
|
12
|
+
var useControllableState = require('../utils/use-controllable-state.cjs');
|
|
11
13
|
var CodeBlock = require('./internal/CodeBlock.cjs');
|
|
12
14
|
var Button = require('./internal/Button.cjs');
|
|
13
15
|
var ChevronRight = require('../icons/ChevronRight.cjs');
|
|
@@ -42,15 +44,14 @@ function AiToolConfirmation({
|
|
|
42
44
|
variant = "default",
|
|
43
45
|
confirm,
|
|
44
46
|
cancel,
|
|
47
|
+
overrides: overrides$1,
|
|
45
48
|
className,
|
|
46
49
|
...props
|
|
47
50
|
}) {
|
|
48
|
-
const { status, args, respond,
|
|
51
|
+
const { status, args, respond, name, invocationId } = contexts.useAiToolInvocationContext();
|
|
52
|
+
const $ = overrides.useOverrides(overrides$1);
|
|
49
53
|
const enabled = status === "executing";
|
|
50
|
-
const context = react.useMemo(
|
|
51
|
-
() => ({ toolName, toolCallId }),
|
|
52
|
-
[toolName, toolCallId]
|
|
53
|
-
);
|
|
54
|
+
const context = react.useMemo(() => ({ name, invocationId }), [name, invocationId]);
|
|
54
55
|
const onConfirmClick = react.useCallback(async () => {
|
|
55
56
|
if (enabled) {
|
|
56
57
|
respond(await confirm(args, context));
|
|
@@ -61,7 +62,7 @@ function AiToolConfirmation({
|
|
|
61
62
|
respond(await cancel(args, context));
|
|
62
63
|
}
|
|
63
64
|
}, [enabled, args, cancel, respond, context]);
|
|
64
|
-
if (status === "executed") {
|
|
65
|
+
if (status === "executed" && !children) {
|
|
65
66
|
return null;
|
|
66
67
|
}
|
|
67
68
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
@@ -72,22 +73,22 @@ function AiToolConfirmation({
|
|
|
72
73
|
className: "lb-ai-tool-confirmation-content",
|
|
73
74
|
children
|
|
74
75
|
}) : null,
|
|
75
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
76
|
+
status !== "executed" && /* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
76
77
|
className: "lb-ai-tool-confirmation-footer",
|
|
77
78
|
children: /* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
78
79
|
className: "lb-ai-tool-confirmation-actions",
|
|
79
80
|
children: [
|
|
80
81
|
/* @__PURE__ */ jsxRuntime.jsx(Button.Button, {
|
|
81
82
|
disabled: !enabled,
|
|
82
|
-
onClick:
|
|
83
|
-
variant:
|
|
84
|
-
children:
|
|
83
|
+
onClick: onCancelClick,
|
|
84
|
+
variant: "secondary",
|
|
85
|
+
children: $.AI_TOOL_CONFIRMATION_CANCEL
|
|
85
86
|
}),
|
|
86
87
|
/* @__PURE__ */ jsxRuntime.jsx(Button.Button, {
|
|
87
88
|
disabled: !enabled,
|
|
88
|
-
onClick:
|
|
89
|
-
variant: "
|
|
90
|
-
children:
|
|
89
|
+
onClick: onConfirmClick,
|
|
90
|
+
variant: variant === "destructive" ? "destructive" : "primary",
|
|
91
|
+
children: $.AI_TOOL_CONFIRMATION_CONFIRM
|
|
91
92
|
})
|
|
92
93
|
]
|
|
93
94
|
})
|
|
@@ -98,27 +99,39 @@ function AiToolConfirmation({
|
|
|
98
99
|
function prettifyString(string) {
|
|
99
100
|
return string.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().toLowerCase().replace(/^\w/, (character) => character.toUpperCase());
|
|
100
101
|
}
|
|
101
|
-
const noop = () => {
|
|
102
|
-
};
|
|
103
102
|
const AiTool = Object.assign(
|
|
104
103
|
react.forwardRef(
|
|
105
|
-
({
|
|
104
|
+
({
|
|
105
|
+
children,
|
|
106
|
+
title,
|
|
107
|
+
icon,
|
|
108
|
+
collapsed,
|
|
109
|
+
onCollapsedChange,
|
|
110
|
+
className,
|
|
111
|
+
...props
|
|
112
|
+
}, forwardedRef) => {
|
|
106
113
|
const {
|
|
107
114
|
status,
|
|
108
|
-
|
|
115
|
+
name,
|
|
109
116
|
[core.kInternal]: { execute }
|
|
110
117
|
} = contexts.useAiToolInvocationContext();
|
|
111
|
-
const [
|
|
118
|
+
const [semiControlledCollapsed, onSemiControlledCollapsed] = useControllableState.useSemiControllableState(collapsed ?? false, onCollapsedChange);
|
|
112
119
|
const hasContent = react.Children.count(children) > 0;
|
|
113
120
|
const resolvedTitle = react.useMemo(() => {
|
|
114
|
-
return title ?? prettifyString(
|
|
115
|
-
}, [title,
|
|
121
|
+
return title ?? prettifyString(name);
|
|
122
|
+
}, [title, name]);
|
|
123
|
+
const handleCollapsibleOpenChange = react.useCallback(
|
|
124
|
+
(open) => {
|
|
125
|
+
onSemiControlledCollapsed(!open);
|
|
126
|
+
},
|
|
127
|
+
[onSemiControlledCollapsed]
|
|
128
|
+
);
|
|
116
129
|
return /* @__PURE__ */ jsxRuntime.jsxs(index.Root, {
|
|
117
130
|
ref: forwardedRef,
|
|
118
131
|
className: classNames.classNames("lb-collapsible lb-ai-tool", className),
|
|
119
132
|
...props,
|
|
120
|
-
open: hasContent ?
|
|
121
|
-
onOpenChange:
|
|
133
|
+
open: hasContent ? !semiControlledCollapsed : false,
|
|
134
|
+
onOpenChange: handleCollapsibleOpenChange,
|
|
122
135
|
disabled: !hasContent,
|
|
123
136
|
children: [
|
|
124
137
|
/* @__PURE__ */ jsxRuntime.jsxs(index.Trigger, {
|
|
@@ -142,7 +155,7 @@ const AiTool = Object.assign(
|
|
|
142
155
|
})
|
|
143
156
|
]
|
|
144
157
|
}),
|
|
145
|
-
|
|
158
|
+
hasContent ? /* @__PURE__ */ jsxRuntime.jsx(index.Content, {
|
|
146
159
|
className: "lb-collapsible-content lb-ai-tool-content-container",
|
|
147
160
|
children: /* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
148
161
|
className: "lb-ai-tool-content",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiTool.cjs","sources":["../../src/components/AiTool.tsx"],"sourcesContent":["import {\n type AiToolExecuteCallback,\n type AiToolTypePack,\n type JsonObject,\n kInternal,\n type NoInfr,\n type ToolResultData,\n} from \"@liveblocks/core\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { Children, forwardRef, useCallback, useMemo, useState } from \"react\";\n\nimport { Button } from \"../_private\";\nimport { CheckCircleFillIcon, ChevronRightIcon, SpinnerIcon } from \"../icons\";\nimport { useAiToolInvocationContext } from \"../primitives/AiMessage/contexts\";\nimport * as Collapsible from \"../primitives/Collapsible\";\nimport { classNames } from \"../utils/class-names\";\nimport { CodeBlock } from \"./internal/CodeBlock\";\n\nexport interface AiToolProps\n extends Omit<ComponentProps<\"div\">, \"title\" | \"children\"> {\n /**\n * The tool's title.\n *\n * By default, a human-readable version of the tool's name is used:\n * - `\"showTodo\"` → \"Show todo\"\n * - `\"get_weather\"` → \"Get weather\"\n */\n title?: string;\n\n /**\n * An optional icon displayed next to the title.\n */\n icon?: ReactNode;\n\n /**\n * The content shown in the tool.\n */\n children?: ReactNode;\n}\n\nexport type AiToolIconProps = ComponentProps<\"div\">;\n\nexport type AiToolInspectorProps = ComponentProps<\"div\">;\n\n// TODO: AiToolConfirmationProps might need a generic since we're outside of the\n// tool definition so things like inferred args and result types are not\n// available here for `confirm` and `cancel`\n\n/**\n * @private This API will change, and is not considered stable. DO NOT RELY on it.\n *\n * This component can be used to build human-in-the-loop interfaces.\n */\nexport interface AiToolConfirmationProps<\n A extends JsonObject,\n R extends ToolResultData,\n> extends ComponentProps<\"div\"> {\n types?: NoInfr<AiToolTypePack<A, R>>;\n args?: A;\n confirm: AiToolExecuteCallback<A, R>;\n cancel: AiToolExecuteCallback<A, R>;\n variant?: \"default\" | \"destructive\";\n\n // TODO: Use existing overrides API to allow customizing the \"Confirm\" and \"Cancel\" labels\n // overrides?: Partial<GlobalOverrides & AiToolConfirmationOverrides>;\n}\n\nfunction AiToolIcon({ className, ...props }: AiToolIconProps) {\n return (\n <div className={classNames(\"lb-ai-tool-icon\", className)} {...props} />\n );\n}\n\nfunction AiToolInspector({ className, ...props }: AiToolInspectorProps) {\n const { args, partialArgs, result } = useAiToolInvocationContext();\n\n return (\n <div className={classNames(\"lb-ai-tool-inspector\", className)} {...props}>\n <CodeBlock\n title=\"Arguments\"\n code={JSON.stringify(args ?? partialArgs, null, 2)}\n />\n {result !== undefined ? (\n <CodeBlock title=\"Result\" code={JSON.stringify(result, null, 2)} />\n ) : null}\n </div>\n );\n}\n\nfunction AiToolConfirmation<\n TPack extends AiToolTypePack,\n A extends JsonObject = TPack[\"A\"],\n R extends ToolResultData = TPack[\"R\"],\n>({\n children,\n variant = \"default\",\n confirm,\n cancel,\n className,\n ...props\n}: AiToolConfirmationProps<A, R>) {\n const { status, args, respond, toolName, toolCallId } =\n useAiToolInvocationContext();\n\n const enabled = status === \"executing\";\n\n const context = useMemo(\n () => ({ toolName, toolCallId }),\n [toolName, toolCallId]\n );\n\n const onConfirmClick = useCallback(async () => {\n if (enabled) {\n respond(await confirm(args as A, context));\n }\n }, [enabled, args, confirm, respond, context]);\n\n const onCancelClick = useCallback(async () => {\n if (enabled) {\n respond(await cancel(args as A, context));\n }\n }, [enabled, args, cancel, respond, context]);\n\n if (status === \"executed\") {\n return null;\n }\n\n return (\n <div\n className={classNames(\"lb-ai-tool-confirmation\", className)}\n {...props}\n >\n {children ? (\n <div className=\"lb-ai-tool-confirmation-content\">{children}</div>\n ) : null}\n <div className=\"lb-ai-tool-confirmation-footer\">\n <div className=\"lb-ai-tool-confirmation-actions\">\n <Button\n disabled={!enabled}\n onClick={onConfirmClick}\n variant={variant === \"destructive\" ? \"destructive\" : \"primary\"}\n >\n Confirm\n </Button>\n <Button\n disabled={!enabled}\n onClick={onCancelClick}\n variant=\"secondary\"\n >\n Cancel\n </Button>\n </div>\n </div>\n </div>\n );\n}\n\nfunction prettifyString(string: string) {\n return (\n string\n // Convert camelCase to spaces\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n // Convert snake_case and kebab-case to spaces\n .replace(/[_-]+/g, \" \")\n // Collapse multiple following spaces\n .replace(/\\s+/g, \" \")\n // Trim leading and trailing spaces\n .trim()\n // Capitalize first word\n .toLowerCase()\n .replace(/^\\w/, (character) => character.toUpperCase())\n );\n}\n\nconst noop = () => {};\n\nexport const AiTool = Object.assign(\n forwardRef<HTMLDivElement, AiToolProps>(\n ({ children, title, icon, className, ...props }, forwardedRef) => {\n const {\n status,\n toolName,\n [kInternal]: { execute },\n } = useAiToolInvocationContext();\n const [isOpen, setIsOpen] = useState(true);\n // TODO: This check won't work for cases like:\n // <AiTool>\n // <ComponentThatRendersNull />\n // <ComponentThatAlsoRendersNull />\n // </AiTool>\n // One solution could be to check the DOM on every render with `useLayoutEffect`\n // to see if there's any actual content.\n const hasContent = Children.count(children) > 0;\n const resolvedTitle = useMemo(() => {\n return title ?? prettifyString(toolName);\n }, [title, toolName]);\n\n return (\n <Collapsible.Root\n ref={forwardedRef}\n className={classNames(\"lb-collapsible lb-ai-tool\", className)}\n {...props}\n open={hasContent ? isOpen : false}\n onOpenChange={hasContent ? setIsOpen : noop}\n disabled={!hasContent}\n >\n <Collapsible.Trigger className=\"lb-collapsible-trigger lb-ai-tool-header\">\n {icon ? (\n <div className=\"lb-ai-tool-header-icon-container\">{icon}</div>\n ) : null}\n <span className=\"lb-ai-tool-header-title\">{resolvedTitle}</span>\n {hasContent ? (\n <span className=\"lb-collapsible-chevron lb-icon-container\">\n <ChevronRightIcon />\n </span>\n ) : null}\n <div className=\"lb-ai-tool-header-status\">\n {status === \"executed\" ? (\n <CheckCircleFillIcon />\n ) : execute !== undefined ? (\n // Only show a spinner if the tool has an `execute` method.\n <SpinnerIcon />\n ) : null}\n </div>\n </Collapsible.Trigger>\n\n {children ? (\n <Collapsible.Content className=\"lb-collapsible-content lb-ai-tool-content-container\">\n <div className=\"lb-ai-tool-content\">{children}</div>\n </Collapsible.Content>\n ) : null}\n </Collapsible.Root>\n );\n }\n ),\n {\n Icon: AiToolIcon,\n Inspector: AiToolInspector,\n Confirmation: AiToolConfirmation,\n }\n);\n"],"names":["jsx","classNames","useAiToolInvocationContext","jsxs","CodeBlock","useMemo","useCallback","Button","forwardRef","kInternal","useState","Children","Collapsible.Root","Collapsible.Trigger","ChevronRightIcon","CheckCircleFillIcon","SpinnerIcon","Collapsible.Content"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AiTool.cjs","sources":["../../src/components/AiTool.tsx"],"sourcesContent":["import {\n type AiToolExecuteCallback,\n type AiToolTypePack,\n type JsonObject,\n kInternal,\n type NoInfr,\n type ToolResultData,\n} from \"@liveblocks/core\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { Children, forwardRef, useCallback, useMemo } from \"react\";\n\nimport { Button } from \"../_private\";\nimport { CheckCircleFillIcon, ChevronRightIcon, SpinnerIcon } from \"../icons\";\nimport {\n type AiToolConfirmationOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../overrides\";\nimport { useAiToolInvocationContext } from \"../primitives/AiMessage/contexts\";\nimport * as Collapsible from \"../primitives/Collapsible\";\nimport { classNames } from \"../utils/class-names\";\nimport { useSemiControllableState } from \"../utils/use-controllable-state\";\nimport { CodeBlock } from \"./internal/CodeBlock\";\n\nexport interface AiToolProps\n extends Omit<ComponentProps<\"div\">, \"title\" | \"children\"> {\n /**\n * The tool's title.\n *\n * By default, a human-readable version of the tool's name is used:\n * - `\"showTodo\"` → \"Show todo\"\n * - `\"get_weather\"` → \"Get weather\"\n */\n title?: string;\n\n /**\n * An optional icon displayed next to the title.\n */\n icon?: ReactNode;\n\n /**\n * The content shown in the tool.\n */\n children?: ReactNode;\n\n /**\n * Whether the content is currently collapsed.\n * It is not a traditional controlled value, as in if you set it to `true` it would only stay expanded.\n * Instead, it is \"semi-controlled\", meaning that setting it to `true` will expand it, but it\n * can still be collapsed/expanded by clicking on it.\n */\n collapsed?: boolean;\n\n /**\n * The event handler called when the content is collapsed or expanded by clicking on it.\n */\n onCollapsedChange?: (collapsed: boolean) => void;\n}\n\nexport type AiToolIconProps = ComponentProps<\"div\">;\n\nexport type AiToolInspectorProps = ComponentProps<\"div\">;\n\n/**\n * @private This API will change, and is not considered stable. DO NOT RELY on it.\n *\n * This component can be used to build human-in-the-loop interfaces.\n */\nexport interface AiToolConfirmationProps<\n A extends JsonObject,\n R extends ToolResultData,\n> extends ComponentProps<\"div\"> {\n types?: NoInfr<AiToolTypePack<A, R>>;\n args?: A;\n confirm: AiToolExecuteCallback<A, R>;\n cancel: AiToolExecuteCallback<A, R>;\n variant?: \"default\" | \"destructive\";\n overrides?: Partial<GlobalOverrides & AiToolConfirmationOverrides>;\n}\n\nfunction AiToolIcon({ className, ...props }: AiToolIconProps) {\n return (\n <div className={classNames(\"lb-ai-tool-icon\", className)} {...props} />\n );\n}\n\nfunction AiToolInspector({ className, ...props }: AiToolInspectorProps) {\n const { args, partialArgs, result } = useAiToolInvocationContext();\n\n return (\n <div className={classNames(\"lb-ai-tool-inspector\", className)} {...props}>\n <CodeBlock\n title=\"Arguments\"\n code={JSON.stringify(args ?? partialArgs, null, 2)}\n />\n {result !== undefined ? (\n <CodeBlock title=\"Result\" code={JSON.stringify(result, null, 2)} />\n ) : null}\n </div>\n );\n}\n\nfunction AiToolConfirmation<\n TPack extends AiToolTypePack,\n A extends JsonObject = TPack[\"A\"],\n R extends ToolResultData = TPack[\"R\"],\n>({\n children,\n variant = \"default\",\n confirm,\n cancel,\n overrides,\n className,\n ...props\n}: AiToolConfirmationProps<A, R>) {\n const { status, args, respond, name, invocationId } =\n useAiToolInvocationContext();\n const $ = useOverrides(overrides);\n\n const enabled = status === \"executing\";\n\n const context = useMemo(() => ({ name, invocationId }), [name, invocationId]);\n\n const onConfirmClick = useCallback(async () => {\n if (enabled) {\n respond(await confirm(args as A, context));\n }\n }, [enabled, args, confirm, respond, context]);\n\n const onCancelClick = useCallback(async () => {\n if (enabled) {\n respond(await cancel(args as A, context));\n }\n }, [enabled, args, cancel, respond, context]);\n\n // If there's no content and the tool has been executed (so there's no\n // confirmation UI displayed either), don't render anything.\n if (status === \"executed\" && !children) {\n return null;\n }\n\n return (\n <div\n className={classNames(\"lb-ai-tool-confirmation\", className)}\n {...props}\n >\n {children ? (\n <div className=\"lb-ai-tool-confirmation-content\">{children}</div>\n ) : null}\n {status !== \"executed\" && (\n <div className=\"lb-ai-tool-confirmation-footer\">\n <div className=\"lb-ai-tool-confirmation-actions\">\n <Button\n disabled={!enabled}\n onClick={onCancelClick}\n variant=\"secondary\"\n >\n {$.AI_TOOL_CONFIRMATION_CANCEL}\n </Button>\n <Button\n disabled={!enabled}\n onClick={onConfirmClick}\n variant={variant === \"destructive\" ? \"destructive\" : \"primary\"}\n >\n {$.AI_TOOL_CONFIRMATION_CONFIRM}\n </Button>\n </div>\n </div>\n )}\n </div>\n );\n}\n\nfunction prettifyString(string: string) {\n return (\n string\n // Convert camelCase to spaces\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n // Convert snake_case and kebab-case to spaces\n .replace(/[_-]+/g, \" \")\n // Collapse multiple following spaces\n .replace(/\\s+/g, \" \")\n // Trim leading and trailing spaces\n .trim()\n // Capitalize first word\n .toLowerCase()\n .replace(/^\\w/, (character) => character.toUpperCase())\n );\n}\n\nexport const AiTool = Object.assign(\n forwardRef<HTMLDivElement, AiToolProps>(\n (\n {\n children,\n title,\n icon,\n collapsed,\n onCollapsedChange,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const {\n status,\n name,\n [kInternal]: { execute },\n } = useAiToolInvocationContext();\n const [semiControlledCollapsed, onSemiControlledCollapsed] =\n useSemiControllableState(collapsed ?? false, onCollapsedChange);\n // TODO: This check won't work for cases like:\n // <AiTool>\n // <ComponentThatRendersNull />\n // <ComponentThatAlsoRendersNull />\n // </AiTool>\n // One solution could be to check the DOM on every render with `useLayoutEffect`\n // to see if there's any actual content.\n // For now we're limiting the visual issues caused by the above by using CSS's\n // `:empty` pseudo-class to make the content 0px high if it's actually empty.\n const hasContent = Children.count(children) > 0;\n const resolvedTitle = useMemo(() => {\n return title ?? prettifyString(name);\n }, [title, name]);\n\n // `AiTool` uses \"collapsed\" instead of \"open\" (like the `Composer` component) because \"open\"\n // makes sense next to something called \"Collapsible\" but less so for something called \"AiTool\".\n const handleCollapsibleOpenChange = useCallback(\n (open: boolean) => {\n onSemiControlledCollapsed(!open);\n },\n [onSemiControlledCollapsed]\n );\n\n return (\n <Collapsible.Root\n ref={forwardedRef}\n className={classNames(\"lb-collapsible lb-ai-tool\", className)}\n {...props}\n // Regardless of `semiControlledCollapsed`, the collapsible is closed if there's no content.\n open={hasContent ? !semiControlledCollapsed : false}\n onOpenChange={handleCollapsibleOpenChange}\n disabled={!hasContent}\n >\n <Collapsible.Trigger className=\"lb-collapsible-trigger lb-ai-tool-header\">\n {icon ? (\n <div className=\"lb-ai-tool-header-icon-container\">{icon}</div>\n ) : null}\n <span className=\"lb-ai-tool-header-title\">{resolvedTitle}</span>\n {hasContent ? (\n <span className=\"lb-collapsible-chevron lb-icon-container\">\n <ChevronRightIcon />\n </span>\n ) : null}\n <div className=\"lb-ai-tool-header-status\">\n {status === \"executed\" ? (\n <CheckCircleFillIcon />\n ) : execute !== undefined ? (\n // Only show a spinner if the tool has an `execute` method.\n <SpinnerIcon />\n ) : null}\n </div>\n </Collapsible.Trigger>\n\n {hasContent ? (\n <Collapsible.Content className=\"lb-collapsible-content lb-ai-tool-content-container\">\n <div className=\"lb-ai-tool-content\">{children}</div>\n </Collapsible.Content>\n ) : null}\n </Collapsible.Root>\n );\n }\n ),\n {\n Icon: AiToolIcon,\n Inspector: AiToolInspector,\n Confirmation: AiToolConfirmation,\n }\n);\n"],"names":["jsx","classNames","useAiToolInvocationContext","jsxs","CodeBlock","overrides","useOverrides","useMemo","useCallback","Button","forwardRef","kInternal","useSemiControllableState","Children","Collapsible.Root","Collapsible.Trigger","ChevronRightIcon","CheckCircleFillIcon","SpinnerIcon","Collapsible.Content"],"mappings":";;;;;;;;;;;;;;;;;;AAgFA,SAAS,UAAW,CAAA,EAAE,SAAc,EAAA,GAAA,KAAA,EAA0B,EAAA;AAC5D,EAAA,uBACGA,cAAA,CAAA,KAAA,EAAA;AAAA,IAAI,SAAA,EAAWC,qBAAW,CAAA,iBAAA,EAAmB,SAAS,CAAA;AAAA,IAAI,GAAG,KAAA;AAAA,GAAO,CAAA,CAAA;AAEzE,CAAA;AAEA,SAAS,eAAgB,CAAA,EAAE,SAAc,EAAA,GAAA,KAAA,EAA+B,EAAA;AACtE,EAAA,MAAM,EAAE,IAAA,EAAM,WAAa,EAAA,MAAA,KAAWC,mCAA2B,EAAA,CAAA;AAEjE,EAAA,uBACGC,eAAA,CAAA,KAAA,EAAA;AAAA,IAAI,SAAA,EAAWF,qBAAW,CAAA,sBAAA,EAAwB,SAAS,CAAA;AAAA,IAAI,GAAG,KAAA;AAAA,IACjE,QAAA,EAAA;AAAA,sBAACD,cAAA,CAAAI,mBAAA,EAAA;AAAA,QACC,KAAM,EAAA,WAAA;AAAA,QACN,MAAM,IAAK,CAAA,SAAA,CAAU,IAAQ,IAAA,WAAA,EAAa,MAAM,CAAC,CAAA;AAAA,OACnD,CAAA;AAAA,MACC,MAAA,KAAW,yBACTJ,cAAA,CAAAI,mBAAA,EAAA;AAAA,QAAU,KAAM,EAAA,QAAA;AAAA,QAAS,IAAM,EAAA,IAAA,CAAK,SAAU,CAAA,MAAA,EAAQ,MAAM,CAAC,CAAA;AAAA,OAAG,CAC/D,GAAA,IAAA;AAAA,KAAA;AAAA,GACN,CAAA,CAAA;AAEJ,CAAA;AAEA,SAAS,kBAIP,CAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAU,GAAA,SAAA;AAAA,EACV,OAAA;AAAA,EACA,MAAA;AAAA,aACAC,WAAA;AAAA,EACA,SAAA;AAAA,EACG,GAAA,KAAA;AACL,CAAkC,EAAA;AAChC,EAAA,MAAM,EAAE,MAAQ,EAAA,IAAA,EAAM,SAAS,IAAM,EAAA,YAAA,KACnCH,mCAA2B,EAAA,CAAA;AAC7B,EAAM,MAAA,CAAA,GAAII,uBAAaD,WAAS,CAAA,CAAA;AAEhC,EAAA,MAAM,UAAU,MAAW,KAAA,WAAA,CAAA;AAE3B,EAAM,MAAA,OAAA,GAAUE,aAAQ,CAAA,OAAO,EAAE,IAAA,EAAM,cAAiB,CAAA,EAAA,CAAC,IAAM,EAAA,YAAY,CAAC,CAAA,CAAA;AAE5E,EAAM,MAAA,cAAA,GAAiBC,kBAAY,YAAY;AAC7C,IAAA,IAAI,OAAS,EAAA;AACX,MAAA,OAAA,CAAQ,MAAM,OAAA,CAAQ,IAAW,EAAA,OAAO,CAAC,CAAA,CAAA;AAAA,KAC3C;AAAA,KACC,CAAC,OAAA,EAAS,MAAM,OAAS,EAAA,OAAA,EAAS,OAAO,CAAC,CAAA,CAAA;AAE7C,EAAM,MAAA,aAAA,GAAgBA,kBAAY,YAAY;AAC5C,IAAA,IAAI,OAAS,EAAA;AACX,MAAA,OAAA,CAAQ,MAAM,MAAA,CAAO,IAAW,EAAA,OAAO,CAAC,CAAA,CAAA;AAAA,KAC1C;AAAA,KACC,CAAC,OAAA,EAAS,MAAM,MAAQ,EAAA,OAAA,EAAS,OAAO,CAAC,CAAA,CAAA;AAI5C,EAAI,IAAA,MAAA,KAAW,UAAc,IAAA,CAAC,QAAU,EAAA;AACtC,IAAO,OAAA,IAAA,CAAA;AAAA,GACT;AAEA,EAAA,uBACGL,eAAA,CAAA,KAAA,EAAA;AAAA,IACC,SAAA,EAAWF,qBAAW,CAAA,yBAAA,EAA2B,SAAS,CAAA;AAAA,IACzD,GAAG,KAAA;AAAA,IAEH,QAAA,EAAA;AAAA,MAAA,QAAA,mBACED,cAAA,CAAA,KAAA,EAAA;AAAA,QAAI,SAAU,EAAA,iCAAA;AAAA,QAAmC,QAAA;AAAA,OAAS,CACzD,GAAA,IAAA;AAAA,MACH,MAAA,KAAW,8BACTA,cAAA,CAAA,KAAA,EAAA;AAAA,QAAI,SAAU,EAAA,gCAAA;AAAA,QACb,QAAC,kBAAAG,eAAA,CAAA,KAAA,EAAA;AAAA,UAAI,SAAU,EAAA,iCAAA;AAAA,UACb,QAAA,EAAA;AAAA,4BAACH,cAAA,CAAAS,aAAA,EAAA;AAAA,cACC,UAAU,CAAC,OAAA;AAAA,cACX,OAAS,EAAA,aAAA;AAAA,cACT,OAAQ,EAAA,WAAA;AAAA,cAEP,QAAE,EAAA,CAAA,CAAA,2BAAA;AAAA,aACL,CAAA;AAAA,4BACCT,cAAA,CAAAS,aAAA,EAAA;AAAA,cACC,UAAU,CAAC,OAAA;AAAA,cACX,OAAS,EAAA,cAAA;AAAA,cACT,OAAA,EAAS,OAAY,KAAA,aAAA,GAAgB,aAAgB,GAAA,SAAA;AAAA,cAEpD,QAAE,EAAA,CAAA,CAAA,4BAAA;AAAA,aACL,CAAA;AAAA,WAAA;AAAA,SACF,CAAA;AAAA,OACF,CAAA;AAAA,KAAA;AAAA,GAEJ,CAAA,CAAA;AAEJ,CAAA;AAEA,SAAS,eAAe,MAAgB,EAAA;AACtC,EACE,OAAA,MAAA,CAEG,QAAQ,iBAAmB,EAAA,OAAO,EAElC,OAAQ,CAAA,QAAA,EAAU,GAAG,CAAA,CAErB,OAAQ,CAAA,MAAA,EAAQ,GAAG,CAEnB,CAAA,IAAA,EAEA,CAAA,WAAA,EACA,CAAA,OAAA,CAAQ,OAAO,CAAC,SAAA,KAAc,SAAU,CAAA,WAAA,EAAa,CAAA,CAAA;AAE5D,CAAA;AAEO,MAAM,SAAS,MAAO,CAAA,MAAA;AAAA,EAC3BC,gBAAA;AAAA,IACE,CACE;AAAA,MACE,QAAA;AAAA,MACA,KAAA;AAAA,MACA,IAAA;AAAA,MACA,SAAA;AAAA,MACA,iBAAA;AAAA,MACA,SAAA;AAAA,MACG,GAAA,KAAA;AAAA,OAEL,YACG,KAAA;AACH,MAAM,MAAA;AAAA,QACJ,MAAA;AAAA,QACA,IAAA;AAAA,QACC,CAAAC,cAAA,GAAY,EAAE,OAAQ,EAAA;AAAA,UACrBT,mCAA2B,EAAA,CAAA;AAC/B,MAAA,MAAM,CAAC,uBAAyB,EAAA,yBAAyB,IACvDU,6CAAyB,CAAA,SAAA,IAAa,OAAO,iBAAiB,CAAA,CAAA;AAUhE,MAAA,MAAM,UAAa,GAAAC,cAAA,CAAS,KAAM,CAAA,QAAQ,CAAI,GAAA,CAAA,CAAA;AAC9C,MAAM,MAAA,aAAA,GAAgBN,cAAQ,MAAM;AAClC,QAAO,OAAA,KAAA,IAAS,eAAe,IAAI,CAAA,CAAA;AAAA,OAClC,EAAA,CAAC,KAAO,EAAA,IAAI,CAAC,CAAA,CAAA;AAIhB,MAAA,MAAM,2BAA8B,GAAAC,iBAAA;AAAA,QAClC,CAAC,IAAkB,KAAA;AACjB,UAAA,yBAAA,CAA0B,CAAC,IAAI,CAAA,CAAA;AAAA,SACjC;AAAA,QACA,CAAC,yBAAyB,CAAA;AAAA,OAC5B,CAAA;AAEA,MACE,uBAAAL,eAAA,CAACW,UAAA,EAAA;AAAA,QACC,GAAK,EAAA,YAAA;AAAA,QACL,SAAA,EAAWb,qBAAW,CAAA,2BAAA,EAA6B,SAAS,CAAA;AAAA,QAC3D,GAAG,KAAA;AAAA,QAEJ,IAAA,EAAM,UAAa,GAAA,CAAC,uBAA0B,GAAA,KAAA;AAAA,QAC9C,YAAc,EAAA,2BAAA;AAAA,QACd,UAAU,CAAC,UAAA;AAAA,QAEX,QAAA,EAAA;AAAA,0BAAAE,eAAA,CAACY,aAAA,EAAA;AAAA,YAAoB,SAAU,EAAA,0CAAA;AAAA,YAC5B,QAAA,EAAA;AAAA,cAAA,IAAA,mBACEf,cAAA,CAAA,KAAA,EAAA;AAAA,gBAAI,SAAU,EAAA,kCAAA;AAAA,gBAAoC,QAAA,EAAA,IAAA;AAAA,eAAK,CACtD,GAAA,IAAA;AAAA,8BACHA,cAAA,CAAA,MAAA,EAAA;AAAA,gBAAK,SAAU,EAAA,yBAAA;AAAA,gBAA2B,QAAA,EAAA,aAAA;AAAA,eAAc,CAAA;AAAA,cACxD,6BACEA,cAAA,CAAA,MAAA,EAAA;AAAA,gBAAK,SAAU,EAAA,0CAAA;AAAA,gBACd,yCAACgB,6BAAiB,EAAA,EAAA,CAAA;AAAA,eACpB,CACE,GAAA,IAAA;AAAA,8BACHhB,cAAA,CAAA,KAAA,EAAA;AAAA,gBAAI,SAAU,EAAA,0BAAA;AAAA,gBACZ,QAAA,EAAA,MAAA,KAAW,6BACTA,cAAA,CAAAiB,mCAAA,EAAA,EAAoB,IACnB,OAAY,KAAA,KAAA,CAAA,mBAEbjB,cAAA,CAAAkB,mBAAA,EAAA,EAAY,CACX,GAAA,IAAA;AAAA,eACN,CAAA;AAAA,aAAA;AAAA,WACF,CAAA;AAAA,UAEC,UAAA,mBACElB,cAAA,CAAAmB,aAAA,EAAA;AAAA,YAAoB,SAAU,EAAA,qDAAA;AAAA,YAC7B,QAAC,kBAAAnB,cAAA,CAAA,KAAA,EAAA;AAAA,cAAI,SAAU,EAAA,oBAAA;AAAA,cAAsB,QAAA;AAAA,aAAS,CAAA;AAAA,WAChD,CACE,GAAA,IAAA;AAAA,SAAA;AAAA,OACN,CAAA,CAAA;AAAA,KAEJ;AAAA,GACF;AAAA,EACA;AAAA,IACE,IAAM,EAAA,UAAA;AAAA,IACN,SAAW,EAAA,eAAA;AAAA,IACX,YAAc,EAAA,kBAAA;AAAA,GAChB;AACF;;;;"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import { kInternal } from '@liveblocks/core';
|
|
3
|
-
import { useMemo, useCallback, forwardRef,
|
|
3
|
+
import { useMemo, useCallback, forwardRef, Children } from 'react';
|
|
4
4
|
import '../_private/index.js';
|
|
5
5
|
import '../icons/index.js';
|
|
6
|
+
import { useOverrides } from '../overrides.js';
|
|
6
7
|
import { useAiToolInvocationContext } from '../primitives/AiMessage/contexts.js';
|
|
7
8
|
import { Root as CollapsibleRoot, Trigger as CollapsibleTrigger, Content as CollapsibleContent } from '../primitives/Collapsible/index.js';
|
|
8
9
|
import { classNames } from '../utils/class-names.js';
|
|
10
|
+
import { useSemiControllableState } from '../utils/use-controllable-state.js';
|
|
9
11
|
import { CodeBlock } from './internal/CodeBlock.js';
|
|
10
12
|
import { Button } from './internal/Button.js';
|
|
11
13
|
import { ChevronRightIcon } from '../icons/ChevronRight.js';
|
|
@@ -40,15 +42,14 @@ function AiToolConfirmation({
|
|
|
40
42
|
variant = "default",
|
|
41
43
|
confirm,
|
|
42
44
|
cancel,
|
|
45
|
+
overrides,
|
|
43
46
|
className,
|
|
44
47
|
...props
|
|
45
48
|
}) {
|
|
46
|
-
const { status, args, respond,
|
|
49
|
+
const { status, args, respond, name, invocationId } = useAiToolInvocationContext();
|
|
50
|
+
const $ = useOverrides(overrides);
|
|
47
51
|
const enabled = status === "executing";
|
|
48
|
-
const context = useMemo(
|
|
49
|
-
() => ({ toolName, toolCallId }),
|
|
50
|
-
[toolName, toolCallId]
|
|
51
|
-
);
|
|
52
|
+
const context = useMemo(() => ({ name, invocationId }), [name, invocationId]);
|
|
52
53
|
const onConfirmClick = useCallback(async () => {
|
|
53
54
|
if (enabled) {
|
|
54
55
|
respond(await confirm(args, context));
|
|
@@ -59,7 +60,7 @@ function AiToolConfirmation({
|
|
|
59
60
|
respond(await cancel(args, context));
|
|
60
61
|
}
|
|
61
62
|
}, [enabled, args, cancel, respond, context]);
|
|
62
|
-
if (status === "executed") {
|
|
63
|
+
if (status === "executed" && !children) {
|
|
63
64
|
return null;
|
|
64
65
|
}
|
|
65
66
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -70,22 +71,22 @@ function AiToolConfirmation({
|
|
|
70
71
|
className: "lb-ai-tool-confirmation-content",
|
|
71
72
|
children
|
|
72
73
|
}) : null,
|
|
73
|
-
/* @__PURE__ */ jsx("div", {
|
|
74
|
+
status !== "executed" && /* @__PURE__ */ jsx("div", {
|
|
74
75
|
className: "lb-ai-tool-confirmation-footer",
|
|
75
76
|
children: /* @__PURE__ */ jsxs("div", {
|
|
76
77
|
className: "lb-ai-tool-confirmation-actions",
|
|
77
78
|
children: [
|
|
78
79
|
/* @__PURE__ */ jsx(Button, {
|
|
79
80
|
disabled: !enabled,
|
|
80
|
-
onClick:
|
|
81
|
-
variant:
|
|
82
|
-
children:
|
|
81
|
+
onClick: onCancelClick,
|
|
82
|
+
variant: "secondary",
|
|
83
|
+
children: $.AI_TOOL_CONFIRMATION_CANCEL
|
|
83
84
|
}),
|
|
84
85
|
/* @__PURE__ */ jsx(Button, {
|
|
85
86
|
disabled: !enabled,
|
|
86
|
-
onClick:
|
|
87
|
-
variant: "
|
|
88
|
-
children:
|
|
87
|
+
onClick: onConfirmClick,
|
|
88
|
+
variant: variant === "destructive" ? "destructive" : "primary",
|
|
89
|
+
children: $.AI_TOOL_CONFIRMATION_CONFIRM
|
|
89
90
|
})
|
|
90
91
|
]
|
|
91
92
|
})
|
|
@@ -96,27 +97,39 @@ function AiToolConfirmation({
|
|
|
96
97
|
function prettifyString(string) {
|
|
97
98
|
return string.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().toLowerCase().replace(/^\w/, (character) => character.toUpperCase());
|
|
98
99
|
}
|
|
99
|
-
const noop = () => {
|
|
100
|
-
};
|
|
101
100
|
const AiTool = Object.assign(
|
|
102
101
|
forwardRef(
|
|
103
|
-
({
|
|
102
|
+
({
|
|
103
|
+
children,
|
|
104
|
+
title,
|
|
105
|
+
icon,
|
|
106
|
+
collapsed,
|
|
107
|
+
onCollapsedChange,
|
|
108
|
+
className,
|
|
109
|
+
...props
|
|
110
|
+
}, forwardedRef) => {
|
|
104
111
|
const {
|
|
105
112
|
status,
|
|
106
|
-
|
|
113
|
+
name,
|
|
107
114
|
[kInternal]: { execute }
|
|
108
115
|
} = useAiToolInvocationContext();
|
|
109
|
-
const [
|
|
116
|
+
const [semiControlledCollapsed, onSemiControlledCollapsed] = useSemiControllableState(collapsed ?? false, onCollapsedChange);
|
|
110
117
|
const hasContent = Children.count(children) > 0;
|
|
111
118
|
const resolvedTitle = useMemo(() => {
|
|
112
|
-
return title ?? prettifyString(
|
|
113
|
-
}, [title,
|
|
119
|
+
return title ?? prettifyString(name);
|
|
120
|
+
}, [title, name]);
|
|
121
|
+
const handleCollapsibleOpenChange = useCallback(
|
|
122
|
+
(open) => {
|
|
123
|
+
onSemiControlledCollapsed(!open);
|
|
124
|
+
},
|
|
125
|
+
[onSemiControlledCollapsed]
|
|
126
|
+
);
|
|
114
127
|
return /* @__PURE__ */ jsxs(CollapsibleRoot, {
|
|
115
128
|
ref: forwardedRef,
|
|
116
129
|
className: classNames("lb-collapsible lb-ai-tool", className),
|
|
117
130
|
...props,
|
|
118
|
-
open: hasContent ?
|
|
119
|
-
onOpenChange:
|
|
131
|
+
open: hasContent ? !semiControlledCollapsed : false,
|
|
132
|
+
onOpenChange: handleCollapsibleOpenChange,
|
|
120
133
|
disabled: !hasContent,
|
|
121
134
|
children: [
|
|
122
135
|
/* @__PURE__ */ jsxs(CollapsibleTrigger, {
|
|
@@ -140,7 +153,7 @@ const AiTool = Object.assign(
|
|
|
140
153
|
})
|
|
141
154
|
]
|
|
142
155
|
}),
|
|
143
|
-
|
|
156
|
+
hasContent ? /* @__PURE__ */ jsx(CollapsibleContent, {
|
|
144
157
|
className: "lb-collapsible-content lb-ai-tool-content-container",
|
|
145
158
|
children: /* @__PURE__ */ jsx("div", {
|
|
146
159
|
className: "lb-ai-tool-content",
|