@uzooai/anime-ai-cli 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/uzoo-anime-cli/references/commands.md +2 -2
- package/.agents/skills/uzoo-anime-cli/references/material-graph-workflow.md +1 -1
- package/.agents/skills/uzoo-anime-cli/references/node-workflow.md +3 -6
- package/CHANGELOG.md +14 -6
- package/README.md +26 -22
- package/docs/Agent/346/216/245/345/205/245/344/270/216/347/211/210/346/234/254/346/233/264/346/226/260.md +23 -25
- package/package.json +3 -2
- package/src/canvas.js +3 -1
- package/src/materials.js +1 -1
- package/src/node-placement.js +78 -0
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
| `model list` | 必填 `--space personal` 或 `--space team --team-uid UID`;可选 `--type image/video/text/audio`;以返回 `items[].label` 为模型名称,按目录能力选择参数 |
|
|
22
22
|
| `canvas list` / `canvas get ID` | 列表必填 `--space personal` 或 `--space team --team-uid UID`,可带 `--project-id ID --cursor CURSOR --limit 1..100`;个人空间不能带团队/项目;生成使用已有或 CLI 创建的文档和节点 |
|
|
23
23
|
| `canvas create` | 必填 `--name NAME` 及 `--space personal` 或 `--space team --team-uid UID`;团队可选 `--project-id ID`(映射 canvas_project_id);返回完整画布及 id/version,不发起生成 |
|
|
24
|
-
| `node add` | `--doc ID --type TYPE`;仅支持普通节点 text/image/video/audio、image_upload/video_upload/audio_upload、video-repaint;可选 `--node-id ID --input node-patch.json`;默认生成 UUID 节点 ID
|
|
24
|
+
| `node add` | `--doc ID --type TYPE`;仅支持普通节点 text/image/video/audio、image_upload/video_upload/audio_upload、video-repaint;可选 `--node-id ID --input node-patch.json`;默认生成 UUID 节点 ID,未指定 `position` 时自动找空位,返回 doc_id/node_id/version/node;派生节点由真实业务流程创建 |
|
|
25
25
|
| `node update` | `--doc ID --node-id ID --input node-patch.json`;只更新指定节点,不改 id/type,返回同上 |
|
|
26
26
|
| `resource upload` | `--file FILE --doc ID --type image|video|audio --mime MIME`;普通素材节点加 `--node ID`,成功后补齐网页持久化引用,检查 canvas_bound;旧画布资产接口的图片加 `--asset-type character|scene|prop --name NAME`。返回 API 的资源引用,按实际返回字段使用 |
|
|
27
27
|
| `resource bind` | `--doc ID --node ID --input upload-response.json`;复用真实上传响应,补齐普通素材节点的持久化媒体字段,不重新上传 |
|
|
28
28
|
| `material folders/list/get ID` | 必填 `--space personal` 或 `--space team --team-uid UID`;list 可带 `--folder-id ID --keyword TEXT --limit 1..200 --offset N`,get 返回同空间素材 |
|
|
29
29
|
| `material download ID` | 必填空间参数及--output FILE,可选--sha256 HEX;核对目录/素材归属,再请求素材专属download入口,匿名下载原格式完整url,返回output/bytes/sha256/asset_id/format/可选filename及无画布/生成/入库副作用标记。仅original,不支持自定义服务端名称或转换;拒绝不回退list/get的URL |
|
|
30
30
|
| `material upload` | `--file FILE --mime MIME --folder-id ID --name NAME` 及空间参数;上传到独立素材空间,先核对实时限制和目标目录 |
|
|
31
|
-
| `material use ID` | `--doc ID --node-id FIXED_ID` 及空间参数;可选 `--input position.json
|
|
31
|
+
| `material use ID` | `--doc ID --node-id FIXED_ID` 及空间参数;可选 `--input position.json`,未指定 `position` 时自动找空位;从库导入普通素材节点,复用真实引用,不重传 |
|
|
32
32
|
| `material save` | `--input save.json --operation NAME` 默认只预检;加 --yes 直接保存原节点结果到素材库。输入 doc_id/node_id/space、团队 team_uid、folder_id,可选 name/description;限同空间已完成图片/视频真实任务;持久化原请求/幂等键,不写画布 |
|
|
33
33
|
| `material save-get NAME` | 查询原入库 operation,已有 asset_id 时核验原素材,返回 state/task_id/folder_id/asset_id/asset/可选 last_failure;不发保存 POST |
|
|
34
34
|
| `material save-recover NAME` | 默认只查询;未知结果且没有 asset_id 时,明确恢复才加 --yes 沿用原请求与幂等键。已知 asset_id 仅查询,rejected 不自动重提;账号/服务/来源改变停止 |
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
4. `canvas_bound=false` 表示文件已上传但兼容字段保存未确认。先读回节点;凭原始真实上传响应执行 `resource bind --doc DOC_ID --node NODE_ID --input upload-response.json` 仅修复原节点,不重传文件。响应须与画布/节点/媒体类型绑定,已有其他素材引用时停止。已打开网页可能持有旧快照;若出现节点或引用在后续快照反复消失,先暂停写入并协调关闭该画布页面,再核对是否为用户主动删除。用户允许恢复后才恢复缺失节点;优先复用原上传/原生成任务,不能通过重做生成弥补显示缺失。CAS 冲突不自动覆盖。
|
|
17
17
|
5. 网页当前“素材库”使用独立素材空间 `/material-space`,不同于旧的 `/canvas/docs/.../assets`。使用 `material folders/list/get`,每次明确 `--space personal` 或 `--space team --team-uid UID`;分页为 limit/offset。目录来自实际列表,不猜文件夹 ID。团队没有素材时不能自动退回个人素材库。
|
|
18
18
|
6. 素材库上传用 `material upload --file FILE --mime MIME --folder-id FOLDER_ID --name NAME --space team --team-uid UID`。CLI 校验同空间目录和实时上传限制,用通用直传会话上传再落素材空间。结果不明先查原目录/名称,不盲目重传;当前没有素材库分片恢复命令。
|
|
19
|
-
7. 从库使用 `material use ASSET_ID --doc DOC_ID --node-id FIXED_ID --space team --team-uid UID
|
|
19
|
+
7. 从库使用 `material use ASSET_ID --doc DOC_ID --node-id FIXED_ID --space team --team-uid UID`;默认在画布空位放置,可选 `--input position.json`(`{"position":{"x":120,"y":130}}`)指定位置。校验素材与画布同空间,从真实响应创建普通素材节点,复用 URL/file_ref,不重新上传。固定节点 ID 便于结果未知时读回核对。当前不支持跨空间导入、素材删除/迁移/收藏或文件夹编辑,不用临时 API 调用冒充已支持命令。
|
|
20
20
|
|
|
21
21
|
## 整理、分组和组合输出
|
|
22
22
|
|
|
@@ -36,19 +36,16 @@ CLI 业务命令直接通过 HTTP 调用后端,不加载网页或执行前端
|
|
|
36
36
|
## 执行顺序
|
|
37
37
|
|
|
38
38
|
1. 复用有效登录,`canvas get DOC_ID` 核对 `team_uid` 与已确认空间一致,确认有写权限。记录当前版本、已有节点、边、视口和 meta;不要清空画布。
|
|
39
|
-
2.
|
|
40
|
-
3. UTF-8 保存 `node-patch.json`,例如:
|
|
39
|
+
2. 确定节点类型和标题。用户未指定坐标时省略 `position`,由 CLI 根据最新快照避开已有节点和分组;指定坐标时原样使用。每个创建意图先记录固定 `--node-id` 与输入文件,再写入,便于断连后识别。
|
|
40
|
+
3. 如需自定义字段,UTF-8 保存 `node-patch.json`,例如:
|
|
41
41
|
|
|
42
42
|
```json
|
|
43
43
|
{
|
|
44
|
-
"position": {"x":120,"y":130},
|
|
45
|
-
"width":300,
|
|
46
|
-
"height":260,
|
|
47
44
|
"data":{"title":"图片节点","prompt":""}
|
|
48
45
|
}
|
|
49
46
|
```
|
|
50
47
|
|
|
51
|
-
4. 执行一次 `uzoo-anime --profile PROFILE node add --doc DOC_ID --type image --node-id NODE_ID --input node-patch.json`。同画布按顺序创建,不并发写入完整快照。其他普通节点换成表中的 CLI 类型;不额外拼接内部组件字段或流程派生标记。
|
|
48
|
+
4. 执行一次 `uzoo-anime --profile PROFILE node add --doc DOC_ID --type image --node-id NODE_ID --input node-patch.json`,没有自定义字段可省略 `--input`。同画布按顺序创建,不并发写入完整快照。其他普通节点换成表中的 CLI 类型;不额外拼接内部组件字段或流程派生标记。
|
|
52
49
|
5. `canvas get DOC_ID` 读回,验证节点 ID、API type、扩展 role、中文标题、位置、数量与版本;同时核对已有节点/边/meta 未丢失、归属未变化。不要只以命令退出 0 判定网页兼容通过。
|
|
53
50
|
6. 网页可用时打开同一画布并刷新,检查组件、标题及空状态。网页可能自动保存并增加版本,后续操作始终重新读取,保留网页补齐的数据。网页暂不可用时仅标记 API 保存/读取通过,网页待验证。
|
|
54
51
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
# 更新记录
|
|
2
2
|
|
|
3
|
+
## 0.6.2
|
|
4
|
+
|
|
5
|
+
- 新建普通节点和从素材库导入时,未指定坐标会自动避开已有节点和分组。
|
|
6
|
+
- 保留手动指定的坐标,不移动画布上的已有节点。
|
|
7
|
+
|
|
8
|
+
## 0.6.1
|
|
9
|
+
|
|
10
|
+
- 更新产品介绍、安装指南和项目主页链接。
|
|
11
|
+
|
|
3
12
|
## 0.6.0
|
|
4
13
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
- npm 归档仅包含运行资源和公开使用说明。
|
|
14
|
+
- 新增 CLI 与 Agent Skill 一键安装。
|
|
15
|
+
- 支持选择多个 Agent、同步同版本 Skill,并保护手动修改的 Skill。
|
|
16
|
+
- Agent 可提示可用更新,是否更新由用户决定。
|
|
9
17
|
|
|
10
18
|
## 0.5.0
|
|
11
19
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
20
|
+
- 支持浏览器授权、画布、素材、普通生成与下载。
|
|
21
|
+
- 随包提供完整 Agent Skill。
|
package/README.md
CHANGED
|
@@ -1,46 +1,50 @@
|
|
|
1
1
|
# Uzoo Anime CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Uzoo 官方命令行工具与 Agent Skills。通过终端或 Agent 管理画布和创作素材,发起图片与视频生成,并查询和下载结果。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[Uzoo 官网](https://uzooai.com/) · [npm 软件包](https://www.npmjs.com/package/@uzooai/anime-ai-cli)
|
|
6
|
+
|
|
7
|
+
## 安装与更新
|
|
8
|
+
|
|
9
|
+
需要 **Node.js 22 或更高版本**。
|
|
10
|
+
|
|
11
|
+
**CLI + Agent Skill**
|
|
6
12
|
|
|
7
13
|
```sh
|
|
8
|
-
|
|
9
|
-
uzoo-anime --version
|
|
10
|
-
uzoo-anime --help
|
|
14
|
+
npx @uzooai/anime-ai-cli@latest install
|
|
11
15
|
```
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
按提示选择要接入的 Agent。支持选择 Codex、WorkBuddy 桌面版或 Claude Code,可一次选择多个。已有同版本 CLI 时,运行 `uzoo-anime install` 即可为其他 Agent 添加 Skill。
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
**仅安装 CLI**
|
|
16
20
|
|
|
17
21
|
```sh
|
|
18
|
-
|
|
22
|
+
npm install --global @uzooai/anime-ai-cli@latest
|
|
19
23
|
```
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
该命令不会修改 Agent 的技能目录。若需要手动接入,运行 `uzoo-anime skill path` 找到随包 Skill,将完整目录导入对应 Agent。
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
更新 CLI 和 Skill 可重跑一键安装命令,选择要同步的 Agent。更新由用户决定,CLI 不会自动安装新版。
|
|
24
28
|
|
|
25
|
-
##
|
|
29
|
+
## 快速开始
|
|
26
30
|
|
|
27
31
|
```sh
|
|
28
|
-
uzoo-anime
|
|
32
|
+
uzoo-anime --version
|
|
29
33
|
uzoo-anime auth login
|
|
30
|
-
uzoo-anime
|
|
34
|
+
uzoo-anime canvas list --space personal
|
|
31
35
|
```
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
在浏览器中登录 Uzoo 并授权 CLI 后即可使用。团队操作须选择对应团队。使用 Agent 时,可直接提出任务,例如:“列出我的个人画布,并帮我查看指定画布中的素材。”
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
- [支持的节点与工具](.agents/skills/uzoo-anime-cli/references/node-capabilities.md):区分 CLI 能力和网页专属操作。
|
|
37
|
-
- [生成与恢复](.agents/skills/uzoo-anime-cli/references/generation-workflow.md):先确认素材、参数和实际报价;中断时查询并恢复原 operation。
|
|
38
|
-
- [图片工具](.agents/skills/uzoo-anime-cli/references/image-tools-workflow.md)与[视频工具](.agents/skills/uzoo-anime-cli/references/video-tools-workflow.md):按需检查 FFmpeg;CLI 不自动安装媒体工具。
|
|
39
|
+
## 主要功能
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
- **画布与节点**:查询和管理画布、节点、分组及连线。新增普通节点或从素材库导入时,未指定坐标会自动找空位。
|
|
42
|
+
- **素材管理**:上传、查询、导入和下载素材。
|
|
43
|
+
- **图片与视频**:准备生成请求、查询任务和获取结果;支持样片转正片,以及裁剪、标注、抠图、剪辑和截帧等工具。
|
|
44
|
+
- **任务恢复**:查询或继续原任务,保留已知任务和结果。
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
付费任务会在提交前展示报价,由用户确认。运行 `uzoo-anime --help` 查看可用命令及参数。部分媒体工具需要本机 FFmpeg。
|
|
43
47
|
|
|
44
|
-
|
|
48
|
+
## 许可证
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
CLI 源码采用 [MIT 许可证](LICENSE)。Uzoo 在线服务适用相应服务条款。
|
|
@@ -1,43 +1,41 @@
|
|
|
1
1
|
# Agent 接入与版本更新
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
需要 Node.js 22 或更高版本。CLI 和完整 Skill 由同一个 npm 包分发。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 一键安装
|
|
6
6
|
|
|
7
|
-
```
|
|
7
|
+
```sh
|
|
8
|
+
npx @uzooai/anime-ai-cli@latest install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
安装器会列出检测到的本地 Agent,支持多选。也可明确指定:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npx @uzooai/anime-ai-cli@latest install --agent codex --agent workbuddy
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
可选名称为 `codex`、`workbuddy`(WorkBuddy 桌面版)和 `claude-code`。重复传入 `--agent` 可安装到多个宿主。自动化脚本须明确指定目标并传入 `--yes`;用 `--dry-run` 可先查看安装计划。已有同版本 CLI 时,运行 `uzoo-anime install` 即可添加 Agent Skill。
|
|
18
|
+
|
|
19
|
+
## 只安装 CLI 或手动接入
|
|
20
|
+
|
|
21
|
+
```sh
|
|
8
22
|
npm install --global @uzooai/anime-ai-cli@latest
|
|
9
23
|
uzoo-anime --version
|
|
10
24
|
uzoo-anime skill path --format json
|
|
11
25
|
```
|
|
12
26
|
|
|
13
|
-
|
|
27
|
+
普通 npm 安装不修改 Agent 的技能目录。需要手动接入时,复制完整的 `directory`,保留 `SKILL.md` 与 `references/`:
|
|
14
28
|
|
|
15
|
-
|
|
|
29
|
+
| 宿主 | 用户级目标目录 |
|
|
16
30
|
| --- | --- |
|
|
17
31
|
| Codex | `~/.agents/skills/uzoo-anime-cli` |
|
|
18
32
|
| WorkBuddy 桌面版 | `~/.workbuddy/skills/uzoo-anime-cli` |
|
|
19
33
|
| Claude Code | `~/.claude/skills/uzoo-anime-cli` |
|
|
20
34
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## 含一键安装入口的版本
|
|
24
|
-
|
|
25
|
-
仅在 `uzoo-anime install --help` 可用时使用本节。0.6.0 的安装入口已在 Windows 的 Codex、WorkBuddy 桌面版验证;Claude Code 可明确指定,尚未验收真实宿主。WorkBuddy AI 或远程 Claw 不在此范围。
|
|
26
|
-
|
|
27
|
-
```sh
|
|
28
|
-
uzoo-anime install
|
|
29
|
-
uzoo-anime install --agent codex --agent workbuddy
|
|
30
|
-
uzoo-anime install --agent workbuddy --dry-run
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
默认列出检测到的 Agent,允许多选,确认后才写入。脚本须明确传入至少一个 `--agent` 并使用 `--yes`。安装器复用同版本全局 CLI,只给选中的宿主同步 Skill。发现手动安装或改过的副本会停止,给出目录,供用户先备份或只选择其他宿主。安装器不会代登录、安装 Agent 或改 PATH。
|
|
34
|
-
|
|
35
|
-
当包含该功能的版本成为 npm 的 `latest` 后,才可用 `npx @uzooai/anime-ai-cli@latest install` 首次一键接入或主动升级。为已有同版本 CLI 加装新 Agent,直接用本机 `uzoo-anime install` 即可。
|
|
35
|
+
打开新的 Agent 会话,确认能发现 Skill 并运行 `uzoo-anime --version`。找不到命令时,检查宿主的 PATH 是否包含 npm 全局命令目录。登录 Uzoo 时运行 `uzoo-anime auth login`,并在浏览器中本人授权。
|
|
36
36
|
|
|
37
|
-
##
|
|
37
|
+
## 更新与已有配置
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
- 一键安装用户:通过含一键安装功能的新版入口更新 CLI,并选择所有要同步的 Agent。
|
|
41
|
-
- 更新不要求重新登录;可继续使用原 profile 和 operation。不要因更新重提未知任务。
|
|
39
|
+
要同时更新 CLI 和 Skill,重跑一键安装并选中要同步的 Agent。安装器不会覆盖手动安装或修改过的 Skill;先备份至技能扫描目录外,再按需要更新。只更新 CLI 可运行普通 npm 安装命令。
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
Agent 首次使用时可查询 npm 稳定版;有新版时由用户决定是否更新。查询失败不影响正常使用。更新时保留原 profile 和任务记录。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uzooai/anime-ai-cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"CHANGELOG.md",
|
|
37
37
|
"LICENSE"
|
|
38
38
|
],
|
|
39
|
-
"description": "Uzoo
|
|
39
|
+
"description": "Official Uzoo CLI and Agent Skills for canvases, media assets, and image and video generation.",
|
|
40
|
+
"homepage": "https://uzooai.com/",
|
|
40
41
|
"repository": {
|
|
41
42
|
"type": "git",
|
|
42
43
|
"url": "git+https://github.com/UzooAI/uzooai-cli.git"
|
package/src/canvas.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import crypto from 'node:crypto'
|
|
2
|
+
import { findNodePosition } from './node-placement.js'
|
|
2
3
|
import { segment } from './workflows.js'
|
|
3
4
|
|
|
4
5
|
// Public CLI names map to the persisted API types and roles used by the web canvas.
|
|
@@ -10,7 +11,7 @@ function createNode(id, type) {
|
|
|
10
11
|
apiType = 'video_repain_begain'
|
|
11
12
|
data = { extra: { repaint: { role: 'entry', splitMode: 'intelligent' } } }
|
|
12
13
|
}
|
|
13
|
-
return { id, type: apiType,
|
|
14
|
+
return { id, type: apiType, data }
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
const object = v => v !== null && typeof v === 'object' && !Array.isArray(v)
|
|
@@ -89,6 +90,7 @@ export async function writeNode(client, { docId, nodeId, type, patch = {}, updat
|
|
|
89
90
|
if (!update && matches.length) throw new Error('节点 id 已存在;请读取节点确认,勿重复创建')
|
|
90
91
|
const node = update ? patchNode(matches[0], patch) : candidate
|
|
91
92
|
if (update && !isDerivedNode(matches[0]) && isDerivedNode(node)) throw new Error(derivedNodeMessage)
|
|
93
|
+
if (!update && node.position === undefined) node.position = findNodePosition(doc, node)
|
|
92
94
|
const nodes = update ? doc.nodes.map(n => n.id === id ? node : n) : [...doc.nodes, node]
|
|
93
95
|
const body = { version: doc.version, name: doc.name, viewport: doc.viewport, nodes, edges: doc.edges, meta: doc.meta }
|
|
94
96
|
// Backend CAS protects concurrent website edits. Do not reload and overwrite on conflict.
|
package/src/materials.js
CHANGED
|
@@ -131,7 +131,7 @@ export async function materialUse(client, { space, teamUid, assetId, docId, node
|
|
|
131
131
|
expectedSpace: space,
|
|
132
132
|
expectedTeamUid: teamUid,
|
|
133
133
|
patch: {
|
|
134
|
-
position
|
|
134
|
+
position,
|
|
135
135
|
data: {
|
|
136
136
|
title: asset.name,
|
|
137
137
|
url: asset.url,
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const ORIGIN = { x: 120, y: 130 }
|
|
2
|
+
const COLUMNS = 4
|
|
3
|
+
const GAP_X = 80
|
|
4
|
+
const GAP_Y = 64
|
|
5
|
+
|
|
6
|
+
const positive = value => Number.isFinite(Number(value)) && Number(value) > 0 ? Number(value) : 0
|
|
7
|
+
|
|
8
|
+
// Match the web snapshot's fallback sizes. data.width/height are media pixels, not canvas sizes.
|
|
9
|
+
function fallbackSize({ type, data = {} }) {
|
|
10
|
+
if (type === 'text') return [300, 300]
|
|
11
|
+
if (type === 'audio' || type === 'audio_upload') return [266, 156]
|
|
12
|
+
if (type === 'video_repain_begain') return [590, 760]
|
|
13
|
+
if (data.extra?.repaint?.role === 'split-detail') return [480, 540]
|
|
14
|
+
if (!['image', 'image_upload', 'video', 'video_upload'].includes(type)) return [486, 300]
|
|
15
|
+
|
|
16
|
+
const asset = type.endsWith('_upload')
|
|
17
|
+
const ratioFields = asset
|
|
18
|
+
? [data.node_output?.aspect_ratio, data.aspectRatio, data.aspect_ratio, data.extra?.aspect_ratio]
|
|
19
|
+
: [data.generation_config?.aspect_ratio, data.extra?.aspect_ratio, data.aspectRatio, data.aspect_ratio]
|
|
20
|
+
const ratioValue = [...ratioFields, data.result?.aspect_ratio].find(value => String(value ?? '').trim())
|
|
21
|
+
const ratioMatch = /^(\d+(?:\.\d+)?)[::](\d+(?:\.\d+)?)$/.exec(String(ratioValue ?? '').trim())
|
|
22
|
+
const w = positive(ratioMatch?.[1])
|
|
23
|
+
const h = positive(ratioMatch?.[2])
|
|
24
|
+
const mediaWidth = positive(data.result?.width) || positive(data.width)
|
|
25
|
+
const mediaHeight = positive(data.result?.height) || positive(data.height)
|
|
26
|
+
const ratio = w && h ? positive(w / h) : asset && mediaHeight ? positive(mediaWidth / mediaHeight) : 0
|
|
27
|
+
if (!ratio && type === 'image_upload') return [266, 300]
|
|
28
|
+
const previewRatio = type === 'image'
|
|
29
|
+
? Math.min(21 / 9, Math.max(9 / 16, ratio || 16 / 9))
|
|
30
|
+
: ratio || 16 / 9
|
|
31
|
+
// The browser sizes the preview from a 272px short side, plus its border and title.
|
|
32
|
+
return [
|
|
33
|
+
positive(Math.round(272 * Math.max(1, previewRatio)) + 2) || 486,
|
|
34
|
+
positive(Math.round(272 * Math.max(1, 1 / previewRatio)) + 28) || 300,
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function nodeSize(node) {
|
|
39
|
+
const [width, height] = fallbackSize({ ...node, data: node.data ?? {} })
|
|
40
|
+
return [
|
|
41
|
+
Math.max(positive(node.width) || positive(node.w), width),
|
|
42
|
+
Math.max(positive(node.height) || positive(node.h), height),
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function findNodePosition(doc, node) {
|
|
47
|
+
const [width, height] = nodeSize(node)
|
|
48
|
+
const bounds = []
|
|
49
|
+
const add = (position, w, h) => {
|
|
50
|
+
if (!position || !w || !h) return
|
|
51
|
+
const x = Number(position.x) || 0
|
|
52
|
+
const y = Number(position.y) || 0
|
|
53
|
+
if (Number.isFinite(x) && Number.isFinite(y)) bounds.push({ x, y, right: x + w, bottom: y + h })
|
|
54
|
+
}
|
|
55
|
+
for (const existing of doc.nodes) add(existing.position, ...nodeSize(existing))
|
|
56
|
+
for (const group of Array.isArray(doc.meta.canvas_groups) ? doc.meta.canvas_groups : []) {
|
|
57
|
+
add(group?.position, positive(group?.size?.width), positive(group?.size?.height))
|
|
58
|
+
}
|
|
59
|
+
bounds.sort((a, b) => a.y - b.y)
|
|
60
|
+
|
|
61
|
+
// Find the highest free slot in each column. Skipping to a blocker’s bottom
|
|
62
|
+
// avoids scanning a pixel grid when the canvas already contains large nodes.
|
|
63
|
+
const stride = Math.ceil(Math.max(486, width)) + GAP_X
|
|
64
|
+
let best
|
|
65
|
+
for (let column = 0; column < COLUMNS; column++) {
|
|
66
|
+
const x = ORIGIN.x + column * stride
|
|
67
|
+
let y = ORIGIN.y
|
|
68
|
+
for (const box of bounds) {
|
|
69
|
+
if (box.y >= y + height + GAP_Y) break
|
|
70
|
+
if (box.x >= x + width + GAP_X || box.right + GAP_X <= x || box.bottom + GAP_Y <= y) continue
|
|
71
|
+
y = Math.ceil(box.bottom + GAP_Y)
|
|
72
|
+
}
|
|
73
|
+
if (Number.isFinite(x) && Number.isFinite(y) && (!best || y < best.y)) best = { x, y }
|
|
74
|
+
if (best?.y === ORIGIN.y) break
|
|
75
|
+
}
|
|
76
|
+
if (!best) throw new Error('无法计算新节点位置;请显式指定 position')
|
|
77
|
+
return best
|
|
78
|
+
}
|