@hypit/hypit 0.1.3 → 0.1.4
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 +1 -1
- package/dist/public/studio-adapter.d.ts +31 -2
- package/package.json +4 -1
- package/packages/audio-track-studio/src/index.ts +7 -4
- package/packages/build-result/README.md +58 -0
- package/packages/build-result/package.json +2 -1
- package/packages/build-result/src/decode.ts +10 -8
- package/packages/build-result/src/file-reference.ts +42 -0
- package/packages/build-result/src/index.ts +3 -0
- package/packages/build-result/src/replace-file-windows.ts +58 -0
- package/packages/build-result/src/replace-file.ts +11 -0
- package/packages/build-result/src/store.ts +44 -13
- package/packages/build-result/src/types.ts +38 -8
- package/packages/build-result/src/writer.ts +12 -1
- package/packages/build-result-fs/README.md +23 -1
- package/packages/build-result-s3/README.md +5 -0
- package/packages/build-result-s3/src/repository.ts +19 -5
- package/packages/caption-fine/README.md +17 -2
- package/packages/caption-fine/src/manifest.ts +1 -1
- package/packages/caption-fine/src/render.ts +48 -36
- package/packages/caption-fine-studio/package.json +8 -3
- package/packages/caption-fine-studio/src/index.ts +10 -1
- package/packages/cli/README.md +25 -0
- package/packages/cli/src/arguments.ts +16 -11
- package/packages/cli/src/build-planning.ts +6 -1
- package/packages/cli/src/command.ts +1 -1
- package/packages/cli/src/commands/environment.ts +42 -13
- package/packages/cli/src/machine-view.ts +12 -3
- package/packages/cli/src/main.ts +4 -0
- package/packages/cli/src/output.ts +28 -12
- package/packages/cli/src/result-export.ts +22 -13
- package/packages/cli/src/run-file.ts +19 -8
- package/packages/comment-sticker-studio/src/index.ts +2 -1
- package/packages/compiler-node/src/compiler.ts +1 -0
- package/packages/estimate/README.md +56 -3
- package/packages/estimate/src/program.ts +7 -8
- package/packages/fonts-open/README.md +5 -0
- package/packages/fonts-open/package.json +5 -1
- package/packages/fonts-open/src/studio.ts +21 -0
- package/packages/media-track-studio/src/index.ts +7 -2
- package/packages/provider-whisperx-local/README.md +57 -0
- package/packages/ranking/src/render.ts +11 -9
- package/packages/ranking-studio/README.md +11 -0
- package/packages/ranking-studio/package.json +8 -3
- package/packages/ranking-studio/src/index.ts +11 -4
- package/packages/runtime/src/catalog.ts +2 -0
- package/packages/runtime-host-node/src/index.ts +1 -0
- package/packages/runtime-local/README.md +19 -0
- package/packages/runtime-local/src/programs.ts +39 -24
- package/packages/runtime-local/src/runtime.ts +2 -0
- package/packages/script/README.md +22 -0
- package/packages/script/src/edit.ts +123 -73
- package/packages/script/src/lexical.ts +40 -6
- package/packages/script/src/parser.ts +36 -8
- package/packages/script/src/types.ts +4 -0
- package/packages/seedance-kits/README.md +3 -1
- package/packages/seedance-kits/kits/speaker-v1.svs +5 -5
- package/packages/speech-alignment/src/align.ts +45 -21
- package/packages/studio/INSPECTOR.md +104 -0
- package/packages/studio/README.md +65 -4
- package/packages/studio/src/build-library.ts +99 -84
- package/packages/studio/src/library-media.ts +19 -0
- package/packages/studio/src/parameter-values.ts +41 -1
- package/packages/studio/src/parameters.ts +20 -21
- package/packages/studio/src/server.ts +58 -30
- package/packages/studio/src/shared.ts +20 -6
- package/packages/studio/src/studio-registry.ts +22 -0
- package/packages/studio/src/style.css +135 -144
- package/packages/studio/src/temporal-edit.ts +131 -0
- package/packages/studio/src/ui/artifact-name.ts +80 -0
- package/packages/studio/src/ui/artifact-preview.ts +99 -0
- package/packages/studio/src/ui/icons.ts +5 -1
- package/packages/studio/src/ui/library.ts +373 -192
- package/packages/studio/src/ui/main.ts +153 -31
- package/packages/studio/src/ui/material-preview.ts +1 -1
- package/packages/studio/src/ui/overlay.ts +21 -20
- package/packages/studio/src/ui/sidebar-panel.ts +31 -0
- package/packages/studio/src/ui/stage.ts +124 -8
- package/packages/studio/src/ui/timeline.ts +16 -179
- package/packages/studio/start.ts +15 -3
- package/packages/studio-adapter/README.md +21 -0
- package/packages/studio-adapter/src/index.ts +26 -1
- package/packages/temporal-markup/EDITING.md +183 -0
- package/packages/temporal-markup/README.md +4 -0
- package/packages/typography-track-studio/package.json +8 -3
- package/packages/typography-track-studio/src/index.ts +5 -1
- package/packages/video-cli/README.md +12 -3
- package/packages/video-cli/src/creation.ts +11 -5
- package/packages/whisperx/README.md +7 -0
- package/packages/workspace/src/index.ts +2 -0
- package/packages/workspace-fs-node/README.md +4 -1
- package/packages/workspace-fs-node/src/workspace.ts +3 -1
|
@@ -238,8 +238,16 @@ function referencedParameters(input: {
|
|
|
238
238
|
const file = sourceFor(input.root, target.sourcePath, input.files);
|
|
239
239
|
if (file === undefined) return [];
|
|
240
240
|
return input.declarations.flatMap((declaration) => {
|
|
241
|
+
const nestedPath = target.references[declaration.name];
|
|
242
|
+
const nested = nestedPath === undefined || declaration.referenced === undefined ? [] : referencedParameters({
|
|
243
|
+
root: input.root, files: input.files, draft: input.draft, placements: input.placements,
|
|
244
|
+
referenceName: `${input.referenceName}.${declaration.name}`,
|
|
245
|
+
referencePath: nestedPath,
|
|
246
|
+
...(target.resolvedReferences[declaration.name] === undefined ? {} : { referenceRef: target.resolvedReferences[declaration.name] }),
|
|
247
|
+
declarations: declaration.referenced,
|
|
248
|
+
});
|
|
241
249
|
const range = target.attributeValueRanges[declaration.name];
|
|
242
|
-
if (range === undefined) return
|
|
250
|
+
if (range === undefined) return nested;
|
|
243
251
|
const preimage = file.text.slice(range.start, range.end);
|
|
244
252
|
const reference = target.references[declaration.name];
|
|
245
253
|
const value = reference === undefined ? (target.attributes[declaration.name] ?? preimage) : reference;
|
|
@@ -263,7 +271,7 @@ function referencedParameters(input: {
|
|
|
263
271
|
...(!writable
|
|
264
272
|
? { disabledReason: reference === undefined ? "该几何值由组件声明为只读。" : "引用由作者在 SVML 中绑定,面板不替换引用关系。" }
|
|
265
273
|
: {}),
|
|
266
|
-
} satisfies StudioSourceBinding];
|
|
274
|
+
} satisfies StudioSourceBinding, ...nested];
|
|
267
275
|
});
|
|
268
276
|
}
|
|
269
277
|
|
|
@@ -484,7 +492,7 @@ export function resolveTimelineEditHandles(
|
|
|
484
492
|
moveEffect?: "translate-window" | "move-start",
|
|
485
493
|
): StudioEditHandle => {
|
|
486
494
|
const unavailable = affected.find(({ endpoint }) => endpoint.authority.kind === "fixed");
|
|
487
|
-
if (unavailable !== undefined) return disabled(gesture, "
|
|
495
|
+
if (unavailable !== undefined) return disabled(gesture, "该时间表达未开放时间轴回写。");
|
|
488
496
|
const projected: ({ readonly missing: string } | StudioEditSource)[] = [];
|
|
489
497
|
for (const { endpoint, role } of affected) {
|
|
490
498
|
if (endpoint.authority.kind !== "parameter") continue;
|
|
@@ -527,24 +535,15 @@ export function resolveTimelineEditHandles(
|
|
|
527
535
|
}
|
|
528
536
|
const start = { endpoint: projection.start, role: "start" as const };
|
|
529
537
|
const end = { endpoint: projection.end, role: "end" as const };
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
? [start]
|
|
537
|
-
: projection.end.authority.kind === "parameter" && projection.end.authority.relation === "after-start"
|
|
538
|
-
? [start, end]
|
|
539
|
-
: [start];
|
|
540
|
-
const trimEnd = projection.end.authority.kind === "parameter" && projection.end.authority.relation === "after-start"
|
|
541
|
-
? [end]
|
|
542
|
-
: projection.start.authority.kind === "parameter" && projection.start.authority.relation === "before-end"
|
|
543
|
-
? [start, end]
|
|
544
|
-
: [end];
|
|
538
|
+
if (projection.end.authority.kind === "parameter" && projection.end.authority.relation === "after-start") {
|
|
539
|
+
return [handle("move", [start], "translate-window"), handle("trim-end", [end])];
|
|
540
|
+
}
|
|
541
|
+
if (projection.start.authority.kind === "parameter" && projection.start.authority.relation === "before-end") {
|
|
542
|
+
return [handle("move", [end], "translate-window"), handle("trim-start", [start])];
|
|
543
|
+
}
|
|
545
544
|
return [
|
|
546
|
-
handle("move",
|
|
547
|
-
handle("trim-start",
|
|
548
|
-
handle("trim-end",
|
|
545
|
+
handle("move", [start, end], "translate-window"),
|
|
546
|
+
handle("trim-start", [start]),
|
|
547
|
+
handle("trim-end", [end]),
|
|
549
548
|
];
|
|
550
549
|
}
|
|
@@ -15,12 +15,13 @@ import type { ServedFile } from "./compile.js";
|
|
|
15
15
|
import type { StudioDomain } from "./domain.js";
|
|
16
16
|
import type { StudioCompanionRegistry } from "./studio-registry.js";
|
|
17
17
|
import { loadStudioRun } from "./run.js";
|
|
18
|
-
import { serializeParameterValue, validateParameterValue } from "./parameter-values.js";
|
|
18
|
+
import { parameterAuthorValue, parameterOption, serializeParameterValue, validateParameterValue } from "./parameter-values.js";
|
|
19
19
|
import { readStudioSession } from "./session.js";
|
|
20
|
-
import type { Range, StudioFailure, StudioLibraryView, StudioMutation, StudioSnapshot } from "./shared.js";
|
|
20
|
+
import type { Range, StudioFailure, StudioLibraryRequest, StudioLibraryView, StudioMutation, StudioSnapshot } from "./shared.js";
|
|
21
21
|
import { createStudioStoryboard } from "./storyboard.js";
|
|
22
22
|
import type { StudioStoryboard } from "./storyboard.js";
|
|
23
23
|
import { findSurfacePreview } from "./surface-preview.js";
|
|
24
|
+
import { formatTemporalPointEdit, semanticGestureSpan } from "./temporal-edit.js";
|
|
24
25
|
import { replaceSourceFiles } from "./source-transaction.js";
|
|
25
26
|
|
|
26
27
|
export type StudioPluginOptions = {
|
|
@@ -93,8 +94,9 @@ export function studioPlugin(options: StudioPluginOptions): Plugin {
|
|
|
93
94
|
const watchedFiles = new Set<string>();
|
|
94
95
|
const storyboards = new Map<string, Promise<StudioStoryboard>>();
|
|
95
96
|
|
|
96
|
-
const readLibrary = async (
|
|
97
|
-
return await options.buildLibrary?.library(
|
|
97
|
+
const readLibrary = async (request: StudioLibraryRequest): Promise<StudioLibraryView> => {
|
|
98
|
+
return await options.buildLibrary?.library(request) ?? {
|
|
99
|
+
section: request.section,
|
|
98
100
|
environment: options.workspaceRoot,
|
|
99
101
|
tasks: [],
|
|
100
102
|
artifacts: [],
|
|
@@ -271,7 +273,7 @@ export function studioPlugin(options: StudioPluginOptions): Plugin {
|
|
|
271
273
|
throw new Error("A timeline target must use valid whole frames.");
|
|
272
274
|
}
|
|
273
275
|
if (temporal.kind === "window") {
|
|
274
|
-
if (mutation.gesture === "move"
|
|
276
|
+
if (mutation.gesture === "move" && handle.semantic?.kind !== "selection"
|
|
275
277
|
&& endFrameExclusive - startFrame !== clip.endFrameExclusive - clip.startFrame) {
|
|
276
278
|
throw new Error("Move must preserve the Window duration.");
|
|
277
279
|
}
|
|
@@ -287,6 +289,7 @@ export function studioPlugin(options: StudioPluginOptions): Plugin {
|
|
|
287
289
|
|
|
288
290
|
const patches: Patch[] = [];
|
|
289
291
|
const semanticTarget = mutation.target.semantic;
|
|
292
|
+
if (handle.semantic !== undefined && semanticTarget === undefined) throw new Error("A semantic edit requires explicit target anchors.");
|
|
290
293
|
if (semanticTarget !== undefined) {
|
|
291
294
|
if (handle.semantic?.kind !== semanticTarget.kind) {
|
|
292
295
|
throw new Error(`Entity ${mutation.entityId} is not bound to a writable ${semanticTarget.kind}.`);
|
|
@@ -298,15 +301,9 @@ export function studioPlugin(options: StudioPluginOptions): Plugin {
|
|
|
298
301
|
|| script.narrativeId !== current.semantic.narrativeId) {
|
|
299
302
|
throw new Error("The timeline entity and writable Script do not belong to the selected Narrative.");
|
|
300
303
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if (startIndex < 0 || endIndex < 0 || startIndex >= endIndex
|
|
305
|
-
|| current.semantic.anchors[startIndex]!.frame >= current.semantic.anchors[endIndex]!.frame) {
|
|
306
|
-
throw new Error("A Selection must span two ordered semantic Anchors with positive time.");
|
|
307
|
-
}
|
|
308
|
-
} else if (!current.semantic.anchors.some((anchor) => anchor.id === semanticTarget.anchorId)) {
|
|
309
|
-
throw new Error(`Moment Anchor ${semanticTarget.anchorId} does not exist in the current semantic Candidate.`);
|
|
304
|
+
const projected = semanticGestureSpan(current.semantic.anchors, handle, semanticTarget);
|
|
305
|
+
if (projected === undefined || projected.startFrame !== startFrame || projected.endFrameExclusive !== endFrameExclusive) {
|
|
306
|
+
throw new Error("The semantic edit does not produce the requested timeline projection.");
|
|
310
307
|
}
|
|
311
308
|
const absolute = resolve(options.workspaceRoot, script.sourcePath);
|
|
312
309
|
const source = await readFile(absolute, "utf8");
|
|
@@ -366,20 +363,15 @@ export function studioPlugin(options: StudioPluginOptions): Plugin {
|
|
|
366
363
|
}
|
|
367
364
|
return snapshot?.semantic?.moments.find((candidate) => candidate.id === id)?.frame;
|
|
368
365
|
};
|
|
369
|
-
const projectedPointValue = (endpoint: StudioTemporalInstantProjection, desired: number): string =>
|
|
370
|
-
|
|
371
|
-
const base = projectionBaseFrame(endpoint);
|
|
372
|
-
if (base === undefined) throw new Error(`The ${endpoint.reference} projection base is unavailable.`);
|
|
373
|
-
const offset = desired - base;
|
|
374
|
-
return offset === 0 ? endpoint.reference : `${endpoint.reference}${offset > 0 ? "+" : ""}${offset}f`;
|
|
375
|
-
};
|
|
366
|
+
const projectedPointValue = (endpoint: StudioTemporalInstantProjection, desired: number): string =>
|
|
367
|
+
formatTemporalPointEdit(endpoint.reference, desired, projectionBaseFrame(endpoint));
|
|
376
368
|
const writeEndpoint = (
|
|
377
369
|
endpoint: StudioTemporalInstantProjection,
|
|
378
370
|
desired: number,
|
|
379
371
|
role: "start" | "end",
|
|
380
372
|
): void => {
|
|
381
373
|
if (desired === endpoint.frame) return;
|
|
382
|
-
if (endpoint.authority.kind === "fixed") throw new Error(`The ${role} endpoint
|
|
374
|
+
if (endpoint.authority.kind === "fixed") throw new Error(`The ${role} endpoint has no timeline write target.`);
|
|
383
375
|
if (endpoint.authority.kind === "semantic") {
|
|
384
376
|
if (semanticFrame(endpoint) !== desired) throw new Error(`The ${role} endpoint does not match its semantic Anchor.`);
|
|
385
377
|
return;
|
|
@@ -413,20 +405,22 @@ export function studioPlugin(options: StudioPluginOptions): Plugin {
|
|
|
413
405
|
if (parameter === undefined) {
|
|
414
406
|
throw new Error(`Entity ${mutation.entityId} has no writable parameter ${mutation.parameterId}.`);
|
|
415
407
|
}
|
|
408
|
+
const authorValue = parameterAuthorValue(parameter, mutation.value);
|
|
416
409
|
if (parameter.schema !== undefined) {
|
|
417
|
-
validateParameterValue(
|
|
410
|
+
validateParameterValue(authorValue, parameter.schema, parameter.label);
|
|
418
411
|
} else if (parameter.control === "boolean" && typeof mutation.value !== "boolean") {
|
|
419
412
|
throw new Error(`${parameter.label} expects true or false.`);
|
|
420
413
|
} else if (parameter.control === "number" && (typeof mutation.value !== "number" || !Number.isFinite(mutation.value))) {
|
|
421
414
|
throw new Error(`${parameter.label} expects a number.`);
|
|
422
|
-
} else if ((parameter.control === "text" || parameter.control === "color"
|
|
415
|
+
} else if ((parameter.control === "text" || parameter.control === "color")
|
|
423
416
|
&& typeof mutation.value !== "string") {
|
|
424
417
|
throw new Error(`${parameter.label} expects text.`);
|
|
425
418
|
}
|
|
426
|
-
if (parameter.options !== undefined &&
|
|
419
|
+
if (parameter.options !== undefined && !parameter.options.some(option => parameterOption(option).value === authorValue)) {
|
|
427
420
|
throw new Error(`${parameter.label} does not accept ${mutation.value}.`);
|
|
428
421
|
}
|
|
429
|
-
|
|
422
|
+
if (parameter.control === "color") validateParameterValue(authorValue, { kind: "string", format: "color" }, parameter.label);
|
|
423
|
+
const encoded = serializeParameterValue(authorValue, parameter.language);
|
|
430
424
|
const replacement = `${parameter.source.prefix ?? ""}${encoded}${parameter.source.suffix ?? ""}`;
|
|
431
425
|
return replacement === parameter.source.preimage ? [] : [{
|
|
432
426
|
...parameter.source,
|
|
@@ -525,6 +519,28 @@ export function studioPlugin(options: StudioPluginOptions): Plugin {
|
|
|
525
519
|
})();
|
|
526
520
|
return;
|
|
527
521
|
}
|
|
522
|
+
if (request.method === "PUT" && url.pathname === "/__studio/artifact-name") {
|
|
523
|
+
void (async () => {
|
|
524
|
+
try {
|
|
525
|
+
const chunks: Buffer[] = [];
|
|
526
|
+
for await (const chunk of request) chunks.push(Buffer.from(chunk));
|
|
527
|
+
const body = JSON.parse(Buffer.concat(chunks).toString("utf8")) as {
|
|
528
|
+
build?: unknown; output?: unknown; displayName?: unknown;
|
|
529
|
+
};
|
|
530
|
+
if (typeof body.build !== "string" || typeof body.output !== "string"
|
|
531
|
+
|| (typeof body.displayName !== "string" && body.displayName !== null)) {
|
|
532
|
+
json(response, 400, { error: "Expected Build, Output and displayName" });
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (options.buildLibrary === undefined) throw new Error("Result Repository is unavailable");
|
|
536
|
+
const displayName = await options.buildLibrary.renameArtifact(body.build, body.output, body.displayName);
|
|
537
|
+
json(response, 200, { displayName: displayName ?? null });
|
|
538
|
+
} catch (error) {
|
|
539
|
+
json(response, 422, { error: error instanceof Error ? error.message : String(error) });
|
|
540
|
+
}
|
|
541
|
+
})();
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
528
544
|
if (request.method === "POST" && url.pathname === "/__studio/mutation") {
|
|
529
545
|
void (async () => {
|
|
530
546
|
try {
|
|
@@ -577,8 +593,21 @@ export function studioPlugin(options: StudioPluginOptions): Plugin {
|
|
|
577
593
|
return;
|
|
578
594
|
}
|
|
579
595
|
if (url.pathname === "/__studio/library") {
|
|
596
|
+
const section = url.searchParams.get("section");
|
|
597
|
+
if (section !== "tasks" && section !== "artifacts") {
|
|
598
|
+
json(response, 400, { error: "Choose tasks or artifacts with the section parameter" });
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const media = url.searchParams.get("media") ?? undefined;
|
|
602
|
+
if (media !== undefined && media !== "image" && media !== "video" && media !== "audio") {
|
|
603
|
+
json(response, 400, { error: "Choose image, video or audio with the media parameter" });
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
580
606
|
const before = url.searchParams.get("before") ?? undefined;
|
|
581
|
-
|
|
607
|
+
const run = url.searchParams.get("run") ?? undefined;
|
|
608
|
+
const build = url.searchParams.get("build") ?? undefined;
|
|
609
|
+
void readLibrary({ section, ...(media === undefined ? {} : { media }), ...(before === undefined ? {} : { before }),
|
|
610
|
+
...(run === undefined ? {} : { run }), ...(build === undefined ? {} : { build }) }).then(
|
|
582
611
|
(view) => json(response, 200, view),
|
|
583
612
|
(error) => json(response, 500, { error: error instanceof Error ? error.message : String(error) }),
|
|
584
613
|
);
|
|
@@ -662,13 +691,12 @@ export function studioPlugin(options: StudioPluginOptions): Plugin {
|
|
|
662
691
|
void (async () => {
|
|
663
692
|
const build = url.searchParams.get("build");
|
|
664
693
|
const output = url.searchParams.get("output");
|
|
665
|
-
|
|
666
|
-
if (build === null || output === null || valuePath === null || options.buildLibrary === undefined) {
|
|
694
|
+
if (build === null || output === null || options.buildLibrary === undefined) {
|
|
667
695
|
response.statusCode = 404;
|
|
668
696
|
response.end();
|
|
669
697
|
return;
|
|
670
698
|
}
|
|
671
|
-
const artifact = await options.buildLibrary.openArtifact(build, output
|
|
699
|
+
const artifact = await options.buildLibrary.openArtifact(build, output);
|
|
672
700
|
if (artifact === undefined) {
|
|
673
701
|
response.statusCode = 404;
|
|
674
702
|
response.end();
|
|
@@ -190,12 +190,14 @@ export type StudioTaskView = {
|
|
|
190
190
|
readonly note?: string;
|
|
191
191
|
readonly highlightedOutputs?: readonly string[];
|
|
192
192
|
readonly createdAt: number;
|
|
193
|
-
readonly
|
|
193
|
+
readonly finishedAt?: number;
|
|
194
|
+
readonly ongoing: boolean;
|
|
195
|
+
readonly status: "queued" | "running" | "waiting" | "complete" | "failed" | "cancelled" | "saving-result" | "attention";
|
|
196
|
+
readonly detail?: string;
|
|
197
|
+
readonly requests?: { readonly total: number; readonly completed: number };
|
|
194
198
|
readonly source: string;
|
|
195
199
|
readonly run?: string;
|
|
196
200
|
readonly targets: readonly string[];
|
|
197
|
-
readonly acceptedRecords: number;
|
|
198
|
-
readonly outstandingCommands: number;
|
|
199
201
|
readonly operations: readonly {
|
|
200
202
|
readonly status: "pending" | "completed" | "failed" | "cancelled";
|
|
201
203
|
readonly phase?: string;
|
|
@@ -206,7 +208,10 @@ export type StudioTaskView = {
|
|
|
206
208
|
};
|
|
207
209
|
|
|
208
210
|
export type StudioArtifactView = {
|
|
209
|
-
|
|
211
|
+
readonly nameEditable?: boolean;
|
|
212
|
+
/** Author-provided display name; the Output identifier remains unchanged. */
|
|
213
|
+
readonly displayName?: string;
|
|
214
|
+
/** File-owner identity; references to the same file share one card. */
|
|
210
215
|
readonly id: string;
|
|
211
216
|
readonly build: string;
|
|
212
217
|
readonly createdAt: number;
|
|
@@ -214,18 +219,27 @@ export type StudioArtifactView = {
|
|
|
214
219
|
readonly highlighted: boolean;
|
|
215
220
|
readonly buildTitle?: string;
|
|
216
221
|
readonly buildNote?: string;
|
|
217
|
-
readonly
|
|
218
|
-
readonly ownerBuild: string;
|
|
222
|
+
readonly ownerBuild?: string;
|
|
219
223
|
readonly ownerOutput: string;
|
|
220
224
|
readonly filePath: string;
|
|
221
225
|
readonly size: number;
|
|
222
226
|
readonly mediaType: string;
|
|
223
227
|
readonly source: string;
|
|
224
228
|
readonly run?: string;
|
|
229
|
+
readonly origins: readonly { readonly build: string; readonly output: string; readonly run?: string; readonly source: string }[];
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export type StudioLibraryRequest = {
|
|
233
|
+
readonly media?: "image" | "video" | "audio";
|
|
234
|
+
readonly section: "tasks" | "artifacts";
|
|
235
|
+
readonly before?: string;
|
|
236
|
+
readonly run?: string;
|
|
237
|
+
readonly build?: string;
|
|
225
238
|
};
|
|
226
239
|
|
|
227
240
|
/** Read-only view of the Runtime context selected for this Studio environment. */
|
|
228
241
|
export type StudioLibraryView = {
|
|
242
|
+
readonly section: StudioLibraryRequest["section"];
|
|
229
243
|
readonly environment: string;
|
|
230
244
|
readonly runtime?: string;
|
|
231
245
|
/** Cursor for the next older page of immutable Build Results. */
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
StudioSpan,
|
|
15
15
|
} from "@hypit/studio-adapter";
|
|
16
16
|
import { studioParameterControls } from "@hypit/studio-adapter";
|
|
17
|
+
import { parameterOption } from "./parameter-values.js";
|
|
17
18
|
import { compositionTypes } from "@hypit/composition";
|
|
18
19
|
import { sameModule, sameType } from "@hypit/protocol";
|
|
19
20
|
import type { ModuleRef, TypeRef } from "@hypit/protocol";
|
|
@@ -67,6 +68,27 @@ function validateInspector(
|
|
|
67
68
|
if (field.control === "select" && (field.options === undefined || field.options.length === 0)) {
|
|
68
69
|
throw new Error(`${subject} Inspector field ${field.binding} has no select options`);
|
|
69
70
|
}
|
|
71
|
+
const choices = (field.options ?? []).map(parameterOption);
|
|
72
|
+
if (choices.some(option => !option.label.trim() || !["string", "number", "boolean"].includes(typeof option.value)
|
|
73
|
+
|| typeof option.value === "number" && !Number.isFinite(option.value))
|
|
74
|
+
|| new Set(choices.map(option => JSON.stringify(option.value))).size !== choices.length) {
|
|
75
|
+
throw new Error(`${subject} Inspector field ${field.binding} has invalid or repeated choices`);
|
|
76
|
+
}
|
|
77
|
+
const numeric = field.number;
|
|
78
|
+
if (numeric !== undefined) {
|
|
79
|
+
if (field.control !== "number" || numeric.scale !== undefined && (!Number.isFinite(numeric.scale) || numeric.scale <= 0)
|
|
80
|
+
|| numeric.step !== undefined && (!Number.isFinite(numeric.step) || numeric.step <= 0)
|
|
81
|
+
|| numeric.minimum !== undefined && !Number.isFinite(numeric.minimum)
|
|
82
|
+
|| numeric.maximum !== undefined && !Number.isFinite(numeric.maximum)
|
|
83
|
+
|| numeric.minimum !== undefined && numeric.maximum !== undefined && numeric.minimum > numeric.maximum
|
|
84
|
+
|| numeric.suffixes !== undefined && (numeric.suffixes.length === 0 || numeric.suffixes.some(suffix => !suffix.trim()))) {
|
|
85
|
+
throw new Error(`${subject} Inspector field ${field.binding} has an invalid numeric presentation`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (field.multiline && field.control !== "text") throw new Error(`${subject} multiline editing requires text`);
|
|
89
|
+
if (field.swatches !== undefined && (field.control !== "color" || field.swatches.some(color => !/^#[0-9a-f]{6}(?:[0-9a-f]{2})?$/iu.test(color)))) {
|
|
90
|
+
throw new Error(`${subject} color suggestions require hex colors`);
|
|
91
|
+
}
|
|
70
92
|
}
|
|
71
93
|
}
|
|
72
94
|
|