@asiyst/sdk 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/README.md +63 -0
- package/dist/index.cjs +2366 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +301 -0
- package/dist/index.d.ts +301 -0
- package/dist/index.js +2346 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2366 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/errors/index.ts
|
|
4
|
+
var AsiystError = class extends Error {
|
|
5
|
+
constructor(code, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "AsiystError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var ConfigurationError = class extends AsiystError {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super("configuration_error", message);
|
|
14
|
+
this.name = "ConfigurationError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var AuthenticationError = class extends AsiystError {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super("authentication_error", message);
|
|
20
|
+
this.name = "AuthenticationError";
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var InitializationError = class extends AsiystError {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super("initialization_error", message);
|
|
26
|
+
this.name = "InitializationError";
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var TargetNotFoundError = class extends AsiystError {
|
|
30
|
+
constructor(message) {
|
|
31
|
+
super("target_not_found", message);
|
|
32
|
+
this.name = "TargetNotFoundError";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var ActionNotAllowedError = class extends AsiystError {
|
|
36
|
+
constructor(message) {
|
|
37
|
+
super("action_not_allowed", message);
|
|
38
|
+
this.name = "ActionNotAllowedError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var NetworkError = class extends AsiystError {
|
|
42
|
+
constructor(message) {
|
|
43
|
+
super("network_error", message);
|
|
44
|
+
this.name = "NetworkError";
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var TaskExecutionError = class extends AsiystError {
|
|
48
|
+
constructor(message) {
|
|
49
|
+
super("task_execution_error", message);
|
|
50
|
+
this.name = "TaskExecutionError";
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// src/types/index.ts
|
|
55
|
+
var ALL_ACTION_KINDS = [
|
|
56
|
+
"navigate",
|
|
57
|
+
"click",
|
|
58
|
+
"highlight",
|
|
59
|
+
"scroll",
|
|
60
|
+
"type",
|
|
61
|
+
"select",
|
|
62
|
+
"open-menu",
|
|
63
|
+
"open-modal",
|
|
64
|
+
"search",
|
|
65
|
+
"wait",
|
|
66
|
+
"explain",
|
|
67
|
+
"complete"
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
// src/core/constants.ts
|
|
71
|
+
var SDK_VERSION = "0.1.0";
|
|
72
|
+
var DEFAULT_API_BASE_URL = "https://api.asiyst.com";
|
|
73
|
+
var CONFIG_SCHEMA_VERSION = 1;
|
|
74
|
+
var HOST_ELEMENT_ID = "asiyst-host";
|
|
75
|
+
var DATA_ATTR = "data-asiyst";
|
|
76
|
+
var DATA_ATTR_DESCRIPTION = "data-asiyst-description";
|
|
77
|
+
var STORAGE_PREFIX = "asiyst";
|
|
78
|
+
var ANALYTICS_FLUSH_INTERVAL_MS = 4e3;
|
|
79
|
+
var ANALYTICS_BATCH_SIZE = 12;
|
|
80
|
+
var DOM_SCAN_DEBOUNCE_MS = 350;
|
|
81
|
+
var VIEWPORT_HANDLER_THROTTLE_MS = 80;
|
|
82
|
+
var AVATAR_VIEWPORT_PADDING = 12;
|
|
83
|
+
var AVATAR_TARGET_GAP = 16;
|
|
84
|
+
var WEBSITE_MAP_TEXT_LIMIT = 80;
|
|
85
|
+
var CONFIG_CACHE_TTL_MS = 1e3 * 60 * 10;
|
|
86
|
+
|
|
87
|
+
// src/security/selectors.ts
|
|
88
|
+
var DANGEROUS_SELECTOR = /javascript:|expression\(|@import|url\s*\(|behavior:|binding:|<script/i;
|
|
89
|
+
var FORBIDDEN_SELECTOR_TOKENS = /[;{}]|\)\s*\[/;
|
|
90
|
+
function isSafeSelector(selector) {
|
|
91
|
+
const trimmed = selector.trim();
|
|
92
|
+
if (!trimmed || trimmed.length > 240) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (DANGEROUS_SELECTOR.test(trimmed) || FORBIDDEN_SELECTOR_TOKENS.test(trimmed)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
function querySafeSelector(root, selector) {
|
|
101
|
+
if (!isSafeSelector(selector)) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
return root.querySelector(selector);
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/security/sanitize.ts
|
|
112
|
+
var CONTROL_CHARS = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g;
|
|
113
|
+
function sanitizeText(input, maxLength = 2e3) {
|
|
114
|
+
return input.replace(CONTROL_CHARS, "").replace(/\s+/g, " ").trim().slice(0, maxLength);
|
|
115
|
+
}
|
|
116
|
+
function renderSafeText(node, text) {
|
|
117
|
+
node.textContent = sanitizeText(text);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/config/schema.ts
|
|
121
|
+
var ANCHORS = [
|
|
122
|
+
"bottom-right",
|
|
123
|
+
"bottom-left",
|
|
124
|
+
"top-right",
|
|
125
|
+
"top-left"
|
|
126
|
+
];
|
|
127
|
+
function isRecord(value) {
|
|
128
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
129
|
+
}
|
|
130
|
+
function asString(value, fallback, max = 80) {
|
|
131
|
+
if (typeof value !== "string") {
|
|
132
|
+
return fallback;
|
|
133
|
+
}
|
|
134
|
+
return sanitizeText(value, max) || fallback;
|
|
135
|
+
}
|
|
136
|
+
function asNumber(value, fallback, min, max) {
|
|
137
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
138
|
+
return fallback;
|
|
139
|
+
}
|
|
140
|
+
return Math.min(max, Math.max(min, value));
|
|
141
|
+
}
|
|
142
|
+
function parseAllowedActions(value, fallback) {
|
|
143
|
+
if (!Array.isArray(value)) {
|
|
144
|
+
return [...fallback];
|
|
145
|
+
}
|
|
146
|
+
const allowed = new Set(ALL_ACTION_KINDS);
|
|
147
|
+
const next = [];
|
|
148
|
+
for (const item of value) {
|
|
149
|
+
if (typeof item === "string" && allowed.has(item)) {
|
|
150
|
+
next.push(item);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return next.length > 0 ? next : [...fallback];
|
|
154
|
+
}
|
|
155
|
+
function parseTheme(value) {
|
|
156
|
+
if (!isRecord(value)) {
|
|
157
|
+
return {};
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
accent: typeof value.accent === "string" ? sanitizeText(value.accent, 32) : void 0,
|
|
161
|
+
background: typeof value.background === "string" ? sanitizeText(value.background, 32) : void 0,
|
|
162
|
+
text: typeof value.text === "string" ? sanitizeText(value.text, 32) : void 0
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function parsePersonality(value) {
|
|
166
|
+
if (!isRecord(value)) {
|
|
167
|
+
return {};
|
|
168
|
+
}
|
|
169
|
+
const result = {};
|
|
170
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
171
|
+
if (typeof entry === "string" && key.length < 40) {
|
|
172
|
+
result[sanitizeText(key, 40)] = sanitizeText(entry, 120);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
function parseBehavior(value) {
|
|
178
|
+
if (!isRecord(value)) {
|
|
179
|
+
return {};
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
walkingSpeed: asNumber(value.walkingSpeed, 1, 0.2, 4),
|
|
183
|
+
idleAnimation: typeof value.idleAnimation === "string" ? sanitizeText(value.idleAnimation, 40) : void 0,
|
|
184
|
+
pointingAnimation: typeof value.pointingAnimation === "string" ? sanitizeText(value.pointingAnimation, 40) : void 0,
|
|
185
|
+
greetingAnimation: typeof value.greetingAnimation === "string" ? sanitizeText(value.greetingAnimation, 40) : void 0,
|
|
186
|
+
thinkingAnimation: typeof value.thinkingAnimation === "string" ? sanitizeText(value.thinkingAnimation, 40) : void 0,
|
|
187
|
+
successAnimation: typeof value.successAnimation === "string" ? sanitizeText(value.successAnimation, 40) : void 0,
|
|
188
|
+
voiceSpeed: asNumber(value.voiceSpeed, 1, 0.5, 2),
|
|
189
|
+
speechBubble: typeof value.speechBubble === "boolean" ? value.speechBubble : true
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function parseElementSelectors(value) {
|
|
193
|
+
if (!isRecord(value)) {
|
|
194
|
+
return {};
|
|
195
|
+
}
|
|
196
|
+
const result = {};
|
|
197
|
+
for (const [id, selector] of Object.entries(value)) {
|
|
198
|
+
if (typeof selector === "string" && isSafeSelector(selector)) {
|
|
199
|
+
result[sanitizeText(id, 64)] = selector.trim();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
function fallbackConfig() {
|
|
205
|
+
return {
|
|
206
|
+
schemaVersion: CONFIG_SCHEMA_VERSION,
|
|
207
|
+
version: 0,
|
|
208
|
+
avatarName: "Asiyst",
|
|
209
|
+
avatar: "avatar_default",
|
|
210
|
+
size: 96,
|
|
211
|
+
position: "bottom-right",
|
|
212
|
+
animation: "friendly",
|
|
213
|
+
theme: {
|
|
214
|
+
accent: "#2563eb",
|
|
215
|
+
background: "#0f172a",
|
|
216
|
+
text: "#f8fafc"
|
|
217
|
+
},
|
|
218
|
+
personality: {},
|
|
219
|
+
behavior: {
|
|
220
|
+
walkingSpeed: 1,
|
|
221
|
+
speechBubble: true
|
|
222
|
+
},
|
|
223
|
+
mode: "guided",
|
|
224
|
+
allowedActions: ["navigate", "highlight", "scroll", "wait", "explain", "complete"],
|
|
225
|
+
elementSelectors: {}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function normalizeProjectConfig(raw) {
|
|
229
|
+
const base = fallbackConfig();
|
|
230
|
+
if (!isRecord(raw)) {
|
|
231
|
+
return base;
|
|
232
|
+
}
|
|
233
|
+
const position = ANCHORS.includes(raw.position) ? raw.position : base.position;
|
|
234
|
+
return {
|
|
235
|
+
schemaVersion: CONFIG_SCHEMA_VERSION,
|
|
236
|
+
version: asNumber(raw.version, base.version, 0, Number.MAX_SAFE_INTEGER),
|
|
237
|
+
avatarName: asString(raw.avatarName, base.avatarName, 40),
|
|
238
|
+
avatar: asString(raw.avatar, base.avatar, 64),
|
|
239
|
+
size: asNumber(raw.size, base.size, 48, 220),
|
|
240
|
+
position,
|
|
241
|
+
voice: typeof raw.voice === "string" ? sanitizeText(raw.voice, 64) : void 0,
|
|
242
|
+
animation: asString(raw.animation, base.animation ?? "friendly", 40),
|
|
243
|
+
theme: { ...base.theme, ...parseTheme(raw.theme) },
|
|
244
|
+
personality: parsePersonality(raw.personality),
|
|
245
|
+
behavior: { ...base.behavior, ...parseBehavior(raw.behavior) },
|
|
246
|
+
mode: raw.mode === "assist" ? "assist" : "guided",
|
|
247
|
+
allowedActions: parseAllowedActions(raw.allowedActions, base.allowedActions),
|
|
248
|
+
elementSelectors: parseElementSelectors(raw.elementSelectors)
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function validateInitOptions(options) {
|
|
252
|
+
if (typeof options.projectId !== "string" || !options.projectId.trim()) {
|
|
253
|
+
throw new ConfigurationError("projectId is required");
|
|
254
|
+
}
|
|
255
|
+
if (typeof options.publicKey !== "string" || !options.publicKey.trim()) {
|
|
256
|
+
throw new ConfigurationError("publicKey is required");
|
|
257
|
+
}
|
|
258
|
+
if (options.projectId.length > 128 || options.publicKey.length > 256) {
|
|
259
|
+
throw new ConfigurationError("project credentials exceed allowed length");
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
projectId: options.projectId.trim(),
|
|
263
|
+
publicKey: options.publicKey.trim()
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/events/EventBus.ts
|
|
268
|
+
var EventBus = class {
|
|
269
|
+
constructor() {
|
|
270
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
271
|
+
}
|
|
272
|
+
on(event, handler) {
|
|
273
|
+
const set = this.listeners.get(event) ?? /* @__PURE__ */ new Set();
|
|
274
|
+
set.add(handler);
|
|
275
|
+
this.listeners.set(event, set);
|
|
276
|
+
return () => this.off(event, handler);
|
|
277
|
+
}
|
|
278
|
+
off(event, handler) {
|
|
279
|
+
this.listeners.get(event)?.delete(handler);
|
|
280
|
+
}
|
|
281
|
+
emit(event, payload) {
|
|
282
|
+
const handlers = this.listeners.get(event);
|
|
283
|
+
if (!handlers) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
for (const handler of [...handlers]) {
|
|
287
|
+
try {
|
|
288
|
+
handler(payload);
|
|
289
|
+
} catch {
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
removeAll() {
|
|
294
|
+
this.listeners.clear();
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// src/communication/HttpTransport.ts
|
|
299
|
+
var HttpTransport = class {
|
|
300
|
+
constructor(options) {
|
|
301
|
+
this.options = options;
|
|
302
|
+
}
|
|
303
|
+
async request(req) {
|
|
304
|
+
const url = `${this.options.apiBaseUrl.replace(/\/$/, "")}${req.path}`;
|
|
305
|
+
let response;
|
|
306
|
+
try {
|
|
307
|
+
response = await fetch(url, {
|
|
308
|
+
method: req.method,
|
|
309
|
+
headers: {
|
|
310
|
+
Accept: "application/json",
|
|
311
|
+
"Content-Type": "application/json",
|
|
312
|
+
"X-Asiyst-Project-Id": this.options.projectId,
|
|
313
|
+
"X-Asiyst-Public-Key": this.options.publicKey,
|
|
314
|
+
"X-Asiyst-SDK-Version": SDK_VERSION
|
|
315
|
+
},
|
|
316
|
+
body: req.body === void 0 ? void 0 : JSON.stringify(req.body),
|
|
317
|
+
signal: req.signal
|
|
318
|
+
});
|
|
319
|
+
} catch {
|
|
320
|
+
throw new NetworkError("Asiyst Cloud is unreachable");
|
|
321
|
+
}
|
|
322
|
+
let data = null;
|
|
323
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
324
|
+
if (contentType.includes("application/json")) {
|
|
325
|
+
try {
|
|
326
|
+
data = await response.json();
|
|
327
|
+
} catch {
|
|
328
|
+
data = null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (!response.ok) {
|
|
332
|
+
return { ok: false, status: response.status, data };
|
|
333
|
+
}
|
|
334
|
+
return { ok: true, status: response.status, data };
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// src/communication/CloudClient.ts
|
|
339
|
+
var CloudClient = class {
|
|
340
|
+
constructor(transport, projectId) {
|
|
341
|
+
this.transport = transport;
|
|
342
|
+
this.projectId = projectId;
|
|
343
|
+
}
|
|
344
|
+
async fetchConfig() {
|
|
345
|
+
const response = await this.transport.request({
|
|
346
|
+
path: `/v1/projects/${encodeURIComponent(this.projectId)}/config`,
|
|
347
|
+
method: "GET"
|
|
348
|
+
});
|
|
349
|
+
if (response.status === 401 || response.status === 403) {
|
|
350
|
+
throw new AuthenticationError("Project credentials were rejected");
|
|
351
|
+
}
|
|
352
|
+
if (!response.ok || !response.data) {
|
|
353
|
+
throw new NetworkError("Project configuration could not be loaded");
|
|
354
|
+
}
|
|
355
|
+
return normalizeProjectConfig(response.data.config ?? response.data);
|
|
356
|
+
}
|
|
357
|
+
async requestTask(userText, pageUrl) {
|
|
358
|
+
const response = await this.transport.request({
|
|
359
|
+
path: "/v1/tasks",
|
|
360
|
+
method: "POST",
|
|
361
|
+
body: {
|
|
362
|
+
projectId: this.projectId,
|
|
363
|
+
text: userText,
|
|
364
|
+
pageUrl
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
if (!response.ok || !response.data?.task?.id || !Array.isArray(response.data.task.steps)) {
|
|
368
|
+
throw new NetworkError("Task planning is unavailable");
|
|
369
|
+
}
|
|
370
|
+
return response.data.task;
|
|
371
|
+
}
|
|
372
|
+
async fetchWorkflow(workflowId) {
|
|
373
|
+
const response = await this.transport.request({
|
|
374
|
+
path: `/v1/workflows/${encodeURIComponent(workflowId)}`,
|
|
375
|
+
method: "GET"
|
|
376
|
+
});
|
|
377
|
+
if (!response.ok || !response.data?.workflow?.id || !Array.isArray(response.data.workflow.steps)) {
|
|
378
|
+
throw new NetworkError("Workflow is unavailable");
|
|
379
|
+
}
|
|
380
|
+
return response.data.workflow;
|
|
381
|
+
}
|
|
382
|
+
async sendConversationMessage(text, pageUrl) {
|
|
383
|
+
const response = await this.transport.request({
|
|
384
|
+
path: "/v1/conversations/messages",
|
|
385
|
+
method: "POST",
|
|
386
|
+
body: { projectId: this.projectId, text, pageUrl }
|
|
387
|
+
});
|
|
388
|
+
if (!response.ok || !response.data?.message?.text) {
|
|
389
|
+
throw new NetworkError("Conversation service is unavailable");
|
|
390
|
+
}
|
|
391
|
+
return response.data;
|
|
392
|
+
}
|
|
393
|
+
async sendWebsiteMap(snapshot) {
|
|
394
|
+
await this.safePost("/v1/website-maps", snapshot);
|
|
395
|
+
}
|
|
396
|
+
async sendAnalytics(events) {
|
|
397
|
+
await this.safePost("/v1/analytics", { events });
|
|
398
|
+
}
|
|
399
|
+
async sendTaskUpdate(taskId, status, stepId) {
|
|
400
|
+
await this.safePost(`/v1/tasks/${encodeURIComponent(taskId)}/events`, {
|
|
401
|
+
status,
|
|
402
|
+
stepId
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
async safePost(path, body) {
|
|
406
|
+
try {
|
|
407
|
+
await this.transport.request({ path, method: "POST", body });
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
// src/storage/NamespacedStorage.ts
|
|
414
|
+
var NamespacedStorage = class {
|
|
415
|
+
constructor(projectId, store) {
|
|
416
|
+
this.projectId = projectId;
|
|
417
|
+
this.store = store;
|
|
418
|
+
}
|
|
419
|
+
key(suffix) {
|
|
420
|
+
return `${STORAGE_PREFIX}:${this.projectId}:${suffix}`;
|
|
421
|
+
}
|
|
422
|
+
read(suffix) {
|
|
423
|
+
if (!this.store) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
427
|
+
return this.store.getItem(this.key(suffix));
|
|
428
|
+
} catch {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
write(suffix, value) {
|
|
433
|
+
if (!this.store) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
try {
|
|
437
|
+
this.store.setItem(this.key(suffix), value);
|
|
438
|
+
} catch {
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
remove(suffix) {
|
|
442
|
+
if (!this.store) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
try {
|
|
446
|
+
this.store.removeItem(this.key(suffix));
|
|
447
|
+
} catch {
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
function browserLocalStorage() {
|
|
452
|
+
try {
|
|
453
|
+
return typeof localStorage === "undefined" ? null : localStorage;
|
|
454
|
+
} catch {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/config/ConfigCache.ts
|
|
460
|
+
var ConfigCache = class {
|
|
461
|
+
constructor(storage) {
|
|
462
|
+
this.storage = storage;
|
|
463
|
+
}
|
|
464
|
+
read(projectId) {
|
|
465
|
+
const raw = this.storage.read("config");
|
|
466
|
+
if (!raw) {
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
try {
|
|
470
|
+
const parsed = JSON.parse(raw);
|
|
471
|
+
if (parsed.schemaVersion !== CONFIG_SCHEMA_VERSION || parsed.projectId !== projectId) {
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
if (Date.now() - parsed.fetchedAt > CONFIG_CACHE_TTL_MS) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
return normalizeProjectConfig(parsed.config);
|
|
478
|
+
} catch {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
write(projectId, config) {
|
|
483
|
+
const envelope = {
|
|
484
|
+
schemaVersion: CONFIG_SCHEMA_VERSION,
|
|
485
|
+
projectId,
|
|
486
|
+
fetchedAt: Date.now(),
|
|
487
|
+
config
|
|
488
|
+
};
|
|
489
|
+
this.storage.write("config", JSON.stringify(envelope));
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// src/config/ConfigManager.ts
|
|
494
|
+
var ConfigManager = class {
|
|
495
|
+
constructor(options, cloud, events) {
|
|
496
|
+
this.options = options;
|
|
497
|
+
this.cloud = cloud;
|
|
498
|
+
this.events = events;
|
|
499
|
+
const credentials = validateInitOptions(options);
|
|
500
|
+
this.options = { ...options, ...credentials };
|
|
501
|
+
this.cache = new ConfigCache(
|
|
502
|
+
new NamespacedStorage(credentials.projectId, browserLocalStorage())
|
|
503
|
+
);
|
|
504
|
+
const cached = this.cache.read(credentials.projectId);
|
|
505
|
+
this.current = cached ?? this.localFallback();
|
|
506
|
+
}
|
|
507
|
+
get() {
|
|
508
|
+
return this.current;
|
|
509
|
+
}
|
|
510
|
+
async refresh() {
|
|
511
|
+
try {
|
|
512
|
+
const remote = await this.cloud.fetchConfig();
|
|
513
|
+
this.current = normalizeProjectConfig(remote);
|
|
514
|
+
this.cache.write(this.options.projectId, this.current);
|
|
515
|
+
return this.current;
|
|
516
|
+
} catch (error) {
|
|
517
|
+
this.events.emit("asiyst:error", {
|
|
518
|
+
code: "config_refresh_failed",
|
|
519
|
+
message: error instanceof Error ? error.message : "Failed to refresh configuration"
|
|
520
|
+
});
|
|
521
|
+
return this.current;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
localFallback() {
|
|
525
|
+
const base = fallbackConfig();
|
|
526
|
+
return {
|
|
527
|
+
...base,
|
|
528
|
+
mode: this.options.mode ?? base.mode,
|
|
529
|
+
allowedActions: this.options.allowedActions ?? base.allowedActions
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// src/analytics/Analytics.ts
|
|
535
|
+
var Analytics = class {
|
|
536
|
+
constructor(projectId, cloud) {
|
|
537
|
+
this.projectId = projectId;
|
|
538
|
+
this.cloud = cloud;
|
|
539
|
+
this.queue = [];
|
|
540
|
+
this.destroyed = false;
|
|
541
|
+
}
|
|
542
|
+
start() {
|
|
543
|
+
if (this.timer) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
this.timer = setInterval(() => {
|
|
547
|
+
void this.flush();
|
|
548
|
+
}, ANALYTICS_FLUSH_INTERVAL_MS);
|
|
549
|
+
}
|
|
550
|
+
track(name, properties) {
|
|
551
|
+
if (this.destroyed) {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
this.queue.push({
|
|
555
|
+
name,
|
|
556
|
+
at: Date.now(),
|
|
557
|
+
projectId: this.projectId,
|
|
558
|
+
properties
|
|
559
|
+
});
|
|
560
|
+
if (this.queue.length >= ANALYTICS_BATCH_SIZE) {
|
|
561
|
+
void this.flush();
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
async flush() {
|
|
565
|
+
if (this.queue.length === 0) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
const batch = this.queue.slice(0, ANALYTICS_BATCH_SIZE);
|
|
569
|
+
this.queue = this.queue.slice(batch.length);
|
|
570
|
+
try {
|
|
571
|
+
await this.cloud.sendAnalytics(batch);
|
|
572
|
+
} catch {
|
|
573
|
+
this.queue = batch.concat(this.queue).slice(0, 100);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
async destroy() {
|
|
577
|
+
this.destroyed = true;
|
|
578
|
+
if (this.timer) {
|
|
579
|
+
clearInterval(this.timer);
|
|
580
|
+
this.timer = void 0;
|
|
581
|
+
}
|
|
582
|
+
await this.flush();
|
|
583
|
+
}
|
|
584
|
+
pendingCount() {
|
|
585
|
+
return this.queue.length;
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
// src/core/HostRoot.ts
|
|
590
|
+
var HostRoot = class {
|
|
591
|
+
constructor(doc) {
|
|
592
|
+
this.host = doc.createElement("div");
|
|
593
|
+
this.host.id = HOST_ELEMENT_ID;
|
|
594
|
+
this.host.setAttribute("data-asiyst-root", "true");
|
|
595
|
+
this.host.style.all = "initial";
|
|
596
|
+
this.host.style.position = "relative";
|
|
597
|
+
this.host.style.zIndex = "2147483646";
|
|
598
|
+
this.shadow = this.host.attachShadow({ mode: "open" });
|
|
599
|
+
this.overlay = doc.createElement("div");
|
|
600
|
+
this.overlay.setAttribute("data-asiyst-layer", "overlay");
|
|
601
|
+
this.chrome = doc.createElement("div");
|
|
602
|
+
this.chrome.setAttribute("data-asiyst-layer", "chrome");
|
|
603
|
+
this.shadow.append(this.overlay, this.chrome);
|
|
604
|
+
doc.body.appendChild(this.host);
|
|
605
|
+
}
|
|
606
|
+
destroy() {
|
|
607
|
+
this.host.remove();
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
// src/dom/semantics.ts
|
|
612
|
+
var SEARCH_HINT = /search|query|find/i;
|
|
613
|
+
function classifyElement(el) {
|
|
614
|
+
const tag = el.tagName.toLowerCase();
|
|
615
|
+
const role = el.getAttribute("role");
|
|
616
|
+
const type = (el.getAttribute("type") ?? "").toLowerCase();
|
|
617
|
+
const asiystId = el.getAttribute("data-asiyst") ?? "";
|
|
618
|
+
if (asiystId === "search" || SEARCH_HINT.test(asiystId) || type === "search") {
|
|
619
|
+
return "search";
|
|
620
|
+
}
|
|
621
|
+
if (tag === "dialog" || role === "dialog" || role === "alertdialog") {
|
|
622
|
+
return "dialog";
|
|
623
|
+
}
|
|
624
|
+
if (tag === "nav" || role === "navigation") {
|
|
625
|
+
return "navigation";
|
|
626
|
+
}
|
|
627
|
+
if (tag === "form") {
|
|
628
|
+
return "form";
|
|
629
|
+
}
|
|
630
|
+
if (tag === "select") {
|
|
631
|
+
return "select";
|
|
632
|
+
}
|
|
633
|
+
if (tag === "textarea") {
|
|
634
|
+
return "textarea";
|
|
635
|
+
}
|
|
636
|
+
if (tag === "input") {
|
|
637
|
+
return "input";
|
|
638
|
+
}
|
|
639
|
+
if (tag === "a") {
|
|
640
|
+
return "link";
|
|
641
|
+
}
|
|
642
|
+
if (tag === "button" || role === "button" || type === "button" || type === "submit") {
|
|
643
|
+
return "button";
|
|
644
|
+
}
|
|
645
|
+
if (role === "tab") {
|
|
646
|
+
return "tab";
|
|
647
|
+
}
|
|
648
|
+
if (role === "menu" || role === "menubar") {
|
|
649
|
+
return "menu";
|
|
650
|
+
}
|
|
651
|
+
if (/^h[1-6]$/.test(tag)) {
|
|
652
|
+
return "heading";
|
|
653
|
+
}
|
|
654
|
+
if (el.getAttribute("data-asiyst-kind") === "card" || /\bcard\b/i.test(el.className)) {
|
|
655
|
+
return "card";
|
|
656
|
+
}
|
|
657
|
+
if (tag === "header" || tag === "main" || tag === "footer" || tag === "section" || tag === "aside") {
|
|
658
|
+
return "section";
|
|
659
|
+
}
|
|
660
|
+
return "other";
|
|
661
|
+
}
|
|
662
|
+
function accessibleLabel(el) {
|
|
663
|
+
const labelledBy = el.getAttribute("aria-labelledby");
|
|
664
|
+
if (labelledBy && el.ownerDocument) {
|
|
665
|
+
const labels = labelledBy.split(/\s+/).map((id) => el.ownerDocument.getElementById(id)?.textContent?.trim()).filter((text) => Boolean(text));
|
|
666
|
+
if (labels.length > 0) {
|
|
667
|
+
return labels.join(" ");
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return (el.getAttribute("aria-label") || el.getAttribute("title") || el.placeholder || "").trim();
|
|
671
|
+
}
|
|
672
|
+
function visibleText(el, max = 240) {
|
|
673
|
+
const text = (el.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
674
|
+
return text.slice(0, max);
|
|
675
|
+
}
|
|
676
|
+
function isDisabled(el) {
|
|
677
|
+
return el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true" || el instanceof HTMLInputElement && el.disabled;
|
|
678
|
+
}
|
|
679
|
+
function isSkippable(el) {
|
|
680
|
+
const tag = el.tagName.toLowerCase();
|
|
681
|
+
if (tag === "script" || tag === "style" || tag === "noscript" || tag === "link" || tag === "meta") {
|
|
682
|
+
return true;
|
|
683
|
+
}
|
|
684
|
+
if (el.id === "asiyst-host" || el.closest("#asiyst-host")) {
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// src/dom/visibility.ts
|
|
691
|
+
function isElementVisible(el) {
|
|
692
|
+
if (!(el instanceof HTMLElement)) {
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
if (el.hidden || el.getAttribute("aria-hidden") === "true") {
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
const style = el.ownerDocument.defaultView?.getComputedStyle(el);
|
|
699
|
+
if (!style) {
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
const rect = el.getBoundingClientRect();
|
|
706
|
+
return rect.width > 0 && rect.height > 0;
|
|
707
|
+
}
|
|
708
|
+
function toViewportRect(el) {
|
|
709
|
+
const rect = el.getBoundingClientRect();
|
|
710
|
+
return { x: rect.left, y: rect.top, width: rect.width, height: rect.height };
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// src/dom/inspect.ts
|
|
714
|
+
var CANDIDATE_SELECTOR = [
|
|
715
|
+
`[${DATA_ATTR}]`,
|
|
716
|
+
"a[href]",
|
|
717
|
+
"button",
|
|
718
|
+
"input",
|
|
719
|
+
"select",
|
|
720
|
+
"textarea",
|
|
721
|
+
"form",
|
|
722
|
+
"nav",
|
|
723
|
+
"h1",
|
|
724
|
+
"h2",
|
|
725
|
+
"h3",
|
|
726
|
+
"[role='button']",
|
|
727
|
+
"[role='link']",
|
|
728
|
+
"[role='navigation']",
|
|
729
|
+
"[role='dialog']",
|
|
730
|
+
"[role='tab']",
|
|
731
|
+
"[role='menu']",
|
|
732
|
+
"[role='search']"
|
|
733
|
+
].join(",");
|
|
734
|
+
function sectionName(el) {
|
|
735
|
+
const section = el.closest("header, nav, main, footer, aside, section, [data-asiyst-section]");
|
|
736
|
+
if (!section) {
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
return section.getAttribute("data-asiyst-section") || section.getAttribute("aria-label") || section.tagName.toLowerCase();
|
|
740
|
+
}
|
|
741
|
+
function elementId(el, index) {
|
|
742
|
+
const explicit = el.getAttribute(DATA_ATTR);
|
|
743
|
+
if (explicit) {
|
|
744
|
+
return explicit;
|
|
745
|
+
}
|
|
746
|
+
const attrId = el.getAttribute("id");
|
|
747
|
+
if (attrId) {
|
|
748
|
+
return `dom:${attrId}`;
|
|
749
|
+
}
|
|
750
|
+
return `auto:${el.tagName.toLowerCase()}:${index}`;
|
|
751
|
+
}
|
|
752
|
+
function inspectDocument(doc) {
|
|
753
|
+
const pageUrl = doc.location?.href ?? "";
|
|
754
|
+
const nodes = Array.from(doc.querySelectorAll(CANDIDATE_SELECTOR));
|
|
755
|
+
const results = [];
|
|
756
|
+
const seen = /* @__PURE__ */ new Set();
|
|
757
|
+
nodes.forEach((el, index) => {
|
|
758
|
+
if (seen.has(el) || isSkippable(el) || el.closest(`#${HOST_ELEMENT_ID}`)) {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
seen.add(el);
|
|
762
|
+
const kind = classifyElement(el);
|
|
763
|
+
const developerDefined = el.hasAttribute(DATA_ATTR);
|
|
764
|
+
if (kind === "other" && !developerDefined) {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
const text = visibleText(el, WEBSITE_MAP_TEXT_LIMIT);
|
|
768
|
+
const label = accessibleLabel(el).slice(0, WEBSITE_MAP_TEXT_LIMIT);
|
|
769
|
+
results.push({
|
|
770
|
+
id: elementId(el, index),
|
|
771
|
+
kind,
|
|
772
|
+
tagName: el.tagName.toLowerCase(),
|
|
773
|
+
role: el.getAttribute("role"),
|
|
774
|
+
text,
|
|
775
|
+
label,
|
|
776
|
+
href: el instanceof HTMLAnchorElement ? el.getAttribute("href") : null,
|
|
777
|
+
pageUrl,
|
|
778
|
+
rect: toViewportRect(el),
|
|
779
|
+
visible: isElementVisible(el),
|
|
780
|
+
enabled: !isDisabled(el),
|
|
781
|
+
developerDefined,
|
|
782
|
+
description: el.getAttribute(DATA_ATTR_DESCRIPTION),
|
|
783
|
+
section: sectionName(el)
|
|
784
|
+
});
|
|
785
|
+
});
|
|
786
|
+
return results;
|
|
787
|
+
}
|
|
788
|
+
function findElementByAsiystId(doc, id) {
|
|
789
|
+
return doc.querySelector(`[${DATA_ATTR}="${cssEscape(id)}"]`);
|
|
790
|
+
}
|
|
791
|
+
function cssEscape(value) {
|
|
792
|
+
if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
|
|
793
|
+
return CSS.escape(value);
|
|
794
|
+
}
|
|
795
|
+
return value.replace(/"/g, '\\"');
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// src/utils/timing.ts
|
|
799
|
+
function debounce(fn, waitMs) {
|
|
800
|
+
let timer;
|
|
801
|
+
const wrapped = ((...args) => {
|
|
802
|
+
if (timer) {
|
|
803
|
+
clearTimeout(timer);
|
|
804
|
+
}
|
|
805
|
+
timer = setTimeout(() => {
|
|
806
|
+
fn(...args);
|
|
807
|
+
}, waitMs);
|
|
808
|
+
});
|
|
809
|
+
wrapped.cancel = () => {
|
|
810
|
+
if (timer) {
|
|
811
|
+
clearTimeout(timer);
|
|
812
|
+
timer = void 0;
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
return wrapped;
|
|
816
|
+
}
|
|
817
|
+
function throttle(fn, waitMs) {
|
|
818
|
+
let last = 0;
|
|
819
|
+
let timer;
|
|
820
|
+
let pending;
|
|
821
|
+
const invoke = (args) => {
|
|
822
|
+
last = Date.now();
|
|
823
|
+
fn(...args);
|
|
824
|
+
};
|
|
825
|
+
const wrapped = ((...args) => {
|
|
826
|
+
const now = Date.now();
|
|
827
|
+
const remaining = waitMs - (now - last);
|
|
828
|
+
pending = args;
|
|
829
|
+
if (remaining <= 0) {
|
|
830
|
+
if (timer) {
|
|
831
|
+
clearTimeout(timer);
|
|
832
|
+
timer = void 0;
|
|
833
|
+
}
|
|
834
|
+
invoke(args);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
if (!timer) {
|
|
838
|
+
timer = setTimeout(() => {
|
|
839
|
+
timer = void 0;
|
|
840
|
+
if (pending) {
|
|
841
|
+
invoke(pending);
|
|
842
|
+
}
|
|
843
|
+
}, remaining);
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
wrapped.cancel = () => {
|
|
847
|
+
if (timer) {
|
|
848
|
+
clearTimeout(timer);
|
|
849
|
+
timer = void 0;
|
|
850
|
+
}
|
|
851
|
+
pending = void 0;
|
|
852
|
+
};
|
|
853
|
+
return wrapped;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// src/dom/DomObserver.ts
|
|
857
|
+
var DomObserver = class {
|
|
858
|
+
constructor(onChange) {
|
|
859
|
+
this.notify = debounce(onChange, DOM_SCAN_DEBOUNCE_MS);
|
|
860
|
+
}
|
|
861
|
+
start(root) {
|
|
862
|
+
this.stop();
|
|
863
|
+
this.observer = new MutationObserver(() => this.notify());
|
|
864
|
+
this.observer.observe(root, {
|
|
865
|
+
subtree: true,
|
|
866
|
+
childList: true,
|
|
867
|
+
attributes: true,
|
|
868
|
+
attributeFilter: ["class", "style", "hidden", "aria-hidden", "data-asiyst", "disabled"]
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
stop() {
|
|
872
|
+
this.observer?.disconnect();
|
|
873
|
+
this.observer = void 0;
|
|
874
|
+
this.notify.cancel();
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
// src/dom/TargetResolver.ts
|
|
879
|
+
function asRef(input) {
|
|
880
|
+
if (typeof input === "string") {
|
|
881
|
+
return { id: input };
|
|
882
|
+
}
|
|
883
|
+
return input;
|
|
884
|
+
}
|
|
885
|
+
function normalize(text) {
|
|
886
|
+
return text.toLowerCase().replace(/\s+/g, " ").trim();
|
|
887
|
+
}
|
|
888
|
+
var TargetResolver = class {
|
|
889
|
+
constructor(doc, configuredSelectors, mappedElements) {
|
|
890
|
+
this.doc = doc;
|
|
891
|
+
this.configuredSelectors = configuredSelectors;
|
|
892
|
+
this.mappedElements = mappedElements;
|
|
893
|
+
}
|
|
894
|
+
resolve(input, source) {
|
|
895
|
+
const ref = asRef(input);
|
|
896
|
+
const element = this.findElement(ref, source);
|
|
897
|
+
if (!element || !isElementVisible(element) || isDisabled(element)) {
|
|
898
|
+
throw new TargetNotFoundError("Target does not exist or is not interactable");
|
|
899
|
+
}
|
|
900
|
+
const mapped = this.mappedElements().find((item) => this.matchesMapped(item, element, ref));
|
|
901
|
+
return {
|
|
902
|
+
element,
|
|
903
|
+
mapped: mapped ?? this.adHocMapped(element, ref)
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
tryResolve(input, source) {
|
|
907
|
+
try {
|
|
908
|
+
return this.resolve(input, source);
|
|
909
|
+
} catch {
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
findElement(ref, source) {
|
|
914
|
+
if (ref.id) {
|
|
915
|
+
const byAttr = findElementByAsiystId(this.doc, ref.id);
|
|
916
|
+
if (byAttr) {
|
|
917
|
+
return byAttr;
|
|
918
|
+
}
|
|
919
|
+
const configured = this.configuredSelectors()[ref.id];
|
|
920
|
+
if (configured) {
|
|
921
|
+
const found = querySafeSelector(this.doc, configured);
|
|
922
|
+
if (found) {
|
|
923
|
+
return found;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
const mapped = this.mappedElements().find((item) => item.id === ref.id);
|
|
927
|
+
if (mapped) {
|
|
928
|
+
const byMapped = findElementByAsiystId(this.doc, mapped.id) ?? this.doc.getElementById(mapped.id.replace(/^dom:/, ""));
|
|
929
|
+
if (byMapped) {
|
|
930
|
+
return byMapped;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
if (ref.selector && source === "developer") {
|
|
935
|
+
return querySafeSelector(this.doc, ref.selector);
|
|
936
|
+
}
|
|
937
|
+
if (ref.selector && source === "cloud") {
|
|
938
|
+
const allowed = Object.values(this.configuredSelectors());
|
|
939
|
+
if (allowed.includes(ref.selector)) {
|
|
940
|
+
return querySafeSelector(this.doc, ref.selector);
|
|
941
|
+
}
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
return this.matchBySemantics(ref);
|
|
945
|
+
}
|
|
946
|
+
matchBySemantics(ref) {
|
|
947
|
+
const wantedText = ref.text ? normalize(ref.text) : "";
|
|
948
|
+
const wantedRole = ref.role ? normalize(ref.role) : "";
|
|
949
|
+
let best;
|
|
950
|
+
let bestScore = 0;
|
|
951
|
+
for (const item of this.mappedElements()) {
|
|
952
|
+
if (!item.visible || !item.enabled) {
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
let score = 0;
|
|
956
|
+
if (wantedRole && (normalize(item.role ?? "") === wantedRole || item.kind === wantedRole)) {
|
|
957
|
+
score += 2;
|
|
958
|
+
}
|
|
959
|
+
const haystack = normalize(`${item.label} ${item.text} ${item.description ?? ""}`);
|
|
960
|
+
if (wantedText && haystack.includes(wantedText)) {
|
|
961
|
+
score += 3;
|
|
962
|
+
}
|
|
963
|
+
if (score > bestScore) {
|
|
964
|
+
bestScore = score;
|
|
965
|
+
best = item;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
if (!best || bestScore < 2) {
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
return findElementByAsiystId(this.doc, best.id) ?? this.doc.querySelector(`[id="${best.id.replace(/^dom:/, "")}"]`);
|
|
972
|
+
}
|
|
973
|
+
matchesMapped(item, element, ref) {
|
|
974
|
+
if (ref.id && item.id === ref.id) {
|
|
975
|
+
return true;
|
|
976
|
+
}
|
|
977
|
+
return element.getAttribute("data-asiyst") === item.id;
|
|
978
|
+
}
|
|
979
|
+
adHocMapped(element, ref) {
|
|
980
|
+
return {
|
|
981
|
+
id: ref.id ?? element.getAttribute("data-asiyst") ?? "unknown",
|
|
982
|
+
kind: "other",
|
|
983
|
+
tagName: element.tagName.toLowerCase(),
|
|
984
|
+
role: element.getAttribute("role"),
|
|
985
|
+
text: (element.textContent ?? "").trim().slice(0, 80),
|
|
986
|
+
label: element.getAttribute("aria-label") ?? "",
|
|
987
|
+
href: element.getAttribute("href"),
|
|
988
|
+
pageUrl: this.doc.location?.href ?? "",
|
|
989
|
+
rect: { x: 0, y: 0, width: 0, height: 0 },
|
|
990
|
+
visible: true,
|
|
991
|
+
enabled: true,
|
|
992
|
+
developerDefined: element.hasAttribute("data-asiyst"),
|
|
993
|
+
description: element.getAttribute("data-asiyst-description"),
|
|
994
|
+
section: null
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
// src/website-map/WebsiteMap.ts
|
|
1000
|
+
var WebsiteMap = class {
|
|
1001
|
+
constructor() {
|
|
1002
|
+
this.elements = [];
|
|
1003
|
+
}
|
|
1004
|
+
replace(elements) {
|
|
1005
|
+
this.elements = elements;
|
|
1006
|
+
}
|
|
1007
|
+
list() {
|
|
1008
|
+
return this.elements;
|
|
1009
|
+
}
|
|
1010
|
+
snapshot(doc) {
|
|
1011
|
+
const url = new URL(doc.location?.href ?? "https://invalid.local/");
|
|
1012
|
+
url.search = "";
|
|
1013
|
+
url.hash = "";
|
|
1014
|
+
return {
|
|
1015
|
+
pageUrl: url.toString(),
|
|
1016
|
+
path: url.pathname,
|
|
1017
|
+
title: (doc.title ?? "").slice(0, 120),
|
|
1018
|
+
capturedAt: Date.now(),
|
|
1019
|
+
elements: this.elements.filter((el) => el.visible).map((el) => ({
|
|
1020
|
+
...el,
|
|
1021
|
+
href: el.href && !el.href.toLowerCase().startsWith("javascript:") ? el.href : null
|
|
1022
|
+
}))
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
// src/navigation/HistoryObserver.ts
|
|
1028
|
+
var HistoryObserver = class {
|
|
1029
|
+
constructor(onChange) {
|
|
1030
|
+
this.wrapped = false;
|
|
1031
|
+
this.onChange = onChange;
|
|
1032
|
+
}
|
|
1033
|
+
start(win, observeHistory) {
|
|
1034
|
+
win.addEventListener("popstate", this.onChange);
|
|
1035
|
+
win.addEventListener("hashchange", this.onChange);
|
|
1036
|
+
if (!observeHistory || this.wrapped) {
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
this.originalPush = win.history.pushState.bind(win.history);
|
|
1040
|
+
this.originalReplace = win.history.replaceState.bind(win.history);
|
|
1041
|
+
const notify = this.onChange;
|
|
1042
|
+
win.history.pushState = (...args) => {
|
|
1043
|
+
this.originalPush?.(...args);
|
|
1044
|
+
notify();
|
|
1045
|
+
};
|
|
1046
|
+
win.history.replaceState = (...args) => {
|
|
1047
|
+
this.originalReplace?.(...args);
|
|
1048
|
+
notify();
|
|
1049
|
+
};
|
|
1050
|
+
this.wrapped = true;
|
|
1051
|
+
}
|
|
1052
|
+
stop(win) {
|
|
1053
|
+
win.removeEventListener("popstate", this.onChange);
|
|
1054
|
+
win.removeEventListener("hashchange", this.onChange);
|
|
1055
|
+
if (this.wrapped && this.originalPush && this.originalReplace) {
|
|
1056
|
+
win.history.pushState = this.originalPush;
|
|
1057
|
+
win.history.replaceState = this.originalReplace;
|
|
1058
|
+
}
|
|
1059
|
+
this.wrapped = false;
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
// src/highlighting/HighlightLayer.ts
|
|
1064
|
+
var STYLE_TEXT = `
|
|
1065
|
+
.asiyst-highlight-root { position: fixed; inset: 0; pointer-events: none; z-index: 2147483646; }
|
|
1066
|
+
.asiyst-dim { position: absolute; inset: 0; background: rgba(2, 6, 23, 0.45); }
|
|
1067
|
+
.asiyst-box {
|
|
1068
|
+
position: absolute;
|
|
1069
|
+
border-radius: 10px;
|
|
1070
|
+
box-sizing: border-box;
|
|
1071
|
+
transition: top 80ms linear, left 80ms linear, width 80ms linear, height 80ms linear;
|
|
1072
|
+
}
|
|
1073
|
+
.asiyst-outline { border: 2px solid #38bdf8; box-shadow: 0 0 0 2px rgba(56, 189, 248, 0.25); }
|
|
1074
|
+
.asiyst-glow { box-shadow: 0 0 0 4px rgba(56, 189, 248, 0.35), 0 0 24px rgba(56, 189, 248, 0.55); }
|
|
1075
|
+
.asiyst-pulse { border: 2px solid #38bdf8; animation: asiyst-pulse 1.4s ease-in-out infinite; }
|
|
1076
|
+
.asiyst-spotlight {
|
|
1077
|
+
box-shadow: 0 0 0 9999px rgba(2, 6, 23, 0.55);
|
|
1078
|
+
border: 2px solid #f8fafc;
|
|
1079
|
+
}
|
|
1080
|
+
.asiyst-pointer::after {
|
|
1081
|
+
content: "";
|
|
1082
|
+
position: absolute;
|
|
1083
|
+
left: 50%;
|
|
1084
|
+
top: -18px;
|
|
1085
|
+
width: 10px;
|
|
1086
|
+
height: 10px;
|
|
1087
|
+
margin-left: -5px;
|
|
1088
|
+
border-radius: 50%;
|
|
1089
|
+
background: #38bdf8;
|
|
1090
|
+
}
|
|
1091
|
+
@keyframes asiyst-pulse {
|
|
1092
|
+
0%, 100% { box-shadow: 0 0 0 0 rgba(56, 189, 248, 0.55); }
|
|
1093
|
+
50% { box-shadow: 0 0 0 10px rgba(56, 189, 248, 0); }
|
|
1094
|
+
}
|
|
1095
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1096
|
+
.asiyst-box, .asiyst-pulse { animation: none; transition: none; }
|
|
1097
|
+
}
|
|
1098
|
+
`;
|
|
1099
|
+
var HighlightLayer = class {
|
|
1100
|
+
constructor(root, win) {
|
|
1101
|
+
this.root = root;
|
|
1102
|
+
this.win = win;
|
|
1103
|
+
const style = root.ownerDocument.createElement("style");
|
|
1104
|
+
style.textContent = STYLE_TEXT;
|
|
1105
|
+
root.appendChild(style);
|
|
1106
|
+
this.onViewport = throttle(() => this.sync(), VIEWPORT_HANDLER_THROTTLE_MS);
|
|
1107
|
+
}
|
|
1108
|
+
start() {
|
|
1109
|
+
this.win.addEventListener("scroll", this.onViewport, true);
|
|
1110
|
+
this.win.addEventListener("resize", this.onViewport);
|
|
1111
|
+
}
|
|
1112
|
+
show(target, style) {
|
|
1113
|
+
this.target = target;
|
|
1114
|
+
if (!this.box) {
|
|
1115
|
+
this.box = this.root.ownerDocument.createElement("div");
|
|
1116
|
+
this.box.className = "asiyst-box";
|
|
1117
|
+
this.root.appendChild(this.box);
|
|
1118
|
+
}
|
|
1119
|
+
this.box.className = `asiyst-box asiyst-${style === "dim" ? "outline" : style}`;
|
|
1120
|
+
if (style === "dim" || style === "spotlight") {
|
|
1121
|
+
if (!this.dim) {
|
|
1122
|
+
this.dim = this.root.ownerDocument.createElement("div");
|
|
1123
|
+
this.dim.className = "asiyst-dim";
|
|
1124
|
+
this.root.insertBefore(this.dim, this.box);
|
|
1125
|
+
}
|
|
1126
|
+
this.dim.style.display = style === "dim" ? "block" : "none";
|
|
1127
|
+
} else if (this.dim) {
|
|
1128
|
+
this.dim.style.display = "none";
|
|
1129
|
+
}
|
|
1130
|
+
this.sync();
|
|
1131
|
+
}
|
|
1132
|
+
hide() {
|
|
1133
|
+
this.target = void 0;
|
|
1134
|
+
if (this.box) {
|
|
1135
|
+
this.box.style.display = "none";
|
|
1136
|
+
}
|
|
1137
|
+
if (this.dim) {
|
|
1138
|
+
this.dim.style.display = "none";
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
isShowing() {
|
|
1142
|
+
return Boolean(this.target);
|
|
1143
|
+
}
|
|
1144
|
+
stop() {
|
|
1145
|
+
this.hide();
|
|
1146
|
+
this.win.removeEventListener("scroll", this.onViewport, true);
|
|
1147
|
+
this.win.removeEventListener("resize", this.onViewport);
|
|
1148
|
+
this.onViewport.cancel();
|
|
1149
|
+
}
|
|
1150
|
+
sync() {
|
|
1151
|
+
if (!this.box || !this.target) {
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
const rect = this.target.getBoundingClientRect();
|
|
1155
|
+
this.box.style.display = "block";
|
|
1156
|
+
this.box.style.left = `${rect.left - 4}px`;
|
|
1157
|
+
this.box.style.top = `${rect.top - 4}px`;
|
|
1158
|
+
this.box.style.width = `${rect.width + 8}px`;
|
|
1159
|
+
this.box.style.height = `${rect.height + 8}px`;
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
|
|
1163
|
+
// src/avatar/CssAvatarRenderer.ts
|
|
1164
|
+
var AVATAR_CSS = `
|
|
1165
|
+
.asiyst-avatar {
|
|
1166
|
+
position: fixed;
|
|
1167
|
+
z-index: 2147483647;
|
|
1168
|
+
display: flex;
|
|
1169
|
+
flex-direction: column;
|
|
1170
|
+
align-items: center;
|
|
1171
|
+
gap: 6px;
|
|
1172
|
+
pointer-events: auto;
|
|
1173
|
+
user-select: none;
|
|
1174
|
+
}
|
|
1175
|
+
.asiyst-avatar.asiyst-animate {
|
|
1176
|
+
transition: left 420ms cubic-bezier(0.22, 1, 0.36, 1), top 420ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
1177
|
+
}
|
|
1178
|
+
.asiyst-figure {
|
|
1179
|
+
border-radius: 28px;
|
|
1180
|
+
display: grid;
|
|
1181
|
+
place-items: center;
|
|
1182
|
+
color: #fff;
|
|
1183
|
+
font: 700 14px/1.2 system-ui, sans-serif;
|
|
1184
|
+
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.28);
|
|
1185
|
+
}
|
|
1186
|
+
.asiyst-figure[data-pose="point"] { transform: rotate(-12deg); }
|
|
1187
|
+
.asiyst-figure[data-pose="think"] { opacity: 0.85; }
|
|
1188
|
+
.asiyst-figure[data-pose="celebrate"] { transform: scale(1.08); }
|
|
1189
|
+
.asiyst-bubble {
|
|
1190
|
+
max-width: 220px;
|
|
1191
|
+
background: #0f172a;
|
|
1192
|
+
color: #f8fafc;
|
|
1193
|
+
border-radius: 12px;
|
|
1194
|
+
padding: 8px 10px;
|
|
1195
|
+
font: 13px/1.4 system-ui, sans-serif;
|
|
1196
|
+
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.2);
|
|
1197
|
+
}
|
|
1198
|
+
.asiyst-name { font: 600 12px/1 system-ui, sans-serif; color: #0f172a; }
|
|
1199
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1200
|
+
.asiyst-avatar.asiyst-animate { transition: none; }
|
|
1201
|
+
}
|
|
1202
|
+
`;
|
|
1203
|
+
var CssAvatarRenderer = class {
|
|
1204
|
+
constructor() {
|
|
1205
|
+
this.size = 96;
|
|
1206
|
+
this.hidden = false;
|
|
1207
|
+
this.facing = "right";
|
|
1208
|
+
this.pose = "idle";
|
|
1209
|
+
}
|
|
1210
|
+
mount(container, config) {
|
|
1211
|
+
const doc = container.ownerDocument;
|
|
1212
|
+
const style = doc.createElement("style");
|
|
1213
|
+
style.textContent = AVATAR_CSS;
|
|
1214
|
+
container.appendChild(style);
|
|
1215
|
+
this.root = doc.createElement("div");
|
|
1216
|
+
this.root.className = "asiyst-avatar asiyst-animate";
|
|
1217
|
+
this.root.setAttribute("role", "img");
|
|
1218
|
+
this.figure = doc.createElement("button");
|
|
1219
|
+
this.figure.type = "button";
|
|
1220
|
+
this.figure.className = "asiyst-figure";
|
|
1221
|
+
this.figure.setAttribute("aria-label", `${config.avatarName} assistant`);
|
|
1222
|
+
this.nameEl = doc.createElement("div");
|
|
1223
|
+
this.nameEl.className = "asiyst-name";
|
|
1224
|
+
this.bubble = doc.createElement("div");
|
|
1225
|
+
this.bubble.className = "asiyst-bubble";
|
|
1226
|
+
this.bubble.hidden = true;
|
|
1227
|
+
this.root.append(this.bubble, this.figure, this.nameEl);
|
|
1228
|
+
container.appendChild(this.root);
|
|
1229
|
+
this.applyConfig(config);
|
|
1230
|
+
}
|
|
1231
|
+
unmount() {
|
|
1232
|
+
this.root?.remove();
|
|
1233
|
+
this.root = void 0;
|
|
1234
|
+
}
|
|
1235
|
+
applyConfig(config) {
|
|
1236
|
+
this.size = config.size;
|
|
1237
|
+
if (this.figure) {
|
|
1238
|
+
this.figure.style.width = `${config.size}px`;
|
|
1239
|
+
this.figure.style.height = `${config.size}px`;
|
|
1240
|
+
this.figure.style.background = config.theme.accent ?? "#2563eb";
|
|
1241
|
+
renderSafeText(this.figure, config.avatarName.slice(0, 1).toUpperCase());
|
|
1242
|
+
}
|
|
1243
|
+
if (this.nameEl) {
|
|
1244
|
+
renderSafeText(this.nameEl, config.avatarName);
|
|
1245
|
+
}
|
|
1246
|
+
if (this.root) {
|
|
1247
|
+
this.root.setAttribute("aria-label", config.avatarName);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
setPosition(x, y, animate) {
|
|
1251
|
+
if (!this.root) {
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
this.root.classList.toggle("asiyst-animate", animate);
|
|
1255
|
+
this.root.style.left = `${x}px`;
|
|
1256
|
+
this.root.style.top = `${y}px`;
|
|
1257
|
+
}
|
|
1258
|
+
setFacing(direction) {
|
|
1259
|
+
this.facing = direction;
|
|
1260
|
+
this.syncTransform();
|
|
1261
|
+
}
|
|
1262
|
+
setPose(pose) {
|
|
1263
|
+
this.pose = pose;
|
|
1264
|
+
this.figure?.setAttribute("data-pose", pose);
|
|
1265
|
+
this.syncTransform();
|
|
1266
|
+
}
|
|
1267
|
+
syncTransform() {
|
|
1268
|
+
if (!this.figure) {
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
const face = this.facing === "left" ? "scaleX(-1)" : "";
|
|
1272
|
+
const pose = this.pose === "point" ? "rotate(-12deg)" : this.pose === "celebrate" ? "scale(1.08)" : "";
|
|
1273
|
+
this.figure.style.transform = `${face} ${pose}`.trim();
|
|
1274
|
+
}
|
|
1275
|
+
setSpeech(text) {
|
|
1276
|
+
if (!this.bubble) {
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
if (!text) {
|
|
1280
|
+
this.bubble.hidden = true;
|
|
1281
|
+
this.bubble.textContent = "";
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
this.bubble.hidden = false;
|
|
1285
|
+
renderSafeText(this.bubble, text);
|
|
1286
|
+
}
|
|
1287
|
+
getSize() {
|
|
1288
|
+
return { width: this.size, height: this.size + 28 };
|
|
1289
|
+
}
|
|
1290
|
+
setHidden(hidden) {
|
|
1291
|
+
this.hidden = hidden;
|
|
1292
|
+
if (this.root) {
|
|
1293
|
+
this.root.hidden = hidden;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
isHidden() {
|
|
1297
|
+
return this.hidden;
|
|
1298
|
+
}
|
|
1299
|
+
getFigure() {
|
|
1300
|
+
return this.figure;
|
|
1301
|
+
}
|
|
1302
|
+
};
|
|
1303
|
+
|
|
1304
|
+
// src/accessibility/a11y.ts
|
|
1305
|
+
function prefersReducedMotion(windowLike) {
|
|
1306
|
+
if (!windowLike || typeof windowLike.matchMedia !== "function") {
|
|
1307
|
+
return false;
|
|
1308
|
+
}
|
|
1309
|
+
try {
|
|
1310
|
+
return windowLike.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1311
|
+
} catch {
|
|
1312
|
+
return false;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
function createLiveRegion(root) {
|
|
1316
|
+
const region = root.ownerDocument.createElement("div");
|
|
1317
|
+
region.setAttribute("role", "status");
|
|
1318
|
+
region.setAttribute("aria-live", "polite");
|
|
1319
|
+
region.setAttribute("aria-atomic", "true");
|
|
1320
|
+
region.style.position = "absolute";
|
|
1321
|
+
region.style.width = "1px";
|
|
1322
|
+
region.style.height = "1px";
|
|
1323
|
+
region.style.overflow = "hidden";
|
|
1324
|
+
region.style.clipPath = "inset(50%)";
|
|
1325
|
+
root.appendChild(region);
|
|
1326
|
+
return region;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
// src/navigation/scroll.ts
|
|
1330
|
+
function estimateSafeInsets(doc) {
|
|
1331
|
+
const insets = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
1332
|
+
const view = doc.defaultView;
|
|
1333
|
+
if (!view) {
|
|
1334
|
+
return insets;
|
|
1335
|
+
}
|
|
1336
|
+
const candidates = Array.from(doc.querySelectorAll("body *")).slice(0, 200);
|
|
1337
|
+
for (const el of candidates) {
|
|
1338
|
+
if (!(el instanceof HTMLElement) || el.id === "asiyst-host") {
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
const style = view.getComputedStyle(el);
|
|
1342
|
+
const position = style.position;
|
|
1343
|
+
if (position !== "fixed" && position !== "sticky") {
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
const rect = el.getBoundingClientRect();
|
|
1347
|
+
if (rect.height <= 0 || rect.width <= 0) {
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
if (rect.top <= 8 && rect.height < view.innerHeight / 3) {
|
|
1351
|
+
insets.top = Math.max(insets.top, Math.min(rect.bottom, 160));
|
|
1352
|
+
}
|
|
1353
|
+
if (rect.bottom >= view.innerHeight - 8 && rect.height < view.innerHeight / 3) {
|
|
1354
|
+
insets.bottom = Math.max(insets.bottom, Math.min(view.innerHeight - rect.top, 120));
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
return insets;
|
|
1358
|
+
}
|
|
1359
|
+
async function scrollWindowTo(win, left, top, reducedMotion) {
|
|
1360
|
+
win.scrollTo({
|
|
1361
|
+
left,
|
|
1362
|
+
top,
|
|
1363
|
+
behavior: reducedMotion ? "auto" : "smooth"
|
|
1364
|
+
});
|
|
1365
|
+
if (!reducedMotion) {
|
|
1366
|
+
await waitForScrollIdle(win);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
function waitForScrollIdle(win) {
|
|
1370
|
+
return new Promise((resolve) => {
|
|
1371
|
+
let last = win.scrollY;
|
|
1372
|
+
let stable = 0;
|
|
1373
|
+
const timer = win.setInterval(() => {
|
|
1374
|
+
if (Math.abs(win.scrollY - last) < 1) {
|
|
1375
|
+
stable += 1;
|
|
1376
|
+
} else {
|
|
1377
|
+
stable = 0;
|
|
1378
|
+
last = win.scrollY;
|
|
1379
|
+
}
|
|
1380
|
+
if (stable >= 3) {
|
|
1381
|
+
win.clearInterval(timer);
|
|
1382
|
+
resolve();
|
|
1383
|
+
}
|
|
1384
|
+
}, 50);
|
|
1385
|
+
win.setTimeout(() => {
|
|
1386
|
+
win.clearInterval(timer);
|
|
1387
|
+
resolve();
|
|
1388
|
+
}, 1200);
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// src/utils/geometry.ts
|
|
1393
|
+
function clamp(value, min, max) {
|
|
1394
|
+
return Math.min(max, Math.max(min, value));
|
|
1395
|
+
}
|
|
1396
|
+
function isRectInComfortableView(target, viewport, insets) {
|
|
1397
|
+
const top = insets.top + 8;
|
|
1398
|
+
const left = insets.left + 8;
|
|
1399
|
+
const right = viewport.width - insets.right - 8;
|
|
1400
|
+
const bottom = viewport.height - insets.bottom - 8;
|
|
1401
|
+
return target.x >= left && target.y >= top && target.x + target.width <= right && target.y + target.height <= bottom;
|
|
1402
|
+
}
|
|
1403
|
+
function computeScrollTarget(target, viewport, insets) {
|
|
1404
|
+
const visibleHeight = viewport.height - insets.top - insets.bottom;
|
|
1405
|
+
const visibleWidth = viewport.width - insets.left - insets.right;
|
|
1406
|
+
const desiredTop = target.y + viewport.scrollY - insets.top - visibleHeight / 2 + target.height / 2;
|
|
1407
|
+
const desiredLeft = target.x + viewport.scrollX - insets.left - visibleWidth / 2 + target.width / 2;
|
|
1408
|
+
return {
|
|
1409
|
+
left: Math.max(0, desiredLeft),
|
|
1410
|
+
top: Math.max(0, desiredTop)
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
function computeAvatarDestination(input) {
|
|
1414
|
+
const gap = input.gap ?? AVATAR_TARGET_GAP;
|
|
1415
|
+
const padding = AVATAR_VIEWPORT_PADDING;
|
|
1416
|
+
const { target, viewport, avatarSize, insets } = input;
|
|
1417
|
+
const minX = insets.left + padding;
|
|
1418
|
+
const minY = insets.top + padding;
|
|
1419
|
+
const maxX = viewport.width - insets.right - avatarSize.width - padding;
|
|
1420
|
+
const maxY = viewport.height - insets.bottom - avatarSize.height - padding;
|
|
1421
|
+
const leftCandidate = target.x - avatarSize.width - gap;
|
|
1422
|
+
const rightCandidate = target.x + target.width + gap;
|
|
1423
|
+
const centeredY = target.y + target.height / 2 - avatarSize.height / 2;
|
|
1424
|
+
let avatarX;
|
|
1425
|
+
let facing;
|
|
1426
|
+
if (leftCandidate >= minX) {
|
|
1427
|
+
avatarX = leftCandidate;
|
|
1428
|
+
facing = "right";
|
|
1429
|
+
} else if (rightCandidate + avatarSize.width <= maxX + avatarSize.width) {
|
|
1430
|
+
avatarX = rightCandidate;
|
|
1431
|
+
facing = "left";
|
|
1432
|
+
} else {
|
|
1433
|
+
avatarX = minX;
|
|
1434
|
+
facing = "right";
|
|
1435
|
+
}
|
|
1436
|
+
const avatarY = clamp(centeredY, minY, Math.max(minY, maxY));
|
|
1437
|
+
avatarX = clamp(avatarX, minX, Math.max(minX, maxX));
|
|
1438
|
+
const needsScroll = !isRectInComfortableView(target, viewport, insets);
|
|
1439
|
+
const scroll = needsScroll ? computeScrollTarget(target, viewport, insets) : {
|
|
1440
|
+
left: viewport.scrollX,
|
|
1441
|
+
top: viewport.scrollY
|
|
1442
|
+
};
|
|
1443
|
+
return {
|
|
1444
|
+
avatarX,
|
|
1445
|
+
avatarY,
|
|
1446
|
+
facing,
|
|
1447
|
+
needsScroll,
|
|
1448
|
+
scrollLeft: scroll.left,
|
|
1449
|
+
scrollTop: scroll.top
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
function anchorToViewport(position, viewport, avatarSize, insets) {
|
|
1453
|
+
const padding = AVATAR_VIEWPORT_PADDING;
|
|
1454
|
+
const xLeft = insets.left + padding;
|
|
1455
|
+
const yTop = insets.top + padding;
|
|
1456
|
+
const xRight = viewport.width - insets.right - avatarSize.width - padding;
|
|
1457
|
+
const yBottom = viewport.height - insets.bottom - avatarSize.height - padding;
|
|
1458
|
+
switch (position) {
|
|
1459
|
+
case "bottom-left":
|
|
1460
|
+
return { x: xLeft, y: Math.max(yTop, yBottom) };
|
|
1461
|
+
case "top-right":
|
|
1462
|
+
return { x: Math.max(xLeft, xRight), y: yTop };
|
|
1463
|
+
case "top-left":
|
|
1464
|
+
return { x: xLeft, y: yTop };
|
|
1465
|
+
case "bottom-right":
|
|
1466
|
+
default:
|
|
1467
|
+
return { x: Math.max(xLeft, xRight), y: Math.max(yTop, yBottom) };
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// src/movement/MovementEngine.ts
|
|
1472
|
+
var MovementEngine = class {
|
|
1473
|
+
constructor(win, doc, renderer, resolver, events, getConfig) {
|
|
1474
|
+
this.win = win;
|
|
1475
|
+
this.doc = doc;
|
|
1476
|
+
this.renderer = renderer;
|
|
1477
|
+
this.resolver = resolver;
|
|
1478
|
+
this.events = events;
|
|
1479
|
+
this.getConfig = getConfig;
|
|
1480
|
+
this.x = 0;
|
|
1481
|
+
this.y = 0;
|
|
1482
|
+
this.onViewport = throttle(() => {
|
|
1483
|
+
if (this.followTarget) {
|
|
1484
|
+
this.placeBeside(this.followTarget, false);
|
|
1485
|
+
}
|
|
1486
|
+
}, VIEWPORT_HANDLER_THROTTLE_MS);
|
|
1487
|
+
}
|
|
1488
|
+
start() {
|
|
1489
|
+
this.win.addEventListener("scroll", this.onViewport, true);
|
|
1490
|
+
this.win.addEventListener("resize", this.onViewport);
|
|
1491
|
+
}
|
|
1492
|
+
stop() {
|
|
1493
|
+
this.win.removeEventListener("scroll", this.onViewport, true);
|
|
1494
|
+
this.win.removeEventListener("resize", this.onViewport);
|
|
1495
|
+
this.onViewport.cancel();
|
|
1496
|
+
}
|
|
1497
|
+
goToAnchor() {
|
|
1498
|
+
const config = this.getConfig();
|
|
1499
|
+
const size = this.renderer.getSize();
|
|
1500
|
+
const viewport = this.viewport();
|
|
1501
|
+
const point = anchorToViewport(config.position, viewport, size, estimateSafeInsets(this.doc));
|
|
1502
|
+
this.setPosition(point.x, point.y, !prefersReducedMotion(this.win));
|
|
1503
|
+
}
|
|
1504
|
+
async moveTo(target, source) {
|
|
1505
|
+
const resolved = this.resolver.resolve(target, source);
|
|
1506
|
+
this.events.emit("asiyst:target:found", { target: typeof target === "string" ? { id: target } : target, elementId: resolved.mapped.id });
|
|
1507
|
+
await this.placeBeside(resolved.element, true);
|
|
1508
|
+
this.followTarget = resolved.element;
|
|
1509
|
+
return resolved.mapped.rect;
|
|
1510
|
+
}
|
|
1511
|
+
async pointAt(target, source) {
|
|
1512
|
+
await this.moveTo(target, source);
|
|
1513
|
+
this.renderer.setPose("point");
|
|
1514
|
+
}
|
|
1515
|
+
async placeBeside(element, mayScroll) {
|
|
1516
|
+
const reduced = prefersReducedMotion(this.win);
|
|
1517
|
+
const size = this.renderer.getSize();
|
|
1518
|
+
const viewport = this.viewport();
|
|
1519
|
+
const insets = estimateSafeInsets(this.doc);
|
|
1520
|
+
const rect = element.getBoundingClientRect();
|
|
1521
|
+
const plan = computeAvatarDestination({
|
|
1522
|
+
target: { x: rect.left, y: rect.top, width: rect.width, height: rect.height },
|
|
1523
|
+
viewport,
|
|
1524
|
+
avatarSize: size,
|
|
1525
|
+
insets
|
|
1526
|
+
});
|
|
1527
|
+
if (mayScroll && plan.needsScroll) {
|
|
1528
|
+
await scrollWindowTo(this.win, plan.scrollLeft, plan.scrollTop, reduced);
|
|
1529
|
+
return this.placeBeside(element, false);
|
|
1530
|
+
}
|
|
1531
|
+
this.renderer.setFacing(plan.facing);
|
|
1532
|
+
this.renderer.setPose("walk");
|
|
1533
|
+
this.setPosition(plan.avatarX, plan.avatarY, !reduced);
|
|
1534
|
+
this.renderer.setPose("idle");
|
|
1535
|
+
}
|
|
1536
|
+
setPosition(x, y, animate) {
|
|
1537
|
+
this.x = x;
|
|
1538
|
+
this.y = y;
|
|
1539
|
+
this.renderer.setPosition(x, y, animate);
|
|
1540
|
+
this.events.emit("asiyst:avatar:moved", { x, y });
|
|
1541
|
+
}
|
|
1542
|
+
currentPosition() {
|
|
1543
|
+
return { x: this.x, y: this.y };
|
|
1544
|
+
}
|
|
1545
|
+
viewport() {
|
|
1546
|
+
return {
|
|
1547
|
+
width: this.win.innerWidth,
|
|
1548
|
+
height: this.win.innerHeight,
|
|
1549
|
+
scrollX: this.win.scrollX,
|
|
1550
|
+
scrollY: this.win.scrollY
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
};
|
|
1554
|
+
|
|
1555
|
+
// src/avatar/AvatarController.ts
|
|
1556
|
+
var AvatarController = class {
|
|
1557
|
+
constructor(renderer, movement, highlights, resolver, events, liveRegion, getConfig) {
|
|
1558
|
+
this.renderer = renderer;
|
|
1559
|
+
this.movement = movement;
|
|
1560
|
+
this.highlights = highlights;
|
|
1561
|
+
this.resolver = resolver;
|
|
1562
|
+
this.events = events;
|
|
1563
|
+
this.liveRegion = liveRegion;
|
|
1564
|
+
this.getConfig = getConfig;
|
|
1565
|
+
}
|
|
1566
|
+
show() {
|
|
1567
|
+
this.renderer.setHidden(false);
|
|
1568
|
+
this.movement.goToAnchor();
|
|
1569
|
+
this.events.emit("asiyst:avatar:shown", { name: this.getConfig().avatarName });
|
|
1570
|
+
}
|
|
1571
|
+
hide() {
|
|
1572
|
+
this.renderer.setHidden(true);
|
|
1573
|
+
this.highlights.hide();
|
|
1574
|
+
this.events.emit("asiyst:avatar:hidden", { name: this.getConfig().avatarName });
|
|
1575
|
+
}
|
|
1576
|
+
async moveTo(target, source = "developer") {
|
|
1577
|
+
await this.movement.moveTo(target, source);
|
|
1578
|
+
}
|
|
1579
|
+
async pointAt(target, source = "developer") {
|
|
1580
|
+
await this.movement.pointAt(target, source);
|
|
1581
|
+
}
|
|
1582
|
+
async highlight(target, style = "outline", source = "developer") {
|
|
1583
|
+
const resolved = this.resolver.resolve(target, source);
|
|
1584
|
+
await this.movement.moveTo(target, source);
|
|
1585
|
+
this.highlights.show(resolved.element, style);
|
|
1586
|
+
this.events.emit("asiyst:target:highlighted", {
|
|
1587
|
+
elementId: resolved.mapped.id,
|
|
1588
|
+
style
|
|
1589
|
+
});
|
|
1590
|
+
return resolved.mapped.id;
|
|
1591
|
+
}
|
|
1592
|
+
speak(message) {
|
|
1593
|
+
const text = message.trim();
|
|
1594
|
+
this.renderer.setSpeech(text || null);
|
|
1595
|
+
this.renderer.setPose("speak");
|
|
1596
|
+
renderSafeText(this.liveRegion, text);
|
|
1597
|
+
}
|
|
1598
|
+
think() {
|
|
1599
|
+
this.renderer.setPose("think");
|
|
1600
|
+
}
|
|
1601
|
+
celebrate() {
|
|
1602
|
+
this.renderer.setPose("celebrate");
|
|
1603
|
+
}
|
|
1604
|
+
setPosition(x, y) {
|
|
1605
|
+
this.movement.setPosition(x, y, true);
|
|
1606
|
+
}
|
|
1607
|
+
applyConfig(config) {
|
|
1608
|
+
this.renderer.applyConfig(config);
|
|
1609
|
+
}
|
|
1610
|
+
getFigure() {
|
|
1611
|
+
return this.renderer.getFigure();
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
|
|
1615
|
+
// src/interaction/permissions.ts
|
|
1616
|
+
function isActionAllowed(action, config, source) {
|
|
1617
|
+
if (source === "developer") {
|
|
1618
|
+
return true;
|
|
1619
|
+
}
|
|
1620
|
+
return config.allowedActions.includes(action);
|
|
1621
|
+
}
|
|
1622
|
+
function assertActionAllowed(action, config, source) {
|
|
1623
|
+
if (!isActionAllowed(action, config, source)) {
|
|
1624
|
+
throw new ActionNotAllowedError(`Action "${action}" is not permitted for this project`);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
function shouldWaitForUser(action, mode, waitForUser) {
|
|
1628
|
+
if (waitForUser === true) {
|
|
1629
|
+
return true;
|
|
1630
|
+
}
|
|
1631
|
+
if (waitForUser === false) {
|
|
1632
|
+
return false;
|
|
1633
|
+
}
|
|
1634
|
+
if (mode === "guided" && (action === "click" || action === "type" || action === "select" || action === "navigate")) {
|
|
1635
|
+
return true;
|
|
1636
|
+
}
|
|
1637
|
+
return false;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// src/interaction/InteractionEngine.ts
|
|
1641
|
+
var InteractionEngine = class {
|
|
1642
|
+
constructor(avatar, resolver, getConfig, win) {
|
|
1643
|
+
this.avatar = avatar;
|
|
1644
|
+
this.resolver = resolver;
|
|
1645
|
+
this.getConfig = getConfig;
|
|
1646
|
+
this.win = win;
|
|
1647
|
+
}
|
|
1648
|
+
cancelWait() {
|
|
1649
|
+
this.stopWait?.();
|
|
1650
|
+
}
|
|
1651
|
+
async execute(step, source) {
|
|
1652
|
+
const config = this.getConfig();
|
|
1653
|
+
assertActionAllowed(step.action, config, source);
|
|
1654
|
+
const wait = shouldWaitForUser(step.action, config.mode, step.waitForUser);
|
|
1655
|
+
switch (step.action) {
|
|
1656
|
+
case "explain":
|
|
1657
|
+
case "wait":
|
|
1658
|
+
if (step.message) {
|
|
1659
|
+
this.avatar.speak(step.message);
|
|
1660
|
+
}
|
|
1661
|
+
return { ok: true, waitedForUser: false };
|
|
1662
|
+
case "highlight":
|
|
1663
|
+
return this.highlight(step, source, wait);
|
|
1664
|
+
case "scroll":
|
|
1665
|
+
case "click":
|
|
1666
|
+
case "open-menu":
|
|
1667
|
+
case "open-modal":
|
|
1668
|
+
case "search":
|
|
1669
|
+
case "type":
|
|
1670
|
+
case "select":
|
|
1671
|
+
return this.guideToward(step, source, wait);
|
|
1672
|
+
case "navigate":
|
|
1673
|
+
return this.navigate(step, source, wait);
|
|
1674
|
+
case "complete":
|
|
1675
|
+
this.avatar.celebrate();
|
|
1676
|
+
if (step.message) {
|
|
1677
|
+
this.avatar.speak(step.message);
|
|
1678
|
+
}
|
|
1679
|
+
return { ok: true, waitedForUser: false };
|
|
1680
|
+
default:
|
|
1681
|
+
throw new TaskExecutionError("Unsupported action");
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
async highlight(step, source, wait) {
|
|
1685
|
+
if (!step.target) {
|
|
1686
|
+
throw new TaskExecutionError("Highlight requires a target");
|
|
1687
|
+
}
|
|
1688
|
+
const style = step.highlightStyle ?? "outline";
|
|
1689
|
+
const elementId2 = await this.avatar.highlight(step.target, style, source);
|
|
1690
|
+
if (step.message) {
|
|
1691
|
+
this.avatar.speak(step.message);
|
|
1692
|
+
}
|
|
1693
|
+
return { ok: true, waitedForUser: wait, elementId: elementId2 };
|
|
1694
|
+
}
|
|
1695
|
+
async guideToward(step, source, wait) {
|
|
1696
|
+
if (!step.target) {
|
|
1697
|
+
throw new TaskExecutionError(`${step.action} requires a target`);
|
|
1698
|
+
}
|
|
1699
|
+
if (step.action === "type" || step.action === "select" || step.action === "click") {
|
|
1700
|
+
if (!wait) {
|
|
1701
|
+
throw new TaskExecutionError(`${step.action} may only run when the user performs it or waitForUser is enabled`);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
const elementId2 = await this.avatar.highlight(step.target, step.highlightStyle ?? "pulse", source);
|
|
1705
|
+
if (step.message) {
|
|
1706
|
+
this.avatar.speak(step.message);
|
|
1707
|
+
} else {
|
|
1708
|
+
this.avatar.speak(defaultInstruction(step.action, step.target));
|
|
1709
|
+
}
|
|
1710
|
+
return { ok: true, waitedForUser: true, elementId: elementId2 };
|
|
1711
|
+
}
|
|
1712
|
+
async navigate(step, source, wait) {
|
|
1713
|
+
if (step.target) {
|
|
1714
|
+
return this.guideToward(step, source, wait);
|
|
1715
|
+
}
|
|
1716
|
+
if (!step.url) {
|
|
1717
|
+
throw new TaskExecutionError("Navigate requires a target or url");
|
|
1718
|
+
}
|
|
1719
|
+
if (!wait) {
|
|
1720
|
+
throw new TaskExecutionError("Automatic navigation is not enabled");
|
|
1721
|
+
}
|
|
1722
|
+
if (step.message) {
|
|
1723
|
+
this.avatar.speak(step.message);
|
|
1724
|
+
}
|
|
1725
|
+
return { ok: true, waitedForUser: true };
|
|
1726
|
+
}
|
|
1727
|
+
waitForElementClick(elementId2, timeoutMs) {
|
|
1728
|
+
return new Promise((resolve) => {
|
|
1729
|
+
const resolved = this.resolver.tryResolve(elementId2, "local");
|
|
1730
|
+
if (!resolved) {
|
|
1731
|
+
resolve(false);
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
const finish = (clicked) => {
|
|
1735
|
+
this.win.clearTimeout(timer);
|
|
1736
|
+
this.win.removeEventListener("click", onClick, true);
|
|
1737
|
+
this.stopWait = void 0;
|
|
1738
|
+
resolve(clicked);
|
|
1739
|
+
};
|
|
1740
|
+
const timer = this.win.setTimeout(() => finish(false), timeoutMs);
|
|
1741
|
+
const onClick = (event) => {
|
|
1742
|
+
const node = event.target;
|
|
1743
|
+
if (!(node instanceof Element)) {
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
if (node === resolved.element || resolved.element.contains(node)) {
|
|
1747
|
+
finish(true);
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1750
|
+
this.stopWait = () => finish(false);
|
|
1751
|
+
this.win.addEventListener("click", onClick, true);
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
};
|
|
1755
|
+
function defaultInstruction(action, target) {
|
|
1756
|
+
const name = typeof target === "string" ? target : target.id ?? target.text ?? "this control";
|
|
1757
|
+
switch (action) {
|
|
1758
|
+
case "type":
|
|
1759
|
+
return `Type into ${name}.`;
|
|
1760
|
+
case "select":
|
|
1761
|
+
return `Choose an option in ${name}.`;
|
|
1762
|
+
case "navigate":
|
|
1763
|
+
return `Open ${name}.`;
|
|
1764
|
+
default:
|
|
1765
|
+
return `Click ${name}.`;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// src/task/states.ts
|
|
1770
|
+
var TaskStatus = {
|
|
1771
|
+
Idle: "idle",
|
|
1772
|
+
UserRequest: "user_request",
|
|
1773
|
+
IntentDetected: "intent_detected",
|
|
1774
|
+
TaskCreated: "task_created",
|
|
1775
|
+
TargetResolved: "target_resolved",
|
|
1776
|
+
ActionStarted: "action_started",
|
|
1777
|
+
WaitingForUser: "waiting_for_user",
|
|
1778
|
+
UserActionDetected: "user_action_detected",
|
|
1779
|
+
StepCompleted: "step_completed",
|
|
1780
|
+
NextStep: "next_step",
|
|
1781
|
+
TaskCompleted: "task_completed",
|
|
1782
|
+
Failed: "failed",
|
|
1783
|
+
Cancelled: "cancelled",
|
|
1784
|
+
Timeout: "timeout",
|
|
1785
|
+
TargetNotFound: "target_not_found"
|
|
1786
|
+
};
|
|
1787
|
+
var TRANSITIONS = {
|
|
1788
|
+
[TaskStatus.Idle]: [TaskStatus.UserRequest],
|
|
1789
|
+
[TaskStatus.UserRequest]: [TaskStatus.IntentDetected, TaskStatus.Failed, TaskStatus.Cancelled],
|
|
1790
|
+
[TaskStatus.IntentDetected]: [TaskStatus.TaskCreated, TaskStatus.Failed, TaskStatus.Cancelled],
|
|
1791
|
+
[TaskStatus.TaskCreated]: [TaskStatus.TargetResolved, TaskStatus.ActionStarted, TaskStatus.Failed, TaskStatus.Cancelled, TaskStatus.TargetNotFound],
|
|
1792
|
+
[TaskStatus.TargetResolved]: [
|
|
1793
|
+
TaskStatus.ActionStarted,
|
|
1794
|
+
TaskStatus.Failed,
|
|
1795
|
+
TaskStatus.Cancelled,
|
|
1796
|
+
TaskStatus.TargetNotFound
|
|
1797
|
+
],
|
|
1798
|
+
[TaskStatus.ActionStarted]: [
|
|
1799
|
+
TaskStatus.WaitingForUser,
|
|
1800
|
+
TaskStatus.StepCompleted,
|
|
1801
|
+
TaskStatus.Failed,
|
|
1802
|
+
TaskStatus.Cancelled,
|
|
1803
|
+
TaskStatus.Timeout,
|
|
1804
|
+
TaskStatus.TargetNotFound
|
|
1805
|
+
],
|
|
1806
|
+
[TaskStatus.WaitingForUser]: [
|
|
1807
|
+
TaskStatus.UserActionDetected,
|
|
1808
|
+
TaskStatus.Timeout,
|
|
1809
|
+
TaskStatus.Cancelled,
|
|
1810
|
+
TaskStatus.Failed
|
|
1811
|
+
],
|
|
1812
|
+
[TaskStatus.UserActionDetected]: [TaskStatus.StepCompleted, TaskStatus.Failed, TaskStatus.Cancelled],
|
|
1813
|
+
[TaskStatus.StepCompleted]: [TaskStatus.NextStep, TaskStatus.TaskCompleted, TaskStatus.Cancelled],
|
|
1814
|
+
[TaskStatus.NextStep]: [TaskStatus.TargetResolved, TaskStatus.ActionStarted, TaskStatus.Failed, TaskStatus.Cancelled, TaskStatus.TargetNotFound],
|
|
1815
|
+
[TaskStatus.TaskCompleted]: [TaskStatus.Idle],
|
|
1816
|
+
[TaskStatus.Failed]: [TaskStatus.Idle],
|
|
1817
|
+
[TaskStatus.Cancelled]: [TaskStatus.Idle],
|
|
1818
|
+
[TaskStatus.Timeout]: [TaskStatus.Idle, TaskStatus.Failed],
|
|
1819
|
+
[TaskStatus.TargetNotFound]: [TaskStatus.Idle, TaskStatus.Failed]
|
|
1820
|
+
};
|
|
1821
|
+
function canTransition(from, to) {
|
|
1822
|
+
return TRANSITIONS[from].includes(to);
|
|
1823
|
+
}
|
|
1824
|
+
function transition(from, to) {
|
|
1825
|
+
if (!canTransition(from, to)) {
|
|
1826
|
+
throw new Error(`Invalid task transition: ${from} -> ${to}`);
|
|
1827
|
+
}
|
|
1828
|
+
return to;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
// src/task/TaskEngine.ts
|
|
1832
|
+
var TaskEngine = class {
|
|
1833
|
+
constructor(interaction, events, analytics, cloud) {
|
|
1834
|
+
this.interaction = interaction;
|
|
1835
|
+
this.events = events;
|
|
1836
|
+
this.analytics = analytics;
|
|
1837
|
+
this.cloud = cloud;
|
|
1838
|
+
this.status = TaskStatus.Idle;
|
|
1839
|
+
this.abort = false;
|
|
1840
|
+
}
|
|
1841
|
+
getStatus() {
|
|
1842
|
+
return this.status;
|
|
1843
|
+
}
|
|
1844
|
+
async run(task, source) {
|
|
1845
|
+
this.abort = false;
|
|
1846
|
+
this.active = task;
|
|
1847
|
+
this.move(TaskStatus.UserRequest);
|
|
1848
|
+
this.move(TaskStatus.IntentDetected);
|
|
1849
|
+
this.move(TaskStatus.TaskCreated);
|
|
1850
|
+
this.events.emit("asiyst:task:started", { taskId: task.id });
|
|
1851
|
+
this.analytics.track("task_started", { taskId: task.id });
|
|
1852
|
+
this.analytics.track("guide_started", { taskId: task.id });
|
|
1853
|
+
void this.cloud.sendTaskUpdate(task.id, this.status);
|
|
1854
|
+
try {
|
|
1855
|
+
for (const [index, step] of task.steps.entries()) {
|
|
1856
|
+
if (this.abort) {
|
|
1857
|
+
return TaskStatus.Cancelled;
|
|
1858
|
+
}
|
|
1859
|
+
if (index > 0) {
|
|
1860
|
+
this.move(TaskStatus.NextStep);
|
|
1861
|
+
}
|
|
1862
|
+
if (step.target) {
|
|
1863
|
+
this.move(TaskStatus.TargetResolved);
|
|
1864
|
+
}
|
|
1865
|
+
this.move(TaskStatus.ActionStarted);
|
|
1866
|
+
const result = await this.interaction.execute(step, source);
|
|
1867
|
+
if (result.waitedForUser && result.elementId) {
|
|
1868
|
+
this.move(TaskStatus.WaitingForUser);
|
|
1869
|
+
const clicked = await this.interaction.waitForElementClick(result.elementId, step.timeoutMs ?? 3e4);
|
|
1870
|
+
if (this.abort) {
|
|
1871
|
+
return TaskStatus.Cancelled;
|
|
1872
|
+
}
|
|
1873
|
+
if (!clicked) {
|
|
1874
|
+
this.move(TaskStatus.Timeout);
|
|
1875
|
+
return this.finish(TaskStatus.Failed, "Timed out waiting for the user");
|
|
1876
|
+
}
|
|
1877
|
+
this.move(TaskStatus.UserActionDetected);
|
|
1878
|
+
this.events.emit("asiyst:user:clicked", { elementId: result.elementId });
|
|
1879
|
+
this.analytics.track("user_clicked_instructed_element", { elementId: result.elementId });
|
|
1880
|
+
}
|
|
1881
|
+
this.move(TaskStatus.StepCompleted);
|
|
1882
|
+
this.events.emit("asiyst:task:step-completed", { taskId: task.id, stepId: step.id });
|
|
1883
|
+
void this.cloud.sendTaskUpdate(task.id, this.status, step.id);
|
|
1884
|
+
}
|
|
1885
|
+
this.move(TaskStatus.TaskCompleted);
|
|
1886
|
+
this.events.emit("asiyst:task:completed", { taskId: task.id });
|
|
1887
|
+
this.analytics.track("task_completed", { taskId: task.id });
|
|
1888
|
+
this.analytics.track("guide_completed", { taskId: task.id });
|
|
1889
|
+
void this.cloud.sendTaskUpdate(task.id, this.status);
|
|
1890
|
+
this.move(TaskStatus.Idle);
|
|
1891
|
+
return TaskStatus.TaskCompleted;
|
|
1892
|
+
} catch (error) {
|
|
1893
|
+
if (error instanceof TargetNotFoundError) {
|
|
1894
|
+
if (canTransition(this.status, TaskStatus.TargetNotFound)) {
|
|
1895
|
+
this.move(TaskStatus.TargetNotFound);
|
|
1896
|
+
}
|
|
1897
|
+
this.events.emit("asiyst:target:not-found", { target: {} });
|
|
1898
|
+
this.analytics.track("target_not_found", { taskId: task.id });
|
|
1899
|
+
return this.finish(TaskStatus.Failed, error.message);
|
|
1900
|
+
}
|
|
1901
|
+
const message = error instanceof Error ? error.message : "Task failed";
|
|
1902
|
+
return this.finish(TaskStatus.Failed, message);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
cancel() {
|
|
1906
|
+
this.abort = true;
|
|
1907
|
+
this.interaction.cancelWait();
|
|
1908
|
+
if (this.status !== TaskStatus.Idle) {
|
|
1909
|
+
this.finish(TaskStatus.Cancelled);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
finish(terminal, reason) {
|
|
1913
|
+
const taskId = this.active?.id ?? "unknown";
|
|
1914
|
+
if (this.status !== terminal && canTransition(this.status, terminal)) {
|
|
1915
|
+
this.move(terminal);
|
|
1916
|
+
}
|
|
1917
|
+
if (terminal === TaskStatus.Cancelled) {
|
|
1918
|
+
this.events.emit("asiyst:task:cancelled", { taskId });
|
|
1919
|
+
} else if (terminal !== TaskStatus.TaskCompleted) {
|
|
1920
|
+
this.events.emit("asiyst:task:failed", { taskId, reason: reason ?? terminal });
|
|
1921
|
+
this.analytics.track("task_failed", { taskId });
|
|
1922
|
+
}
|
|
1923
|
+
if (canTransition(this.status, TaskStatus.Idle)) {
|
|
1924
|
+
this.move(TaskStatus.Idle);
|
|
1925
|
+
} else {
|
|
1926
|
+
this.status = TaskStatus.Idle;
|
|
1927
|
+
}
|
|
1928
|
+
return terminal;
|
|
1929
|
+
}
|
|
1930
|
+
move(next) {
|
|
1931
|
+
this.status = transition(this.status, next);
|
|
1932
|
+
}
|
|
1933
|
+
};
|
|
1934
|
+
|
|
1935
|
+
// src/workflow/WorkflowEngine.ts
|
|
1936
|
+
var WorkflowEngine = class {
|
|
1937
|
+
constructor(cloud, tasks) {
|
|
1938
|
+
this.cloud = cloud;
|
|
1939
|
+
this.tasks = tasks;
|
|
1940
|
+
}
|
|
1941
|
+
async start(workflowId, source = "developer") {
|
|
1942
|
+
const workflow = await this.cloud.fetchWorkflow(workflowId);
|
|
1943
|
+
await this.run(workflow, source);
|
|
1944
|
+
}
|
|
1945
|
+
async run(workflow, source) {
|
|
1946
|
+
await this.tasks.run(
|
|
1947
|
+
{
|
|
1948
|
+
id: workflow.id,
|
|
1949
|
+
title: workflow.title,
|
|
1950
|
+
steps: workflow.steps
|
|
1951
|
+
},
|
|
1952
|
+
source
|
|
1953
|
+
);
|
|
1954
|
+
}
|
|
1955
|
+
};
|
|
1956
|
+
|
|
1957
|
+
// src/client/ConversationPanel.ts
|
|
1958
|
+
var ConversationPanel = class {
|
|
1959
|
+
constructor(root, onSubmit, onToggle) {
|
|
1960
|
+
this.onSubmit = onSubmit;
|
|
1961
|
+
this.onToggle = onToggle;
|
|
1962
|
+
this.conversationId = "";
|
|
1963
|
+
this.openState = false;
|
|
1964
|
+
const doc = root.ownerDocument;
|
|
1965
|
+
const style = doc.createElement("style");
|
|
1966
|
+
style.textContent = `
|
|
1967
|
+
.asiyst-panel {
|
|
1968
|
+
position: fixed;
|
|
1969
|
+
right: 16px;
|
|
1970
|
+
bottom: 16px;
|
|
1971
|
+
width: min(360px, calc(100vw - 32px));
|
|
1972
|
+
max-height: min(480px, 70vh);
|
|
1973
|
+
background: #fff;
|
|
1974
|
+
color: #0f172a;
|
|
1975
|
+
border-radius: 16px;
|
|
1976
|
+
box-shadow: 0 16px 50px rgba(15, 23, 42, 0.22);
|
|
1977
|
+
display: flex;
|
|
1978
|
+
flex-direction: column;
|
|
1979
|
+
font: 14px/1.45 system-ui, sans-serif;
|
|
1980
|
+
overflow: hidden;
|
|
1981
|
+
}
|
|
1982
|
+
.asiyst-panel[hidden] { display: none; }
|
|
1983
|
+
.asiyst-panel header {
|
|
1984
|
+
display: flex;
|
|
1985
|
+
justify-content: space-between;
|
|
1986
|
+
align-items: center;
|
|
1987
|
+
padding: 12px 14px;
|
|
1988
|
+
background: #0f172a;
|
|
1989
|
+
color: #f8fafc;
|
|
1990
|
+
}
|
|
1991
|
+
.asiyst-log { flex: 1; overflow: auto; padding: 12px; display: flex; flex-direction: column; gap: 8px; }
|
|
1992
|
+
.asiyst-msg { padding: 8px 10px; border-radius: 10px; max-width: 90%; }
|
|
1993
|
+
.asiyst-msg[data-role="user"] { align-self: flex-end; background: #dbeafe; }
|
|
1994
|
+
.asiyst-msg[data-role="assistant"] { align-self: flex-start; background: #f1f5f9; }
|
|
1995
|
+
.asiyst-form { display: flex; gap: 8px; padding: 10px; border-top: 1px solid #e2e8f0; }
|
|
1996
|
+
.asiyst-form input { flex: 1; border: 1px solid #cbd5e1; border-radius: 8px; padding: 8px; }
|
|
1997
|
+
.asiyst-form button, .asiyst-close {
|
|
1998
|
+
border: 0; border-radius: 8px; padding: 8px 10px; background: #2563eb; color: #fff; cursor: pointer;
|
|
1999
|
+
}
|
|
2000
|
+
.asiyst-close { background: transparent; color: #f8fafc; }
|
|
2001
|
+
`;
|
|
2002
|
+
root.appendChild(style);
|
|
2003
|
+
this.panel = doc.createElement("section");
|
|
2004
|
+
this.panel.className = "asiyst-panel";
|
|
2005
|
+
this.panel.hidden = true;
|
|
2006
|
+
this.panel.setAttribute("role", "dialog");
|
|
2007
|
+
this.panel.setAttribute("aria-modal", "false");
|
|
2008
|
+
this.panel.setAttribute("aria-label", "Asiyst assistant");
|
|
2009
|
+
const header = doc.createElement("header");
|
|
2010
|
+
const title = doc.createElement("strong");
|
|
2011
|
+
title.textContent = "Asiyst";
|
|
2012
|
+
const close = doc.createElement("button");
|
|
2013
|
+
close.type = "button";
|
|
2014
|
+
close.className = "asiyst-close";
|
|
2015
|
+
close.textContent = "Close";
|
|
2016
|
+
close.addEventListener("click", () => this.close());
|
|
2017
|
+
header.append(title, close);
|
|
2018
|
+
this.log = doc.createElement("div");
|
|
2019
|
+
this.log.className = "asiyst-log";
|
|
2020
|
+
this.form = doc.createElement("form");
|
|
2021
|
+
this.form.className = "asiyst-form";
|
|
2022
|
+
this.input = doc.createElement("input");
|
|
2023
|
+
this.input.type = "text";
|
|
2024
|
+
this.input.setAttribute("aria-label", "Message Asiyst");
|
|
2025
|
+
this.input.autocomplete = "off";
|
|
2026
|
+
const send = doc.createElement("button");
|
|
2027
|
+
send.type = "submit";
|
|
2028
|
+
send.textContent = "Send";
|
|
2029
|
+
this.form.append(this.input, send);
|
|
2030
|
+
this.form.addEventListener("submit", (event) => {
|
|
2031
|
+
event.preventDefault();
|
|
2032
|
+
void this.submit();
|
|
2033
|
+
});
|
|
2034
|
+
this.panel.append(header, this.log, this.form);
|
|
2035
|
+
root.appendChild(this.panel);
|
|
2036
|
+
doc.addEventListener("keydown", (event) => {
|
|
2037
|
+
if (event.key === "Escape" && this.openState) {
|
|
2038
|
+
this.close();
|
|
2039
|
+
}
|
|
2040
|
+
});
|
|
2041
|
+
}
|
|
2042
|
+
isOpen() {
|
|
2043
|
+
return this.openState;
|
|
2044
|
+
}
|
|
2045
|
+
getConversationId() {
|
|
2046
|
+
return this.conversationId;
|
|
2047
|
+
}
|
|
2048
|
+
open() {
|
|
2049
|
+
if (!this.conversationId) {
|
|
2050
|
+
this.conversationId = `conv_${Date.now().toString(36)}`;
|
|
2051
|
+
}
|
|
2052
|
+
this.openState = true;
|
|
2053
|
+
this.panel.hidden = false;
|
|
2054
|
+
this.input.focus();
|
|
2055
|
+
this.onToggle(true);
|
|
2056
|
+
}
|
|
2057
|
+
close() {
|
|
2058
|
+
this.openState = false;
|
|
2059
|
+
this.panel.hidden = true;
|
|
2060
|
+
this.onToggle(false);
|
|
2061
|
+
}
|
|
2062
|
+
append(role, text) {
|
|
2063
|
+
const item = this.log.ownerDocument.createElement("div");
|
|
2064
|
+
item.className = "asiyst-msg";
|
|
2065
|
+
item.setAttribute("data-role", role);
|
|
2066
|
+
renderSafeText(item, text);
|
|
2067
|
+
this.log.appendChild(item);
|
|
2068
|
+
this.log.scrollTop = this.log.scrollHeight;
|
|
2069
|
+
}
|
|
2070
|
+
async submit() {
|
|
2071
|
+
const text = sanitizeText(this.input.value);
|
|
2072
|
+
if (!text) {
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
this.input.value = "";
|
|
2076
|
+
this.append("user", text);
|
|
2077
|
+
await this.onSubmit(text);
|
|
2078
|
+
}
|
|
2079
|
+
};
|
|
2080
|
+
|
|
2081
|
+
// src/core/isolate.ts
|
|
2082
|
+
async function isolateAsync(fn, fallback) {
|
|
2083
|
+
try {
|
|
2084
|
+
return await fn();
|
|
2085
|
+
} catch {
|
|
2086
|
+
return fallback;
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
function withIsolation(fn) {
|
|
2090
|
+
try {
|
|
2091
|
+
fn();
|
|
2092
|
+
} catch {
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
// src/core/Runtime.ts
|
|
2097
|
+
var AsiystRuntime = class {
|
|
2098
|
+
constructor(options, doc, win) {
|
|
2099
|
+
this.events = new EventBus();
|
|
2100
|
+
this.map = new WebsiteMap();
|
|
2101
|
+
this.observer = new DomObserver(() => this.rescan());
|
|
2102
|
+
this.destroyed = false;
|
|
2103
|
+
this.rescanDebounced = debounce(() => this.rescan(), DOM_SCAN_DEBOUNCE_MS);
|
|
2104
|
+
if (!doc.body) {
|
|
2105
|
+
throw new InitializationError("document.body is not available");
|
|
2106
|
+
}
|
|
2107
|
+
this.options = options;
|
|
2108
|
+
const transport = new HttpTransport({
|
|
2109
|
+
apiBaseUrl: options.apiBaseUrl ?? DEFAULT_API_BASE_URL,
|
|
2110
|
+
projectId: options.projectId,
|
|
2111
|
+
publicKey: options.publicKey
|
|
2112
|
+
});
|
|
2113
|
+
this.cloud = new CloudClient(transport, options.projectId);
|
|
2114
|
+
this.config = new ConfigManager(options, this.cloud, this.events);
|
|
2115
|
+
this.analytics = new Analytics(options.projectId, this.cloud);
|
|
2116
|
+
this.host = new HostRoot(doc);
|
|
2117
|
+
this.resolver = new TargetResolver(
|
|
2118
|
+
doc,
|
|
2119
|
+
() => this.config.get().elementSelectors,
|
|
2120
|
+
() => this.map.list()
|
|
2121
|
+
);
|
|
2122
|
+
this.renderer = new CssAvatarRenderer();
|
|
2123
|
+
this.renderer.mount(this.host.chrome, this.config.get());
|
|
2124
|
+
this.highlights = new HighlightLayer(this.host.overlay, win);
|
|
2125
|
+
this.movement = new MovementEngine(win, doc, this.renderer, this.resolver, this.events, () => this.config.get());
|
|
2126
|
+
const live = createLiveRegion(this.host.shadow);
|
|
2127
|
+
this.avatar = new AvatarController(
|
|
2128
|
+
this.renderer,
|
|
2129
|
+
this.movement,
|
|
2130
|
+
this.highlights,
|
|
2131
|
+
this.resolver,
|
|
2132
|
+
this.events,
|
|
2133
|
+
live,
|
|
2134
|
+
() => this.config.get()
|
|
2135
|
+
);
|
|
2136
|
+
const interaction = new InteractionEngine(this.avatar, this.resolver, () => this.config.get(), win);
|
|
2137
|
+
this.tasks = new TaskEngine(interaction, this.events, this.analytics, this.cloud);
|
|
2138
|
+
this.workflows = new WorkflowEngine(this.cloud, this.tasks);
|
|
2139
|
+
this.panel = new ConversationPanel(
|
|
2140
|
+
this.host.chrome,
|
|
2141
|
+
(text) => this.handleUserMessage(text, doc),
|
|
2142
|
+
(open) => {
|
|
2143
|
+
if (open) {
|
|
2144
|
+
this.analytics.track("assistant_opened");
|
|
2145
|
+
this.events.emit("asiyst:conversation:started", { conversationId: this.panel.getConversationId() });
|
|
2146
|
+
} else {
|
|
2147
|
+
this.analytics.track("assistant_closed");
|
|
2148
|
+
this.events.emit("asiyst:conversation:closed", { conversationId: this.panel.getConversationId() });
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
);
|
|
2152
|
+
this.history = new HistoryObserver(() => this.rescanDebounced());
|
|
2153
|
+
this.history.start(win, options.observeHistory !== false);
|
|
2154
|
+
this.observer.start(doc.documentElement);
|
|
2155
|
+
this.highlights.start();
|
|
2156
|
+
this.movement.start();
|
|
2157
|
+
this.analytics.start();
|
|
2158
|
+
this.rescan();
|
|
2159
|
+
this.avatar.show();
|
|
2160
|
+
this.renderer.getFigure()?.addEventListener("click", () => this.open());
|
|
2161
|
+
win.addEventListener("pagehide", () => {
|
|
2162
|
+
void this.analytics.flush();
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
async start() {
|
|
2166
|
+
this.events.emit("asiyst:initialized", { projectId: this.options.projectId });
|
|
2167
|
+
const cfg = await isolateAsync(() => this.config.refresh(), this.config.get());
|
|
2168
|
+
this.avatar.applyConfig(cfg);
|
|
2169
|
+
this.avatar.show();
|
|
2170
|
+
this.events.emit("asiyst:ready", { projectId: this.options.projectId, configVersion: cfg.version });
|
|
2171
|
+
}
|
|
2172
|
+
getConfig() {
|
|
2173
|
+
return this.config.get();
|
|
2174
|
+
}
|
|
2175
|
+
open() {
|
|
2176
|
+
this.panel.open();
|
|
2177
|
+
this.avatar.show();
|
|
2178
|
+
}
|
|
2179
|
+
close() {
|
|
2180
|
+
this.panel.close();
|
|
2181
|
+
}
|
|
2182
|
+
avatarApi() {
|
|
2183
|
+
return this.avatar;
|
|
2184
|
+
}
|
|
2185
|
+
async startTaskFromText(text, doc) {
|
|
2186
|
+
this.analytics.track("question_started");
|
|
2187
|
+
const task = await this.cloud.requestTask(text, doc.location.href);
|
|
2188
|
+
const result = await this.tasks.run(task, "cloud");
|
|
2189
|
+
if (result === "task_completed") {
|
|
2190
|
+
this.analytics.track("question_completed");
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
async startTask(task) {
|
|
2194
|
+
await this.tasks.run(task, "developer");
|
|
2195
|
+
}
|
|
2196
|
+
cancelTask() {
|
|
2197
|
+
this.tasks.cancel();
|
|
2198
|
+
}
|
|
2199
|
+
async startWorkflow(id) {
|
|
2200
|
+
await this.workflows.start(id, "cloud");
|
|
2201
|
+
}
|
|
2202
|
+
async startWorkflowDefinition(workflow) {
|
|
2203
|
+
await this.workflows.run(workflow, "developer");
|
|
2204
|
+
}
|
|
2205
|
+
on(event, handler) {
|
|
2206
|
+
return this.events.on(event, handler);
|
|
2207
|
+
}
|
|
2208
|
+
off(event, handler) {
|
|
2209
|
+
this.events.off(event, handler);
|
|
2210
|
+
}
|
|
2211
|
+
async destroy(win) {
|
|
2212
|
+
if (this.destroyed) {
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
this.destroyed = true;
|
|
2216
|
+
this.observer.stop();
|
|
2217
|
+
this.history.stop(win);
|
|
2218
|
+
this.movement.stop();
|
|
2219
|
+
this.highlights.stop();
|
|
2220
|
+
this.rescanDebounced.cancel();
|
|
2221
|
+
this.renderer.unmount();
|
|
2222
|
+
this.host.destroy();
|
|
2223
|
+
this.events.emit("asiyst:destroyed", { projectId: this.options.projectId });
|
|
2224
|
+
this.events.removeAll();
|
|
2225
|
+
await this.analytics.destroy();
|
|
2226
|
+
}
|
|
2227
|
+
rescan() {
|
|
2228
|
+
withIsolation(() => {
|
|
2229
|
+
const doc = this.host.host.ownerDocument;
|
|
2230
|
+
this.map.replace(inspectDocument(doc));
|
|
2231
|
+
void this.cloud.sendWebsiteMap(this.map.snapshot(doc));
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
async handleUserMessage(text, doc) {
|
|
2235
|
+
this.events.emit("asiyst:conversation:message", { role: "user", text });
|
|
2236
|
+
this.avatar.think();
|
|
2237
|
+
try {
|
|
2238
|
+
const reply = await this.cloud.sendConversationMessage(text, doc.location.href);
|
|
2239
|
+
this.panel.append("assistant", reply.message.text);
|
|
2240
|
+
this.avatar.speak(reply.message.text);
|
|
2241
|
+
this.events.emit("asiyst:conversation:message", { role: "assistant", text: reply.message.text });
|
|
2242
|
+
await this.startTaskFromText(text, doc);
|
|
2243
|
+
} catch (error) {
|
|
2244
|
+
const message = error instanceof Error ? error.message : "Asiyst Cloud is unavailable";
|
|
2245
|
+
this.panel.append("assistant", message);
|
|
2246
|
+
this.events.emit("asiyst:error", { code: "conversation_failed", message });
|
|
2247
|
+
this.avatar.speak(message);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
};
|
|
2251
|
+
|
|
2252
|
+
// src/client/Asiyst.ts
|
|
2253
|
+
var runtime;
|
|
2254
|
+
var initPromise;
|
|
2255
|
+
function requireRuntime() {
|
|
2256
|
+
if (!runtime) {
|
|
2257
|
+
throw new InitializationError("Call Asiyst.init() before using the SDK");
|
|
2258
|
+
}
|
|
2259
|
+
return runtime;
|
|
2260
|
+
}
|
|
2261
|
+
var Asiyst = {
|
|
2262
|
+
async init(options) {
|
|
2263
|
+
if (runtime) {
|
|
2264
|
+
throw new InitializationError("Asiyst is already initialized");
|
|
2265
|
+
}
|
|
2266
|
+
const credentials = validateInitOptions(options);
|
|
2267
|
+
const doc = document;
|
|
2268
|
+
const win = window;
|
|
2269
|
+
runtime = new AsiystRuntime({ ...options, ...credentials }, doc, win);
|
|
2270
|
+
initPromise = runtime.start();
|
|
2271
|
+
await initPromise;
|
|
2272
|
+
},
|
|
2273
|
+
async destroy() {
|
|
2274
|
+
if (!runtime) {
|
|
2275
|
+
return;
|
|
2276
|
+
}
|
|
2277
|
+
const current = runtime;
|
|
2278
|
+
runtime = void 0;
|
|
2279
|
+
initPromise = void 0;
|
|
2280
|
+
await current.destroy(window);
|
|
2281
|
+
},
|
|
2282
|
+
open() {
|
|
2283
|
+
withIsolation(() => requireRuntime().open());
|
|
2284
|
+
},
|
|
2285
|
+
close() {
|
|
2286
|
+
withIsolation(() => requireRuntime().close());
|
|
2287
|
+
},
|
|
2288
|
+
getConfig() {
|
|
2289
|
+
return requireRuntime().getConfig();
|
|
2290
|
+
},
|
|
2291
|
+
avatar: {
|
|
2292
|
+
show() {
|
|
2293
|
+
withIsolation(() => requireRuntime().avatarApi().show());
|
|
2294
|
+
},
|
|
2295
|
+
hide() {
|
|
2296
|
+
withIsolation(() => requireRuntime().avatarApi().hide());
|
|
2297
|
+
},
|
|
2298
|
+
async moveTo(target) {
|
|
2299
|
+
await requireRuntime().avatarApi().moveTo(target, "developer");
|
|
2300
|
+
},
|
|
2301
|
+
async pointAt(target) {
|
|
2302
|
+
await requireRuntime().avatarApi().pointAt(target, "developer");
|
|
2303
|
+
},
|
|
2304
|
+
async highlight(target, style) {
|
|
2305
|
+
await requireRuntime().avatarApi().highlight(target, style ?? "outline", "developer");
|
|
2306
|
+
},
|
|
2307
|
+
speak(message) {
|
|
2308
|
+
withIsolation(() => requireRuntime().avatarApi().speak(message));
|
|
2309
|
+
},
|
|
2310
|
+
think() {
|
|
2311
|
+
withIsolation(() => requireRuntime().avatarApi().think());
|
|
2312
|
+
},
|
|
2313
|
+
celebrate() {
|
|
2314
|
+
withIsolation(() => requireRuntime().avatarApi().celebrate());
|
|
2315
|
+
},
|
|
2316
|
+
setPosition(x, y) {
|
|
2317
|
+
withIsolation(() => requireRuntime().avatarApi().setPosition(x, y));
|
|
2318
|
+
}
|
|
2319
|
+
},
|
|
2320
|
+
task: {
|
|
2321
|
+
async start(input) {
|
|
2322
|
+
const current = requireRuntime();
|
|
2323
|
+
if (typeof input === "string") {
|
|
2324
|
+
await current.startTaskFromText(input, document);
|
|
2325
|
+
return;
|
|
2326
|
+
}
|
|
2327
|
+
await current.startTask(input);
|
|
2328
|
+
},
|
|
2329
|
+
cancel() {
|
|
2330
|
+
withIsolation(() => requireRuntime().cancelTask());
|
|
2331
|
+
}
|
|
2332
|
+
},
|
|
2333
|
+
workflow: {
|
|
2334
|
+
async start(workflowId) {
|
|
2335
|
+
await requireRuntime().startWorkflow(workflowId);
|
|
2336
|
+
}
|
|
2337
|
+
},
|
|
2338
|
+
on(event, handler) {
|
|
2339
|
+
return requireRuntime().on(event, handler);
|
|
2340
|
+
},
|
|
2341
|
+
off(event, handler) {
|
|
2342
|
+
requireRuntime().off(event, handler);
|
|
2343
|
+
}
|
|
2344
|
+
};
|
|
2345
|
+
|
|
2346
|
+
exports.ActionNotAllowedError = ActionNotAllowedError;
|
|
2347
|
+
exports.Asiyst = Asiyst;
|
|
2348
|
+
exports.AsiystError = AsiystError;
|
|
2349
|
+
exports.AuthenticationError = AuthenticationError;
|
|
2350
|
+
exports.ConfigurationError = ConfigurationError;
|
|
2351
|
+
exports.InitializationError = InitializationError;
|
|
2352
|
+
exports.NetworkError = NetworkError;
|
|
2353
|
+
exports.SDK_VERSION = SDK_VERSION;
|
|
2354
|
+
exports.TargetNotFoundError = TargetNotFoundError;
|
|
2355
|
+
exports.TaskExecutionError = TaskExecutionError;
|
|
2356
|
+
exports.TaskStatus = TaskStatus;
|
|
2357
|
+
exports.anchorToViewport = anchorToViewport;
|
|
2358
|
+
exports.canTransition = canTransition;
|
|
2359
|
+
exports.computeAvatarDestination = computeAvatarDestination;
|
|
2360
|
+
exports.fallbackConfig = fallbackConfig;
|
|
2361
|
+
exports.isSafeSelector = isSafeSelector;
|
|
2362
|
+
exports.normalizeProjectConfig = normalizeProjectConfig;
|
|
2363
|
+
exports.sanitizeText = sanitizeText;
|
|
2364
|
+
exports.validateInitOptions = validateInitOptions;
|
|
2365
|
+
//# sourceMappingURL=index.cjs.map
|
|
2366
|
+
//# sourceMappingURL=index.cjs.map
|