@oxvo/ai-live-assist 7.3.0 → 7.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/AiLiveAssist.d.ts +1 -1
- package/cjs/AiLiveAssist.js +1 -1
- package/cjs/client.js +2 -2
- package/cjs/context.d.ts +8 -0
- package/cjs/context.js +116 -19
- package/cjs/control.js +3 -3
- package/cjs/index.js +1 -1
- package/cjs/messages.js +11 -11
- package/cjs/types.d.ts +1 -0
- package/cjs/ui.js +13 -10
- package/cjs/version.d.ts +1 -1
- package/cjs/version.js +1 -1
- package/lib/AiLiveAssist.d.ts +1 -1
- package/lib/AiLiveAssist.js +1 -1
- package/lib/client.js +2 -2
- package/lib/context.d.ts +8 -0
- package/lib/context.js +116 -19
- package/lib/control.js +3 -3
- package/lib/index.js +1 -1
- package/lib/messages.js +11 -11
- package/lib/types.d.ts +1 -0
- package/lib/ui.js +13 -10
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +2 -2
package/cjs/AiLiveAssist.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare const confirmationReplyIntent: (value: string) => ConfirmationRep
|
|
|
5
5
|
export default class AiLiveAssist {
|
|
6
6
|
private readonly app;
|
|
7
7
|
private readonly options;
|
|
8
|
-
readonly version = "7.3.
|
|
8
|
+
readonly version = "7.3.4";
|
|
9
9
|
private readonly siteKey;
|
|
10
10
|
private tabId;
|
|
11
11
|
private readonly windowId;
|
package/cjs/AiLiveAssist.js
CHANGED
|
@@ -1671,7 +1671,7 @@ class AiLiveAssist {
|
|
|
1671
1671
|
? 'Microphone access was not granted. Allow microphone access to use voice assist.'
|
|
1672
1672
|
: 'Microphone access was not granted. You can continue with text.';
|
|
1673
1673
|
}
|
|
1674
|
-
return 'AI
|
|
1674
|
+
return 'AI Guide is temporarily unavailable.';
|
|
1675
1675
|
}
|
|
1676
1676
|
retryable(error) {
|
|
1677
1677
|
return error instanceof client_js_1.RuntimeClientError ? error.retryable : false;
|
package/cjs/client.js
CHANGED
|
@@ -20,7 +20,7 @@ class RuntimeClient {
|
|
|
20
20
|
this.clientVersion = clientVersion;
|
|
21
21
|
const url = new URL(runtimeUrl, window.location.href);
|
|
22
22
|
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
23
|
-
throw new Error('AI
|
|
23
|
+
throw new Error('AI Guide runtime URL must use HTTP or HTTPS.');
|
|
24
24
|
}
|
|
25
25
|
this.baseUrl = url.origin + url.pathname.replace(/\/$/, '');
|
|
26
26
|
}
|
|
@@ -187,7 +187,7 @@ class RuntimeClient {
|
|
|
187
187
|
}
|
|
188
188
|
throw new RuntimeClientError(typeof error.code === 'string' ? error.code : 'RUNTIME_UNAVAILABLE', typeof error.message === 'string'
|
|
189
189
|
? error.message
|
|
190
|
-
: 'AI
|
|
190
|
+
: 'AI Guide is temporarily unavailable.', error.retryable === true, typeof error.retryAfterMs === 'number' ? error.retryAfterMs : null, response.status);
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
exports.RuntimeClient = RuntimeClient;
|
package/cjs/context.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export declare class PageContextCollector {
|
|
|
17
17
|
private running;
|
|
18
18
|
private mutationObserver;
|
|
19
19
|
private captureTimer;
|
|
20
|
+
private mutationCaptureTimer;
|
|
20
21
|
private targetIds;
|
|
21
22
|
private textIds;
|
|
22
23
|
private optionIds;
|
|
@@ -28,6 +29,7 @@ export declare class PageContextCollector {
|
|
|
28
29
|
private pendingCapture;
|
|
29
30
|
private deferredNavigation;
|
|
30
31
|
private recentSelection;
|
|
32
|
+
private latestSemanticSignature;
|
|
31
33
|
constructor(callbacks: ContextCallbacks, selectorRegionRules?: SelectorRegionRule[], policyVersion?: number);
|
|
32
34
|
get currentRevision(): number;
|
|
33
35
|
get currentPageId(): string;
|
|
@@ -36,6 +38,7 @@ export declare class PageContextCollector {
|
|
|
36
38
|
stop(): void;
|
|
37
39
|
captureAndPublish(): Promise<PageContextSnapshot>;
|
|
38
40
|
capture(): PageContextSnapshot;
|
|
41
|
+
private captureSnapshot;
|
|
39
42
|
captureVisualContext(): VisualContextCapture | null;
|
|
40
43
|
execute(grant: ActionGrant): Promise<ActionExecutionResult>;
|
|
41
44
|
completeActionExecution(actionId: string): Promise<string | null>;
|
|
@@ -47,6 +50,11 @@ export declare class PageContextCollector {
|
|
|
47
50
|
private actionApplied;
|
|
48
51
|
private clippedGeometry;
|
|
49
52
|
private scheduleCapture;
|
|
53
|
+
private scheduleMutationCapture;
|
|
54
|
+
private scheduleViewportCapture;
|
|
55
|
+
private handleMutations;
|
|
56
|
+
private captureAndPublishIfChanged;
|
|
57
|
+
private semanticSignature;
|
|
50
58
|
private handleSelectionChange;
|
|
51
59
|
private handleControlStateChange;
|
|
52
60
|
private readSafeSelection;
|
package/cjs/context.js
CHANGED
|
@@ -8,6 +8,8 @@ const MAX_TOTAL_TEXT = 24000;
|
|
|
8
8
|
const MAX_OPTIONS = 100;
|
|
9
9
|
const MAX_SELECTED_TEXT = 2000;
|
|
10
10
|
const SELECTION_RETENTION_MS = 120000;
|
|
11
|
+
const CAPTURE_SETTLE_MS = 250;
|
|
12
|
+
const MUTATION_CAPTURE_DEBOUNCE_MS = 350;
|
|
11
13
|
const PRIVATE_SELECTOR = [
|
|
12
14
|
'[data-oxvo-ai-live-assist-root]',
|
|
13
15
|
'[data-oxvo-private]',
|
|
@@ -66,6 +68,25 @@ const isCovered = (element) => {
|
|
|
66
68
|
return Boolean(top && top !== element && !element.contains(top) && !top.contains(element));
|
|
67
69
|
};
|
|
68
70
|
const safePageUrl = () => `${window.location.origin}${window.location.pathname}`;
|
|
71
|
+
const safeDestinationPath = (element) => {
|
|
72
|
+
if (!(element instanceof HTMLAnchorElement))
|
|
73
|
+
return null;
|
|
74
|
+
try {
|
|
75
|
+
const destination = new URL(element.href, window.location.href);
|
|
76
|
+
if (destination.origin !== window.location.origin ||
|
|
77
|
+
destination.search ||
|
|
78
|
+
destination.hash ||
|
|
79
|
+
!destination.pathname.startsWith('/') ||
|
|
80
|
+
destination.pathname.startsWith('//') ||
|
|
81
|
+
destination.pathname.length > 1024) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return destination.pathname;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
69
90
|
const sensitivityFor = (element, name) => {
|
|
70
91
|
const input = element instanceof HTMLInputElement ? element : null;
|
|
71
92
|
const type = input?.type.toLowerCase() ?? '';
|
|
@@ -235,6 +256,7 @@ class PageContextCollector {
|
|
|
235
256
|
this.running = false;
|
|
236
257
|
this.mutationObserver = null;
|
|
237
258
|
this.captureTimer = null;
|
|
259
|
+
this.mutationCaptureTimer = null;
|
|
238
260
|
this.targetIds = new WeakMap();
|
|
239
261
|
this.textIds = new WeakMap();
|
|
240
262
|
this.optionIds = new WeakMap();
|
|
@@ -246,6 +268,7 @@ class PageContextCollector {
|
|
|
246
268
|
this.pendingCapture = false;
|
|
247
269
|
this.deferredNavigation = null;
|
|
248
270
|
this.recentSelection = null;
|
|
271
|
+
this.latestSemanticSignature = null;
|
|
249
272
|
this.scheduleCapture = () => {
|
|
250
273
|
if (!this.running)
|
|
251
274
|
return;
|
|
@@ -253,20 +276,53 @@ class PageContextCollector {
|
|
|
253
276
|
this.pendingCapture = true;
|
|
254
277
|
return;
|
|
255
278
|
}
|
|
279
|
+
if (this.mutationCaptureTimer)
|
|
280
|
+
clearTimeout(this.mutationCaptureTimer);
|
|
281
|
+
this.mutationCaptureTimer = null;
|
|
256
282
|
if (this.captureTimer)
|
|
257
283
|
return;
|
|
258
284
|
this.captureTimer = setTimeout(() => {
|
|
259
285
|
this.captureTimer = null;
|
|
260
|
-
void this.
|
|
261
|
-
},
|
|
286
|
+
void this.captureAndPublishIfChanged();
|
|
287
|
+
}, CAPTURE_SETTLE_MS);
|
|
288
|
+
};
|
|
289
|
+
this.scheduleMutationCapture = () => {
|
|
290
|
+
if (!this.running)
|
|
291
|
+
return;
|
|
292
|
+
if (this.executingActionId) {
|
|
293
|
+
this.pendingCapture = true;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (this.captureTimer)
|
|
297
|
+
return;
|
|
298
|
+
if (this.mutationCaptureTimer)
|
|
299
|
+
clearTimeout(this.mutationCaptureTimer);
|
|
300
|
+
this.mutationCaptureTimer = setTimeout(() => {
|
|
301
|
+
this.mutationCaptureTimer = null;
|
|
302
|
+
void this.captureAndPublishIfChanged();
|
|
303
|
+
}, MUTATION_CAPTURE_DEBOUNCE_MS);
|
|
304
|
+
};
|
|
305
|
+
this.scheduleViewportCapture = () => {
|
|
306
|
+
this.scheduleMutationCapture();
|
|
307
|
+
};
|
|
308
|
+
this.handleMutations = (records) => {
|
|
309
|
+
const hasSemanticMutation = records.some((record) => record.type !== 'attributes' ||
|
|
310
|
+
(record.attributeName !== 'class' && record.attributeName !== 'style'));
|
|
311
|
+
if (hasSemanticMutation)
|
|
312
|
+
this.scheduleCapture();
|
|
313
|
+
else
|
|
314
|
+
this.scheduleMutationCapture();
|
|
262
315
|
};
|
|
263
316
|
this.handleSelectionChange = () => {
|
|
264
317
|
const selection = this.readSafeSelection();
|
|
265
318
|
if (selection)
|
|
266
319
|
this.recentSelection = selection;
|
|
267
|
-
this.
|
|
320
|
+
this.scheduleMutationCapture();
|
|
268
321
|
};
|
|
269
|
-
this.handleControlStateChange = () => {
|
|
322
|
+
this.handleControlStateChange = (event) => {
|
|
323
|
+
if (event.target instanceof Element && event.target.closest('[data-oxvo-ai-live-assist-root]')) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
270
326
|
this.scheduleCapture();
|
|
271
327
|
};
|
|
272
328
|
this.handleNavigation = () => {
|
|
@@ -321,7 +377,7 @@ class PageContextCollector {
|
|
|
321
377
|
return;
|
|
322
378
|
this.running = true;
|
|
323
379
|
this.installNavigationHooks();
|
|
324
|
-
this.mutationObserver = new MutationObserver(
|
|
380
|
+
this.mutationObserver = new MutationObserver(this.handleMutations);
|
|
325
381
|
if (document.body) {
|
|
326
382
|
this.mutationObserver.observe(document.body, {
|
|
327
383
|
subtree: true,
|
|
@@ -335,6 +391,7 @@ class PageContextCollector {
|
|
|
335
391
|
'aria-live',
|
|
336
392
|
'class',
|
|
337
393
|
'disabled',
|
|
394
|
+
'href',
|
|
338
395
|
'hidden',
|
|
339
396
|
'inert',
|
|
340
397
|
'role',
|
|
@@ -342,19 +399,20 @@ class PageContextCollector {
|
|
|
342
399
|
],
|
|
343
400
|
});
|
|
344
401
|
}
|
|
345
|
-
window.addEventListener('resize', this.
|
|
346
|
-
document.addEventListener('scroll', this.
|
|
402
|
+
window.addEventListener('resize', this.scheduleViewportCapture, { passive: true });
|
|
403
|
+
document.addEventListener('scroll', this.scheduleViewportCapture, {
|
|
347
404
|
capture: true,
|
|
348
405
|
passive: true,
|
|
349
406
|
});
|
|
350
|
-
window.visualViewport?.addEventListener('resize', this.
|
|
407
|
+
window.visualViewport?.addEventListener('resize', this.scheduleViewportCapture, {
|
|
351
408
|
passive: true,
|
|
352
409
|
});
|
|
353
|
-
window.visualViewport?.addEventListener('scroll', this.
|
|
410
|
+
window.visualViewport?.addEventListener('scroll', this.scheduleViewportCapture, {
|
|
354
411
|
passive: true,
|
|
355
412
|
});
|
|
356
413
|
window.addEventListener('popstate', this.handleNavigation);
|
|
357
414
|
document.addEventListener('selectionchange', this.handleSelectionChange);
|
|
415
|
+
document.addEventListener('click', this.handleControlStateChange, true);
|
|
358
416
|
document.addEventListener('input', this.handleControlStateChange, true);
|
|
359
417
|
document.addEventListener('change', this.handleControlStateChange, true);
|
|
360
418
|
void this.captureAndPublish();
|
|
@@ -366,12 +424,16 @@ class PageContextCollector {
|
|
|
366
424
|
if (this.captureTimer)
|
|
367
425
|
clearTimeout(this.captureTimer);
|
|
368
426
|
this.captureTimer = null;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
window.
|
|
427
|
+
if (this.mutationCaptureTimer)
|
|
428
|
+
clearTimeout(this.mutationCaptureTimer);
|
|
429
|
+
this.mutationCaptureTimer = null;
|
|
430
|
+
window.removeEventListener('resize', this.scheduleViewportCapture);
|
|
431
|
+
document.removeEventListener('scroll', this.scheduleViewportCapture, true);
|
|
432
|
+
window.visualViewport?.removeEventListener('resize', this.scheduleViewportCapture);
|
|
433
|
+
window.visualViewport?.removeEventListener('scroll', this.scheduleViewportCapture);
|
|
373
434
|
window.removeEventListener('popstate', this.handleNavigation);
|
|
374
435
|
document.removeEventListener('selectionchange', this.handleSelectionChange);
|
|
436
|
+
document.removeEventListener('click', this.handleControlStateChange, true);
|
|
375
437
|
document.removeEventListener('input', this.handleControlStateChange, true);
|
|
376
438
|
document.removeEventListener('change', this.handleControlStateChange, true);
|
|
377
439
|
this.restoreNavigationHooks();
|
|
@@ -381,6 +443,7 @@ class PageContextCollector {
|
|
|
381
443
|
this.pendingCapture = false;
|
|
382
444
|
this.deferredNavigation = null;
|
|
383
445
|
this.recentSelection = null;
|
|
446
|
+
this.latestSemanticSignature = null;
|
|
384
447
|
this.callbacks.onHighlight(null, 'focus');
|
|
385
448
|
}
|
|
386
449
|
async captureAndPublish() {
|
|
@@ -390,8 +453,10 @@ class PageContextCollector {
|
|
|
390
453
|
return snapshot;
|
|
391
454
|
}
|
|
392
455
|
capture() {
|
|
393
|
-
this.
|
|
394
|
-
|
|
456
|
+
return this.captureSnapshot(true);
|
|
457
|
+
}
|
|
458
|
+
captureSnapshot(force) {
|
|
459
|
+
const revision = this.revision + 1;
|
|
395
460
|
const nextTargets = new Map();
|
|
396
461
|
const targetValues = [];
|
|
397
462
|
const elements = document.querySelectorAll('a[href],button,input,select,textarea,summary,form,[contenteditable="true"],[role],[tabindex]');
|
|
@@ -444,6 +509,7 @@ class PageContextCollector {
|
|
|
444
509
|
const role = roleFor(element);
|
|
445
510
|
const sensitivity = sensitivityFor(element, name);
|
|
446
511
|
const visible = isVisible(element);
|
|
512
|
+
const destinationPath = safeDestinationPath(element);
|
|
447
513
|
const target = {
|
|
448
514
|
targetId,
|
|
449
515
|
revision,
|
|
@@ -456,6 +522,7 @@ class PageContextCollector {
|
|
|
456
522
|
? { autocomplete: normalize(element.getAttribute('autocomplete') ?? '', 128) }
|
|
457
523
|
: {}),
|
|
458
524
|
...(semanticRegion ? { semanticRegion } : {}),
|
|
525
|
+
...(destinationPath ? { destinationPath } : {}),
|
|
459
526
|
sensitivity,
|
|
460
527
|
protectedRegion: sensitivity !== 'none' || matchedRuleDigests.length > 0,
|
|
461
528
|
frameOrigin: 'same_origin',
|
|
@@ -478,7 +545,6 @@ class PageContextCollector {
|
|
|
478
545
|
targetValues.push(target);
|
|
479
546
|
nextTargets.set(targetId, { element, revision, options: optionMap, target });
|
|
480
547
|
}
|
|
481
|
-
this.targets = nextTargets;
|
|
482
548
|
const visibleText = [];
|
|
483
549
|
let totalText = 0;
|
|
484
550
|
const walker = document.createTreeWalker(document.body ?? document.documentElement, NodeFilter.SHOW_TEXT);
|
|
@@ -518,7 +584,7 @@ class PageContextCollector {
|
|
|
518
584
|
Date.now() - Date.parse(this.recentSelection.capturedAt) > SELECTION_RETENTION_MS) {
|
|
519
585
|
this.recentSelection = null;
|
|
520
586
|
}
|
|
521
|
-
|
|
587
|
+
const snapshot = {
|
|
522
588
|
schemaVersion: 1,
|
|
523
589
|
pageId: this.pageId,
|
|
524
590
|
revision,
|
|
@@ -537,6 +603,13 @@ class PageContextCollector {
|
|
|
537
603
|
alerts,
|
|
538
604
|
truncated,
|
|
539
605
|
};
|
|
606
|
+
const signature = this.semanticSignature(snapshot);
|
|
607
|
+
if (!force && signature === this.latestSemanticSignature)
|
|
608
|
+
return null;
|
|
609
|
+
this.revision = revision;
|
|
610
|
+
this.targets = nextTargets;
|
|
611
|
+
this.latestSemanticSignature = signature;
|
|
612
|
+
return snapshot;
|
|
540
613
|
}
|
|
541
614
|
captureVisualContext() {
|
|
542
615
|
if (this.revision < 1 || this.targets.size === 0)
|
|
@@ -659,7 +732,7 @@ class PageContextCollector {
|
|
|
659
732
|
element.focus({ preventScroll: false });
|
|
660
733
|
break;
|
|
661
734
|
case 'click':
|
|
662
|
-
this.click(element, grant.actionId, grant.desiredChecked);
|
|
735
|
+
this.click(element, grant.actionId, grant.desiredChecked, handle.target.destinationPath);
|
|
663
736
|
break;
|
|
664
737
|
case 'type':
|
|
665
738
|
this.type(element, grant.value ?? '');
|
|
@@ -737,7 +810,7 @@ class PageContextCollector {
|
|
|
737
810
|
? deferred.toUrl
|
|
738
811
|
: null;
|
|
739
812
|
}
|
|
740
|
-
click(element, actionId, desiredChecked) {
|
|
813
|
+
click(element, actionId, desiredChecked, expectedDestinationPath) {
|
|
741
814
|
if (!(element instanceof HTMLElement))
|
|
742
815
|
throw new Error('Target is not clickable.');
|
|
743
816
|
if (desiredChecked !== undefined) {
|
|
@@ -752,6 +825,10 @@ class PageContextCollector {
|
|
|
752
825
|
}
|
|
753
826
|
if (element instanceof HTMLAnchorElement) {
|
|
754
827
|
const target = new URL(element.href, window.location.href);
|
|
828
|
+
if (expectedDestinationPath !== undefined &&
|
|
829
|
+
safeDestinationPath(element) !== expectedDestinationPath) {
|
|
830
|
+
throw new Error('The navigation destination changed before execution.');
|
|
831
|
+
}
|
|
755
832
|
if (target.origin !== window.location.origin) {
|
|
756
833
|
throw new Error('External navigation is blocked.');
|
|
757
834
|
}
|
|
@@ -895,6 +972,26 @@ class PageContextCollector {
|
|
|
895
972
|
height,
|
|
896
973
|
};
|
|
897
974
|
}
|
|
975
|
+
async captureAndPublishIfChanged() {
|
|
976
|
+
const snapshot = this.captureSnapshot(false);
|
|
977
|
+
if (snapshot && this.running)
|
|
978
|
+
await this.callbacks.onSnapshot(snapshot);
|
|
979
|
+
}
|
|
980
|
+
semanticSignature(snapshot) {
|
|
981
|
+
return JSON.stringify({
|
|
982
|
+
...snapshot,
|
|
983
|
+
revision: 0,
|
|
984
|
+
...(snapshot.selection
|
|
985
|
+
? {
|
|
986
|
+
selection: {
|
|
987
|
+
text: snapshot.selection.text,
|
|
988
|
+
source: snapshot.selection.source,
|
|
989
|
+
},
|
|
990
|
+
}
|
|
991
|
+
: {}),
|
|
992
|
+
targets: snapshot.targets.map((target) => ({ ...target, revision: 0 })),
|
|
993
|
+
});
|
|
994
|
+
}
|
|
898
995
|
readSafeSelection() {
|
|
899
996
|
const selection = window.getSelection();
|
|
900
997
|
if (!selection || selection.isCollapsed || selection.rangeCount !== 1)
|
package/cjs/control.js
CHANGED
|
@@ -31,7 +31,7 @@ class ControlChannel {
|
|
|
31
31
|
}
|
|
32
32
|
setFencingToken(value) {
|
|
33
33
|
if (!Number.isSafeInteger(value) || value < 1) {
|
|
34
|
-
throw new Error('Invalid AI
|
|
34
|
+
throw new Error('Invalid AI Guide fencing token.');
|
|
35
35
|
}
|
|
36
36
|
this.fencingToken = value;
|
|
37
37
|
}
|
|
@@ -70,7 +70,7 @@ class ControlChannel {
|
|
|
70
70
|
socket.addEventListener('error', () => {
|
|
71
71
|
signal.removeEventListener('abort', abort);
|
|
72
72
|
if (socket.readyState !== WebSocket.OPEN) {
|
|
73
|
-
reject(new Error('The AI
|
|
73
|
+
reject(new Error('The AI Guide control channel could not connect.'));
|
|
74
74
|
}
|
|
75
75
|
}, { once: true });
|
|
76
76
|
socket.addEventListener('close', () => {
|
|
@@ -82,7 +82,7 @@ class ControlChannel {
|
|
|
82
82
|
}
|
|
83
83
|
send(type, payload) {
|
|
84
84
|
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
85
|
-
throw new Error('The AI
|
|
85
|
+
throw new Error('The AI Guide control channel is not connected.');
|
|
86
86
|
}
|
|
87
87
|
const id = `msg_${crypto.randomUUID()}`;
|
|
88
88
|
this.clientSequence += 1;
|
package/cjs/index.js
CHANGED
|
@@ -19,7 +19,7 @@ function aiLiveAssist(options = {}) {
|
|
|
19
19
|
return undefined;
|
|
20
20
|
}
|
|
21
21
|
if (!app.checkRequiredVersion?.('7.3.0')) {
|
|
22
|
-
console.warn('OXVO AI
|
|
22
|
+
console.warn('OXVO AI Guide requires @oxvo/browser version 7.3.0 or newer.');
|
|
23
23
|
return undefined;
|
|
24
24
|
}
|
|
25
25
|
const instance = new AiLiveAssist_js_1.default(app, options);
|
package/cjs/messages.js
CHANGED
|
@@ -3,12 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.defaultMessages = void 0;
|
|
4
4
|
exports.defaultMessages = {
|
|
5
5
|
launcher: "Ask AI",
|
|
6
|
-
introTitle: "AI
|
|
6
|
+
introTitle: "AI Guide",
|
|
7
7
|
disclosure: "You are interacting with an AI assistant.",
|
|
8
8
|
requiredConsent: "I understand that I am interacting with an AI assistant.",
|
|
9
|
-
consentAgreement: "I agree to use AI
|
|
9
|
+
consentAgreement: "I agree to use AI Guide.",
|
|
10
10
|
legalDetails: "Terms and privacy",
|
|
11
|
-
legalTitle: "AI
|
|
11
|
+
legalTitle: "AI Guide terms and privacy",
|
|
12
12
|
legalClose: "Close details",
|
|
13
13
|
termsOfService: "Terms of service",
|
|
14
14
|
microphoneConsent: "Send microphone audio to the AI provider for this session.",
|
|
@@ -38,8 +38,8 @@ exports.defaultMessages = {
|
|
|
38
38
|
close: "Close",
|
|
39
39
|
minimize: "Minimize",
|
|
40
40
|
maximize: "Expand conversation",
|
|
41
|
-
activeCall: "AI
|
|
42
|
-
movePanel: "Move AI
|
|
41
|
+
activeCall: "AI Guide is still active",
|
|
42
|
+
movePanel: "Move AI Guide",
|
|
43
43
|
mute: "Mute",
|
|
44
44
|
unmute: "Unmute",
|
|
45
45
|
interrupt: "Stop AI response",
|
|
@@ -66,19 +66,19 @@ exports.defaultMessages = {
|
|
|
66
66
|
actionVerified: "Action completed and verified",
|
|
67
67
|
actionFailed: "The action could not be verified",
|
|
68
68
|
limitReached: "This session reached its configured limit.",
|
|
69
|
-
ended: "This AI
|
|
69
|
+
ended: "This AI Guide session has ended.",
|
|
70
70
|
idleEnded: "This session ended after no visitor activity.",
|
|
71
71
|
feedbackQuestion: "Did this resolve your issue?",
|
|
72
72
|
resolved: "Resolved",
|
|
73
73
|
partiallyResolved: "Partly resolved",
|
|
74
74
|
notResolved: "Not resolved",
|
|
75
75
|
submitFeedback: "Submit feedback",
|
|
76
|
-
error: "AI
|
|
76
|
+
error: "AI Guide is temporarily unavailable.",
|
|
77
77
|
retry: "Retry",
|
|
78
|
-
activeElsewhere: "AI
|
|
79
|
-
visitorLimitReached: "The daily AI
|
|
80
|
-
visitorCooldown: "Please wait before starting another AI
|
|
81
|
-
visitorAlreadyActive: "An AI
|
|
78
|
+
activeElsewhere: "AI Guide is active in another tab.",
|
|
79
|
+
visitorLimitReached: "The daily AI Guide limit has been reached for this visitor.",
|
|
80
|
+
visitorCooldown: "Please wait before starting another AI Guide session.",
|
|
81
|
+
visitorAlreadyActive: "An AI Guide session is already active for this visitor.",
|
|
82
82
|
safetyBoundary: "Payment details, passwords, and verification codes are always handled by you. You can pause actions or end this session at any time.",
|
|
83
83
|
privacyPolicy: "Privacy policy",
|
|
84
84
|
moreControls: "More controls",
|
package/cjs/types.d.ts
CHANGED
package/cjs/ui.js
CHANGED
|
@@ -532,18 +532,21 @@ const style = `
|
|
|
532
532
|
.visual-request h3 { margin: 0 0 6px; font-size: 14px; line-height: 20px; }
|
|
533
533
|
.visual-request p { margin: 4px 0; color: #4b5563; font-size: 12px; line-height: 17px; }
|
|
534
534
|
.feedback { display: none; }
|
|
535
|
-
.feedback.visible { display: grid; gap:
|
|
535
|
+
.feedback.visible { display: grid; gap: 10px; margin-top: 10px; padding: 8px 0 4px; }
|
|
536
536
|
.attribution-bubble {
|
|
537
|
+
justify-self: center;
|
|
537
538
|
width: max-content;
|
|
538
539
|
max-width: 100%;
|
|
539
|
-
|
|
540
|
+
margin-top: 2px;
|
|
541
|
+
padding: 0;
|
|
540
542
|
color: var(--assist-muted);
|
|
541
|
-
background:
|
|
542
|
-
border:
|
|
543
|
-
border-radius:
|
|
544
|
-
box-shadow:
|
|
545
|
-
font-size:
|
|
546
|
-
line-height:
|
|
543
|
+
background: transparent;
|
|
544
|
+
border: 0;
|
|
545
|
+
border-radius: 0;
|
|
546
|
+
box-shadow: none;
|
|
547
|
+
font-size: 11px;
|
|
548
|
+
line-height: 16px;
|
|
549
|
+
text-align: center;
|
|
547
550
|
animation: attribution-in .18s ease-out both;
|
|
548
551
|
}
|
|
549
552
|
.attribution-bubble a {
|
|
@@ -1475,13 +1478,13 @@ class WidgetView {
|
|
|
1475
1478
|
<div class="status-card" data-role="notice"></div>
|
|
1476
1479
|
<button type="button" class="secondary" data-role="retry" hidden></button>
|
|
1477
1480
|
<div class="feedback">
|
|
1478
|
-
<div class="attribution-bubble" data-role="attribution" role="status" hidden>Powered by <a href="https://oxvo.com/" target="_blank" rel="noopener noreferrer">OXVO.com</a></div>
|
|
1479
1481
|
<strong></strong>
|
|
1480
1482
|
<div class="feedback-options">
|
|
1481
1483
|
<button type="button" class="secondary" data-outcome="resolved"></button>
|
|
1482
1484
|
<button type="button" class="secondary" data-outcome="partially_resolved"></button>
|
|
1483
1485
|
<button type="button" class="secondary" data-outcome="not_resolved"></button>
|
|
1484
1486
|
</div>
|
|
1487
|
+
<div class="attribution-bubble" data-role="attribution" role="status" hidden>Powered by <a href="https://oxvo.com/" target="_blank" rel="noopener noreferrer">OXVO.com</a></div>
|
|
1485
1488
|
</div>
|
|
1486
1489
|
</div>
|
|
1487
1490
|
<footer class="controls">
|
|
@@ -2346,7 +2349,7 @@ class WidgetView {
|
|
|
2346
2349
|
require(selector) {
|
|
2347
2350
|
const element = this.shadow.querySelector(selector);
|
|
2348
2351
|
if (!element)
|
|
2349
|
-
throw new Error(`Missing AI
|
|
2352
|
+
throw new Error(`Missing AI Guide UI element: ${selector}`);
|
|
2350
2353
|
return element;
|
|
2351
2354
|
}
|
|
2352
2355
|
}
|
package/cjs/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "7.3.
|
|
1
|
+
export declare const VERSION = "7.3.4";
|
package/cjs/version.js
CHANGED
package/lib/AiLiveAssist.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare const confirmationReplyIntent: (value: string) => ConfirmationRep
|
|
|
5
5
|
export default class AiLiveAssist {
|
|
6
6
|
private readonly app;
|
|
7
7
|
private readonly options;
|
|
8
|
-
readonly version = "7.3.
|
|
8
|
+
readonly version = "7.3.4";
|
|
9
9
|
private readonly siteKey;
|
|
10
10
|
private tabId;
|
|
11
11
|
private readonly windowId;
|
package/lib/AiLiveAssist.js
CHANGED
|
@@ -1667,7 +1667,7 @@ export default class AiLiveAssist {
|
|
|
1667
1667
|
? 'Microphone access was not granted. Allow microphone access to use voice assist.'
|
|
1668
1668
|
: 'Microphone access was not granted. You can continue with text.';
|
|
1669
1669
|
}
|
|
1670
|
-
return 'AI
|
|
1670
|
+
return 'AI Guide is temporarily unavailable.';
|
|
1671
1671
|
}
|
|
1672
1672
|
retryable(error) {
|
|
1673
1673
|
return error instanceof RuntimeClientError ? error.retryable : false;
|
package/lib/client.js
CHANGED
|
@@ -15,7 +15,7 @@ export class RuntimeClient {
|
|
|
15
15
|
this.clientVersion = clientVersion;
|
|
16
16
|
const url = new URL(runtimeUrl, window.location.href);
|
|
17
17
|
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
18
|
-
throw new Error('AI
|
|
18
|
+
throw new Error('AI Guide runtime URL must use HTTP or HTTPS.');
|
|
19
19
|
}
|
|
20
20
|
this.baseUrl = url.origin + url.pathname.replace(/\/$/, '');
|
|
21
21
|
}
|
|
@@ -182,6 +182,6 @@ export class RuntimeClient {
|
|
|
182
182
|
}
|
|
183
183
|
throw new RuntimeClientError(typeof error.code === 'string' ? error.code : 'RUNTIME_UNAVAILABLE', typeof error.message === 'string'
|
|
184
184
|
? error.message
|
|
185
|
-
: 'AI
|
|
185
|
+
: 'AI Guide is temporarily unavailable.', error.retryable === true, typeof error.retryAfterMs === 'number' ? error.retryAfterMs : null, response.status);
|
|
186
186
|
}
|
|
187
187
|
}
|
package/lib/context.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export declare class PageContextCollector {
|
|
|
17
17
|
private running;
|
|
18
18
|
private mutationObserver;
|
|
19
19
|
private captureTimer;
|
|
20
|
+
private mutationCaptureTimer;
|
|
20
21
|
private targetIds;
|
|
21
22
|
private textIds;
|
|
22
23
|
private optionIds;
|
|
@@ -28,6 +29,7 @@ export declare class PageContextCollector {
|
|
|
28
29
|
private pendingCapture;
|
|
29
30
|
private deferredNavigation;
|
|
30
31
|
private recentSelection;
|
|
32
|
+
private latestSemanticSignature;
|
|
31
33
|
constructor(callbacks: ContextCallbacks, selectorRegionRules?: SelectorRegionRule[], policyVersion?: number);
|
|
32
34
|
get currentRevision(): number;
|
|
33
35
|
get currentPageId(): string;
|
|
@@ -36,6 +38,7 @@ export declare class PageContextCollector {
|
|
|
36
38
|
stop(): void;
|
|
37
39
|
captureAndPublish(): Promise<PageContextSnapshot>;
|
|
38
40
|
capture(): PageContextSnapshot;
|
|
41
|
+
private captureSnapshot;
|
|
39
42
|
captureVisualContext(): VisualContextCapture | null;
|
|
40
43
|
execute(grant: ActionGrant): Promise<ActionExecutionResult>;
|
|
41
44
|
completeActionExecution(actionId: string): Promise<string | null>;
|
|
@@ -47,6 +50,11 @@ export declare class PageContextCollector {
|
|
|
47
50
|
private actionApplied;
|
|
48
51
|
private clippedGeometry;
|
|
49
52
|
private scheduleCapture;
|
|
53
|
+
private scheduleMutationCapture;
|
|
54
|
+
private scheduleViewportCapture;
|
|
55
|
+
private handleMutations;
|
|
56
|
+
private captureAndPublishIfChanged;
|
|
57
|
+
private semanticSignature;
|
|
50
58
|
private handleSelectionChange;
|
|
51
59
|
private handleControlStateChange;
|
|
52
60
|
private readSafeSelection;
|
package/lib/context.js
CHANGED
|
@@ -5,6 +5,8 @@ const MAX_TOTAL_TEXT = 24000;
|
|
|
5
5
|
const MAX_OPTIONS = 100;
|
|
6
6
|
const MAX_SELECTED_TEXT = 2000;
|
|
7
7
|
const SELECTION_RETENTION_MS = 120000;
|
|
8
|
+
const CAPTURE_SETTLE_MS = 250;
|
|
9
|
+
const MUTATION_CAPTURE_DEBOUNCE_MS = 350;
|
|
8
10
|
const PRIVATE_SELECTOR = [
|
|
9
11
|
'[data-oxvo-ai-live-assist-root]',
|
|
10
12
|
'[data-oxvo-private]',
|
|
@@ -63,6 +65,25 @@ const isCovered = (element) => {
|
|
|
63
65
|
return Boolean(top && top !== element && !element.contains(top) && !top.contains(element));
|
|
64
66
|
};
|
|
65
67
|
const safePageUrl = () => `${window.location.origin}${window.location.pathname}`;
|
|
68
|
+
const safeDestinationPath = (element) => {
|
|
69
|
+
if (!(element instanceof HTMLAnchorElement))
|
|
70
|
+
return null;
|
|
71
|
+
try {
|
|
72
|
+
const destination = new URL(element.href, window.location.href);
|
|
73
|
+
if (destination.origin !== window.location.origin ||
|
|
74
|
+
destination.search ||
|
|
75
|
+
destination.hash ||
|
|
76
|
+
!destination.pathname.startsWith('/') ||
|
|
77
|
+
destination.pathname.startsWith('//') ||
|
|
78
|
+
destination.pathname.length > 1024) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return destination.pathname;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
66
87
|
const sensitivityFor = (element, name) => {
|
|
67
88
|
const input = element instanceof HTMLInputElement ? element : null;
|
|
68
89
|
const type = input?.type.toLowerCase() ?? '';
|
|
@@ -232,6 +253,7 @@ export class PageContextCollector {
|
|
|
232
253
|
this.running = false;
|
|
233
254
|
this.mutationObserver = null;
|
|
234
255
|
this.captureTimer = null;
|
|
256
|
+
this.mutationCaptureTimer = null;
|
|
235
257
|
this.targetIds = new WeakMap();
|
|
236
258
|
this.textIds = new WeakMap();
|
|
237
259
|
this.optionIds = new WeakMap();
|
|
@@ -243,6 +265,7 @@ export class PageContextCollector {
|
|
|
243
265
|
this.pendingCapture = false;
|
|
244
266
|
this.deferredNavigation = null;
|
|
245
267
|
this.recentSelection = null;
|
|
268
|
+
this.latestSemanticSignature = null;
|
|
246
269
|
this.scheduleCapture = () => {
|
|
247
270
|
if (!this.running)
|
|
248
271
|
return;
|
|
@@ -250,20 +273,53 @@ export class PageContextCollector {
|
|
|
250
273
|
this.pendingCapture = true;
|
|
251
274
|
return;
|
|
252
275
|
}
|
|
276
|
+
if (this.mutationCaptureTimer)
|
|
277
|
+
clearTimeout(this.mutationCaptureTimer);
|
|
278
|
+
this.mutationCaptureTimer = null;
|
|
253
279
|
if (this.captureTimer)
|
|
254
280
|
return;
|
|
255
281
|
this.captureTimer = setTimeout(() => {
|
|
256
282
|
this.captureTimer = null;
|
|
257
|
-
void this.
|
|
258
|
-
},
|
|
283
|
+
void this.captureAndPublishIfChanged();
|
|
284
|
+
}, CAPTURE_SETTLE_MS);
|
|
285
|
+
};
|
|
286
|
+
this.scheduleMutationCapture = () => {
|
|
287
|
+
if (!this.running)
|
|
288
|
+
return;
|
|
289
|
+
if (this.executingActionId) {
|
|
290
|
+
this.pendingCapture = true;
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (this.captureTimer)
|
|
294
|
+
return;
|
|
295
|
+
if (this.mutationCaptureTimer)
|
|
296
|
+
clearTimeout(this.mutationCaptureTimer);
|
|
297
|
+
this.mutationCaptureTimer = setTimeout(() => {
|
|
298
|
+
this.mutationCaptureTimer = null;
|
|
299
|
+
void this.captureAndPublishIfChanged();
|
|
300
|
+
}, MUTATION_CAPTURE_DEBOUNCE_MS);
|
|
301
|
+
};
|
|
302
|
+
this.scheduleViewportCapture = () => {
|
|
303
|
+
this.scheduleMutationCapture();
|
|
304
|
+
};
|
|
305
|
+
this.handleMutations = (records) => {
|
|
306
|
+
const hasSemanticMutation = records.some((record) => record.type !== 'attributes' ||
|
|
307
|
+
(record.attributeName !== 'class' && record.attributeName !== 'style'));
|
|
308
|
+
if (hasSemanticMutation)
|
|
309
|
+
this.scheduleCapture();
|
|
310
|
+
else
|
|
311
|
+
this.scheduleMutationCapture();
|
|
259
312
|
};
|
|
260
313
|
this.handleSelectionChange = () => {
|
|
261
314
|
const selection = this.readSafeSelection();
|
|
262
315
|
if (selection)
|
|
263
316
|
this.recentSelection = selection;
|
|
264
|
-
this.
|
|
317
|
+
this.scheduleMutationCapture();
|
|
265
318
|
};
|
|
266
|
-
this.handleControlStateChange = () => {
|
|
319
|
+
this.handleControlStateChange = (event) => {
|
|
320
|
+
if (event.target instanceof Element && event.target.closest('[data-oxvo-ai-live-assist-root]')) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
267
323
|
this.scheduleCapture();
|
|
268
324
|
};
|
|
269
325
|
this.handleNavigation = () => {
|
|
@@ -318,7 +374,7 @@ export class PageContextCollector {
|
|
|
318
374
|
return;
|
|
319
375
|
this.running = true;
|
|
320
376
|
this.installNavigationHooks();
|
|
321
|
-
this.mutationObserver = new MutationObserver(
|
|
377
|
+
this.mutationObserver = new MutationObserver(this.handleMutations);
|
|
322
378
|
if (document.body) {
|
|
323
379
|
this.mutationObserver.observe(document.body, {
|
|
324
380
|
subtree: true,
|
|
@@ -332,6 +388,7 @@ export class PageContextCollector {
|
|
|
332
388
|
'aria-live',
|
|
333
389
|
'class',
|
|
334
390
|
'disabled',
|
|
391
|
+
'href',
|
|
335
392
|
'hidden',
|
|
336
393
|
'inert',
|
|
337
394
|
'role',
|
|
@@ -339,19 +396,20 @@ export class PageContextCollector {
|
|
|
339
396
|
],
|
|
340
397
|
});
|
|
341
398
|
}
|
|
342
|
-
window.addEventListener('resize', this.
|
|
343
|
-
document.addEventListener('scroll', this.
|
|
399
|
+
window.addEventListener('resize', this.scheduleViewportCapture, { passive: true });
|
|
400
|
+
document.addEventListener('scroll', this.scheduleViewportCapture, {
|
|
344
401
|
capture: true,
|
|
345
402
|
passive: true,
|
|
346
403
|
});
|
|
347
|
-
window.visualViewport?.addEventListener('resize', this.
|
|
404
|
+
window.visualViewport?.addEventListener('resize', this.scheduleViewportCapture, {
|
|
348
405
|
passive: true,
|
|
349
406
|
});
|
|
350
|
-
window.visualViewport?.addEventListener('scroll', this.
|
|
407
|
+
window.visualViewport?.addEventListener('scroll', this.scheduleViewportCapture, {
|
|
351
408
|
passive: true,
|
|
352
409
|
});
|
|
353
410
|
window.addEventListener('popstate', this.handleNavigation);
|
|
354
411
|
document.addEventListener('selectionchange', this.handleSelectionChange);
|
|
412
|
+
document.addEventListener('click', this.handleControlStateChange, true);
|
|
355
413
|
document.addEventListener('input', this.handleControlStateChange, true);
|
|
356
414
|
document.addEventListener('change', this.handleControlStateChange, true);
|
|
357
415
|
void this.captureAndPublish();
|
|
@@ -363,12 +421,16 @@ export class PageContextCollector {
|
|
|
363
421
|
if (this.captureTimer)
|
|
364
422
|
clearTimeout(this.captureTimer);
|
|
365
423
|
this.captureTimer = null;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
window.
|
|
424
|
+
if (this.mutationCaptureTimer)
|
|
425
|
+
clearTimeout(this.mutationCaptureTimer);
|
|
426
|
+
this.mutationCaptureTimer = null;
|
|
427
|
+
window.removeEventListener('resize', this.scheduleViewportCapture);
|
|
428
|
+
document.removeEventListener('scroll', this.scheduleViewportCapture, true);
|
|
429
|
+
window.visualViewport?.removeEventListener('resize', this.scheduleViewportCapture);
|
|
430
|
+
window.visualViewport?.removeEventListener('scroll', this.scheduleViewportCapture);
|
|
370
431
|
window.removeEventListener('popstate', this.handleNavigation);
|
|
371
432
|
document.removeEventListener('selectionchange', this.handleSelectionChange);
|
|
433
|
+
document.removeEventListener('click', this.handleControlStateChange, true);
|
|
372
434
|
document.removeEventListener('input', this.handleControlStateChange, true);
|
|
373
435
|
document.removeEventListener('change', this.handleControlStateChange, true);
|
|
374
436
|
this.restoreNavigationHooks();
|
|
@@ -378,6 +440,7 @@ export class PageContextCollector {
|
|
|
378
440
|
this.pendingCapture = false;
|
|
379
441
|
this.deferredNavigation = null;
|
|
380
442
|
this.recentSelection = null;
|
|
443
|
+
this.latestSemanticSignature = null;
|
|
381
444
|
this.callbacks.onHighlight(null, 'focus');
|
|
382
445
|
}
|
|
383
446
|
async captureAndPublish() {
|
|
@@ -387,8 +450,10 @@ export class PageContextCollector {
|
|
|
387
450
|
return snapshot;
|
|
388
451
|
}
|
|
389
452
|
capture() {
|
|
390
|
-
this.
|
|
391
|
-
|
|
453
|
+
return this.captureSnapshot(true);
|
|
454
|
+
}
|
|
455
|
+
captureSnapshot(force) {
|
|
456
|
+
const revision = this.revision + 1;
|
|
392
457
|
const nextTargets = new Map();
|
|
393
458
|
const targetValues = [];
|
|
394
459
|
const elements = document.querySelectorAll('a[href],button,input,select,textarea,summary,form,[contenteditable="true"],[role],[tabindex]');
|
|
@@ -441,6 +506,7 @@ export class PageContextCollector {
|
|
|
441
506
|
const role = roleFor(element);
|
|
442
507
|
const sensitivity = sensitivityFor(element, name);
|
|
443
508
|
const visible = isVisible(element);
|
|
509
|
+
const destinationPath = safeDestinationPath(element);
|
|
444
510
|
const target = {
|
|
445
511
|
targetId,
|
|
446
512
|
revision,
|
|
@@ -453,6 +519,7 @@ export class PageContextCollector {
|
|
|
453
519
|
? { autocomplete: normalize(element.getAttribute('autocomplete') ?? '', 128) }
|
|
454
520
|
: {}),
|
|
455
521
|
...(semanticRegion ? { semanticRegion } : {}),
|
|
522
|
+
...(destinationPath ? { destinationPath } : {}),
|
|
456
523
|
sensitivity,
|
|
457
524
|
protectedRegion: sensitivity !== 'none' || matchedRuleDigests.length > 0,
|
|
458
525
|
frameOrigin: 'same_origin',
|
|
@@ -475,7 +542,6 @@ export class PageContextCollector {
|
|
|
475
542
|
targetValues.push(target);
|
|
476
543
|
nextTargets.set(targetId, { element, revision, options: optionMap, target });
|
|
477
544
|
}
|
|
478
|
-
this.targets = nextTargets;
|
|
479
545
|
const visibleText = [];
|
|
480
546
|
let totalText = 0;
|
|
481
547
|
const walker = document.createTreeWalker(document.body ?? document.documentElement, NodeFilter.SHOW_TEXT);
|
|
@@ -515,7 +581,7 @@ export class PageContextCollector {
|
|
|
515
581
|
Date.now() - Date.parse(this.recentSelection.capturedAt) > SELECTION_RETENTION_MS) {
|
|
516
582
|
this.recentSelection = null;
|
|
517
583
|
}
|
|
518
|
-
|
|
584
|
+
const snapshot = {
|
|
519
585
|
schemaVersion: 1,
|
|
520
586
|
pageId: this.pageId,
|
|
521
587
|
revision,
|
|
@@ -534,6 +600,13 @@ export class PageContextCollector {
|
|
|
534
600
|
alerts,
|
|
535
601
|
truncated,
|
|
536
602
|
};
|
|
603
|
+
const signature = this.semanticSignature(snapshot);
|
|
604
|
+
if (!force && signature === this.latestSemanticSignature)
|
|
605
|
+
return null;
|
|
606
|
+
this.revision = revision;
|
|
607
|
+
this.targets = nextTargets;
|
|
608
|
+
this.latestSemanticSignature = signature;
|
|
609
|
+
return snapshot;
|
|
537
610
|
}
|
|
538
611
|
captureVisualContext() {
|
|
539
612
|
if (this.revision < 1 || this.targets.size === 0)
|
|
@@ -656,7 +729,7 @@ export class PageContextCollector {
|
|
|
656
729
|
element.focus({ preventScroll: false });
|
|
657
730
|
break;
|
|
658
731
|
case 'click':
|
|
659
|
-
this.click(element, grant.actionId, grant.desiredChecked);
|
|
732
|
+
this.click(element, grant.actionId, grant.desiredChecked, handle.target.destinationPath);
|
|
660
733
|
break;
|
|
661
734
|
case 'type':
|
|
662
735
|
this.type(element, grant.value ?? '');
|
|
@@ -734,7 +807,7 @@ export class PageContextCollector {
|
|
|
734
807
|
? deferred.toUrl
|
|
735
808
|
: null;
|
|
736
809
|
}
|
|
737
|
-
click(element, actionId, desiredChecked) {
|
|
810
|
+
click(element, actionId, desiredChecked, expectedDestinationPath) {
|
|
738
811
|
if (!(element instanceof HTMLElement))
|
|
739
812
|
throw new Error('Target is not clickable.');
|
|
740
813
|
if (desiredChecked !== undefined) {
|
|
@@ -749,6 +822,10 @@ export class PageContextCollector {
|
|
|
749
822
|
}
|
|
750
823
|
if (element instanceof HTMLAnchorElement) {
|
|
751
824
|
const target = new URL(element.href, window.location.href);
|
|
825
|
+
if (expectedDestinationPath !== undefined &&
|
|
826
|
+
safeDestinationPath(element) !== expectedDestinationPath) {
|
|
827
|
+
throw new Error('The navigation destination changed before execution.');
|
|
828
|
+
}
|
|
752
829
|
if (target.origin !== window.location.origin) {
|
|
753
830
|
throw new Error('External navigation is blocked.');
|
|
754
831
|
}
|
|
@@ -892,6 +969,26 @@ export class PageContextCollector {
|
|
|
892
969
|
height,
|
|
893
970
|
};
|
|
894
971
|
}
|
|
972
|
+
async captureAndPublishIfChanged() {
|
|
973
|
+
const snapshot = this.captureSnapshot(false);
|
|
974
|
+
if (snapshot && this.running)
|
|
975
|
+
await this.callbacks.onSnapshot(snapshot);
|
|
976
|
+
}
|
|
977
|
+
semanticSignature(snapshot) {
|
|
978
|
+
return JSON.stringify({
|
|
979
|
+
...snapshot,
|
|
980
|
+
revision: 0,
|
|
981
|
+
...(snapshot.selection
|
|
982
|
+
? {
|
|
983
|
+
selection: {
|
|
984
|
+
text: snapshot.selection.text,
|
|
985
|
+
source: snapshot.selection.source,
|
|
986
|
+
},
|
|
987
|
+
}
|
|
988
|
+
: {}),
|
|
989
|
+
targets: snapshot.targets.map((target) => ({ ...target, revision: 0 })),
|
|
990
|
+
});
|
|
991
|
+
}
|
|
895
992
|
readSafeSelection() {
|
|
896
993
|
const selection = window.getSelection();
|
|
897
994
|
if (!selection || selection.isCollapsed || selection.rangeCount !== 1)
|
package/lib/control.js
CHANGED
|
@@ -28,7 +28,7 @@ export class ControlChannel {
|
|
|
28
28
|
}
|
|
29
29
|
setFencingToken(value) {
|
|
30
30
|
if (!Number.isSafeInteger(value) || value < 1) {
|
|
31
|
-
throw new Error('Invalid AI
|
|
31
|
+
throw new Error('Invalid AI Guide fencing token.');
|
|
32
32
|
}
|
|
33
33
|
this.fencingToken = value;
|
|
34
34
|
}
|
|
@@ -67,7 +67,7 @@ export class ControlChannel {
|
|
|
67
67
|
socket.addEventListener('error', () => {
|
|
68
68
|
signal.removeEventListener('abort', abort);
|
|
69
69
|
if (socket.readyState !== WebSocket.OPEN) {
|
|
70
|
-
reject(new Error('The AI
|
|
70
|
+
reject(new Error('The AI Guide control channel could not connect.'));
|
|
71
71
|
}
|
|
72
72
|
}, { once: true });
|
|
73
73
|
socket.addEventListener('close', () => {
|
|
@@ -79,7 +79,7 @@ export class ControlChannel {
|
|
|
79
79
|
}
|
|
80
80
|
send(type, payload) {
|
|
81
81
|
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
82
|
-
throw new Error('The AI
|
|
82
|
+
throw new Error('The AI Guide control channel is not connected.');
|
|
83
83
|
}
|
|
84
84
|
const id = `msg_${crypto.randomUUID()}`;
|
|
85
85
|
this.clientSequence += 1;
|
package/lib/index.js
CHANGED
|
@@ -13,7 +13,7 @@ export default function aiLiveAssist(options = {}) {
|
|
|
13
13
|
return undefined;
|
|
14
14
|
}
|
|
15
15
|
if (!app.checkRequiredVersion?.('7.3.0')) {
|
|
16
|
-
console.warn('OXVO AI
|
|
16
|
+
console.warn('OXVO AI Guide requires @oxvo/browser version 7.3.0 or newer.');
|
|
17
17
|
return undefined;
|
|
18
18
|
}
|
|
19
19
|
const instance = new AiLiveAssist(app, options);
|
package/lib/messages.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export const defaultMessages = {
|
|
2
2
|
launcher: "Ask AI",
|
|
3
|
-
introTitle: "AI
|
|
3
|
+
introTitle: "AI Guide",
|
|
4
4
|
disclosure: "You are interacting with an AI assistant.",
|
|
5
5
|
requiredConsent: "I understand that I am interacting with an AI assistant.",
|
|
6
|
-
consentAgreement: "I agree to use AI
|
|
6
|
+
consentAgreement: "I agree to use AI Guide.",
|
|
7
7
|
legalDetails: "Terms and privacy",
|
|
8
|
-
legalTitle: "AI
|
|
8
|
+
legalTitle: "AI Guide terms and privacy",
|
|
9
9
|
legalClose: "Close details",
|
|
10
10
|
termsOfService: "Terms of service",
|
|
11
11
|
microphoneConsent: "Send microphone audio to the AI provider for this session.",
|
|
@@ -35,8 +35,8 @@ export const defaultMessages = {
|
|
|
35
35
|
close: "Close",
|
|
36
36
|
minimize: "Minimize",
|
|
37
37
|
maximize: "Expand conversation",
|
|
38
|
-
activeCall: "AI
|
|
39
|
-
movePanel: "Move AI
|
|
38
|
+
activeCall: "AI Guide is still active",
|
|
39
|
+
movePanel: "Move AI Guide",
|
|
40
40
|
mute: "Mute",
|
|
41
41
|
unmute: "Unmute",
|
|
42
42
|
interrupt: "Stop AI response",
|
|
@@ -63,19 +63,19 @@ export const defaultMessages = {
|
|
|
63
63
|
actionVerified: "Action completed and verified",
|
|
64
64
|
actionFailed: "The action could not be verified",
|
|
65
65
|
limitReached: "This session reached its configured limit.",
|
|
66
|
-
ended: "This AI
|
|
66
|
+
ended: "This AI Guide session has ended.",
|
|
67
67
|
idleEnded: "This session ended after no visitor activity.",
|
|
68
68
|
feedbackQuestion: "Did this resolve your issue?",
|
|
69
69
|
resolved: "Resolved",
|
|
70
70
|
partiallyResolved: "Partly resolved",
|
|
71
71
|
notResolved: "Not resolved",
|
|
72
72
|
submitFeedback: "Submit feedback",
|
|
73
|
-
error: "AI
|
|
73
|
+
error: "AI Guide is temporarily unavailable.",
|
|
74
74
|
retry: "Retry",
|
|
75
|
-
activeElsewhere: "AI
|
|
76
|
-
visitorLimitReached: "The daily AI
|
|
77
|
-
visitorCooldown: "Please wait before starting another AI
|
|
78
|
-
visitorAlreadyActive: "An AI
|
|
75
|
+
activeElsewhere: "AI Guide is active in another tab.",
|
|
76
|
+
visitorLimitReached: "The daily AI Guide limit has been reached for this visitor.",
|
|
77
|
+
visitorCooldown: "Please wait before starting another AI Guide session.",
|
|
78
|
+
visitorAlreadyActive: "An AI Guide session is already active for this visitor.",
|
|
79
79
|
safetyBoundary: "Payment details, passwords, and verification codes are always handled by you. You can pause actions or end this session at any time.",
|
|
80
80
|
privacyPolicy: "Privacy policy",
|
|
81
81
|
moreControls: "More controls",
|
package/lib/types.d.ts
CHANGED
package/lib/ui.js
CHANGED
|
@@ -529,18 +529,21 @@ const style = `
|
|
|
529
529
|
.visual-request h3 { margin: 0 0 6px; font-size: 14px; line-height: 20px; }
|
|
530
530
|
.visual-request p { margin: 4px 0; color: #4b5563; font-size: 12px; line-height: 17px; }
|
|
531
531
|
.feedback { display: none; }
|
|
532
|
-
.feedback.visible { display: grid; gap:
|
|
532
|
+
.feedback.visible { display: grid; gap: 10px; margin-top: 10px; padding: 8px 0 4px; }
|
|
533
533
|
.attribution-bubble {
|
|
534
|
+
justify-self: center;
|
|
534
535
|
width: max-content;
|
|
535
536
|
max-width: 100%;
|
|
536
|
-
|
|
537
|
+
margin-top: 2px;
|
|
538
|
+
padding: 0;
|
|
537
539
|
color: var(--assist-muted);
|
|
538
|
-
background:
|
|
539
|
-
border:
|
|
540
|
-
border-radius:
|
|
541
|
-
box-shadow:
|
|
542
|
-
font-size:
|
|
543
|
-
line-height:
|
|
540
|
+
background: transparent;
|
|
541
|
+
border: 0;
|
|
542
|
+
border-radius: 0;
|
|
543
|
+
box-shadow: none;
|
|
544
|
+
font-size: 11px;
|
|
545
|
+
line-height: 16px;
|
|
546
|
+
text-align: center;
|
|
544
547
|
animation: attribution-in .18s ease-out both;
|
|
545
548
|
}
|
|
546
549
|
.attribution-bubble a {
|
|
@@ -1472,13 +1475,13 @@ export class WidgetView {
|
|
|
1472
1475
|
<div class="status-card" data-role="notice"></div>
|
|
1473
1476
|
<button type="button" class="secondary" data-role="retry" hidden></button>
|
|
1474
1477
|
<div class="feedback">
|
|
1475
|
-
<div class="attribution-bubble" data-role="attribution" role="status" hidden>Powered by <a href="https://oxvo.com/" target="_blank" rel="noopener noreferrer">OXVO.com</a></div>
|
|
1476
1478
|
<strong></strong>
|
|
1477
1479
|
<div class="feedback-options">
|
|
1478
1480
|
<button type="button" class="secondary" data-outcome="resolved"></button>
|
|
1479
1481
|
<button type="button" class="secondary" data-outcome="partially_resolved"></button>
|
|
1480
1482
|
<button type="button" class="secondary" data-outcome="not_resolved"></button>
|
|
1481
1483
|
</div>
|
|
1484
|
+
<div class="attribution-bubble" data-role="attribution" role="status" hidden>Powered by <a href="https://oxvo.com/" target="_blank" rel="noopener noreferrer">OXVO.com</a></div>
|
|
1482
1485
|
</div>
|
|
1483
1486
|
</div>
|
|
1484
1487
|
<footer class="controls">
|
|
@@ -2343,7 +2346,7 @@ export class WidgetView {
|
|
|
2343
2346
|
require(selector) {
|
|
2344
2347
|
const element = this.shadow.querySelector(selector);
|
|
2345
2348
|
if (!element)
|
|
2346
|
-
throw new Error(`Missing AI
|
|
2349
|
+
throw new Error(`Missing AI Guide UI element: ${selector}`);
|
|
2347
2350
|
return element;
|
|
2348
2351
|
}
|
|
2349
2352
|
}
|
package/lib/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "7.3.
|
|
1
|
+
export declare const VERSION = "7.3.4";
|
package/lib/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '7.3.
|
|
1
|
+
export const VERSION = '7.3.4';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxvo/ai-live-assist",
|
|
3
|
-
"description": "Secure AI
|
|
4
|
-
"version": "7.3.
|
|
3
|
+
"description": "Secure AI Guide browser plugin for OXVO Sessions.",
|
|
4
|
+
"version": "7.3.4",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-live-assist",
|
|
7
7
|
"webrtc",
|