From 42435e8f76cab07bf2554cf7dd0889b76453e75e Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 27 Sep 2025 15:05:26 +0800 Subject: [PATCH] refactor(agent): update useUpdateAgent hook and related components - Refactor useUpdateAgent to return both updateAgent and updateModel functions - Update all components using useUpdateAgent to use the new hook structure - Improve model selection by reusing SelectAgentModelButton component - Add pagination support to useApiModels hook --- .../components/Popups/agent/AgentModal.tsx | 2 +- src/renderer/src/hooks/agents/useModels.ts | 18 +++++++-- .../src/hooks/agents/useUpdateAgent.ts | 20 ++++++++-- src/renderer/src/pages/home/ChatNavbar.tsx | 21 ++++++---- .../components/SelectAgentModelButton.tsx | 29 ++++++++------ .../AgentSettings/AdvancedSettings.tsx | 2 +- .../AgentSettings/AgentEssentialSettings.tsx | 2 +- .../AgentSettings/AgentSettingsPopup.tsx | 2 +- .../settings/AgentSettings/ModelSetting.tsx | 38 +++---------------- .../settings/AgentSettings/PromptSettings.tsx | 2 +- .../SessionEssentialSettings.tsx | 2 +- 11 files changed, 75 insertions(+), 63 deletions(-) diff --git a/src/renderer/src/components/Popups/agent/AgentModal.tsx b/src/renderer/src/components/Popups/agent/AgentModal.tsx index 96d6cda087..b4ab96fe68 100644 --- a/src/renderer/src/components/Popups/agent/AgentModal.tsx +++ b/src/renderer/src/components/Popups/agent/AgentModal.tsx @@ -98,7 +98,7 @@ export const AgentModal: React.FC = ({ agent, trigger, isOpen: _isOpen, o const loadingRef = useRef(false) // const { setTimeoutTimer } = useTimer() const { addAgent } = useAgents() - const updateAgent = useUpdateAgent() + const { updateAgent } = useUpdateAgent() // hard-coded. We only support anthropic for now. const { models } = useApiModels({ providerType: 'anthropic' }) const isEditing = (agent?: AgentWithTools) => agent !== undefined diff --git a/src/renderer/src/hooks/agents/useModels.ts b/src/renderer/src/hooks/agents/useModels.ts index e35d58dde1..c23a056c7d 100644 --- a/src/renderer/src/hooks/agents/useModels.ts +++ b/src/renderer/src/hooks/agents/useModels.ts @@ -1,4 +1,4 @@ -import { ApiModelsFilter } from '@renderer/types' +import { ApiModel, ApiModelsFilter } from '@renderer/types' import { merge } from 'lodash' import { useCallback } from 'react' import useSWR from 'swr' @@ -11,8 +11,20 @@ export const useApiModels = (filter?: ApiModelsFilter) => { const defaultFilter = {} satisfies ApiModelsFilter const finalFilter = merge(filter, defaultFilter) const path = client.getModelsPath(finalFilter) - const fetcher = useCallback(() => { - return client.getModels(finalFilter) + const fetcher = useCallback(async () => { + const limit = finalFilter.limit || 100 + let offset = finalFilter.offset || 0 + const allModels: ApiModel[] = [] + let total = Infinity + + while (offset < total) { + const pageFilter = { ...finalFilter, limit, offset } + const res = await client.getModels(pageFilter) + allModels.push(...(res.data || [])) + total = res.total ?? 0 + offset += limit + } + return { data: allModels, total } }, [client, finalFilter]) const { data, error, isLoading } = useSWR(path, fetcher) return { diff --git a/src/renderer/src/hooks/agents/useUpdateAgent.ts b/src/renderer/src/hooks/agents/useUpdateAgent.ts index eee9eba94e..b2589095ef 100644 --- a/src/renderer/src/hooks/agents/useUpdateAgent.ts +++ b/src/renderer/src/hooks/agents/useUpdateAgent.ts @@ -6,20 +6,27 @@ import { mutate } from 'swr' import { useAgentClient } from './useAgentClient' +export type UpdateAgentOptions = { + /** Whether to show success toast after updating. Defaults to true. */ + showSuccessToast?: boolean +} + export const useUpdateAgent = () => { const { t } = useTranslation() const client = useAgentClient() const listKey = client.agentPaths.base const updateAgent = useCallback( - async (form: UpdateAgentForm) => { + async (form: UpdateAgentForm, options?: UpdateAgentOptions) => { try { const itemKey = client.agentPaths.withId(form.id) // may change to optimistic update const result = await client.updateAgent(form) mutate(listKey, (prev) => prev?.map((a) => (a.id === result.id ? result : a)) ?? []) mutate(itemKey, result) - window.toast.success(t('common.update_success')) + if (options?.showSuccessToast ?? true) { + window.toast.success(t('common.update_success')) + } } catch (error) { window.toast.error(formatErrorMessageWithPrefix(error, t('agent.update.error.failed'))) } @@ -27,5 +34,12 @@ export const useUpdateAgent = () => { [client, listKey, t] ) - return updateAgent + const updateModel = useCallback( + async (agentId: string, modelId: string, options?: UpdateAgentOptions) => { + updateAgent({ id: agentId, model: modelId }, options) + }, + [updateAgent] + ) + + return { updateAgent, updateModel } } diff --git a/src/renderer/src/pages/home/ChatNavbar.tsx b/src/renderer/src/pages/home/ChatNavbar.tsx index e03e0f9e96..a33b5f2bd3 100644 --- a/src/renderer/src/pages/home/ChatNavbar.tsx +++ b/src/renderer/src/pages/home/ChatNavbar.tsx @@ -2,7 +2,7 @@ import { NavbarHeader } from '@renderer/components/app/Navbar' import { HStack } from '@renderer/components/Layout' import SearchPopup from '@renderer/components/Popups/SearchPopup' import { useAgent } from '@renderer/hooks/agents/useAgent' -import { useApiModel } from '@renderer/hooks/agents/useModel' +import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' import { useAssistant } from '@renderer/hooks/useAssistant' import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime' import { useSettings } from '@renderer/hooks/useSettings' @@ -11,12 +11,12 @@ import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { useAppDispatch } from '@renderer/store' import { setNarrowMode } from '@renderer/store/settings' -import { Assistant, Topic } from '@renderer/types' +import { ApiModel, Assistant, Topic } from '@renderer/types' import { Tooltip } from 'antd' import { t } from 'i18next' import { Menu, PanelLeftClose, PanelRightClose, Search } from 'lucide-react' import { AnimatePresence, motion } from 'motion/react' -import { FC } from 'react' +import { FC, useCallback } from 'react' import styled from 'styled-components' import AssistantsDrawer from './components/AssistantsDrawer' @@ -41,8 +41,7 @@ const HeaderNavbar: FC = ({ activeAssistant, setActiveAssistant, activeTo const { chat } = useRuntime() const { activeTopicOrSession, activeAgentId } = chat const { agent } = useAgent(activeAgentId) - // TODO: filter is temporally for agent since it cannot get all models once - const agentModel = useApiModel({ id: agent?.model, filter: { providerType: 'anthropic' } }) + const { updateModel } = useUpdateAgent() useShortcut('toggle_show_assistants', toggleShowAssistants) @@ -72,6 +71,14 @@ const HeaderNavbar: FC = ({ activeAssistant, setActiveAssistant, activeTo }) } + const handleUpdateModel = useCallback( + async (model: ApiModel) => { + if (!agent) return + return updateModel(agent.id, model.id, { showSuccessToast: false }) + }, + [agent, updateModel] + ) + return (
@@ -103,8 +110,8 @@ const HeaderNavbar: FC = ({ activeAssistant, setActiveAssistant, activeTo )} {activeTopicOrSession === 'topic' && } - {activeTopicOrSession === 'session' && agent && agentModel && ( - + {activeTopicOrSession === 'session' && agent && ( + )}
diff --git a/src/renderer/src/pages/home/components/SelectAgentModelButton.tsx b/src/renderer/src/pages/home/components/SelectAgentModelButton.tsx index 35612266c9..bc6ca3ed35 100644 --- a/src/renderer/src/pages/home/components/SelectAgentModelButton.tsx +++ b/src/renderer/src/pages/home/components/SelectAgentModelButton.tsx @@ -1,8 +1,9 @@ import { Button } from '@heroui/react' import ModelAvatar from '@renderer/components/Avatar/ModelAvatar' import { SelectApiModelPopup } from '@renderer/components/Popups/SelectModelPopup' -import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' -import { AgentEntity, ApiModel } from '@renderer/types' +import { useApiModel } from '@renderer/hooks/agents/useModel' +import { getProviderNameById } from '@renderer/services/ProviderService' +import { AgentBaseWithId, ApiModel, isAgentEntity } from '@renderer/types' import { getModelFilterByAgentType } from '@renderer/utils/agentSession' import { apiModelAdapter } from '@renderer/utils/model' import { ChevronsUpDown } from 'lucide-react' @@ -10,31 +11,37 @@ import { FC } from 'react' import { useTranslation } from 'react-i18next' interface Props { - agent: AgentEntity - model: ApiModel + agent: AgentBaseWithId + onSelect: (model: ApiModel) => Promise + isDisabled?: boolean } -const SelectAgentModelButton: FC = ({ agent, model }) => { +const SelectAgentModelButton: FC = ({ agent, onSelect, isDisabled }) => { const { t } = useTranslation() - const update = useUpdateAgent() + const model = useApiModel({ id: agent?.model }) - const modelFilter = getModelFilterByAgentType(agent.type) + const modelFilter = isAgentEntity(agent) ? getModelFilterByAgentType(agent.type) : undefined if (!agent) return null const onSelectModel = async () => { const selectedModel = await SelectApiModelPopup.show({ model, filter: modelFilter }) if (selectedModel && selectedModel.id !== agent.model) { - update({ id: agent.id, model: selectedModel.id }) + onSelect(selectedModel) } } - const providerName = model.provider_name + const providerName = model?.provider ? getProviderNameById(model.provider) : model?.provider_name return ( -