fix: unified restart required (#1978)
Unified restart-required detection and notification mechanism so that model, tool, and configuration changes all follow the same signature-based comparison logic.
This commit is contained in:
parent
1809d04905
commit
27f638e909
7 changed files with 295 additions and 18 deletions
|
|
@ -29,6 +29,7 @@ var gateway = struct {
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
owned bool // true if we started the process, false if we attached to an existing one
|
owned bool // true if we started the process, false if we attached to an existing one
|
||||||
bootDefaultModel string
|
bootDefaultModel string
|
||||||
|
bootConfigSignature string
|
||||||
runtimeStatus string
|
runtimeStatus string
|
||||||
startupDeadline time.Time
|
startupDeadline time.Time
|
||||||
logs *LogBuffer
|
logs *LogBuffer
|
||||||
|
|
@ -177,14 +178,93 @@ func lookupModelConfig(cfg *config.Config, modelName string) *config.ModelConfig
|
||||||
return modelCfg
|
return modelCfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func gatewayRestartRequired(configDefaultModel, bootDefaultModel, gatewayStatus string) bool {
|
func computeConfigSignature(cfg *config.Config) string {
|
||||||
|
if cfg == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var parts []string
|
||||||
|
defaultModel := strings.TrimSpace(cfg.Agents.Defaults.GetModelName())
|
||||||
|
if defaultModel != "" {
|
||||||
|
parts = append(parts, "model:"+defaultModel)
|
||||||
|
}
|
||||||
|
toolSignatures := []string{}
|
||||||
|
if cfg.Tools.ReadFile.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "read_file")
|
||||||
|
}
|
||||||
|
if cfg.Tools.WriteFile.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "write_file")
|
||||||
|
}
|
||||||
|
if cfg.Tools.ListDir.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "list_dir")
|
||||||
|
}
|
||||||
|
if cfg.Tools.EditFile.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "edit_file")
|
||||||
|
}
|
||||||
|
if cfg.Tools.AppendFile.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "append_file")
|
||||||
|
}
|
||||||
|
if cfg.Tools.Exec.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "exec")
|
||||||
|
}
|
||||||
|
if cfg.Tools.Cron.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "cron")
|
||||||
|
}
|
||||||
|
if cfg.Tools.Web.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "web")
|
||||||
|
}
|
||||||
|
if cfg.Tools.WebFetch.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "web_fetch")
|
||||||
|
}
|
||||||
|
if cfg.Tools.Message.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "message")
|
||||||
|
}
|
||||||
|
if cfg.Tools.SendFile.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "send_file")
|
||||||
|
}
|
||||||
|
if cfg.Tools.FindSkills.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "find_skills")
|
||||||
|
}
|
||||||
|
if cfg.Tools.InstallSkill.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "install_skill")
|
||||||
|
}
|
||||||
|
if cfg.Tools.Spawn.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "spawn")
|
||||||
|
}
|
||||||
|
if cfg.Tools.SpawnStatus.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "spawn_status")
|
||||||
|
}
|
||||||
|
if cfg.Tools.I2C.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "i2c")
|
||||||
|
}
|
||||||
|
if cfg.Tools.SPI.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "spi")
|
||||||
|
}
|
||||||
|
if cfg.Tools.MCP.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "mcp")
|
||||||
|
}
|
||||||
|
if cfg.Tools.MCP.Discovery.Enabled {
|
||||||
|
toolSignatures = append(toolSignatures, "mcp_discovery")
|
||||||
|
}
|
||||||
|
if cfg.Tools.MCP.Discovery.UseRegex {
|
||||||
|
toolSignatures = append(toolSignatures, "mcp_discovery_regex")
|
||||||
|
}
|
||||||
|
if cfg.Tools.MCP.Discovery.UseBM25 {
|
||||||
|
toolSignatures = append(toolSignatures, "mcp_discovery_bm25")
|
||||||
|
}
|
||||||
|
if len(toolSignatures) > 0 {
|
||||||
|
parts = append(parts, "tools:"+strings.Join(toolSignatures, ","))
|
||||||
|
}
|
||||||
|
return strings.Join(parts, ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
func gatewayRestartRequiredBySignature(bootSignature, currentSignature, gatewayStatus string) bool {
|
||||||
if gatewayStatus != "running" {
|
if gatewayStatus != "running" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(configDefaultModel) == "" || strings.TrimSpace(bootDefaultModel) == "" {
|
if bootSignature == "" || currentSignature == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return configDefaultModel != bootDefaultModel
|
return bootSignature != currentSignature
|
||||||
}
|
}
|
||||||
|
|
||||||
func isCmdProcessAliveLocked(cmd *exec.Cmd) bool {
|
func isCmdProcessAliveLocked(cmd *exec.Cmd) bool {
|
||||||
|
|
@ -228,10 +308,11 @@ func attachToGatewayProcessLocked(pid int, cfg *config.Config) error {
|
||||||
gateway.owned = false // We didn't start this process
|
gateway.owned = false // We didn't start this process
|
||||||
setGatewayRuntimeStatusLocked("running")
|
setGatewayRuntimeStatusLocked("running")
|
||||||
|
|
||||||
// Update bootDefaultModel from config
|
// Update bootDefaultModel and bootConfigSignature from config
|
||||||
if cfg != nil {
|
if cfg != nil {
|
||||||
defaultModelName := strings.TrimSpace(cfg.Agents.Defaults.GetModelName())
|
defaultModelName := strings.TrimSpace(cfg.Agents.Defaults.GetModelName())
|
||||||
gateway.bootDefaultModel = defaultModelName
|
gateway.bootDefaultModel = defaultModelName
|
||||||
|
gateway.bootConfigSignature = computeConfigSignature(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.InfoC("gateway", fmt.Sprintf("Attached to gateway process (PID: %d)", pid))
|
logger.InfoC("gateway", fmt.Sprintf("Attached to gateway process (PID: %d)", pid))
|
||||||
|
|
@ -419,6 +500,7 @@ func (h *Handler) startGatewayLocked(initialStatus string, existingPid int) (int
|
||||||
gateway.cmd = cmd
|
gateway.cmd = cmd
|
||||||
gateway.owned = true // We started this process
|
gateway.owned = true // We started this process
|
||||||
gateway.bootDefaultModel = defaultModelName
|
gateway.bootDefaultModel = defaultModelName
|
||||||
|
gateway.bootConfigSignature = computeConfigSignature(cfg)
|
||||||
setGatewayRuntimeStatusLocked(initialStatus)
|
setGatewayRuntimeStatusLocked(initialStatus)
|
||||||
pid = cmd.Process.Pid
|
pid = cmd.Process.Pid
|
||||||
logger.InfoC("gateway", fmt.Sprintf("Started picoclaw gateway (PID: %d) from %s", pid, execPath))
|
logger.InfoC("gateway", fmt.Sprintf("Started picoclaw gateway (PID: %d) from %s", pid, execPath))
|
||||||
|
|
@ -439,6 +521,7 @@ func (h *Handler) startGatewayLocked(initialStatus string, existingPid int) (int
|
||||||
if gateway.cmd == cmd {
|
if gateway.cmd == cmd {
|
||||||
gateway.cmd = nil
|
gateway.cmd = nil
|
||||||
gateway.bootDefaultModel = ""
|
gateway.bootDefaultModel = ""
|
||||||
|
gateway.bootConfigSignature = ""
|
||||||
if gateway.runtimeStatus != "restarting" {
|
if gateway.runtimeStatus != "restarting" {
|
||||||
setGatewayRuntimeStatusLocked("stopped")
|
setGatewayRuntimeStatusLocked("stopped")
|
||||||
}
|
}
|
||||||
|
|
@ -713,7 +796,7 @@ func (h *Handler) handleGatewayStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func (h *Handler) gatewayStatusData() map[string]any {
|
func (h *Handler) gatewayStatusData() map[string]any {
|
||||||
data := map[string]any{}
|
data := map[string]any{}
|
||||||
configDefaultModel := ""
|
var configDefaultModel string
|
||||||
cfg, cfgErr := config.LoadConfig(h.configPath)
|
cfg, cfgErr := config.LoadConfig(h.configPath)
|
||||||
if cfgErr == nil && cfg != nil {
|
if cfgErr == nil && cfg != nil {
|
||||||
configDefaultModel = strings.TrimSpace(cfg.Agents.Defaults.GetModelName())
|
configDefaultModel = strings.TrimSpace(cfg.Agents.Defaults.GetModelName())
|
||||||
|
|
@ -784,11 +867,14 @@ func (h *Handler) gatewayStatusData() map[string]any {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bootDefaultModel, _ := data["boot_default_model"].(string)
|
|
||||||
gatewayStatus, _ := data["gateway_status"].(string)
|
gatewayStatus, _ := data["gateway_status"].(string)
|
||||||
data["gateway_restart_required"] = gatewayRestartRequired(
|
currentConfigSignature := computeConfigSignature(cfg)
|
||||||
configDefaultModel,
|
gateway.mu.Lock()
|
||||||
bootDefaultModel,
|
bootConfigSignature := gateway.bootConfigSignature
|
||||||
|
gateway.mu.Unlock()
|
||||||
|
data["gateway_restart_required"] = gatewayRestartRequiredBySignature(
|
||||||
|
bootConfigSignature,
|
||||||
|
currentConfigSignature,
|
||||||
gatewayStatus,
|
gatewayStatus,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ func resetGatewayTestState(t *testing.T) {
|
||||||
gateway.mu.Lock()
|
gateway.mu.Lock()
|
||||||
gateway.cmd = nil
|
gateway.cmd = nil
|
||||||
gateway.bootDefaultModel = ""
|
gateway.bootDefaultModel = ""
|
||||||
|
gateway.bootConfigSignature = ""
|
||||||
setGatewayRuntimeStatusLocked("stopped")
|
setGatewayRuntimeStatusLocked("stopped")
|
||||||
gateway.mu.Unlock()
|
gateway.mu.Unlock()
|
||||||
})
|
})
|
||||||
|
|
@ -502,9 +503,11 @@ func TestGatewayStatusRequiresRestartAfterDefaultModelChange(t *testing.T) {
|
||||||
t.Fatalf("FindProcess() error = %v", err)
|
t.Fatalf("FindProcess() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bootSignature := computeConfigSignature(cfg)
|
||||||
gateway.mu.Lock()
|
gateway.mu.Lock()
|
||||||
gateway.cmd = &exec.Cmd{Process: process}
|
gateway.cmd = &exec.Cmd{Process: process}
|
||||||
gateway.bootDefaultModel = cfg.ModelList[0].ModelName
|
gateway.bootDefaultModel = cfg.ModelList[0].ModelName
|
||||||
|
gateway.bootConfigSignature = bootSignature
|
||||||
setGatewayRuntimeStatusLocked("running")
|
setGatewayRuntimeStatusLocked("running")
|
||||||
gateway.mu.Unlock()
|
gateway.mu.Unlock()
|
||||||
|
|
||||||
|
|
@ -548,6 +551,188 @@ func TestGatewayStatusRequiresRestartAfterDefaultModelChange(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGatewayStatusRequiresRestartAfterToolChange(t *testing.T) {
|
||||||
|
resetGatewayTestState(t)
|
||||||
|
|
||||||
|
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||||
|
cfg := config.DefaultConfig()
|
||||||
|
cfg.Agents.Defaults.ModelName = cfg.ModelList[0].ModelName
|
||||||
|
cfg.ModelList[0].SetAPIKey("test-key")
|
||||||
|
cfg.Tools.WriteFile.Enabled = true
|
||||||
|
if err := config.SaveConfig(configPath, cfg); err != nil {
|
||||||
|
t.Fatalf("SaveConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h := NewHandler(configPath)
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
h.RegisterRoutes(mux)
|
||||||
|
|
||||||
|
process, err := os.FindProcess(os.Getpid())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("FindProcess() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bootSignature := computeConfigSignature(cfg)
|
||||||
|
gateway.mu.Lock()
|
||||||
|
gateway.cmd = &exec.Cmd{Process: process}
|
||||||
|
gateway.bootDefaultModel = cfg.ModelList[0].ModelName
|
||||||
|
gateway.bootConfigSignature = bootSignature
|
||||||
|
setGatewayRuntimeStatusLocked("running")
|
||||||
|
gateway.mu.Unlock()
|
||||||
|
|
||||||
|
updatedCfg, err := config.LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
updatedCfg.Tools.WriteFile.Enabled = false
|
||||||
|
if err := config.SaveConfig(configPath, updatedCfg); err != nil {
|
||||||
|
t.Fatalf("SaveConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gatewayHealthGet = func(string, time.Duration) (*http.Response, error) {
|
||||||
|
return mockGatewayHealthResponse(http.StatusOK, os.Getpid()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/gateway/status", nil)
|
||||||
|
mux.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body map[string]any
|
||||||
|
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
||||||
|
t.Fatalf("unmarshal response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := body["gateway_status"]; got != "running" {
|
||||||
|
t.Fatalf("gateway_status = %#v, want %q", got, "running")
|
||||||
|
}
|
||||||
|
if got := body["gateway_restart_required"]; got != true {
|
||||||
|
t.Fatalf("gateway_restart_required = %#v, want true", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGatewayStatusNoRestartRequiredForNonSensitiveChanges(t *testing.T) {
|
||||||
|
resetGatewayTestState(t)
|
||||||
|
|
||||||
|
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||||
|
cfg := config.DefaultConfig()
|
||||||
|
cfg.Agents.Defaults.ModelName = cfg.ModelList[0].ModelName
|
||||||
|
cfg.ModelList[0].SetAPIKey("test-key")
|
||||||
|
cfg.Agents.Defaults.MaxTokens = 1000
|
||||||
|
if err := config.SaveConfig(configPath, cfg); err != nil {
|
||||||
|
t.Fatalf("SaveConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h := NewHandler(configPath)
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
h.RegisterRoutes(mux)
|
||||||
|
|
||||||
|
process, err := os.FindProcess(os.Getpid())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("FindProcess() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bootSignature := computeConfigSignature(cfg)
|
||||||
|
gateway.mu.Lock()
|
||||||
|
gateway.cmd = &exec.Cmd{Process: process}
|
||||||
|
gateway.bootDefaultModel = cfg.ModelList[0].ModelName
|
||||||
|
gateway.bootConfigSignature = bootSignature
|
||||||
|
setGatewayRuntimeStatusLocked("running")
|
||||||
|
gateway.mu.Unlock()
|
||||||
|
|
||||||
|
updatedCfg, err := config.LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
updatedCfg.Agents.Defaults.MaxTokens = 2000
|
||||||
|
if err := config.SaveConfig(configPath, updatedCfg); err != nil {
|
||||||
|
t.Fatalf("SaveConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gatewayHealthGet = func(string, time.Duration) (*http.Response, error) {
|
||||||
|
return mockGatewayHealthResponse(http.StatusOK, os.Getpid()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/gateway/status", nil)
|
||||||
|
mux.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body map[string]any
|
||||||
|
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
||||||
|
t.Fatalf("unmarshal response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := body["gateway_status"]; got != "running" {
|
||||||
|
t.Fatalf("gateway_status = %#v, want %q", got, "running")
|
||||||
|
}
|
||||||
|
if got := body["gateway_restart_required"]; got != false {
|
||||||
|
t.Fatalf("gateway_restart_required = %#v, want false", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGatewayStatusNoRestartRequiredWhenNotRunning(t *testing.T) {
|
||||||
|
resetGatewayTestState(t)
|
||||||
|
|
||||||
|
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||||
|
cfg := config.DefaultConfig()
|
||||||
|
cfg.Agents.Defaults.ModelName = cfg.ModelList[0].ModelName
|
||||||
|
cfg.ModelList[0].SetAPIKey("test-key")
|
||||||
|
if err := config.SaveConfig(configPath, cfg); err != nil {
|
||||||
|
t.Fatalf("SaveConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h := NewHandler(configPath)
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
h.RegisterRoutes(mux)
|
||||||
|
|
||||||
|
gateway.mu.Lock()
|
||||||
|
gateway.cmd = nil
|
||||||
|
gateway.bootDefaultModel = ""
|
||||||
|
gateway.bootConfigSignature = ""
|
||||||
|
setGatewayRuntimeStatusLocked("stopped")
|
||||||
|
gateway.mu.Unlock()
|
||||||
|
|
||||||
|
updatedCfg, err := config.LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
updatedCfg.Agents.Defaults.ModelName = "different-model"
|
||||||
|
if err := config.SaveConfig(configPath, updatedCfg); err != nil {
|
||||||
|
t.Fatalf("SaveConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gatewayHealthGet = func(string, time.Duration) (*http.Response, error) {
|
||||||
|
return nil, errors.New("no gateway running")
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/gateway/status", nil)
|
||||||
|
mux.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body map[string]any
|
||||||
|
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
||||||
|
t.Fatalf("unmarshal response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := body["gateway_status"]; got != "stopped" {
|
||||||
|
t.Fatalf("gateway_status = %#v, want %q", got, "stopped")
|
||||||
|
}
|
||||||
|
if got := body["gateway_restart_required"]; got != false {
|
||||||
|
t.Fatalf("gateway_restart_required = %#v, want false", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGatewayStatusReturnsErrorAfterStartupWindowExpires(t *testing.T) {
|
func TestGatewayStatusReturnsErrorAfterStartupWindowExpires(t *testing.T) {
|
||||||
resetGatewayTestState(t)
|
resetGatewayTestState(t)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import {
|
||||||
} from "@/components/config/form-model"
|
} from "@/components/config/form-model"
|
||||||
import { PageHeader } from "@/components/page-header"
|
import { PageHeader } from "@/components/page-header"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { refreshGatewayState } from "@/store/gateway"
|
||||||
|
|
||||||
export function ConfigPage() {
|
export function ConfigPage() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
@ -281,6 +282,7 @@ export function ConfigPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success(t("pages.config.save_success"))
|
toast.success(t("pages.config.save_success"))
|
||||||
|
void refreshGatewayState({ force: true })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(
|
toast.error(
|
||||||
err instanceof Error ? err.message : t("pages.config.save_error"),
|
err instanceof Error ? err.message : t("pages.config.save_error"),
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import {
|
||||||
} from "@/components/ui/alert-dialog"
|
} from "@/components/ui/alert-dialog"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
import { Textarea } from "@/components/ui/textarea"
|
||||||
|
import { refreshGatewayState } from "@/store/gateway"
|
||||||
|
|
||||||
export function RawConfigPage() {
|
export function RawConfigPage() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
@ -56,6 +57,7 @@ export function RawConfigPage() {
|
||||||
} catch {
|
} catch {
|
||||||
queryClient.invalidateQueries({ queryKey: ["config"] })
|
queryClient.invalidateQueries({ queryKey: ["config"] })
|
||||||
}
|
}
|
||||||
|
void refreshGatewayState({ force: true })
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast.error(t("pages.config.save_error"))
|
toast.error(t("pages.config.save_error"))
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card"
|
} from "@/components/ui/card"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import { refreshGatewayState } from "@/store/gateway"
|
||||||
|
|
||||||
export function ToolsPage() {
|
export function ToolsPage() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
@ -33,6 +34,7 @@ export function ToolsPage() {
|
||||||
: t("pages.agent.tools.disable_success"),
|
: t("pages.agent.tools.disable_success"),
|
||||||
)
|
)
|
||||||
void queryClient.invalidateQueries({ queryKey: ["tools"] })
|
void queryClient.invalidateQueries({ queryKey: ["tools"] })
|
||||||
|
void refreshGatewayState({ force: true })
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
toast.error(
|
toast.error(
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@
|
||||||
"restarting": "Restarting Gateway...",
|
"restarting": "Restarting Gateway...",
|
||||||
"stopping": "Stopping Gateway..."
|
"stopping": "Stopping Gateway..."
|
||||||
},
|
},
|
||||||
"restartRequired": "Model changes require a gateway restart to take effect."
|
"restartRequired": "Configuration changes require a gateway restart to take effect."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@
|
||||||
"restarting": "服务重启中...",
|
"restarting": "服务重启中...",
|
||||||
"stopping": "服务停止中..."
|
"stopping": "服务停止中..."
|
||||||
},
|
},
|
||||||
"restartRequired": "切换默认模型后需要重启服务才能生效。"
|
"restartRequired": "配置变更后需要重启服务才能生效。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue