mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
Increase resiliency, and relax deletion/creation of new chats.
This commit is contained in:
+20
-22
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user