From 3f653161e33e6e1ef9d1a6b50f2fc91b527f123c Mon Sep 17 00:00:00 2001 From: SiYue-ZO <2835601846@qq.com> Date: Mon, 18 May 2026 13:53:34 +0800 Subject: [PATCH] feat(frontend): add factory reset button with confirmation dialog Add resetAppConfig API function, AlertDialog-confirmed factory reset button in config page, and i18n keys for en/zh/pt-br locales. --- cmd/picoclaw/main.go | 2 +- cmd/picoclaw/main_test.go | 1 + web/frontend/src/api/channels.ts | 6 ++ .../src/components/config/config-page.tsx | 88 +++++++++++++++++-- web/frontend/src/i18n/locales/en.json | 8 +- web/frontend/src/i18n/locales/pt-br.json | 8 +- web/frontend/src/i18n/locales/zh.json | 8 +- 7 files changed, 110 insertions(+), 11 deletions(-) diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index 11d0a8bb7..1b5d0974a 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -16,8 +16,8 @@ import ( "github.com/sipeed/picoclaw/cmd/picoclaw/internal" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/agent" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/auth" - configcmd "github.com/sipeed/picoclaw/cmd/picoclaw/internal/config" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/cliui" + configcmd "github.com/sipeed/picoclaw/cmd/picoclaw/internal/config" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/cron" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/gateway" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/mcp" diff --git a/cmd/picoclaw/main_test.go b/cmd/picoclaw/main_test.go index 037c7c2e6..e4f9de5e9 100644 --- a/cmd/picoclaw/main_test.go +++ b/cmd/picoclaw/main_test.go @@ -39,6 +39,7 @@ func TestNewPicoclawCommand(t *testing.T) { allowedCommands := []string{ "agent", "auth", + "config", "cron", "gateway", "mcp", diff --git a/web/frontend/src/api/channels.ts b/web/frontend/src/api/channels.ts index 42a3a0606..36389ca84 100644 --- a/web/frontend/src/api/channels.ts +++ b/web/frontend/src/api/channels.ts @@ -77,6 +77,12 @@ export async function patchAppConfig( }) } +export async function resetAppConfig(): Promise { + return request("/api/config/reset", { + method: "POST", + }) +} + // WeChat QR login flow API export interface WeixinFlowResponse { diff --git a/web/frontend/src/components/config/config-page.tsx b/web/frontend/src/components/config/config-page.tsx index 8af5815f0..4db059cd3 100644 --- a/web/frontend/src/components/config/config-page.tsx +++ b/web/frontend/src/components/config/config-page.tsx @@ -5,7 +5,7 @@ import { useEffect, useState } from "react" import { useTranslation } from "react-i18next" import { toast } from "sonner" -import { patchAppConfig } from "@/api/channels" +import { patchAppConfig, resetAppConfig } from "@/api/channels" import { launcherFetch } from "@/api/http" import { postLauncherDashboardSetup } from "@/api/launcher-auth" import { @@ -41,6 +41,17 @@ import { } from "@/components/config/form-model" import { PageHeader } from "@/components/page-header" import { Badge } from "@/components/ui/badge" +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog" import { Button } from "@/components/ui/button" import { showSaveSuccessOrRestartToast } from "@/lib/restart-required" import { refreshGatewayState } from "@/store/gateway" @@ -72,15 +83,24 @@ export function ConfigPage() { const [autoStartEnabled, setAutoStartEnabled] = useState(false) const [autoStartBaseline, setAutoStartBaseline] = useState(false) const [saving, setSaving] = useState(false) + const [showFactoryResetDialog, setShowFactoryResetDialog] = useState(false) const { data, isLoading, error } = useQuery({ queryKey: ["config"], queryFn: async () => { - const res = await launcherFetch("/api/config") - if (!res.ok) { - throw new Error("Failed to load config") + const controller = new AbortController() + const timer = setTimeout(() => controller.abort(), 5000) + try { + const res = await launcherFetch("/api/config", { + signal: controller.signal, + }) + if (!res.ok) { + throw new Error("Failed to load config") + } + return res.json() + } finally { + clearTimeout(timer) } - return res.json() }, }) @@ -208,6 +228,27 @@ export function ConfigPage() { toast.info(t("pages.config.reset_success")) } + const handleFactoryReset = async () => { + try { + await resetAppConfig() + const fresh = await launcherFetch("/api/config").then((r) => r.json()) + const parsed = buildFormFromConfig(fresh) + setForm(parsed) + setBaseline(parsed) + await queryClient.invalidateQueries({ queryKey: ["config"] }) + await refreshGatewayState() + toast.success(t("pages.config.factory_reset_success")) + } catch (err) { + toast.error( + err instanceof Error + ? err.message + : t("pages.config.factory_reset_error"), + ) + } finally { + setShowFactoryResetDialog(false) + } + } + const handleSave = async () => { try { setSaving(true) @@ -625,8 +666,38 @@ export function ConfigPage() { } } + const factoryResetButton = ( + + + + + + + + {t("pages.config.factory_reset_confirm_title")} + + + {t("pages.config.factory_reset_confirm_desc")} + + + + {t("common.cancel")} + + {t("pages.config.factory_reset_confirm")} + + + + + ) + const actionButtons = (
+ {factoryResetButton}