From 2b4c4f46e65f11110d49186be7abd2cae9f5cbc2 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Mon, 1 Jul 2024 17:33:31 +0800 Subject: [PATCH] feat: add topic list items --- .eslintrc.cjs | 1 + src/renderer/src/assets/styles/index.scss | 4 +- src/renderer/src/assets/styles/markdown.scss | 2 +- src/renderer/src/components/app/Navbar.tsx | 2 +- src/renderer/src/hooks/useAgents.ts | 25 ++++++---- .../src/pages/home/components/Agents.tsx | 2 - .../src/pages/home/components/Chat/Chat.tsx | 16 +++---- .../home/components/Chat/Conversations.tsx | 21 ++++---- .../pages/home/components/Chat/Inputbar.tsx | 17 +++++-- .../pages/home/components/Chat/Message.tsx | 10 ++-- .../pages/home/components/Chat/TopicList.tsx | 48 +++++++++++++++++-- src/renderer/src/services/agent.ts | 13 ++++- src/renderer/src/store/agents.ts | 15 +++--- src/renderer/src/store/settings.ts | 2 +- src/renderer/src/types/index.ts | 3 +- 15 files changed, 124 insertions(+), 57 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 803a64d1..9028c8c3 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -12,6 +12,7 @@ module.exports = { '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/no-explicit-any': 'off', 'unused-imports/no-unused-imports': 'error', + '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', 'sort-imports': [ 'error', { diff --git a/src/renderer/src/assets/styles/index.scss b/src/renderer/src/assets/styles/index.scss index 6303f185..e63526d9 100644 --- a/src/renderer/src/assets/styles/index.scss +++ b/src/renderer/src/assets/styles/index.scss @@ -25,8 +25,8 @@ --color-icon: #ffffff99; --color-icon-white: #ffffff; - --navbar-height: 40px; - --sidebar-width: 65px; + --navbar-height: 45px; + --sidebar-width: 68px; --agents-width: 250px; --topic-list-width: var(--agents-width); --settings-width: 280px; diff --git a/src/renderer/src/assets/styles/markdown.scss b/src/renderer/src/assets/styles/markdown.scss index 6debd3c0..ecb875f6 100644 --- a/src/renderer/src/assets/styles/markdown.scss +++ b/src/renderer/src/assets/styles/markdown.scss @@ -56,7 +56,7 @@ p { margin: 1em 0; - color: #ccc; + color: #fff; } ul, diff --git a/src/renderer/src/components/app/Navbar.tsx b/src/renderer/src/components/app/Navbar.tsx index 4ccb1caa..1f5849bc 100644 --- a/src/renderer/src/components/app/Navbar.tsx +++ b/src/renderer/src/components/app/Navbar.tsx @@ -26,7 +26,7 @@ const NavbarContainer = styled.div` min-height: var(--navbar-height); max-height: var(--navbar-height); background-color: #111; - border-bottom: 1px solid #ffffff20; + border-bottom: 0.5px solid #ffffff20; -webkit-app-region: drag; ` diff --git a/src/renderer/src/hooks/useAgents.ts b/src/renderer/src/hooks/useAgents.ts index f217736f..ddaa40ac 100644 --- a/src/renderer/src/hooks/useAgents.ts +++ b/src/renderer/src/hooks/useAgents.ts @@ -1,12 +1,12 @@ import { useAppDispatch, useAppSelector } from '@renderer/store' import { + addConversation as _addConversation, + removeConversation as _removeConversation, addAgent, - addConversationToAgent, removeAgent, - removeConversationFromAgent, updateAgent } from '@renderer/store/agents' -import { Agent } from '@renderer/types' +import { Agent, Conversation } from '@renderer/types' import localforage from 'localforage' export default function useAgents() { @@ -23,12 +23,21 @@ export default function useAgents() { agent.conversations.forEach((id) => localforage.removeItem(`conversation:${id}`)) } }, - updateAgent: (agent: Agent) => dispatch(updateAgent(agent)), - addConversation: (agentId: string, conversationId: string) => { - dispatch(addConversationToAgent({ agentId, conversationId })) + updateAgent: (agent: Agent) => dispatch(updateAgent(agent)) + } +} + +export function useAgent(id: string) { + const agent = useAppSelector((state) => state.agents.agents.find((a) => a.id === id) as Agent) + const dispatch = useAppDispatch() + + return { + agent, + addConversation: (conversation: Conversation) => { + dispatch(_addConversation({ agentId: agent?.id!, conversation })) }, - removeConversation: (agentId: string, conversationId: string) => { - dispatch(removeConversationFromAgent({ agentId, conversationId })) + removeConversation: (conversation: Conversation) => { + dispatch(_removeConversation({ agentId: agent?.id!, conversation })) } } } diff --git a/src/renderer/src/pages/home/components/Agents.tsx b/src/renderer/src/pages/home/components/Agents.tsx index b4441ecf..75436f18 100644 --- a/src/renderer/src/pages/home/components/Agents.tsx +++ b/src/renderer/src/pages/home/components/Agents.tsx @@ -39,8 +39,6 @@ const Agents: FC = ({ activeAgent, onActive }) => { } ] - console.debug('activeAgent', activeAgent) - return ( {agents.map((agent) => ( diff --git a/src/renderer/src/pages/home/components/Chat/Chat.tsx b/src/renderer/src/pages/home/components/Chat/Chat.tsx index e82c459f..412a344b 100644 --- a/src/renderer/src/pages/home/components/Chat/Chat.tsx +++ b/src/renderer/src/pages/home/components/Chat/Chat.tsx @@ -1,22 +1,18 @@ import { Agent } from '@renderer/types' -import { FC, useEffect, useState } from 'react' +import { FC } from 'react' import styled from 'styled-components' import Inputbar from './Inputbar' import Conversations from './Conversations' -import { uuid } from '@renderer/utils' import { Flex } from 'antd' import TopicList from './TopicList' +import { useAgent } from '@renderer/hooks/useAgents' interface Props { agent: Agent } -const Chat: FC = ({ agent }) => { - const [conversationId, setConversationId] = useState(agent?.conversations[0] || uuid()) - - useEffect(() => { - setConversationId(agent?.conversations[0] || uuid()) - }, [agent]) +const Chat: FC = (props) => { + const { agent } = useAgent(props.agent.id) if (!agent) { return null @@ -25,10 +21,10 @@ const Chat: FC = ({ agent }) => { return ( - + - + ) } diff --git a/src/renderer/src/pages/home/components/Chat/Conversations.tsx b/src/renderer/src/pages/home/components/Chat/Conversations.tsx index 31ac72df..15a4b46c 100644 --- a/src/renderer/src/pages/home/components/Chat/Conversations.tsx +++ b/src/renderer/src/pages/home/components/Chat/Conversations.tsx @@ -1,4 +1,3 @@ -import useAgents from '@renderer/hooks/useAgents' import { EVENT_NAMES, EventEmitter } from '@renderer/services/event' import { openaiProvider } from '@renderer/services/provider' import { Agent, Conversation, Message } from '@renderer/types' @@ -8,28 +7,32 @@ import { FC, useCallback, useEffect, useState } from 'react' import styled from 'styled-components' import MessageItem from './Message' import { reverse } from 'lodash' +import hljs from 'highlight.js' interface Props { agent: Agent - conversationId: string } -const Conversations: FC = ({ agent, conversationId }) => { +const Conversations: FC = ({ agent }) => { const [messages, setMessages] = useState([]) const [lastMessage, setLastMessage] = useState(null) - const { addConversation } = useAgents() + + const { id: conversationId } = agent.conversations[0] const onSendMessage = useCallback( (message: Message) => { const _messages = [...messages, message] setMessages(_messages) - addConversation(agent.id, conversationId) - localforage.setItem(`conversation:${conversationId}`, { + + const conversation = { id: conversationId, + name: 'Default Topic', messages: _messages - }) + } + + localforage.setItem(`conversation:${conversationId}`, conversation) }, - [addConversation, agent.id, conversationId, messages] + [conversationId, messages] ) const fetchChatCompletion = useCallback( @@ -86,6 +89,8 @@ const Conversations: FC = ({ agent, conversationId }) => { }) }, [conversationId]) + useEffect(() => hljs.highlightAll()) + return ( {lastMessage && } diff --git a/src/renderer/src/pages/home/components/Chat/Inputbar.tsx b/src/renderer/src/pages/home/components/Chat/Inputbar.tsx index b29a2136..3ce494df 100644 --- a/src/renderer/src/pages/home/components/Chat/Inputbar.tsx +++ b/src/renderer/src/pages/home/components/Chat/Inputbar.tsx @@ -1,11 +1,12 @@ import { EVENT_NAMES, EventEmitter } from '@renderer/services/event' -import { Agent, Message } from '@renderer/types' +import { Agent, Conversation, Message } from '@renderer/types' import { uuid } from '@renderer/utils' import { FC, useState } from 'react' import styled from 'styled-components' import { MoreOutlined } from '@ant-design/icons' import { Tooltip } from 'antd' import { useShowRightSidebar } from '@renderer/hooks/useStore' +import { useAgent } from '@renderer/hooks/useAgents' interface Props { agent: Agent @@ -14,6 +15,7 @@ interface Props { const Inputbar: FC = ({ agent }) => { const [text, setText] = useState('') const { setShowRightSidebar } = useShowRightSidebar() + const { addConversation } = useAgent(agent.id) const handleKeyDown = (event: React.KeyboardEvent) => { if (event.key === 'Enter') { @@ -35,18 +37,27 @@ const Inputbar: FC = ({ agent }) => { } } + const addNewConversation = () => { + const conversation: Conversation = { + id: uuid(), + name: 'Default Topic', + messages: [] + } + addConversation(conversation) + } + return ( - + - + diff --git a/src/renderer/src/pages/home/components/Chat/Message.tsx b/src/renderer/src/pages/home/components/Chat/Message.tsx index b670f2ad..04dbbd84 100644 --- a/src/renderer/src/pages/home/components/Chat/Message.tsx +++ b/src/renderer/src/pages/home/components/Chat/Message.tsx @@ -1,19 +1,15 @@ import { Message } from '@renderer/types' import { Avatar } from 'antd' -import hljs from 'highlight.js' import { marked } from 'marked' -import { FC, useEffect } from 'react' +import { FC } from 'react' import styled from 'styled-components' +import Logo from '@renderer/assets/images/logo.png' const MessageItem: FC<{ message: Message }> = ({ message }) => { - useEffect(() => { - hljs.highlightAll() - }) - return ( - Y + {message.role === 'agent' ? : Y}
diff --git a/src/renderer/src/pages/home/components/Chat/TopicList.tsx b/src/renderer/src/pages/home/components/Chat/TopicList.tsx index 9ce54ad2..23cf2471 100644 --- a/src/renderer/src/pages/home/components/Chat/TopicList.tsx +++ b/src/renderer/src/pages/home/components/Chat/TopicList.tsx @@ -1,21 +1,61 @@ import { useShowRightSidebar } from '@renderer/hooks/useStore' -import { FC } from 'react' +import { Agent } from '@renderer/types' +import { FC, useEffect, useState } from 'react' import styled from 'styled-components' -const TopicList: FC = () => { - const { showRightSidebar } = useShowRightSidebar() +interface Props { + agent: Agent +} - return +const TopicList: FC = ({ agent }) => { + const { showRightSidebar } = useShowRightSidebar() + const [activeTopic, setActiveTopic] = useState(agent.conversations[0]) + + useEffect(() => { + setActiveTopic(agent.conversations[0]) + }, [agent.conversations, agent.id]) + + if (!showRightSidebar) { + return null + } + + return ( + + {agent.conversations.map((topic) => ( + setActiveTopic(topic)}> + {topic.name} + + ))} + + ) } const Container = styled.div` width: var(--topic-list-width); height: 100%; border-left: 0.5px solid #ffffff20; + padding: 10px; &.collapsed { width: 0; border-left: none; } ` +const TopicListItem = styled.div` + padding: 8px 15px; + margin-bottom: 5px; + cursor: pointer; + border-radius: 5px; + font-size: 13px; + &:hover { + background-color: var(--color-background-soft); + } + &.active { + background-color: var(--color-background-soft); + } +` + export default TopicList diff --git a/src/renderer/src/services/agent.ts b/src/renderer/src/services/agent.ts index 73f2fb82..ec2e494b 100644 --- a/src/renderer/src/services/agent.ts +++ b/src/renderer/src/services/agent.ts @@ -1,8 +1,17 @@ -export function getDefaultAgent() { +import { Agent } from '@renderer/types' +import { uuid } from '@renderer/utils' + +export function getDefaultAgent(): Agent { return { id: 'default', name: 'Default Agent', description: "Hello, I'm Default Agent.", - conversations: [] + conversations: [ + { + id: uuid(), + name: 'Default Topic', + messages: [] + } + ] } } diff --git a/src/renderer/src/store/agents.ts b/src/renderer/src/store/agents.ts index 2b2bff38..fc816871 100644 --- a/src/renderer/src/store/agents.ts +++ b/src/renderer/src/store/agents.ts @@ -1,6 +1,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { getDefaultAgent } from '@renderer/services/agent' -import { Agent } from '@renderer/types' +import { Agent, Conversation } from '@renderer/types' +import { uniqBy } from 'lodash' export interface AgentsState { agents: Agent[] @@ -23,22 +24,23 @@ const agentsSlice = createSlice({ updateAgent: (state, action: PayloadAction) => { state.agents = state.agents.map((c) => (c.id === action.payload.id ? action.payload : c)) }, - addConversationToAgent: (state, action: PayloadAction<{ agentId: string; conversationId: string }>) => { + addConversation: (state, action: PayloadAction<{ agentId: string; conversation: Conversation }>) => { + console.debug(action.payload) state.agents = state.agents.map((agent) => agent.id === action.payload.agentId ? { ...agent, - conversations: [...new Set([...agent.conversations, action.payload.conversationId])] + conversations: uniqBy([action.payload.conversation, ...agent.conversations], 'id') } : agent ) }, - removeConversationFromAgent: (state, action: PayloadAction<{ agentId: string; conversationId: string }>) => { + removeConversation: (state, action: PayloadAction<{ agentId: string; conversation: Conversation }>) => { state.agents = state.agents.map((agent) => agent.id === action.payload.agentId ? { ...agent, - conversations: agent.conversations.filter((id) => id !== action.payload.conversationId) + conversations: agent.conversations.filter(({ id }) => id !== action.payload.conversation.id) } : agent ) @@ -46,7 +48,6 @@ const agentsSlice = createSlice({ } }) -export const { addAgent, removeAgent, updateAgent, addConversationToAgent, removeConversationFromAgent } = - agentsSlice.actions +export const { addAgent, removeAgent, updateAgent, addConversation, removeConversation } = agentsSlice.actions export default agentsSlice.reducer diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index d34cc93b..c3533b14 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -5,7 +5,7 @@ export interface SettingsState { } const initialState: SettingsState = { - showRightSidebar: true + showRightSidebar: false } const settingsSlice = createSlice({ diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 3dd71c45..15d9c457 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -2,7 +2,7 @@ export type Agent = { id: string name: string description: string - conversations: string[] + conversations: Conversation[] } export type Message = { @@ -16,6 +16,7 @@ export type Message = { export type Conversation = { id: string + name: string messages: Message[] }