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"