diff --git a/src/renderer/src/hooks/useAgents.ts b/src/renderer/src/hooks/useAgents.ts index 5fc013b9..6734e486 100644 --- a/src/renderer/src/hooks/useAgents.ts +++ b/src/renderer/src/hooks/useAgents.ts @@ -1,6 +1,7 @@ import { useAppDispatch, useAppSelector } from '@renderer/store' import { addTopic as _addTopic, + removeAllTopics as _removeAllTopics, removeTopic as _removeTopic, updateTopic as _updateTopic, addAgent, @@ -35,13 +36,16 @@ export function useAgent(id: string) { return { agent, addTopic: (topic: Topic) => { - dispatch(_addTopic({ agentId: agent?.id, topic })) + dispatch(_addTopic({ agentId: agent.id, topic })) }, removeTopic: (topic: Topic) => { - dispatch(_removeTopic({ agentId: agent?.id, topic })) + dispatch(_removeTopic({ agentId: agent.id, topic })) }, updateTopic: (topic: Topic) => { - dispatch(_updateTopic({ agentId: agent?.id, topic })) + dispatch(_updateTopic({ agentId: agent.id, topic })) + }, + removeAllTopics: () => { + dispatch(_removeAllTopics({ agentId: agent.id })) } } } diff --git a/src/renderer/src/pages/home/components/Chat/Conversations.tsx b/src/renderer/src/pages/home/components/Chat/Conversations.tsx index 0ce5b786..3356ac77 100644 --- a/src/renderer/src/pages/home/components/Chat/Conversations.tsx +++ b/src/renderer/src/pages/home/components/Chat/Conversations.tsx @@ -7,10 +7,10 @@ import MessageItem from './Message' import { reverse } from 'lodash' import hljs from 'highlight.js' import { fetchChatCompletion, fetchConversationSummary } from '@renderer/services/api' -import { getTopicMessages } from '@renderer/services/topic' import { useAgent } from '@renderer/hooks/useAgents' import { DEFAULT_TOPIC_NAME } from '@renderer/config/constant' import { runAsyncFunction } from '@renderer/utils' +import LocalStorage from '@renderer/services/storage' interface Props { agent: Agent @@ -61,7 +61,7 @@ const Conversations: FC = ({ agent, topic }) => { useEffect(() => { runAsyncFunction(async () => { - const messages = await getTopicMessages(topic.id) + const messages = await LocalStorage.getTopicMessages(topic.id) setMessages(messages) }) }, [topic.id]) diff --git a/src/renderer/src/pages/home/components/Chat/TopicList.tsx b/src/renderer/src/pages/home/components/Chat/TopicList.tsx index 4a9fe4ae..edfcde11 100644 --- a/src/renderer/src/pages/home/components/Chat/TopicList.tsx +++ b/src/renderer/src/pages/home/components/Chat/TopicList.tsx @@ -2,11 +2,12 @@ import PromptPopup from '@renderer/components/Popups/PromptPopup' import { useAgent } from '@renderer/hooks/useAgents' import { useShowRightSidebar } from '@renderer/hooks/useStore' import { fetchConversationSummary } from '@renderer/services/api' -import { getTopicMessages } from '@renderer/services/topic' import { Agent, Topic } from '@renderer/types' -import { Dropdown, MenuProps } from 'antd' +import { Button, Dropdown, MenuProps, Popconfirm } from 'antd' import { FC, useRef } from 'react' import styled from 'styled-components' +import { DeleteOutlined } from '@ant-design/icons' +import LocalStorage from '@renderer/services/storage' interface Props { agent: Agent @@ -16,16 +17,16 @@ interface Props { const TopicList: FC = ({ agent, activeTopic, setActiveTopic }) => { const { showRightSidebar } = useShowRightSidebar() - const { removeTopic, updateTopic } = useAgent(agent.id) + const { removeTopic, updateTopic, removeAllTopics } = useAgent(agent.id) const currentTopic = useRef(null) - const items: MenuProps['items'] = [ + const topicMenuItems: MenuProps['items'] = [ { label: 'AI Rename', key: 'ai-rename', async onClick() { if (currentTopic.current) { - const messages = await getTopicMessages(currentTopic.current.id) + const messages = await LocalStorage.getTopicMessages(currentTopic.current.id) if (messages.length >= 2) { const summaryText = await fetchConversationSummary({ messages }) if (summaryText) { @@ -52,8 +53,8 @@ const TopicList: FC = ({ agent, activeTopic, setActiveTopic }) => { ] if (agent.topics.length > 1) { - items.push({ type: 'divider' }) - items.push({ + topicMenuItems.push({ type: 'divider' }) + topicMenuItems.push({ label: 'Delete', danger: true, key: 'delete', @@ -72,10 +73,23 @@ const TopicList: FC = ({ agent, activeTopic, setActiveTopic }) => { return ( - Topics ({agent.topics.length}) + + Topics ({agent.topics.length}) + + + + + + {agent.topics.map((topic) => ( open && (currentTopic.current = topic)}> @@ -118,6 +132,26 @@ const TopicTitle = styled.div` margin-bottom: 10px; font-size: 14px; color: var(--color-text-1); + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +` + +const DeleteButton = styled(Button)` + width: 30px; + height: 30px; + border-radius: 50%; + padding: 0; + &:hover { + .anticon { + color: #ff4d4f; + } + } +` + +const DeleteIcon = styled(DeleteOutlined)` + font-size: 16px; ` export default TopicList diff --git a/src/renderer/src/services/agent.ts b/src/renderer/src/services/agent.ts index 42ee0cc3..6dd541f4 100644 --- a/src/renderer/src/services/agent.ts +++ b/src/renderer/src/services/agent.ts @@ -1,6 +1,5 @@ -import { DEFAULT_TOPIC_NAME } from '@renderer/config/constant' import { Agent } from '@renderer/types' -import { uuid } from '@renderer/utils' +import { getDefaultTopic } from './topic' export function getDefaultAgent(): Agent { return { @@ -8,12 +7,6 @@ export function getDefaultAgent(): Agent { name: 'Default Agent', description: "Hello, I'm Default Agent.", prompt: '', - topics: [ - { - id: uuid(), - name: DEFAULT_TOPIC_NAME, - messages: [] - } - ] + topics: [getDefaultTopic()] } } diff --git a/src/renderer/src/services/storage.ts b/src/renderer/src/services/storage.ts new file mode 100644 index 00000000..3dc21869 --- /dev/null +++ b/src/renderer/src/services/storage.ts @@ -0,0 +1,13 @@ +import { Topic } from '@renderer/types' +import localforage from 'localforage' + +export default class LocalStorage { + static async getTopicMessages(id: string) { + const topic = await localforage.getItem(`topic:${id}`) + return topic ? topic.messages : [] + } + + static async removeTopic(id: string) { + localforage.removeItem(`topic:${id}`) + } +} diff --git a/src/renderer/src/services/topic.ts b/src/renderer/src/services/topic.ts index 193394c3..fb1fb5af 100644 --- a/src/renderer/src/services/topic.ts +++ b/src/renderer/src/services/topic.ts @@ -1,7 +1,10 @@ import { Topic } from '@renderer/types' -import localforage from 'localforage' +import { uuid } from '@renderer/utils' -export async function getTopicMessages(id: string) { - const topic = await localforage.getItem(`topic:${id}`) - return topic ? topic.messages : [] +export function getDefaultTopic(): Topic { + return { + id: uuid(), + name: 'Default Topic', + messages: [] + } } diff --git a/src/renderer/src/store/agents.ts b/src/renderer/src/store/agents.ts index e12c8cb5..576425e7 100644 --- a/src/renderer/src/store/agents.ts +++ b/src/renderer/src/store/agents.ts @@ -1,5 +1,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { getDefaultAgent } from '@renderer/services/agent' +import LocalStorage from '@renderer/services/storage' +import { getDefaultTopic } from '@renderer/services/topic' import { Agent, Topic } from '@renderer/types' import { uniqBy } from 'lodash' @@ -53,10 +55,23 @@ const agentsSlice = createSlice({ } : agent ) + }, + removeAllTopics: (state, action: PayloadAction<{ agentId: string }>) => { + state.agents = state.agents.map((agent) => { + if (agent.id === action.payload.agentId) { + agent.topics.forEach((topic) => LocalStorage.removeTopic(topic.id)) + return { + ...agent, + topics: [getDefaultTopic()] + } + } + return agent + }) } } }) -export const { addAgent, removeAgent, updateAgent, addTopic, removeTopic, updateTopic } = agentsSlice.actions +export const { addAgent, removeAgent, updateAgent, addTopic, removeTopic, updateTopic, removeAllTopics } = + agentsSlice.actions export default agentsSlice.reducer