feat: add edutainment drawing and visual package flows
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
# 宝贝爱画本地 Demo 运行态实现方案 2026-05-13
|
||||
|
||||
## 1. 范围
|
||||
|
||||
本方案落地寓教于乐独立关卡:
|
||||
|
||||
```text
|
||||
baby-love-drawing / 宝贝爱画
|
||||
```
|
||||
|
||||
当前范围只做本地 Demo 闭环:
|
||||
|
||||
1. 寓教于乐频道默认关卡卡片;
|
||||
2. 独立运行态;
|
||||
3. mocap 与开发者调试输入;
|
||||
4. Canvas 绘制和擦除;
|
||||
5. image-2 绘画魔法后端代理;
|
||||
6. localStorage 本地保存;
|
||||
7. 直达路由开关保护。
|
||||
|
||||
本阶段不接正式持久化表,不新增作品发布、作品号、公开详情或搜索入口。
|
||||
|
||||
## 2. 前端接入点
|
||||
|
||||
已新增页面阶段:
|
||||
|
||||
```text
|
||||
baby-love-drawing-runtime
|
||||
```
|
||||
|
||||
已新增路由:
|
||||
|
||||
```text
|
||||
/runtime/baby-love-drawing
|
||||
```
|
||||
|
||||
已新增文件:
|
||||
|
||||
```text
|
||||
packages/shared/src/contracts/edutainmentBabyDrawing.ts
|
||||
src/services/edutainment-baby-drawing/babyDrawingClient.ts
|
||||
src/components/edutainment-runtime/babyLoveDrawingModel.ts
|
||||
src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.tsx
|
||||
server-rs/crates/api-server/src/edutainment_baby_drawing.rs
|
||||
```
|
||||
|
||||
已接入:
|
||||
|
||||
1. `src/components/rpg-entry/RpgEntryHomeView.tsx`:寓教于乐频道默认展示宝贝爱画卡片;
|
||||
2. `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`:启动宝贝爱画运行态;
|
||||
3. `src/components/platform-entry/platformEntryTypes.ts`:扩展 `SelectionStage`;
|
||||
4. `src/routing/appPageRoutes.ts`:扩展路由;
|
||||
5. `src/routing/appRoutes.tsx`:直达路由开关保护;
|
||||
6. `src/index.css`:补齐寓教于乐默认关卡卡片和宝贝爱画运行态样式;
|
||||
7. `server-rs/crates/api-server/src/app.rs`:挂载绘画魔法后端路由。
|
||||
|
||||
## 3. 契约
|
||||
|
||||
契约放在:
|
||||
|
||||
```text
|
||||
packages/shared/src/contracts/edutainmentBabyDrawing.ts
|
||||
```
|
||||
|
||||
核心字段:
|
||||
|
||||
1. `templateId = "baby-love-drawing"`;
|
||||
2. `templateName = "宝贝爱画"`;
|
||||
3. `originalImageSrc` 保存原始画布图;
|
||||
4. `magicImageSrc` 保存 image-2 魔法图,可为 `null`;
|
||||
5. `strokeTrace` 保存画笔和橡皮轨迹;
|
||||
6. `saveMode = "original-only" | "original-and-magic"` 记录保存结果。
|
||||
|
||||
## 4. 运行态模型
|
||||
|
||||
运行态状态:
|
||||
|
||||
```text
|
||||
drawing
|
||||
finished
|
||||
magicPending
|
||||
magicReady
|
||||
saved
|
||||
```
|
||||
|
||||
工具:
|
||||
|
||||
```text
|
||||
brush
|
||||
eraser
|
||||
```
|
||||
|
||||
颜色:
|
||||
|
||||
```text
|
||||
红、橙、黄、绿、青、蓝、紫
|
||||
```
|
||||
|
||||
按钮悬停:
|
||||
|
||||
1. 颜色选择只接受左手悬停,阈值 1500ms;
|
||||
2. 按钮选择接受任一手悬停,阈值 2000ms;
|
||||
3. 工具切换只接受右手在工具区域握拳。
|
||||
4. 画笔 / 橡皮光标位置只接受右手坐标;左手缺帧或左手移动不得重置、替换或驱动画笔位置。
|
||||
5. 左手需要显示独立位置指示器,帮助用户确认当前是否悬停在目标颜色上;该指示器只表达左手位置,不参与画笔 / 橡皮操作。
|
||||
6. 本地 mocap handedness 当前按摄像头视角输出,宝贝爱画运行态消费前需要换算为用户身体视角:`rightHand` 作为用户左手,`leftHand` 作为用户右手。键鼠调试输入不做该换算。
|
||||
7. 真实硬件短暂缺失某只手时,显示层保留上一帧位置约 320ms 并做轻微坐标平滑;绘制层仍只在当前帧确认用户右手存在时生效。
|
||||
8. 为避免左手抢画笔,本关不做动态 handedness 换手纠正;`rightHand` 永远只进入用户左手选色通道,`leftHand` 永远只进入用户右手画笔通道。若硬件侧 handedness 继续抖动,宁可右手画笔短暂停住,也不允许左手驱动画笔。
|
||||
9. 右手画笔通道增加单帧最大位移门禁;若 camera-left 候选点相对上一帧右手位置出现不合理大跳,判定为不可信帧,只保留上一帧光标并停止绘制。
|
||||
|
||||
## 5. Canvas 绘制
|
||||
|
||||
画板使用 DOM Canvas。
|
||||
|
||||
绘制规则:
|
||||
|
||||
1. 右手在画板内且状态为 `grab` 时生效;
|
||||
2. 工具为 `brush` 时,以当前颜色绘制连续线段;
|
||||
3. 工具为 `eraser` 时,以 `destination-out` 擦除;
|
||||
4. 右手状态为 `open_palm` 或离开画板时结束当前笔画;
|
||||
5. 当前帧没有右手坐标时只结束当前笔画,不把左手坐标用于绘制、擦除或光标定位;
|
||||
6. 每条笔画记录工具、颜色、点位和时间。
|
||||
|
||||
## 6. 绘画魔法
|
||||
|
||||
前端 service:
|
||||
|
||||
```text
|
||||
createBabyDrawingMagicImage(payload)
|
||||
```
|
||||
|
||||
后端接口:
|
||||
|
||||
```text
|
||||
POST /api/creation/edutainment/baby-love-drawing/magic
|
||||
```
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"originalImageSrc": "data:image/png;base64,...",
|
||||
"strokeTrace": []
|
||||
}
|
||||
```
|
||||
|
||||
响应体:
|
||||
|
||||
```json
|
||||
{
|
||||
"magicImageSrc": "data:image/png;base64,...",
|
||||
"generationProvider": "vector-engine-gpt-image-2",
|
||||
"prompt": "..."
|
||||
}
|
||||
```
|
||||
|
||||
后端使用 VectorEngine `gpt-image-2-all`,把原始画布图作为参考图,生成绘本风格图片。
|
||||
|
||||
本地未配置 VectorEngine 或接口失败时,前端允许提示错误并保留原图保存能力;不得把失败伪装成正式魔法图。
|
||||
|
||||
后端接入约束:
|
||||
|
||||
1. 接口需要 Bearer 鉴权;
|
||||
2. 请求体限制为 8MB;
|
||||
3. `originalImageSrc` 只接受图片 Data URL;
|
||||
4. 笔触数量上限为 600 条;
|
||||
5. 上游参考图字段使用 VectorEngine 统一契约 `image`;
|
||||
6. 关闭入口时,`creation_entry_config` 路由熔断可识别 `baby-love-drawing`。
|
||||
|
||||
## 7. 本地保存
|
||||
|
||||
本地保存使用:
|
||||
|
||||
```text
|
||||
localStorage key = genarrative.edutainmentBabyDrawing.localDrawings.v1
|
||||
```
|
||||
|
||||
保存策略:
|
||||
|
||||
1. 魔法生成前保存:`saveMode = "original-only"`,只保存 `originalImageSrc`;
|
||||
2. 未保存原图直接生成魔法后保存:`saveMode = "original-and-magic"`,保存 `originalImageSrc` 和 `magicImageSrc`;
|
||||
3. 保存后展示“再画一张”和“返回”。
|
||||
|
||||
## 8. 验收命令
|
||||
|
||||
```bash
|
||||
npm run test -- src/components/edutainment-runtime/babyLoveDrawingModel.test.ts src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.test.tsx src/services/edutainment-baby-drawing/babyDrawingClient.test.ts src/routing/appRoutes.test.ts
|
||||
cargo test -p api-server edutainment_baby_drawing --manifest-path server-rs/Cargo.toml
|
||||
cargo test -p api-server resolves_runtime_paths_to_creation_type_ids --manifest-path server-rs/Cargo.toml
|
||||
npx eslint src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.tsx src/components/edutainment-runtime/babyLoveDrawingModel.ts src/services/edutainment-baby-drawing/babyDrawingClient.ts src/routing/appRoutes.tsx --ext .ts,.tsx --max-warnings 0
|
||||
npm run typecheck
|
||||
npm run check:encoding
|
||||
```
|
||||
|
||||
## 9. 已覆盖测试
|
||||
|
||||
1. `src/components/edutainment-runtime/babyLoveDrawingModel.test.ts`:颜色 / 按钮悬停阈值、坐标归一化、笔触追加;
|
||||
2. `src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.test.tsx`:画板、七色、画笔 / 橡皮、完成保存、返回按钮、左手位置指示器、mocap 摄像头视角到用户身体视角换算、左手输入不替换画笔光标位置、左手短暂缺帧不闪烁、用户左手不能抢占右手画笔、camera-left 大跳不接入画笔;
|
||||
3. `src/services/edutainment-baby-drawing/babyDrawingClient.test.ts`:原图保存、原图加魔法图保存、后端魔法接口请求;
|
||||
4. `src/routing/appRoutes.test.ts`:`/runtime/baby-love-drawing` 开启可达、关闭回落主应用;
|
||||
5. `server-rs/crates/api-server/src/edutainment_baby_drawing.rs` 内部单测:prompt、Data URL 校验、PNG 输出和轨迹范围摘要;
|
||||
6. `server-rs/crates/api-server/src/creation_entry_config.rs` 路由映射单测:确认后端熔断可识别 `baby-love-drawing`。
|
||||
@@ -30,13 +30,15 @@ baby-object-match
|
||||
宝贝识物
|
||||
```
|
||||
|
||||
入口文件:
|
||||
工程接入文件:
|
||||
|
||||
1. `src/config/newWorkEntryConfig.ts`
|
||||
1. `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs`
|
||||
2. `src/components/platform-entry/platformEntryCreationTypes.ts`
|
||||
3. `src/components/platform-entry/PlatformEntryCreationTypeModal.tsx`
|
||||
4. `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
|
||||
|
||||
`src/config/newWorkEntryConfig.ts` 已迁移删除,不再作为入口事实源。`baby-object-match` 必须存在于 SpacetimeDB `creation_entry_type_config` 默认种子中,默认展示名为 `宝贝识物`、`visible=true`、`open=true`、`sortOrder=90`;前端只通过 `GET /api/creation-entry/config` 读取后端配置并在 `platformEntryCreationTypes.ts` 做展示派生。
|
||||
|
||||
`baby-object-match` 必须复用 `VITE_ENABLE_EDUTAINMENT_ENTRY` 开关;开关关闭时,创作类型弹层不展示 `宝贝识物`,创作页作品架不展示本地宝贝识物草稿或已发布作品卡,公开发现、搜索、详情、作品号和浏览历史也继续完全不可见。
|
||||
|
||||
新增阶段:
|
||||
@@ -62,7 +64,8 @@ packages/shared/src/contracts/edutainmentBabyObject.ts
|
||||
2. `BabyObjectMatchDraft.templateName = "宝贝识物"`;
|
||||
3. `BabyObjectMatchDraft.themeTags` 必须包含精确 `寓教于乐`;
|
||||
4. `BabyObjectMatchItemAsset.generationProvider` 首版允许为 `vector-engine-gpt-image-2` 或 `placeholder`;
|
||||
5. `BabyObjectMatchPublishRequest.draft.themeTags` 发布前必须归一化补齐 `寓教于乐`。
|
||||
5. `BabyObjectMatchDraft.visualPackage` 可选承载背景环境、UI 装饰框、礼物盒、篮子和烟雾弹出特效五类视觉资源;
|
||||
6. `BabyObjectMatchPublishRequest.draft.themeTags` 发布前必须归一化补齐 `寓教于乐`。
|
||||
|
||||
## 4. Service 边界
|
||||
|
||||
@@ -76,9 +79,64 @@ src/services/edutainment-baby-object/babyObjectMatchClient.ts
|
||||
|
||||
1. `createBabyObjectMatchDraft(payload)`;
|
||||
2. `saveBabyObjectMatchDraft(draft)`;
|
||||
3. `publishBabyObjectMatchWork(payload)`。
|
||||
3. `publishBabyObjectMatchWork(payload)`;
|
||||
4. `deleteLocalBabyObjectMatchDraft(profileId)`;
|
||||
5. `regenerateBabyObjectMatchDraftAssets(draft)`;
|
||||
6. `hasBabyObjectMatchPlaceholderAssets(draft)`。
|
||||
|
||||
当前后端正式接口未在本线程扩表落地,因此 service 先走本地 Demo 存储,并把 asset 结果标记为 `placeholder`。后续后端接入时,应替换为:
|
||||
当前后端正式作品持久化接口未在本线程扩表落地,因此 service 仍使用本地 Demo 存储草稿和发布状态。由于 image-2 会返回多张 base64 PNG 大图,本地 Demo 草稿必须优先写入 IndexedDB `genarrative-edutainment-baby-object-drafts/drafts`,不得把完整草稿 JSON 写入 `localStorage`;`localStorage` 仅作为旧版小草稿迁移读取来源,读取后迁移到 IndexedDB 并清理旧 key,避免触发浏览器 `Storage` 配额错误。
|
||||
|
||||
物品图片生成已接入后端 image-2 接口:
|
||||
|
||||
```text
|
||||
POST /api/creation/edutainment/baby-object-match/assets
|
||||
```
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"itemNames": ["苹果", "香蕉"]
|
||||
}
|
||||
```
|
||||
|
||||
响应体:
|
||||
|
||||
```json
|
||||
{
|
||||
"assets": [
|
||||
{
|
||||
"itemId": "baby-object-item-1",
|
||||
"itemName": "苹果",
|
||||
"imageSrc": "data:image/png;base64,...",
|
||||
"assetObjectId": null,
|
||||
"generationProvider": "vector-engine-gpt-image-2",
|
||||
"prompt": "..."
|
||||
}
|
||||
],
|
||||
"visualPackage": {
|
||||
"themePrompt": "...",
|
||||
"assets": [
|
||||
{
|
||||
"assetId": "baby-object-visual-background",
|
||||
"assetKind": "background",
|
||||
"imageSrc": "data:image/png;base64,...",
|
||||
"assetObjectId": null,
|
||||
"generationProvider": "vector-engine-gpt-image-2",
|
||||
"prompt": "..."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
该接口返回物品透明 PNG data URL,以及同一次创作生成的视觉主题包。本地 Demo 阶段暂不写入 OSS 或 SpacetimeDB `asset_object`。当前创作链路必须真实拿到 `generationProvider = "vector-engine-gpt-image-2"` 的物品图和视觉主题包后才允许进入结果页;若本地未配置 VectorEngine、登录态失效、接口返回 401/5xx、上游生成失败或响应缺少任一资源,前端 service 必须抛出错误并停留在生成失败状态,不得静默回退到占位图。
|
||||
|
||||
由于一次创作会生成 2 张物品图和 `background`、`ui-frame`、`gift-box`、`basket`、`smoke-puff` 5 张视觉包装图,该请求属于长耗时 image-2 链路。前端 `babyObjectMatchClient` 对该 POST 使用 10 分钟请求超时,且不做自动重试,避免第一次生成仍在后端执行时又发起第二次重复生成。后端同时启动物品图与视觉主题包生成,并把该路由的 VectorEngine 单图请求等待预算提升到至少 8 分钟,避免某张图 3 分钟附近仍在生成时被后端提前断开。后端日志记录每类资源的开始、完成和耗时,排查时优先按同一次 HTTP 请求查看 `宝贝识物 image-2 物品资源生成完成`、`宝贝识物 image-2 视觉资源生成完成` 与 `VectorEngine 图片生成上游错误`。
|
||||
|
||||
历史本地草稿中若已保存 `generationProvider = "placeholder"` 的旧占位资源,结果页必须提示“重新生成 image-2 资源”,并禁用试玩和发布。用户点击重新生成、发布或试玩前,前端统一调用 `regenerateBabyObjectMatchDraftAssets(draft)` 补齐资源;补齐失败时保留在结果页并展示错误。
|
||||
|
||||
后续正式作品持久化接入时,应补齐:
|
||||
|
||||
```text
|
||||
POST /api/creation/edutainment/baby-object-match/drafts
|
||||
@@ -88,6 +146,25 @@ POST /api/creation/edutainment/baby-object-match/drafts/{draftId}/publish
|
||||
|
||||
图片生成必须在后端调用 VectorEngine `gpt-image-2-all`,不得从前端直接调用外部图片接口。
|
||||
|
||||
后端 image-2 prompt 约束:
|
||||
|
||||
1. 锁定寓教于乐板块统一的卡通绘本草地舞台插画风;
|
||||
2. 每张图只能围绕对应关键词生成一个单一物品;
|
||||
3. 不生成背景、场景、氛围渲染、人物、手、篮子、礼物盒、文字、水印或 UI;
|
||||
4. 优先要求纯白或透明抠图友好的干净背景,服务端再统一转透明 PNG 并执行背景 alpha 清理;
|
||||
5. 返回 `generationProvider = "vector-engine-gpt-image-2"` 的素材必须已经完成透明抠图。
|
||||
|
||||
后端视觉主题包 prompt 约束:
|
||||
|
||||
1. 同一次请求根据两个物品关键词生成 `background`、`ui-frame`、`gift-box`、`basket`、`smoke-puff` 五类资源;
|
||||
2. 总风格继续锁定寓教于乐明亮卡通绘本插画风;
|
||||
3. 若关键词偏动漫角色、玩具或公仔,背景环境和 UI 元素匹配动漫、玩具主题;若关键词偏水果,匹配果园、自然主题;其它关键词按语义匹配合适主题;
|
||||
4. 背景环境图使用非透明 16:9 图,但必须保证中间、中下方和底部左右篮子区域清爽,给放大后的礼物盒、中央物品和左右篮子预留空间,不画入礼物盒、篮子、物品、人物、文字或操作 UI;
|
||||
5. UI 装饰框、礼物盒、篮子和烟雾弹出特效使用透明 PNG 后处理,不生成文字、数字、按钮、人物或待分类物品;
|
||||
6. `gift-box` 提示词必须面向运行态约 2 倍视觉尺寸生成主体饱满的大号礼物盒,`basket` 提示词必须面向运行态约 1.5 倍视觉尺寸生成可读性高的大号篮子;
|
||||
7. `smoke-puff` 只生成礼物盒打开瞬间使用的柔和烟雾云朵特效,不生成礼物盒、篮子、物品或文字;
|
||||
8. 左右篮子的固定选项规则不受主题包影响,运行态只把 `basket` 作为篮子造型包装复用。
|
||||
|
||||
## 5. UI 边界
|
||||
|
||||
工作台只展示两个必填输入和生成按钮。
|
||||
@@ -107,27 +184,37 @@ src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx
|
||||
运行态直接消费 `BabyObjectMatchDraft`,必须使用草稿中的两个物品名称和物品图。
|
||||
每轮只随机当前从礼物盒跳出的物品;左右篮子不随机交换,左侧固定为草稿 `itemAssets[0]`,右侧固定为草稿 `itemAssets[1]`。
|
||||
|
||||
若草稿包含 `visualPackage`,运行态通过背景图片层、CSS 变量和图片节点消费:
|
||||
|
||||
1. `background`:作为舞台最底层 `ResolvedAssetImage` 背景图;存在该资源时必须关闭默认草地兜底层,避免生成场景被 CSS 草地遮住或弱化;
|
||||
2. `ui-frame`:作为字幕条和计数器装饰背景;
|
||||
3. `gift-box`:替换 CSS 礼物盒主体,按旧视觉约 2 倍尺寸展示,只在礼盒入场和打开阶段存在;
|
||||
4. `basket`:替换篮子主体造型,按旧视觉约 1.5 倍尺寸展示,左右两侧复用同一张主题篮子图;
|
||||
5. `smoke-puff`:作为礼物盒打开和中央物品弹出期间的透明烟雾特效资源。
|
||||
|
||||
旧草稿或接口失败时 `visualPackage = null`,运行态继续使用现有 CSS 绘本风兜底。
|
||||
|
||||
首关状态机:
|
||||
|
||||
1. `waiting`:礼物盒关闭,等待任意手抬起;
|
||||
2. `active`:当前物品停留在屏幕中央;
|
||||
3. `correct`:展示“真棒”反馈,成功次数加 1;
|
||||
4. `wrong`:展示“再想一想吧”反馈,当前物品回到中央;
|
||||
5. `complete`:成功次数达到 20,展示“恭喜你!小朋友!”和按钮。
|
||||
1. `gift-entering`:礼物盒从上方落下入场动画阶段,不接受动作判定;
|
||||
2. `gift-opening`:礼物盒打开并播放烟雾特效阶段,不接受动作判定;
|
||||
3. `item-appearing`:礼物盒从舞台移除,当前物品从烟雾中出现并停稳,不接受动作判定;
|
||||
4. `active`:物品彻底出现后才开放选篮判定;
|
||||
5. `correct`:展示“真棒”反馈,对应篮筐播放正确特效并停顿,成功次数加 1;特效完全结束后重新进入 `gift-entering`,下一轮礼物盒从上方落下;
|
||||
6. `wrong`:展示“再想一想吧”反馈,物品弹回中央;反馈结束后回到 `active`,不重新随机物品;
|
||||
7. `complete`:成功次数达到 20,展示“恭喜你!小朋友!”和按钮。
|
||||
|
||||
动作输入:
|
||||
|
||||
1. 任意手完成一次 `open_palm -> grab` 抓握序列:打开礼物盒并生成当前物品;
|
||||
2. 左手连续横向移动达到阈值:将当前物品送入左侧篮子;
|
||||
3. 右手连续横向移动达到阈值:将当前物品送入右侧篮子。
|
||||
1. 左手连续横向移动达到阈值:将当前物品送入左侧篮子;
|
||||
2. 右手连续横向移动达到阈值:将当前物品送入右侧篮子。
|
||||
|
||||
运行态直接通过 `useMocapInput` 消费本地 mocap WebSocket `/stream`。选篮只使用明确 `leftHand` 或 `rightHand` 的连续横向轨迹阈值,不再通过 `wave_left_hand`、`wave_right_hand`、`wave` 等动作名触发;侧别为 `unknown` 的手部轨迹也不参与选篮,以避免多套判定误命中和连续误触发。当前本地 mocap 输出的 handedness 按摄像头视角标记,宝贝识物运行态必须先换算为用户身体视角:`rightHand` 轨迹映射玩家左手并进入左侧篮子,`leftHand` 轨迹映射玩家右手并进入右侧篮子。草稿试玩、发布后正式体验和热身关后的本地 Demo 都复用同一个运行态,因此三条入口都必须具备同一套动作控制能力。
|
||||
运行态直接通过 `useMocapInput` 消费本地 mocap WebSocket `/stream`。选篮只使用明确 `leftHand` 或 `rightHand` 的连续横向轨迹阈值,不再通过 `wave_left_hand`、`wave_right_hand`、`wave` 等动作名触发;侧别为 `unknown` 的手部轨迹也不参与选篮,以避免多套判定误命中和连续误触发。动作判定只在 `active` 阶段开放,礼盒入场、礼盒打开、物品出现、正确反馈和错误反馈阶段收到的动作包必须清空轨迹并忽略,不允许跨阶段补判定。当前本地 mocap 输出的 handedness 按摄像头视角标记,宝贝识物运行态必须先换算为用户身体视角:`rightHand` 轨迹映射玩家左手并进入左侧篮子,`leftHand` 轨迹映射玩家右手并进入右侧篮子。草稿试玩、发布后正式体验和热身关后的本地 Demo 都复用同一个运行态,因此三条入口都必须具备同一套动作控制能力。
|
||||
|
||||
开发者调试输入:
|
||||
|
||||
1. `F`:映射任意手抬起,打开礼物盒并生成当前物品;
|
||||
2. 鼠标左键按下并拖动:映射左手轨迹,抬起后将当前物品送入左侧篮子;
|
||||
3. 鼠标右键按下并拖动:映射右手轨迹,抬起后将当前物品送入右侧篮子。
|
||||
1. 鼠标左键按下并拖动:映射左手轨迹,抬起后将当前物品送入左侧篮子;
|
||||
2. 鼠标右键按下并拖动:映射右手轨迹,抬起后将当前物品送入右侧篮子。
|
||||
|
||||
运行态不得新增计时、失败次数、分数、体力或难度递增规则。
|
||||
|
||||
@@ -154,6 +241,7 @@ src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx
|
||||
|
||||
```bash
|
||||
npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts src/components/edutainment-creation/BabyObjectMatchWorkspace.test.tsx src/components/edutainment-result/BabyObjectMatchResultView.test.tsx src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.test.tsx src/components/child-motion-demo/ChildMotionWarmupDemo.test.tsx src/services/edutainment-baby-object/babyObjectMatchClient.test.ts
|
||||
cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml
|
||||
npx vitest run src/components/platform-entry/platformEdutainmentVisibility.test.ts src/components/platform-entry/PlatformWorkDetailView.test.tsx src/components/custom-world-home/creationWorkShelf.test.ts src/services/useMocapInput.test.ts src/services/child-motion-demo/childMotionDebugInput.test.ts src/routing/appRoutes.test.ts
|
||||
npx eslint src/components/platform-entry/platformEntryCreationTypes.ts src/components/platform-entry/platformEntryCreationTypes.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --ext .ts,.tsx --max-warnings 0
|
||||
npm run check:encoding
|
||||
|
||||
@@ -170,6 +170,9 @@
|
||||
3. 位置类状态必须满足“到达绿色圆环并保持 2 秒”。
|
||||
4. 动作类状态没有最长等待时间。
|
||||
5. 动作类状态等待 3 秒后可以播放对应引导动画。
|
||||
6. 每个步骤进入时需要先展示本步骤文字字幕和语音播报入口,约 1 秒后再进入可交互阶段并展示绿色圆环、手势引导等检测提示。
|
||||
7. 步骤完成后需要先进入完成停顿阶段,当前停顿约 0.8 秒;停顿期间保留完成反馈位置,后续可在该阶段补充完成特效或音效,再切换到下一步骤。
|
||||
8. 入场等待和完成停顿阶段不消费动作完成判定,避免用户上一步残留动作直接触发下一步。
|
||||
|
||||
### 6.3 开发者调试输入
|
||||
|
||||
@@ -368,6 +371,17 @@
|
||||
|
||||
用户完成挥动左手。
|
||||
|
||||
当前本地 mocap 的 handedness 按摄像头视角输出,热身关内需要先换算成用户身体视角再判断:摄像头右侧手对应用户左手。挥动左手不是普通横向轨迹检测,而是用于确认现实环境中用户左侧手臂打开空间足够和安全。
|
||||
|
||||
完成条件必须同时满足:
|
||||
|
||||
1. 使用用户身体左手轨迹。
|
||||
2. 手腕在左肩外侧达到最小外展距离。
|
||||
3. 手腕不能处于自然下垂低位。
|
||||
4. 最近连续有效帧中,手臂存在足够上下摆动幅度。
|
||||
5. 最近连续有效帧中,肩膀到手腕向量的角度变化达到阈值。
|
||||
6. 至少出现一次上下摆动方向变化。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
```text
|
||||
@@ -376,7 +390,7 @@
|
||||
|
||||
#### 数据记录
|
||||
|
||||
记录用户挥动左手的空间,保存为该用户对应的行为坐标。
|
||||
记录用户挥动左手的轨迹、空间包络、角度范围和最大外展距离,保存为该用户对应的行为坐标。
|
||||
|
||||
---
|
||||
|
||||
@@ -398,6 +412,17 @@
|
||||
|
||||
用户完成挥动右手。
|
||||
|
||||
当前本地 mocap 的 handedness 按摄像头视角输出,热身关内需要先换算成用户身体视角再判断:摄像头左侧手对应用户右手。挥动右手不是普通横向轨迹检测,而是用于确认现实环境中用户右侧手臂打开空间足够和安全。
|
||||
|
||||
完成条件必须同时满足:
|
||||
|
||||
1. 使用用户身体右手轨迹。
|
||||
2. 手腕在右肩外侧达到最小外展距离。
|
||||
3. 手腕不能处于自然下垂低位。
|
||||
4. 最近连续有效帧中,手臂存在足够上下摆动幅度。
|
||||
5. 最近连续有效帧中,肩膀到手腕向量的角度变化达到阈值。
|
||||
6. 至少出现一次上下摆动方向变化。
|
||||
|
||||
#### 完成反馈
|
||||
|
||||
```text
|
||||
@@ -406,7 +431,7 @@
|
||||
|
||||
#### 数据记录
|
||||
|
||||
记录用户挥动右手的空间,保存为该用户对应的行为坐标。
|
||||
记录用户挥动右手的轨迹、空间包络、角度范围和最大外展距离,保存为该用户对应的行为坐标。
|
||||
|
||||
---
|
||||
|
||||
@@ -653,18 +678,21 @@
|
||||
3. 鼠标左键按下并拖动映射左手轨迹。
|
||||
4. 鼠标右键按下并拖动映射右手轨迹。
|
||||
5. 空格键映射原地跳跃。
|
||||
6. 调试输入只在步骤可交互阶段触发步骤完成;步骤入场字幕阶段和完成停顿阶段会忽略完成判定,便于观察节奏和后续补充特效。
|
||||
|
||||
当前硬件和动作检测接口接入:
|
||||
|
||||
1. 浏览器摄像头视频流已接入舞台背景。
|
||||
2. 热身关全流程已通过 `src/services/useMocapInput.ts` 接入本地 mocap WebSocket `/stream`;动作数据源状态优先于浏览器背景摄像头状态展示。
|
||||
3. mocap 包支持从 `general.body.center_norm` 读取身体中心,位置类步骤使用该身体中心更新角色剪影横向位置并完成圆环保持检测。
|
||||
4. mocap 包支持从 `actions/action/gesture/gestures/event/name/type` 读取动作名,并支持 `hands[]`、`leftHand/rightHand`、`left_hand/right_hand` 读取左右手坐标。
|
||||
5. `hands[].landmarks` 存在时优先用手腕和 MCP 点计算掌心中心;掌心点不足时退回 wrist landmark,再退回 hand 直出坐标。
|
||||
6. `wave_greeting` 可由 `wave/wave_greeting/hand_wave/open_palm` 等动作或 open palm 手势完成。
|
||||
7. `wave_left_hand` 和 `wave_right_hand` 优先消费对应左右手动作名;当硬件只持续输出手部坐标时,也可以根据连续手部横向轨迹完成挥手检测。
|
||||
8. `jump_once` 消费 `jump/jump_once/hop` 等跳跃动作事件完成。
|
||||
9. 键盘 `A/D/Space` 与鼠标左右键拖拽仍保留为本地 Demo 调试兜底,不代表正式硬件口径。
|
||||
4. 身体中心横向坐标进入角色剪影前必须做输入稳定化处理:先 clamp 到 `0..1`,再使用小幅死区、低通阻尼和单包最大步长限制,避免硬件噪声造成角色左右误判、画面抽搐或视觉上的忽大忽小。当前实现参数为死区 `0.012`、阻尼系数 `0.28`、单包最大步长 `0.035`;位置保持检测使用稳定化后的角色坐标。
|
||||
5. 角色剪影渲染需要把水平位移和跳跃表现拆开:外层只负责横向定位,内层资源只负责轮廓图和跳跃位移,避免 `left` 与 `transform` 同时抢占导致半透明资源重采样抖动。
|
||||
6. mocap 包支持从 `actions/action/gesture/gestures/event/name/type` 读取动作名,并支持 `hands[]`、`leftHand/rightHand`、`left_hand/right_hand` 读取左右手坐标。
|
||||
7. `hands[].landmarks` 存在时优先用手腕和 MCP 点计算掌心中心;掌心点不足时退回 wrist landmark,再退回 hand 直出坐标。
|
||||
8. `wave_greeting` 只消费左手、右手或未知单手的连续横向挥手轨迹,不再使用 `wave`、`hand_wave`、`open_palm`、张手状态或动作名直接完成判定;进入轨迹判定前必须先满足抬手有效区:优先使用 `hands[].landmarks.wrist` 与 `general.limb_nodes` 的同侧 `*_elbow` / `*_shoulder` 判断,当前阈值为 `wrist.y <= elbow.y + 0.04`,缺少肘部时使用 `wrist.y <= shoulder.y + 0.08`;缺少同侧肘部和肩膀参考时不允许招呼通过,不再使用身体中心兜底判断抬手。轨迹阈值为至少 5 个连续抬手点,横向 `x` 范围差值不小于 `0.075`,且至少出现 1 次横向方向变化,避免“手刚露出画面”或“手自然下垂抖动”被误判为招手。
|
||||
9. `wave_left_hand` 和 `wave_right_hand` 只消费用户身体侧对应手的连续坐标轨迹,不再使用动作名、张手状态或 primary hand 兜底完成判定;本地 mocap handedness 当前按摄像头视角输出,因此用户左手使用 camera-right,用户右手使用 camera-left。完成判定必须同时满足对应肩肘腕外展、手腕非自然下垂、连续有效帧、横向范围、上下摆动范围、肩腕角度范围和上下方向变化,当前阈值为连续外展点不少于 5 个、横向 `x` 范围不小于 `0.055`、垂直 `y` 范围不小于 `0.08`、肩腕角度范围不小于 `28°`、外展距离不小于 `0.12`、手腕相对肩膀外侧距离不小于 `0.1`;后续以真实体验结果继续调参。
|
||||
10. `jump_once` 消费 `jump/jump_once/hop` 等跳跃动作事件完成。
|
||||
11. 键盘 `A/D/Space` 与鼠标左右键拖拽仍保留为本地 Demo 调试兜底,不代表正式硬件口径。
|
||||
|
||||
当前未接入但已保留边界:
|
||||
|
||||
@@ -682,14 +710,17 @@
|
||||
5. 当前已生成并接入以下正式 Demo 资源:
|
||||
- `public/child-motion-demo/picture-book-grass-stage.png`:默认草地舞台背景。
|
||||
- `public/child-motion-demo/picture-book-foreground-grass-v2.png`:底部前景草坪条,只覆盖舞台下沿,不作为整块地板拉伸。
|
||||
- `public/child-motion-demo/picture-book-ground-ring-v2.png`:已按透视绘制的地面椭圆指示环,CSS 只等比缩放。
|
||||
- `public/child-motion-demo/picture-book-ground-ring-v3.png`:已按透视绘制的浅蓝与暖黄色地面椭圆指示环,和草地材质做明显区分,CSS 只等比缩放。
|
||||
- `public/child-motion-demo/picture-book-character-outline-v2.png`:半透明用户角色轮廓,使用独立去背后处理避免内部填充被误删。
|
||||
- `public/child-motion-demo/picture-book-hud-strip-v2.png`:顶部 HUD 细长软纸条。
|
||||
- `public/child-motion-demo/picture-book-calibration-strip-v2.png`:右下角五格热身状态条。
|
||||
- `public/child-motion-demo/picture-book-start-panel-v2.png`:开始按钮背后的轻盈托盘。
|
||||
- `public/child-motion-demo/picture-book-ui-button-v2.png`:开始按钮绘本风按钮底图。
|
||||
- `public/child-motion-demo/picture-book-wave-cat-body-guide-v6.png`:招手阶段中央猫咪身体底座资源,按可动纸偶结构只包含猫头、短身体和肩部连接点,不再和旧猫头、胸口或猫爪资源叠加。
|
||||
- `public/child-motion-demo/picture-book-wave-cat-arm-guide-v6.png`:招手阶段左右独立手臂资源,也用于左右手阶段单手提示;网页用同一拆件镜像复用,并围绕肩部挂点做挥手摆动动画。
|
||||
6. v2 资源按最终用途拆分,CSS 必须按资源原始比例、`aspect-ratio` 或 `background-size: contain / auto` 等方式等比使用;禁止把方形面板强行拉伸为 HUD、状态条或地板,也禁止把底部草坪扩展成覆盖角色脚下的大色块。
|
||||
7. 若后续补充或重绘资源,应先运行 `npm run assets:child-motion-demo -- --dry-run` 核对 prompt 和输出路径,再使用 `--live --only <asset-id>` 小批量生成;仅调整透明去背、裁切、画布归一或品红边缘时,可用 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>` 复用 `tmp/child-motion-demo-assets/` 中的源图,不额外请求 image-2;不得把 `VECTOR_ENGINE_API_KEY`、源图或中间预览图提交到仓库。
|
||||
7. 猫咪招手引导资源使用 `cat-guide` 透明后处理:先由 image-2 生成品红底源图,再通过边缘背景连通区域去背,避免把浅粉、淡橘和暖棕主体误删。源图只保存在 `tmp/child-motion-demo-assets/`,正式页面只引用 `public/child-motion-demo/` 下的最终 PNG。
|
||||
8. 若后续补充或重绘资源,应先运行 `npm run assets:child-motion-demo -- --dry-run` 核对 prompt 和输出路径,再使用 `--live --only <asset-id>` 小批量生成;仅调整透明去背、裁切、画布归一或品红边缘时,可用 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>` 复用 `tmp/child-motion-demo-assets/` 中的源图,不额外请求 image-2;不得把 `VECTOR_ENGINE_API_KEY`、源图或中间预览图提交到仓库。
|
||||
|
||||
已执行的定向验证命令:
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ process didn't exit successfully: `server-rs\target\debug\api-server.exe`
|
||||
|
||||
主站和后台 Vite 也追加 `--strictPort`,避免默认漂移到 `3001`、`3103` 等端口后让浏览器继续访问旧页面。
|
||||
|
||||
`--skip-spacetime` 是复用既有 SpacetimeDB 宿主的模式。该模式下脚本不会再把 SpacetimeDB 端口纳入可用性漂移;如果传入 `--spacetime-port 3101`,后端就会连接 `http://127.0.0.1:3101`。这可以避免 `3101` 已有 SpacetimeDB 在线时,端口工具误把它当作冲突并改到空闲的 `3102`,导致 api-server 连接空端口后 `/api/creation-entry/config`、作品架和公开图库接口同时返回 `502`。
|
||||
|
||||
## 排障步骤
|
||||
|
||||
PowerShell 查看默认端口占用:
|
||||
@@ -72,3 +74,4 @@ node scripts/run-bash-script.mjs scripts/dev-rust-stack.sh \
|
||||
1. `bash -n scripts/dev-rust-stack.sh` 通过。
|
||||
2. 默认端口被占用时重新运行完整栈,脚本应在 publish 前失败并打印占用进程。
|
||||
3. 清理占用进程或换端口后,重新启动时不再出现 Vite 端口漂移或 `api-server` `AddrInUse`。
|
||||
4. 复用 SpacetimeDB 时执行 `npm run dev -- --skip-spacetime --skip-publish --spacetime-port 3101`,启动日志里的 `[dev:rust] spacetime:` 应保持为 `http://127.0.0.1:3101`,并且 `GET /api/creation-entry/config` 不应因连接 `3102` 这类空端口而失败。
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
1. 新建作品入口配置统一存放在 SpacetimeDB 的 `creation_entry_config` / `creation_entry_type_config` 表;默认种子位于 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs`。
|
||||
2. `visible` 控制玩法是否展示在创作 Tab 模板入口、新建作品入口和创作类型弹层中。
|
||||
3. `open` 控制玩法是否允许点击创建以及对应创作 / runtime API 路由是否放行;`open: false` 时入口保持展示但禁用,并由 `api-server` 熔断对应玩法 API。
|
||||
- 前端作品架、发现页聚合和预加载只应请求 `open: true` 的玩法接口;`open: false` 的未开放玩法可以展示为敬请期待入口,但不得把对应 API 熔断错误透传到草稿页或发现页。
|
||||
4. `title`、`subtitle`、`badge` 控制玩法卡片文案。
|
||||
5. `startCard` 控制旧创作中心顶部新建作品模块的标题、辅助文案和移动端角标文案;当前创作 Tab 首屏标题固定在 `PlatformEntryFlowShellImpl.tsx`,不再由 `startCard` 控制。
|
||||
6. `typeModal` 控制平台创作类型弹层标题和描述。
|
||||
@@ -28,6 +29,21 @@
|
||||
| AIRP | 是 | 否 | 保留入口,显示敬请期待 |
|
||||
| 视觉小说 | 是 | 否 | 保留入口,显示敬请期待,暂不允许创建视觉小说草稿 |
|
||||
| 智能创作 | 否 | 是 | 入口隐藏,既有 `creative-agent` 链路保留 |
|
||||
| 宝贝识物 | 是 | 是 | 寓教于乐首关模板,必须由 `creation_entry_type_config` 默认种子或后台入口开关保持存在 |
|
||||
|
||||
## 排障约束
|
||||
|
||||
`baby-object-match` 是寓教于乐创作模板和发现页宝贝识物作品合流的共同开关。若 `creation_entry_type_config` 缺少该行,前端会同时出现“创作界面没有宝贝识物模板”和“寓教于乐分类下已发布作品消失”的现象。
|
||||
|
||||
默认种子必须包含:
|
||||
|
||||
- `id = baby-object-match`
|
||||
- `title = 宝贝识物`
|
||||
- `visible = true`
|
||||
- `open = true`
|
||||
- `sort_order = 90`
|
||||
|
||||
入口图首版复用 `/child-motion-demo/picture-book-grass-stage.png`,后续如生成专属 image-2 入口图,可通过后台入口开关更新 `imageSrc`。
|
||||
|
||||
## 验收
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
## 文档列表
|
||||
|
||||
- [WECHAT_MINIPROGRAM_WEB_VIEW_SHELL_2026-05-03.md](./WECHAT_MINIPROGRAM_WEB_VIEW_SHELL_2026-05-03.md):记录微信小程序 `web-view` 壳的最小接入范围、需要填写的 H5 业务域名、微信后台配置、`npm run check:wechat-miniprogram-auth` 可重复登录链路 smoke 和后续原生化边界。
|
||||
- [BABY_LOVE_DRAWING_RUNTIME_DEMO_IMPLEMENTATION_2026-05-13.md](./BABY_LOVE_DRAWING_RUNTIME_DEMO_IMPLEMENTATION_2026-05-13.md):冻结寓教于乐 `宝贝爱画` 独立本地 Demo 运行态实现方案,明确发现页默认卡片、`/runtime/baby-love-drawing` 路由、画板交互、mocap/键鼠调试映射、本地保存和 VectorEngine image-2 绘画魔法后端代理。
|
||||
- [BARK_BATTLE_BACKEND_DDD_TECHNICAL_PLAN_2026-05-11.md](./BARK_BATTLE_BACKEND_DDD_TECHNICAL_PLAN_2026-05-11.md):冻结“汪汪声浪大作战 / bark-battle”后端 DDD 技术方案,明确 `server-rs + Axum + SpacetimeDB` 分层边界、shared contracts、作品配置、runtime run、派生成绩、排行榜、`work_play_start` 埋点、migration/绑定生成策略,以及不保存原始麦克风音频的隐私与反作弊约束。
|
||||
- [BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md](./BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md):冻结“汪汪声浪大作战 / bark-battle”2D 浏览器 runtime 技术方案,明确 Phaser + TypeScript + Vite 选型、纯 TS simulation 与 Phaser renderer/DOM HUD 边界、Web Audio 输入适配、移动端权限降级和后续测试验证命令。
|
||||
- [PUBLIC_WORK_DETAIL_NOT_FOUND_RECOVERY_2026-05-11.md](./PUBLIC_WORK_DETAIL_NOT_FOUND_RECOVERY_2026-05-11.md):记录直接访问公开作品详情深链时作品不存在或已下架的回首页修复,避免关闭提示后停在 `work-detail` 空状态白屏。
|
||||
|
||||
@@ -36,8 +36,9 @@ npm run dev:rust
|
||||
|
||||
1. `npm run dev` / `npm run dev:rust` 会先检查 SpacetimeDB、Rust `api-server`、主站 Vite、后台 Vite 需要使用的端口。
|
||||
2. 如果优先端口不可用,脚本会从该端口开始向后寻找可用端口,并将解析后的端口覆盖到后续 `spacetime start`、`spacetime publish --server`、`GENARRATIVE_API_PORT`、`RUST_SERVER_TARGET`、`GENARRATIVE_RUNTIME_SERVER_TARGET`、`ADMIN_API_TARGET` 与 Vite 启动参数。
|
||||
3. 控制台会打印 `[dev:ports] ... 可用` 或 `[dev:ports] ... 不可用,改用 ...`,排查代理错配时以该日志和后续 `[dev:rust] web/admin web/rust api/spacetime` 实际地址为准。
|
||||
4. 单独 `npm run dev:web` 也会检查主站 Vite 端口;`WEB_PORT` 或默认 `3000` 不可用时,会自动切到后续可用端口并继续严格端口启动。
|
||||
3. 显式传入 `--skip-spacetime` 时,脚本不会对 SpacetimeDB 端口做可用性漂移;此时 `--spacetime-port` 表示要复用的既有 SpacetimeDB 地址。后端会直接使用该地址,避免 `3101` 已有可用宿主时被误改到空闲但未启动的 `3102`。
|
||||
4. 控制台会打印 `[dev:ports] ... 可用` 或 `[dev:ports] ... 不可用,改用 ...`,排查代理错配时以该日志和后续 `[dev:rust] web/admin web/rust api/spacetime` 实际地址为准。
|
||||
5. 单独 `npm run dev:web` 也会检查主站 Vite 端口;`WEB_PORT` 或默认 `3000` 不可用时,会自动切到后续可用端口并继续严格端口启动。
|
||||
|
||||
默认流程:
|
||||
|
||||
@@ -70,7 +71,7 @@ Vite 代理覆盖范围:
|
||||
|
||||
本地联调跳过策略:
|
||||
|
||||
1. 如果 `3101` 已被当前可复用的 SpacetimeDB standalone 占用,脚本会优先按 `spacetime.pid` 与 `dev-rust-spacetime-url` 复用该宿主;如果确认不是可复用宿主,则会先输出占用进程并选择最近可用端口。也可显式使用 `npm run dev -- --skip-spacetime` 跳过 SpacetimeDB 宿主启动,或用 `--spacetime-port` 指定起始探测端口。
|
||||
1. 如果 `3101` 已被当前可复用的 SpacetimeDB standalone 占用,脚本会优先按 `spacetime.pid` 与 `dev-rust-spacetime-url` 复用该宿主;如果确认不是可复用宿主,则会先输出占用进程并选择最近可用端口。也可显式使用 `npm run dev -- --skip-spacetime --spacetime-port 3101` 跳过 SpacetimeDB 宿主启动并复用 `http://127.0.0.1:3101`。在 `--skip-spacetime` 模式下,`--spacetime-port` 不是起始探测端口,而是必须已经在线的目标端口。
|
||||
2. 如果当前没有修改 `server-rs/crates/spacetime-module`,可使用 `npm run dev -- --skip-publish` 跳过数据库发布,降低本地启动时的 SpacetimeDB wasm 编译耗时。
|
||||
3. 如果当前阶段只需要检查 `spacetime-module` 语法,不需要重新发布本地数据库,可执行 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`。该命令只做 Rust 编译检查,不生成新数据库,也不刷新 bindings。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user