diff --git a/src/apps/chat/AppChat.tsx b/src/apps/chat/AppChat.tsx index 0f1a470ae..23c58da2a 100644 --- a/src/apps/chat/AppChat.tsx +++ b/src/apps/chat/AppChat.tsx @@ -88,6 +88,7 @@ export function AppChat() { const { title: focusedChatTitle, chatIdx: focusedChatNumber, + isNoChat: isNoChat, isChatEmpty: isFocusedChatEmpty, areChatsEmpty, newConversationId, @@ -337,27 +338,24 @@ export function AppChat() { const handleConversationClear = React.useCallback((conversationId: DConversationId) => setClearConversationId(conversationId), []); - const handleConfirmedDeleteConversation = () => { - if (deleteConversationId) { - let nextConversationId: DConversationId | null; - if (deleteConversationId === SPECIAL_ID_WIPE_ALL) - nextConversationId = wipeAllConversations(focusedSystemPurposeId ?? undefined, activeFolderId); - else - nextConversationId = deleteConversation(deleteConversationId); - setFocusedConversationId(nextConversationId); - setDeleteConversationId(null); - } - }; - const handleConversationsDeleteAll = React.useCallback(() => setDeleteConversationId(SPECIAL_ID_WIPE_ALL), []); - const handleConversationDelete = React.useCallback( - (conversationId: DConversationId, bypassConfirmation: boolean) => { - if (bypassConfirmation) setFocusedConversationId(deleteConversation(conversationId)); - else setDeleteConversationId(conversationId); - }, - [deleteConversation, setFocusedConversationId], - ); + const handleConversationDelete = React.useCallback((conversationId: DConversationId, bypassConfirmation: boolean) => { + // show dialog if not bypassed + if (!bypassConfirmation) + return setDeleteConversationId(conversationId); + + const nextConversationId = conversationId === SPECIAL_ID_WIPE_ALL + ? wipeAllConversations(activeFolderId /* restricted to this folder (or null for all) */, focusedSystemPurposeId ?? undefined) + : deleteConversation(conversationId, focusedSystemPurposeId ?? undefined); + setFocusedConversationId(nextConversationId); + + setDeleteConversationId(null); + }, [activeFolderId, deleteConversation, focusedSystemPurposeId, setFocusedConversationId, wipeAllConversations]); + + const handleConfirmedDeleteConversation = React.useCallback(() => { + deleteConversationId && handleConversationDelete(deleteConversationId, true); + }, [deleteConversationId, handleConversationDelete]); // Shortcuts @@ -393,7 +391,7 @@ export function AppChat() { activeConversationId={focusedConversationId} activeFolderId={activeFolderId} chatPanesConversationIds={chatPanes.map(pane => pane.conversationId).filter(Boolean) as DConversationId[]} - disableNewButton={isFocusedChatEmpty} + disableNewButton={isFocusedChatEmpty && !isNoChat} onConversationActivate={setFocusedConversationId} onConversationDelete={handleConversationDelete} onConversationExportDialog={handleConversationExport} @@ -402,15 +400,15 @@ export function AppChat() { onConversationsDeleteAll={handleConversationsDeleteAll} setActiveFolderId={setActiveFolderId} />, - [activeFolderId, chatPanes, focusedConversationId, handleConversationDelete, handleConversationExport, handleConversationImportDialog, handleConversationNew, handleConversationsDeleteAll, isFocusedChatEmpty, setFocusedConversationId], + [activeFolderId, chatPanes, focusedConversationId, handleConversationDelete, handleConversationExport, handleConversationImportDialog, handleConversationNew, handleConversationsDeleteAll, isFocusedChatEmpty, isNoChat, setFocusedConversationId], ); const menuItems = React.useMemo(() => 1 || (selectConversationsCount === 1 && !chatNavItems[0].isEmpty); - const singleChat = selectConversationsCount === 1; - const softMaxReached = selectConversationsCount >= 10; + const softMaxReached = selectConversationsCount >= 40; const handleButtonNew = React.useCallback(() => { @@ -130,8 +129,8 @@ function ChatDrawer(props: { const handleConversationDelete = React.useCallback((conversationId: DConversationId) => { - !singleChat && conversationId && onConversationDelete(conversationId, true); - }, [onConversationDelete, singleChat]); + conversationId && onConversationDelete(conversationId, true); + }, [onConversationDelete]); // Folder change request @@ -288,7 +287,6 @@ function ChatDrawer(props: { // usign a custom function because `ChatNavigationItemData` is a complex object and memo won't work isDeepEqual(prev.item, next.item) && - prev.isLonely === next.isLonely && prev.showSymbols === next.showSymbols && prev.bottomBarBasis === next.bottomBarBasis && prev.onConversationActivate === next.onConversationActivate && @@ -65,7 +64,6 @@ export interface FolderChangeRequest { function ChatDrawerItem(props: { // NOTE: always update the Memo comparison if you add or remove props item: ChatNavigationItemData, - isLonely: boolean, showSymbols: boolean, bottomBarBasis: number, onConversationActivate: (conversationId: DConversationId, closeMenu: boolean) => void, @@ -307,7 +305,7 @@ function ChatDrawerItem(props: { {/* Delete [armed, arming] buttons */} - {!props.isLonely && !searchFrequency && <> + {!searchFrequency && <> {deleteArmed && ( diff --git a/src/apps/chat/components/ChatPageMenuItems.tsx b/src/apps/chat/components/ChatPageMenuItems.tsx index a3180f1cc..c2d29743c 100644 --- a/src/apps/chat/components/ChatPageMenuItems.tsx +++ b/src/apps/chat/components/ChatPageMenuItems.tsx @@ -24,8 +24,8 @@ import { usePaneDuplicateOrClose } from './panes/usePanesManager'; export function ChatPageMenuItems(props: { isMobile: boolean, conversationId: DConversationId | null, + disableItems: boolean, hasConversations: boolean, - isConversationEmpty: boolean, isMessageSelectionMode: boolean, onConversationBranch: (conversationId: DConversationId, messageId: string | null) => void, onConversationClear: (conversationId: DConversationId) => void, @@ -38,9 +38,6 @@ export function ChatPageMenuItems(props: { const { canAddPane, isMultiPane, duplicateFocusedPane, removeOtherPanes } = usePaneDuplicateOrClose(); const [showSystemMessages, setShowSystemMessages] = useChatShowSystemMessages(); - // derived state - const disabled = !props.conversationId || props.isConversationEmpty; - const handleToggleMultiPane = React.useCallback((_event: React.MouseEvent) => { if (isMultiPane) @@ -118,30 +115,30 @@ export function ChatPageMenuItems(props: { - + Branch - + {props.isMessageSelectionMode ? : } Cleanup ... - + Compress ... - + Reset Chat - {!disabled && } + {!props.disableItems && } diff --git a/src/common/state/store-chats.ts b/src/common/state/store-chats.ts index 552576c0e..dc36c27e5 100644 --- a/src/common/state/store-chats.ts +++ b/src/common/state/store-chats.ts @@ -119,8 +119,8 @@ interface ChatActions { prependNewConversation: (personaId: SystemPurposeId | undefined) => DConversationId; importConversation: (conversation: DConversation, preventClash: boolean) => DConversationId; branchConversation: (conversationId: DConversationId, messageId: string | null) => DConversationId | null; - deleteConversation: (conversationId: DConversationId) => DConversationId | null; - wipeAllConversations: (personaId: SystemPurposeId | undefined, folderId: string | null) => DConversationId; + deleteConversation: (conversationId: DConversationId, newConversationPersonaId?: SystemPurposeId) => DConversationId; + wipeAllConversations: (folderId: string | null, newConversationPersonaId?: SystemPurposeId) => DConversationId; // within a conversation startTyping: (conversationId: string, abortController: AbortController | null) => void; @@ -235,7 +235,7 @@ export const useChatStore = create()(devtools( return branched.id; }, - deleteConversation: (conversationId: DConversationId): DConversationId | null => { + deleteConversation: (conversationId: DConversationId, newConversationPersonaId?: SystemPurposeId): DConversationId => { let { conversations } = _get(); // abort pending requests on this conversation @@ -245,37 +245,39 @@ export const useChatStore = create()(devtools( // remove from the list conversations = conversations.filter(_c => _c.id !== conversationId); + + // create a new conversation if there are no more + if (!conversations.length) + conversations.push(createDConversation(newConversationPersonaId)); + _set({ conversations, }); // return the next conversation Id in line, if valid - return conversations.length - ? conversations[(cIndex >= 0 && cIndex < conversations.length) ? cIndex : conversations.length - 1].id - : null; + return conversations[(cIndex >= 0 && cIndex < conversations.length) ? cIndex : conversations.length - 1].id; }, - wipeAllConversations: (personaId: SystemPurposeId | undefined, folderId: string | null): DConversationId => { + wipeAllConversations: (onlyInFolderId: string | null, newConversationPersonaId?: SystemPurposeId): DConversationId => { let { conversations } = _get(); // abort any pending requests on all conversations conversations.forEach(conversation => conversation.abortController?.abort()); // If a folder is selected, only delete conversations in that folder - if (folderId) { + if (onlyInFolderId) { const { folders, removeConversationFromFolder } = useFolderStore.getState(); - const folderConversations = folders.find(folder => folder.id === folderId)?.conversationIds || []; + const folderConversations = folders.find(folder => folder.id === onlyInFolderId)?.conversationIds || []; conversations = conversations.filter(conversation => !folderConversations.includes(conversation.id)); // Update the folder to remove the deleted conversation IDs - folderConversations.forEach(conversationId => removeConversationFromFolder(folderId, conversationId)); - + folderConversations.forEach(conversationId => removeConversationFromFolder(onlyInFolderId, conversationId)); } - const conversation = createDConversation(personaId); + const conversation = createDConversation(newConversationPersonaId); _set({ - conversations: folderId ? conversations : [conversation], + conversations: onlyInFolderId ? conversations : [conversation], }); return conversation.id; @@ -556,6 +558,7 @@ export const useConversation = (conversationId: DConversationId | null) => useCh const conversation = conversationId ? conversations.find(_c => _c.id === conversationId) ?? null : null; const title = conversation ? conversationTitle(conversation) : null; const chatIdx = conversation ? conversations.findIndex(_c => _c.id === conversation.id) : -1; + const isNoChat = chatIdx === -1; const isChatEmpty = conversation ? !conversation.messages.length : true; const areChatsEmpty = isChatEmpty && conversations.length < 2; const newConversationId: DConversationId | null = (conversations.length && !conversations[0].messages.length) ? conversations[0].id : null; @@ -564,6 +567,7 @@ export const useConversation = (conversationId: DConversationId | null) => useCh return { title, chatIdx, + isNoChat, isChatEmpty, areChatsEmpty, newConversationId,