@uzooai/anime-ai-cli 0.6.1 → 0.6.3
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/SKILL.md +1 -0
- package/.agents/skills/uzoo-anime-cli/references/commands.md +3 -2
- package/.agents/skills/uzoo-anime-cli/references/generation-workflow.md +16 -0
- 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 +10 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/canvas.js +3 -1
- package/src/cli.js +24 -0
- package/src/materials.js +1 -1
- package/src/node-placement.js +78 -0
- package/src/workflows.js +2 -1
|
@@ -30,6 +30,7 @@ If a newer stable version exists, show the installed and available versions and
|
|
|
30
30
|
| Explain node purposes, toolbar tools or missing CLI features | [Node capabilities](references/node-capabilities.md) |
|
|
31
31
|
| Upload, save node results to library, reuse assets, download material originals, arrange/group and connect nodes | [Material and graph workflow](references/material-graph-workflow.md) |
|
|
32
32
|
| Prepare references, estimate, generate, synchronize/recover results, finalize an existing Seedance draft, or select/add ordinary image/video history | [Generation workflow](references/generation-workflow.md) |
|
|
33
|
+
| Ordinary generation submission limits, duplicate requests, or HTTP 429 on submit | Run `uzoo-anime generation limits` for the offline reference and follow [submission limits and recovery](references/generation-workflow.md#生成提交限额与429处理) |
|
|
33
34
|
| Automatically apply sd25-pe while preparing Seedance 2.5 video generation; no explicit skill invocation needed | [Prompt handoff](references/generation-workflow.md#seedance-25提示词优化sd25-pe) |
|
|
34
35
|
| Explicit Seedance image/video material review, status and recovery | [Material review workflow](references/seedance-review-workflow.md) |
|
|
35
36
|
| Account consent, first use/updated agreement or API code 4401 | [Agreement workflow](references/agreement-workflow.md) |
|
|
@@ -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 不自动重提;账号/服务/来源改变停止 |
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
| `generation protocol-status` | 只读查询视频《统一协议》及《Seedance2.0模型使用承诺书》的 required/agreed/version;status=awaiting_agreement 与 api_code=4401 表示待签署 |
|
|
58
58
|
| `generation protocol-open` | 打开独立本机协议阅读确认页;可选 --browser default/msedge/chrome --timeout MS;本人勾选点击后记录并查询核验,不提交生成 |
|
|
59
59
|
| `generation estimate` | `--input request.json`;保留文档/节点上下文,返回费用和 `will_intercept` |
|
|
60
|
+
| `generation limits` | 可选 `--format json\|table`;离线查看普通生成提交限额和等待建议,不需要登录,不查询实时剩余额度或提交任务。遇到 429 见 [生成提交限额与处理](generation-workflow.md#生成提交限额与429处理) |
|
|
60
61
|
| `generation submit` | `--input request.json --operation NAME` 默认只预估;Seedance 预览额外返回 protocol;--yes 在需要协议时打开阅读页并等待本人确认,核验后继续原操作;可选 --browser 和 --agreement-timeout;首次 --yes 必须带 `--expected-cost OO` 与当次确认费用严格比对,不一致停止;返回 `{operation, estimate, task}`。保存 operation,任务 ID 是 `task.task_id` |
|
|
61
62
|
| `generation recover NAME` | 有已知任务 ID 时只查询该任务;未知提交结果时默认只预估,加 `--yes` 以原请求和原幂等键恢复;原记录无确认金额或价格变化时必须重新卡片确认并带 `--expected-cost OO`,已有有效金额且报价未变可沿用。新授权金额先落原记录,不改请求或键。不得改用新 operation |
|
|
62
63
|
| `generation operations` | 列出本地 operation、taskId、状态与创建时间;失败时包含脱敏 lastFailure(HTTP/业务错误码),不输出原始请求 |
|
|
@@ -49,6 +49,22 @@ Seedance 首次使用或协议更新时,按 [协议签署与继续原任务](a
|
|
|
49
49
|
|
|
50
50
|
`generation protocol-open` 可单独打开本机阅读确认页,不提交生成。已获当次费用确认的 submit/recover --yes 在需要签署时会打开同样页面,用户本人阅读、勾选并确认后,CLI 核验账号/版本/输入/费用,再以原请求与幂等键继续一次。独立页面不重新打开画布,不向网页暴露 CLI 令牌。正文不可用、用户不同意/超时、版本或参数/费用变化时停止,保留原 operation。不得代填 protocol_agreed 或自动同意。
|
|
51
51
|
|
|
52
|
+
## 生成提交限额与429处理
|
|
53
|
+
|
|
54
|
+
以下是普通 AI 生成提交(`POST /canvas/generations`)的规则参考,也可用 `uzoo-anime generation limits --format table` 离线查看。它不是实时剩余额度;预估、任务查询及专用提交接口不套用此表。普通生成逐笔提交,上一笔提交请求返回后再发送下一笔,无需等生成完成。
|
|
55
|
+
|
|
56
|
+
| 规则 | 限额 | 触发时怎么处理 |
|
|
57
|
+
| --- | ---: | --- |
|
|
58
|
+
| 单用户每秒提交 | 20 次 | 等约 1 秒 |
|
|
59
|
+
| 单用户每分钟提交 | 120 次 | 等额度释放,当前窗口最多 60 秒 |
|
|
60
|
+
| 全站每分钟提交 | 1,200 次 | 等额度释放,当前窗口最多 60 秒 |
|
|
61
|
+
| 同时提交请求 | 32 个 | 等前面的提交请求结束;这不是正在生成的任务数上限 |
|
|
62
|
+
| 相同内容去重窗口 | 2 秒 | 等 2 秒,先确认是否已有原任务 |
|
|
63
|
+
|
|
64
|
+
- 优先按服务端给出的具体原因和 `Retry-After`(若有)等待。仅凭 HTTP 429 不能判断触发了哪条规则,也不能据此认定登录失效;分钟窗口结束不保证繁忙时下一次必定成功。
|
|
65
|
+
- 保留错误码、服务端提示与请求编号(若有),先查原节点任务(`generation list --doc DOC_ID --node NODE_ID`)。已有匹配任务就按原 task ID 查询或等待。暂时查不到不等于未提交;不要重复上传仍可用的素材,也不要换 operation、改请求内容或换幂等键重提。
|
|
66
|
+
- CLI 不自动重提。按建议等待、核对原任务后,确需恢复且原费用授权仍有效时,按下节以原 operation、请求和幂等键恢复;报价或输入变化按费用确认流程处理。若再次限流,停止连续重试,报告服务端原因和请求编号(若有)。
|
|
67
|
+
|
|
52
68
|
## 提交中断
|
|
53
69
|
|
|
54
70
|
遇到超时、断连、网关或内部业务错误时先运行 service status,优先判断当前后端连接和数据库/Redis就绪,再查原 operation/任务和画布副作用。CLI 会对网络/服务类异常追加同源诊断,但保留原错误;ready 不代表对象存储/队列/业务事务都正常,也不证明先前没有提交。连接恢复不构成重发或另一次生成授权。
|
|
@@ -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,5 +1,15 @@
|
|
|
1
1
|
# 更新记录
|
|
2
2
|
|
|
3
|
+
## 0.6.3
|
|
4
|
+
|
|
5
|
+
- 新增 `generation limits`,可离线查看普通 AI 生成提交限额与等待建议。
|
|
6
|
+
- 生成提交遇到 HTTP 429 时提示先查询原节点任务,再按原操作恢复;随包 Skill 同步补充处理规则。
|
|
7
|
+
|
|
8
|
+
## 0.6.2
|
|
9
|
+
|
|
10
|
+
- 新建普通节点和从素材库导入时,未指定坐标会自动避开已有节点和分组。
|
|
11
|
+
- 保留手动指定的坐标,不移动画布上的已有节点。
|
|
12
|
+
|
|
3
13
|
## 0.6.1
|
|
4
14
|
|
|
5
15
|
- 更新产品介绍、安装指南和项目主页链接。
|
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ uzoo-anime canvas list --space personal
|
|
|
38
38
|
|
|
39
39
|
## 主要功能
|
|
40
40
|
|
|
41
|
-
-
|
|
41
|
+
- **画布与节点**:查询和管理画布、节点、分组及连线。新增普通节点或从素材库导入时,未指定坐标会自动找空位。
|
|
42
42
|
- **素材管理**:上传、查询、导入和下载素材。
|
|
43
43
|
- **图片与视频**:准备生成请求、查询任务和获取结果;支持样片转正片,以及裁剪、标注、抠图、剪辑和截帧等工具。
|
|
44
44
|
- **任务恢复**:查询或继续原任务,保留已知任务和结果。
|
package/package.json
CHANGED
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/cli.js
CHANGED
|
@@ -104,6 +104,7 @@ const help = `Uzoo Anime CLI — 浏览器授权与独立 CLI 会话
|
|
|
104
104
|
generation prepare --doc ID --node ID --space personal|team [--team-uid UID --output request.json]
|
|
105
105
|
generation list --doc ID [--node ID] [--status STATUS] [--cursor ID] [--limit N]
|
|
106
106
|
generation estimate --input request.json
|
|
107
|
+
generation limits [--format json|table](离线查看普通 AI 生成提交限额及 429 处理)
|
|
107
108
|
generation submit --input request.json --operation NAME [--yes --expected-cost OO]
|
|
108
109
|
generation recover NAME [--yes] | operations
|
|
109
110
|
generation get ID | logs ID | wait ID [--interval MS] [--timeout MS]
|
|
@@ -179,6 +180,23 @@ async function input(file) {
|
|
|
179
180
|
}
|
|
180
181
|
function query(endpoint, fields) { const q = new URLSearchParams(Object.entries(fields).filter(([, v]) => v !== undefined)); return `${endpoint}${q.size ? '?' + q : ''}` }
|
|
181
182
|
|
|
183
|
+
const generationLimits = {
|
|
184
|
+
endpoint: 'POST /canvas/generations',
|
|
185
|
+
source: 'documented_reference',
|
|
186
|
+
items: [
|
|
187
|
+
{ rule: '单用户每秒提交', limit: 20, wait: '约 1 秒' },
|
|
188
|
+
{ rule: '单用户每分钟提交', limit: 120, wait: '等额度释放,当前窗口最多 60 秒' },
|
|
189
|
+
{ rule: '全站每分钟提交', limit: 1200, wait: '等额度释放,当前窗口最多 60 秒' },
|
|
190
|
+
{ rule: '同时提交请求', limit: 32, wait: '等前面的提交请求结束,无需等生成完成' },
|
|
191
|
+
{ rule: '相同内容去重窗口(秒)', limit: 2, wait: '等 2 秒,并先确认是否已有任务' },
|
|
192
|
+
],
|
|
193
|
+
notes: [
|
|
194
|
+
'这是平台提供的提交规则参考,并非实时剩余额度;以服务端实际响应和 Retry-After(若有)为准。',
|
|
195
|
+
'HTTP 429 本身不能确定触发哪条规则。先查原节点任务;已有任务就继续查询或等待,不重复提交。',
|
|
196
|
+
'CLI 不自动重提。需要恢复时沿用原 operation、请求和幂等键;确认费用授权仍有效后再执行恢复。',
|
|
197
|
+
],
|
|
198
|
+
}
|
|
199
|
+
|
|
182
200
|
let diagnosticClient
|
|
183
201
|
async function main(args) {
|
|
184
202
|
if (args[0] === 'install') {
|
|
@@ -195,6 +213,12 @@ async function main(args) {
|
|
|
195
213
|
if (!['json', 'table'].includes(format)) throw new Error('--format 必须是 json 或 table')
|
|
196
214
|
if (p.length > 3) throw new Error('多余的位置参数')
|
|
197
215
|
const [group, action, idArg] = p
|
|
216
|
+
if (group === 'generation' && action === 'limits') {
|
|
217
|
+
if (idArg || Object.keys(o).some(key => key !== 'format')) throw new Error('generation limits 仅支持 --format json|table')
|
|
218
|
+
print(generationLimits, format)
|
|
219
|
+
if (format === 'table') console.log('\n' + generationLimits.notes.join('\n'))
|
|
220
|
+
return
|
|
221
|
+
}
|
|
198
222
|
if(group==='version'||group==='skill'){
|
|
199
223
|
if(Object.keys(o).some(k=>k!=='format')||idArg||
|
|
200
224
|
(group==='version'&&p.length!==1)||(group==='skill'&&(action!=='path'||p.length!==2)))
|
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
|
+
}
|
package/src/workflows.js
CHANGED
|
@@ -129,7 +129,8 @@ export async function submit(client, profile, body, operation, confirmed, expect
|
|
|
129
129
|
await atomic(file, record)
|
|
130
130
|
if(error.apiCode===4401)throw agreementError('AGREEMENT_REQUIRED','《统一协议》《Seedance2.0模型使用承诺书》需要签署或版本已更新(code 4401);本次生成提交被前置检查阻止,不是生成任务执行失败;保留原 operation',error.protocol || protocol)
|
|
131
131
|
const detail=record.lastFailure.apiCode ? `(接口返回 code ${record.lastFailure.apiCode},HTTP ${record.lastFailure.status})` : record.lastFailure.status ? `(HTTP ${record.lastFailure.status})` : ''
|
|
132
|
-
const
|
|
132
|
+
const rateLimitHint = error.status === 429 ? ';请先查原节点是否已有任务,再按 generation limits 的等待建议处理' : ''
|
|
133
|
+
const failure = new Error(`提交结果未确认${detail}${rateLimitHint};使用 generation recover ${operation} 以原幂等键恢复,勿创建新 operation`,{cause:error})
|
|
133
134
|
failure.code = 'SUBMISSION_UNKNOWN'; throw failure
|
|
134
135
|
}
|
|
135
136
|
}
|