diff --git a/src/components/CreateChatButton.tsx b/src/components/CreateChatButton.tsx
index 500c42f..6491061 100644
--- a/src/components/CreateChatButton.tsx
+++ b/src/components/CreateChatButton.tsx
@@ -2,7 +2,7 @@ import { notifications } from "@mantine/notifications";
import { Chat, detaDB, generateKey } from "../db";
import { Button } from "@mantine/core";
import { IconPlus } from "@tabler/icons-react";
-import { useChats, useSettings } from "../hooks/contexts";
+import { useChats, useIncognitoMode, useSettings } from "../hooks/contexts";
import { useNavigate } from "@tanstack/react-location";
import { ReactNode } from "react";
@@ -11,6 +11,7 @@ export function CreateChatButton(props: { children: ReactNode, [x:string]: any }
const navigate = useNavigate();
const { settings } = useSettings()
const { setChats } = useChats()
+ const { incognitoMode } = useIncognitoMode()
const handleCreate = async () => {
if (!settings?.openAiApiKey) {
@@ -23,9 +24,10 @@ export function CreateChatButton(props: { children: ReactNode, [x:string]: any }
};
const item = await detaDB.chats.put({
- description: "New Chat",
+ description: incognitoMode ? "New Private Chat" : "New Chat",
prompt: null,
totalTokens: 0,
+ private: incognitoMode,
createdAt: new Date().toISOString(),
}, generateKey())
diff --git a/src/components/DeleteChatsModal.tsx b/src/components/DeleteChatsModal.tsx
index b6ee133..3db2609 100644
--- a/src/components/DeleteChatsModal.tsx
+++ b/src/components/DeleteChatsModal.tsx
@@ -1,49 +1,91 @@
import { Button, Modal, Stack, Text } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
-import { IconTrash } from "@tabler/icons-react";
+import { IconFlame } from "@tabler/icons-react";
import { Chat, Message, detaDB } from "../db";
+import { notifications } from "@mantine/notifications";
+import floating from "../utils/floating";
+import { useChat, useChats } from "../hooks/contexts";
+import { useNavigate } from "@tanstack/react-location";
-export function DeleteChatsModal({ onOpen }: { onOpen: () => void }) {
+export function DeleteChatsModal({ onOpen }: { onOpen?: () => void }) {
const [opened, { open, close }] = useDisclosure(false, { onOpen });
+ const navigate = useNavigate()
+
+ const { setChats } = useChats()
+ const { setChat } = useChat()
+
+ const deleteChats = async () => {
+ let res = await detaDB.chats.fetch({ private: true })
+ let chats = res.items;
+ while (res.last) {
+ res = await detaDB.chats.fetch({ private: true }, { last: res.last });
+ chats = chats.concat(res.items);
+ }
+
+ await Promise.all(chats.map(async (chat) => {
+ await detaDB.chats.delete((chat as unknown as Chat).key)
+
+ res = await detaDB.messages.fetch({ chatId: chat.key })
+ let messages = res.items;
+ while (res.last) {
+ res = await detaDB.messages.fetch({ chatId: chat.key }, { last: res.last })
+ messages = messages.concat(res.items);
+ }
+
+ await Promise.all(messages.map(async (message) => {
+ await detaDB.messages.delete((message as unknown as Message).key)
+ }))
+ }))
+
+ for (let i = 0; i < 100; i++) {
+ setTimeout(() => {
+ floating({
+ content: "🔥",
+ number: 2,
+ duration: 0.5,
+ repeat: 1,
+ elem: undefined
+ });
+ }, (i * 5) + (Math.random() * 10));
+ }
+
+ setChat(null)
+ setChats([])
+ navigate({ to: '/', replace: true })
+
+ notifications.show({
+ title: "Burned",
+ color: "green",
+ message: "Private chats deleted.",
+ });
+
+ close()
+ }
return (
<>
}
onClick={open}
- variant="outline"
- color="red"
- leftIcon={}
>
- Delete Chats
+ Burn Private Chats
- Are you sure you want to delete your chats?
+ Are you sure you want to delete all your private chats?
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
index b4f1c96..552d74f 100644
--- a/src/components/Layout.tsx
+++ b/src/components/Layout.tsx
@@ -3,6 +3,7 @@ import {
AppShell,
Box,
Burger,
+ Flex,
MediaQuery,
Navbar,
rem,
@@ -20,7 +21,7 @@ import {
IconSpyOff,
IconX,
} from "@tabler/icons-react";
-import { Link, Outlet, useRouter } from "@tanstack/react-location";
+import { Link, Outlet, useNavigate, useRouter } from "@tanstack/react-location";
import { useEffect, useState } from "react";
import { Chat, detaDB, Prompt, Settings } from "../db";
import { useChatId } from "../hooks/useChatId";
@@ -34,25 +35,22 @@ import { ChatContext, ChatsContext, IncognitoModeContext, PromptsContext, Settin
import { ChatHeader } from "./ChatHeader";
import { useLocalStorage } from "@mantine/hooks";
import { CreateChatButton } from "./CreateChatButton";
-
-declare global {
- interface Window {
- todesktop?: any;
- }
-}
+import { DeleteChatsModal } from "./DeleteChatsModal";
export function Layout() {
const theme = useMantineTheme();
- const [opened, setOpened] = useState(false);
- const [tab, setTab] = useState<"Chats" | "Prompts">("Chats");
- const { colorScheme, toggleColorScheme } = useMantineColorScheme();
+ const navigate = useNavigate();
const router = useRouter();
- const [search, setSearch] = useState("");
+ const { colorScheme, toggleColorScheme } = useMantineColorScheme();
const chatId = useChatId();
+ const [opened, setOpened] = useState(false);
+ const [tab, setTab] = useState<"Chats" | "Prompts">("Chats");
+ const [search, setSearch] = useState("");
+
const [incognitoMode, setIncognitoMode] = useLocalStorage({
- key: 'incognito-mode', defaultValue: false
+ key: 'incognito-mode', defaultValue: false, getInitialValueInEffect: false
});
const [settings, setSettings] = useState(null);
@@ -93,19 +91,17 @@ export function Layout() {
const [chats, setChats] = useState([]);
useEffect(() => {
- // fetch data
const dataFetch = async () => {
- const { items } = await detaDB.chats.fetch();
+ const { items } = await detaDB.chats.fetch(incognitoMode ? { private: true } : { 'private?ne': true });
setChats(items as unknown as Chat[]);
};
dataFetch();
- }, []);
+ }, [incognitoMode]);
const [prompts, setPrompts] = useState([]);
useEffect(() => {
- // fetch data
const dataFetch = async () => {
const { items } = await detaDB.prompts.fetch();
@@ -124,16 +120,21 @@ export function Layout() {
}, [router.state.location]);
const handleIncognito = () => {
- if (incognitoMode) {
- setIncognitoMode(false)
- if (colorScheme === 'dark') {
- toggleColorScheme()
- }
- } else {
- setIncognitoMode(true)
- if (colorScheme !== 'dark') {
- toggleColorScheme()
- }
+ const newValue = !incognitoMode
+
+ // if we are in a chat that doesn't match the mode, navigate to home view
+ if (chat && (chat?.private ?? false) !== newValue) {
+ setChat(null)
+ navigate({ to: `/`, replace: true });
+ }
+
+ setIncognitoMode(newValue)
+
+ const isDark = colorScheme === 'dark'
+ if (newValue && !isDark) {
+ toggleColorScheme()
+ } else if (!newValue && isDark) {
+ toggleColorScheme()
}
}
@@ -255,21 +256,26 @@ export function Layout() {
}
/>
-
+
{tab === "Chats" && }
{tab === "Prompts" && (
setTab("Chats")} />
)}
-
+
{tab === "Chats" && (
-
- {incognitoMode ? "New Incognito Chat" : "New Chat"}
-
+ <>
+ { incognitoMode && (
+
+ )}
+
+ {incognitoMode ? "New Private Chat" : "New Chat"}
+
+ >
)}
{tab === "Prompts" && }
-
+
}
diff --git a/src/db/index.ts b/src/db/index.ts
index 87b34af..39fcc91 100644
--- a/src/db/index.ts
+++ b/src/db/index.ts
@@ -13,6 +13,7 @@ export interface Chat {
writingStyle?: string | null;
writingFormat?: string | null;
model?: string | null;
+ private?: boolean;
createdAt: string;
}
diff --git a/src/routes/ChatRoute.tsx b/src/routes/ChatRoute.tsx
index 7969588..4db949d 100644
--- a/src/routes/ChatRoute.tsx
+++ b/src/routes/ChatRoute.tsx
@@ -198,7 +198,7 @@ export function ChatRoute() {
setChatStream(chatCompletionStream)
setSubmitting(false);
- if (chat?.description === "New Chat") {
+ if (chat?.description === "New Chat" || chat?.description === "New Private Chat") {
const res = await detaDB.messages.fetch({ chatId })
const messages = res.items as unknown as Message[]
// const messages = await db.messages
diff --git a/src/routes/IndexRoute.tsx b/src/routes/IndexRoute.tsx
index 4d0a07c..1f143e2 100644
--- a/src/routes/IndexRoute.tsx
+++ b/src/routes/IndexRoute.tsx
@@ -17,12 +17,13 @@ import {
} from "@tabler/icons-react";
import { Logo } from "../components/Logo";
import { SettingsModal } from "../components/SettingsModal";
-import { useSettings } from "../hooks/contexts";
+import { useIncognitoMode, useSettings } from "../hooks/contexts";
import { CreateChatButton } from "../components/CreateChatButton";
export function IndexRoute() {
const { settings } = useSettings()
const theme = useMantineTheme()
+ const { incognitoMode } = useIncognitoMode()
return (
<>
@@ -58,7 +59,7 @@ export function IndexRoute() {
{settings?.openAiApiKey && (
- Create a New Chat
+ {incognitoMode ? "Create a New Private Chat" : "Create a New Chat"}
)}
diff --git a/src/utils/floating.ts b/src/utils/floating.ts
new file mode 100644
index 0000000..4d3f815
--- /dev/null
+++ b/src/utils/floating.ts
@@ -0,0 +1,117 @@
+/**
+ * Float a number of things up on a page (hearts, flowers, 👌 ...)
+ *
+ * You give the options in an object.
+ *
+ * @module floating
+ * @param {string} [options.content='👌']
+ * the character or string to float
+ * @param {number} [options.number=1]
+ * the number of items
+ * @param {number} [options.duration=10]
+ * the amount of seconds it takes to float up
+ * @param {number|string} [options.repeat='infinite']
+ * the number of times you want the animation to repeat
+ * @param {string} [options.direction='normal']
+ * The
+ * animation-direction of the main animation
+ * @param {number|array} [options.sizes=2]
+ * The size (in em) of each element. Giving two values in an array will
+ * give a random size between those values.
+ */
+export default function floating(
+ {
+ content = '👌',
+ number = 1,
+ duration = 10,
+ repeat = 1,
+ direction = 'normal',
+ size = 2,
+ elem = document.body
+ } = {}
+ ) {
+ const style = document.createElement('style');
+ style.id = 'floating-style';
+
+ if (!document.getElementById('floating-style')) {
+ document.head.appendChild(style);
+ }
+
+ const MAX = 201;
+
+ const styles = `
+ .float-container {
+ width: 100vw;
+ height: 100vh;
+ overflow: hidden;
+ position: absolute;
+ top: 0;
+ left: 0;
+ pointer-events: none;
+ z-index: 999999;
+ }
+
+ .float-container div * {
+ width: 1em;
+ height: 1em
+ }
+
+ @keyframes float{
+ ${Array.apply(null, { length: MAX + 1 })
+ .map((v, x) => ({
+ percent: x * 100 / MAX,
+ width: Math.sin(x),
+ height: 110 + x * (-120 / MAX),
+ }))
+ .map(
+ ({ percent, width, height }) =>
+ `${percent}% {
+ transform: translate(
+ ${width}vw,
+ ${height}vh
+ )
+ }`
+ )
+ .join('')}
+ }`;
+
+ document.getElementById('floating-style').innerHTML = styles;
+
+ const container = document.createElement('div');
+
+ container.className = 'float-container';
+
+ const _size = Array.isArray(size)
+ ? Math.floor(Math.random() * (size[1] - size[0] + 1)) + size[0]
+ : size;
+
+ for (let i = 0; i < number; i++) {
+ const floater = document.createElement('div');
+ floater.innerHTML = content;
+
+ floater.style.cssText = `
+ position: absolute;
+ left: 0;
+ font-size: ${_size}em;
+ transform: translateY(110vh);
+ animation:
+ float
+ ${duration}s
+ linear
+ ${i * Math.random()}s
+ ${repeat}
+ ${direction};
+ margin-left: ${Math.random() * 100}vw;`;
+
+ floater.addEventListener('animationend', e => {
+ if (e.animationName === 'float') {
+ container.removeChild(floater);
+ }
+ });
+
+ container.appendChild(floater);
+ }
+
+ elem.appendChild(container);
+ }
+
\ No newline at end of file