From 5c1fd7c56fbf82bf3df4794f13737dd80caaedb8 Mon Sep 17 00:00:00 2001 From: Stephanie Yang Date: Mon, 29 Jul 2024 18:19:23 -0500 Subject: [PATCH 1/4] add messages slots --- src/SessionMessages/SessionMessagePanel.tsx | 13 ++ src/SessionMessages/SessionMessages.tsx | 80 ++++++------- src/SessionMessages/SessionMessagesHeader.tsx | 27 +++++ src/SessionMessages/index.ts | 2 + src/SessionsList/SessionListItem.tsx | 4 +- stories/Demos.stories.tsx | 113 ++++++++++++++---- 6 files changed, 170 insertions(+), 69 deletions(-) create mode 100644 src/SessionMessages/SessionMessagePanel.tsx create mode 100644 src/SessionMessages/SessionMessagesHeader.tsx diff --git a/src/SessionMessages/SessionMessagePanel.tsx b/src/SessionMessages/SessionMessagePanel.tsx new file mode 100644 index 0000000..3bef877 --- /dev/null +++ b/src/SessionMessages/SessionMessagePanel.tsx @@ -0,0 +1,13 @@ +import { FC, PropsWithChildren, useContext } from 'react'; +import { cn } from 'reablocks'; +import { SessionsContext } from '@/SessionsContext'; + +export const SessionMessagePanel: FC = ({ children }) => { + const { theme } = useContext(SessionsContext); + + return ( +
+
{children}
+
+ ); +}; diff --git a/src/SessionMessages/SessionMessages.tsx b/src/SessionMessages/SessionMessages.tsx index bd60105..26ddb5a 100644 --- a/src/SessionMessages/SessionMessages.tsx +++ b/src/SessionMessages/SessionMessages.tsx @@ -11,8 +11,9 @@ import { SessionEmpty } from './SessionEmpty'; import { SessionMessage } from './SessionMessage'; import { SessionsContext } from '@/SessionsContext'; import { Button, cn, DateFormat, Ellipsis, useInfinityList } from 'reablocks'; -import { Slot } from '@radix-ui/react-slot'; +import { Slot, Slottable } from '@radix-ui/react-slot'; import { AnimatePresence, motion } from 'framer-motion'; +import { SessionMessagesHeader } from './SessionMessagesHeader'; const containerVariants = { hidden: {}, @@ -83,7 +84,7 @@ export const SessionMessages: React.FC = ({ // Reverse the conversations so the last one is the first one const reversedConvos = useMemo( - () => [...activeSession?.conversations ?? []].reverse(), + () => [...(activeSession?.conversations ?? [])].reverse(), [activeSession] ); @@ -103,50 +104,39 @@ export const SessionMessages: React.FC = ({ } return ( -
-
-

- -

- -
-
- {hasMore && ( - - )} - - { - setIsAnimating(false); - }} - > - {convosToRender.map((conversation, index) => ( - + {hasMore && ( + + )} + + { + setIsAnimating(false); + }} + > + {convosToRender.map((conversation, index) => ( + + - - {children} - - - ))} - - -
+ {children} + + + ))} + +
); }; diff --git a/src/SessionMessages/SessionMessagesHeader.tsx b/src/SessionMessages/SessionMessagesHeader.tsx new file mode 100644 index 0000000..8dbf34c --- /dev/null +++ b/src/SessionMessages/SessionMessagesHeader.tsx @@ -0,0 +1,27 @@ +import { SessionsContext } from '@/SessionsContext'; +import { Slot } from '@radix-ui/react-slot'; +import { cn, Ellipsis, DateFormat } from 'reablocks'; +import { FC, PropsWithChildren, useContext } from 'react'; + +export const SessionMessagesHeader: FC = ({ children }) => { + const { activeSession, theme } = useContext(SessionsContext); + const Comp = children ? Slot : 'header'; + + if (!activeSession) return null; + + return ( + + {children || ( + <> +

+ +

+ + + )} +
+ ); +}; diff --git a/src/SessionMessages/index.ts b/src/SessionMessages/index.ts index 4507d40..ef4fe4c 100644 --- a/src/SessionMessages/index.ts +++ b/src/SessionMessages/index.ts @@ -1,4 +1,6 @@ export * from './SessionEmpty'; +export * from './SessionMessagesHeader'; +export * from './SessionMessagePanel'; export * from './SessionMessage'; export * from './SessionMessages'; export * from './MessageSource'; diff --git a/src/SessionsList/SessionListItem.tsx b/src/SessionsList/SessionListItem.tsx index a94ef2f..a4624cc 100644 --- a/src/SessionsList/SessionListItem.tsx +++ b/src/SessionsList/SessionListItem.tsx @@ -43,7 +43,9 @@ export const SessionListItem: FC = ({ dense disableGutters active={session.id === activeSessionId} - className={cn(theme.sessions.session.base, { [theme.sessions.session.active]: session.id === activeSessionId })} + className={cn(theme.sessions.session.base, { + [theme.sessions.session.active]: session.id === activeSessionId + })} onClick={() => selectSession?.(session.id)} end={ <> diff --git a/stories/Demos.stories.tsx b/stories/Demos.stories.tsx index b6e4714..1b7c977 100644 --- a/stories/Demos.stories.tsx +++ b/stories/Demos.stories.tsx @@ -1,4 +1,11 @@ -import { useState, useCallback, useRef, useEffect, FC } from 'react'; +import { + useState, + useCallback, + useRef, + useEffect, + FC, + useContext +} from 'react'; import OpenAI from 'openai'; import { Meta } from '@storybook/react'; import { @@ -12,10 +19,14 @@ import { SessionMessages, SessionGroups, SessionInput, - SessionListItemProps + SessionListItemProps, + SessionMessagePanel, + SessionMessagesHeader, + SessionsContext } from '../src'; import { Card, + DateFormat, Divider, IconButton, Input, @@ -310,7 +321,7 @@ export const Loading = () => {
- +
@@ -328,12 +339,17 @@ export const FileUploads = () => { { id: '1', question: 'Here are some files I uploaded', - response: 'Ive received your files. Let me know if you have any questions about them.', + response: + 'Ive received your files. Let me know if you have any questions about them.', createdAt: new Date(), updatedAt: new Date(), files: [ { name: 'document.pdf', size: 1024000, type: 'application/pdf' }, - { name: 'report.docx', size: 512000, type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' } + { + name: 'report.docx', + size: 512000, + type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + } ] } ] @@ -467,14 +483,24 @@ export const UndeleteableSessions = () => { }; export const SessionGrouping = () => { - const createSessionWithDate = (id: string, title: string, daysAgo: number): Session => ({ + const createSessionWithDate = ( + id: string, + title: string, + daysAgo: number + ): Session => ({ id, title, createdAt: subDays(new Date(), daysAgo), updatedAt: subDays(new Date(), daysAgo), conversations: [ - { id: `${id}-1`, question: 'Sample question', response: 'Sample response', createdAt: subDays(new Date(), daysAgo), updatedAt: subDays(new Date(), daysAgo) }, - ], + { + id: `${id}-1`, + question: 'Sample question', + response: 'Sample response', + createdAt: subDays(new Date(), daysAgo), + updatedAt: subDays(new Date(), daysAgo) + } + ] }); const sessionsWithVariousDates: Session[] = [ @@ -487,11 +513,23 @@ export const SessionGrouping = () => { createSessionWithDate('6', 'Two Months Ago Session', 65), createSessionWithDate('7', 'Six Months Ago Session', 180), createSessionWithDate('8', 'Last Year Session', 370), - createSessionWithDate('9', 'Two Years Ago Session', 740), + createSessionWithDate('9', 'Two Years Ago Session', 740) ]; return ( -
+
{ ); }; -const CustomSessionMessage: FC = ({ - question, - response -}) => ( -
+const CustomMessagesHeader: FC = () => { + const { activeSession } = useContext(SessionsContext); + + return ( +
+
+ +
+

{activeSession?.title}

+
+ ); +}; + +const CustomSessionMessage: FC = ({ question, response }) => ( +
This is my question: {question} @@ -1060,12 +1108,17 @@ const CustomSessionMessage: FC = ({
); -const CustomSessionListItem: FC = ({ session }) => { +const CustomSessionListItem: FC = ({ + session, + children, + ...rest +}) => { const [open, setOpen] = useState(false); const btnRef = useRef(null); return ( <> = ({ session }) => { > {session.title} - setOpen(false)} reference={btnRef}> + setOpen(false)} + reference={btnRef} + appendToBody={false} + > alert('rename')}>Rename @@ -1129,12 +1187,15 @@ export const CustomComponents = () => { } -
+ + + + -
+
); @@ -1174,7 +1235,9 @@ export const EmptyState = () => { ))} {groups.length === 0 && (
-

No sessions yet. Start a new session!

+

+ No sessions yet. Start a new session! +

)} @@ -1185,7 +1248,9 @@ export const EmptyState = () => { -

No messages yet. Start a new conversation!

+

+ No messages yet. Start a new conversation! +

} /> @@ -1220,11 +1285,13 @@ These activities increase the concentration of greenhouse gases in the atmospher sources: [ { title: 'NASA: Causes of Climate Change', - image: 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e5/NASA_logo.svg/1224px-NASA_logo.svg.png', + image: + 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e5/NASA_logo.svg/1224px-NASA_logo.svg.png', url: 'https://climate.nasa.gov/causes/' }, { - title: 'IPCC: Climate Change 2021: The Physical Science Basis and Global Warming Is the Last War We will Fight', + title: + 'IPCC: Climate Change 2021: The Physical Science Basis and Global Warming Is the Last War We will Fight', url: 'https://www.ipcc.ch/report/ar6/wg1/' } ] From 2d2611c031b8a5375f9d383eca33c12d4b1d597a Mon Sep 17 00:00:00 2001 From: Stephanie Yang Date: Mon, 29 Jul 2024 18:41:31 -0500 Subject: [PATCH 2/4] update session message panel to include default children if no children passed --- src/SessionMessages/SessionMessagePanel.tsx | 13 +++- stories/Demos.stories.tsx | 75 +++++---------------- 2 files changed, 29 insertions(+), 59 deletions(-) diff --git a/src/SessionMessages/SessionMessagePanel.tsx b/src/SessionMessages/SessionMessagePanel.tsx index 3bef877..75ad358 100644 --- a/src/SessionMessages/SessionMessagePanel.tsx +++ b/src/SessionMessages/SessionMessagePanel.tsx @@ -1,13 +1,24 @@ import { FC, PropsWithChildren, useContext } from 'react'; import { cn } from 'reablocks'; import { SessionsContext } from '@/SessionsContext'; +import { SessionMessagesHeader } from './SessionMessagesHeader'; +import { SessionMessages } from './SessionMessages'; +import { SessionInput } from '@/SessionInput'; export const SessionMessagePanel: FC = ({ children }) => { const { theme } = useContext(SessionsContext); return (
-
{children}
+
+ {children ?? ( + <> + + + + + )} +
); }; diff --git a/stories/Demos.stories.tsx b/stories/Demos.stories.tsx index 1b7c977..3e7fee1 100644 --- a/stories/Demos.stories.tsx +++ b/stories/Demos.stories.tsx @@ -123,10 +123,7 @@ export const Console = () => { } -
- - -
+
); @@ -205,10 +202,7 @@ export const Embeds = () => { } -
- - -
+ ); @@ -249,10 +243,7 @@ export const DefaultSession = () => { } -
- - -
+ ); @@ -319,10 +310,7 @@ export const Loading = () => { } -
- - -
+ ); @@ -390,10 +378,11 @@ export const FileUploads = () => { } -
+ + -
+
); @@ -434,10 +423,7 @@ export const DefaultInputValue = () => { } -
- - -
+ ); @@ -473,10 +459,7 @@ export const UndeleteableSessions = () => { } -
- - -
+ ); @@ -550,10 +533,7 @@ export const SessionGrouping = () => { } -
- - -
+ ); @@ -609,10 +589,7 @@ export const HundredSessions = () => { } -
- - -
+ ); @@ -672,10 +649,7 @@ export const HundredConversations = () => { } -
- - -
+ ); @@ -727,10 +701,7 @@ export const LongSessionNames = () => { } -
- - -
+ ); @@ -848,10 +819,7 @@ export const MarkdownShowcase = () => { } -
- - -
+ ); @@ -927,10 +895,7 @@ export const CVEExample = () => { } -
- - -
+ ); @@ -1075,10 +1040,7 @@ export const OpenAIIntegration = () => { } -
- - -
+ @@ -1333,10 +1295,7 @@ These activities increase the concentration of greenhouse gases in the atmospher } -
- - -
+ ); From bc9a667b9e4253a1c684d9d82306140c351f984b Mon Sep 17 00:00:00 2001 From: Stephanie Yang Date: Mon, 29 Jul 2024 21:38:56 -0500 Subject: [PATCH 3/4] make message slots for question, response, files and sources --- src/SessionMessages/MessageActions.tsx | 159 +++++++++ src/SessionMessages/MessageFiles.tsx | 39 ++ src/SessionMessages/MessageQuestion.tsx | 38 ++ src/SessionMessages/MessageResponse.tsx | 58 +++ src/SessionMessages/MessageSources.tsx | 43 +++ src/SessionMessages/SessionMessage.tsx | 197 +++-------- src/SessionMessages/SessionMessagePanel.tsx | 13 +- src/SessionMessages/SessionMessages.tsx | 38 +- stories/Demos.stories.tsx | 374 +++++++++++++++----- 9 files changed, 688 insertions(+), 271 deletions(-) create mode 100644 src/SessionMessages/MessageActions.tsx create mode 100644 src/SessionMessages/MessageFiles.tsx create mode 100644 src/SessionMessages/MessageQuestion.tsx create mode 100644 src/SessionMessages/MessageResponse.tsx create mode 100644 src/SessionMessages/MessageSources.tsx diff --git a/src/SessionMessages/MessageActions.tsx b/src/SessionMessages/MessageActions.tsx new file mode 100644 index 0000000..1a7f0b8 --- /dev/null +++ b/src/SessionMessages/MessageActions.tsx @@ -0,0 +1,159 @@ +import { SessionsContext } from '@/SessionsContext'; +import { Slot } from '@radix-ui/react-slot'; +import { cn, IconButton } from 'reablocks'; +import { + FC, + PropsWithChildren, + ReactElement, + ReactNode, + useContext +} from 'react'; +import CopyIcon from '@/assets/copy.svg?react'; +import ThumbsDownIcon from '@/assets/thumbs-down.svg?react'; +import ThumbUpIcon from '@/assets/thumbs-up.svg?react'; +import RefreshIcon from '@/assets/refresh.svg?react'; + +export interface MessageActionsProps extends PropsWithChildren { + /** + * Question to be copied + */ + question: string; + + /** + * Response to be copied + */ + response?: string; + + /** + * Icon to show for copy. + */ + copyIcon?: ReactElement; + + /** + * Icon to show for thumbs up. + */ + thumbsUpIcon?: ReactElement; + + /** + * Icon to show for thumbs down. + */ + thumbsDownIcon?: ReactElement; + + /** + * Icon to show for refresh. + */ + refreshIcon?: ReactElement; + + /** + * Callback function to handle copying. + */ + onCopy?: () => void; + + /** + * Callback function to handle upvoting. + */ + onUpvote?: () => void; + + /** + * Callback function to handle downvoting. + */ + onDownvote?: () => void; + + /** + * Callback function to handle refreshing. + */ + onRefresh?: () => void; + + /** + * Children to render as MessageAction slot. + */ + children?: ReactNode; +} + +export const MessageActions: FC = ({ + children, + ...props +}) => { + const { theme } = useContext(SessionsContext); + const { + question, + response, + copyIcon = , + thumbsUpIcon = , + thumbsDownIcon = , + refreshIcon = , + onCopy, + onUpvote, + onDownvote, + onRefresh + } = props; + const Comp = children ? Slot : 'div'; + + const handleCopy = (text: string) => { + navigator.clipboard + .writeText(text) + .then(() => { + console.log('Text copied to clipboard'); + }) + .catch(err => { + console.error('Could not copy text: ', err); + }); + }; + + return ( + (copyIcon || thumbsDownIcon || thumbsUpIcon || refreshIcon) && ( + + {children || ( + <> + {copyIcon && ( + handleCopy(`${question}\n${response}`) + } + > + {copyIcon} + + )} + {thumbsUpIcon && ( + + {thumbsUpIcon} + + )} + {thumbsDownIcon && ( + + {thumbsDownIcon} + + )} + {refreshIcon && ( + + {refreshIcon} + + )} + + )} + + ) + ); +}; diff --git a/src/SessionMessages/MessageFiles.tsx b/src/SessionMessages/MessageFiles.tsx new file mode 100644 index 0000000..3be27ac --- /dev/null +++ b/src/SessionMessages/MessageFiles.tsx @@ -0,0 +1,39 @@ +import { SessionsContext } from '@/SessionsContext'; +import { ConversationFile } from '@/types'; +import { cn } from 'reablocks'; +import { FC, PropsWithChildren, ReactNode, useContext } from 'react'; +import { MessageFile } from './MessageFile'; +import { Slot } from '@radix-ui/react-slot'; + +interface MessageFilesProps extends PropsWithChildren { + /** + * Files to render. + */ + files: ConversationFile[]; + + /** + * Children to render as MessageFile slot. + */ + children?: ReactNode; +} + +export const MessageFiles: FC = ({ files, children }) => { + const { theme } = useContext(SessionsContext); + const Comp = children ? Slot : MessageFile; + + if (!files || files.length === 0) { + return null; + } + + return ( + files.length > 0 && ( +
+ {files.map((file, index) => ( + + {children} + + ))} +
+ ) + ); +}; diff --git a/src/SessionMessages/MessageQuestion.tsx b/src/SessionMessages/MessageQuestion.tsx new file mode 100644 index 0000000..c2f8817 --- /dev/null +++ b/src/SessionMessages/MessageQuestion.tsx @@ -0,0 +1,38 @@ +import { SessionsContext } from '@/SessionsContext'; +import { Slot } from '@radix-ui/react-slot'; +import { cn } from 'reablocks'; +import { FC, PropsWithChildren, ReactNode, useContext } from 'react'; +import { Markdown } from '@/Markdown'; +import { PluggableList } from 'react-markdown/lib'; +import remarkGfm from 'remark-gfm'; +import remarkYoutube from 'remark-youtube'; + +export interface MessageQuestionProps extends PropsWithChildren { + /** + * Question to render. + */ + question: string; + + /** + * Children to render as MessageQuestion slot. + */ + children?: ReactNode; +} + +export const MessageQuestion: FC = ({ + question, + children +}) => { + const { theme, remarkPlugins = [remarkGfm, remarkYoutube] } = + useContext(SessionsContext); + const Comp = children ? Slot : 'div'; + return ( + + {children || ( + + {question} + + )} + + ); +}; diff --git a/src/SessionMessages/MessageResponse.tsx b/src/SessionMessages/MessageResponse.tsx new file mode 100644 index 0000000..47c040a --- /dev/null +++ b/src/SessionMessages/MessageResponse.tsx @@ -0,0 +1,58 @@ +import { SessionsContext } from '@/SessionsContext'; +import { Slot } from '@radix-ui/react-slot'; +import { motion } from 'framer-motion'; +import { cn } from 'reablocks'; +import { FC, ReactNode, useContext } from 'react'; +import { Markdown } from '@/Markdown'; +import { PluggableList } from 'react-markdown/lib'; +import remarkGfm from 'remark-gfm'; +import remarkYoutube from 'remark-youtube'; + +export interface MessageResponseProps { + /** + * Response to render. + */ + response: string; + + /** + * Whether the response is loading. + */ + isLoading?: boolean; + + /** + * Children to render as MessageResponse slot. + */ + children?: ReactNode; +} + +export const MessageResponse: FC = ({ + response, + isLoading, + children +}) => { + const { theme, remarkPlugins = [remarkGfm, remarkYoutube] } = + useContext(SessionsContext); + const Comp = children ? Slot : 'div'; + return ( + + {children || ( + <> + + {response} + + {isLoading && ( + + )} + + )} + + ); +}; diff --git a/src/SessionMessages/MessageSources.tsx b/src/SessionMessages/MessageSources.tsx new file mode 100644 index 0000000..21c56c0 --- /dev/null +++ b/src/SessionMessages/MessageSources.tsx @@ -0,0 +1,43 @@ +import { SessionsContext } from '@/SessionsContext'; +import { ConversationSource } from '@/types'; +import { cn } from 'reablocks'; +import { FC, PropsWithChildren, ReactNode, useContext } from 'react'; +import { Slot } from '@radix-ui/react-slot'; +import { MessageSource } from './MessageSource'; + +interface MessageSourcesProps extends PropsWithChildren { + /** + * Sources to render. + */ + sources: ConversationSource[]; + + /** + * Children to render as MessageSource slot. + */ + children?: ReactNode; +} + +export const MessageSources: FC = ({ + sources, + children +}) => { + const { theme } = useContext(SessionsContext); + const Comp = children ? Slot : MessageSource; + + if (!sources || sources.length === 0) { + return null; + } + + return ( + sources && + sources.length > 0 && ( +
+ {sources.map((source, index) => ( + + {children} + + ))} +
+ ) + ); +}; diff --git a/src/SessionMessages/SessionMessage.tsx b/src/SessionMessages/SessionMessage.tsx index 81125c8..ed99f55 100644 --- a/src/SessionMessages/SessionMessage.tsx +++ b/src/SessionMessages/SessionMessage.tsx @@ -1,169 +1,74 @@ -import { FC, ReactElement, useContext } from 'react'; +import { FC, PropsWithChildren, useContext } from 'react'; import { SessionsContext } from '@/SessionsContext'; -import { Card, IconButton, cn } from 'reablocks'; -import remarkGfm from 'remark-gfm'; +import { Card, cn } from 'reablocks'; import CopyIcon from '@/assets/copy.svg?react'; import ThumbsDownIcon from '@/assets/thumbs-down.svg?react'; import ThumbUpIcon from '@/assets/thumbs-up.svg?react'; import RefreshIcon from '@/assets/refresh.svg?react'; -import { PluggableList } from 'react-markdown/lib'; -import { Markdown } from '@/Markdown'; -import remarkYoutube from 'remark-youtube'; import { Conversation } from '@/types'; -import { MessageSource } from './MessageSource'; -import { MessageFile } from './MessageFile'; import { motion } from 'framer-motion'; +import { MessageQuestion } from './MessageQuestion'; +import { MessageResponse } from './MessageResponse'; +import { MessageFiles } from './MessageFiles'; +import { MessageSources } from './MessageSources'; +import { MessageActions } from './MessageActions'; -export interface SessionMessageProps extends Conversation { - /** - * Icon to show for copy. - */ - copyIcon?: ReactElement; - - /** - * Icon to show for thumbs up. - */ - thumbsUpIcon?: ReactElement; - - /** - * Icon to show for thumbs down. - */ - thumbsDownIcon?: ReactElement; - - /** - * Icon to show for refresh. - */ - refreshIcon?: ReactElement; - - /** - * Whether to display a loading state. - */ - isLoading?: boolean; - - /** - * Callback function to handle upvoting. - */ - onUpvote?: () => void; +const messageVariants = { + hidden: { + opacity: 0, + y: 20 + }, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.4 + } + } +}; +interface SessionMessageProps extends PropsWithChildren { /** - * Callback function to handle downvoting. + * Conversation to render. */ - onDownvote?: () => void; + conversation: Conversation; /** - * Callback function to handle refreshing. + * Whether the message is the last one in the list. */ - onRefresh?: () => void; + isLast?: boolean; } export const SessionMessage: FC = ({ - question = '', - response = '', - copyIcon = , - thumbsUpIcon = , - thumbsDownIcon = , - refreshIcon = , - isLoading, - sources, - files, - onUpvote, - onDownvote, - onRefresh + conversation, + isLast, + children }) => { - const { theme, remarkPlugins = [remarkGfm, remarkYoutube] } = useContext(SessionsContext); - - const handleCopy = (text: string) => { - navigator.clipboard - .writeText(text) - .then(() => { - console.log('Text copied to clipboard'); - }) - .catch(err => { - console.error('Could not copy text: ', err); - }); - }; + const { theme, isLoading } = useContext(SessionsContext); return ( - - {files?.length > 0 && ( -
- {files.map((file, index) => ( - - ))} -
- )} -
- - {question} - -
-
- - {response} - - {isLoading && ( - + + + {children || ( + <> + + + + + } + thumbsUpIcon={} + thumbsDownIcon={} + refreshIcon={} + /> + )} -
- {sources && sources.length > 0 && ( -
- {sources.map((source, index) => ( - - ))} -
- )} - {(copyIcon || thumbsDownIcon || thumbsUpIcon || refreshIcon) && ( -
- {copyIcon && ( - handleCopy(`${question}\n${response}`)} - > - {copyIcon} - - )} - {thumbsUpIcon && ( - - {thumbsUpIcon} - - )} - {thumbsDownIcon && ( - - {thumbsDownIcon} - - )} - {refreshIcon && ( - - {refreshIcon} - - )} -
- )} -
+ + ); }; diff --git a/src/SessionMessages/SessionMessagePanel.tsx b/src/SessionMessages/SessionMessagePanel.tsx index 75ad358..3bef877 100644 --- a/src/SessionMessages/SessionMessagePanel.tsx +++ b/src/SessionMessages/SessionMessagePanel.tsx @@ -1,24 +1,13 @@ import { FC, PropsWithChildren, useContext } from 'react'; import { cn } from 'reablocks'; import { SessionsContext } from '@/SessionsContext'; -import { SessionMessagesHeader } from './SessionMessagesHeader'; -import { SessionMessages } from './SessionMessages'; -import { SessionInput } from '@/SessionInput'; export const SessionMessagePanel: FC = ({ children }) => { const { theme } = useContext(SessionsContext); return (
-
- {children ?? ( - <> - - - - - )} -
+
{children}
); }; diff --git a/src/SessionMessages/SessionMessages.tsx b/src/SessionMessages/SessionMessages.tsx index 26ddb5a..8ad3f79 100644 --- a/src/SessionMessages/SessionMessages.tsx +++ b/src/SessionMessages/SessionMessages.tsx @@ -1,5 +1,4 @@ import React, { - PropsWithChildren, ReactNode, useContext, useEffect, @@ -10,10 +9,10 @@ import React, { import { SessionEmpty } from './SessionEmpty'; import { SessionMessage } from './SessionMessage'; import { SessionsContext } from '@/SessionsContext'; -import { Button, cn, DateFormat, Ellipsis, useInfinityList } from 'reablocks'; -import { Slot, Slottable } from '@radix-ui/react-slot'; +import { Button, cn, useInfinityList } from 'reablocks'; +import { Slot } from '@radix-ui/react-slot'; import { AnimatePresence, motion } from 'framer-motion'; -import { SessionMessagesHeader } from './SessionMessagesHeader'; +import { Conversation } from '@/types'; const containerVariants = { hidden: {}, @@ -24,21 +23,8 @@ const containerVariants = { } }; -const messageVariants = { - hidden: { - opacity: 0, - y: 20 - }, - visible: { - opacity: 1, - y: 0, - transition: { - duration: 0.4 - } - } -}; -interface SessionMessagesProps extends PropsWithChildren { +interface SessionMessagesProps { /** * Content to display when there are no sessions selected or a new session is started. */ @@ -53,6 +39,11 @@ interface SessionMessagesProps extends PropsWithChildren { * Text to display for the show more button. */ showMoreText?: string; + + /** + * Render function for the session messages. + */ + children?: (conversations: Conversation[]) => ReactNode; } export const SessionMessages: React.FC = ({ @@ -125,16 +116,7 @@ export const SessionMessages: React.FC = ({ setIsAnimating(false); }} > - {convosToRender.map((conversation, index) => ( - - - {children} - - - ))} + {children(convosToRender)} diff --git a/stories/Demos.stories.tsx b/stories/Demos.stories.tsx index 3e7fee1..7fbc9f8 100644 --- a/stories/Demos.stories.tsx +++ b/stories/Demos.stories.tsx @@ -22,7 +22,8 @@ import { SessionListItemProps, SessionMessagePanel, SessionMessagesHeader, - SessionsContext + SessionsContext, + SessionMessage } from '../src'; import { Card, @@ -32,10 +33,23 @@ import { Input, List, ListItem, - Menu + Menu, + RefreshIcon } from 'reablocks'; import { subDays, subMinutes, subHours } from 'date-fns'; import MenuIcon from '@/assets/menu.svg?react'; +import { motion } from 'framer-motion'; +import { MessageActions } from '@/SessionMessages/MessageActions'; +import { MessageFiles } from '@/SessionMessages/MessageFiles'; +import { + MessageQuestion, + MessageQuestionProps +} from '@/SessionMessages/MessageQuestion'; +import { + MessageResponse, + MessageResponseProps +} from '@/SessionMessages/MessageResponse'; +import { MessageSources } from '@/SessionMessages/MessageSources'; export default { title: 'Demos', @@ -123,7 +137,17 @@ export const Console = () => { } - + + + + {conversations => + conversations.map(conversation => ( + + )) + } + + + ); @@ -202,7 +226,17 @@ export const Embeds = () => { } - + + + + {conversations => + conversations.map(conversation => ( + + )) + } + + + ); @@ -243,7 +277,18 @@ export const DefaultSession = () => { } - + + + + + {conversations => + conversations.map(conversation => ( + + )) + } + + + ); @@ -310,40 +355,54 @@ export const Loading = () => { } - + + + + + {conversations => + conversations.map((conversation, index) => ( + + )) + } + + + ); }; -export const FileUploads = () => { - const sessionsWithFiles: Session[] = [ - { - id: '1', - title: 'Session with Files', - createdAt: new Date(), - updatedAt: new Date(), - conversations: [ - { - id: '1', - question: 'Here are some files I uploaded', - response: - 'Ive received your files. Let me know if you have any questions about them.', - createdAt: new Date(), - updatedAt: new Date(), - files: [ - { name: 'document.pdf', size: 1024000, type: 'application/pdf' }, - { - name: 'report.docx', - size: 512000, - type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' - } - ] - } - ] - } - ]; +const sessionsWithFiles: Session[] = [ + { + id: 'session-files', + title: 'Session with Files', + createdAt: new Date(), + updatedAt: new Date(), + conversations: [ + { + id: '1', + question: 'Here are some files I uploaded', + response: + 'Ive received your files. Let me know if you have any questions about them.', + createdAt: new Date(), + updatedAt: new Date(), + files: [ + { name: 'document.pdf', size: 1024000, type: 'application/pdf' }, + { + name: 'report.docx', + size: 512000, + type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + } + ] + } + ] + } +]; +export const FileUploads = () => { return (
{ alert('delete!')} > @@ -380,7 +439,13 @@ export const FileUploads = () => { - + + {conversations => + conversations.map(conversation => ( + + )) + } + @@ -423,7 +488,18 @@ export const DefaultInputValue = () => { } - + + + + + {conversations => + conversations.map(conversation => ( + + )) + } + + +
); @@ -459,7 +535,18 @@ export const UndeleteableSessions = () => { } - + + + + + {conversations => + conversations.map(conversation => ( + + )) + } + + + ); @@ -533,7 +620,18 @@ export const SessionGrouping = () => { } - + + + + + {conversations => + conversations.map(conversation => ( + + )) + } + + + ); @@ -589,7 +687,18 @@ export const HundredSessions = () => { } - + + + + + {conversations => + conversations.map(conversation => ( + + )) + } + + + ); @@ -649,7 +758,18 @@ export const HundredConversations = () => { } - + + + + + {conversations => + conversations.map(conversation => ( + + )) + } + + + ); @@ -701,7 +821,18 @@ export const LongSessionNames = () => { } - + + + + + {conversations => + conversations.map(conversation => ( + + )) + } + + + ); @@ -819,7 +950,18 @@ export const MarkdownShowcase = () => { } - + + + + + {conversations => + conversations.map(conversation => ( + + )) + } + + + ); @@ -895,7 +1037,18 @@ export const CVEExample = () => { } - + + + + + {conversations => + conversations.map(conversation => ( + + )) + } + + + ); @@ -1040,7 +1193,18 @@ export const OpenAIIntegration = () => { } - + + + + + {conversations => + conversations.map(conversation => ( + + )) + } + + + @@ -1060,14 +1224,16 @@ const CustomMessagesHeader: FC = () => { ); }; -const CustomSessionMessage: FC = ({ question, response }) => ( -
- - This is my question: {question} - -
+const CustomMessageQuestion: FC = ({ question }) => ( + + This is my question: {question} + +); + +const CustomMessageResponse: FC = ({ response }) => ( +
This is the response: {response} -
+ ); const CustomSessionListItem: FC = ({ @@ -1129,7 +1295,14 @@ export const CustomComponents = () => { borderRadius: 5 }} > - + @@ -1154,7 +1327,27 @@ export const CustomComponents = () => { - + {conversations => + conversations.map((conversation, index) => ( + + + + + + + + + + + + )) + }
@@ -1223,18 +1416,17 @@ export const EmptyState = () => { ); }; -export const ConversationSources = () => { - const sessionWithSources: Session[] = [ - { - id: 'session-sources', - title: 'Session with Sources', - createdAt: subHours(new Date(), 1), - updatedAt: new Date(), - conversations: [ - { - id: 'conversation-1', - question: 'What are the main causes of climate change?', - response: `Climate change is primarily caused by human activities that release greenhouse gases into the atmosphere. The main causes include: +const sessionWithSources: Session[] = [ + { + id: 'session-sources', + title: 'Session with Sources', + createdAt: subHours(new Date(), 1), + updatedAt: new Date(), + conversations: [ + { + id: 'conversation-1', + question: 'What are the main causes of climate change?', + response: `Climate change is primarily caused by human activities that release greenhouse gases into the atmosphere. The main causes include: 1. Burning of fossil fuels (coal, oil, and natural gas) 2. Deforestation and land-use changes @@ -1242,26 +1434,27 @@ export const ConversationSources = () => { 4. Agriculture and livestock farming These activities increase the concentration of greenhouse gases in the atmosphere, leading to the greenhouse effect and global warming.`, - createdAt: new Date(), - updatedAt: new Date(), - sources: [ - { - title: 'NASA: Causes of Climate Change', - image: - 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e5/NASA_logo.svg/1224px-NASA_logo.svg.png', - url: 'https://climate.nasa.gov/causes/' - }, - { - title: - 'IPCC: Climate Change 2021: The Physical Science Basis and Global Warming Is the Last War We will Fight', - url: 'https://www.ipcc.ch/report/ar6/wg1/' - } - ] - } - ] - } - ]; + createdAt: new Date(), + updatedAt: new Date(), + sources: [ + { + title: 'NASA: Causes of Climate Change', + image: + 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e5/NASA_logo.svg/1224px-NASA_logo.svg.png', + url: 'https://climate.nasa.gov/causes/' + }, + { + title: + 'IPCC: Climate Change 2021: The Physical Science Basis and Global Warming Is the Last War We will Fight', + url: 'https://www.ipcc.ch/report/ar6/wg1/' + } + ] + } + ] + } +]; +export const ConversationSources = () => { return (
- + + + + + {conversations => + conversations.map(conversation => ( + + )) + } + + +
); From 05c3cf18d846d65af2f396d52bfd4f4daefa1933 Mon Sep 17 00:00:00 2001 From: Stephanie Yang Date: Mon, 29 Jul 2024 21:57:49 -0500 Subject: [PATCH 4/4] more custom story updates --- src/SessionMessages/MessageFile.tsx | 6 ++-- src/SessionMessages/MessageSource.tsx | 2 +- stories/Demos.stories.tsx | 49 +++++++++++++++++++++++---- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/SessionMessages/MessageFile.tsx b/src/SessionMessages/MessageFile.tsx index 2fd69ad..5e5610a 100644 --- a/src/SessionMessages/MessageFile.tsx +++ b/src/SessionMessages/MessageFile.tsx @@ -4,11 +4,11 @@ import { SessionsContext } from '@/SessionsContext'; import { Ellipsis, cn } from 'reablocks'; import FileIcon from '@/assets/file.svg?react'; -interface MessageFileProps extends ConversationFile { - /** +export interface MessageFileProps extends ConversationFile { + /** * Icon to show for delete. */ - fileIcon?: ReactElement; + fileIcon?: ReactElement; /** * Limit for the name. diff --git a/src/SessionMessages/MessageSource.tsx b/src/SessionMessages/MessageSource.tsx index 881a1ca..07cb5b7 100644 --- a/src/SessionMessages/MessageSource.tsx +++ b/src/SessionMessages/MessageSource.tsx @@ -3,7 +3,7 @@ import { ConversationSource } from '@/types'; import { SessionsContext } from '@/SessionsContext'; import { Ellipsis, cn } from 'reablocks'; -interface MessageSourceProps extends ConversationSource { +export interface MessageSourceProps extends ConversationSource { /** * Limit for the title. */ diff --git a/stories/Demos.stories.tsx b/stories/Demos.stories.tsx index 7fbc9f8..61899e5 100644 --- a/stories/Demos.stories.tsx +++ b/stories/Demos.stories.tsx @@ -27,6 +27,8 @@ import { } from '../src'; import { Card, + Chip, + cn, DateFormat, Divider, IconButton, @@ -50,6 +52,7 @@ import { MessageResponseProps } from '@/SessionMessages/MessageResponse'; import { MessageSources } from '@/SessionMessages/MessageSources'; +import { MessageFileProps } from '@/SessionMessages/MessageFile'; export default { title: 'Demos', @@ -1211,7 +1214,7 @@ export const OpenAIIntegration = () => { ); }; -const CustomMessagesHeader: FC = () => { +const CustomMessagesHeader: FC = () => { const { activeSession } = useContext(SessionsContext); return ( @@ -1224,18 +1227,46 @@ const CustomMessagesHeader: FC = () => { ); }; -const CustomMessageQuestion: FC = ({ question }) => ( +const CustomMessageQuestion: FC = ({ question }) => ( This is my question: {question} ); -const CustomMessageResponse: FC = ({ response }) => ( +const CustomMessageResponse: FC = ({ response }) => (
This is the response: {response}
); +const CustomMessageFile: FC = ({ name, type }) => ( + + {name || type} + +); + +const CustomMessageSource: FC = ({ title, url, image }) => { + const { theme } = useContext(SessionsContext); + return ( + alert('take me to ' + url)} + start={ + image && ( + {title} + ) + } + > + {title || url} + + ); +}; + const CustomSessionListItem: FC = ({ session, children, @@ -1334,13 +1365,17 @@ export const CustomComponents = () => { isLast={index === conversations.length - 1} > - + - + + + - + - + + +