Skip to content

Commit

Permalink
chore: checkpoint ai log and ai msg
Browse files Browse the repository at this point in the history
  • Loading branch information
TheSisb committed Apr 24, 2024
1 parent d6c1b73 commit c34b24c
Show file tree
Hide file tree
Showing 14 changed files with 520 additions and 1 deletion.
11 changes: 11 additions & 0 deletions packages/paste-core/components/ai-log/__tests__/index.spec.tsx
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-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-log/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
37 changes: 37 additions & 0 deletions packages/paste-core/components/ai-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_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-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-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 = "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";
40 changes: 40 additions & 0 deletions packages/paste-core/components/ai-log/src/AIChatMessageContent.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement, AIChatMessageContentProps>(
({ children, element = "CHAT_MESSAGE_CONTENT", ...props }, ref) => {
return (
<Box
display="inline-block"
color="colorText"
fontSize="fontSize30"
lineHeight="lineHeight20"
wordWrap="break-word"
maxWidth="100%"
minWidth={0}
element={element}
ref={ref}
whiteSpace="pre-wrap"
{...safelySpreadBoxProps(props)}
>
{children}
</Box>
);
},
);

AIChatMessageContent.displayName = "AIChatMessageContent";
43 changes: 43 additions & 0 deletions packages/paste-core/components/ai-log/src/AIChatMessageMeta.tsx
Original file line number Diff line number Diff line change
@@ -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<React.ReactNode>;
/**
* 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<HTMLDivElement, AIChatMessageMetaProps>(
({ children, element = "CHAT_MESSAGE_META", ...props }, ref) => {
return (
<Box
{...safelySpreadBoxProps(props)}
ref={ref}
element={element}
display="flex"
alignItems="center"
columnGap="space30"
fontWeight="fontWeightMedium"
>
{children}
</Box>
);
},
);

AIChatMessageMeta.displayName = "AIChatMessageMeta";
Original file line number Diff line number Diff line change
@@ -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<React.ReactNode>;
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<HTMLDivElement, AIChatMessageMetaItemProps>(
({ children, element = "CHAT_MESSAGE_META_ITEM", ...props }, ref) => (
<Box
ref={ref}
element={element}
display="flex"
alignItems="flex-start"
columnGap="space30"
color="colorText"
lineHeight="lineHeight50"
fontSize="fontSize40"
{...variantStyles[props.variant]}
{...safelySpreadBoxProps(props)}
>
{children}
</Box>
),
);

AIChatMessageMetaItem.displayName = "AIChatMessageMetaItem";
15 changes: 15 additions & 0 deletions packages/paste-core/components/ai-log/src/index.tsx
Original file line number Diff line number Diff line change
@@ -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";
39 changes: 39 additions & 0 deletions packages/paste-core/components/ai-log/src/useAIChatLogger.tsx
Original file line number Diff line number Diff line change
@@ -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<AIChat, "id"> & Partial<Pick<AIChat, "id">>;

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<AIChat[]>(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 };
};
Loading

0 comments on commit c34b24c

Please sign in to comment.