@pixerate/editor 0.140.4 → 0.142.1
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/dist/agent/canvasPlayback.d.cts +15 -0
- package/dist/agent/canvasPlayback.d.ts +15 -0
- package/dist/agent/index.cjs +173 -33
- package/dist/agent/index.d.cts +1 -0
- package/dist/agent/index.d.ts +1 -0
- package/dist/agent/index.js +147 -7
- package/dist/agent/providers.d.cts +12 -1
- package/dist/agent/providers.d.ts +12 -1
- package/dist/agent/types.d.cts +3 -0
- package/dist/agent/types.d.ts +3 -0
- package/dist/animation/index.cjs +2 -2
- package/dist/animation/index.js +1 -1
- package/dist/animation/presence.d.cts +1 -75
- package/dist/animation/presence.d.ts +1 -75
- package/dist/animation-editor/index.cjs +69 -19
- package/dist/animation-editor/index.d.cts +1 -0
- package/dist/animation-editor/index.d.ts +1 -0
- package/dist/animation-editor/index.js +54 -4
- package/dist/animation-editor/playback.d.cts +10 -0
- package/dist/animation-editor/playback.d.ts +10 -0
- package/dist/bridge/cli.cjs +3 -3
- package/dist/bridge/cli.js +2 -2
- package/dist/bridge/index.cjs +3 -3
- package/dist/bridge/index.js +2 -2
- package/dist/canvas/index.cjs +336 -646
- package/dist/canvas/index.d.cts +1 -0
- package/dist/canvas/index.d.ts +1 -0
- package/dist/canvas/index.js +44 -354
- package/dist/canvas/playback.d.cts +14 -0
- package/dist/canvas/playback.d.ts +14 -0
- package/dist/chart/index.cjs +255 -171
- package/dist/chart/index.d.cts +1 -0
- package/dist/chart/index.d.ts +1 -0
- package/dist/chart/index.js +244 -160
- package/dist/chart/playback.d.cts +22 -0
- package/dist/chart/playback.d.ts +22 -0
- package/dist/chunk-54GVEVXS.js +408 -0
- package/dist/{chunk-E747F3D5.js → chunk-CC6WKVIH.js} +86 -5
- package/dist/chunk-DW4CV5H4.js +591 -0
- package/dist/{chunk-UMS6YFA6.js → chunk-EAQ4BGNS.js} +1 -1
- package/dist/{chunk-AGCD3EHB.cjs → chunk-ENIZYNEF.cjs} +7 -7
- package/dist/{chunk-BSGPXPTW.js → chunk-PEEP2YR7.js} +1 -1
- package/dist/{chunk-VTY64I6H.cjs → chunk-QSM3MDDJ.cjs} +85 -85
- package/dist/{chunk-2RUNJYOX.cjs → chunk-QZFEX2N4.cjs} +86 -5
- package/dist/{chunk-RB62U6MS.js → chunk-RFBFTLVN.js} +27 -5
- package/dist/chunk-UBRYHNRU.cjs +591 -0
- package/dist/{chunk-GEY73RUW.cjs → chunk-UJGAA6AK.cjs} +1 -1
- package/dist/{chunk-LMSFXRAJ.js → chunk-VGJTUCWM.js} +1 -1
- package/dist/chunk-X2HQP7DF.cjs +408 -0
- package/dist/chunk-XH32BATE.cjs +68 -0
- package/dist/{chunk-HDK3T2QS.cjs → chunk-Y4BWZB6J.cjs} +29 -7
- package/dist/chunk-Y6SGC6KO.js +68 -0
- package/dist/commands/index.cjs +37 -1
- package/dist/commands/index.js +36 -0
- package/dist/commands/registry.d.cts +11 -0
- package/dist/commands/registry.d.ts +11 -0
- package/dist/diagram/index.cjs +7 -7
- package/dist/diagram/index.js +1 -1
- package/dist/image-editor/index.cjs +108 -16
- package/dist/image-editor/index.d.cts +1 -0
- package/dist/image-editor/index.d.ts +1 -0
- package/dist/image-editor/index.js +107 -15
- package/dist/image-editor/playback.d.cts +22 -0
- package/dist/image-editor/playback.d.ts +22 -0
- package/dist/index.cjs +7 -3
- package/dist/index.js +6 -2
- package/dist/paint-editor/index.cjs +73 -25
- package/dist/paint-editor/index.d.cts +1 -0
- package/dist/paint-editor/index.d.ts +1 -0
- package/dist/paint-editor/index.js +68 -20
- package/dist/paint-editor/playback.d.cts +16 -0
- package/dist/paint-editor/playback.d.ts +16 -0
- package/dist/parameters/index.cjs +32 -8
- package/dist/parameters/index.d.cts +1 -0
- package/dist/parameters/index.d.ts +1 -0
- package/dist/parameters/index.js +28 -4
- package/dist/parameters/playback.d.cts +10 -0
- package/dist/parameters/playback.d.ts +10 -0
- package/dist/spreadsheet/index.cjs +7 -3
- package/dist/spreadsheet/index.d.cts +1 -0
- package/dist/spreadsheet/index.d.ts +1 -0
- package/dist/spreadsheet/index.js +6 -2
- package/dist/spreadsheet/playback.d.cts +15 -0
- package/dist/spreadsheet/playback.d.ts +15 -0
- package/dist/tools/index.cjs +40 -3
- package/dist/tools/index.d.cts +3 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +39 -2
- package/dist/tools/playback.d.cts +205 -0
- package/dist/tools/playback.d.ts +205 -0
- package/dist/tools/presence.d.cts +82 -0
- package/dist/tools/presence.d.ts +82 -0
- package/dist/tools/registry.d.cts +45 -6
- package/dist/tools/registry.d.ts +45 -6
- package/dist/tools/script.d.cts +77 -0
- package/dist/tools/script.d.ts +77 -0
- package/dist/tools/view.d.cts +2 -4
- package/dist/tools/view.d.ts +2 -4
- package/dist/vector/index.cjs +217 -100
- package/dist/vector/index.d.cts +1 -0
- package/dist/vector/index.d.ts +1 -0
- package/dist/vector/index.js +140 -23
- package/dist/vector/playback.d.cts +13 -0
- package/dist/vector/playback.d.ts +13 -0
- package/dist/video-editor/index.cjs +81 -28
- package/dist/video-editor/index.d.cts +1 -0
- package/dist/video-editor/index.d.ts +1 -0
- package/dist/video-editor/index.js +58 -5
- package/dist/video-editor/playback.d.cts +20 -0
- package/dist/video-editor/playback.d.ts +20 -0
- package/package.json +1 -1
- package/skills/pixerate-agent-tools/SKILL.md +13 -0
- package/skills/pixerate-animation-editor/SKILL.md +1 -0
- package/skills/pixerate-canvas/SKILL.md +3 -0
- package/skills/pixerate-chart/SKILL.md +1 -0
- package/skills/pixerate-image-editor/SKILL.md +2 -2
- package/skills/pixerate-paint-editor/SKILL.md +1 -0
- package/skills/pixerate-parameters/SKILL.md +1 -0
- package/skills/pixerate-spreadsheet/SKILL.md +1 -0
- package/skills/pixerate-vector/SKILL.md +5 -0
- package/skills/pixerate-video-editor/SKILL.md +1 -0
- package/dist/chunk-BTZS3KZY.js +0 -221
- package/dist/chunk-K73HMKQP.js +0 -0
- package/dist/chunk-LACYURUW.cjs +0 -1
- package/dist/chunk-MRD3NXKR.cjs +0 -221
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PRESENCE_COLORS,
|
|
3
|
+
realPresenceClock
|
|
4
|
+
} from "./chunk-PEEP2YR7.js";
|
|
5
|
+
|
|
6
|
+
// src/tools/playback.ts
|
|
7
|
+
var ARG_KINDS = {
|
|
8
|
+
nodeId: "node",
|
|
9
|
+
edgeId: "edge",
|
|
10
|
+
layerId: "layer",
|
|
11
|
+
clipId: "clip",
|
|
12
|
+
trackId: "track",
|
|
13
|
+
seriesId: "series",
|
|
14
|
+
parameterId: "parameter",
|
|
15
|
+
keyframeId: "keyframe",
|
|
16
|
+
shapeId: "shape"
|
|
17
|
+
};
|
|
18
|
+
function pushRef(out, kind, id) {
|
|
19
|
+
if (typeof id !== "string" || !id) return;
|
|
20
|
+
if (!out.some((r) => r.kind === kind && r.id === id)) out.push({ kind, id });
|
|
21
|
+
}
|
|
22
|
+
function playbackRefsFromArgs(args, defaultKind = "item") {
|
|
23
|
+
const out = [];
|
|
24
|
+
const visit = (value, depth) => {
|
|
25
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || depth > 1) return;
|
|
26
|
+
const record = value;
|
|
27
|
+
pushRef(out, defaultKind, record.id);
|
|
28
|
+
if (Array.isArray(record.ids)) for (const id of record.ids) pushRef(out, defaultKind, id);
|
|
29
|
+
for (const [key, kind] of Object.entries(ARG_KINDS)) {
|
|
30
|
+
pushRef(out, kind, record[key]);
|
|
31
|
+
const many = record[`${key}s`];
|
|
32
|
+
if (Array.isArray(many)) for (const id of many) pushRef(out, kind, id);
|
|
33
|
+
}
|
|
34
|
+
pushRef(out, "cell", record.range);
|
|
35
|
+
pushRef(out, "cell", record.cell);
|
|
36
|
+
visit(record.params, depth + 1);
|
|
37
|
+
};
|
|
38
|
+
visit(args, 0);
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
function playbackRefsFromResult(value, defaultKind = "item") {
|
|
42
|
+
const out = [];
|
|
43
|
+
const visit = (v, depth) => {
|
|
44
|
+
if (!v || typeof v !== "object" || depth > 2) return;
|
|
45
|
+
if (Array.isArray(v)) {
|
|
46
|
+
for (const item of v) if (item && typeof item === "object") pushRef(out, defaultKind, item.id);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const record = v;
|
|
50
|
+
if (Array.isArray(record.content) && record.structuredContent) return visit(record.structuredContent, depth);
|
|
51
|
+
pushRef(out, defaultKind, record.id);
|
|
52
|
+
if (Array.isArray(record.ids)) for (const id of record.ids) pushRef(out, defaultKind, id);
|
|
53
|
+
if (record.node && typeof record.node === "object") pushRef(out, "node", record.node.id);
|
|
54
|
+
pushRef(out, "layer", record.layerId);
|
|
55
|
+
if (record.layer && typeof record.layer === "object") pushRef(out, "layer", record.layer.id);
|
|
56
|
+
const created = record.created;
|
|
57
|
+
if (Array.isArray(created)) visit(created, depth + 1);
|
|
58
|
+
else if (created && typeof created === "object") {
|
|
59
|
+
const c = created;
|
|
60
|
+
for (const n of c.nodes ?? []) pushRef(out, "node", n?.id);
|
|
61
|
+
for (const e of c.edges ?? []) pushRef(out, "edge", e?.id);
|
|
62
|
+
}
|
|
63
|
+
if (record.result && typeof record.result === "object") visit(record.result, depth + 1);
|
|
64
|
+
};
|
|
65
|
+
visit(value, 0);
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
function derivePlaybackSteps(args, defaultKind) {
|
|
69
|
+
const refs = playbackRefsFromArgs(args, defaultKind).slice(0, 8);
|
|
70
|
+
if (!refs.length) return [{ type: "commit" }];
|
|
71
|
+
return [...refs.map((ref) => ({ type: "target", ref })), { type: "press" }, { type: "release" }, { type: "commit" }];
|
|
72
|
+
}
|
|
73
|
+
function readingTime(text) {
|
|
74
|
+
return Math.min(2200, Math.max(600, text.length * 65));
|
|
75
|
+
}
|
|
76
|
+
function unionRects(rects) {
|
|
77
|
+
const list = rects.filter((r) => !!r);
|
|
78
|
+
if (!list.length) return null;
|
|
79
|
+
const x = Math.min(...list.map((r) => r.x));
|
|
80
|
+
const y = Math.min(...list.map((r) => r.y));
|
|
81
|
+
return { x, y, width: Math.max(...list.map((r) => r.x + r.width)) - x, height: Math.max(...list.map((r) => r.y + r.height)) - y };
|
|
82
|
+
}
|
|
83
|
+
function normalizePlan(plan) {
|
|
84
|
+
if (!plan) return null;
|
|
85
|
+
if (Array.isArray(plan)) return plan.length ? { steps: plan } : null;
|
|
86
|
+
return plan.steps?.length || plan.surface || plan.readOnly !== void 0 ? plan : null;
|
|
87
|
+
}
|
|
88
|
+
var isErrorResult = (value) => !!value && typeof value === "object" && value.isError === true;
|
|
89
|
+
var center = (r) => ({ x: r.x + r.width / 2, y: r.y + r.height / 2 });
|
|
90
|
+
var finiteRect = (r) => r && [r.x, r.y, r.width, r.height].every(Number.isFinite) ? r : null;
|
|
91
|
+
var GLANCE_STEPS = /* @__PURE__ */ new Set(["move", "target", "focus", "say", "wait", "commit"]);
|
|
92
|
+
var AgentPlayback = class {
|
|
93
|
+
/** 1 = natural pace; `Infinity` plays nothing (calls run instantly). Takes effect on the next step. */
|
|
94
|
+
speed;
|
|
95
|
+
reads;
|
|
96
|
+
clock;
|
|
97
|
+
linger;
|
|
98
|
+
defaultActor;
|
|
99
|
+
surfaces = /* @__PURE__ */ new Map();
|
|
100
|
+
actors = /* @__PURE__ */ new Map();
|
|
101
|
+
recordings = /* @__PURE__ */ new Set();
|
|
102
|
+
installed = /* @__PURE__ */ new WeakSet();
|
|
103
|
+
listeners = /* @__PURE__ */ new Set();
|
|
104
|
+
generation = 0;
|
|
105
|
+
running = 0;
|
|
106
|
+
calls = 0;
|
|
107
|
+
constructor(options = {}) {
|
|
108
|
+
this.clock = options.clock ?? realPresenceClock;
|
|
109
|
+
this.speed = options.speed ?? 1;
|
|
110
|
+
this.reads = options.reads ?? "glance";
|
|
111
|
+
this.linger = options.linger ?? 1500;
|
|
112
|
+
this.defaultActor = options.actor ?? { id: "agent", name: "Agent" };
|
|
113
|
+
}
|
|
114
|
+
/** The registry middleware that plays each call. */
|
|
115
|
+
middleware = (call, next) => this.perform(call, next);
|
|
116
|
+
/** Adds the middleware to `registry` once (later calls with the same registry do nothing). */
|
|
117
|
+
install(registry) {
|
|
118
|
+
if (this.installed.has(registry)) return;
|
|
119
|
+
this.installed.add(registry);
|
|
120
|
+
registry.use(this.middleware);
|
|
121
|
+
}
|
|
122
|
+
/** Whether a performance is playing now (e.g. to make the editor read-only meanwhile). */
|
|
123
|
+
get active() {
|
|
124
|
+
return this.running > 0;
|
|
125
|
+
}
|
|
126
|
+
/** Called when `active` changes. Returns an unsubscribe function. */
|
|
127
|
+
subscribe(listener) {
|
|
128
|
+
this.listeners.add(listener);
|
|
129
|
+
return () => {
|
|
130
|
+
this.listeners.delete(listener);
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** Attaches a surface under a provider id (or the id providers name as `playbackSurface`). */
|
|
134
|
+
attach(id, surface) {
|
|
135
|
+
this.surfaces.set(id, surface);
|
|
136
|
+
return () => {
|
|
137
|
+
if (this.surfaces.get(id) !== surface) return;
|
|
138
|
+
this.surfaces.delete(id);
|
|
139
|
+
for (const actor of this.actors.values()) if (actor.surface === id) actor.surface = void 0;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** The surface attached under `id`. */
|
|
143
|
+
surface(id) {
|
|
144
|
+
return this.surfaces.get(id);
|
|
145
|
+
}
|
|
146
|
+
/** Shows `text` in the actor's bubble on the surface it last worked on (until its next step). */
|
|
147
|
+
say(text, options = {}) {
|
|
148
|
+
const actor = this.actorState(options.actor);
|
|
149
|
+
const surface = actor.surface ? this.surfaces.get(actor.surface) : void 0;
|
|
150
|
+
if (surface?.presence.get(actor.info.id)) surface.presence.set({ id: actor.info.id, message: text });
|
|
151
|
+
}
|
|
152
|
+
/** Finishes every running performance at once (the calls still run and keep their results). */
|
|
153
|
+
stop() {
|
|
154
|
+
this.generation++;
|
|
155
|
+
}
|
|
156
|
+
/** Collects every step played from now on (for tests). */
|
|
157
|
+
record() {
|
|
158
|
+
const list = [];
|
|
159
|
+
this.recordings.add(list);
|
|
160
|
+
return list;
|
|
161
|
+
}
|
|
162
|
+
actorState(info) {
|
|
163
|
+
const base = info ?? this.defaultActor;
|
|
164
|
+
let actor = this.actors.get(base.id);
|
|
165
|
+
if (!actor) {
|
|
166
|
+
actor = { info: { ...base, color: base.color ?? PRESENCE_COLORS[this.actors.size % PRESENCE_COLORS.length] }, queue: Promise.resolve(), version: 0 };
|
|
167
|
+
this.actors.set(base.id, actor);
|
|
168
|
+
} else if (info?.name && info.name !== actor.info.name) actor.info = { ...actor.info, name: info.name };
|
|
169
|
+
return actor;
|
|
170
|
+
}
|
|
171
|
+
enqueue(actor, run) {
|
|
172
|
+
const result = actor.queue.then(run);
|
|
173
|
+
actor.queue = result.catch(() => void 0);
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
surfaceFor(provider, hint) {
|
|
177
|
+
for (const id of [hint, provider.playbackSurface, provider.id, provider.namespace]) {
|
|
178
|
+
const surface = id ? this.surfaces.get(id) : void 0;
|
|
179
|
+
if (id && surface) return [id, surface];
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
async perform(call, next) {
|
|
184
|
+
const tool = call.tool;
|
|
185
|
+
if (tool.playback === false || !Number.isFinite(this.speed)) return next();
|
|
186
|
+
const actor = this.actorState(call.context.actor);
|
|
187
|
+
if (tool.playback === "self") return this.enqueue(actor, next);
|
|
188
|
+
const fallback = this.surfaceFor(call.provider);
|
|
189
|
+
let planned = null;
|
|
190
|
+
if (tool.plan) {
|
|
191
|
+
try {
|
|
192
|
+
const state = {
|
|
193
|
+
surfaceId: fallback?.[0] ?? null,
|
|
194
|
+
locate: (ref) => fallback?.[1].locate?.(ref) ?? null
|
|
195
|
+
};
|
|
196
|
+
planned = normalizePlan(tool.plan(call.args, state));
|
|
197
|
+
} catch {
|
|
198
|
+
planned = null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const readOnly = planned?.readOnly ?? tool.annotations?.readOnlyHint === true;
|
|
202
|
+
if (readOnly && this.reads === "skip") return next();
|
|
203
|
+
const found = this.surfaceFor(call.provider, planned?.surface);
|
|
204
|
+
if (!found) return next();
|
|
205
|
+
const [surfaceId, surface] = found;
|
|
206
|
+
const steps = planned?.steps?.length ? planned.steps : derivePlaybackSteps(call.args, surface.defaultKind);
|
|
207
|
+
const glance = readOnly && this.reads === "glance";
|
|
208
|
+
const played = glance ? steps.filter((s) => GLANCE_STEPS.has(s.type)) : steps;
|
|
209
|
+
if (!played.some((s) => s.type !== "commit")) return next();
|
|
210
|
+
return this.enqueue(actor, async () => {
|
|
211
|
+
if (surface.visible && !surface.visible()) {
|
|
212
|
+
await surface.reveal?.();
|
|
213
|
+
if (surface.visible && !surface.visible()) return next();
|
|
214
|
+
}
|
|
215
|
+
return this.play({ call, next, actor, surfaceId, surface, steps: played, readOnly, glance });
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
async play(run) {
|
|
219
|
+
const { call, next, actor, surfaceId, surface, readOnly } = run;
|
|
220
|
+
const id = actor.info.id;
|
|
221
|
+
const gen = this.generation;
|
|
222
|
+
const instant = () => gen !== this.generation || !Number.isFinite(this.speed);
|
|
223
|
+
const pace = () => Math.max(0.1, this.speed) * (run.glance ? 1.6 : 1);
|
|
224
|
+
const wait = (ms) => instant() || ms <= 0 ? Promise.resolve() : this.clock.wait(ms / pace());
|
|
225
|
+
const presence = surface.presence;
|
|
226
|
+
const locate = (ref) => finiteRect(surface.locate?.(ref.kind === "item" ? { kind: surface.defaultKind ?? "item", id: ref.id } : ref) ?? null);
|
|
227
|
+
const record = (step) => {
|
|
228
|
+
for (const list of this.recordings) list.push({ actor: id, surface: surfaceId, tool: call.name, step });
|
|
229
|
+
};
|
|
230
|
+
this.running++;
|
|
231
|
+
if (this.running === 1) this.listeners.forEach((l) => l());
|
|
232
|
+
try {
|
|
233
|
+
if (actor.surface && actor.surface !== surfaceId) this.surfaces.get(actor.surface)?.presence.remove(id);
|
|
234
|
+
if (!presence.get(id)) {
|
|
235
|
+
const bounds = surface.bounds?.() ?? null;
|
|
236
|
+
let at = actor.viewport && surface.fromViewport ? surface.fromViewport(actor.viewport) : null;
|
|
237
|
+
if (at && bounds) at = { x: Math.min(bounds.x + bounds.width, Math.max(bounds.x, at.x)), y: Math.min(bounds.y + bounds.height, Math.max(bounds.y, at.y)) };
|
|
238
|
+
at ??= bounds ? { x: bounds.x + bounds.width - 48, y: bounds.y + 32 } : null;
|
|
239
|
+
if (!at) {
|
|
240
|
+
const first = run.steps.find((s) => s.type === "move" || s.type === "target" || s.type === "drag");
|
|
241
|
+
const rect = first?.type === "target" ? locate(first.ref) : null;
|
|
242
|
+
at = first && first.type !== "target" ? first.to : rect ? center(rect) : { x: 0, y: 0 };
|
|
243
|
+
}
|
|
244
|
+
presence.set({ id, name: actor.info.name ?? id, color: actor.info.color, x: at.x, y: at.y, pressed: false });
|
|
245
|
+
} else presence.set({ id, name: actor.info.name ?? id, color: actor.info.color });
|
|
246
|
+
actor.surface = surfaceId;
|
|
247
|
+
actor.version++;
|
|
248
|
+
const argRefs = playbackRefsFromArgs(call.args, surface.defaultKind);
|
|
249
|
+
if (!readOnly) surface.beginGesture?.(`agent:${id}:${++this.calls}`);
|
|
250
|
+
let result;
|
|
251
|
+
let committed = false;
|
|
252
|
+
try {
|
|
253
|
+
for (const step of run.steps) {
|
|
254
|
+
record(step);
|
|
255
|
+
if (step.type === "commit") {
|
|
256
|
+
if (committed) continue;
|
|
257
|
+
committed = true;
|
|
258
|
+
result = await next();
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (step.type === "open") {
|
|
262
|
+
if (!surface.open) continue;
|
|
263
|
+
const rect = locate(step.ref);
|
|
264
|
+
if (rect) {
|
|
265
|
+
await this.glide(presence, id, { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 }, step.ms, instant);
|
|
266
|
+
presence.set({ id, pressed: true });
|
|
267
|
+
await wait(90);
|
|
268
|
+
}
|
|
269
|
+
await surface.open(step.ref);
|
|
270
|
+
presence.set({ id, pressed: false });
|
|
271
|
+
await wait(220);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
await this.step(presence, locate, id, step, wait, instant);
|
|
275
|
+
}
|
|
276
|
+
if (!committed) {
|
|
277
|
+
record({ type: "commit" });
|
|
278
|
+
result = await next();
|
|
279
|
+
}
|
|
280
|
+
} catch (error) {
|
|
281
|
+
presence.set({ id, pressed: false, message: `\u2717 ${error instanceof Error ? error.message : String(error)}`.slice(0, 140) });
|
|
282
|
+
await wait(900);
|
|
283
|
+
throw error;
|
|
284
|
+
} finally {
|
|
285
|
+
if (!readOnly) surface.endGesture?.();
|
|
286
|
+
presence.set({ id, pressed: false });
|
|
287
|
+
}
|
|
288
|
+
if (isErrorResult(result)) {
|
|
289
|
+
const text = result.content?.find((c) => c.type === "text")?.text ?? "The tool failed.";
|
|
290
|
+
presence.set({ id, message: `\u2717 ${text}`.slice(0, 140) });
|
|
291
|
+
await wait(900);
|
|
292
|
+
} else if (!readOnly) {
|
|
293
|
+
const known = new Set(argRefs.map((r) => `${r.kind}:${r.id}`));
|
|
294
|
+
const fresh = playbackRefsFromResult(result, surface.defaultKind).filter((r) => !known.has(`${r.kind}:${r.id}`));
|
|
295
|
+
const rect = unionRects(fresh.slice(0, 16).map(locate));
|
|
296
|
+
if (rect) {
|
|
297
|
+
const step = { type: "focus", rect };
|
|
298
|
+
record(step);
|
|
299
|
+
presence.set({ id, focus: rect });
|
|
300
|
+
await wait(450);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
presence.set({ id, message: void 0, focus: void 0 });
|
|
304
|
+
const here = presence.get(id);
|
|
305
|
+
if (here && surface.toViewport) actor.viewport = surface.toViewport({ x: here.x, y: here.y });
|
|
306
|
+
this.scheduleLinger(actor, surface);
|
|
307
|
+
return result;
|
|
308
|
+
} finally {
|
|
309
|
+
this.running--;
|
|
310
|
+
if (this.running === 0) this.listeners.forEach((l) => l());
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
scheduleLinger(actor, surface) {
|
|
314
|
+
if (this.linger <= 0) return;
|
|
315
|
+
const version = actor.version;
|
|
316
|
+
void this.clock.wait(this.linger).then(() => {
|
|
317
|
+
if (actor.version !== version) return;
|
|
318
|
+
surface.presence.remove(actor.info.id);
|
|
319
|
+
for (const [sid, s] of this.surfaces) if (s === surface && actor.surface === sid) actor.surface = void 0;
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
async step(presence, locate, id, step, wait, instant) {
|
|
323
|
+
switch (step.type) {
|
|
324
|
+
case "move":
|
|
325
|
+
return this.glide(presence, id, step.to, step.ms, instant);
|
|
326
|
+
case "drag":
|
|
327
|
+
presence.set({ id, pressed: true });
|
|
328
|
+
return this.glide(presence, id, step.to, step.ms, instant);
|
|
329
|
+
case "target": {
|
|
330
|
+
const rect = locate(step.ref);
|
|
331
|
+
if (!rect) return;
|
|
332
|
+
await this.glide(presence, id, center(rect), step.ms, instant);
|
|
333
|
+
presence.set({ id, focus: rect });
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
case "focus":
|
|
337
|
+
presence.set({ id, focus: step.rect ?? void 0 });
|
|
338
|
+
return;
|
|
339
|
+
case "say":
|
|
340
|
+
presence.set({ id, message: step.text });
|
|
341
|
+
return wait(step.ms ?? readingTime(step.text));
|
|
342
|
+
case "press":
|
|
343
|
+
presence.set({ id, pressed: true });
|
|
344
|
+
return wait(90);
|
|
345
|
+
case "release":
|
|
346
|
+
presence.set({ id, pressed: false });
|
|
347
|
+
return wait(60);
|
|
348
|
+
case "type": {
|
|
349
|
+
const total = step.ms ?? Math.min(900, 40 * step.text.length + 150);
|
|
350
|
+
if (instant() || !(total > 0) || !Number.isFinite(total)) {
|
|
351
|
+
presence.set({ id, message: step.text });
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const start = this.clock.now();
|
|
355
|
+
for (; ; ) {
|
|
356
|
+
const t = Math.min(1, (this.clock.now() - start) / (total / Math.max(0.1, this.speed)));
|
|
357
|
+
presence.set({ id, message: step.text.slice(0, Math.max(1, Math.round(step.text.length * t))) });
|
|
358
|
+
if (t >= 1 || instant()) break;
|
|
359
|
+
await this.clock.wait(16);
|
|
360
|
+
}
|
|
361
|
+
presence.set({ id, message: step.text });
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
case "wait":
|
|
365
|
+
return wait(step.ms);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/** Glides along a slight arc with ease-out, by elapsed time (fewer frames in a throttled tab, never longer). */
|
|
369
|
+
async glide(presence, id, to, ms, instant) {
|
|
370
|
+
if (!Number.isFinite(to.x) || !Number.isFinite(to.y)) return;
|
|
371
|
+
const a = presence.get(id);
|
|
372
|
+
const from = a && Number.isFinite(a.x) && Number.isFinite(a.y) ? { x: a.x, y: a.y } : to;
|
|
373
|
+
const dist = Math.hypot(to.x - from.x, to.y - from.y);
|
|
374
|
+
const duration = (ms ?? Math.min(900, 260 + dist * 0.5)) / Math.max(0.1, this.speed);
|
|
375
|
+
if (instant() || dist < 1 || !(duration > 0) || !Number.isFinite(duration)) {
|
|
376
|
+
presence.set({ id, x: to.x, y: to.y });
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const bend = Math.min(60, dist * 0.12);
|
|
380
|
+
const ctrl = { x: (from.x + to.x) / 2 - (to.y - from.y) / dist * bend, y: (from.y + to.y) / 2 + (to.x - from.x) / dist * bend };
|
|
381
|
+
const start = this.clock.now();
|
|
382
|
+
for (; ; ) {
|
|
383
|
+
const t = Math.min(1, (this.clock.now() - start) / duration);
|
|
384
|
+
const e = 1 - Math.pow(1 - t, 3);
|
|
385
|
+
const u = 1 - e;
|
|
386
|
+
presence.set({ id, x: u * u * from.x + 2 * u * e * ctrl.x + e * e * to.x, y: u * u * from.y + 2 * u * e * ctrl.y + e * e * to.y });
|
|
387
|
+
if (t >= 1) break;
|
|
388
|
+
if (instant()) {
|
|
389
|
+
presence.set({ id, x: to.x, y: to.y });
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
await this.clock.wait(16);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
function createAgentPlayback(options = {}) {
|
|
397
|
+
return new AgentPlayback(options);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export {
|
|
401
|
+
playbackRefsFromArgs,
|
|
402
|
+
playbackRefsFromResult,
|
|
403
|
+
derivePlaybackSteps,
|
|
404
|
+
readingTime,
|
|
405
|
+
unionRects,
|
|
406
|
+
AgentPlayback,
|
|
407
|
+
createAgentPlayback
|
|
408
|
+
};
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
formatIsoDate,
|
|
9
9
|
formatIsoDateTime,
|
|
10
10
|
formatTextFunction,
|
|
11
|
+
indexToColName,
|
|
11
12
|
isBlank,
|
|
12
13
|
isFormulaError,
|
|
13
14
|
isoDateHasTime,
|
|
@@ -2947,16 +2948,16 @@ var FormulaEvaluator = class {
|
|
|
2947
2948
|
const value = this.evaluateNode(argument);
|
|
2948
2949
|
if (!Array.isArray(value)) return value;
|
|
2949
2950
|
if (value.length <= 1) return firstValue(value);
|
|
2950
|
-
const
|
|
2951
|
-
if (argument.type === "Range" &&
|
|
2951
|
+
const cell2 = this.context.getFormulaCell?.();
|
|
2952
|
+
if (argument.type === "Range" && cell2) {
|
|
2952
2953
|
const range = toFormulaRange(value);
|
|
2953
2954
|
const top = Math.min(argument.startRow, argument.endRow);
|
|
2954
2955
|
const left = Math.min(colNameToIndex(argument.startCol), colNameToIndex(argument.endCol));
|
|
2955
2956
|
if (range.cols === 1) {
|
|
2956
|
-
const r =
|
|
2957
|
+
const r = cell2.row - top;
|
|
2957
2958
|
if (r >= 0 && r < range.rows) return range.values[r];
|
|
2958
2959
|
} else if (range.rows === 1) {
|
|
2959
|
-
const c = colNameToIndex(
|
|
2960
|
+
const c = colNameToIndex(cell2.col) - left;
|
|
2960
2961
|
if (c >= 0 && c < range.cols) return range.values[c];
|
|
2961
2962
|
}
|
|
2962
2963
|
return this.fail("#VALUE!", `The @ operator found no cell of ${argument.raw} in the formula's row or column.`);
|
|
@@ -3757,6 +3758,84 @@ function getFillDirection(source, target) {
|
|
|
3757
3758
|
return null;
|
|
3758
3759
|
}
|
|
3759
3760
|
|
|
3761
|
+
// src/spreadsheet/playback.ts
|
|
3762
|
+
var CELL = /^\s*\$?([A-Za-z]{1,3})\$?(\d+)\s*$/;
|
|
3763
|
+
var COL = /^\s*([A-Za-z]{1,3})\s*$/;
|
|
3764
|
+
var ROW = /^\s*(\d+)\s*$/;
|
|
3765
|
+
function parseSpreadsheetPlaybackRef(ref) {
|
|
3766
|
+
const [a, b = a] = ref.id.split(":");
|
|
3767
|
+
if (!a) return null;
|
|
3768
|
+
if (ref.kind === "cell") {
|
|
3769
|
+
const s = CELL.exec(a);
|
|
3770
|
+
const e = CELL.exec(b);
|
|
3771
|
+
if (!s || !e) return null;
|
|
3772
|
+
const r = [Number(s[2]) - 1, Number(e[2]) - 1];
|
|
3773
|
+
const c = [colNameToIndex(s[1].toUpperCase()), colNameToIndex(e[1].toUpperCase())];
|
|
3774
|
+
return { startRow: Math.min(r[0], r[1]), endRow: Math.max(r[0], r[1]), startCol: Math.min(c[0], c[1]), endCol: Math.max(c[0], c[1]) };
|
|
3775
|
+
}
|
|
3776
|
+
if (ref.kind === "row") {
|
|
3777
|
+
const s = ROW.exec(a);
|
|
3778
|
+
const e = ROW.exec(b);
|
|
3779
|
+
if (!s || !e) return null;
|
|
3780
|
+
return { startRow: Math.min(+s[1], +e[1]) - 1, endRow: Math.max(+s[1], +e[1]) - 1, startCol: null, endCol: null };
|
|
3781
|
+
}
|
|
3782
|
+
if (ref.kind === "column") {
|
|
3783
|
+
const s = COL.exec(a);
|
|
3784
|
+
const e = COL.exec(b);
|
|
3785
|
+
if (!s || !e) return null;
|
|
3786
|
+
const c = [colNameToIndex(s[1].toUpperCase()), colNameToIndex(e[1].toUpperCase())];
|
|
3787
|
+
return { startRow: null, endRow: null, startCol: Math.min(c[0], c[1]), endCol: Math.max(c[0], c[1]) };
|
|
3788
|
+
}
|
|
3789
|
+
return null;
|
|
3790
|
+
}
|
|
3791
|
+
var cell = (id) => ({ kind: "cell", id });
|
|
3792
|
+
var shown = (v) => {
|
|
3793
|
+
if (v === null || v === void 0 || v === "") return null;
|
|
3794
|
+
const text = String(v);
|
|
3795
|
+
return text.length > 40 ? `${text.slice(0, 39)}\u2026` : text;
|
|
3796
|
+
};
|
|
3797
|
+
function planSpreadsheetToolPlayback(tool, args) {
|
|
3798
|
+
const count = Number.isInteger(args.count) && args.count > 0 ? args.count : 1;
|
|
3799
|
+
switch (tool) {
|
|
3800
|
+
case "set_cell": {
|
|
3801
|
+
if (typeof args.cell !== "string") return null;
|
|
3802
|
+
const value = shown(args.value);
|
|
3803
|
+
return [{ type: "target", ref: cell(args.cell) }, { type: "press" }, { type: "release" }, ...value ? [{ type: "type", text: value }] : []];
|
|
3804
|
+
}
|
|
3805
|
+
case "set_range": {
|
|
3806
|
+
const start = typeof args.start === "string" ? CELL.exec(args.start) : null;
|
|
3807
|
+
const rows = Array.isArray(args.values) ? args.values : [];
|
|
3808
|
+
if (!start || !rows.length) return null;
|
|
3809
|
+
const width = Math.max(1, ...rows.map((r) => Array.isArray(r) ? r.length : 1));
|
|
3810
|
+
const col = colNameToIndex(start[1].toUpperCase());
|
|
3811
|
+
const end = `${indexToColName(col + width - 1)}${Number(start[2]) + rows.length - 1}`;
|
|
3812
|
+
const first = shown(rows.flat().find((v) => v !== null && v !== void 0 && v !== ""));
|
|
3813
|
+
return [
|
|
3814
|
+
{ type: "target", ref: cell(args.start) },
|
|
3815
|
+
{ type: "press" },
|
|
3816
|
+
{ type: "target", ref: cell(`${args.start}:${end}`) },
|
|
3817
|
+
{ type: "release" },
|
|
3818
|
+
...first ? [{ type: "type", text: first }] : []
|
|
3819
|
+
];
|
|
3820
|
+
}
|
|
3821
|
+
case "insert_rows":
|
|
3822
|
+
case "delete_rows": {
|
|
3823
|
+
const from = Number(tool === "insert_rows" ? args.before : args.start);
|
|
3824
|
+
if (!Number.isInteger(from) || from < 1) return null;
|
|
3825
|
+
return [{ type: "target", ref: { kind: "row", id: `${from}:${from + count - 1}` } }, { type: "press" }, { type: "release" }];
|
|
3826
|
+
}
|
|
3827
|
+
case "insert_columns":
|
|
3828
|
+
case "delete_columns": {
|
|
3829
|
+
const from = String(tool === "insert_columns" ? args.before : args.start ?? "");
|
|
3830
|
+
if (!COL.test(from)) return null;
|
|
3831
|
+
const index = colNameToIndex(from.trim().toUpperCase());
|
|
3832
|
+
return [{ type: "target", ref: { kind: "column", id: `${indexToColName(index)}:${indexToColName(index + count - 1)}` } }, { type: "press" }, { type: "release" }];
|
|
3833
|
+
}
|
|
3834
|
+
default:
|
|
3835
|
+
return null;
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3760
3839
|
export {
|
|
3761
3840
|
tokenizeFormula,
|
|
3762
3841
|
WHOLE_COLUMN_END_ROW,
|
|
@@ -3794,5 +3873,7 @@ export {
|
|
|
3794
3873
|
roundFloat,
|
|
3795
3874
|
fillSeries,
|
|
3796
3875
|
computeFillTarget,
|
|
3797
|
-
getFillDirection
|
|
3876
|
+
getFillDirection,
|
|
3877
|
+
parseSpreadsheetPlaybackRef,
|
|
3878
|
+
planSpreadsheetToolPlayback
|
|
3798
3879
|
};
|