Kirjava
commited on
feat: navigate away when deleting current chat (#44)
Browse files- package.json +1 -0
- packages/bolt/app/components/header/Header.tsx +4 -1
- packages/bolt/app/components/sidebar/Menu.client.tsx +10 -3
- packages/bolt/app/lib/persistence/ChatDescription.client.tsx +6 -0
- packages/bolt/app/lib/persistence/db.ts +1 -1
- packages/bolt/app/lib/persistence/useChatHistory.ts +19 -24
package.json
CHANGED
|
@@ -5,6 +5,7 @@
|
|
| 5 |
"scripts": {
|
| 6 |
"playground:dev": "pnpm run --filter=playground dev",
|
| 7 |
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .",
|
|
|
|
| 8 |
"build": "pnpm run -r build",
|
| 9 |
"test": "pnpm run -r test",
|
| 10 |
"typecheck": "pnpm run -r typecheck"
|
|
|
|
| 5 |
"scripts": {
|
| 6 |
"playground:dev": "pnpm run --filter=playground dev",
|
| 7 |
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .",
|
| 8 |
+
"lint:fix": "npm run lint -- --fix",
|
| 9 |
"build": "pnpm run -r build",
|
| 10 |
"test": "pnpm run -r test",
|
| 11 |
"typecheck": "pnpm run -r typecheck"
|
packages/bolt/app/components/header/Header.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { ClientOnly } from 'remix-utils/client-only';
|
|
| 3 |
import { chatStore } from '~/lib/stores/chat';
|
| 4 |
import { classNames } from '~/utils/classNames';
|
| 5 |
import { HeaderActionButtons } from './HeaderActionButtons.client';
|
|
|
|
| 6 |
|
| 7 |
export function Header() {
|
| 8 |
const chat = useStore(chatStore);
|
|
@@ -23,7 +24,9 @@ export function Header() {
|
|
| 23 |
<span className="i-bolt:logo-text?mask w-[46px] inline-block" />
|
| 24 |
</a>
|
| 25 |
</div>
|
| 26 |
-
<
|
|
|
|
|
|
|
| 27 |
{chat.started && (
|
| 28 |
<ClientOnly>
|
| 29 |
{() => (
|
|
|
|
| 3 |
import { chatStore } from '~/lib/stores/chat';
|
| 4 |
import { classNames } from '~/utils/classNames';
|
| 5 |
import { HeaderActionButtons } from './HeaderActionButtons.client';
|
| 6 |
+
import { ChatDescription } from '~/lib/persistence/ChatDescription.client';
|
| 7 |
|
| 8 |
export function Header() {
|
| 9 |
const chat = useStore(chatStore);
|
|
|
|
| 24 |
<span className="i-bolt:logo-text?mask w-[46px] inline-block" />
|
| 25 |
</a>
|
| 26 |
</div>
|
| 27 |
+
<span className="flex-1 px-4 truncate text-center text-bolt-elements-textPrimary">
|
| 28 |
+
<ClientOnly>{() => <ChatDescription />}</ClientOnly>
|
| 29 |
+
</span>
|
| 30 |
{chat.started && (
|
| 31 |
<ClientOnly>
|
| 32 |
{() => (
|
packages/bolt/app/components/sidebar/Menu.client.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { toast } from 'react-toastify';
|
|
| 4 |
import { Dialog, DialogButton, DialogDescription, DialogRoot, DialogTitle } from '~/components/ui/Dialog';
|
| 5 |
import { IconButton } from '~/components/ui/IconButton';
|
| 6 |
import { ThemeSwitch } from '~/components/ui/ThemeSwitch';
|
| 7 |
-
import { db,
|
| 8 |
import { cubicEasingFn } from '~/utils/easings';
|
| 9 |
import { logger } from '~/utils/logger';
|
| 10 |
import { HistoryItem } from './HistoryItem';
|
|
@@ -52,8 +52,15 @@ export function Menu() {
|
|
| 52 |
event.preventDefault();
|
| 53 |
|
| 54 |
if (db) {
|
| 55 |
-
|
| 56 |
-
.then(() =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
.catch((error) => {
|
| 58 |
toast.error('Failed to delete conversation');
|
| 59 |
logger.error(error);
|
|
|
|
| 4 |
import { Dialog, DialogButton, DialogDescription, DialogRoot, DialogTitle } from '~/components/ui/Dialog';
|
| 5 |
import { IconButton } from '~/components/ui/IconButton';
|
| 6 |
import { ThemeSwitch } from '~/components/ui/ThemeSwitch';
|
| 7 |
+
import { db, deleteById, getAll, chatId, type ChatHistoryItem } from '~/lib/persistence';
|
| 8 |
import { cubicEasingFn } from '~/utils/easings';
|
| 9 |
import { logger } from '~/utils/logger';
|
| 10 |
import { HistoryItem } from './HistoryItem';
|
|
|
|
| 52 |
event.preventDefault();
|
| 53 |
|
| 54 |
if (db) {
|
| 55 |
+
deleteById(db, item.id)
|
| 56 |
+
.then(() => {
|
| 57 |
+
loadEntries();
|
| 58 |
+
|
| 59 |
+
if (chatId.get() === item.id) {
|
| 60 |
+
// hard page navigation to clear the stores
|
| 61 |
+
window.location.pathname = '/';
|
| 62 |
+
}
|
| 63 |
+
})
|
| 64 |
.catch((error) => {
|
| 65 |
toast.error('Failed to delete conversation');
|
| 66 |
logger.error(error);
|
packages/bolt/app/lib/persistence/ChatDescription.client.tsx
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useStore } from '@nanostores/react';
|
| 2 |
+
import { description } from './useChatHistory';
|
| 3 |
+
|
| 4 |
+
export function ChatDescription() {
|
| 5 |
+
return useStore(description);
|
| 6 |
+
}
|
packages/bolt/app/lib/persistence/db.ts
CHANGED
|
@@ -92,7 +92,7 @@ export async function getMessagesById(db: IDBDatabase, id: string): Promise<Chat
|
|
| 92 |
});
|
| 93 |
}
|
| 94 |
|
| 95 |
-
export async function
|
| 96 |
return new Promise((resolve, reject) => {
|
| 97 |
const transaction = db.transaction('chats', 'readwrite');
|
| 98 |
const store = transaction.objectStore('chats');
|
|
|
|
| 92 |
});
|
| 93 |
}
|
| 94 |
|
| 95 |
+
export async function deleteById(db: IDBDatabase, id: string): Promise<void> {
|
| 96 |
return new Promise((resolve, reject) => {
|
| 97 |
const transaction = db.transaction('chats', 'readwrite');
|
| 98 |
const store = transaction.objectStore('chats');
|
packages/bolt/app/lib/persistence/useChatHistory.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import { useLoaderData, useNavigate } from '@remix-run/react';
|
|
|
|
|
|
|
| 2 |
import type { Message } from 'ai';
|
| 3 |
-
import { useEffect, useState } from 'react';
|
| 4 |
import { toast } from 'react-toastify';
|
| 5 |
import { AnalyticsAction, sendAnalyticsEvent } from '~/lib/analytics';
|
| 6 |
import { workbenchStore } from '~/lib/stores/workbench';
|
|
@@ -18,16 +19,16 @@ const persistenceEnabled = !import.meta.env.VITE_DISABLE_PERSISTENCE;
|
|
| 18 |
|
| 19 |
export const db = persistenceEnabled ? await openDatabase() : undefined;
|
| 20 |
|
|
|
|
|
|
|
|
|
|
| 21 |
export function useChatHistory() {
|
| 22 |
const navigate = useNavigate();
|
| 23 |
const { id: mixedId } = useLoaderData<{ id?: string }>();
|
| 24 |
|
| 25 |
-
const [chatId, setChatId] = useState(mixedId);
|
| 26 |
const [initialMessages, setInitialMessages] = useState<Message[]>([]);
|
| 27 |
const [ready, setReady] = useState<boolean>(false);
|
| 28 |
-
const [entryId, setEntryId] = useState<string | undefined>();
|
| 29 |
const [urlId, setUrlId] = useState<string | undefined>();
|
| 30 |
-
const [description, setDescription] = useState<string | undefined>();
|
| 31 |
|
| 32 |
useEffect(() => {
|
| 33 |
if (!db) {
|
|
@@ -40,14 +41,14 @@ export function useChatHistory() {
|
|
| 40 |
return;
|
| 41 |
}
|
| 42 |
|
| 43 |
-
if (
|
| 44 |
-
getMessages(db,
|
| 45 |
.then((storedMessages) => {
|
| 46 |
if (storedMessages && storedMessages.messages.length > 0) {
|
| 47 |
setInitialMessages(storedMessages.messages);
|
| 48 |
setUrlId(storedMessages.urlId);
|
| 49 |
-
|
| 50 |
-
|
| 51 |
} else {
|
| 52 |
navigate(`/`, { replace: true });
|
| 53 |
}
|
|
@@ -61,7 +62,7 @@ export function useChatHistory() {
|
|
| 61 |
}, []);
|
| 62 |
|
| 63 |
return {
|
| 64 |
-
ready: !
|
| 65 |
initialMessages,
|
| 66 |
storeMessageHistory: async (messages: Message[]) => {
|
| 67 |
if (!db || messages.length === 0) {
|
|
@@ -77,27 +78,21 @@ export function useChatHistory() {
|
|
| 77 |
setUrlId(urlId);
|
| 78 |
}
|
| 79 |
|
| 80 |
-
if (!description && firstArtifact?.title) {
|
| 81 |
-
|
| 82 |
}
|
| 83 |
|
| 84 |
-
if (initialMessages.length === 0) {
|
| 85 |
-
|
| 86 |
-
const nextId = await getNextId(db);
|
| 87 |
-
|
| 88 |
-
await setMessages(db, nextId, messages, urlId, description);
|
| 89 |
|
| 90 |
-
|
| 91 |
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
}
|
| 95 |
-
} else {
|
| 96 |
-
await setMessages(db, entryId, messages, urlId, description);
|
| 97 |
}
|
| 98 |
-
} else {
|
| 99 |
-
await setMessages(db, chatId as string, messages, urlId, description);
|
| 100 |
}
|
|
|
|
|
|
|
| 101 |
},
|
| 102 |
};
|
| 103 |
}
|
|
|
|
| 1 |
import { useLoaderData, useNavigate } from '@remix-run/react';
|
| 2 |
+
import { useState, useEffect } from 'react';
|
| 3 |
+
import { atom } from 'nanostores';
|
| 4 |
import type { Message } from 'ai';
|
|
|
|
| 5 |
import { toast } from 'react-toastify';
|
| 6 |
import { AnalyticsAction, sendAnalyticsEvent } from '~/lib/analytics';
|
| 7 |
import { workbenchStore } from '~/lib/stores/workbench';
|
|
|
|
| 19 |
|
| 20 |
export const db = persistenceEnabled ? await openDatabase() : undefined;
|
| 21 |
|
| 22 |
+
export const chatId = atom<string | undefined>(undefined);
|
| 23 |
+
export const description = atom<string | undefined>(undefined);
|
| 24 |
+
|
| 25 |
export function useChatHistory() {
|
| 26 |
const navigate = useNavigate();
|
| 27 |
const { id: mixedId } = useLoaderData<{ id?: string }>();
|
| 28 |
|
|
|
|
| 29 |
const [initialMessages, setInitialMessages] = useState<Message[]>([]);
|
| 30 |
const [ready, setReady] = useState<boolean>(false);
|
|
|
|
| 31 |
const [urlId, setUrlId] = useState<string | undefined>();
|
|
|
|
| 32 |
|
| 33 |
useEffect(() => {
|
| 34 |
if (!db) {
|
|
|
|
| 41 |
return;
|
| 42 |
}
|
| 43 |
|
| 44 |
+
if (mixedId) {
|
| 45 |
+
getMessages(db, mixedId)
|
| 46 |
.then((storedMessages) => {
|
| 47 |
if (storedMessages && storedMessages.messages.length > 0) {
|
| 48 |
setInitialMessages(storedMessages.messages);
|
| 49 |
setUrlId(storedMessages.urlId);
|
| 50 |
+
description.set(storedMessages.description);
|
| 51 |
+
chatId.set(storedMessages.id);
|
| 52 |
} else {
|
| 53 |
navigate(`/`, { replace: true });
|
| 54 |
}
|
|
|
|
| 62 |
}, []);
|
| 63 |
|
| 64 |
return {
|
| 65 |
+
ready: !mixedId || ready,
|
| 66 |
initialMessages,
|
| 67 |
storeMessageHistory: async (messages: Message[]) => {
|
| 68 |
if (!db || messages.length === 0) {
|
|
|
|
| 78 |
setUrlId(urlId);
|
| 79 |
}
|
| 80 |
|
| 81 |
+
if (!description.get() && firstArtifact?.title) {
|
| 82 |
+
description.set(firstArtifact?.title);
|
| 83 |
}
|
| 84 |
|
| 85 |
+
if (initialMessages.length === 0 && !chatId.get()) {
|
| 86 |
+
const nextId = await getNextId(db);
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
+
chatId.set(nextId);
|
| 89 |
|
| 90 |
+
if (!urlId) {
|
| 91 |
+
navigateChat(nextId);
|
|
|
|
|
|
|
|
|
|
| 92 |
}
|
|
|
|
|
|
|
| 93 |
}
|
| 94 |
+
|
| 95 |
+
await setMessages(db, chatId.get() as string, messages, urlId, description.get());
|
| 96 |
},
|
| 97 |
};
|
| 98 |
}
|