From 5101be7478c8f5f4bffd27b5a940e7bd04737a3e Mon Sep 17 00:00:00 2001 From: rain9 <15911122312@163.com> Date: Sat, 6 Jun 2026 00:21:48 +0800 Subject: [PATCH 1/2] chore(pnpm): allow esbuild and msw in onlyBuiltDependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pnpm 10+ 默认阻止依赖的安装期脚本,但 esbuild(Vite 工具链)和 msw(Vitest 测试 mock)需要运行 install/postinstall 步骤来准备 平台二进制,否则本地安装/测试可能异常或构建降级。 - pnpm-workspace.yaml: 在 onlyBuiltDependencies 中追加 esbuild、msw - 行为不变;只解除 pnpm 对这两个包的脚本拦截 --- pnpm-workspace.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index fab9eb3b7b..cc6f8e076a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,3 +2,5 @@ packages: [] onlyBuiltDependencies: - '@tailwindcss/oxide' + - esbuild + - msw From e8c88b415c1942bcda85825eba69eba1c41a22b5 Mon Sep 17 00:00:00 2001 From: rain9 <15911122312@163.com> Date: Sat, 6 Jun 2026 00:22:01 +0800 Subject: [PATCH 2/2] =?UTF-8?q?refactor(app):=20=E6=8A=BD=E5=87=BA?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E8=AE=A2=E9=98=85=E4=B8=8E=20auto-sync=20toa?= =?UTF-8?q?st=20=E9=80=9A=E7=94=A8=20hook=EF=BC=8C=E5=B9=B6=E6=94=B6?= =?UTF-8?q?=E7=B4=A7=20ref=20=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 只做结构与类型收敛,不改变任何运行时行为: - 新增 useProviderSwitchListener(activeApp, onRefetch) 统一管理 providersApi.onSwitched 订阅与卸载,替代 App 内的内联 useEffect - 新增 useAutoSyncErrorToast(eventName, queryClient, t, toastI18nKey) 合并 WebDAV/S3 自动同步失败 toast 的重复逻辑 - 收紧 Panel ref 类型:PromptPanelRef/McpPanelRef/SkillsPageRef/ UnifiedSkillsPanelRef 改为 useRef<...Handle | null>(null) 取代此前的 useRef,调用点改用可选链避免运行时空方法 - 不新增组件文件,限制在 App.tsx 内重构,便于评审 --- src/App.tsx | 155 ++++++++++++++++++++++++++++------------------------ 1 file changed, 85 insertions(+), 70 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 966e48a6ff..ff47c8028c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next"; import { motion, AnimatePresence } from "framer-motion"; import { toast } from "sonner"; import { invoke } from "@tauri-apps/api/core"; -import { useQueryClient } from "@tanstack/react-query"; +import { useQueryClient, type QueryClient } from "@tanstack/react-query"; import { Plus, Settings, @@ -90,6 +90,10 @@ import ToolsPanel from "@/components/openclaw/ToolsPanel"; import AgentsDefaultsPanel from "@/components/openclaw/AgentsDefaultsPanel"; import OpenClawHealthBanner from "@/components/openclaw/OpenClawHealthBanner"; import HermesMemoryPanel from "@/components/hermes/HermesMemoryPanel"; +import type { PromptPanelHandle } from "@/components/prompts/PromptPanel"; +import type { UnifiedMcpPanelHandle } from "@/components/mcp/UnifiedMcpPanel"; +import type { SkillsPageHandle } from "@/components/skills/SkillsPage"; +import type { UnifiedSkillsPanelHandle } from "@/components/skills/UnifiedSkillsPanel"; type View = | "providers" @@ -113,6 +117,64 @@ interface SyncStatusUpdatedPayload { error?: string; } +function useProviderSwitchListener( + activeApp: AppId, + onRefetch: () => Promise, +) { + useEffect(() => { + let unsubscribe: (() => void) | undefined; + let active = true; + + const setupListener = async () => { + try { + const off = await providersApi.onSwitched( + async (event: ProviderSwitchEvent) => { + if (event.appType === activeApp) { + await onRefetch(); + } + }, + ); + if (!active) { + off(); + return; + } + unsubscribe = off; + } catch (error) { + console.error("[App] Failed to subscribe provider switch event", error); + } + }; + + void setupListener(); + return () => { + active = false; + unsubscribe?.(); + }; + }, [activeApp, onRefetch]); +} + +function useAutoSyncErrorToast( + eventName: string, + queryClient: QueryClient, + t: (key: any, options?: any) => string, + toastI18nKey: string, +) { + useTauriEvent( + eventName, + async (payload) => { + const statusPayload = payload ?? {}; + await queryClient.invalidateQueries({ queryKey: ["settings"] }); + if (statusPayload.source !== "auto" || statusPayload.status !== "error") { + return; + } + toast.error( + t(toastI18nKey, { + error: statusPayload.error || t("common.unknown"), + }), + ); + }, + ); +} + const DEFAULT_DRAG_BAR_HEIGHT = isWindows() || isLinux() ? 0 : 28; // px const HEADER_HEIGHT = 64; // px @@ -241,10 +303,10 @@ function App() { useUsageCacheBridge(); - const promptPanelRef = useRef(null); - const mcpPanelRef = useRef(null); - const skillsPageRef = useRef(null); - const unifiedSkillsPanelRef = useRef(null); + const promptPanelRef = useRef(null); + const mcpPanelRef = useRef(null); + const skillsPageRef = useRef(null); + const unifiedSkillsPanelRef = useRef(null); const addActionButtonClass = "bg-orange-500 hover:bg-orange-600 dark:bg-orange-500 dark:hover:bg-orange-600 text-white shadow-lg shadow-orange-500/30 dark:shadow-orange-500/40 rounded-full w-8 h-8"; @@ -298,6 +360,8 @@ function App() { isProxyRunning && isCurrentAppTakeoverActive, ); + useProviderSwitchListener(activeApp, refetch); + const disableOmoMutation = useDisableCurrentOmo(); const handleDisableOmo = () => { disableOmoMutation.mutate(undefined, { @@ -332,36 +396,6 @@ function App() { }); }; - useEffect(() => { - let unsubscribe: (() => void) | undefined; - let active = true; - - const setupListener = async () => { - try { - const off = await providersApi.onSwitched( - async (event: ProviderSwitchEvent) => { - if (event.appType === activeApp) { - await refetch(); - } - }, - ); - if (!active) { - off(); - return; - } - unsubscribe = off; - } catch (error) { - console.error("[App] Failed to subscribe provider switch event", error); - } - }; - - void setupListener(); - return () => { - active = false; - unsubscribe?.(); - }; - }, [activeApp, refetch]); - useTauriEvent("universal-provider-synced", async () => { await queryClient.invalidateQueries({ queryKey: ["providers"] }); try { @@ -371,36 +405,17 @@ function App() { } }); - useTauriEvent( + useAutoSyncErrorToast( "webdav-sync-status-updated", - async (payload) => { - const statusPayload = payload ?? {}; - await queryClient.invalidateQueries({ queryKey: ["settings"] }); - if (statusPayload.source !== "auto" || statusPayload.status !== "error") { - return; - } - toast.error( - t("settings.webdavSync.autoSyncFailedToast", { - error: statusPayload.error || t("common.unknown"), - }), - ); - }, + queryClient, + t, + "settings.webdavSync.autoSyncFailedToast", ); - - useTauriEvent( + useAutoSyncErrorToast( "s3-sync-status-updated", - async (payload) => { - const statusPayload = payload ?? {}; - await queryClient.invalidateQueries({ queryKey: ["settings"] }); - if (statusPayload.source !== "auto" || statusPayload.status !== "error") { - return; - } - toast.error( - t("settings.s3Sync.autoSyncFailedToast", { - error: statusPayload.error || t("common.unknown"), - }), - ); - }, + queryClient, + t, + "settings.s3Sync.autoSyncFailedToast", ); useTauriEvent<{ appType: string; providerName: string }>( @@ -1245,7 +1260,7 @@ function App() {