Merge remote-tracking branch 'origin/master' into codex/bark-battle
This commit is contained in:
23
.codex/config.toml
Normal file
23
.codex/config.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
# Genarrative 项目级 Codex 配置。
|
||||
# 这里仅保存可进入仓库的 hook 配置与脚本;个人 token、MCP server、模型路由仍放在个人 ~/.codex/config.toml。
|
||||
|
||||
[features]
|
||||
hooks = true
|
||||
|
||||
# Codex 准备执行 git commit 前检查 TypeScript / admin-web / api-server 编译错误。
|
||||
# 脚本也可手动运行:
|
||||
# node .codex/hooks/pre-submit-compile-check.mjs
|
||||
[[hooks.PreToolUse]]
|
||||
matcher = "Bash|shell_command|functions.shell_command"
|
||||
command = "node .codex/hooks/pre-submit-compile-check.mjs"
|
||||
timeout = 180
|
||||
statusMessage = "提交前检查编译错误"
|
||||
|
||||
# Codex 每次工具修改文件后执行:同步 CodeGraph 索引。
|
||||
# 脚本也可手动运行:
|
||||
# node .codex/hooks/post-edit-codegraph-sync.mjs
|
||||
[[hooks.PostToolUse]]
|
||||
matcher = "Bash|Edit|MultiEdit|Write|apply_patch|shell_command|functions.shell_command|functions.apply_patch"
|
||||
command = "node .codex/hooks/post-edit-codegraph-sync.mjs"
|
||||
timeout = 60
|
||||
statusMessage = "更新 CodeGraph 索引"
|
||||
51
.codex/hooks/post-edit-codegraph-sync.mjs
Normal file
51
.codex/hooks/post-edit-codegraph-sync.mjs
Normal file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { existsSync, mkdirSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = resolve(scriptDir, '..', '..');
|
||||
const logDir = resolve(repoRoot, '.codex', 'logs');
|
||||
const hasCodegraphConfig = existsSync(resolve(repoRoot, '.codegraph', 'config.json'));
|
||||
const npmCommand = process.platform === 'win32' ? 'cmd' : 'npm';
|
||||
|
||||
if (!hasCodegraphConfig) {
|
||||
console.log('[codex-hook] 未发现 .codegraph/config.json,跳过 CodeGraph 同步。');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const result = spawnSync(npmCommand, process.platform === 'win32' ? ['/d', '/s', '/c', 'npm run codegraph:sync'] : ['run', 'codegraph:sync'], {
|
||||
cwd: repoRoot,
|
||||
shell: false,
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
NO_COLOR: process.env.NO_COLOR ?? '1',
|
||||
},
|
||||
});
|
||||
|
||||
mkdirSync(logDir, { recursive: true });
|
||||
if (result.stdout) {
|
||||
process.stdout.write(result.stdout);
|
||||
}
|
||||
if (result.stderr) {
|
||||
process.stderr.write(result.stderr);
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
console.error(`[codex-hook] CodeGraph 同步启动失败:${result.error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (result.signal) {
|
||||
console.error(`[codex-hook] CodeGraph 同步被信号终止:${result.signal}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if ((result.status ?? 0) !== 0) {
|
||||
console.error('[codex-hook] CodeGraph 同步失败,请手动运行 npm run codegraph:sync 查看详情。');
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
|
||||
console.log('[codex-hook] CodeGraph 已同步。');
|
||||
122
.codex/hooks/pre-submit-compile-check.mjs
Normal file
122
.codex/hooks/pre-submit-compile-check.mjs
Normal file
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = resolve(scriptDir, '..', '..');
|
||||
const npmCommand = process.platform === 'win32' ? 'cmd' : 'npm';
|
||||
const hookInput = readHookInput();
|
||||
|
||||
if (hookInput && !isGitCommitCommand(extractShellCommand(hookInput))) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const validationSteps = [
|
||||
{
|
||||
label: 'TypeScript typecheck',
|
||||
command: npmCommand,
|
||||
args: process.platform === 'win32' ? ['/d', '/s', '/c', 'npm run typecheck'] : ['run', 'typecheck'],
|
||||
},
|
||||
{
|
||||
label: 'Admin web typecheck',
|
||||
command: npmCommand,
|
||||
args: process.platform === 'win32' ? ['/d', '/s', '/c', 'npm run admin-web:typecheck'] : ['run', 'admin-web:typecheck'],
|
||||
},
|
||||
{
|
||||
label: 'Rust api-server compile check',
|
||||
command: 'cargo',
|
||||
args: ['check', '-p', 'api-server', '--manifest-path', 'server-rs/Cargo.toml'],
|
||||
},
|
||||
];
|
||||
|
||||
for (const step of validationSteps) {
|
||||
const result = runStep(step);
|
||||
if (!result.ok) {
|
||||
const reason = `[codex-hook] 提交前编译检查失败:${step.label}。请修复编译错误后再提交。`;
|
||||
console.error(reason);
|
||||
if (hookInput) {
|
||||
console.log(JSON.stringify({ decision: 'block', reason }));
|
||||
process.exit(0);
|
||||
}
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
}
|
||||
|
||||
console.error('[codex-hook] 提交前编译检查通过。');
|
||||
|
||||
function runStep(step) {
|
||||
console.error(`[codex-hook] ${step.label}`);
|
||||
const result = spawnSync(step.command, step.args, {
|
||||
cwd: repoRoot,
|
||||
shell: false,
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
NO_COLOR: process.env.NO_COLOR ?? '1',
|
||||
},
|
||||
});
|
||||
|
||||
if (result.stdout) {
|
||||
process.stderr.write(result.stdout);
|
||||
}
|
||||
if (result.stderr) {
|
||||
process.stderr.write(result.stderr);
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
console.error(`[codex-hook] ${step.label} 启动失败:${result.error.message}`);
|
||||
return { ok: false, status: 1 };
|
||||
}
|
||||
|
||||
if (result.signal) {
|
||||
console.error(`[codex-hook] ${step.label} 被信号终止:${result.signal}`);
|
||||
return { ok: false, status: 1 };
|
||||
}
|
||||
|
||||
return {
|
||||
ok: (result.status ?? 0) === 0,
|
||||
status: result.status ?? 1,
|
||||
};
|
||||
}
|
||||
|
||||
function readHookInput() {
|
||||
try {
|
||||
const rawInput = readFileSync(0, 'utf8').trim();
|
||||
if (!rawInput) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(rawInput);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function extractShellCommand(input) {
|
||||
const candidates = [
|
||||
input?.tool_input?.command,
|
||||
input?.toolInput?.command,
|
||||
input?.tool_args?.command,
|
||||
input?.toolArgs?.command,
|
||||
input?.arguments?.command,
|
||||
input?.params?.command,
|
||||
input?.command,
|
||||
];
|
||||
|
||||
const command = candidates.find(value => typeof value === 'string' && value.trim().length > 0);
|
||||
if (command) {
|
||||
return command;
|
||||
}
|
||||
|
||||
const shellCommand = input?.tool_input?.cmd ?? input?.toolInput?.cmd ?? input?.arguments?.cmd;
|
||||
if (Array.isArray(shellCommand)) {
|
||||
return shellCommand.join(' ');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function isGitCommitCommand(command) {
|
||||
return /(^|[;&|]\s*)git(?:\.exe)?\b[\s\S]{0,200}\bcommit\b/iu.test(command);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
Set-Location 'C:\Genarrative'
|
||||
$env:RUST_SERVER_TARGET = 'http://127.0.0.1:8082'
|
||||
$env:GENARRATIVE_RUNTIME_SERVER_TARGET = 'http://127.0.0.1:8082'
|
||||
npm.cmd run dev:web *> 'C:\Genarrative\.codex\logs\dev-web-final.out.log'
|
||||
@@ -1 +0,0 @@
|
||||
../../.hermes/skills/behavior-driven-development/
|
||||
@@ -7,35 +7,46 @@ metadata:
|
||||
version: "1.0"
|
||||
---
|
||||
|
||||
# Genarrative 新增玩法类型接入流程
|
||||
# Genarrative 新增玩法创作工具平台 SOP
|
||||
|
||||
用于在 Genarrative 中新增一个创作入口/玩法类型,而不是单纯说明用户如何从入口创建作品。
|
||||
把新增玩法当成平台能力接入,不把任何既有玩法当作默认模板。先确定通用模式和契约,再写具体玩法代码。
|
||||
|
||||
## 适用场景
|
||||
## 硬性禁区
|
||||
|
||||
- 新增一个游戏玩法入口
|
||||
- 让某个玩法从“敬请期待”变为可创建
|
||||
- 为新玩法补齐创作工作台、结果页、发布与试玩链路
|
||||
- 将新玩法接入创作中心作品架与广场
|
||||
- 不恢复前端硬编码入口配置;创作入口事实源必须来自 SpacetimeDB 和 `/api/creation-entry/config`。
|
||||
- 不把聊天输入区、流式消息或轻输入 Agent 作为新增玩法默认工作台。
|
||||
- 不在新页面内手写图片上传、参考图、AI 重绘、历史图选择、预览或删除确认逻辑。
|
||||
- 不把通用系列素材建模成任一玩法专属 DTO;玩法只能追加自己的运行态字段。
|
||||
- 不让前端承接正式业务真相;发布、试玩、通关、失败、计分、资产持久化和作品状态以后端投影为准。
|
||||
- 不新建平行入口系统、平行作品架或平行公开列表;优先扩展现有平台壳、现有阶段和现有聚合。
|
||||
- 不在 UI 面板内默认写功能说明、规则说明或开发解释文案。
|
||||
|
||||
## 先判断接入级别
|
||||
## 接入前输入
|
||||
|
||||
### 1. 只做入口占位
|
||||
开始编码前,PRD 或当前玩法文档必须已经明确:
|
||||
|
||||
只需要新增入口配置,不接 session/workspace/result/runtime。
|
||||
- `playId`、对外名称、工程域名、入口 `visible/open` 状态。
|
||||
- 创作链路:入口 -> 工作台 -> 生成页 -> 结果页 -> 试玩 -> 发布 -> 运行态。
|
||||
- 表单字段:字段名、默认值、校验、后端落库位置、生成提示词来源。
|
||||
- 单图资产槽位:`slotId`、`slotType`、`slotName`、提示词来源、读取字段、写回字段、是否允许历史图和 AI 重绘。
|
||||
- 系列素材槽位:`batchId` 语义、`sheetSpec`、`slotSpecs`、切图规则、透明化规则、失败回写、局部重生成策略。
|
||||
- API 命名空间:`/api/creation/<play>/sessions`、`actions`、`works`、`runtime`。
|
||||
- 草稿恢复、生成中恢复、失败重试、登录切换、发布后回读和移动端行为。
|
||||
- 验证命令和例外声明;没有例外时写明“无创作工具模式例外”。
|
||||
|
||||
适合:
|
||||
## 默认模式
|
||||
|
||||
- 敬请期待
|
||||
- 灰度占位
|
||||
新增玩法默认采用表单/图片输入创作工作台:
|
||||
|
||||
### 2. 可进入创作工作台
|
||||
```text
|
||||
创作入口 -> 表单/图片输入工作台 -> 生成页 -> 结果页 -> 试玩 -> 发布 -> 运行态
|
||||
```
|
||||
|
||||
需要补齐前端分流、session、工作台、结果页,至少能生成草稿。
|
||||
工作台只提交结构化表单、图片槽位和配置 payload。确需自然语言对话时,先走“例外流程”,不能把聊天区直接加进默认工作台。
|
||||
|
||||
### 3. 完整玩法闭环
|
||||
## SOP
|
||||
|
||||
需要补齐:
|
||||
### 1. 文档和领域词先行
|
||||
|
||||
- 创作入口
|
||||
- 工作台
|
||||
@@ -78,345 +89,208 @@ metadata:
|
||||
11. **规则参数归属**:哪些配置是创作者可编辑;哪些阈值、时长、冷却、计分、反作弊、裁决规则必须留在后端规则集。
|
||||
12. **旧数据策略**:旧草稿、旧发布配置、旧分享码是迁移、降级展示、重新生成,还是明确不兼容。
|
||||
|
||||
## 推荐接入顺序
|
||||
- `AGENTS.md`
|
||||
- `.hermes/shared-memory/`
|
||||
- `CONTEXT.md`
|
||||
- `docs/README.md`
|
||||
- `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
|
||||
- 相关玩法 PRD 或设计文档
|
||||
|
||||
### Step 1: 先定玩法 ID 和能力边界
|
||||
如果文档不能精确指导字段、契约、资产槽位、生成流程和恢复语义,先补文档再编码。新增长期约定时同步 `.hermes/shared-memory/`。
|
||||
|
||||
先明确:
|
||||
### 2. 定玩法边界
|
||||
|
||||
- `id` 是什么
|
||||
- 入口是否可见
|
||||
- 是否可点击创建
|
||||
- 是否需要对话式创作
|
||||
- 是否需要生成中页面
|
||||
- 是否需要 result/runtime/gallery/share
|
||||
固定 `playId`、对外名称、工程域、入口状态、是否支持结果页、试玩、发布、作品架、广场、分享和 runtime。不要先用临时 ID 接线后再批量改名。
|
||||
|
||||
不要先随便起临时 ID 再改名。
|
||||
### 3. 接入口配置
|
||||
|
||||
### Step 2: 新增入口配置
|
||||
入口配置事实源是 SpacetimeDB `creation_entry_type_config`。后台通过 `/admin/api/creation-entry/config` 管理,前台通过 `/api/creation-entry/config` 读取。
|
||||
|
||||
文件:
|
||||
前端只允许在展示层派生:
|
||||
|
||||
- `src/config/newWorkEntryConfig.ts`
|
||||
- 可见入口卡片。
|
||||
- 锁定或开放状态。
|
||||
- 排序、图标、短标题等展示信息。
|
||||
|
||||
在 `creationTypes` 中新增:
|
||||
`api-server` 路由熔断必须使用同一份入口配置。禁止新增或恢复前端本地默认入口配置作为事实源。
|
||||
|
||||
- `id`
|
||||
- `title`
|
||||
- `subtitle`
|
||||
- `badge`
|
||||
- `visible`
|
||||
- `open`
|
||||
### 4. 前端阶段
|
||||
|
||||
如果只是占位:
|
||||
按需要扩展 `SelectionStage`:
|
||||
|
||||
- `visible: true`
|
||||
- `open: false`
|
||||
- `<play>-workspace`
|
||||
- `<play>-generating`
|
||||
- `<play>-result`
|
||||
- `<play>-runtime`
|
||||
- `<play>-gallery-detail`
|
||||
|
||||
### Step 3: 确认类型过滤逻辑
|
||||
阶段名可以按玩法命名,UI 形态必须仍是表单/图片创作工作台。进入工作台时只初始化结构化草稿状态,不启动默认聊天会话。
|
||||
|
||||
文件:
|
||||
### 5. 工作台实现
|
||||
|
||||
- `src/components/platform-entry/platformEntryCreationTypes.ts`
|
||||
工作台必须满足:
|
||||
|
||||
检查:
|
||||
- 使用表单控件、图片槽位、风格选项、难度选项、开关和提交按钮组织输入。
|
||||
- 单图槽位统一使用 `CreativeImageInputPanel`。
|
||||
- 组件缺少能力时先扩展 `CreativeImageInputPanel` 的受控 props,不在玩法页面复制上传、参考图、AI 重绘、历史图、预览或删除确认。
|
||||
- 主图读取、裁剪、历史素材弹层、计费确认、自动保存和后端请求由外层页面持有;通用面板只表达输入 UI 和短生命周期 UI 状态。
|
||||
- 提交 payload 必须是表单字段与图片槽位结构,不是用户消息文本。
|
||||
|
||||
- `getVisiblePlatformCreationTypes()` 是否能展示新类型
|
||||
- `isPlatformCreationTypeVisible()` 是否能识别新类型
|
||||
- `locked` / `hidden` 是否正确映射
|
||||
### 6. 单图资产槽位
|
||||
|
||||
### Step 4: 扩展页面阶段
|
||||
角色形象、UI 背景、容器、封面、分享图、图标等单张图都按单图资产槽位处理。
|
||||
|
||||
文件:
|
||||
统一约定:
|
||||
|
||||
- `src/components/platform-entry/platformEntryTypes.ts`
|
||||
- 槽位用 `slotId` 稳定标识,`slotType` 表达用途,`slotName` 用于 UI 标签。
|
||||
- 上传图、参考图、AI 重绘、历史图选择和删除确认都通过 `CreativeImageInputPanel` 入口表达。
|
||||
- 后端写回 `imageSrc`、`imageObjectKey`、`assetObjectId` 中可用字段;前端展示前通过平台资产读取能力换签。
|
||||
- 单个槽位重生成只禁用该槽位动作,不阻塞结果页其它槽位、系列素材槽位或导航。
|
||||
|
||||
为新玩法补充 `SelectionStage`:
|
||||
### 7. 系列素材图集生成
|
||||
|
||||
- `*-agent-workspace`
|
||||
- `*-generating`(可选)
|
||||
- `*-result`
|
||||
- `*-runtime`(可选)
|
||||
- `*-gallery-detail`(可选)
|
||||
地块、物品、障碍、装饰、UI 部件等一组同类素材都走通用系列素材图集生成流程:
|
||||
|
||||
### Step 5: 在总流程中加类型分流
|
||||
```text
|
||||
批量规划 -> sheet 生图 -> 后端切图 -> 去背景/透明化 -> PNG 输出 -> OSS 持久化 -> 状态回写 -> 局部重生成
|
||||
```
|
||||
|
||||
文件:
|
||||
玩法只提供:
|
||||
|
||||
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
||||
- `sheetSpec`:画布比例、行列、单格尺寸、输出格式、背景处理策略。
|
||||
- `slotSpecs`:每个素材槽位的 `slotId`、`slotType`、`slotName`、提示词、sheet 单元格映射。
|
||||
- 玩法字段映射:把通用素材结果映射回玩法自己的 draft/profile/runtime 字段。
|
||||
|
||||
在 `handleCreationHubCreateType(type)` 中新增分支,确保:
|
||||
通用系列素材结果建议字段:
|
||||
|
||||
- 能进入对应工作台
|
||||
- 能设置对应 `selectionStage`
|
||||
- 能关闭类型弹层
|
||||
|
||||
同时按玩法补齐:
|
||||
|
||||
- `open<Play>AgentWorkspace()`
|
||||
- `leave<Play>Flow()`
|
||||
- `submit<Play>Message()`(对话式玩法)
|
||||
- `execute<Play>Action()`
|
||||
|
||||
### Step 6: 接入通用 Agent flow controller
|
||||
|
||||
文件:
|
||||
|
||||
- `src/components/platform-entry/usePlatformCreationAgentFlowController.ts`
|
||||
|
||||
如果是 Agent 型玩法,复用通用控制器:
|
||||
|
||||
- `createSession`
|
||||
- `getSession`
|
||||
- `streamMessage`
|
||||
- `executeAction`
|
||||
- `isBusy`
|
||||
- `batchId`
|
||||
- `slotId`
|
||||
- `slotType`
|
||||
- `slotName`
|
||||
- `prompt`
|
||||
- `imageSrc`
|
||||
- `imageObjectKey`
|
||||
- `assetObjectId`
|
||||
- `sourceSheetCell`
|
||||
- `status`
|
||||
- `error`
|
||||
- `streamingReplyText`
|
||||
- `selectionStage` 切换
|
||||
|
||||
### Step 7: 定义 shared contracts
|
||||
玩法可追加运行态字段,例如半径、宽度、视图索引或碰撞参数,但不能依赖任何玩法专属字段作为平台通用模型。新增玩法 compile action 内部调用通用系列素材服务;如果通用服务还缺能力,先补通用服务再接玩法。
|
||||
|
||||
前端:
|
||||
### 8. 契约与 API
|
||||
|
||||
前后端必须同步补契约:
|
||||
|
||||
- `packages/shared/src/contracts/`
|
||||
|
||||
后端:
|
||||
|
||||
- `server-rs/crates/shared-contracts/src/`
|
||||
|
||||
至少补齐:
|
||||
玩法 API 保留独立命名空间:
|
||||
|
||||
- session snapshot
|
||||
- create session request/response
|
||||
- message request/response
|
||||
- action request/response
|
||||
- draft/result 结构
|
||||
- work summary / gallery 结构(如果需要)
|
||||
- runtime 结构(如果需要)
|
||||
- `POST /api/creation/<play>/sessions`
|
||||
- `GET /api/creation/<play>/sessions/{sessionId}`
|
||||
- `POST /api/creation/<play>/sessions/{sessionId}/actions`
|
||||
- `/api/creation/<play>/works`
|
||||
- `/api/creation/<play>/runtime`
|
||||
|
||||
### Step 8: 实现前端 service client
|
||||
契约需要区分:
|
||||
|
||||
目录参考:
|
||||
- 工作台输入。
|
||||
- 草稿 snapshot。
|
||||
- 单图资产槽位。
|
||||
- 系列素材批次与槽位。
|
||||
- 结果页操作。
|
||||
- 发布作品摘要。
|
||||
- runtime snapshot。
|
||||
|
||||
- `src/services/`
|
||||
### 9. 后端分层
|
||||
|
||||
按玩法补:
|
||||
按 DDD 边界落地:
|
||||
|
||||
- creation client
|
||||
- runtime client(可选)
|
||||
- works client(可选)
|
||||
- gallery client(可选)
|
||||
- `module-<play>`:纯领域规则、状态机、draft/runtime 校验。
|
||||
- `shared-contracts`:前后端 DTO。
|
||||
- `spacetime-module`:表、reducer、procedure、事务编排、migration。
|
||||
- `spacetime-client`:typed facade 和 row mapper。
|
||||
- `api-server`:Axum 路由、鉴权、BFF、SSE、生成编排。
|
||||
- `platform-*`:LLM、图片生成、OSS、认证等外部副作用。
|
||||
|
||||
建议保持和现有玩法一致的 API base 与命名风格。
|
||||
涉及 SpacetimeDB schema 时同步 `migration.rs`、表目录和绑定,并运行 `npm run check:spacetime-schema`。
|
||||
|
||||
### Step 9: 接后端 API
|
||||
|
||||
文件参考:
|
||||
|
||||
- `server-rs/crates/api-server/src/puzzle.rs`
|
||||
- `server-rs/crates/api-server/src/puzzle_agent_turn.rs`
|
||||
- `server-rs/crates/api-server/src/match3d.rs`
|
||||
|
||||
通常需要:
|
||||
|
||||
- create session
|
||||
- get session
|
||||
- send message
|
||||
- stream message
|
||||
- execute action
|
||||
- publish / save / delete
|
||||
- runtime start / action(可选)
|
||||
- gallery / detail(可选)
|
||||
|
||||
后端设计优先按 Genarrative 的 DDD 分层拆开,不要把玩法规则、数据库事务、LLM 调用和 HTTP handler 混在一个文件里:
|
||||
|
||||
- `module-<play>`:纯领域规则、状态机、draft/runtime 校验,不依赖 Axum、SpacetimeDB 或外部平台。
|
||||
- `shared-contracts`:前后端 DTO、请求/响应、session snapshot、draft/result/runtime 结构。
|
||||
- `spacetime-module`:表定义、reducer/procedure、事务编排、migration;表结构变化要同步生成绑定。
|
||||
- `spacetime-client`:api-server 到 SpacetimeDB 的 facade,隐藏 reducer 调用细节。
|
||||
- `api-server`:Axum 路由、鉴权、SSE/stream、应用层编排。
|
||||
- `platform-*`:LLM、资产上传、鉴权、第三方服务等副作用。
|
||||
|
||||
建议按四条线设计后端能力:
|
||||
|
||||
- Agent 创作线:session、turn、stream、compile action。
|
||||
- Works 作品线:保存、发布、删除、草稿恢复。
|
||||
- Gallery 广场线:公开列表、详情、like/remix/share。
|
||||
- Runtime 运行态线:开始试玩、提交动作、读取状态。
|
||||
|
||||
### Step 10: 新增工作台组件
|
||||
|
||||
目录建议:
|
||||
|
||||
- `src/components/<play>-creation/<Play>AgentWorkspace.tsx`
|
||||
|
||||
两种形态:
|
||||
|
||||
#### 对话式
|
||||
|
||||
适合设定逐轮补齐。
|
||||
|
||||
参考:
|
||||
|
||||
- `BigFishAgentWorkspace.tsx`
|
||||
- `Match3DAgentWorkspace.tsx`
|
||||
|
||||
#### 表单式
|
||||
|
||||
适合输入结构明确的玩法。
|
||||
|
||||
参考:
|
||||
|
||||
- `PuzzleAgentWorkspace.tsx`
|
||||
|
||||
### Step 11: 在渲染树中挂载新页面
|
||||
|
||||
文件:
|
||||
|
||||
- `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
||||
|
||||
补齐:
|
||||
|
||||
- workspace 分支
|
||||
- generating 分支(如需要)
|
||||
- result 分支
|
||||
- runtime 分支(如需要)
|
||||
|
||||
### Step 12: 新增结果页
|
||||
|
||||
目录建议:
|
||||
|
||||
- `src/components/<play>-result/<Play>ResultView.tsx`
|
||||
### 10. 结果页
|
||||
|
||||
结果页至少支持:
|
||||
|
||||
- 展示 draft
|
||||
- 返回编辑
|
||||
- 发布
|
||||
- 试玩
|
||||
- 错误展示
|
||||
- 展示草稿和生成状态。
|
||||
- 返回工作台编辑。
|
||||
- 单图槽位重生成。
|
||||
- 系列素材追加、替换、局部重生成。
|
||||
- 发布。
|
||||
- 试玩。
|
||||
- 错误展示和失败重试。
|
||||
|
||||
### Step 13: 需要试玩就补 runtime
|
||||
单图槽位和系列素材槽位的生成状态互不阻塞。已有可查看结果时,局部重生成不能把作品架草稿重新变成不可打开的全局生成中。
|
||||
|
||||
目录建议:
|
||||
### 11. 运行态、作品架和广场
|
||||
|
||||
- `src/components/<play>-runtime/<Play>RuntimeShell.tsx`
|
||||
需要试玩或发布时补齐:
|
||||
|
||||
如果玩法是游戏类,建议补完整 runtime 闭环。
|
||||
- runtime start/action/finish API。
|
||||
- 作品保存、发布、删除、回读。
|
||||
- 作品架摘要。
|
||||
- 公开列表、详情、分享码。
|
||||
- 公开列表优先消费后端投影或 BFF 缓存,不让前端直接拼源表事实。
|
||||
|
||||
### Step 14: 接入作品架 / 广场 / 分享
|
||||
运行态可以做低延迟表现,但正式胜负、分数、奖励、排行榜和发布状态以后端裁决为准。
|
||||
|
||||
需要改:
|
||||
### 12. 恢复与登录态
|
||||
|
||||
- `src/components/custom-world-home/creationWorkShelf.ts`
|
||||
- `src/components/custom-world-home/CustomWorldCreationHub.tsx`
|
||||
- `src/services/publicWorkCode.ts`
|
||||
必须处理:
|
||||
|
||||
如果玩法支持发布,还要补:
|
||||
- 刷新恢复生成中草稿。
|
||||
- 生成页计时从后端摘要时间恢复。
|
||||
- 失败后回读 session/work detail 再决定是否展示失败。
|
||||
- 退出登录清空私有玩法状态。
|
||||
- 私有生成图展示前换签。
|
||||
- result/runtime 缺必要 draft 时回到可恢复入口,不停在空白页。
|
||||
|
||||
- public work code
|
||||
- public detail
|
||||
- publish share modal
|
||||
- like/remix(可选)
|
||||
### 13. 例外流程
|
||||
|
||||
### Step 15: 处理登录态与草稿恢复
|
||||
任何非表单/图片工作台、对话式 Agent、独立创作系统或特殊资产模型都必须先更新 PRD 和平台文档。例外声明至少写清:
|
||||
|
||||
要考虑:
|
||||
- 为什么默认表单/图片工作台不能满足。
|
||||
- 例外影响哪些输入、契约、后端流程和测试。
|
||||
- 如何保留单图资产槽位和系列素材槽位的通用能力。
|
||||
- 如何回退到平台默认链路。
|
||||
|
||||
- 刷新恢复草稿
|
||||
- 退出登录清空私有状态
|
||||
- result/draft 缺失时回退
|
||||
- busy / generating / runtime 中断恢复
|
||||
没有文档例外,不进入编码。
|
||||
|
||||
### Step 16: 补测试
|
||||
## PRD 检查块
|
||||
|
||||
至少覆盖:
|
||||
在新增玩法 PRD 中保留这一段:
|
||||
|
||||
- 入口展示
|
||||
- 类型分流
|
||||
- 工作台打开
|
||||
- session 创建
|
||||
- compile action
|
||||
- result 页切换
|
||||
- 发布后刷新作品架
|
||||
- runtime 进入与退出
|
||||
```md
|
||||
## 创作工具平台接入声明
|
||||
|
||||
## 最小改动清单
|
||||
|
||||
### 只做占位
|
||||
|
||||
只改:
|
||||
|
||||
- `src/config/newWorkEntryConfig.ts`
|
||||
|
||||
### 做到可进入工作台
|
||||
|
||||
至少改:
|
||||
|
||||
- `newWorkEntryConfig.ts`
|
||||
- `platformEntryTypes.ts`
|
||||
- `PlatformEntryFlowShellImpl.tsx`
|
||||
- 新玩法 service client
|
||||
- 新玩法工作台组件
|
||||
- shared contracts
|
||||
- 后端 API
|
||||
|
||||
### 做到完整闭环
|
||||
|
||||
还要补:
|
||||
|
||||
- result 页
|
||||
- runtime
|
||||
- works / gallery
|
||||
- public code
|
||||
- share
|
||||
- 作品架聚合
|
||||
- 测试
|
||||
|
||||
## 常见坑
|
||||
|
||||
1. 只加入口配置不够,类型分流和页面阶段也要补。
|
||||
2. `SelectionStage` 不扩展,前端无法安全切页。
|
||||
3. 新玩法如果要出现在作品架,必须改聚合逻辑,不只是加入口。
|
||||
4. 发布后不刷新 works/gallery,用户会看不到新作品。
|
||||
5. 如果走 SpacetimeDB,表结构变化要同步 migration 和绑定;`spacetime-client/src/module_bindings/` 通常是生成物,不要为了修编译或格式化而手改,优先改 module 源 schema/reducer/procedure 后重新生成。
|
||||
6. 做 analytics/tracking 这类 runtime 能力时,不要只补 API DTO;先在 `module-runtime` 写纯函数测试(例如 day/week/month/quarter/year bucket 聚合、scope/event 过滤),RED 后再补领域类型与聚合函数。
|
||||
7. 时间粒度聚合建议复用已有 date dimension 逻辑,把 daily stat 映射到 day/week/month/quarter/year bucket;bucket 输出要有稳定排序,并显式携带 `bucketKey`、`bucketStartDateKey`、`bucketEndDateKey`、`value`。
|
||||
8. 后端 shared-contracts 与前端 `packages/shared/src/contracts/runtime.ts` 要同步补 request/response/type union;admin-web 若有独立 `api/adminApiTypes.ts`,也要同步,避免共享包已更新但管理端本地类型缺失。
|
||||
9. 退出登录时要清空新玩法私有状态,避免串用户。
|
||||
10. 移动端入口卡片增多后要检查布局和滚动体验。
|
||||
|
||||
## 验证标准
|
||||
|
||||
一个玩法算真正接入成功,至少要满足:
|
||||
|
||||
- 入口能展示
|
||||
- 能进入对应工作台
|
||||
- 能创建 session
|
||||
- 能生成草稿
|
||||
- 能进入结果页
|
||||
- 能返回编辑
|
||||
- 如果需要,可试玩
|
||||
- 如果需要,可发布
|
||||
- 发布后能回到作品架 / 广场 / 分享链路
|
||||
|
||||
## 建议验证命令
|
||||
|
||||
按改动范围选择:
|
||||
|
||||
```bash
|
||||
# 后端 contracts / module-runtime / api-server
|
||||
cd server-rs
|
||||
cargo test -p shared-contracts
|
||||
cargo test -p module-runtime
|
||||
cargo check -p api-server
|
||||
|
||||
# SpacetimeDB schema/reducer/procedure 改动后,优先在有 CLI 的机器重新生成 bindings
|
||||
npm run spacetime:generate -- --rust-only
|
||||
|
||||
# 前端类型
|
||||
npm run admin-web:typecheck
|
||||
- 工作台模式:表单/图片输入创作工作台
|
||||
- 创作链路:入口 -> 工作台 -> 生成页 -> 结果页 -> 试玩 -> 发布 -> 运行态
|
||||
- 单图资产槽位:
|
||||
- slotId / slotType / slotName / 提示词来源 / 写回字段 / 是否允许历史图 / 是否允许 AI 重绘
|
||||
- 系列素材槽位:
|
||||
- batchId / sheetSpec / slotSpecs / 切图规则 / 透明化规则 / 失败回写 / 局部重生成
|
||||
- API 命名空间:/api/creation/<play>/...
|
||||
- 业务真相:后端裁决字段和前端表现字段边界
|
||||
- 创作工具模式例外:无;如有,先写明例外原因和回退方式
|
||||
- 验证命令:
|
||||
```
|
||||
|
||||
如果新增完整前端玩法闭环,还要按项目实际脚本补充 web typecheck、lint 或 Playwright/单元测试。
|
||||
## 验证门禁
|
||||
|
||||
按改动范围运行:
|
||||
|
||||
- `npm run check:encoding`
|
||||
- `npm run typecheck`
|
||||
- 前端工作台测试:确认没有聊天式 Agent 输入,提交的是表单/图片 payload。
|
||||
- `CreativeImageInputPanel` 测试:覆盖多玩法标签、上传、AI 重绘、参考图上限、历史图入口和删除确认。
|
||||
- 系列素材测试:覆盖 sheet layout、切图、透明化、OSS 持久化、追加、替换、局部重生成和失败回写。
|
||||
- 结果页测试:覆盖单图槽位重生成和系列素材槽位重生成互不阻塞。
|
||||
- 后端定向测试:覆盖 compile action、资产持久化、失败回写、发布和 runtime start。
|
||||
- 涉及 SpacetimeDB schema 时运行 `npm run check:spacetime-schema`。
|
||||
|
||||
@@ -5,7 +5,7 @@ description: Generate or inspect project image assets through this repository's
|
||||
|
||||
# gpt-image-2 VectorEngine
|
||||
|
||||
Use this skill for project-local image asset generation that must match the repository's `server-rs` VectorEngine `gpt-image-2-all` path. The folder still contains `apimart` in its name for compatibility with existing local plugin references.
|
||||
Use this skill for project-local image asset generation that must match the repository's `server-rs` VectorEngine `gpt-image-2` path. The folder still contains `apimart` in its name for compatibility with existing local plugin references.
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -40,22 +40,14 @@ Default body:
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "gpt-image-2-all",
|
||||
"model": "gpt-image-2",
|
||||
"prompt": "<prompt>",
|
||||
"n": 1,
|
||||
"size": "1024x1024"
|
||||
}
|
||||
```
|
||||
|
||||
For weak visual references in text-to-image generation, add:
|
||||
|
||||
```json
|
||||
{
|
||||
"image": ["data:image/png;base64,..."]
|
||||
}
|
||||
```
|
||||
|
||||
For image-to-image work that must follow a reference image closely, use the VectorEngine edits endpoint instead of the generations `image` array:
|
||||
For visual references, use the edit endpoint instead of the create endpoint:
|
||||
|
||||
```text
|
||||
POST {VECTOR_ENGINE_BASE_URL}/v1/images/edits
|
||||
@@ -73,9 +65,9 @@ size=1024x1024
|
||||
image=@reference.png
|
||||
```
|
||||
|
||||
Prefer edits for workflows where the reference image controls composition, pose, container shape, or layout. In this repository, Match3D container UI generation uses edits with `public/match3d-background-references/pot-fused-reference.png` as the `image` part.
|
||||
In this repository, calls with no reference images use `POST /v1/images/generations`; calls with any reference image use `POST /v1/images/edits` and pass references as one or more `image` form parts. Match3D container UI generation embeds `public/match3d-background-references/pot-fused-reference.png` into the edit request as an `image` part.
|
||||
|
||||
Accept image output from `data[].url`, `data[].b64_json`, or direct nested `url` fields. VectorEngine GPT-image-2-all currently returns synchronously; do not poll APIMart task endpoints.
|
||||
Accept image output from `data[].url`, `data[].b64_json`, or direct nested `url` fields. VectorEngine GPT-image-2 currently returns synchronously; do not poll APIMart task endpoints.
|
||||
|
||||
## Environment
|
||||
|
||||
|
||||
@@ -245,7 +245,7 @@ async function downloadUrl(url, timeoutMs) {
|
||||
|
||||
async function generateOne(env, entry, outDir) {
|
||||
const requestBody = {
|
||||
model: 'gpt-image-2-all',
|
||||
model: 'gpt-image-2',
|
||||
prompt: buildPrompt(entry),
|
||||
n: 1,
|
||||
size: '1024x1024',
|
||||
@@ -305,7 +305,7 @@ if (dryRun) {
|
||||
id: entry.id,
|
||||
title: entry.title,
|
||||
body: {
|
||||
model: 'gpt-image-2-all',
|
||||
model: 'gpt-image-2',
|
||||
prompt: buildPrompt(entry),
|
||||
n: 1,
|
||||
size: '1024x1024',
|
||||
|
||||
@@ -211,7 +211,7 @@ async function downloadUrl(url, timeoutMs) {
|
||||
|
||||
async function generateOne(env, template, outDir) {
|
||||
const requestBody = {
|
||||
model: 'gpt-image-2-all',
|
||||
model: 'gpt-image-2',
|
||||
prompt: buildPrompt(template),
|
||||
n: 1,
|
||||
size: '1024x1024',
|
||||
@@ -275,7 +275,7 @@ if (dryRun) {
|
||||
id: template.id,
|
||||
title: template.title,
|
||||
body: {
|
||||
model: 'gpt-image-2-all',
|
||||
model: 'gpt-image-2',
|
||||
prompt: buildPrompt(template),
|
||||
n: 1,
|
||||
size: '1024x1024',
|
||||
|
||||
35281
.codex/tmp-schema.json
35281
.codex/tmp-schema.json
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user