From cfc23d1909e868b336dfb32666b25dc7ded7ac5c Mon Sep 17 00:00:00 2001 From: BetaHuhn Date: Thu, 31 Aug 2023 12:29:00 +0200 Subject: [PATCH] feat: group chats by date and fixes --- package-lock.json | 26 ++++++ package.json | 1 + src/components/App.tsx | 2 +- src/components/ChatHeader.tsx | 1 + src/components/ChatItem.tsx | 56 ++++++++++++ src/components/Chats.tsx | 129 +++++++++++++-------------- src/components/CreatePromptModal.tsx | 1 + src/components/DeleteChatModal.tsx | 1 + src/components/DeletePromptModal.tsx | 1 + src/components/EditChatModal.tsx | 3 +- src/components/EditPromptModal.tsx | 1 + src/components/Logo.tsx | 6 +- src/components/MessageItem.tsx | 1 + src/components/SettingsModal.tsx | 6 ++ src/routes/ChatRoute.tsx | 20 +---- src/routes/IndexRoute.tsx | 4 +- yarn.lock | 9 +- 17 files changed, 178 insertions(+), 90 deletions(-) create mode 100644 src/components/ChatItem.tsx diff --git a/package-lock.json b/package-lock.json index 9c0fb1c..bc23916 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "@types/react-dom": "^18.0.11", "@vitejs/plugin-react": "^4.0.4", "buffer": "^5.7.1", + "date-fns": "^2.30.0", "deta": "^2.0.0-rc.1", "path-browserify": "^1.0.1", "process": "^0.11.10", @@ -2545,6 +2546,22 @@ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -9245,6 +9262,15 @@ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" }, + "date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.21.0" + } + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index 88e66d5..10a1267 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@types/react-dom": "^18.0.11", "@vitejs/plugin-react": "^4.0.4", "buffer": "^5.7.1", + "date-fns": "^2.30.0", "deta": "^2.0.0-rc.1", "path-browserify": "^1.0.1", "process": "^0.11.10", diff --git a/src/components/App.tsx b/src/components/App.tsx index d3363db..c1d6f59 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -49,7 +49,7 @@ export function App() { withCSSVariables theme={{ colorScheme, - primaryColor: "teal", + primaryColor: "orange", defaultRadius: "md", globalStyles: (theme) => ({ body: { diff --git a/src/components/ChatHeader.tsx b/src/components/ChatHeader.tsx index 8dfd68d..7e1d333 100644 --- a/src/components/ChatHeader.tsx +++ b/src/components/ChatHeader.tsx @@ -33,6 +33,7 @@ export function ChatHeader() { notifications.show({ title: "Saved", + color: "green", message: "Chat name updated.", }); } diff --git a/src/components/ChatItem.tsx b/src/components/ChatItem.tsx new file mode 100644 index 0000000..4dc2fb1 --- /dev/null +++ b/src/components/ChatItem.tsx @@ -0,0 +1,56 @@ +import { ActionIcon, Flex, Menu, useMantineTheme } from "@mantine/core"; +import { IconDotsVertical, IconMessages } from "@tabler/icons-react"; +import { Link } from "@tanstack/react-location"; +import { DeleteChatModal } from "./DeleteChatModal"; +import { EditChatModal } from "./EditChatModal"; +import { MainLink } from "./MainLink"; +import { useHover } from "@mantine/hooks"; +import { Chat } from "../db"; + +export function ChatItem({ chat, active = false }: { chat: Chat, active?: boolean }) { + const { hovered, ref } = useHover(); + const theme = useMantineTheme(); + + return ( + ({ + marginTop: 1, + "&:hover, &.active": { + backgroundColor: + theme.colorScheme === "dark" + ? theme.colors.dark[6] + : theme.colors.gray[1], + }, + })} + > + + } + color={theme.primaryColor} + label={chat.description} + /> + + + + {hovered && ( + + + + + + )} + + + Edit + + + Delete + + + + + ); +} diff --git a/src/components/Chats.tsx b/src/components/Chats.tsx index 332aa5f..573aca2 100644 --- a/src/components/Chats.tsx +++ b/src/components/Chats.tsx @@ -1,85 +1,80 @@ -import { ActionIcon, Flex, Menu, useMantineTheme } from "@mantine/core"; -import { IconDotsVertical, IconMessages } from "@tabler/icons-react"; -import { Link } from "@tanstack/react-location"; import { useMemo } from "react"; +import { Text } from '@mantine/core'; +import { format, isToday, isYesterday, isThisWeek, subWeeks, isSameYear, isSameWeek } from 'date-fns'; + import { useChatId } from "../hooks/useChatId"; -import { DeleteChatModal } from "./DeleteChatModal"; -import { EditChatModal } from "./EditChatModal"; -import { MainLink } from "./MainLink"; import { useChats } from "../hooks/contexts"; +import { ChatItem } from "./ChatItem"; export function Chats({ search }: { search: string }) { const chatId = useChatId(); - - const theme = useMantineTheme(); - - const { chats } = useChats() - // const [chats, setChats] = useState(); - - // useEffect(() => { - // // fetch data - // const dataFetch = async () => { - // const { items } = await detaDB.chats.fetch(); - - // setChats(items as unknown as Chat[]); - // }; - - // dataFetch(); - // }, []); - - // const chats = useLiveQuery(() => - // db.chats.orderBy("createdAt").reverse().toArray() - // ); const filteredChats = useMemo( () => - (chats ?? []).filter((chat) => { - if (!search) return true; - return chat.description.toLowerCase().includes(search); - }), + (chats ?? []) + .filter((chat) => { + if (!search) return true; + return chat.description.toLowerCase().includes(search); + }) + .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()), [chats, search] ); + const groupedChats = useMemo(() => filteredChats.reduce((acc, chat) => { + const chatDate = new Date(chat.createdAt); + if (isToday(chatDate)) { + if (!acc['Today']) { + acc['Today'] = [chat]; + } else { + acc['Today'].push(chat); + } + } else if (isYesterday(chatDate)) { + if (!acc['Yesterday']) { + acc['Yesterday'] = [chat]; + } else { + acc['Yesterday'].push(chat); + } + } else if (isThisWeek(chatDate, { weekStartsOn: 1 })) { + const chatWeekday = format(chatDate, 'EEEE'); + if (!acc[chatWeekday]) { + acc[chatWeekday] = [chat]; + } else { + acc[chatWeekday].push(chat); + } + } else if (isSameWeek(chatDate, subWeeks(new Date(), 1), { weekStartsOn: 1 })) { + if (!acc['Last Week']) { + acc['Last Week'] = [chat]; + } else { + acc['Last Week'].push(chat); + } + } else if (isSameYear(chatDate, new Date())) { + const chatMonth = format(chatDate, 'MMMM'); + if (!acc[chatMonth]) { + acc[chatMonth] = [chat]; + } else { + acc[chatMonth].push(chat); + } + } else { + const chatYear = ` ${format(chatDate, 'yyyy')}`; // space to avoid js object key sorting + if (!acc[chatYear]) { + acc[chatYear] = [chat]; + } else { + acc[chatYear].push(chat); + } + } + return acc; + }, {} as Record), [filteredChats]); + return ( <> - {filteredChats.map((chat) => ( - ({ - marginTop: 1, - "&:hover, &.active": { - backgroundColor: - theme.colorScheme === "dark" - ? theme.colors.dark[6] - : theme.colors.gray[1], - }, - })} - > - - } - color={theme.primaryColor} - label={chat.description} - /> - - - - - - - - - - Edit - - - Delete - - - - + {Object.entries(groupedChats).map(([date, chats]) => ( +
+ {date} + {chats.map((chat) => ( + + ))} +
))} ); diff --git a/src/components/CreatePromptModal.tsx b/src/components/CreatePromptModal.tsx index 3a70f1d..3343810 100644 --- a/src/components/CreatePromptModal.tsx +++ b/src/components/CreatePromptModal.tsx @@ -77,6 +77,7 @@ export function CreatePromptModal({ content, title: titleProp, open: openProp }: notifications.show({ title: "Saved", + color: "green", message: "Prompt created", }); diff --git a/src/components/DeleteChatModal.tsx b/src/components/DeleteChatModal.tsx index 570f3ba..2c4e205 100644 --- a/src/components/DeleteChatModal.tsx +++ b/src/components/DeleteChatModal.tsx @@ -58,6 +58,7 @@ export function DeleteChatModal({ notifications.show({ title: "Deleted", + color: "green", message: "Chat deleted.", }); } catch (error: any) { diff --git a/src/components/DeletePromptModal.tsx b/src/components/DeletePromptModal.tsx index 270511b..faaebb2 100644 --- a/src/components/DeletePromptModal.tsx +++ b/src/components/DeletePromptModal.tsx @@ -36,6 +36,7 @@ export function DeletePromptModal({ prompt }: { prompt: Prompt }) { notifications.show({ title: "Deleted", + color: "green", message: "Prompt deleted.", }); } catch (error: any) { diff --git a/src/components/EditChatModal.tsx b/src/components/EditChatModal.tsx index 4c6335e..73e0574 100644 --- a/src/components/EditChatModal.tsx +++ b/src/components/EditChatModal.tsx @@ -110,7 +110,8 @@ export function EditChatModal({ notifications.show({ title: "Saved", - message: "", + color: "green", + message: "Chat updated.", }); close(); } catch (error: any) { diff --git a/src/components/EditPromptModal.tsx b/src/components/EditPromptModal.tsx index 929e930..e11207c 100644 --- a/src/components/EditPromptModal.tsx +++ b/src/components/EditPromptModal.tsx @@ -70,6 +70,7 @@ export function EditPromptModal({ prompt }: { prompt: Prompt }) { notifications.show({ title: "Saved", + color: "green", message: "Prompt updated", }); diff --git a/src/components/Logo.tsx b/src/components/Logo.tsx index 00eb611..7cbc0a0 100644 --- a/src/components/Logo.tsx +++ b/src/components/Logo.tsx @@ -14,7 +14,7 @@ export function LogoText(props: JSX.IntrinsicElements["svg"]) { ); } -export function Logo(props: JSX.IntrinsicElements["svg"]) { +export function Logo(props: JSX.IntrinsicElements["svg"] & { color1: string, color2: string }) { return ( - - + + diff --git a/src/components/MessageItem.tsx b/src/components/MessageItem.tsx index f066821..6e3e7d7 100644 --- a/src/components/MessageItem.tsx +++ b/src/components/MessageItem.tsx @@ -46,6 +46,7 @@ export function MessageItem({ message, onDeleted }: { message: Message, onDelete notifications.show({ title: "Deleted", + color: "green", message: "Message deleted.", }); } diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx index 23507f6..91dc3be 100644 --- a/src/components/SettingsModal.tsx +++ b/src/components/SettingsModal.tsx @@ -89,6 +89,7 @@ export function SettingsModal({ children }: { children: ReactElement }) { // }); notifications.show({ title: "Saved", + color: "green", message: "Your OpenAI Key has been saved.", }); } catch (error: any) { @@ -159,6 +160,7 @@ export function SettingsModal({ children }: { children: ReactElement }) { notifications.show({ title: "Saved", + color: "green", message: "Your OpenAI Type has been saved.", }); } catch (error: any) { @@ -198,6 +200,7 @@ export function SettingsModal({ children }: { children: ReactElement }) { notifications.show({ title: "Saved", + color: "green", message: "Your OpenAI Model has been saved.", }); } catch (error: any) { @@ -241,6 +244,7 @@ export function SettingsModal({ children }: { children: ReactElement }) { notifications.show({ title: "Saved", + color: "green", message: "Your OpenAI Auth has been saved.", }); } catch (error: any) { @@ -280,6 +284,7 @@ export function SettingsModal({ children }: { children: ReactElement }) { notifications.show({ title: "Saved", + color: "green", message: "Your OpenAI Base has been saved.", }); } catch (error: any) { @@ -331,6 +336,7 @@ export function SettingsModal({ children }: { children: ReactElement }) { notifications.show({ title: "Saved", + color: "green", message: "Your OpenAI Version has been saved.", }); } catch (error: any) { diff --git a/src/routes/ChatRoute.tsx b/src/routes/ChatRoute.tsx index 252206b..83c78ff 100644 --- a/src/routes/ChatRoute.tsx +++ b/src/routes/ChatRoute.tsx @@ -14,7 +14,7 @@ import { notifications } from "@mantine/notifications"; import { KeyboardEvent, useState, type ChangeEvent, useEffect } from "react"; import { AiOutlineSend } from "react-icons/ai"; import { MessageItem } from "../components/MessageItem"; -import { Chat, Message, Prompt, detaDB, generateKey } from "../db"; +import { Message, Prompt, detaDB, generateKey } from "../db"; import { useChatId } from "../hooks/useChatId"; import { createChatCompletion, @@ -28,7 +28,6 @@ export function ChatRoute() { const chatId = useChatId(); const { settings } = useSettings() - const { prompts } = usePrompts() const [messages, setMessages] = useState([]); @@ -63,21 +62,10 @@ export function ChatRoute() { const { chat, setChat } = useChat() useEffect(() => { - const dataFetch = async () => { - const item = await detaDB.chats.get(chatId!); - const fetchedChat = item as unknown as Chat - - setChat(fetchedChat); - - if (fetchedChat.prompt) { - setPromptKey(fetchedChat.prompt) - } - }; - - if (!chat) { - dataFetch(); + if (chat?.prompt) { + setPromptKey(chat.prompt) } - }, [chatId]); + }, [chat]); // const chat = useLiveQuery(async () => { // if (!chatId) return null; diff --git a/src/routes/IndexRoute.tsx b/src/routes/IndexRoute.tsx index 3bc9b63..a739104 100644 --- a/src/routes/IndexRoute.tsx +++ b/src/routes/IndexRoute.tsx @@ -7,6 +7,7 @@ import { SimpleGrid, Text, ThemeIcon, + useMantineTheme, } from "@mantine/core"; import { IconCloudDownload, @@ -22,6 +23,7 @@ import { useSettings } from "../hooks/contexts"; export function IndexRoute() { const { settings } = useSettings() + const theme = useMantineTheme() return ( <> @@ -29,7 +31,7 @@ export function IndexRoute() { GPT-4 Ready - + Not just another ChatGPT user-interface! diff --git a/yarn.lock b/yarn.lock index b590ba6..d0ff3e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -174,7 +174,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.7": "integrity" "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==" "resolved" "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz" "version" "7.21.0" @@ -1188,6 +1188,13 @@ "resolved" "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz" "version" "1.0.8" +"date-fns@^2.30.0": + "integrity" "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==" + "resolved" "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz" + "version" "2.30.0" + dependencies: + "@babel/runtime" "^7.21.0" + "debug@^3.2.7": "integrity" "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==" "resolved" "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"