Skip to content

Commit

Permalink
Merge pull request #3 from reaviz/syang/message-slots
Browse files Browse the repository at this point in the history
Slots for Messages and Message components
  • Loading branch information
amcdnl authored Jul 30, 2024
2 parents 7ec5d0b + 05c3cf1 commit 915d2de
Show file tree
Hide file tree
Showing 14 changed files with 860 additions and 337 deletions.
159 changes: 159 additions & 0 deletions src/SessionMessages/MessageActions.tsx
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>
)
);
};
6 changes: 3 additions & 3 deletions src/SessionMessages/MessageFile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
39 changes: 39 additions & 0 deletions src/SessionMessages/MessageFiles.tsx
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>
)
);
};
38 changes: 38 additions & 0 deletions src/SessionMessages/MessageQuestion.tsx
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>
);
};
58 changes: 58 additions & 0 deletions src/SessionMessages/MessageResponse.tsx
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>
);
};
2 changes: 1 addition & 1 deletion src/SessionMessages/MessageSource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
43 changes: 43 additions & 0 deletions src/SessionMessages/MessageSources.tsx
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>
)
);
};
Loading

0 comments on commit 915d2de

Please sign in to comment.