Skip to content

chore(assistant): use ai components #4112

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

Merged
merged 9 commits into from
Nov 4, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ const MessageWithLoadingAndStop = () => {
return (
<AIChatLog>
<AIChatMessage variant="bot">
<AIChatMessageAuthor aria-label="AI said" bot>
<AIChatMessageAuthor aria-label="AI said">
Good Bot
</AIChatMessageAuthor>
<AIChatMessageBody>
Expand All @@ -120,7 +120,7 @@ const MessageWithLoading = () => {
return (
<AIChatLog>
<AIChatMessage variant="bot">
<AIChatMessageAuthor aria-label="AI said" bot>
<AIChatMessageAuthor aria-label="AI said">
Good Bot
</AIChatMessageAuthor>
<AIChatMessageBody>
Expand Down
55 changes: 36 additions & 19 deletions packages/paste-website/src/components/assistant/AssistantCanvas.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useIsMutating, useQuery } from "@tanstack/react-query";
import { AIChatLog } from "@twilio-paste/ai-chat-log";
import { Box } from "@twilio-paste/box";
import { ChatBookend, ChatBookendItem, ChatLog } from "@twilio-paste/chat-log";
import { Text } from "@twilio-paste/text";
import * as React from "react";
import { useShallow } from "zustand/react/shallow";

Expand All @@ -17,7 +18,6 @@ type AssistantCanvasProps = {

export const AssistantCanvas: React.FC<AssistantCanvasProps> = ({ selectedThreadID }) => {
const [mounted, setMounted] = React.useState(false);
const [logWidth, setLogWidth] = React.useState(0);
const messages = useAssistantMessagesStore(useShallow((state) => state.messages));
const setMessages = useAssistantMessagesStore(useShallow((state) => state.setMessages));
const activeRun = useAssistantRunStore(useShallow((state) => state.activeRun));
Expand Down Expand Up @@ -48,8 +48,6 @@ export const AssistantCanvas: React.FC<AssistantCanvasProps> = ({ selectedThread

React.useEffect(() => {
setMounted(true);
// whats the width of the log? You'll need it to render the skeleton loader
setLogWidth(loggerRef.current?.offsetWidth ?? 0);
}, []);

// scroll to bottom of chat log when new messages are added
Expand All @@ -62,29 +60,48 @@ export const AssistantCanvas: React.FC<AssistantCanvasProps> = ({ selectedThread
<Box ref={scrollerRef} tabIndex={0} overflowY="auto">
<Box maxWidth="1000px" marginX="auto">
{activeRun != null && <AssistantMessagePoller />}
<ChatLog ref={loggerRef}>
<ChatBookend>
<ChatBookendItem>
<AIChatLog ref={loggerRef}>
<Box display="flex" flexDirection="column" rowGap="space40">
<Text
as="span"
color="colorTextWeak"
fontSize="fontSize20"
lineHeight="lineHeight20"
fontWeight="fontWeightMedium"
textAlign="center"
>
Welcome to the Paste Design System Assistant! We&apos;re excited to have you here.
</ChatBookendItem>
</ChatBookend>
<ChatBookend>
<ChatBookendItem>
Keep in mind that this is an experimental tool and so the information provided{" "}
<strong>may not be entirely accurate</strong>.
</ChatBookendItem>
<ChatBookendItem>
</Text>
<Text
as="span"
color="colorTextWeak"
fontSize="fontSize20"
lineHeight="lineHeight20"
fontWeight="fontWeightMedium"
textAlign="center"
>
Keep in mind that this is an experimental tool and so the information provided may not be entirely
accurate.
</Text>
<Text
as="span"
color="colorTextWeak"
fontSize="fontSize20"
lineHeight="lineHeight20"
fontWeight="fontWeightMedium"
textAlign="center"
>
Your conversations are not used to train OpenAI&apos;s models, but are stored by OpenAI.
</ChatBookendItem>
</ChatBookend>
</Text>
</Box>
{messages?.map((threadMessage): React.ReactNode => {
if (threadMessage.role === "assistant") {
return <AssistantMessage key={threadMessage.id} threadMessage={threadMessage} />;
}
return <UserMessage key={threadMessage.id} threadMessage={threadMessage} />;
})}
{(isCreatingAResponse || activeRun != null) && <LoadingMessage maxWidth={logWidth} />}
</ChatLog>
{(isCreatingAResponse || activeRun != null) && <LoadingMessage />}
</AIChatLog>
</Box>
</Box>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { ChatComposer } from "@twilio-paste/chat-composer";
import { $getRoot, ClearEditorPlugin, type EditorState } from "@twilio-paste/lexical-library";
import { Button } from "@twilio-paste/button";
import { ChatComposer, ChatComposerActionGroup, ChatComposerContainer } from "@twilio-paste/chat-composer";
import { SendIcon } from "@twilio-paste/icons/esm/SendIcon";
import {
$getRoot,
CLEAR_EDITOR_COMMAND,
ClearEditorPlugin,
type EditorState,
type LexicalEditor,
} from "@twilio-paste/lexical-library";
import * as React from "react";

import { useAssistantThreadsStore } from "../../stores/assistantThreadsStore";
import useStoreWithLocalStorage from "../../stores/useStore";
import { EnterKeySubmitPlugin } from "./EnterKeySubmitPlugin";
import { FocusComposerPlugin } from "./FocusComposerPlugin";
import { SendButtonPlugin } from "./SendButtonPlugin";

export const AssistantComposer: React.FC<{ onMessageCreation: (message: string, selectedThread: string) => void }> = ({
onMessageCreation,
Expand All @@ -15,7 +21,7 @@ export const AssistantComposer: React.FC<{ onMessageCreation: (message: string,
const threadsStore = useStoreWithLocalStorage(useAssistantThreadsStore, (state) => state);
const selectedThread = threadsStore?.selectedThreadID;

const editorRef = React.useRef(null);
const editorInstanceRef = React.useRef<LexicalEditor>(null);

const handleComposerChange = (editorState: EditorState): void => {
editorState.read(() => {
Expand All @@ -29,24 +35,40 @@ export const AssistantComposer: React.FC<{ onMessageCreation: (message: string,
onMessageCreation(message, selectedThread);
};

React.useEffect(() => {
editorInstanceRef.current?.focus();
}, [editorInstanceRef, selectedThread]);

return (
<ChatComposer
maxHeight="size10"
config={{
namespace: "foo",
onError: (error: Error) => {
throw error;
},
}}
ariaLabel="Message"
placeholder="Type here..."
onChange={handleComposerChange}
ref={editorRef}
>
<ClearEditorPlugin />
<SendButtonPlugin onClick={submitMessage} disabled={selectedThread == null} />
<EnterKeySubmitPlugin onKeyDown={submitMessage} />
<FocusComposerPlugin selectedThread={selectedThread} />
</ChatComposer>
<ChatComposerContainer variant="contained">
<ChatComposer
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes new chat composer! 🤟

maxHeight="size10"
config={{
namespace: "foo",
onError: (error: Error) => {
throw error;
},
}}
ariaLabel="Message"
placeholder="Type here..."
onChange={handleComposerChange}
editorInstanceRef={editorInstanceRef}
>
<ClearEditorPlugin />
<EnterKeySubmitPlugin onKeyDown={submitMessage} />
</ChatComposer>
<ChatComposerActionGroup>
<Button
variant="primary_icon"
size="reset"
onClick={() => {
submitMessage();
editorInstanceRef.current?.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined);
}}
>
<SendIcon decorative={false} title="Send" />
</Button>
</ChatComposerActionGroup>
</ChatComposerContainer>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export const AssistantEmptyState: React.FC<{ onCannedThreadCreation: (message: s
}) => {
return (
<Box display="flex" justifyContent="center" alignItems="center" height="100vh">
<Box maxWidth="900px" display="flex" flexDirection="column" rowGap="space190" paddingX="space120">
<Box display="flex" columnGap="space190" alignItems="center">
<Box maxWidth="900px" display="flex" flexDirection="column" rowGap="space150" paddingX="space120">
<Box display="flex" columnGap="space150" alignItems="center">
<Box>
<Heading as="h1" variant="heading20">
Welcome to the Paste Design System Assistant
Expand All @@ -39,7 +39,7 @@ export const AssistantEmptyState: React.FC<{ onCannedThreadCreation: (message: s
<Link href="https://github.com/twilio-labs/paste/discussions/new/choose">Github Discussions</Link>.
</Paragraph>
</Box>
<Image src={EmptyDoSomething} width={400} aria-hidden="true" alt="" priority />
<Image src={EmptyDoSomething} width={300} aria-hidden="true" alt="" priority />
</Box>
<Box>
<Heading as="h2" variant="heading30">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,7 @@ const ThreadsHeader: React.FC<React.PropsWithChildren> = ({ children }) => {
export const Composer: React.FC<React.PropsWithChildren> = ({ children }) => {
return (
<Box
borderStyle="solid"
borderWidth="borderWidth0"
borderTopWidth="borderWidth10"
borderColor="colorBorderWeak"
backgroundColor="colorBackgroundBody"
display="flex"
flexDirection="row"
columnGap="space30"
paddingX="space70"
paddingY="space50"
position="sticky"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
import { ChatBubble, ChatMessage, ChatMessageMeta, ChatMessageMetaItem } from "@twilio-paste/chat-log";
import { AIChatMessage, AIChatMessageAuthor, AIChatMessageBody } from "@twilio-paste/ai-chat-log";
import { type ThreadMessage } from "openai/resources/beta/threads/messages";
import * as React from "react";

import { Logo } from "../../assets/Logo";
import { formatTimestamp } from "../../utils/formatTimestamp";
import { AssistantMarkdown } from "./AssistantMarkdown";

export const AssistantMessage: React.FC<{ threadMessage: ThreadMessage }> = ({ threadMessage }) => {
return (
<ChatMessage variant="inbound">
<ChatBubble>
<AIChatMessage variant="bot">
<AIChatMessageAuthor aria-label={`said by paste assistant at ${formatTimestamp(threadMessage.created_at)}`}>
PasteBot
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great aria-label!

</AIChatMessageAuthor>
<AIChatMessageBody>
{threadMessage.content[0].type === "text" && (
<AssistantMarkdown key={threadMessage.id}>{threadMessage.content[0].text.value}</AssistantMarkdown>
)}
</ChatBubble>
<ChatMessageMeta aria-label={`said by the assistant at ${formatTimestamp(threadMessage.created_at)}`}>
<ChatMessageMetaItem>
<Logo size={20} />
PasteBot ・ {formatTimestamp(threadMessage.created_at)}
</ChatMessageMetaItem>
</ChatMessageMeta>
</ChatMessage>
</AIChatMessageBody>
</AIChatMessage>
);
};

This file was deleted.

55 changes: 10 additions & 45 deletions packages/paste-website/src/components/assistant/LoadingMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,20 @@
/* eslint-disable camelcase */
import { Box } from "@twilio-paste/box";
import { ChatBubble, ChatMessage, ChatMessageMeta, ChatMessageMetaItem } from "@twilio-paste/chat-log";
import { SkeletonLoader } from "@twilio-paste/skeleton-loader";
import { useUID } from "@twilio-paste/uid-library";
import { AIChatMessage, AIChatMessageAuthor, AIChatMessageBody, AIChatMessageLoading } from "@twilio-paste/ai-chat-log";
import * as React from "react";

import { Logo } from "../../assets/Logo";
import { useAssistantRunStore } from "../../stores/assistantRunStore";
import { formatTimestamp } from "../../utils/formatTimestamp";

const getRandomNumber = (max: number): number => {
return Math.floor(Math.random() * max);
};

const STATUS_MAP = {
queued: "Queued...",
in_progress: "Researching...",
requires_action: "Researching...",
cancelling: "Concelling...",
cancelled: "Cancelled.",
failed: "Failed.",
completed: "Finished.",
expired: "Expired.",
};

export const LoadingMessage: React.FC<{ maxWidth: number }> = ({ maxWidth }) => {
const activeRun = useAssistantRunStore((state) => state.activeRun);

export const LoadingMessage: React.FC = () => {
const newDateTime = new Date();
const timestamp = Math.floor(newDateTime.getTime() / 1000);

const randomWidths = React.useMemo(() => {
return Array.from({ length: 3 }, () => getRandomNumber(maxWidth));
}, [maxWidth]);

return (
<ChatMessage variant="inbound">
<ChatBubble>
<Box display="grid" rowGap="space30">
{randomWidths.map((width) => (
<SkeletonLoader key={useUID()} height="20px" width={`${width}px`} />
))}
</Box>
</ChatBubble>
<ChatMessageMeta aria-label={`said by the assistant at ${formatTimestamp(timestamp)}`}>
<ChatMessageMetaItem>
<Logo size={20} />
PasteBot ・ {activeRun?.status ? STATUS_MAP[activeRun?.status] : "Thinking..."}
</ChatMessageMetaItem>
</ChatMessageMeta>
</ChatMessage>
<AIChatMessage variant="bot">
<AIChatMessageAuthor aria-label={`said by paste assistant at ${formatTimestamp(timestamp)}`}>
PasteBot
</AIChatMessageAuthor>
<AIChatMessageBody>
<AIChatMessageLoading />
</AIChatMessageBody>
</AIChatMessage>
);
};
/* eslint-enable camelcase */

This file was deleted.

Loading
Loading