Skip to content

Commit 219e25c

Browse files
Update ai chat (#4331)
* feat(ai-chat): align user chat to right * feat(ai-chat): update varaint user style * feat(ai-chat): update full screen styles * feat(ai-chat): update author component styles * feat(ai-chat): add timestamp prop * feat(ai-chat): add ai chat event * feat(ai-chat): add agent variant * feat(ai-chat): update yarn * feat(ai-chat): update api for fullscreen size * feat(ai-chat): update typedocs * feat(ai-chat): add changeset * feat(ai-chat): change order of disclosure opening * feat(ai-chat): resolve github comments * feat(ai-chat): update padding * feat(ai-chat): update examples * feat(ai-chat): update withSeparator comment * feat(ai-chat): update typedocs * feat(ai-chat): update typedocs * feat(ai-chat): update AIChatMessageBody * feat(ai-chat): fix eslint issue * feat(ai-chat): update docs * feat(ai-chat): update changeset * feat(ai-chat): fix space --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent 793258b commit 219e25c

23 files changed

+2232
-191
lines changed

.changeset/real-tools-hide.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@twilio-paste/ai-chat-log": minor
3+
"@twilio-paste/core": minor
4+
"@twilio-paste/codemods": minor
5+
---
6+
7+
[AI Chat Log] Update styles of AI Chat Log, add new props, add event component. Deprecates `size` prop from `AIChatMessageBody` and move it to `AIChatLog`

packages/paste-codemods/tools/.cache/mappings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"AccountSwitcherItemRadio": "@twilio-paste/core/account-switcher",
77
"AccountSwitcherSeparator": "@twilio-paste/core/account-switcher",
88
"useAccountSwitcherState": "@twilio-paste/core/account-switcher",
9+
"AIChatEvent": "@twilio-paste/core/ai-chat-log",
910
"AIChatLog": "@twilio-paste/core/ai-chat-log",
1011
"AIChatLogger": "@twilio-paste/core/ai-chat-log",
1112
"AIChatMessage": "@twilio-paste/core/ai-chat-log",

packages/paste-core/components/ai-chat-log/__tests__/aiChatLog.spec.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const ExampleAIChatLog: React.FC<React.PropsWithChildren> = () => (
1717
<AIChatMessageAuthor aria-label="AI said" data-testid="author">
1818
Good Bot
1919
</AIChatMessageAuthor>
20-
<AIChatMessageBody size="default">Lorem ipsum dolor.</AIChatMessageBody>
20+
<AIChatMessageBody>Lorem ipsum dolor.</AIChatMessageBody>
2121
<AIChatMessageActionGroup data-testid="action_group">
2222
<AIChatMessageActionCard aria-label="action" data-testid="action">
2323
Is this helpful?
@@ -34,9 +34,7 @@ const CustomExampleAIChatLog: React.FC<React.PropsWithChildren> = () => (
3434
<AIChatMessageAuthor aria-label="AI said" data-testid="author" element="FOO_AI_CHAT_MESSAGE_AUTHOR">
3535
Good Bot
3636
</AIChatMessageAuthor>
37-
<AIChatMessageBody size="default" element="FOO_AI_CHAT_MESSAGE_BODY">
38-
Lorem ipsum dolor.
39-
</AIChatMessageBody>
37+
<AIChatMessageBody element="FOO_AI_CHAT_MESSAGE_BODY">Lorem ipsum dolor.</AIChatMessageBody>
4038
<AIChatMessageActionGroup data-testid="action_group" element="FOO_AI_CHAT_MESSAGE_ACTION_GROUP">
4139
<AIChatMessageActionCard
4240
aria-label="Feedback form"

packages/paste-core/components/ai-chat-log/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@twilio-paste/design-tokens": "^10.3.0",
3636
"@twilio-paste/icons": "^13.0.0",
3737
"@twilio-paste/screen-reader-only": "^14.0.0",
38+
"@twilio-paste/separator": "^9.0.1",
3839
"@twilio-paste/skeleton-loader": "^7.0.0",
3940
"@twilio-paste/spinner": "^15.0.0",
4041
"@twilio-paste/stack": "^9.0.0",
@@ -60,6 +61,7 @@
6061
"@twilio-paste/design-tokens": "^10.12.0",
6162
"@twilio-paste/icons": "^13.0.1",
6263
"@twilio-paste/screen-reader-only": "^14.0.1",
64+
"@twilio-paste/separator": "^9.0.1",
6365
"@twilio-paste/skeleton-loader": "^7.0.1",
6466
"@twilio-paste/spinner": "^15.0.1",
6567
"@twilio-paste/stack": "^9.0.1",
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import type { BoxElementProps } from "@twilio-paste/box";
2+
import { Box, safelySpreadBoxProps } from "@twilio-paste/box";
3+
import { Separator } from "@twilio-paste/separator";
4+
import type { HTMLPasteProps } from "@twilio-paste/types";
5+
import * as React from "react";
6+
7+
import { AILogContext } from "./AILogContext";
8+
9+
export interface AIChatEventProps extends HTMLPasteProps<"div"> {
10+
children?: React.ReactNode;
11+
/**
12+
* Overrides the default element name to apply unique styles with the Customization Provider
13+
*
14+
* @default "AI_CHAT_EVENT"
15+
* @type {BoxProps["element"]}
16+
* @memberof AIChatEventProps
17+
*/
18+
element?: BoxElementProps["element"];
19+
/**
20+
* If true, adds a separator on either side of the event message.
21+
*
22+
* @default false
23+
* @type Boolean
24+
* @memberof AIChatEventProps
25+
*/
26+
withSeparator?: boolean;
27+
}
28+
29+
export const AIChatEvent = React.forwardRef<HTMLDivElement, AIChatEventProps>(
30+
({ children, element = "AI_CHAT_MESSAGE", withSeparator, ...props }, ref) => {
31+
const { size } = React.useContext(AILogContext);
32+
33+
return (
34+
<Box
35+
display="flex"
36+
justifyContent="center"
37+
columnGap="space10"
38+
alignItems="center"
39+
color="colorTextWeak"
40+
fontSize="fontSize20"
41+
fontWeight="fontWeightMedium"
42+
ref={ref}
43+
element={element}
44+
marginY={size === "fullScreen" ? "space50" : "space0"}
45+
{...safelySpreadBoxProps(props)}
46+
>
47+
{withSeparator && (
48+
<Box flexGrow={1}>
49+
<Separator orientation="horizontal" />
50+
</Box>
51+
)}
52+
{children}
53+
{withSeparator && (
54+
<Box flexGrow={1}>
55+
<Separator orientation="horizontal" />
56+
</Box>
57+
)}
58+
</Box>
59+
);
60+
},
61+
);
62+
63+
AIChatEvent.displayName = "AIChatEvent";

packages/paste-core/components/ai-chat-log/src/AIChatLog.tsx

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import type { BoxProps } from "@twilio-paste/box";
33
import type { HTMLPasteProps } from "@twilio-paste/types";
44
import * as React from "react";
55

6+
import { AILogContext, type AILogSizes } from "./AILogContext";
7+
68
export interface AIChatLogProps extends HTMLPasteProps<"div"> {
79
children?: React.ReactNode;
810
/**
@@ -12,25 +14,35 @@ export interface AIChatLogProps extends HTMLPasteProps<"div"> {
1214
* @memberof AIChatLogProps
1315
*/
1416
element?: BoxProps["element"];
17+
/**
18+
* Use a larger font size and line height for fullscreen experiences.
19+
*
20+
* @default "default"
21+
* @type {AILogSizes}
22+
* @memberof AIChatLogProps
23+
*/
24+
size?: AILogSizes;
1525
}
1626

1727
export const AIChatLog = React.forwardRef<HTMLDivElement, AIChatLogProps>(
18-
({ element = "AI_CHAT_LOG", children, ...props }, ref) => {
28+
({ element = "AI_CHAT_LOG", children, size, ...props }, ref) => {
1929
return (
20-
<Box role="log" paddingY="space70" element={element} ref={ref} {...safelySpreadBoxProps(props)}>
21-
<Box
22-
as="div"
23-
role="list"
24-
margin="space0"
25-
padding="space0"
26-
display="flex"
27-
flexDirection="column"
28-
rowGap="space130"
29-
element={`${element}_LIST`}
30-
>
31-
{children}
30+
<AILogContext.Provider value={{ size }}>
31+
<Box role="log" paddingY="space70" element={element} ref={ref} {...safelySpreadBoxProps(props)}>
32+
<Box
33+
as="div"
34+
role="list"
35+
margin="space0"
36+
padding="space0"
37+
display="flex"
38+
flexDirection="column"
39+
rowGap="space70"
40+
element={`${element}_LIST`}
41+
>
42+
{children}
43+
</Box>
3244
</Box>
33-
</Box>
45+
</AILogContext.Provider>
3446
);
3547
},
3648
);

packages/paste-core/components/ai-chat-log/src/AIChatMessage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export const AIChatMessage = React.forwardRef<HTMLDivElement, AIChatMessageProps
4646
rowGap="space40"
4747
ref={ref}
4848
element={element}
49+
alignItems={variant === "user" ? "flex-end" : "inherit"}
4950
{...safelySpreadBoxProps(props)}
5051
>
5152
{children}

packages/paste-core/components/ai-chat-log/src/AIChatMessageActionGroup.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export const AIChatMessageActionGroup = React.forwardRef<HTMLDivElement, AIChatM
2727
alignItems="center"
2828
columnGap="space40"
2929
fontWeight="fontWeightMedium"
30-
marginTop="space40"
3130
>
3231
{children}
3332
</Box>

packages/paste-core/components/ai-chat-log/src/AIChatMessageAuthor.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { GenericIconProps } from "@twilio-paste/icons/esm/types";
66
import type { HTMLPasteProps } from "@twilio-paste/types";
77
import * as React from "react";
88

9+
import { AILogContext } from "./AILogContext";
910
import { AIMessageContext } from "./AIMessageContext";
1011

1112
export interface AIChatMessageAuthorProps extends HTMLPasteProps<"div"> {
@@ -57,6 +58,8 @@ export interface AIChatMessageAuthorProps extends HTMLPasteProps<"div"> {
5758
export const AIChatMessageAuthor = React.forwardRef<HTMLDivElement, AIChatMessageAuthorProps>(
5859
({ children, element = "AI_CHAT_MESSAGE_AUTHOR", avatarName, avatarIcon, avatarSrc, ...props }, ref) => {
5960
const { variant } = React.useContext(AIMessageContext);
61+
const { size } = React.useContext(AILogContext);
62+
const isFullScreen = size === "fullScreen";
6063

6164
return (
6265
<Box
@@ -67,19 +70,21 @@ export const AIChatMessageAuthor = React.forwardRef<HTMLDivElement, AIChatMessag
6770
display="flex"
6871
alignItems="center"
6972
columnGap="space40"
70-
fontWeight="fontWeightMedium"
73+
fontWeight="fontWeightSemibold"
74+
fontSize={isFullScreen ? "fontSize30" : "fontSize20"}
75+
color="colorTextWeak"
7176
>
7277
{variant === "bot" ? (
7378
<Avatar
7479
name={children}
75-
size="sizeIcon50"
80+
size="sizeIcon30"
7681
color="decorative20"
7782
icon={ArtificialIntelligenceIcon}
7883
element={`${element}_BOT_AVATAR`}
7984
/>
8085
) : (
8186
<Avatar
82-
size="sizeIcon50"
87+
size="sizeIcon30"
8388
color="decorative30"
8489
element={`${element}_USER_AVATAR`}
8590
name={avatarName || children}

packages/paste-core/components/ai-chat-log/src/AIChatMessageBody.tsx

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { Box, safelySpreadBoxProps } from "@twilio-paste/box";
22
import type { BoxElementProps, BoxStyleProps } from "@twilio-paste/box";
3+
import { css, styled } from "@twilio-paste/styling-library";
34
import type { HTMLPasteProps } from "@twilio-paste/types";
45
import * as React from "react";
56

7+
import { AILogContext, type AILogSizes } from "./AILogContext";
68
import { AIMessageContext } from "./AIMessageContext";
79
import { useAnimatedText } from "./utils";
810

@@ -31,11 +33,12 @@ export interface AIChatMessageBodyProps extends HTMLPasteProps<"div"> {
3133
/**
3234
* Use a larger font size and line height for fullscreen experiences.
3335
*
36+
* @deprecated Use the `size` prop on the AIChatLog component instead.
3437
* @default "default"
35-
* @type {"default" | "fullScreen"}
38+
* @type {AILogSizes}
3639
* @memberof AIChatMessageBodyProps
3740
*/
38-
size?: "default" | "fullScreen";
41+
size?: AILogSizes;
3942
/**
4043
* Whether the text should be animated with type writer effect
4144
*
@@ -60,8 +63,27 @@ export interface AIChatMessageBodyProps extends HTMLPasteProps<"div"> {
6063
* @memberof AIChatMessageBodyProps
6164
*/
6265
onAnimationEnd?: () => void;
66+
/**
67+
* The timestamp of the message
68+
*
69+
* @default undefined
70+
* @type {string}
71+
* @memberof AIChatMessageBodyProps
72+
*/
73+
timestamp?: string;
6374
}
6475

76+
const StyledBox = styled(Box)(
77+
css({
78+
"& p:first-of-type": {
79+
marginTop: "0",
80+
},
81+
"& p:last-of-type": {
82+
marginBottom: "0",
83+
},
84+
}),
85+
);
86+
6587
export const AIChatMessageBody = React.forwardRef<HTMLDivElement, AIChatMessageBodyProps>(
6688
(
6789
{
@@ -71,15 +93,43 @@ export const AIChatMessageBody = React.forwardRef<HTMLDivElement, AIChatMessageB
7193
animated = false,
7294
onAnimationEnd,
7395
onAnimationStart,
96+
timestamp,
7497
...props
7598
},
7699
ref,
77100
) => {
78-
const { id } = React.useContext(AIMessageContext);
101+
const { id, variant } = React.useContext(AIMessageContext);
102+
const { size: sizeContext } = React.useContext(AILogContext);
79103
const [showAnimation] = React.useState(animated && children !== undefined);
80-
const animationSpeed = size === "fullScreen" ? 8 : 10;
104+
const isFullScreen = size === "fullScreen" || sizeContext === "fullScreen";
105+
const animationSpeed = isFullScreen ? 8 : 10;
81106
const { animatedChildren, isAnimating } = useAnimatedText(children, animationSpeed, showAnimation);
82107

108+
const commonStyles: BoxStyleProps = {
109+
paddingY: isFullScreen ? "space50" : "space30",
110+
paddingX: "space40",
111+
borderRadius: "borderRadius40",
112+
maxWidth: isFullScreen ? "530px" : "260px",
113+
};
114+
115+
const Styles: Record<string, BoxStyleProps> = {
116+
bot: {
117+
backgroundColor: "inherit",
118+
padding: "space0",
119+
borderRadius: "borderRadius0",
120+
maxWidth: "100%",
121+
},
122+
user: {
123+
...commonStyles,
124+
backgroundColor: "colorBackgroundWeakElevation",
125+
},
126+
agent: {
127+
...commonStyles,
128+
backgroundColor: "colorBackgroundBody",
129+
boxShadow: "shadowElevation05",
130+
},
131+
};
132+
83133
React.useEffect(() => {
84134
if (onAnimationStart && animated && isAnimating) {
85135
onAnimationStart();
@@ -91,21 +141,33 @@ export const AIChatMessageBody = React.forwardRef<HTMLDivElement, AIChatMessageB
91141
}, [isAnimating, showAnimation]);
92142

93143
return (
94-
<Box
144+
<StyledBox
95145
{...safelySpreadBoxProps(props)}
96-
{...Sizes[size]}
146+
{...Sizes[sizeContext || size]}
97147
display="inline-block"
98148
color="colorText"
99149
wordWrap="break-word"
100-
maxWidth="100%"
101150
minWidth={0}
102151
element={element}
103152
ref={ref}
104153
whiteSpace="pre-wrap"
105154
id={id}
155+
marginBottom={isFullScreen ? "space30" : "space0"}
156+
{...Styles[variant]}
106157
>
107158
{animatedChildren}
108-
</Box>
159+
{timestamp && (
160+
<Box
161+
fontSize="fontSize20"
162+
color="colorTextWeak"
163+
marginTop="space30"
164+
element={`${element}_TIMESTAMP`}
165+
textAlign={variant === "bot" ? "left" : "right"}
166+
>
167+
{timestamp}
168+
</Box>
169+
)}
170+
</StyledBox>
109171
);
110172
},
111173
);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as React from "react";
2+
3+
export type AILogSizes = "default" | "fullScreen";
4+
5+
export interface AILogContextProps {
6+
size?: AILogSizes;
7+
}
8+
export const AILogContext = React.createContext<AILogContextProps>({} as any);

packages/paste-core/components/ai-chat-log/src/AIMessageContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from "react";
22

3-
export type AIMessageVariants = "bot" | "user";
3+
export type AIMessageVariants = "bot" | "user" | "agent";
44

55
export interface AIMessageContextProps {
66
variant: AIMessageVariants;

packages/paste-core/components/ai-chat-log/src/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export { AIChatMessageActionCard } from "./AIChatMessageActionCard";
1010
export type { AIChatMessageActionCardProps } from "./AIChatMessageActionCard";
1111
export { AIChatMessageLoading } from "./AIChatMessageLoading";
1212
export type { AIChatMessageLoadingProps } from "./AIChatMessageLoading";
13+
export { AIChatEvent } from "./AIChatEvent";
14+
export type { AIChatEventProps } from "./AIChatEvent";
1315

1416
export { AIChatLog } from "./AIChatLog";
1517
export type { AIChatLogProps } from "./AIChatLog";

0 commit comments

Comments
 (0)