Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Folders for conversation categorization, improving navigation and manageability. #296

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ffd62f7
Add and Show Folders
joriskalz Dec 21, 2023
e4a45fa
first folder structure
joriskalz Dec 22, 2023
d9afbed
Renamed components to reflect existing structure
joriskalz Dec 22, 2023
37dbfb6
Delete confirmation buttons
joriskalz Dec 22, 2023
ddc0566
formatting
joriskalz Dec 22, 2023
3f843b7
select newly created folder
joriskalz Dec 22, 2023
bcc6cf0
allow creations of new chats for empty folders
joriskalz Dec 22, 2023
e5adfb1
Cleaning things up
joriskalz Dec 22, 2023
853ff1d
Merge branch 'enricoros:main' into feature-folders
joriskalz Dec 22, 2023
0115125
Merge branch 'enricoros:main' into feature-folders
joriskalz Dec 22, 2023
a9c1cb7
Enable editing of folder names
joriskalz Dec 22, 2023
6f1504f
Initial new idea
joriskalz Dec 24, 2023
a93d252
Added ChatNavigationItemMemo again
joriskalz Dec 24, 2023
401f9c7
We need more colors
joriskalz Dec 24, 2023
75efd60
ConversationList as a new file
joriskalz Dec 25, 2023
8dffdd5
Add New Folder button
joriskalz Dec 25, 2023
43579ef
Enable Deletion of Folders
joriskalz Dec 25, 2023
9d25492
aider: Modified delete all conversations function to only delete conv…
joriskalz Dec 25, 2023
d2192ab
Modified delete all conversations
joriskalz Dec 25, 2023
d133d04
Delete All dialog folder aware
joriskalz Dec 26, 2023
2f723f1
reduced required parameters
joriskalz Dec 26, 2023
768b2a1
Enable new button
joriskalz Dec 26, 2023
e7b09e0
Add Folder Drop Down to the AppBar
joriskalz Dec 26, 2023
996279a
fix edit behaviour
joriskalz Dec 26, 2023
5f84ec7
removed console.log
joriskalz Dec 26, 2023
d866480
Close menu after color selection
joriskalz Dec 26, 2023
1287705
Remove focus border after editing
joriskalz Dec 26, 2023
9587b40
Inline new folder
joriskalz Dec 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"plantuml-encoder": "^1.4.0",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.2.0",
"react-katex": "^3.0.1",
"react-markdown": "^9.0.1",
Expand All @@ -55,6 +56,7 @@
"@types/plantuml-encoder": "^1.4.2",
"@types/prismjs": "^1.26.3",
"@types/react": "^18.2.45",
"@types/react-beautiful-dnd": "^13.1.7",
"@types/react-dom": "^18.2.17",
"@types/react-katex": "^3.0.4",
"@types/react-timeago": "^4.1.6",
Expand Down
80 changes: 64 additions & 16 deletions src/apps/chat/AppChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { useChatLLM, useModelsStore } from '~/modules/llms/store-llms';
import { ConfirmationModal } from '~/common/components/ConfirmationModal';
import { GlobalShortcutItem, ShortcutKeyName, useGlobalShortcuts } from '~/common/components/useGlobalShortcut';
import { addSnackbar, removeSnackbar } from '~/common/components/useSnackbarsStore';
import { createDMessage, DConversationId, DMessage, getConversation, useConversation } from '~/common/state/store-chats';
import { createDMessage, DConversationId, DMessage, getConversation, useChatStore, useConversation } from '~/common/state/store-chats';
import { openLayoutLLMOptions, useLayoutPluggable } from '~/common/layout/store-applayout';
import { useUXLabsStore } from '~/common/state/store-ux-labs';

Expand All @@ -35,6 +35,7 @@ import { runAssistantUpdatingState } from './editors/chat-stream';
import { runBrowseUpdatingState } from './editors/browse-load';
import { runImageGenerationUpdatingState } from './editors/image-generate';
import { runReActUpdatingState } from './editors/react-tangent';
import { useFolderStore } from '~/common/state/store-folders';


/**
Expand All @@ -60,9 +61,16 @@ export function AppChat() {
const [flattenConversationId, setFlattenConversationId] = React.useState<DConversationId | null>(null);
const showNextTitle = React.useRef(false);
const composerTextAreaRef = React.useRef<HTMLTextAreaElement>(null);
const [selectedFolderId, setSelectedFolderId] = React.useState<string | null>(null);


// external state
const { chatLLM } = useChatLLM();
const addConversationToFolder = useFolderStore((state) => state.addConversationToFolder);

// Get the list of conversations from the store
const conversations = useChatStore(state => state.conversations);


const {
chatPanes,
Expand Down Expand Up @@ -246,18 +254,49 @@ export function AppChat() {
await speakText(text);
};

// Function to check if the selected folder has an empty chat
const doesSelectedFolderHaveEmptyChat = (selectedFolderId: string | null, conversations: any[]) => {
// If no folder is selected (default folder), check if there is an empty chat globally
if (selectedFolderId === null) {
// get all conversations that are not in a folder
const conversationsNotInFolder = conversations.filter(convo => convo.folderId === null);
return conversationsNotInFolder.some(convo => convo.messages.length === 0);
}


// Retrieve the folder's conversations
const folderConversations = useFolderStore.getState().folders.find(folder => folder.id === selectedFolderId)?.conversationIds || [];

// Check if any of the folder's conversations are empty
return folderConversations.some(convoId => {
const convo = conversations.find(conversation => conversation.id === convoId);
return convo && convo.messages.length === 0;
});
};


// Chat actions
// Determine if the "New" button should be disabled
const disableNewButton = doesSelectedFolderHaveEmptyChat(selectedFolderId, conversations);

const handleConversationNew = React.useCallback(() => {
// activate an existing new conversation if present, or create another
setFocusedConversationId(newConversationId
? newConversationId
: prependNewConversation(focusedSystemPurposeId ?? undefined),
);
composerTextAreaRef.current?.focus();
}, [focusedSystemPurposeId, newConversationId, prependNewConversation, setFocusedConversationId]);

const handleConversationNew = React.useCallback(() => {
// Create a new conversation
const newConversationId = prependNewConversation(focusedSystemPurposeId ?? undefined);

// If a folder is selected, add the new conversation to the folder
if (selectedFolderId) {
addConversationToFolder(selectedFolderId, newConversationId);
}

// Focus on the new conversation
setFocusedConversationId(newConversationId);
composerTextAreaRef.current?.focus();

// Return the new conversation ID
return newConversationId;
}, [focusedSystemPurposeId, prependNewConversation, setFocusedConversationId, selectedFolderId, addConversationToFolder]);


const handleConversationImportDialog = () => setTradeConfig({ dir: 'import' });

const handleConversationExport = (conversationId: DConversationId | null) => setTradeConfig({ dir: 'export', conversationId });
Expand Down Expand Up @@ -299,15 +338,16 @@ export function AppChat() {
if (deleteConversationId) {
let nextConversationId: DConversationId | null;
if (deleteConversationId === SPECIAL_ID_WIPE_ALL)
nextConversationId = wipeAllConversations(focusedSystemPurposeId ?? undefined);
nextConversationId = wipeAllConversations(focusedSystemPurposeId ?? undefined, selectedFolderId);
else
nextConversationId = deleteConversation(deleteConversationId);
setFocusedConversationId(nextConversationId);
setDeleteConversationId(null);
}
};


const handleConversationsDeleteAll = () => setDeleteConversationId(SPECIAL_ID_WIPE_ALL);
const handleConversationsDeleteAll = (folderId: string | null) => setDeleteConversationId(SPECIAL_ID_WIPE_ALL);

const handleConversationDelete = React.useCallback((conversationId: DConversationId, bypassConfirmation: boolean) => {
if (bypassConfirmation)
Expand Down Expand Up @@ -348,14 +388,16 @@ export function AppChat() {
const drawerItems = React.useMemo(() =>
<ChatDrawerItemsMemo
activeConversationId={focusedConversationId}
disableNewButton={isFocusedChatEmpty}
disableNewButton={disableNewButton}
onConversationActivate={setFocusedConversationId}
onConversationDelete={handleConversationDelete}
onConversationImportDialog={handleConversationImportDialog}
onConversationNew={handleConversationNew}
onConversationsDeleteAll={handleConversationsDeleteAll}
selectedFolderId={selectedFolderId}
setSelectedFolderId={setSelectedFolderId}
/>,
[focusedConversationId, handleConversationDelete, handleConversationNew, isFocusedChatEmpty, setFocusedConversationId],
[focusedConversationId, handleConversationDelete, handleConversationNew, setFocusedConversationId, selectedFolderId, disableNewButton],
);

const menuItems = React.useMemo(() =>
Expand All @@ -373,6 +415,11 @@ export function AppChat() {
[areChatsEmpty, focusedConversationId, handleConversationBranch, isFocusedChatEmpty, isMessageSelectionMode],
);

const conversationsToDeleteCount = selectedFolderId
? useFolderStore.getState().folders.find(folder => folder.id === selectedFolderId)?.conversationIds.length || 0
: conversations.length;


useLayoutPluggable(centerItems, drawerItems, menuItems);

return <>
Expand Down Expand Up @@ -472,12 +519,13 @@ export function AppChat() {
{!!deleteConversationId && <ConfirmationModal
open onClose={() => setDeleteConversationId(null)} onPositive={handleConfirmedDeleteConversation}
confirmationText={deleteConversationId === SPECIAL_ID_WIPE_ALL
? 'Are you absolutely sure you want to delete ALL conversations? This action cannot be undone.'
? `Are you absolutely sure you want to delete ${selectedFolderId ? 'ALL conversations in this folder' : 'ALL conversations'}? This action cannot be undone.`
: 'Are you sure you want to delete this conversation?'}
positiveActionText={deleteConversationId === SPECIAL_ID_WIPE_ALL
? 'Yes, delete all'
? `Yes, delete all ${conversationsToDeleteCount} conversations`
: 'Delete conversation'}
/>}

</>;
}

Loading