From cbe6a0907c662490c95c32d4ded80d844791a7d8 Mon Sep 17 00:00:00 2001
From: SiYue-ZO <2835601846@qq.com>
Date: Sat, 25 Apr 2026 02:03:26 +0800
Subject: [PATCH] feat: complete tool and model restart feedback
---
.../src/components/agent/tools/tools-page.tsx | 3 ++
.../components/agent/tools/use-tools-page.ts | 39 ++++++++++++++-----
.../components/agent/tools/web-search-tab.tsx | 13 ++++++-
.../src/components/models/models-page.tsx | 14 ++++++-
web/frontend/src/hooks/use-chat-models.ts | 15 ++++++-
5 files changed, 71 insertions(+), 13 deletions(-)
diff --git a/web/frontend/src/components/agent/tools/tools-page.tsx b/web/frontend/src/components/agent/tools/tools-page.tsx
index 7d2d0fac6..c221f911c 100644
--- a/web/frontend/src/components/agent/tools/tools-page.tsx
+++ b/web/frontend/src/components/agent/tools/tools-page.tsx
@@ -1,5 +1,6 @@
import { useLayoutEffect, useRef } from "react"
import { useTranslation } from "react-i18next"
+
import { PageHeader } from "@/components/page-header"
import { ToolLibraryTab } from "./tool-library-tab"
@@ -26,6 +27,7 @@ export function ToolsPage() {
isToolsLoading,
isWebSearchLoading,
isWebSearchSaving,
+ isWebSearchDirty,
setActiveTab,
setSearchQuery,
setStatusFilter,
@@ -72,6 +74,7 @@ export function ToolsPage() {
isLoading={isWebSearchLoading}
hasError={hasWebSearchError}
isSaving={isWebSearchSaving}
+ isDirty={isWebSearchDirty}
onSave={saveWebSearchConfig}
onToggleProviderExpand={toggleExpandedProvider}
onUpdateDraft={updateWebSearchDraft}
diff --git a/web/frontend/src/components/agent/tools/use-tools-page.ts b/web/frontend/src/components/agent/tools/use-tools-page.ts
index 07f9d50d4..ecc433b0e 100644
--- a/web/frontend/src/components/agent/tools/use-tools-page.ts
+++ b/web/frontend/src/components/agent/tools/use-tools-page.ts
@@ -4,12 +4,13 @@ import { useTranslation } from "react-i18next"
import { toast } from "sonner"
import {
+ type WebSearchConfigResponse,
getTools,
getWebSearchConfig,
setToolEnabled,
updateWebSearchConfig,
- type WebSearchConfigResponse,
} from "@/api/tools"
+import { showSaveSuccessOrRestartToast } from "@/lib/restart-required"
import { refreshGatewayState } from "@/store/gateway"
import type { GroupedTools, ToolStatusFilter, ToolsPageTab } from "./types"
@@ -35,24 +36,38 @@ export function useToolsPage() {
queryFn: getWebSearchConfig,
})
- const tools = useMemo(() => toolsQuery.data?.tools ?? [], [toolsQuery.data?.tools])
+ const tools = useMemo(
+ () => toolsQuery.data?.tools ?? [],
+ [toolsQuery.data?.tools],
+ )
const normalizedSearchQuery = deferredSearchQuery.trim().toLowerCase()
const webSearchDraft = webSearchDraftOverride ?? webSearchQuery.data ?? null
+ const isWebSearchDirty = useMemo(() => {
+ if (!webSearchDraft || !webSearchQuery.data) {
+ return false
+ }
+ return (
+ JSON.stringify(webSearchDraft) !== JSON.stringify(webSearchQuery.data)
+ )
+ }, [webSearchDraft, webSearchQuery.data])
const toggleToolMutation = useMutation({
mutationFn: async ({ name, enabled }: { name: string; enabled: boolean }) =>
setToolEnabled(name, enabled),
- onSuccess: (_, variables) => {
- toast.success(
+ onSuccess: async (_, variables) => {
+ const gateway = await refreshGatewayState({ force: true })
+ showSaveSuccessOrRestartToast(
+ t,
variables.enabled
? t("pages.agent.tools.enable_success", "Tool enabled successfully")
: t(
"pages.agent.tools.disable_success",
"Tool disabled successfully",
),
+ t("navigation.tools", "Tools"),
+ gateway?.restartRequired === true,
)
void queryClient.invalidateQueries({ queryKey: ["tools"] })
- void refreshGatewayState({ force: true })
},
onError: (error) => {
toast.error(
@@ -65,20 +80,23 @@ export function useToolsPage() {
const saveWebSearchMutation = useMutation({
mutationFn: updateWebSearchConfig,
- onSuccess: (updatedConfig) => {
+ onSuccess: async (updatedConfig) => {
queryClient.setQueryData(["tools", "web-search-config"], updatedConfig)
setWebSearchDraftOverride(null)
- toast.success(
+ const gateway = await refreshGatewayState({ force: true })
+ showSaveSuccessOrRestartToast(
+ t,
t(
"pages.agent.tools.web_search.save_success",
"Settings saved successfully",
),
+ t("pages.agent.tools.web_search.title", "Web Search Configuration"),
+ gateway?.restartRequired === true,
)
void queryClient.invalidateQueries({
queryKey: ["tools", "web-search-config"],
})
void queryClient.invalidateQueries({ queryKey: ["tools"] })
- void refreshGatewayState({ force: true })
},
onError: (error) => {
toast.error(
@@ -105,7 +123,9 @@ export function useToolsPage() {
}
if (normalizedSearchQuery) {
- const matchesName = tool.name.toLowerCase().includes(normalizedSearchQuery)
+ const matchesName = tool.name
+ .toLowerCase()
+ .includes(normalizedSearchQuery)
const matchesDescription = (tool.description || "")
.toLowerCase()
.includes(normalizedSearchQuery)
@@ -177,6 +197,7 @@ export function useToolsPage() {
isToolsLoading: toolsQuery.isLoading,
isWebSearchLoading: webSearchQuery.isLoading,
isWebSearchSaving: saveWebSearchMutation.isPending,
+ isWebSearchDirty,
setActiveTab,
setSearchQuery,
setStatusFilter,
diff --git a/web/frontend/src/components/agent/tools/web-search-tab.tsx b/web/frontend/src/components/agent/tools/web-search-tab.tsx
index b3f9d0750..866e0f27f 100644
--- a/web/frontend/src/components/agent/tools/web-search-tab.tsx
+++ b/web/frontend/src/components/agent/tools/web-search-tab.tsx
@@ -1,6 +1,7 @@
import { useTranslation } from "react-i18next"
import type { WebSearchConfigResponse } from "@/api/tools"
+import { ConfigChangeNotice } from "@/components/config-change-notice"
import { Button } from "@/components/ui/button"
import { Skeleton } from "@/components/ui/skeleton"
@@ -15,6 +16,7 @@ interface WebSearchTabProps {
isLoading: boolean
hasError: boolean
isSaving: boolean
+ isDirty: boolean
onSave: () => void
onToggleProviderExpand: (providerId: string) => void
onUpdateDraft: WebSearchDraftUpdater
@@ -27,6 +29,7 @@ export function WebSearchTab({
isLoading,
hasError,
isSaving,
+ isDirty,
onSave,
onToggleProviderExpand,
onUpdateDraft,
@@ -66,13 +69,21 @@ export function WebSearchTab({
+ {isDirty && (
+