import type { ReactNode } from "react"; import { useEffect } from "react"; import { QueryClientProvider } from "@tanstack/react-query"; import { App, ConfigProvider } from "antd"; import zhCN from "antd/locale/zh_CN"; import { AuthSessionHydrator } from "@/components/auth/auth-session-hydrator"; import { ClientRootInit } from "@/components/layout/client-root-init"; import { getAntThemeConfig } from "@/lib/app-theme"; import { applySkinTheme } from "@/lib/skin-themes"; import { appQueryClient } from "@/lib/query-client"; import { useThemeStore } from "@/stores/use-theme-store"; import { applyAppearanceMetadata, useAppearanceStore } from "@/stores/use-appearance-store"; import { listRegisteredPlugins } from "@/lib/plugins/plugin-registry"; import { usePluginStore } from "@/stores/use-plugin-store"; import { fetchPluginRuntimeState, setUserPluginEnabled } from "@/services/api/plugins"; import { useUserStore } from "@/stores/use-user-store"; export function AppProviders({ children }: { children: ReactNode }) { const theme = useThemeStore((state) => state.theme); const dark = theme === "dark"; const appearance = useAppearanceStore((state) => state.appearance); const ensurePlugin = usePluginStore((state) => state.ensurePlugin); const setRuntimeStatuses = usePluginStore((state) => state.setRuntimeStatuses); const setPluginStates = usePluginStore((state) => state.setPluginStates); const pluginStoreHydrated = usePluginStore((state) => state.hydrated); const userId = useUserStore((state) => state.user?.id); useEffect(() => { if (!pluginStoreHydrated) return; for (const plugin of listRegisteredPlugins()) ensurePlugin(plugin.manifest); }, [ensurePlugin, pluginStoreHydrated, userId]); useEffect(() => { if (!userId) { setRuntimeStatuses({}); setPluginStates({}); return; } if (!pluginStoreHydrated) return; let cancelled = false; void fetchPluginRuntimeState() .then(async ({ statuses, states }) => { const legacyEnabledIds = usePluginStore .getState() .installations.filter((installation) => installation.enabled && states[installation.manifest.id]?.canToggle && !states[installation.manifest.id]?.userConfigured) .map((installation) => installation.manifest.id); if (legacyEnabledIds.length) { try { const migrated = await Promise.all(legacyEnabledIds.map((pluginId) => setUserPluginEnabled(pluginId, true))); for (const state of migrated) states[state.pluginId] = state; for (const pluginId of legacyEnabledIds) statuses[pluginId] = states[pluginId]?.effectiveEnabled ? "enabled" : "disabled"; } catch (error) { console.warn("迁移用户插件启用状态失败,已保留服务端状态", error); } } if (!cancelled) { setRuntimeStatuses(statuses); setPluginStates(states); } }) .catch(() => { if (!cancelled) { setRuntimeStatuses({}); setPluginStates({}); } }); return () => { cancelled = true; }; }, [pluginStoreHydrated, setPluginStates, setRuntimeStatuses, userId]); useEffect(() => { document.documentElement.classList.toggle("dark", dark); document.documentElement.style.colorScheme = theme; applySkinTheme(appearance.activeSkin, theme); applyAppearanceMetadata(appearance); }, [appearance, dark, theme]); // DEV 复现台必须是同源本地确定性场景:AuthSessionHydrator 会打 /api/auth/session, // ClientRootInit 会打 /api/model-catalog,没有后端时产生真实 502,与导演台无关却会污染判据。 // 只精确匹配该路径;生产构建中 import.meta.env.DEV 为 false,本分支被摇树删除。 const isolateDevRepro = import.meta.env.DEV && typeof window !== "undefined" && window.location.pathname === "/dev/director-repro"; return ( {isolateDevRepro ? ( children ) : ( {children} )} ); }