feat: switch topic

This commit is contained in:
kangfenmao 2024-07-01 17:41:33 +08:00
parent 2b4c4f46e6
commit 4b17e4cd16
8 changed files with 52 additions and 51 deletions

View File

@ -1,12 +1,12 @@
import { useAppDispatch, useAppSelector } from '@renderer/store' import { useAppDispatch, useAppSelector } from '@renderer/store'
import { import {
addConversation as _addConversation, addTopic as _addTopic,
removeConversation as _removeConversation, removeTopic as _removeTopic,
addAgent, addAgent,
removeAgent, removeAgent,
updateAgent updateAgent
} from '@renderer/store/agents' } from '@renderer/store/agents'
import { Agent, Conversation } from '@renderer/types' import { Agent, Topic } from '@renderer/types'
import localforage from 'localforage' import localforage from 'localforage'
export default function useAgents() { export default function useAgents() {
@ -20,7 +20,7 @@ export default function useAgents() {
dispatch(removeAgent({ id })) dispatch(removeAgent({ id }))
const agent = agents.find((a) => a.id === id) const agent = agents.find((a) => a.id === id)
if (agent) { if (agent) {
agent.conversations.forEach((id) => localforage.removeItem(`conversation:${id}`)) agent.topics.forEach((id) => localforage.removeItem(`topic:${id}`))
} }
}, },
updateAgent: (agent: Agent) => dispatch(updateAgent(agent)) updateAgent: (agent: Agent) => dispatch(updateAgent(agent))
@ -33,11 +33,11 @@ export function useAgent(id: string) {
return { return {
agent, agent,
addConversation: (conversation: Conversation) => { addTopic: (topic: Topic) => {
dispatch(_addConversation({ agentId: agent?.id!, conversation })) dispatch(_addTopic({ agentId: agent?.id!, topic }))
}, },
removeConversation: (conversation: Conversation) => { removeTopic: (topic: Topic) => {
dispatch(_removeConversation({ agentId: agent?.id!, conversation })) dispatch(_removeTopic({ agentId: agent?.id!, topic }))
} }
} }
} }

View File

@ -1,5 +1,5 @@
import { Agent } from '@renderer/types' import { Agent } from '@renderer/types'
import { FC } from 'react' import { FC, useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import Inputbar from './Inputbar' import Inputbar from './Inputbar'
import Conversations from './Conversations' import Conversations from './Conversations'
@ -13,6 +13,7 @@ interface Props {
const Chat: FC<Props> = (props) => { const Chat: FC<Props> = (props) => {
const { agent } = useAgent(props.agent.id) const { agent } = useAgent(props.agent.id)
const [activeTopic, setActiveTopic] = useState(agent.topics[0])
if (!agent) { if (!agent) {
return null return null
@ -21,10 +22,10 @@ const Chat: FC<Props> = (props) => {
return ( return (
<Container id="chat"> <Container id="chat">
<Flex vertical flex={1} justify="space-between"> <Flex vertical flex={1} justify="space-between">
<Conversations agent={agent} /> <Conversations agent={agent} topic={activeTopic} />
<Inputbar agent={agent} /> <Inputbar agent={agent} setActiveTopic={setActiveTopic} />
</Flex> </Flex>
<TopicList agent={agent} /> <TopicList agent={agent} activeTopic={activeTopic} setActiveTopic={setActiveTopic} />
</Container> </Container>
) )
} }

View File

@ -1,6 +1,6 @@
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event' import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
import { openaiProvider } from '@renderer/services/provider' import { openaiProvider } from '@renderer/services/provider'
import { Agent, Conversation, Message } from '@renderer/types' import { Agent, Message, Topic } from '@renderer/types'
import { runAsyncFunction, uuid } from '@renderer/utils' import { runAsyncFunction, uuid } from '@renderer/utils'
import localforage from 'localforage' import localforage from 'localforage'
import { FC, useCallback, useEffect, useState } from 'react' import { FC, useCallback, useEffect, useState } from 'react'
@ -11,28 +11,29 @@ import hljs from 'highlight.js'
interface Props { interface Props {
agent: Agent agent: Agent
topic: Topic
} }
const Conversations: FC<Props> = ({ agent }) => { const Conversations: FC<Props> = ({ agent, topic }) => {
const [messages, setMessages] = useState<Message[]>([]) const [messages, setMessages] = useState<Message[]>([])
const [lastMessage, setLastMessage] = useState<Message | null>(null) const [lastMessage, setLastMessage] = useState<Message | null>(null)
const { id: conversationId } = agent.conversations[0] const { id: topicId } = topic
const onSendMessage = useCallback( const onSendMessage = useCallback(
(message: Message) => { (message: Message) => {
const _messages = [...messages, message] const _messages = [...messages, message]
setMessages(_messages) setMessages(_messages)
const conversation = { const topic = {
id: conversationId, id: topicId,
name: 'Default Topic', name: 'Default Topic',
messages: _messages messages: _messages
} }
localforage.setItem<Conversation>(`conversation:${conversationId}`, conversation) localforage.setItem<Topic>(`topic:${topicId}`, topic)
}, },
[conversationId, messages] [topicId, messages]
) )
const fetchChatCompletion = useCallback( const fetchChatCompletion = useCallback(
@ -48,7 +49,7 @@ const Conversations: FC<Props> = ({ agent }) => {
role: 'agent', role: 'agent',
content: '', content: '',
agentId: agent.id, agentId: agent.id,
conversationId, topicId,
createdAt: 'now' createdAt: 'now'
} }
@ -65,7 +66,7 @@ const Conversations: FC<Props> = ({ agent }) => {
return _message return _message
}, },
[agent.id, conversationId] [agent.id, topicId]
) )
useEffect(() => { useEffect(() => {
@ -84,15 +85,15 @@ const Conversations: FC<Props> = ({ agent }) => {
useEffect(() => { useEffect(() => {
runAsyncFunction(async () => { runAsyncFunction(async () => {
const conversation = await localforage.getItem<Conversation>(`conversation:${conversationId}`) const topic = await localforage.getItem<Topic>(`topic:${topicId}`)
setMessages(conversation ? conversation.messages : []) setMessages(topic ? topic.messages : [])
}) })
}, [conversationId]) }, [topicId])
useEffect(() => hljs.highlightAll()) useEffect(() => hljs.highlightAll())
return ( return (
<Container id="conversations"> <Container id="topics">
{lastMessage && <MessageItem message={lastMessage} />} {lastMessage && <MessageItem message={lastMessage} />}
{reverse([...messages]).map((message) => ( {reverse([...messages]).map((message) => (
<MessageItem message={message} key={message.id} /> <MessageItem message={message} key={message.id} />

View File

@ -1,5 +1,5 @@
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event' import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
import { Agent, Conversation, Message } from '@renderer/types' import { Agent, Message, Topic } from '@renderer/types'
import { uuid } from '@renderer/utils' import { uuid } from '@renderer/utils'
import { FC, useState } from 'react' import { FC, useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
@ -10,23 +10,24 @@ import { useAgent } from '@renderer/hooks/useAgents'
interface Props { interface Props {
agent: Agent agent: Agent
setActiveTopic: (topic: Topic) => void
} }
const Inputbar: FC<Props> = ({ agent }) => { const Inputbar: FC<Props> = ({ agent, setActiveTopic }) => {
const [text, setText] = useState('') const [text, setText] = useState('')
const { setShowRightSidebar } = useShowRightSidebar() const { setShowRightSidebar } = useShowRightSidebar()
const { addConversation } = useAgent(agent.id) const { addTopic } = useAgent(agent.id)
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => { const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
const conversationId = agent.conversations[0] ? agent.conversations[0] : uuid() const topicId = agent.topics[0] ? agent.topics[0] : uuid()
const message: Message = { const message: Message = {
id: uuid(), id: uuid(),
role: 'user', role: 'user',
content: text, content: text,
agentId: agent.id, agentId: agent.id,
conversationId, topicId,
createdAt: 'now' createdAt: 'now'
} }
@ -38,12 +39,13 @@ const Inputbar: FC<Props> = ({ agent }) => {
} }
const addNewConversation = () => { const addNewConversation = () => {
const conversation: Conversation = { const topic: Topic = {
id: uuid(), id: uuid(),
name: 'Default Topic', name: 'Default Topic',
messages: [] messages: []
} }
addConversation(conversation) addTopic(topic)
setActiveTopic(topic)
} }
return ( return (

View File

@ -1,19 +1,16 @@
import { useShowRightSidebar } from '@renderer/hooks/useStore' import { useShowRightSidebar } from '@renderer/hooks/useStore'
import { Agent } from '@renderer/types' import { Agent, Topic } from '@renderer/types'
import { FC, useEffect, useState } from 'react' import { FC } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
interface Props { interface Props {
agent: Agent agent: Agent
activeTopic: Topic
setActiveTopic: (topic: Topic) => void
} }
const TopicList: FC<Props> = ({ agent }) => { const TopicList: FC<Props> = ({ agent, activeTopic, setActiveTopic }) => {
const { showRightSidebar } = useShowRightSidebar() const { showRightSidebar } = useShowRightSidebar()
const [activeTopic, setActiveTopic] = useState(agent.conversations[0])
useEffect(() => {
setActiveTopic(agent.conversations[0])
}, [agent.conversations, agent.id])
if (!showRightSidebar) { if (!showRightSidebar) {
return null return null
@ -21,7 +18,7 @@ const TopicList: FC<Props> = ({ agent }) => {
return ( return (
<Container className={showRightSidebar ? '' : 'collapsed'}> <Container className={showRightSidebar ? '' : 'collapsed'}>
{agent.conversations.map((topic) => ( {agent.topics.map((topic) => (
<TopicListItem <TopicListItem
key={topic.id} key={topic.id}
className={topic.id === activeTopic?.id ? 'active' : ''} className={topic.id === activeTopic?.id ? 'active' : ''}

View File

@ -6,7 +6,7 @@ export function getDefaultAgent(): Agent {
id: 'default', id: 'default',
name: 'Default Agent', name: 'Default Agent',
description: "Hello, I'm Default Agent.", description: "Hello, I'm Default Agent.",
conversations: [ topics: [
{ {
id: uuid(), id: uuid(),
name: 'Default Topic', name: 'Default Topic',

View File

@ -1,6 +1,6 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { getDefaultAgent } from '@renderer/services/agent' import { getDefaultAgent } from '@renderer/services/agent'
import { Agent, Conversation } from '@renderer/types' import { Agent, Topic } from '@renderer/types'
import { uniqBy } from 'lodash' import { uniqBy } from 'lodash'
export interface AgentsState { export interface AgentsState {
@ -24,23 +24,23 @@ const agentsSlice = createSlice({
updateAgent: (state, action: PayloadAction<Agent>) => { updateAgent: (state, action: PayloadAction<Agent>) => {
state.agents = state.agents.map((c) => (c.id === action.payload.id ? action.payload : c)) state.agents = state.agents.map((c) => (c.id === action.payload.id ? action.payload : c))
}, },
addConversation: (state, action: PayloadAction<{ agentId: string; conversation: Conversation }>) => { addTopic: (state, action: PayloadAction<{ agentId: string; topic: Topic }>) => {
console.debug(action.payload) console.debug(action.payload)
state.agents = state.agents.map((agent) => state.agents = state.agents.map((agent) =>
agent.id === action.payload.agentId agent.id === action.payload.agentId
? { ? {
...agent, ...agent,
conversations: uniqBy([action.payload.conversation, ...agent.conversations], 'id') topics: uniqBy([action.payload.topic, ...agent.topics], 'id')
} }
: agent : agent
) )
}, },
removeConversation: (state, action: PayloadAction<{ agentId: string; conversation: Conversation }>) => { removeTopic: (state, action: PayloadAction<{ agentId: string; topic: Topic }>) => {
state.agents = state.agents.map((agent) => state.agents = state.agents.map((agent) =>
agent.id === action.payload.agentId agent.id === action.payload.agentId
? { ? {
...agent, ...agent,
conversations: agent.conversations.filter(({ id }) => id !== action.payload.conversation.id) topics: agent.topics.filter(({ id }) => id !== action.payload.topic.id)
} }
: agent : agent
) )
@ -48,6 +48,6 @@ const agentsSlice = createSlice({
} }
}) })
export const { addAgent, removeAgent, updateAgent, addConversation, removeConversation } = agentsSlice.actions export const { addAgent, removeAgent, updateAgent, addTopic, removeTopic } = agentsSlice.actions
export default agentsSlice.reducer export default agentsSlice.reducer

View File

@ -2,7 +2,7 @@ export type Agent = {
id: string id: string
name: string name: string
description: string description: string
conversations: Conversation[] topics: Topic[]
} }
export type Message = { export type Message = {
@ -10,11 +10,11 @@ export type Message = {
role: 'user' | 'agent' role: 'user' | 'agent'
content: string content: string
agentId: string agentId: string
conversationId: string topicId: string
createdAt: string createdAt: string
} }
export type Conversation = { export type Topic = {
id: string id: string
name: string name: string
messages: Message[] messages: Message[]