@juspay/neurolink 12.51.3 → 12.51.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -2
- package/dist/browser/neurolink.min.js +408 -409
- package/dist/rag/chunkers/HTMLChunker.js +7 -4
- package/dist/rag/chunking/htmlChunker.js +19 -10
- package/dist/rag/document/loaders.js +20 -11
- package/dist/utils/htmlText.d.ts +45 -0
- package/dist/utils/htmlText.js +148 -0
- package/package.json +2 -1
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Splits HTML content by semantic tags.
|
|
5
5
|
*/
|
|
6
|
+
import { removeHtmlMarkup } from "../../utils/htmlText.js";
|
|
6
7
|
import { BaseChunker, DEFAULT_CHUNKER_CONFIG } from "./BaseChunker.js";
|
|
7
8
|
/**
|
|
8
9
|
* HTML Chunker
|
|
@@ -28,10 +29,12 @@ export class HTMLChunker extends BaseChunker {
|
|
|
28
29
|
* Strip HTML tags from content
|
|
29
30
|
*/
|
|
30
31
|
stripHtml(html) {
|
|
31
|
-
return html
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
return removeHtmlMarkup(html, {
|
|
33
|
+
tagReplacement: " ",
|
|
34
|
+
commentReplacement: " ",
|
|
35
|
+
lineBreakEndTags: [],
|
|
36
|
+
lineBreakVoidTags: [],
|
|
37
|
+
})
|
|
35
38
|
.replace(/\s+/g, " ")
|
|
36
39
|
.trim();
|
|
37
40
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { randomUUID } from "crypto";
|
|
8
8
|
import { decodeHtmlTextEntitiesOnce } from "../../utils/htmlEntities.js";
|
|
9
|
+
import { removeHtmlMarkup } from "../../utils/htmlText.js";
|
|
9
10
|
/**
|
|
10
11
|
* HTML-aware chunker implementation
|
|
11
12
|
* Splits based on HTML structure (tags, elements)
|
|
@@ -171,16 +172,24 @@ export class HTMLChunker {
|
|
|
171
172
|
* Extract plain text from HTML
|
|
172
173
|
*/
|
|
173
174
|
extractText(html) {
|
|
174
|
-
return decodeHtmlTextEntitiesOnce(html
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
175
|
+
return decodeHtmlTextEntitiesOnce(removeHtmlMarkup(html, {
|
|
176
|
+
tagReplacement: "",
|
|
177
|
+
commentReplacement: "",
|
|
178
|
+
lineBreakEndTags: [
|
|
179
|
+
"p",
|
|
180
|
+
"div",
|
|
181
|
+
"br",
|
|
182
|
+
"h1",
|
|
183
|
+
"h2",
|
|
184
|
+
"h3",
|
|
185
|
+
"h4",
|
|
186
|
+
"h5",
|
|
187
|
+
"h6",
|
|
188
|
+
"li",
|
|
189
|
+
"tr",
|
|
190
|
+
],
|
|
191
|
+
lineBreakVoidTags: [],
|
|
192
|
+
}), { nbsp: " ", amp: "&", lt: "<", gt: ">", quot: '"', "#039": "'" })
|
|
184
193
|
.replace(/\s+/g, " ")
|
|
185
194
|
.trim();
|
|
186
195
|
}
|
|
@@ -28,6 +28,7 @@ import { readFile } from "fs/promises";
|
|
|
28
28
|
import { basename, extname } from "path";
|
|
29
29
|
import { logger } from "../../utils/logger.js";
|
|
30
30
|
import { decodeHtmlTextEntitiesOnce } from "../../utils/htmlEntities.js";
|
|
31
|
+
import { removeHtmlMarkup } from "../../utils/htmlText.js";
|
|
31
32
|
import { CSVProcessor, stripBom, stripMetadataLine, splitCsvLines, dedupeColumnNames, sanitizeColumnName, } from "../../utils/csvProcessor.js";
|
|
32
33
|
import { splitCSVFields } from "../../utils/csvUtils.js";
|
|
33
34
|
import { MDocument } from "./MDocument.js";
|
|
@@ -429,17 +430,25 @@ export class WebLoader {
|
|
|
429
430
|
* Convert HTML to plain text
|
|
430
431
|
*/
|
|
431
432
|
htmlToText(html) {
|
|
432
|
-
return decodeHtmlTextEntitiesOnce(html
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
433
|
+
return decodeHtmlTextEntitiesOnce(removeHtmlMarkup(html, {
|
|
434
|
+
tagReplacement: "",
|
|
435
|
+
commentReplacement: "",
|
|
436
|
+
lineBreakEndTags: [
|
|
437
|
+
"p",
|
|
438
|
+
"div",
|
|
439
|
+
"h1",
|
|
440
|
+
"h2",
|
|
441
|
+
"h3",
|
|
442
|
+
"h4",
|
|
443
|
+
"h5",
|
|
444
|
+
"h6",
|
|
445
|
+
"br",
|
|
446
|
+
"li",
|
|
447
|
+
"tr",
|
|
448
|
+
"blockquote",
|
|
449
|
+
],
|
|
450
|
+
lineBreakVoidTags: ["br", "hr"],
|
|
451
|
+
}), {
|
|
443
452
|
nbsp: " ",
|
|
444
453
|
amp: "&",
|
|
445
454
|
lt: "<",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-pass markup removal for turning HTML into indexable text.
|
|
3
|
+
*
|
|
4
|
+
* This is text conversion, not output sanitization. The result is plain text
|
|
5
|
+
* for retrieval and chunking; it is not meant to be written back into a page,
|
|
6
|
+
* and the caller's entity decoding can legitimately turn `<b>` into
|
|
7
|
+
* `<b>`.
|
|
8
|
+
*
|
|
9
|
+
* Why one pass: the previous implementation chained several global regex
|
|
10
|
+
* replacements (script, style, comment, block end, generic tag). Each removal
|
|
11
|
+
* could join the text on either side of it into a new tag for the next
|
|
12
|
+
* replacement (`<scr<!---->ipt>`), and the lazy `[\s\S]*?` bodies rescanned
|
|
13
|
+
* the rest of the input once per unclosed `<script`, which is quadratic.
|
|
14
|
+
* Here the input is read left to right exactly once. Every character of the
|
|
15
|
+
* output is copied from an unremoved stretch of the input, a removed stretch
|
|
16
|
+
* is never re-read, and every search either consumes what it scans or is
|
|
17
|
+
* answered in constant time from an index computed up front, so the work is
|
|
18
|
+
* linear in the input length.
|
|
19
|
+
*
|
|
20
|
+
* Invariant: the output never matches `<[^>]+>`. A `<` survives only when no
|
|
21
|
+
* `>` follows it in the input, or when the next character is the `>` itself
|
|
22
|
+
* (`<>`, which is not a tag). A removed span therefore cannot leave behind,
|
|
23
|
+
* or assemble, a tag.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Remove HTML markup from `html` in one linear pass.
|
|
27
|
+
*
|
|
28
|
+
* - `<script>` / `<style>` elements are removed with their bodies. The end
|
|
29
|
+
* tag may carry whitespace or attributes (`</script >`). An element with
|
|
30
|
+
* no usable end tag loses only its start tag, as before.
|
|
31
|
+
* - `<!-- ... -->` is replaced with `commentReplacement`. An unterminated
|
|
32
|
+
* comment is treated like any other tag and ends at the next `>`.
|
|
33
|
+
* - A closing tag named in `lineBreakEndTags` and a void tag named in
|
|
34
|
+
* `lineBreakVoidTags` (`<br>`, `<hr />`) become a newline.
|
|
35
|
+
* - Any other `<...>` becomes `tagReplacement`. A `<` with no `>` after it,
|
|
36
|
+
* and the empty `<>`, are literal text and are kept.
|
|
37
|
+
*
|
|
38
|
+
* Tag-name lists are lower case.
|
|
39
|
+
*/
|
|
40
|
+
export declare function removeHtmlMarkup(html: string, options: {
|
|
41
|
+
readonly tagReplacement: string;
|
|
42
|
+
readonly commentReplacement: string;
|
|
43
|
+
readonly lineBreakEndTags: readonly string[];
|
|
44
|
+
readonly lineBreakVoidTags: readonly string[];
|
|
45
|
+
}): string;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-pass markup removal for turning HTML into indexable text.
|
|
3
|
+
*
|
|
4
|
+
* This is text conversion, not output sanitization. The result is plain text
|
|
5
|
+
* for retrieval and chunking; it is not meant to be written back into a page,
|
|
6
|
+
* and the caller's entity decoding can legitimately turn `<b>` into
|
|
7
|
+
* `<b>`.
|
|
8
|
+
*
|
|
9
|
+
* Why one pass: the previous implementation chained several global regex
|
|
10
|
+
* replacements (script, style, comment, block end, generic tag). Each removal
|
|
11
|
+
* could join the text on either side of it into a new tag for the next
|
|
12
|
+
* replacement (`<scr<!---->ipt>`), and the lazy `[\s\S]*?` bodies rescanned
|
|
13
|
+
* the rest of the input once per unclosed `<script`, which is quadratic.
|
|
14
|
+
* Here the input is read left to right exactly once. Every character of the
|
|
15
|
+
* output is copied from an unremoved stretch of the input, a removed stretch
|
|
16
|
+
* is never re-read, and every search either consumes what it scans or is
|
|
17
|
+
* answered in constant time from an index computed up front, so the work is
|
|
18
|
+
* linear in the input length.
|
|
19
|
+
*
|
|
20
|
+
* Invariant: the output never matches `<[^>]+>`. A `<` survives only when no
|
|
21
|
+
* `>` follows it in the input, or when the next character is the `>` itself
|
|
22
|
+
* (`<>`, which is not a tag). A removed span therefore cannot leave behind,
|
|
23
|
+
* or assemble, a tag.
|
|
24
|
+
*/
|
|
25
|
+
const SLASH = 0x2f;
|
|
26
|
+
const GREATER_THAN = 0x3e;
|
|
27
|
+
const WHITESPACE = /\s/;
|
|
28
|
+
function isAsciiAlphanumeric(code) {
|
|
29
|
+
return ((code >= 0x30 && code <= 0x39) ||
|
|
30
|
+
(code >= 0x41 && code <= 0x5a) ||
|
|
31
|
+
(code >= 0x61 && code <= 0x7a));
|
|
32
|
+
}
|
|
33
|
+
// The characters after which an HTML tokenizer ends a tag name.
|
|
34
|
+
function endsTagName(code) {
|
|
35
|
+
return (code === 0x09 ||
|
|
36
|
+
code === 0x0a ||
|
|
37
|
+
code === 0x0c ||
|
|
38
|
+
code === 0x0d ||
|
|
39
|
+
code === 0x20 ||
|
|
40
|
+
code === SLASH ||
|
|
41
|
+
code === GREATER_THAN);
|
|
42
|
+
}
|
|
43
|
+
function scanTagName(html, from) {
|
|
44
|
+
let end = from;
|
|
45
|
+
while (end < html.length && isAsciiAlphanumeric(html.charCodeAt(end))) {
|
|
46
|
+
end++;
|
|
47
|
+
}
|
|
48
|
+
return end;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Index of the next `</name` that is followed by whitespace, `/` or `>`
|
|
52
|
+
* (so `</script >` and `</SCRIPT foo>` count), or -1. Case-insensitive.
|
|
53
|
+
*/
|
|
54
|
+
function findEndTag(html, name, from) {
|
|
55
|
+
let at = html.indexOf("</", from);
|
|
56
|
+
while (at !== -1) {
|
|
57
|
+
const nameEnd = at + 2 + name.length;
|
|
58
|
+
if (html.slice(at + 2, nameEnd).toLowerCase() === name &&
|
|
59
|
+
endsTagName(html.charCodeAt(nameEnd))) {
|
|
60
|
+
return at;
|
|
61
|
+
}
|
|
62
|
+
at = html.indexOf("</", at + 2);
|
|
63
|
+
}
|
|
64
|
+
return -1;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Remove HTML markup from `html` in one linear pass.
|
|
68
|
+
*
|
|
69
|
+
* - `<script>` / `<style>` elements are removed with their bodies. The end
|
|
70
|
+
* tag may carry whitespace or attributes (`</script >`). An element with
|
|
71
|
+
* no usable end tag loses only its start tag, as before.
|
|
72
|
+
* - `<!-- ... -->` is replaced with `commentReplacement`. An unterminated
|
|
73
|
+
* comment is treated like any other tag and ends at the next `>`.
|
|
74
|
+
* - A closing tag named in `lineBreakEndTags` and a void tag named in
|
|
75
|
+
* `lineBreakVoidTags` (`<br>`, `<hr />`) become a newline.
|
|
76
|
+
* - Any other `<...>` becomes `tagReplacement`. A `<` with no `>` after it,
|
|
77
|
+
* and the empty `<>`, are literal text and are kept.
|
|
78
|
+
*
|
|
79
|
+
* Tag-name lists are lower case.
|
|
80
|
+
*/
|
|
81
|
+
export function removeHtmlMarkup(html, options) {
|
|
82
|
+
const { tagReplacement, commentReplacement } = options;
|
|
83
|
+
const lineBreakEndTags = new Set(options.lineBreakEndTags);
|
|
84
|
+
const lineBreakVoidTags = new Set(options.lineBreakVoidTags);
|
|
85
|
+
// Answers "is there any `>` / `-->` after here" without a scan. Once an
|
|
86
|
+
// element finds no usable end tag, no later element of that name can.
|
|
87
|
+
const lastGreaterThan = html.lastIndexOf(">");
|
|
88
|
+
const lastCommentEnd = html.lastIndexOf("-->");
|
|
89
|
+
const withoutEndTag = new Set();
|
|
90
|
+
const markupAt = (at) => {
|
|
91
|
+
if (html.startsWith("<!--", at) && lastCommentEnd >= at + 4) {
|
|
92
|
+
return {
|
|
93
|
+
end: html.indexOf("-->", at + 4) + 3,
|
|
94
|
+
replacement: commentReplacement,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const closing = html.charCodeAt(at + 1) === SLASH;
|
|
98
|
+
const nameStart = closing ? at + 2 : at + 1;
|
|
99
|
+
const nameEnd = scanTagName(html, nameStart);
|
|
100
|
+
const name = html.slice(nameStart, nameEnd).toLowerCase();
|
|
101
|
+
if (!closing &&
|
|
102
|
+
(name === "script" || name === "style") &&
|
|
103
|
+
endsTagName(html.charCodeAt(nameEnd)) &&
|
|
104
|
+
!withoutEndTag.has(name)) {
|
|
105
|
+
const startTagEnd = html.indexOf(">", nameEnd);
|
|
106
|
+
const endTag = findEndTag(html, name, startTagEnd + 1);
|
|
107
|
+
if (endTag !== -1 && endTag < lastGreaterThan) {
|
|
108
|
+
return { end: html.indexOf(">", endTag) + 1, replacement: "" };
|
|
109
|
+
}
|
|
110
|
+
withoutEndTag.add(name);
|
|
111
|
+
}
|
|
112
|
+
if (closing &&
|
|
113
|
+
html.charCodeAt(nameEnd) === GREATER_THAN &&
|
|
114
|
+
lineBreakEndTags.has(name)) {
|
|
115
|
+
return { end: nameEnd + 1, replacement: "\n" };
|
|
116
|
+
}
|
|
117
|
+
if (!closing && lineBreakVoidTags.has(name)) {
|
|
118
|
+
let probe = nameEnd;
|
|
119
|
+
while (WHITESPACE.test(html.charAt(probe))) {
|
|
120
|
+
probe++;
|
|
121
|
+
}
|
|
122
|
+
if (html.charCodeAt(probe) === SLASH) {
|
|
123
|
+
probe++;
|
|
124
|
+
}
|
|
125
|
+
if (html.charCodeAt(probe) === GREATER_THAN) {
|
|
126
|
+
return { end: probe + 1, replacement: "\n" };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const end = html.indexOf(">", at + 1);
|
|
130
|
+
return end === at + 1
|
|
131
|
+
? null
|
|
132
|
+
: { end: end + 1, replacement: tagReplacement };
|
|
133
|
+
};
|
|
134
|
+
let output = "";
|
|
135
|
+
let copied = 0;
|
|
136
|
+
let open = html.indexOf("<");
|
|
137
|
+
while (open !== -1 && open < lastGreaterThan) {
|
|
138
|
+
const markup = markupAt(open);
|
|
139
|
+
if (markup === null) {
|
|
140
|
+
open = html.indexOf("<", open + 1);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
output += html.slice(copied, open) + markup.replacement;
|
|
144
|
+
copied = markup.end;
|
|
145
|
+
open = html.indexOf("<", copied);
|
|
146
|
+
}
|
|
147
|
+
return output + html.slice(copied);
|
|
148
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.51.
|
|
3
|
+
"version": "12.51.4",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · TypeSafe Jev and more.",
|
|
6
6
|
"author": {
|
|
@@ -164,6 +164,7 @@
|
|
|
164
164
|
"test:embedding-response-validation": "pnpm exec tsx test/continuous-test-suite-embedding-response-validation.ts",
|
|
165
165
|
"test:pdf-image-streaming": "pnpm exec tsx test/continuous-test-suite-pdf-image-streaming.ts",
|
|
166
166
|
"test:rag": "pnpm exec tsx test/continuous-test-suite-rag.ts",
|
|
167
|
+
"test:rag-html-markup": "pnpm exec tsx test/continuous-test-suite-rag-html-markup.ts",
|
|
167
168
|
"test:skills": "pnpm exec tsx test/continuous-test-suite-skills.ts",
|
|
168
169
|
"test:decide": "pnpm exec tsx test/continuous-test-suite-decide.ts",
|
|
169
170
|
"test:decide-catalog": "pnpm exec tsx test/continuous-test-suite-decide-catalog.ts",
|