diff --git a/.changeset/pretty-melons-punch.md b/.changeset/pretty-melons-punch.md index 3b8d854eb0..fe79eb8599 100644 --- a/.changeset/pretty-melons-punch.md +++ b/.changeset/pretty-melons-punch.md @@ -3,4 +3,4 @@ "@twilio-paste/core": patch --- -[Button] Add border radius 20 to size="reset" buttons which can be overridden by passing a border radius token to variant="reset" and size="reset" buttons \ No newline at end of file +[Button] Add border radius 20 to size="reset" buttons which can be overridden by passing a border radius token to variant="reset" and size="reset" buttons diff --git a/cypress/integration/sitemap-vrt/constants.ts b/cypress/integration/sitemap-vrt/constants.ts index 49e88dd27e..c7367f49f6 100644 --- a/cypress/integration/sitemap-vrt/constants.ts +++ b/cypress/integration/sitemap-vrt/constants.ts @@ -24,6 +24,9 @@ export const SITEMAP = [ "/components/account-switcher/", "/components/account-switcher/api", "/components/account-switcher/changelog", + "/components/ai-chat-log/", + "/components/ai-chat-log/api", + "/components/ai-chat-log/changelog", "/components/aspect-ratio/", "/components/aspect-ratio/api", "/components/aspect-ratio/changelog", diff --git a/packages/paste-core/components/ai-chat-log/CHANGELOG.md b/packages/paste-core/components/ai-chat-log/CHANGELOG.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/paste-core/components/ai-chat-log/package.json b/packages/paste-core/components/ai-chat-log/package.json index bef2f140bd..9e6c788a0a 100644 --- a/packages/paste-core/components/ai-chat-log/package.json +++ b/packages/paste-core/components/ai-chat-log/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "category": "data display", "status": "production", - "description": "Ai chat log.", + "description": "An AI Chat Log is a collection of AI Chat components for displaying conversations between a human and an AI bot.", "author": "Twilio Inc.", "license": "MIT", "main:dev": "src/index.tsx", diff --git a/packages/paste-website/package.json b/packages/paste-website/package.json index b98320ca70..192f8f2ef3 100644 --- a/packages/paste-website/package.json +++ b/packages/paste-website/package.json @@ -34,6 +34,7 @@ "@tanstack/react-query": "^5.17.9", "@tanstack/react-query-devtools": "^5.17.10", "@twilio-paste/account-switcher": "^3.0.1", + "@twilio-paste/ai-chat-log": "^0.0.0", "@twilio-paste/alert": "^14.1.0", "@twilio-paste/alert-dialog": "^9.2.0", "@twilio-paste/anchor": "^12.1.0", diff --git a/packages/paste-website/src/component-examples/AIChatLogExamples.ts b/packages/paste-website/src/component-examples/AIChatLogExamples.ts new file mode 100644 index 0000000000..fc679bd134 --- /dev/null +++ b/packages/paste-website/src/component-examples/AIChatLogExamples.ts @@ -0,0 +1,310 @@ +export const basicBotMessage = ` +const BasicMessage = () => { + return ( + + + Good Bot + + Here is what I found, error code 30003 means: The destination phone is unavailable or turned off, or it may be a landline or phone that doesn't support SMS. + + + + ); +}; + +render( + +)`.trim(); +export const basicHumanMessage = ` +const BasicMessage = () => { + return ( + + + Gibby Radki + + I would like some information on twilio error codes for undelivered messages + + + + ); +}; + +render( + +)`.trim(); +export const botWithFeedback = ` +const MessageWithFeedback = () => { + return ( + + + Good Bot + + Here is what I found, error code 30003 means: The destination phone is unavailable or turned off, or it may be a landline or phone that doesn't support SMS. + + + + Is this helpful? + + + + + + + + + + + ); +}; + +render( + +)`.trim(); +export const botWithBodyActions = ` +const MessageWithFeedback = () => { + return ( + + + Good Bot + + Below is a list of actions that can be taken with flex wrapping supported: + + + + + + + + + ); +}; + +render( + +)`.trim(); +export const botWithLoadingStopButton = ` +const MessageWithLoadingAndStop = () => { + return ( + + + + Good Bot + + + {}} /> + + + + ); +}; + +render( + +)`.trim(); +export const botWithLoading = ` +const MessageWithLoading = () => { + return ( + + + + Good Bot + + + + + + + ); +}; + +render( + +)`.trim(); +export const kitchenSink = ` +const AIChatLogExample = () => { + return ( + + + Gibby Radki + + Hi, I'm getting errors codes when sending an SMS. + + + + Good Bot + + Error codes can be returned from various parts of the process. What error codes are you encountering? + + + + + + + + + Good Bot + + Error 21608 means you're trying to send a message from an unverified number. Is your number verified in your Twilio account? + + + + Is this helpful? + + + + + + + + Gibby Radki + + + No, how do I verify it? + + + + + Good Bot + + + {}} /> + + + + ); +}; + +render( + +)`.trim(); +export const aiChatLoggerExample = ` +const aiChatFactory = ([ message, variant, metaLabel, meta ]) => { + const time = new Date(0).toLocaleString( + 'en-US', + { hour: 'numeric', minute: 'numeric', timeZone: 'UTC', hour12: true } + ) + + return { + variant, + content: ( + + {meta} + + {message} + + + ) + } +}; + +const chatTemplates = [ + ["Hello", "user", "You said at ", "Gibby Radki"], + ["Hi there", "bot", "AI said at ", "Good Bot"], + ["Greetings", "user", "You said at ", "Gibby Radki"], + ["Good to meet you", "bot", "AI said at ", "Good Bot"] +]; + +const AIChatLoggerExample = () => { + const [templateIdx, setTemplateIdx] = React.useState(2); + const { aiChats, push, pop, clear } = useAIChatLogger( + aiChatFactory(chatTemplates[0]), + aiChatFactory(chatTemplates[1]) + ); + const [loading, setLoading] = React.useState(false); + + const pushChat = () => { + const template = chatTemplates[templateIdx]; + setTemplateIdx((idx) => ++idx % chatTemplates.length); + const chat = aiChatFactory(template); + + if (template[1] === "bot") { + const id = uid(chat.content); + setLoading(true); + push({ + id, + variant: template[1], + content: ( + + Good Bot + + + + + ), + }); + setTimeout(() => { + pop(id); + setLoading(false); + push(chat); + }, 1000); + } else { + push(chat); + } + } + + const popChat = () => { + pop(); + setTemplateIdx((idx) => idx === 0 ? idx : --idx % chatTemplates.length); + } + + return( + + + + + + + + + ) +} + +render(); +`.trim(); +export const avatarExample = ` +const AvatarExample = () => { + return ( + + + Gibby Radki + + + Gibby Radki + + + ); +}; + +render( + +)`.trim(); diff --git a/packages/paste-website/src/pages/components/ai-chat-log/api.mdx b/packages/paste-website/src/pages/components/ai-chat-log/api.mdx new file mode 100644 index 0000000000..20adf134bd --- /dev/null +++ b/packages/paste-website/src/pages/components/ai-chat-log/api.mdx @@ -0,0 +1,100 @@ +export const meta = { + title: "AI Chat Log - API", + package: "@twilio-paste/ai-chat-log", + description: + "An AI Chat Log is a collection of Chat components for displaying conversations between a human and an AI bot", + slug: "/components/ai-chat-log/api", +}; + +import Changelog from "@twilio-paste/ai-chat-log/CHANGELOG.md"; // I don't know why this is needed but if you remove it the page fails to render +import packageJson from "@twilio-paste/ai-chat-log/package.json"; + +import { SidebarCategoryRoutes } from "../../../constants"; +import ComponentPageLayout from "../../../layouts/ComponentPageLayout"; +import { getFeature, getNavigationData, getComponentApi } from "../../../utils/api"; + +export default ComponentPageLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + const feature = await getFeature("AI Chat Log"); + const { componentApi, componentApiTocData } = getComponentApi("@twilio-paste/ai-chat-log"); + return { + props: { + data: { + ...packageJson, + ...feature, + }, + componentApi, + mdxHeadings: [...mdxHeadings, ...componentApiTocData], + navigationData, + pageHeaderData: { + categoryRoute: SidebarCategoryRoutes.COMPONENTS, + githubUrl: "https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/ai-chat-log", + storybookUrl: "/?path=/story/components-chatlog--example-chat-log", + }, + }, + }; +}; + +## Installation + +```bash +yarn add @twilio-paste/ai-chat-log - or - yarn add @twilio-paste/core +``` + +## Usage + +```jsx +import { + AIChatLog, + AIChatMessage, + AIChatMessageAuthor, + AIChatMessageBody, + AIChatMessageFeedback, + AIChatMessageLoading, + AIChatMessageMeta, +} from "@twilio-paste/ai-chat-log"; + +export const Basic = () => { + return ( + + + Gibby Radki + + Hi, I'm getting errors codes when sending an SMS. + + + + Good Bot + + Error codes can be returned from various parts of the process. What error codes are you encountering? + + + + Is this helpful? + + + + + + + + + + + ); +}; +``` + +## Props + + diff --git a/packages/paste-website/src/pages/components/ai-chat-log/changelog.mdx b/packages/paste-website/src/pages/components/ai-chat-log/changelog.mdx new file mode 100644 index 0000000000..b78049f582 --- /dev/null +++ b/packages/paste-website/src/pages/components/ai-chat-log/changelog.mdx @@ -0,0 +1,38 @@ +export const meta = { + title: "AI Chat Log - Components", + package: "@twilio-paste/ai-chat-log", + description: + "An AI Chat Log is a collection of Chat components for displaying conversations between a human and an AI bot.", + slug: "/components/ai-chat-log/changelog", +}; + +import Changelog from "@twilio-paste/ai-chat-log/CHANGELOG.md"; +import packageJson from "@twilio-paste/ai-chat-log/package.json"; + +import { SidebarCategoryRoutes } from "../../../constants"; +import ComponentPageLayout from "../../../layouts/ComponentPageLayout"; +import { getFeature, getNavigationData } from "../../../utils/api"; + +export default ComponentPageLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + const feature = await getFeature("AI Chat Log"); + return { + props: { + data: { + ...packageJson, + ...feature, + }, + navigationData, + mdxHeadings, + pageHeaderData: { + categoryRoute: SidebarCategoryRoutes.COMPONENTS, + githubUrl: "https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/ai-chat-log", + storybookUrl: "/?path=/story/components-chatlog--example-chat-log", + }, + }, + }; +}; + + diff --git a/packages/paste-website/src/pages/components/ai-chat-log/index.mdx b/packages/paste-website/src/pages/components/ai-chat-log/index.mdx new file mode 100644 index 0000000000..b4a86406f7 --- /dev/null +++ b/packages/paste-website/src/pages/components/ai-chat-log/index.mdx @@ -0,0 +1,363 @@ +export const meta = { + title: "AI Chat Log - Components", + package: "@twilio-paste/ai-chat-log", + description: + "An AI Chat Log is a collection of AI Chat components for displaying conversations between a human and an AI bot.", + slug: "/components/ai-chat-log/", +}; + +import { uid } from "@twilio-paste/uid-library"; +import { HelpText } from "@twilio-paste/help-text"; +import { Button } from "@twilio-paste/button"; +import { ButtonGroup } from "@twilio-paste/button-group"; +import { Stack } from "@twilio-paste/stack"; +import { Paragraph } from "@twilio-paste/paragraph"; +import { + AIChatLog, + AIChatMessage, + AIChatMessageAuthor, + AIChatMessageBody, + AIChatMessageLoading, + AIChatLogger, + useAIChatLogger, + AIChatMessageActionCard, + AIChatMessageActionGroup, +} from "@twilio-paste/ai-chat-log"; +import { ThumbsUpIcon } from "@twilio-paste/icons/esm/ThumbsUpIcon"; +import { ThumbsDownIcon } from "@twilio-paste/icons/esm/ThumbsDownIcon"; +import { RefreshIcon } from "@twilio-paste/icons/esm/RefreshIcon"; +import { CopyIcon } from "@twilio-paste/icons/esm/CopyIcon"; +import { LogoTwilioIcon } from "@twilio-paste/icons/esm/LogoTwilioIcon"; +import Changelog from "@twilio-paste/ai-chat-log/CHANGELOG.md"; + +import { SidebarCategoryRoutes } from "../../../constants"; +import Logo from "../../../assets/logo.svg"; + +import { + basicBotMessage, + basicHumanMessage, + botWithFeedback, + botWithLoadingStopButton, + botWithLoading, + kitchenSink, + aiChatLoggerExample, + botWithBodyActions, + avatarExample, +} from "../../../component-examples/AIChatLogExamples"; +import packageJson from "@twilio-paste/ai-chat-log/package.json"; +import ComponentPageLayout from "../../../layouts/ComponentPageLayout"; +import { getFeature, getNavigationData } from "../../../utils/api"; + +export default ComponentPageLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + const feature = await getFeature("AI Chat Log"); + return { + props: { + data: { + ...packageJson, + ...feature, + }, + navigationData, + mdxHeadings, + pageHeaderData: { + categoryRoute: SidebarCategoryRoutes.COMPONENTS, + githubUrl: "https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/ai-chat-log", + storybookUrl: "/?path=/story/components-chatlog--example-chat-log", + }, + }, + }; +}; + + + {` + + Gibby Radki + + What does the SMS delivery error code 30003 mean? + + + + Good Bot + + Here is what I found, error code 30003 means: The destination phone is unavailable or turned off, or it may be a landline or phone that doesn't support SMS. + + +`} + + +## Guidelines + +### About AI Chat Log + +An AI Chat Log is a way to display conversations between a user and AI. If you are looking for a chat between 2 or more humans, please refer to [Chat Log](/components/chat-log). + +The AI Chat Log package includes these main components: + +- AIChatLog +- AIChatMessage +- AIChatMessageAuthor +- AIChatMessageBody +- AIChatMessageActionGroup +- AIChatMessageActionCard +- AIChatMessageLoading + +### Accessibility + +To ensure the chat is accessible, only use the AI Chat components within an `AIChatLog` component and use `AIChatMessage` to wrap `AIChatMessageBody`, `AIChatMessageActionGroup` and components together. + +The only other accessibility requirement is providing the `AIChatMessageActionCard` a descriptive label via the `aria-label` React prop. + +The AIChatLog component has `role="log"` which means that any new messages added to it are announced by assistive technology. + +## Examples + +### Basic Message + +A message must include the author and body. Any message text from a user or a bot must be contained within the `AIChatMessageBody` component. Due to lengthy AI responses, the chat layout is top-down. + +#### Bot + + + {basicBotMessage} + + +#### User + + + {basicHumanMessage} + + +### Message Body Sizes + +The `AIChatMessageBody` component has two sizes, `size="default"` and `size="fullScreen"`. The fullScreen size is used where the ChatLog is displayed in the full width of the page where larger font size is needed. + + + {` + + Gibby Radki + + I'm a message that should be displayed in compact elements + + + + Gibby Radki + + I'm a message that will be displayed in full screen width + + +`} + + +### Message with Actions + +Message actions can be used to provide quick responses or actions to the user. + +`AIChatMessageActionGroup` should be a child of `AIChatMessage` so that the text and meta information are correctly grouped together for assistive technologies. `AIChatMessageActionCard` also needs a readable `aria-label` that summarizes what the meta information says. + +Each item within `AIChatMessageActionGroup` should be wrapped with `AIChatMessageActionCard`. It is recommended to use reset button variants for content within `AIChatMessageActionCard`. + +Actions can still be added in `AIChatMessageBody` which are returned from the AI response. + +#### Feedback in AIChatMessageActionCard + + + {botWithFeedback} + + +#### Buttons in AIChatMessageBody from AI Response + + + {botWithBodyActions} + + +### Loading States + +Use the `AIChatMessageLoading` component to indicate that the bot is typing or processing a response. During this time **no user input should be accepted**. No new messages should be added to a chat until the AI operation is finished processing. + +The SkeletonLoader lengths vary on each render to give a more natural pending message body interaction. + +#### Loading + + + {botWithLoading} + + +#### Loading with Stop Button + + + {botWithLoadingStopButton} + + +### Customizing Avatar + +`AIChatMessageAuthor` can utilize custom icons by passing an icon to the prop `avatarIcon` or an image to the `avatarSrc` prop. + + + {avatarExample} + + +### Example AI Chat Log + +This example combines all the separate features displayed previously into one example. It shows how all the features work together harmoniously through composition. + + + {kitchenSink} + + +### useAIChatLogger hook + +The `useAIChatLogger` hook provides a hook-based approach to managing AI chat state. It is best used with the `` component. + +`useAIChatLogger` returns 4 things: + +- An array of `aiChats`. +- A `push` method used to add a chat, optionally with a custom ID. +- A `pop` method used to remove a chat, optionally via its ID. +- A `clear` method used to remove all chats. + +##### AIChatLogger component + +The `` component handles rendering the chats it is passed via props. It handles how chats enter and leave the UI. + +``` +const { aiChats } = useAIChatLogger(); +return ; +``` + +##### Adding and removing a chat + +You can push or pop a chat based on an action or event. In this example it's based on a button click: + + + {aiChatLoggerExample} + + +## Composition Notes + +Keep any generated responses from the AI contained in the `AIChatMessageBody` component. Each chat message should only have one `AIChatMessageBody` component. diff --git a/packages/paste-website/src/pages/components/chat-composer/api.mdx b/packages/paste-website/src/pages/components/chat-composer/api.mdx index 2ba0e32372..99fc28949b 100644 --- a/packages/paste-website/src/pages/components/chat-composer/api.mdx +++ b/packages/paste-website/src/pages/components/chat-composer/api.mdx @@ -1,23 +1,23 @@ export const meta = { - title: 'Chat Composer', - package: '@twilio-paste/chat-composer', - description: 'A Chat Composer is an input made for users to type rich chat messages.', - slug: '/components/chat-composer/api', + title: "Chat Composer", + package: "@twilio-paste/chat-composer", + description: "A Chat Composer is an input made for users to type rich chat messages.", + slug: "/components/chat-composer/api", }; -import Changelog from '@twilio-paste/chat-composer/CHANGELOG.md'; // I don't know why this is needed but if you remove it the page fails to render -import packageJson from '@twilio-paste/chat-composer/package.json'; +import Changelog from "@twilio-paste/chat-composer/CHANGELOG.md"; // I don't know why this is needed but if you remove it the page fails to render +import packageJson from "@twilio-paste/chat-composer/package.json"; -import {SidebarCategoryRoutes} from '../../../constants'; -import ComponentPageLayout from '../../../layouts/ComponentPageLayout'; -import {getFeature, getNavigationData, getComponentApi} from '../../../utils/api'; +import { SidebarCategoryRoutes } from "../../../constants"; +import ComponentPageLayout from "../../../layouts/ComponentPageLayout"; +import { getFeature, getNavigationData, getComponentApi } from "../../../utils/api"; export default ComponentPageLayout; export const getStaticProps = async () => { const navigationData = await getNavigationData(); - const feature = await getFeature('Chat Composer'); - const {componentApi, componentApiTocData} = getComponentApi('@twilio-paste/chat-composer'); + const feature = await getFeature("Chat Composer"); + const { componentApi, componentApiTocData } = getComponentApi("@twilio-paste/chat-composer"); return { props: { data: { @@ -29,8 +29,8 @@ export const getStaticProps = async () => { navigationData, pageHeaderData: { categoryRoute: SidebarCategoryRoutes.COMPONENTS, - githubUrl: 'https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/chat-composer', - storybookUrl: '/?path=/story/components-chat-composer--default', + githubUrl: "https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/chat-composer", + storybookUrl: "/?path=/story/components-chat-composer--default", }, }, }; @@ -45,12 +45,12 @@ yarn add @twilio-paste/chat-composer - or - yarn add @twilio-paste/core ## Usage ```jsx -import {ChatComposer} from '@twilio-paste/core/chat-composer'; +import { ChatComposer } from "@twilio-paste/core/chat-composer"; export const BasicChatComposer = () => ( { throw e; }, diff --git a/packages/paste-website/src/pages/components/chat-log/index.mdx b/packages/paste-website/src/pages/components/chat-log/index.mdx index 6638f26bcb..d7f6729504 100644 --- a/packages/paste-website/src/pages/components/chat-log/index.mdx +++ b/packages/paste-website/src/pages/components/chat-log/index.mdx @@ -100,7 +100,7 @@ export const getStaticProps = async () => { ### About Chat Log -A Chat Log is a way to display conversations between people and can include complex content like attachments. The chat can be between two or more people. +A Chat Log is a way to display conversations between people and can include complex content like attachments. The chat can be between two or more people. If you are looking for a chat between a human and an AI please refer to [AIChatLog](/components/ai-chat-log). The Chat Log package includes these main components: @@ -292,7 +292,7 @@ This example combines all the separate features displayed previously into one ex The `useChatLogger` hook provides a hook based approach to managing chat state. It is best used with the `` component. -`useChatLogger` returns 3 things: +`useChatLogger` returns 4 things: - An array of `chats`. - A `push` method used to add a chat, optionally with a custom ID diff --git a/yarn.lock b/yarn.lock index ef6f733466..6324d27113 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15858,6 +15858,7 @@ __metadata: "@tanstack/react-query-devtools": ^5.17.10 "@testing-library/react": ^13.4.0 "@twilio-paste/account-switcher": ^3.0.1 + "@twilio-paste/ai-chat-log": ^0.0.0 "@twilio-paste/alert": ^14.1.0 "@twilio-paste/alert-dialog": ^9.2.0 "@twilio-paste/anchor": ^12.1.0