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

Chat Only View #32

Merged
merged 6 commits into from
Aug 7, 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
14 changes: 9 additions & 5 deletions src/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { useHotkeys } from 'reakeys';
import { cn, useComponentTheme } from 'reablocks';
import { Session } from './types';
import { ChatTheme, chatTheme } from './theme';
import { ChatContext } from './ChatContext';
import { ChatContext, ChatViewType } from './ChatContext';
import { PluggableList } from 'react-markdown/lib';
import { AnimatePresence } from 'framer-motion';
import { useDimensions } from './utils/useDimensions';
Expand All @@ -28,11 +28,13 @@ export interface ChatProps extends PropsWithChildren {
className?: string;

/**
* The type of prompt to display. Companion prompts are smaller and are
* meant to be displayed alongside other content. Full prompts are larger
* and are meant to be displayed on their own.
* The type of prompt to display.
*
* - Companion: Smaller prompt screen with session lists.
* - Console: Full screen experience.
* - Chat: Only chat, no sessions.
*/
viewType?: 'companion' | 'console';
viewType?: ChatViewType;

/**
* The list of sessions to display.
Expand Down Expand Up @@ -172,6 +174,7 @@ export const Chat: FC<ChatProps> = ({
disabled,
isLoading,
isCompact,
viewType,
activeSessionId: internalActiveSessionID,
selectSession: handleSelectSession,
deleteSession: handleDeleteSession,
Expand All @@ -183,6 +186,7 @@ export const Chat: FC<ChatProps> = ({
[
isLoading,
isCompact,
viewType,
disabled,
theme,
remarkPlugins,
Expand Down
3 changes: 3 additions & 0 deletions src/ChatContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import { Session } from './types';
import { ChatTheme } from './theme';
import { PluggableList } from 'react-markdown/lib';

export type ChatViewType = 'chat' | 'companion' | 'console';

export interface ChatContextProps {
sessions: Session[];
disabled?: boolean;
activeSessionId: string | null;
theme?: ChatTheme;
isLoading?: boolean;
isCompact?: boolean;
viewType?: ChatViewType;
activeSession?: Session | null;
remarkPlugins?: PluggableList[];
selectSession?: (sessionId: string) => void;
Expand Down
8 changes: 4 additions & 4 deletions src/SessionMessages/SessionMessagePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { motion } from 'framer-motion';
import BackIcon from '@/assets/back.svg?react';

export const SessionMessagePanel: FC<PropsWithChildren> = ({ children }) => {
const { activeSessionId, theme, isCompact, selectSession } =
const { activeSessionId, theme, isCompact, selectSession, viewType } =
useContext(ChatContext);
const isVisible = isCompact && activeSessionId;
const isVisible = (isCompact && activeSessionId) || viewType === 'chat' || !isCompact;

return (
(!isCompact || isVisible) && (
isVisible && (
<motion.div
initial={{ translateX: '200%' }}
animate={{
Expand All @@ -29,7 +29,7 @@ export const SessionMessagePanel: FC<PropsWithChildren> = ({ children }) => {
})}
>
<div className={cn(theme.messages.inner)}>
{isCompact && (
{(isCompact && viewType !== 'chat') && (
<Button
variant="text"
size="small"
Expand Down
6 changes: 3 additions & 3 deletions src/SessionMessages/SessionMessages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const SessionMessages: React.FC<SessionMessagesProps> = ({
}, [activeSession, isAnimating]);

function handleShowMore() {
showNext(10);
showNext(limit);
requestAnimationFrame(() => (contentRef.current.scrollTop = 0));
}

Expand All @@ -78,7 +78,7 @@ export const SessionMessages: React.FC<SessionMessagesProps> = ({

const { data, hasMore, showNext } = useInfinityList({
items: reversedConvos,
limit
size: limit
});

// Reverse the data to the last one last now
Expand Down Expand Up @@ -110,7 +110,7 @@ export const SessionMessages: React.FC<SessionMessagesProps> = ({
initial="hidden"
animate="visible"
onAnimationComplete={() => {
setIsAnimating(false);
requestAnimationFrame(() => setIsAnimating(false));
}}
>
{children(convosToRender)}
Expand Down
4 changes: 2 additions & 2 deletions src/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export interface ChatTheme {

export const chatTheme: ChatTheme = {
base: 'dark:text-white text-gray-500',
console: 'flex w-full gap-10 h-full',
console: 'flex w-full gap-4 h-full',
companion: 'w-full h-full overflow-hidden',
empty: 'text-center flex-1',
sessions: {
Expand All @@ -110,7 +110,7 @@ export const chatTheme: ChatTheme = {
},
messages: {
base: '',
console: 'flex flex-col mr-5 flex-1 overflow-hidden',
console: 'flex flex-col mx-5 flex-1 overflow-hidden',
companion: 'flex w-full h-full',
back: 'self-start p-0 my-2',
inner: 'flex-1 h-full flex flex-col',
Expand Down
213 changes: 213 additions & 0 deletions stories/Chat.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import { Meta } from '@storybook/react';
import {
Chat,
SessionsList,
SessionsGroup,
SessionListItem,
NewSessionButton,
SessionMessages,
SessionGroups,
ChatInput,
SessionMessagePanel,
SessionMessagesHeader,
SessionMessage,
Session
} from '../src';
import {
fakeSessions,
sessionWithSources,
sessionsWithFiles
} from './examples';
import { useState } from 'react';
import Placeholder from '@/assets/placeholder.svg?react';
import PlaceholderDark from '@/assets/placeholder-dark.svg?react';

export default {
title: 'Demos/Chat',
component: Chat
} as Meta;

export const Compact = () => {
const [activeId, setActiveId] = useState<string>(fakeSessions[0].id);
const [sessions, setSessions] = useState<Session[]>([
...fakeSessions,
...sessionsWithFiles,
...sessionWithSources
]);

return (
<div
className="dark:bg-gray-950 bg-white"
style={{
width: 350,
height: 500,
padding: 20,
borderRadius: 5
}}
>
<Chat
viewType="chat"
sessions={sessions}
activeSessionId={activeId}
onNewSession={() => {
const newId = (sessions.length + 1).toLocaleString();
setSessions([
...sessions,
{
id: newId,
title: `New Session #${newId}`,
createdAt: new Date(),
updatedAt: new Date(),
conversations: []
}
]);
setActiveId(newId);
}}
onSelectSession={setActiveId}
onDeleteSession={() => alert('delete!')}
>
<SessionMessagePanel>
<SessionMessages>
{conversations =>
conversations.map((conversation, index) => (
<SessionMessage
key={conversation.id}
conversation={conversation}
isLast={index === conversations.length - 1}
/>
))
}
</SessionMessages>
<ChatInput />
</SessionMessagePanel>
</Chat>
</div>
);
};

export const FullScreen = () => {
const [activeId, setActiveId] = useState<string>(fakeSessions[0].id);
const [sessions, setSessions] = useState<Session[]>([
...fakeSessions,
...sessionsWithFiles,
...sessionWithSources
]);

return (
<div
className="dark:bg-gray-950 bg-white"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
padding: 20,
margin: 20,
borderRadius: 5
}}
>
<Chat
viewType="chat"
sessions={sessions}
activeSessionId={activeId}
onNewSession={() => {
const newId = (sessions.length + 1).toLocaleString();
setSessions([
...sessions,
{
id: newId,
title: `New Session #${newId}`,
createdAt: new Date(),
updatedAt: new Date(),
conversations: []
}
]);
setActiveId(newId);
}}
onSelectSession={setActiveId}
onDeleteSession={() => alert('delete!')}
>
<SessionMessagePanel>
<SessionMessages>
{conversations =>
conversations.map((conversation, index) => (
<SessionMessage
key={conversation.id}
conversation={conversation}
isLast={index === conversations.length - 1}
/>
))
}
</SessionMessages>
<ChatInput />
</SessionMessagePanel>
</Chat>
</div>
);
};

export const Empty = () => {
const [activeId, setActiveId] = useState<string>();
const [sessions, setSessions] = useState<Session[]>([]);

return (
<div
className="dark:bg-gray-950 bg-white"
style={{
width: 350,
height: 500,
padding: 20,
borderRadius: 5
}}
>
<Chat
viewType="chat"
sessions={sessions}
activeSessionId={activeId}
onNewSession={() => {
const newId = (sessions.length + 1).toLocaleString();
setSessions([
...sessions,
{
id: newId,
title: `New Session #${newId}`,
createdAt: new Date(),
updatedAt: new Date(),
conversations: []
}
]);
setActiveId(newId);
}}
onSelectSession={setActiveId}
onDeleteSession={() => alert('delete!')}
>
<SessionMessagePanel>
<SessionMessages
newSessionContent={
<div className="flex flex-col gap-2 items-center justify-center h-full">
<Placeholder className="h-[50%] block dark:hidden max-w-[100%]" />
<PlaceholderDark className="h-[50%] hidden dark:block max-w-[100%]" />
<p className="text-gray-500 max-w-[400px] text-center">
Welcome to Reachat, a UI library for effortlessly building and
customizing chat experiences with Tailwind.
</p>
</div>
}
>
{conversations =>
conversations.map((conversation, index) => (
<SessionMessage
key={conversation.id}
conversation={conversation}
isLast={index === conversations.length - 1}
/>
))
}
</SessionMessages>
<ChatInput />
</SessionMessagePanel>
</Chat>
</div>
);
};
Loading