@mydatavalue/polter 0.1.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/LICENSE +21 -0
- package/README.md +330 -0
- package/dist/index.d.mts +220 -0
- package/dist/index.d.ts +220 -0
- package/dist/index.js +1274 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1263 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +68 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1263 @@
|
|
|
1
|
+
import { createContext, useRef, useState, useCallback, useMemo, useContext, useEffect, useId } from 'react';
|
|
2
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/components/AgentActionProvider.tsx
|
|
5
|
+
|
|
6
|
+
// src/core/schemaGenerator.ts
|
|
7
|
+
function zodToJsonSchema(schema) {
|
|
8
|
+
if (typeof schema.toJSONSchema === "function") {
|
|
9
|
+
try {
|
|
10
|
+
const result = schema.toJSONSchema();
|
|
11
|
+
delete result.$schema;
|
|
12
|
+
return result;
|
|
13
|
+
} catch {
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const def = schema._def;
|
|
17
|
+
const description = schema.description;
|
|
18
|
+
switch (def.typeName) {
|
|
19
|
+
case "ZodString": {
|
|
20
|
+
const result = { type: "string" };
|
|
21
|
+
if (description) result.description = description;
|
|
22
|
+
if (def.checks) {
|
|
23
|
+
for (const check of def.checks) {
|
|
24
|
+
if (check.kind === "min") result.minLength = check.value;
|
|
25
|
+
if (check.kind === "max") result.maxLength = check.value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
case "ZodNumber": {
|
|
31
|
+
const result = { type: "number" };
|
|
32
|
+
if (description) result.description = description;
|
|
33
|
+
if (def.checks) {
|
|
34
|
+
for (const check of def.checks) {
|
|
35
|
+
if (check.kind === "int") result.type = "integer";
|
|
36
|
+
if (check.kind === "min")
|
|
37
|
+
result[check.inclusive ? "minimum" : "exclusiveMinimum"] = check.value;
|
|
38
|
+
if (check.kind === "max")
|
|
39
|
+
result[check.inclusive ? "maximum" : "exclusiveMaximum"] = check.value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
case "ZodBoolean": {
|
|
45
|
+
const result = { type: "boolean" };
|
|
46
|
+
if (description) result.description = description;
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
case "ZodArray": {
|
|
50
|
+
const result = {
|
|
51
|
+
type: "array",
|
|
52
|
+
items: zodToJsonSchema(def.type)
|
|
53
|
+
};
|
|
54
|
+
if (description) result.description = description;
|
|
55
|
+
if (def.minLength !== null && def.minLength !== void 0)
|
|
56
|
+
result.minItems = def.minLength.value;
|
|
57
|
+
if (def.maxLength !== null && def.maxLength !== void 0)
|
|
58
|
+
result.maxItems = def.maxLength.value;
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
case "ZodObject": {
|
|
62
|
+
const shape = def.shape();
|
|
63
|
+
const properties = {};
|
|
64
|
+
const required = [];
|
|
65
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
66
|
+
properties[key] = zodToJsonSchema(value);
|
|
67
|
+
if (!value.isOptional()) {
|
|
68
|
+
required.push(key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const result = { type: "object", properties };
|
|
72
|
+
if (required.length > 0) result.required = required;
|
|
73
|
+
if (description) result.description = description;
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
case "ZodEnum": {
|
|
77
|
+
const result = { type: "string", enum: [...def.values] };
|
|
78
|
+
if (description) result.description = description;
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
case "ZodLiteral": {
|
|
82
|
+
const result = { type: typeof def.value, const: def.value };
|
|
83
|
+
if (description) result.description = description;
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
case "ZodUnion":
|
|
87
|
+
case "ZodDiscriminatedUnion": {
|
|
88
|
+
const options = def.options;
|
|
89
|
+
const result = { anyOf: options.map((o) => zodToJsonSchema(o)) };
|
|
90
|
+
if (description) result.description = description;
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
case "ZodOptional": {
|
|
94
|
+
const inner = zodToJsonSchema(def.innerType);
|
|
95
|
+
if (description && !inner.description) inner.description = description;
|
|
96
|
+
return inner;
|
|
97
|
+
}
|
|
98
|
+
case "ZodNullable": {
|
|
99
|
+
const inner = zodToJsonSchema(def.innerType);
|
|
100
|
+
if (description && !inner.description) inner.description = description;
|
|
101
|
+
return inner;
|
|
102
|
+
}
|
|
103
|
+
case "ZodDefault": {
|
|
104
|
+
const inner = zodToJsonSchema(def.innerType);
|
|
105
|
+
if (description && !inner.description) inner.description = description;
|
|
106
|
+
return inner;
|
|
107
|
+
}
|
|
108
|
+
case "ZodRecord": {
|
|
109
|
+
const result = {
|
|
110
|
+
type: "object",
|
|
111
|
+
additionalProperties: zodToJsonSchema(def.valueType)
|
|
112
|
+
};
|
|
113
|
+
if (description) result.description = description;
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
case "ZodNull": {
|
|
117
|
+
return { type: "null", ...description && { description } };
|
|
118
|
+
}
|
|
119
|
+
case "ZodAny":
|
|
120
|
+
case "ZodUnknown": {
|
|
121
|
+
return description ? { description } : {};
|
|
122
|
+
}
|
|
123
|
+
default: {
|
|
124
|
+
return { type: "object", ...description && { description } };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function generateToolSchemas(actions) {
|
|
129
|
+
return actions.filter((a) => !a.disabled).map((action) => ({
|
|
130
|
+
name: action.name,
|
|
131
|
+
description: action.description,
|
|
132
|
+
parameters: action.parameters ? zodToJsonSchema(action.parameters) : { type: "object", properties: {} }
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/executor/visualExecutor.ts
|
|
137
|
+
var stylesInjected = false;
|
|
138
|
+
function injectStyles() {
|
|
139
|
+
if (stylesInjected) return;
|
|
140
|
+
if (typeof document === "undefined") return;
|
|
141
|
+
stylesInjected = true;
|
|
142
|
+
const style = document.createElement("style");
|
|
143
|
+
style.id = "polter-styles";
|
|
144
|
+
style.textContent = `
|
|
145
|
+
@keyframes polter-pulse {
|
|
146
|
+
0%, 100% { opacity: 1; transform: scale(1); }
|
|
147
|
+
50% { opacity: 0.7; transform: scale(1.02); }
|
|
148
|
+
}
|
|
149
|
+
@keyframes polter-fade-in {
|
|
150
|
+
from { opacity: 0; transform: translateX(-50%) translateY(4px); }
|
|
151
|
+
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
152
|
+
}
|
|
153
|
+
@keyframes polter-cursor-click {
|
|
154
|
+
0% { transform: scale(1); }
|
|
155
|
+
50% { transform: scale(0.85); }
|
|
156
|
+
100% { transform: scale(1); }
|
|
157
|
+
}
|
|
158
|
+
`;
|
|
159
|
+
document.head.appendChild(style);
|
|
160
|
+
}
|
|
161
|
+
var CURSOR_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
|
162
|
+
<path d="M5.5 3.21V20.8c0 .45.54.67.85.35l4.86-4.86a.5.5 0 0 1 .35-.15h6.87c.48 0 .68-.61.3-.92L5.95 2.87a.5.5 0 0 0-.45.34z" fill="#1e293b" stroke="white" stroke-width="1.5" stroke-linejoin="round"/>
|
|
163
|
+
</svg>`;
|
|
164
|
+
function createBlockingOverlay() {
|
|
165
|
+
const overlay = document.createElement("div");
|
|
166
|
+
overlay.className = "polter-blocking-overlay";
|
|
167
|
+
overlay.style.cssText = `
|
|
168
|
+
position:fixed;
|
|
169
|
+
inset:0;
|
|
170
|
+
z-index:99997;
|
|
171
|
+
cursor:not-allowed;
|
|
172
|
+
`;
|
|
173
|
+
document.body.appendChild(overlay);
|
|
174
|
+
return { remove: () => overlay.remove() };
|
|
175
|
+
}
|
|
176
|
+
function createCursor() {
|
|
177
|
+
injectStyles();
|
|
178
|
+
const cursor = document.createElement("div");
|
|
179
|
+
cursor.className = "polter-cursor";
|
|
180
|
+
cursor.innerHTML = CURSOR_SVG;
|
|
181
|
+
cursor.style.cssText = `
|
|
182
|
+
position:fixed;
|
|
183
|
+
left:-40px;
|
|
184
|
+
top:-40px;
|
|
185
|
+
z-index:100000;
|
|
186
|
+
pointer-events:none;
|
|
187
|
+
transition:left 0.4s cubic-bezier(0.4,0,0.2,1),top 0.4s cubic-bezier(0.4,0,0.2,1);
|
|
188
|
+
filter:drop-shadow(0 2px 4px rgba(0,0,0,0.3));
|
|
189
|
+
`;
|
|
190
|
+
document.body.appendChild(cursor);
|
|
191
|
+
return { remove: () => cursor.remove() };
|
|
192
|
+
}
|
|
193
|
+
function moveCursorTo(target, signal) {
|
|
194
|
+
const cursor = document.querySelector(".polter-cursor");
|
|
195
|
+
if (!cursor) return Promise.resolve();
|
|
196
|
+
const rect = target.getBoundingClientRect();
|
|
197
|
+
cursor.style.left = `${rect.left + rect.width / 2}px`;
|
|
198
|
+
cursor.style.top = `${rect.top + rect.height / 2}px`;
|
|
199
|
+
return delay(450, signal);
|
|
200
|
+
}
|
|
201
|
+
function animateCursorClick() {
|
|
202
|
+
const cursor = document.querySelector(".polter-cursor");
|
|
203
|
+
if (!cursor) return;
|
|
204
|
+
cursor.style.animation = "polter-cursor-click 0.2s ease";
|
|
205
|
+
cursor.addEventListener("animationend", () => {
|
|
206
|
+
cursor.style.animation = "";
|
|
207
|
+
}, { once: true });
|
|
208
|
+
}
|
|
209
|
+
function createSpotlight(target, label, config) {
|
|
210
|
+
injectStyles();
|
|
211
|
+
const rect = target.getBoundingClientRect();
|
|
212
|
+
const padding = config.spotlightPadding;
|
|
213
|
+
const overlayRgba = `rgba(0, 0, 0, ${config.overlayOpacity})`;
|
|
214
|
+
const container = document.createElement("div");
|
|
215
|
+
container.className = "polter-spotlight-container";
|
|
216
|
+
container.style.cssText = "position:fixed;inset:0;z-index:99998;pointer-events:none;";
|
|
217
|
+
const spotlight = document.createElement("div");
|
|
218
|
+
spotlight.className = "polter-spotlight";
|
|
219
|
+
spotlight.style.cssText = `
|
|
220
|
+
position:fixed;
|
|
221
|
+
left:${rect.left - padding}px;
|
|
222
|
+
top:${rect.top - padding}px;
|
|
223
|
+
width:${rect.width + padding * 2}px;
|
|
224
|
+
height:${rect.height + padding * 2}px;
|
|
225
|
+
border-radius:8px;
|
|
226
|
+
box-shadow:0 0 0 9999px ${overlayRgba};
|
|
227
|
+
z-index:99998;
|
|
228
|
+
pointer-events:none;
|
|
229
|
+
transition:all 0.3s ease;
|
|
230
|
+
`;
|
|
231
|
+
const ring = document.createElement("div");
|
|
232
|
+
ring.className = "polter-ring";
|
|
233
|
+
ring.style.cssText = `
|
|
234
|
+
position:fixed;
|
|
235
|
+
left:${rect.left - padding - 2}px;
|
|
236
|
+
top:${rect.top - padding - 2}px;
|
|
237
|
+
width:${rect.width + padding * 2 + 4}px;
|
|
238
|
+
height:${rect.height + padding * 2 + 4}px;
|
|
239
|
+
border:2px solid #3b82f6;
|
|
240
|
+
border-radius:10px;
|
|
241
|
+
z-index:99999;
|
|
242
|
+
pointer-events:none;
|
|
243
|
+
animation:polter-pulse 1.5s ease-in-out infinite;
|
|
244
|
+
`;
|
|
245
|
+
container.appendChild(spotlight);
|
|
246
|
+
container.appendChild(ring);
|
|
247
|
+
if (label && config.tooltipEnabled) {
|
|
248
|
+
const tooltip = document.createElement("div");
|
|
249
|
+
tooltip.className = "polter-tooltip";
|
|
250
|
+
tooltip.textContent = label;
|
|
251
|
+
const spaceBelow = window.innerHeight - rect.bottom - padding;
|
|
252
|
+
const isBelow = spaceBelow > 60;
|
|
253
|
+
const tooltipTop = isBelow ? rect.bottom + padding + 12 : rect.top - padding - 44;
|
|
254
|
+
tooltip.style.cssText = `
|
|
255
|
+
position:fixed;
|
|
256
|
+
left:${rect.left + rect.width / 2}px;
|
|
257
|
+
top:${tooltipTop}px;
|
|
258
|
+
transform:translateX(-50%);
|
|
259
|
+
background:#1e293b;
|
|
260
|
+
color:#f8fafc;
|
|
261
|
+
padding:8px 14px;
|
|
262
|
+
border-radius:6px;
|
|
263
|
+
font-size:13px;
|
|
264
|
+
font-weight:500;
|
|
265
|
+
line-height:1.4;
|
|
266
|
+
white-space:nowrap;
|
|
267
|
+
z-index:99999;
|
|
268
|
+
pointer-events:none;
|
|
269
|
+
animation:polter-fade-in 0.2s ease;
|
|
270
|
+
box-shadow:0 4px 12px rgba(0,0,0,0.15);
|
|
271
|
+
`;
|
|
272
|
+
container.appendChild(tooltip);
|
|
273
|
+
}
|
|
274
|
+
document.body.appendChild(container);
|
|
275
|
+
return {
|
|
276
|
+
remove: () => container.remove()
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function delay(ms, signal) {
|
|
280
|
+
return new Promise((resolve, reject) => {
|
|
281
|
+
if (signal?.aborted) {
|
|
282
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const timer = setTimeout(resolve, ms);
|
|
286
|
+
signal?.addEventListener(
|
|
287
|
+
"abort",
|
|
288
|
+
() => {
|
|
289
|
+
clearTimeout(timer);
|
|
290
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
291
|
+
},
|
|
292
|
+
{ once: true }
|
|
293
|
+
);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
function setNativeInputValue(input, value) {
|
|
297
|
+
const nativeSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set ?? Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
|
|
298
|
+
if (nativeSetter) {
|
|
299
|
+
nativeSetter.call(input, value);
|
|
300
|
+
} else {
|
|
301
|
+
input.value = value;
|
|
302
|
+
}
|
|
303
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
304
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
305
|
+
}
|
|
306
|
+
async function simulateTyping(element, value, signal) {
|
|
307
|
+
const input = element;
|
|
308
|
+
input.focus();
|
|
309
|
+
if (input.value) {
|
|
310
|
+
setNativeInputValue(input, "");
|
|
311
|
+
await delay(30, signal);
|
|
312
|
+
}
|
|
313
|
+
const charDelay = Math.max(15, Math.min(40, 800 / value.length));
|
|
314
|
+
for (let i = 0; i < value.length; i++) {
|
|
315
|
+
if (signal?.aborted) return;
|
|
316
|
+
setNativeInputValue(input, value.slice(0, i + 1));
|
|
317
|
+
await delay(charDelay, signal);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async function resolveStepElement(target, actionName, params, config) {
|
|
321
|
+
if (target.prepareView) {
|
|
322
|
+
await target.prepareView(params);
|
|
323
|
+
await delay(200, config.signal);
|
|
324
|
+
}
|
|
325
|
+
if (target.fromParam && config.resolveTarget) {
|
|
326
|
+
const raw = params[target.fromParam];
|
|
327
|
+
const paramValue = String(Array.isArray(raw) ? raw[0] ?? "" : raw ?? "");
|
|
328
|
+
return config.resolveTarget(actionName, target.fromParam, paramValue, config.signal);
|
|
329
|
+
}
|
|
330
|
+
if (target.fromTarget && config.resolveNamedTarget) {
|
|
331
|
+
return config.resolveNamedTarget(actionName, target.fromTarget, config.signal);
|
|
332
|
+
}
|
|
333
|
+
return target.element;
|
|
334
|
+
}
|
|
335
|
+
async function executeInstant(action, params) {
|
|
336
|
+
try {
|
|
337
|
+
if (action.onExecute) {
|
|
338
|
+
await action.onExecute(params);
|
|
339
|
+
} else {
|
|
340
|
+
const targets = action.getExecutionTargets();
|
|
341
|
+
for (const target of targets) {
|
|
342
|
+
target.element?.click();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return { success: true, actionName: action.name };
|
|
346
|
+
} catch (err) {
|
|
347
|
+
return { success: false, actionName: action.name, error: String(err) };
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function isElementVisible(el) {
|
|
351
|
+
if (!el.isConnected) return false;
|
|
352
|
+
const rect = el.getBoundingClientRect();
|
|
353
|
+
return rect.width > 0 && rect.height > 0;
|
|
354
|
+
}
|
|
355
|
+
async function executeGuided(action, params, config) {
|
|
356
|
+
const targets = action.getExecutionTargets();
|
|
357
|
+
if (targets.length === 0 || targets.every((t) => t.element && !isElementVisible(t.element))) {
|
|
358
|
+
if (action.onExecute) {
|
|
359
|
+
await action.onExecute(params);
|
|
360
|
+
}
|
|
361
|
+
return { success: true, actionName: action.name };
|
|
362
|
+
}
|
|
363
|
+
let spotlight = null;
|
|
364
|
+
let cursor = null;
|
|
365
|
+
const blocker = createBlockingOverlay();
|
|
366
|
+
if (config.cursorEnabled) {
|
|
367
|
+
cursor = createCursor();
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
for (let i = 0; i < targets.length; i++) {
|
|
371
|
+
const target = targets[i];
|
|
372
|
+
const isLast = i === targets.length - 1;
|
|
373
|
+
const element = await resolveStepElement(target, action.name, params, config);
|
|
374
|
+
if (!element) continue;
|
|
375
|
+
if (!isElementVisible(element)) {
|
|
376
|
+
if (targets.length > 1) {
|
|
377
|
+
blocker.remove();
|
|
378
|
+
cursor?.remove();
|
|
379
|
+
return { success: false, actionName: action.name, error: `Step element not visible: "${target.label}"` };
|
|
380
|
+
}
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
element.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
384
|
+
await delay(300, config.signal);
|
|
385
|
+
if (cursor) {
|
|
386
|
+
await moveCursorTo(element, config.signal);
|
|
387
|
+
}
|
|
388
|
+
spotlight = createSpotlight(element, target.label, config);
|
|
389
|
+
await delay(config.stepDelay, config.signal);
|
|
390
|
+
if (target.setParam) {
|
|
391
|
+
const inputEl = element.tagName === "INPUT" || element.tagName === "TEXTAREA" ? element : element.querySelector("input, textarea") ?? element;
|
|
392
|
+
const value = String(params[target.setParam] ?? "");
|
|
393
|
+
await simulateTyping(inputEl, value, config.signal);
|
|
394
|
+
} else if (target.setValue && target.onSetValue) {
|
|
395
|
+
const value = params[target.setValue];
|
|
396
|
+
target.onSetValue(value);
|
|
397
|
+
} else if (target.fromParam || target.fromTarget) {
|
|
398
|
+
animateCursorClick();
|
|
399
|
+
element.click();
|
|
400
|
+
} else if (action.onExecute) {
|
|
401
|
+
if (!isLast) {
|
|
402
|
+
animateCursorClick();
|
|
403
|
+
element.click();
|
|
404
|
+
}
|
|
405
|
+
} else {
|
|
406
|
+
animateCursorClick();
|
|
407
|
+
element.click();
|
|
408
|
+
}
|
|
409
|
+
spotlight.remove();
|
|
410
|
+
spotlight = null;
|
|
411
|
+
if (!isLast) {
|
|
412
|
+
await delay(200, config.signal);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (action.onExecute) {
|
|
416
|
+
await action.onExecute(params);
|
|
417
|
+
}
|
|
418
|
+
blocker.remove();
|
|
419
|
+
cursor?.remove();
|
|
420
|
+
return { success: true, actionName: action.name };
|
|
421
|
+
} catch (err) {
|
|
422
|
+
spotlight?.remove();
|
|
423
|
+
blocker.remove();
|
|
424
|
+
cursor?.remove();
|
|
425
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
426
|
+
return { success: false, actionName: action.name, error: "Execution cancelled" };
|
|
427
|
+
}
|
|
428
|
+
return { success: false, actionName: action.name, error: String(err) };
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
async function executeAction(action, params, config) {
|
|
432
|
+
if (config.mode === "instant") {
|
|
433
|
+
return executeInstant(action, params);
|
|
434
|
+
}
|
|
435
|
+
return executeGuided(action, params, config);
|
|
436
|
+
}
|
|
437
|
+
var AgentActionContext = createContext(null);
|
|
438
|
+
function AgentActionProvider({
|
|
439
|
+
mode = "guided",
|
|
440
|
+
stepDelay = 600,
|
|
441
|
+
overlayOpacity = 0.5,
|
|
442
|
+
spotlightPadding = 8,
|
|
443
|
+
tooltipEnabled = true,
|
|
444
|
+
cursorEnabled = true,
|
|
445
|
+
children,
|
|
446
|
+
onExecutionStart,
|
|
447
|
+
onExecutionComplete
|
|
448
|
+
}) {
|
|
449
|
+
const actionsRef = useRef(/* @__PURE__ */ new Map());
|
|
450
|
+
const targetsRef = useRef(/* @__PURE__ */ new Map());
|
|
451
|
+
const [version, setVersion] = useState(0);
|
|
452
|
+
const [isExecuting, setIsExecuting] = useState(false);
|
|
453
|
+
const currentExecutionRef = useRef(null);
|
|
454
|
+
const registerAction = useCallback((action) => {
|
|
455
|
+
const existing = actionsRef.current.get(action.name);
|
|
456
|
+
actionsRef.current.set(action.name, action);
|
|
457
|
+
if (!existing || existing.description !== action.description || existing.disabled !== action.disabled || existing.disabledReason !== action.disabledReason) {
|
|
458
|
+
setVersion((v) => v + 1);
|
|
459
|
+
}
|
|
460
|
+
}, []);
|
|
461
|
+
const unregisterAction = useCallback((name) => {
|
|
462
|
+
if (actionsRef.current.delete(name)) {
|
|
463
|
+
setVersion((v) => v + 1);
|
|
464
|
+
}
|
|
465
|
+
}, []);
|
|
466
|
+
const registerTarget = useCallback((id, entry) => {
|
|
467
|
+
targetsRef.current.set(id, entry);
|
|
468
|
+
}, []);
|
|
469
|
+
const unregisterTarget = useCallback((id) => {
|
|
470
|
+
targetsRef.current.delete(id);
|
|
471
|
+
}, []);
|
|
472
|
+
const resolveTarget = useCallback(
|
|
473
|
+
async (actionName, param, value, signal) => {
|
|
474
|
+
const normalizedValue = value.toLowerCase();
|
|
475
|
+
const maxWait = 3e3;
|
|
476
|
+
const pollInterval = 50;
|
|
477
|
+
const start = Date.now();
|
|
478
|
+
while (Date.now() - start < maxWait) {
|
|
479
|
+
if (signal?.aborted) return null;
|
|
480
|
+
for (const entry of targetsRef.current.values()) {
|
|
481
|
+
if ((!entry.action || entry.action === actionName) && entry.param === param && entry.value?.toLowerCase() === normalizedValue && entry.element.isConnected) {
|
|
482
|
+
return entry.element;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
486
|
+
}
|
|
487
|
+
return null;
|
|
488
|
+
},
|
|
489
|
+
[]
|
|
490
|
+
);
|
|
491
|
+
const resolveNamedTarget = useCallback(
|
|
492
|
+
async (actionName, name, signal) => {
|
|
493
|
+
const maxWait = 3e3;
|
|
494
|
+
const pollInterval = 50;
|
|
495
|
+
const start = Date.now();
|
|
496
|
+
while (Date.now() - start < maxWait) {
|
|
497
|
+
if (signal?.aborted) return null;
|
|
498
|
+
for (const entry of targetsRef.current.values()) {
|
|
499
|
+
if ((!entry.action || entry.action === actionName) && entry.name === name && entry.element.isConnected) {
|
|
500
|
+
return entry.element;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
504
|
+
}
|
|
505
|
+
return null;
|
|
506
|
+
},
|
|
507
|
+
[]
|
|
508
|
+
);
|
|
509
|
+
const execute = useCallback(
|
|
510
|
+
async (actionName, params) => {
|
|
511
|
+
currentExecutionRef.current?.abort();
|
|
512
|
+
const controller = new AbortController();
|
|
513
|
+
currentExecutionRef.current = controller;
|
|
514
|
+
const action = actionsRef.current.get(actionName);
|
|
515
|
+
if (!action) {
|
|
516
|
+
return { success: false, actionName, error: `Action "${actionName}" not found` };
|
|
517
|
+
}
|
|
518
|
+
if (action.disabled) {
|
|
519
|
+
return {
|
|
520
|
+
success: false,
|
|
521
|
+
actionName,
|
|
522
|
+
error: action.disabledReason || "Action is disabled"
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
setIsExecuting(true);
|
|
526
|
+
onExecutionStart?.(actionName);
|
|
527
|
+
try {
|
|
528
|
+
const result = await executeAction(action, params ?? {}, {
|
|
529
|
+
mode,
|
|
530
|
+
stepDelay,
|
|
531
|
+
overlayOpacity,
|
|
532
|
+
spotlightPadding,
|
|
533
|
+
tooltipEnabled,
|
|
534
|
+
cursorEnabled,
|
|
535
|
+
signal: controller.signal,
|
|
536
|
+
resolveTarget,
|
|
537
|
+
resolveNamedTarget
|
|
538
|
+
});
|
|
539
|
+
onExecutionComplete?.(result);
|
|
540
|
+
return result;
|
|
541
|
+
} catch (err) {
|
|
542
|
+
const result = {
|
|
543
|
+
success: false,
|
|
544
|
+
actionName,
|
|
545
|
+
error: err instanceof DOMException && err.name === "AbortError" ? "Execution cancelled" : String(err)
|
|
546
|
+
};
|
|
547
|
+
onExecutionComplete?.(result);
|
|
548
|
+
return result;
|
|
549
|
+
} finally {
|
|
550
|
+
setIsExecuting(false);
|
|
551
|
+
if (currentExecutionRef.current === controller) {
|
|
552
|
+
currentExecutionRef.current = null;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
[mode, stepDelay, overlayOpacity, spotlightPadding, tooltipEnabled, cursorEnabled, onExecutionStart, onExecutionComplete, resolveTarget, resolveNamedTarget]
|
|
557
|
+
);
|
|
558
|
+
const availableActions = useMemo(
|
|
559
|
+
() => Array.from(actionsRef.current.values()).map((a) => ({
|
|
560
|
+
name: a.name,
|
|
561
|
+
description: a.description,
|
|
562
|
+
disabled: a.disabled,
|
|
563
|
+
disabledReason: a.disabledReason,
|
|
564
|
+
hasParameters: !!a.parameters
|
|
565
|
+
})),
|
|
566
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
567
|
+
[version]
|
|
568
|
+
);
|
|
569
|
+
const schemas = useMemo(
|
|
570
|
+
() => generateToolSchemas(Array.from(actionsRef.current.values())),
|
|
571
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
572
|
+
[version]
|
|
573
|
+
);
|
|
574
|
+
const contextValue = useMemo(
|
|
575
|
+
() => ({
|
|
576
|
+
registerAction,
|
|
577
|
+
unregisterAction,
|
|
578
|
+
registerTarget,
|
|
579
|
+
unregisterTarget,
|
|
580
|
+
execute,
|
|
581
|
+
availableActions,
|
|
582
|
+
schemas,
|
|
583
|
+
isExecuting,
|
|
584
|
+
mode
|
|
585
|
+
}),
|
|
586
|
+
[
|
|
587
|
+
registerAction,
|
|
588
|
+
unregisterAction,
|
|
589
|
+
registerTarget,
|
|
590
|
+
unregisterTarget,
|
|
591
|
+
execute,
|
|
592
|
+
availableActions,
|
|
593
|
+
schemas,
|
|
594
|
+
isExecuting,
|
|
595
|
+
mode
|
|
596
|
+
]
|
|
597
|
+
);
|
|
598
|
+
return /* @__PURE__ */ jsx(AgentActionContext.Provider, { value: contextValue, children });
|
|
599
|
+
}
|
|
600
|
+
var AgentStepContext = createContext(null);
|
|
601
|
+
function AgentAction({
|
|
602
|
+
name,
|
|
603
|
+
description,
|
|
604
|
+
parameters,
|
|
605
|
+
onExecute,
|
|
606
|
+
disabled = false,
|
|
607
|
+
disabledReason,
|
|
608
|
+
children
|
|
609
|
+
}) {
|
|
610
|
+
const context = useContext(AgentActionContext);
|
|
611
|
+
if (!context) {
|
|
612
|
+
throw new Error("AgentAction must be used within an AgentActionProvider");
|
|
613
|
+
}
|
|
614
|
+
const wrapperRef = useRef(null);
|
|
615
|
+
const stepsRef = useRef(/* @__PURE__ */ new Map());
|
|
616
|
+
const onExecuteRef = useRef(onExecute);
|
|
617
|
+
onExecuteRef.current = onExecute;
|
|
618
|
+
const parametersRef = useRef(parameters);
|
|
619
|
+
parametersRef.current = parameters;
|
|
620
|
+
const stableOnExecute = useCallback((params) => {
|
|
621
|
+
return onExecuteRef.current?.(params);
|
|
622
|
+
}, []);
|
|
623
|
+
const getExecutionTargets = useCallback(() => {
|
|
624
|
+
if (stepsRef.current.size > 0) {
|
|
625
|
+
const steps = Array.from(stepsRef.current.values());
|
|
626
|
+
const withElements = steps.filter((s) => s.element);
|
|
627
|
+
const withoutElements = steps.filter((s) => !s.element && (s.fromParam || s.fromTarget));
|
|
628
|
+
withElements.sort((a, b) => {
|
|
629
|
+
if (!a.element || !b.element) return 0;
|
|
630
|
+
const pos = a.element.compareDocumentPosition(b.element);
|
|
631
|
+
return pos & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
|
|
632
|
+
});
|
|
633
|
+
const ordered = [...withElements, ...withoutElements];
|
|
634
|
+
return ordered.map((s) => ({
|
|
635
|
+
label: s.label,
|
|
636
|
+
element: s.element,
|
|
637
|
+
fromParam: s.fromParam,
|
|
638
|
+
fromTarget: s.fromTarget,
|
|
639
|
+
setParam: s.setParam,
|
|
640
|
+
setValue: s.setValue,
|
|
641
|
+
onSetValue: s.onSetValue,
|
|
642
|
+
prepareView: s.prepareView
|
|
643
|
+
}));
|
|
644
|
+
}
|
|
645
|
+
const el = wrapperRef.current?.firstElementChild;
|
|
646
|
+
return el ? [{ label: description, element: el }] : [];
|
|
647
|
+
}, [description]);
|
|
648
|
+
const { registerAction, unregisterAction } = context;
|
|
649
|
+
useEffect(() => {
|
|
650
|
+
registerAction({
|
|
651
|
+
name,
|
|
652
|
+
description,
|
|
653
|
+
parameters: parametersRef.current,
|
|
654
|
+
onExecute: onExecuteRef.current ? stableOnExecute : void 0,
|
|
655
|
+
disabled,
|
|
656
|
+
disabledReason,
|
|
657
|
+
getExecutionTargets
|
|
658
|
+
});
|
|
659
|
+
return () => unregisterAction(name);
|
|
660
|
+
}, [name, description, disabled, disabledReason, stableOnExecute, getExecutionTargets, registerAction, unregisterAction]);
|
|
661
|
+
const registerStep = useCallback(
|
|
662
|
+
(id, data) => {
|
|
663
|
+
stepsRef.current.set(id, data);
|
|
664
|
+
},
|
|
665
|
+
[]
|
|
666
|
+
);
|
|
667
|
+
const unregisterStep = useCallback((id) => {
|
|
668
|
+
stepsRef.current.delete(id);
|
|
669
|
+
}, []);
|
|
670
|
+
const stepContextValue = useMemo(
|
|
671
|
+
() => ({ registerStep, unregisterStep }),
|
|
672
|
+
[registerStep, unregisterStep]
|
|
673
|
+
);
|
|
674
|
+
if (!children) return null;
|
|
675
|
+
return /* @__PURE__ */ jsx(AgentStepContext.Provider, { value: stepContextValue, children: /* @__PURE__ */ jsx("div", { ref: wrapperRef, style: { display: "contents" }, children }) });
|
|
676
|
+
}
|
|
677
|
+
function AgentStep({
|
|
678
|
+
label,
|
|
679
|
+
children,
|
|
680
|
+
fromParam,
|
|
681
|
+
fromTarget,
|
|
682
|
+
setParam,
|
|
683
|
+
setValue,
|
|
684
|
+
onSetValue,
|
|
685
|
+
prepareView
|
|
686
|
+
}) {
|
|
687
|
+
const id = useId();
|
|
688
|
+
const wrapperRef = useRef(null);
|
|
689
|
+
const stepContext = useContext(AgentStepContext);
|
|
690
|
+
if (!stepContext) {
|
|
691
|
+
throw new Error("AgentStep must be used within an AgentAction");
|
|
692
|
+
}
|
|
693
|
+
const onSetValueRef = useRef(onSetValue);
|
|
694
|
+
onSetValueRef.current = onSetValue;
|
|
695
|
+
const prepareViewRef = useRef(prepareView);
|
|
696
|
+
prepareViewRef.current = prepareView;
|
|
697
|
+
useEffect(() => {
|
|
698
|
+
const element = children ? wrapperRef.current?.firstElementChild : null;
|
|
699
|
+
stepContext.registerStep(id, {
|
|
700
|
+
label,
|
|
701
|
+
element,
|
|
702
|
+
fromParam,
|
|
703
|
+
fromTarget,
|
|
704
|
+
setParam,
|
|
705
|
+
setValue,
|
|
706
|
+
onSetValue: onSetValueRef.current,
|
|
707
|
+
prepareView: prepareViewRef.current
|
|
708
|
+
});
|
|
709
|
+
return () => stepContext.unregisterStep(id);
|
|
710
|
+
}, [id, label, fromParam, fromTarget, setParam, setValue, stepContext]);
|
|
711
|
+
if (!children) return null;
|
|
712
|
+
return /* @__PURE__ */ jsx("div", { ref: wrapperRef, style: { display: "contents" }, children });
|
|
713
|
+
}
|
|
714
|
+
function AgentTarget({ action, param, value, name, children }) {
|
|
715
|
+
const id = useId();
|
|
716
|
+
const wrapperRef = useRef(null);
|
|
717
|
+
const context = useContext(AgentActionContext);
|
|
718
|
+
if (!context) {
|
|
719
|
+
throw new Error("AgentTarget must be used within an AgentActionProvider");
|
|
720
|
+
}
|
|
721
|
+
const { registerTarget, unregisterTarget } = context;
|
|
722
|
+
useEffect(() => {
|
|
723
|
+
const element = wrapperRef.current?.firstElementChild;
|
|
724
|
+
if (element) {
|
|
725
|
+
registerTarget(id, { action, param, value, name, element });
|
|
726
|
+
}
|
|
727
|
+
return () => unregisterTarget(id);
|
|
728
|
+
}, [id, action, param, value, name, registerTarget, unregisterTarget]);
|
|
729
|
+
return /* @__PURE__ */ jsx("div", { ref: wrapperRef, style: { display: "contents" }, children });
|
|
730
|
+
}
|
|
731
|
+
function useAgentActions() {
|
|
732
|
+
const context = useContext(AgentActionContext);
|
|
733
|
+
if (!context) {
|
|
734
|
+
throw new Error("useAgentActions must be used within an AgentActionProvider");
|
|
735
|
+
}
|
|
736
|
+
return context;
|
|
737
|
+
}
|
|
738
|
+
var logId = 0;
|
|
739
|
+
var PANEL_WIDTH = 440;
|
|
740
|
+
function getSchemaFields(schemas, actionName) {
|
|
741
|
+
const schema = schemas.find((s) => s.name === actionName);
|
|
742
|
+
if (!schema?.parameters) return [];
|
|
743
|
+
const params = schema.parameters;
|
|
744
|
+
const properties = params.properties ?? {};
|
|
745
|
+
const required = params.required ?? [];
|
|
746
|
+
return Object.entries(properties).map(([name, prop]) => ({
|
|
747
|
+
name,
|
|
748
|
+
type: prop.type ?? "string",
|
|
749
|
+
description: prop.description,
|
|
750
|
+
enumValues: prop.enum,
|
|
751
|
+
isRequired: required.includes(name)
|
|
752
|
+
}));
|
|
753
|
+
}
|
|
754
|
+
function AgentDevTools({ defaultOpen = false }) {
|
|
755
|
+
const { availableActions, execute, isExecuting, mode, schemas } = useAgentActions();
|
|
756
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
757
|
+
const [log, setLog] = useState([]);
|
|
758
|
+
const [expandedAction, setExpandedAction] = useState(null);
|
|
759
|
+
const [paramInputs, setParamInputs] = useState({});
|
|
760
|
+
const [filter, setFilter] = useState("");
|
|
761
|
+
const [tab, setTab] = useState("actions");
|
|
762
|
+
const logEndRef = useRef(null);
|
|
763
|
+
useEffect(() => {
|
|
764
|
+
if (tab === "log") logEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
765
|
+
}, [log, tab]);
|
|
766
|
+
const setFieldValue = useCallback((actionName, fieldName, value) => {
|
|
767
|
+
setParamInputs((prev) => ({
|
|
768
|
+
...prev,
|
|
769
|
+
[actionName]: { ...prev[actionName], [fieldName]: value }
|
|
770
|
+
}));
|
|
771
|
+
}, []);
|
|
772
|
+
const handleExecute = useCallback(
|
|
773
|
+
async (action) => {
|
|
774
|
+
const entryId = ++logId;
|
|
775
|
+
let params;
|
|
776
|
+
const fields = getSchemaFields(schemas, action.name);
|
|
777
|
+
const rawInputs = paramInputs[action.name];
|
|
778
|
+
if (fields.length > 0 && rawInputs) {
|
|
779
|
+
params = {};
|
|
780
|
+
for (const field of fields) {
|
|
781
|
+
const raw = rawInputs[field.name];
|
|
782
|
+
if (raw === void 0 || raw === "") continue;
|
|
783
|
+
if (field.type === "number" || field.type === "integer") {
|
|
784
|
+
params[field.name] = Number(raw);
|
|
785
|
+
} else if (field.type === "boolean") {
|
|
786
|
+
params[field.name] = raw === "true";
|
|
787
|
+
} else if (field.type === "array") {
|
|
788
|
+
try {
|
|
789
|
+
params[field.name] = JSON.parse(raw);
|
|
790
|
+
} catch {
|
|
791
|
+
setLog((prev) => [
|
|
792
|
+
...prev,
|
|
793
|
+
{
|
|
794
|
+
id: entryId,
|
|
795
|
+
action: action.name,
|
|
796
|
+
timestamp: Date.now(),
|
|
797
|
+
result: { success: false, actionName: action.name, error: `Invalid JSON for ${field.name}` }
|
|
798
|
+
}
|
|
799
|
+
]);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
} else {
|
|
803
|
+
params[field.name] = raw;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
setLog((prev) => [...prev, { id: entryId, action: action.name, params, timestamp: Date.now() }]);
|
|
808
|
+
const result = await execute(action.name, params);
|
|
809
|
+
setLog((prev) => prev.map((e) => e.id === entryId ? { ...e, result } : e));
|
|
810
|
+
},
|
|
811
|
+
[execute, paramInputs, schemas]
|
|
812
|
+
);
|
|
813
|
+
const filtered = filter ? availableActions.filter((a) => a.name.includes(filter) || a.description.includes(filter)) : availableActions;
|
|
814
|
+
if (!open) {
|
|
815
|
+
return /* @__PURE__ */ jsxs(
|
|
816
|
+
"button",
|
|
817
|
+
{
|
|
818
|
+
onClick: () => setOpen(true),
|
|
819
|
+
style: {
|
|
820
|
+
position: "fixed",
|
|
821
|
+
bottom: 16,
|
|
822
|
+
right: 16,
|
|
823
|
+
zIndex: 99990,
|
|
824
|
+
height: 36,
|
|
825
|
+
paddingLeft: 12,
|
|
826
|
+
paddingRight: 14,
|
|
827
|
+
borderRadius: 18,
|
|
828
|
+
border: "none",
|
|
829
|
+
cursor: "pointer",
|
|
830
|
+
background: "#3b82f6",
|
|
831
|
+
color: "white",
|
|
832
|
+
fontSize: 13,
|
|
833
|
+
fontWeight: 600,
|
|
834
|
+
fontFamily: "system-ui, sans-serif",
|
|
835
|
+
boxShadow: "0 2px 12px rgba(59,130,246,0.4)",
|
|
836
|
+
display: "flex",
|
|
837
|
+
alignItems: "center",
|
|
838
|
+
gap: 6
|
|
839
|
+
},
|
|
840
|
+
children: [
|
|
841
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 16 }, children: "\u2699" }),
|
|
842
|
+
"Agent \xB7 ",
|
|
843
|
+
availableActions.length
|
|
844
|
+
]
|
|
845
|
+
}
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
849
|
+
/* @__PURE__ */ jsx(
|
|
850
|
+
"div",
|
|
851
|
+
{
|
|
852
|
+
onClick: () => setOpen(false),
|
|
853
|
+
style: {
|
|
854
|
+
position: "fixed",
|
|
855
|
+
inset: 0,
|
|
856
|
+
zIndex: 99989,
|
|
857
|
+
background: "rgba(0,0,0,0.15)"
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
),
|
|
861
|
+
/* @__PURE__ */ jsxs(
|
|
862
|
+
"div",
|
|
863
|
+
{
|
|
864
|
+
style: {
|
|
865
|
+
position: "fixed",
|
|
866
|
+
top: 0,
|
|
867
|
+
right: 0,
|
|
868
|
+
bottom: 0,
|
|
869
|
+
zIndex: 99990,
|
|
870
|
+
width: PANEL_WIDTH,
|
|
871
|
+
display: "flex",
|
|
872
|
+
flexDirection: "column",
|
|
873
|
+
background: "#0f172a",
|
|
874
|
+
color: "#e2e8f0",
|
|
875
|
+
boxShadow: "-4px 0 24px rgba(0,0,0,0.3)",
|
|
876
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
877
|
+
fontSize: 13
|
|
878
|
+
},
|
|
879
|
+
children: [
|
|
880
|
+
/* @__PURE__ */ jsxs(
|
|
881
|
+
"div",
|
|
882
|
+
{
|
|
883
|
+
style: {
|
|
884
|
+
padding: "16px 20px",
|
|
885
|
+
display: "flex",
|
|
886
|
+
alignItems: "center",
|
|
887
|
+
justifyContent: "space-between",
|
|
888
|
+
borderBottom: "1px solid #1e293b"
|
|
889
|
+
},
|
|
890
|
+
children: [
|
|
891
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
892
|
+
/* @__PURE__ */ jsx("div", { style: { fontWeight: 700, fontSize: 15 }, children: "Agent DevTools" }),
|
|
893
|
+
/* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: "#64748b", marginTop: 2 }, children: [
|
|
894
|
+
availableActions.length,
|
|
895
|
+
" actions registered \xB7 ",
|
|
896
|
+
mode,
|
|
897
|
+
" mode"
|
|
898
|
+
] })
|
|
899
|
+
] }),
|
|
900
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setOpen(false), style: closeBtnStyle, children: "\u2715" })
|
|
901
|
+
]
|
|
902
|
+
}
|
|
903
|
+
),
|
|
904
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", borderBottom: "1px solid #1e293b" }, children: [
|
|
905
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setTab("actions"), style: tabStyle(tab === "actions"), children: "Actions" }),
|
|
906
|
+
/* @__PURE__ */ jsxs("button", { onClick: () => setTab("log"), style: tabStyle(tab === "log"), children: [
|
|
907
|
+
"Log ",
|
|
908
|
+
log.length > 0 && `(${log.length})`
|
|
909
|
+
] })
|
|
910
|
+
] }),
|
|
911
|
+
tab === "actions" ? /* @__PURE__ */ jsxs("div", { style: { flex: 1, overflowY: "auto", minHeight: 0 }, children: [
|
|
912
|
+
/* @__PURE__ */ jsx("div", { style: { padding: "12px 20px" }, children: /* @__PURE__ */ jsx(
|
|
913
|
+
"input",
|
|
914
|
+
{
|
|
915
|
+
value: filter,
|
|
916
|
+
onChange: (e) => setFilter(e.target.value),
|
|
917
|
+
placeholder: "Filter actions...",
|
|
918
|
+
style: filterInputStyle
|
|
919
|
+
}
|
|
920
|
+
) }),
|
|
921
|
+
filtered.length > 0 && filtered.map((action) => /* @__PURE__ */ jsx(
|
|
922
|
+
ActionRow,
|
|
923
|
+
{
|
|
924
|
+
action,
|
|
925
|
+
schemas,
|
|
926
|
+
expanded: expandedAction === action.name,
|
|
927
|
+
onToggle: () => setExpandedAction(expandedAction === action.name ? null : action.name),
|
|
928
|
+
fieldValues: paramInputs[action.name] ?? {},
|
|
929
|
+
onFieldChange: (field, value) => setFieldValue(action.name, field, value),
|
|
930
|
+
onExecute: () => handleExecute(action),
|
|
931
|
+
isExecuting
|
|
932
|
+
},
|
|
933
|
+
action.name
|
|
934
|
+
)),
|
|
935
|
+
filtered.length === 0 && /* @__PURE__ */ jsx("div", { style: { padding: 20, color: "#475569", textAlign: "center" }, children: filter ? "No matching actions" : "No actions registered" })
|
|
936
|
+
] }) : /* @__PURE__ */ jsx("div", { style: { flex: 1, overflowY: "auto", minHeight: 0 }, children: log.length === 0 ? /* @__PURE__ */ jsx("div", { style: { padding: 20, color: "#475569", textAlign: "center" }, children: "No executions yet. Run an action to see results here." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
937
|
+
/* @__PURE__ */ jsx("div", { style: { padding: "8px 20px", display: "flex", justifyContent: "flex-end" }, children: /* @__PURE__ */ jsx("button", { onClick: () => setLog([]), style: clearBtnStyle, children: "Clear" }) }),
|
|
938
|
+
log.map((entry) => /* @__PURE__ */ jsxs("div", { style: { padding: "10px 20px", borderBottom: "1px solid #1e293b" }, children: [
|
|
939
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
|
|
940
|
+
/* @__PURE__ */ jsx(StatusDot, { result: entry.result }),
|
|
941
|
+
/* @__PURE__ */ jsx("span", { style: { fontWeight: 600, fontSize: 13 }, children: entry.action }),
|
|
942
|
+
/* @__PURE__ */ jsx("span", { style: { marginLeft: "auto", fontSize: 11, color: "#475569" }, children: new Date(entry.timestamp).toLocaleTimeString() })
|
|
943
|
+
] }),
|
|
944
|
+
entry.params && /* @__PURE__ */ jsx("pre", { style: logParamsStyle, children: JSON.stringify(entry.params, null, 2) }),
|
|
945
|
+
entry.result && !entry.result.success && /* @__PURE__ */ jsx("div", { style: { marginTop: 4, fontSize: 12, color: "#f87171" }, children: entry.result.error })
|
|
946
|
+
] }, entry.id)),
|
|
947
|
+
/* @__PURE__ */ jsx("div", { ref: logEndRef })
|
|
948
|
+
] }) })
|
|
949
|
+
]
|
|
950
|
+
}
|
|
951
|
+
)
|
|
952
|
+
] });
|
|
953
|
+
}
|
|
954
|
+
function ActionRow({
|
|
955
|
+
action,
|
|
956
|
+
schemas,
|
|
957
|
+
expanded,
|
|
958
|
+
onToggle,
|
|
959
|
+
fieldValues,
|
|
960
|
+
onFieldChange,
|
|
961
|
+
onExecute,
|
|
962
|
+
isExecuting,
|
|
963
|
+
badge
|
|
964
|
+
}) {
|
|
965
|
+
const fields = getSchemaFields(schemas, action.name);
|
|
966
|
+
return /* @__PURE__ */ jsxs(
|
|
967
|
+
"div",
|
|
968
|
+
{
|
|
969
|
+
style: {
|
|
970
|
+
padding: "10px 20px",
|
|
971
|
+
borderBottom: "1px solid #1e293b",
|
|
972
|
+
opacity: action.disabled ? 0.5 : 1,
|
|
973
|
+
cursor: "pointer"
|
|
974
|
+
},
|
|
975
|
+
onKeyDown: (e) => {
|
|
976
|
+
if (e.key === "Enter" && expanded && !action.disabled && !isExecuting) {
|
|
977
|
+
e.preventDefault();
|
|
978
|
+
onExecute();
|
|
979
|
+
}
|
|
980
|
+
},
|
|
981
|
+
children: [
|
|
982
|
+
/* @__PURE__ */ jsxs("div", { onClick: onToggle, style: { display: "flex", alignItems: "center", gap: 8 }, children: [
|
|
983
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#475569", fontSize: 10, flexShrink: 0 }, children: expanded ? "\u25BC" : "\u25B6" }),
|
|
984
|
+
/* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
985
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
|
|
986
|
+
/* @__PURE__ */ jsx("span", { style: { fontWeight: 600, color: "#93c5fd" }, children: action.name }),
|
|
987
|
+
badge && /* @__PURE__ */ jsx(
|
|
988
|
+
"span",
|
|
989
|
+
{
|
|
990
|
+
style: {
|
|
991
|
+
fontSize: 9,
|
|
992
|
+
padding: "1px 5px",
|
|
993
|
+
borderRadius: 3,
|
|
994
|
+
background: "#1e3a5f",
|
|
995
|
+
color: "#60a5fa",
|
|
996
|
+
fontWeight: 600
|
|
997
|
+
},
|
|
998
|
+
children: badge
|
|
999
|
+
}
|
|
1000
|
+
),
|
|
1001
|
+
action.disabled && /* @__PURE__ */ jsx(
|
|
1002
|
+
"span",
|
|
1003
|
+
{
|
|
1004
|
+
style: {
|
|
1005
|
+
fontSize: 9,
|
|
1006
|
+
padding: "1px 5px",
|
|
1007
|
+
borderRadius: 3,
|
|
1008
|
+
background: "#3f1d1d",
|
|
1009
|
+
color: "#f87171",
|
|
1010
|
+
fontWeight: 600
|
|
1011
|
+
},
|
|
1012
|
+
children: "disabled"
|
|
1013
|
+
}
|
|
1014
|
+
)
|
|
1015
|
+
] }),
|
|
1016
|
+
/* @__PURE__ */ jsx("div", { style: { color: "#94a3b8", fontSize: 12, marginTop: 2 }, children: action.disabled && action.disabledReason ? action.disabledReason : action.description })
|
|
1017
|
+
] }),
|
|
1018
|
+
/* @__PURE__ */ jsx(
|
|
1019
|
+
"button",
|
|
1020
|
+
{
|
|
1021
|
+
onClick: (e) => {
|
|
1022
|
+
e.stopPropagation();
|
|
1023
|
+
onExecute();
|
|
1024
|
+
},
|
|
1025
|
+
disabled: action.disabled || isExecuting,
|
|
1026
|
+
style: {
|
|
1027
|
+
padding: "5px 14px",
|
|
1028
|
+
borderRadius: 6,
|
|
1029
|
+
border: "none",
|
|
1030
|
+
fontSize: 12,
|
|
1031
|
+
fontWeight: 600,
|
|
1032
|
+
cursor: action.disabled ? "not-allowed" : "pointer",
|
|
1033
|
+
background: action.disabled ? "#334155" : "#3b82f6",
|
|
1034
|
+
color: "white",
|
|
1035
|
+
flexShrink: 0
|
|
1036
|
+
},
|
|
1037
|
+
children: isExecuting ? "\xB7\xB7\xB7" : "Run"
|
|
1038
|
+
}
|
|
1039
|
+
)
|
|
1040
|
+
] }),
|
|
1041
|
+
expanded && /* @__PURE__ */ jsx("div", { style: { marginTop: 10, marginLeft: 18 }, children: fields.length > 0 ? /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: fields.map((field) => /* @__PURE__ */ jsxs("div", { children: [
|
|
1042
|
+
/* @__PURE__ */ jsxs("label", { style: { fontSize: 11, color: "#64748b", display: "block", marginBottom: 3 }, children: [
|
|
1043
|
+
field.name,
|
|
1044
|
+
field.isRequired && /* @__PURE__ */ jsx("span", { style: { color: "#f87171", marginLeft: 2 }, children: "*" }),
|
|
1045
|
+
field.description && /* @__PURE__ */ jsx("span", { style: { color: "#475569", marginLeft: 6 }, children: field.description })
|
|
1046
|
+
] }),
|
|
1047
|
+
field.enumValues ? /* @__PURE__ */ jsxs(
|
|
1048
|
+
"select",
|
|
1049
|
+
{
|
|
1050
|
+
value: fieldValues[field.name] ?? "",
|
|
1051
|
+
onChange: (e) => onFieldChange(field.name, e.target.value),
|
|
1052
|
+
style: fieldSelectStyle,
|
|
1053
|
+
children: [
|
|
1054
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Select..." }),
|
|
1055
|
+
field.enumValues.map((v) => /* @__PURE__ */ jsx("option", { value: v, children: v }, v))
|
|
1056
|
+
]
|
|
1057
|
+
}
|
|
1058
|
+
) : field.type === "boolean" ? /* @__PURE__ */ jsxs(
|
|
1059
|
+
"select",
|
|
1060
|
+
{
|
|
1061
|
+
value: fieldValues[field.name] ?? "",
|
|
1062
|
+
onChange: (e) => onFieldChange(field.name, e.target.value),
|
|
1063
|
+
style: fieldSelectStyle,
|
|
1064
|
+
children: [
|
|
1065
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Select..." }),
|
|
1066
|
+
/* @__PURE__ */ jsx("option", { value: "true", children: "true" }),
|
|
1067
|
+
/* @__PURE__ */ jsx("option", { value: "false", children: "false" })
|
|
1068
|
+
]
|
|
1069
|
+
}
|
|
1070
|
+
) : field.type === "array" ? /* @__PURE__ */ jsx(
|
|
1071
|
+
"textarea",
|
|
1072
|
+
{
|
|
1073
|
+
value: fieldValues[field.name] ?? "",
|
|
1074
|
+
onChange: (e) => onFieldChange(field.name, e.target.value),
|
|
1075
|
+
placeholder: "[1, 2, 3]",
|
|
1076
|
+
rows: 2,
|
|
1077
|
+
style: textareaStyle
|
|
1078
|
+
}
|
|
1079
|
+
) : /* @__PURE__ */ jsx(
|
|
1080
|
+
"input",
|
|
1081
|
+
{
|
|
1082
|
+
type: field.type === "number" || field.type === "integer" ? "number" : "text",
|
|
1083
|
+
value: fieldValues[field.name] ?? "",
|
|
1084
|
+
onChange: (e) => onFieldChange(field.name, e.target.value),
|
|
1085
|
+
placeholder: field.description ?? field.name,
|
|
1086
|
+
style: fieldInputStyle
|
|
1087
|
+
}
|
|
1088
|
+
)
|
|
1089
|
+
] }, field.name)) }) : /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#475569" }, children: "No parameters" }) })
|
|
1090
|
+
]
|
|
1091
|
+
}
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
function StatusDot({ result }) {
|
|
1095
|
+
const color = !result ? "#fbbf24" : result.success ? "#4ade80" : "#f87171";
|
|
1096
|
+
return /* @__PURE__ */ jsx(
|
|
1097
|
+
"span",
|
|
1098
|
+
{
|
|
1099
|
+
style: {
|
|
1100
|
+
width: 8,
|
|
1101
|
+
height: 8,
|
|
1102
|
+
borderRadius: "50%",
|
|
1103
|
+
background: color,
|
|
1104
|
+
display: "inline-block",
|
|
1105
|
+
flexShrink: 0
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
var closeBtnStyle = {
|
|
1111
|
+
width: 28,
|
|
1112
|
+
height: 28,
|
|
1113
|
+
borderRadius: 6,
|
|
1114
|
+
border: "1px solid #334155",
|
|
1115
|
+
background: "transparent",
|
|
1116
|
+
color: "#94a3b8",
|
|
1117
|
+
cursor: "pointer",
|
|
1118
|
+
fontSize: 14,
|
|
1119
|
+
display: "flex",
|
|
1120
|
+
alignItems: "center",
|
|
1121
|
+
justifyContent: "center"
|
|
1122
|
+
};
|
|
1123
|
+
var tabStyle = (active) => ({
|
|
1124
|
+
flex: 1,
|
|
1125
|
+
padding: "10px 0",
|
|
1126
|
+
border: "none",
|
|
1127
|
+
cursor: "pointer",
|
|
1128
|
+
fontSize: 13,
|
|
1129
|
+
fontWeight: 600,
|
|
1130
|
+
fontFamily: "inherit",
|
|
1131
|
+
background: active ? "#1e293b" : "transparent",
|
|
1132
|
+
color: active ? "#e2e8f0" : "#64748b",
|
|
1133
|
+
borderBottom: active ? "2px solid #3b82f6" : "2px solid transparent"
|
|
1134
|
+
});
|
|
1135
|
+
var filterInputStyle = {
|
|
1136
|
+
width: "100%",
|
|
1137
|
+
background: "#1e293b",
|
|
1138
|
+
border: "1px solid #334155",
|
|
1139
|
+
borderRadius: 6,
|
|
1140
|
+
padding: "8px 12px",
|
|
1141
|
+
color: "#e2e8f0",
|
|
1142
|
+
fontSize: 13,
|
|
1143
|
+
outline: "none",
|
|
1144
|
+
fontFamily: "inherit"
|
|
1145
|
+
};
|
|
1146
|
+
var clearBtnStyle = {
|
|
1147
|
+
padding: "4px 10px",
|
|
1148
|
+
borderRadius: 4,
|
|
1149
|
+
border: "1px solid #334155",
|
|
1150
|
+
background: "transparent",
|
|
1151
|
+
color: "#64748b",
|
|
1152
|
+
cursor: "pointer",
|
|
1153
|
+
fontSize: 11,
|
|
1154
|
+
fontFamily: "inherit"
|
|
1155
|
+
};
|
|
1156
|
+
var fieldInputStyle = {
|
|
1157
|
+
width: "100%",
|
|
1158
|
+
background: "#1e293b",
|
|
1159
|
+
border: "1px solid #334155",
|
|
1160
|
+
borderRadius: 6,
|
|
1161
|
+
padding: "7px 10px",
|
|
1162
|
+
color: "#e2e8f0",
|
|
1163
|
+
fontSize: 12,
|
|
1164
|
+
outline: "none",
|
|
1165
|
+
fontFamily: "inherit"
|
|
1166
|
+
};
|
|
1167
|
+
var fieldSelectStyle = {
|
|
1168
|
+
width: "100%",
|
|
1169
|
+
background: "#1e293b",
|
|
1170
|
+
border: "1px solid #334155",
|
|
1171
|
+
borderRadius: 6,
|
|
1172
|
+
padding: "7px 10px",
|
|
1173
|
+
color: "#e2e8f0",
|
|
1174
|
+
fontSize: 12,
|
|
1175
|
+
outline: "none",
|
|
1176
|
+
fontFamily: "inherit",
|
|
1177
|
+
cursor: "pointer"
|
|
1178
|
+
};
|
|
1179
|
+
var textareaStyle = {
|
|
1180
|
+
width: "100%",
|
|
1181
|
+
background: "#1e293b",
|
|
1182
|
+
border: "1px solid #334155",
|
|
1183
|
+
borderRadius: 6,
|
|
1184
|
+
padding: "8px 10px",
|
|
1185
|
+
color: "#e2e8f0",
|
|
1186
|
+
fontSize: 12,
|
|
1187
|
+
outline: "none",
|
|
1188
|
+
resize: "vertical",
|
|
1189
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace"
|
|
1190
|
+
};
|
|
1191
|
+
var logParamsStyle = {
|
|
1192
|
+
marginTop: 4,
|
|
1193
|
+
padding: "6px 8px",
|
|
1194
|
+
background: "#1e293b",
|
|
1195
|
+
borderRadius: 4,
|
|
1196
|
+
fontSize: 11,
|
|
1197
|
+
color: "#94a3b8",
|
|
1198
|
+
overflow: "auto",
|
|
1199
|
+
maxHeight: 80,
|
|
1200
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace"
|
|
1201
|
+
};
|
|
1202
|
+
function useAgentAction(config) {
|
|
1203
|
+
const context = useContext(AgentActionContext);
|
|
1204
|
+
if (!context) {
|
|
1205
|
+
throw new Error("useAgentAction must be used within an AgentActionProvider");
|
|
1206
|
+
}
|
|
1207
|
+
const configRef = useRef(config);
|
|
1208
|
+
configRef.current = config;
|
|
1209
|
+
const { registerAction, unregisterAction } = context;
|
|
1210
|
+
useEffect(() => {
|
|
1211
|
+
const items = Array.isArray(configRef.current) ? configRef.current : [configRef.current];
|
|
1212
|
+
for (const item of items) {
|
|
1213
|
+
const onExecute = item.onExecute;
|
|
1214
|
+
const steps = item.steps;
|
|
1215
|
+
registerAction({
|
|
1216
|
+
name: item.name,
|
|
1217
|
+
description: item.description,
|
|
1218
|
+
parameters: item.parameters,
|
|
1219
|
+
onExecute: onExecute ? (params) => onExecute(params) : void 0,
|
|
1220
|
+
disabled: item.disabled ?? false,
|
|
1221
|
+
disabledReason: item.disabledReason,
|
|
1222
|
+
getExecutionTargets: () => {
|
|
1223
|
+
if (!steps?.length) return [];
|
|
1224
|
+
return steps.map((s) => ({
|
|
1225
|
+
label: s.label,
|
|
1226
|
+
element: null,
|
|
1227
|
+
fromParam: s.fromParam,
|
|
1228
|
+
fromTarget: s.fromTarget,
|
|
1229
|
+
setParam: s.setParam,
|
|
1230
|
+
setValue: s.setValue,
|
|
1231
|
+
onSetValue: s.onSetValue,
|
|
1232
|
+
prepareView: s.prepareView
|
|
1233
|
+
}));
|
|
1234
|
+
}
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
return () => {
|
|
1238
|
+
const items2 = Array.isArray(configRef.current) ? configRef.current : [configRef.current];
|
|
1239
|
+
for (const item of items2) {
|
|
1240
|
+
unregisterAction(item.name);
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
1243
|
+
}, [registerAction, unregisterAction]);
|
|
1244
|
+
}
|
|
1245
|
+
function useAgentCommandRouter(fallback, getActionName) {
|
|
1246
|
+
const { execute, availableActions } = useAgentActions();
|
|
1247
|
+
return useCallback(
|
|
1248
|
+
async (command) => {
|
|
1249
|
+
const actionName = getActionName(command);
|
|
1250
|
+
const isRegistered = availableActions.some((a) => a.name === actionName && !a.disabled);
|
|
1251
|
+
if (isRegistered) {
|
|
1252
|
+
await execute(actionName, command);
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
await fallback?.(command);
|
|
1256
|
+
},
|
|
1257
|
+
[execute, availableActions, fallback, getActionName]
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
export { AgentAction, AgentActionProvider, AgentDevTools, AgentStep, AgentTarget, generateToolSchemas, useAgentAction, useAgentActions, useAgentCommandRouter, zodToJsonSchema };
|
|
1262
|
+
//# sourceMappingURL=index.mjs.map
|
|
1263
|
+
//# sourceMappingURL=index.mjs.map
|