diff --git a/packages/paste-core/components/ai-log/__tests__/index.spec.tsx b/packages/paste-core/components/ai-log/__tests__/index.spec.tsx
new file mode 100644
index 0000000000..3383edf6bd
--- /dev/null
+++ b/packages/paste-core/components/ai-log/__tests__/index.spec.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import {render} from '@testing-library/react';
+
+import {AiLog} from '../src';
+
+describe('AiLog', () => {
+ it('should render', () => {
+ const {getByText} = render(test);
+ expect(getByText('test')).toBeDefined();
+ });
+});
diff --git a/packages/paste-core/components/ai-log/build.js b/packages/paste-core/components/ai-log/build.js
new file mode 100644
index 0000000000..a4edeab49b
--- /dev/null
+++ b/packages/paste-core/components/ai-log/build.js
@@ -0,0 +1,3 @@
+const {build} = require('../../../../tools/build/esbuild');
+
+build(require('./package.json'));
diff --git a/packages/paste-core/components/ai-log/package.json b/packages/paste-core/components/ai-log/package.json
new file mode 100644
index 0000000000..8b487c5aef
--- /dev/null
+++ b/packages/paste-core/components/ai-log/package.json
@@ -0,0 +1,59 @@
+{
+ "name": "@twilio-paste/ai-log",
+ "version": "0.0.0",
+ "category": "data display",
+ "status": "production",
+ "description": "Ai chat log.",
+ "author": "Twilio Inc.",
+ "license": "MIT",
+ "main:dev": "src/index.tsx",
+ "main": "dist/index.js",
+ "module": "dist/index.es.js",
+ "types": "dist/index.d.ts",
+ "sideEffects": false,
+ "publishConfig": {
+ "access": "public"
+ },
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "build": "yarn clean && NODE_ENV=production node build.js && tsc",
+ "build:js": "NODE_ENV=development node build.js",
+ "build:typedocs": "tsx ../../../../tools/build/generate-type-docs",
+ "clean": "rm -rf ./dist",
+ "tsc": "tsc"
+ },
+ "peerDependencies": {
+ "@twilio-paste/animation-library": "^2.0.0",
+ "@twilio-paste/box": "^10.2.0",
+ "@twilio-paste/color-contrast-utils": "^5.0.0",
+ "@twilio-paste/customization": "^8.1.1",
+ "@twilio-paste/design-tokens": "^10.3.0",
+ "@twilio-paste/style-props": "^9.1.1",
+ "@twilio-paste/styling-library": "^3.0.0",
+ "@twilio-paste/theme": "^11.0.1",
+ "@twilio-paste/types": "^6.0.0",
+ "@types/react": "^16.8.6 || ^17.0.2 || ^18.0.27",
+ "@types/react-dom": "^16.8.6 || ^17.0.2 || ^18.0.10",
+ "react": "^16.8.6 || ^17.0.2 || ^18.0.0",
+ "react-dom": "^16.8.6 || ^17.0.2 || ^18.0.0"
+ },
+ "devDependencies": {
+ "@twilio-paste/animation-library": "^2.0.0",
+ "@twilio-paste/box": "^10.2.0",
+ "@twilio-paste/color-contrast-utils": "^5.0.0",
+ "@twilio-paste/customization": "^8.1.1",
+ "@twilio-paste/design-tokens": "^10.3.0",
+ "@twilio-paste/style-props": "^9.1.1",
+ "@twilio-paste/styling-library": "^3.0.0",
+ "@twilio-paste/theme": "^11.0.1",
+ "@twilio-paste/types": "^6.0.0",
+ "@types/react": "^18.0.27",
+ "@types/react-dom": "^18.0.10",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0",
+ "tsx": "^3.12.10",
+ "typescript": "^4.9.4"
+ }
+}
diff --git a/packages/paste-core/components/ai-log/src/AIChatLog.tsx b/packages/paste-core/components/ai-log/src/AIChatLog.tsx
new file mode 100644
index 0000000000..23a31253a9
--- /dev/null
+++ b/packages/paste-core/components/ai-log/src/AIChatLog.tsx
@@ -0,0 +1,37 @@
+import { Box, safelySpreadBoxProps } from "@twilio-paste/box";
+import type { BoxProps } from "@twilio-paste/box";
+import type { HTMLPasteProps } from "@twilio-paste/types";
+import * as React from "react";
+
+export interface AIChatLogProps extends HTMLPasteProps<"div"> {
+ children?: React.ReactNode;
+ /**
+ * Overrides the default element name to apply unique styles with the Customization Provider
+ * @default '{constantCase component-name}'
+ * @type {BoxProps['element']}
+ * @memberof AIChatLogProps
+ */
+ element?: BoxProps["element"];
+}
+
+export const AIChatLog = React.forwardRef(
+ ({ element = "AI_LOG", children, ...props }, ref) => {
+ return (
+
+
+ {children}
+
+
+ );
+ },
+);
+
+AIChatLog.displayName = "AIChatLog";
diff --git a/packages/paste-core/components/ai-log/src/AIChatLogger.tsx b/packages/paste-core/components/ai-log/src/AIChatLogger.tsx
new file mode 100644
index 0000000000..8424516062
--- /dev/null
+++ b/packages/paste-core/components/ai-log/src/AIChatLogger.tsx
@@ -0,0 +1,58 @@
+import { animated, useReducedMotion, useTransition } from "@twilio-paste/animation-library";
+import { Box } from "@twilio-paste/box";
+import type { HTMLPasteProps } from "@twilio-paste/types";
+import * as React from "react";
+
+import { AIChatLog } from "./AIChatLog";
+import type { AIChat } from "./useAIChatLogger";
+
+const AnimatedAI = animated(Box);
+type StyleProps = React.ComponentProps["style"];
+
+export interface AIChatLoggerProps extends HTMLPasteProps<"div"> {
+ /**
+ * Array of AIs in the log. Use with useAIChatLogger()
+ *
+ * @default 'AI_ATTACHMENT'
+ * @type {BoxProps['element']}
+ * @memberof AIAttachmentProps
+ */
+ AIs: AIChat[];
+ children?: never;
+}
+
+const buildTransitionX = (AIChat: AIChat): number => {
+ if (AIChat.variant === "ai") return -100;
+ if (AIChat.variant === "user") return 100;
+ return 0;
+};
+
+export const AIChatLogger = React.forwardRef(({ AIs, ...props }, ref) => {
+ const transitions = useTransition(AIs, {
+ keys: (AIChat: AIChat) => AIChat.id,
+ from: (AIChat: AIChat): StyleProps => ({ opacity: 0, x: buildTransitionX(AIChat) }),
+ enter: { opacity: 1, x: 0 },
+ leave: (AIChat: AIChat): StyleProps => ({ opacity: 0, x: buildTransitionX(AIChat) }),
+ config: {
+ mass: 0.7,
+ tension: 190,
+ friction: 16,
+ },
+ });
+
+ const animatedAIs = useReducedMotion()
+ ? AIs.map((AIChat) => React.cloneElement(AIChat.content, { key: AIChat.id }))
+ : transitions((styles: StyleProps, AIChat: AIChat, { key }: { key: string }) => (
+
+ {AIChat.content}
+
+ ));
+
+ return (
+
+ {animatedAIs}
+
+ );
+});
+
+AIChatLogger.displayName = "AIChatLogger";
diff --git a/packages/paste-core/components/ai-log/src/AIChatMessage.tsx b/packages/paste-core/components/ai-log/src/AIChatMessage.tsx
new file mode 100644
index 0000000000..9f4b56e970
--- /dev/null
+++ b/packages/paste-core/components/ai-log/src/AIChatMessage.tsx
@@ -0,0 +1,36 @@
+import { Box, safelySpreadBoxProps } from "@twilio-paste/box";
+import type { BoxElementProps, BoxStyleProps } from "@twilio-paste/box";
+import type { HTMLPasteProps } from "@twilio-paste/types";
+import * as React from "react";
+
+export interface AIChatMessageProps extends HTMLPasteProps<"div"> {
+ children?: React.ReactNode;
+ /**
+ * Overrides the default element name to apply unique styles with the Customization Provider
+ *
+ * @default "CHAT_MESSAGE"
+ * @type {BoxProps["element"]}
+ * @memberof ChatMessageProps
+ */
+ element?: BoxElementProps["element"];
+}
+
+export const AIChatMessage = React.forwardRef(
+ ({ children, element = "CHAT_MESSAGE", ...props }, ref) => {
+ return (
+
+ {children}
+
+ );
+ },
+);
+
+AIChatMessage.displayName = "AIChatMessage";
diff --git a/packages/paste-core/components/ai-log/src/AIChatMessageContent.tsx b/packages/paste-core/components/ai-log/src/AIChatMessageContent.tsx
new file mode 100644
index 0000000000..0958389f62
--- /dev/null
+++ b/packages/paste-core/components/ai-log/src/AIChatMessageContent.tsx
@@ -0,0 +1,40 @@
+import { Box, safelySpreadBoxProps } from "@twilio-paste/box";
+import type { BoxElementProps, BoxStyleProps } from "@twilio-paste/box";
+import type { HTMLPasteProps } from "@twilio-paste/types";
+import * as React from "react";
+
+export interface AIChatMessageContentProps extends HTMLPasteProps<"div"> {
+ children?: React.ReactNode;
+ /**
+ * Overrides the default element name to apply unique styles with the Customization Provider
+ *
+ * @default "CHAT_BUBBLE"
+ * @type {BoxProps["element"]}
+ * @memberof AIChatBubbleProps
+ */
+ element?: BoxElementProps["element"];
+}
+
+export const AIChatMessageContent = React.forwardRef(
+ ({ children, element = "CHAT_MESSAGE_CONTENT", ...props }, ref) => {
+ return (
+
+ {children}
+
+ );
+ },
+);
+
+AIChatMessageContent.displayName = "AIChatMessageContent";
diff --git a/packages/paste-core/components/ai-log/src/AIChatMessageMeta.tsx b/packages/paste-core/components/ai-log/src/AIChatMessageMeta.tsx
new file mode 100644
index 0000000000..dd19c55fcc
--- /dev/null
+++ b/packages/paste-core/components/ai-log/src/AIChatMessageMeta.tsx
@@ -0,0 +1,43 @@
+import { Box, safelySpreadBoxProps } from "@twilio-paste/box";
+import type { BoxElementProps } from "@twilio-paste/box";
+import type { HTMLPasteProps } from "@twilio-paste/types";
+import * as React from "react";
+
+export interface AIChatMessageMetaProps extends HTMLPasteProps<"div"> {
+ /**
+ *
+ * @default null
+ * @type {string}
+ * @memberof AIChatMessageMetaProps
+ */
+ "aria-label": string;
+ children: NonNullable;
+ /**
+ * Overrides the default element name to apply unique styles with the Customization Provider
+ *
+ * @default "CHAT_MESSAGE_META"
+ * @type {BoxProps["element"]}
+ * @memberof AIChatMessageMetaProps
+ */
+ element?: BoxElementProps["element"];
+}
+
+export const AIChatMessageMeta = React.forwardRef(
+ ({ children, element = "CHAT_MESSAGE_META", ...props }, ref) => {
+ return (
+
+ {children}
+
+ );
+ },
+);
+
+AIChatMessageMeta.displayName = "AIChatMessageMeta";
diff --git a/packages/paste-core/components/ai-log/src/AIChatMessageMetaItem.tsx b/packages/paste-core/components/ai-log/src/AIChatMessageMetaItem.tsx
new file mode 100644
index 0000000000..72b7e033d2
--- /dev/null
+++ b/packages/paste-core/components/ai-log/src/AIChatMessageMetaItem.tsx
@@ -0,0 +1,47 @@
+import { Box, safelySpreadBoxProps } from "@twilio-paste/box";
+import type { BoxElementProps } from "@twilio-paste/box";
+import type { HTMLPasteProps } from "@twilio-paste/types";
+import * as React from "react";
+
+export interface AIChatMessageMetaItemProps extends HTMLPasteProps<"div"> {
+ children: NonNullable;
+ variant: "author" | "timestamp";
+ /**
+ * Overrides the default element name to apply unique styles with the Customization Provider
+ *
+ * @default "CHAT_MESSAGE_META_ITEM"
+ * @type {BoxProps["element"]}
+ * @memberof AIChatMessageMetaItemProps
+ */
+ element?: BoxElementProps["element"];
+}
+
+const variantStyles = {
+ author: {
+ color: "colorText",
+ lineHeight: "lineHeight50",
+ fontSize: "fontSize40",
+ },
+ timestamp: {},
+};
+
+export const AIChatMessageMetaItem = React.forwardRef(
+ ({ children, element = "CHAT_MESSAGE_META_ITEM", ...props }, ref) => (
+
+ {children}
+
+ ),
+);
+
+AIChatMessageMetaItem.displayName = "AIChatMessageMetaItem";
diff --git a/packages/paste-core/components/ai-log/src/index.tsx b/packages/paste-core/components/ai-log/src/index.tsx
new file mode 100644
index 0000000000..65e31ebf04
--- /dev/null
+++ b/packages/paste-core/components/ai-log/src/index.tsx
@@ -0,0 +1,15 @@
+export { AIChatMessage } from "./AIChatMessage";
+export type { AIChatMessageProps } from "./AIChatMessage";
+export { AIChatMessageMeta } from "./AIChatMessageMeta";
+export type { AIChatMessageMetaProps } from "./AIChatMessageMeta";
+export { AIChatMessageMetaItem } from "./AIChatMessageMetaItem";
+export type { AIChatMessageMetaItemProps } from "./AIChatMessageMetaItem";
+export { AIChatMessageContent } from "./AIChatMessageContent";
+export type { AIChatMessageContentProps } from "./AIChatMessageContent";
+
+export { AIChatLog } from "./AIChatLog";
+export type { AIChatLogProps } from "./AIChatLog";
+export { useAIChatLogger } from "./useAIChatLogger";
+export type { UseAIChatLogger } from "./useAIChatLogger";
+export { AIChatLogger } from "./AIChatLogger";
+export type { AIChatLoggerProps } from "./AIChatLogger";
diff --git a/packages/paste-core/components/ai-log/src/useAIChatLogger.tsx b/packages/paste-core/components/ai-log/src/useAIChatLogger.tsx
new file mode 100644
index 0000000000..6d785d5f6b
--- /dev/null
+++ b/packages/paste-core/components/ai-log/src/useAIChatLogger.tsx
@@ -0,0 +1,39 @@
+import { uid } from "@twilio-paste/uid-library";
+import * as React from "react";
+
+export type AIChat = {
+ id: string;
+ content: React.ReactElement;
+};
+
+export type PartialIDChat = Omit & Partial>;
+
+type PushAIChat = (chat: PartialIDChat) => void;
+type PopAIChat = (id?: string) => void;
+
+export type UseAIChatLogger = (...initialChats: PartialIDChat[]) => {
+ aiChats: AIChat[];
+ push: PushAIChat;
+ pop: PopAIChat;
+ clear: () => void;
+};
+
+const aiChatWithId = (chat: PartialIDChat): AIChat => ({ ...chat, id: chat.id || uid(chat.content) });
+
+export const useAIChatLogger: UseAIChatLogger = (...initialChats) => {
+ const parsedInitialChats = React.useMemo(() => initialChats.map(aiChatWithId), [initialChats]);
+
+ const [aiChats, setAIChats] = React.useState(parsedInitialChats);
+
+ const push: PushAIChat = React.useCallback((next) => {
+ setAIChats((prev) => prev.concat(aiChatWithId(next)));
+ }, []);
+
+ const pop: PopAIChat = React.useCallback((id) => {
+ setAIChats((prev) => (id ? prev.filter((chat) => chat.id !== id) : prev.slice(0, -1)));
+ }, []);
+
+ const clear: () => void = React.useCallback(() => setAIChats([]), []);
+
+ return { push, pop, aiChats, clear };
+};
diff --git a/packages/paste-core/components/ai-log/stories/index.stories.tsx b/packages/paste-core/components/ai-log/stories/index.stories.tsx
new file mode 100644
index 0000000000..2f9fc10400
--- /dev/null
+++ b/packages/paste-core/components/ai-log/stories/index.stories.tsx
@@ -0,0 +1,66 @@
+import * as React from "react";
+
+import { Avatar } from "@twilio-paste/avatar";
+import { Box } from "@twilio-paste/box";
+import { ArtificialIntelligenceIcon } from "@twilio-paste/icons/esm/ArtificialIntelligenceIcon";
+import { useUID } from "@twilio-paste/uid-library";
+import { AIChatLog, AIChatMessage, AIChatMessageContent, AIChatMessageMeta, AIChatMessageMetaItem } from "../src";
+
+// eslint-disable-next-line import/no-default-export
+export default {
+ title: "Components/AI Log",
+ component: AIChatLog,
+};
+
+export const Default = (): React.ReactNode => {
+ const [showButton, setShowButton] = React.useState(true);
+ const chatBoxUniqueID = useUID();
+ return (
+
+
+
+
+
+
+ You
+
+
+
+ Lorem ipsum dolor, sit amet consectetur adipisicing elit. Deserunt delectus fuga, necessitatibus eligendi
+ iure adipisci facilis exercitationem officiis dolorem laborum, ex fugiat quisquam itaque, earum sit nesciunt
+ impedit repellat assumenda.
+
+
+ 30007
+ 30007
+ 30007
+
+
+ Was this helpful?
+
+
+
+
+
+
+ AI Bot
+
+
+
+ Lorem ipsum dolor, sit amet consectetur adipisicing elit. Deserunt delectus fuga, necessitatibus eligendi
+ iure adipisci facilis exercitationem officiis dolorem laborum, ex fugiat quisquam itaque, earum sit nesciunt
+ impedit repellat assumenda.
+
+
+ 30007
+ 30007
+ 30007
+
+
+ Was this helpful?
+
+
+
+
+ );
+};
diff --git a/packages/paste-core/components/ai-log/tsconfig.json b/packages/paste-core/components/ai-log/tsconfig.json
new file mode 100644
index 0000000000..b5daed7034
--- /dev/null
+++ b/packages/paste-core/components/ai-log/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../../../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist/",
+ },
+ "include": [
+ "src/**/*",
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/yarn.lock b/yarn.lock
index 5d5fac83e8..e21441e8ef 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11216,6 +11216,42 @@ __metadata:
languageName: unknown
linkType: soft
+"@twilio-paste/ai-log@workspace:packages/paste-core/components/ai-log":
+ version: 0.0.0-use.local
+ resolution: "@twilio-paste/ai-log@workspace:packages/paste-core/components/ai-log"
+ dependencies:
+ "@twilio-paste/animation-library": ^2.0.0
+ "@twilio-paste/box": ^10.2.0
+ "@twilio-paste/color-contrast-utils": ^5.0.0
+ "@twilio-paste/customization": ^8.1.1
+ "@twilio-paste/design-tokens": ^10.3.0
+ "@twilio-paste/style-props": ^9.1.1
+ "@twilio-paste/styling-library": ^3.0.0
+ "@twilio-paste/theme": ^11.0.1
+ "@twilio-paste/types": ^6.0.0
+ "@types/react": ^18.0.27
+ "@types/react-dom": ^18.0.10
+ react: ^18.0.0
+ react-dom: ^18.0.0
+ tsx: ^3.12.10
+ typescript: ^4.9.4
+ peerDependencies:
+ "@twilio-paste/animation-library": ^2.0.0
+ "@twilio-paste/box": ^10.2.0
+ "@twilio-paste/color-contrast-utils": ^5.0.0
+ "@twilio-paste/customization": ^8.1.1
+ "@twilio-paste/design-tokens": ^10.3.0
+ "@twilio-paste/style-props": ^9.1.1
+ "@twilio-paste/styling-library": ^3.0.0
+ "@twilio-paste/theme": ^11.0.1
+ "@twilio-paste/types": ^6.0.0
+ "@types/react": ^16.8.6 || ^17.0.2 || ^18.0.27
+ "@types/react-dom": ^16.8.6 || ^17.0.2 || ^18.0.10
+ react: ^16.8.6 || ^17.0.2 || ^18.0.0
+ react-dom: ^16.8.6 || ^17.0.2 || ^18.0.0
+ languageName: unknown
+ linkType: soft
+
"@twilio-paste/alert-dialog@^9.2.0, @twilio-paste/alert-dialog@workspace:packages/paste-core/components/alert-dialog":
version: 0.0.0-use.local
resolution: "@twilio-paste/alert-dialog@workspace:packages/paste-core/components/alert-dialog"
@@ -41657,7 +41693,7 @@ resolve@^2.0.0-next.3:
languageName: node
linkType: hard
-"source-map-support@npm:^0.5.16, source-map-support@npm:~0.5.12, source-map-support@npm:~0.5.19, source-map-support@npm:~0.5.20":
+"source-map-support@npm:^0.5.16, source-map-support@npm:^0.5.21, source-map-support@npm:~0.5.12, source-map-support@npm:~0.5.19, source-map-support@npm:~0.5.20":
version: 0.5.21
resolution: "source-map-support@npm:0.5.21"
dependencies:
@@ -43669,6 +43705,23 @@ resolve@^2.0.0-next.3:
languageName: node
linkType: hard
+"tsx@npm:^3.12.10":
+ version: 3.14.0
+ resolution: "tsx@npm:3.14.0"
+ dependencies:
+ esbuild: ~0.18.20
+ fsevents: ~2.3.3
+ get-tsconfig: ^4.7.2
+ source-map-support: ^0.5.21
+ dependenciesMeta:
+ fsevents:
+ optional: true
+ bin:
+ tsx: dist/cli.mjs
+ checksum: afcef5d9b90b5800cf1ffb749e943f63042d78a4c0d9eef6e13e43f4ecab465d45e2c9812a2c515cbdc2ee913ff1cd01bf5c606a48013dd3ce2214a631b45557
+ languageName: node
+ linkType: hard
+
"tsx@npm:^4.0.0":
version: 4.6.2
resolution: "tsx@npm:4.6.2"