Skip to content

Commit

Permalink
upgrade, abstraction, cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
dissorial committed Jun 2, 2023
1 parent ec2104d commit e14f30e
Show file tree
Hide file tree
Showing 19 changed files with 535 additions and 512 deletions.
36 changes: 36 additions & 0 deletions components/buttons/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { ReactElement } from 'react';
import { Cog6ToothIcon, PlusCircleIcon } from '@heroicons/react/20/solid';

interface ButtonProps {
buttonType?: 'primary' | 'secondary';
onClick?: () => void;
buttonText: string;
icon?: any;
}

const Button = ({
buttonType,
onClick,
buttonText,
icon: Icon,
}: ButtonProps): ReactElement => {
let buttonClassName =
'inline-flex items-center gap-x-2 rounded-md px-3.5 py-2.5 text-sm font-semibold shadow-sm w-full mb-8';

if (buttonType === 'primary') {
buttonClassName +=
' bg-indigo-600 text-white hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600';
} else if (buttonType === 'secondary') {
buttonClassName +=
' bg-gray-800 text-gray-400 hover:bg-gray-800 hover:text-white';
}

return (
<button type="button" className={buttonClassName} onClick={onClick}>
{Icon && <Icon className="h-5 w-5" />}
{buttonText}
</button>
);
};

export default Button;
52 changes: 52 additions & 0 deletions components/header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import { Bars3Icon } from '@heroicons/react/24/outline';
import ProfileDropdown from '../other/ProfileDropdown';
import { signOut } from 'next-auth/react';

interface HeaderProps {
setSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>;
userImage: string;
userName: string;
}

const Header: React.FC<HeaderProps> = ({
setSidebarOpen,
userImage,
userName,
}) => {
const defaultUserImage = '/images/user.png';
const defaultUserName = 'User';

return (
<div className="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-x-4 border-b border-gray-800 bg-gray-900 px-4 shadow-sm sm:gap-x-6 sm:px-6 lg:px-8">
<button
type="button"
className="-m-2.5 p-2.5 text-gray-700 lg:hidden"
onClick={() => setSidebarOpen(true)}
>
<span className="sr-only">Open sidebar</span>
<Bars3Icon className="h-6 w-6" aria-hidden="true" />
</button>

<div className="flex flex-1 gap-x-4 self-stretch lg:gap-x-6 items-center">
<span className="w-full text-center items-center rounded-md bg-blue-400/10 px-2 py-1 text-xs sm:text-sm md:text-md md:text-lg font-medium text-blue-400 ring-1 ring-inset ring-pink-blue/30">
DOC CHATBOT
</span>

<div className="flex items-center gap-x-4 lg:gap-x-6">
<div
className="hidden lg:block lg:h-6 lg:w-px lg:bg-gray-900/10"
aria-hidden="true"
/>
<ProfileDropdown
userImage={userImage || defaultUserImage}
userName={userName || defaultUserName}
signOut={signOut}
/>
</div>
</div>
</div>
);
};

export default Header;
8 changes: 4 additions & 4 deletions components/main/ChatForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const ChatForm = ({
setQuery,
}: ChatFormProps) => {
const otherRef = useRef<HTMLTextAreaElement>(null);

const adjustTextareaHeight = useCallback(() => {
if (otherRef.current) {
otherRef.current.style.height = 'auto';
Expand All @@ -45,20 +46,20 @@ const ChatForm = ({
return (
<form
onSubmit={handleSubmit}
className="items-center justify-center flex p-4 sm:px-4 sm:py-10 flex-grow bg-gradient-to-t from-gray-900 via-gray-900/70 to-gray-800/30"
className="items-center absolute bottom-0 w-full max-w-2xl mx-auto justify-center flex p-4 sm:px-4 sm:py-10"
>
<label htmlFor="userInput" className="sr-only">
Your message
</label>
<div className="flex flex-grow max-w-xl xl:max-w-2xl items-center rounded-lg bg-gray-800 border border-gray-700 shadow-xl ">
<div className="flex flex-grow items-center rounded-lg bg-gray-800 border border-gray-700 shadow-xl ">
<textarea
disabled={loading}
onKeyDown={handleEnter}
ref={otherRef}
className="block py-3 w-full text-xs sm:text-sm md:text-base rounded-lg border bg-gray-800 border-transparent placeholder-gray-400 text-white focus:outline-none resize-none whitespace-pre-wrap overflow-y-auto"
autoFocus={false}
rows={1}
maxLength={512}
maxLength={2048}
id="userInput"
name="userInput"
placeholder={
Expand All @@ -78,7 +79,6 @@ const ChatForm = ({
className="inline-flex justify-center p-2 rounded-full cursor-pointer text-blue-500 hover:text-blue-300"
>
{loading ? (
// <LoadingDots color="#ffffff" />
<></>
) : error ? (
<ExclamationCircleIcon className="h-6 w-6 text-red-500" />
Expand Down
30 changes: 30 additions & 0 deletions components/main/EmptyState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';

interface EmptyStateProps {
nameSpaceHasChats: boolean;
selectedNamespace: string;
}

const EmptyState: React.FC<EmptyStateProps> = ({
nameSpaceHasChats,
selectedNamespace,
}) => {
const noChatsMessage = !nameSpaceHasChats
? 'You have no chats in this namespace. Create a new chat to get started.'
: 'You have no chats. Create a new chat to get started.';

const selectNamespaceMessage = 'Select a namespace to display chats';

return (
<div className="flex flex-col items-center justify-center align-center h-screen px-4">
<h1 className="text-xl md:text-3xl text-center font-semibold text-gray-100">
Welcome to doc-chatbot
</h1>
<p className="text-md md:text-xl text-center text-gray-100 mt-4">
{selectedNamespace ? noChatsMessage : selectNamespaceMessage}
</p>
</div>
);
};

export default EmptyState;
59 changes: 14 additions & 45 deletions components/main/MessageList.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import React from 'react';
import ReactMarkdown from 'react-markdown';
import LoadingDots from '@/components/other/LoadingDots';
import { CodeBracketSquareIcon } from '@heroicons/react/24/solid';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@/components/other/Accordion';
import Image from 'next/image';
import remarkGfm from 'remark-gfm';
import { Message } from '@/types';

Expand All @@ -24,53 +22,31 @@ function MessageList({
messages,
loading,
messageListRef,
userImage,
userName,
}: MessageListProps) {
return (
<>
<div className="overflow-y-auto">
<div ref={messageListRef}>
{messages.map((message, index) => {
const isApiMessage = message.type === 'apiMessage';
const messageClasses = ` ${
isApiMessage ? 'bg-gray-700/50' : 'bg-gray-800/90'
}`;

return (
<div
key={`chatMessage-${index}`}
className={` ${
message.type === 'apiMessage'
? 'bg-gray-700/50'
: 'bg-gray-800/90'
}`}
>
<div key={`chatMessage-${index}`} className={messageClasses}>
<div className="flex items-center justify-start max-w-full sm:max-w-4xl mx-auto overflow-hidden px-2 sm:px-4">
{/* user and bot image */}
{/* {message.type === 'apiMessage' ? (
<div className="flex-shrink-0 p-1">
<CodeBracketSquareIcon className="h-8 sm:h-10 w-8 sm:w-10 text-white rounded-full object-cover mr-2 sm:mr-3" />
</div>
) : (
<div className="flex-shrink-0 p-1">
<Image
src={userImage || '/images/user.png'}
alt=""
width={30}
height={30}
className="h-8 sm:h-10 w-8 sm:w-10 rounded-full object-cover mr-2 sm:mr-3"
/>
</div>
)} */}
{/* user and bot image */}
<div className="flex flex-col w-full ">
<div className="flex flex-col w-full">
<div className="w-full text-gray-300 p-2 sm:p-4 overflow-wrap break-words">
<span
className={`mt-2 inline-flex items-center rounded-md px-2 py-1 text-xs sm:text-sm font-medium ring-1 ring-inset ${
message.type === 'apiMessage'
isApiMessage
? 'bg-indigo-400/10 text-indigo-400 ring-indigo-400/30'
: 'bg-purple-400/10 text-purple-400 ring-purple-400/30'
}`}
>
{message.type === 'apiMessage'
? 'pdf-chatbot'
: userName}
{isApiMessage ? 'pdf-chatbot' : userName}
</span>
<div className="mx-auto max-w-full">
<ReactMarkdown
Expand All @@ -84,23 +60,23 @@ function MessageList({
</div>
{message.sourceDocs && (
<div
className="mt-4 mx-2 sm:mx-4 "
className="mt-4 mx-2 sm:mx-4"
key={`sourceDocsAccordion-${index}`}
>
<Accordion
type="single"
collapsible
className="flex flex-col"
>
{message.sourceDocs.map((doc, index) => (
{message.sourceDocs.map((doc, docIndex) => (
<div
key={`messageSourceDocs-${index}`}
key={`messageSourceDocs-${docIndex}`}
className="mb-6 px-4 py-0 sm:py-1 bg-gray-700 rounded-lg shadow-md"
>
<AccordionItem value={`item-${index}`}>
<AccordionItem value={`item-${docIndex}`}>
<AccordionTrigger>
<h3 className="text-xs sm:text-sm md:text-base text-white">
Source {index + 1}
Source {docIndex + 1}
</h3>
</AccordionTrigger>
<AccordionContent className="mt-2 overflow-wrap break-words">
Expand All @@ -109,18 +85,11 @@ function MessageList({
className="markdown text-xs sm:text-sm md:text-base text-gray-300 leading-relaxed"
remarkPlugins={[remarkGfm]}
>
{/* {doc.pageContent} */}
{doc.pageContent.replace(
/(?<=\S)\n/g,
' \n',
)}
</ReactMarkdown>
{/* <p className="mt-2">
<b>Source:</b>{' '}
{doc.metadata.source.match(
/[^\\]*$/,
)?.[0] ?? doc.metadata.source}
</p> */}
</AccordionContent>
</AccordionItem>
</div>
Expand Down
36 changes: 21 additions & 15 deletions components/sidebar/ListOfChats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,24 @@ const ListOfChats = ({
updateChatName: (chatId: string, newChatName: string) => void;
deleteChat: (chatId: string) => void;
}) => {
const handleChatClick = (chatId: string) => {
setChatId(chatId);
setSelectedChatId(chatId);
};

const handleEditChatName = (e: React.MouseEvent, chatId: string) => {
e.stopPropagation();
const newChatName = prompt('Enter a new name for this chat:');
if (newChatName) {
updateChatName(chatId, newChatName);
}
};

const handleDeleteChat = (e: React.MouseEvent, chatId: string) => {
e.stopPropagation();
deleteChat(chatId);
};

return (
<ul role="list" className="-mx-2 space-y-1 mt-2 px-2">
<div className="text-xs sm:text-sm font-semibold leading-6 text-blue-400">
Expand All @@ -36,33 +54,21 @@ const ListOfChats = ({
: 'text-gray-400 hover:text-white hover:bg-gray-800',
'group flex w-full gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold cursor-pointer',
)}
onClick={() => {
setChatId(chatId);
setSelectedChatId(chatId);
}}
onClick={() => handleChatClick(chatId)}
>
{chatNames[chatId] || `Chat ${index}`}
{chatId === selectedChatId && (
<div className="ml-auto">
<button
className="text-gray-300 hover:text-gray-400 ml-2"
onClick={(e) => {
e.stopPropagation();
const newChatName = prompt('Enter a new name for this chat:');
if (newChatName) {
updateChatName(chatId, newChatName);
}
}}
onClick={(e) => handleEditChatName(e, chatId)}
>
<PencilIcon className="h-4 w-4" />
</button>

<button
className="text-red-500 hover:text-red-600 ml-2"
onClick={(e) => {
e.stopPropagation();
deleteChat(chatId);
}}
onClick={(e) => handleDeleteChat(e, chatId)}
>
<TrashIcon className="h-4 w-4" />
</button>
Expand Down
7 changes: 5 additions & 2 deletions components/sidebar/ListOfNamespaces.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ const ListOfNamespaces = ({
selectedNamespace: string;
setSelectedNamespace: (namespace: string) => void;
}) => {
const handleNamespaceClick = (namespace: string) => {
setSelectedNamespace(namespace);
};

return (
<li>
<div className="text-xs sm:text-sm text-blue-400 font-semibold leading-6 ">
Expand All @@ -24,14 +28,13 @@ const ListOfNamespaces = ({
<li key={namespace}>
<Link
href={`/namespace/${namespace}`}
key={namespace}
className={classNames(
namespace === selectedNamespace
? 'bg-gray-800 text-white'
: 'text-gray-400 hover:text-white hover:bg-gray-800',
'group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold',
)}
onClick={() => setSelectedNamespace(namespace)}
onClick={() => handleNamespaceClick(namespace)}
>
<span className="truncate">{namespace}</span>
</Link>
Expand Down
Loading

0 comments on commit e14f30e

Please sign in to comment.