Increase resiliency, and relax deletion/creation of new chats.

This commit is contained in:
Enrico Ros
2024-02-06 16:42:49 -08:00
parent f82ac7a476
commit 9f222caadf
5 changed files with 47 additions and 52 deletions
+20 -22
View File
@@ -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(() =>
<ChatPageMenuItems
isMobile={isMobile}
conversationId={focusedConversationId}
disableItems={!focusedConversationId || isFocusedChatEmpty}
hasConversations={!areChatsEmpty}
isConversationEmpty={isFocusedChatEmpty}
isMessageSelectionMode={isMessageSelectionMode}
onConversationBranch={handleConversationBranch}
onConversationClear={handleConversationClear}
+3 -5
View File
@@ -112,8 +112,7 @@ function ChatDrawer(props: {
// derived state
const selectConversationsCount = chatNavItems.length;
const nonEmptyChats = selectConversationsCount > 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: {
<ChatDrawerItemMemo
key={'nav-' + item.conversationId}
item={item}
isLonely={singleChat}
showSymbols={showSymbols}
bottomBarBasis={(softMaxReached || debouncedSearchQuery) ? bottomBarBasis : 0}
onConversationActivate={handleConversationActivate}
+1 -3
View File
@@ -34,7 +34,6 @@ export const FadeInButton = styled(IconButton)({
export const ChatDrawerItemMemo = React.memo(ChatDrawerItem, (prev, next) =>
// 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: {
<Box sx={{ flex: 1 }} />
{/* Delete [armed, arming] buttons */}
{!props.isLonely && !searchFrequency && <>
{!searchFrequency && <>
{deleteArmed && (
<Tooltip disableInteractive title='Confirm Deletion'>
<FadeInButton key='btn-del' variant='solid' color='success' size='sm' onClick={handleConversationDelete} sx={{ opacity: 1 }}>
@@ -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: {
<ListDivider />
<MenuItem disabled={disabled} onClick={handleConversationBranch}>
<MenuItem disabled={props.disableItems} onClick={handleConversationBranch}>
<ListItemDecorator><ForkRightIcon /></ListItemDecorator>
Branch
</MenuItem>
<MenuItem disabled={disabled} onClick={handleToggleMessageSelectionMode}>
<MenuItem disabled={props.disableItems} onClick={handleToggleMessageSelectionMode}>
<ListItemDecorator>{props.isMessageSelectionMode ? <CheckBoxOutlinedIcon /> : <CheckBoxOutlineBlankOutlinedIcon />}</ListItemDecorator>
<span style={props.isMessageSelectionMode ? { fontWeight: 800 } : {}}>
Cleanup ...
</span>
</MenuItem>
<MenuItem disabled={disabled} onClick={handleConversationFlatten}>
<MenuItem disabled={props.disableItems} onClick={handleConversationFlatten}>
<ListItemDecorator><CompressIcon color='success' /></ListItemDecorator>
Compress ...
</MenuItem>
<ListDivider inset='startContent' />
<MenuItem disabled={disabled} onClick={handleConversationClear}>
<MenuItem disabled={props.disableItems} onClick={handleConversationClear}>
<ListItemDecorator><ClearIcon /></ListItemDecorator>
<Box sx={{ flexGrow: 1, display: 'flex', justifyContent: 'space-between', gap: 1 }}>
Reset Chat
{!disabled && <KeyStroke combo='Ctrl + Alt + X' />}
{!props.disableItems && <KeyStroke combo='Ctrl + Alt + X' />}
</Box>
</MenuItem>
+17 -13
View File
@@ -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<ConversationsStore>()(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<ConversationsStore>()(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,