240 lines
7.3 KiB
TypeScript
240 lines
7.3 KiB
TypeScript
import { useCallback, useContext, useEffect } from 'react';
|
|
|
|
import { useTranslation } from 'next-i18next';
|
|
|
|
import { useCreateReducer } from '@/hooks/useCreateReducer';
|
|
|
|
import { DEFAULT_SYSTEM_PROMPT, DEFAULT_TEMPERATURE } from '@/utils/app/const';
|
|
import { saveConversation, saveConversations } from '@/utils/app/conversation';
|
|
import { saveFolders } from '@/utils/app/folders';
|
|
import { exportData, importData } from '@/utils/app/importExport';
|
|
|
|
import { Conversation } from '@/types/chat';
|
|
import { LatestExportFormat, SupportedExportFormats } from '@/types/export';
|
|
import { OpenAIModels } from '@/types/openai';
|
|
import { PluginKey } from '@/types/plugin';
|
|
|
|
import HomeContext from '@/pages/api/home/home.context';
|
|
|
|
import { ChatFolders } from './components/ChatFolders';
|
|
import { ChatbarSettings } from './components/ChatbarSettings';
|
|
import { Conversations } from './components/Conversations';
|
|
|
|
import Sidebar from '../Sidebar';
|
|
import ChatbarContext from './Chatbar.context';
|
|
import { ChatbarInitialState, initialState } from './Chatbar.state';
|
|
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
export const Chatbar = () => {
|
|
const { t } = useTranslation('sidebar');
|
|
|
|
const chatBarContextValue = useCreateReducer<ChatbarInitialState>({
|
|
initialState,
|
|
});
|
|
|
|
const {
|
|
state: { conversations, showChatbar, defaultModelId, folders, pluginKeys },
|
|
dispatch: homeDispatch,
|
|
handleCreateFolder,
|
|
handleNewConversation,
|
|
handleUpdateConversation,
|
|
} = useContext(HomeContext);
|
|
|
|
const {
|
|
state: { searchTerm, filteredConversations },
|
|
dispatch: chatDispatch,
|
|
} = chatBarContextValue;
|
|
|
|
const handleApiKeyChange = useCallback(
|
|
(apiKey: string) => {
|
|
homeDispatch({ field: 'apiKey', value: apiKey });
|
|
|
|
localStorage.setItem('apiKey', apiKey);
|
|
},
|
|
[homeDispatch],
|
|
);
|
|
|
|
const handlePluginKeyChange = (pluginKey: PluginKey) => {
|
|
if (pluginKeys.some((key) => key.pluginId === pluginKey.pluginId)) {
|
|
const updatedPluginKeys = pluginKeys.map((key) => {
|
|
if (key.pluginId === pluginKey.pluginId) {
|
|
return pluginKey;
|
|
}
|
|
|
|
return key;
|
|
});
|
|
|
|
homeDispatch({ field: 'pluginKeys', value: updatedPluginKeys });
|
|
|
|
localStorage.setItem('pluginKeys', JSON.stringify(updatedPluginKeys));
|
|
} else {
|
|
homeDispatch({ field: 'pluginKeys', value: [...pluginKeys, pluginKey] });
|
|
|
|
localStorage.setItem(
|
|
'pluginKeys',
|
|
JSON.stringify([...pluginKeys, pluginKey]),
|
|
);
|
|
}
|
|
};
|
|
|
|
const handleClearPluginKey = (pluginKey: PluginKey) => {
|
|
const updatedPluginKeys = pluginKeys.filter(
|
|
(key) => key.pluginId !== pluginKey.pluginId,
|
|
);
|
|
|
|
if (updatedPluginKeys.length === 0) {
|
|
homeDispatch({ field: 'pluginKeys', value: [] });
|
|
localStorage.removeItem('pluginKeys');
|
|
return;
|
|
}
|
|
|
|
homeDispatch({ field: 'pluginKeys', value: updatedPluginKeys });
|
|
|
|
localStorage.setItem('pluginKeys', JSON.stringify(updatedPluginKeys));
|
|
};
|
|
|
|
const handleExportData = () => {
|
|
exportData();
|
|
};
|
|
|
|
const handleImportConversations = (data: SupportedExportFormats) => {
|
|
const { history, folders, prompts }: LatestExportFormat = importData(data);
|
|
homeDispatch({ field: 'conversations', value: history });
|
|
homeDispatch({
|
|
field: 'selectedConversation',
|
|
value: history[history.length - 1],
|
|
});
|
|
homeDispatch({ field: 'folders', value: folders });
|
|
homeDispatch({ field: 'prompts', value: prompts });
|
|
};
|
|
|
|
const handleClearConversations = () => {
|
|
defaultModelId &&
|
|
homeDispatch({
|
|
field: 'selectedConversation',
|
|
value: {
|
|
id: uuidv4(),
|
|
name: t('New Conversation'),
|
|
messages: [],
|
|
model: OpenAIModels[defaultModelId],
|
|
prompt: DEFAULT_SYSTEM_PROMPT,
|
|
temperature: DEFAULT_TEMPERATURE,
|
|
folderId: null,
|
|
},
|
|
});
|
|
|
|
homeDispatch({ field: 'conversations', value: [] });
|
|
|
|
localStorage.removeItem('conversationHistory');
|
|
localStorage.removeItem('selectedConversation');
|
|
|
|
const updatedFolders = folders.filter((f) => f.type !== 'chat');
|
|
|
|
homeDispatch({ field: 'folders', value: updatedFolders });
|
|
saveFolders(updatedFolders);
|
|
};
|
|
|
|
const handleDeleteConversation = (conversation: Conversation) => {
|
|
const updatedConversations = conversations.filter(
|
|
(c) => c.id !== conversation.id,
|
|
);
|
|
|
|
homeDispatch({ field: 'conversations', value: updatedConversations });
|
|
chatDispatch({ field: 'searchTerm', value: '' });
|
|
saveConversations(updatedConversations);
|
|
|
|
if (updatedConversations.length > 0) {
|
|
homeDispatch({
|
|
field: 'selectedConversation',
|
|
value: updatedConversations[updatedConversations.length - 1],
|
|
});
|
|
|
|
saveConversation(updatedConversations[updatedConversations.length - 1]);
|
|
} else {
|
|
defaultModelId &&
|
|
homeDispatch({
|
|
field: 'selectedConversation',
|
|
value: {
|
|
id: uuidv4(),
|
|
name: t('New Conversation'),
|
|
messages: [],
|
|
model: OpenAIModels[defaultModelId],
|
|
prompt: DEFAULT_SYSTEM_PROMPT,
|
|
temperature: DEFAULT_TEMPERATURE,
|
|
folderId: null,
|
|
},
|
|
});
|
|
|
|
localStorage.removeItem('selectedConversation');
|
|
}
|
|
};
|
|
|
|
const handleToggleChatbar = () => {
|
|
homeDispatch({ field: 'showChatbar', value: !showChatbar });
|
|
localStorage.setItem('showChatbar', JSON.stringify(!showChatbar));
|
|
};
|
|
|
|
const handleDrop = (e: any) => {
|
|
if (e.dataTransfer) {
|
|
const conversation = JSON.parse(e.dataTransfer.getData('conversation'));
|
|
handleUpdateConversation(conversation, { key: 'folderId', value: 0 });
|
|
chatDispatch({ field: 'searchTerm', value: '' });
|
|
e.target.style.background = 'none';
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (searchTerm) {
|
|
chatDispatch({
|
|
field: 'filteredConversations',
|
|
value: conversations.filter((conversation) => {
|
|
const searchable =
|
|
conversation.name.toLocaleLowerCase() +
|
|
' ' +
|
|
conversation.messages.map((message) => message.content).join(' ');
|
|
return searchable.toLowerCase().includes(searchTerm.toLowerCase());
|
|
}),
|
|
});
|
|
} else {
|
|
chatDispatch({
|
|
field: 'filteredConversations',
|
|
value: conversations,
|
|
});
|
|
}
|
|
}, [searchTerm, conversations]);
|
|
|
|
return (
|
|
<ChatbarContext.Provider
|
|
value={{
|
|
...chatBarContextValue,
|
|
handleDeleteConversation,
|
|
handleClearConversations,
|
|
handleImportConversations,
|
|
handleExportData,
|
|
handlePluginKeyChange,
|
|
handleClearPluginKey,
|
|
handleApiKeyChange,
|
|
}}
|
|
>
|
|
<Sidebar<Conversation>
|
|
side={'left'}
|
|
isOpen={showChatbar}
|
|
addItemButtonTitle={t('New chat')}
|
|
itemComponent={<Conversations conversations={filteredConversations} />}
|
|
folderComponent={<ChatFolders searchTerm={searchTerm} />}
|
|
items={filteredConversations}
|
|
searchTerm={searchTerm}
|
|
handleSearchTerm={(searchTerm: string) =>
|
|
chatDispatch({ field: 'searchTerm', value: searchTerm })
|
|
}
|
|
toggleOpen={handleToggleChatbar}
|
|
handleCreateItem={handleNewConversation}
|
|
handleCreateFolder={() => handleCreateFolder(t('New folder'), 'chat')}
|
|
handleDrop={handleDrop}
|
|
footerComponent={<ChatbarSettings />}
|
|
/>
|
|
</ChatbarContext.Provider>
|
|
);
|
|
};
|