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.
This commit is contained in:
kangfenmao 2024-06-18 20:06:47 +08:00
parent 2e980e234e
commit 9d08e77dd1
11 changed files with 103 additions and 21 deletions

View File

@ -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",

View File

@ -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 }))
}
}
}

View File

@ -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 = () => {
<i className="iconfont icon-a-addchat"></i>
</NewButton>
</NavbarLeft>
<NavbarCenter style={{ border: 'none' }}>{activeThread.name}</NavbarCenter>
<NavbarCenter style={{ border: 'none' }}>{activeThread?.name}</NavbarCenter>
<NavbarRight style={{ justifyContent: 'flex-end', padding: 5 }}>
<NewButton>
<i className="iconfont icon-showsidebarhoriz"></i>

View File

@ -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<Message[]>([])
const onSendMessage = (message: Message) => {

View File

@ -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<Props> = ({ activeThread, onSendMessage }) => {
const [text, setText] = useState('')
const handleKeyDown = (event) => {
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
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()
}
}

View File

@ -16,22 +16,17 @@ const Threads: FC = () => {
className={thread.id === activeThread?.id ? 'active' : ''}>
<Dropdown
trigger="click"
stopPropagation
render={
<Dropdown.Menu>
<Dropdown.Item
onClick={(event) => {
removeThread(thread.id)
event.stopPropagation()
}}>
Delete
</Dropdown.Item>
<Dropdown.Item onClick={() => removeThread(thread.id)}>Delete</Dropdown.Item>
</Dropdown.Menu>
}>
<IconMore style={{ position: 'absolute', right: 12, top: 12 }} />
</Dropdown>
<ThreadTime>{thread.lastMessageAt}</ThreadTime>
<ThreadName>{thread.name}</ThreadName>
<ThreadLastMessage>{thread.lastMessage}</ThreadLastMessage>
<ThreadTime>{thread.lastMessageAt}</ThreadTime>
</ThreadItem>
))}
</Container>
@ -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

View File

@ -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: []
}
}

View File

@ -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<Thread>) => {
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

View File

@ -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
}

View File

@ -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<any>, interval = 200, stopTi
}
})()
}
export const uuid = () => uuidv4()

View File

@ -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"