Skip to content
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

Slots #1

Merged
merged 20 commits into from
Jul 28, 2024
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,5 +114,6 @@
"hooks": {
"pre-commit": "lint-staged"
}
}
},
"packageManager": "[email protected]+sha1.8c155dc114e1689d18937974f6571e0ceee66f1d"
Copy link
Member

Choose a reason for hiding this comment

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

pnpm?

}
29 changes: 29 additions & 0 deletions src/NewSessionButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Button, cn } from 'reablocks';
import { FC, PropsWithChildren, useContext } from 'react';
import { SessionsContext } from './SessionsContext';
import { Slot } from '@radix-ui/react-slot';

interface NewSessionButtonProps extends PropsWithChildren {
asChild?: boolean;
newSessionText?: string;
}

export const NewSessionButton: FC<NewSessionButtonProps> = ({
children,
asChild,
newSessionText = 'New Session'
}) => {
const { theme, createSession } = useContext(SessionsContext);
const Comp = asChild ? Slot : Button;
return (
<Comp
fullWidth
disableMargins
color="primary"
className={cn(theme.sessions.create)}
onClick={createSession}
>
{asChild ? children : newSessionText}
</Comp>
);
};
14 changes: 14 additions & 0 deletions src/SessionEmpty.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { FC, ReactNode, useContext } from 'react';
import { SessionsContext } from './SessionsContext';
import { cn } from 'reablocks';

interface SessionEmptyProps {
newSessionContent?: string | ReactNode;
}

export const SessionEmpty: FC<SessionEmptyProps> = ({
newSessionContent = ''
}) => {
const { theme } = useContext(SessionsContext);
return <div className={cn(theme.empty)}>{newSessionContent}</div>;
};
21 changes: 12 additions & 9 deletions src/SessionInput.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { FC, useState, KeyboardEvent, ReactElement, useRef, ChangeEvent } from 'react';
import {
FC,
useState,
KeyboardEvent,
ReactElement,
useRef,
ChangeEvent,
useContext
} from 'react';
import { Button, Textarea, cn } from 'reablocks';
import SendIcon from '@/assets/send.svg?react';
import StopIcon from '@/assets/stop.svg?react';
import AttachIcon from '@/assets/paperclip.svg?react';
import { ChatTheme } from './theme';
import { SessionsContext } from './SessionsContext';

interface SessionInputProps {
/**
* Default value for the input field.
*/
inputDefaultValue?: string;

/**
* Theme to use for the input.
*/
theme?: ChatTheme;

/**
* Allowed file types for upload.
*/
Expand Down Expand Up @@ -63,7 +66,6 @@ interface SessionInputProps {
}

export const SessionInput: FC<SessionInputProps> = ({
theme,
allowedFiles,
onSendMessage,
isLoading,
Expand All @@ -75,6 +77,7 @@ export const SessionInput: FC<SessionInputProps> = ({
stopIcon = <StopIcon />,
attachIcon = <AttachIcon />
}) => {
const { theme } = useContext(SessionsContext);
const [message, setMessage] = useState<string>('');
const fileInputRef = useRef<HTMLInputElement>(null);

Expand Down Expand Up @@ -106,7 +109,7 @@ export const SessionInput: FC<SessionInputProps> = ({
minRows={1}
autoFocus
value={message}
onChange={(e) => setMessage(e.target.value)}
onChange={e => setMessage(e.target.value)}
defaultValue={inputDefaultValue}
onKeyPress={handleKeyPress}
placeholder={inputPlaceholder}
Expand Down
94 changes: 41 additions & 53 deletions src/SessionListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,72 +1,60 @@
import { FC, ReactElement } from 'react';
import { FC, ReactElement, ReactNode, useContext } from 'react';
import { ListItem, IconButton, cn, Ellipsis } from 'reablocks';
import { Session } from './types';
import TrashIcon from '@/assets/trash.svg?react';
import { ChatTheme } from './theme';
import { SessionsContext } from './SessionsContext';
import { Slot } from '@radix-ui/react-slot';

interface SessionListItemProps {
/**
* Theme to use for the session list item.
*/
theme?: ChatTheme;
asChild?: boolean;
children?: ReactNode;

/**
* Session to display.
*/
session: Session;

/**
* Indicates whether this session is currently active
*/
isActive: boolean;

/**
* Icon to show for delete.
*/
deleteIcon?: ReactElement;

/**
* Callback function to handle session selection, receives the session ID
*/
onSelectSession?: (sessionId: string) => void;

/**
* Callback function to handle session deletion, receives the session ID
*/
onDeleteSession?: (sessionId: string) => void;
}

export const SessionListItem: FC<SessionListItemProps> = ({
children,
asChild,
session,
isActive,
theme,
onSelectSession,
onDeleteSession,
deleteIcon = <TrashIcon className={cn(theme.sessions.session.delete)} />
}) => (
<ListItem
dense
disableGutters
active={isActive}
className={cn(theme.sessions.session.base)}
onClick={() => onSelectSession?.(session.id)}
end={
<>
{onDeleteSession && (
<IconButton
size="small"
variant="text"
onClick={(e) => {
e.stopPropagation();
onDeleteSession(session.id);
}}
>
{deleteIcon}
</IconButton>
)}
</>
}
>
<Ellipsis value={session.title} limit={100} />
</ListItem>
);
deleteIcon = <TrashIcon />
}) => {
const { activeSessionId, selectSession, deleteSession, theme } =
useContext(SessionsContext);
const Comp = asChild ? Slot : ListItem;
return (
<Comp
dense
disableGutters
active={session.id === activeSessionId}
className={cn(theme.sessions.session.base)}
onClick={() => selectSession?.(session.id)}
end={
<>
{deleteSession && (
<IconButton
size="small"
variant="text"
onClick={e => {
e.stopPropagation();
deleteSession(session.id);
}}
className={cn(theme.sessions.session.delete)}
>
{deleteIcon}
</IconButton>
)}
</>
}
>
{asChild ? children : <Ellipsis value={session.title} limit={100} />}
</Comp>
);
};
34 changes: 16 additions & 18 deletions src/SessionMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC, ReactElement, useContext } from 'react';
import { FC, PropsWithChildren, ReactElement, useContext } from 'react';
import { SessionsContext } from './SessionsContext';
import { IconButton, cn } from 'reablocks';
import remarkGfm from 'remark-gfm';
Expand All @@ -9,16 +9,16 @@ import RefreshIcon from '@/assets/refresh.svg?react';
import { PluggableList } from 'react-markdown/lib';
import { Markdown } from './Markdown';

interface SessionMessageProps {
export interface SessionMessageProps {
/**
* Question to display.
*/
question: string;
question?: string;

/**
* Response to display.
*/
response: string;
response?: string;

/**
* Icon to show for copy.
Expand Down Expand Up @@ -57,8 +57,8 @@ interface SessionMessageProps {
}

export const SessionMessage: FC<SessionMessageProps> = ({
question,
response,
question = '',
response = '',
copyIcon = <CopyIcon />,
thumbsUpIcon = <ThumbUpIcon />,
thumbsDownIcon = <ThumbsDownIcon />,
Expand All @@ -67,23 +67,21 @@ export const SessionMessage: FC<SessionMessageProps> = ({
onDownvote,
onRefresh
}) => {
const {
theme,
remarkPlugins = [remarkGfm]
} = useContext(SessionsContext);
const { theme, remarkPlugins = [remarkGfm] } = 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);
});
navigator.clipboard
.writeText(text)
.then(() => {
console.log('Text copied to clipboard');
})
.catch(err => {
console.error('Could not copy text: ', err);
});
};

return (
<div
className={cn(theme.messages.message.base)}
>
<div className={cn(theme.messages.message.base)}>
<div className={cn(theme.messages.message.question)}>
<Markdown remarkPlugins={remarkPlugins as PluggableList[]}>
{question}
Expand Down
Loading
Loading