@seed-ship/mcp-ui-solid 3.0.5 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +115 -0
- package/README.md +253 -280
- package/dist/components/ChartJSRenderer.cjs +37 -15
- package/dist/components/ChartJSRenderer.cjs.map +1 -1
- package/dist/components/ChartJSRenderer.d.ts.map +1 -1
- package/dist/components/ChartJSRenderer.js +37 -15
- package/dist/components/ChartJSRenderer.js.map +1 -1
- package/dist/components/DataPreviewSection.cjs +172 -0
- package/dist/components/DataPreviewSection.cjs.map +1 -0
- package/dist/components/DataPreviewSection.d.ts +19 -0
- package/dist/components/DataPreviewSection.d.ts.map +1 -0
- package/dist/components/DataPreviewSection.js +172 -0
- package/dist/components/DataPreviewSection.js.map +1 -0
- package/dist/components/MapRenderer.cjs +168 -26
- package/dist/components/MapRenderer.cjs.map +1 -1
- package/dist/components/MapRenderer.d.ts +2 -2
- package/dist/components/MapRenderer.d.ts.map +1 -1
- package/dist/components/MapRenderer.js +169 -27
- package/dist/components/MapRenderer.js.map +1 -1
- package/dist/components/ScratchpadPanel.cjs +74 -0
- package/dist/components/ScratchpadPanel.cjs.map +1 -1
- package/dist/components/ScratchpadPanel.d.ts.map +1 -1
- package/dist/components/ScratchpadPanel.js +75 -1
- package/dist/components/ScratchpadPanel.js.map +1 -1
- package/dist/components/VerifiedText.cjs +166 -0
- package/dist/components/VerifiedText.cjs.map +1 -0
- package/dist/components/VerifiedText.d.ts +22 -0
- package/dist/components/VerifiedText.d.ts.map +1 -0
- package/dist/components/VerifiedText.js +166 -0
- package/dist/components/VerifiedText.js.map +1 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components.cjs +4 -0
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.cts +4 -0
- package/dist/components.d.ts +4 -0
- package/dist/components.js +4 -0
- package/dist/components.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useDataValidator.cjs +31 -0
- package/dist/hooks/useDataValidator.cjs.map +1 -0
- package/dist/hooks/useDataValidator.d.ts +42 -0
- package/dist/hooks/useDataValidator.d.ts.map +1 -0
- package/dist/hooks/useDataValidator.js +31 -0
- package/dist/hooks/useDataValidator.js.map +1 -0
- package/dist/hooks.cjs +2 -0
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.cts +2 -0
- package/dist/hooks.d.ts +2 -0
- package/dist/hooks.js +2 -0
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +8 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -5
- package/dist/index.d.ts +9 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/node_modules/.pnpm/@mapbox_point-geometry@1.1.0/node_modules/@mapbox/point-geometry/index.cjs +290 -0
- package/dist/node_modules/.pnpm/@mapbox_point-geometry@1.1.0/node_modules/@mapbox/point-geometry/index.cjs.map +1 -0
- package/dist/node_modules/.pnpm/@mapbox_point-geometry@1.1.0/node_modules/@mapbox/point-geometry/index.js +291 -0
- package/dist/node_modules/.pnpm/@mapbox_point-geometry@1.1.0/node_modules/@mapbox/point-geometry/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@mapbox_vector-tile@2.0.4/node_modules/@mapbox/vector-tile/index.cjs +243 -0
- package/dist/node_modules/.pnpm/@mapbox_vector-tile@2.0.4/node_modules/@mapbox/vector-tile/index.cjs.map +1 -0
- package/dist/node_modules/.pnpm/@mapbox_vector-tile@2.0.4/node_modules/@mapbox/vector-tile/index.js +243 -0
- package/dist/node_modules/.pnpm/@mapbox_vector-tile@2.0.4/node_modules/@mapbox/vector-tile/index.js.map +1 -0
- package/dist/node_modules/.pnpm/color2k@2.0.3/node_modules/color2k/dist/index.exports.import.es.cjs +137 -0
- package/dist/node_modules/.pnpm/color2k@2.0.3/node_modules/color2k/dist/index.exports.import.es.cjs.map +1 -0
- package/dist/node_modules/.pnpm/color2k@2.0.3/node_modules/color2k/dist/index.exports.import.es.js +137 -0
- package/dist/node_modules/.pnpm/color2k@2.0.3/node_modules/color2k/dist/index.exports.import.es.js.map +1 -0
- package/dist/node_modules/.pnpm/pbf@4.0.1/node_modules/pbf/index.cjs +686 -0
- package/dist/node_modules/.pnpm/pbf@4.0.1/node_modules/pbf/index.cjs.map +1 -0
- package/dist/node_modules/.pnpm/pbf@4.0.1/node_modules/pbf/index.js +687 -0
- package/dist/node_modules/.pnpm/pbf@4.0.1/node_modules/pbf/index.js.map +1 -0
- package/dist/node_modules/.pnpm/pmtiles@3.2.1/node_modules/pmtiles/dist/index.cjs +1366 -0
- package/dist/node_modules/.pnpm/pmtiles@3.2.1/node_modules/pmtiles/dist/index.cjs.map +1 -0
- package/dist/node_modules/.pnpm/pmtiles@3.2.1/node_modules/pmtiles/dist/index.js +1366 -0
- package/dist/node_modules/.pnpm/pmtiles@3.2.1/node_modules/pmtiles/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/potpack@1.0.2/node_modules/potpack/index.cjs +54 -0
- package/dist/node_modules/.pnpm/potpack@1.0.2/node_modules/potpack/index.cjs.map +1 -0
- package/dist/node_modules/.pnpm/potpack@1.0.2/node_modules/potpack/index.js +55 -0
- package/dist/node_modules/.pnpm/potpack@1.0.2/node_modules/potpack/index.js.map +1 -0
- package/dist/node_modules/.pnpm/protomaps-leaflet@4.1.1/node_modules/protomaps-leaflet/dist/esm/index.cjs +1256 -0
- package/dist/node_modules/.pnpm/protomaps-leaflet@4.1.1/node_modules/protomaps-leaflet/dist/esm/index.cjs.map +1 -0
- package/dist/node_modules/.pnpm/protomaps-leaflet@4.1.1/node_modules/protomaps-leaflet/dist/esm/index.js +1256 -0
- package/dist/node_modules/.pnpm/protomaps-leaflet@4.1.1/node_modules/protomaps-leaflet/dist/esm/index.js.map +1 -0
- package/dist/node_modules/.pnpm/quickselect@2.0.0/node_modules/quickselect/index.cjs +47 -0
- package/dist/node_modules/.pnpm/quickselect@2.0.0/node_modules/quickselect/index.cjs.map +1 -0
- package/dist/node_modules/.pnpm/quickselect@2.0.0/node_modules/quickselect/index.js +48 -0
- package/dist/node_modules/.pnpm/quickselect@2.0.0/node_modules/quickselect/index.js.map +1 -0
- package/dist/node_modules/.pnpm/rbush@3.0.1/node_modules/rbush/index.cjs +378 -0
- package/dist/node_modules/.pnpm/rbush@3.0.1/node_modules/rbush/index.cjs.map +1 -0
- package/dist/node_modules/.pnpm/rbush@3.0.1/node_modules/rbush/index.js +379 -0
- package/dist/node_modules/.pnpm/rbush@3.0.1/node_modules/rbush/index.js.map +1 -0
- package/dist/services/data-validator.cjs +85 -0
- package/dist/services/data-validator.cjs.map +1 -0
- package/dist/services/data-validator.d.ts +28 -0
- package/dist/services/data-validator.d.ts.map +1 -0
- package/dist/services/data-validator.js +85 -0
- package/dist/services/data-validator.js.map +1 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/types/chat-bus.d.ts +88 -1
- package/dist/types/chat-bus.d.ts.map +1 -1
- package/dist/types/index.d.ts +135 -6
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types.d.cts +135 -6
- package/dist/types.d.ts +135 -6
- package/package.json +5 -1
- package/src/components/ChartJSRenderer.tsx +35 -13
- package/src/components/DataPreviewSection.tsx +206 -0
- package/src/components/MapRenderer.test.tsx +94 -5
- package/src/components/MapRenderer.tsx +246 -45
- package/src/components/ScratchpadPanel.tsx +10 -2
- package/src/components/VerifiedText.tsx +187 -0
- package/src/components/index.ts +7 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/useDataValidator.ts +68 -0
- package/src/index.ts +26 -1
- package/src/services/data-validator.test.ts +151 -0
- package/src/services/data-validator.ts +149 -0
- package/src/services/index.ts +2 -0
- package/src/types/chat-bus.ts +98 -1
- package/src/types/index.ts +145 -6
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import quickselect from "../../../quickselect@2.0.0/node_modules/quickselect/index.js";
|
|
2
|
+
class RBush {
|
|
3
|
+
constructor(maxEntries = 9) {
|
|
4
|
+
this._maxEntries = Math.max(4, maxEntries);
|
|
5
|
+
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
|
|
6
|
+
this.clear();
|
|
7
|
+
}
|
|
8
|
+
all() {
|
|
9
|
+
return this._all(this.data, []);
|
|
10
|
+
}
|
|
11
|
+
search(bbox) {
|
|
12
|
+
let node = this.data;
|
|
13
|
+
const result = [];
|
|
14
|
+
if (!intersects(bbox, node)) return result;
|
|
15
|
+
const toBBox = this.toBBox;
|
|
16
|
+
const nodesToSearch = [];
|
|
17
|
+
while (node) {
|
|
18
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
19
|
+
const child = node.children[i];
|
|
20
|
+
const childBBox = node.leaf ? toBBox(child) : child;
|
|
21
|
+
if (intersects(bbox, childBBox)) {
|
|
22
|
+
if (node.leaf) result.push(child);
|
|
23
|
+
else if (contains(bbox, childBBox)) this._all(child, result);
|
|
24
|
+
else nodesToSearch.push(child);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
node = nodesToSearch.pop();
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
collides(bbox) {
|
|
32
|
+
let node = this.data;
|
|
33
|
+
if (!intersects(bbox, node)) return false;
|
|
34
|
+
const nodesToSearch = [];
|
|
35
|
+
while (node) {
|
|
36
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
37
|
+
const child = node.children[i];
|
|
38
|
+
const childBBox = node.leaf ? this.toBBox(child) : child;
|
|
39
|
+
if (intersects(bbox, childBBox)) {
|
|
40
|
+
if (node.leaf || contains(bbox, childBBox)) return true;
|
|
41
|
+
nodesToSearch.push(child);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
node = nodesToSearch.pop();
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
load(data) {
|
|
49
|
+
if (!(data && data.length)) return this;
|
|
50
|
+
if (data.length < this._minEntries) {
|
|
51
|
+
for (let i = 0; i < data.length; i++) {
|
|
52
|
+
this.insert(data[i]);
|
|
53
|
+
}
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
let node = this._build(data.slice(), 0, data.length - 1, 0);
|
|
57
|
+
if (!this.data.children.length) {
|
|
58
|
+
this.data = node;
|
|
59
|
+
} else if (this.data.height === node.height) {
|
|
60
|
+
this._splitRoot(this.data, node);
|
|
61
|
+
} else {
|
|
62
|
+
if (this.data.height < node.height) {
|
|
63
|
+
const tmpNode = this.data;
|
|
64
|
+
this.data = node;
|
|
65
|
+
node = tmpNode;
|
|
66
|
+
}
|
|
67
|
+
this._insert(node, this.data.height - node.height - 1, true);
|
|
68
|
+
}
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
insert(item) {
|
|
72
|
+
if (item) this._insert(item, this.data.height - 1);
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
clear() {
|
|
76
|
+
this.data = createNode([]);
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
remove(item, equalsFn) {
|
|
80
|
+
if (!item) return this;
|
|
81
|
+
let node = this.data;
|
|
82
|
+
const bbox = this.toBBox(item);
|
|
83
|
+
const path = [];
|
|
84
|
+
const indexes = [];
|
|
85
|
+
let i, parent, goingUp;
|
|
86
|
+
while (node || path.length) {
|
|
87
|
+
if (!node) {
|
|
88
|
+
node = path.pop();
|
|
89
|
+
parent = path[path.length - 1];
|
|
90
|
+
i = indexes.pop();
|
|
91
|
+
goingUp = true;
|
|
92
|
+
}
|
|
93
|
+
if (node.leaf) {
|
|
94
|
+
const index = findItem(item, node.children, equalsFn);
|
|
95
|
+
if (index !== -1) {
|
|
96
|
+
node.children.splice(index, 1);
|
|
97
|
+
path.push(node);
|
|
98
|
+
this._condense(path);
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!goingUp && !node.leaf && contains(node, bbox)) {
|
|
103
|
+
path.push(node);
|
|
104
|
+
indexes.push(i);
|
|
105
|
+
i = 0;
|
|
106
|
+
parent = node;
|
|
107
|
+
node = node.children[0];
|
|
108
|
+
} else if (parent) {
|
|
109
|
+
i++;
|
|
110
|
+
node = parent.children[i];
|
|
111
|
+
goingUp = false;
|
|
112
|
+
} else node = null;
|
|
113
|
+
}
|
|
114
|
+
return this;
|
|
115
|
+
}
|
|
116
|
+
toBBox(item) {
|
|
117
|
+
return item;
|
|
118
|
+
}
|
|
119
|
+
compareMinX(a, b) {
|
|
120
|
+
return a.minX - b.minX;
|
|
121
|
+
}
|
|
122
|
+
compareMinY(a, b) {
|
|
123
|
+
return a.minY - b.minY;
|
|
124
|
+
}
|
|
125
|
+
toJSON() {
|
|
126
|
+
return this.data;
|
|
127
|
+
}
|
|
128
|
+
fromJSON(data) {
|
|
129
|
+
this.data = data;
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
_all(node, result) {
|
|
133
|
+
const nodesToSearch = [];
|
|
134
|
+
while (node) {
|
|
135
|
+
if (node.leaf) result.push(...node.children);
|
|
136
|
+
else nodesToSearch.push(...node.children);
|
|
137
|
+
node = nodesToSearch.pop();
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
_build(items, left, right, height) {
|
|
142
|
+
const N = right - left + 1;
|
|
143
|
+
let M = this._maxEntries;
|
|
144
|
+
let node;
|
|
145
|
+
if (N <= M) {
|
|
146
|
+
node = createNode(items.slice(left, right + 1));
|
|
147
|
+
calcBBox(node, this.toBBox);
|
|
148
|
+
return node;
|
|
149
|
+
}
|
|
150
|
+
if (!height) {
|
|
151
|
+
height = Math.ceil(Math.log(N) / Math.log(M));
|
|
152
|
+
M = Math.ceil(N / Math.pow(M, height - 1));
|
|
153
|
+
}
|
|
154
|
+
node = createNode([]);
|
|
155
|
+
node.leaf = false;
|
|
156
|
+
node.height = height;
|
|
157
|
+
const N2 = Math.ceil(N / M);
|
|
158
|
+
const N1 = N2 * Math.ceil(Math.sqrt(M));
|
|
159
|
+
multiSelect(items, left, right, N1, this.compareMinX);
|
|
160
|
+
for (let i = left; i <= right; i += N1) {
|
|
161
|
+
const right2 = Math.min(i + N1 - 1, right);
|
|
162
|
+
multiSelect(items, i, right2, N2, this.compareMinY);
|
|
163
|
+
for (let j = i; j <= right2; j += N2) {
|
|
164
|
+
const right3 = Math.min(j + N2 - 1, right2);
|
|
165
|
+
node.children.push(this._build(items, j, right3, height - 1));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
calcBBox(node, this.toBBox);
|
|
169
|
+
return node;
|
|
170
|
+
}
|
|
171
|
+
_chooseSubtree(bbox, node, level, path) {
|
|
172
|
+
while (true) {
|
|
173
|
+
path.push(node);
|
|
174
|
+
if (node.leaf || path.length - 1 === level) break;
|
|
175
|
+
let minArea = Infinity;
|
|
176
|
+
let minEnlargement = Infinity;
|
|
177
|
+
let targetNode;
|
|
178
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
179
|
+
const child = node.children[i];
|
|
180
|
+
const area = bboxArea(child);
|
|
181
|
+
const enlargement = enlargedArea(bbox, child) - area;
|
|
182
|
+
if (enlargement < minEnlargement) {
|
|
183
|
+
minEnlargement = enlargement;
|
|
184
|
+
minArea = area < minArea ? area : minArea;
|
|
185
|
+
targetNode = child;
|
|
186
|
+
} else if (enlargement === minEnlargement) {
|
|
187
|
+
if (area < minArea) {
|
|
188
|
+
minArea = area;
|
|
189
|
+
targetNode = child;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
node = targetNode || node.children[0];
|
|
194
|
+
}
|
|
195
|
+
return node;
|
|
196
|
+
}
|
|
197
|
+
_insert(item, level, isNode) {
|
|
198
|
+
const bbox = isNode ? item : this.toBBox(item);
|
|
199
|
+
const insertPath = [];
|
|
200
|
+
const node = this._chooseSubtree(bbox, this.data, level, insertPath);
|
|
201
|
+
node.children.push(item);
|
|
202
|
+
extend(node, bbox);
|
|
203
|
+
while (level >= 0) {
|
|
204
|
+
if (insertPath[level].children.length > this._maxEntries) {
|
|
205
|
+
this._split(insertPath, level);
|
|
206
|
+
level--;
|
|
207
|
+
} else break;
|
|
208
|
+
}
|
|
209
|
+
this._adjustParentBBoxes(bbox, insertPath, level);
|
|
210
|
+
}
|
|
211
|
+
// split overflowed node into two
|
|
212
|
+
_split(insertPath, level) {
|
|
213
|
+
const node = insertPath[level];
|
|
214
|
+
const M = node.children.length;
|
|
215
|
+
const m = this._minEntries;
|
|
216
|
+
this._chooseSplitAxis(node, m, M);
|
|
217
|
+
const splitIndex = this._chooseSplitIndex(node, m, M);
|
|
218
|
+
const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
|
|
219
|
+
newNode.height = node.height;
|
|
220
|
+
newNode.leaf = node.leaf;
|
|
221
|
+
calcBBox(node, this.toBBox);
|
|
222
|
+
calcBBox(newNode, this.toBBox);
|
|
223
|
+
if (level) insertPath[level - 1].children.push(newNode);
|
|
224
|
+
else this._splitRoot(node, newNode);
|
|
225
|
+
}
|
|
226
|
+
_splitRoot(node, newNode) {
|
|
227
|
+
this.data = createNode([node, newNode]);
|
|
228
|
+
this.data.height = node.height + 1;
|
|
229
|
+
this.data.leaf = false;
|
|
230
|
+
calcBBox(this.data, this.toBBox);
|
|
231
|
+
}
|
|
232
|
+
_chooseSplitIndex(node, m, M) {
|
|
233
|
+
let index;
|
|
234
|
+
let minOverlap = Infinity;
|
|
235
|
+
let minArea = Infinity;
|
|
236
|
+
for (let i = m; i <= M - m; i++) {
|
|
237
|
+
const bbox1 = distBBox(node, 0, i, this.toBBox);
|
|
238
|
+
const bbox2 = distBBox(node, i, M, this.toBBox);
|
|
239
|
+
const overlap = intersectionArea(bbox1, bbox2);
|
|
240
|
+
const area = bboxArea(bbox1) + bboxArea(bbox2);
|
|
241
|
+
if (overlap < minOverlap) {
|
|
242
|
+
minOverlap = overlap;
|
|
243
|
+
index = i;
|
|
244
|
+
minArea = area < minArea ? area : minArea;
|
|
245
|
+
} else if (overlap === minOverlap) {
|
|
246
|
+
if (area < minArea) {
|
|
247
|
+
minArea = area;
|
|
248
|
+
index = i;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return index || M - m;
|
|
253
|
+
}
|
|
254
|
+
// sorts node children by the best axis for split
|
|
255
|
+
_chooseSplitAxis(node, m, M) {
|
|
256
|
+
const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
|
|
257
|
+
const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
|
|
258
|
+
const xMargin = this._allDistMargin(node, m, M, compareMinX);
|
|
259
|
+
const yMargin = this._allDistMargin(node, m, M, compareMinY);
|
|
260
|
+
if (xMargin < yMargin) node.children.sort(compareMinX);
|
|
261
|
+
}
|
|
262
|
+
// total margin of all possible split distributions where each node is at least m full
|
|
263
|
+
_allDistMargin(node, m, M, compare) {
|
|
264
|
+
node.children.sort(compare);
|
|
265
|
+
const toBBox = this.toBBox;
|
|
266
|
+
const leftBBox = distBBox(node, 0, m, toBBox);
|
|
267
|
+
const rightBBox = distBBox(node, M - m, M, toBBox);
|
|
268
|
+
let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
|
|
269
|
+
for (let i = m; i < M - m; i++) {
|
|
270
|
+
const child = node.children[i];
|
|
271
|
+
extend(leftBBox, node.leaf ? toBBox(child) : child);
|
|
272
|
+
margin += bboxMargin(leftBBox);
|
|
273
|
+
}
|
|
274
|
+
for (let i = M - m - 1; i >= m; i--) {
|
|
275
|
+
const child = node.children[i];
|
|
276
|
+
extend(rightBBox, node.leaf ? toBBox(child) : child);
|
|
277
|
+
margin += bboxMargin(rightBBox);
|
|
278
|
+
}
|
|
279
|
+
return margin;
|
|
280
|
+
}
|
|
281
|
+
_adjustParentBBoxes(bbox, path, level) {
|
|
282
|
+
for (let i = level; i >= 0; i--) {
|
|
283
|
+
extend(path[i], bbox);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
_condense(path) {
|
|
287
|
+
for (let i = path.length - 1, siblings; i >= 0; i--) {
|
|
288
|
+
if (path[i].children.length === 0) {
|
|
289
|
+
if (i > 0) {
|
|
290
|
+
siblings = path[i - 1].children;
|
|
291
|
+
siblings.splice(siblings.indexOf(path[i]), 1);
|
|
292
|
+
} else this.clear();
|
|
293
|
+
} else calcBBox(path[i], this.toBBox);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function findItem(item, items, equalsFn) {
|
|
298
|
+
if (!equalsFn) return items.indexOf(item);
|
|
299
|
+
for (let i = 0; i < items.length; i++) {
|
|
300
|
+
if (equalsFn(item, items[i])) return i;
|
|
301
|
+
}
|
|
302
|
+
return -1;
|
|
303
|
+
}
|
|
304
|
+
function calcBBox(node, toBBox) {
|
|
305
|
+
distBBox(node, 0, node.children.length, toBBox, node);
|
|
306
|
+
}
|
|
307
|
+
function distBBox(node, k, p, toBBox, destNode) {
|
|
308
|
+
if (!destNode) destNode = createNode(null);
|
|
309
|
+
destNode.minX = Infinity;
|
|
310
|
+
destNode.minY = Infinity;
|
|
311
|
+
destNode.maxX = -Infinity;
|
|
312
|
+
destNode.maxY = -Infinity;
|
|
313
|
+
for (let i = k; i < p; i++) {
|
|
314
|
+
const child = node.children[i];
|
|
315
|
+
extend(destNode, node.leaf ? toBBox(child) : child);
|
|
316
|
+
}
|
|
317
|
+
return destNode;
|
|
318
|
+
}
|
|
319
|
+
function extend(a, b) {
|
|
320
|
+
a.minX = Math.min(a.minX, b.minX);
|
|
321
|
+
a.minY = Math.min(a.minY, b.minY);
|
|
322
|
+
a.maxX = Math.max(a.maxX, b.maxX);
|
|
323
|
+
a.maxY = Math.max(a.maxY, b.maxY);
|
|
324
|
+
return a;
|
|
325
|
+
}
|
|
326
|
+
function compareNodeMinX(a, b) {
|
|
327
|
+
return a.minX - b.minX;
|
|
328
|
+
}
|
|
329
|
+
function compareNodeMinY(a, b) {
|
|
330
|
+
return a.minY - b.minY;
|
|
331
|
+
}
|
|
332
|
+
function bboxArea(a) {
|
|
333
|
+
return (a.maxX - a.minX) * (a.maxY - a.minY);
|
|
334
|
+
}
|
|
335
|
+
function bboxMargin(a) {
|
|
336
|
+
return a.maxX - a.minX + (a.maxY - a.minY);
|
|
337
|
+
}
|
|
338
|
+
function enlargedArea(a, b) {
|
|
339
|
+
return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
|
|
340
|
+
}
|
|
341
|
+
function intersectionArea(a, b) {
|
|
342
|
+
const minX = Math.max(a.minX, b.minX);
|
|
343
|
+
const minY = Math.max(a.minY, b.minY);
|
|
344
|
+
const maxX = Math.min(a.maxX, b.maxX);
|
|
345
|
+
const maxY = Math.min(a.maxY, b.maxY);
|
|
346
|
+
return Math.max(0, maxX - minX) * Math.max(0, maxY - minY);
|
|
347
|
+
}
|
|
348
|
+
function contains(a, b) {
|
|
349
|
+
return a.minX <= b.minX && a.minY <= b.minY && b.maxX <= a.maxX && b.maxY <= a.maxY;
|
|
350
|
+
}
|
|
351
|
+
function intersects(a, b) {
|
|
352
|
+
return b.minX <= a.maxX && b.minY <= a.maxY && b.maxX >= a.minX && b.maxY >= a.minY;
|
|
353
|
+
}
|
|
354
|
+
function createNode(children) {
|
|
355
|
+
return {
|
|
356
|
+
children,
|
|
357
|
+
height: 1,
|
|
358
|
+
leaf: true,
|
|
359
|
+
minX: Infinity,
|
|
360
|
+
minY: Infinity,
|
|
361
|
+
maxX: -Infinity,
|
|
362
|
+
maxY: -Infinity
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function multiSelect(arr, left, right, n, compare) {
|
|
366
|
+
const stack = [left, right];
|
|
367
|
+
while (stack.length) {
|
|
368
|
+
right = stack.pop();
|
|
369
|
+
left = stack.pop();
|
|
370
|
+
if (right - left <= n) continue;
|
|
371
|
+
const mid = left + Math.ceil((right - left) / n / 2) * n;
|
|
372
|
+
quickselect(arr, mid, left, right, compare);
|
|
373
|
+
stack.push(left, mid, mid, right);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
export {
|
|
377
|
+
RBush as default
|
|
378
|
+
};
|
|
379
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../../../../node_modules/.pnpm/rbush@3.0.1/node_modules/rbush/index.js"],"sourcesContent":["import quickselect from 'quickselect';\n\nexport default class RBush {\n constructor(maxEntries = 9) {\n // max entries in a node is 9 by default; min node fill is 40% for best performance\n this._maxEntries = Math.max(4, maxEntries);\n this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));\n this.clear();\n }\n\n all() {\n return this._all(this.data, []);\n }\n\n search(bbox) {\n let node = this.data;\n const result = [];\n\n if (!intersects(bbox, node)) return result;\n\n const toBBox = this.toBBox;\n const nodesToSearch = [];\n\n while (node) {\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n const childBBox = node.leaf ? toBBox(child) : child;\n\n if (intersects(bbox, childBBox)) {\n if (node.leaf) result.push(child);\n else if (contains(bbox, childBBox)) this._all(child, result);\n else nodesToSearch.push(child);\n }\n }\n node = nodesToSearch.pop();\n }\n\n return result;\n }\n\n collides(bbox) {\n let node = this.data;\n\n if (!intersects(bbox, node)) return false;\n\n const nodesToSearch = [];\n while (node) {\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n const childBBox = node.leaf ? this.toBBox(child) : child;\n\n if (intersects(bbox, childBBox)) {\n if (node.leaf || contains(bbox, childBBox)) return true;\n nodesToSearch.push(child);\n }\n }\n node = nodesToSearch.pop();\n }\n\n return false;\n }\n\n load(data) {\n if (!(data && data.length)) return this;\n\n if (data.length < this._minEntries) {\n for (let i = 0; i < data.length; i++) {\n this.insert(data[i]);\n }\n return this;\n }\n\n // recursively build the tree with the given data from scratch using OMT algorithm\n let node = this._build(data.slice(), 0, data.length - 1, 0);\n\n if (!this.data.children.length) {\n // save as is if tree is empty\n this.data = node;\n\n } else if (this.data.height === node.height) {\n // split root if trees have the same height\n this._splitRoot(this.data, node);\n\n } else {\n if (this.data.height < node.height) {\n // swap trees if inserted one is bigger\n const tmpNode = this.data;\n this.data = node;\n node = tmpNode;\n }\n\n // insert the small tree into the large tree at appropriate level\n this._insert(node, this.data.height - node.height - 1, true);\n }\n\n return this;\n }\n\n insert(item) {\n if (item) this._insert(item, this.data.height - 1);\n return this;\n }\n\n clear() {\n this.data = createNode([]);\n return this;\n }\n\n remove(item, equalsFn) {\n if (!item) return this;\n\n let node = this.data;\n const bbox = this.toBBox(item);\n const path = [];\n const indexes = [];\n let i, parent, goingUp;\n\n // depth-first iterative tree traversal\n while (node || path.length) {\n\n if (!node) { // go up\n node = path.pop();\n parent = path[path.length - 1];\n i = indexes.pop();\n goingUp = true;\n }\n\n if (node.leaf) { // check current node\n const index = findItem(item, node.children, equalsFn);\n\n if (index !== -1) {\n // item found, remove the item and condense tree upwards\n node.children.splice(index, 1);\n path.push(node);\n this._condense(path);\n return this;\n }\n }\n\n if (!goingUp && !node.leaf && contains(node, bbox)) { // go down\n path.push(node);\n indexes.push(i);\n i = 0;\n parent = node;\n node = node.children[0];\n\n } else if (parent) { // go right\n i++;\n node = parent.children[i];\n goingUp = false;\n\n } else node = null; // nothing found\n }\n\n return this;\n }\n\n toBBox(item) { return item; }\n\n compareMinX(a, b) { return a.minX - b.minX; }\n compareMinY(a, b) { return a.minY - b.minY; }\n\n toJSON() { return this.data; }\n\n fromJSON(data) {\n this.data = data;\n return this;\n }\n\n _all(node, result) {\n const nodesToSearch = [];\n while (node) {\n if (node.leaf) result.push(...node.children);\n else nodesToSearch.push(...node.children);\n\n node = nodesToSearch.pop();\n }\n return result;\n }\n\n _build(items, left, right, height) {\n\n const N = right - left + 1;\n let M = this._maxEntries;\n let node;\n\n if (N <= M) {\n // reached leaf level; return leaf\n node = createNode(items.slice(left, right + 1));\n calcBBox(node, this.toBBox);\n return node;\n }\n\n if (!height) {\n // target height of the bulk-loaded tree\n height = Math.ceil(Math.log(N) / Math.log(M));\n\n // target number of root entries to maximize storage utilization\n M = Math.ceil(N / Math.pow(M, height - 1));\n }\n\n node = createNode([]);\n node.leaf = false;\n node.height = height;\n\n // split the items into M mostly square tiles\n\n const N2 = Math.ceil(N / M);\n const N1 = N2 * Math.ceil(Math.sqrt(M));\n\n multiSelect(items, left, right, N1, this.compareMinX);\n\n for (let i = left; i <= right; i += N1) {\n\n const right2 = Math.min(i + N1 - 1, right);\n\n multiSelect(items, i, right2, N2, this.compareMinY);\n\n for (let j = i; j <= right2; j += N2) {\n\n const right3 = Math.min(j + N2 - 1, right2);\n\n // pack each entry recursively\n node.children.push(this._build(items, j, right3, height - 1));\n }\n }\n\n calcBBox(node, this.toBBox);\n\n return node;\n }\n\n _chooseSubtree(bbox, node, level, path) {\n while (true) {\n path.push(node);\n\n if (node.leaf || path.length - 1 === level) break;\n\n let minArea = Infinity;\n let minEnlargement = Infinity;\n let targetNode;\n\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n const area = bboxArea(child);\n const enlargement = enlargedArea(bbox, child) - area;\n\n // choose entry with the least area enlargement\n if (enlargement < minEnlargement) {\n minEnlargement = enlargement;\n minArea = area < minArea ? area : minArea;\n targetNode = child;\n\n } else if (enlargement === minEnlargement) {\n // otherwise choose one with the smallest area\n if (area < minArea) {\n minArea = area;\n targetNode = child;\n }\n }\n }\n\n node = targetNode || node.children[0];\n }\n\n return node;\n }\n\n _insert(item, level, isNode) {\n const bbox = isNode ? item : this.toBBox(item);\n const insertPath = [];\n\n // find the best node for accommodating the item, saving all nodes along the path too\n const node = this._chooseSubtree(bbox, this.data, level, insertPath);\n\n // put the item into the node\n node.children.push(item);\n extend(node, bbox);\n\n // split on node overflow; propagate upwards if necessary\n while (level >= 0) {\n if (insertPath[level].children.length > this._maxEntries) {\n this._split(insertPath, level);\n level--;\n } else break;\n }\n\n // adjust bboxes along the insertion path\n this._adjustParentBBoxes(bbox, insertPath, level);\n }\n\n // split overflowed node into two\n _split(insertPath, level) {\n const node = insertPath[level];\n const M = node.children.length;\n const m = this._minEntries;\n\n this._chooseSplitAxis(node, m, M);\n\n const splitIndex = this._chooseSplitIndex(node, m, M);\n\n const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));\n newNode.height = node.height;\n newNode.leaf = node.leaf;\n\n calcBBox(node, this.toBBox);\n calcBBox(newNode, this.toBBox);\n\n if (level) insertPath[level - 1].children.push(newNode);\n else this._splitRoot(node, newNode);\n }\n\n _splitRoot(node, newNode) {\n // split root node\n this.data = createNode([node, newNode]);\n this.data.height = node.height + 1;\n this.data.leaf = false;\n calcBBox(this.data, this.toBBox);\n }\n\n _chooseSplitIndex(node, m, M) {\n let index;\n let minOverlap = Infinity;\n let minArea = Infinity;\n\n for (let i = m; i <= M - m; i++) {\n const bbox1 = distBBox(node, 0, i, this.toBBox);\n const bbox2 = distBBox(node, i, M, this.toBBox);\n\n const overlap = intersectionArea(bbox1, bbox2);\n const area = bboxArea(bbox1) + bboxArea(bbox2);\n\n // choose distribution with minimum overlap\n if (overlap < minOverlap) {\n minOverlap = overlap;\n index = i;\n\n minArea = area < minArea ? area : minArea;\n\n } else if (overlap === minOverlap) {\n // otherwise choose distribution with minimum area\n if (area < minArea) {\n minArea = area;\n index = i;\n }\n }\n }\n\n return index || M - m;\n }\n\n // sorts node children by the best axis for split\n _chooseSplitAxis(node, m, M) {\n const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;\n const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;\n const xMargin = this._allDistMargin(node, m, M, compareMinX);\n const yMargin = this._allDistMargin(node, m, M, compareMinY);\n\n // if total distributions margin value is minimal for x, sort by minX,\n // otherwise it's already sorted by minY\n if (xMargin < yMargin) node.children.sort(compareMinX);\n }\n\n // total margin of all possible split distributions where each node is at least m full\n _allDistMargin(node, m, M, compare) {\n node.children.sort(compare);\n\n const toBBox = this.toBBox;\n const leftBBox = distBBox(node, 0, m, toBBox);\n const rightBBox = distBBox(node, M - m, M, toBBox);\n let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);\n\n for (let i = m; i < M - m; i++) {\n const child = node.children[i];\n extend(leftBBox, node.leaf ? toBBox(child) : child);\n margin += bboxMargin(leftBBox);\n }\n\n for (let i = M - m - 1; i >= m; i--) {\n const child = node.children[i];\n extend(rightBBox, node.leaf ? toBBox(child) : child);\n margin += bboxMargin(rightBBox);\n }\n\n return margin;\n }\n\n _adjustParentBBoxes(bbox, path, level) {\n // adjust bboxes along the given tree path\n for (let i = level; i >= 0; i--) {\n extend(path[i], bbox);\n }\n }\n\n _condense(path) {\n // go through the path, removing empty nodes and updating bboxes\n for (let i = path.length - 1, siblings; i >= 0; i--) {\n if (path[i].children.length === 0) {\n if (i > 0) {\n siblings = path[i - 1].children;\n siblings.splice(siblings.indexOf(path[i]), 1);\n\n } else this.clear();\n\n } else calcBBox(path[i], this.toBBox);\n }\n }\n}\n\nfunction findItem(item, items, equalsFn) {\n if (!equalsFn) return items.indexOf(item);\n\n for (let i = 0; i < items.length; i++) {\n if (equalsFn(item, items[i])) return i;\n }\n return -1;\n}\n\n// calculate node's bbox from bboxes of its children\nfunction calcBBox(node, toBBox) {\n distBBox(node, 0, node.children.length, toBBox, node);\n}\n\n// min bounding rectangle of node children from k to p-1\nfunction distBBox(node, k, p, toBBox, destNode) {\n if (!destNode) destNode = createNode(null);\n destNode.minX = Infinity;\n destNode.minY = Infinity;\n destNode.maxX = -Infinity;\n destNode.maxY = -Infinity;\n\n for (let i = k; i < p; i++) {\n const child = node.children[i];\n extend(destNode, node.leaf ? toBBox(child) : child);\n }\n\n return destNode;\n}\n\nfunction extend(a, b) {\n a.minX = Math.min(a.minX, b.minX);\n a.minY = Math.min(a.minY, b.minY);\n a.maxX = Math.max(a.maxX, b.maxX);\n a.maxY = Math.max(a.maxY, b.maxY);\n return a;\n}\n\nfunction compareNodeMinX(a, b) { return a.minX - b.minX; }\nfunction compareNodeMinY(a, b) { return a.minY - b.minY; }\n\nfunction bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }\nfunction bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }\n\nfunction enlargedArea(a, b) {\n return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *\n (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));\n}\n\nfunction intersectionArea(a, b) {\n const minX = Math.max(a.minX, b.minX);\n const minY = Math.max(a.minY, b.minY);\n const maxX = Math.min(a.maxX, b.maxX);\n const maxY = Math.min(a.maxY, b.maxY);\n\n return Math.max(0, maxX - minX) *\n Math.max(0, maxY - minY);\n}\n\nfunction contains(a, b) {\n return a.minX <= b.minX &&\n a.minY <= b.minY &&\n b.maxX <= a.maxX &&\n b.maxY <= a.maxY;\n}\n\nfunction intersects(a, b) {\n return b.minX <= a.maxX &&\n b.minY <= a.maxY &&\n b.maxX >= a.minX &&\n b.maxY >= a.minY;\n}\n\nfunction createNode(children) {\n return {\n children,\n height: 1,\n leaf: true,\n minX: Infinity,\n minY: Infinity,\n maxX: -Infinity,\n maxY: -Infinity\n };\n}\n\n// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;\n// combines selection algorithm with binary divide & conquer approach\n\nfunction multiSelect(arr, left, right, n, compare) {\n const stack = [left, right];\n\n while (stack.length) {\n right = stack.pop();\n left = stack.pop();\n\n if (right - left <= n) continue;\n\n const mid = left + Math.ceil((right - left) / n / 2) * n;\n quickselect(arr, mid, left, right, compare);\n\n stack.push(left, mid, mid, right);\n }\n}\n"],"names":[],"mappings":"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","x_google_ignoreList":[0]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const DEFAULT_IGNORE_COLUMNS = /* @__PURE__ */ new Set(["id", "code_geo", "code_parent"]);
|
|
4
|
+
const DEFAULT_IGNORE_PATTERNS = [
|
|
5
|
+
/^20[012]\d$/,
|
|
6
|
+
// years 2000-2029
|
|
7
|
+
/^\d{5}$/,
|
|
8
|
+
// postal codes / INSEE codes
|
|
9
|
+
/^\d{1,2}$/
|
|
10
|
+
// indices, ranks (1-99)
|
|
11
|
+
];
|
|
12
|
+
function extractSourceNumbers(rows, ignoreColumns) {
|
|
13
|
+
const numbers = /* @__PURE__ */ new Set();
|
|
14
|
+
for (const row of rows) {
|
|
15
|
+
for (const [col, val] of Object.entries(row)) {
|
|
16
|
+
if (ignoreColumns.has(col)) continue;
|
|
17
|
+
if (typeof val === "number" && isFinite(val)) {
|
|
18
|
+
numbers.add(val);
|
|
19
|
+
} else if (typeof val === "string") {
|
|
20
|
+
const parsed = Number(val.replace(/\s/g, "").replace(",", "."));
|
|
21
|
+
if (!isNaN(parsed) && isFinite(parsed)) {
|
|
22
|
+
numbers.add(parsed);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return numbers;
|
|
28
|
+
}
|
|
29
|
+
function extractLLMNumbers(text, ignorePatterns) {
|
|
30
|
+
const numberRegex = /\d[\d\s,.]*\d|\d+/g;
|
|
31
|
+
const results = [];
|
|
32
|
+
let match;
|
|
33
|
+
while ((match = numberRegex.exec(text)) !== null) {
|
|
34
|
+
const raw = match[0];
|
|
35
|
+
const cleaned = raw.replace(/[\s.]/g, "").replace(",", ".");
|
|
36
|
+
const value = Number(cleaned);
|
|
37
|
+
if (isNaN(value) || !isFinite(value)) continue;
|
|
38
|
+
if (ignorePatterns.some((p) => p.test(raw.trim()))) continue;
|
|
39
|
+
results.push({
|
|
40
|
+
value,
|
|
41
|
+
position: match.index,
|
|
42
|
+
context: text.slice(
|
|
43
|
+
Math.max(0, match.index - 10),
|
|
44
|
+
match.index + raw.length + 10
|
|
45
|
+
)
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return results;
|
|
49
|
+
}
|
|
50
|
+
function validateAgainstSource(text, sourceRows, options = {}) {
|
|
51
|
+
const tolerance = options.tolerance ?? 0.01;
|
|
52
|
+
const ignoreColumns = new Set(options.ignoreColumns || [...DEFAULT_IGNORE_COLUMNS]);
|
|
53
|
+
const ignorePatterns = options.ignorePatterns || DEFAULT_IGNORE_PATTERNS;
|
|
54
|
+
const sourceNumbers = extractSourceNumbers(sourceRows, ignoreColumns);
|
|
55
|
+
const llmNumbers = extractLLMNumbers(text, ignorePatterns);
|
|
56
|
+
const hallucinated = [];
|
|
57
|
+
for (const num of llmNumbers) {
|
|
58
|
+
if (sourceNumbers.has(num.value)) continue;
|
|
59
|
+
let closest;
|
|
60
|
+
let minDistance = Infinity;
|
|
61
|
+
for (const src of sourceNumbers) {
|
|
62
|
+
const dist = Math.abs(num.value - src) / Math.max(Math.abs(src), 1);
|
|
63
|
+
if (dist < minDistance) {
|
|
64
|
+
minDistance = dist;
|
|
65
|
+
closest = src;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (minDistance <= tolerance) continue;
|
|
69
|
+
hallucinated.push({
|
|
70
|
+
...num,
|
|
71
|
+
closest,
|
|
72
|
+
distance: minDistance
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const confidence = llmNumbers.length > 0 ? 1 - hallucinated.length / llmNumbers.length : 1;
|
|
76
|
+
return {
|
|
77
|
+
valid: hallucinated.length === 0,
|
|
78
|
+
llmNumbers,
|
|
79
|
+
sourceNumbers,
|
|
80
|
+
hallucinated,
|
|
81
|
+
confidence
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
exports.validateAgainstSource = validateAgainstSource;
|
|
85
|
+
//# sourceMappingURL=data-validator.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-validator.cjs","sources":["../../src/services/data-validator.ts"],"sourcesContent":["/**\n * Data Validator — anti-hallucination for LLM-generated text\n * v3.1.0: Pure regex-based number verification against source data\n *\n * Compares numbers in LLM text to numbers in source data rows.\n * Detects ~90% of numerical hallucinations with zero LLM cost, <1ms latency.\n */\n\nimport type { DataValidation, DataValidationOptions, LLMNumber, HallucinatedNumber } from '../types/chat-bus'\n\nconst DEFAULT_IGNORE_COLUMNS = new Set(['id', 'code_geo', 'code_parent'])\nconst DEFAULT_IGNORE_PATTERNS: RegExp[] = [\n /^20[012]\\d$/, // years 2000-2029\n /^\\d{5}$/, // postal codes / INSEE codes\n /^\\d{1,2}$/, // indices, ranks (1-99)\n]\n\n/**\n * Extract all numeric values from source data rows.\n * Handles both number types and string-encoded numbers (e.g. \"22 306\", \"3,337\").\n */\nfunction extractSourceNumbers(\n rows: Record<string, unknown>[],\n ignoreColumns: Set<string>\n): Set<number> {\n const numbers = new Set<number>()\n for (const row of rows) {\n for (const [col, val] of Object.entries(row)) {\n if (ignoreColumns.has(col)) continue\n if (typeof val === 'number' && isFinite(val)) {\n numbers.add(val)\n } else if (typeof val === 'string') {\n const parsed = Number(val.replace(/\\s/g, '').replace(',', '.'))\n if (!isNaN(parsed) && isFinite(parsed)) {\n numbers.add(parsed)\n }\n }\n }\n }\n return numbers\n}\n\n/**\n * Extract all numbers from LLM text.\n * Handles French/European formats: \"22 306\", \"3 337\", \"3,337\", \"22306\".\n */\nfunction extractLLMNumbers(\n text: string,\n ignorePatterns: RegExp[]\n): LLMNumber[] {\n const numberRegex = /\\d[\\d\\s,.]*\\d|\\d+/g\n const results: LLMNumber[] = []\n let match: RegExpExecArray | null\n\n while ((match = numberRegex.exec(text)) !== null) {\n const raw = match[0]\n // Normalize: remove spaces/dots (thousand separators), comma → decimal point\n const cleaned = raw.replace(/[\\s.]/g, '').replace(',', '.')\n const value = Number(cleaned)\n\n if (isNaN(value) || !isFinite(value)) continue\n if (ignorePatterns.some(p => p.test(raw.trim()))) continue\n\n results.push({\n value,\n position: match.index,\n context: text.slice(\n Math.max(0, match.index - 10),\n match.index + raw.length + 10\n ),\n })\n }\n\n return results\n}\n\n/**\n * Validate LLM-generated text against source data rows.\n *\n * Pure function — no IO, no LLM calls, no side effects.\n * Returns which numbers in the text are verified vs hallucinated.\n *\n * @example\n * ```typescript\n * const rows = [{ type: 'Appartement', ventes: 22306, prix_m2: 3337 }]\n * const result = validateAgainstSource(\n * \"On observe 22 306 ventes à 3 337 EUR/m². En 2023, 18 245 ventes.\",\n * rows\n * )\n * // result.valid === false\n * // result.hallucinated === [{ value: 18245, ... }]\n * // result.confidence === 0.67\n * ```\n */\nexport function validateAgainstSource(\n text: string,\n sourceRows: Record<string, unknown>[],\n options: DataValidationOptions = {}\n): DataValidation {\n const tolerance = options.tolerance ?? 0.01\n const ignoreColumns = new Set(options.ignoreColumns || [...DEFAULT_IGNORE_COLUMNS])\n const ignorePatterns = options.ignorePatterns || DEFAULT_IGNORE_PATTERNS\n\n // 1. Extract source numbers\n const sourceNumbers = extractSourceNumbers(sourceRows, ignoreColumns)\n\n // 2. Extract LLM numbers\n const llmNumbers = extractLLMNumbers(text, ignorePatterns)\n\n // 3. Check each LLM number against source\n const hallucinated: HallucinatedNumber[] = []\n\n for (const num of llmNumbers) {\n // Exact match\n if (sourceNumbers.has(num.value)) continue\n\n // Tolerance match (rounding)\n let closest: number | undefined\n let minDistance = Infinity\n\n for (const src of sourceNumbers) {\n const dist = Math.abs(num.value - src) / Math.max(Math.abs(src), 1)\n if (dist < minDistance) {\n minDistance = dist\n closest = src\n }\n }\n\n if (minDistance <= tolerance) continue // acceptable rounding\n\n hallucinated.push({\n ...num,\n closest,\n distance: minDistance,\n })\n }\n\n const confidence = llmNumbers.length > 0\n ? 1 - (hallucinated.length / llmNumbers.length)\n : 1\n\n return {\n valid: hallucinated.length === 0,\n llmNumbers,\n sourceNumbers,\n hallucinated,\n confidence,\n }\n}\n"],"names":[],"mappings":";;AAUA,MAAM,yBAAyB,oBAAI,IAAI,CAAC,MAAM,YAAY,aAAa,CAAC;AACxE,MAAM,0BAAoC;AAAA,EACxC;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AAMA,SAAS,qBACP,MACA,eACa;AACb,QAAM,8BAAc,IAAA;AACpB,aAAW,OAAO,MAAM;AACtB,eAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC5C,UAAI,cAAc,IAAI,GAAG,EAAG;AAC5B,UAAI,OAAO,QAAQ,YAAY,SAAS,GAAG,GAAG;AAC5C,gBAAQ,IAAI,GAAG;AAAA,MACjB,WAAW,OAAO,QAAQ,UAAU;AAClC,cAAM,SAAS,OAAO,IAAI,QAAQ,OAAO,EAAE,EAAE,QAAQ,KAAK,GAAG,CAAC;AAC9D,YAAI,CAAC,MAAM,MAAM,KAAK,SAAS,MAAM,GAAG;AACtC,kBAAQ,IAAI,MAAM;AAAA,QACpB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAMA,SAAS,kBACP,MACA,gBACa;AACb,QAAM,cAAc;AACpB,QAAM,UAAuB,CAAA;AAC7B,MAAI;AAEJ,UAAQ,QAAQ,YAAY,KAAK,IAAI,OAAO,MAAM;AAChD,UAAM,MAAM,MAAM,CAAC;AAEnB,UAAM,UAAU,IAAI,QAAQ,UAAU,EAAE,EAAE,QAAQ,KAAK,GAAG;AAC1D,UAAM,QAAQ,OAAO,OAAO;AAE5B,QAAI,MAAM,KAAK,KAAK,CAAC,SAAS,KAAK,EAAG;AACtC,QAAI,eAAe,KAAK,CAAA,MAAK,EAAE,KAAK,IAAI,KAAA,CAAM,CAAC,EAAG;AAElD,YAAQ,KAAK;AAAA,MACX;AAAA,MACA,UAAU,MAAM;AAAA,MAChB,SAAS,KAAK;AAAA,QACZ,KAAK,IAAI,GAAG,MAAM,QAAQ,EAAE;AAAA,QAC5B,MAAM,QAAQ,IAAI,SAAS;AAAA,MAAA;AAAA,IAC7B,CACD;AAAA,EACH;AAEA,SAAO;AACT;AAoBO,SAAS,sBACd,MACA,YACA,UAAiC,CAAA,GACjB;AAChB,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,gBAAgB,IAAI,IAAI,QAAQ,iBAAiB,CAAC,GAAG,sBAAsB,CAAC;AAClF,QAAM,iBAAiB,QAAQ,kBAAkB;AAGjD,QAAM,gBAAgB,qBAAqB,YAAY,aAAa;AAGpE,QAAM,aAAa,kBAAkB,MAAM,cAAc;AAGzD,QAAM,eAAqC,CAAA;AAE3C,aAAW,OAAO,YAAY;AAE5B,QAAI,cAAc,IAAI,IAAI,KAAK,EAAG;AAGlC,QAAI;AACJ,QAAI,cAAc;AAElB,eAAW,OAAO,eAAe;AAC/B,YAAM,OAAO,KAAK,IAAI,IAAI,QAAQ,GAAG,IAAI,KAAK,IAAI,KAAK,IAAI,GAAG,GAAG,CAAC;AAClE,UAAI,OAAO,aAAa;AACtB,sBAAc;AACd,kBAAU;AAAA,MACZ;AAAA,IACF;AAEA,QAAI,eAAe,UAAW;AAE9B,iBAAa,KAAK;AAAA,MAChB,GAAG;AAAA,MACH;AAAA,MACA,UAAU;AAAA,IAAA,CACX;AAAA,EACH;AAEA,QAAM,aAAa,WAAW,SAAS,IACnC,IAAK,aAAa,SAAS,WAAW,SACtC;AAEJ,SAAO;AAAA,IACL,OAAO,aAAa,WAAW;AAAA,IAC/B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;;"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Validator — anti-hallucination for LLM-generated text
|
|
3
|
+
* v3.1.0: Pure regex-based number verification against source data
|
|
4
|
+
*
|
|
5
|
+
* Compares numbers in LLM text to numbers in source data rows.
|
|
6
|
+
* Detects ~90% of numerical hallucinations with zero LLM cost, <1ms latency.
|
|
7
|
+
*/
|
|
8
|
+
import type { DataValidation, DataValidationOptions } from '../types/chat-bus';
|
|
9
|
+
/**
|
|
10
|
+
* Validate LLM-generated text against source data rows.
|
|
11
|
+
*
|
|
12
|
+
* Pure function — no IO, no LLM calls, no side effects.
|
|
13
|
+
* Returns which numbers in the text are verified vs hallucinated.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const rows = [{ type: 'Appartement', ventes: 22306, prix_m2: 3337 }]
|
|
18
|
+
* const result = validateAgainstSource(
|
|
19
|
+
* "On observe 22 306 ventes à 3 337 EUR/m². En 2023, 18 245 ventes.",
|
|
20
|
+
* rows
|
|
21
|
+
* )
|
|
22
|
+
* // result.valid === false
|
|
23
|
+
* // result.hallucinated === [{ value: 18245, ... }]
|
|
24
|
+
* // result.confidence === 0.67
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function validateAgainstSource(text: string, sourceRows: Record<string, unknown>[], options?: DataValidationOptions): DataValidation;
|
|
28
|
+
//# sourceMappingURL=data-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-validator.d.ts","sourceRoot":"","sources":["../../src/services/data-validator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAiC,MAAM,mBAAmB,CAAA;AAoE7G;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACrC,OAAO,GAAE,qBAA0B,GAClC,cAAc,CAkDhB"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const DEFAULT_IGNORE_COLUMNS = /* @__PURE__ */ new Set(["id", "code_geo", "code_parent"]);
|
|
2
|
+
const DEFAULT_IGNORE_PATTERNS = [
|
|
3
|
+
/^20[012]\d$/,
|
|
4
|
+
// years 2000-2029
|
|
5
|
+
/^\d{5}$/,
|
|
6
|
+
// postal codes / INSEE codes
|
|
7
|
+
/^\d{1,2}$/
|
|
8
|
+
// indices, ranks (1-99)
|
|
9
|
+
];
|
|
10
|
+
function extractSourceNumbers(rows, ignoreColumns) {
|
|
11
|
+
const numbers = /* @__PURE__ */ new Set();
|
|
12
|
+
for (const row of rows) {
|
|
13
|
+
for (const [col, val] of Object.entries(row)) {
|
|
14
|
+
if (ignoreColumns.has(col)) continue;
|
|
15
|
+
if (typeof val === "number" && isFinite(val)) {
|
|
16
|
+
numbers.add(val);
|
|
17
|
+
} else if (typeof val === "string") {
|
|
18
|
+
const parsed = Number(val.replace(/\s/g, "").replace(",", "."));
|
|
19
|
+
if (!isNaN(parsed) && isFinite(parsed)) {
|
|
20
|
+
numbers.add(parsed);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return numbers;
|
|
26
|
+
}
|
|
27
|
+
function extractLLMNumbers(text, ignorePatterns) {
|
|
28
|
+
const numberRegex = /\d[\d\s,.]*\d|\d+/g;
|
|
29
|
+
const results = [];
|
|
30
|
+
let match;
|
|
31
|
+
while ((match = numberRegex.exec(text)) !== null) {
|
|
32
|
+
const raw = match[0];
|
|
33
|
+
const cleaned = raw.replace(/[\s.]/g, "").replace(",", ".");
|
|
34
|
+
const value = Number(cleaned);
|
|
35
|
+
if (isNaN(value) || !isFinite(value)) continue;
|
|
36
|
+
if (ignorePatterns.some((p) => p.test(raw.trim()))) continue;
|
|
37
|
+
results.push({
|
|
38
|
+
value,
|
|
39
|
+
position: match.index,
|
|
40
|
+
context: text.slice(
|
|
41
|
+
Math.max(0, match.index - 10),
|
|
42
|
+
match.index + raw.length + 10
|
|
43
|
+
)
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return results;
|
|
47
|
+
}
|
|
48
|
+
function validateAgainstSource(text, sourceRows, options = {}) {
|
|
49
|
+
const tolerance = options.tolerance ?? 0.01;
|
|
50
|
+
const ignoreColumns = new Set(options.ignoreColumns || [...DEFAULT_IGNORE_COLUMNS]);
|
|
51
|
+
const ignorePatterns = options.ignorePatterns || DEFAULT_IGNORE_PATTERNS;
|
|
52
|
+
const sourceNumbers = extractSourceNumbers(sourceRows, ignoreColumns);
|
|
53
|
+
const llmNumbers = extractLLMNumbers(text, ignorePatterns);
|
|
54
|
+
const hallucinated = [];
|
|
55
|
+
for (const num of llmNumbers) {
|
|
56
|
+
if (sourceNumbers.has(num.value)) continue;
|
|
57
|
+
let closest;
|
|
58
|
+
let minDistance = Infinity;
|
|
59
|
+
for (const src of sourceNumbers) {
|
|
60
|
+
const dist = Math.abs(num.value - src) / Math.max(Math.abs(src), 1);
|
|
61
|
+
if (dist < minDistance) {
|
|
62
|
+
minDistance = dist;
|
|
63
|
+
closest = src;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (minDistance <= tolerance) continue;
|
|
67
|
+
hallucinated.push({
|
|
68
|
+
...num,
|
|
69
|
+
closest,
|
|
70
|
+
distance: minDistance
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const confidence = llmNumbers.length > 0 ? 1 - hallucinated.length / llmNumbers.length : 1;
|
|
74
|
+
return {
|
|
75
|
+
valid: hallucinated.length === 0,
|
|
76
|
+
llmNumbers,
|
|
77
|
+
sourceNumbers,
|
|
78
|
+
hallucinated,
|
|
79
|
+
confidence
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export {
|
|
83
|
+
validateAgainstSource
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=data-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-validator.js","sources":["../../src/services/data-validator.ts"],"sourcesContent":["/**\n * Data Validator — anti-hallucination for LLM-generated text\n * v3.1.0: Pure regex-based number verification against source data\n *\n * Compares numbers in LLM text to numbers in source data rows.\n * Detects ~90% of numerical hallucinations with zero LLM cost, <1ms latency.\n */\n\nimport type { DataValidation, DataValidationOptions, LLMNumber, HallucinatedNumber } from '../types/chat-bus'\n\nconst DEFAULT_IGNORE_COLUMNS = new Set(['id', 'code_geo', 'code_parent'])\nconst DEFAULT_IGNORE_PATTERNS: RegExp[] = [\n /^20[012]\\d$/, // years 2000-2029\n /^\\d{5}$/, // postal codes / INSEE codes\n /^\\d{1,2}$/, // indices, ranks (1-99)\n]\n\n/**\n * Extract all numeric values from source data rows.\n * Handles both number types and string-encoded numbers (e.g. \"22 306\", \"3,337\").\n */\nfunction extractSourceNumbers(\n rows: Record<string, unknown>[],\n ignoreColumns: Set<string>\n): Set<number> {\n const numbers = new Set<number>()\n for (const row of rows) {\n for (const [col, val] of Object.entries(row)) {\n if (ignoreColumns.has(col)) continue\n if (typeof val === 'number' && isFinite(val)) {\n numbers.add(val)\n } else if (typeof val === 'string') {\n const parsed = Number(val.replace(/\\s/g, '').replace(',', '.'))\n if (!isNaN(parsed) && isFinite(parsed)) {\n numbers.add(parsed)\n }\n }\n }\n }\n return numbers\n}\n\n/**\n * Extract all numbers from LLM text.\n * Handles French/European formats: \"22 306\", \"3 337\", \"3,337\", \"22306\".\n */\nfunction extractLLMNumbers(\n text: string,\n ignorePatterns: RegExp[]\n): LLMNumber[] {\n const numberRegex = /\\d[\\d\\s,.]*\\d|\\d+/g\n const results: LLMNumber[] = []\n let match: RegExpExecArray | null\n\n while ((match = numberRegex.exec(text)) !== null) {\n const raw = match[0]\n // Normalize: remove spaces/dots (thousand separators), comma → decimal point\n const cleaned = raw.replace(/[\\s.]/g, '').replace(',', '.')\n const value = Number(cleaned)\n\n if (isNaN(value) || !isFinite(value)) continue\n if (ignorePatterns.some(p => p.test(raw.trim()))) continue\n\n results.push({\n value,\n position: match.index,\n context: text.slice(\n Math.max(0, match.index - 10),\n match.index + raw.length + 10\n ),\n })\n }\n\n return results\n}\n\n/**\n * Validate LLM-generated text against source data rows.\n *\n * Pure function — no IO, no LLM calls, no side effects.\n * Returns which numbers in the text are verified vs hallucinated.\n *\n * @example\n * ```typescript\n * const rows = [{ type: 'Appartement', ventes: 22306, prix_m2: 3337 }]\n * const result = validateAgainstSource(\n * \"On observe 22 306 ventes à 3 337 EUR/m². En 2023, 18 245 ventes.\",\n * rows\n * )\n * // result.valid === false\n * // result.hallucinated === [{ value: 18245, ... }]\n * // result.confidence === 0.67\n * ```\n */\nexport function validateAgainstSource(\n text: string,\n sourceRows: Record<string, unknown>[],\n options: DataValidationOptions = {}\n): DataValidation {\n const tolerance = options.tolerance ?? 0.01\n const ignoreColumns = new Set(options.ignoreColumns || [...DEFAULT_IGNORE_COLUMNS])\n const ignorePatterns = options.ignorePatterns || DEFAULT_IGNORE_PATTERNS\n\n // 1. Extract source numbers\n const sourceNumbers = extractSourceNumbers(sourceRows, ignoreColumns)\n\n // 2. Extract LLM numbers\n const llmNumbers = extractLLMNumbers(text, ignorePatterns)\n\n // 3. Check each LLM number against source\n const hallucinated: HallucinatedNumber[] = []\n\n for (const num of llmNumbers) {\n // Exact match\n if (sourceNumbers.has(num.value)) continue\n\n // Tolerance match (rounding)\n let closest: number | undefined\n let minDistance = Infinity\n\n for (const src of sourceNumbers) {\n const dist = Math.abs(num.value - src) / Math.max(Math.abs(src), 1)\n if (dist < minDistance) {\n minDistance = dist\n closest = src\n }\n }\n\n if (minDistance <= tolerance) continue // acceptable rounding\n\n hallucinated.push({\n ...num,\n closest,\n distance: minDistance,\n })\n }\n\n const confidence = llmNumbers.length > 0\n ? 1 - (hallucinated.length / llmNumbers.length)\n : 1\n\n return {\n valid: hallucinated.length === 0,\n llmNumbers,\n sourceNumbers,\n hallucinated,\n confidence,\n }\n}\n"],"names":[],"mappings":"AAUA,MAAM,yBAAyB,oBAAI,IAAI,CAAC,MAAM,YAAY,aAAa,CAAC;AACxE,MAAM,0BAAoC;AAAA,EACxC;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AAMA,SAAS,qBACP,MACA,eACa;AACb,QAAM,8BAAc,IAAA;AACpB,aAAW,OAAO,MAAM;AACtB,eAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC5C,UAAI,cAAc,IAAI,GAAG,EAAG;AAC5B,UAAI,OAAO,QAAQ,YAAY,SAAS,GAAG,GAAG;AAC5C,gBAAQ,IAAI,GAAG;AAAA,MACjB,WAAW,OAAO,QAAQ,UAAU;AAClC,cAAM,SAAS,OAAO,IAAI,QAAQ,OAAO,EAAE,EAAE,QAAQ,KAAK,GAAG,CAAC;AAC9D,YAAI,CAAC,MAAM,MAAM,KAAK,SAAS,MAAM,GAAG;AACtC,kBAAQ,IAAI,MAAM;AAAA,QACpB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAMA,SAAS,kBACP,MACA,gBACa;AACb,QAAM,cAAc;AACpB,QAAM,UAAuB,CAAA;AAC7B,MAAI;AAEJ,UAAQ,QAAQ,YAAY,KAAK,IAAI,OAAO,MAAM;AAChD,UAAM,MAAM,MAAM,CAAC;AAEnB,UAAM,UAAU,IAAI,QAAQ,UAAU,EAAE,EAAE,QAAQ,KAAK,GAAG;AAC1D,UAAM,QAAQ,OAAO,OAAO;AAE5B,QAAI,MAAM,KAAK,KAAK,CAAC,SAAS,KAAK,EAAG;AACtC,QAAI,eAAe,KAAK,CAAA,MAAK,EAAE,KAAK,IAAI,KAAA,CAAM,CAAC,EAAG;AAElD,YAAQ,KAAK;AAAA,MACX;AAAA,MACA,UAAU,MAAM;AAAA,MAChB,SAAS,KAAK;AAAA,QACZ,KAAK,IAAI,GAAG,MAAM,QAAQ,EAAE;AAAA,QAC5B,MAAM,QAAQ,IAAI,SAAS;AAAA,MAAA;AAAA,IAC7B,CACD;AAAA,EACH;AAEA,SAAO;AACT;AAoBO,SAAS,sBACd,MACA,YACA,UAAiC,CAAA,GACjB;AAChB,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,gBAAgB,IAAI,IAAI,QAAQ,iBAAiB,CAAC,GAAG,sBAAsB,CAAC;AAClF,QAAM,iBAAiB,QAAQ,kBAAkB;AAGjD,QAAM,gBAAgB,qBAAqB,YAAY,aAAa;AAGpE,QAAM,aAAa,kBAAkB,MAAM,cAAc;AAGzD,QAAM,eAAqC,CAAA;AAE3C,aAAW,OAAO,YAAY;AAE5B,QAAI,cAAc,IAAI,IAAI,KAAK,EAAG;AAGlC,QAAI;AACJ,QAAI,cAAc;AAElB,eAAW,OAAO,eAAe;AAC/B,YAAM,OAAO,KAAK,IAAI,IAAI,QAAQ,GAAG,IAAI,KAAK,IAAI,KAAK,IAAI,GAAG,GAAG,CAAC;AAClE,UAAI,OAAO,aAAa;AACtB,sBAAc;AACd,kBAAU;AAAA,MACZ;AAAA,IACF;AAEA,QAAI,eAAe,UAAW;AAE9B,iBAAa,KAAK;AAAA,MAChB,GAAG;AAAA,MACH;AAAA,MACA,UAAU;AAAA,IAAA,CACX;AAAA,EACH;AAEA,QAAM,aAAa,WAAW,SAAS,IACnC,IAAK,aAAa,SAAS,WAAW,SACtC;AAEJ,SAAO;AAAA,IACL,OAAO,aAAa,WAAW;AAAA,IAC/B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
|
package/dist/services/index.d.ts
CHANGED
|
@@ -6,4 +6,5 @@
|
|
|
6
6
|
export { validateComponent, validateLayout, validateIframeDomain, getIframeSandbox, DEFAULT_RESOURCE_LIMITS, DEFAULT_IFRAME_DOMAINS, TRUSTED_IFRAME_DOMAINS, } from './validation';
|
|
7
7
|
export { ComponentRegistry } from './component-registry';
|
|
8
8
|
export { createEventEmitter, createCommandHandler, createChatBus, mergeScratchpadSections } from './chat-bus';
|
|
9
|
+
export { validateAgainstSource } from './data-validator';
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|