From 509632030b587822cbdb9c279aa404ebf6de690d Mon Sep 17 00:00:00 2001 From: suyao Date: Fri, 13 Jun 2025 05:14:53 +0800 Subject: [PATCH] refactor(assistant): replace useAgents with useAssistants across components, streamline assistant management and enhance template handling --- .../components/Popups/AddAssistantPopup.tsx | 3 +- src/renderer/src/hooks/useAgents.ts | 28 -------- src/renderer/src/hooks/useAssistant.ts | 19 ++++- src/renderer/src/hooks/useKnowledge.ts | 14 ---- src/renderer/src/pages/agents/AgentsPage.tsx | 4 +- .../pages/agents/components/AddAgentPopup.tsx | 10 +-- .../src/pages/agents/components/AgentCard.tsx | 4 +- .../agents/components/ImportAgentPopup.tsx | 4 +- .../agents/components/ManageAgentsPopup.tsx | 5 +- .../pages/home/MainSidebar/MainSidebar.tsx | 8 +-- .../src/pages/home/Tabs/AssistantsTab.tsx | 4 -- .../home/Tabs/components/AssistantItem.tsx | 9 +-- .../settings/AssistantSettings/index.tsx | 23 +++--- src/renderer/src/services/AssistantService.ts | 22 ++++-- src/renderer/src/store/agents.ts | 50 +++---------- src/renderer/src/store/assistants.ts | 70 +++++++++++++++---- src/renderer/src/store/index.ts | 2 +- src/renderer/src/store/migrate.ts | 31 ++++++++ src/renderer/src/types/index.ts | 10 +-- 19 files changed, 168 insertions(+), 152 deletions(-) delete mode 100644 src/renderer/src/hooks/useAgents.ts diff --git a/src/renderer/src/components/Popups/AddAssistantPopup.tsx b/src/renderer/src/components/Popups/AddAssistantPopup.tsx index 8215ee0ae5..3a82081401 100644 --- a/src/renderer/src/components/Popups/AddAssistantPopup.tsx +++ b/src/renderer/src/components/Popups/AddAssistantPopup.tsx @@ -1,5 +1,4 @@ import { TopView } from '@renderer/components/TopView' -import { useAgents } from '@renderer/hooks/useAgents' import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant' import { useSystemAgents } from '@renderer/pages/agents' import { createAssistantFromAgent } from '@renderer/services/AssistantService' @@ -24,7 +23,7 @@ interface Props { const PopupContainer: React.FC = ({ resolve }) => { const [open, setOpen] = useState(true) const { t } = useTranslation() - const { agents: userAgents } = useAgents() + const { assistants: userAgents } = useAssistants() const [searchText, setSearchText] = useState('') const { defaultAssistant } = useDefaultAssistant() const { assistants, addAssistant } = useAssistants() diff --git a/src/renderer/src/hooks/useAgents.ts b/src/renderer/src/hooks/useAgents.ts deleted file mode 100644 index 238f04ec5c..0000000000 --- a/src/renderer/src/hooks/useAgents.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useAppDispatch, useAppSelector } from '@renderer/store' -import { addAgent, removeAgent, updateAgent, updateAgents, updateAgentSettings } from '@renderer/store/agents' -import { Agent, AssistantSettings } from '@renderer/types' - -export function useAgents() { - const agents = useAppSelector((state) => state.agents.agents) - const dispatch = useAppDispatch() - - return { - agents, - updateAgents: (agents: Agent[]) => dispatch(updateAgents(agents)), - addAgent: (agent: Agent) => dispatch(addAgent(agent)), - removeAgent: (id: string) => dispatch(removeAgent({ id })) - } -} - -export function useAgent(id: string) { - const agent = useAppSelector((state) => state.agents.agents.find((a) => a.id === id) as Agent) - const dispatch = useAppDispatch() - - return { - agent, - updateAgent: (agent: Agent) => dispatch(updateAgent(agent)), - updateAgentSettings: (settings: Partial) => { - dispatch(updateAgentSettings({ assistantId: agent.id, settings })) - } - } -} diff --git a/src/renderer/src/hooks/useAssistant.ts b/src/renderer/src/hooks/useAssistant.ts index 130c3282a8..6e02809bac 100644 --- a/src/renderer/src/hooks/useAssistant.ts +++ b/src/renderer/src/hooks/useAssistant.ts @@ -2,7 +2,10 @@ import { db } from '@renderer/databases' import { useAppDispatch, useAppSelector } from '@renderer/store' import { addAssistant, + createAssistantFromTemplate, removeAssistant, + selectActiveAssistants, + selectTemplates, setModel, updateAssistant, updateAssistants, @@ -15,20 +18,32 @@ import { Assistant, AssistantSettings, Model, Topic } from '@renderer/types' import { useCallback, useMemo } from 'react' export function useAssistants() { - const { assistants } = useAppSelector((state) => state.assistants) + const assistants = useAppSelector(selectActiveAssistants) + const templates = useAppSelector(selectTemplates) const dispatch = useAppDispatch() + const getAssistantById = useCallback((id: string) => assistants.find((a) => a.id === id), [assistants]) + return { assistants, + templates, + getAssistantById, updateAssistants: (assistants: Assistant[]) => dispatch(updateAssistants(assistants)), addAssistant: (assistant: Assistant) => { - dispatch(addAssistant(assistant)) + dispatch(addAssistant({ ...assistant, isTemplate: false })) dispatch(topicsActions.addDefaultTopic({ assistantId: assistant.id })) }, + addTemplate: (template: Assistant) => { + dispatch(addAssistant({ ...template, isTemplate: true })) + }, removeAssistant: (id: string) => { dispatch(removeAssistant({ id })) // Remove all topics for this assistant dispatch(topicsActions.removeAllTopics({ assistantId: id })) + }, + createAssistantFromTemplate: (templateId: string, assistantId: string) => { + dispatch(createAssistantFromTemplate({ templateId, assistantId })) + dispatch(topicsActions.addDefaultTopic({ assistantId })) } } } diff --git a/src/renderer/src/hooks/useKnowledge.ts b/src/renderer/src/hooks/useKnowledge.ts index 662829612e..deaaaa2526 100644 --- a/src/renderer/src/hooks/useKnowledge.ts +++ b/src/renderer/src/hooks/useKnowledge.ts @@ -26,7 +26,6 @@ import { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { v4 as uuidv4 } from 'uuid' -import { useAgents } from './useAgents' import { useAssistants } from './useAssistant' export const useKnowledge = (baseId: string) => { @@ -319,7 +318,6 @@ export const useKnowledgeBases = () => { const dispatch = useDispatch() const bases = useSelector((state: RootState) => state.knowledge.bases) const { assistants, updateAssistants } = useAssistants() - const { agents, updateAgents } = useAgents() const addKnowledgeBase = (base: KnowledgeBase) => { dispatch(addBase(base)) @@ -343,19 +341,7 @@ export const useKnowledgeBases = () => { return assistant }) - // remove agent knowledge_base - const _agents = agents.map((agent) => { - if (agent.knowledge_bases?.find((kb) => kb.id === baseId)) { - return { - ...agent, - knowledge_bases: agent.knowledge_bases.filter((kb) => kb.id !== baseId) - } - } - return agent - }) - updateAssistants(_assistants) - updateAgents(_agents) } const updateKnowledgeBases = (bases: KnowledgeBase[]) => { diff --git a/src/renderer/src/pages/agents/AgentsPage.tsx b/src/renderer/src/pages/agents/AgentsPage.tsx index 8cf05d1614..f6698711d1 100644 --- a/src/renderer/src/pages/agents/AgentsPage.tsx +++ b/src/renderer/src/pages/agents/AgentsPage.tsx @@ -3,7 +3,7 @@ import { NavbarCenter, NavbarMain } from '@renderer/components/app/Navbar' import CustomTag from '@renderer/components/CustomTag' import ListItem from '@renderer/components/ListItem' import Scrollbar from '@renderer/components/Scrollbar' -import { useAgents } from '@renderer/hooks/useAgents' +import { useAssistants } from '@renderer/hooks/useAssistant' import { createAssistantFromAgent } from '@renderer/services/AssistantService' import { Agent } from '@renderer/types' import { uuid } from '@renderer/utils' @@ -28,7 +28,7 @@ const AgentsPage: FC = () => { const [activeGroup, setActiveGroup] = useState('我的') const [agentGroups, setAgentGroups] = useState>({}) const systemAgents = useSystemAgents() - const { agents: userAgents } = useAgents() + const { templates: userAgents } = useAssistants() useEffect(() => { const systemAgentsGroupList = groupByCategories(systemAgents) diff --git a/src/renderer/src/pages/agents/components/AddAgentPopup.tsx b/src/renderer/src/pages/agents/components/AddAgentPopup.tsx index 108052a701..720bf4544c 100644 --- a/src/renderer/src/pages/agents/components/AddAgentPopup.tsx +++ b/src/renderer/src/pages/agents/components/AddAgentPopup.tsx @@ -4,7 +4,7 @@ import { CheckOutlined, LoadingOutlined, RollbackOutlined, ThunderboltOutlined } import EmojiPicker from '@renderer/components/EmojiPicker' import { TopView } from '@renderer/components/TopView' import { AGENT_PROMPT } from '@renderer/config/prompts' -import { useAgents } from '@renderer/hooks/useAgents' +import { useAssistants } from '@renderer/hooks/useAssistant' import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon' import { fetchGenerate } from '@renderer/services/ApiService' import { getDefaultModel } from '@renderer/services/AssistantService' @@ -34,7 +34,7 @@ const PopupContainer: React.FC = ({ resolve }) => { const [open, setOpen] = useState(true) const [form] = Form.useForm() const { t } = useTranslation() - const { addAgent } = useAgents() + const { addTemplate } = useAssistants() const formRef = useRef(null) const [emoji, setEmoji] = useState('') const [loading, setLoading] = useState(false) @@ -82,12 +82,12 @@ const PopupContainer: React.FC = ({ resolve }) => { emoji: _emoji, prompt: values.prompt, defaultModel: getDefaultModel(), - type: 'agent', topics: [], - messages: [] + messages: [], + isTemplate: true } - addAgent(_agent) + addTemplate(_agent) resolve(_agent) setOpen(false) } diff --git a/src/renderer/src/pages/agents/components/AgentCard.tsx b/src/renderer/src/pages/agents/components/AgentCard.tsx index 9e2f08eb1c..b5c46694bd 100644 --- a/src/renderer/src/pages/agents/components/AgentCard.tsx +++ b/src/renderer/src/pages/agents/components/AgentCard.tsx @@ -7,7 +7,7 @@ import { SortAscendingOutlined } from '@ant-design/icons' import CustomTag from '@renderer/components/CustomTag' -import { useAgents } from '@renderer/hooks/useAgents' +import { useAssistants } from '@renderer/hooks/useAssistant' import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings' import { createAssistantFromAgent } from '@renderer/services/AssistantService' import type { Agent } from '@renderer/types' @@ -27,7 +27,7 @@ interface Props { } const AgentCard: FC = ({ agent, onClick, activegroup, getLocalizedGroupName }) => { - const { removeAgent } = useAgents() + const { removeAssistant: removeAgent } = useAssistants() const [isVisible, setIsVisible] = useState(false) const cardRef = useRef(null) diff --git a/src/renderer/src/pages/agents/components/ImportAgentPopup.tsx b/src/renderer/src/pages/agents/components/ImportAgentPopup.tsx index 64c6f34b2f..926ece091f 100644 --- a/src/renderer/src/pages/agents/components/ImportAgentPopup.tsx +++ b/src/renderer/src/pages/agents/components/ImportAgentPopup.tsx @@ -1,5 +1,5 @@ import { TopView } from '@renderer/components/TopView' -import { useAgents } from '@renderer/hooks/useAgents' +import { useAssistants } from '@renderer/hooks/useAssistant' import { getDefaultModel } from '@renderer/services/AssistantService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { Agent } from '@renderer/types' @@ -16,7 +16,7 @@ const PopupContainer: React.FC = ({ resolve }) => { const [open, setOpen] = useState(true) const [form] = Form.useForm() const { t } = useTranslation() - const { addAgent } = useAgents() + const { addTemplate: addAgent } = useAssistants() const [importType, setImportType] = useState<'url' | 'file'>('url') const [loading, setLoading] = useState(false) diff --git a/src/renderer/src/pages/agents/components/ManageAgentsPopup.tsx b/src/renderer/src/pages/agents/components/ManageAgentsPopup.tsx index 5307293b1e..04c6a6d473 100644 --- a/src/renderer/src/pages/agents/components/ManageAgentsPopup.tsx +++ b/src/renderer/src/pages/agents/components/ManageAgentsPopup.tsx @@ -2,7 +2,7 @@ import { MenuOutlined } from '@ant-design/icons' import DragableList from '@renderer/components/DragableList' import { Box, HStack } from '@renderer/components/Layout' import { TopView } from '@renderer/components/TopView' -import { useAgents } from '@renderer/hooks/useAgents' +import { useAssistants } from '@renderer/hooks/useAssistant' import { Empty, Modal } from 'antd' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -11,7 +11,8 @@ import styled from 'styled-components' const PopupContainer: React.FC = () => { const [open, setOpen] = useState(true) const { t } = useTranslation() - const { agents, updateAgents } = useAgents() + const { assistants, updateAssistants: updateAgents } = useAssistants() + const agents = assistants.filter((a) => a.isTemplate) const onOk = () => { setOpen(false) diff --git a/src/renderer/src/pages/home/MainSidebar/MainSidebar.tsx b/src/renderer/src/pages/home/MainSidebar/MainSidebar.tsx index 5e1813a6bb..4fd201cc61 100644 --- a/src/renderer/src/pages/home/MainSidebar/MainSidebar.tsx +++ b/src/renderer/src/pages/home/MainSidebar/MainSidebar.tsx @@ -2,7 +2,6 @@ import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar' import UserPopup from '@renderer/components/Popups/UserPopup' import { AppLogo, UserAvatar } from '@renderer/config/env' import { useTheme } from '@renderer/context/ThemeProvider' -import { useAssistants } from '@renderer/hooks/useAssistant' import useAvatar from '@renderer/hooks/useAvatar' import { useChat } from '@renderer/hooks/useChat' import { useMinappPopup } from '@renderer/hooks/useMinappPopup' @@ -11,6 +10,7 @@ import { useShortcut } from '@renderer/hooks/useShortcuts' import { useShowAssistants } from '@renderer/hooks/useStore' import i18n from '@renderer/i18n' import AssistantItem from '@renderer/pages/home/Tabs/components/AssistantItem' +import { getAssistantById } from '@renderer/services/AssistantService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { ThemeMode } from '@renderer/types' import { isEmoji } from '@renderer/utils' @@ -57,7 +57,6 @@ import PinnedApps from './PinnedApps' type Tab = 'assistants' | 'topic' const MainSidebar: FC = () => { - const { assistants } = useAssistants() const navigate = useNavigate() const [tab, setTab] = useState('assistants') const avatar = useAvatar() @@ -92,7 +91,7 @@ const MainSidebar: FC = () => { useEffect(() => { const unsubscribes = [ EventEmitter.on(EVENT_NAMES.SWITCH_ASSISTANT, (assistantId: string) => { - const newAssistant = assistants.find((a) => a.id === assistantId) + const newAssistant = getAssistantById(assistantId) if (newAssistant) { setActiveAssistant(newAssistant) } @@ -104,7 +103,7 @@ const MainSidebar: FC = () => { ] return () => unsubscribes.forEach((unsubscribe) => unsubscribe()) - }, [assistants, setActiveAssistant, tab]) + }, [setActiveAssistant, tab]) useEffect(() => { const canMinimize = !showAssistants && !showTopics @@ -204,7 +203,6 @@ const MainSidebar: FC = () => { sortBy="list" onSwitch={() => {}} onDelete={() => {}} - addAgent={() => {}} addAssistant={() => {}} onCreateDefaultAssistant={() => {}} handleSortByChange={() => {}} diff --git a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx index 183f3118a8..1e486e4cb7 100644 --- a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx @@ -2,7 +2,6 @@ import { DownOutlined, PlusOutlined, RightOutlined } from '@ant-design/icons' import { Draggable, Droppable, DropResult } from '@hello-pangea/dnd' import DragableList from '@renderer/components/DragableList' import Scrollbar from '@renderer/components/Scrollbar' -import { useAgents } from '@renderer/hooks/useAgents' import { useAssistants } from '@renderer/hooks/useAssistant' import { useAssistantsTabSortType } from '@renderer/hooks/useStore' import { useTags } from '@renderer/hooks/useTags' @@ -29,7 +28,6 @@ const Assistants: FC = ({ const { assistants, removeAssistant, addAssistant, updateAssistants } = useAssistants() const [dragging, setDragging] = useState(false) const [collapsedTags, setCollapsedTags] = useState>({}) - const { addAgent } = useAgents() const { t } = useTranslation() const { getGroupedAssistants, allTags, updateTagsOrder } = useTags() const { assistantsTabSortType = 'list', setAssistantsTabSortType } = useAssistantsTabSortType() @@ -206,7 +204,6 @@ const Assistants: FC = ({ isActive={assistant?.id === activeAssistant.id} onSwitch={setActiveAssistant} onDelete={onDelete} - addAgent={addAgent} addAssistant={addAssistant} onCreateDefaultAssistant={() => {}} /> @@ -249,7 +246,6 @@ const Assistants: FC = ({ sortBy={assistantsTabSortType} onSwitch={setActiveAssistant} onDelete={onDelete} - addAgent={addAgent} addAssistant={addAssistant} onCreateDefaultAssistant={onCreateDefaultAssistant} handleSortByChange={handleSortByChange} diff --git a/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx b/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx index 348e68876c..b23416e7e2 100644 --- a/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx +++ b/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx @@ -39,7 +39,6 @@ interface AssistantItemProps { onSwitch: (assistant: Assistant) => void onDelete: (assistant: Assistant) => void onCreateDefaultAssistant: () => void - addAgent: (agent: any) => void addAssistant: (assistant: Assistant) => void onTagClick?: (tag: string) => void handleSortByChange?: (sortType: AssistantsSortType) => void @@ -52,7 +51,6 @@ const AssistantItem: FC = ({ sortBy, onSwitch, onDelete, - addAgent, addAssistant, handleSortByChange, singleLine = false @@ -95,7 +93,6 @@ const AssistantItem: FC = ({ allTags, assistants, updateAssistants, - addAgent, addAssistant, onSwitch, onDelete, @@ -112,7 +109,6 @@ const AssistantItem: FC = ({ allTags, assistants, updateAssistants, - addAgent, addAssistant, onSwitch, onDelete, @@ -280,7 +276,6 @@ function getMenuItems({ allTags, assistants, updateAssistants, - addAgent, addAssistant, onSwitch, onDelete, @@ -329,8 +324,8 @@ function getMenuItems({ onClick: async () => { const agent = omit(assistant, ['model', 'emoji']) agent.id = uuid() - agent.type = 'agent' - addAgent(agent) + agent.isTemplate = true + addAssistant(agent) window.message.success({ content: t('assistants.save.success'), key: 'save-to-agent' diff --git a/src/renderer/src/pages/settings/AssistantSettings/index.tsx b/src/renderer/src/pages/settings/AssistantSettings/index.tsx index a107ed2275..474dbb6625 100644 --- a/src/renderer/src/pages/settings/AssistantSettings/index.tsx +++ b/src/renderer/src/pages/settings/AssistantSettings/index.tsx @@ -1,6 +1,5 @@ import { HStack } from '@renderer/components/Layout' import { TopView } from '@renderer/components/TopView' -import { useAgent } from '@renderer/hooks/useAgents' import { useAssistant } from '@renderer/hooks/useAssistant' import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon' import { Assistant } from '@renderer/types' @@ -31,13 +30,7 @@ const AssistantSettingPopupContainer: React.FC = ({ resolve, tab, ...prop const { t } = useTranslation() const [menu, setMenu] = useState(tab || 'prompt') - const _useAssistant = useAssistant(props.assistant.id) - const _useAgent = useAgent(props.assistant.id) - const isAgent = props.assistant.type === 'agent' - - const assistant = isAgent ? _useAgent.agent : _useAssistant.assistant - const updateAssistant = isAgent ? _useAgent.updateAgent : _useAssistant.updateAssistant - const updateAssistantSettings = isAgent ? _useAgent.updateAgentSettings : _useAssistant.updateAssistantSettings + const { updateAssistant, updateAssistantSettings } = useAssistant(props.assistant.id) const showKnowledgeIcon = useSidebarIconShow('knowledge') @@ -50,7 +43,7 @@ const AssistantSettingPopupContainer: React.FC = ({ resolve, tab, ...prop } const afterClose = () => { - resolve(assistant) + resolve(props.assistant) } const items = [ @@ -84,7 +77,7 @@ const AssistantSettingPopupContainer: React.FC = ({ resolve, tab, ...prop onCancel={onCancel} afterClose={afterClose} footer={null} - title={assistant.name} + title={props.assistant.name} transitionName="animation-move-down" styles={{ content: { @@ -112,34 +105,34 @@ const AssistantSettingPopupContainer: React.FC = ({ resolve, tab, ...prop {menu === 'prompt' && ( )} {menu === 'model' && ( )} {menu === 'knowledge_base' && showKnowledgeIcon && ( )} {menu === 'mcp' && ( )} {menu === 'regular_phrases' && ( - + )} diff --git a/src/renderer/src/services/AssistantService.ts b/src/renderer/src/services/AssistantService.ts index db5f3322b6..f4e54d358b 100644 --- a/src/renderer/src/services/AssistantService.ts +++ b/src/renderer/src/services/AssistantService.ts @@ -14,8 +14,8 @@ export function getDefaultAssistant(): Assistant { prompt: '', topics: [], messages: [], - type: 'assistant', - regularPhrases: [] // Added regularPhrases + regularPhrases: [], + isTemplate: false } } @@ -126,8 +126,8 @@ export async function createAssistantFromAgent(agent: Agent) { name: agent.name, emoji: agent.emoji, model: agent.defaultModel, - type: 'assistant', - regularPhrases: agent.regularPhrases || [] // Ensured regularPhrases + isTemplate: false, + regularPhrases: agent.regularPhrases || [] } store.dispatch(addAssistant(assistant)) @@ -140,3 +140,17 @@ export async function createAssistantFromAgent(agent: Agent) { return assistant } + +export function createTemplate(templateData: Partial): Assistant { + return { + id: uuid(), + name: templateData.name || 'New Template', + prompt: templateData.prompt || '', + topics: [], + messages: [], + type: 'assistant', + isTemplate: true, + regularPhrases: [], + ...templateData + } +} diff --git a/src/renderer/src/store/agents.ts b/src/renderer/src/store/agents.ts index 11bc493959..be2d757bb0 100644 --- a/src/renderer/src/store/agents.ts +++ b/src/renderer/src/store/agents.ts @@ -1,8 +1,13 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit' -import { DEFAULT_CONTEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant' -import { Agent, AssistantSettings } from '@renderer/types' +import { createSlice } from '@reduxjs/toolkit' +import { Agent } from '@renderer/types' +/** + * @deprecated use assistants instead + */ export interface AgentsState { + /** + * @deprecated use assistants instead + */ agents: Agent[] } @@ -13,44 +18,7 @@ const initialState: AgentsState = { const assistantsSlice = createSlice({ name: 'agents', initialState, - reducers: { - updateAgents: (state, action: PayloadAction) => { - state.agents = action.payload - }, - addAgent: (state, action: PayloadAction) => { - state.agents.push(action.payload) - }, - removeAgent: (state, action: PayloadAction<{ id: string }>) => { - state.agents = state.agents.filter((c) => c.id !== action.payload.id) - }, - updateAgent: (state, action: PayloadAction) => { - state.agents = state.agents.map((c) => (c.id === action.payload.id ? action.payload : c)) - }, - updateAgentSettings: ( - state, - action: PayloadAction<{ assistantId: string; settings: Partial }> - ) => { - for (const agent of state.agents) { - const settings = action.payload.settings - if (agent.id === action.payload.assistantId) { - for (const key in settings) { - if (!agent.settings) { - agent.settings = { - temperature: DEFAULT_TEMPERATURE, - contextCount: DEFAULT_CONTEXTCOUNT, - enableMaxTokens: false, - maxTokens: 0, - streamOutput: true - } - } - agent.settings[key] = settings[key] - } - } - } - } - } + reducers: {} }) -export const { updateAgents, addAgent, removeAgent, updateAgent, updateAgentSettings } = assistantsSlice.actions - export default assistantsSlice.reducer diff --git a/src/renderer/src/store/assistants.ts b/src/renderer/src/store/assistants.ts index f2f41a1935..42ed9a30ee 100644 --- a/src/renderer/src/store/assistants.ts +++ b/src/renderer/src/store/assistants.ts @@ -1,4 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import { createSelector } from '@reduxjs/toolkit' import { DEFAULT_CONTEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant' import { getDefaultAssistant } from '@renderer/services/AssistantService' import { Assistant, AssistantSettings, Model } from '@renderer/types' @@ -15,6 +16,30 @@ const initialState: AssistantsState = { tagsOrder: [] } +// ----------- selectors ----------- +// 基础selector +export const selectAssistantsState = (state: { assistants: AssistantsState }) => state.assistants + +// 获取所有助手(不含模板) +export const selectActiveAssistants = createSelector(selectAssistantsState, (state) => + state.assistants.filter((a) => !a.isTemplate) +) + +// 获取所有模板 +export const selectTemplates = createSelector(selectAssistantsState, (state) => + state.assistants.filter((a) => a.isTemplate) +) + +// 通过id查找助手(不含模板) +export const selectAssistantById = (id: string) => + createSelector(selectActiveAssistants, (assistants) => assistants.find((a) => a.id === id)) + +// 通过id查找模板 +export const selectTemplateById = (id: string) => + createSelector(selectTemplates, (templates) => templates.find((a) => a.id === id)) + +// ----------- end selectors ----------- + const assistantsSlice = createSlice({ name: 'assistants', initialState, @@ -24,7 +49,14 @@ const assistantsSlice = createSlice({ state.defaultAssistant = assistant }, updateAssistants: (state, action: PayloadAction) => { - state.assistants = action.payload + const assistants = action.payload + const templates = assistants.filter((a) => a.isTemplate) + state.assistants = [...assistants.filter((a) => !a.isTemplate), ...templates] + }, + updateTemplates: (state, action: PayloadAction) => { + const templates = action.payload + const assistants = state.assistants.filter((a) => !a.isTemplate) + state.assistants = [...assistants, ...templates] }, addAssistant: (state, action: PayloadAction) => { state.assistants.push(action.payload) @@ -40,9 +72,9 @@ const assistantsSlice = createSlice({ state, action: PayloadAction<{ assistantId: string; settings: Partial }> ) => { + const { assistantId, settings } = action.payload for (const assistant of state.assistants) { - const settings = action.payload.settings - if (assistant.id === action.payload.assistantId) { + if (assistant.id === assistantId) { for (const key in settings) { if (!assistant.settings) { assistant.settings = { @@ -58,17 +90,30 @@ const assistantsSlice = createSlice({ } } }, - setModel: (state, action: PayloadAction<{ assistantId: string; model: Model }>) => { const { assistantId, model } = action.payload - state.assistants = state.assistants.map((assistant) => - assistant.id === assistantId - ? { - ...assistant, - model: model - } - : assistant - ) + for (let i = 0; i < state.assistants.length; i++) { + if (state.assistants[i].id === assistantId) { + state.assistants[i] = { + ...state.assistants[i], + model: model + } + break + } + } + }, + // 从模板创建助手 + createAssistantFromTemplate: (state, action: PayloadAction<{ templateId: string; assistantId: string }>) => { + const { templateId, assistantId } = action.payload + const template = state.assistants.find((t) => t.id === templateId && t.isTemplate) + if (template) { + const newAssistant: Assistant = { + ...template, + id: assistantId, + isTemplate: false + } + state.assistants.push(newAssistant) + } }, setTagsOrder: (state, action: PayloadAction) => { state.tagsOrder = action.payload @@ -82,6 +127,7 @@ export const { addAssistant, removeAssistant, updateAssistant, + createAssistantFromTemplate, setModel, setTagsOrder, updateAssistantSettings diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index f5c0a444ee..295d59ebb3 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -52,7 +52,7 @@ const persistedReducer = persistReducer( { key: 'cherry-studio', storage, - version: 113, + version: 114, blacklist: ['runtime', 'messages', 'messageBlocks'], migrate }, diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index a2d53d80e7..a9fdec8646 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -1658,6 +1658,37 @@ const migrateConfig = { topicIdsByAssistant } + return state + } catch (error) { + return state + } + }, + '114': (state: RootState) => { + try { + if ( + state.assistants && + state.assistants.defaultAssistant && + state.assistants.defaultAssistant.isTemplate === undefined + ) { + state.assistants.defaultAssistant.isTemplate = false + } + if (state.assistants && state.assistants.assistants.length > 0) { + state.assistants.assistants + .filter((assistant) => assistant.isTemplate !== undefined) + .forEach((assistant) => { + assistant.isTemplate = false + }) + } + + if (state.agents && state.agents.agents.length > 0) { + state.agents.agents.forEach((agent) => { + agent.isTemplate = true + state.assistants.assistants.push(agent) + }) + } + + // @ts-ignore eslint-disable-next-line + delete state.agents return state } catch (error) { return state diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index fe5af00aa4..999f0b5531 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -12,7 +12,8 @@ export type Assistant = { knowledge_bases?: KnowledgeBase[] /** @deprecated 话题现在通过独立的 topics slice 管理,请使用 selectTopicsForAssistant selector */ topics: Topic[] - type: string + /** @deprecated 助手类型已废弃,请使用 isTemplate 字段 */ + type?: string emoji?: string description?: string model?: Model @@ -27,6 +28,8 @@ export type Assistant = { knowledgeRecognition?: 'off' | 'on' regularPhrases?: QuickPhrase[] // Added for regular phrase tags?: string[] // 助手标签 + isTemplate?: boolean + group?: string[] } export type AssistantsSortType = 'tags' | 'list' @@ -66,9 +69,8 @@ export type AssistantSettings = { toolUseMode?: 'function' | 'prompt' } -export type Agent = Omit & { - group?: string[] -} +// 为了兼容性,保留Agent类型别名 +export type Agent = Assistant /** * @deprecated 旧版消息类型,已废弃