diff --git a/src/components/Chat/ChatSidebar.tsx b/src/components/Chat/ChatSidebar.tsx index 49dc1a49..cfbbf0e2 100644 --- a/src/components/Chat/ChatSidebar.tsx +++ b/src/components/Chat/ChatSidebar.tsx @@ -1,41 +1,36 @@ import React, { useState } from 'react' - -import { RiChatNewFill, RiArrowDownSLine } from 'react-icons/ri' import { IoChatbubbles } from 'react-icons/io5' -import posthog from 'posthog-js' +import { RiChatNewFill, RiArrowDownSLine } from 'react-icons/ri' import { useChatContext } from '@/contexts/ChatContext' import { useContentContext } from '@/contexts/ContentContext' import { ChatMetadata } from '../../lib/llm/types' +import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@/components/ui/context-menu' export interface ChatItemProps { chatMetadata: ChatMetadata } export const ChatItem: React.FC = ({ chatMetadata }) => { - const { currentOpenChatID } = useChatContext() - const { openContent, showContextMenu } = useContentContext() + const { currentChat, deleteChat } = useChatContext() + const { openContent } = useContentContext() const itemClasses = ` flex items-center cursor-pointer py-2 px-3 rounded-md transition-colors duration-150 ease-in-out - ${chatMetadata.id === currentOpenChatID ? 'bg-neutral-700 text-white' : 'text-gray-300 hover:bg-neutral-800'} + ${chatMetadata.id === currentChat?.id ? 'bg-neutral-700 text-white' : 'text-gray-300 hover:bg-neutral-800'} ` return ( -
-
{ - openContent(chatMetadata.id) - }} - className={itemClasses} - onContextMenu={(e) => { - e.stopPropagation() - showContextMenu(e, 'ChatItem', { chatMetadata }) - }} - > - - {chatMetadata.displayName} -
-
+ + +
openContent(chatMetadata.id)} className={itemClasses}> + + {chatMetadata.displayName} +
+
+ + deleteChat(chatMetadata.id)}>Delete Chat + +
) } @@ -43,7 +38,7 @@ export const ChatSidebar: React.FC = () => { const [isRecentsOpen, setIsRecentsOpen] = useState(true) const dropdownAnimationDelay = 0.02 - const { setShowChatbot, allChatsMetadata, setCurrentOpenChatID } = useChatContext() + const { allChatsMetadata, openNewChat } = useChatContext() const toggleRecents = () => setIsRecentsOpen((prev) => !prev) @@ -58,18 +53,13 @@ export const ChatSidebar: React.FC = () => { shadow-md transition-colors duration-200 hover:bg-blue-400 hover:text-gray-200 hover:shadow-lg" type="button" - onClick={() => { - posthog.capture('create_new_chat') - setShowChatbot(true) - setCurrentOpenChatID(undefined) - }} + onClick={() => openNewChat()} > Start New Chat - {/* Recents Section */}

Recents

diff --git a/src/components/Chat/index.tsx b/src/components/Chat/index.tsx index 5a63089c..6c19b3d6 100644 --- a/src/components/Chat/index.tsx +++ b/src/components/Chat/index.tsx @@ -19,8 +19,7 @@ import { appendToolCallsAndAutoExecuteTools, convertToolConfigToZodSchema } from const ChatComponent: React.FC = () => { const [loadingState, setLoadingState] = useState('idle') const [defaultModelName, setDefaultLLMName] = useState('') - const [currentChat, setCurrentChat] = useState(undefined) - const { saveChat, currentOpenChatID, setCurrentOpenChatID } = useChatContext() + const { currentChat, setCurrentChat, saveChat } = useChatContext() const abortControllerRef = useRef(null) useEffect(() => { @@ -36,18 +35,10 @@ const ChatComponent: React.FC = () => { if (abortControllerRef.current) { abortControllerRef.current.abort() } - - const chat = await window.electronStore.getChat(currentOpenChatID) - setCurrentChat((oldChat) => { - if (oldChat && oldChat.id !== currentOpenChatID) { - saveChat(oldChat) - } - return chat - }) setLoadingState('idle') } fetchChat() - }, [currentOpenChatID, saveChat]) + }, [currentChat?.id, saveChat]) const handleNewChatMessage = useCallback( async (chat: Chat | undefined, userTextFieldInput?: string, agentConfig?: AgentConfig) => { @@ -65,11 +56,9 @@ const ChatComponent: React.FC = () => { } setCurrentChat(outputChat) - setCurrentOpenChatID(outputChat.id) await saveChat(outputChat) const llmClient = await resolveLLMClient(defaultLLMName) - abortControllerRef.current = new AbortController() const toolsZodSchema = Object.assign({}, ...outputChat.toolDefinitions.map(convertToolConfigToZodSchema)) const { textStream, toolCalls } = await streamText({ @@ -140,7 +129,7 @@ const ChatComponent: React.FC = () => { abortControllerRef.current = null } }, - [setCurrentOpenChatID, saveChat], + [saveChat, setCurrentChat], ) return ( diff --git a/src/components/Common/CustomContextMenu.tsx b/src/components/Common/CustomContextMenu.tsx deleted file mode 100644 index 9a180446..00000000 --- a/src/components/Common/CustomContextMenu.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React from 'react' -import { FileInfoNode } from 'electron/main/filesystem/types' -import { useFileContext } from '@/contexts/FileContext' -import { useChatContext } from '@/contexts/ChatContext' -import { useModalOpeners } from '@/contexts/ModalContext' -import { useContentContext } from '@/contexts/ContentContext' -import { ChatMetadata } from '../../lib/llm/types' -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' - -export type ContextMenuLocations = 'FileSidebar' | 'FileItem' | 'ChatItem' | 'DirectoryItem' | 'None' - -export interface AdditionalContextMenuData { - file?: FileInfoNode - chatMetadata?: ChatMetadata -} - -export interface OnShowContextMenuData extends AdditionalContextMenuData { - currentSelection: ContextMenuLocations - position: Position -} - -export type ShowContextMenuInputType = ( - event: React.MouseEvent, - locationOnScreen: ContextMenuLocations, - additionalData?: AdditionalContextMenuData, -) => void - -interface Position { - x: number - y: number -} - -interface MenuItemType { - title: string - onSelect: ((...args: any[]) => void) | null - icon: string -} - -const CustomContextMenu: React.FC = () => { - const { focusedItem, hideFocusedItem, createUntitledNote } = useContentContext() - const { currentSelection, position, file, chatMetadata } = focusedItem - - const { setIsNewDirectoryModalOpen, setIsFlashcardModeOpen, setInitialFileToCreateFlashcard } = useModalOpeners() - - const { deleteFile, setNoteToBeRenamed } = useFileContext() - const { deleteChat } = useChatContext() - - const handleMakeFlashcard = (noteName: string | null) => { - if (!noteName) return - setIsFlashcardModeOpen(!!noteName) - setInitialFileToCreateFlashcard(noteName) - } - - const menuItems = { - newNote: { title: 'New Note', onSelect: createUntitledNote, icon: '' }, - newDirectory: { title: 'New Directory', onSelect: () => setIsNewDirectoryModalOpen(true), icon: '' }, - delete: { title: 'Delete', onSelect: () => deleteFile(file?.path), icon: '' }, - rename: { - title: 'Rename', - onSelect: () => file?.path && setNoteToBeRenamed(file.path), - icon: '', - }, - createFlashcard: { - title: 'Create flashcard set', - onSelect: () => handleMakeFlashcard(file?.path ?? null), - icon: '', - }, - deleteChat: { title: 'Delete Chat', onSelect: () => deleteChat(chatMetadata?.id), icon: '' }, - } - - const menuConfigurations: Record, MenuItemType[]> = { - FileSidebar: [menuItems.newNote, menuItems.newDirectory], - FileItem: [menuItems.delete, menuItems.rename, menuItems.createFlashcard], - ChatItem: [menuItems.deleteChat], - DirectoryItem: [ - menuItems.newDirectory, - menuItems.newNote, - menuItems.delete, - menuItems.rename, - menuItems.createFlashcard, - ], - } - - const displayList = currentSelection !== 'None' ? menuConfigurations[currentSelection] : [] - - const handleSubmit = (item: MenuItemType) => { - if (item.onSelect) item.onSelect() - hideFocusedItem() - } - - return ( - - -
- - - {displayList.map((item) => ( - handleSubmit(item)}> - {item.title} - - ))} - - - ) -} - -export default CustomContextMenu diff --git a/src/components/MainPage.tsx b/src/components/MainPage.tsx index 88e05c3c..3831621c 100644 --- a/src/components/MainPage.tsx +++ b/src/components/MainPage.tsx @@ -14,7 +14,6 @@ import WritingAssistant from './WritingAssistant/WritingAssistant' import { ChatProvider, useChatContext } from '@/contexts/ChatContext' import { FileProvider, useFileContext } from '@/contexts/FileContext' import ModalProvider from '@/contexts/ModalContext' -import CustomContextMenu from './Common/CustomContextMenu' import CommonModals from './Common/CommonModals' const MainPageContent: React.FC = () => { @@ -32,7 +31,6 @@ const MainPageContent: React.FC = () => { setShowSimilarFiles(!showSimilarFiles) }} /> -
diff --git a/src/contexts/ChatContext.tsx b/src/contexts/ChatContext.tsx index 8a86be32..9ee0db36 100644 --- a/src/contexts/ChatContext.tsx +++ b/src/contexts/ChatContext.tsx @@ -2,26 +2,25 @@ import React, { createContext, useContext, useState, useCallback, useEffect } fr import { SidebarAbleToShow } from '@/components/Sidebars/MainSidebar' import { Chat, ChatMetadata } from '@/lib/llm/types' -export const UNINITIALIZED_STATE = 'UNINITIALIZED_STATE' - interface ChatContextType { + currentChat: Chat | undefined + setCurrentChat: React.Dispatch> sidebarShowing: SidebarAbleToShow setSidebarShowing: (option: SidebarAbleToShow) => void showChatbot: boolean setShowChatbot: (show: boolean) => void - currentOpenChatID: string | undefined - setCurrentOpenChatID: (chatID: string | undefined) => void allChatsMetadata: ChatMetadata[] - deleteChat: (chatID: string | undefined) => Promise + deleteChat: (chatID: string | undefined) => Promise saveChat: (updatedChat: Chat) => Promise + openNewChat: (chatID?: string) => Promise } const ChatContext = createContext(undefined) export const ChatProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [currentChat, setCurrentChat] = useState(undefined) const [showChatbot, setShowChatbot] = useState(false) const [sidebarShowing, setSidebarShowing] = useState('files') - const [currentOpenChatID, setCurrentOpenChatID] = useState(undefined) const [allChatsMetadata, setAllChatsMetadata] = useState([]) useEffect(() => { @@ -39,42 +38,52 @@ export const ChatProvider: React.FC<{ children: React.ReactNode }> = ({ children setAllChatsMetadata(retrievedChatsMetadata) }, []) - const deleteChat = useCallback( - async (chatID: string | undefined) => { - if (!chatID) return false - await window.electronStore.deleteChat(chatID) - const retrievedChatsMetadata = await window.electronStore.getAllChatsMetadata() - setAllChatsMetadata(retrievedChatsMetadata) - if (currentOpenChatID === chatID) { - setCurrentOpenChatID(undefined) - } - return true + const deleteChat = useCallback(async (chatID: string | undefined) => { + if (!chatID) return + await window.electronStore.deleteChat(chatID) + const retrievedChatsMetadata = await window.electronStore.getAllChatsMetadata() + setAllChatsMetadata(retrievedChatsMetadata) + setCurrentChat(undefined) + }, []) + + const openNewChat = useCallback( + async (chatID?: string) => { + const chat = await window.electronStore.getChat(chatID) + setCurrentChat((oldChat) => { + if (oldChat && oldChat.id !== chatID) { + saveChat(oldChat) + } + return chat + }) + setShowChatbot(true) }, - [currentOpenChatID], + [setShowChatbot, saveChat], ) const value = React.useMemo( () => ({ + currentChat, + setCurrentChat, allChatsMetadata, sidebarShowing, setSidebarShowing, showChatbot, setShowChatbot, - currentOpenChatID, - setCurrentOpenChatID, deleteChat, saveChat, + openNewChat, }), [ + currentChat, + setCurrentChat, allChatsMetadata, sidebarShowing, setSidebarShowing, showChatbot, setShowChatbot, - currentOpenChatID, - setCurrentOpenChatID, deleteChat, saveChat, + openNewChat, ], ) diff --git a/src/contexts/ContentContext.tsx b/src/contexts/ContentContext.tsx index 50106e91..f0a0c7c1 100644 --- a/src/contexts/ContentContext.tsx +++ b/src/contexts/ContentContext.tsx @@ -3,14 +3,10 @@ import React, { createContext, useContext, useMemo, ReactNode, useState, useCall import posthog from 'posthog-js' import { useChatContext } from './ChatContext' import { useFileContext } from './FileContext' -import { OnShowContextMenuData, ShowContextMenuInputType } from '@/components/Common/CustomContextMenu' import { getFilesInDirectory, getNextAvailableFileNameGivenBaseName } from '@/lib/file' interface ContentContextType { openContent: (pathOrChatID: string, optionalContentToWriteOnCreate?: string, dontUpdateChatHistory?: boolean) => void - focusedItem: OnShowContextMenuData - showContextMenu: ShowContextMenuInputType - hideFocusedItem: () => void currentOpenFileOrChatID: string | null createUntitledNote: (parentFileOrDirectory?: string) => void } @@ -30,13 +26,9 @@ interface ContentProviderProps { } export const ContentProvider: React.FC = ({ children }) => { - const [focusedItem, setFocusedItem] = useState({ - currentSelection: 'None', - position: { x: 0, y: 0 }, - }) const [currentOpenFileOrChatID, setCurrentOpenFileOrChatID] = useState(null) - const { setCurrentOpenChatID, allChatsMetadata, setShowChatbot, setSidebarShowing } = useChatContext() + const { allChatsMetadata, setShowChatbot, setSidebarShowing, openNewChat } = useChatContext() const { vaultFilesFlattened: flattenedFiles, openOrCreateFile, @@ -50,7 +42,7 @@ export const ContentProvider: React.FC = ({ children }) => const chatMetadata = allChatsMetadata.find((chat) => chat.id === pathOrChatID) if (chatMetadata) { setShowChatbot(true) - setCurrentOpenChatID(pathOrChatID) + openNewChat(pathOrChatID) } else { setShowChatbot(false) setSidebarShowing('files') @@ -61,36 +53,8 @@ export const ContentProvider: React.FC = ({ children }) => addToNavigationHistory(pathOrChatID) } }, - [ - allChatsMetadata, - setShowChatbot, - setCurrentOpenChatID, - setSidebarShowing, - openOrCreateFile, - addToNavigationHistory, - setCurrentOpenFileOrChatID, - ], + [allChatsMetadata, setShowChatbot, openNewChat, setSidebarShowing, openOrCreateFile, addToNavigationHistory], ) - const showContextMenu: ShowContextMenuInputType = React.useCallback( - (event, locationOnScreen, additionalData = {}) => { - event.preventDefault() - setFocusedItem({ - currentSelection: locationOnScreen, - position: { x: event.clientX, y: event.clientY }, - ...additionalData, - }) - }, - [setFocusedItem], - ) - - const hideFocusedItem = React.useCallback(() => { - setFocusedItem((prevItem: OnShowContextMenuData) => ({ - currentSelection: 'None', - position: { x: 0, y: 0 }, - file: prevItem.file, - chatMetadata: prevItem.chatMetadata, - })) - }, [setFocusedItem]) const createUntitledNote = useCallback( async (parentDirectory?: string) => { @@ -113,13 +77,10 @@ export const ContentProvider: React.FC = ({ children }) => const ContentContextMemo = useMemo( () => ({ openContent, - focusedItem, - showContextMenu, - hideFocusedItem, currentOpenFileOrChatID, createUntitledNote, }), - [openContent, focusedItem, showContextMenu, hideFocusedItem, currentOpenFileOrChatID, createUntitledNote], + [openContent, currentOpenFileOrChatID, createUntitledNote], ) return {children}