import { Fragment, useCallback, useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { launcherNotifications } from '../../app/constants'; import { closeDialogOnEscape } from '../../app/dialogs'; import { resolveTauriInvoke } from '../../app/tauri'; import type { LocalGameProjectRevisionStatus } from '../../app/types'; import { useWindowChrome, WINDOW_CHROME_DEFAULT_TITLE, } from '../../components/windowChromeContext'; import HomeView from '../../view/home'; import { type LauncherView, Sidebar } from '../../view/layout'; import ProjectDevelopmentView from '../../view/project-development'; import { createProjectManifestMergeState, mergeProjectManifestSnapshot, type ProjectManifestMergeState, type ProjectManifestSnapshot, type ProjectManifestSnapshotMetadata, } from '../../view/project-development/projectResourceLiveUpdateModel'; import { RuntimeConfigDialog } from '../runtime-config/RuntimeConfigDialog'; import { AccountWalletBar, AccountWalletDialogs } from './AccountWallet'; import { DeveloperAgentDialogs, DeveloperAgentPanel, } from './DeveloperAgentPanel'; import type { WorkspaceLauncherShellProps } from './model'; import { NonEmptyProjectDialog, ProjectsPage } from './ProjectCreation'; import { useAccountWallet } from './useAccountWallet'; import { useDeveloperAgentPanel } from './useDeveloperAgentPanel'; import { useHomeProjectCreation } from './useHomeProjectCreation'; import { useRecentProjects } from './useRecentProjects'; export function WorkspaceLauncherShell({ currentUser, onLogout, initialView = 'home', ProjectSupervisor, }: WorkspaceLauncherShellProps) { const { isWindowChrome, setTitle: setWindowTitle, walletSlot, } = useWindowChrome(); const accountWallet = useAccountWallet(currentUser.id); const [status, setStatus] = useState(''); const recentProjects = useRecentProjects(setStatus); const { recentProjectRows, rememberRecentWorkspace } = recentProjects; const [launcherView, setLauncherView] = useState(initialView); const [runtimeConfigOpen, setRuntimeConfigOpen] = useState(false); const [playRequest, setPlayRequest] = useState<{ projectPath: string; requestId: number; } | null>(null); const playRequestIdRef = useRef(0); const [launcherNotice, setLauncherNotice] = useState<{ title: string; message: string; } | null>(null); const developerAgent = useDeveloperAgentPanel(launcherView); const homeProject = useHomeProjectCreation({ setStatus, setLauncherView, setAgentChatProjectPath: developerAgent.setAgentChatProjectPath, rememberRecentWorkspace, }); const { projectPath, setProjectPath, currentProjectContext, setCurrentProjectContext, activeProjectPreview, setActiveProjectPreview, activeProjectAgentRuntimeSummaries, setAgentRuntimeSummaries: setActiveProjectAgentRuntimeSummaries, activeProjectAgentResults, setAgentResults: setActiveProjectAgentResults, resetLauncherHomeDraft, startGameFromApprovedGdd, createHomeDraftAutomatically, openProject, } = homeProject; const activeProjectContextRef = useRef(currentProjectContext); const manifestMergeRef = useRef(null); activeProjectContextRef.current = currentProjectContext; useEffect(() => { const nextTitle = launcherView === 'project-development' ? currentProjectContext?.projectName.trim() : ''; setWindowTitle(nextTitle || WINDOW_CHROME_DEFAULT_TITLE); return () => setWindowTitle(WINDOW_CHROME_DEFAULT_TITLE); }, [currentProjectContext?.projectName, launcherView, setWindowTitle]); useEffect(() => { const current = activeProjectContextRef.current; manifestMergeRef.current = current?.projectRevision === null || !current ? null : createProjectManifestMergeState({ projectPath: current.projectPath, projectId: current.manifest.projectId, revision: current.projectRevision, manifest: current.manifest, source: 'initial', }); }, [ currentProjectContext?.createdAt, currentProjectContext?.projectName, currentProjectContext?.projectPath, ]); const applyManifestSnapshot = useCallback( (snapshot: ProjectManifestSnapshot) => { const current = activeProjectContextRef.current; if ( !current || current.projectPath !== snapshot.projectPath || current.manifest.projectId !== snapshot.projectId ) { return; } let previous = manifestMergeRef.current; if ( !previous || previous.projectPath !== current.projectPath || previous.projectId !== current.manifest.projectId ) { previous = current.projectRevision === null ? null : createProjectManifestMergeState({ projectPath: current.projectPath, projectId: current.manifest.projectId, revision: current.projectRevision, manifest: current.manifest, source: 'initial', }); } if (!previous) { manifestMergeRef.current = createProjectManifestMergeState(snapshot); } else { const merged = mergeProjectManifestSnapshot(previous, snapshot); manifestMergeRef.current = merged.state; if (merged.decision !== 'accepted') { return; } } setCurrentProjectContext((active) => active && active.projectPath === snapshot.projectPath && active.manifest.projectId === snapshot.projectId ? { ...active, manifest: snapshot.manifest, projectRevision: snapshot.revision, } : active, ); }, [setCurrentProjectContext], ); const syncActiveProjectManifest = useCallback( ( sourceProjectPath: string, manifest: NonNullable['manifest'], metadata?: ProjectManifestSnapshotMetadata, ) => { const current = activeProjectContextRef.current; if (!current || current.projectPath !== sourceProjectPath) { return; } if (metadata) { applyManifestSnapshot({ projectPath: sourceProjectPath, manifest, ...metadata, }); return; } const invoke = resolveTauriInvoke(); if (!invoke) { if (current.projectRevision === null) { setCurrentProjectContext((active) => active?.projectPath === sourceProjectPath ? { ...active, manifest } : active, ); } return; } void invoke( 'get_local_game_project_revision', { projectPath: sourceProjectPath }, ) .then((status) => { applyManifestSnapshot({ projectPath: sourceProjectPath, projectId: manifest.projectId, revision: status.revision, manifest, source: 'supervisor', }); }) .catch(() => { if (activeProjectContextRef.current?.projectRevision === null) { setCurrentProjectContext((active) => active?.projectPath === sourceProjectPath ? { ...active, manifest } : active, ); } }); }, [applyManifestSnapshot, setCurrentProjectContext], ); const requestCurrentProjectPlay = useCallback((nextProjectPath: string) => { playRequestIdRef.current += 1; setPlayRequest({ projectPath: nextProjectPath, requestId: playRequestIdRef.current, }); }, []); const handlePlayRequestHandled = useCallback((requestId: number) => { setPlayRequest((current) => current?.requestId === requestId ? null : current, ); }, []); function showLauncherNotice(title: string) { setLauncherNotice({ title, message: `${title}正在接入中,当前版本会先保留入口。`, }); } const currentHelpTitle = launcherView === 'guide' ? '使用指南' : launcherView === 'contact' ? '联系我们' : '最新动态'; const currentHelpItems = launcherView === 'guide' ? ['创建游戏项目', '上传参考素材', '选择审批档位', '生成试玩原型'] : launcherView === 'contact' ? ['产品反馈', '商务合作', '账号与充值支持'] : [ 'GameAgent V1.0 首页改版', '项目工作区和 Agent 会话建设中', '美术生成将接入平台 API', ]; return (
{ resetLauncherHomeDraft(); accountWallet.resetWalletBalance(); onLogout(); }} onNoticeRequest={showLauncherNotice} onRechargeRequest={accountWallet.openRecharge} onRuntimeConfigOpen={() => setRuntimeConfigOpen(true)} onViewChange={setLauncherView} />
0 ? 'launcher-main launcher-main-with-promo' : 'launcher-main' } > {launcherNotifications.length > 0 ? (
{launcherNotifications.map((notification) => ( {notification.label} {notification.detail} ))}
) : null} {launcherView === 'home' ? ( 0} onStatusChange={setStatus} recentProjectRows={recentProjectRows} onCreateDraftAutomatically={createHomeDraftAutomatically} onProjectsOpen={() => setLauncherView('projects')} onProjectOpen={(path) => { setProjectPath(path); void openProject(path, 'open'); }} onProjectPick={() => void homeProject.pickAndOpenProject()} /> ) : launcherView === 'projects' ? ( ) : launcherView === 'agent-chat' ? ( setRuntimeConfigOpen(true)} /> ) : launcherView === 'project-development' && currentProjectContext ? ( requestCurrentProjectPlay(currentProjectContext.projectPath) } onManifestChange={syncActiveProjectManifest} onHomeOpen={() => setLauncherView('home')} onProjectsOpen={() => setLauncherView('projects')} supervisor={ } /> ) : (

{currentHelpTitle}

陶泥儿 GameAgent

{currentHelpItems.map((item) => (
{item}
))}
)}
{runtimeConfigOpen ? ( setRuntimeConfigOpen(false)} /> ) : null} {walletSlot ? ( createPortal( , walletSlot, ) ) : isWindowChrome ? null : ( )} {launcherNotice ? (
{ if (event.target === event.currentTarget) { setLauncherNotice(null); } }} >
closeDialogOnEscape(event, () => setLauncherNotice(null)) } >

{launcherNotice.title}

{launcherNotice.message}

) : null}
); }