-
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from reaviz/syang/message-slots
Slots for Messages and Message components
- Loading branch information
Showing
14 changed files
with
860 additions
and
337 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<MessageActionsProps> = ({ | ||
children, | ||
...props | ||
}) => { | ||
const { theme } = useContext(SessionsContext); | ||
const { | ||
question, | ||
response, | ||
copyIcon = <CopyIcon />, | ||
thumbsUpIcon = <ThumbUpIcon />, | ||
thumbsDownIcon = <ThumbsDownIcon />, | ||
refreshIcon = <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) && ( | ||
<Comp className={cn(theme.messages.message.footer.base)}> | ||
{children || ( | ||
<> | ||
{copyIcon && ( | ||
<IconButton | ||
variant="text" | ||
disablePadding | ||
title="Copy question and response" | ||
className={cn(theme.messages.message.footer.copy)} | ||
onClick={ | ||
onCopy ? onCopy : () => handleCopy(`${question}\n${response}`) | ||
} | ||
> | ||
{copyIcon} | ||
</IconButton> | ||
)} | ||
{thumbsUpIcon && ( | ||
<IconButton | ||
variant="text" | ||
disablePadding | ||
title="Upvote" | ||
className={cn(theme.messages.message.footer.upvote)} | ||
onClick={onUpvote} | ||
> | ||
{thumbsUpIcon} | ||
</IconButton> | ||
)} | ||
{thumbsDownIcon && ( | ||
<IconButton | ||
variant="text" | ||
disablePadding | ||
title="Downvote" | ||
className={cn(theme.messages.message.footer.downvote)} | ||
onClick={onDownvote} | ||
> | ||
{thumbsDownIcon} | ||
</IconButton> | ||
)} | ||
{refreshIcon && ( | ||
<IconButton | ||
variant="text" | ||
disablePadding | ||
title="Refresh" | ||
className={cn(theme.messages.message.footer.refresh)} | ||
onClick={onRefresh} | ||
> | ||
{refreshIcon} | ||
</IconButton> | ||
)} | ||
</> | ||
)} | ||
</Comp> | ||
) | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<MessageFilesProps> = ({ files, children }) => { | ||
const { theme } = useContext(SessionsContext); | ||
const Comp = children ? Slot : MessageFile; | ||
|
||
if (!files || files.length === 0) { | ||
return null; | ||
} | ||
|
||
return ( | ||
files.length > 0 && ( | ||
<div className={cn(theme.messages.message.files.base)}> | ||
{files.map((file, index) => ( | ||
<Comp key={index} {...file}> | ||
{children} | ||
</Comp> | ||
))} | ||
</div> | ||
) | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<MessageQuestionProps> = ({ | ||
question, | ||
children | ||
}) => { | ||
const { theme, remarkPlugins = [remarkGfm, remarkYoutube] } = | ||
useContext(SessionsContext); | ||
const Comp = children ? Slot : 'div'; | ||
return ( | ||
<Comp className={cn(theme.messages.message.question)}> | ||
{children || ( | ||
<Markdown remarkPlugins={remarkPlugins as PluggableList[]}> | ||
{question} | ||
</Markdown> | ||
)} | ||
</Comp> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<MessageResponseProps> = ({ | ||
response, | ||
isLoading, | ||
children | ||
}) => { | ||
const { theme, remarkPlugins = [remarkGfm, remarkYoutube] } = | ||
useContext(SessionsContext); | ||
const Comp = children ? Slot : 'div'; | ||
return ( | ||
<Comp className={cn(theme.messages.message.response)}> | ||
{children || ( | ||
<> | ||
<Markdown remarkPlugins={remarkPlugins as PluggableList[]}> | ||
{response} | ||
</Markdown> | ||
{isLoading && ( | ||
<motion.div | ||
className={cn(theme.messages.message.cursor)} | ||
animate={{ opacity: [1, 0] }} | ||
transition={{ | ||
duration: 0.7, | ||
repeat: Infinity, | ||
repeatType: 'reverse' | ||
}} | ||
/> | ||
)} | ||
</> | ||
)} | ||
</Comp> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<MessageSourcesProps> = ({ | ||
sources, | ||
children | ||
}) => { | ||
const { theme } = useContext(SessionsContext); | ||
const Comp = children ? Slot : MessageSource; | ||
|
||
if (!sources || sources.length === 0) { | ||
return null; | ||
} | ||
|
||
return ( | ||
sources && | ||
sources.length > 0 && ( | ||
<div className={cn(theme.messages.message.sources.base)}> | ||
{sources.map((source, index) => ( | ||
<Comp key={index} {...source}> | ||
{children} | ||
</Comp> | ||
))} | ||
</div> | ||
) | ||
); | ||
}; |
Oops, something went wrong.