import { useEffect, useRef, useState } from 'react'; import { useQuery } from 'react-query'; import { GetServerSideProps } from 'next'; import { useTranslation } from 'next-i18next'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import Head from 'next/head'; import { useCreateReducer } from '@/hooks/useCreateReducer'; import useErrorService from '@/services/errorService'; import useApiService from '@/services/useApiService'; import { cleanConversationHistory, cleanSelectedConversation, } from '@/utils/app/clean'; import { DEFAULT_SYSTEM_PROMPT, DEFAULT_TEMPERATURE } from '@/utils/app/const'; import { saveConversation, saveConversations, updateConversation, } from '@/utils/app/conversation'; import { saveFolders } from '@/utils/app/folders'; import { savePrompts } from '@/utils/app/prompts'; import { getSettings } from '@/utils/app/settings'; import { Conversation } from '@/types/chat'; import { KeyValuePair } from '@/types/data'; import { FolderInterface, FolderType } from '@/types/folder'; import { OpenAIModelID, OpenAIModels, fallbackModelID } from '@/types/openai'; import { Prompt } from '@/types/prompt'; import { Chat } from '@/components/Chat/Chat'; import { Chatbar } from '@/components/Chatbar/Chatbar'; import { Navbar } from '@/components/Mobile/Navbar'; import Promptbar from '@/components/Promptbar'; import HomeContext from './home.context'; import { HomeInitialState, initialState } from './home.state'; import { v4 as uuidv4 } from 'uuid'; interface Props { serverSideApiKeyIsSet: boolean; serverSidePluginKeysSet: boolean; defaultModelId: OpenAIModelID; } const Home = ({ serverSideApiKeyIsSet, serverSidePluginKeysSet, defaultModelId, }: Props) => { const { t } = useTranslation('chat'); const [initialRender, setInitialRender] = useState(true); const contextValue = useCreateReducer({ initialState, }); const { state: { apiKey, lightMode, folders, conversations, selectedConversation, prompts, temperature, }, dispatch, } = contextValue; const stopConversationRef = useRef(false); // FETCH MODELS ---------------------------------------------- const handleSelectConversation = (conversation: Conversation) => { dispatch({ field: 'selectedConversation', value: conversation, }); saveConversation(conversation); }; // FOLDER OPERATIONS -------------------------------------------- const handleCreateFolder = (name: string, type: FolderType) => { const newFolder: FolderInterface = { id: uuidv4(), name, type, }; const updatedFolders = [...folders, newFolder]; dispatch({ field: 'folders', value: updatedFolders }); saveFolders(updatedFolders); }; const handleDeleteFolder = (folderId: string) => { const updatedFolders = folders.filter((f) => f.id !== folderId); dispatch({ field: 'folders', value: updatedFolders }); saveFolders(updatedFolders); const updatedConversations: Conversation[] = conversations.map((c) => { if (c.folderId === folderId) { return { ...c, folderId: null, }; } return c; }); dispatch({ field: 'conversations', value: updatedConversations }); saveConversations(updatedConversations); const updatedPrompts: Prompt[] = prompts.map((p) => { if (p.folderId === folderId) { return { ...p, folderId: null, }; } return p; }); dispatch({ field: 'prompts', value: updatedPrompts }); savePrompts(updatedPrompts); }; const handleUpdateFolder = (folderId: string, name: string) => { const updatedFolders = folders.map((f) => { if (f.id === folderId) { return { ...f, name, }; } return f; }); dispatch({ field: 'folders', value: updatedFolders }); saveFolders(updatedFolders); }; // CONVERSATION OPERATIONS -------------------------------------------- const handleNewConversation = () => { const lastConversation = conversations[conversations.length - 1]; const newConversation: Conversation = { id: uuidv4(), name: t('New Conversation'), messages: [], model: lastConversation?.model || { id: OpenAIModels[defaultModelId].id, name: OpenAIModels[defaultModelId].name, maxLength: OpenAIModels[defaultModelId].maxLength, tokenLimit: OpenAIModels[defaultModelId].tokenLimit, }, prompt: DEFAULT_SYSTEM_PROMPT, temperature: lastConversation?.temperature ?? DEFAULT_TEMPERATURE, folderId: null, }; const updatedConversations = [...conversations, newConversation]; dispatch({ field: 'selectedConversation', value: newConversation }); dispatch({ field: 'conversations', value: updatedConversations }); saveConversation(newConversation); saveConversations(updatedConversations); dispatch({ field: 'loading', value: false }); }; const handleUpdateConversation = ( conversation: Conversation, data: KeyValuePair, ) => { const updatedConversation = { ...conversation, [data.key]: data.value, }; const { single, all } = updateConversation( updatedConversation, conversations, ); dispatch({ field: 'selectedConversation', value: single }); dispatch({ field: 'conversations', value: all }); }; // EFFECTS -------------------------------------------- useEffect(() => { if (window.innerWidth < 640) { dispatch({ field: 'showChatbar', value: false }); } }, [selectedConversation]); useEffect(() => { defaultModelId && dispatch({ field: 'defaultModelId', value: defaultModelId }); serverSideApiKeyIsSet && dispatch({ field: 'serverSideApiKeyIsSet', value: serverSideApiKeyIsSet, }); serverSidePluginKeysSet && dispatch({ field: 'serverSidePluginKeysSet', value: serverSidePluginKeysSet, }); }, [defaultModelId, serverSideApiKeyIsSet, serverSidePluginKeysSet]); // ON LOAD -------------------------------------------- useEffect(() => { const settings = getSettings(); if (settings.theme) { dispatch({ field: 'lightMode', value: settings.theme, }); } const apiKey = "test"; if (serverSideApiKeyIsSet) { dispatch({ field: 'apiKey', value: '' }); localStorage.removeItem('apiKey'); } else if (apiKey) { dispatch({ field: 'apiKey', value: apiKey }); } const pluginKeys = localStorage.getItem('pluginKeys'); if (serverSidePluginKeysSet) { dispatch({ field: 'pluginKeys', value: [] }); localStorage.removeItem('pluginKeys'); } else if (pluginKeys) { dispatch({ field: 'pluginKeys', value: pluginKeys }); } if (window.innerWidth < 640) { dispatch({ field: 'showChatbar', value: false }); dispatch({ field: 'showPromptbar', value: false }); } const showChatbar = localStorage.getItem('showChatbar'); if (showChatbar) { dispatch({ field: 'showChatbar', value: showChatbar === 'true' }); } const showPromptbar = localStorage.getItem('showPromptbar'); if (showPromptbar) { dispatch({ field: 'showPromptbar', value: showPromptbar === 'true' }); } const folders = localStorage.getItem('folders'); if (folders) { dispatch({ field: 'folders', value: JSON.parse(folders) }); } const prompts = localStorage.getItem('prompts'); if (prompts) { dispatch({ field: 'prompts', value: JSON.parse(prompts) }); } const conversationHistory = localStorage.getItem('conversationHistory'); if (conversationHistory) { const parsedConversationHistory: Conversation[] = JSON.parse(conversationHistory); const cleanedConversationHistory = cleanConversationHistory( parsedConversationHistory, ); dispatch({ field: 'conversations', value: cleanedConversationHistory }); } const selectedConversation = localStorage.getItem('selectedConversation'); if (selectedConversation) { const parsedSelectedConversation: Conversation = JSON.parse(selectedConversation); const cleanedSelectedConversation = cleanSelectedConversation( parsedSelectedConversation, ); dispatch({ field: 'selectedConversation', value: cleanedSelectedConversation, }); } else { const lastConversation = conversations[conversations.length - 1]; dispatch({ field: 'selectedConversation', value: { id: uuidv4(), name: t('New Conversation'), messages: [], model: OpenAIModels[defaultModelId], prompt: DEFAULT_SYSTEM_PROMPT, temperature: lastConversation?.temperature ?? DEFAULT_TEMPERATURE, folderId: null, }, }); } }, [ defaultModelId, dispatch, serverSideApiKeyIsSet, serverSidePluginKeysSet, ]); return ( Chatbot UI {selectedConversation && (
)}
); }; export default Home; export const getServerSideProps: GetServerSideProps = async ({ locale }) => { const defaultModelId = (process.env.DEFAULT_MODEL && Object.values(OpenAIModelID).includes( process.env.DEFAULT_MODEL as OpenAIModelID, ) && process.env.DEFAULT_MODEL) || fallbackModelID; let serverSidePluginKeysSet = false; const googleApiKey = process.env.GOOGLE_API_KEY; const googleCSEId = process.env.GOOGLE_CSE_ID; if (googleApiKey && googleCSEId) { serverSidePluginKeysSet = true; } return { props: { serverSideApiKeyIsSet: !!process.env.OPENAI_API_KEY, defaultModelId, serverSidePluginKeysSet, ...(await serverSideTranslations(locale ?? 'en', [ 'common', 'chat', 'sidebar', 'markdown', 'promptbar', 'settings', ])), }, }; };