diff --git a/.vscode/settings.json b/.vscode/settings.json index 684fc502..4f6177a3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,7 @@ { "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit", - "source.organizeImports": "explicit" + "source.fixAll.eslint": "explicit" }, "search.exclude": { "**/dist/**": true diff --git a/src/main/index.ts b/src/main/index.ts index bd30411d..87886877 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,8 +1,7 @@ -import { app, BrowserWindow, ipcMain, shell } from 'electron' import { electronApp, is, optimizer } from '@electron-toolkit/utils' +import { app, BrowserWindow, ipcMain, shell } from 'electron' import windowStateKeeper from 'electron-window-state' import { join } from 'path' -import { initStore } from './store' import icon from '../../resources/icon.png?asset' function createWindow(): void { @@ -68,8 +67,6 @@ app.whenReady().then(() => { // IPC test ipcMain.on('ping', () => console.log('pong')) - initStore() - createWindow() app.on('activate', function () { diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 38c58fc3..7d128cd8 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1,7 +1,8 @@ import '@fontsource/inter' -import store from '@renderer/store' +import store, { persistor } from '@renderer/store' import { Provider } from 'react-redux' import { BrowserRouter, Route, Routes } from 'react-router-dom' +import { PersistGate } from 'redux-persist/integration/react' import Sidebar from './components/app/Sidebar' import Statusbar from './components/app/Statusbar' import AppsPage from './pages/apps/AppsPage' @@ -11,15 +12,17 @@ import SettingsPage from './pages/settings/SettingsPage' function App(): JSX.Element { return ( - - - - } /> - } /> - } /> - - - + + + + + } /> + } /> + } /> + + + + ) } diff --git a/src/renderer/src/hooks/useConversactions.ts b/src/renderer/src/hooks/useConversactions.ts deleted file mode 100644 index 2269a3e0..00000000 --- a/src/renderer/src/hooks/useConversactions.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { runAsyncFunction } from '@renderer/utils' -import localforage from 'localforage' -import { useEffect, useState } from 'react' - -export type Conversation = { - id: string - name: string - avatar: string - lastMessage: string - lastMessageAt: string -} - -export default function useConversations() { - const [conversations, setConversations] = useState([]) - const [activeConversation, setActiveConversation] = useState() - - // Use localforage to initialize conversations - useEffect(() => { - runAsyncFunction(async () => { - const conversations = await localforage.getItem('conversations') - conversations && setConversations(conversations) - }) - }, []) - - // Update localforage - useEffect(() => { - localforage.setItem('conversations', conversations) - }, [conversations]) - - const addConversation = (conversation) => { - setConversations([...conversations, conversation]) - } - - const removeConversation = (conversationId) => { - setConversations(conversations.filter((c) => c.id !== conversationId)) - } - - const updateConversation = (conversation) => { - setConversations(conversations.map((c) => (c.id === conversation.id ? conversation : c))) - } - - return { - conversations, - activeConversation, - setConversations, - addConversation, - removeConversation, - updateConversation, - setActiveConversation - } -} diff --git a/src/renderer/src/hooks/useThreads.ts b/src/renderer/src/hooks/useThreads.ts new file mode 100644 index 00000000..e0b26bc1 --- /dev/null +++ b/src/renderer/src/hooks/useThreads.ts @@ -0,0 +1,17 @@ +import { useAppDispatch, useAppSelector } from '@renderer/store' +import { addThread, removeThread, setActiveThread, updateThread } from '@renderer/store/threads' +import { Thread } from '@renderer/types' + +export default function useThreads() { + const { threads, activeThread } = useAppSelector((state) => state.threads) + const dispatch = useAppDispatch() + + return { + threads, + activeThread, + setActiveThread: (thread: Thread) => dispatch(setActiveThread(thread)), + addThread: (thread: Thread) => dispatch(addThread(thread)), + removeThread: (id: string) => dispatch(removeThread({ id })), + updateThread: (thread: Thread) => dispatch(updateThread(thread)) + } +} diff --git a/src/renderer/src/pages/home/HomePage.tsx b/src/renderer/src/pages/home/HomePage.tsx index ca2be215..63cec426 100644 --- a/src/renderer/src/pages/home/HomePage.tsx +++ b/src/renderer/src/pages/home/HomePage.tsx @@ -1,21 +1,21 @@ import { Navbar, NavbarCenter, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar' -import useConversations from '@renderer/hooks/useConversactions' +import useThreads from '@renderer/hooks/useThreads' import { FC, useEffect } from 'react' import styled from 'styled-components' -import Conversations from './components/Conversations' import Chat from './components/Chat' +import Threads from './components/Threads' const HomePage: FC = () => { - const { conversations, activeConversation, setActiveConversation, addConversation } = useConversations() + const { threads, activeThread, setActiveThread, addThread } = useThreads() useEffect(() => { - if (!activeConversation) { - setActiveConversation(conversations[0]) + if (!activeThread) { + setActiveThread(threads[0]) } - }, [activeConversation, conversations]) + }, [activeThread, threads]) const onCreateConversation = () => { - const _conversation = { + const _thread = { // ID auto increment id: Math.random().toString(), name: 'New conversation', @@ -24,8 +24,8 @@ const HomePage: FC = () => { lastMessage: 'message', lastMessageAt: 'now' } - addConversation(_conversation) - setActiveConversation(_conversation) + addThread(_thread) + setActiveThread(_thread) } return ( @@ -40,12 +40,8 @@ const HomePage: FC = () => { - - + + diff --git a/src/renderer/src/pages/home/components/Chat.tsx b/src/renderer/src/pages/home/components/Chat.tsx index 0628d633..dd0b30a6 100644 --- a/src/renderer/src/pages/home/components/Chat.tsx +++ b/src/renderer/src/pages/home/components/Chat.tsx @@ -1,13 +1,13 @@ -import { Conversation } from '@renderer/hooks/useConversactions' +import { Thread } from '@renderer/types' import { FC } from 'react' import styled from 'styled-components' interface Props { - activeConversation?: Conversation + activeThread?: Thread } -const Chat: FC = ({ activeConversation }) => { - return {activeConversation?.lastMessage} +const Chat: FC = ({ activeThread }) => { + return {activeThread?.lastMessage} } const Container = styled.div` @@ -15,6 +15,7 @@ const Container = styled.div` height: 100%; flex: 1; border-right: 1px solid #ffffff20; + padding: 15px; ` export default Chat diff --git a/src/renderer/src/pages/home/components/Conversations.tsx b/src/renderer/src/pages/home/components/Threads.tsx similarity index 50% rename from src/renderer/src/pages/home/components/Conversations.tsx rename to src/renderer/src/pages/home/components/Threads.tsx index bf0c1613..e4bfc302 100644 --- a/src/renderer/src/pages/home/components/Conversations.tsx +++ b/src/renderer/src/pages/home/components/Threads.tsx @@ -1,25 +1,25 @@ -import type { Conversation } from '@renderer/hooks/useConversactions' +import type { Thread } from '@renderer/types' import { FC } from 'react' import styled from 'styled-components' interface Props { - conversations: Conversation[] - activeConversation?: Conversation - onSelectConversation: (conversation: Conversation) => void + threads: Thread[] + activeThread?: Thread + onSelectThread: (conversation: Thread) => void } -const Conversations: FC = ({ conversations, activeConversation, onSelectConversation }) => { +const Conversations: FC = ({ threads, activeThread, onSelectThread }) => { return ( - {conversations.map((conversation) => ( - onSelectConversation(conversation)} - className={conversation.id === activeConversation?.id ? 'active' : ''}> - {conversation.lastMessageAt} - {conversation.name} - {conversation.lastMessage} - + {threads.map((thread) => ( + onSelectThread(thread)} + className={thread.id === activeThread?.id ? 'active' : ''}> + {thread.lastMessageAt} + {thread.name} + {thread.lastMessage} + ))} ) @@ -38,7 +38,7 @@ const Container = styled.div` } ` -const Conversation = styled.div` +const ThreadItem = styled.div` display: flex; flex-direction: column; padding: 10px; @@ -54,18 +54,18 @@ const Conversation = styled.div` margin-bottom: 10px; ` -const ConversationTime = styled.div` +const ThreadTime = styled.div` font-size: 12px; color: var(--color-text-2); ` -const ConversationName = styled.div` +const ThreadName = styled.div` font-size: 14px; color: var(--color-text-1); font-weight: bold; ` -const ConversationLastMessage = styled.div` +const ThreadLastMessage = styled.div` font-size: 12px; color: var(--color-text-2); ` diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index 25cf17af..b447e7c5 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -1,19 +1,35 @@ -import { configureStore } from '@reduxjs/toolkit' - -import { combineReducers } from '@reduxjs/toolkit' -import { useDispatch } from 'react-redux' +import { combineReducers, configureStore } from '@reduxjs/toolkit' +import { useDispatch, useSelector, useStore } from 'react-redux' +import { FLUSH, PAUSE, PERSIST, persistReducer, persistStore, PURGE, REGISTER, REHYDRATE } from 'redux-persist' +import storage from 'redux-persist/lib/storage' import threads from './threads' -const rootReducer = combineReducers({ - threads -}) - const store = configureStore({ - reducer: rootReducer + reducer: persistReducer( + { + key: 'cherry-ai', + storage, + version: 1 + }, + combineReducers({ + threads + }) + ), + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + serializableCheck: { + ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER] + } + }) }) -export type RootState = ReturnType +export type RootState = ReturnType export type AppDispatch = typeof store.dispatch + +export const persistor = persistStore(store) export const useAppDispatch = useDispatch.withTypes() +export const useAppSelector = useSelector.withTypes() +export const useAppStore = useStore.withTypes() +// export const dispatch: AppDispatch = useDispatch() export default store diff --git a/src/renderer/src/store/threads.ts b/src/renderer/src/store/threads.ts index 2a3003a3..f9364224 100644 --- a/src/renderer/src/store/threads.ts +++ b/src/renderer/src/store/threads.ts @@ -1,12 +1,14 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { Thread } from '@renderer/types' -interface State { +export interface ThreadsState { threads: Thread[] + activeThread?: Thread } -const initialState: State = { - threads: [] +const initialState: ThreadsState = { + threads: [], + activeThread: undefined } const threadsSlice = createSlice({ @@ -21,10 +23,13 @@ const threadsSlice = createSlice({ }, updateThread: (state, action: PayloadAction) => { state.threads = state.threads.map((c) => (c.id === action.payload.id ? action.payload : c)) + }, + setActiveThread: (state, action: PayloadAction) => { + state.activeThread = action.payload } } }) -export const { addThread, removeThread, updateThread } = threadsSlice.actions +export const { addThread, removeThread, updateThread, setActiveThread } = threadsSlice.actions -export default threadsSlice +export default threadsSlice.reducer