@uzooai/anime-ai-cli 0.0.0-stage → 0.5.0
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 +83 -0
- package/.agents/skills/uzoo-anime-cli/references/agreement-workflow.md +51 -0
- package/.agents/skills/uzoo-anime-cli/references/canvas-workflow.md +53 -0
- package/.agents/skills/uzoo-anime-cli/references/commands.md +250 -0
- package/.agents/skills/uzoo-anime-cli/references/generation-workflow.md +152 -0
- package/.agents/skills/uzoo-anime-cli/references/image-tools-workflow.md +175 -0
- package/.agents/skills/uzoo-anime-cli/references/interaction-workflow.md +67 -0
- package/.agents/skills/uzoo-anime-cli/references/material-graph-workflow.md +123 -0
- package/.agents/skills/uzoo-anime-cli/references/node-capabilities.md +95 -0
- package/.agents/skills/uzoo-anime-cli/references/node-workflow.md +94 -0
- package/.agents/skills/uzoo-anime-cli/references/remake-workflow.md +54 -0
- package/.agents/skills/uzoo-anime-cli/references/repaint-workflow.md +300 -0
- package/.agents/skills/uzoo-anime-cli/references/seedance-review-workflow.md +41 -0
- package/.agents/skills/uzoo-anime-cli/references/video-tools-workflow.md +71 -0
- package/CHANGELOG.md +92 -0
- package/LICENSE +21 -0
- package/README.md +242 -3
- package/assets/README.md +4 -0
- package/assets/favicon-uzoo.png +0 -0
- package/assets/logo-uzoo.svg +7 -0
- package/docs/CLI/345/275/223/345/211/215/350/214/203/345/233/264/344/270/216/346/234/254/345/234/260/346/211/223/345/214/205-20261103.md +134 -0
- package/docs/CLI/346/216/210/346/235/203/346/216/245/345/217/243/344/270/216/344/273/244/347/211/214/350/207/252/345/212/250/345/210/267/346/226/260/350/256/276/350/256/241.md +540 -0
- package/docs/CLI/347/274/272/345/217/243/346/240/270/345/257/271/344/270/216/346/224/266/345/260/276-20261105.md +65 -0
- package/docs/CLI/347/275/221/351/241/265/344/277/235/345/255/230/350/212/202/347/202/271/347/274/272/345/244/261/344/272/244/346/216/245.md +52 -0
- package/docs/M0-M5/345/256/236/346/226/275/344/270/216/351/252/214/346/224/266.md +89 -0
- package/docs/npm/347/272/277/344/270/212/345/217/221/345/270/203/344/270/216/345/256/211/350/243/205/344/275/277/347/224/250.md +151 -0
- package/docs//344/270/213/350/275/275/346/216/245/345/217/243/351/200/202/351/205/215/344/270/216/351/252/214/346/224/266.md +107 -0
- package/docs//344/270/232/345/212/241/350/203/275/345/212/233/350/241/245/345/205/250/344/270/216Skill/347/273/223/346/236/204/350/247/204/345/210/222.md +311 -0
- package/docs//345/205/250/345/261/200/351/231/204/344/273/266/350/265/204/346/272/220/345/234/260/345/235/200/344/270/216/346/250/241/345/236/213/350/257/273/345/217/226/345/267/256/345/274/202.md +47 -0
- package/docs//345/206/205/351/203/250Windows/345/256/211/350/243/205/344/270/216/345/215/207/347/272/247/351/252/214/346/224/266/346/270/205/345/215/225.md +34 -0
- package/docs//345/233/276/347/211/207/346/212/240/345/233/276/346/216/245/345/205/245/344/270/216/351/252/214/346/224/266-20261104.md +110 -0
- package/docs//345/274/200/345/217/221/350/256/241/345/210/222.md +127 -0
- package/docs//345/274/202/346/255/245/346/212/240/345/233/276CLI/346/235/203/351/231/220/344/272/244/346/216/245-20261105.md +50 -0
- package/docs//345/274/202/346/255/245/346/212/240/345/233/276/347/234/237/345/256/236/351/252/214/346/224/266-20261105.md +44 -0
- package/docs//346/226/260/344/274/232/350/257/235/344/273/273/345/212/241/344/272/244/344/273/243-/351/230/266/346/256/2652A.md +79 -0
- package/docs//346/226/260/351/203/250/347/275/262/347/224/273/345/270/203/345/206/262/347/252/201/351/252/214/346/224/266-20261102.md +61 -0
- package/docs//346/231/256/351/200/232/346/265/217/350/247/210/345/231/250/347/231/273/345/275/225.md +53 -0
- package/docs//346/255/243/345/274/217/347/253/231/345/210/207/346/215/242/344/270/216/351/252/214/346/224/266-20261104.md +90 -0
- package/docs//347/224/273/345/270/203CLI/344/270/213/344/270/200/351/230/266/346/256/265/345/274/200/345/217/221/350/256/241/345/210/222.md +136 -0
- package/docs//347/224/273/345/270/203CLI/351/235/236/350/275/254/347/273/230/345/256/214/345/226/204/350/256/241/345/210/222.md +183 -0
- package/docs//347/224/273/345/270/203/350/203/275/345/212/233/345/245/221/347/272/246/346/240/270/345/257/271.md +59 -0
- package/docs//347/234/237/345/256/236/344/270/232/345/212/241/346/216/245/345/205/245/344/270/216/346/265/213/350/257/225.md +1004 -0
- package/docs//347/264/240/346/235/220/345/205/245/345/272/223/344/270/216/347/224/273/345/270/203/344/277/235/345/255/230/351/227/256/351/242/230/345/256/232/344/275/215.md +52 -0
- package/docs//351/230/266/346/256/2653B/351/252/214/346/224/266/344/272/244/346/216/245.md +44 -0
- package/docs//351/230/266/346/256/265/344/270/200/351/252/214/346/224/266.md +60 -0
- package/docs//351/235/236/350/275/254/347/273/230/346/214/201/347/273/255/345/256/214/345/226/204/344/270/216/351/252/214/346/224/266-20261103.md +76 -0
- package/docs//351/235/236/350/275/254/347/273/230/347/254/254/344/272/214/346/211/271/345/256/236/346/226/275/344/270/216/351/252/214/346/224/266.md +39 -0
- package/docs//351/235/236/350/275/254/347/273/230/350/241/245/345/205/205/346/235/241/344/273/266/351/252/214/346/224/266-20261103.md +113 -0
- package/docs//351/235/236/350/275/254/347/273/230/351/246/226/346/211/271/345/256/236/346/226/275/344/270/216/351/252/214/346/224/266.md +71 -0
- package/examples/matting.json +5 -0
- package/examples/real-business/image-request.template.json +7 -0
- package/examples/real-business/node-patch.template.json +11 -0
- package/extensions/uzoo-cli-login/manifest.json +9 -0
- package/extensions/uzoo-cli-login/popup.css +1 -0
- package/extensions/uzoo-cli-login/popup.html +14 -0
- package/extensions/uzoo-cli-login/popup.js +65 -0
- package/package.json +56 -6
- package/scripts/check-package-resources.mjs +56 -0
- package/scripts/check-release.mjs +19 -0
- package/scripts/pack-local.mjs +33 -0
- package/scripts/release-policy.mjs +22 -0
- package/src/agreement-page.js +62 -0
- package/src/agreement.js +419 -0
- package/src/annotation-media.js +386 -0
- package/src/api.js +168 -0
- package/src/browser-login.js +13 -0
- package/src/callback-page.js +44 -0
- package/src/canvas.js +105 -0
- package/src/cli-authorization.js +122 -0
- package/src/cli.js +459 -0
- package/src/download-name.js +17 -0
- package/src/draft-finalize.js +229 -0
- package/src/generation-graph.js +124 -0
- package/src/generation-input.js +472 -0
- package/src/graph-disconnect.js +89 -0
- package/src/graph.js +431 -0
- package/src/group-edit.js +53 -0
- package/src/group-layout.js +113 -0
- package/src/group-membership.js +126 -0
- package/src/history-add.js +119 -0
- package/src/image-annotation.js +246 -0
- package/src/image-crop.js +283 -0
- package/src/image-matting.js +82 -0
- package/src/image-media.js +89 -0
- package/src/image-output.js +385 -0
- package/src/local-output.js +23 -0
- package/src/material-download.js +55 -0
- package/src/material-save.js +254 -0
- package/src/materials.js +147 -0
- package/src/matting-background.js +73 -0
- package/src/matting-jobs.js +63 -0
- package/src/matting-worker.js +49 -0
- package/src/matting.js +170 -0
- package/src/media-copy.js +53 -0
- package/src/media.js +343 -0
- package/src/model-options.js +76 -0
- package/src/multipart.js +28 -0
- package/src/node-copy.js +95 -0
- package/src/node-delete.js +49 -0
- package/src/node-history.js +231 -0
- package/src/normal-login.js +118 -0
- package/src/package-info.js +8 -0
- package/src/png-alpha.js +60 -0
- package/src/protocol.js +83 -0
- package/src/remake.js +738 -0
- package/src/repaint-archive.js +53 -0
- package/src/repaint-delivery.js +783 -0
- package/src/repaint-export.js +63 -0
- package/src/repaint-graph.js +214 -0
- package/src/repaint-history.js +267 -0
- package/src/repaint-materials.js +601 -0
- package/src/repaint-reverse.js +614 -0
- package/src/repaint-sequence.js +132 -0
- package/src/repaint-settings.js +576 -0
- package/src/repaint-video.js +798 -0
- package/src/repaint.js +326 -0
- package/src/resource.js +110 -0
- package/src/review.js +435 -0
- package/src/service.js +85 -0
- package/src/store.js +100 -0
- package/src/video-clip.js +381 -0
- package/src/video-frame.js +296 -0
- package/src/workflows.js +214 -0
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
import { archiveRepaintResult } from './repaint-archive.js'
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import crypto from 'node:crypto'
|
|
5
|
+
import { isDeepStrictEqual } from 'node:util'
|
|
6
|
+
import { setTimeout as sleep } from 'node:timers/promises'
|
|
7
|
+
import { atomic, json, root, safeName } from './store.js'
|
|
8
|
+
import {
|
|
9
|
+
repaintPromptContext,
|
|
10
|
+
repaintPromptFields as fields
|
|
11
|
+
} from './repaint-reverse.js'
|
|
12
|
+
import { repaintOf, jobPath } from './repaint-graph.js'
|
|
13
|
+
import { requireServiceReady } from './service.js'
|
|
14
|
+
import { cancellation } from './api.js'
|
|
15
|
+
import { repaintAttachmentView } from './repaint-settings.js'
|
|
16
|
+
|
|
17
|
+
const hash = (value) =>
|
|
18
|
+
crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex')
|
|
19
|
+
const text = (value) => String(value ?? '')
|
|
20
|
+
const fail = (code, message, cause) =>
|
|
21
|
+
Object.assign(new Error(message), {
|
|
22
|
+
code,
|
|
23
|
+
...(cause
|
|
24
|
+
? {
|
|
25
|
+
cause,
|
|
26
|
+
status: cause.status,
|
|
27
|
+
apiCode: cause.apiCode,
|
|
28
|
+
requestId: cause.requestId
|
|
29
|
+
}
|
|
30
|
+
: {})
|
|
31
|
+
})
|
|
32
|
+
export const deliveryPath = (profile, operation) =>
|
|
33
|
+
path.join(
|
|
34
|
+
root(),
|
|
35
|
+
'repaint-delivery',
|
|
36
|
+
safeName(profile),
|
|
37
|
+
safeName(operation) + '.json'
|
|
38
|
+
)
|
|
39
|
+
const persist = (record) =>
|
|
40
|
+
atomic(deliveryPath(record.profile, record.operation), record)
|
|
41
|
+
const api = (client, endpoint, options = {}, controls) =>
|
|
42
|
+
client.raw(
|
|
43
|
+
endpoint,
|
|
44
|
+
{
|
|
45
|
+
...options,
|
|
46
|
+
headers: { ...options.headers, 'X-Client-Source': 'canvas' }
|
|
47
|
+
},
|
|
48
|
+
controls
|
|
49
|
+
)
|
|
50
|
+
const object = (value) =>
|
|
51
|
+
value && typeof value === 'object' && !Array.isArray(value)
|
|
52
|
+
export function deliveryInput(value = {}) {
|
|
53
|
+
if (
|
|
54
|
+
!object(value) ||
|
|
55
|
+
Object.keys(value).some((k) => !['rules', 'edited_sections'].includes(k))
|
|
56
|
+
)
|
|
57
|
+
throw new Error(
|
|
58
|
+
'最终提示词输入只支持rules/edited_sections;附件来自已同步全局节点,单次附件、模型或文化指令额外字段尚未接入'
|
|
59
|
+
)
|
|
60
|
+
if (
|
|
61
|
+
value.rules !== undefined &&
|
|
62
|
+
(!object(value.rules) ||
|
|
63
|
+
Object.keys(value.rules).some(
|
|
64
|
+
(k) =>
|
|
65
|
+
!['character', 'scene', 'sceneList', 'prop', 'dialogue'].includes(k)
|
|
66
|
+
))
|
|
67
|
+
)
|
|
68
|
+
throw new Error('替换规则仅支持character/scene/sceneList/prop/dialogue')
|
|
69
|
+
const rules = value.rules || {}
|
|
70
|
+
for (const key of ['character', 'scene', 'sceneList', 'prop', 'dialogue']) {
|
|
71
|
+
const list = rules[key]
|
|
72
|
+
if (list === undefined) continue
|
|
73
|
+
if (key === 'scene' && typeof list === 'string') {
|
|
74
|
+
if (list.length > 12000) throw new Error('场景替换过长')
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
if (
|
|
78
|
+
!Array.isArray(list) ||
|
|
79
|
+
list.length > 100 ||
|
|
80
|
+
list.some(
|
|
81
|
+
(item) =>
|
|
82
|
+
!object(item) ||
|
|
83
|
+
Object.keys(item).some((k) => !['source', 'target'].includes(k)) ||
|
|
84
|
+
typeof item.source !== 'string' ||
|
|
85
|
+
typeof item.target !== 'string' ||
|
|
86
|
+
item.source.length > 300 ||
|
|
87
|
+
item.target.length > 300
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
throw new Error(
|
|
91
|
+
'替换列表须为最多100项的source/target字符串,单项最长300字符'
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
if (
|
|
95
|
+
value.edited_sections !== undefined &&
|
|
96
|
+
(!object(value.edited_sections) ||
|
|
97
|
+
Object.entries(value.edited_sections).some(
|
|
98
|
+
([k, v]) =>
|
|
99
|
+
!['character', 'scene', 'prop', 'dialogue'].includes(k) ||
|
|
100
|
+
typeof v !== 'string' ||
|
|
101
|
+
v.length > 12000
|
|
102
|
+
))
|
|
103
|
+
)
|
|
104
|
+
throw new Error('edited_sections只接受四类文本')
|
|
105
|
+
return structuredClone(value)
|
|
106
|
+
}
|
|
107
|
+
function dialogueReplace(section, rules) {
|
|
108
|
+
const map = new Map(rules.map((item) => [item.source, item.target]))
|
|
109
|
+
return section
|
|
110
|
+
.split(/(\r?\n)/)
|
|
111
|
+
.map((line) => {
|
|
112
|
+
const source = line.trim(),
|
|
113
|
+
replacement = map.get(source)
|
|
114
|
+
if (!replacement) return line
|
|
115
|
+
const quoted = source.match(/^(.+?)\s*[::]\s*[“\"]([\s\S]*?)[”\"]\s*$/),
|
|
116
|
+
plain = source.match(/^(.+?)\s*[::]\s*(.+)$/)
|
|
117
|
+
const speaker = (quoted || plain)?.[1]?.trim()
|
|
118
|
+
return speaker ? `${speaker}:“${replacement}”` : replacement
|
|
119
|
+
})
|
|
120
|
+
.join('')
|
|
121
|
+
}
|
|
122
|
+
// Kept equivalent to the website's buildDeliveryPromptPayload; verified against it.
|
|
123
|
+
export function deliveryBody(reversePrompt, value = {}) {
|
|
124
|
+
const input = deliveryInput(value),
|
|
125
|
+
sections = { character: '', scene: '', prop: '', dialogue: '' }
|
|
126
|
+
if (input.edited_sections) Object.assign(sections, input.edited_sections)
|
|
127
|
+
else {
|
|
128
|
+
const keys = {
|
|
129
|
+
角色: 'character',
|
|
130
|
+
场景: 'scene',
|
|
131
|
+
道具: 'prop',
|
|
132
|
+
台词: 'dialogue'
|
|
133
|
+
}
|
|
134
|
+
const pattern =
|
|
135
|
+
/【(角色|场景|道具|台词)】\s*([\s\S]*?)(?=【(?:上一段视频结尾|开头|镜头|画面|角色|场景|道具|台词|负面提示词)】|$)/g
|
|
136
|
+
for (const match of text(reversePrompt?.prompt).matchAll(pattern))
|
|
137
|
+
sections[keys[match[1]]] = match[2].trim()
|
|
138
|
+
}
|
|
139
|
+
const normalize = (list) =>
|
|
140
|
+
(Array.isArray(list) ? list : [])
|
|
141
|
+
.map((item) => ({
|
|
142
|
+
source: item.source.trim(),
|
|
143
|
+
target: item.target.trim()
|
|
144
|
+
}))
|
|
145
|
+
.filter(
|
|
146
|
+
(item) => item.source && item.target && item.source !== item.target
|
|
147
|
+
)
|
|
148
|
+
const rules = input.rules || {},
|
|
149
|
+
replacement = typeof rules.scene === 'string' ? rules.scene.trim() : ''
|
|
150
|
+
const scene = replacement
|
|
151
|
+
? sections.scene !== replacement
|
|
152
|
+
? [{ source: sections.scene, target: replacement }]
|
|
153
|
+
: []
|
|
154
|
+
: normalize(Array.isArray(rules.scene) ? rules.scene : rules.sceneList)
|
|
155
|
+
sections.dialogue = dialogueReplace(
|
|
156
|
+
sections.dialogue,
|
|
157
|
+
normalize(rules.dialogue)
|
|
158
|
+
)
|
|
159
|
+
if (
|
|
160
|
+
Object.values(sections).some(
|
|
161
|
+
(value) => !value.trim() || value.length > 12000
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
throw new Error('反推正文缺少有效角色/场景/道具/台词章节;不能补造内容提交')
|
|
165
|
+
if (
|
|
166
|
+
scene.some((item) => item.source.length > 300 || item.target.length > 300)
|
|
167
|
+
)
|
|
168
|
+
throw new Error(
|
|
169
|
+
'场景映射超过接口300字符限制,改用有效的章节编辑或短名称替换'
|
|
170
|
+
)
|
|
171
|
+
return {
|
|
172
|
+
character: normalize(rules.character),
|
|
173
|
+
scene,
|
|
174
|
+
prop: normalize(rules.prop),
|
|
175
|
+
cultural_reference: '',
|
|
176
|
+
sections,
|
|
177
|
+
reference_images: []
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const promptOf = (job, index) =>
|
|
181
|
+
(job.delivery_prompts || []).find((item) => item.segment_index === index) ||
|
|
182
|
+
null
|
|
183
|
+
const runState = (ctx) => ({
|
|
184
|
+
status:
|
|
185
|
+
ctx.output.delivery_prompt_status ||
|
|
186
|
+
(ctx.result ? 'completed' : 'not_started'),
|
|
187
|
+
prompt: ctx.result,
|
|
188
|
+
billing: ctx.output.delivery_prompt_billing || null
|
|
189
|
+
})
|
|
190
|
+
// The current attachment worker passes image URLs straight to the model and
|
|
191
|
+
// fetches PDFs by URL. A browser-only proxy address is not a storage resolver.
|
|
192
|
+
export function deliveryAttachmentBlockers(attachments) {
|
|
193
|
+
return attachments.flatMap((a, index) => {
|
|
194
|
+
const url = new URL(a.url),
|
|
195
|
+
host = url.hostname.toLowerCase().replace(/^\[|\]$/g, '')
|
|
196
|
+
if (url.protocol === 'data:') {
|
|
197
|
+
const prefix = `data:${a.media_type.toLowerCase()};base64,`
|
|
198
|
+
if (
|
|
199
|
+
a.url.length <= 8000000 &&
|
|
200
|
+
a.url.toLowerCase().startsWith(prefix) &&
|
|
201
|
+
/^[a-zA-Z0-9+/]+={0,2}$/.test(a.url.slice(prefix.length))
|
|
202
|
+
)
|
|
203
|
+
return []
|
|
204
|
+
} else if (
|
|
205
|
+
url.protocol === 'https:' &&
|
|
206
|
+
!(
|
|
207
|
+
/^(?:localhost|.*\.localhost|.*\.local|0\.|10\.|127\.|169\.254\.|192\.168\.|172\.(?:1[6-9]|2\d|3[01])\.)/.test(
|
|
208
|
+
host
|
|
209
|
+
) ||
|
|
210
|
+
host === '::' ||
|
|
211
|
+
host === '::1' ||
|
|
212
|
+
/^(?:fc|fd|fe[89ab])[0-9a-f]*:/i.test(host) ||
|
|
213
|
+
host.includes(':')
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
return []
|
|
217
|
+
return [
|
|
218
|
+
{
|
|
219
|
+
code: 'DELIVERY_ATTACHMENT_ADDRESS_UNUSABLE',
|
|
220
|
+
attachment_index: index + 1,
|
|
221
|
+
name: a.name,
|
|
222
|
+
message:
|
|
223
|
+
'附件已上传,但当前地址只适用于本机/内网或格式不适合服务端读取;原文件引用保留。需同服务可访问的HTTPS文件地址,或服务端解析存储引用后再处理。'
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
async function context(client, profile, parent, index) {
|
|
229
|
+
const ctx = await repaintPromptContext(client, profile, parent, index)
|
|
230
|
+
const reverse = ctx.output.reverse_prompt,
|
|
231
|
+
reverseStatus =
|
|
232
|
+
ctx.output.reverse_prompt_status ||
|
|
233
|
+
(reverse ? 'completed' : 'not_started')
|
|
234
|
+
if (reverseStatus !== 'completed' || !text(reverse?.prompt).trim())
|
|
235
|
+
throw new Error('先完成原片段反推,不用空节点代替反推结果')
|
|
236
|
+
if (ctx.final.type !== 'text') throw new Error('最终提示词节点类型已变化')
|
|
237
|
+
const generation = ctx.doc.nodes.filter(
|
|
238
|
+
(n) =>
|
|
239
|
+
repaintOf(n).jobId === ctx.job.id &&
|
|
240
|
+
repaintOf(n).role === 'generation' &&
|
|
241
|
+
repaintOf(n).segmentIndex === index
|
|
242
|
+
)
|
|
243
|
+
if (
|
|
244
|
+
generation.length !== 1 ||
|
|
245
|
+
!ctx.doc.edges.some(
|
|
246
|
+
(e) =>
|
|
247
|
+
e.source === ctx.final.id &&
|
|
248
|
+
e.target === generation[0].id &&
|
|
249
|
+
e.data?.role === 'final-generation-link'
|
|
250
|
+
) ||
|
|
251
|
+
!ctx.doc.edges.some(
|
|
252
|
+
(e) =>
|
|
253
|
+
e.source === ctx.global.id &&
|
|
254
|
+
e.target === ctx.final.id &&
|
|
255
|
+
e.data?.role === 'global-prompt-link'
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
throw new Error('最终提示词下游或全局系统线缺失,不重建')
|
|
259
|
+
const localAttachments = repaintAttachmentView(
|
|
260
|
+
repaintOf(ctx.global).attachments,
|
|
261
|
+
client.baseUrl
|
|
262
|
+
),
|
|
263
|
+
remoteAttachments = repaintAttachmentView(
|
|
264
|
+
ctx.job.delivery_prompt_attachments,
|
|
265
|
+
client.baseUrl
|
|
266
|
+
)
|
|
267
|
+
if (!isDeepStrictEqual(localAttachments, remoteAttachments))
|
|
268
|
+
throw new Error('画布与后端全局附件不一致,不清空或覆盖后提交')
|
|
269
|
+
ctx.attachments = remoteAttachments
|
|
270
|
+
ctx.result = promptOf(ctx.job, index)
|
|
271
|
+
ctx.node = ctx.final
|
|
272
|
+
ctx.binding = {
|
|
273
|
+
...ctx.binding,
|
|
274
|
+
node_id: ctx.final.id,
|
|
275
|
+
reverse_hash: hash(reverse),
|
|
276
|
+
...(ctx.attachments.length ? { attachments: ctx.attachments } : {})
|
|
277
|
+
}
|
|
278
|
+
return ctx
|
|
279
|
+
}
|
|
280
|
+
async function quote(client, ctx) {
|
|
281
|
+
const pricing = await api(client, '/video-segmentation/pricing'),
|
|
282
|
+
unit = pricing.ai_edit_credits_per_segment,
|
|
283
|
+
benefit = ctx.job.billing?.has_base_benefit
|
|
284
|
+
if (
|
|
285
|
+
pricing.currency !== 'OO' ||
|
|
286
|
+
!Number.isSafeInteger(unit) ||
|
|
287
|
+
unit < 0 ||
|
|
288
|
+
typeof benefit !== 'boolean'
|
|
289
|
+
)
|
|
290
|
+
throw new Error('最终提示词实时单价或基础权益未知')
|
|
291
|
+
const me = await client.raw('/auth/me'),
|
|
292
|
+
team =
|
|
293
|
+
ctx.parent.input.space === 'team'
|
|
294
|
+
? me.teams?.find(
|
|
295
|
+
(t) =>
|
|
296
|
+
text(t.uid) === ctx.parent.input.team_uid && t.status === 'active'
|
|
297
|
+
)
|
|
298
|
+
: null
|
|
299
|
+
if (
|
|
300
|
+
text(me.uid) !== ctx.parent.owner ||
|
|
301
|
+
(ctx.parent.input.space === 'team' && !team)
|
|
302
|
+
)
|
|
303
|
+
throw new Error('账号或团队成员身份变化')
|
|
304
|
+
const first = benefit && !ctx.result
|
|
305
|
+
let extra = 0,
|
|
306
|
+
attachmentPricing
|
|
307
|
+
if (ctx.attachments.length) {
|
|
308
|
+
const keys = [
|
|
309
|
+
'attachment_free_images',
|
|
310
|
+
'attachment_free_docs',
|
|
311
|
+
'attachment_extra_fee',
|
|
312
|
+
'attachment_max'
|
|
313
|
+
]
|
|
314
|
+
if (
|
|
315
|
+
keys.some((k) => !Number.isSafeInteger(pricing[k]) || pricing[k] < 0) ||
|
|
316
|
+
pricing.attachment_max < ctx.attachments.length
|
|
317
|
+
)
|
|
318
|
+
throw new Error('附件实时限额/价格无效或数量超限')
|
|
319
|
+
const images = ctx.attachments.filter((a) =>
|
|
320
|
+
a.media_type.toLowerCase().startsWith('image/')
|
|
321
|
+
).length,
|
|
322
|
+
docs = ctx.attachments.length - images
|
|
323
|
+
extra =
|
|
324
|
+
images > pricing.attachment_free_images ||
|
|
325
|
+
docs > pricing.attachment_free_docs
|
|
326
|
+
? pricing.attachment_extra_fee
|
|
327
|
+
: 0
|
|
328
|
+
const estimated = await api(
|
|
329
|
+
client,
|
|
330
|
+
jobPath(ctx.job.id) + '/billing-estimate',
|
|
331
|
+
{
|
|
332
|
+
method: 'POST',
|
|
333
|
+
body: { segment_count: 1, attachments: ctx.attachments }
|
|
334
|
+
},
|
|
335
|
+
{ retry: false }
|
|
336
|
+
)
|
|
337
|
+
if (
|
|
338
|
+
estimated?.currency !== 'OO' ||
|
|
339
|
+
estimated.segment_count !== 1 ||
|
|
340
|
+
['ai_edit_credits_per_segment', ...keys].some(
|
|
341
|
+
(k) => estimated.pricing?.[k] !== pricing[k]
|
|
342
|
+
) ||
|
|
343
|
+
estimated.manual_segmentation !== !benefit ||
|
|
344
|
+
estimated.attachments?.count !== ctx.attachments.length ||
|
|
345
|
+
estimated.attachments.image_files !== images ||
|
|
346
|
+
estimated.attachments.pdf_files !== docs ||
|
|
347
|
+
estimated.attachments.extra_fee !== extra ||
|
|
348
|
+
estimated.breakdown?.ai_edit?.first_run !== (benefit ? 0 : unit) ||
|
|
349
|
+
estimated.breakdown?.ai_edit?.rerun_all !== unit ||
|
|
350
|
+
estimated.breakdown?.attachment_extra?.first_run !== extra ||
|
|
351
|
+
estimated.breakdown?.attachment_extra?.rerun_all !== extra
|
|
352
|
+
)
|
|
353
|
+
throw new Error(
|
|
354
|
+
'附件预估与实时价格/数量/权益不一致;不使用历史任务价格或整任务总价提交单段处理'
|
|
355
|
+
)
|
|
356
|
+
attachmentPricing = {
|
|
357
|
+
images,
|
|
358
|
+
docs,
|
|
359
|
+
...Object.fromEntries(keys.map((k) => [k, pricing[k]])),
|
|
360
|
+
extra_fee_per_segment_per_run: extra,
|
|
361
|
+
server_estimate_verified: true
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
currency: 'OO',
|
|
366
|
+
credits: (first ? 0 : unit) + extra,
|
|
367
|
+
base_credits: first ? 0 : unit,
|
|
368
|
+
attachment_credits: extra,
|
|
369
|
+
unit_price: unit,
|
|
370
|
+
first_run_included: first,
|
|
371
|
+
attachments: ctx.attachments.length,
|
|
372
|
+
...(attachmentPricing ? { attachment_pricing: attachmentPricing } : {}),
|
|
373
|
+
wallet: {
|
|
374
|
+
space: ctx.parent.input.space,
|
|
375
|
+
team_uid: ctx.parent.input.team_uid || null,
|
|
376
|
+
team_name: team?.name || null,
|
|
377
|
+
shared: Boolean(team),
|
|
378
|
+
balance_snapshot: (team || me).oo_balance ?? null
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const costKey = (q) =>
|
|
383
|
+
hash({
|
|
384
|
+
credits: q.credits,
|
|
385
|
+
unit_price: q.unit_price,
|
|
386
|
+
first_run_included: q.first_run_included,
|
|
387
|
+
space: q.wallet.space,
|
|
388
|
+
team_uid: q.wallet.team_uid,
|
|
389
|
+
...(q.attachments
|
|
390
|
+
? { attachments: q.attachments, attachment_pricing: q.attachment_pricing }
|
|
391
|
+
: {})
|
|
392
|
+
})
|
|
393
|
+
async function bound(client, profile, operation) {
|
|
394
|
+
const record = await json(deliveryPath(profile, operation), null)
|
|
395
|
+
if (
|
|
396
|
+
!record ||
|
|
397
|
+
record.schema !== 1 ||
|
|
398
|
+
record.profile !== profile ||
|
|
399
|
+
record.baseUrl !== client.baseUrl
|
|
400
|
+
)
|
|
401
|
+
throw new Error('最终提示词operation不存在或服务不匹配')
|
|
402
|
+
const ctx = await context(
|
|
403
|
+
client,
|
|
404
|
+
profile,
|
|
405
|
+
record.parentOperation,
|
|
406
|
+
record.index
|
|
407
|
+
)
|
|
408
|
+
if (
|
|
409
|
+
record.owner !== ctx.parent.owner ||
|
|
410
|
+
!isDeepStrictEqual(ctx.binding, record.binding)
|
|
411
|
+
)
|
|
412
|
+
throw new Error('原方案、反推原文、全局规则/附件或节点关联变化,停止恢复')
|
|
413
|
+
return { record, ctx }
|
|
414
|
+
}
|
|
415
|
+
async function preventDuplicate(profile, operation, ctx) {
|
|
416
|
+
const dir = path.dirname(deliveryPath(profile, operation)),
|
|
417
|
+
files = await fs.readdir(dir).catch((e) => {
|
|
418
|
+
if (e.code === 'ENOENT') return []
|
|
419
|
+
throw e
|
|
420
|
+
})
|
|
421
|
+
for (const file of files.filter((f) => f.endsWith('.json'))) {
|
|
422
|
+
const r = await json(path.join(dir, file))
|
|
423
|
+
if (
|
|
424
|
+
r.operation !== operation &&
|
|
425
|
+
r.owner === ctx.parent.owner &&
|
|
426
|
+
r.baseUrl === ctx.parent.baseUrl &&
|
|
427
|
+
r.binding?.job_id === ctx.job.id &&
|
|
428
|
+
r.index === ctx.binding.index &&
|
|
429
|
+
r.attempted &&
|
|
430
|
+
!['completed', 'completed_fallback', 'failed'].includes(r.state)
|
|
431
|
+
)
|
|
432
|
+
throw new Error('该片段已有未结束或未知最终提示词operation,不能换名重提')
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
function summary(record, ctx) {
|
|
436
|
+
const state = runState(ctx),
|
|
437
|
+
prompt = state.prompt?.prompt
|
|
438
|
+
return {
|
|
439
|
+
operation: record.operation,
|
|
440
|
+
repaint_operation: record.parentOperation,
|
|
441
|
+
state: record.state,
|
|
442
|
+
job_id: ctx.job.id,
|
|
443
|
+
plan_version: ctx.job.plan_version,
|
|
444
|
+
segment_index: record.index,
|
|
445
|
+
node_id: ctx.node.id,
|
|
446
|
+
attachment_count: ctx.attachments.length,
|
|
447
|
+
attachment_address_blockers: deliveryAttachmentBlockers(ctx.attachments),
|
|
448
|
+
processing_submitted: record.accepted
|
|
449
|
+
? true
|
|
450
|
+
: record.attempted
|
|
451
|
+
? null
|
|
452
|
+
: false,
|
|
453
|
+
remote_status: state.status,
|
|
454
|
+
canvas_synced:
|
|
455
|
+
record.canvasSynced === true &&
|
|
456
|
+
Boolean(prompt) &&
|
|
457
|
+
isDeepStrictEqual(fields(ctx.node), {
|
|
458
|
+
input: prompt,
|
|
459
|
+
prompt,
|
|
460
|
+
output: prompt
|
|
461
|
+
}),
|
|
462
|
+
billing: state.billing,
|
|
463
|
+
delivery_prompt: state.prompt,
|
|
464
|
+
fallback: ctx.output.delivery_prompt_fallback === true,
|
|
465
|
+
warning: ctx.output.delivery_prompt_warning || null,
|
|
466
|
+
remote_error: ctx.output.delivery_prompt_error || null,
|
|
467
|
+
library_saved: false
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
export function validateDeliveryRun(record, ctx) {
|
|
471
|
+
if (!record.accepted)
|
|
472
|
+
throw fail(
|
|
473
|
+
'DELIVERY_SUBMISSION_UNKNOWN',
|
|
474
|
+
'最终提示词响应未知;仅查询原任务,不认领观察结果或重发POST'
|
|
475
|
+
)
|
|
476
|
+
const state = runState(ctx),
|
|
477
|
+
billing = state.billing
|
|
478
|
+
if (
|
|
479
|
+
!billing ||
|
|
480
|
+
billing.plan_version !== record.binding.plan_version ||
|
|
481
|
+
billing.amount !== record.approvedCost ||
|
|
482
|
+
(record.reservationKey && billing.reservation_key !== record.reservationKey)
|
|
483
|
+
)
|
|
484
|
+
throw new Error('最终提示词运行/计费标记变化,不认领另一轮结果')
|
|
485
|
+
if (state.status === 'completed' && !text(state.prompt?.prompt).trim())
|
|
486
|
+
throw new Error('完成但缺少真实最终提示词,不回填空内容')
|
|
487
|
+
if (
|
|
488
|
+
record.resultHash &&
|
|
489
|
+
(state.status !== 'completed' || hash(state.prompt) !== record.resultHash)
|
|
490
|
+
)
|
|
491
|
+
throw new Error('原最终提示词被另一轮替换,停止覆盖')
|
|
492
|
+
return state
|
|
493
|
+
}
|
|
494
|
+
const validateRun = validateDeliveryRun
|
|
495
|
+
export async function startDelivery(
|
|
496
|
+
client,
|
|
497
|
+
profile,
|
|
498
|
+
parent,
|
|
499
|
+
index,
|
|
500
|
+
operation,
|
|
501
|
+
value = {},
|
|
502
|
+
confirmed = false,
|
|
503
|
+
expectedCost
|
|
504
|
+
) {
|
|
505
|
+
safeName(parent)
|
|
506
|
+
safeName(operation)
|
|
507
|
+
const input = deliveryInput(value),
|
|
508
|
+
ctx = await context(client, profile, parent, index)
|
|
509
|
+
let record = await json(deliveryPath(profile, operation), null)
|
|
510
|
+
if (
|
|
511
|
+
record &&
|
|
512
|
+
(record.parentOperation !== parent ||
|
|
513
|
+
record.index !== index ||
|
|
514
|
+
record.baseUrl !== client.baseUrl ||
|
|
515
|
+
record.owner !== ctx.parent.owner ||
|
|
516
|
+
!isDeepStrictEqual(record.binding, ctx.binding) ||
|
|
517
|
+
!isDeepStrictEqual(record.input, input))
|
|
518
|
+
)
|
|
519
|
+
throw new Error('原最终提示词operation参数不能修改')
|
|
520
|
+
if (record?.attempted) return recoverDelivery(client, profile, operation)
|
|
521
|
+
await preventDuplicate(profile, operation, ctx)
|
|
522
|
+
const state = runState(ctx)
|
|
523
|
+
if (!['not_started', 'completed'].includes(state.status))
|
|
524
|
+
throw new Error(
|
|
525
|
+
'该片段最终提示词运行中、失败或状态未知,先查原操作,不自动重做'
|
|
526
|
+
)
|
|
527
|
+
const body = deliveryBody(ctx.output.reverse_prompt, input),
|
|
528
|
+
estimate = await quote(client, ctx),
|
|
529
|
+
blockers = deliveryAttachmentBlockers(ctx.attachments)
|
|
530
|
+
if (!confirmed) {
|
|
531
|
+
record = {
|
|
532
|
+
schema: 1,
|
|
533
|
+
profile,
|
|
534
|
+
operation,
|
|
535
|
+
parentOperation: parent,
|
|
536
|
+
index,
|
|
537
|
+
owner: ctx.parent.owner,
|
|
538
|
+
baseUrl: client.baseUrl,
|
|
539
|
+
binding: ctx.binding,
|
|
540
|
+
input,
|
|
541
|
+
body,
|
|
542
|
+
baseline: state,
|
|
543
|
+
nodeBefore: fields(ctx.node),
|
|
544
|
+
estimate,
|
|
545
|
+
estimateKey: costKey(estimate),
|
|
546
|
+
state: 'prepared',
|
|
547
|
+
createdAt: new Date().toISOString(),
|
|
548
|
+
...(record?.settingsAttempted
|
|
549
|
+
? { settingsAttempted: true, settingsVerified: record.settingsVerified }
|
|
550
|
+
: {})
|
|
551
|
+
}
|
|
552
|
+
await persist(record)
|
|
553
|
+
return {
|
|
554
|
+
...summary(record, ctx),
|
|
555
|
+
submitted: false,
|
|
556
|
+
submission_available: blockers.length === 0,
|
|
557
|
+
blockers,
|
|
558
|
+
estimate,
|
|
559
|
+
parameters: {
|
|
560
|
+
start_ms: ctx.binding.start_ms,
|
|
561
|
+
end_ms: ctx.binding.end_ms,
|
|
562
|
+
global_rules: ctx.binding.rules,
|
|
563
|
+
attachments: ctx.attachments.map((a) => ({
|
|
564
|
+
name: a.name,
|
|
565
|
+
media_type: a.media_type
|
|
566
|
+
})),
|
|
567
|
+
body,
|
|
568
|
+
model: 'service_managed'
|
|
569
|
+
},
|
|
570
|
+
next: '通过交互卡片明确替换要求或不替换,展示钱包/规则/费用;当次确认后同operation加--yes --expected-cost。不生成视频'
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if (blockers.length)
|
|
574
|
+
throw Object.assign(
|
|
575
|
+
fail(
|
|
576
|
+
'DELIVERY_ATTACHMENT_ADDRESS_UNUSABLE',
|
|
577
|
+
'附件已上传,但服务端/模型读取地址不适用;未提交最终提示词,不重新上传素材'
|
|
578
|
+
),
|
|
579
|
+
{ blockers }
|
|
580
|
+
)
|
|
581
|
+
if (
|
|
582
|
+
!record ||
|
|
583
|
+
expectedCost === undefined ||
|
|
584
|
+
Number(expectedCost) !== estimate.credits ||
|
|
585
|
+
record.estimateKey !== costKey(estimate) ||
|
|
586
|
+
!isDeepStrictEqual(record.body, body) ||
|
|
587
|
+
!isDeepStrictEqual(record.baseline, state) ||
|
|
588
|
+
!isDeepStrictEqual(record.nodeBefore, fields(ctx.node))
|
|
589
|
+
)
|
|
590
|
+
throw new Error('先预估并当次确认;费用/原文/规则/节点变化须重新核对')
|
|
591
|
+
await requireServiceReady(client)
|
|
592
|
+
if (record.settingsAttempted && !record.settingsVerified)
|
|
593
|
+
throw fail(
|
|
594
|
+
'DELIVERY_SUBMISSION_UNKNOWN',
|
|
595
|
+
'设置保存响应未知;先delivery-recover核对,不重发设置或生成'
|
|
596
|
+
)
|
|
597
|
+
if (!record.settingsVerified) {
|
|
598
|
+
record.settingsAttempted = true
|
|
599
|
+
record.state = 'saving_settings'
|
|
600
|
+
await persist(record)
|
|
601
|
+
try {
|
|
602
|
+
await api(
|
|
603
|
+
client,
|
|
604
|
+
jobPath(ctx.job.id) + '/delivery-prompt-settings',
|
|
605
|
+
{
|
|
606
|
+
method: 'POST',
|
|
607
|
+
body: {
|
|
608
|
+
custom_prompt: ctx.binding.rules,
|
|
609
|
+
attachments: ctx.attachments
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
{ retry: false }
|
|
613
|
+
)
|
|
614
|
+
record.settingsVerified = true
|
|
615
|
+
await persist(record)
|
|
616
|
+
} catch (error) {
|
|
617
|
+
record.state = 'settings_unknown'
|
|
618
|
+
await persist(record)
|
|
619
|
+
throw fail(
|
|
620
|
+
'DELIVERY_SUBMISSION_UNKNOWN',
|
|
621
|
+
'全局设置保存未确认,未发送生成;先检查后端并查原操作',
|
|
622
|
+
error
|
|
623
|
+
)
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
const fresh = await context(client, profile, parent, index)
|
|
627
|
+
if (
|
|
628
|
+
!isDeepStrictEqual(fresh.binding, record.binding) ||
|
|
629
|
+
!isDeepStrictEqual(runState(fresh), record.baseline) ||
|
|
630
|
+
!isDeepStrictEqual(fields(fresh.node), record.nodeBefore) ||
|
|
631
|
+
costKey(await quote(client, fresh)) !== record.estimateKey
|
|
632
|
+
)
|
|
633
|
+
throw new Error('设置保存后参数/费用变化,尚未提交最终提示词')
|
|
634
|
+
record.attempted = true
|
|
635
|
+
record.approvedCost = estimate.credits
|
|
636
|
+
record.state = 'submitting'
|
|
637
|
+
await persist(record)
|
|
638
|
+
try {
|
|
639
|
+
const response = await api(
|
|
640
|
+
client,
|
|
641
|
+
jobPath(ctx.job.id) + '/segments/' + index + '/delivery-prompt',
|
|
642
|
+
{ method: 'POST', body },
|
|
643
|
+
{ retry: false }
|
|
644
|
+
),
|
|
645
|
+
output = response?.outputs?.find((o) => o.index === index && !o.type)
|
|
646
|
+
if (
|
|
647
|
+
response?.id === ctx.job.id &&
|
|
648
|
+
response.canvas_doc_id === ctx.doc.id &&
|
|
649
|
+
text(response.team_uid || '') === text(ctx.doc.team_uid || '') &&
|
|
650
|
+
response.plan_version === record.binding.plan_version &&
|
|
651
|
+
output?.delivery_prompt_billing?.plan_version ===
|
|
652
|
+
record.binding.plan_version
|
|
653
|
+
) {
|
|
654
|
+
record.accepted = true
|
|
655
|
+
record.reservationKey =
|
|
656
|
+
output.delivery_prompt_billing.reservation_key || null
|
|
657
|
+
await persist(record)
|
|
658
|
+
}
|
|
659
|
+
if (!record.accepted) throw new Error('响应缺少本次原任务运行标记')
|
|
660
|
+
record.state = 'running'
|
|
661
|
+
await persist(record)
|
|
662
|
+
} catch (error) {
|
|
663
|
+
record.state = 'submission_unknown'
|
|
664
|
+
await persist(record)
|
|
665
|
+
throw fail(
|
|
666
|
+
'DELIVERY_SUBMISSION_UNKNOWN',
|
|
667
|
+
'最终提示词提交结果未确认;仅查原job/segment,不重发或换operation',
|
|
668
|
+
error
|
|
669
|
+
)
|
|
670
|
+
}
|
|
671
|
+
return recoverDelivery(client, profile, operation)
|
|
672
|
+
}
|
|
673
|
+
export async function getDelivery(client, profile, operation) {
|
|
674
|
+
const { record, ctx } = await bound(client, profile, operation)
|
|
675
|
+
if (record.accepted) validateRun(record, ctx)
|
|
676
|
+
return summary(record, ctx)
|
|
677
|
+
}
|
|
678
|
+
export async function recoverDelivery(client, profile, operation) {
|
|
679
|
+
const { record, ctx } = await bound(client, profile, operation)
|
|
680
|
+
if (!record.attempted) {
|
|
681
|
+
if (record.settingsAttempted) {
|
|
682
|
+
record.settingsVerified = true
|
|
683
|
+
record.state = 'settings_verified'
|
|
684
|
+
await persist(record)
|
|
685
|
+
}
|
|
686
|
+
return summary(record, ctx)
|
|
687
|
+
}
|
|
688
|
+
const state = validateRun(record, ctx)
|
|
689
|
+
if (state.status === 'failed') {
|
|
690
|
+
record.state = 'failed'
|
|
691
|
+
await persist(record)
|
|
692
|
+
return summary(record, ctx)
|
|
693
|
+
}
|
|
694
|
+
if (state.status !== 'completed') {
|
|
695
|
+
record.state = 'running'
|
|
696
|
+
await persist(record)
|
|
697
|
+
return summary(record, ctx)
|
|
698
|
+
}
|
|
699
|
+
await archiveRepaintResult(record, 'delivery', state.prompt, state.billing, { fallback: ctx.output.delivery_prompt_fallback === true, warning: ctx.output.delivery_prompt_warning || null })
|
|
700
|
+
const prompt = state.prompt.prompt,
|
|
701
|
+
expected = { input: prompt, prompt, output: prompt },
|
|
702
|
+
current = fields(ctx.node)
|
|
703
|
+
if (
|
|
704
|
+
!isDeepStrictEqual(current, expected) &&
|
|
705
|
+
!isDeepStrictEqual(current, record.nodeBefore)
|
|
706
|
+
)
|
|
707
|
+
throw fail(
|
|
708
|
+
'DELIVERY_SYNC_PENDING',
|
|
709
|
+
'最终提示词节点已被用户编辑,不覆盖;原结果仍可查询'
|
|
710
|
+
)
|
|
711
|
+
record.resultHash = hash(state.prompt)
|
|
712
|
+
record.state = ctx.output.delivery_prompt_fallback
|
|
713
|
+
? 'completed_fallback'
|
|
714
|
+
: 'completed'
|
|
715
|
+
await persist(record)
|
|
716
|
+
if (!isDeepStrictEqual(current, expected)) {
|
|
717
|
+
ctx.node.data = { ...ctx.node.data, ...expected }
|
|
718
|
+
try {
|
|
719
|
+
const saved = await api(
|
|
720
|
+
client,
|
|
721
|
+
'/canvas/docs/' + encodeURIComponent(ctx.doc.id),
|
|
722
|
+
{
|
|
723
|
+
method: 'PUT',
|
|
724
|
+
body: {
|
|
725
|
+
version: ctx.doc.version,
|
|
726
|
+
name: ctx.doc.name,
|
|
727
|
+
viewport: ctx.doc.viewport,
|
|
728
|
+
nodes: ctx.doc.nodes,
|
|
729
|
+
edges: ctx.doc.edges,
|
|
730
|
+
meta: ctx.doc.meta
|
|
731
|
+
}
|
|
732
|
+
},
|
|
733
|
+
{ retry: false }
|
|
734
|
+
)
|
|
735
|
+
if (
|
|
736
|
+
saved.version !== ctx.doc.version + 1 ||
|
|
737
|
+
!isDeepStrictEqual(
|
|
738
|
+
fields(saved.nodes?.find((n) => n.id === ctx.node.id) || {}),
|
|
739
|
+
expected
|
|
740
|
+
)
|
|
741
|
+
)
|
|
742
|
+
throw new Error('保存响应不完整')
|
|
743
|
+
} catch (error) {
|
|
744
|
+
record.state = 'sync_pending'
|
|
745
|
+
await persist(record)
|
|
746
|
+
throw fail(
|
|
747
|
+
'DELIVERY_SYNC_PENDING',
|
|
748
|
+
'原最终提示词已完成,画布保存未确认;恢复只同步原结果,不重新生成',
|
|
749
|
+
error
|
|
750
|
+
)
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
record.canvasSynced = true
|
|
754
|
+
await persist(record)
|
|
755
|
+
return summary(record, ctx)
|
|
756
|
+
}
|
|
757
|
+
export async function waitDelivery(
|
|
758
|
+
client,
|
|
759
|
+
profile,
|
|
760
|
+
operation,
|
|
761
|
+
interval = 2000,
|
|
762
|
+
timeout = 180000
|
|
763
|
+
) {
|
|
764
|
+
if (![interval, timeout].every((v) => Number.isSafeInteger(v) && v > 0))
|
|
765
|
+
throw new Error('等待参数必须为正整数')
|
|
766
|
+
const deadline = Date.now() + timeout
|
|
767
|
+
while (Date.now() < deadline) {
|
|
768
|
+
const result = await recoverDelivery(client, profile, operation)
|
|
769
|
+
if (['completed', 'completed_fallback'].includes(result.state))
|
|
770
|
+
return result
|
|
771
|
+
if (result.state === 'failed')
|
|
772
|
+
throw fail('TASK_FAILED', '原最终提示词任务失败,不自动重做')
|
|
773
|
+
if (!result.processing_submitted) return result
|
|
774
|
+
await sleep(
|
|
775
|
+
Math.min(interval, Math.max(1, deadline - Date.now())),
|
|
776
|
+
undefined,
|
|
777
|
+
{
|
|
778
|
+
signal: cancellation.signal
|
|
779
|
+
}
|
|
780
|
+
)
|
|
781
|
+
}
|
|
782
|
+
throw fail('WAIT_TIMEOUT', '最终提示词等待超时;继续原operation,不重新提交')
|
|
783
|
+
}
|