@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.
225 lines (222 loc) • 8.67 kB
JavaScript
import { jsx, jsxs } from 'react/jsx-runtime';
import { kInternal } from '@liveblocks/core';
import { useMemo, useCallback, forwardRef, Children } from 'react';
import '../_private/index.js';
import '../icons/index.js';
import { useOverrides } from '../overrides.js';
import { useAiToolInvocationContext } from '../primitives/AiMessage/contexts.js';
import { Root as CollapsibleRoot, Trigger as CollapsibleTrigger, Content as CollapsibleContent } from '../primitives/Collapsible/index.js';
import { cn } from '../utils/cn.js';
import { useSemiControllableState } from '../utils/use-controllable-state.js';
import { CodeBlock } from './internal/CodeBlock.js';
import { Button } from './internal/Button.js';
import { ChevronRightIcon } from '../icons/ChevronRight.js';
import { CheckCircleFillIcon } from '../icons/CheckCircleFill.js';
import { CrossCircleFillIcon } from '../icons/CrossCircleFill.js';
import { MinusCircleIcon } from '../icons/MinusCircle.js';
import { SpinnerIcon } from '../icons/Spinner.js';
function AiToolIcon({ className, ...props }) {
return /* @__PURE__ */ jsx("div", { className: cn("lb-ai-tool-icon", className), ...props });
}
function AiToolInspector({ className, ...props }) {
const { args, partialArgs, result } = useAiToolInvocationContext();
return /* @__PURE__ */ jsxs("div", { className: cn("lb-ai-tool-inspector", className), ...props, children: [
/* @__PURE__ */ jsx(
CodeBlock,
{
title: "Arguments",
code: JSON.stringify(args ?? partialArgs, null, 2)
}
),
result !== void 0 ? /* @__PURE__ */ jsx(CodeBlock, { title: "Result", code: JSON.stringify(result, null, 2) }) : null
] });
}
function AiToolConfirmation({
children,
variant = "default",
confirm,
cancel,
overrides,
className,
...props
}) {
const { stage, args, respond, name, invocationId } = useAiToolInvocationContext();
const $ = useOverrides(overrides);
const enabled = stage === "executing";
const context = useMemo(() => ({ name, invocationId }), [name, invocationId]);
const onConfirmClick = useCallback(async () => {
if (enabled) {
const result = await confirm(args, context);
respond(result ?? void 0);
}
}, [enabled, args, confirm, respond, context]);
const onCancelClick = 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__ */ jsxs("div", { className: cn("lb-ai-tool-confirmation", className), ...props, children: [
children ? /* @__PURE__ */ jsx("div", { className: "lb-ai-tool-confirmation-content", children }) : null,
stage !== "executed" && /* @__PURE__ */ jsx("div", { className: "lb-ai-tool-confirmation-footer", children: /* @__PURE__ */ jsxs("div", { className: "lb-ai-tool-confirmation-actions", children: [
/* @__PURE__ */ jsx(
Button,
{
disabled: !enabled,
onClick: onCancelClick,
variant: "secondary",
children: $.AI_TOOL_CONFIRMATION_CANCEL
}
),
/* @__PURE__ */ jsx(
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(
forwardRef(
({
children,
title,
icon,
collapsible,
collapsed,
onCollapsedChange,
variant = "block",
className,
...props
}, forwardedRef) => {
const {
stage,
result,
name,
[kInternal]: { execute, messageStatus }
} = 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] = useSemiControllableState(collapsed ?? false, onCollapsedChange);
const hasContent = Children.count(children) > 0;
const isCollapsible = hasContent ? collapsible ?? true : false;
const resolvedTitle = useMemo(() => {
return title ?? prettifyString(name);
}, [title, name]);
const handleCollapsibleOpenChange = useCallback(
(open) => {
onSemiControlledCollapsed(!open);
},
[onSemiControlledCollapsed]
);
return /* @__PURE__ */ jsxs(
CollapsibleRoot,
{
ref: forwardedRef,
className: 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__ */ jsxs(
CollapsibleTrigger,
{
className: cn(
"lb-collapsible-trigger lb-ai-tool-header",
variant === "minimal" && isVisuallyPending && "lb-ai-chat-pending"
),
children: [
icon ? /* @__PURE__ */ jsx("div", { className: "lb-ai-tool-header-icon-container", children: icon }) : null,
/* @__PURE__ */ jsx("span", { className: "lb-ai-tool-header-title", children: resolvedTitle }),
isCollapsible ? /* @__PURE__ */ jsx("span", { className: "lb-collapsible-chevron lb-icon-container", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) }) : null,
variant !== "minimal" ? /* @__PURE__ */ jsx("div", { className: "lb-ai-tool-header-status", children: stage === "executed" ? result.type === "success" ? /* @__PURE__ */ jsx(CheckCircleFillIcon, {}) : result.type === "error" ? /* @__PURE__ */ jsx(CrossCircleFillIcon, {}) : result.type === "cancelled" ? /* @__PURE__ */ jsx(MinusCircleIcon, {}) : null : isVisuallyPending ? /* @__PURE__ */ jsx(SpinnerIcon, {}) : null }) : null
]
}
),
hasContent ? /* @__PURE__ */ jsx(CollapsibleContent, { className: "lb-collapsible-content lb-ai-tool-content-container", children: /* @__PURE__ */ 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
}
);
export { AiTool };
//# sourceMappingURL=AiTool.js.map