Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ai-chat-log): add new AI Chat Log components [WIP] #3873

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .codesandbox/ci.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"/packages/paste-icons",
"/packages/paste-core/core-bundle",
"/packages/paste-core/components/account-switcher",
"/packages/paste-core/components/ai-chat-log",
"/packages/paste-core/components/alert",
"/packages/paste-core/components/alert-dialog",
"/packages/paste-core/components/anchor",
Expand Down
7 changes: 7 additions & 0 deletions packages/paste-codemods/tools/.cache/mappings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
"AccountSwitcherItemRadio": "@twilio-paste/core/account-switcher",
"AccountSwitcherSeparator": "@twilio-paste/core/account-switcher",
"useAccountSwitcherState": "@twilio-paste/core/account-switcher",
"AIChatLog": "@twilio-paste/core/ai-chat-log",
"AIChatLogger": "@twilio-paste/core/ai-chat-log",
"AIChatMessage": "@twilio-paste/core/ai-chat-log",
"AIChatMessageContent": "@twilio-paste/core/ai-chat-log",
"AIChatMessageMeta": "@twilio-paste/core/ai-chat-log",
"AIChatMessageMetaItem": "@twilio-paste/core/ai-chat-log",
"useAIChatLogger": "@twilio-paste/core/ai-chat-log",
"Alert": "@twilio-paste/core/alert",
"AlertDialog": "@twilio-paste/core/alert-dialog",
"Anchor": "@twilio-paste/core/anchor",
Expand Down
Original file line number Diff line number Diff line change
@@ -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(<AiLog>test</AiLog>);
expect(getByText('test')).toBeDefined();
});
});
3 changes: 3 additions & 0 deletions packages/paste-core/components/ai-chat-log/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const {build} = require('../../../../tools/build/esbuild');

build(require('./package.json'));
59 changes: 59 additions & 0 deletions packages/paste-core/components/ai-chat-log/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"name": "@twilio-paste/ai-chat-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": "^4.0.0",
"typescript": "^4.9.4"
}
}
37 changes: 37 additions & 0 deletions packages/paste-core/components/ai-chat-log/src/AIChatLog.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement, AIChatLogProps>(
({ element = "AI_CHAT_LOG", children, ...props }, ref) => {
return (
<Box role="log" padding="space70" element={element} ref={ref} {...safelySpreadBoxProps(props)}>
<Box
as="div"
role="list"
margin="space0"
padding="space0"
display="flex"
flexDirection="column"
rowGap="space130"
>
{children}
</Box>
</Box>
);
},
);

AIChatLog.displayName = "AIChatLog";
58 changes: 58 additions & 0 deletions packages/paste-core/components/ai-chat-log/src/AIChatLogger.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof AnimatedAI>["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<HTMLDivElement, AIChatLoggerProps>(({ 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 }) => (
<AnimatedAI as="div" style={styles} key={key}>
{AIChat.content}
</AnimatedAI>
));

return (
<AIChatLog {...props} ref={ref}>
{animatedAIs}
</AIChatLog>
);
});

AIChatLogger.displayName = "AIChatLogger";
36 changes: 36 additions & 0 deletions packages/paste-core/components/ai-chat-log/src/AIChatMessage.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement, AIChatMessageProps>(
({ children, element = "AI_CHAT_MESSAGE", ...props }, ref) => {
return (
<Box
role="listitem"
display="flex"
flexDirection="column"
rowGap="space40"
ref={ref}
element={element}
{...safelySpreadBoxProps(props)}
>
{children}
</Box>
);
},
);

AIChatMessage.displayName = "AIChatMessage";
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Avatar } from "@twilio-paste/avatar";
import type { BoxElementProps } from "@twilio-paste/box";
import { ArtificialIntelligenceIcon } from "@twilio-paste/icons/esm/ArtificialIntelligenceIcon";
import type { ThemeShape } from "@twilio-paste/theme";
import type { HTMLPasteProps } from "@twilio-paste/types";
import * as React from "react";
import { AIChatMessageMeta } from "./AIChatMessageMeta";

export interface AIChatMessageAuthorProps extends HTMLPasteProps<"div"> {
/**
* The name of the author of the chat message
*
* @type {string}
* @memberof AIChatMessageAuthorProps
*/
children: string;
/**
* Overrides the default element name to apply unique styles with the Customization Provider
*
* @default "AI_CHAT_MESSAGE_AUTHOR"
* @type {BoxProps["element"]}
* @memberof AIChatMessageAuthorProps
*/
element?: BoxElementProps["element"];
/**
* Screen reader label for the author
*
* @type {string}
* @memberof AIChatMessageAuthorProps
*/
"aria-label": string;
/**
* Whether the author is a bot or not
*
* @default false
* @type {boolean}
* @memberof AIChatMessageAuthorProps
*/
bot?: boolean;
}

export const AIChatMessageAuthor = React.forwardRef<HTMLDivElement, AIChatMessageAuthorProps>(
({ children, bot = false, element = "AI_CHAT_MESSAGE_AUTHOR", ...props }, ref) => {
return (
<AIChatMessageMeta {...props} ref={ref} aria-label={props["aria-label"]} element={element}>
{bot ? (
<Avatar name={children} size="sizeIcon50" icon={ArtificialIntelligenceIcon} element={`${element}_AVATAR}`} />
) : (
<Avatar name={children} size="sizeIcon50" color="decorative30" element={`${element}_AVATAR}`} />
)}
{children}
</AIChatMessageMeta>
);
},
);

AIChatMessageAuthor.displayName = "AIChatMessageAuthor";
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Box, safelySpreadBoxProps } from "@twilio-paste/box";
import type { BoxElementProps } from "@twilio-paste/box";
import type { ThemeShape } from "@twilio-paste/theme";
import type { HTMLPasteProps } from "@twilio-paste/types";
import * as React from "react";

const Variants = {
default: {
fontSize: "fontSize30" as ThemeShape["fontSizes"],
lineHeight: "lineHeight30" as ThemeShape["lineHeights"],
},
fullScreen: {
fontSize: "fontSize40" as ThemeShape["fontSizes"],
lineHeight: "lineHeight40" as ThemeShape["lineHeights"],
},
};

export interface AIChatMessageBodyProps 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"];
/**
* Override the font size for full screen experiences.
*
* @default "CHAT_BUBBLE"
* @type {"default" | "fullScreen"}
* @memberof AIChatBubbleProps
*/
variant?: "default" | "fullScreen";
}

export const AIChatMessageBody = React.forwardRef<HTMLDivElement, AIChatMessageBodyProps>(
({ children, variant = "default", element = "AI_CHAT_MESSAGE_BODY", ...props }, ref) => {
return (
<Box
{...safelySpreadBoxProps(props)}
{...Variants[variant]}
display="inline-block"
color="colorText"
wordWrap="break-word"
maxWidth="100%"
minWidth={0}
element={element}
ref={ref}
whiteSpace="pre-wrap"
>
{children}
</Box>
);
},
);

AIChatMessageBody.displayName = "AIChatMessageBody";
Loading
Loading