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.

227 lines (223 loc) 9 kB
'use strict'; var jsxRuntime = require('react/jsx-runtime'); var core = require('@liveblocks/core'); var react = require('react'); require('../_private/index.cjs'); require('../icons/index.cjs'); var overrides = require('../overrides.cjs'); var contexts = require('../primitives/AiMessage/contexts.cjs'); var index = require('../primitives/Collapsible/index.cjs'); var cn = require('../utils/cn.cjs'); var useControllableState = require('../utils/use-controllable-state.cjs'); var CodeBlock = require('./internal/CodeBlock.cjs'); var Button = require('./internal/Button.cjs'); var ChevronRight = require('../icons/ChevronRight.cjs'); var CheckCircleFill = require('../icons/CheckCircleFill.cjs'); var CrossCircleFill = require('../icons/CrossCircleFill.cjs'); var MinusCircle = require('../icons/MinusCircle.cjs'); var Spinner = require('../icons/Spinner.cjs'); function AiToolIcon({ className, ...props }) { return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn.cn("lb-ai-tool-icon", className), ...props }); } function AiToolInspector({ className, ...props }) { const { args, partialArgs, result } = contexts.useAiToolInvocationContext(); return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn.cn("lb-ai-tool-inspector", className), ...props, children: [ /* @__PURE__ */ jsxRuntime.jsx( CodeBlock.CodeBlock, { title: "Arguments", code: JSON.stringify(args ?? partialArgs, null, 2) } ), result !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(CodeBlock.CodeBlock, { title: "Result", code: JSON.stringify(result, null, 2) }) : null ] }); } function AiToolConfirmation({ children, variant = "default", confirm, cancel, overrides: overrides$1, className, ...props }) { const { stage, args, respond, name, invocationId } = contexts.useAiToolInvocationContext(); const $ = overrides.useOverrides(overrides$1); const enabled = stage === "executing"; const context = react.useMemo(() => ({ name, invocationId }), [name, invocationId]); const onConfirmClick = react.useCallback(async () => { if (enabled) { const result = await confirm(args, context); respond(result ?? void 0); } }, [enabled, args, confirm, respond, context]); const onCancelClick = react.useCallback(async () => { if (enabled) { if (cancel === void 0) { respond({ cancel: true }); } else { const result = await cancel(args, context); respond(result ?? void 0); } } }, [enabled, args, cancel, respond, context]); if (stage === "executed" && !children) { return null; } return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn.cn("lb-ai-tool-confirmation", className), ...props, children: [ children ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lb-ai-tool-confirmation-content", children }) : null, stage !== "executed" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lb-ai-tool-confirmation-footer", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lb-ai-tool-confirmation-actions", children: [ /* @__PURE__ */ jsxRuntime.jsx( Button.Button, { disabled: !enabled, onClick: onCancelClick, variant: "secondary", children: $.AI_TOOL_CONFIRMATION_CANCEL } ), /* @__PURE__ */ jsxRuntime.jsx( Button.Button, { disabled: !enabled, onClick: onConfirmClick, variant: variant === "destructive" ? "destructive" : "primary", children: $.AI_TOOL_CONFIRMATION_CONFIRM } ) ] }) }) ] }); } function prettifyString(string) { return string.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().toLowerCase().replace(/^\w/, (character) => character.toUpperCase()); } const AiTool = Object.assign( react.forwardRef( ({ children, title, icon, collapsible, collapsed, onCollapsedChange, variant = "block", className, ...props }, forwardedRef) => { const { stage, result, name, [core.kInternal]: { execute, messageStatus } } = contexts.useAiToolInvocationContext(); const isVisuallyPending = execute !== void 0 && stage !== "executed" && // If it's in the "receiving" stage, we also check that the outer message is still generating. (stage === "receiving" ? messageStatus === "generating" : true); const [semiControlledCollapsed, onSemiControlledCollapsed] = useControllableState.useSemiControllableState(collapsed ?? false, onCollapsedChange); const hasContent = react.Children.count(children) > 0; const isCollapsible = hasContent ? collapsible ?? true : false; const resolvedTitle = react.useMemo(() => { return title ?? prettifyString(name); }, [title, name]); const handleCollapsibleOpenChange = react.useCallback( (open) => { onSemiControlledCollapsed(!open); }, [onSemiControlledCollapsed] ); return /* @__PURE__ */ jsxRuntime.jsxs( index.Root, { ref: forwardedRef, className: cn.cn( "lb-collapsible lb-ai-tool", `lb-ai-tool:variant-${variant}`, className ), ...props, open: hasContent ? !semiControlledCollapsed : false, onOpenChange: handleCollapsibleOpenChange, disabled: !isCollapsible, "data-result": result?.type, "data-stage": stage, children: [ /* @__PURE__ */ jsxRuntime.jsxs( index.Trigger, { className: cn.cn( "lb-collapsible-trigger lb-ai-tool-header", variant === "minimal" && isVisuallyPending && "lb-ai-chat-pending" ), children: [ icon ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lb-ai-tool-header-icon-container", children: icon }) : null, /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lb-ai-tool-header-title", children: resolvedTitle }), isCollapsible ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lb-collapsible-chevron lb-icon-container", children: /* @__PURE__ */ jsxRuntime.jsx(ChevronRight.ChevronRightIcon, {}) }) : null, variant !== "minimal" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lb-ai-tool-header-status", children: stage === "executed" ? result.type === "success" ? /* @__PURE__ */ jsxRuntime.jsx(CheckCircleFill.CheckCircleFillIcon, {}) : result.type === "error" ? /* @__PURE__ */ jsxRuntime.jsx(CrossCircleFill.CrossCircleFillIcon, {}) : result.type === "cancelled" ? /* @__PURE__ */ jsxRuntime.jsx(MinusCircle.MinusCircleIcon, {}) : null : isVisuallyPending ? /* @__PURE__ */ jsxRuntime.jsx(Spinner.SpinnerIcon, {}) : null }) : null ] } ), hasContent ? /* @__PURE__ */ jsxRuntime.jsx(index.Content, { className: "lb-collapsible-content lb-ai-tool-content-container", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lb-ai-tool-content", children }) }) : null ] } ); } ), { /** * Display an icon in a container. * * @example * <AiTool * icon={ * <AiTool.Icon>🔍</AiTool.Icon> * } * /> */ Icon: AiToolIcon, /** * Display the tool's arguments and result, which can be useful during * development. * * @example * <AiTool> * <AiTool.Inspector /> * </AiTool> */ Inspector: AiToolInspector, /** * Display a human-in-the-loop confirmation step which can be accepted * or cancelled by the user. * * The `confirm` and `cancel` callbacks work like `execute` in tool definitions: they can * perform side-effects, be async if needed, and return a result. The tool call will stay * pending until either `confirm` or `cancel` is called. * * @example * <AiTool> * <AiTool.Confirmation * // Use a destructive visual appearance * variant="destructive" * * // The tool's arguments can be directly accessed like in `execute` * confirm={({ pageIds }) => { * const deletedPageTitles = pages * .filter((p) => pageIds.includes(p.id)) * .map((page) => page.title); * * deletePages(pageIds); * * // This result will be available as `result` in the tool's `render` props * return { data: { deletedPageTitles } }; * }} * * // If needed, `cancel={() => ...}` would work similarly * > * Do you want to delete these pages? * <PagesPreviews /> * </AiTool.Confirmation> * </AiTool> */ Confirmation: AiToolConfirmation } ); exports.AiTool = AiTool; //# sourceMappingURL=AiTool.cjs.map