Skip to content

Commit

Permalink
added chat history UI, added create new chat button and function
Browse files Browse the repository at this point in the history
  • Loading branch information
Adebesin-Cell committed Sep 26, 2023
1 parent 9aac19a commit bb02b85
Show file tree
Hide file tree
Showing 6 changed files with 395 additions and 49 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@dnd-kit/sortable": "^7.0.2",
"@radix-ui/react-dialog": "^1.0.3",
"@radix-ui/react-dropdown-menu": "^2.0.4",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-switch": "^1.0.2",
"@types/object-hash": "^3.0.2",
"axios": "^1.3.5",
Expand Down
76 changes: 58 additions & 18 deletions src/components/Sidebar/chat/ChatHistory.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,71 @@
import { useChatHistory } from "../../../hooks/useChatHistory";
import * as Select from "@radix-ui/react-select";
import { RiTimeLine } from "react-icons/ri";
import { useChatHistory } from '../../../hooks/useChatHistory';
import * as Select from '@radix-ui/react-select';
import { RiAddLine, RiTimeLine } from 'react-icons/ri';
import { generateReadableRelativeDate } from '../../../utils/generateReadableDate';

const ChatHistory = () => {
const { history, setCurrentChatId, currentChatId, getChatHistory } =
useChatHistory();
const {
history,
setCurrentChatId,
currentChatId,
getChatHistory,
createChatHistory
} = useChatHistory();

const currentChat = getChatHistory(currentChatId);

return (
<div>
<Select.Root>
<Select.Trigger className="cdx-border cdx-border-neutral-500/20 cdx-flex cdx-gap-2 cdx-items-center cdx-py-2 cdx-px-3 cdx-text-sm cdx-text-neutral-700 dark:cdx-text-neutral-300 cdx-rounded-md">
<RiTimeLine /> {currentChat?.name}
<Select.Trigger className='cdx-border cdx-border-neutral-500/20 cdx-flex cdx-gap-2 cdx-items-center cdx-py-2 cdx-px-3 cdx-text-sm cdx-text-neutral-700 dark:cdx-text-neutral-300 cdx-rounded-md'>
<RiTimeLine /> <span>{currentChat?.name}</span>
</Select.Trigger>
<Select.Content>
History
<div>
{history.map((chat) => (
<Select.Item
value={chat.id}
key={chat.id}
onSelect={() => setCurrentChatId(chat.id)}
<Select.Content
side='top'
position='popper'
className='cdx-w-full cdx-max-w-xs cdx-min-w-[230px] cdx-bg-white/90 dark:cdx-bg-[#1f1f1fe5] cdx-rounded-lg cdx-mb-1.5 cdx-pb-3 cdx-overflow-hidden'
>
<div className='cdx-backdrop-blur-md'>
<div className='cdx-flex cdx-justify-between cdx-items-center cdx-p-3 cdx-border-b-[#E5E7EB] cdx-border-b dark:cdx-border-b-[#2F2F2F]'>
<h1 className='cdx-text-sm cdx-font-bold cdx-text-[#5A5A5A] dark:cdx-text-[#E3E3E3]'>
History
</h1>
<button
type='button'
className='cdx-flex cdx-items-center cdx-bg-[#3B82F6] cdx-gap-1.5 cdx-px-2.5 cdx-py-1.5 cdx-rounded-sm cdx-font-medium'
onClick={() => {
createChatHistory('New Chat');
}}
>
{chat.name}
</Select.Item>
))}
<RiAddLine />
New Chat
</button>
</div>
<div>
{history.map((chat, i) => (
<Select.Item
value={chat.id}
key={chat.id}
onSelect={() => setCurrentChatId(chat.id)}
className={`cdx-px-3 cdx-py-1.5 cdx-relative cdx-flex cdx-justify-between cdx-items-center cdx-border-b dark:cdx-border-b-[#2F2F2F] ${
i === history.length - 1
? 'cdx-border-b-0'
: 'cdx-border-b-[#E5E7EB]'
} cdx-cursor-pointer`}
>
<div
className='cdx-absolute cdx-left-0 cdx-h-full cdx-w-[3px] data-[currentChat]:cdx-bg-[#70A3F3]'
data-currentChat={currentChat?.id === chat.id || undefined}
/>
<span className='cdx-lowercase cdx-text-sm dark:cdx-text-[#E3E3E3] cdx-text-[#5A5A5A]'>
{chat.name}
</span>
<span className='cdx-text-[10px] cdx-text-[#898989] dark:cdx-text-white/50'>
{generateReadableRelativeDate(chat.createdAt)}
</span>
</Select.Item>
))}
</div>
</div>
</Select.Content>
</Select.Root>
Expand Down
45 changes: 24 additions & 21 deletions src/components/Sidebar/chat/ChatInput.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useEffect, useState } from "react";
import TextareaAutosize from "react-textarea-autosize";
import { GiMagicBroom } from "react-icons/gi";
import { IoSend } from "react-icons/io5";
import { HiHand } from "react-icons/hi";
import ChatHistory from "./ChatHistory";
import { useEffect, useState } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { GiMagicBroom } from 'react-icons/gi';
import { IoSend } from 'react-icons/io5';
import { HiHand } from 'react-icons/hi';
import ChatHistory from './ChatHistory';

interface SidebarInputProps {
loading: boolean;
Expand All @@ -18,9 +18,9 @@ export function SidebarInput({
submitMessage,
clearMessages,
chatIsEmpty,
cancelRequest,
cancelRequest
}: SidebarInputProps) {
const [text, setText] = useState("");
const [text, setText] = useState('');
const [delayedLoading, setDelayedLoading] = useState(false);

useEffect(() => {
Expand All @@ -34,62 +34,65 @@ export function SidebarInput({

const handleSubmit = () => {
submitMessage(text);
setText("");
setText('');
};

return (
<div className="cdx-fixed cdx-bottom-0 cdx-left-0 cdx-right-0 cdx-flex cdx-flex-col ">
<div className="cdx-flex cdx-mx-3 cdx-items-center cdx-justify-between">
<div className='cdx-fixed cdx-bottom-0 cdx-left-0 cdx-right-0 cdx-flex cdx-flex-col '>
<div className='cdx-flex cdx-mx-3 cdx-items-center cdx-justify-between'>
{!chatIsEmpty ? (
<button
type='button'
onClick={clearMessages}
className="cdx-rounded-full cdx-h-10 cdx-w-10 cdx-grid cdx-place-items-center cdx-text-center cdx-bg-blue-500 hover:cdx-bg-blue-700 cdx-text-white"
className='cdx-rounded-full cdx-h-10 cdx-w-10 cdx-grid cdx-place-items-center cdx-text-center cdx-bg-blue-500 hover:cdx-bg-blue-700 cdx-text-white'
>
<GiMagicBroom size={18} className="mx-auto" />
<GiMagicBroom size={18} className='mx-auto' />
</button>
) : (
<div />
)}
<ChatHistory />
</div>

<div className="cdx-m-2 cdx-rounded-md cdx-border dark:cdx-border-neutral-800 cdx-border-neutral-300 dark:cdx-bg-neutral-900/90 cdx-bg-neutral-200/90 focus:cdx-outline-none focus:cdx-ring-2 focus:cdx-ring-blue-900 focus:cdx-ring-opacity-50">
<div className='cdx-m-2 cdx-rounded-md cdx-border dark:cdx-border-neutral-800 cdx-border-neutral-300 dark:cdx-bg-neutral-900/90 cdx-bg-neutral-200/90 focus:cdx-outline-none focus:cdx-ring-2 focus:cdx-ring-blue-900 focus:cdx-ring-opacity-50'>
<TextareaAutosize
minRows={2}
maxLength={10000}
placeholder="Type your message here..."
placeholder='Type your message here...'
value={text}
disabled={loading}
className="cdx-p-3 cdx-w-full cdx-text-sm cdx-resize-none cdx-max-h-96 cdx-pb-0 cdx-bg-transparent !cdx-border-none focus:!cdx-outline-none"
className='cdx-p-3 cdx-w-full cdx-text-sm cdx-resize-none cdx-max-h-96 cdx-pb-0 cdx-bg-transparent !cdx-border-none focus:!cdx-outline-none'
onChange={(e) => {
e.preventDefault();
setText(e.target.value);
}}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSubmit();
}
}}
/>
<div className="cdx-flex cdx-justify-between cdx-items-center cdx-p-3">
<div className='cdx-flex cdx-justify-between cdx-items-center cdx-p-3'>
<div>
<span className="cdx-text-xs cdx-text-neutral-500 dark:cdx-text-neutral-200">
<span className='cdx-text-xs cdx-text-neutral-500 dark:cdx-text-neutral-200'>
{text.length} / 10,000
</span>
</div>
{!delayedLoading ? (
<button
type='button'
disabled={loading}
onClick={handleSubmit}
className="cdx-flex cdx-gap-2 disabled:cdx-bg-slate-500 disabled:cdx-text-slate-400 cdx-items-center cdx-bg-blue-500 hover:cdx-bg-blue-700 cdx-text-white cdx-py-2 cdx-px-4 cdx-rounded"
className='cdx-flex cdx-gap-2 disabled:cdx-bg-slate-500 disabled:cdx-text-slate-400 cdx-items-center cdx-bg-blue-500 hover:cdx-bg-blue-700 cdx-text-white cdx-py-2 cdx-px-4 cdx-rounded'
>
<span>Send</span> <IoSend size={10} />
</button>
) : (
<button
type='button'
onClick={cancelRequest}
className="cdx-flex cdx-gap-2 disabled:cdx-bg-slate-500 disabled:cdx-text-slate-400 cdx-items-center cdx-bg-red-500 hover:cdx-bg-red-700 cdx-text-white cdx-py-2 cdx-px-4 cdx-rounded"
className='cdx-flex cdx-gap-2 disabled:cdx-bg-slate-500 disabled:cdx-text-slate-400 cdx-items-center cdx-bg-red-500 hover:cdx-bg-red-700 cdx-text-white cdx-py-2 cdx-px-4 cdx-rounded'
>
<HiHand size={18} /> <span>Stop</span>
</button>
Expand Down
20 changes: 10 additions & 10 deletions src/components/Sidebar/chat/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useEffect } from "react";
import ChatList from "./ChatList";
import { SidebarInput } from "./ChatInput";
import { useChatCompletion } from "../../../hooks/useChatCompletion";
import { SYSTEM_PROMPT } from "../../../config/prompts";
import { Settings } from "../../../config/settings";
import { useEffect } from 'react';
import ChatList from './ChatList';
import { SidebarInput } from './ChatInput';
import { useChatCompletion } from '../../../hooks/useChatCompletion';
import { SYSTEM_PROMPT } from '../../../config/prompts';
import { Settings } from '../../../config/settings';

interface ChatProps {
settings: Settings;
Expand All @@ -17,7 +17,7 @@ const Chat = ({ settings, chatId }: ChatProps) => {
apiKey: settings.chat.openAIKey!,
mode: settings.chat.mode,
systemPrompt: SYSTEM_PROMPT,
chatId,
chatId
});

useEffect(() => {
Expand All @@ -26,14 +26,14 @@ const Chat = ({ settings, chatId }: ChatProps) => {
action: string;
prompt: string;
};
if (action === "generate") {
if (action === 'generate') {
submitQuery(prompt);
}
};
window.addEventListener("message", handleWindowMessage);
window.addEventListener('message', handleWindowMessage);

return () => {
window.removeEventListener("message", handleWindowMessage);
window.removeEventListener('message', handleWindowMessage);
};
}, []);

Expand Down
47 changes: 47 additions & 0 deletions src/utils/generateReadableDate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export const generateReadableRelativeDate = (
date: Date | number | string,
nowDate: Date | number | string = Date.now(),
rft: Intl.RelativeTimeFormat = new Intl.RelativeTimeFormat('en-US', {
numeric: 'auto',
}),
) => {
const SECOND = 1000
const MINUTE = 60 * SECOND
const HOUR = 60 * MINUTE
const DAY = 24 * HOUR
const WEEK = 7 * DAY
const MONTH = 30 * DAY
const YEAR = 365 * DAY
const intervals = [
{ ge: YEAR, divisor: YEAR, unit: 'yr' },
{ ge: MONTH, divisor: MONTH, unit: 'mo' },
{ ge: WEEK, divisor: WEEK, unit: 'wk' },
{ ge: DAY, divisor: DAY, unit: 'day' },
{ ge: HOUR, divisor: HOUR, unit: 'hr' },
{ ge: MINUTE, divisor: MINUTE, unit: 'min' },
{ ge: 30 * SECOND, divisor: SECOND, unit: 'sec' },
{ ge: 0, divisor: 1, text: 'just now' },
]
const now =
typeof nowDate === 'object'
? (nowDate as Date).getTime()
: new Date(nowDate).getTime()
const diff =
now -
(typeof date === 'object'
? (date as Date).getTime()
: new Date(date).getTime())
const diffAbs = Math.abs(diff)
for (const interval of intervals) {
if (diffAbs >= interval.ge) {
const x = Math.round(Math.abs(diff) / interval.divisor)
const isFuture = diff < 0
return interval.unit
? rft.format(
isFuture ? x : -x,
interval.unit as Intl.RelativeTimeFormatUnit,
)
: interval.text
}
}
}
Loading

0 comments on commit bb02b85

Please sign in to comment.