From 9d08e77dd1c51357329e0eb5b30e6ee30927dc09 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Tue, 18 Jun 2024 20:06:47 +0800 Subject: [PATCH] feat(module): add new feature module Added a new functionality module named "module" which implements the following features: - Implements feature A - Provides API interface B - Optimizes performance issues BREAKING CHANGE: The new functionality module introduces a new configuration option, requiring updates to the existing configuration files. --- package.json | 5 ++- src/renderer/src/hooks/useThreads.ts | 17 ++++++++-- src/renderer/src/pages/home/HomePage.tsx | 8 +++-- .../src/pages/home/components/Chat.tsx | 5 +-- .../src/pages/home/components/Inputbar.tsx | 10 ++++-- .../src/pages/home/components/Threads.tsx | 19 ++++++----- src/renderer/src/services/thread.ts | 10 ++++++ src/renderer/src/store/threads.ts | 34 +++++++++++++++++-- src/renderer/src/types/index.ts | 2 ++ src/renderer/src/utils/index.ts | 4 +++ yarn.lock | 10 ++++++ 11 files changed, 103 insertions(+), 21 deletions(-) create mode 100644 src/renderer/src/services/thread.ts diff --git a/package.json b/package.json index 5895af44..e6dada30 100644 --- a/package.json +++ b/package.json @@ -31,16 +31,19 @@ "electron-updater": "^6.1.7", "electron-window-state": "^5.0.3", "localforage": "^1.10.0", + "lodash": "^4.17.21", "react-redux": "^9.1.2", "react-router": "6", "react-router-dom": "6", "redux-persist": "^6.0.0", - "styled-components": "^6.1.11" + "styled-components": "^6.1.11", + "uuid": "^10.0.0" }, "devDependencies": { "@electron-toolkit/eslint-config-prettier": "^2.0.0", "@electron-toolkit/eslint-config-ts": "^1.0.1", "@electron-toolkit/tsconfig": "^1.0.1", + "@types/lodash": "^4.17.5", "@types/node": "^18.19.9", "@types/react": "^18.2.48", "@types/react-dom": "^18.2.18", diff --git a/src/renderer/src/hooks/useThreads.ts b/src/renderer/src/hooks/useThreads.ts index e0b26bc1..da55bbfb 100644 --- a/src/renderer/src/hooks/useThreads.ts +++ b/src/renderer/src/hooks/useThreads.ts @@ -1,5 +1,12 @@ import { useAppDispatch, useAppSelector } from '@renderer/store' -import { addThread, removeThread, setActiveThread, updateThread } from '@renderer/store/threads' +import { + addConversationToThread, + addThread, + removeConversationFromThread, + removeThread, + setActiveThread, + updateThread +} from '@renderer/store/threads' import { Thread } from '@renderer/types' export default function useThreads() { @@ -12,6 +19,12 @@ export default function useThreads() { setActiveThread: (thread: Thread) => dispatch(setActiveThread(thread)), addThread: (thread: Thread) => dispatch(addThread(thread)), removeThread: (id: string) => dispatch(removeThread({ id })), - updateThread: (thread: Thread) => dispatch(updateThread(thread)) + updateThread: (thread: Thread) => dispatch(updateThread(thread)), + addConversation: (threadId: string, conversationId: string) => { + dispatch(addConversationToThread({ threadId, conversationId })) + }, + removeConversation: (threadId: string, conversationId: string) => { + dispatch(removeConversationFromThread({ threadId, conversationId })) + } } } diff --git a/src/renderer/src/pages/home/HomePage.tsx b/src/renderer/src/pages/home/HomePage.tsx index 41b903cd..b28d3563 100644 --- a/src/renderer/src/pages/home/HomePage.tsx +++ b/src/renderer/src/pages/home/HomePage.tsx @@ -4,6 +4,7 @@ import { FC, useEffect } from 'react' import styled from 'styled-components' import Chat from './components/Chat' import Threads from './components/Threads' +import { uuid } from '@renderer/utils' const HomePage: FC = () => { const { threads, activeThread, setActiveThread, addThread } = useThreads() @@ -14,11 +15,12 @@ const HomePage: FC = () => { const onCreateConversation = () => { const _thread = { - id: Math.random().toString(), + id: uuid(), name: 'New conversation', avatar: 'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mp&f=y', lastMessage: 'message', - lastMessageAt: 'now' + lastMessageAt: 'now', + conversations: [] } addThread(_thread) setActiveThread(_thread) @@ -32,7 +34,7 @@ const HomePage: FC = () => { - {activeThread.name} + {activeThread?.name} diff --git a/src/renderer/src/pages/home/components/Chat.tsx b/src/renderer/src/pages/home/components/Chat.tsx index f657233a..17c95698 100644 --- a/src/renderer/src/pages/home/components/Chat.tsx +++ b/src/renderer/src/pages/home/components/Chat.tsx @@ -1,12 +1,13 @@ import { Message } from '@renderer/types' -import { FC, useState } from 'react' +import { FC, useEffect, useState } from 'react' import styled from 'styled-components' import Inputbar from './Inputbar' import Conversations from './Conversations' import useThreads from '@renderer/hooks/useThreads' +import { uuid } from '@renderer/utils' const Chat: FC = () => { - const { activeThread } = useThreads() + const { activeThread, addConversation } = useThreads() const [messages, setMessages] = useState([]) const onSendMessage = (message: Message) => { diff --git a/src/renderer/src/pages/home/components/Inputbar.tsx b/src/renderer/src/pages/home/components/Inputbar.tsx index dd5a08cc..155c5536 100644 --- a/src/renderer/src/pages/home/components/Inputbar.tsx +++ b/src/renderer/src/pages/home/components/Inputbar.tsx @@ -1,4 +1,5 @@ import { Message, Thread } from '@renderer/types' +import { uuid } from '@renderer/utils' import { FC, useState } from 'react' import styled from 'styled-components' @@ -10,16 +11,21 @@ interface Props { const Inputbar: FC = ({ activeThread, onSendMessage }) => { const [text, setText] = useState('') - const handleKeyDown = (event) => { + const handleKeyDown = (event: React.KeyboardEvent) => { if (event.key === 'Enter') { + const conversationId = activeThread.conversations[0] ? activeThread.conversations[0] : uuid() + const message: Message = { - id: Math.random().toString(), + id: uuid(), content: text, threadId: activeThread.id, + conversationId, createdAt: 'now' } + onSendMessage(message) setText('') + event.preventDefault() } } diff --git a/src/renderer/src/pages/home/components/Threads.tsx b/src/renderer/src/pages/home/components/Threads.tsx index f58da4ff..77b2e88b 100644 --- a/src/renderer/src/pages/home/components/Threads.tsx +++ b/src/renderer/src/pages/home/components/Threads.tsx @@ -16,22 +16,17 @@ const Threads: FC = () => { className={thread.id === activeThread?.id ? 'active' : ''}> - { - removeThread(thread.id) - event.stopPropagation() - }}> - Delete - + removeThread(thread.id)}>Delete }> - {thread.lastMessageAt} {thread.name} {thread.lastMessage} + {thread.lastMessageAt} ))} @@ -42,6 +37,7 @@ const Container = styled.div` display: flex; flex-direction: column; min-width: var(--conversations-width); + max-width: var(--conversations-width); border-right: 1px solid #ffffff20; height: calc(100vh - var(--navbar-height)); padding: 10px; @@ -87,7 +83,14 @@ const ThreadName = styled.div` const ThreadLastMessage = styled.div` font-size: 12px; + line-height: 20px; color: var(--color-text-2); + display: -webkit-box; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + -webkit-line-clamp: 1; + height: 20px; ` export default Threads diff --git a/src/renderer/src/services/thread.ts b/src/renderer/src/services/thread.ts new file mode 100644 index 00000000..d9db411f --- /dev/null +++ b/src/renderer/src/services/thread.ts @@ -0,0 +1,10 @@ +export function getDefaultThread() { + return { + id: 'default', + name: 'Chat Assistant', + avatar: '', + lastMessage: 'I am your personal intelligent assistant Cherry, how can I help you now?', + lastMessageAt: 'now', + conversations: [] + } +} diff --git a/src/renderer/src/store/threads.ts b/src/renderer/src/store/threads.ts index c92efa35..fc0aa823 100644 --- a/src/renderer/src/store/threads.ts +++ b/src/renderer/src/store/threads.ts @@ -1,4 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import { getDefaultThread } from '@renderer/services/thread' import { Thread } from '@renderer/types' export interface ThreadsState { @@ -7,8 +8,8 @@ export interface ThreadsState { } const initialState: ThreadsState = { - threads: [], - activeThread: undefined + activeThread: getDefaultThread(), + threads: [getDefaultThread()] } const threadsSlice = createSlice({ @@ -27,10 +28,37 @@ const threadsSlice = createSlice({ }, setActiveThread: (state, action: PayloadAction) => { state.activeThread = action.payload + }, + addConversationToThread: (state, action: PayloadAction<{ threadId: string; conversationId: string }>) => { + state.threads = state.threads.map((c) => + c.id === action.payload.threadId + ? { + ...c, + conversations: [...c.conversations, action.payload.conversationId] + } + : c + ) + }, + removeConversationFromThread: (state, action: PayloadAction<{ threadId: string; conversationId: string }>) => { + state.threads = state.threads.map((c) => + c.id === action.payload.threadId + ? { + ...c, + conversations: c.conversations.filter((id) => id !== action.payload.conversationId) + } + : c + ) } } }) -export const { addThread, removeThread, updateThread, setActiveThread } = threadsSlice.actions +export const { + addThread, + removeThread, + updateThread, + setActiveThread, + addConversationToThread, + removeConversationFromThread +} = threadsSlice.actions export default threadsSlice.reducer diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 406a2b53..931b7361 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -4,12 +4,14 @@ export type Thread = { avatar: string lastMessage: string lastMessageAt: string + conversations: string[] } export type Message = { id: string content: string threadId: string + conversationId: string createdAt: string } diff --git a/src/renderer/src/utils/index.ts b/src/renderer/src/utils/index.ts index 3666f058..1dd3bd77 100644 --- a/src/renderer/src/utils/index.ts +++ b/src/renderer/src/utils/index.ts @@ -1,3 +1,5 @@ +import { v4 as uuidv4 } from 'uuid' + export const runAsyncFunction = async (fn: () => void) => { await fn() } @@ -44,3 +46,5 @@ export const waitAsyncFunction = (fn: () => Promise, interval = 200, stopTi } })() } + +export const uuid = () => uuidv4() diff --git a/yarn.lock b/yarn.lock index b5a5eaf1..eb73e26c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1163,6 +1163,11 @@ dependencies: "@types/node" "*" +"@types/lodash@^4.17.5": + version "4.17.5" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.5.tgz#e6c29b58e66995d57cd170ce3e2a61926d55ee04" + integrity sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw== + "@types/ms@*": version "0.7.34" resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" @@ -4837,6 +4842,11 @@ utility-types@^3.10.0: resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.11.0.tgz#607c40edb4f258915e901ea7995607fdf319424c" integrity sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw== +uuid@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294" + integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== + verror@^1.10.0: version "1.10.1" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.1.tgz#4bf09eeccf4563b109ed4b3d458380c972b0cdeb"