@lingjingai/lj-awb-cli-pre 0.3.15
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/README.md +335 -0
- package/build/_shared.mjs +130 -0
- package/build/build.mjs +50 -0
- package/build/pre-publish.mjs +57 -0
- package/build/pre.mjs +42 -0
- package/build/prod.mjs +52 -0
- package/install.mjs +53 -0
- package/package.json +44 -0
- package/packages/awb-cli/README.md +19 -0
- package/packages/awb-cli/bin/lj-awb +19 -0
- package/packages/awb-cli/bin/lj-awb.js +11 -0
- package/packages/awb-cli/package.json +18 -0
- package/packages/awb-core/README.md +12 -0
- package/packages/awb-core/package.json +21 -0
- package/packages/awb-core/src/api.js +349 -0
- package/packages/awb-core/src/artifact.js +936 -0
- package/packages/awb-core/src/auth.js +80 -0
- package/packages/awb-core/src/commands.js +1321 -0
- package/packages/awb-core/src/common.js +508 -0
- package/packages/awb-core/src/output.js +1189 -0
- package/packages/awb-core/src/services.js +3811 -0
- package/packages/awb-core/src/standalone.js +1213 -0
- package/skills/lj-awb/SKILL.md +160 -0
- package/skills/lj-awb/VERSION +1 -0
- package/skills/lj-awb/compat.json +6 -0
- package/skills/lj-awb/modules/account.md +30 -0
- package/skills/lj-awb/modules/artifact/asset.md +64 -0
- package/skills/lj-awb/modules/artifact/clip.md +65 -0
- package/skills/lj-awb/modules/artifact/script.md +37 -0
- package/skills/lj-awb/modules/artifact/video.md +65 -0
- package/skills/lj-awb/modules/artifact.md +65 -0
- package/skills/lj-awb/modules/asset.md +53 -0
- package/skills/lj-awb/modules/auth.md +30 -0
- package/skills/lj-awb/modules/create-contract.md +118 -0
- package/skills/lj-awb/modules/credits.md +28 -0
- package/skills/lj-awb/modules/evals.md +186 -0
- package/skills/lj-awb/modules/image.md +75 -0
- package/skills/lj-awb/modules/model.md +110 -0
- package/skills/lj-awb/modules/project.md +30 -0
- package/skills/lj-awb/modules/subject.md +32 -0
- package/skills/lj-awb/modules/task-manual.md +185 -0
- package/skills/lj-awb/modules/task.md +62 -0
- package/skills/lj-awb/modules/upload.md +33 -0
- package/skills/lj-awb/modules/video.md +102 -0
- package/skills/lj-awb/modules/workflows.md +482 -0
- package/skills/lj-awb/references/error-codes.md +102 -0
- package/skills/lj-awb/references/model-options-read.md +49 -0
- package/skills/lj-awb/references/output-fields.md +113 -0
- package/skills/lj-awb/scripts/resolve-lj-awb-cmd.sh +10 -0
|
@@ -0,0 +1,936 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { apiFetch } from './api.js';
|
|
4
|
+
import { LingjingAwbCliError, parseJsonArg, toBool, trimToNull } from './common.js';
|
|
5
|
+
|
|
6
|
+
const WORKBENCH_PREFIX = '/api/anime/workbench';
|
|
7
|
+
|
|
8
|
+
function argumentError(message, hint = '') {
|
|
9
|
+
return new LingjingAwbCliError(message, {
|
|
10
|
+
type: 'argument_error',
|
|
11
|
+
exitCode: 2,
|
|
12
|
+
hint,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function ensureConfirmed(kwargs, message, details = {}) {
|
|
17
|
+
if (toBool(kwargs?.dryRun)) return;
|
|
18
|
+
if (toBool(kwargs?.yes)) return;
|
|
19
|
+
throw new LingjingAwbCliError(message, {
|
|
20
|
+
type: 'confirmation_required',
|
|
21
|
+
exitCode: 10,
|
|
22
|
+
hint: '确认后追加 --yes 重试;如果只是预览,请追加 --dry-run。',
|
|
23
|
+
details,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function compactRecord(record = {}) {
|
|
28
|
+
return Object.fromEntries(Object.entries(record)
|
|
29
|
+
.filter(([, value]) => value !== undefined && value !== null && value !== ''));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isPlainObject(value) {
|
|
33
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function requireValue(kwargs, key, label = key) {
|
|
37
|
+
const value = trimToNull(kwargs?.[key]);
|
|
38
|
+
if (!value) {
|
|
39
|
+
throw argumentError(`缺少参数:--${label.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`)}`);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function requireProjectId(kwargs) {
|
|
45
|
+
const projectId = requireValue(kwargs, 'projectId', 'project-id');
|
|
46
|
+
if (!/^\d+$/.test(String(projectId))) throw argumentError('--project-id 必须是数字');
|
|
47
|
+
return projectId;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function requireNumericValue(kwargs, key, label = key) {
|
|
51
|
+
const value = requireValue(kwargs, key, label);
|
|
52
|
+
if (!/^\d+$/.test(String(value))) throw argumentError(`--${label.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`)} 必须是数字`);
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function artifactPath(kind, projectId, suffix = '') {
|
|
57
|
+
return `${WORKBENCH_PREFIX}/${kind}-output/projects/${encodeURIComponent(projectId)}${suffix}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function readJsonFile(filePath) {
|
|
61
|
+
const absolutePath = path.resolve(String(filePath || ''));
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(await fs.readFile(absolutePath, 'utf8'));
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error?.code === 'ENOENT') {
|
|
66
|
+
throw argumentError(`JSON 文件不存在:${absolutePath}`);
|
|
67
|
+
}
|
|
68
|
+
if (error instanceof SyntaxError) {
|
|
69
|
+
throw argumentError(`JSON 文件解析失败:${absolutePath}`, error.message);
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function pathExists(filePath) {
|
|
76
|
+
try {
|
|
77
|
+
await fs.access(filePath);
|
|
78
|
+
return true;
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function readBodyPayload(kwargs = {}) {
|
|
85
|
+
const bodyJson = trimToNull(kwargs.bodyJson);
|
|
86
|
+
const inputFile = trimToNull(kwargs.inputFile);
|
|
87
|
+
if (bodyJson) {
|
|
88
|
+
try {
|
|
89
|
+
return parseJsonArg(bodyJson);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
throw argumentError('--body-json 不是有效 JSON', error.message);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (inputFile) return await readJsonFile(inputFile);
|
|
95
|
+
throw argumentError('缺少请求体:传 --body-json <json> 或 --input-file <path>');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function firstValue(source, ...keys) {
|
|
99
|
+
if (!isPlainObject(source)) return undefined;
|
|
100
|
+
for (const key of keys) {
|
|
101
|
+
if (source[key] !== undefined && source[key] !== null) return source[key];
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function textValue(source, ...keys) {
|
|
107
|
+
const value = firstValue(source, ...keys);
|
|
108
|
+
return value === undefined || value === null ? undefined : String(value);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function numberFromId(value, fallback = 0) {
|
|
112
|
+
const match = String(value || '').match(/(\d+)/);
|
|
113
|
+
return match ? Number.parseInt(match[1], 10) : fallback;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function byKind(rows) {
|
|
117
|
+
return rows.reduce((acc, row) => {
|
|
118
|
+
const key = row.rowKind || 'unknown';
|
|
119
|
+
acc[key] = (acc[key] || 0) + 1;
|
|
120
|
+
return acc;
|
|
121
|
+
}, {});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function scriptDocumentRow(script) {
|
|
125
|
+
return compactRecord({
|
|
126
|
+
rowKind: 'document',
|
|
127
|
+
entityKey: 'document',
|
|
128
|
+
parentKey: null,
|
|
129
|
+
schemaVersion: firstValue(script, 'schemaVersion', 'schema_version'),
|
|
130
|
+
revisionNo: firstValue(script, 'revisionNo', 'revision_no') ?? 1,
|
|
131
|
+
sortOrder: 0,
|
|
132
|
+
title: script.title,
|
|
133
|
+
worldview: script.worldview,
|
|
134
|
+
worldviewRaw: firstValue(script, 'worldviewRaw', 'worldview_raw'),
|
|
135
|
+
style: script.style,
|
|
136
|
+
mode: script.mode,
|
|
137
|
+
compat: compactRecord({
|
|
138
|
+
source: 'script.json',
|
|
139
|
+
schemaVersion: firstValue(script, 'schemaVersion', 'schema_version'),
|
|
140
|
+
}),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function scriptAssetRows(script) {
|
|
145
|
+
const rows = [];
|
|
146
|
+
const actors = Array.isArray(script.actors) ? script.actors : [];
|
|
147
|
+
const locations = Array.isArray(script.locations) ? script.locations : [];
|
|
148
|
+
const props = Array.isArray(script.props) ? script.props : [];
|
|
149
|
+
|
|
150
|
+
for (const [index, actor] of actors.entries()) {
|
|
151
|
+
const assetKey = textValue(actor, 'actorKey', 'actor_key', 'actorId', 'actor_id');
|
|
152
|
+
if (!assetKey) continue;
|
|
153
|
+
rows.push(compactRecord({
|
|
154
|
+
rowKind: 'asset',
|
|
155
|
+
entityKey: assetKey,
|
|
156
|
+
sortOrder: index + 1,
|
|
157
|
+
assetKind: 'actor',
|
|
158
|
+
assetKey,
|
|
159
|
+
assetName: textValue(actor, 'actorName', 'actor_name', 'name'),
|
|
160
|
+
assetDescription: textValue(actor, 'description'),
|
|
161
|
+
roleType: textValue(actor, 'roleType', 'role_type'),
|
|
162
|
+
compat: actor,
|
|
163
|
+
}));
|
|
164
|
+
const states = Array.isArray(actor.states) ? actor.states : [];
|
|
165
|
+
for (const [stateIndex, state] of states.entries()) {
|
|
166
|
+
const stateKey = textValue(state, 'stateKey', 'state_key', 'stateId', 'state_id');
|
|
167
|
+
if (!stateKey) continue;
|
|
168
|
+
rows.push(compactRecord({
|
|
169
|
+
rowKind: 'state',
|
|
170
|
+
entityKey: `${assetKey}/${stateKey}`,
|
|
171
|
+
parentKey: assetKey,
|
|
172
|
+
sortOrder: stateIndex + 1,
|
|
173
|
+
assetKind: 'actor',
|
|
174
|
+
assetKey,
|
|
175
|
+
stateKey,
|
|
176
|
+
stateName: textValue(state, 'stateName', 'state_name'),
|
|
177
|
+
stateDescription: textValue(state, 'description'),
|
|
178
|
+
roleType: textValue(state, 'roleType', 'role_type') ?? textValue(actor, 'roleType', 'role_type'),
|
|
179
|
+
compat: state,
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
for (const [index, location] of locations.entries()) {
|
|
185
|
+
const assetKey = textValue(location, 'locationKey', 'location_key', 'locationId', 'location_id');
|
|
186
|
+
if (!assetKey) continue;
|
|
187
|
+
rows.push(compactRecord({
|
|
188
|
+
rowKind: 'asset',
|
|
189
|
+
entityKey: assetKey,
|
|
190
|
+
sortOrder: actors.length + index + 1,
|
|
191
|
+
assetKind: 'location',
|
|
192
|
+
assetKey,
|
|
193
|
+
assetName: textValue(location, 'locationName', 'location_name', 'name'),
|
|
194
|
+
assetDescription: textValue(location, 'description'),
|
|
195
|
+
groupName: textValue(location, 'groupName', 'group_name', 'group'),
|
|
196
|
+
compat: location,
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
for (const [index, prop] of props.entries()) {
|
|
201
|
+
const assetKey = textValue(prop, 'propKey', 'prop_key', 'propId', 'prop_id');
|
|
202
|
+
if (!assetKey) continue;
|
|
203
|
+
rows.push(compactRecord({
|
|
204
|
+
rowKind: 'asset',
|
|
205
|
+
entityKey: assetKey,
|
|
206
|
+
sortOrder: actors.length + locations.length + index + 1,
|
|
207
|
+
assetKind: 'prop',
|
|
208
|
+
assetKey,
|
|
209
|
+
assetName: textValue(prop, 'propName', 'prop_name', 'name'),
|
|
210
|
+
assetDescription: textValue(prop, 'description'),
|
|
211
|
+
compat: prop,
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return rows;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function scriptSpeakerRows(script) {
|
|
219
|
+
const speakers = Array.isArray(script.speakers) ? script.speakers : [];
|
|
220
|
+
return speakers
|
|
221
|
+
.map((speaker, index) => {
|
|
222
|
+
const speakerKey = textValue(speaker, 'speakerKey', 'speaker_key', 'speakerId', 'speaker_id', 'id', 'key');
|
|
223
|
+
if (!speakerKey) return null;
|
|
224
|
+
return compactRecord({
|
|
225
|
+
rowKind: 'speaker',
|
|
226
|
+
entityKey: speakerKey,
|
|
227
|
+
sortOrder: index + 1,
|
|
228
|
+
speakerKey,
|
|
229
|
+
speakerDisplayName: textValue(speaker, 'speakerDisplayName', 'speaker_display_name', 'displayName', 'display_name', 'name'),
|
|
230
|
+
speakerSourceKind: textValue(speaker, 'speakerSourceKind', 'speaker_source_kind', 'sourceKind', 'source_kind'),
|
|
231
|
+
speakerSourceKey: textValue(speaker, 'speakerSourceKey', 'speaker_source_key', 'sourceKey', 'source_key', 'sourceId', 'source_id'),
|
|
232
|
+
voiceDesc: textValue(speaker, 'voiceDesc', 'voice_desc'),
|
|
233
|
+
compat: speaker,
|
|
234
|
+
});
|
|
235
|
+
})
|
|
236
|
+
.filter(Boolean);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function scriptEpisodeRows(script) {
|
|
240
|
+
const rows = [];
|
|
241
|
+
const episodes = Array.isArray(script.episodes) ? script.episodes : [];
|
|
242
|
+
for (const [episodeIndex, episode] of episodes.entries()) {
|
|
243
|
+
const episodeKey = textValue(episode, 'episodeKey', 'episode_key', 'episodeId', 'episode_id');
|
|
244
|
+
if (!episodeKey) continue;
|
|
245
|
+
rows.push(compactRecord({
|
|
246
|
+
rowKind: 'episode',
|
|
247
|
+
entityKey: episodeKey,
|
|
248
|
+
sortOrder: episodeIndex + 1,
|
|
249
|
+
episodeKey,
|
|
250
|
+
episodeNo: numberFromId(episodeKey, episodeIndex + 1),
|
|
251
|
+
episodeTitle: textValue(episode, 'episodeTitle', 'episode_title', 'title'),
|
|
252
|
+
compat: { ...episode, scenes: undefined },
|
|
253
|
+
}));
|
|
254
|
+
const scenes = Array.isArray(episode.scenes) ? episode.scenes : [];
|
|
255
|
+
for (const [sceneIndex, scene] of scenes.entries()) {
|
|
256
|
+
const sceneKey = textValue(scene, 'sceneKey', 'scene_key', 'sceneId', 'scene_id');
|
|
257
|
+
if (!sceneKey) continue;
|
|
258
|
+
rows.push(compactRecord({
|
|
259
|
+
rowKind: 'scene',
|
|
260
|
+
entityKey: sceneKey,
|
|
261
|
+
parentKey: episodeKey,
|
|
262
|
+
sortOrder: sceneIndex + 1,
|
|
263
|
+
episodeKey,
|
|
264
|
+
episodeNo: numberFromId(episodeKey, episodeIndex + 1),
|
|
265
|
+
sceneKey,
|
|
266
|
+
sceneNo: numberFromId(sceneKey, sceneIndex + 1),
|
|
267
|
+
environmentSpace: firstValue(scene.environment, 'space'),
|
|
268
|
+
environmentTime: firstValue(scene.environment, 'time'),
|
|
269
|
+
sceneLocations: scene.locations,
|
|
270
|
+
sceneActors: scene.actors,
|
|
271
|
+
sceneProps: scene.props,
|
|
272
|
+
compat: { ...scene, actions: undefined },
|
|
273
|
+
}));
|
|
274
|
+
const actions = Array.isArray(scene.actions) ? scene.actions : [];
|
|
275
|
+
for (const [actionIndex, action] of actions.entries()) {
|
|
276
|
+
rows.push(compactRecord({
|
|
277
|
+
rowKind: 'action',
|
|
278
|
+
entityKey: `${episodeKey}/${sceneKey}#${actionIndex}`,
|
|
279
|
+
parentKey: sceneKey,
|
|
280
|
+
sortOrder: actionIndex,
|
|
281
|
+
episodeKey,
|
|
282
|
+
sceneKey,
|
|
283
|
+
actionIndex,
|
|
284
|
+
actionType: textValue(action, 'actionType', 'action_type', 'type'),
|
|
285
|
+
content: textValue(action, 'content'),
|
|
286
|
+
actorKey: textValue(action, 'actorKey', 'actor_key', 'actorId', 'actor_id'),
|
|
287
|
+
actionSpeakerKey: textValue(action, 'actionSpeakerKey', 'action_speaker_key', 'speakerKey', 'speaker_key'),
|
|
288
|
+
delivery: textValue(action, 'delivery'),
|
|
289
|
+
emotion: textValue(action, 'emotion'),
|
|
290
|
+
speakers: firstValue(action, 'speakers'),
|
|
291
|
+
lines: firstValue(action, 'lines'),
|
|
292
|
+
stateChanges: firstValue(action, 'stateChanges', 'state_changes'),
|
|
293
|
+
transitionPrompt: firstValue(action, 'transitionPrompt', 'transition_prompt'),
|
|
294
|
+
compat: action,
|
|
295
|
+
}));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return rows;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function buildScriptRows(script) {
|
|
303
|
+
if (!isPlainObject(script)) throw argumentError('script.json 顶层必须是 object');
|
|
304
|
+
return [
|
|
305
|
+
scriptDocumentRow(script),
|
|
306
|
+
...scriptAssetRows(script),
|
|
307
|
+
...scriptSpeakerRows(script),
|
|
308
|
+
...scriptEpisodeRows(script),
|
|
309
|
+
];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function stateEntries(entry) {
|
|
313
|
+
if (!isPlainObject(entry)) return [];
|
|
314
|
+
const reserved = new Set([
|
|
315
|
+
'id',
|
|
316
|
+
'project_id',
|
|
317
|
+
'projectId',
|
|
318
|
+
'name',
|
|
319
|
+
'actor_name',
|
|
320
|
+
'actorName',
|
|
321
|
+
'voice_text',
|
|
322
|
+
'voiceText',
|
|
323
|
+
'voice_desc',
|
|
324
|
+
'voiceDesc',
|
|
325
|
+
'voice',
|
|
326
|
+
'voice_url',
|
|
327
|
+
'voiceUrl',
|
|
328
|
+
'voice_status',
|
|
329
|
+
'voiceStatus',
|
|
330
|
+
'voice_candidates',
|
|
331
|
+
'voiceCandidates',
|
|
332
|
+
'selected_voice_candidate_id',
|
|
333
|
+
'selectedVoiceCandidateId',
|
|
334
|
+
'voice_backend',
|
|
335
|
+
'voiceBackend',
|
|
336
|
+
'voice_reference_image_url',
|
|
337
|
+
'voiceReferenceImageUrl',
|
|
338
|
+
'subject_id',
|
|
339
|
+
'subjectId',
|
|
340
|
+
'group',
|
|
341
|
+
'group_name',
|
|
342
|
+
'groupName',
|
|
343
|
+
'group_default',
|
|
344
|
+
'groupDefault',
|
|
345
|
+
'group_sheet',
|
|
346
|
+
'groupSheet',
|
|
347
|
+
'group_sheet_url',
|
|
348
|
+
'groupSheetUrl',
|
|
349
|
+
'group_id',
|
|
350
|
+
'groupId',
|
|
351
|
+
'image_prompts',
|
|
352
|
+
'imagePrompts',
|
|
353
|
+
'episodes',
|
|
354
|
+
'lab',
|
|
355
|
+
'is_deleted',
|
|
356
|
+
'isDeleted',
|
|
357
|
+
'created_at',
|
|
358
|
+
'createdAt',
|
|
359
|
+
'updated_at',
|
|
360
|
+
'updatedAt',
|
|
361
|
+
]);
|
|
362
|
+
return Object.entries(entry)
|
|
363
|
+
.filter(([key, value]) => !reserved.has(key) && isPlainObject(value))
|
|
364
|
+
.filter(([key, value]) => key === 'default' || /^st[_-]/i.test(key) || value.state_name != null || value.stateName != null);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function normalizeActorState(stateKey, state, actor = {}) {
|
|
368
|
+
return compactRecord({
|
|
369
|
+
stateKey,
|
|
370
|
+
stateName: textValue(state, 'stateName', 'state_name') ?? stateKey,
|
|
371
|
+
roleType: textValue(state, 'roleType', 'role_type') ?? textValue(actor, 'roleType', 'role_type'),
|
|
372
|
+
gender: textValue(state, 'gender'),
|
|
373
|
+
age: textValue(state, 'age'),
|
|
374
|
+
assetsWorldviewType: textValue(state, 'assetsWorldviewType', 'assets_worldview_type'),
|
|
375
|
+
subjectId: textValue(state, 'subjectId', 'subject_id'),
|
|
376
|
+
threeViewId: textValue(state, 'threeViewId', 'three_view_id'),
|
|
377
|
+
faceView: textValue(state, 'faceView', 'face_view'),
|
|
378
|
+
faceViewUrl: textValue(state, 'faceViewUrl', 'face_view_url'),
|
|
379
|
+
sideView: textValue(state, 'sideView', 'side_view'),
|
|
380
|
+
sideViewUrl: textValue(state, 'sideViewUrl', 'side_view_url'),
|
|
381
|
+
backView: textValue(state, 'backView', 'back_view'),
|
|
382
|
+
backViewUrl: textValue(state, 'backViewUrl', 'back_view_url'),
|
|
383
|
+
threeView: textValue(state, 'threeView', 'three_view'),
|
|
384
|
+
threeViewUrl: textValue(state, 'threeViewUrl', 'three_view_url'),
|
|
385
|
+
matchedHeadAsset: firstValue(state, 'matchedHeadAsset', 'matched_head_asset'),
|
|
386
|
+
matchedBodyAsset: firstValue(state, 'matchedBodyAsset', 'matched_body_asset'),
|
|
387
|
+
imagePrompts: firstValue(state, 'imagePrompts', 'image_prompts'),
|
|
388
|
+
description: textValue(state, 'description'),
|
|
389
|
+
episodes: firstValue(state, 'episodes'),
|
|
390
|
+
status: textValue(state, 'status'),
|
|
391
|
+
genPrompt: textValue(state, 'genPrompt', 'gen_prompt'),
|
|
392
|
+
frontFullBodyPrompt: textValue(state, 'frontFullBodyPrompt', 'front_full_body_prompt'),
|
|
393
|
+
threeViewPrompt: textValue(state, 'threeViewPrompt', 'three_view_prompt'),
|
|
394
|
+
promptHead: textValue(state, 'promptHead', 'prompt_head'),
|
|
395
|
+
promptBody: textValue(state, 'promptBody', 'prompt_body'),
|
|
396
|
+
frontUseAssetMatch: firstValue(state, 'frontUseAssetMatch', 'front_use_asset_match'),
|
|
397
|
+
frontAssetMatchMode: textValue(state, 'frontAssetMatchMode', 'front_asset_match_mode'),
|
|
398
|
+
frontSavedMatchedAssets: firstValue(state, 'frontSavedMatchedAssets', 'front_saved_matched_assets'),
|
|
399
|
+
frontManualRefs: firstValue(state, 'frontManualRefs', 'front_manual_refs'),
|
|
400
|
+
headCloseup: textValue(state, 'headCloseup', 'head_closeup'),
|
|
401
|
+
headCloseupUrl: textValue(state, 'headCloseupUrl', 'head_closeup_url'),
|
|
402
|
+
lab: firstValue(state, 'lab'),
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function normalizeNamedState(stateKey, state) {
|
|
407
|
+
return compactRecord({
|
|
408
|
+
stateKey,
|
|
409
|
+
stateName: textValue(state, 'stateName', 'state_name') ?? stateKey,
|
|
410
|
+
description: textValue(state, 'description'),
|
|
411
|
+
genPrompt: textValue(state, 'genPrompt', 'gen_prompt'),
|
|
412
|
+
status: textValue(state, 'status'),
|
|
413
|
+
main: textValue(state, 'main'),
|
|
414
|
+
mainUrl: textValue(state, 'mainUrl', 'main_url'),
|
|
415
|
+
auxiliary: textValue(state, 'auxiliary'),
|
|
416
|
+
auxiliaryUrl: textValue(state, 'auxiliaryUrl', 'auxiliary_url'),
|
|
417
|
+
lab: firstValue(state, 'lab'),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function rowsFromAssetObject(payload, keyName, normalizer) {
|
|
422
|
+
if (Array.isArray(payload)) return payload;
|
|
423
|
+
if (!isPlainObject(payload)) throw argumentError(`${keyName}.json 顶层必须是 object 或 array`);
|
|
424
|
+
return Object.entries(payload).map(([assetKey, entry]) => normalizer(assetKey, entry));
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export function buildActorRequests(payload) {
|
|
428
|
+
return rowsFromAssetObject(payload, 'actors', (actorKey, actor = {}) => compactRecord({
|
|
429
|
+
actorKey,
|
|
430
|
+
name: textValue(actor, 'name', 'actorName', 'actor_name') ?? actorKey,
|
|
431
|
+
actorName: textValue(actor, 'actorName', 'actor_name', 'name') ?? actorKey,
|
|
432
|
+
voiceText: textValue(actor, 'voiceText', 'voice_text'),
|
|
433
|
+
voiceDesc: textValue(actor, 'voiceDesc', 'voice_desc'),
|
|
434
|
+
voice: textValue(actor, 'voice'),
|
|
435
|
+
voiceUrl: textValue(actor, 'voiceUrl', 'voice_url'),
|
|
436
|
+
voiceStatus: textValue(actor, 'voiceStatus', 'voice_status'),
|
|
437
|
+
voiceCandidates: firstValue(actor, 'voiceCandidates', 'voice_candidates'),
|
|
438
|
+
selectedVoiceCandidateId: textValue(actor, 'selectedVoiceCandidateId', 'selected_voice_candidate_id'),
|
|
439
|
+
voiceBackend: textValue(actor, 'voiceBackend', 'voice_backend'),
|
|
440
|
+
voiceReferenceImageUrl: textValue(actor, 'voiceReferenceImageUrl', 'voice_reference_image_url'),
|
|
441
|
+
lab: firstValue(actor, 'lab'),
|
|
442
|
+
states: stateEntries(actor).map(([stateKey, state]) => normalizeActorState(stateKey, state, actor)),
|
|
443
|
+
}));
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export function buildPropRequests(payload) {
|
|
447
|
+
return rowsFromAssetObject(payload, 'props', (propKey, prop = {}) => compactRecord({
|
|
448
|
+
propKey,
|
|
449
|
+
name: textValue(prop, 'name', 'propName', 'prop_name') ?? propKey,
|
|
450
|
+
subjectId: textValue(prop, 'subjectId', 'subject_id'),
|
|
451
|
+
groupName: textValue(prop, 'groupName', 'group_name', 'group'),
|
|
452
|
+
groupDefault: firstValue(prop, 'groupDefault', 'group_default'),
|
|
453
|
+
imagePrompts: firstValue(prop, 'imagePrompts', 'image_prompts'),
|
|
454
|
+
episodes: firstValue(prop, 'episodes'),
|
|
455
|
+
lab: firstValue(prop, 'lab'),
|
|
456
|
+
states: stateEntries(prop).map(([stateKey, state]) => normalizeNamedState(stateKey, state)),
|
|
457
|
+
}));
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export function buildLocationRequests(payload) {
|
|
461
|
+
return rowsFromAssetObject(payload, 'locations', (locationKey, location = {}) => compactRecord({
|
|
462
|
+
locationKey,
|
|
463
|
+
name: textValue(location, 'name', 'locationName', 'location_name') ?? locationKey,
|
|
464
|
+
subjectId: textValue(location, 'subjectId', 'subject_id'),
|
|
465
|
+
groupSheet: textValue(location, 'groupSheet', 'group_sheet'),
|
|
466
|
+
groupSheetUrl: textValue(location, 'groupSheetUrl', 'group_sheet_url'),
|
|
467
|
+
groupName: textValue(location, 'groupName', 'group_name', 'group'),
|
|
468
|
+
groupId: firstValue(location, 'groupId', 'group_id'),
|
|
469
|
+
imagePrompts: firstValue(location, 'imagePrompts', 'image_prompts'),
|
|
470
|
+
episodes: firstValue(location, 'episodes'),
|
|
471
|
+
lab: firstValue(location, 'lab'),
|
|
472
|
+
states: stateEntries(location).map(([stateKey, state]) => normalizeNamedState(stateKey, state)),
|
|
473
|
+
}));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function episodeIdFromStoryboard(storyboard, inputFile = '') {
|
|
477
|
+
const explicit = textValue(storyboard, 'episodeId', 'episode_id');
|
|
478
|
+
if (explicit) return explicit;
|
|
479
|
+
const basename = path.basename(inputFile);
|
|
480
|
+
const match = basename.match(/ep[_-]?(\d+)/i);
|
|
481
|
+
if (match) return `ep_${match[1].padStart(3, '0')}`;
|
|
482
|
+
throw argumentError('storyboard 缺少 episode_id,且无法从文件名推断');
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export function buildVideoImportRequests(storyboard, inputFile = '') {
|
|
486
|
+
if (!isPlainObject(storyboard)) throw argumentError('storyboard 顶层必须是 object');
|
|
487
|
+
const episodeId = episodeIdFromStoryboard(storyboard, inputFile);
|
|
488
|
+
const scenes = Array.isArray(storyboard.scenes) ? storyboard.scenes : [];
|
|
489
|
+
const sceneRequests = [];
|
|
490
|
+
const clipsByScene = new Map();
|
|
491
|
+
for (const [sceneIndex, scene] of scenes.entries()) {
|
|
492
|
+
const sceneId = textValue(scene, 'sceneId', 'scene_id') ?? `scn_${String(sceneIndex + 1).padStart(3, '0')}`;
|
|
493
|
+
sceneRequests.push(compactRecord({
|
|
494
|
+
sceneId,
|
|
495
|
+
payload: scene,
|
|
496
|
+
lab: compactRecord({ sourceFile: inputFile }),
|
|
497
|
+
}));
|
|
498
|
+
const clips = Array.isArray(scene.clips) ? scene.clips : [];
|
|
499
|
+
clipsByScene.set(sceneId, clips.map((clip, clipIndex) => compactRecord({
|
|
500
|
+
clipId: textValue(clip, 'clipId', 'clip_id') ?? `clip_${String(clipIndex + 1).padStart(3, '0')}`,
|
|
501
|
+
expectedDuration: textValue(clip, 'expectedDuration', 'expected_duration'),
|
|
502
|
+
completePrompt: textValue(clip, 'completePrompt', 'complete_prompt'),
|
|
503
|
+
videoUrls: firstValue(clip, 'videoUrls', 'video_urls') ?? [],
|
|
504
|
+
payload: clip,
|
|
505
|
+
lab: compactRecord({ sourceFile: inputFile }),
|
|
506
|
+
})));
|
|
507
|
+
}
|
|
508
|
+
return {
|
|
509
|
+
episodeId,
|
|
510
|
+
episode: {
|
|
511
|
+
episodeId,
|
|
512
|
+
payload: storyboard,
|
|
513
|
+
lab: compactRecord({ sourceFile: inputFile }),
|
|
514
|
+
},
|
|
515
|
+
scenes: sceneRequests,
|
|
516
|
+
clipsByScene,
|
|
517
|
+
clipCount: [...clipsByScene.values()].reduce((sum, clips) => sum + clips.length, 0),
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export async function artifactScriptGet(kwargs = {}) {
|
|
522
|
+
return await apiFetch(artifactPath('script', requireProjectId(kwargs)), { method: 'GET' });
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export async function artifactScriptDocument(kwargs = {}) {
|
|
526
|
+
return await apiFetch(artifactPath('script', requireProjectId(kwargs), '/document'), { method: 'GET' });
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
export async function artifactScriptRows(kwargs = {}) {
|
|
530
|
+
const projectId = requireProjectId(kwargs);
|
|
531
|
+
return await apiFetch(artifactPath('script', projectId, '/rows'), {
|
|
532
|
+
method: 'GET',
|
|
533
|
+
query: {
|
|
534
|
+
rowKind: kwargs.rowKind,
|
|
535
|
+
parentKey: kwargs.parentKey,
|
|
536
|
+
revisionAfter: kwargs.revisionAfter,
|
|
537
|
+
includeDeleted: kwargs.includeDeleted,
|
|
538
|
+
},
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export async function artifactScriptRow(kwargs = {}) {
|
|
543
|
+
const projectId = requireProjectId(kwargs);
|
|
544
|
+
return await apiFetch(artifactPath('script', projectId, '/rows/detail'), {
|
|
545
|
+
method: 'GET',
|
|
546
|
+
query: {
|
|
547
|
+
rowKind: requireValue(kwargs, 'rowKind', 'row-kind'),
|
|
548
|
+
entityKey: requireValue(kwargs, 'entityKey', 'entity-key'),
|
|
549
|
+
},
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export async function artifactScriptChildren(kwargs = {}) {
|
|
554
|
+
const projectId = requireProjectId(kwargs);
|
|
555
|
+
return await apiFetch(artifactPath('script', projectId, '/children'), {
|
|
556
|
+
method: 'GET',
|
|
557
|
+
query: {
|
|
558
|
+
parentKey: requireValue(kwargs, 'parentKey', 'parent-key'),
|
|
559
|
+
rowKind: kwargs.rowKind,
|
|
560
|
+
},
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export async function artifactScriptUpsertRow(kwargs = {}) {
|
|
565
|
+
const projectId = requireProjectId(kwargs);
|
|
566
|
+
const body = await readBodyPayload(kwargs);
|
|
567
|
+
if (toBool(kwargs.dryRun)) return { dryRun: true, action: 'artifact script upsert-row', projectId, request: body };
|
|
568
|
+
ensureConfirmed(kwargs, '写入剧本产物行需要确认', { action: 'artifact script upsert-row', projectId });
|
|
569
|
+
return await apiFetch(artifactPath('script', projectId, '/rows'), { body });
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export async function artifactScriptDeleteRow(kwargs = {}) {
|
|
573
|
+
const projectId = requireProjectId(kwargs);
|
|
574
|
+
const rowKind = requireValue(kwargs, 'rowKind', 'row-kind');
|
|
575
|
+
const entityKey = requireValue(kwargs, 'entityKey', 'entity-key');
|
|
576
|
+
if (toBool(kwargs.dryRun)) return { dryRun: true, action: 'artifact script delete-row', projectId, rowKind, entityKey };
|
|
577
|
+
ensureConfirmed(kwargs, '删除剧本产物行需要确认', { action: 'artifact script delete-row', projectId, rowKind, entityKey });
|
|
578
|
+
return await apiFetch(artifactPath('script', projectId, '/rows'), {
|
|
579
|
+
method: 'DELETE',
|
|
580
|
+
query: { rowKind, entityKey },
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
export async function artifactScriptImport(kwargs = {}) {
|
|
585
|
+
const projectId = requireProjectId(kwargs);
|
|
586
|
+
const inputFile = trimToNull(kwargs.inputFile) || 'output/script.json';
|
|
587
|
+
const script = await readJsonFile(inputFile);
|
|
588
|
+
const rows = buildScriptRows(script);
|
|
589
|
+
const summary = {
|
|
590
|
+
projectId,
|
|
591
|
+
sourceFile: path.resolve(inputFile),
|
|
592
|
+
rowCount: rows.length,
|
|
593
|
+
rowsByKind: byKind(rows),
|
|
594
|
+
};
|
|
595
|
+
if (toBool(kwargs.dryRun)) {
|
|
596
|
+
return { dryRun: true, action: 'artifact script import', ...summary, rows };
|
|
597
|
+
}
|
|
598
|
+
ensureConfirmed(kwargs, '导入剧本最终产物需要确认', { action: 'artifact script import', projectId, rowCount: rows.length });
|
|
599
|
+
const result = await apiFetch(artifactPath('script', projectId, '/rows/batch'), { body: rows });
|
|
600
|
+
return { imported: true, ...summary, result };
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
export async function artifactAssetGet(kwargs = {}) {
|
|
604
|
+
return await apiFetch(artifactPath('asset', requireProjectId(kwargs)), { method: 'GET' });
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async function assetList(kind, kwargs = {}) {
|
|
608
|
+
const projectId = requireProjectId(kwargs);
|
|
609
|
+
return await apiFetch(artifactPath('asset', projectId, `/${kind}`), {
|
|
610
|
+
method: 'GET',
|
|
611
|
+
query: { includeStates: kwargs.includeStates ?? true },
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
async function assetGetOne(kind, keyName, kwargs = {}) {
|
|
616
|
+
const projectId = requireProjectId(kwargs);
|
|
617
|
+
const key = requireValue(kwargs, keyName, keyName.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`));
|
|
618
|
+
return await apiFetch(artifactPath('asset', projectId, `/${kind}/${encodeURIComponent(key)}`), {
|
|
619
|
+
method: 'GET',
|
|
620
|
+
query: { includeStates: kwargs.includeStates ?? true },
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
async function assetUpsert(kind, kwargs = {}) {
|
|
625
|
+
const projectId = requireProjectId(kwargs);
|
|
626
|
+
const body = await readBodyPayload(kwargs);
|
|
627
|
+
if (toBool(kwargs.dryRun)) return { dryRun: true, action: `artifact asset upsert-${kind.slice(0, -1)}`, projectId, request: body };
|
|
628
|
+
ensureConfirmed(kwargs, '写入资产最终产物需要确认', { action: `artifact asset upsert-${kind.slice(0, -1)}`, projectId });
|
|
629
|
+
return await apiFetch(artifactPath('asset', projectId, `/${kind}`), { body });
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
async function assetUpsertState(kind, keyName, kwargs = {}) {
|
|
633
|
+
const projectId = requireProjectId(kwargs);
|
|
634
|
+
const key = requireValue(kwargs, keyName, keyName.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`));
|
|
635
|
+
const body = await readBodyPayload(kwargs);
|
|
636
|
+
if (toBool(kwargs.dryRun)) return { dryRun: true, action: `artifact asset upsert-${kind.slice(0, -1)}-state`, projectId, key, request: body };
|
|
637
|
+
ensureConfirmed(kwargs, '写入资产状态最终产物需要确认', { action: `artifact asset upsert-${kind.slice(0, -1)}-state`, projectId, key });
|
|
638
|
+
return await apiFetch(artifactPath('asset', projectId, `/${kind}/${encodeURIComponent(key)}/states`), { body });
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
async function assetDelete(kind, keyName, kwargs = {}) {
|
|
642
|
+
const projectId = requireProjectId(kwargs);
|
|
643
|
+
const key = requireValue(kwargs, keyName, keyName.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`));
|
|
644
|
+
if (toBool(kwargs.dryRun)) return { dryRun: true, action: `artifact asset delete-${kind.slice(0, -1)}`, projectId, key };
|
|
645
|
+
ensureConfirmed(kwargs, '删除资产最终产物需要确认', { action: `artifact asset delete-${kind.slice(0, -1)}`, projectId, key });
|
|
646
|
+
return await apiFetch(artifactPath('asset', projectId, `/${kind}/${encodeURIComponent(key)}`), { method: 'DELETE' });
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
async function assetDeleteState(kind, keyName, kwargs = {}) {
|
|
650
|
+
const projectId = requireProjectId(kwargs);
|
|
651
|
+
const key = requireValue(kwargs, keyName, keyName.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`));
|
|
652
|
+
const stateKey = requireValue(kwargs, 'stateKey', 'state-key');
|
|
653
|
+
if (toBool(kwargs.dryRun)) return { dryRun: true, action: `artifact asset delete-${kind.slice(0, -1)}-state`, projectId, key, stateKey };
|
|
654
|
+
ensureConfirmed(kwargs, '删除资产状态最终产物需要确认', { action: `artifact asset delete-${kind.slice(0, -1)}-state`, projectId, key, stateKey });
|
|
655
|
+
return await apiFetch(artifactPath('asset', projectId, `/${kind}/${encodeURIComponent(key)}/states/${encodeURIComponent(stateKey)}`), { method: 'DELETE' });
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
export async function artifactAssetActors(kwargs = {}) { return await assetList('actors', kwargs); }
|
|
659
|
+
export async function artifactAssetActor(kwargs = {}) { return await assetGetOne('actors', 'actorKey', kwargs); }
|
|
660
|
+
export async function artifactAssetProps(kwargs = {}) { return await assetList('props', kwargs); }
|
|
661
|
+
export async function artifactAssetProp(kwargs = {}) { return await assetGetOne('props', 'propKey', kwargs); }
|
|
662
|
+
export async function artifactAssetLocations(kwargs = {}) { return await assetList('locations', kwargs); }
|
|
663
|
+
export async function artifactAssetLocation(kwargs = {}) { return await assetGetOne('locations', 'locationKey', kwargs); }
|
|
664
|
+
export async function artifactAssetUpsertActor(kwargs = {}) { return await assetUpsert('actors', kwargs); }
|
|
665
|
+
export async function artifactAssetUpsertProp(kwargs = {}) { return await assetUpsert('props', kwargs); }
|
|
666
|
+
export async function artifactAssetUpsertLocation(kwargs = {}) { return await assetUpsert('locations', kwargs); }
|
|
667
|
+
export async function artifactAssetUpsertActorState(kwargs = {}) { return await assetUpsertState('actors', 'actorKey', kwargs); }
|
|
668
|
+
export async function artifactAssetUpsertPropState(kwargs = {}) { return await assetUpsertState('props', 'propKey', kwargs); }
|
|
669
|
+
export async function artifactAssetUpsertLocationState(kwargs = {}) { return await assetUpsertState('locations', 'locationKey', kwargs); }
|
|
670
|
+
export async function artifactAssetDeleteActor(kwargs = {}) { return await assetDelete('actors', 'actorKey', kwargs); }
|
|
671
|
+
export async function artifactAssetDeleteProp(kwargs = {}) { return await assetDelete('props', 'propKey', kwargs); }
|
|
672
|
+
export async function artifactAssetDeleteLocation(kwargs = {}) { return await assetDelete('locations', 'locationKey', kwargs); }
|
|
673
|
+
export async function artifactAssetDeleteActorState(kwargs = {}) { return await assetDeleteState('actors', 'actorKey', kwargs); }
|
|
674
|
+
export async function artifactAssetDeletePropState(kwargs = {}) { return await assetDeleteState('props', 'propKey', kwargs); }
|
|
675
|
+
export async function artifactAssetDeleteLocationState(kwargs = {}) { return await assetDeleteState('locations', 'locationKey', kwargs); }
|
|
676
|
+
|
|
677
|
+
async function resolveAssetImportFile(inputDir, explicitFile, type) {
|
|
678
|
+
if (trimToNull(explicitFile)) {
|
|
679
|
+
const absolute = path.resolve(explicitFile);
|
|
680
|
+
if (await pathExists(absolute)) return absolute;
|
|
681
|
+
throw argumentError(`${type}.json 文件不存在:${absolute}`);
|
|
682
|
+
}
|
|
683
|
+
const dir = path.resolve(inputDir || 'output');
|
|
684
|
+
const candidates = [
|
|
685
|
+
path.join(dir, type, `${type}.json`),
|
|
686
|
+
path.join(dir, `${type}.json`),
|
|
687
|
+
path.join(dir, 'output', type, `${type}.json`),
|
|
688
|
+
path.join(dir, 'output', `${type}.json`),
|
|
689
|
+
];
|
|
690
|
+
for (const candidate of candidates) {
|
|
691
|
+
if (await pathExists(candidate)) return candidate;
|
|
692
|
+
}
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
export async function artifactAssetImport(kwargs = {}) {
|
|
697
|
+
const projectId = requireProjectId(kwargs);
|
|
698
|
+
const inputDir = trimToNull(kwargs.inputDir) || 'output';
|
|
699
|
+
const actorsFile = await resolveAssetImportFile(inputDir, kwargs.actorsFile, 'actors');
|
|
700
|
+
const propsFile = await resolveAssetImportFile(inputDir, kwargs.propsFile, 'props');
|
|
701
|
+
const locationsFile = await resolveAssetImportFile(inputDir, kwargs.locationsFile, 'locations');
|
|
702
|
+
if (!actorsFile && !propsFile && !locationsFile) {
|
|
703
|
+
throw argumentError('未找到可导入的资产 JSON', '传 --input-dir <2_asset/output>,或显式传 --actors-file / --props-file / --locations-file。');
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const actors = actorsFile ? buildActorRequests(await readJsonFile(actorsFile)) : [];
|
|
707
|
+
const props = propsFile ? buildPropRequests(await readJsonFile(propsFile)) : [];
|
|
708
|
+
const locations = locationsFile ? buildLocationRequests(await readJsonFile(locationsFile)) : [];
|
|
709
|
+
const summary = {
|
|
710
|
+
projectId,
|
|
711
|
+
inputDir: path.resolve(inputDir),
|
|
712
|
+
actorCount: actors.length,
|
|
713
|
+
propCount: props.length,
|
|
714
|
+
locationCount: locations.length,
|
|
715
|
+
files: compactRecord({ actorsFile, propsFile, locationsFile }),
|
|
716
|
+
};
|
|
717
|
+
if (toBool(kwargs.dryRun)) {
|
|
718
|
+
return { dryRun: true, action: 'artifact asset import', ...summary, actors, props, locations };
|
|
719
|
+
}
|
|
720
|
+
ensureConfirmed(kwargs, '导入资产最终产物需要确认', {
|
|
721
|
+
action: 'artifact asset import',
|
|
722
|
+
projectId,
|
|
723
|
+
actorCount: actors.length,
|
|
724
|
+
propCount: props.length,
|
|
725
|
+
locationCount: locations.length,
|
|
726
|
+
});
|
|
727
|
+
const result = {};
|
|
728
|
+
if (actors.length) result.actors = await apiFetch(artifactPath('asset', projectId, '/actors/batch'), { body: actors });
|
|
729
|
+
if (props.length) result.props = await apiFetch(artifactPath('asset', projectId, '/props/batch'), { body: props });
|
|
730
|
+
if (locations.length) result.locations = await apiFetch(artifactPath('asset', projectId, '/locations/batch'), { body: locations });
|
|
731
|
+
return { imported: true, ...summary, result };
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
export async function artifactVideoGet(kwargs = {}) {
|
|
735
|
+
return await apiFetch(artifactPath('video', requireProjectId(kwargs)), { method: 'GET' });
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
export async function artifactVideoEpisodes(kwargs = {}) {
|
|
739
|
+
return await apiFetch(artifactPath('video', requireProjectId(kwargs), '/episodes'), { method: 'GET' });
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
export async function artifactVideoEpisode(kwargs = {}) {
|
|
743
|
+
const projectId = requireProjectId(kwargs);
|
|
744
|
+
const episodeId = requireValue(kwargs, 'episodeId', 'episode-id');
|
|
745
|
+
return await apiFetch(artifactPath('video', projectId, `/episodes/${encodeURIComponent(episodeId)}`), { method: 'GET' });
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
export async function artifactVideoScenes(kwargs = {}) {
|
|
749
|
+
const projectId = requireProjectId(kwargs);
|
|
750
|
+
const episodeId = requireValue(kwargs, 'episodeId', 'episode-id');
|
|
751
|
+
return await apiFetch(artifactPath('video', projectId, `/episodes/${encodeURIComponent(episodeId)}/scenes`), { method: 'GET' });
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
export async function artifactVideoScene(kwargs = {}) {
|
|
755
|
+
const projectId = requireProjectId(kwargs);
|
|
756
|
+
const episodeId = requireValue(kwargs, 'episodeId', 'episode-id');
|
|
757
|
+
const sceneId = requireValue(kwargs, 'sceneId', 'scene-id');
|
|
758
|
+
return await apiFetch(artifactPath('video', projectId, `/episodes/${encodeURIComponent(episodeId)}/scenes/${encodeURIComponent(sceneId)}`), { method: 'GET' });
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
export async function artifactVideoClips(kwargs = {}) {
|
|
762
|
+
const projectId = requireProjectId(kwargs);
|
|
763
|
+
const episodeId = requireValue(kwargs, 'episodeId', 'episode-id');
|
|
764
|
+
const sceneId = requireValue(kwargs, 'sceneId', 'scene-id');
|
|
765
|
+
return await apiFetch(artifactPath('video', projectId, `/episodes/${encodeURIComponent(episodeId)}/scenes/${encodeURIComponent(sceneId)}/clips`), { method: 'GET' });
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
export async function artifactVideoClip(kwargs = {}) {
|
|
769
|
+
const projectId = requireProjectId(kwargs);
|
|
770
|
+
const episodeId = requireValue(kwargs, 'episodeId', 'episode-id');
|
|
771
|
+
const sceneId = requireValue(kwargs, 'sceneId', 'scene-id');
|
|
772
|
+
const clipId = requireValue(kwargs, 'clipId', 'clip-id');
|
|
773
|
+
return await apiFetch(artifactPath('video', projectId, `/episodes/${encodeURIComponent(episodeId)}/scenes/${encodeURIComponent(sceneId)}/clips/${encodeURIComponent(clipId)}`), { method: 'GET' });
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
async function videoUpsert(level, kwargs = {}) {
|
|
777
|
+
const projectId = requireProjectId(kwargs);
|
|
778
|
+
const body = await readBodyPayload(kwargs);
|
|
779
|
+
let suffix = '/episodes';
|
|
780
|
+
if (level === 'scene') {
|
|
781
|
+
const episodeId = requireValue(kwargs, 'episodeId', 'episode-id');
|
|
782
|
+
suffix = `/episodes/${encodeURIComponent(episodeId)}/scenes`;
|
|
783
|
+
}
|
|
784
|
+
if (level === 'clip') {
|
|
785
|
+
const episodeId = requireValue(kwargs, 'episodeId', 'episode-id');
|
|
786
|
+
const sceneId = requireValue(kwargs, 'sceneId', 'scene-id');
|
|
787
|
+
suffix = `/episodes/${encodeURIComponent(episodeId)}/scenes/${encodeURIComponent(sceneId)}/clips`;
|
|
788
|
+
}
|
|
789
|
+
if (toBool(kwargs.dryRun)) return { dryRun: true, action: `artifact video upsert-${level}`, projectId, request: body };
|
|
790
|
+
ensureConfirmed(kwargs, '写入视频最终产物需要确认', { action: `artifact video upsert-${level}`, projectId });
|
|
791
|
+
return await apiFetch(artifactPath('video', projectId, suffix), { body });
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
async function videoDelete(level, kwargs = {}) {
|
|
795
|
+
const projectId = requireProjectId(kwargs);
|
|
796
|
+
const episodeId = requireValue(kwargs, 'episodeId', 'episode-id');
|
|
797
|
+
let suffix = `/episodes/${encodeURIComponent(episodeId)}`;
|
|
798
|
+
const details = { action: `artifact video delete-${level}`, projectId, episodeId };
|
|
799
|
+
if (level === 'scene' || level === 'clip') {
|
|
800
|
+
const sceneId = requireValue(kwargs, 'sceneId', 'scene-id');
|
|
801
|
+
suffix += `/scenes/${encodeURIComponent(sceneId)}`;
|
|
802
|
+
details.sceneId = sceneId;
|
|
803
|
+
}
|
|
804
|
+
if (level === 'clip') {
|
|
805
|
+
const clipId = requireValue(kwargs, 'clipId', 'clip-id');
|
|
806
|
+
suffix += `/clips/${encodeURIComponent(clipId)}`;
|
|
807
|
+
details.clipId = clipId;
|
|
808
|
+
}
|
|
809
|
+
if (toBool(kwargs.dryRun)) return { dryRun: true, ...details };
|
|
810
|
+
ensureConfirmed(kwargs, '删除视频最终产物需要确认', details);
|
|
811
|
+
return await apiFetch(artifactPath('video', projectId, suffix), { method: 'DELETE' });
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
export async function artifactVideoUpsertEpisode(kwargs = {}) { return await videoUpsert('episode', kwargs); }
|
|
815
|
+
export async function artifactVideoUpsertScene(kwargs = {}) { return await videoUpsert('scene', kwargs); }
|
|
816
|
+
export async function artifactVideoUpsertClip(kwargs = {}) { return await videoUpsert('clip', kwargs); }
|
|
817
|
+
export async function artifactVideoDeleteEpisode(kwargs = {}) { return await videoDelete('episode', kwargs); }
|
|
818
|
+
export async function artifactVideoDeleteScene(kwargs = {}) { return await videoDelete('scene', kwargs); }
|
|
819
|
+
export async function artifactVideoDeleteClip(kwargs = {}) { return await videoDelete('clip', kwargs); }
|
|
820
|
+
|
|
821
|
+
export async function artifactVideoUpdateClipVideoUrls(kwargs = {}) {
|
|
822
|
+
const projectId = requireProjectId(kwargs);
|
|
823
|
+
const episodeId = requireValue(kwargs, 'episodeId', 'episode-id');
|
|
824
|
+
const sceneId = requireValue(kwargs, 'sceneId', 'scene-id');
|
|
825
|
+
const clipId = requireValue(kwargs, 'clipId', 'clip-id');
|
|
826
|
+
let videoUrls;
|
|
827
|
+
try {
|
|
828
|
+
videoUrls = parseJsonArg(requireValue(kwargs, 'videoUrlsJson', 'video-urls-json'));
|
|
829
|
+
} catch (error) {
|
|
830
|
+
throw argumentError('--video-urls-json 不是有效 JSON', error.message);
|
|
831
|
+
}
|
|
832
|
+
if (!Array.isArray(videoUrls)) throw argumentError('--video-urls-json 必须是 JSON 数组');
|
|
833
|
+
const body = { videoUrls };
|
|
834
|
+
if (toBool(kwargs.dryRun)) return { dryRun: true, action: 'artifact video update-clip-urls', projectId, episodeId, sceneId, clipId, request: body };
|
|
835
|
+
ensureConfirmed(kwargs, '回写 clip 视频链接需要确认', { action: 'artifact video update-clip-urls', projectId, episodeId, sceneId, clipId });
|
|
836
|
+
return await apiFetch(artifactPath('video', projectId, `/episodes/${encodeURIComponent(episodeId)}/scenes/${encodeURIComponent(sceneId)}/clips/${encodeURIComponent(clipId)}/video-urls`), { body });
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
export async function artifactVideoImportStoryboard(kwargs = {}) {
|
|
840
|
+
const projectId = requireProjectId(kwargs);
|
|
841
|
+
const inputFile = requireValue(kwargs, 'inputFile', 'input-file');
|
|
842
|
+
const storyboard = await readJsonFile(inputFile);
|
|
843
|
+
const requests = buildVideoImportRequests(storyboard, path.resolve(inputFile));
|
|
844
|
+
const summary = {
|
|
845
|
+
projectId,
|
|
846
|
+
sourceFile: path.resolve(inputFile),
|
|
847
|
+
episodeId: requests.episodeId,
|
|
848
|
+
sceneCount: requests.scenes.length,
|
|
849
|
+
clipCount: requests.clipCount,
|
|
850
|
+
};
|
|
851
|
+
if (toBool(kwargs.dryRun)) {
|
|
852
|
+
return {
|
|
853
|
+
dryRun: true,
|
|
854
|
+
action: 'artifact video import-storyboard',
|
|
855
|
+
...summary,
|
|
856
|
+
episode: requests.episode,
|
|
857
|
+
scenes: requests.scenes,
|
|
858
|
+
clipsByScene: Object.fromEntries(requests.clipsByScene.entries()),
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
ensureConfirmed(kwargs, '导入视频 storyboard 最终产物需要确认', { action: 'artifact video import-storyboard', ...summary });
|
|
862
|
+
const result = {};
|
|
863
|
+
result.episode = await apiFetch(artifactPath('video', projectId, '/episodes'), { body: requests.episode });
|
|
864
|
+
if (requests.scenes.length) {
|
|
865
|
+
result.scenes = await apiFetch(artifactPath('video', projectId, `/episodes/${encodeURIComponent(requests.episodeId)}/scenes/batch`), { body: requests.scenes });
|
|
866
|
+
}
|
|
867
|
+
result.clips = {};
|
|
868
|
+
for (const [sceneId, clips] of requests.clipsByScene.entries()) {
|
|
869
|
+
if (!clips.length) continue;
|
|
870
|
+
result.clips[sceneId] = await apiFetch(artifactPath('video', projectId, `/episodes/${encodeURIComponent(requests.episodeId)}/scenes/${encodeURIComponent(sceneId)}/clips/batch`), { body: clips });
|
|
871
|
+
}
|
|
872
|
+
return { imported: true, ...summary, result };
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
export async function artifactClipGet(kwargs = {}) {
|
|
876
|
+
return await apiFetch(artifactPath('clip', requireProjectId(kwargs)), { method: 'GET' });
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
export async function artifactClipEpisodes(kwargs = {}) {
|
|
880
|
+
return await apiFetch(artifactPath('clip', requireProjectId(kwargs), '/episodes'), { method: 'GET' });
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
export async function artifactClipEpisode(kwargs = {}) {
|
|
884
|
+
const projectId = requireProjectId(kwargs);
|
|
885
|
+
const videoEpisodeId = requireNumericValue(kwargs, 'videoEpisodeId', 'video-episode-id');
|
|
886
|
+
return await apiFetch(artifactPath('clip', projectId, `/episodes/${encodeURIComponent(videoEpisodeId)}`), { method: 'GET' });
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
export async function artifactClipEpisodeById(kwargs = {}) {
|
|
890
|
+
const projectId = requireProjectId(kwargs);
|
|
891
|
+
const episodeId = requireValue(kwargs, 'episodeId', 'episode-id');
|
|
892
|
+
return await apiFetch(artifactPath('clip', projectId, `/episode-id/${encodeURIComponent(episodeId)}`), { method: 'GET' });
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
export async function artifactClipUpsertEpisode(kwargs = {}) {
|
|
896
|
+
const projectId = requireProjectId(kwargs);
|
|
897
|
+
const body = await readBodyPayload(kwargs);
|
|
898
|
+
if (toBool(kwargs.dryRun)) return { dryRun: true, action: 'artifact clip upsert-episode', projectId, request: body };
|
|
899
|
+
ensureConfirmed(kwargs, '写入剪辑最终产物需要确认', { action: 'artifact clip upsert-episode', projectId, videoEpisodeId: body?.videoEpisodeId });
|
|
900
|
+
return await apiFetch(artifactPath('clip', projectId, '/episodes'), { body });
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
export async function artifactClipUpsertBatch(kwargs = {}) {
|
|
904
|
+
const projectId = requireProjectId(kwargs);
|
|
905
|
+
const body = await readBodyPayload(kwargs);
|
|
906
|
+
if (!Array.isArray(body)) throw argumentError('剪辑批量写入请求体必须是 JSON 数组');
|
|
907
|
+
if (toBool(kwargs.dryRun)) return { dryRun: true, action: 'artifact clip upsert-batch', projectId, episodeCount: body.length, request: body };
|
|
908
|
+
ensureConfirmed(kwargs, '批量写入剪辑最终产物需要确认', { action: 'artifact clip upsert-batch', projectId, episodeCount: body.length });
|
|
909
|
+
return await apiFetch(artifactPath('clip', projectId, '/episodes/batch'), { body });
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
export async function artifactClipUpdateStatus(kwargs = {}) {
|
|
913
|
+
const projectId = requireProjectId(kwargs);
|
|
914
|
+
const videoEpisodeId = requireNumericValue(kwargs, 'videoEpisodeId', 'video-episode-id');
|
|
915
|
+
const status = requireValue(kwargs, 'status');
|
|
916
|
+
let messages;
|
|
917
|
+
if (trimToNull(kwargs.messagesJson)) {
|
|
918
|
+
try {
|
|
919
|
+
messages = parseJsonArg(kwargs.messagesJson);
|
|
920
|
+
} catch (error) {
|
|
921
|
+
throw argumentError('--messages-json 不是有效 JSON', error.message);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
const body = compactRecord({ status, messages });
|
|
925
|
+
if (toBool(kwargs.dryRun)) return { dryRun: true, action: 'artifact clip update-status', projectId, videoEpisodeId, request: body };
|
|
926
|
+
ensureConfirmed(kwargs, '更新剪辑最终产物状态需要确认', { action: 'artifact clip update-status', projectId, videoEpisodeId, status });
|
|
927
|
+
return await apiFetch(artifactPath('clip', projectId, `/episodes/${encodeURIComponent(videoEpisodeId)}/status`), { body });
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
export async function artifactClipDeleteEpisode(kwargs = {}) {
|
|
931
|
+
const projectId = requireProjectId(kwargs);
|
|
932
|
+
const videoEpisodeId = requireNumericValue(kwargs, 'videoEpisodeId', 'video-episode-id');
|
|
933
|
+
if (toBool(kwargs.dryRun)) return { dryRun: true, action: 'artifact clip delete-episode', projectId, videoEpisodeId };
|
|
934
|
+
ensureConfirmed(kwargs, '删除剪辑最终产物需要确认', { action: 'artifact clip delete-episode', projectId, videoEpisodeId });
|
|
935
|
+
return await apiFetch(artifactPath('clip', projectId, `/episodes/${encodeURIComponent(videoEpisodeId)}`), { method: 'DELETE' });
|
|
936
|
+
}
|