diff --git a/frontend-architecture-v3.md b/frontend-architecture-v3.md new file mode 100644 index 0000000..e63867f --- /dev/null +++ b/frontend-architecture-v3.md @@ -0,0 +1,48 @@ +# Windup 前端粗架构 + +## 本 PR 的目标 + +本 PR 只确定粗粒度模块、公开接口和必要类型。具体目录细分、服务端调用、流程状态转换、生成处理、页面交互、测试和工程配置分别由后续小 PR 实现。 + +## 依赖方向 + +```text +app -> pages -> features -> workflow-controller / entities -> shared +``` + +- `app`:启动、路由、布局。 +- `pages`:路由页面和模块组合。 +- `features`:角色设置、生成、审核、导出。 +- `workflow-controller`:Quick Start 与 Workflow Editor 共享的整体流程接口。 +- `entities`:业务数据及其服务端 APIs。 +- `shared`:不理解 Windup 业务语义的通用 UI、Hooks、工具与配置边界。 + +`shared` 只能被上层依赖,不能反向导入任何 Entity 或业务模块。当前没有需要沉淀的 +公共实现,因此只保留职责说明,不为了目录完整度制造空工具代码。 + +## 两种制作方式 + +Quick Start 使用自然语言和参考图直接表达目标,自动流程隐藏内部步骤;Workflow Editor 让用户逐步操作。两者页面和显示内容完全不同,但共享 WorkflowRun、WorkflowStep 和 Workflow Controller 接口。 + +## Workflow 边界 + +Workflow Controller 负责维护步骤数据、当前步骤、历史 Revision 和流程推进。每个 Revision 通过 `currentStepId` 明确指向当前步骤;服务端异步结果携带发起请求时的 `revisionId`,防止重启后的旧结果串入新版本。需要服务端结果的步骤由 Controller 调用相应业务 APIs,并把返回结果更新到 WorkflowStep。WorkflowRun 作为业务资源由服务端持久化。 + +Controller 是一个整体,不把推进、重启、中断、恢复或服务端结果处理拆成独立模块。 + +## 多方向资产 + +Project 通过 `DirectionMode` 决定单方向、四方向或八方向。`BaseFrame` 使用 +`SpriteDirection` 标注方向,Action 使用 `ActionSequence[]` 保存各方向的帧序列, +不依赖隐含的数组顺序猜测方向。 + +## Generation 边界 + +前端接触的是 Generation 和 Task 业务记录:创建记录、查询状态、展示结果,并据此更新 WorkflowStep。真正的模型调用发生在后端,前端不表达模型能力层。 + +## 当前未冻结 + +- 服务端 URL、DTO 外壳、认证和错误格式。 +- 图片上传方法和媒体引用形式。 +- 审核、导出和事件传输方法。 +- Workflow Controller 的具体状态转换实现。 diff --git a/frontend/API_CONTRACT.md b/frontend/API_CONTRACT.md new file mode 100644 index 0000000..e9d104f --- /dev/null +++ b/frontend/API_CONTRACT.md @@ -0,0 +1,111 @@ +# 前端公开接口 + +本文件描述模块调用形状,不声明服务端 URL、请求外壳或错误格式。 + +## 命名约定 + +- TypeScript 类型和组件使用 PascalCase,字段、参数和方法使用 camelCase。 +- URL 路径和源码目录使用 kebab-case,例如 `/quick-start`、`workflow-controller/`。 +- 会进入业务 JSON 的枚举值统一使用 snake_case,例如 `create_character`、 + `character_template`、`north_east`。 +- 服务端业务接口集合遵循导师约定统一使用 `*APIs`,不混用其他接口后缀。 +- 所有容易混淆的模板和候选字段必须带业务前缀,例如 `actionTemplateId`、 + `characterTemplateCandidateId`。 + +## 业务 APIs + +业务模块就近公开以下七组 APIs: + +- `ProjectAPIs`:项目。 +- `CharacterAPIs`:角色、造型和动作。 +- `ActionTemplateAPIs`:动作模板。 +- `WorkflowRunAPIs`:工作流持久化。 +- `GenerationAPIs`:生成业务记录。 +- `TaskAPIs`:异步任务快照。 +- `PlaytestInspectionAPIs`:Playtest 独立核验记录。 + +它们只描述前端已确认的业务操作,具体实现等待对应后端合同。 + +```ts +interface ProjectAPIs { + list(): Promise + get(projectId: string): Promise + create(input: CreateProjectInput): Promise + update(projectId: string, input: UpdateProjectInput): Promise + remove(projectId: string): Promise +} + +interface CharacterAPIs { + get(characterId: string): Promise + listByProject(projectId: string): Promise + create(input: CreateCharacterInput): Promise + update(character: Character): Promise + confirmCharacterTemplate( + characterId: string, + input: ConfirmCharacterTemplateInput, + ): Promise + addAction(characterId: string, input: AddActionInput): Promise +} + +interface ActionTemplateAPIs { + listAvailable(projectId: string): Promise +} + +interface WorkflowRunAPIs { + create(input: CreateWorkflowRunInput): Promise + get(runId: string): Promise + update(run: WorkflowRun): Promise +} + +interface GenerationAPIs { + create(input: CreateGenerationInput): Promise + get(generationId: string): Promise +} + +interface TaskAPIs { + get(taskId: string): Promise +} + +interface PlaytestInspectionAPIs { + getLatest(target: { + characterId: string + outfitId: string + }): Promise + record(input: RecordPlaytestInspectionInput): Promise +} +``` + +图片上传、审核、导出和事件传输的正式方法尚未冻结,本 PR 不提前声明。 + +## Workflow Controller + +Workflow Controller 操作一份 WorkflowRun 数据,不按 next、restart 等方法拆模块。 + +```ts +interface WorkflowController { + create(input: CreateWorkflowRunInput): Promise + getWorkflow(): WorkflowRun + nextStep(): Promise + updateStep(input: UpdateWorkflowStepInput): Promise + restartFromStep(input: RestartWorkflowFromStepInput): Promise + interrupt(): Promise + resume(): Promise + applyServerResult(input: ApplyWorkflowServerResultInput): Promise +} +``` + +步骤的当前数据保存在 `WorkflowStep.data`,当前步骤由 `WorkflowRevision.currentStepId` 明确指向。从历史步骤重新开始时追加 Revision,旧 Revision 保持只读;异步服务端结果必须携带发起请求时的 `revisionId`,不能污染重启后的新版本。如何清理后续步骤由后续实现 PR 完成。 + +## 关键术语 + +- `ActionTemplate`:动作模板,描述动作名称与提示词。 +- `ActionSource`:动作来自 ActionTemplate,还是来自用户自定义提示词。 +- `candidateCharacterTemplates`:生成出的角色母版候选。 +- `characterTemplateCandidateId`:用户确认的具体角色母版候选 ID。 +- `characterTemplateUrl`:用户确认的角色母版。 +- `baseFrames`:母版确认后展开的多方向基础帧。 +- `DirectionMode`:Project 要求单方向、四方向还是八方向资产。 +- `SpriteDirection`:基础帧和动作序列共同使用的明确精灵朝向。 +- `ActionSequence`:一个动作在某个精灵朝向下的完整帧序列。 +- `Generation`:前端创建、查询和展示的生成业务记录。 +- `Task`:服务端异步任务快照,不等于 WorkflowStep。 diff --git a/frontend/MODULES.md b/frontend/MODULES.md new file mode 100644 index 0000000..6d4db1d --- /dev/null +++ b/frontend/MODULES.md @@ -0,0 +1,41 @@ +# 前端模块 + +## app + +只负责启动、路由和全局布局。它不构造业务服务,也不决定 Workflow 如何推进。本 PR 保留 `src/app/README.md` 固定边界,不提交 app 运行实现。 + +## pages + +页面是路由入口,负责组合粗粒度 Feature。Quick Start 与 Workflow Editor 是两个独立页面;Playtest 是只读核验入口。本 PR 保留 `src/pages/README.md` 固定边界,不提交页面运行实现。 + +## features + +- `character-setup`:角色资料、造型、动作定义与参考素材。 +- `generation`:创建和展示 Generation 业务记录。 +- `review`:查看生成结果并形成审核结论。 +- `export`:展示导出条件、配置与结果。 + +当前不继续拆分 Feature 内部目录。 +本 PR 保留 `src/features/README.md` 固定边界,不提交 Feature 实现或占位组件。 + +## workflow-controller + +维护同一份 WorkflowRun/WorkflowStep 数据,对外提供创建、推进、更新、重启、中断、恢复和接收服务端结果的方法。当前只声明整体接口,不提交状态转换和服务端调用实现。 + +## entities + +业务实体按规模就近维护,通常使用: + +- `types.ts`:数据结构。 +- `apis.ts`:该业务资源对应的服务端方法集合。 +- `index.ts`:唯一公开入口。 + +内容较少的实体可以直接在 `index.ts` 中声明类型和 APIs,避免为了形式增加空文件。 + +Generation 和 Task 是前端可见的业务数据。后端如何调用图像模型不属于前端模块。 + +## shared + +`shared` 是无业务含义的公共基础层,可以在出现真实需求后承载通用 UI、Hooks、纯 +工具和配置读取。它不能依赖任何上层模块,也不能保存 Entity、业务 APIs 或 +Workflow 规则。当前用 `shared/README.md` 固定边界,不提前创建空子目录和占位实现。 diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..7cb99c7 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,21 @@ +# Windup Frontend + +当前前端只提交粗架构、公开接口和必要业务类型,不包含具体业务实现、测试、页面壳或依赖安装入口。 + +## 目录 + +```text +src/ + workflow-controller/ 两套制作界面共享的整体流程接口 + entities/ Project、Character、WorkflowRun、Generation、Task 等业务数据 + shared/ 无业务含义的公共基础层边界说明 +``` + +Quick Start 与 Workflow Editor 使用同一种 WorkflowRun,但页面独立:Quick Start 隐藏步骤并由自动流程决策,Workflow Editor 逐步展示操作。当前没有服务端调用实现;后续按业务模块拆分小 PR 接入。 + +## 文件说明 + +- `API_CONTRACT.md`:业务 APIs 和 WorkflowController 的公开签名说明。 +- `MODULES.md`:app、pages、features、workflow-controller、entities、shared 的职责边界。 +- `src/shared/README.md`:公共基础层允许和禁止承载的内容。 +- `../frontend-architecture-v3.md`:本次粗架构 PR 的总体设计与未冻结范围。 diff --git a/frontend/src/app/README.md b/frontend/src/app/README.md new file mode 100644 index 0000000..d37bd46 --- /dev/null +++ b/frontend/src/app/README.md @@ -0,0 +1,20 @@ +# App 应用入口层 + +## 模块职责 + +`app` 只负责前端应用启动、路由挂载和全局布局。它回答的是“用户进来后先看到哪些入口、不同 URL 去哪个页面”,不回答“角色怎么生成、流程怎么推进、接口怎么调用”。 + +## 后续允许放入的内容 + +- 路由表,例如首页、Quick Start、Workflow Editor、项目详情和 Playtest 入口。 +- 全局应用外壳,例如顶部导航、页面容器和全局错误边界。 +- 只影响整个前端应用的 Provider,例如路由 Provider 或全局主题 Provider。 + +## 不允许放入的内容 + +- 不在这里写 Project、Character、WorkflowRun 等业务数据结构。 +- 不在这里实现 Workflow 的下一步、重启、中断等业务规则。 +- 不在这里直接拼服务端业务 APIs。 +- 不在这里为了方便把页面内部状态提升成全局状态。 + +判断标准是:如果一段代码离开“应用怎么启动和怎么路由”这个问题,它就不应该放在 `app`。 diff --git a/frontend/src/entities/action-template/index.ts b/frontend/src/entities/action-template/index.ts new file mode 100644 index 0000000..554f22e --- /dev/null +++ b/frontend/src/entities/action-template/index.ts @@ -0,0 +1,25 @@ +/** + * ActionTemplate 实体及其公开 APIs。 + * + * ActionTemplate 表示“动作模板”,用于描述可复用的动作名称与生成提示词;它与 + * 角色母版 CharacterTemplate 完全不同。当前内容较少,因此遵循粗粒度原则集中在 + * 一个入口文件中,不为 types/apis 额外创建空目录层级。 + */ +interface ActionTemplateBase { + /** 动作模板的稳定业务标识。 */ + id: string + /** 面向用户展示的动作名称,例如“行走”或“待机”。 */ + name: string + /** 后端生成动作时使用的模板提示词。 */ + prompt: string +} + +/** 系统内置动作模板没有 Project 归属。 */ +export type ActionTemplate = ActionTemplateBase & + ({ scope: 'system'; projectId: null } | { scope: 'project'; projectId: string }) + +/** 动作模板对应的服务端 API。 */ +export interface ActionTemplateAPIs { + /** 返回系统内置模板与指定项目自定义模板的可用集合。 */ + listAvailable(projectId: string): Promise +} diff --git a/frontend/src/entities/character/apis.ts b/frontend/src/entities/character/apis.ts new file mode 100644 index 0000000..22f5104 --- /dev/null +++ b/frontend/src/entities/character/apis.ts @@ -0,0 +1,31 @@ +/** + * Character 聚合资源对应的服务端接口集合。 + * + * Character、Outfit、Action 作为一棵聚合数据更新,避免前端为尚未冻结的细粒度 + * 路由提前创建多层抽象。这里只声明调用形状,不提供实现。 + */ +import type { + AddActionInput, + Character, + ConfirmCharacterTemplateInput, + CreateCharacterInput, +} from './types' + +/** Character 聚合数据对应的服务端 API;更新粒度以后端合同为准。 */ +export interface CharacterAPIs { + /** 读取包含 Outfit 与 Action 的完整 Character。 */ + get(characterId: Character['id']): Promise + /** 按 Project 归属列出角色。 */ + listByProject(projectId: Character['projectId']): Promise + /** 创建角色基础资料;母版和动作由后续流程补充。 */ + create(input: CreateCharacterInput): Promise + /** 按后端约定提交完整 Character 聚合更新。 */ + update(character: Character): Promise + /** 确认某个 Outfit 的角色母版候选。 */ + confirmCharacterTemplate( + characterId: Character['id'], + input: ConfirmCharacterTemplateInput, + ): Promise + /** 基于动作模板或自定义提示词,为指定 Outfit 增加动作。 */ + addAction(characterId: Character['id'], input: AddActionInput): Promise +} diff --git a/frontend/src/entities/character/index.ts b/frontend/src/entities/character/index.ts new file mode 100644 index 0000000..9c1be08 --- /dev/null +++ b/frontend/src/entities/character/index.ts @@ -0,0 +1,24 @@ +/** + * Character 模块的唯一公共出口。 + * + * 外部模块应从此文件导入 Character 相关类型与 APIs,而不是跨目录引用内部文件。 + * 这样后续调整文件布局时不会迫使所有调用方同步修改路径。 + */ +export type { CharacterAPIs } from './apis' +export type { + Action, + ActionSource, + ActionStatus, + ActionType, + AddActionInput, + ActionSequence, + BaseFrame, + Character, + CharacterTemplateCandidate, + ConfirmCharacterTemplateInput, + CreateCharacterInput, + Frame, + FrameQcResult, + FrameRootMotion, + Outfit, +} from './types' diff --git a/frontend/src/entities/character/types.ts b/frontend/src/entities/character/types.ts new file mode 100644 index 0000000..2670091 --- /dev/null +++ b/frontend/src/entities/character/types.ts @@ -0,0 +1,137 @@ +/** + * Character 聚合的数据合同。 + * + * 一个 Character 归属于 Project,并包含若干独立 Outfit;每个 Outfit 拥有自己的 + * 角色母版、基础帧与动作。这里仅表达前端确认过的业务字段,不包含生成算法或 + * 服务端 DTO 转换实现。 + */ +import type { SpriteDirection } from '../project' + +/** + * Action 的定义来源。 + * + * `action_template` 表示通过一个明确的 ActionTemplate 创建;该模板既可以是系统 + * 内置模板,也可以是 Project 模板,因此不能笼统理解为“系统预设”。`custom` 表示用户 + * 直接提供提示词。该字段描述来源,不与 ActionType 的动作类别混用。 + */ +export type ActionSource = 'action_template' | 'custom' +/** 当前支持的标准动作类别;custom 承载标准集合之外的动作。 */ +export type ActionType = 'walk' | 'idle' | 'attack' | 'jump' | 'custom' +/** 动作从计划、生成候选到人工确认的业务阶段。 */ +export type ActionStatus = 'planned' | 'generating' | 'candidate' | 'confirmed' | 'failed' +/** 单帧自动质量检查结论。 */ +export type FrameQcResult = 'pending' | 'passed' | 'failed' + +/** 单帧相对动作首帧的根位移,单位为像素。 */ +export interface FrameRootMotion { + dx: number + /** 正值表示向上。 */ + dy: number +} + +/** ActionSequence.frames 的数组位置就是帧序号。 */ +export interface Frame { + /** 当前帧图像的可访问地址;媒体 ID 合同冻结后可能调整。 */ + imageUrl: string + /** 独立显示时长,单位毫秒;null 时使用所属 Action.fps。 */ + durationMs: number | null + rootMotion: FrameRootMotion | null + qc: FrameQcResult + /** 人工退回标记,与自动质检分开记录。 */ + rejected: boolean +} + +/** 角色母版候选,不是动作模板。 */ +export interface CharacterTemplateCandidate { + /** 候选图自身标识,用于确认操作,不等于 Generation ID。 */ + id: string + /** 候选角色母版图地址。 */ + imageUrl: string + /** 产生该候选图的 Generation 业务记录。 */ + generationId: string +} + +/** 母版确认后展开出的基础参考帧。 */ +export interface BaseFrame { + /** 该基础参考帧明确对应的精灵朝向。 */ + direction: SpriteDirection + /** 当前方向的基础参考帧地址。 */ + imageUrl: string +} + +/** 一个 Action 在某个明确朝向下的完整帧序列。 */ +export interface ActionSequence { + direction: SpriteDirection + /** 关键帧在当前方向 frames 中的零基下标;没有时为 null。 */ + keyFrameIndex: number | null + /** 数组位置即帧序号,避免重复保存容易失真的 index 字段。 */ + frames: Frame[] +} + +/** 一个 Outfit 下可播放、审核和导出的动作。 */ +export interface Action { + id: string + outfitId: string + name: string + /** 动作定义来自 ActionTemplate,还是用户自定义提示词。 */ + source: ActionSource + type: ActionType + status: ActionStatus + /** 每秒帧数,仅作为缺少逐帧时长时的回退。 */ + fps: number + /** 按精灵朝向组织的动画序列;每个方向最多出现一次。 */ + sequences: ActionSequence[] +} + +/** 同一角色的一套独立造型。 */ +export interface Outfit { + id: string + characterId: string + name: string + /** 尚未确认前可供用户选择的角色母版候选。 */ + candidateCharacterTemplates: CharacterTemplateCandidate[] + /** 用户确认的角色母版;尚未确认时为 null。 */ + characterTemplateUrl: string | null + baseFrames: BaseFrame[] + /** 该造型独有的动作集合,不与其他 Outfit 混用。 */ + actions: Action[] +} + +/** 角色聚合根;Project 是其归属和查询边界。 */ +export interface Character { + id: string + projectId: string + name: string + outfits: Outfit[] + createdAt: string + updatedAt: string +} + +export interface CreateCharacterInput { + projectId: string + name: string + description: string + /** 可选参考图;正式媒体引用合同冻结前使用 URL。 */ + referenceImageUrl?: string | null +} + +/** 给既有 Character 的指定 Outfit 增加动作所需输入。 */ +export interface AddActionInput { + outfitId: Outfit['id'] + name: string + type: ActionType + /** + * 使用 source 作为判别字段:模板来源必须提供 actionTemplateId,自定义来源必须 + * 提供 prompt,避免同时出现互相矛盾的可选字段。 + */ + definition: + | { source: 'action_template'; actionTemplateId: string } + | { source: 'custom'; prompt: string } +} + +/** 从候选集中确认角色母版所需输入。 */ +export interface ConfirmCharacterTemplateInput { + outfitId: Outfit['id'] + /** 被确认的角色母版候选 ID,不得与动作模板或其他候选类型混淆。 */ + characterTemplateCandidateId: CharacterTemplateCandidate['id'] +} diff --git a/frontend/src/entities/generation/apis.ts b/frontend/src/entities/generation/apis.ts new file mode 100644 index 0000000..bb31cb9 --- /dev/null +++ b/frontend/src/entities/generation/apis.ts @@ -0,0 +1,15 @@ +/** + * Generation 业务记录对应的服务端接口集合。 + * + * 前端只创建记录并读取最新快照;模型调用、队列处理和事件传输属于服务端或尚未 + * 冻结的合同,不在本文件提前实现。 + */ +import type { CreateGenerationInput, Generation } from './types' + +/** Generation 业务记录对应的服务端 API。 */ +export interface GenerationAPIs { + /** 按不同生成阶段创建一条业务记录。 */ + create(input: CreateGenerationInput): Promise + /** 读取 Generation 的当前状态与结果。 */ + get(generationId: Generation['id']): Promise +} diff --git a/frontend/src/entities/generation/index.ts b/frontend/src/entities/generation/index.ts new file mode 100644 index 0000000..8a9790d --- /dev/null +++ b/frontend/src/entities/generation/index.ts @@ -0,0 +1,8 @@ +/** + * Generation 模块的唯一公共出口。 + * + * 这里只转发业务记录、状态、创建输入及 APIs 类型,不暴露模型供应商、图片生成 + * SDK 或后端内部任务实现。 + */ +export type { GenerationAPIs } from './apis' +export type { CreateGenerationInput, Generation, GenerationStatus, GenerationType } from './types' diff --git a/frontend/src/entities/generation/types.ts b/frontend/src/entities/generation/types.ts new file mode 100644 index 0000000..ac91de9 --- /dev/null +++ b/frontend/src/entities/generation/types.ts @@ -0,0 +1,55 @@ +/** + * Generation 业务记录的数据合同。 + * + * 三种 Generation 对应用户可见的角色母版、动作首帧和完整动画阶段。联合类型使用 + * type 作为判别字段,使调用方在编译期获得对应输入,而无需把所有字段都设为可选。 + */ +import type { ActionType } from '../character' + +/** 当前已确认的三类生成业务。 */ +export type GenerationType = 'character_template' | 'first_frame' | 'complete_animation' +/** Generation 记录的生命周期;具体 Task 状态由 Task 实体单独表达。 */ +export type GenerationStatus = 'pending' | 'running' | 'completed' | 'failed' + +/** 前端创建和查询的生成业务记录;模型调用细节只存在于后端。 */ +export interface Generation { + /** Generation 业务记录标识。 */ + id: string + /** 归属 Project,确保 Quick Start 产物也能进入项目资产。 */ + projectId: string + type: GenerationType + status: GenerationStatus + taskId: string | null + /** 正式结果合同冻结前保持 unknown,禁止调用方在未缩窄类型时直接使用。 */ + result: unknown + error: string | null + createdAt: string + updatedAt: string +} + +/** 根据生成类型严格区分必填字段的创建输入。 */ +export type CreateGenerationInput = + | { + type: 'character_template' + projectId: string + prompt: string + /** 当前页面提交的参考图地址;后续随正式媒体合同调整,不在本 PR 定义上传接口。 */ + referenceImageUrls: readonly string[] + } + | { + type: 'first_frame' + projectId: string + characterId: string + outfitId: string + actionType: ActionType + prompt: string | null + } + | { + type: 'complete_animation' + projectId: string + characterId: string + outfitId: string + actionType: ActionType + firstFrameUrl: string + prompt: string | null + } diff --git a/frontend/src/entities/index.ts b/frontend/src/entities/index.ts new file mode 100644 index 0000000..7bc4d4c --- /dev/null +++ b/frontend/src/entities/index.ts @@ -0,0 +1,62 @@ +/** + * 全部 Entity 类型的统一公共门面。 + * + * Page、Feature 和 WorkflowController 可以从本入口导入跨实体类型;各实体内部 + * 仍优先从自己的入口导入,避免形成隐蔽循环依赖。本文件只转发类型,不创建全局 + * 状态,也不装配 APIs 实现。 + */ +export type { ActionTemplate, ActionTemplateAPIs } from './action-template' +export type { + Action, + ActionSource, + ActionStatus, + ActionType, + AddActionInput, + ActionSequence, + BaseFrame, + Character, + CharacterAPIs, + CharacterTemplateCandidate, + ConfirmCharacterTemplateInput, + CreateCharacterInput, + Frame, + FrameQcResult, + FrameRootMotion, + Outfit, +} from './character' +export type { + CreateGenerationInput, + Generation, + GenerationAPIs, + GenerationStatus, + GenerationType, +} from './generation' +export type { + PlaytestInspection, + PlaytestInspectionAPIs, + PlaytestInspectionStatus, + RecordPlaytestInspectionInput, +} from './playtest-inspection' +export type { + CharacterPerspective, + CreateProjectInput, + DirectionMode, + Project, + ProjectAPIs, + SpriteDirection, + UpdateProjectInput, +} from './project' +export type { Task, TaskAPIs, TaskStatus } from './task' +export type { + CreateWorkflowRunInput, + WorkflowDriver, + WorkflowRevision, + WorkflowRevisionStatus, + WorkflowRun, + WorkflowRunAPIs, + WorkflowRunPurpose, + WorkflowRunStatus, + WorkflowStep, + WorkflowStepStatus, + WorkflowStepType, +} from './workflow-run' diff --git a/frontend/src/entities/playtest-inspection/index.ts b/frontend/src/entities/playtest-inspection/index.ts new file mode 100644 index 0000000..b6c5244 --- /dev/null +++ b/frontend/src/entities/playtest-inspection/index.ts @@ -0,0 +1,39 @@ +/** + * Playtest 只读核验记录及其公开 APIs。 + * + * 核验记录独立于 Character 和 WorkflowRun:它可以指出某个 Outfit 存在问题,但 + * 不能直接改写制作数据。当前类型和接口规模较小,因此集中在一个入口文件中。 + */ +/** 核验通过,或发现需要返回制作流程处理的问题。 */ +export type PlaytestInspectionStatus = 'passed' | 'issues_found' + +/** Playtest 只读核验产生的独立记录,不写入 Character 或 WorkflowRun。 */ +export interface PlaytestInspection { + /** 独立核验记录标识。 */ + id: string + /** 被核验的 Character。 */ + characterId: string + /** 被核验的具体 Outfit;不同造型的动作不能混为一次核验。 */ + outfitId: string + /** 可选来源版本;独立入口打开当前产物时允许为空。 */ + source: { runId: string; revisionId: string } | null + status: PlaytestInspectionStatus + createdAt: string + updatedAt: string +} + +/** 保存一次 Playtest 核验结论所需输入。 */ +export interface RecordPlaytestInspectionInput { + characterId: string + outfitId: string + source?: { runId: string; revisionId: string } | null + status: PlaytestInspectionStatus +} + +/** Playtest 核验记录对应的服务端 API。 */ +export interface PlaytestInspectionAPIs { + /** 读取指定 Character/Outfit 最近一次核验,没有记录时返回 null。 */ + getLatest(target: { characterId: string; outfitId: string }): Promise + /** 新增独立核验记录,不覆盖 Character 或 WorkflowRun。 */ + record(input: RecordPlaytestInspectionInput): Promise +} diff --git a/frontend/src/entities/project/apis.ts b/frontend/src/entities/project/apis.ts new file mode 100644 index 0000000..bcde863 --- /dev/null +++ b/frontend/src/entities/project/apis.ts @@ -0,0 +1,21 @@ +/** + * Project 资源对应的服务端接口集合。 + * + * 方法采用直接的 list/get/create/update/remove 命名,表达具体业务操作,不引入通用存储 + * 抽象。当前只声明异步签名,URL、DTO 外壳、认证和错误处理等待后续实现 PR。 + */ +import type { CreateProjectInput, Project, UpdateProjectInput } from './types' + +/** Project 对应的一组服务端 API;本 PR 只声明调用形状。 */ +export interface ProjectAPIs { + /** 列出当前用户可访问的 Project。 */ + list(): Promise + /** 按稳定 ID 读取单个 Project。 */ + get(projectId: Project['id']): Promise + /** 创建 Quick Start 或手动流程所依赖的真实 Project。 */ + create(input: CreateProjectInput): Promise + /** 更新 Project 的名称、视角、方向、尺寸或画风设置。 */ + update(projectId: Project['id'], input: UpdateProjectInput): Promise + /** 删除指定 Project;关联资源处理规则由后端合同决定。 */ + remove(projectId: Project['id']): Promise +} diff --git a/frontend/src/entities/project/index.ts b/frontend/src/entities/project/index.ts new file mode 100644 index 0000000..e7631a2 --- /dev/null +++ b/frontend/src/entities/project/index.ts @@ -0,0 +1,15 @@ +/** + * Project 模块的唯一公共出口。 + * + * 统一从这里导出数据类型与 ProjectAPIs,避免外部依赖模块内部的 types/apis 文件 + * 布局。本文件只做类型转发,不包含运行时代码。 + */ +export type { ProjectAPIs } from './apis' +export type { + CharacterPerspective, + CreateProjectInput, + DirectionMode, + Project, + SpriteDirection, + UpdateProjectInput, +} from './types' diff --git a/frontend/src/entities/project/types.ts b/frontend/src/entities/project/types.ts new file mode 100644 index 0000000..3c091bd --- /dev/null +++ b/frontend/src/entities/project/types.ts @@ -0,0 +1,82 @@ +/** + * Project 实体及创建输入。 + * + * Project 是 Character、WorkflowRun 和生成记录的顶层归属边界。字段只保留当前 + * 产品已确认的游戏视角、方向、精灵尺寸和画风信息;后端枚举映射尚未冻结的部分 + * 会明确标注,避免把临时前端值伪装成正式 API 合同。 + */ +/** + * Project 采用的角色观察视角。 + * + * 持久化枚举统一使用 snake_case:`side_view` 表示横版侧视,`top_down` 表示俯视, + * `isometric` 表示等距视角。与后端枚举的最终映射尚未冻结。 + */ +export type CharacterPerspective = 'side_view' | 'top_down' | 'isometric' + +/** + * Project 要求每个动作覆盖的方向模式。 + * + * 该类型只回答“需要几个方向”,不表示某张图的实际朝向;具体朝向由 + * `SpriteDirection` 表达。四方向和八方向值使用 snake_case,与其他持久化枚举 + * 保持一致。 + */ +export type DirectionMode = 'single' | 'four_way' | 'eight_way' + +/** + * 精灵图在画面中的明确朝向。 + * + * `default` 用于不区分朝向的单方向项目;四方向使用四个正方向,八方向在此基础上 + * 增加四个斜方向。该命名不绑定键盘按键或模型供应商,适合被 BaseFrame、Action + * 和 Playtest 共同复用。 + */ +export type SpriteDirection = + | 'default' + | 'north' + | 'north_east' + | 'east' + | 'south_east' + | 'south' + | 'south_west' + | 'west' + | 'north_west' + +/** Project 是角色、动作及生成记录的归属边界。 */ +export interface Project { + /** Project 的稳定业务标识。 */ + id: string + /** 用户可编辑的项目名称。 */ + name: string + perspective: CharacterPerspective + /** 单方向、四方向或八方向的资产覆盖要求。 */ + directionMode: DirectionMode + /** 目标精灵宽高,单位为像素。 */ + spriteSize: { width: number; height: number } + /** 项目级画风说明;未设置时为 null。 */ + gameStyle: string | null + /** 项目级画风参考图;媒体合同冻结前保持 URL 形状。 */ + sampleImageUrl: string | null + /** ISO 8601 时间。 */ + createdAt: string + /** ISO 8601 时间。 */ + updatedAt: string +} + +/** 创建 Project 所需的前端字段。 */ +export interface CreateProjectInput { + name: string + perspective: CharacterPerspective + directionMode: DirectionMode + spriteSize: { width: number; height: number } + gameStyle?: string | null + sampleImageUrl?: string | null +} + +/** 修改既有 Project 的可编辑字段;未提供的字段保持不变。 */ +export interface UpdateProjectInput { + name?: string + perspective?: CharacterPerspective + directionMode?: DirectionMode + spriteSize?: { width: number; height: number } + gameStyle?: string | null + sampleImageUrl?: string | null +} diff --git a/frontend/src/entities/task/apis.ts b/frontend/src/entities/task/apis.ts new file mode 100644 index 0000000..dbf4ae1 --- /dev/null +++ b/frontend/src/entities/task/apis.ts @@ -0,0 +1,13 @@ +/** + * 服务端异步 Task 的最小查询接口。 + * + * 当前后端事件、取消与重试合同尚未冻结,因此只保留按 ID 获取快照的方法,不提前 + * 创建 EventSource、轮询器或两套实现。 + */ +import type { Task } from './types' + +/** 异步任务查询 API;取消和事件传输形式等待后端合同。 */ +export interface TaskAPIs { + /** 读取某个异步任务的最新服务端快照。 */ + get(taskId: Task['id']): Promise +} diff --git a/frontend/src/entities/task/index.ts b/frontend/src/entities/task/index.ts new file mode 100644 index 0000000..dd95f98 --- /dev/null +++ b/frontend/src/entities/task/index.ts @@ -0,0 +1,8 @@ +/** + * Task 模块的唯一公共出口。 + * + * Task 是服务端异步任务快照,不是 WorkflowStep,也不在这里表达浏览器事件流的 + * 具体协议。 + */ +export type { TaskAPIs } from './apis' +export type { Task, TaskStatus } from './types' diff --git a/frontend/src/entities/task/types.ts b/frontend/src/entities/task/types.ts new file mode 100644 index 0000000..eb228f9 --- /dev/null +++ b/frontend/src/entities/task/types.ts @@ -0,0 +1,22 @@ +/** + * 服务端异步 Task 的前端快照类型。 + * + * Task 与一次 Generation 关联,回答“服务端工作执行到哪里”;WorkflowStep 则回答 + * “产品流程走到哪里”。两者生命周期不同,不能合并成同一个概念。 + */ +import type { Generation } from '../generation' + +/** 当前已确认的最小任务生命周期。 */ +export type TaskStatus = 'pending' | 'running' | 'completed' | 'failed' + +/** 服务端异步任务快照,不等同于 WorkflowStep。 */ +export interface Task { + /** 服务端任务标识。 */ + id: string + /** 该任务服务于哪一条 Generation 业务记录。 */ + generationId: Generation['id'] + status: TaskStatus + result: unknown + /** 失败信息;非失败状态下为 null。 */ + error: string | null +} diff --git a/frontend/src/entities/workflow-run/apis.ts b/frontend/src/entities/workflow-run/apis.ts new file mode 100644 index 0000000..f624051 --- /dev/null +++ b/frontend/src/entities/workflow-run/apis.ts @@ -0,0 +1,17 @@ +/** + * WorkflowRun 业务资源的服务端持久化接口。 + * + * 后端负责创建、读取和保存完整业务资源;“下一步是什么”和“重启后清理哪些步骤” + * 仍由前端 WorkflowController 决定。这里不提供 Controller 的替代实现。 + */ +import type { CreateWorkflowRunInput, WorkflowRun } from './types' + +/** WorkflowRun 持久化对应的服务端 API,不包含步骤推进规则。 */ +export interface WorkflowRunAPIs { + /** 创建并由后端分配稳定 ID 的 WorkflowRun。 */ + create(input: CreateWorkflowRunInput): Promise + /** 按运行 ID 读取包含 Revision 和 Step 的完整资源。 */ + get(runId: WorkflowRun['id']): Promise + /** 提交前端 Controller 已推进后的完整 WorkflowRun 状态。 */ + update(run: WorkflowRun): Promise +} diff --git a/frontend/src/entities/workflow-run/index.ts b/frontend/src/entities/workflow-run/index.ts new file mode 100644 index 0000000..f5b25c0 --- /dev/null +++ b/frontend/src/entities/workflow-run/index.ts @@ -0,0 +1,19 @@ +/** + * WorkflowRun 实体模块的唯一公共出口。 + * + * 本入口导出服务端持久化 APIs 与流程数据类型。节点推进规则不属于 Entity APIs, + * 而由独立但粗粒度的 WorkflowController 维护。 + */ +export type { WorkflowRunAPIs } from './apis' +export type { + CreateWorkflowRunInput, + WorkflowDriver, + WorkflowRevision, + WorkflowRevisionStatus, + WorkflowRun, + WorkflowRunPurpose, + WorkflowRunStatus, + WorkflowStep, + WorkflowStepStatus, + WorkflowStepType, +} from './types' diff --git a/frontend/src/entities/workflow-run/types.ts b/frontend/src/entities/workflow-run/types.ts new file mode 100644 index 0000000..19ce91e --- /dev/null +++ b/frontend/src/entities/workflow-run/types.ts @@ -0,0 +1,98 @@ +/** + * WorkflowRun、Revision 与 Step 的共享数据合同。 + * + * Quick Start 和 Workflow Editor 使用同一种流程数据,只在交互展示和驱动方式上 + * 不同。前端负责节点推进规则,后端负责持久化;本文件只描述数据,不实现状态机。 + */ +/** Quick Start 与手动编辑器共享数据模型,只改变交互方式。 */ +export type WorkflowDriver = 'ai' | 'manual' +/** WorkflowRun 本次要完成的顶层业务目标。 */ +export type WorkflowRunPurpose = 'create_character' | 'add_action' + +/** 当前确认的页面步骤;数组位置表达实际执行顺序。 */ +export type WorkflowStepType = + | 'character_setup' + | 'character_template' + | 'character_template_selection' + | 'action_setup' + | 'first_frame' + | 'complete_animation' + | 'review' + | 'export' + +export type WorkflowStepStatus = 'locked' | 'active' | 'completed' | 'failed' +/** Revision 的生命周期;重启产生新 Revision 后旧分支可以标记 abandoned。 */ +export type WorkflowRevisionStatus = 'active' | 'completed' | 'failed' | 'abandoned' +/** 整条 WorkflowRun 的可见状态;interrupted 表示自动流程已被用户中断。 */ +export type WorkflowRunStatus = 'active' | 'interrupted' | 'completed' | 'failed' + +/** Workflow Controller 操作的最小步骤数据。 */ +export interface WorkflowStep { + /** Step 的稳定标识,用于更新和从指定步骤重启。 */ + id: string + /** Step 的业务种类,不直接等同于后端 Task 类型。 */ + type: WorkflowStepType + /** 当前 Revision 中该 Step 的执行状态。 */ + status: WorkflowStepStatus + /** 进入步骤时保存的数据;具体业务类型由该步骤的调用方解释。 */ + data: unknown +} + +/** 从历史步骤重新开始时追加 Revision,旧 Revision 保持只读。 */ +export interface WorkflowRevision { + /** Revision 自身标识。 */ + id: string + /** 首个版本为 null;重启生成的新版本指向来源版本。 */ + basedOnRevisionId: WorkflowRevision['id'] | null + /** 从哪个历史 Step 重启;非重启创建的版本为 null。 */ + restartStepId: WorkflowStep['id'] | null + /** 指向唯一 active Step;Revision 已完成或失败且不可继续时为 null。 */ + currentStepId: WorkflowStep['id'] | null + status: WorkflowRevisionStatus + steps: WorkflowStep[] + /** ISO 8601 创建时间。 */ + createdAt: string +} + +/** 前端维护步骤状态,服务端负责持久化该业务资源。 */ +export interface WorkflowRun { + /** WorkflowRun 稳定业务标识。 */ + id: string + /** 所有 WorkflowRun 都归属真实 Project,包括 Quick Start 自动创建的项目。 */ + projectId: string + /** 创建角色早期允许为空;角色建立后写入。 */ + characterId: string | null + /** 创建具体造型前允许为空;加动作流程必须提供。 */ + outfitId: string | null + purpose: WorkflowRunPurpose + driver: WorkflowDriver + status: WorkflowRunStatus + /** revisions 中当前可继续推进的版本标识。 */ + currentRevisionId: WorkflowRevision['id'] + /** 保留历史只读版本,支持回看和从历史步骤重新开始。 */ + revisions: WorkflowRevision[] + /** Quick Start 的自然语言目标或手动流程补充说明。 */ + prompt: string | null +} + +/** 创建不同 WorkflowRun 共同需要的字段。 */ +interface CreateWorkflowRunInputBase { + projectId: string + driver: WorkflowDriver + prompt?: string +} + +/** 创建角色与给既有 Outfit 加动作两种入口的判别联合。 */ +export type CreateWorkflowRunInput = CreateWorkflowRunInputBase & + ( + | { + purpose: 'create_character' + } + | { + purpose: 'add_action' + characterId: string + outfitId: string + characterTemplateUrl: string + baseFrameUrls: readonly string[] + } + ) diff --git a/frontend/src/features/README.md b/frontend/src/features/README.md new file mode 100644 index 0000000..67964a8 --- /dev/null +++ b/frontend/src/features/README.md @@ -0,0 +1,23 @@ +# Features 业务能力层 + +## 模块职责 + +`features` 保存页面会组合使用的粗粒度业务能力。它比页面更聚焦,比 Entity 更接近用户操作。Feature 可以调用 Entity APIs,也可以把用户输入整理成 Workflow Controller 能理解的数据,但不拥有整条 WorkflowRun 的推进规则。 + +## 已确认的 Feature 边界 + +- `character-setup`:角色资料、造型、动作定义和参考素材输入。 +- `generation`:创建并展示 Generation 业务记录。 +- `review`:查看生成结果,形成审核结论。 +- `export`:展示导出条件、导出配置和导出结果。 + +当前只固定边界,不提交组件实现。后续出现真实页面时,再按以上业务能力创建子目录和具体代码。 + +## 不允许放入的内容 + +- 不把 `nextStep`、`restartFromStep`、`interrupt` 等流程推进规则拆进各个 Feature。 +- 不在 Feature 内私自维护另一份 WorkflowRun 状态。 +- 不在 Feature 里发明后端还没有确认的接口。 +- 不为了显得“模块化”提前拆出空组件、空 Hook 或空 service。 + +判断标准是:Feature 负责一个用户可感知的业务能力,但整条流程的版本、步骤顺序和重启规则仍由 Workflow Controller 统一维护。 diff --git a/frontend/src/pages/README.md b/frontend/src/pages/README.md new file mode 100644 index 0000000..cce6f74 --- /dev/null +++ b/frontend/src/pages/README.md @@ -0,0 +1,24 @@ +# Pages 页面入口层 + +## 模块职责 + +`pages` 是路由进入业务界面的第一层。页面负责读取路由参数,组合粗粒度 Feature,并把用户带到对应的业务场景。页面不应该沉下去实现实体规则,也不应该把一个大流程拆成互相不知道彼此状态的小服务。 + +## 已确认的页面入口 + +- `HomePage`:首页和主要入口。 +- `QuickStartPage`:自然语言快速创建流程;无 `runId` 时创建流程,有 `runId` 时继续查看或推进同一条流程。 +- `ProjectsPage`:项目列表。 +- `ProjectDetailPage`:项目详情;读取 `projectId` 后组合项目、角色和工作流记录。 +- `AssetLibraryPage`:项目资产库;读取 `projectId` 后展示该项目下可复用资产。 +- `WorkflowEditorPage`:手动工作流编辑入口;读取 `runId` 和可选 `stage`,通过 Workflow Controller 推进同一份 WorkflowRun。 +- `PlaytestPage`:只读核验入口;读取 `characterId` 和 `outfitId`,展示 PlaytestInspection 结论。 +- `NotFoundPage`:兜底页面。 + +## 不允许放入的内容 + +- 不在页面里重新定义 Project、Character、WorkflowRun 等实体类型。 +- 不在页面里绕过 Workflow Controller 直接改 WorkflowStep。 +- 不在页面里实现可复用业务能力;可以组合 Feature,但不要把 Feature 的内部逻辑写散在页面里。 + +判断标准是:页面负责“把场景摆出来”,具体业务能力交给 Feature、Workflow Controller 和 Entity APIs。 diff --git a/frontend/src/shared/README.md b/frontend/src/shared/README.md new file mode 100644 index 0000000..6756c33 --- /dev/null +++ b/frontend/src/shared/README.md @@ -0,0 +1,26 @@ +# Shared 公共基础层 + +## 文件职责 + +`shared` 保存与 Windup 具体业务无关、可以被任意上层模块复用的前端基础代码。它是 +依赖方向的最底层,不能反向依赖 `entities`、`workflow-controller`、`features`、 +`pages` 或 `app`。 + +## 后续允许放入的内容 + +- `ui/`:按钮、弹窗、加载状态等不包含业务含义的展示组件。 +- `hooks/`:通用浏览器或 React 行为,例如媒体查询、键盘快捷键。 +- `utils/`:纯函数工具,例如日期格式化、文件大小显示、类型守卫。 +- `config/`:前端通用常量与经过校验的运行时配置读取。 + +这些目录只在出现真实代码时创建,本次骨架 PR 不为了占位增加空文件。 + +## 不允许放入的内容 + +- Project、Character、Generation、Task、WorkflowRun 等业务数据。 +- `ProjectAPIs`、`CharacterAPIs` 等业务接口集合或其实现。 +- Workflow 的步骤推进、重启、中断和 Revision 规则。 +- 为开发与生产环境各维护一套实现的切换机制。 +- 只被单个 Page 或 Feature 使用、却为了“复用”名义提前抽出的代码。 + +判断标准是:如果代码需要理解 Windup 的业务词汇,它就不属于 `shared`。 diff --git a/frontend/src/workflow-controller/index.ts b/frontend/src/workflow-controller/index.ts new file mode 100644 index 0000000..dbe6198 --- /dev/null +++ b/frontend/src/workflow-controller/index.ts @@ -0,0 +1,56 @@ +/** + * 两种制作界面共享的整体 WorkflowController 契约。 + * + * Controller 围绕同一份 WorkflowRun 数据提供推进、更新、重启和中断方法,不能把 + * 这些操作再拆成互不共享状态的独立模块。当前 PR 只确定公开接口;状态转换、业务 + * APIs 调用与持久化顺序在后续实现 PR 中完成。 + */ +import type { + CreateWorkflowRunInput, + WorkflowRevision, + WorkflowRun, + WorkflowStep, +} from '../entities/workflow-run' + +/** 更新当前 Revision 中某个 Step 的业务数据。 */ +export interface UpdateWorkflowStepInput { + stepId: WorkflowStep['id'] + data: unknown +} + +/** 从指定 Revision 的指定 Step 建立新的执行版本。 */ +export interface RestartWorkflowFromStepInput { + revisionId: WorkflowRevision['id'] + stepId: WorkflowStep['id'] +} + +/** 把某个服务端业务调用的结果应用回目标 Step。 */ +export interface ApplyWorkflowServerResultInput { + /** 发起请求时所属的 Revision,防止旧异步结果污染重启后的新版本。 */ + revisionId: WorkflowRevision['id'] + stepId: WorkflowStep['id'] + result: unknown +} + +/** + * Quick Start 与 Workflow Editor 共用的整体流程接口。 + * 具体状态转换、服务端调用和持久化将在后续小 PR 中实现。 + */ +export interface WorkflowController { + /** 初始化一条创建角色或增加动作的 WorkflowRun。 */ + create(input: CreateWorkflowRunInput): Promise + /** 读取 Controller 当前维护的完整 WorkflowRun 快照。 */ + getWorkflow(): WorkflowRun + /** 按前端规则完成当前 Step 并进入下一 Step。 */ + nextStep(): Promise + /** 更新指定 Step 数据,但不绕过 Controller 直接修改全局状态。 */ + updateStep(input: UpdateWorkflowStepInput): Promise + /** 从历史 Step 创建新 Revision,保留旧 Revision 只读。 */ + restartFromStep(input: RestartWorkflowFromStepInput): Promise + /** 中断自动流程;后续是否取消服务端 Task 由正式接口合同决定。 */ + interrupt(): Promise + /** 恢复被用户中断的自动流程,从当前 Revision 的 currentStepId 继续。 */ + resume(): Promise + /** 将 Project、Character、Generation 等服务端调用结果映射回目标 Step。 */ + applyServerResult(input: ApplyWorkflowServerResultInput): Promise +}