/** * 应用主入口组件 * * 管理页面路由和全局状态 * 支持静态页面和动态插件页面路由 * 包含启动画面和全局图标侧边栏 * * _需求: 2.2, 3.2, 5.2_ */ import React, { useState, useEffect, useCallback } from "react"; import styled from "styled-components"; import { invoke } from "@tauri-apps/api/core"; import { withI18nPatch } from "./i18n/withI18nPatch"; import { SplashScreen } from "./components/SplashScreen"; import { AppSidebar } from "./components/AppSidebar"; import { SettingsPageV2 } from "./components/settings-v2"; import { ToolsPage } from "./components/tools/ToolsPage"; import { ResourcesPage } from "./components/resources"; import { MemoryPage } from "./components/memory"; import { AgentChatPage } from "./components/agent"; import { PluginsPage } from "./components/plugins/PluginsPage"; import { ImageGenPage } from "./components/image-gen"; import { BatchPage } from "./components/batch"; import { RecentImageInsertFloating } from "./components/image-gen/RecentImageInsertFloating"; import { CreateProjectDialog } from "./components/projects/CreateProjectDialog"; import { WorkbenchPage } from "./components/workspace"; import { ProjectType, createProject, isUserProjectType, resolveProjectRootPath, } from "./lib/api/project"; import { TerminalWorkspace, SysinfoView, FileBrowserView, WebView, } from "./components/terminal"; import { OnboardingWizard, useOnboardingState } from "./components/onboarding"; import { ConnectConfirmDialog } from "./components/connect"; import { showRegistryLoadError } from "./lib/utils/connectError"; import { useDeepLink } from "./hooks/useDeepLink"; import { useRelayRegistry } from "./hooks/useRelayRegistry"; import { ComponentDebugProvider } from "./contexts/ComponentDebugContext"; import { SoundProvider } from "./contexts/SoundProvider"; import { ComponentDebugOverlay } from "./components/dev"; import { AgentPageParams, getThemeByWorkspacePage, getThemeWorkspacePage, isThemeWorkspacePage, LAST_THEME_WORKSPACE_PAGE_STORAGE_KEY, Page, PageParams, ProjectDetailPageParams, SettingsPageParams, ThemeWorkspacePage, WorkspaceTheme, } from "./types/page"; import { SettingsTabs } from "./types/settings"; import { toast } from "sonner"; import { recordWorkspaceRepair } from "@/lib/workspaceHealthTelemetry"; const AppContainer = styled.div` display: flex; height: 100vh; width: 100vw; background-color: hsl(var(--background)); overflow: hidden; `; const MainContent = styled.main<{ $withSidebarGap?: boolean }>` flex: 1; overflow: hidden; display: flex; flex-direction: column; min-height: 0; padding-left: ${(props) => (props.$withSidebarGap ? "10px" : "0")}; `; const PageWrapper = styled.div<{ $isActive: boolean }>` flex: 1; padding: 24px; overflow: auto; display: ${(props) => (props.$isActive ? "block" : "none")}; `; const FullscreenWrapper = styled.div<{ $isActive: boolean }>` flex: 1; min-height: 0; overflow: hidden; display: ${(props) => (props.$isActive ? "flex" : "none")}; flex-direction: column; position: relative; `; const THEME_WORKSPACE_PAGES: ThemeWorkspacePage[] = [ "workspace-general", "workspace-social-media", "workspace-poster", "workspace-music", "workspace-knowledge", "workspace-planning", "workspace-document", "workspace-video", "workspace-novel", ]; function AppContent() { const [showSplash, setShowSplash] = useState(true); const [currentPage, setCurrentPage] = useState("agent"); const [pageParams, setPageParams] = useState({}); const [agentHasMessages, setAgentHasMessages] = useState(false); const { needsOnboarding, completeOnboarding } = useOnboardingState(); const [projectDialogOpen, setProjectDialogOpen] = useState(false); const [pendingRecommendation, setPendingRecommendation] = useState<{ shortLabel: string; fullPrompt: string; projectType: ProjectType; projectName: string; } | null>(null); const resolveWorkspacePage = useCallback( (workspaceTheme?: WorkspaceTheme): ThemeWorkspacePage => { if (workspaceTheme) { return getThemeWorkspacePage(workspaceTheme); } if (typeof window !== "undefined") { const savedPage = localStorage.getItem( LAST_THEME_WORKSPACE_PAGE_STORAGE_KEY, ); if ( savedPage && THEME_WORKSPACE_PAGES.includes(savedPage as ThemeWorkspacePage) ) { return savedPage as ThemeWorkspacePage; } } return getThemeWorkspacePage("general"); }, [], ); const handleNavigate = useCallback( (page: Page, params?: PageParams) => { if (page === "workspace") { setCurrentPage("agent"); setPageParams( (params as AgentPageParams | undefined) || { theme: "general", lockTheme: false, }, ); return; } if (page === "api-server") { setCurrentPage("settings"); setPageParams({ tab: SettingsTabs.ApiServer } as SettingsPageParams); return; } if (page === "provider-pool") { setCurrentPage("settings"); setPageParams({ tab: SettingsTabs.Providers } as SettingsPageParams); return; } if (page === "mcp") { setCurrentPage("settings"); setPageParams({ tab: SettingsTabs.McpServer } as SettingsPageParams); return; } if (page === "projects") { const projectParams = params as | { projectId?: string; workspaceTheme?: WorkspaceTheme; } | undefined; const targetWorkspacePage = resolveWorkspacePage( projectParams?.workspaceTheme, ); if (typeof window !== "undefined") { localStorage.setItem( LAST_THEME_WORKSPACE_PAGE_STORAGE_KEY, targetWorkspacePage, ); } setCurrentPage(targetWorkspacePage); setPageParams({ ...(projectParams?.projectId ? { projectId: projectParams.projectId } : {}), workspaceViewMode: "project-management", }); return; } if (page === "project-detail") { const projectParams = params as ProjectDetailPageParams | undefined; const targetWorkspacePage = resolveWorkspacePage( projectParams?.workspaceTheme, ); const workspaceViewMode = projectParams?.projectId ? "workspace" : "project-management"; if (typeof window !== "undefined") { localStorage.setItem( LAST_THEME_WORKSPACE_PAGE_STORAGE_KEY, targetWorkspacePage, ); } setCurrentPage(targetWorkspacePage); setPageParams({ ...(projectParams?.projectId ? { projectId: projectParams.projectId } : {}), workspaceViewMode, }); return; } if (isThemeWorkspacePage(page) && typeof window !== "undefined") { localStorage.setItem(LAST_THEME_WORKSPACE_PAGE_STORAGE_KEY, page); } setCurrentPage(page); setPageParams(params ? { ...params } : {}); }, [resolveWorkspacePage], ); const _handleRequestRecommendation = useCallback( (shortLabel: string, fullPrompt: string, currentTheme: string) => { const themeLabels: Record = { "social-media": "社媒", poster: "海报", music: "音乐", knowledge: "知识", planning: "计划", novel: "小说", document: "文档", video: "视频", general: "对话", }; const prefix = themeLabels[currentTheme] || "项目"; const projectName = `${prefix}:${shortLabel}`; setPendingRecommendation({ shortLabel, fullPrompt, projectType: currentTheme as ProjectType, projectName, }); setProjectDialogOpen(true); }, [], ); const handleCreateProjectFromRecommendation = async ( name: string, type: ProjectType, ) => { const projectPath = await resolveProjectRootPath(name); const project = await createProject({ name, rootPath: projectPath, workspaceType: type, }); if (pendingRecommendation) { handleNavigate(getThemeWorkspacePage(type as WorkspaceTheme), { projectId: project.id, workspaceViewMode: "workspace", workspaceCreatePrompt: pendingRecommendation.fullPrompt, workspaceCreateSource: "workspace_prompt", workspaceCreateFallbackTitle: name, }); setPendingRecommendation(null); } else if (isUserProjectType(type)) { handleNavigate(getThemeWorkspacePage(type as WorkspaceTheme), { projectId: project.id, workspaceViewMode: "project-management", }); } else { handleNavigate("agent", { projectId: project.id, }); } toast.success("项目创建成功"); }; const { connectPayload, relayInfo, isVerified, isDialogOpen, isSaving, error, handleConfirm, handleCancel, } = useDeepLink(); const { error: registryError, refresh: _refreshRegistry } = useRelayRegistry(); useEffect(() => { if (registryError) { console.warn("[App] Registry 加载失败:", registryError); showRegistryLoadError(registryError.message); } }, [registryError]); useEffect(() => { void invoke<{ workspaceId: string; rootPath: string; created: boolean; repaired: boolean; relocated?: boolean; } | null>("workspace_ensure_default_ready") .then((result) => { if (result?.repaired) { recordWorkspaceRepair({ workspaceId: result.workspaceId, rootPath: result.rootPath, source: "app_startup", }); console.info( "[App] 启动时检测到默认工作区目录缺失,已自动修复:", result.rootPath, ); } }) .catch((error) => { console.warn("[App] 启动时工作区健康检查失败:", error); }); }, []); useEffect(() => { const mainElement = document.querySelector("main"); if (mainElement) { mainElement.scrollTop = 0; } }, [currentPage]); const handleSplashComplete = useCallback(() => { setShowSplash(false); }, []); const renderThemeWorkspaces = () => { if (!THEME_WORKSPACE_PAGES.includes(currentPage as ThemeWorkspacePage)) { return null; } const page = currentPage as ThemeWorkspacePage; const theme = getThemeByWorkspacePage(page); return (
); }; const renderAllPages = () => { return ( <>
{currentPage === "agent" ? ( ) : null}
{renderThemeWorkspaces()}
); }; const handleOnboardingComplete = useCallback(() => { completeOnboarding(); }, [completeOnboarding]); if (showSplash) { return ; } if (needsOnboarding === null) { return null; } if (needsOnboarding) { return ; } const currentAgentParams = pageParams as AgentPageParams; const shouldHideSidebarForAgent = currentPage === "agent" && (Boolean(currentAgentParams.fromResources) || (agentHasMessages && Boolean(currentAgentParams.lockTheme))); const shouldShowAppSidebar = currentPage !== "settings" && currentPage !== "memory" && currentPage !== "image-gen" && currentPage !== "tools" && currentPage !== "plugins" && currentPage !== "resources" && !isThemeWorkspacePage(currentPage) && !shouldHideSidebarForAgent; const shouldAddMainContentGap = shouldShowAppSidebar && currentPage === "agent"; return ( {shouldShowAppSidebar && ( )} {renderAllPages()} { setProjectDialogOpen(open); if (!open) { setPendingRecommendation(null); } }} onSubmit={handleCreateProjectFromRecommendation} defaultType={pendingRecommendation?.projectType} defaultName={pendingRecommendation?.projectName} /> ); } const App = withI18nPatch(AppContent); export default App;