@qontinui/ui-bridge 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/index.d.mts +312 -155
- package/dist/ai/index.d.ts +312 -155
- package/dist/ai/index.js +2363 -67
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/index.mjs +2328 -68
- package/dist/ai/index.mjs.map +1 -1
- package/dist/annotations/index.d.mts +218 -0
- package/dist/annotations/index.d.ts +218 -0
- package/dist/annotations/index.js +246 -0
- package/dist/annotations/index.js.map +1 -0
- package/dist/annotations/index.mjs +241 -0
- package/dist/annotations/index.mjs.map +1 -0
- package/dist/assertions-BSR3afVr.d.ts +161 -0
- package/dist/assertions-CTw1hfOx.d.mts +161 -0
- package/dist/babel-plugin/index.js +23 -34
- package/dist/babel-plugin/index.js.map +1 -1
- package/dist/babel-plugin/index.mjs +23 -34
- package/dist/babel-plugin/index.mjs.map +1 -1
- package/dist/browser-capture-Bms60T6f.d.mts +47 -0
- package/dist/browser-capture-CsTU29mb.d.ts +47 -0
- package/dist/control/index.d.mts +26 -7
- package/dist/control/index.d.ts +26 -7
- package/dist/control/index.js +276 -48
- package/dist/control/index.js.map +1 -1
- package/dist/control/index.mjs +276 -48
- package/dist/control/index.mjs.map +1 -1
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs.map +1 -1
- package/dist/debug/index.d.mts +5 -3
- package/dist/debug/index.d.ts +5 -3
- package/dist/debug/index.js +925 -1
- package/dist/debug/index.js.map +1 -1
- package/dist/debug/index.mjs +924 -2
- package/dist/debug/index.mjs.map +1 -1
- package/dist/index.d.mts +12 -7
- package/dist/index.d.ts +12 -7
- package/dist/index.js +4720 -173
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4656 -174
- package/dist/index.mjs.map +1 -1
- package/dist/{metrics-DTA2bwG7.d.mts → metrics-DuA2qIIz.d.mts} +2 -2
- package/dist/{metrics-BfiT_rhZ.d.ts → metrics-KFAAKNEB.d.ts} +2 -2
- package/dist/native/control/index.js +2 -7
- package/dist/native/control/index.js.map +1 -1
- package/dist/native/control/index.mjs +2 -7
- package/dist/native/control/index.mjs.map +1 -1
- package/dist/native/core/index.js.map +1 -1
- package/dist/native/core/index.mjs.map +1 -1
- package/dist/native/debug/index.js +23 -66
- package/dist/native/debug/index.js.map +1 -1
- package/dist/native/debug/index.mjs +23 -66
- package/dist/native/debug/index.mjs.map +1 -1
- package/dist/native/index.js +89 -131
- package/dist/native/index.js.map +1 -1
- package/dist/native/index.mjs +89 -131
- package/dist/native/index.mjs.map +1 -1
- package/dist/native/react/index.js +28 -52
- package/dist/native/react/index.js.map +1 -1
- package/dist/native/react/index.mjs +28 -52
- package/dist/native/react/index.mjs.map +1 -1
- package/dist/native/server/index.js +38 -13
- package/dist/native/server/index.js.map +1 -1
- package/dist/native/server/index.mjs +38 -13
- package/dist/native/server/index.mjs.map +1 -1
- package/dist/react/index.d.mts +107 -8
- package/dist/react/index.d.ts +107 -8
- package/dist/react/index.js +2194 -84
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +2194 -85
- package/dist/react/index.mjs.map +1 -1
- package/dist/{registry-BKLEm-yk.d.ts → registry-C6dDtn1v.d.ts} +27 -2
- package/dist/{registry-BmZgyCz8.d.mts → registry-POtcxnal.d.mts} +27 -2
- package/dist/render-log/index.d.mts +1 -1
- package/dist/render-log/index.d.ts +1 -1
- package/dist/server/express.d.mts +5 -4
- package/dist/server/express.d.ts +5 -4
- package/dist/server/express.js +104 -2
- package/dist/server/express.js.map +1 -1
- package/dist/server/express.mjs +104 -2
- package/dist/server/express.mjs.map +1 -1
- package/dist/server/handlers.d.mts +36 -5
- package/dist/server/handlers.d.ts +36 -5
- package/dist/server/handlers.js +3129 -224
- package/dist/server/handlers.js.map +1 -1
- package/dist/server/handlers.mjs +3129 -224
- package/dist/server/handlers.mjs.map +1 -1
- package/dist/server/index.d.mts +7 -5
- package/dist/server/index.d.ts +7 -5
- package/dist/server/index.js +3215 -183
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +3215 -183
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/nextjs.d.mts +6 -4
- package/dist/server/nextjs.d.ts +6 -4
- package/dist/server/nextjs.js +106 -3
- package/dist/server/nextjs.js.map +1 -1
- package/dist/server/nextjs.mjs +106 -3
- package/dist/server/nextjs.mjs.map +1 -1
- package/dist/server/standalone.d.mts +6 -5
- package/dist/server/standalone.d.ts +6 -5
- package/dist/server/standalone.js +131 -5
- package/dist/server/standalone.js.map +1 -1
- package/dist/server/standalone.mjs +131 -5
- package/dist/server/standalone.mjs.map +1 -1
- package/dist/specs/index.d.mts +365 -0
- package/dist/specs/index.d.ts +365 -0
- package/dist/specs/index.js +2809 -0
- package/dist/specs/index.js.map +1 -0
- package/dist/specs/index.mjs +2786 -0
- package/dist/specs/index.mjs.map +1 -0
- package/dist/{standalone-BURj8J3G.d.ts → standalone-B6GLIEmR.d.ts} +6 -2
- package/dist/{standalone-Dwmel29d.d.mts → standalone-CjdYqj3P.d.mts} +6 -2
- package/dist/{types-CHnlwiTK.d.ts → types-B2EfvEaq.d.ts} +83 -3
- package/dist/{types-B7J7noLK.d.mts → types-C7gVYRnF.d.ts} +72 -2
- package/dist/{types-BkNRILUa.d.ts → types-CJGrBEhC.d.mts} +72 -2
- package/dist/types-CebMQj76.d.ts +1275 -0
- package/dist/types-D_ypYl3T.d.mts +1275 -0
- package/dist/types-UBtp7R0u.d.mts +132 -0
- package/dist/types-UBtp7R0u.d.ts +132 -0
- package/dist/{types-CEQLnFMv.d.mts → types-gO696T_t.d.mts} +83 -3
- package/dist/{types-jKVgTI6_.d.mts → types-suaYwWWg.d.mts} +173 -2
- package/dist/{types-jKVgTI6_.d.ts → types-suaYwWWg.d.ts} +173 -2
- package/package.json +18 -2
- package/dist/types-B5Q0GVo0.d.mts +0 -646
- package/dist/types-DfPqwU-i.d.ts +0 -646
package/dist/ai/index.js
CHANGED
|
@@ -451,17 +451,72 @@ function generateDescription(input) {
|
|
|
451
451
|
}
|
|
452
452
|
parts.push(`"${name}"`);
|
|
453
453
|
}
|
|
454
|
-
const typeWords = ELEMENT_ACTION_WORDS[input.elementType || ""] || [
|
|
454
|
+
const typeWords = ELEMENT_ACTION_WORDS[input.elementType || ""] || [
|
|
455
|
+
input.elementType || "element"
|
|
456
|
+
];
|
|
455
457
|
parts.push(typeWords[0]);
|
|
456
458
|
if (input.inputType && input.inputType !== "text") {
|
|
457
459
|
parts.push(`(${input.inputType})`);
|
|
458
460
|
}
|
|
459
461
|
return parts.join(" ");
|
|
460
462
|
}
|
|
463
|
+
var CONTENT_TYPES = /* @__PURE__ */ new Set([
|
|
464
|
+
"heading",
|
|
465
|
+
"paragraph",
|
|
466
|
+
"list-item",
|
|
467
|
+
"table-cell",
|
|
468
|
+
"table-header",
|
|
469
|
+
"label",
|
|
470
|
+
"caption",
|
|
471
|
+
"blockquote",
|
|
472
|
+
"code-block",
|
|
473
|
+
"badge",
|
|
474
|
+
"status-message",
|
|
475
|
+
"metric-value",
|
|
476
|
+
"description-text",
|
|
477
|
+
"nav-text",
|
|
478
|
+
"content-generic"
|
|
479
|
+
]);
|
|
461
480
|
function generatePurpose(input) {
|
|
462
481
|
const text = (input.textContent || input.ariaLabel || input.title || "").toLowerCase();
|
|
463
482
|
const type = input.elementType?.toLowerCase() || "";
|
|
464
483
|
const inputType = input.inputType?.toLowerCase() || "";
|
|
484
|
+
if (CONTENT_TYPES.has(type)) {
|
|
485
|
+
switch (type) {
|
|
486
|
+
case "heading":
|
|
487
|
+
return "Section heading";
|
|
488
|
+
case "paragraph":
|
|
489
|
+
return "Body text content";
|
|
490
|
+
case "list-item":
|
|
491
|
+
return "List item";
|
|
492
|
+
case "table-cell":
|
|
493
|
+
return "Table data cell";
|
|
494
|
+
case "table-header":
|
|
495
|
+
return "Table column header";
|
|
496
|
+
case "label":
|
|
497
|
+
return "Field label or definition term";
|
|
498
|
+
case "caption":
|
|
499
|
+
return "Figure or table caption";
|
|
500
|
+
case "blockquote":
|
|
501
|
+
return "Quoted content";
|
|
502
|
+
case "code-block":
|
|
503
|
+
return "Code or preformatted text";
|
|
504
|
+
case "badge":
|
|
505
|
+
return "Status badge or tag";
|
|
506
|
+
case "status-message":
|
|
507
|
+
return "Dynamic status indicator";
|
|
508
|
+
case "metric-value":
|
|
509
|
+
return "Metric or statistic value";
|
|
510
|
+
case "description-text":
|
|
511
|
+
return "Description or definition";
|
|
512
|
+
case "nav-text":
|
|
513
|
+
return "Navigation label";
|
|
514
|
+
case "content-generic":
|
|
515
|
+
return "Text content";
|
|
516
|
+
default:
|
|
517
|
+
return "Static content";
|
|
518
|
+
}
|
|
519
|
+
}
|
|
465
520
|
if (type === "button" || inputType === "submit") {
|
|
466
521
|
if (text.match(/submit|send|save|confirm|ok|done|finish|apply/)) {
|
|
467
522
|
return "Submits the form";
|
|
@@ -526,6 +581,10 @@ function generateSuggestedActions(input) {
|
|
|
526
581
|
const inputType = input.inputType?.toLowerCase() || "";
|
|
527
582
|
const text = (input.textContent || input.ariaLabel || "").toLowerCase();
|
|
528
583
|
const actions = [];
|
|
584
|
+
if (CONTENT_TYPES.has(type)) {
|
|
585
|
+
actions.push("read text content", "verify text matches expected");
|
|
586
|
+
return actions;
|
|
587
|
+
}
|
|
529
588
|
switch (type) {
|
|
530
589
|
case "button":
|
|
531
590
|
actions.push(`click "${text || "this button"}"`);
|
|
@@ -577,6 +636,241 @@ function areSynonyms(word1, word2) {
|
|
|
577
636
|
return synonyms1.includes(w2) || synonyms2.includes(w1);
|
|
578
637
|
}
|
|
579
638
|
|
|
639
|
+
// src/annotations/types.ts
|
|
640
|
+
var ANNOTATION_CONFIG_VERSION = "1.0.0";
|
|
641
|
+
|
|
642
|
+
// src/annotations/store.ts
|
|
643
|
+
var AnnotationStore = class {
|
|
644
|
+
constructor() {
|
|
645
|
+
this.store = /* @__PURE__ */ new Map();
|
|
646
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Get an annotation by element ID.
|
|
650
|
+
*/
|
|
651
|
+
get(elementId) {
|
|
652
|
+
return this.store.get(elementId);
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Get all annotations as a record.
|
|
656
|
+
*/
|
|
657
|
+
getAll() {
|
|
658
|
+
const result = {};
|
|
659
|
+
for (const [id, annotation] of this.store) {
|
|
660
|
+
result[id] = annotation;
|
|
661
|
+
}
|
|
662
|
+
return result;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Set an annotation for an element. Auto-sets `updatedAt`.
|
|
666
|
+
*/
|
|
667
|
+
set(elementId, annotation) {
|
|
668
|
+
const updated = {
|
|
669
|
+
...annotation,
|
|
670
|
+
updatedAt: Date.now()
|
|
671
|
+
};
|
|
672
|
+
this.store.set(elementId, updated);
|
|
673
|
+
this.emit({
|
|
674
|
+
type: "annotation:set",
|
|
675
|
+
elementId,
|
|
676
|
+
annotation: updated,
|
|
677
|
+
timestamp: Date.now()
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Delete an annotation by element ID.
|
|
682
|
+
*
|
|
683
|
+
* @returns true if the annotation existed and was deleted
|
|
684
|
+
*/
|
|
685
|
+
delete(elementId) {
|
|
686
|
+
const existed = this.store.delete(elementId);
|
|
687
|
+
if (existed) {
|
|
688
|
+
this.emit({
|
|
689
|
+
type: "annotation:deleted",
|
|
690
|
+
elementId,
|
|
691
|
+
timestamp: Date.now()
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
return existed;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Check if an annotation exists for an element.
|
|
698
|
+
*/
|
|
699
|
+
has(elementId) {
|
|
700
|
+
return this.store.has(elementId);
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Get the number of stored annotations.
|
|
704
|
+
*/
|
|
705
|
+
get count() {
|
|
706
|
+
return this.store.size;
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Clear all annotations.
|
|
710
|
+
*/
|
|
711
|
+
clear() {
|
|
712
|
+
this.store.clear();
|
|
713
|
+
this.emit({
|
|
714
|
+
type: "annotation:cleared",
|
|
715
|
+
timestamp: Date.now()
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Import annotations from a config object.
|
|
720
|
+
*
|
|
721
|
+
* Merges with existing annotations (new values overwrite per element ID).
|
|
722
|
+
*
|
|
723
|
+
* @returns Number of annotations imported
|
|
724
|
+
*
|
|
725
|
+
* @example
|
|
726
|
+
* ```ts
|
|
727
|
+
* const config: AnnotationConfig = {
|
|
728
|
+
* version: '1.0.0',
|
|
729
|
+
* annotations: {
|
|
730
|
+
* 'btn-1': { description: 'Submit button', tags: ['form'] },
|
|
731
|
+
* 'input-1': { description: 'Name field' },
|
|
732
|
+
* },
|
|
733
|
+
* };
|
|
734
|
+
* const count = store.importConfig(config); // 2
|
|
735
|
+
* ```
|
|
736
|
+
*/
|
|
737
|
+
importConfig(config) {
|
|
738
|
+
let count = 0;
|
|
739
|
+
for (const [id, annotation] of Object.entries(config.annotations)) {
|
|
740
|
+
this.store.set(id, {
|
|
741
|
+
...annotation,
|
|
742
|
+
updatedAt: annotation.updatedAt ?? Date.now()
|
|
743
|
+
});
|
|
744
|
+
count++;
|
|
745
|
+
}
|
|
746
|
+
this.emit({
|
|
747
|
+
type: "annotation:imported",
|
|
748
|
+
count,
|
|
749
|
+
timestamp: Date.now()
|
|
750
|
+
});
|
|
751
|
+
return count;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Export all annotations as a config object.
|
|
755
|
+
*
|
|
756
|
+
* The returned object can be serialized to JSON and saved to a file,
|
|
757
|
+
* then later re-imported with {@link importConfig}.
|
|
758
|
+
*
|
|
759
|
+
* @param metadata - Optional metadata to include (appName, description, etc.)
|
|
760
|
+
* @returns AnnotationConfig with all current annotations
|
|
761
|
+
*
|
|
762
|
+
* @example
|
|
763
|
+
* ```ts
|
|
764
|
+
* const config = store.exportConfig({ appName: 'MyApp' });
|
|
765
|
+
* // config.version === '1.0.0'
|
|
766
|
+
* // config.annotations === { 'btn-1': { ... }, 'input-1': { ... } }
|
|
767
|
+
* // config.metadata === { appName: 'MyApp', exportedAt: 1706900000000 }
|
|
768
|
+
*
|
|
769
|
+
* // Save to file
|
|
770
|
+
* fs.writeFileSync('annotations.json', JSON.stringify(config, null, 2));
|
|
771
|
+
* ```
|
|
772
|
+
*/
|
|
773
|
+
exportConfig(metadata) {
|
|
774
|
+
return {
|
|
775
|
+
version: ANNOTATION_CONFIG_VERSION,
|
|
776
|
+
annotations: this.getAll(),
|
|
777
|
+
metadata: {
|
|
778
|
+
...metadata,
|
|
779
|
+
exportedAt: Date.now()
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Compute annotation coverage against a set of known element IDs.
|
|
785
|
+
*
|
|
786
|
+
* Compares the store's annotations against the provided list of element IDs
|
|
787
|
+
* to determine what percentage of elements have been annotated.
|
|
788
|
+
*
|
|
789
|
+
* @param allElementIds - Array of all known element IDs in the UI
|
|
790
|
+
* @returns Coverage statistics including percentages and lists of annotated/unannotated IDs
|
|
791
|
+
*
|
|
792
|
+
* @example
|
|
793
|
+
* ```ts
|
|
794
|
+
* store.set('btn-1', { description: 'Submit' });
|
|
795
|
+
* store.set('input-1', { description: 'Name' });
|
|
796
|
+
*
|
|
797
|
+
* const coverage = store.getCoverage(['btn-1', 'input-1', 'input-2', 'link-1']);
|
|
798
|
+
* // coverage.totalElements === 4
|
|
799
|
+
* // coverage.annotatedElements === 2
|
|
800
|
+
* // coverage.coveragePercent === 50
|
|
801
|
+
* // coverage.annotatedIds === ['btn-1', 'input-1']
|
|
802
|
+
* // coverage.unannotatedIds === ['input-2', 'link-1']
|
|
803
|
+
* ```
|
|
804
|
+
*/
|
|
805
|
+
getCoverage(allElementIds) {
|
|
806
|
+
const annotatedIds = [];
|
|
807
|
+
const unannotatedIds = [];
|
|
808
|
+
for (const id of allElementIds) {
|
|
809
|
+
if (this.store.has(id)) {
|
|
810
|
+
annotatedIds.push(id);
|
|
811
|
+
} else {
|
|
812
|
+
unannotatedIds.push(id);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
const total = allElementIds.length;
|
|
816
|
+
return {
|
|
817
|
+
totalElements: total,
|
|
818
|
+
annotatedElements: annotatedIds.length,
|
|
819
|
+
coveragePercent: total > 0 ? annotatedIds.length / total * 100 : 0,
|
|
820
|
+
annotatedIds,
|
|
821
|
+
unannotatedIds,
|
|
822
|
+
timestamp: Date.now()
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Subscribe to annotation events.
|
|
827
|
+
*
|
|
828
|
+
* The listener is called whenever annotations are set, deleted, imported,
|
|
829
|
+
* or cleared. Returns an unsubscribe function to stop listening.
|
|
830
|
+
*
|
|
831
|
+
* @param listener - Callback function receiving {@link AnnotationEvent} objects
|
|
832
|
+
* @returns Unsubscribe function - call it to remove the listener
|
|
833
|
+
*
|
|
834
|
+
* @example
|
|
835
|
+
* ```ts
|
|
836
|
+
* const unsubscribe = store.on((event) => {
|
|
837
|
+
* if (event.type === 'annotation:set') {
|
|
838
|
+
* console.log(`Element ${event.elementId} annotated:`, event.annotation);
|
|
839
|
+
* }
|
|
840
|
+
* });
|
|
841
|
+
*
|
|
842
|
+
* store.set('btn-1', { description: 'Submit' });
|
|
843
|
+
* // Logs: "Element btn-1 annotated: { description: 'Submit', updatedAt: ... }"
|
|
844
|
+
*
|
|
845
|
+
* unsubscribe(); // Stop listening
|
|
846
|
+
* ```
|
|
847
|
+
*/
|
|
848
|
+
on(listener) {
|
|
849
|
+
this.listeners.add(listener);
|
|
850
|
+
return () => {
|
|
851
|
+
this.listeners.delete(listener);
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Emit an event to all listeners.
|
|
856
|
+
*/
|
|
857
|
+
emit(event) {
|
|
858
|
+
for (const listener of this.listeners) {
|
|
859
|
+
try {
|
|
860
|
+
listener(event);
|
|
861
|
+
} catch {
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
var globalStore = null;
|
|
867
|
+
function getGlobalAnnotationStore() {
|
|
868
|
+
if (!globalStore) {
|
|
869
|
+
globalStore = new AnnotationStore();
|
|
870
|
+
}
|
|
871
|
+
return globalStore;
|
|
872
|
+
}
|
|
873
|
+
|
|
580
874
|
// src/ai/search-engine.ts
|
|
581
875
|
var DEFAULT_SEARCH_CONFIG = {
|
|
582
876
|
fuzzyThreshold: 0.7,
|
|
@@ -619,17 +913,40 @@ var SearchEngine = class {
|
|
|
619
913
|
if ("getState" in element && typeof element.getState === "function") {
|
|
620
914
|
state = getState ? getState(element) : element.getState();
|
|
621
915
|
textContent = state.textContent || void 0;
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
916
|
+
try {
|
|
917
|
+
tagName = element.element.tagName.toLowerCase();
|
|
918
|
+
} catch {
|
|
919
|
+
tagName = element.type || "unknown";
|
|
920
|
+
}
|
|
921
|
+
try {
|
|
922
|
+
role = element.element.getAttribute("role") || void 0;
|
|
923
|
+
ariaLabel = element.element.getAttribute("aria-label") || void 0;
|
|
924
|
+
placeholder = element.element.getAttribute("placeholder") || void 0;
|
|
925
|
+
title = element.element.getAttribute("title") || void 0;
|
|
926
|
+
} catch {
|
|
927
|
+
}
|
|
928
|
+
if (!ariaLabel && element.label) {
|
|
929
|
+
ariaLabel = element.label;
|
|
930
|
+
}
|
|
931
|
+
try {
|
|
932
|
+
if (element.element.id) {
|
|
933
|
+
const labelEl = document.querySelector(`label[for="${element.element.id}"]`);
|
|
934
|
+
labelText = labelEl?.textContent?.trim() || void 0;
|
|
935
|
+
}
|
|
936
|
+
} catch {
|
|
937
|
+
}
|
|
938
|
+
if (!labelText && element.label) {
|
|
939
|
+
labelText = element.label;
|
|
940
|
+
}
|
|
941
|
+
if (!textContent && element.label) {
|
|
942
|
+
textContent = element.label;
|
|
943
|
+
}
|
|
944
|
+
try {
|
|
945
|
+
if (element.element instanceof HTMLInputElement || element.element instanceof HTMLTextAreaElement || element.element instanceof HTMLSelectElement) {
|
|
946
|
+
value = element.element.value || void 0;
|
|
947
|
+
}
|
|
948
|
+
} catch {
|
|
949
|
+
value = state.value || void 0;
|
|
633
950
|
}
|
|
634
951
|
} else {
|
|
635
952
|
const discovered = element;
|
|
@@ -638,8 +955,11 @@ var SearchEngine = class {
|
|
|
638
955
|
tagName = discovered.tagName;
|
|
639
956
|
role = discovered.role || void 0;
|
|
640
957
|
ariaLabel = discovered.accessibleName || void 0;
|
|
958
|
+
if (!labelText && element.label) {
|
|
959
|
+
labelText = element.label;
|
|
960
|
+
}
|
|
641
961
|
}
|
|
642
|
-
|
|
962
|
+
let aliases = generateAliases({
|
|
643
963
|
textContent,
|
|
644
964
|
ariaLabel,
|
|
645
965
|
placeholder,
|
|
@@ -649,7 +969,14 @@ var SearchEngine = class {
|
|
|
649
969
|
labelText,
|
|
650
970
|
value
|
|
651
971
|
});
|
|
652
|
-
|
|
972
|
+
if ("aliases" in element && Array.isArray(element.aliases) && element.aliases.length > 0) {
|
|
973
|
+
const aliasSet = /* @__PURE__ */ new Set([
|
|
974
|
+
...aliases,
|
|
975
|
+
...element.aliases.map((a) => a.toLowerCase())
|
|
976
|
+
]);
|
|
977
|
+
aliases = [...aliasSet];
|
|
978
|
+
}
|
|
979
|
+
let description = generateDescription({
|
|
653
980
|
textContent,
|
|
654
981
|
ariaLabel,
|
|
655
982
|
placeholder,
|
|
@@ -658,6 +985,22 @@ var SearchEngine = class {
|
|
|
658
985
|
id: element.id,
|
|
659
986
|
labelText
|
|
660
987
|
});
|
|
988
|
+
if (!description && "description" in element && element.description) {
|
|
989
|
+
description = element.description;
|
|
990
|
+
}
|
|
991
|
+
const annotation = getGlobalAnnotationStore().get(element.id);
|
|
992
|
+
if (annotation) {
|
|
993
|
+
if (annotation.description) {
|
|
994
|
+
description = annotation.description;
|
|
995
|
+
}
|
|
996
|
+
if (annotation.tags && annotation.tags.length > 0) {
|
|
997
|
+
const tagSet = /* @__PURE__ */ new Set([...aliases, ...annotation.tags.map((t) => t.toLowerCase())]);
|
|
998
|
+
aliases = [...tagSet];
|
|
999
|
+
}
|
|
1000
|
+
if (annotation.notes) {
|
|
1001
|
+
aliases.push(annotation.notes.toLowerCase());
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
661
1004
|
return {
|
|
662
1005
|
id: element.id,
|
|
663
1006
|
element,
|
|
@@ -760,7 +1103,12 @@ var SearchEngine = class {
|
|
|
760
1103
|
threshold: criteria.fuzzyThreshold ?? this.config.fuzzyThreshold
|
|
761
1104
|
};
|
|
762
1105
|
if (criteria.text) {
|
|
763
|
-
const textScore = this.scoreTextMatch(
|
|
1106
|
+
const textScore = this.scoreTextMatch(
|
|
1107
|
+
searchable,
|
|
1108
|
+
criteria.text,
|
|
1109
|
+
criteria.fuzzy !== false,
|
|
1110
|
+
fuzzyConfig.threshold
|
|
1111
|
+
);
|
|
764
1112
|
scores.text = textScore.score;
|
|
765
1113
|
if (textScore.score > 0) {
|
|
766
1114
|
matchReasons.push(...textScore.reasons);
|
|
@@ -768,8 +1116,37 @@ var SearchEngine = class {
|
|
|
768
1116
|
weightedScore += textScore.score * this.config.textWeight;
|
|
769
1117
|
totalWeight += this.config.textWeight;
|
|
770
1118
|
}
|
|
1119
|
+
if (criteria.textContent && !criteria.text) {
|
|
1120
|
+
const alternatives = criteria.textContent.includes("|") ? criteria.textContent.split("|").map((s) => s.trim()).filter(Boolean) : [criteria.textContent];
|
|
1121
|
+
let bestScore = 0;
|
|
1122
|
+
let bestReasons = [];
|
|
1123
|
+
for (const alt of alternatives) {
|
|
1124
|
+
const exactScore = this.scoreTextMatch(
|
|
1125
|
+
searchable,
|
|
1126
|
+
alt,
|
|
1127
|
+
criteria.fuzzy !== false,
|
|
1128
|
+
fuzzyConfig.threshold
|
|
1129
|
+
);
|
|
1130
|
+
const containsScore = this.scoreContainsMatch(searchable, alt, criteria.fuzzy !== false);
|
|
1131
|
+
const altBest = Math.max(exactScore.score, containsScore.score);
|
|
1132
|
+
if (altBest > bestScore) {
|
|
1133
|
+
bestScore = altBest;
|
|
1134
|
+
bestReasons = exactScore.score >= containsScore.score ? exactScore.reasons : containsScore.reasons;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
scores.text = bestScore;
|
|
1138
|
+
if (bestScore > 0) {
|
|
1139
|
+
matchReasons.push(...bestReasons);
|
|
1140
|
+
}
|
|
1141
|
+
weightedScore += bestScore * this.config.textWeight;
|
|
1142
|
+
totalWeight += this.config.textWeight;
|
|
1143
|
+
}
|
|
771
1144
|
if (criteria.textContains) {
|
|
772
|
-
const containsScore = this.scoreContainsMatch(
|
|
1145
|
+
const containsScore = this.scoreContainsMatch(
|
|
1146
|
+
searchable,
|
|
1147
|
+
criteria.textContains,
|
|
1148
|
+
criteria.fuzzy !== false
|
|
1149
|
+
);
|
|
773
1150
|
scores.text = Math.max(scores.text || 0, containsScore.score);
|
|
774
1151
|
if (containsScore.score > 0 && containsScore.reasons.length > 0) {
|
|
775
1152
|
matchReasons.push(...containsScore.reasons);
|
|
@@ -818,7 +1195,11 @@ var SearchEngine = class {
|
|
|
818
1195
|
totalWeight += this.config.spatialWeight;
|
|
819
1196
|
}
|
|
820
1197
|
if (criteria.placeholder && searchable.placeholder) {
|
|
821
|
-
const placeholderResult = fuzzyMatch(
|
|
1198
|
+
const placeholderResult = fuzzyMatch(
|
|
1199
|
+
searchable.placeholder,
|
|
1200
|
+
criteria.placeholder,
|
|
1201
|
+
fuzzyConfig
|
|
1202
|
+
);
|
|
822
1203
|
if (placeholderResult.isMatch) {
|
|
823
1204
|
matchReasons.push(`placeholder matches`);
|
|
824
1205
|
weightedScore += placeholderResult.similarity * this.config.textWeight;
|
|
@@ -863,11 +1244,9 @@ var SearchEngine = class {
|
|
|
863
1244
|
scoreTextMatch(searchable, text, fuzzy, threshold) {
|
|
864
1245
|
const reasons = [];
|
|
865
1246
|
let maxScore = 0;
|
|
866
|
-
const textsToMatch = [
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
searchable.value
|
|
870
|
-
].filter(Boolean);
|
|
1247
|
+
const textsToMatch = [searchable.textContent, searchable.labelText, searchable.value].filter(
|
|
1248
|
+
Boolean
|
|
1249
|
+
);
|
|
871
1250
|
for (const targetText of textsToMatch) {
|
|
872
1251
|
if (targetText.toLowerCase() === text.toLowerCase()) {
|
|
873
1252
|
maxScore = Math.max(maxScore, 1);
|
|
@@ -963,7 +1342,9 @@ var SearchEngine = class {
|
|
|
963
1342
|
heading: ["h1", "h2", "h3", "h4", "h5", "h6"]
|
|
964
1343
|
};
|
|
965
1344
|
const inferredRoles = tagRoleMap[normalizedRole] || [];
|
|
966
|
-
if (inferredRoles.some(
|
|
1345
|
+
if (inferredRoles.some(
|
|
1346
|
+
(r) => searchable.tagName === r || searchable.type.toLowerCase() === normalizedRole
|
|
1347
|
+
)) {
|
|
967
1348
|
return { score: 0.8, reasons: [`inferred role: ${role}`] };
|
|
968
1349
|
}
|
|
969
1350
|
return { score: 0, reasons };
|
|
@@ -1153,11 +1534,14 @@ function generatePageSummary(elements, pageContext, config = {}) {
|
|
|
1153
1534
|
if (finalConfig.includeElementCounts) {
|
|
1154
1535
|
const counts = countElementTypes(elements);
|
|
1155
1536
|
const countParts = [];
|
|
1156
|
-
if (counts.button > 0)
|
|
1537
|
+
if (counts.button > 0)
|
|
1538
|
+
countParts.push(`${counts.button} button${counts.button > 1 ? "s" : ""}`);
|
|
1157
1539
|
if (counts.input > 0) countParts.push(`${counts.input} input${counts.input > 1 ? "s" : ""}`);
|
|
1158
1540
|
if (counts.link > 0) countParts.push(`${counts.link} link${counts.link > 1 ? "s" : ""}`);
|
|
1159
|
-
if (counts.select > 0)
|
|
1160
|
-
|
|
1541
|
+
if (counts.select > 0)
|
|
1542
|
+
countParts.push(`${counts.select} dropdown${counts.select > 1 ? "s" : ""}`);
|
|
1543
|
+
if (counts.checkbox > 0)
|
|
1544
|
+
countParts.push(`${counts.checkbox} checkbox${counts.checkbox > 1 ? "es" : ""}`);
|
|
1161
1545
|
if (countParts.length > 0) {
|
|
1162
1546
|
lines.push(`Contains: ${countParts.join(", ")}`);
|
|
1163
1547
|
}
|
|
@@ -1221,7 +1605,9 @@ function generateFormSummary(form, verbosity) {
|
|
|
1221
1605
|
if (verbosity === "brief") {
|
|
1222
1606
|
const fieldCount = form.fields.length;
|
|
1223
1607
|
const filledCount = form.fields.filter((f) => f.value).length;
|
|
1224
|
-
lines.push(
|
|
1608
|
+
lines.push(
|
|
1609
|
+
` ${filledCount}/${fieldCount} fields filled, ${form.isValid ? "valid" : "has errors"}`
|
|
1610
|
+
);
|
|
1225
1611
|
} else {
|
|
1226
1612
|
for (const field of form.fields) {
|
|
1227
1613
|
let fieldLine = ` - ${field.label || field.id}`;
|
|
@@ -1317,7 +1703,9 @@ function generateDiffSummary(appeared, disappeared, modified) {
|
|
|
1317
1703
|
if (modified.length > 0) {
|
|
1318
1704
|
lines.push("Changed:");
|
|
1319
1705
|
for (const mod of modified.slice(0, 5)) {
|
|
1320
|
-
lines.push(
|
|
1706
|
+
lines.push(
|
|
1707
|
+
` - ${mod.description}: ${mod.property} changed from "${mod.from}" to "${mod.to}"`
|
|
1708
|
+
);
|
|
1321
1709
|
}
|
|
1322
1710
|
if (modified.length > 5) {
|
|
1323
1711
|
lines.push(` ... and ${modified.length - 5} more changes`);
|
|
@@ -1767,7 +2155,9 @@ function parseNLInstruction(instruction) {
|
|
|
1767
2155
|
}
|
|
1768
2156
|
}
|
|
1769
2157
|
if (pattern.action === "assert") {
|
|
1770
|
-
const assertMatch = trimmed.match(
|
|
2158
|
+
const assertMatch = trimmed.match(
|
|
2159
|
+
/(visible|hidden|enabled|disabled|checked|unchecked|focused|contains|has)/i
|
|
2160
|
+
);
|
|
1771
2161
|
if (assertMatch) {
|
|
1772
2162
|
parsed.assertionType = ASSERTION_TYPE_MAP[assertMatch[1].toLowerCase()];
|
|
1773
2163
|
}
|
|
@@ -2282,7 +2672,9 @@ function formatErrorContext(context) {
|
|
|
2282
2672
|
lines.push("");
|
|
2283
2673
|
if (context.searchResults.nearestMatch) {
|
|
2284
2674
|
const match = context.searchResults.nearestMatch;
|
|
2285
|
-
lines.push(
|
|
2675
|
+
lines.push(
|
|
2676
|
+
`Nearest match: "${match.element.description}" (${(match.confidence * 100).toFixed(0)}% confidence)`
|
|
2677
|
+
);
|
|
2286
2678
|
lines.push(`Why not used: ${match.whyNotSelected}`);
|
|
2287
2679
|
lines.push("");
|
|
2288
2680
|
}
|
|
@@ -2629,7 +3021,9 @@ var NLActionExecutor = class {
|
|
|
2629
3021
|
switch (errorCode) {
|
|
2630
3022
|
case "PARSE_ERROR":
|
|
2631
3023
|
suggestions.push('Try using a simpler phrase like "click Submit button"');
|
|
2632
|
-
suggestions.push(
|
|
3024
|
+
suggestions.push(
|
|
3025
|
+
'Ensure the instruction follows patterns like "click X" or "type Y into X"'
|
|
3026
|
+
);
|
|
2633
3027
|
break;
|
|
2634
3028
|
case "ELEMENT_NOT_FOUND":
|
|
2635
3029
|
if (alternatives.length > 0) {
|
|
@@ -2700,9 +3094,15 @@ var AssertionExecutor = class {
|
|
|
2700
3094
|
async assert(request) {
|
|
2701
3095
|
const startTime = performance.now();
|
|
2702
3096
|
const timeout = request.timeout ?? this.config.defaultTimeout;
|
|
2703
|
-
const
|
|
3097
|
+
const searchResult = this.findElementDetailed(request.target, request.fuzzy !== false);
|
|
3098
|
+
const element = searchResult?.element ?? null;
|
|
3099
|
+
const searchDetails = searchResult ? {
|
|
3100
|
+
confidence: searchResult.confidence,
|
|
3101
|
+
matchReasons: searchResult.matchReasons,
|
|
3102
|
+
candidateCount: this.elements.length
|
|
3103
|
+
} : void 0;
|
|
2704
3104
|
if (!element && request.type !== "notExists") {
|
|
2705
|
-
|
|
3105
|
+
const result2 = this.createResult(
|
|
2706
3106
|
false,
|
|
2707
3107
|
typeof request.target === "string" ? request.target : JSON.stringify(request.target),
|
|
2708
3108
|
"element not found",
|
|
@@ -2712,8 +3112,16 @@ var AssertionExecutor = class {
|
|
|
2712
3112
|
this.config.includeSuggestions ? "Check if the element exists and is properly labeled" : void 0,
|
|
2713
3113
|
startTime
|
|
2714
3114
|
);
|
|
3115
|
+
if (searchDetails) {
|
|
3116
|
+
result2.searchDetails = searchDetails;
|
|
3117
|
+
}
|
|
3118
|
+
return result2;
|
|
2715
3119
|
}
|
|
2716
|
-
|
|
3120
|
+
const result = await this.executeAssertion(request, element, timeout, startTime);
|
|
3121
|
+
if (searchDetails) {
|
|
3122
|
+
result.searchDetails = searchDetails;
|
|
3123
|
+
}
|
|
3124
|
+
return result;
|
|
2717
3125
|
}
|
|
2718
3126
|
/**
|
|
2719
3127
|
* Execute multiple assertions
|
|
@@ -2818,16 +3226,26 @@ var AssertionExecutor = class {
|
|
|
2818
3226
|
return this.assert({ target, type: "count", expected: expectedCount, timeout });
|
|
2819
3227
|
}
|
|
2820
3228
|
/**
|
|
2821
|
-
* Find element by target
|
|
3229
|
+
* Find element by target with full search metadata.
|
|
3230
|
+
* Returns the SearchResult (including confidence, matchReasons, scores)
|
|
3231
|
+
* or null if no match above the fuzzy threshold.
|
|
2822
3232
|
*/
|
|
2823
|
-
|
|
3233
|
+
findElementDetailed(target, fuzzy = true) {
|
|
2824
3234
|
const criteria = typeof target === "string" ? { text: target, fuzzy } : { ...target, fuzzy };
|
|
2825
|
-
const searchResult = this.searchEngine.findBest(criteria);
|
|
3235
|
+
const searchResult = this.searchEngine.findBest(criteria, this.elements);
|
|
2826
3236
|
if (searchResult && searchResult.confidence >= this.config.fuzzyThreshold) {
|
|
2827
|
-
return searchResult
|
|
3237
|
+
return searchResult;
|
|
2828
3238
|
}
|
|
2829
3239
|
return null;
|
|
2830
3240
|
}
|
|
3241
|
+
/**
|
|
3242
|
+
* Find element by target (string or criteria).
|
|
3243
|
+
* Public for use by condition evaluation in SpecExecutor.
|
|
3244
|
+
*/
|
|
3245
|
+
async findElement(target, fuzzy = true) {
|
|
3246
|
+
const result = this.findElementDetailed(target, fuzzy);
|
|
3247
|
+
return result?.element ?? null;
|
|
3248
|
+
}
|
|
2831
3249
|
/**
|
|
2832
3250
|
* Execute the actual assertion
|
|
2833
3251
|
*/
|
|
@@ -2836,19 +3254,55 @@ var AssertionExecutor = class {
|
|
|
2836
3254
|
const elementDescription = element?.description || targetStr;
|
|
2837
3255
|
switch (request.type) {
|
|
2838
3256
|
case "visible":
|
|
2839
|
-
return this.assertVisibility(
|
|
3257
|
+
return this.assertVisibility(
|
|
3258
|
+
element,
|
|
3259
|
+
true,
|
|
3260
|
+
elementDescription,
|
|
3261
|
+
request.message,
|
|
3262
|
+
startTime
|
|
3263
|
+
);
|
|
2840
3264
|
case "hidden":
|
|
2841
|
-
return this.assertVisibility(
|
|
3265
|
+
return this.assertVisibility(
|
|
3266
|
+
element,
|
|
3267
|
+
false,
|
|
3268
|
+
elementDescription,
|
|
3269
|
+
request.message,
|
|
3270
|
+
startTime
|
|
3271
|
+
);
|
|
2842
3272
|
case "enabled":
|
|
2843
|
-
return this.assertEnabledState(
|
|
3273
|
+
return this.assertEnabledState(
|
|
3274
|
+
element,
|
|
3275
|
+
true,
|
|
3276
|
+
elementDescription,
|
|
3277
|
+
request.message,
|
|
3278
|
+
startTime
|
|
3279
|
+
);
|
|
2844
3280
|
case "disabled":
|
|
2845
|
-
return this.assertEnabledState(
|
|
3281
|
+
return this.assertEnabledState(
|
|
3282
|
+
element,
|
|
3283
|
+
false,
|
|
3284
|
+
elementDescription,
|
|
3285
|
+
request.message,
|
|
3286
|
+
startTime
|
|
3287
|
+
);
|
|
2846
3288
|
case "focused":
|
|
2847
3289
|
return this.assertFocused(element, elementDescription, request.message, startTime);
|
|
2848
3290
|
case "checked":
|
|
2849
|
-
return this.assertCheckedState(
|
|
3291
|
+
return this.assertCheckedState(
|
|
3292
|
+
element,
|
|
3293
|
+
true,
|
|
3294
|
+
elementDescription,
|
|
3295
|
+
request.message,
|
|
3296
|
+
startTime
|
|
3297
|
+
);
|
|
2850
3298
|
case "unchecked":
|
|
2851
|
-
return this.assertCheckedState(
|
|
3299
|
+
return this.assertCheckedState(
|
|
3300
|
+
element,
|
|
3301
|
+
false,
|
|
3302
|
+
elementDescription,
|
|
3303
|
+
request.message,
|
|
3304
|
+
startTime
|
|
3305
|
+
);
|
|
2852
3306
|
case "hasText":
|
|
2853
3307
|
return this.assertTextMatch(
|
|
2854
3308
|
element,
|
|
@@ -3186,7 +3640,8 @@ var DEFAULT_SNAPSHOT_CONFIG = {
|
|
|
3186
3640
|
detectModals: true,
|
|
3187
3641
|
inferPageType: true,
|
|
3188
3642
|
generateDescriptions: true,
|
|
3189
|
-
maxElements: 500
|
|
3643
|
+
maxElements: 500,
|
|
3644
|
+
useAnnotations: true
|
|
3190
3645
|
};
|
|
3191
3646
|
var SemanticSnapshotManager = class {
|
|
3192
3647
|
constructor(config = {}) {
|
|
@@ -3259,26 +3714,52 @@ var SemanticSnapshotManager = class {
|
|
|
3259
3714
|
* Convert a single element to AI element
|
|
3260
3715
|
*/
|
|
3261
3716
|
convertElement(element) {
|
|
3717
|
+
const isContent = element.category === "content";
|
|
3262
3718
|
const aliases = generateAliases({
|
|
3263
3719
|
textContent: element.state.textContent,
|
|
3264
3720
|
elementType: element.type,
|
|
3265
3721
|
id: element.id,
|
|
3266
3722
|
labelText: element.label
|
|
3267
3723
|
});
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3724
|
+
let description;
|
|
3725
|
+
if (isContent && element.contentMetadata) {
|
|
3726
|
+
description = this.generateContentDescription(element);
|
|
3727
|
+
} else if (this.config.generateDescriptions) {
|
|
3728
|
+
description = generateDescription({
|
|
3729
|
+
textContent: element.state.textContent,
|
|
3730
|
+
elementType: element.type,
|
|
3731
|
+
id: element.id,
|
|
3732
|
+
labelText: element.label
|
|
3733
|
+
});
|
|
3734
|
+
} else {
|
|
3735
|
+
description = element.label || element.id;
|
|
3736
|
+
}
|
|
3737
|
+
const purpose = isContent ? generatePurpose({ textContent: element.state.textContent, elementType: element.type }) : generatePurpose({ textContent: element.state.textContent, elementType: element.type });
|
|
3738
|
+
const suggestedActions = isContent ? generateSuggestedActions({
|
|
3275
3739
|
textContent: element.state.textContent,
|
|
3276
3740
|
elementType: element.type
|
|
3277
|
-
})
|
|
3278
|
-
const suggestedActions = generateSuggestedActions({
|
|
3741
|
+
}) : generateSuggestedActions({
|
|
3279
3742
|
textContent: element.state.textContent,
|
|
3280
3743
|
elementType: element.type
|
|
3281
3744
|
});
|
|
3745
|
+
let finalDescription = description;
|
|
3746
|
+
let finalPurpose = purpose;
|
|
3747
|
+
let finalAliases = aliases;
|
|
3748
|
+
if (this.config.useAnnotations) {
|
|
3749
|
+
const annotation = getGlobalAnnotationStore().get(element.id);
|
|
3750
|
+
if (annotation) {
|
|
3751
|
+
if (annotation.description) {
|
|
3752
|
+
finalDescription = annotation.description;
|
|
3753
|
+
}
|
|
3754
|
+
if (annotation.purpose) {
|
|
3755
|
+
finalPurpose = annotation.purpose;
|
|
3756
|
+
}
|
|
3757
|
+
if (annotation.tags && annotation.tags.length > 0) {
|
|
3758
|
+
const tagSet = /* @__PURE__ */ new Set([...finalAliases, ...annotation.tags.map((t) => t.toLowerCase())]);
|
|
3759
|
+
finalAliases = [...tagSet];
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3282
3763
|
return {
|
|
3283
3764
|
id: element.id,
|
|
3284
3765
|
type: element.type,
|
|
@@ -3289,13 +3770,56 @@ var SemanticSnapshotManager = class {
|
|
|
3289
3770
|
actions: element.actions,
|
|
3290
3771
|
state: element.state,
|
|
3291
3772
|
registered: true,
|
|
3292
|
-
description,
|
|
3293
|
-
aliases,
|
|
3294
|
-
purpose,
|
|
3773
|
+
description: finalDescription,
|
|
3774
|
+
aliases: finalAliases,
|
|
3775
|
+
purpose: finalPurpose,
|
|
3295
3776
|
suggestedActions,
|
|
3296
|
-
semanticType: this.inferSemanticType(element)
|
|
3777
|
+
semanticType: this.inferSemanticType(element),
|
|
3778
|
+
category: element.category,
|
|
3779
|
+
contentMetadata: element.contentMetadata
|
|
3297
3780
|
};
|
|
3298
3781
|
}
|
|
3782
|
+
/**
|
|
3783
|
+
* Generate a content-specific description
|
|
3784
|
+
*/
|
|
3785
|
+
generateContentDescription(element) {
|
|
3786
|
+
const meta = element.contentMetadata;
|
|
3787
|
+
const text = element.state.textContent?.trim() || "";
|
|
3788
|
+
const truncatedText = text.length > 60 ? text.substring(0, 57) + "..." : text;
|
|
3789
|
+
if (!meta) return `"${truncatedText}"`;
|
|
3790
|
+
switch (meta.contentRole) {
|
|
3791
|
+
case "heading":
|
|
3792
|
+
return `Level ${meta.headingLevel || "?"} heading: '${truncatedText}'`;
|
|
3793
|
+
case "table-cell":
|
|
3794
|
+
return `Table cell${meta.structuralContext ? ` (${meta.structuralContext})` : ""}: '${truncatedText}'`;
|
|
3795
|
+
case "table-header":
|
|
3796
|
+
return `Table header${meta.structuralContext ? ` (${meta.structuralContext})` : ""}: '${truncatedText}'`;
|
|
3797
|
+
case "status":
|
|
3798
|
+
return `Status message: '${truncatedText}'`;
|
|
3799
|
+
case "badge":
|
|
3800
|
+
return `Badge: '${truncatedText}'`;
|
|
3801
|
+
case "metric":
|
|
3802
|
+
return `Metric value: '${truncatedText}'`;
|
|
3803
|
+
case "body-text":
|
|
3804
|
+
return `Text: '${truncatedText}'`;
|
|
3805
|
+
case "list-item":
|
|
3806
|
+
return `List item: '${truncatedText}'`;
|
|
3807
|
+
case "quote":
|
|
3808
|
+
return `Blockquote: '${truncatedText}'`;
|
|
3809
|
+
case "code":
|
|
3810
|
+
return `Code block: '${truncatedText}'`;
|
|
3811
|
+
case "caption":
|
|
3812
|
+
return `Caption: '${truncatedText}'`;
|
|
3813
|
+
case "label":
|
|
3814
|
+
return `Label: '${truncatedText}'`;
|
|
3815
|
+
case "description":
|
|
3816
|
+
return `Description: '${truncatedText}'`;
|
|
3817
|
+
case "navigation":
|
|
3818
|
+
return `Navigation text: '${truncatedText}'`;
|
|
3819
|
+
default:
|
|
3820
|
+
return `Content: '${truncatedText}'`;
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3299
3823
|
/**
|
|
3300
3824
|
* Build full page context
|
|
3301
3825
|
*/
|
|
@@ -3399,9 +3923,7 @@ var SemanticSnapshotManager = class {
|
|
|
3399
3923
|
*/
|
|
3400
3924
|
detectModals(elements) {
|
|
3401
3925
|
const modals = [];
|
|
3402
|
-
const dialogElements = elements.filter(
|
|
3403
|
-
(el) => el.type === "dialog" && el.state.visible
|
|
3404
|
-
);
|
|
3926
|
+
const dialogElements = elements.filter((el) => el.type === "dialog" && el.state.visible);
|
|
3405
3927
|
for (const dialog of dialogElements) {
|
|
3406
3928
|
const closeButton = elements.find(
|
|
3407
3929
|
(el) => el.type === "button" && el.state.visible && (el.semanticType === "cancel-button" || el.state.textContent?.toLowerCase().match(/close|cancel|x|dismiss/))
|
|
@@ -3452,9 +3974,7 @@ var SemanticSnapshotManager = class {
|
|
|
3452
3974
|
* Infer form purpose from fields
|
|
3453
3975
|
*/
|
|
3454
3976
|
inferFormPurpose(fields) {
|
|
3455
|
-
const labels = fields.map(
|
|
3456
|
-
(f) => (f.accessibleName || f.label || "").toLowerCase()
|
|
3457
|
-
);
|
|
3977
|
+
const labels = fields.map((f) => (f.accessibleName || f.label || "").toLowerCase());
|
|
3458
3978
|
const allLabels = labels.join(" ");
|
|
3459
3979
|
if (allLabels.includes("email") && allLabels.includes("password")) {
|
|
3460
3980
|
if (allLabels.includes("confirm") || allLabels.includes("name")) {
|
|
@@ -3508,6 +4028,13 @@ var SemanticSnapshotManager = class {
|
|
|
3508
4028
|
* Infer semantic type
|
|
3509
4029
|
*/
|
|
3510
4030
|
inferSemanticType(element) {
|
|
4031
|
+
if (element.category === "content" && element.contentMetadata) {
|
|
4032
|
+
const role = element.contentMetadata.contentRole;
|
|
4033
|
+
if (role === "heading" && element.contentMetadata.headingLevel) {
|
|
4034
|
+
return `heading-${element.contentMetadata.headingLevel}`;
|
|
4035
|
+
}
|
|
4036
|
+
return role;
|
|
4037
|
+
}
|
|
3511
4038
|
const text = (element.state.textContent || element.label || "").toLowerCase();
|
|
3512
4039
|
const type = element.type.toLowerCase();
|
|
3513
4040
|
if (type === "button") {
|
|
@@ -3592,6 +4119,7 @@ function computeDiff(fromSnapshot, toSnapshot, config = {}) {
|
|
|
3592
4119
|
const probableTrigger = detectTrigger(appeared, disappeared, limitedModifications);
|
|
3593
4120
|
const suggestedActions = finalConfig.generateSuggestions ? generateSuggestedActionsFromDiff(appeared, disappeared, limitedModifications, probableTrigger) : void 0;
|
|
3594
4121
|
const pageChanges = detectPageChanges(fromSnapshot, toSnapshot);
|
|
4122
|
+
const contentChanges = detectContentChanges(fromElements, toElements);
|
|
3595
4123
|
const summary = generateDiffSummary(
|
|
3596
4124
|
appeared.map((e) => e.description),
|
|
3597
4125
|
disappeared.map((e) => e.description),
|
|
@@ -3606,6 +4134,7 @@ function computeDiff(fromSnapshot, toSnapshot, config = {}) {
|
|
|
3606
4134
|
disappeared,
|
|
3607
4135
|
modified: limitedModifications
|
|
3608
4136
|
},
|
|
4137
|
+
contentChanges: contentChanges || void 0,
|
|
3609
4138
|
probableTrigger,
|
|
3610
4139
|
suggestedActions,
|
|
3611
4140
|
pageChanges,
|
|
@@ -3740,9 +4269,7 @@ function generateSuggestedActionsFromDiff(appeared, disappeared, modified, trigg
|
|
|
3740
4269
|
suggestions.push("Fix the validation errors before submitting");
|
|
3741
4270
|
}
|
|
3742
4271
|
if (trigger === "Modal opened") {
|
|
3743
|
-
const modal = appeared.find(
|
|
3744
|
-
(e) => e.type === "dialog" || e.semanticType?.includes("dialog")
|
|
3745
|
-
);
|
|
4272
|
+
const modal = appeared.find((e) => e.type === "dialog" || e.semanticType?.includes("dialog"));
|
|
3746
4273
|
if (modal) {
|
|
3747
4274
|
suggestions.push(`Interact with the "${modal.description}" dialog`);
|
|
3748
4275
|
}
|
|
@@ -3813,6 +4340,12 @@ function hasSignificantChanges(diff) {
|
|
|
3813
4340
|
if (diff.changes.disappeared.length > 0) return true;
|
|
3814
4341
|
if (diff.changes.modified.some((m) => m.significant)) return true;
|
|
3815
4342
|
if (diff.pageChanges?.urlChanged) return true;
|
|
4343
|
+
if (diff.contentChanges) {
|
|
4344
|
+
const cc = diff.contentChanges;
|
|
4345
|
+
if (cc.textChanges.length > 0) return true;
|
|
4346
|
+
if (cc.metricChanges.some((m) => m.significant)) return true;
|
|
4347
|
+
if (cc.statusChanges.length > 0) return true;
|
|
4348
|
+
}
|
|
3816
4349
|
return false;
|
|
3817
4350
|
}
|
|
3818
4351
|
function describeDiff(diff) {
|
|
@@ -3830,27 +4363,1778 @@ function describeDiff(diff) {
|
|
|
3830
4363
|
if (diff.pageChanges?.urlChanged) {
|
|
3831
4364
|
parts.push("URL changed");
|
|
3832
4365
|
}
|
|
4366
|
+
if (diff.contentChanges) {
|
|
4367
|
+
parts.push(diff.contentChanges.summary);
|
|
4368
|
+
}
|
|
3833
4369
|
if (parts.length === 0) {
|
|
3834
4370
|
return "No significant changes";
|
|
3835
4371
|
}
|
|
3836
4372
|
return parts.join(", ");
|
|
3837
4373
|
}
|
|
4374
|
+
var METRIC_CONTENT_TYPES = /* @__PURE__ */ new Set(["metric-value"]);
|
|
4375
|
+
var STATUS_CONTENT_TYPES = /* @__PURE__ */ new Set(["status-message", "badge"]);
|
|
4376
|
+
var HEADING_CONTENT_TYPES = /* @__PURE__ */ new Set(["heading"]);
|
|
4377
|
+
function isContentElement(element) {
|
|
4378
|
+
return element.category === "content" || element.contentMetadata !== void 0;
|
|
4379
|
+
}
|
|
4380
|
+
function getContentType(element) {
|
|
4381
|
+
if (element.contentMetadata?.contentRole) {
|
|
4382
|
+
return element.contentMetadata.contentRole;
|
|
4383
|
+
}
|
|
4384
|
+
return element.type;
|
|
4385
|
+
}
|
|
4386
|
+
function detectContentChanges(fromElements, toElements) {
|
|
4387
|
+
const textChanges = [];
|
|
4388
|
+
const metricChanges = [];
|
|
4389
|
+
const statusChanges = [];
|
|
4390
|
+
for (const [id, toElement] of toElements) {
|
|
4391
|
+
const fromElement = fromElements.get(id);
|
|
4392
|
+
if (fromElement) {
|
|
4393
|
+
if (isContentElement(toElement) || isContentElement(fromElement)) {
|
|
4394
|
+
const fromText = (fromElement.state.textContent || "").trim();
|
|
4395
|
+
const toText = (toElement.state.textContent || "").trim();
|
|
4396
|
+
if (fromText !== toText) {
|
|
4397
|
+
const contentType = getContentType(toElement);
|
|
4398
|
+
const label = toElement.description || toElement.accessibleName || id;
|
|
4399
|
+
if (METRIC_CONTENT_TYPES.has(contentType) || contentType === "metric") {
|
|
4400
|
+
const parsed = parseMetricChange(fromText, toText, id, label);
|
|
4401
|
+
if (parsed) {
|
|
4402
|
+
metricChanges.push(parsed);
|
|
4403
|
+
}
|
|
4404
|
+
} else if (STATUS_CONTENT_TYPES.has(contentType) || contentType === "status") {
|
|
4405
|
+
statusChanges.push({
|
|
4406
|
+
elementId: id,
|
|
4407
|
+
label,
|
|
4408
|
+
oldStatus: fromText,
|
|
4409
|
+
newStatus: toText,
|
|
4410
|
+
direction: classifyStatusDirection(fromText, toText)
|
|
4411
|
+
});
|
|
4412
|
+
} else {
|
|
4413
|
+
textChanges.push({
|
|
4414
|
+
elementId: id,
|
|
4415
|
+
contentType,
|
|
4416
|
+
oldText: fromText,
|
|
4417
|
+
newText: toText,
|
|
4418
|
+
changeType: "modified"
|
|
4419
|
+
});
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
}
|
|
4423
|
+
} else {
|
|
4424
|
+
if (isContentElement(toElement)) {
|
|
4425
|
+
const toText = (toElement.state.textContent || "").trim();
|
|
4426
|
+
if (toText) {
|
|
4427
|
+
textChanges.push({
|
|
4428
|
+
elementId: id,
|
|
4429
|
+
contentType: getContentType(toElement),
|
|
4430
|
+
oldText: "",
|
|
4431
|
+
newText: toText,
|
|
4432
|
+
changeType: "added"
|
|
4433
|
+
});
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
for (const [id, fromElement] of fromElements) {
|
|
4439
|
+
if (!toElements.has(id) && isContentElement(fromElement)) {
|
|
4440
|
+
const fromText = (fromElement.state.textContent || "").trim();
|
|
4441
|
+
if (fromText) {
|
|
4442
|
+
textChanges.push({
|
|
4443
|
+
elementId: id,
|
|
4444
|
+
contentType: getContentType(fromElement),
|
|
4445
|
+
oldText: fromText,
|
|
4446
|
+
newText: "",
|
|
4447
|
+
changeType: "removed"
|
|
4448
|
+
});
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
if (textChanges.length === 0 && metricChanges.length === 0 && statusChanges.length === 0) {
|
|
4453
|
+
return null;
|
|
4454
|
+
}
|
|
4455
|
+
return {
|
|
4456
|
+
textChanges,
|
|
4457
|
+
metricChanges,
|
|
4458
|
+
statusChanges,
|
|
4459
|
+
summary: generateContentChangeSummary(textChanges, metricChanges, statusChanges)
|
|
4460
|
+
};
|
|
4461
|
+
}
|
|
4462
|
+
function parseNumericValue(text) {
|
|
4463
|
+
const trimmed = text.trim();
|
|
4464
|
+
if (!trimmed) return null;
|
|
4465
|
+
let working = trimmed;
|
|
4466
|
+
let negate = false;
|
|
4467
|
+
if (working.startsWith("(") && working.endsWith(")")) {
|
|
4468
|
+
working = working.slice(1, -1).trim();
|
|
4469
|
+
negate = true;
|
|
4470
|
+
}
|
|
4471
|
+
if (working.startsWith("-")) {
|
|
4472
|
+
negate = !negate;
|
|
4473
|
+
working = working.slice(1).trim();
|
|
4474
|
+
}
|
|
4475
|
+
if (working.startsWith("+")) {
|
|
4476
|
+
working = working.slice(1).trim();
|
|
4477
|
+
}
|
|
4478
|
+
working = working.replace(/^[£€¥₹$]/, "").trim();
|
|
4479
|
+
const isPercent = working.endsWith("%");
|
|
4480
|
+
if (isPercent) {
|
|
4481
|
+
working = working.slice(0, -1).trim();
|
|
4482
|
+
}
|
|
4483
|
+
working = working.replace(/\s*(ms|s|m|h|d|hrs?|mins?|secs?|days?)$/i, "").trim();
|
|
4484
|
+
working = working.replace(/,/g, "");
|
|
4485
|
+
const num = Number(working);
|
|
4486
|
+
if (isNaN(num) || !isFinite(num) || working === "") {
|
|
4487
|
+
return null;
|
|
4488
|
+
}
|
|
4489
|
+
return negate ? -num : num;
|
|
4490
|
+
}
|
|
4491
|
+
function parseMetricChange(fromText, toText, elementId, label) {
|
|
4492
|
+
const fromNum = parseNumericValue(fromText);
|
|
4493
|
+
const toNum = parseNumericValue(toText);
|
|
4494
|
+
let numericDelta;
|
|
4495
|
+
let percentChange;
|
|
4496
|
+
let significant = false;
|
|
4497
|
+
if (fromNum !== null && toNum !== null) {
|
|
4498
|
+
numericDelta = toNum - fromNum;
|
|
4499
|
+
if (fromNum !== 0) {
|
|
4500
|
+
percentChange = (toNum - fromNum) / Math.abs(fromNum) * 100;
|
|
4501
|
+
}
|
|
4502
|
+
if (percentChange !== void 0 && Math.abs(percentChange) > 10) {
|
|
4503
|
+
significant = true;
|
|
4504
|
+
}
|
|
4505
|
+
if (fromNum > 0 && toNum < 0) significant = true;
|
|
4506
|
+
if (fromNum < 0 && toNum > 0) significant = true;
|
|
4507
|
+
if (fromNum === 0 && toNum !== 0) significant = true;
|
|
4508
|
+
if (fromNum !== 0 && toNum === 0) significant = true;
|
|
4509
|
+
} else {
|
|
4510
|
+
significant = fromText !== toText;
|
|
4511
|
+
}
|
|
4512
|
+
return {
|
|
4513
|
+
elementId,
|
|
4514
|
+
label,
|
|
4515
|
+
oldValue: fromText,
|
|
4516
|
+
newValue: toText,
|
|
4517
|
+
numericDelta,
|
|
4518
|
+
percentChange: percentChange !== void 0 ? Math.round(percentChange * 100) / 100 : void 0,
|
|
4519
|
+
significant
|
|
4520
|
+
};
|
|
4521
|
+
}
|
|
4522
|
+
var STATUS_PROGRESSIONS = [
|
|
4523
|
+
[
|
|
4524
|
+
"failed",
|
|
4525
|
+
"error",
|
|
4526
|
+
"pending",
|
|
4527
|
+
"queued",
|
|
4528
|
+
"running",
|
|
4529
|
+
"in progress",
|
|
4530
|
+
"completed",
|
|
4531
|
+
"success",
|
|
4532
|
+
"done"
|
|
4533
|
+
],
|
|
4534
|
+
["disconnected", "connecting", "connected"],
|
|
4535
|
+
["unhealthy", "degraded", "healthy"],
|
|
4536
|
+
["offline", "online"],
|
|
4537
|
+
["inactive", "active"],
|
|
4538
|
+
["disabled", "enabled"],
|
|
4539
|
+
["down", "up"],
|
|
4540
|
+
["stopped", "starting", "started", "running"],
|
|
4541
|
+
["closed", "open"],
|
|
4542
|
+
["blocked", "unblocked"],
|
|
4543
|
+
["rejected", "pending", "approved"],
|
|
4544
|
+
["critical", "warning", "info", "ok"],
|
|
4545
|
+
["red", "yellow", "green"]
|
|
4546
|
+
];
|
|
4547
|
+
function classifyStatusDirection(oldStatus, newStatus) {
|
|
4548
|
+
const oldLower = oldStatus.toLowerCase().trim();
|
|
4549
|
+
const newLower = newStatus.toLowerCase().trim();
|
|
4550
|
+
for (const progression of STATUS_PROGRESSIONS) {
|
|
4551
|
+
let oldIndex = -1;
|
|
4552
|
+
let newIndex = -1;
|
|
4553
|
+
for (let i = 0; i < progression.length; i++) {
|
|
4554
|
+
if (oldLower.includes(progression[i])) oldIndex = i;
|
|
4555
|
+
if (newLower.includes(progression[i])) newIndex = i;
|
|
4556
|
+
}
|
|
4557
|
+
if (oldIndex >= 0 && newIndex >= 0 && oldIndex !== newIndex) {
|
|
4558
|
+
return newIndex > oldIndex ? "improved" : "degraded";
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4561
|
+
return "neutral";
|
|
4562
|
+
}
|
|
4563
|
+
function generateContentChangeSummary(textChanges, metricChanges, statusChanges) {
|
|
4564
|
+
const parts = [];
|
|
4565
|
+
const modified = textChanges.filter((t) => t.changeType === "modified").length;
|
|
4566
|
+
const added = textChanges.filter((t) => t.changeType === "added").length;
|
|
4567
|
+
const removed = textChanges.filter((t) => t.changeType === "removed").length;
|
|
4568
|
+
const headingChanges = textChanges.filter(
|
|
4569
|
+
(t) => HEADING_CONTENT_TYPES.has(t.contentType) || t.contentType === "heading"
|
|
4570
|
+
);
|
|
4571
|
+
if (headingChanges.length > 0) {
|
|
4572
|
+
parts.push(`${headingChanges.length} heading${headingChanges.length > 1 ? "s" : ""} changed`);
|
|
4573
|
+
}
|
|
4574
|
+
if (metricChanges.length > 0) {
|
|
4575
|
+
const significantMetrics = metricChanges.filter((m) => m.significant);
|
|
4576
|
+
if (significantMetrics.length > 0) {
|
|
4577
|
+
parts.push(
|
|
4578
|
+
`${significantMetrics.length} metric${significantMetrics.length > 1 ? "s" : ""} changed significantly`
|
|
4579
|
+
);
|
|
4580
|
+
} else {
|
|
4581
|
+
parts.push(`${metricChanges.length} metric${metricChanges.length > 1 ? "s" : ""} changed`);
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
if (statusChanges.length > 0) {
|
|
4585
|
+
const degraded = statusChanges.filter((s) => s.direction === "degraded");
|
|
4586
|
+
const improved = statusChanges.filter((s) => s.direction === "improved");
|
|
4587
|
+
if (degraded.length > 0) {
|
|
4588
|
+
parts.push(`${degraded.length} status${degraded.length > 1 ? "es" : ""} degraded`);
|
|
4589
|
+
}
|
|
4590
|
+
if (improved.length > 0) {
|
|
4591
|
+
parts.push(`${improved.length} status${improved.length > 1 ? "es" : ""} improved`);
|
|
4592
|
+
}
|
|
4593
|
+
const neutral = statusChanges.length - degraded.length - improved.length;
|
|
4594
|
+
if (neutral > 0 && degraded.length === 0 && improved.length === 0) {
|
|
4595
|
+
parts.push(`${neutral} status${neutral > 1 ? "es" : ""} changed`);
|
|
4596
|
+
}
|
|
4597
|
+
}
|
|
4598
|
+
const otherModified = modified - headingChanges.filter((h) => h.changeType === "modified").length;
|
|
4599
|
+
if (otherModified > 0) {
|
|
4600
|
+
parts.push(`${otherModified} text${otherModified > 1 ? " values" : " value"} modified`);
|
|
4601
|
+
}
|
|
4602
|
+
if (added > 0) {
|
|
4603
|
+
parts.push(`${added} content${added > 1 ? " elements" : " element"} added`);
|
|
4604
|
+
}
|
|
4605
|
+
if (removed > 0) {
|
|
4606
|
+
parts.push(`${removed} content${removed > 1 ? " elements" : " element"} removed`);
|
|
4607
|
+
}
|
|
4608
|
+
if (parts.length === 0) {
|
|
4609
|
+
return "No content changes";
|
|
4610
|
+
}
|
|
4611
|
+
return parts.join(", ");
|
|
4612
|
+
}
|
|
4613
|
+
|
|
4614
|
+
// src/ai/data-extraction.ts
|
|
4615
|
+
var DEFAULT_DATA_EXTRACTION_CONFIG = {
|
|
4616
|
+
minConfidence: 0.3,
|
|
4617
|
+
normalizeWhitespace: true
|
|
4618
|
+
};
|
|
4619
|
+
function classifyDataType(value) {
|
|
4620
|
+
const trimmed = value.trim();
|
|
4621
|
+
if (!trimmed) return { type: "unknown", confidence: 0 };
|
|
4622
|
+
if (/^(true|false|yes|no|on|off)$/i.test(trimmed)) {
|
|
4623
|
+
return { type: "boolean", confidence: 0.95 };
|
|
4624
|
+
}
|
|
4625
|
+
if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
|
|
4626
|
+
return { type: "email", confidence: 0.95 };
|
|
4627
|
+
}
|
|
4628
|
+
if (/^https?:\/\/\S+/.test(trimmed)) {
|
|
4629
|
+
return { type: "url", confidence: 0.95 };
|
|
4630
|
+
}
|
|
4631
|
+
if (/^[+]?[\d\s\-().]{7,20}$/.test(trimmed) && /\d{3,}/.test(trimmed)) {
|
|
4632
|
+
return { type: "phone", confidence: 0.7 };
|
|
4633
|
+
}
|
|
4634
|
+
if (/^[£$€¥₹][\s]?[\d,.]+$/.test(trimmed) || /^[\d,.]+[\s]?[£$€¥₹]$/.test(trimmed)) {
|
|
4635
|
+
return { type: "currency", confidence: 0.9 };
|
|
4636
|
+
}
|
|
4637
|
+
if (/^[\d,.]+\s?%$/.test(trimmed)) {
|
|
4638
|
+
return { type: "percentage", confidence: 0.95 };
|
|
4639
|
+
}
|
|
4640
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(trimmed) || /^\d{1,2}\/\d{1,2}\/\d{2,4}$/.test(trimmed) || /^\d{1,2}\.\d{1,2}\.\d{2,4}$/.test(trimmed) || /^\w{3,9}\s+\d{1,2},?\s+\d{4}$/.test(trimmed)) {
|
|
4641
|
+
return { type: "date", confidence: 0.85 };
|
|
4642
|
+
}
|
|
4643
|
+
if (/^-?[\d,]+\.?\d*$/.test(trimmed) && trimmed !== "") {
|
|
4644
|
+
return { type: "number", confidence: 0.9 };
|
|
4645
|
+
}
|
|
4646
|
+
return { type: "text", confidence: 0.5 };
|
|
4647
|
+
}
|
|
4648
|
+
function normalizeValue(value, dataType) {
|
|
4649
|
+
const trimmed = value.trim();
|
|
4650
|
+
switch (dataType) {
|
|
4651
|
+
case "number":
|
|
4652
|
+
case "currency":
|
|
4653
|
+
case "percentage": {
|
|
4654
|
+
const numeric = trimmed.replace(/[^0-9.-]/g, "");
|
|
4655
|
+
const parsed = parseFloat(numeric);
|
|
4656
|
+
return isNaN(parsed) ? trimmed.toLowerCase() : parsed.toString();
|
|
4657
|
+
}
|
|
4658
|
+
case "date": {
|
|
4659
|
+
const d = new Date(trimmed);
|
|
4660
|
+
return isNaN(d.getTime()) ? trimmed.toLowerCase() : d.toISOString().split("T")[0];
|
|
4661
|
+
}
|
|
4662
|
+
case "boolean":
|
|
4663
|
+
return /^(true|yes|on)$/i.test(trimmed) ? "true" : "false";
|
|
4664
|
+
case "email":
|
|
4665
|
+
return trimmed.toLowerCase();
|
|
4666
|
+
case "url":
|
|
4667
|
+
return trimmed.replace(/\/+$/, "").toLowerCase();
|
|
4668
|
+
case "phone":
|
|
4669
|
+
return trimmed.replace(/[^\d+]/g, "");
|
|
4670
|
+
default:
|
|
4671
|
+
return trimmed.toLowerCase().replace(/\s+/g, " ");
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
function extractElementValue(element) {
|
|
4675
|
+
const state = element.state;
|
|
4676
|
+
if (state?.value !== void 0 && state.value !== "") {
|
|
4677
|
+
return String(state.value);
|
|
4678
|
+
}
|
|
4679
|
+
if (state?.textContent !== void 0 && state.textContent !== "") {
|
|
4680
|
+
return String(state.textContent);
|
|
4681
|
+
}
|
|
4682
|
+
return "";
|
|
4683
|
+
}
|
|
4684
|
+
function extractLabel(element) {
|
|
4685
|
+
return element.accessibleName || element.labelText || element.label || element.description || element.id;
|
|
4686
|
+
}
|
|
4687
|
+
function extractPageData(elements, config = DEFAULT_DATA_EXTRACTION_CONFIG) {
|
|
4688
|
+
const values = {};
|
|
4689
|
+
let extractedCount = 0;
|
|
4690
|
+
for (const element of elements) {
|
|
4691
|
+
const rawValue = extractElementValue(element);
|
|
4692
|
+
if (!rawValue) continue;
|
|
4693
|
+
const label = extractLabel(element);
|
|
4694
|
+
const { type: dataType, confidence } = classifyDataType(rawValue);
|
|
4695
|
+
if (confidence < config.minConfidence) continue;
|
|
4696
|
+
const normalizedValue = normalizeValue(rawValue, dataType);
|
|
4697
|
+
values[label] = {
|
|
4698
|
+
elementId: element.id,
|
|
4699
|
+
label,
|
|
4700
|
+
rawValue: config.normalizeWhitespace ? rawValue.replace(/\s+/g, " ").trim() : rawValue,
|
|
4701
|
+
normalizedValue,
|
|
4702
|
+
dataType,
|
|
4703
|
+
confidence
|
|
4704
|
+
};
|
|
4705
|
+
extractedCount++;
|
|
4706
|
+
}
|
|
4707
|
+
return {
|
|
4708
|
+
values,
|
|
4709
|
+
scannedCount: elements.length,
|
|
4710
|
+
extractedCount
|
|
4711
|
+
};
|
|
4712
|
+
}
|
|
4713
|
+
|
|
4714
|
+
// src/ai/region-segmentation.ts
|
|
4715
|
+
var DEFAULT_REGION_SEGMENTATION_CONFIG = {
|
|
4716
|
+
minRegionElements: 1,
|
|
4717
|
+
headerFraction: 0.12,
|
|
4718
|
+
footerFraction: 0.9,
|
|
4719
|
+
sidebarFraction: 0.2
|
|
4720
|
+
};
|
|
4721
|
+
function toBounded(el) {
|
|
4722
|
+
const rect = el.state?.rect;
|
|
4723
|
+
if (!rect) return null;
|
|
4724
|
+
return {
|
|
4725
|
+
element: el,
|
|
4726
|
+
x: rect.x ?? 0,
|
|
4727
|
+
y: rect.y ?? 0,
|
|
4728
|
+
width: rect.width ?? 0,
|
|
4729
|
+
height: rect.height ?? 0
|
|
4730
|
+
};
|
|
4731
|
+
}
|
|
4732
|
+
function classifyRegionType(el, relativeY, relativeX, config = DEFAULT_REGION_SEGMENTATION_CONFIG) {
|
|
4733
|
+
const role = (el.role || "").toLowerCase();
|
|
4734
|
+
const semanticType = (el.semanticType || "").toLowerCase();
|
|
4735
|
+
const tag = (el.tagName || "").toLowerCase();
|
|
4736
|
+
if (role === "navigation" || role === "nav" || tag === "nav") {
|
|
4737
|
+
return { type: "navigation", confidence: 0.95 };
|
|
4738
|
+
}
|
|
4739
|
+
if (role === "banner" || tag === "header") {
|
|
4740
|
+
return { type: "header", confidence: 0.95 };
|
|
4741
|
+
}
|
|
4742
|
+
if (role === "contentinfo" || tag === "footer") {
|
|
4743
|
+
return { type: "footer", confidence: 0.95 };
|
|
4744
|
+
}
|
|
4745
|
+
if (role === "main" || tag === "main") {
|
|
4746
|
+
return { type: "main-content", confidence: 0.95 };
|
|
4747
|
+
}
|
|
4748
|
+
if (role === "complementary" || tag === "aside") {
|
|
4749
|
+
return { type: "sidebar", confidence: 0.9 };
|
|
4750
|
+
}
|
|
4751
|
+
if (role === "form" || tag === "form") {
|
|
4752
|
+
return { type: "form", confidence: 0.9 };
|
|
4753
|
+
}
|
|
4754
|
+
if (role === "table" || tag === "table") {
|
|
4755
|
+
return { type: "table", confidence: 0.9 };
|
|
4756
|
+
}
|
|
4757
|
+
if (role === "dialog" || role === "alertdialog") {
|
|
4758
|
+
return { type: "modal", confidence: 0.95 };
|
|
4759
|
+
}
|
|
4760
|
+
if (role === "toolbar") {
|
|
4761
|
+
return { type: "toolbar", confidence: 0.9 };
|
|
4762
|
+
}
|
|
4763
|
+
if (semanticType.includes("card")) {
|
|
4764
|
+
return { type: "card", confidence: 0.8 };
|
|
4765
|
+
}
|
|
4766
|
+
if (relativeY < config.headerFraction) {
|
|
4767
|
+
return { type: "header", confidence: 0.6 };
|
|
4768
|
+
}
|
|
4769
|
+
if (relativeY > config.footerFraction) {
|
|
4770
|
+
return { type: "footer", confidence: 0.6 };
|
|
4771
|
+
}
|
|
4772
|
+
if (relativeX < config.sidebarFraction) {
|
|
4773
|
+
return { type: "sidebar", confidence: 0.5 };
|
|
4774
|
+
}
|
|
4775
|
+
return { type: "main-content", confidence: 0.3 };
|
|
4776
|
+
}
|
|
4777
|
+
function segmentPageRegions(elements, config = DEFAULT_REGION_SEGMENTATION_CONFIG) {
|
|
4778
|
+
const bounded = elements.map(toBounded).filter((b) => b !== null);
|
|
4779
|
+
if (bounded.length === 0) {
|
|
4780
|
+
return { regions: [], assignedCount: 0, unassignedIds: elements.map((e) => e.id) };
|
|
4781
|
+
}
|
|
4782
|
+
let maxX = 0;
|
|
4783
|
+
let maxY = 0;
|
|
4784
|
+
for (const b of bounded) {
|
|
4785
|
+
maxX = Math.max(maxX, b.x + b.width);
|
|
4786
|
+
maxY = Math.max(maxY, b.y + b.height);
|
|
4787
|
+
}
|
|
4788
|
+
if (maxX === 0) maxX = 1;
|
|
4789
|
+
if (maxY === 0) maxY = 1;
|
|
4790
|
+
const regionGroups = /* @__PURE__ */ new Map();
|
|
4791
|
+
const unassignedIds = [];
|
|
4792
|
+
for (const b of bounded) {
|
|
4793
|
+
const relativeX = b.x / maxX;
|
|
4794
|
+
const relativeY = b.y / maxY;
|
|
4795
|
+
const { type, confidence } = classifyRegionType(b.element, relativeY, relativeX, config);
|
|
4796
|
+
if (!regionGroups.has(type)) {
|
|
4797
|
+
regionGroups.set(type, { elements: [], confidences: [] });
|
|
4798
|
+
}
|
|
4799
|
+
regionGroups.get(type).elements.push(b);
|
|
4800
|
+
regionGroups.get(type).confidences.push(confidence);
|
|
4801
|
+
}
|
|
4802
|
+
const regions = [];
|
|
4803
|
+
let assignedCount = 0;
|
|
4804
|
+
for (const [type, group] of regionGroups) {
|
|
4805
|
+
if (group.elements.length < config.minRegionElements) {
|
|
4806
|
+
for (const b of group.elements) unassignedIds.push(b.element.id);
|
|
4807
|
+
continue;
|
|
4808
|
+
}
|
|
4809
|
+
let minX = Infinity, minY = Infinity, maxRX = 0, maxRY = 0;
|
|
4810
|
+
const elementIds = [];
|
|
4811
|
+
for (const b of group.elements) {
|
|
4812
|
+
minX = Math.min(minX, b.x);
|
|
4813
|
+
minY = Math.min(minY, b.y);
|
|
4814
|
+
maxRX = Math.max(maxRX, b.x + b.width);
|
|
4815
|
+
maxRY = Math.max(maxRY, b.y + b.height);
|
|
4816
|
+
elementIds.push(b.element.id);
|
|
4817
|
+
}
|
|
4818
|
+
const avgConfidence = group.confidences.reduce((a, b) => a + b, 0) / group.confidences.length;
|
|
4819
|
+
regions.push({
|
|
4820
|
+
type,
|
|
4821
|
+
bounds: { x: minX, y: minY, width: maxRX - minX, height: maxRY - minY },
|
|
4822
|
+
elementIds,
|
|
4823
|
+
label: type.replace("-", " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
4824
|
+
confidence: Math.round(avgConfidence * 100) / 100
|
|
4825
|
+
});
|
|
4826
|
+
assignedCount += elementIds.length;
|
|
4827
|
+
}
|
|
4828
|
+
return { regions, assignedCount, unassignedIds };
|
|
4829
|
+
}
|
|
4830
|
+
|
|
4831
|
+
// src/ai/table-extraction.ts
|
|
4832
|
+
var DEFAULT_TABLE_EXTRACTION_CONFIG = {
|
|
4833
|
+
minTableColumns: 2,
|
|
4834
|
+
minTableRows: 2,
|
|
4835
|
+
minListItems: 2,
|
|
4836
|
+
columnTolerance: 20,
|
|
4837
|
+
rowTolerance: 10
|
|
4838
|
+
};
|
|
4839
|
+
function getElementBounds(el) {
|
|
4840
|
+
const rect = el.state?.rect;
|
|
4841
|
+
if (!rect || rect.width === 0) return null;
|
|
4842
|
+
const text = el.state?.textContent ?? el.state?.value ?? "";
|
|
4843
|
+
if (!text) return null;
|
|
4844
|
+
return {
|
|
4845
|
+
element: el,
|
|
4846
|
+
x: rect.x ?? 0,
|
|
4847
|
+
y: rect.y ?? 0,
|
|
4848
|
+
width: rect.width ?? 0,
|
|
4849
|
+
height: rect.height ?? 0,
|
|
4850
|
+
text: text.trim()
|
|
4851
|
+
};
|
|
4852
|
+
}
|
|
4853
|
+
function clusterPositions(values, tolerance) {
|
|
4854
|
+
if (values.length === 0) return [];
|
|
4855
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
4856
|
+
const clusters = [sorted[0]];
|
|
4857
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
4858
|
+
if (sorted[i] - clusters[clusters.length - 1] > tolerance) {
|
|
4859
|
+
clusters.push(sorted[i]);
|
|
4860
|
+
}
|
|
4861
|
+
}
|
|
4862
|
+
return clusters;
|
|
4863
|
+
}
|
|
4864
|
+
function assignToCluster(value, clusters, tolerance) {
|
|
4865
|
+
let best = 0;
|
|
4866
|
+
let bestDist = Math.abs(value - clusters[0]);
|
|
4867
|
+
for (let i = 1; i < clusters.length; i++) {
|
|
4868
|
+
const dist = Math.abs(value - clusters[i]);
|
|
4869
|
+
if (dist < bestDist) {
|
|
4870
|
+
bestDist = dist;
|
|
4871
|
+
best = i;
|
|
4872
|
+
}
|
|
4873
|
+
}
|
|
4874
|
+
return bestDist <= tolerance ? best : -1;
|
|
4875
|
+
}
|
|
4876
|
+
function detectTable(elements, config = DEFAULT_TABLE_EXTRACTION_CONFIG) {
|
|
4877
|
+
const withBounds = elements.map(getElementBounds).filter((b) => b !== null);
|
|
4878
|
+
if (withBounds.length < config.minTableColumns * config.minTableRows) return null;
|
|
4879
|
+
const xPositions = withBounds.map((b) => b.x);
|
|
4880
|
+
const yPositions = withBounds.map((b) => b.y);
|
|
4881
|
+
const columnClusters = clusterPositions(xPositions, config.columnTolerance);
|
|
4882
|
+
const rowClusters = clusterPositions(yPositions, config.rowTolerance);
|
|
4883
|
+
if (columnClusters.length < config.minTableColumns || rowClusters.length < config.minTableRows) {
|
|
4884
|
+
return null;
|
|
4885
|
+
}
|
|
4886
|
+
const grid = Array.from(
|
|
4887
|
+
{ length: rowClusters.length },
|
|
4888
|
+
() => Array(columnClusters.length).fill(null)
|
|
4889
|
+
);
|
|
4890
|
+
for (const b of withBounds) {
|
|
4891
|
+
const col = assignToCluster(b.x, columnClusters, config.columnTolerance);
|
|
4892
|
+
const row = assignToCluster(b.y, rowClusters, config.rowTolerance);
|
|
4893
|
+
if (col >= 0 && row >= 0 && grid[row][col] === null) {
|
|
4894
|
+
grid[row][col] = b.text;
|
|
4895
|
+
}
|
|
4896
|
+
}
|
|
4897
|
+
const headers = grid[0].map((h) => h ?? "");
|
|
4898
|
+
const columns = headers.map((header, index) => {
|
|
4899
|
+
const bodyCells = grid.slice(1).map((r) => r[index]).filter((c) => c !== null);
|
|
4900
|
+
const types = bodyCells.map((c) => classifyDataType(c).type);
|
|
4901
|
+
const mostCommon = mode(types) ?? "text";
|
|
4902
|
+
return { header, index, dataType: mostCommon };
|
|
4903
|
+
});
|
|
4904
|
+
const rows = grid.slice(1).map((row) => row.map((cell) => cell ?? ""));
|
|
4905
|
+
return {
|
|
4906
|
+
label: headers[0] || "Table",
|
|
4907
|
+
columns,
|
|
4908
|
+
rows
|
|
4909
|
+
};
|
|
4910
|
+
}
|
|
4911
|
+
function detectList(elements, config = DEFAULT_TABLE_EXTRACTION_CONFIG) {
|
|
4912
|
+
const withBounds = elements.map(getElementBounds).filter((b) => b !== null);
|
|
4913
|
+
if (withBounds.length < config.minListItems) return null;
|
|
4914
|
+
const sorted = [...withBounds].sort((a, b) => a.y - b.y);
|
|
4915
|
+
const yPositions = sorted.map((b) => b.y);
|
|
4916
|
+
const rowClusters = clusterPositions(yPositions, config.rowTolerance);
|
|
4917
|
+
if (rowClusters.length < config.minListItems) return null;
|
|
4918
|
+
const rowGroups = /* @__PURE__ */ new Map();
|
|
4919
|
+
for (const b of sorted) {
|
|
4920
|
+
const row = assignToCluster(b.y, rowClusters, config.rowTolerance);
|
|
4921
|
+
if (row >= 0) {
|
|
4922
|
+
if (!rowGroups.has(row)) rowGroups.set(row, []);
|
|
4923
|
+
rowGroups.get(row).push(b);
|
|
4924
|
+
}
|
|
4925
|
+
}
|
|
4926
|
+
const items = [];
|
|
4927
|
+
const fieldLabels = [];
|
|
4928
|
+
let fieldLabelsInitialized = false;
|
|
4929
|
+
for (const [, rowElements] of [...rowGroups.entries()].sort(([a], [b]) => a - b)) {
|
|
4930
|
+
const sortedRow = [...rowElements].sort((a, b) => a.x - b.x);
|
|
4931
|
+
const item = {};
|
|
4932
|
+
for (let i = 0; i < sortedRow.length; i++) {
|
|
4933
|
+
const label = `field_${i}`;
|
|
4934
|
+
if (!fieldLabelsInitialized) fieldLabels.push(label);
|
|
4935
|
+
item[label] = sortedRow[i].text;
|
|
4936
|
+
}
|
|
4937
|
+
fieldLabelsInitialized = true;
|
|
4938
|
+
items.push(item);
|
|
4939
|
+
}
|
|
4940
|
+
if (items.length < config.minListItems) return null;
|
|
4941
|
+
const fields = fieldLabels.map((label) => {
|
|
4942
|
+
const values = items.map((item) => item[label]).filter(Boolean);
|
|
4943
|
+
const types = values.map((v) => classifyDataType(v).type);
|
|
4944
|
+
return { label, dataType: mode(types) ?? "text" };
|
|
4945
|
+
});
|
|
4946
|
+
return {
|
|
4947
|
+
label: "List",
|
|
4948
|
+
fields,
|
|
4949
|
+
items
|
|
4950
|
+
};
|
|
4951
|
+
}
|
|
4952
|
+
function extractStructuredData(elements, config = DEFAULT_TABLE_EXTRACTION_CONFIG) {
|
|
4953
|
+
const tables = [];
|
|
4954
|
+
const lists = [];
|
|
4955
|
+
const table = detectTable(elements, config);
|
|
4956
|
+
if (table) {
|
|
4957
|
+
tables.push(table);
|
|
4958
|
+
}
|
|
4959
|
+
const listCandidates = elements.filter((el) => {
|
|
4960
|
+
const role = el.role || el.type;
|
|
4961
|
+
return ["listitem", "row", "option", "link", "button"].includes(role);
|
|
4962
|
+
});
|
|
4963
|
+
if (listCandidates.length >= config.minListItems) {
|
|
4964
|
+
const list = detectList(listCandidates, config);
|
|
4965
|
+
if (list) {
|
|
4966
|
+
lists.push(list);
|
|
4967
|
+
}
|
|
4968
|
+
}
|
|
4969
|
+
return { tables, lists };
|
|
4970
|
+
}
|
|
4971
|
+
function mode(arr) {
|
|
4972
|
+
if (arr.length === 0) return void 0;
|
|
4973
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4974
|
+
let best = arr[0];
|
|
4975
|
+
let bestCount = 0;
|
|
4976
|
+
for (const v of arr) {
|
|
4977
|
+
const c = (counts.get(v) ?? 0) + 1;
|
|
4978
|
+
counts.set(v, c);
|
|
4979
|
+
if (c > bestCount) {
|
|
4980
|
+
bestCount = c;
|
|
4981
|
+
best = v;
|
|
4982
|
+
}
|
|
4983
|
+
}
|
|
4984
|
+
return best;
|
|
4985
|
+
}
|
|
4986
|
+
|
|
4987
|
+
// src/ai/format-analysis.ts
|
|
4988
|
+
var DEFAULT_FORMAT_ANALYSIS_CONFIG = {
|
|
4989
|
+
lenientFormatting: true
|
|
4990
|
+
};
|
|
4991
|
+
function detectFormatPattern(value, dataType) {
|
|
4992
|
+
const trimmed = value.trim();
|
|
4993
|
+
switch (dataType) {
|
|
4994
|
+
case "currency": {
|
|
4995
|
+
const hasLeadingSymbol = /^[£$€¥₹]/.test(trimmed);
|
|
4996
|
+
const hasTrailingSymbol = /[£$€¥₹]$/.test(trimmed);
|
|
4997
|
+
const usesCommaThousands = /\d{1,3}(,\d{3})+/.test(trimmed);
|
|
4998
|
+
const usesPeriodThousands = /\d{1,3}(\.\d{3})+,/.test(trimmed);
|
|
4999
|
+
let pattern = hasLeadingSymbol ? "$" : "";
|
|
5000
|
+
if (usesCommaThousands) pattern += "#,###";
|
|
5001
|
+
else if (usesPeriodThousands) pattern += "#.###";
|
|
5002
|
+
else pattern += "#";
|
|
5003
|
+
if (/\.\d{2}$/.test(trimmed)) pattern += ".##";
|
|
5004
|
+
else if (/,\d{2}$/.test(trimmed)) pattern += ",##";
|
|
5005
|
+
if (hasTrailingSymbol) pattern += "$";
|
|
5006
|
+
return pattern;
|
|
5007
|
+
}
|
|
5008
|
+
case "date": {
|
|
5009
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(trimmed)) return "YYYY-MM-DD";
|
|
5010
|
+
if (/^\d{2}\/\d{2}\/\d{4}$/.test(trimmed)) return "MM/DD/YYYY";
|
|
5011
|
+
if (/^\d{2}\.\d{2}\.\d{4}$/.test(trimmed)) return "DD.MM.YYYY";
|
|
5012
|
+
if (/^\d{1,2}\/\d{1,2}\/\d{2}$/.test(trimmed)) return "M/D/YY";
|
|
5013
|
+
if (/^\w{3,9}\s+\d{1,2},?\s+\d{4}$/.test(trimmed)) return "Month DD, YYYY";
|
|
5014
|
+
return "date";
|
|
5015
|
+
}
|
|
5016
|
+
case "percentage":
|
|
5017
|
+
return /\s%$/.test(trimmed) ? "#.## %" : "#.##%";
|
|
5018
|
+
case "number": {
|
|
5019
|
+
const hasCommas = /,/.test(trimmed);
|
|
5020
|
+
const decimalPlaces = trimmed.includes(".") ? trimmed.split(".")[1]?.length || 0 : 0;
|
|
5021
|
+
return (hasCommas ? "#,###" : "#") + (decimalPlaces > 0 ? "." + "#".repeat(decimalPlaces) : "");
|
|
5022
|
+
}
|
|
5023
|
+
case "phone": {
|
|
5024
|
+
if (/^\(\d{3}\)\s?\d{3}-\d{4}$/.test(trimmed)) return "(###) ###-####";
|
|
5025
|
+
if (/^\d{3}-\d{3}-\d{4}$/.test(trimmed)) return "###-###-####";
|
|
5026
|
+
if (/^\+\d/.test(trimmed)) return "+# ###...";
|
|
5027
|
+
return "phone";
|
|
5028
|
+
}
|
|
5029
|
+
default:
|
|
5030
|
+
return dataType;
|
|
5031
|
+
}
|
|
5032
|
+
}
|
|
5033
|
+
function analyzeFormat(elementId, label, rawValue) {
|
|
5034
|
+
const { type: dataType } = classifyDataType(rawValue);
|
|
5035
|
+
const pattern = detectFormatPattern(rawValue, dataType);
|
|
5036
|
+
return {
|
|
5037
|
+
elementId,
|
|
5038
|
+
label,
|
|
5039
|
+
dataType,
|
|
5040
|
+
pattern,
|
|
5041
|
+
example: rawValue.trim()
|
|
5042
|
+
};
|
|
5043
|
+
}
|
|
5044
|
+
function analyzePageFormats(elements) {
|
|
5045
|
+
const descriptors = [];
|
|
5046
|
+
for (const el of elements) {
|
|
5047
|
+
const rawValue = el.state?.value ?? el.state?.textContent ?? "";
|
|
5048
|
+
if (!rawValue) continue;
|
|
5049
|
+
const label = el.accessibleName || el.labelText || el.label || el.description || el.id;
|
|
5050
|
+
descriptors.push(analyzeFormat(el.id, label, rawValue));
|
|
5051
|
+
}
|
|
5052
|
+
return descriptors;
|
|
5053
|
+
}
|
|
5054
|
+
function compareFormats(sourceFormats, targetFormats, config = DEFAULT_FORMAT_ANALYSIS_CONFIG) {
|
|
5055
|
+
const mismatches = [];
|
|
5056
|
+
const targetByLabel = /* @__PURE__ */ new Map();
|
|
5057
|
+
for (const t of targetFormats) {
|
|
5058
|
+
targetByLabel.set(t.label.toLowerCase(), t);
|
|
5059
|
+
}
|
|
5060
|
+
for (const source of sourceFormats) {
|
|
5061
|
+
const target = targetByLabel.get(source.label.toLowerCase());
|
|
5062
|
+
if (!target) continue;
|
|
5063
|
+
if (source.dataType !== target.dataType) {
|
|
5064
|
+
mismatches.push({
|
|
5065
|
+
label: source.label,
|
|
5066
|
+
sourceFormat: source,
|
|
5067
|
+
targetFormat: target,
|
|
5068
|
+
severity: "error",
|
|
5069
|
+
description: `Data type mismatch: source is ${source.dataType}, target is ${target.dataType}`
|
|
5070
|
+
});
|
|
5071
|
+
continue;
|
|
5072
|
+
}
|
|
5073
|
+
if (source.pattern !== target.pattern) {
|
|
5074
|
+
const severity = config.lenientFormatting ? "warning" : "error";
|
|
5075
|
+
mismatches.push({
|
|
5076
|
+
label: source.label,
|
|
5077
|
+
sourceFormat: source,
|
|
5078
|
+
targetFormat: target,
|
|
5079
|
+
severity,
|
|
5080
|
+
description: `Format differs: source uses "${source.pattern}", target uses "${target.pattern}"`
|
|
5081
|
+
});
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
return mismatches;
|
|
5085
|
+
}
|
|
5086
|
+
|
|
5087
|
+
// src/ai/cross-app-diff.ts
|
|
5088
|
+
var DEFAULT_CROSS_APP_DIFF_CONFIG = {
|
|
5089
|
+
matchThreshold: 0.5,
|
|
5090
|
+
accessibleNameWeight: 1,
|
|
5091
|
+
textWeight: 0.95,
|
|
5092
|
+
rolePositionWeight: 0.7
|
|
5093
|
+
};
|
|
5094
|
+
function getElementText(el) {
|
|
5095
|
+
return el.accessibleName || el.labelText || el.label || el.state?.textContent || el.description || "";
|
|
5096
|
+
}
|
|
5097
|
+
function getRole(el) {
|
|
5098
|
+
return (el.role || el.type || "").toLowerCase();
|
|
5099
|
+
}
|
|
5100
|
+
function getCenter(el) {
|
|
5101
|
+
const rect = el.state?.rect;
|
|
5102
|
+
if (!rect) return null;
|
|
5103
|
+
return {
|
|
5104
|
+
x: rect.x + rect.width / 2,
|
|
5105
|
+
y: rect.y + rect.height / 2
|
|
5106
|
+
};
|
|
5107
|
+
}
|
|
5108
|
+
function computeMatchScore(source, target, config) {
|
|
5109
|
+
let bestScore = 0;
|
|
5110
|
+
let bestStrategy = "none";
|
|
5111
|
+
const srcName = (source.accessibleName || "").trim();
|
|
5112
|
+
const tgtName = (target.accessibleName || "").trim();
|
|
5113
|
+
if (srcName && tgtName && srcName.toLowerCase() === tgtName.toLowerCase()) {
|
|
5114
|
+
return { score: config.accessibleNameWeight, strategy: "accessible-name-exact" };
|
|
5115
|
+
}
|
|
5116
|
+
const srcText = getElementText(source);
|
|
5117
|
+
const tgtText = getElementText(target);
|
|
5118
|
+
if (srcText && tgtText && srcText.toLowerCase() === tgtText.toLowerCase()) {
|
|
5119
|
+
const score = config.textWeight;
|
|
5120
|
+
if (score > bestScore) {
|
|
5121
|
+
bestScore = score;
|
|
5122
|
+
bestStrategy = "text-exact";
|
|
5123
|
+
}
|
|
5124
|
+
}
|
|
5125
|
+
if (srcText && tgtText) {
|
|
5126
|
+
const srcNorm = normalizeString(srcText);
|
|
5127
|
+
const tgtNorm = normalizeString(tgtText);
|
|
5128
|
+
const similarity = jaroWinklerSimilarity(srcNorm, tgtNorm);
|
|
5129
|
+
const score = similarity * 0.85;
|
|
5130
|
+
if (score > bestScore) {
|
|
5131
|
+
bestScore = score;
|
|
5132
|
+
bestStrategy = "text-fuzzy";
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
const srcRole = getRole(source);
|
|
5136
|
+
const tgtRole = getRole(target);
|
|
5137
|
+
if (srcRole && srcRole === tgtRole) {
|
|
5138
|
+
const srcCenter = getCenter(source);
|
|
5139
|
+
const tgtCenter = getCenter(target);
|
|
5140
|
+
if (srcCenter && tgtCenter) {
|
|
5141
|
+
const dx = Math.abs(srcCenter.x - tgtCenter.x) / 1920;
|
|
5142
|
+
const dy = Math.abs(srcCenter.y - tgtCenter.y) / 1080;
|
|
5143
|
+
const posSimilarity = 1 - Math.min(1, Math.sqrt(dx * dx + dy * dy));
|
|
5144
|
+
const score = config.rolePositionWeight * posSimilarity;
|
|
5145
|
+
if (score > bestScore) {
|
|
5146
|
+
bestScore = score;
|
|
5147
|
+
bestStrategy = "role-position";
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5150
|
+
}
|
|
5151
|
+
const srcVal = source.state?.value ?? source.state?.textContent ?? "";
|
|
5152
|
+
const tgtVal = target.state?.value ?? target.state?.textContent ?? "";
|
|
5153
|
+
if (srcVal && tgtVal) {
|
|
5154
|
+
const srcType = classifyDataType(srcVal).type;
|
|
5155
|
+
const tgtType = classifyDataType(tgtVal).type;
|
|
5156
|
+
const srcNorm = normalizeValue(srcVal, srcType);
|
|
5157
|
+
const tgtNorm = normalizeValue(tgtVal, tgtType);
|
|
5158
|
+
if (srcNorm === tgtNorm && srcNorm !== "") {
|
|
5159
|
+
const score = 0.6;
|
|
5160
|
+
if (score > bestScore) {
|
|
5161
|
+
bestScore = score;
|
|
5162
|
+
bestStrategy = "data-overlap";
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
}
|
|
5166
|
+
return { score: bestScore, strategy: bestStrategy };
|
|
5167
|
+
}
|
|
5168
|
+
function matchElements(sourceElements, targetElements, config = DEFAULT_CROSS_APP_DIFF_CONFIG) {
|
|
5169
|
+
const candidates = [];
|
|
5170
|
+
for (let si = 0; si < sourceElements.length; si++) {
|
|
5171
|
+
for (let ti = 0; ti < targetElements.length; ti++) {
|
|
5172
|
+
const { score, strategy } = computeMatchScore(sourceElements[si], targetElements[ti], config);
|
|
5173
|
+
if (score >= config.matchThreshold) {
|
|
5174
|
+
candidates.push({ sourceIdx: si, targetIdx: ti, score, strategy });
|
|
5175
|
+
}
|
|
5176
|
+
}
|
|
5177
|
+
}
|
|
5178
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
5179
|
+
const usedSource = /* @__PURE__ */ new Set();
|
|
5180
|
+
const usedTarget = /* @__PURE__ */ new Set();
|
|
5181
|
+
const pairs = [];
|
|
5182
|
+
for (const c of candidates) {
|
|
5183
|
+
if (usedSource.has(c.sourceIdx) || usedTarget.has(c.targetIdx)) continue;
|
|
5184
|
+
usedSource.add(c.sourceIdx);
|
|
5185
|
+
usedTarget.add(c.targetIdx);
|
|
5186
|
+
const src = sourceElements[c.sourceIdx];
|
|
5187
|
+
const tgt = targetElements[c.targetIdx];
|
|
5188
|
+
pairs.push({
|
|
5189
|
+
sourceId: src.id,
|
|
5190
|
+
targetId: tgt.id,
|
|
5191
|
+
sourceLabel: getElementText(src) || src.id,
|
|
5192
|
+
targetLabel: getElementText(tgt) || tgt.id,
|
|
5193
|
+
confidence: Math.round(c.score * 100) / 100,
|
|
5194
|
+
matchStrategy: c.strategy
|
|
5195
|
+
});
|
|
5196
|
+
}
|
|
5197
|
+
return pairs;
|
|
5198
|
+
}
|
|
5199
|
+
function computeCrossAppDiff(sourceElements, targetElements, config = DEFAULT_CROSS_APP_DIFF_CONFIG) {
|
|
5200
|
+
const matchedPairs = matchElements(sourceElements, targetElements, config);
|
|
5201
|
+
const matchedSourceIds = new Set(matchedPairs.map((p) => p.sourceId));
|
|
5202
|
+
const matchedTargetIds = new Set(matchedPairs.map((p) => p.targetId));
|
|
5203
|
+
const unmatchedSourceIds = sourceElements.filter((e) => !matchedSourceIds.has(e.id)).map((e) => e.id);
|
|
5204
|
+
const unmatchedTargetIds = targetElements.filter((e) => !matchedTargetIds.has(e.id)).map((e) => e.id);
|
|
5205
|
+
const sourceData = extractPageData(sourceElements);
|
|
5206
|
+
const targetData = extractPageData(targetElements);
|
|
5207
|
+
const dataComparisons = [];
|
|
5208
|
+
for (const pair of matchedPairs) {
|
|
5209
|
+
const srcEntry = Object.values(sourceData.values).find((v) => v.elementId === pair.sourceId);
|
|
5210
|
+
const tgtEntry = Object.values(targetData.values).find((v) => v.elementId === pair.targetId);
|
|
5211
|
+
if (srcEntry && tgtEntry) {
|
|
5212
|
+
dataComparisons.push({
|
|
5213
|
+
label: pair.sourceLabel,
|
|
5214
|
+
sourceValue: srcEntry.rawValue,
|
|
5215
|
+
targetValue: tgtEntry.rawValue,
|
|
5216
|
+
valuesMatch: srcEntry.normalizedValue === tgtEntry.normalizedValue,
|
|
5217
|
+
formatsMatch: srcEntry.dataType === tgtEntry.dataType
|
|
5218
|
+
});
|
|
5219
|
+
}
|
|
5220
|
+
}
|
|
5221
|
+
const sourceFormats = analyzePageFormats(sourceElements);
|
|
5222
|
+
const targetFormats = analyzePageFormats(targetElements);
|
|
5223
|
+
const formatMismatches = compareFormats(sourceFormats, targetFormats);
|
|
5224
|
+
return {
|
|
5225
|
+
matchedPairs,
|
|
5226
|
+
unmatchedSourceIds,
|
|
5227
|
+
unmatchedTargetIds,
|
|
5228
|
+
dataComparisons,
|
|
5229
|
+
formatMismatches
|
|
5230
|
+
};
|
|
5231
|
+
}
|
|
5232
|
+
|
|
5233
|
+
// src/ai/action-parity.ts
|
|
5234
|
+
var DEFAULT_ACTION_PARITY_CONFIG = {
|
|
5235
|
+
ignoreActions: []
|
|
5236
|
+
};
|
|
5237
|
+
function getActions(el, ignoreActions) {
|
|
5238
|
+
const actions = el.actions || el.suggestedActions || [];
|
|
5239
|
+
const ignoreSet = new Set(ignoreActions.map((a) => a.toLowerCase()));
|
|
5240
|
+
return actions.map(
|
|
5241
|
+
(a) => typeof a === "string" ? a : a.action || a.name || ""
|
|
5242
|
+
).filter((a) => a && !ignoreSet.has(a.toLowerCase()));
|
|
5243
|
+
}
|
|
5244
|
+
function analyzeActionParity(matchedPairs, sourceElements, targetElements, config = DEFAULT_ACTION_PARITY_CONFIG) {
|
|
5245
|
+
const sourceById = new Map(sourceElements.map((e) => [e.id, e]));
|
|
5246
|
+
const targetById = new Map(targetElements.map((e) => [e.id, e]));
|
|
5247
|
+
const results = [];
|
|
5248
|
+
for (const pair of matchedPairs) {
|
|
5249
|
+
const src = sourceById.get(pair.sourceId);
|
|
5250
|
+
const tgt = targetById.get(pair.targetId);
|
|
5251
|
+
if (!src || !tgt) continue;
|
|
5252
|
+
const sourceActions = getActions(src, config.ignoreActions);
|
|
5253
|
+
const targetActions = getActions(tgt, config.ignoreActions);
|
|
5254
|
+
const sourceSet = new Set(sourceActions.map((a) => a.toLowerCase()));
|
|
5255
|
+
const targetSet = new Set(targetActions.map((a) => a.toLowerCase()));
|
|
5256
|
+
const missingInTarget = sourceActions.filter((a) => !targetSet.has(a.toLowerCase()));
|
|
5257
|
+
const missingInSource = targetActions.filter((a) => !sourceSet.has(a.toLowerCase()));
|
|
5258
|
+
results.push({
|
|
5259
|
+
pair,
|
|
5260
|
+
sourceActions,
|
|
5261
|
+
targetActions,
|
|
5262
|
+
missingInTarget,
|
|
5263
|
+
missingInSource
|
|
5264
|
+
});
|
|
5265
|
+
}
|
|
5266
|
+
return results;
|
|
5267
|
+
}
|
|
5268
|
+
|
|
5269
|
+
// src/ai/navigation-map.ts
|
|
5270
|
+
var DEFAULT_NAVIGATION_MAP_CONFIG = {
|
|
5271
|
+
labelMatchThreshold: 0.8
|
|
5272
|
+
};
|
|
5273
|
+
function isNavigationElement(el) {
|
|
5274
|
+
const role = (el.role || "").toLowerCase();
|
|
5275
|
+
const type = (el.type || "").toLowerCase();
|
|
5276
|
+
const semanticType = (el.semanticType || "").toLowerCase();
|
|
5277
|
+
if (["link", "menuitem", "tab"].includes(role)) return true;
|
|
5278
|
+
if (["link", "menuitem"].includes(type)) return true;
|
|
5279
|
+
if (semanticType.includes("nav") || semanticType.includes("menu") || semanticType.includes("tab")) {
|
|
5280
|
+
return true;
|
|
5281
|
+
}
|
|
5282
|
+
const context = (el.parentContext || "").toLowerCase();
|
|
5283
|
+
if (context.includes("nav") || context.includes("menu") || context.includes("sidebar")) {
|
|
5284
|
+
if (role === "button" || type === "button" || role === "link" || type === "link") {
|
|
5285
|
+
return true;
|
|
5286
|
+
}
|
|
5287
|
+
}
|
|
5288
|
+
return false;
|
|
5289
|
+
}
|
|
5290
|
+
function getNavLabel(el) {
|
|
5291
|
+
return el.accessibleName || el.labelText || el.label || el.description || el.id;
|
|
5292
|
+
}
|
|
5293
|
+
function getHref(el) {
|
|
5294
|
+
const state = el.state;
|
|
5295
|
+
return state?.href || void 0;
|
|
5296
|
+
}
|
|
5297
|
+
function hrefsMatch(a, b) {
|
|
5298
|
+
if (!a || !b) return false;
|
|
5299
|
+
const normalize = (h) => h.replace(/^https?:\/\//, "").replace(/localhost:\d+/, "").replace(/\/+$/, "").toLowerCase();
|
|
5300
|
+
return normalize(a) === normalize(b);
|
|
5301
|
+
}
|
|
5302
|
+
function buildNavigationMap(sourceElements, targetElements, config = DEFAULT_NAVIGATION_MAP_CONFIG) {
|
|
5303
|
+
const sourceNav = sourceElements.filter(isNavigationElement);
|
|
5304
|
+
const targetNav = targetElements.filter(isNavigationElement);
|
|
5305
|
+
const pairs = [];
|
|
5306
|
+
const matchedTargetIds = /* @__PURE__ */ new Set();
|
|
5307
|
+
for (const src of sourceNav) {
|
|
5308
|
+
const srcLabel = getNavLabel(src);
|
|
5309
|
+
const srcNorm = normalizeString(srcLabel);
|
|
5310
|
+
let bestTarget = null;
|
|
5311
|
+
let bestScore = 0;
|
|
5312
|
+
for (const tgt of targetNav) {
|
|
5313
|
+
if (matchedTargetIds.has(tgt.id)) continue;
|
|
5314
|
+
const tgtLabel = getNavLabel(tgt);
|
|
5315
|
+
const tgtNorm = normalizeString(tgtLabel);
|
|
5316
|
+
if (srcNorm === tgtNorm) {
|
|
5317
|
+
bestTarget = tgt;
|
|
5318
|
+
bestScore = 1;
|
|
5319
|
+
break;
|
|
5320
|
+
}
|
|
5321
|
+
const similarity = jaroWinklerSimilarity(srcNorm, tgtNorm);
|
|
5322
|
+
if (similarity > bestScore && similarity >= config.labelMatchThreshold) {
|
|
5323
|
+
bestScore = similarity;
|
|
5324
|
+
bestTarget = tgt;
|
|
5325
|
+
}
|
|
5326
|
+
}
|
|
5327
|
+
if (bestTarget) {
|
|
5328
|
+
matchedTargetIds.add(bestTarget.id);
|
|
5329
|
+
const srcHref = getHref(src);
|
|
5330
|
+
const tgtHref = getHref(bestTarget);
|
|
5331
|
+
pairs.push({
|
|
5332
|
+
sourceId: src.id,
|
|
5333
|
+
targetId: bestTarget.id,
|
|
5334
|
+
label: srcLabel,
|
|
5335
|
+
sourceHref: srcHref,
|
|
5336
|
+
targetHref: tgtHref,
|
|
5337
|
+
destinationMatch: hrefsMatch(srcHref, tgtHref)
|
|
5338
|
+
});
|
|
5339
|
+
}
|
|
5340
|
+
}
|
|
5341
|
+
const sourceOnly = sourceNav.filter((s) => !pairs.some((p) => p.sourceId === s.id)).map((s) => s.id);
|
|
5342
|
+
const targetOnly = targetNav.filter((t) => !matchedTargetIds.has(t.id)).map((t) => t.id);
|
|
5343
|
+
return { pairs, sourceOnly, targetOnly };
|
|
5344
|
+
}
|
|
5345
|
+
|
|
5346
|
+
// src/ai/component-comparison.ts
|
|
5347
|
+
var DEFAULT_COMPONENT_COMPARISON_CONFIG = {
|
|
5348
|
+
nameMatchThreshold: 0.75
|
|
5349
|
+
};
|
|
5350
|
+
function computeComponentMatchScore(source, target) {
|
|
5351
|
+
if (source.name.toLowerCase() === target.name.toLowerCase()) return 1;
|
|
5352
|
+
let score = 0;
|
|
5353
|
+
if (source.type === target.type) {
|
|
5354
|
+
score += 0.3;
|
|
5355
|
+
}
|
|
5356
|
+
const nameSimilarity = jaroWinklerSimilarity(
|
|
5357
|
+
normalizeString(source.name),
|
|
5358
|
+
normalizeString(target.name)
|
|
5359
|
+
);
|
|
5360
|
+
score += nameSimilarity * 0.7;
|
|
5361
|
+
return score;
|
|
5362
|
+
}
|
|
5363
|
+
function compareComponents(sourceComponents, targetComponents, config = DEFAULT_COMPONENT_COMPARISON_CONFIG) {
|
|
5364
|
+
const candidates = [];
|
|
5365
|
+
for (let si = 0; si < sourceComponents.length; si++) {
|
|
5366
|
+
for (let ti = 0; ti < targetComponents.length; ti++) {
|
|
5367
|
+
const score = computeComponentMatchScore(sourceComponents[si], targetComponents[ti]);
|
|
5368
|
+
if (score >= config.nameMatchThreshold) {
|
|
5369
|
+
candidates.push({ sourceIdx: si, targetIdx: ti, score });
|
|
5370
|
+
}
|
|
5371
|
+
}
|
|
5372
|
+
}
|
|
5373
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
5374
|
+
const usedSource = /* @__PURE__ */ new Set();
|
|
5375
|
+
const usedTarget = /* @__PURE__ */ new Set();
|
|
5376
|
+
const matches = [];
|
|
5377
|
+
for (const c of candidates) {
|
|
5378
|
+
if (usedSource.has(c.sourceIdx) || usedTarget.has(c.targetIdx)) continue;
|
|
5379
|
+
usedSource.add(c.sourceIdx);
|
|
5380
|
+
usedTarget.add(c.targetIdx);
|
|
5381
|
+
const src = sourceComponents[c.sourceIdx];
|
|
5382
|
+
const tgt = targetComponents[c.targetIdx];
|
|
5383
|
+
const srcKeys = new Set(src.stateKeys);
|
|
5384
|
+
const tgtKeys = new Set(tgt.stateKeys);
|
|
5385
|
+
const missingKeys = src.stateKeys.filter((k) => !tgtKeys.has(k));
|
|
5386
|
+
const extraKeys = tgt.stateKeys.filter((k) => !srcKeys.has(k));
|
|
5387
|
+
const srcActions = new Set(src.actions.map((a) => a.toLowerCase()));
|
|
5388
|
+
const tgtActions = new Set(tgt.actions.map((a) => a.toLowerCase()));
|
|
5389
|
+
const missingActions = src.actions.filter((a) => !tgtActions.has(a.toLowerCase()));
|
|
5390
|
+
const extraActions = tgt.actions.filter((a) => !srcActions.has(a.toLowerCase()));
|
|
5391
|
+
matches.push({
|
|
5392
|
+
source: src,
|
|
5393
|
+
target: tgt,
|
|
5394
|
+
confidence: Math.round(c.score * 100) / 100,
|
|
5395
|
+
stateKeyDiff: { missing: missingKeys, extra: extraKeys },
|
|
5396
|
+
actionDiff: { missing: missingActions, extra: extraActions }
|
|
5397
|
+
});
|
|
5398
|
+
}
|
|
5399
|
+
const sourceOnly = sourceComponents.filter((_, i) => !usedSource.has(i));
|
|
5400
|
+
const targetOnly = targetComponents.filter((_, i) => !usedTarget.has(i));
|
|
5401
|
+
return { matches, sourceOnly, targetOnly };
|
|
5402
|
+
}
|
|
5403
|
+
|
|
5404
|
+
// src/ai/layout-comparison.ts
|
|
5405
|
+
var DEFAULT_LAYOUT_COMPARISON_CONFIG = {
|
|
5406
|
+
gridTolerance: 20
|
|
5407
|
+
};
|
|
5408
|
+
function getRect(el) {
|
|
5409
|
+
const rect = el.state?.rect;
|
|
5410
|
+
if (!rect || !rect.width) return null;
|
|
5411
|
+
return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
|
|
5412
|
+
}
|
|
5413
|
+
function clusterValues(values, tolerance) {
|
|
5414
|
+
if (values.length === 0) return [];
|
|
5415
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
5416
|
+
const clusters = [sorted[0]];
|
|
5417
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
5418
|
+
if (sorted[i] - clusters[clusters.length - 1] > tolerance) {
|
|
5419
|
+
clusters.push(sorted[i]);
|
|
5420
|
+
}
|
|
5421
|
+
}
|
|
5422
|
+
return clusters;
|
|
5423
|
+
}
|
|
5424
|
+
function detectGridStructure(elements, config = DEFAULT_LAYOUT_COMPARISON_CONFIG) {
|
|
5425
|
+
const rects = elements.map(getRect).filter((r) => r !== null);
|
|
5426
|
+
const xPositions = rects.map((r) => r.x);
|
|
5427
|
+
const yPositions = rects.map((r) => r.y);
|
|
5428
|
+
const columns = clusterValues(xPositions, config.gridTolerance);
|
|
5429
|
+
const rows = clusterValues(yPositions, config.gridTolerance);
|
|
5430
|
+
return {
|
|
5431
|
+
columns,
|
|
5432
|
+
rows,
|
|
5433
|
+
columnCount: columns.length,
|
|
5434
|
+
rowCount: rows.length
|
|
5435
|
+
};
|
|
5436
|
+
}
|
|
5437
|
+
function computeMaxDepth(elements) {
|
|
5438
|
+
let maxDepth = 0;
|
|
5439
|
+
for (const el of elements) {
|
|
5440
|
+
const context = el.parentContext || "";
|
|
5441
|
+
const depth = context ? context.split(">").length : 1;
|
|
5442
|
+
maxDepth = Math.max(maxDepth, depth);
|
|
5443
|
+
}
|
|
5444
|
+
return maxDepth;
|
|
5445
|
+
}
|
|
5446
|
+
function computeProminence(element, pageWidth, pageHeight) {
|
|
5447
|
+
const rect = getRect(element);
|
|
5448
|
+
if (!rect || pageWidth === 0 || pageHeight === 0) return 0;
|
|
5449
|
+
const sizeScore = rect.width * rect.height / (pageWidth * pageHeight);
|
|
5450
|
+
const positionScore = 1 - rect.y / pageHeight;
|
|
5451
|
+
return Math.min(1, sizeScore * 0.6 + positionScore * 0.4);
|
|
5452
|
+
}
|
|
5453
|
+
function compareLayouts(sourceElements, targetElements, sourceRegions, targetRegions, config = DEFAULT_LAYOUT_COMPARISON_CONFIG) {
|
|
5454
|
+
const sourceGrid = detectGridStructure(sourceElements, config);
|
|
5455
|
+
const targetGrid = detectGridStructure(targetElements, config);
|
|
5456
|
+
const gridDiff = {
|
|
5457
|
+
sourceGrid,
|
|
5458
|
+
targetGrid,
|
|
5459
|
+
columnDiff: sourceGrid.columnCount - targetGrid.columnCount,
|
|
5460
|
+
rowDiff: sourceGrid.rowCount - targetGrid.rowCount
|
|
5461
|
+
};
|
|
5462
|
+
const sourceDepth = computeMaxDepth(sourceElements);
|
|
5463
|
+
const targetDepth = computeMaxDepth(targetElements);
|
|
5464
|
+
const hierarchyDiff = {
|
|
5465
|
+
sourceDepth,
|
|
5466
|
+
targetDepth,
|
|
5467
|
+
depthDiff: sourceDepth - targetDepth
|
|
5468
|
+
};
|
|
5469
|
+
const sourceRegionCount = sourceRegions?.regions.length || 1;
|
|
5470
|
+
const targetRegionCount = targetRegions?.regions.length || 1;
|
|
5471
|
+
const sourceDensity = sourceElements.length / sourceRegionCount;
|
|
5472
|
+
const targetDensity = targetElements.length / targetRegionCount;
|
|
5473
|
+
const density = {
|
|
5474
|
+
sourceDensity: Math.round(sourceDensity * 100) / 100,
|
|
5475
|
+
targetDensity: Math.round(targetDensity * 100) / 100,
|
|
5476
|
+
ratio: targetDensity > 0 ? Math.round(sourceDensity / targetDensity * 100) / 100 : 0
|
|
5477
|
+
};
|
|
5478
|
+
const gridSimilarity = sourceGrid.columnCount === 0 && targetGrid.columnCount === 0 ? 1 : 1 - Math.abs(gridDiff.columnDiff) / Math.max(sourceGrid.columnCount, targetGrid.columnCount, 1);
|
|
5479
|
+
const hierarchySimilarity = sourceDepth === 0 && targetDepth === 0 ? 1 : 1 - Math.abs(hierarchyDiff.depthDiff) / Math.max(sourceDepth, targetDepth, 1);
|
|
5480
|
+
const densitySimilarity = density.ratio > 0 ? Math.min(density.ratio, 1 / density.ratio) : 0;
|
|
5481
|
+
const similarity = Math.round((gridSimilarity * 0.4 + hierarchySimilarity * 0.3 + densitySimilarity * 0.3) * 100) / 100;
|
|
5482
|
+
return {
|
|
5483
|
+
gridDiff,
|
|
5484
|
+
hierarchyDiff,
|
|
5485
|
+
density,
|
|
5486
|
+
similarity
|
|
5487
|
+
};
|
|
5488
|
+
}
|
|
5489
|
+
|
|
5490
|
+
// src/ai/content-comparison.ts
|
|
5491
|
+
var DEFAULT_CONTENT_COMPARISON_CONFIG = {
|
|
5492
|
+
labelMatchThreshold: 0.8,
|
|
5493
|
+
headingMatchThreshold: 0.75,
|
|
5494
|
+
maxCellDifferences: 50
|
|
5495
|
+
};
|
|
5496
|
+
function getElementText2(el) {
|
|
5497
|
+
return (el.accessibleName || el.labelText || el.label || el.state?.textContent || el.description || "").trim();
|
|
5498
|
+
}
|
|
5499
|
+
function getContentRole(el) {
|
|
5500
|
+
if (el.contentMetadata?.contentRole) {
|
|
5501
|
+
return el.contentMetadata.contentRole;
|
|
5502
|
+
}
|
|
5503
|
+
const t = (el.type || "").toLowerCase();
|
|
5504
|
+
if (t === "heading" || t.startsWith("h") && /^h[1-6]$/.test(t)) return "heading";
|
|
5505
|
+
if (t === "metric-value" || t === "metric") return "metric";
|
|
5506
|
+
if (t === "status-message" || t === "status") return "status";
|
|
5507
|
+
if (t === "label") return "label";
|
|
5508
|
+
if (t === "badge") return "badge";
|
|
5509
|
+
if (t === "table-cell") return "table-cell";
|
|
5510
|
+
if (t === "table-header") return "table-header";
|
|
5511
|
+
if (t === "caption") return "caption";
|
|
5512
|
+
return null;
|
|
5513
|
+
}
|
|
5514
|
+
function getHeadingLevel(el) {
|
|
5515
|
+
if (el.contentMetadata?.headingLevel) {
|
|
5516
|
+
return el.contentMetadata.headingLevel;
|
|
5517
|
+
}
|
|
5518
|
+
const tag = (el.tagName || el.type || "").toLowerCase();
|
|
5519
|
+
const match = /^h([1-6])$/.exec(tag);
|
|
5520
|
+
if (match) return parseInt(match[1], 10);
|
|
5521
|
+
return void 0;
|
|
5522
|
+
}
|
|
5523
|
+
function isContentElement2(el) {
|
|
5524
|
+
if (el.category === "content") return true;
|
|
5525
|
+
if (el.contentMetadata) return true;
|
|
5526
|
+
const role = getContentRole(el);
|
|
5527
|
+
return role !== null;
|
|
5528
|
+
}
|
|
5529
|
+
function normalizeText(text) {
|
|
5530
|
+
return normalizeString(text, { caseSensitive: false, ignoreWhitespace: true });
|
|
5531
|
+
}
|
|
5532
|
+
function parseMetricText(el) {
|
|
5533
|
+
const text = getElementText2(el);
|
|
5534
|
+
const colonMatch = text.match(/^(.+?):\s*(.+)$/);
|
|
5535
|
+
if (colonMatch) {
|
|
5536
|
+
return { label: colonMatch[1].trim(), value: colonMatch[2].trim() };
|
|
5537
|
+
}
|
|
5538
|
+
const dashMatch = text.match(/^(.+?)\s*[-]\s*(.+)$/);
|
|
5539
|
+
if (dashMatch) {
|
|
5540
|
+
return { label: dashMatch[1].trim(), value: dashMatch[2].trim() };
|
|
5541
|
+
}
|
|
5542
|
+
const elLabel = el.accessibleName || el.labelText || el.label || el.id;
|
|
5543
|
+
return { label: elLabel, value: text };
|
|
5544
|
+
}
|
|
5545
|
+
function filterHeadings(elements) {
|
|
5546
|
+
return elements.filter((el) => getContentRole(el) === "heading");
|
|
5547
|
+
}
|
|
5548
|
+
function filterMetrics(elements) {
|
|
5549
|
+
return elements.filter((el) => getContentRole(el) === "metric");
|
|
5550
|
+
}
|
|
5551
|
+
function filterStatuses(elements) {
|
|
5552
|
+
return elements.filter((el) => {
|
|
5553
|
+
const role = getContentRole(el);
|
|
5554
|
+
return role === "status" || role === "badge";
|
|
5555
|
+
});
|
|
5556
|
+
}
|
|
5557
|
+
function filterLabels(elements) {
|
|
5558
|
+
return elements.filter((el) => {
|
|
5559
|
+
const role = getContentRole(el);
|
|
5560
|
+
return role === "label" || role === "caption";
|
|
5561
|
+
});
|
|
5562
|
+
}
|
|
5563
|
+
function matchTexts(sourceTexts, targetTexts, threshold) {
|
|
5564
|
+
const candidates = [];
|
|
5565
|
+
for (let si = 0; si < sourceTexts.length; si++) {
|
|
5566
|
+
const sNorm = normalizeText(sourceTexts[si]);
|
|
5567
|
+
if (!sNorm) continue;
|
|
5568
|
+
for (let ti = 0; ti < targetTexts.length; ti++) {
|
|
5569
|
+
const tNorm = normalizeText(targetTexts[ti]);
|
|
5570
|
+
if (!tNorm) continue;
|
|
5571
|
+
const score = sNorm === tNorm ? 1 : jaroWinklerSimilarity(sNorm, tNorm);
|
|
5572
|
+
if (score >= threshold) {
|
|
5573
|
+
candidates.push({ sourceIdx: si, targetIdx: ti, score });
|
|
5574
|
+
}
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
5578
|
+
const usedSource = /* @__PURE__ */ new Set();
|
|
5579
|
+
const usedTarget = /* @__PURE__ */ new Set();
|
|
5580
|
+
const matched = [];
|
|
5581
|
+
for (const c of candidates) {
|
|
5582
|
+
if (usedSource.has(c.sourceIdx) || usedTarget.has(c.targetIdx)) continue;
|
|
5583
|
+
usedSource.add(c.sourceIdx);
|
|
5584
|
+
usedTarget.add(c.targetIdx);
|
|
5585
|
+
matched.push(c);
|
|
5586
|
+
}
|
|
5587
|
+
const unmatchedSource = sourceTexts.map((_, i) => i).filter((i) => !usedSource.has(i));
|
|
5588
|
+
const unmatchedTarget = targetTexts.map((_, i) => i).filter((i) => !usedTarget.has(i));
|
|
5589
|
+
return { matched, unmatchedSource, unmatchedTarget };
|
|
5590
|
+
}
|
|
5591
|
+
function compareHeadings(sourceElements, targetElements, config) {
|
|
5592
|
+
const srcHeadings = filterHeadings(sourceElements);
|
|
5593
|
+
const tgtHeadings = filterHeadings(targetElements);
|
|
5594
|
+
const srcTexts = srcHeadings.map(getElementText2);
|
|
5595
|
+
const tgtTexts = tgtHeadings.map(getElementText2);
|
|
5596
|
+
const { matched, unmatchedSource, unmatchedTarget } = matchTexts(
|
|
5597
|
+
srcTexts,
|
|
5598
|
+
tgtTexts,
|
|
5599
|
+
config.headingMatchThreshold
|
|
5600
|
+
);
|
|
5601
|
+
const headingMatched = [];
|
|
5602
|
+
const headingChanged = [];
|
|
5603
|
+
for (const m of matched) {
|
|
5604
|
+
const srcText = srcTexts[m.sourceIdx];
|
|
5605
|
+
const tgtText = tgtTexts[m.targetIdx];
|
|
5606
|
+
const srcLevel = getHeadingLevel(srcHeadings[m.sourceIdx]);
|
|
5607
|
+
const tgtLevel = getHeadingLevel(tgtHeadings[m.targetIdx]);
|
|
5608
|
+
if (normalizeText(srcText) === normalizeText(tgtText)) {
|
|
5609
|
+
headingMatched.push({
|
|
5610
|
+
source: srcText,
|
|
5611
|
+
target: tgtText,
|
|
5612
|
+
level: srcLevel
|
|
5613
|
+
});
|
|
5614
|
+
} else {
|
|
5615
|
+
headingChanged.push({
|
|
5616
|
+
source: srcText,
|
|
5617
|
+
target: tgtText,
|
|
5618
|
+
level: srcLevel ?? tgtLevel
|
|
5619
|
+
});
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
return {
|
|
5623
|
+
matched: headingMatched,
|
|
5624
|
+
sourceOnly: unmatchedSource.map((i) => srcTexts[i]),
|
|
5625
|
+
targetOnly: unmatchedTarget.map((i) => tgtTexts[i]),
|
|
5626
|
+
changed: headingChanged
|
|
5627
|
+
};
|
|
5628
|
+
}
|
|
5629
|
+
function compareMetrics(sourceElements, targetElements, config) {
|
|
5630
|
+
const srcMetrics = filterMetrics(sourceElements);
|
|
5631
|
+
const tgtMetrics = filterMetrics(targetElements);
|
|
5632
|
+
const srcParsed = srcMetrics.map(parseMetricText);
|
|
5633
|
+
const tgtParsed = tgtMetrics.map(parseMetricText);
|
|
5634
|
+
const srcLabels = srcParsed.map((p) => p.label);
|
|
5635
|
+
const tgtLabels = tgtParsed.map((p) => p.label);
|
|
5636
|
+
const { matched, unmatchedSource, unmatchedTarget } = matchTexts(
|
|
5637
|
+
srcLabels,
|
|
5638
|
+
tgtLabels,
|
|
5639
|
+
config.labelMatchThreshold
|
|
5640
|
+
);
|
|
5641
|
+
const metricMatched = [];
|
|
5642
|
+
const metricChanged = [];
|
|
5643
|
+
for (const m of matched) {
|
|
5644
|
+
const src = srcParsed[m.sourceIdx];
|
|
5645
|
+
const tgt = tgtParsed[m.targetIdx];
|
|
5646
|
+
if (normalizeText(src.value) === normalizeText(tgt.value)) {
|
|
5647
|
+
metricMatched.push({
|
|
5648
|
+
label: src.label,
|
|
5649
|
+
sourceValue: src.value,
|
|
5650
|
+
targetValue: tgt.value
|
|
5651
|
+
});
|
|
5652
|
+
} else {
|
|
5653
|
+
metricChanged.push({
|
|
5654
|
+
label: src.label,
|
|
5655
|
+
sourceValue: src.value,
|
|
5656
|
+
targetValue: tgt.value
|
|
5657
|
+
});
|
|
5658
|
+
}
|
|
5659
|
+
}
|
|
5660
|
+
return {
|
|
5661
|
+
matched: metricMatched,
|
|
5662
|
+
changed: metricChanged,
|
|
5663
|
+
sourceOnly: unmatchedSource.map((i) => srcParsed[i].label),
|
|
5664
|
+
targetOnly: unmatchedTarget.map((i) => tgtParsed[i].label)
|
|
5665
|
+
};
|
|
5666
|
+
}
|
|
5667
|
+
function compareStatuses(sourceElements, targetElements, config) {
|
|
5668
|
+
const srcStatuses = filterStatuses(sourceElements);
|
|
5669
|
+
const tgtStatuses = filterStatuses(targetElements);
|
|
5670
|
+
const srcParsed = srcStatuses.map(parseMetricText);
|
|
5671
|
+
const tgtParsed = tgtStatuses.map(parseMetricText);
|
|
5672
|
+
const srcLabels = srcParsed.map((p) => p.label);
|
|
5673
|
+
const tgtLabels = tgtParsed.map((p) => p.label);
|
|
5674
|
+
const { matched } = matchTexts(srcLabels, tgtLabels, config.labelMatchThreshold);
|
|
5675
|
+
const statusMatched = [];
|
|
5676
|
+
const statusChanged = [];
|
|
5677
|
+
for (const m of matched) {
|
|
5678
|
+
const src = srcParsed[m.sourceIdx];
|
|
5679
|
+
const tgt = tgtParsed[m.targetIdx];
|
|
5680
|
+
if (normalizeText(src.value) === normalizeText(tgt.value)) {
|
|
5681
|
+
statusMatched.push({
|
|
5682
|
+
label: src.label,
|
|
5683
|
+
sourceStatus: src.value,
|
|
5684
|
+
targetStatus: tgt.value
|
|
5685
|
+
});
|
|
5686
|
+
} else {
|
|
5687
|
+
statusChanged.push({
|
|
5688
|
+
label: src.label,
|
|
5689
|
+
sourceStatus: src.value,
|
|
5690
|
+
targetStatus: tgt.value
|
|
5691
|
+
});
|
|
5692
|
+
}
|
|
5693
|
+
}
|
|
5694
|
+
return {
|
|
5695
|
+
matched: statusMatched,
|
|
5696
|
+
changed: statusChanged
|
|
5697
|
+
};
|
|
5698
|
+
}
|
|
5699
|
+
function compareLabels(sourceElements, targetElements, config) {
|
|
5700
|
+
const srcLabels = filterLabels(sourceElements);
|
|
5701
|
+
const tgtLabels = filterLabels(targetElements);
|
|
5702
|
+
const srcTexts = srcLabels.map(getElementText2);
|
|
5703
|
+
const tgtTexts = tgtLabels.map(getElementText2);
|
|
5704
|
+
const { matched, unmatchedSource, unmatchedTarget } = matchTexts(
|
|
5705
|
+
srcTexts,
|
|
5706
|
+
tgtTexts,
|
|
5707
|
+
config.labelMatchThreshold
|
|
5708
|
+
);
|
|
5709
|
+
return {
|
|
5710
|
+
matched: matched.map((m) => srcTexts[m.sourceIdx]),
|
|
5711
|
+
sourceOnly: unmatchedSource.map((i) => srcTexts[i]),
|
|
5712
|
+
targetOnly: unmatchedTarget.map((i) => tgtTexts[i])
|
|
5713
|
+
};
|
|
5714
|
+
}
|
|
5715
|
+
function compareTables(sourceElements, targetElements, config) {
|
|
5716
|
+
const srcData = extractStructuredData(sourceElements);
|
|
5717
|
+
const tgtData = extractStructuredData(targetElements);
|
|
5718
|
+
const srcTables = srcData.tables;
|
|
5719
|
+
const tgtTables = tgtData.tables;
|
|
5720
|
+
if (srcTables.length === 0 || tgtTables.length === 0) {
|
|
5721
|
+
return [];
|
|
5722
|
+
}
|
|
5723
|
+
const srcTableLabels = srcTables.map((t) => t.label || "");
|
|
5724
|
+
const tgtTableLabels = tgtTables.map((t) => t.label || "");
|
|
5725
|
+
const { matched } = matchTexts(srcTableLabels, tgtTableLabels, config.labelMatchThreshold);
|
|
5726
|
+
const tablePairs = [];
|
|
5727
|
+
if (matched.length > 0) {
|
|
5728
|
+
for (const m of matched) {
|
|
5729
|
+
tablePairs.push({ srcIdx: m.sourceIdx, tgtIdx: m.targetIdx });
|
|
5730
|
+
}
|
|
5731
|
+
} else if (srcTables.length === 1 && tgtTables.length === 1) {
|
|
5732
|
+
tablePairs.push({ srcIdx: 0, tgtIdx: 0 });
|
|
5733
|
+
}
|
|
5734
|
+
const comparisons = [];
|
|
5735
|
+
for (const pair of tablePairs) {
|
|
5736
|
+
const srcTable = srcTables[pair.srcIdx];
|
|
5737
|
+
const tgtTable = tgtTables[pair.tgtIdx];
|
|
5738
|
+
const srcHeaders = srcTable.columns.map((c) => c.header);
|
|
5739
|
+
const tgtHeaders = tgtTable.columns.map((c) => c.header);
|
|
5740
|
+
const srcHeaderSet = new Set(srcHeaders.map(normalizeText));
|
|
5741
|
+
const tgtHeaderSet = new Set(tgtHeaders.map(normalizeText));
|
|
5742
|
+
const sourceOnlyColumns = srcHeaders.filter((h) => !tgtHeaderSet.has(normalizeText(h)));
|
|
5743
|
+
const targetOnlyColumns = tgtHeaders.filter((h) => !srcHeaderSet.has(normalizeText(h)));
|
|
5744
|
+
const columnsMatch = sourceOnlyColumns.length === 0 && targetOnlyColumns.length === 0;
|
|
5745
|
+
const cellDifferences = [];
|
|
5746
|
+
const commonHeaders = srcHeaders.filter((h) => tgtHeaderSet.has(normalizeText(h)));
|
|
5747
|
+
const minRows = Math.min(srcTable.rows.length, tgtTable.rows.length);
|
|
5748
|
+
for (let row = 0; row < minRows; row++) {
|
|
5749
|
+
if (cellDifferences.length >= config.maxCellDifferences) break;
|
|
5750
|
+
for (const header of commonHeaders) {
|
|
5751
|
+
const srcColIdx = srcHeaders.indexOf(header);
|
|
5752
|
+
const tgtColIdx = tgtHeaders.findIndex((h) => normalizeText(h) === normalizeText(header));
|
|
5753
|
+
if (srcColIdx < 0 || tgtColIdx < 0) continue;
|
|
5754
|
+
const srcValue = srcTable.rows[row]?.[srcColIdx] ?? "";
|
|
5755
|
+
const tgtValue = tgtTable.rows[row]?.[tgtColIdx] ?? "";
|
|
5756
|
+
if (normalizeText(srcValue) !== normalizeText(tgtValue)) {
|
|
5757
|
+
cellDifferences.push({
|
|
5758
|
+
row,
|
|
5759
|
+
column: header,
|
|
5760
|
+
sourceValue: srcValue,
|
|
5761
|
+
targetValue: tgtValue
|
|
5762
|
+
});
|
|
5763
|
+
}
|
|
5764
|
+
}
|
|
5765
|
+
}
|
|
5766
|
+
comparisons.push({
|
|
5767
|
+
sourceLabel: srcTable.label,
|
|
5768
|
+
targetLabel: tgtTable.label,
|
|
5769
|
+
columnsMatch,
|
|
5770
|
+
sourceOnlyColumns,
|
|
5771
|
+
targetOnlyColumns,
|
|
5772
|
+
sourceRowCount: srcTable.rows.length,
|
|
5773
|
+
targetRowCount: tgtTable.rows.length,
|
|
5774
|
+
cellDifferences
|
|
5775
|
+
});
|
|
5776
|
+
}
|
|
5777
|
+
return comparisons;
|
|
5778
|
+
}
|
|
5779
|
+
function compareHeadingHierarchy(sourceElements, targetElements) {
|
|
5780
|
+
const srcHeadings = filterHeadings(sourceElements);
|
|
5781
|
+
const tgtHeadings = filterHeadings(targetElements);
|
|
5782
|
+
const srcByLevel = /* @__PURE__ */ new Map();
|
|
5783
|
+
const tgtByLevel = /* @__PURE__ */ new Map();
|
|
5784
|
+
for (const el of srcHeadings) {
|
|
5785
|
+
const level = getHeadingLevel(el) ?? 0;
|
|
5786
|
+
srcByLevel.set(level, (srcByLevel.get(level) ?? 0) + 1);
|
|
5787
|
+
}
|
|
5788
|
+
for (const el of tgtHeadings) {
|
|
5789
|
+
const level = getHeadingLevel(el) ?? 0;
|
|
5790
|
+
tgtByLevel.set(level, (tgtByLevel.get(level) ?? 0) + 1);
|
|
5791
|
+
}
|
|
5792
|
+
const allLevels = /* @__PURE__ */ new Set([...srcByLevel.keys(), ...tgtByLevel.keys()]);
|
|
5793
|
+
const result = [];
|
|
5794
|
+
for (const level of [...allLevels].sort()) {
|
|
5795
|
+
result.push({
|
|
5796
|
+
level,
|
|
5797
|
+
sourceCount: srcByLevel.get(level) ?? 0,
|
|
5798
|
+
targetCount: tgtByLevel.get(level) ?? 0
|
|
5799
|
+
});
|
|
5800
|
+
}
|
|
5801
|
+
return result;
|
|
5802
|
+
}
|
|
5803
|
+
function compareContent(sourceElements, targetElements, config = DEFAULT_CONTENT_COMPARISON_CONFIG) {
|
|
5804
|
+
const srcContent = sourceElements.filter(isContentElement2);
|
|
5805
|
+
const tgtContent = targetElements.filter(isContentElement2);
|
|
5806
|
+
const headings = compareHeadings(srcContent, tgtContent, config);
|
|
5807
|
+
const metrics = compareMetrics(srcContent, tgtContent, config);
|
|
5808
|
+
const statuses = compareStatuses(srcContent, tgtContent, config);
|
|
5809
|
+
const labels = compareLabels(srcContent, tgtContent, config);
|
|
5810
|
+
const tables = compareTables(sourceElements, targetElements, config);
|
|
5811
|
+
const headingHierarchy = compareHeadingHierarchy(srcContent, tgtContent);
|
|
5812
|
+
const contentParity = calculateContentParity(headings, metrics, statuses, labels, tables);
|
|
5813
|
+
return {
|
|
5814
|
+
headings,
|
|
5815
|
+
metrics,
|
|
5816
|
+
statuses,
|
|
5817
|
+
labels,
|
|
5818
|
+
tables,
|
|
5819
|
+
headingHierarchy,
|
|
5820
|
+
contentParity
|
|
5821
|
+
};
|
|
5822
|
+
}
|
|
5823
|
+
function calculateContentParity(headings, metrics, statuses, labels, tables) {
|
|
5824
|
+
const scores = [];
|
|
5825
|
+
const totalHeadings = headings.matched.length + headings.changed.length + headings.sourceOnly.length + headings.targetOnly.length;
|
|
5826
|
+
if (totalHeadings > 0) {
|
|
5827
|
+
scores.push(headings.matched.length / totalHeadings);
|
|
5828
|
+
}
|
|
5829
|
+
const totalMetrics = metrics.matched.length + metrics.changed.length + metrics.sourceOnly.length + metrics.targetOnly.length;
|
|
5830
|
+
if (totalMetrics > 0) {
|
|
5831
|
+
const metricScore = (metrics.matched.length + metrics.changed.length * 0.5) / totalMetrics;
|
|
5832
|
+
scores.push(metricScore);
|
|
5833
|
+
}
|
|
5834
|
+
const totalStatuses = statuses.matched.length + statuses.changed.length;
|
|
5835
|
+
if (totalStatuses > 0) {
|
|
5836
|
+
scores.push(statuses.matched.length / totalStatuses);
|
|
5837
|
+
}
|
|
5838
|
+
const totalLabels = labels.matched.length + labels.sourceOnly.length + labels.targetOnly.length;
|
|
5839
|
+
if (totalLabels > 0) {
|
|
5840
|
+
scores.push(labels.matched.length / totalLabels);
|
|
5841
|
+
}
|
|
5842
|
+
if (tables.length > 0) {
|
|
5843
|
+
let tableScore = 0;
|
|
5844
|
+
for (const table of tables) {
|
|
5845
|
+
let tScore = table.columnsMatch ? 0.5 : 0;
|
|
5846
|
+
if (table.sourceRowCount > 0) {
|
|
5847
|
+
const rowRatio = Math.min(
|
|
5848
|
+
table.targetRowCount / table.sourceRowCount,
|
|
5849
|
+
table.sourceRowCount / table.targetRowCount
|
|
5850
|
+
);
|
|
5851
|
+
tScore += rowRatio * 0.3;
|
|
5852
|
+
} else {
|
|
5853
|
+
tScore += 0.3;
|
|
5854
|
+
}
|
|
5855
|
+
const totalCells = Math.max(table.sourceRowCount, 1) * Math.max(
|
|
5856
|
+
table.sourceOnlyColumns.length + table.targetOnlyColumns.length + (table.columnsMatch ? 1 : 0),
|
|
5857
|
+
1
|
|
5858
|
+
);
|
|
5859
|
+
const diffRatio = totalCells > 0 ? 1 - Math.min(table.cellDifferences.length / totalCells, 1) : 1;
|
|
5860
|
+
tScore += diffRatio * 0.2;
|
|
5861
|
+
tableScore += tScore;
|
|
5862
|
+
}
|
|
5863
|
+
scores.push(tableScore / tables.length);
|
|
5864
|
+
}
|
|
5865
|
+
if (scores.length === 0) return 1;
|
|
5866
|
+
return Math.round(scores.reduce((a, b) => a + b, 0) / scores.length * 100) / 100;
|
|
5867
|
+
}
|
|
5868
|
+
|
|
5869
|
+
// src/ai/comparison-report.ts
|
|
5870
|
+
var DEFAULT_COMPARISON_REPORT_CONFIG = {
|
|
5871
|
+
includeComponents: false
|
|
5872
|
+
};
|
|
5873
|
+
function generateComparisonReport(source, target, options) {
|
|
5874
|
+
const startTime = Date.now();
|
|
5875
|
+
const config = { ...DEFAULT_COMPARISON_REPORT_CONFIG, ...options?.config };
|
|
5876
|
+
const srcElements = source.elements;
|
|
5877
|
+
const tgtElements = target.elements;
|
|
5878
|
+
const diff = computeCrossAppDiff(srcElements, tgtElements);
|
|
5879
|
+
const navigation = buildNavigationMap(srcElements, tgtElements);
|
|
5880
|
+
const sourceRegions = segmentPageRegions(srcElements);
|
|
5881
|
+
const targetRegions = segmentPageRegions(tgtElements);
|
|
5882
|
+
const layout = compareLayouts(srcElements, tgtElements, sourceRegions, targetRegions);
|
|
5883
|
+
const actionParityResults = analyzeActionParity(diff.matchedPairs, srcElements, tgtElements);
|
|
5884
|
+
const componentComparison = config.includeComponents && options?.sourceComponents && options?.targetComponents ? compareComponents(options.sourceComponents, options.targetComponents) : null;
|
|
5885
|
+
const contentComparison = compareContent(srcElements, tgtElements);
|
|
5886
|
+
const sourceData = extractPageData(srcElements);
|
|
5887
|
+
extractPageData(tgtElements);
|
|
5888
|
+
const sourceFieldCount = Object.keys(sourceData.values).length;
|
|
5889
|
+
const matchedDataCount = diff.dataComparisons.length;
|
|
5890
|
+
const dataCompleteness = sourceFieldCount > 0 ? Math.round(matchedDataCount / sourceFieldCount * 100) / 100 : 1;
|
|
5891
|
+
const formatMatchCount = diff.dataComparisons.filter((c) => c.formatsMatch).length;
|
|
5892
|
+
const formatAlignment = matchedDataCount > 0 ? Math.round(formatMatchCount / matchedDataCount * 100) / 100 : 1;
|
|
5893
|
+
const presentationAlignment = layout.similarity;
|
|
5894
|
+
const totalNavItems = navigation.pairs.length + navigation.sourceOnly.length;
|
|
5895
|
+
const navigationParity = totalNavItems > 0 ? Math.round(navigation.pairs.length / totalNavItems * 100) / 100 : 1;
|
|
5896
|
+
const totalActionChecks = actionParityResults.length;
|
|
5897
|
+
const fullParityCount = actionParityResults.filter((r) => r.missingInTarget.length === 0).length;
|
|
5898
|
+
const actionParity = totalActionChecks > 0 ? Math.round(fullParityCount / totalActionChecks * 100) / 100 : 1;
|
|
5899
|
+
const contentParity = contentComparison.contentParity;
|
|
5900
|
+
const overallScore = Math.round(
|
|
5901
|
+
(dataCompleteness * 0.2 + formatAlignment * 0.1 + presentationAlignment * 0.15 + navigationParity * 0.15 + actionParity * 0.15 + contentParity * 0.25) * 100
|
|
5902
|
+
) / 100;
|
|
5903
|
+
const issues = [];
|
|
5904
|
+
for (const srcId of diff.unmatchedSourceIds) {
|
|
5905
|
+
const srcVal = Object.values(sourceData.values).find((v) => v.elementId === srcId);
|
|
5906
|
+
if (srcVal) {
|
|
5907
|
+
issues.push({
|
|
5908
|
+
severity: "warning",
|
|
5909
|
+
category: "missing-data",
|
|
5910
|
+
description: `Data field "${srcVal.label}" (${srcVal.dataType}) exists in source but has no match in target`,
|
|
5911
|
+
sourceElementId: srcId
|
|
5912
|
+
});
|
|
5913
|
+
}
|
|
5914
|
+
}
|
|
5915
|
+
for (const comp of diff.dataComparisons) {
|
|
5916
|
+
if (!comp.valuesMatch) {
|
|
5917
|
+
issues.push({
|
|
5918
|
+
severity: "error",
|
|
5919
|
+
category: "value-mismatch",
|
|
5920
|
+
description: `Value mismatch for "${comp.label}": source="${comp.sourceValue}", target="${comp.targetValue}"`
|
|
5921
|
+
});
|
|
5922
|
+
}
|
|
5923
|
+
}
|
|
5924
|
+
for (const fm of diff.formatMismatches) {
|
|
5925
|
+
issues.push({
|
|
5926
|
+
severity: fm.severity,
|
|
5927
|
+
category: "format-mismatch",
|
|
5928
|
+
description: fm.description
|
|
5929
|
+
});
|
|
5930
|
+
}
|
|
5931
|
+
for (const ap of actionParityResults) {
|
|
5932
|
+
for (const action of ap.missingInTarget) {
|
|
5933
|
+
issues.push({
|
|
5934
|
+
severity: "warning",
|
|
5935
|
+
category: "missing-action",
|
|
5936
|
+
description: `Action "${action}" available on source element "${ap.pair.sourceLabel}" is missing in target`,
|
|
5937
|
+
sourceElementId: ap.pair.sourceId,
|
|
5938
|
+
targetElementId: ap.pair.targetId
|
|
5939
|
+
});
|
|
5940
|
+
}
|
|
5941
|
+
}
|
|
5942
|
+
for (const srcId of navigation.sourceOnly) {
|
|
5943
|
+
issues.push({
|
|
5944
|
+
severity: "warning",
|
|
5945
|
+
category: "navigation-gap",
|
|
5946
|
+
description: `Navigation item "${srcId}" in source has no match in target`,
|
|
5947
|
+
sourceElementId: srcId
|
|
5948
|
+
});
|
|
5949
|
+
}
|
|
5950
|
+
if (layout.similarity < 0.5) {
|
|
5951
|
+
issues.push({
|
|
5952
|
+
severity: "warning",
|
|
5953
|
+
category: "layout-difference",
|
|
5954
|
+
description: `Layout similarity is low (${layout.similarity}). Grid: ${layout.gridDiff.sourceGrid.columnCount} cols vs ${layout.gridDiff.targetGrid.columnCount} cols`
|
|
5955
|
+
});
|
|
5956
|
+
}
|
|
5957
|
+
if (componentComparison) {
|
|
5958
|
+
for (const src of componentComparison.sourceOnly) {
|
|
5959
|
+
issues.push({
|
|
5960
|
+
severity: "info",
|
|
5961
|
+
category: "component-mismatch",
|
|
5962
|
+
description: `Component "${src.name}" (${src.type}) exists in source but not target`
|
|
5963
|
+
});
|
|
5964
|
+
}
|
|
5965
|
+
for (const match of componentComparison.matches) {
|
|
5966
|
+
if (match.stateKeyDiff.missing.length > 0) {
|
|
5967
|
+
issues.push({
|
|
5968
|
+
severity: "warning",
|
|
5969
|
+
category: "component-mismatch",
|
|
5970
|
+
description: `Component "${match.source.name}": state keys missing in target: ${match.stateKeyDiff.missing.join(", ")}`
|
|
5971
|
+
});
|
|
5972
|
+
}
|
|
5973
|
+
}
|
|
5974
|
+
}
|
|
5975
|
+
for (const heading of contentComparison.headings.sourceOnly) {
|
|
5976
|
+
issues.push({
|
|
5977
|
+
severity: "warning",
|
|
5978
|
+
category: "content-difference",
|
|
5979
|
+
description: `Heading "${heading}" exists in source but not in target`
|
|
5980
|
+
});
|
|
5981
|
+
}
|
|
5982
|
+
for (const heading of contentComparison.headings.targetOnly) {
|
|
5983
|
+
issues.push({
|
|
5984
|
+
severity: "info",
|
|
5985
|
+
category: "content-difference",
|
|
5986
|
+
description: `Heading "${heading}" exists in target but not in source`
|
|
5987
|
+
});
|
|
5988
|
+
}
|
|
5989
|
+
for (const change of contentComparison.headings.changed) {
|
|
5990
|
+
issues.push({
|
|
5991
|
+
severity: "warning",
|
|
5992
|
+
category: "content-difference",
|
|
5993
|
+
description: `Heading changed: "${change.source}" -> "${change.target}"`
|
|
5994
|
+
});
|
|
5995
|
+
}
|
|
5996
|
+
for (const change of contentComparison.metrics.changed) {
|
|
5997
|
+
issues.push({
|
|
5998
|
+
severity: "warning",
|
|
5999
|
+
category: "content-difference",
|
|
6000
|
+
description: `Metric "${change.label}" value differs: "${change.sourceValue}" vs "${change.targetValue}"`
|
|
6001
|
+
});
|
|
6002
|
+
}
|
|
6003
|
+
for (const label of contentComparison.metrics.sourceOnly) {
|
|
6004
|
+
issues.push({
|
|
6005
|
+
severity: "warning",
|
|
6006
|
+
category: "content-difference",
|
|
6007
|
+
description: `Metric "${label}" exists in source but not in target`
|
|
6008
|
+
});
|
|
6009
|
+
}
|
|
6010
|
+
for (const change of contentComparison.statuses.changed) {
|
|
6011
|
+
issues.push({
|
|
6012
|
+
severity: "warning",
|
|
6013
|
+
category: "content-difference",
|
|
6014
|
+
description: `Status "${change.label}" differs: "${change.sourceStatus}" vs "${change.targetStatus}"`
|
|
6015
|
+
});
|
|
6016
|
+
}
|
|
6017
|
+
for (const table of contentComparison.tables) {
|
|
6018
|
+
if (!table.columnsMatch) {
|
|
6019
|
+
issues.push({
|
|
6020
|
+
severity: "warning",
|
|
6021
|
+
category: "content-difference",
|
|
6022
|
+
description: `Table "${table.sourceLabel}" column mismatch: source-only=[${table.sourceOnlyColumns.join(", ")}], target-only=[${table.targetOnlyColumns.join(", ")}]`
|
|
6023
|
+
});
|
|
6024
|
+
}
|
|
6025
|
+
if (table.sourceRowCount !== table.targetRowCount) {
|
|
6026
|
+
issues.push({
|
|
6027
|
+
severity: "info",
|
|
6028
|
+
category: "content-difference",
|
|
6029
|
+
description: `Table "${table.sourceLabel}" row count differs: ${table.sourceRowCount} vs ${table.targetRowCount}`
|
|
6030
|
+
});
|
|
6031
|
+
}
|
|
6032
|
+
if (table.cellDifferences.length > 0) {
|
|
6033
|
+
issues.push({
|
|
6034
|
+
severity: "warning",
|
|
6035
|
+
category: "content-difference",
|
|
6036
|
+
description: `Table "${table.sourceLabel}" has ${table.cellDifferences.length} cell value difference(s)`
|
|
6037
|
+
});
|
|
6038
|
+
}
|
|
6039
|
+
}
|
|
6040
|
+
const severityOrder = { error: 0, warning: 1, info: 2 };
|
|
6041
|
+
issues.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
6042
|
+
const errorCount = issues.filter((i) => i.severity === "error").length;
|
|
6043
|
+
const warningCount = issues.filter((i) => i.severity === "warning").length;
|
|
6044
|
+
const infoCount = issues.filter((i) => i.severity === "info").length;
|
|
6045
|
+
const summaryLines = [
|
|
6046
|
+
`Cross-app comparison: ${source.page.url} vs ${target.page.url}`,
|
|
6047
|
+
`Overall score: ${(overallScore * 100).toFixed(0)}%`,
|
|
6048
|
+
`Matched elements: ${diff.matchedPairs.length}`,
|
|
6049
|
+
`Unmatched: ${diff.unmatchedSourceIds.length} source, ${diff.unmatchedTargetIds.length} target`,
|
|
6050
|
+
`Navigation: ${navigation.pairs.length} matched, ${navigation.sourceOnly.length} source-only, ${navigation.targetOnly.length} target-only`
|
|
6051
|
+
];
|
|
6052
|
+
if (componentComparison) {
|
|
6053
|
+
summaryLines.push(
|
|
6054
|
+
`Components: ${componentComparison.matches.length} matched, ${componentComparison.sourceOnly.length} source-only, ${componentComparison.targetOnly.length} target-only`
|
|
6055
|
+
);
|
|
6056
|
+
}
|
|
6057
|
+
const hMatched = contentComparison.headings.matched.length;
|
|
6058
|
+
const hChanged = contentComparison.headings.changed.length;
|
|
6059
|
+
const hSrcOnly = contentComparison.headings.sourceOnly.length;
|
|
6060
|
+
const hTgtOnly = contentComparison.headings.targetOnly.length;
|
|
6061
|
+
const mMatched = contentComparison.metrics.matched.length;
|
|
6062
|
+
const mChanged = contentComparison.metrics.changed.length;
|
|
6063
|
+
const sMatched = contentComparison.statuses.matched.length;
|
|
6064
|
+
const sChanged = contentComparison.statuses.changed.length;
|
|
6065
|
+
const totalContent = hMatched + hChanged + hSrcOnly + hTgtOnly + mMatched + mChanged + sMatched + sChanged;
|
|
6066
|
+
if (totalContent > 0) {
|
|
6067
|
+
summaryLines.push(
|
|
6068
|
+
`Content: headings=${hMatched} matched/${hChanged} changed/${hSrcOnly + hTgtOnly} unmatched, metrics=${mMatched} matched/${mChanged} changed, statuses=${sMatched} matched/${sChanged} changed, parity=${(contentParity * 100).toFixed(0)}%`
|
|
6069
|
+
);
|
|
6070
|
+
}
|
|
6071
|
+
summaryLines.push(`Issues: ${errorCount} errors, ${warningCount} warnings, ${infoCount} info`);
|
|
6072
|
+
const summary = summaryLines.join("\n");
|
|
6073
|
+
const report = {
|
|
6074
|
+
sourceUrl: source.page.url,
|
|
6075
|
+
targetUrl: target.page.url,
|
|
6076
|
+
timestamp: Date.now(),
|
|
6077
|
+
durationMs: Date.now() - startTime,
|
|
6078
|
+
scores: {
|
|
6079
|
+
dataCompleteness,
|
|
6080
|
+
formatAlignment,
|
|
6081
|
+
presentationAlignment,
|
|
6082
|
+
navigationParity,
|
|
6083
|
+
actionParity,
|
|
6084
|
+
overallScore
|
|
6085
|
+
},
|
|
6086
|
+
diff,
|
|
6087
|
+
navigation,
|
|
6088
|
+
layout,
|
|
6089
|
+
contentComparison,
|
|
6090
|
+
issues,
|
|
6091
|
+
summary
|
|
6092
|
+
};
|
|
6093
|
+
if (componentComparison) {
|
|
6094
|
+
report.components = componentComparison;
|
|
6095
|
+
}
|
|
6096
|
+
return report;
|
|
6097
|
+
}
|
|
3838
6098
|
|
|
3839
6099
|
exports.AssertionExecutor = AssertionExecutor;
|
|
6100
|
+
exports.DEFAULT_ACTION_PARITY_CONFIG = DEFAULT_ACTION_PARITY_CONFIG;
|
|
3840
6101
|
exports.DEFAULT_ALIAS_CONFIG = DEFAULT_ALIAS_CONFIG;
|
|
3841
6102
|
exports.DEFAULT_ASSERTION_CONFIG = DEFAULT_ASSERTION_CONFIG;
|
|
6103
|
+
exports.DEFAULT_COMPARISON_REPORT_CONFIG = DEFAULT_COMPARISON_REPORT_CONFIG;
|
|
6104
|
+
exports.DEFAULT_COMPONENT_COMPARISON_CONFIG = DEFAULT_COMPONENT_COMPARISON_CONFIG;
|
|
6105
|
+
exports.DEFAULT_CONTENT_COMPARISON_CONFIG = DEFAULT_CONTENT_COMPARISON_CONFIG;
|
|
6106
|
+
exports.DEFAULT_CROSS_APP_DIFF_CONFIG = DEFAULT_CROSS_APP_DIFF_CONFIG;
|
|
6107
|
+
exports.DEFAULT_DATA_EXTRACTION_CONFIG = DEFAULT_DATA_EXTRACTION_CONFIG;
|
|
3842
6108
|
exports.DEFAULT_DIFF_CONFIG = DEFAULT_DIFF_CONFIG;
|
|
3843
6109
|
exports.DEFAULT_EXECUTOR_CONFIG = DEFAULT_EXECUTOR_CONFIG;
|
|
6110
|
+
exports.DEFAULT_FORMAT_ANALYSIS_CONFIG = DEFAULT_FORMAT_ANALYSIS_CONFIG;
|
|
3844
6111
|
exports.DEFAULT_FUZZY_CONFIG = DEFAULT_FUZZY_CONFIG;
|
|
6112
|
+
exports.DEFAULT_LAYOUT_COMPARISON_CONFIG = DEFAULT_LAYOUT_COMPARISON_CONFIG;
|
|
6113
|
+
exports.DEFAULT_NAVIGATION_MAP_CONFIG = DEFAULT_NAVIGATION_MAP_CONFIG;
|
|
6114
|
+
exports.DEFAULT_REGION_SEGMENTATION_CONFIG = DEFAULT_REGION_SEGMENTATION_CONFIG;
|
|
3845
6115
|
exports.DEFAULT_SEARCH_CONFIG = DEFAULT_SEARCH_CONFIG;
|
|
3846
6116
|
exports.DEFAULT_SNAPSHOT_CONFIG = DEFAULT_SNAPSHOT_CONFIG;
|
|
6117
|
+
exports.DEFAULT_TABLE_EXTRACTION_CONFIG = DEFAULT_TABLE_EXTRACTION_CONFIG;
|
|
3847
6118
|
exports.ErrorCodes = ErrorCodes;
|
|
3848
6119
|
exports.NLActionExecutor = NLActionExecutor;
|
|
3849
6120
|
exports.SearchEngine = SearchEngine;
|
|
3850
6121
|
exports.SemanticDiffManager = SemanticDiffManager;
|
|
3851
6122
|
exports.SemanticSnapshotManager = SemanticSnapshotManager;
|
|
6123
|
+
exports.analyzeActionParity = analyzeActionParity;
|
|
6124
|
+
exports.analyzeFormat = analyzeFormat;
|
|
6125
|
+
exports.analyzePageFormats = analyzePageFormats;
|
|
3852
6126
|
exports.areSynonyms = areSynonyms;
|
|
6127
|
+
exports.buildNavigationMap = buildNavigationMap;
|
|
6128
|
+
exports.classifyDataType = classifyDataType;
|
|
6129
|
+
exports.classifyRegionType = classifyRegionType;
|
|
6130
|
+
exports.classifyStatusDirection = classifyStatusDirection;
|
|
6131
|
+
exports.compareComponents = compareComponents;
|
|
6132
|
+
exports.compareContent = compareContent;
|
|
6133
|
+
exports.compareFormats = compareFormats;
|
|
6134
|
+
exports.compareLayouts = compareLayouts;
|
|
6135
|
+
exports.computeCrossAppDiff = computeCrossAppDiff;
|
|
3853
6136
|
exports.computeDiff = computeDiff;
|
|
6137
|
+
exports.computeProminence = computeProminence;
|
|
3854
6138
|
exports.createAssertionExecutor = createAssertionExecutor;
|
|
3855
6139
|
exports.createDiffManager = createDiffManager;
|
|
3856
6140
|
exports.createErrorContext = createErrorContext;
|
|
@@ -3860,13 +6144,20 @@ exports.createSimpleError = createSimpleError;
|
|
|
3860
6144
|
exports.createSnapshotManager = createSnapshotManager;
|
|
3861
6145
|
exports.describeAction = describeAction;
|
|
3862
6146
|
exports.describeDiff = describeDiff;
|
|
6147
|
+
exports.detectFormatPattern = detectFormatPattern;
|
|
6148
|
+
exports.detectGridStructure = detectGridStructure;
|
|
6149
|
+
exports.detectList = detectList;
|
|
6150
|
+
exports.detectTable = detectTable;
|
|
3863
6151
|
exports.extractModifiers = extractModifiers;
|
|
6152
|
+
exports.extractPageData = extractPageData;
|
|
6153
|
+
exports.extractStructuredData = extractStructuredData;
|
|
3864
6154
|
exports.findAllMatches = findAllMatches;
|
|
3865
6155
|
exports.findBestMatch = findBestMatch;
|
|
3866
6156
|
exports.formatErrorContext = formatErrorContext;
|
|
3867
6157
|
exports.fuzzyContains = fuzzyContains;
|
|
3868
6158
|
exports.fuzzyMatch = fuzzyMatch;
|
|
3869
6159
|
exports.generateAliases = generateAliases;
|
|
6160
|
+
exports.generateComparisonReport = generateComparisonReport;
|
|
3870
6161
|
exports.generateDescription = generateDescription;
|
|
3871
6162
|
exports.generateDiffSummary = generateDiffSummary;
|
|
3872
6163
|
exports.generateElementDescription = generateElementDescription;
|
|
@@ -3879,15 +6170,20 @@ exports.getBestRecoverySuggestion = getBestRecoverySuggestion;
|
|
|
3879
6170
|
exports.getSynonyms = getSynonyms;
|
|
3880
6171
|
exports.hasSignificantChanges = hasSignificantChanges;
|
|
3881
6172
|
exports.inferPageType = inferPageType;
|
|
6173
|
+
exports.isNavigationElement = isNavigationElement;
|
|
3882
6174
|
exports.isRecoverableError = isRecoverableError;
|
|
3883
6175
|
exports.jaroSimilarity = jaroSimilarity;
|
|
3884
6176
|
exports.jaroWinklerSimilarity = jaroWinklerSimilarity;
|
|
3885
6177
|
exports.levenshteinDistance = levenshteinDistance;
|
|
3886
6178
|
exports.levenshteinSimilarity = levenshteinSimilarity;
|
|
6179
|
+
exports.matchElements = matchElements;
|
|
3887
6180
|
exports.ngramSimilarity = ngramSimilarity;
|
|
3888
6181
|
exports.normalizeString = normalizeString;
|
|
6182
|
+
exports.normalizeValue = normalizeValue;
|
|
3889
6183
|
exports.parseNLInstruction = parseNLInstruction;
|
|
3890
6184
|
exports.parseNLInstructions = parseNLInstructions;
|
|
6185
|
+
exports.parseNumericValue = parseNumericValue;
|
|
6186
|
+
exports.segmentPageRegions = segmentPageRegions;
|
|
3891
6187
|
exports.splitCompoundInstruction = splitCompoundInstruction;
|
|
3892
6188
|
exports.tokenSimilarity = tokenSimilarity;
|
|
3893
6189
|
exports.tokenize = tokenize;
|