Kirjava commited on
Commit
a7b1f50
·
unverified ·
1 Parent(s): 1e11ab6

feat: navigate away when deleting current chat (#44)

Browse files
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
- <div className="flex-1" />
 
 
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, deleteId, getAll, type ChatHistoryItem } from '~/lib/persistence';
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
- deleteId(db, item.id)
56
- .then(() => loadEntries())
 
 
 
 
 
 
 
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 deleteId(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');
 
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 (chatId) {
44
- getMessages(db, chatId)
45
  .then((storedMessages) => {
46
  if (storedMessages && storedMessages.messages.length > 0) {
47
  setInitialMessages(storedMessages.messages);
48
  setUrlId(storedMessages.urlId);
49
- setDescription(storedMessages.description);
50
- setChatId(storedMessages.id);
51
  } else {
52
  navigate(`/`, { replace: true });
53
  }
@@ -61,7 +62,7 @@ export function useChatHistory() {
61
  }, []);
62
 
63
  return {
64
- ready: !chatId || 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
- setDescription(firstArtifact?.title);
82
  }
83
 
84
- if (initialMessages.length === 0) {
85
- if (!entryId) {
86
- const nextId = await getNextId(db);
87
-
88
- await setMessages(db, nextId, messages, urlId, description);
89
 
90
- setEntryId(nextId);
91
 
92
- if (!urlId) {
93
- navigateChat(nextId);
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
  }