@pixerate/flair 0.34.0 → 0.35.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/CHANGELOG.md +13 -0
- package/README.md +20 -2
- package/dist/{chunk-ZULFHADW.js → chunk-OLTY4APV.js} +33 -5
- package/dist/{chunk-ZULFHADW.js.map → chunk-OLTY4APV.js.map} +1 -1
- package/dist/cli.js +1 -1
- package/dist/editor/index.d.ts +13 -11
- package/dist/editor/index.js +75 -23
- package/dist/editor/index.js.map +1 -1
- package/dist/index.d.ts +24 -4
- package/dist/index.js +288 -165
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
standaloneHtml,
|
|
10
10
|
standaloneSceneHtml,
|
|
11
11
|
validateScene
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-OLTY4APV.js";
|
|
13
13
|
import {
|
|
14
14
|
calculateShotTimings,
|
|
15
15
|
createLottieEase,
|
|
@@ -870,11 +870,164 @@ function sampleMotionPathAtTime(path, time, duration, ease) {
|
|
|
870
870
|
return sampleMotionPath(path, distanceFraction);
|
|
871
871
|
}
|
|
872
872
|
|
|
873
|
+
// src/agent/batch.ts
|
|
874
|
+
function getByPath(obj, path) {
|
|
875
|
+
if (!obj || typeof obj !== "object") return void 0;
|
|
876
|
+
const parts = path.replace(/\[(\w+)\]/g, ".$1").split(".");
|
|
877
|
+
let curr = obj;
|
|
878
|
+
for (const part of parts) {
|
|
879
|
+
if (curr === void 0 || curr === null) return void 0;
|
|
880
|
+
curr = curr[part];
|
|
881
|
+
}
|
|
882
|
+
return curr;
|
|
883
|
+
}
|
|
884
|
+
var REFERENCE = /\$(\d+)\.([A-Za-z_][\w.[\]]*)/g;
|
|
885
|
+
function resolve(stepResults, stepStr, path, text) {
|
|
886
|
+
const stepIdx = parseInt(stepStr, 10);
|
|
887
|
+
const prevResult = stepResults.get(stepIdx);
|
|
888
|
+
if (!prevResult) {
|
|
889
|
+
throw new Error(`"${text}" refers to step ${stepIdx}, which has not run before this step.`);
|
|
890
|
+
}
|
|
891
|
+
const resolved = getByPath(prevResult, path);
|
|
892
|
+
if (resolved === void 0) {
|
|
893
|
+
throw new Error(`"${text}" does not resolve: step ${stepIdx}'s result has nothing at "${path}".`);
|
|
894
|
+
}
|
|
895
|
+
return resolved;
|
|
896
|
+
}
|
|
897
|
+
function substituteVariables(value, stepResults) {
|
|
898
|
+
if (typeof value === "string") {
|
|
899
|
+
const match = /^\$(\d+)\.([A-Za-z_][\w.[\]]*)$/.exec(value);
|
|
900
|
+
if (match) return resolve(stepResults, match[1], match[2], value);
|
|
901
|
+
return value.replace(
|
|
902
|
+
REFERENCE,
|
|
903
|
+
(fullMatch, stepStr, path) => String(resolve(stepResults, stepStr, path, fullMatch))
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
if (Array.isArray(value)) {
|
|
907
|
+
return value.map((item) => substituteVariables(item, stepResults));
|
|
908
|
+
}
|
|
909
|
+
if (value !== null && typeof value === "object") {
|
|
910
|
+
const copy = {};
|
|
911
|
+
for (const [k, v] of Object.entries(value)) {
|
|
912
|
+
copy[k] = substituteVariables(v, stepResults);
|
|
913
|
+
}
|
|
914
|
+
return copy;
|
|
915
|
+
}
|
|
916
|
+
return value;
|
|
917
|
+
}
|
|
918
|
+
async function applySceneBatch(scene, commands) {
|
|
919
|
+
let currentScene = scene;
|
|
920
|
+
const stepResults = /* @__PURE__ */ new Map();
|
|
921
|
+
const executedSteps = [];
|
|
922
|
+
for (let i = 0; i < commands.length; i++) {
|
|
923
|
+
const stepNumber = i + 1;
|
|
924
|
+
const cmd = commands[i];
|
|
925
|
+
if (cmd.tool === "batch_scene") {
|
|
926
|
+
return {
|
|
927
|
+
ok: false,
|
|
928
|
+
scene,
|
|
929
|
+
// Rollback
|
|
930
|
+
steps: executedSteps,
|
|
931
|
+
error: `Step ${stepNumber} is a batch_scene; batches cannot be nested, so list its steps here instead.`,
|
|
932
|
+
failedStep: stepNumber
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
let resolvedParams;
|
|
936
|
+
try {
|
|
937
|
+
resolvedParams = substituteVariables(cmd.params || {}, stepResults);
|
|
938
|
+
} catch (err) {
|
|
939
|
+
return {
|
|
940
|
+
ok: false,
|
|
941
|
+
scene,
|
|
942
|
+
// Rollback
|
|
943
|
+
steps: executedSteps,
|
|
944
|
+
error: `Step ${stepNumber} (${cmd.tool}) failed: ${err.message}`,
|
|
945
|
+
failedStep: stepNumber
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
const stepResult = await applySceneTool(currentScene, cmd.tool, resolvedParams);
|
|
949
|
+
stepResults.set(stepNumber, stepResult);
|
|
950
|
+
executedSteps.push({
|
|
951
|
+
step: stepNumber,
|
|
952
|
+
tool: cmd.tool,
|
|
953
|
+
result: stepResult
|
|
954
|
+
});
|
|
955
|
+
if (!stepResult.ok) {
|
|
956
|
+
return {
|
|
957
|
+
ok: false,
|
|
958
|
+
scene,
|
|
959
|
+
// Rollback to initial
|
|
960
|
+
steps: executedSteps,
|
|
961
|
+
error: `Step ${stepNumber} (${cmd.tool}) failed: ${stepResult.error}`,
|
|
962
|
+
failedStep: stepNumber
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
currentScene = stepResult.scene;
|
|
966
|
+
}
|
|
967
|
+
return {
|
|
968
|
+
ok: true,
|
|
969
|
+
scene: currentScene,
|
|
970
|
+
steps: executedSteps
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// src/agent/schema.ts
|
|
975
|
+
function typeOf(value) {
|
|
976
|
+
if (value === null) return "null";
|
|
977
|
+
if (Array.isArray(value)) return "array";
|
|
978
|
+
if (typeof value === "number") return Number.isInteger(value) ? "integer" : "number";
|
|
979
|
+
return typeof value;
|
|
980
|
+
}
|
|
981
|
+
function described(type) {
|
|
982
|
+
if (type === "integer") return "a whole number";
|
|
983
|
+
if (type === "null") return "null";
|
|
984
|
+
return `${/^[aeiou]/.test(type) ? "an" : "a"} ${type}`;
|
|
985
|
+
}
|
|
986
|
+
function fits(value, type) {
|
|
987
|
+
const actual = typeOf(value);
|
|
988
|
+
return actual === type || type === "number" && actual === "integer";
|
|
989
|
+
}
|
|
990
|
+
function checkSchema(schema, value, at = "arguments") {
|
|
991
|
+
const problems = [];
|
|
992
|
+
if (schema.type !== void 0) {
|
|
993
|
+
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
994
|
+
if (!types.some((t) => fits(value, t))) {
|
|
995
|
+
const actual = typeOf(value) === "integer" ? "number" : typeOf(value);
|
|
996
|
+
problems.push(`${at} must be ${types.map(described).join(" or ")}, not ${described(actual)}.`);
|
|
997
|
+
return problems;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
if (schema.enum && !schema.enum.includes(value)) {
|
|
1001
|
+
problems.push(`${at} must be one of ${schema.enum.map((v) => JSON.stringify(v)).join(", ")}.`);
|
|
1002
|
+
}
|
|
1003
|
+
if (typeof value === "number" && typeof schema.minimum === "number" && value < schema.minimum) {
|
|
1004
|
+
problems.push(`${at} must be at least ${schema.minimum}.`);
|
|
1005
|
+
}
|
|
1006
|
+
if (Array.isArray(value) && schema.items) {
|
|
1007
|
+
value.forEach((item, i) => problems.push(...checkSchema(schema.items, item, `${at}[${i}]`)));
|
|
1008
|
+
}
|
|
1009
|
+
if (typeOf(value) === "object") {
|
|
1010
|
+
const obj = value;
|
|
1011
|
+
const props = schema.properties ?? {};
|
|
1012
|
+
for (const name of schema.required ?? []) {
|
|
1013
|
+
if (obj[name] === void 0) problems.push(`${at}.${name} is required.`);
|
|
1014
|
+
}
|
|
1015
|
+
for (const [name, v] of Object.entries(obj)) {
|
|
1016
|
+
if (props[name]) problems.push(...checkSchema(props[name], v, `${at}.${name}`));
|
|
1017
|
+
else if (schema.additionalProperties === false) {
|
|
1018
|
+
const known = Object.keys(props);
|
|
1019
|
+
problems.push(`${at}.${name} is not a parameter${known.length ? `; expected ${known.map((k) => `"${k}"`).join(", ")}` : ""}.`);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
return problems;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
873
1026
|
// src/agent/tools.ts
|
|
874
1027
|
var FLAIR_AGENT_TOOLS = [
|
|
875
1028
|
{
|
|
876
1029
|
name: "get_scene",
|
|
877
|
-
description:
|
|
1030
|
+
description: 'Returns the current scene JSON, or with view "outline" a concise structural outline instead, and any static validation issues. Start here.',
|
|
878
1031
|
input_schema: {
|
|
879
1032
|
type: "object",
|
|
880
1033
|
properties: {
|
|
@@ -889,7 +1042,7 @@ var FLAIR_AGENT_TOOLS = [
|
|
|
889
1042
|
},
|
|
890
1043
|
{
|
|
891
1044
|
name: "write_scene",
|
|
892
|
-
description: "Overwrites the entire scene with a new specification and validates it.",
|
|
1045
|
+
description: "Overwrites the entire scene with a new specification and validates it. Prefer update_shot, insert_shots and delete_shots for smaller changes.",
|
|
893
1046
|
input_schema: {
|
|
894
1047
|
type: "object",
|
|
895
1048
|
properties: {
|
|
@@ -909,7 +1062,10 @@ var FLAIR_AGENT_TOOLS = [
|
|
|
909
1062
|
type: "object",
|
|
910
1063
|
properties: {
|
|
911
1064
|
shot: { type: "integer", minimum: 1, description: "1-based index of the shot to update." },
|
|
912
|
-
set: {
|
|
1065
|
+
set: {
|
|
1066
|
+
type: "object",
|
|
1067
|
+
description: "Fields to set or replace on this shot: caption, title, camera, hold (ms), run, effects, stopEffects, behaviors."
|
|
1068
|
+
},
|
|
913
1069
|
remove: {
|
|
914
1070
|
type: "array",
|
|
915
1071
|
items: { type: "string" },
|
|
@@ -934,7 +1090,7 @@ var FLAIR_AGENT_TOOLS = [
|
|
|
934
1090
|
shots: {
|
|
935
1091
|
type: "array",
|
|
936
1092
|
items: { type: "object" },
|
|
937
|
-
description: "
|
|
1093
|
+
description: "Shot objects to insert (fields as for update_shot)."
|
|
938
1094
|
}
|
|
939
1095
|
},
|
|
940
1096
|
required: ["after", "shots"],
|
|
@@ -966,7 +1122,7 @@ var FLAIR_AGENT_TOOLS = [
|
|
|
966
1122
|
id: { type: "string", description: "Media identifier." },
|
|
967
1123
|
media: {
|
|
968
1124
|
type: ["object", "null"],
|
|
969
|
-
description: 'SceneMedia object
|
|
1125
|
+
description: 'SceneMedia object, or null to remove. A shader is { type: "shader", source } with a GLSL ES 3.00 Shadertoy-style void mainImage(out vec4 fragColor, in vec2 fragCoord); it is only compiled when the scene is rendered on a GPU.'
|
|
970
1126
|
}
|
|
971
1127
|
},
|
|
972
1128
|
required: ["id", "media"],
|
|
@@ -975,7 +1131,7 @@ var FLAIR_AGENT_TOOLS = [
|
|
|
975
1131
|
},
|
|
976
1132
|
{
|
|
977
1133
|
name: "preview_scene",
|
|
978
|
-
description: "Simulates the scene fast and off-screen, returning
|
|
1134
|
+
description: "Simulates the scene fast and off-screen, returning its total length, layout and timing warnings, and notes. Run it after a round of edits.",
|
|
979
1135
|
input_schema: {
|
|
980
1136
|
type: "object",
|
|
981
1137
|
properties: {},
|
|
@@ -997,208 +1153,174 @@ var FLAIR_AGENT_TOOLS = [
|
|
|
997
1153
|
required: ["name", "value"],
|
|
998
1154
|
additionalProperties: false
|
|
999
1155
|
}
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
name: "batch_scene",
|
|
1159
|
+
description: `Runs several of these tools in order as one all-or-nothing change: if a step fails, none of the steps apply. A string param "$N.path" is replaced by that path in step N's result (1-based; e.g. "$1.output.outline.shots[0].hold" or "$2.scene.shots[1].title"), and "$N.path" inside a longer string is filled in; a reference that does not resolve fails its step.`,
|
|
1160
|
+
input_schema: {
|
|
1161
|
+
type: "object",
|
|
1162
|
+
properties: {
|
|
1163
|
+
steps: {
|
|
1164
|
+
type: "array",
|
|
1165
|
+
items: {
|
|
1166
|
+
type: "object",
|
|
1167
|
+
properties: {
|
|
1168
|
+
tool: { type: "string", description: "A tool name (not batch_scene)." },
|
|
1169
|
+
params: { type: "object", description: "That tool's arguments." }
|
|
1170
|
+
},
|
|
1171
|
+
required: ["tool"],
|
|
1172
|
+
additionalProperties: false
|
|
1173
|
+
},
|
|
1174
|
+
description: "The tool calls to run, in order."
|
|
1175
|
+
}
|
|
1176
|
+
},
|
|
1177
|
+
required: ["steps"],
|
|
1178
|
+
additionalProperties: false
|
|
1179
|
+
}
|
|
1000
1180
|
}
|
|
1001
1181
|
];
|
|
1182
|
+
var TOOL_NAMES = FLAIR_AGENT_TOOLS.map((t) => t.name);
|
|
1183
|
+
function fail(error, failedStep) {
|
|
1184
|
+
return { ok: false, error, ...failedStep !== void 0 && { failedStep }, output: { ok: false, error } };
|
|
1185
|
+
}
|
|
1186
|
+
function edited(scene, summary) {
|
|
1187
|
+
const issues = validateScene(scene);
|
|
1188
|
+
return { ok: true, scene, issues, output: { ok: true, summary, shotCount: scene.shots.length, issues } };
|
|
1189
|
+
}
|
|
1190
|
+
var shotsWord = (n) => `${n} shot${n === 1 ? "" : "s"}`;
|
|
1191
|
+
function outlineScene(scene) {
|
|
1192
|
+
return {
|
|
1193
|
+
name: scene.name,
|
|
1194
|
+
title: scene.title,
|
|
1195
|
+
description: scene.description,
|
|
1196
|
+
stage: scene.stage,
|
|
1197
|
+
target: scene.target,
|
|
1198
|
+
mediaIds: Object.keys(scene.media || {}),
|
|
1199
|
+
shots: scene.shots.map((s, i) => ({
|
|
1200
|
+
shot: i + 1,
|
|
1201
|
+
caption: s.caption,
|
|
1202
|
+
title: s.title,
|
|
1203
|
+
hold: s.hold,
|
|
1204
|
+
camera: s.camera,
|
|
1205
|
+
commandCount: Array.isArray(s.run) ? s.run.length : 0,
|
|
1206
|
+
effects: Array.isArray(s.effects) ? s.effects.map((e) => e.type) : void 0
|
|
1207
|
+
}))
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1002
1210
|
async function applySceneTool(scene, toolName, input = {}) {
|
|
1211
|
+
const definition = FLAIR_AGENT_TOOLS.find((t) => t.name === toolName);
|
|
1212
|
+
if (!definition) {
|
|
1213
|
+
return fail(`There is no tool "${toolName}"; the tools are ${TOOL_NAMES.join(", ")}.`);
|
|
1214
|
+
}
|
|
1215
|
+
const problems = checkSchema(definition.input_schema, input ?? {});
|
|
1216
|
+
if (problems.length) return fail(`Invalid arguments for ${toolName}: ${problems.join(" ")}`);
|
|
1217
|
+
const shotCount = scene.shots.length;
|
|
1218
|
+
const shotRange = `the scene has ${shotsWord(shotCount)} (1 to ${shotCount})`;
|
|
1003
1219
|
switch (toolName) {
|
|
1004
1220
|
case "get_scene": {
|
|
1005
1221
|
const issues = validateScene(scene);
|
|
1006
1222
|
if (input.view === "outline") {
|
|
1007
|
-
const outline =
|
|
1008
|
-
|
|
1009
|
-
title: scene.title,
|
|
1010
|
-
description: scene.description,
|
|
1011
|
-
stage: scene.stage,
|
|
1012
|
-
target: scene.target,
|
|
1013
|
-
mediaIds: Object.keys(scene.media || {}),
|
|
1014
|
-
shots: scene.shots.map((s, i) => ({
|
|
1015
|
-
shot: i + 1,
|
|
1016
|
-
caption: s.caption,
|
|
1017
|
-
title: s.title,
|
|
1018
|
-
hold: s.hold,
|
|
1019
|
-
camera: s.camera,
|
|
1020
|
-
commandCount: s.run?.length ?? 0,
|
|
1021
|
-
effects: s.effects?.map((e) => e.type)
|
|
1022
|
-
}))
|
|
1023
|
-
};
|
|
1024
|
-
return { ok: true, scene, outline, issues };
|
|
1223
|
+
const outline = outlineScene(scene);
|
|
1224
|
+
return { ok: true, scene, outline, issues, output: { ok: true, outline, issues } };
|
|
1025
1225
|
}
|
|
1026
|
-
return { ok: true, scene, issues };
|
|
1226
|
+
return { ok: true, scene, issues, output: { ok: true, scene, issues } };
|
|
1027
1227
|
}
|
|
1028
1228
|
case "write_scene": {
|
|
1029
|
-
|
|
1030
|
-
|
|
1229
|
+
const next = input.scene;
|
|
1230
|
+
if (!Array.isArray(next.shots) || next.shots.some((s) => !s || typeof s !== "object" || Array.isArray(s))) {
|
|
1231
|
+
return fail("The scene needs a shots array of shot objects; nothing was written.");
|
|
1031
1232
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
return { ok: true, scene: newScene, issues };
|
|
1233
|
+
if (next.shots.length === 0) return fail("The scene needs at least one shot; nothing was written.");
|
|
1234
|
+
return edited(next, `Wrote the scene "${next.name}" with ${shotsWord(next.shots.length)}.`);
|
|
1035
1235
|
}
|
|
1036
1236
|
case "update_shot": {
|
|
1037
1237
|
const idx = input.shot - 1;
|
|
1038
|
-
if (idx
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
const existing = scene.shots[idx];
|
|
1042
|
-
const updated = { ...existing, ...input.set || {} };
|
|
1043
|
-
if (Array.isArray(input.remove)) {
|
|
1044
|
-
for (const k of input.remove) {
|
|
1045
|
-
delete updated[k];
|
|
1046
|
-
}
|
|
1238
|
+
if (idx >= shotCount) return fail(`There is no shot ${input.shot}; ${shotRange}.`);
|
|
1239
|
+
if (input.set === void 0 && input.remove === void 0) {
|
|
1240
|
+
return fail('Give fields to change in "set", field names to delete in "remove", or both.');
|
|
1047
1241
|
}
|
|
1242
|
+
const updated = { ...scene.shots[idx], ...input.set || {} };
|
|
1243
|
+
for (const k of input.remove ?? []) delete updated[k];
|
|
1048
1244
|
const shots = [...scene.shots];
|
|
1049
1245
|
shots[idx] = updated;
|
|
1050
|
-
const
|
|
1051
|
-
return {
|
|
1246
|
+
const changed = [...Object.keys(input.set ?? {}), ...(input.remove ?? []).map((k) => `-${k}`)];
|
|
1247
|
+
return edited({ ...scene, shots }, `Updated shot ${input.shot} (${changed.join(", ")}).`);
|
|
1052
1248
|
}
|
|
1053
1249
|
case "insert_shots": {
|
|
1054
|
-
const after = input.after
|
|
1055
|
-
if (after
|
|
1056
|
-
|
|
1057
|
-
}
|
|
1058
|
-
if (!Array.isArray(input.shots) || input.shots.length === 0) {
|
|
1059
|
-
return { ok: false, error: "Expected non-empty shots array to insert." };
|
|
1060
|
-
}
|
|
1250
|
+
const after = input.after;
|
|
1251
|
+
if (after > shotCount) return fail(`Cannot insert after shot ${after}; ${shotRange}, so "after" is 0 to ${shotCount}.`);
|
|
1252
|
+
if (input.shots.length === 0) return fail("Give at least one shot to insert.");
|
|
1061
1253
|
const shots = [...scene.shots.slice(0, after), ...input.shots, ...scene.shots.slice(after)];
|
|
1062
|
-
const
|
|
1063
|
-
|
|
1254
|
+
const first = after + 1;
|
|
1255
|
+
const last = after + input.shots.length;
|
|
1256
|
+
return edited(
|
|
1257
|
+
{ ...scene, shots },
|
|
1258
|
+
`Inserted ${shotsWord(input.shots.length)} as ${first === last ? `shot ${first}` : `shots ${first}-${last}`}; later shots moved down.`
|
|
1259
|
+
);
|
|
1064
1260
|
}
|
|
1065
1261
|
case "delete_shots": {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1262
|
+
const numbers = input.shots;
|
|
1263
|
+
if (numbers.length === 0) return fail("Give the shot numbers to delete.");
|
|
1264
|
+
const missing = numbers.filter((n) => n > shotCount);
|
|
1265
|
+
if (missing.length) return fail(`There is no shot ${missing.join(", ")}; ${shotRange}. Nothing was deleted.`);
|
|
1266
|
+
const toDelete = new Set(numbers.map((n) => n - 1));
|
|
1070
1267
|
const shots = scene.shots.filter((_, i) => !toDelete.has(i));
|
|
1071
|
-
if (shots.length === 0)
|
|
1072
|
-
|
|
1073
|
-
}
|
|
1074
|
-
const newScene = { ...scene, shots };
|
|
1075
|
-
return { ok: true, scene: newScene, issues: validateScene(newScene) };
|
|
1268
|
+
if (shots.length === 0) return fail("Cannot delete every shot; a scene needs at least one. Nothing was deleted.");
|
|
1269
|
+
return edited({ ...scene, shots }, `Deleted ${shotsWord(toDelete.size)}; the scene has ${shotsWord(shots.length)} now.`);
|
|
1076
1270
|
}
|
|
1077
1271
|
case "set_media": {
|
|
1078
|
-
if (!input.id || typeof input.id !== "string") {
|
|
1079
|
-
return { ok: false, error: "Expected string media id." };
|
|
1080
|
-
}
|
|
1081
1272
|
const media = { ...scene.media || {} };
|
|
1082
1273
|
if (input.media === null) {
|
|
1274
|
+
if (!(input.id in media)) {
|
|
1275
|
+
const ids = Object.keys(media);
|
|
1276
|
+
return fail(`There is no media "${input.id}" to remove; ${ids.length ? `the media are ${ids.join(", ")}` : "the scene has no media"}.`);
|
|
1277
|
+
}
|
|
1083
1278
|
delete media[input.id];
|
|
1084
|
-
|
|
1085
|
-
media[input.id] = input.media;
|
|
1279
|
+
return edited({ ...scene, media }, `Removed media "${input.id}".`);
|
|
1086
1280
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1281
|
+
if (typeof input.media.type !== "string") return fail('Media needs a string type, e.g. { type: "shader", source: "..." }.');
|
|
1282
|
+
const existed = input.id in media;
|
|
1283
|
+
media[input.id] = input.media;
|
|
1284
|
+
return edited({ ...scene, media }, `${existed ? "Replaced" : "Added"} ${input.media.type} media "${input.id}".`);
|
|
1089
1285
|
}
|
|
1090
1286
|
case "preview_scene": {
|
|
1091
1287
|
const { review } = await simulateScene(scene);
|
|
1092
|
-
|
|
1288
|
+
const issues = validateScene(scene);
|
|
1289
|
+
const { markdown: _markdown, ...summary } = review;
|
|
1290
|
+
return { ok: true, scene, issues, review, output: { ok: true, issues, review: summary } };
|
|
1093
1291
|
}
|
|
1094
1292
|
case "set_rig_widget": {
|
|
1095
|
-
|
|
1096
|
-
|
|
1293
|
+
const widgets = scene.rig?.widgets ?? {};
|
|
1294
|
+
if (!scene.rig || !widgets[input.name]) {
|
|
1295
|
+
const names = Object.keys(widgets);
|
|
1296
|
+
return fail(`Scene does not have a rig widget named "${input.name}"; ${names.length ? `its widgets are ${names.join(", ")}` : "it has no rig widgets"}.`);
|
|
1097
1297
|
}
|
|
1098
1298
|
try {
|
|
1099
1299
|
const updatedRig = setRigWidget(scene.rig, input.name, input.value);
|
|
1100
|
-
|
|
1101
|
-
return { ok: true, scene: newScene, issues: validateScene(newScene) };
|
|
1300
|
+
return edited(applyRig(updatedRig, scene), `Set rig widget "${input.name}" to ${JSON.stringify(input.value)}.`);
|
|
1102
1301
|
} catch (err) {
|
|
1103
|
-
return
|
|
1302
|
+
return fail(err.message || String(err));
|
|
1104
1303
|
}
|
|
1105
1304
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
// src/agent/batch.ts
|
|
1112
|
-
function getByPath(obj, path) {
|
|
1113
|
-
if (!obj || typeof obj !== "object") return void 0;
|
|
1114
|
-
const parts = path.replace(/\[(\w+)\]/g, ".$1").split(".");
|
|
1115
|
-
let curr = obj;
|
|
1116
|
-
for (const part of parts) {
|
|
1117
|
-
if (curr === void 0 || curr === null) return void 0;
|
|
1118
|
-
curr = curr[part];
|
|
1119
|
-
}
|
|
1120
|
-
return curr;
|
|
1121
|
-
}
|
|
1122
|
-
function substituteVariables(value, stepResults) {
|
|
1123
|
-
if (typeof value === "string") {
|
|
1124
|
-
const match = /^\$(\d+)\.(.+)$/.exec(value);
|
|
1125
|
-
if (match) {
|
|
1126
|
-
const stepIdx = parseInt(match[1], 10);
|
|
1127
|
-
const path = match[2];
|
|
1128
|
-
const prevResult = stepResults.get(stepIdx);
|
|
1129
|
-
if (prevResult) {
|
|
1130
|
-
const resolved = getByPath(prevResult, path);
|
|
1131
|
-
if (resolved !== void 0) {
|
|
1132
|
-
return resolved;
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
return value.replace(/\$(\d+)\.([a-zA-Z0-9_.[\]]+)/g, (fullMatch, stepStr, path) => {
|
|
1137
|
-
const stepIdx = parseInt(stepStr, 10);
|
|
1138
|
-
const prevResult = stepResults.get(stepIdx);
|
|
1139
|
-
if (prevResult) {
|
|
1140
|
-
const resolved = getByPath(prevResult, path);
|
|
1141
|
-
if (resolved !== void 0) {
|
|
1142
|
-
return String(resolved);
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
return fullMatch;
|
|
1146
|
-
});
|
|
1147
|
-
}
|
|
1148
|
-
if (Array.isArray(value)) {
|
|
1149
|
-
return value.map((item) => substituteVariables(item, stepResults));
|
|
1150
|
-
}
|
|
1151
|
-
if (value !== null && typeof value === "object") {
|
|
1152
|
-
const copy = {};
|
|
1153
|
-
for (const [k, v] of Object.entries(value)) {
|
|
1154
|
-
copy[k] = substituteVariables(v, stepResults);
|
|
1155
|
-
}
|
|
1156
|
-
return copy;
|
|
1157
|
-
}
|
|
1158
|
-
return value;
|
|
1159
|
-
}
|
|
1160
|
-
async function applySceneBatch(scene, commands) {
|
|
1161
|
-
let currentScene = scene;
|
|
1162
|
-
const stepResults = /* @__PURE__ */ new Map();
|
|
1163
|
-
const executedSteps = [];
|
|
1164
|
-
for (let i = 0; i < commands.length; i++) {
|
|
1165
|
-
const stepNumber = i + 1;
|
|
1166
|
-
const cmd = commands[i];
|
|
1167
|
-
const resolvedParams = substituteVariables(cmd.params || {}, stepResults);
|
|
1168
|
-
if (cmd.tool === "batch_scene_tool") {
|
|
1169
|
-
return {
|
|
1170
|
-
ok: false,
|
|
1171
|
-
scene,
|
|
1172
|
-
// Rollback
|
|
1173
|
-
steps: executedSteps,
|
|
1174
|
-
error: `Nested batch_scene_tool is not allowed at step ${stepNumber}.`,
|
|
1175
|
-
failedStep: stepNumber
|
|
1176
|
-
};
|
|
1177
|
-
}
|
|
1178
|
-
const stepResult = await applySceneTool(currentScene, cmd.tool, resolvedParams);
|
|
1179
|
-
stepResults.set(stepNumber, stepResult);
|
|
1180
|
-
executedSteps.push({
|
|
1181
|
-
step: stepNumber,
|
|
1182
|
-
tool: cmd.tool,
|
|
1183
|
-
result: stepResult
|
|
1184
|
-
});
|
|
1185
|
-
if (!stepResult.ok) {
|
|
1305
|
+
case "batch_scene": {
|
|
1306
|
+
const batch = await applySceneBatch(scene, input.steps);
|
|
1307
|
+
if (!batch.ok) return fail(`${batch.error} No steps were applied.`, batch.failedStep);
|
|
1308
|
+
const issues = validateScene(batch.scene);
|
|
1186
1309
|
return {
|
|
1187
|
-
ok:
|
|
1188
|
-
scene,
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1310
|
+
ok: true,
|
|
1311
|
+
scene: batch.scene,
|
|
1312
|
+
issues,
|
|
1313
|
+
output: {
|
|
1314
|
+
ok: true,
|
|
1315
|
+
summary: `Ran ${batch.steps.length} step${batch.steps.length === 1 ? "" : "s"}.`,
|
|
1316
|
+
steps: batch.steps.map((s) => ({ step: s.step, tool: s.tool, ...s.result.output })),
|
|
1317
|
+
shotCount: batch.scene.shots.length,
|
|
1318
|
+
issues
|
|
1319
|
+
}
|
|
1193
1320
|
};
|
|
1194
1321
|
}
|
|
1195
|
-
currentScene = stepResult.scene;
|
|
1196
1322
|
}
|
|
1197
|
-
return {
|
|
1198
|
-
ok: true,
|
|
1199
|
-
scene: currentScene,
|
|
1200
|
-
steps: executedSteps
|
|
1201
|
-
};
|
|
1323
|
+
return fail(`There is no tool "${toolName}".`);
|
|
1202
1324
|
}
|
|
1203
1325
|
export {
|
|
1204
1326
|
ALL_BLEND_MODES,
|
|
@@ -1353,6 +1475,7 @@ export {
|
|
|
1353
1475
|
oklabToOklch,
|
|
1354
1476
|
oklabToSrgb,
|
|
1355
1477
|
oklchToOklab,
|
|
1478
|
+
outlineScene,
|
|
1356
1479
|
overlapArea,
|
|
1357
1480
|
pageHidden,
|
|
1358
1481
|
parseColor,
|