759951ab12
拆出项目工作区视图、命令策略、摘要命令与开发面板模块。 拆出 Runtime action、driver 与 protocol 的职责模块并保留稳定 facade。 保留测试模块、兼容重导出与原有可见性边界。 补充结构拆分决策、踩坑记录与实施计划验收结果。
420 lines
13 KiB
TypeScript
420 lines
13 KiB
TypeScript
import type { Dispatch, FormEventHandler, SetStateAction } from 'react';
|
|
|
|
import type { GameCreationAppLimitedRunCommandDescriptor } from '../../../../../packages/shared/src/contracts/gameCreationApp';
|
|
import type {
|
|
InitLocalProjectResult,
|
|
LocalPreviewResult,
|
|
LocalProjectFileEntry,
|
|
MemoryScope,
|
|
UploadLocalAssetResult,
|
|
} from '../../app/types';
|
|
|
|
type DeveloperProjectPanelsProps = {
|
|
assetCanvasProjectId: string;
|
|
assetGenerationPrompt: string;
|
|
assetKind: string;
|
|
assetLocalPath: string;
|
|
assetMediaType: string;
|
|
assetObjectId: string;
|
|
assetResourceId: string;
|
|
assetSourceKind: string;
|
|
assetStatus: string;
|
|
canvasExportPath: string;
|
|
commandLog: string[];
|
|
editorBaseUrl: string;
|
|
fileDraft: string;
|
|
filePath: string;
|
|
fileStatus: string;
|
|
handleAssetRegister: () => void;
|
|
handleCanvasAssetGenerate: () => void;
|
|
handleCanvasAssetImport: () => void;
|
|
handleCanvasExportImport: () => void;
|
|
handleCanvasProjectOpen: () => void;
|
|
handleCanvasProjectSync: () => void;
|
|
handleFileDelete: () => void;
|
|
handleFileList: () => void | Promise<void>;
|
|
handleFileRead: () => void | Promise<void>;
|
|
handleFileWrite: () => void;
|
|
handleLimitedCommandRun: (
|
|
command: GameCreationAppLimitedRunCommandDescriptor,
|
|
) => void;
|
|
handleMemoryDelete: () => void;
|
|
handleMemoryRead: () => void | Promise<void>;
|
|
handleMemoryWrite: () => void;
|
|
handlePreviewStart: () => void;
|
|
handlePreviewStatus: () => void;
|
|
handlePreviewStop: () => void;
|
|
handleProjectInit: FormEventHandler<HTMLFormElement>;
|
|
handleProjectLogRead: (path: string) => void | Promise<void>;
|
|
limitedCommandStatus: string;
|
|
limitedLocalCommands: GameCreationAppLimitedRunCommandDescriptor[];
|
|
localProject: InitLocalProjectResult | null;
|
|
memoryDraft: string;
|
|
memoryScope: MemoryScope;
|
|
memoryStatus: string;
|
|
preview: LocalPreviewResult | null;
|
|
previewStatus: string;
|
|
projectFiles: LocalProjectFileEntry[];
|
|
projectLogContent: string;
|
|
projectLogStatus: string;
|
|
projectPath: string;
|
|
projectStatus: string;
|
|
refreshLimitedLocalCommands: () => void | Promise<void>;
|
|
setAssetCanvasProjectId: Dispatch<SetStateAction<string>>;
|
|
setAssetGenerationPrompt: Dispatch<SetStateAction<string>>;
|
|
setAssetKind: Dispatch<SetStateAction<string>>;
|
|
setAssetLocalPath: Dispatch<SetStateAction<string>>;
|
|
setAssetMediaType: Dispatch<SetStateAction<string>>;
|
|
setAssetObjectId: Dispatch<SetStateAction<string>>;
|
|
setAssetResourceId: Dispatch<SetStateAction<string>>;
|
|
setAssetSourceKind: Dispatch<SetStateAction<string>>;
|
|
setCanvasExportPath: Dispatch<SetStateAction<string>>;
|
|
setEditorBaseUrl: Dispatch<SetStateAction<string>>;
|
|
setFileDraft: Dispatch<SetStateAction<string>>;
|
|
setFilePath: Dispatch<SetStateAction<string>>;
|
|
setMemoryDraft: Dispatch<SetStateAction<string>>;
|
|
setMemoryScope: Dispatch<SetStateAction<MemoryScope>>;
|
|
setProjectPath: Dispatch<SetStateAction<string>>;
|
|
uploadedAssets: UploadLocalAssetResult[];
|
|
};
|
|
|
|
export function DeveloperProjectPanels({
|
|
assetCanvasProjectId,
|
|
assetGenerationPrompt,
|
|
assetKind,
|
|
assetLocalPath,
|
|
assetMediaType,
|
|
assetObjectId,
|
|
assetResourceId,
|
|
assetSourceKind,
|
|
assetStatus,
|
|
canvasExportPath,
|
|
commandLog,
|
|
editorBaseUrl,
|
|
fileDraft,
|
|
filePath,
|
|
fileStatus,
|
|
handleAssetRegister,
|
|
handleCanvasAssetGenerate,
|
|
handleCanvasAssetImport,
|
|
handleCanvasExportImport,
|
|
handleCanvasProjectOpen,
|
|
handleCanvasProjectSync,
|
|
handleFileDelete,
|
|
handleFileList,
|
|
handleFileRead,
|
|
handleFileWrite,
|
|
handleLimitedCommandRun,
|
|
handleMemoryDelete,
|
|
handleMemoryRead,
|
|
handleMemoryWrite,
|
|
handlePreviewStart,
|
|
handlePreviewStatus,
|
|
handlePreviewStop,
|
|
handleProjectInit,
|
|
handleProjectLogRead,
|
|
limitedCommandStatus,
|
|
limitedLocalCommands,
|
|
localProject,
|
|
memoryDraft,
|
|
memoryScope,
|
|
memoryStatus,
|
|
preview,
|
|
previewStatus,
|
|
projectFiles,
|
|
projectLogContent,
|
|
projectLogStatus,
|
|
projectPath,
|
|
projectStatus,
|
|
refreshLimitedLocalCommands,
|
|
setAssetCanvasProjectId,
|
|
setAssetGenerationPrompt,
|
|
setAssetKind,
|
|
setAssetLocalPath,
|
|
setAssetMediaType,
|
|
setAssetObjectId,
|
|
setAssetResourceId,
|
|
setAssetSourceKind,
|
|
setCanvasExportPath,
|
|
setEditorBaseUrl,
|
|
setFileDraft,
|
|
setFilePath,
|
|
setMemoryDraft,
|
|
setMemoryScope,
|
|
setProjectPath,
|
|
uploadedAssets,
|
|
}: DeveloperProjectPanelsProps) {
|
|
return (
|
|
<>
|
|
<section className="developer-panel asset-pane" aria-label="文件和资产">
|
|
<h2>本地项目</h2>
|
|
<form className="local-project-form" onSubmit={handleProjectInit}>
|
|
<input
|
|
aria-label="本地项目目录"
|
|
value={projectPath}
|
|
onChange={(event) => setProjectPath(event.currentTarget.value)}
|
|
/>
|
|
<button type="submit">初始化</button>
|
|
</form>
|
|
<p className="status-line">{projectStatus}</p>
|
|
<p className="status-line">{assetStatus}</p>
|
|
<div className="asset-register-form">
|
|
<input
|
|
aria-label="资产路径"
|
|
value={assetLocalPath}
|
|
onChange={(event) => setAssetLocalPath(event.currentTarget.value)}
|
|
/>
|
|
<input
|
|
aria-label="资产类型"
|
|
value={assetKind}
|
|
onChange={(event) => setAssetKind(event.currentTarget.value)}
|
|
/>
|
|
<input
|
|
aria-label="媒体类型"
|
|
value={assetMediaType}
|
|
onChange={(event) => setAssetMediaType(event.currentTarget.value)}
|
|
/>
|
|
<select
|
|
aria-label="资产来源"
|
|
value={assetSourceKind}
|
|
onChange={(event) => setAssetSourceKind(event.currentTarget.value)}
|
|
>
|
|
<option value="generated">generated</option>
|
|
<option value="uploaded">uploaded</option>
|
|
<option value="canvas">canvas</option>
|
|
</select>
|
|
<input
|
|
aria-label="画板项目"
|
|
value={assetCanvasProjectId}
|
|
onChange={(event) =>
|
|
setAssetCanvasProjectId(event.currentTarget.value)
|
|
}
|
|
/>
|
|
<input
|
|
aria-label="资源 ID"
|
|
value={assetResourceId}
|
|
onChange={(event) => setAssetResourceId(event.currentTarget.value)}
|
|
/>
|
|
<input
|
|
aria-label="资产对象 ID"
|
|
value={assetObjectId}
|
|
onChange={(event) => setAssetObjectId(event.currentTarget.value)}
|
|
/>
|
|
<input
|
|
aria-label="编辑器地址"
|
|
value={editorBaseUrl}
|
|
onChange={(event) => setEditorBaseUrl(event.currentTarget.value)}
|
|
/>
|
|
<input
|
|
aria-label="画板导出 ZIP"
|
|
value={canvasExportPath}
|
|
onChange={(event) => setCanvasExportPath(event.currentTarget.value)}
|
|
/>
|
|
<input
|
|
aria-label="美术生成提示词"
|
|
value={assetGenerationPrompt}
|
|
onChange={(event) =>
|
|
setAssetGenerationPrompt(event.currentTarget.value)
|
|
}
|
|
/>
|
|
<button type="button" onClick={handleCanvasProjectOpen}>
|
|
打开画板
|
|
</button>
|
|
<button type="button" onClick={handleCanvasProjectSync}>
|
|
同步画板项目
|
|
</button>
|
|
<button type="button" onClick={handleAssetRegister}>
|
|
登记资产
|
|
</button>
|
|
<button type="button" onClick={handleCanvasAssetGenerate}>
|
|
生成美术资产
|
|
</button>
|
|
<button type="button" onClick={handleCanvasAssetImport}>
|
|
导入画板资产
|
|
</button>
|
|
<button type="button" onClick={handleCanvasExportImport}>
|
|
导入导出包
|
|
</button>
|
|
</div>
|
|
<code>game/</code>
|
|
<code>assets/</code>
|
|
<code>memory/</code>
|
|
<code>.agent/manifest.json</code>
|
|
{uploadedAssets.map((asset) => (
|
|
<code key={asset.id}>{asset.localPath}</code>
|
|
))}
|
|
{localProject ? (
|
|
<p className="manifest-path">{localProject.manifestPath}</p>
|
|
) : null}
|
|
</section>
|
|
|
|
<section
|
|
className="developer-panel developer-panel--wide file-pane"
|
|
aria-label="项目文件"
|
|
>
|
|
<header className="panel-header">
|
|
<h2>项目文件</h2>
|
|
<div className="panel-actions">
|
|
<button type="button" onClick={() => void handleFileList()}>
|
|
列出
|
|
</button>
|
|
<button type="button" onClick={() => void handleFileRead()}>
|
|
读取
|
|
</button>
|
|
<button type="button" onClick={handleFileWrite}>
|
|
保存
|
|
</button>
|
|
<button type="button" onClick={handleFileDelete}>
|
|
删除
|
|
</button>
|
|
</div>
|
|
</header>
|
|
<input
|
|
aria-label="项目文件路径"
|
|
value={filePath}
|
|
onChange={(event) => setFilePath(event.currentTarget.value)}
|
|
/>
|
|
<textarea
|
|
aria-label="项目文件内容"
|
|
value={fileDraft}
|
|
onChange={(event) => setFileDraft(event.currentTarget.value)}
|
|
/>
|
|
<p className="status-line">{fileStatus}</p>
|
|
<div className="file-list">
|
|
{projectFiles.map((file) => (
|
|
<button
|
|
key={file.path}
|
|
type="button"
|
|
onClick={() => setFilePath(file.path)}
|
|
>
|
|
{file.kind === 'directory' ? `${file.path}/` : file.path}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
<section
|
|
className="developer-panel developer-panel--wide memory-pane"
|
|
aria-label="记忆"
|
|
>
|
|
<header className="panel-header">
|
|
<h2>记忆</h2>
|
|
<div className="panel-actions">
|
|
<select
|
|
aria-label="记忆范围"
|
|
value={memoryScope}
|
|
onChange={(event) =>
|
|
setMemoryScope(event.currentTarget.value as MemoryScope)
|
|
}
|
|
>
|
|
<option value="long">长期</option>
|
|
<option value="short">短期</option>
|
|
<option value="blackboard">黑板</option>
|
|
</select>
|
|
<button type="button" onClick={() => void handleMemoryRead()}>
|
|
读取
|
|
</button>
|
|
<button type="button" onClick={handleMemoryWrite}>
|
|
保存
|
|
</button>
|
|
<button type="button" onClick={handleMemoryDelete}>
|
|
删除
|
|
</button>
|
|
</div>
|
|
</header>
|
|
<textarea
|
|
aria-label="记忆内容"
|
|
value={memoryDraft}
|
|
onChange={(event) => setMemoryDraft(event.currentTarget.value)}
|
|
/>
|
|
<p className="status-line">{memoryStatus}</p>
|
|
</section>
|
|
|
|
<section
|
|
className="developer-panel developer-panel--wide preview-pane"
|
|
aria-label="预览"
|
|
>
|
|
<header className="panel-header">
|
|
<h2>预览</h2>
|
|
<div className="panel-actions">
|
|
<button type="button" onClick={handlePreviewStatus}>
|
|
状态
|
|
</button>
|
|
<button type="button" onClick={handlePreviewStart}>
|
|
启动
|
|
</button>
|
|
<button type="button" onClick={handlePreviewStop}>
|
|
停止
|
|
</button>
|
|
</div>
|
|
</header>
|
|
<p className="status-line">{previewStatus}</p>
|
|
{preview ? (
|
|
<iframe
|
|
className="preview-frame"
|
|
src={preview.url}
|
|
title="本地游戏预览"
|
|
/>
|
|
) : (
|
|
<div className="preview-frame">127.0.0.1:<port></div>
|
|
)}
|
|
</section>
|
|
|
|
<section
|
|
className="developer-panel developer-panel--wide log-pane"
|
|
aria-label="日志"
|
|
>
|
|
<header className="panel-header">
|
|
<h2>日志</h2>
|
|
<div className="panel-actions">
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
void handleProjectLogRead('.agent/logs/command.log')
|
|
}
|
|
>
|
|
命令日志
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
void handleProjectLogRead('.agent/logs/preview.log')
|
|
}
|
|
>
|
|
预览日志
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => void handleProjectLogRead('.agent/logs/agent.log')}
|
|
>
|
|
Agent日志
|
|
</button>
|
|
<button type="button" onClick={refreshLimitedLocalCommands}>
|
|
刷新
|
|
</button>
|
|
</div>
|
|
</header>
|
|
<div className="limited-command-list">
|
|
{limitedLocalCommands.map((command) => (
|
|
<button
|
|
key={command.id}
|
|
type="button"
|
|
onClick={() => handleLimitedCommandRun(command)}
|
|
>
|
|
{command.title}
|
|
</button>
|
|
))}
|
|
</div>
|
|
<p className="status-line">{limitedCommandStatus}</p>
|
|
<p className="status-line">{projectLogStatus}</p>
|
|
{projectLogContent ? (
|
|
<pre className="project-log-content">{projectLogContent}</pre>
|
|
) : null}
|
|
{commandLog.map((entry, index) => (
|
|
<p key={`${entry}-${index}`}>{entry}</p>
|
|
))}
|
|
</section>
|
|
</>
|
|
);
|
|
}
|