@juspay/neurolink 9.88.3 → 9.88.5
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 +12 -0
- package/dist/browser/neurolink.min.js +311 -311
- package/dist/lib/processors/document/PptxProcessor.d.ts +14 -0
- package/dist/lib/processors/document/PptxProcessor.js +60 -2
- package/dist/lib/utils/fileDetector.js +23 -3
- package/dist/processors/document/PptxProcessor.d.ts +14 -0
- package/dist/processors/document/PptxProcessor.js +60 -2
- package/dist/utils/fileDetector.js +23 -3
- package/package.json +1 -1
|
@@ -42,6 +42,20 @@ export declare class PptxProcessor {
|
|
|
42
42
|
* @throws Error if the buffer is not a valid ZIP/PPTX file
|
|
43
43
|
*/
|
|
44
44
|
static extractText(content: Buffer): Promise<string | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Extract the speaker notes for one slide.
|
|
47
|
+
*
|
|
48
|
+
* Speaker notes live in a separate `ppt/notesSlides/notesSlideN.xml` part
|
|
49
|
+
* whose number is NOT tied to the slide number — the link is declared in the
|
|
50
|
+
* slide's own relationships file (`ppt/slides/_rels/slideN.xml.rels`) via a
|
|
51
|
+
* relationship of type `.../notesSlide`. We resolve that target, then pull the
|
|
52
|
+
* `<a:t>` runs from the notes part. Returns null when the slide has no notes.
|
|
53
|
+
*
|
|
54
|
+
* @param zip - The opened PPTX archive
|
|
55
|
+
* @param slideNumber - 1-indexed slide number
|
|
56
|
+
* @returns The notes text (runs joined by a space), or null when absent
|
|
57
|
+
*/
|
|
58
|
+
private static extractNotesForSlide;
|
|
45
59
|
/**
|
|
46
60
|
* Extract text strings from a slide XML document.
|
|
47
61
|
* Finds all `<a:t>` elements and returns their text content.
|
|
@@ -37,6 +37,14 @@ const TEXT_ELEMENT_REGEX = /<a:t[^>]*>([\s\S]*?)<\/a:t>/g;
|
|
|
37
37
|
* Matches entries like "ppt/slides/slide1.xml", "ppt/slides/slide12.xml".
|
|
38
38
|
*/
|
|
39
39
|
const SLIDE_ENTRY_REGEX = /^ppt\/slides\/slide(\d+)\.xml$/;
|
|
40
|
+
/**
|
|
41
|
+
* A slide's per-slide relationships file, e.g.
|
|
42
|
+
* "ppt/slides/_rels/slide1.xml.rels", which links the slide to its speaker
|
|
43
|
+
* notes part (among other relationships).
|
|
44
|
+
*/
|
|
45
|
+
const SLIDE_RELS_NAME = (n) => `ppt/slides/_rels/slide${n}.xml.rels`;
|
|
46
|
+
/** Matches a single `<Relationship .../>` tag inside a .rels document. */
|
|
47
|
+
const RELATIONSHIP_TAG_REGEX = /<Relationship\b[^>]*\/?>/g;
|
|
40
48
|
/**
|
|
41
49
|
* Static utility class for extracting text from PPTX files.
|
|
42
50
|
*
|
|
@@ -74,15 +82,65 @@ export class PptxProcessor {
|
|
|
74
82
|
parts.push(`Presentation: ${slides.length} slide(s)\n`);
|
|
75
83
|
for (const slide of slides) {
|
|
76
84
|
const texts = PptxProcessor.extractTextFromXml(slide.xml);
|
|
77
|
-
|
|
85
|
+
const notes = PptxProcessor.extractNotesForSlide(zip, slide.slideNumber);
|
|
86
|
+
// Emit a slide section when it has either body text or speaker notes.
|
|
87
|
+
if (texts.length > 0 || notes) {
|
|
78
88
|
parts.push(`### Slide ${slide.slideNumber}`);
|
|
79
|
-
|
|
89
|
+
if (texts.length > 0) {
|
|
90
|
+
parts.push(texts.join("\n"));
|
|
91
|
+
}
|
|
92
|
+
if (notes) {
|
|
93
|
+
parts.push(`**Speaker notes:** ${notes}`);
|
|
94
|
+
}
|
|
80
95
|
parts.push(""); // blank line between slides
|
|
81
96
|
}
|
|
82
97
|
}
|
|
83
98
|
const result = parts.join("\n").trim();
|
|
84
99
|
return result || null;
|
|
85
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Extract the speaker notes for one slide.
|
|
103
|
+
*
|
|
104
|
+
* Speaker notes live in a separate `ppt/notesSlides/notesSlideN.xml` part
|
|
105
|
+
* whose number is NOT tied to the slide number — the link is declared in the
|
|
106
|
+
* slide's own relationships file (`ppt/slides/_rels/slideN.xml.rels`) via a
|
|
107
|
+
* relationship of type `.../notesSlide`. We resolve that target, then pull the
|
|
108
|
+
* `<a:t>` runs from the notes part. Returns null when the slide has no notes.
|
|
109
|
+
*
|
|
110
|
+
* @param zip - The opened PPTX archive
|
|
111
|
+
* @param slideNumber - 1-indexed slide number
|
|
112
|
+
* @returns The notes text (runs joined by a space), or null when absent
|
|
113
|
+
*/
|
|
114
|
+
static extractNotesForSlide(zip, slideNumber) {
|
|
115
|
+
const relsEntry = zip.getEntry(SLIDE_RELS_NAME(slideNumber));
|
|
116
|
+
if (!relsEntry) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const relsXml = relsEntry.getData().toString("utf-8");
|
|
120
|
+
let notesTarget = null;
|
|
121
|
+
RELATIONSHIP_TAG_REGEX.lastIndex = 0;
|
|
122
|
+
for (let match = RELATIONSHIP_TAG_REGEX.exec(relsXml); match !== null; match = RELATIONSHIP_TAG_REGEX.exec(relsXml)) {
|
|
123
|
+
const tag = match[0];
|
|
124
|
+
if (/Type="[^"]*\/notesSlide"/.test(tag)) {
|
|
125
|
+
notesTarget = tag.match(/Target="([^"]+)"/)?.[1] ?? null;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!notesTarget) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
// Targets are relative to the slide's folder (ppt/slides/), e.g.
|
|
133
|
+
// "../notesSlides/notesSlide1.xml" → "ppt/notesSlides/notesSlide1.xml".
|
|
134
|
+
const normalized = notesTarget
|
|
135
|
+
.replace(/^\.\.\//, "ppt/")
|
|
136
|
+
.replace(/^\/+/, "");
|
|
137
|
+
const notesEntry = zip.getEntry(normalized);
|
|
138
|
+
if (!notesEntry) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const notes = PptxProcessor.extractTextFromXml(notesEntry.getData().toString("utf-8")).join(" ");
|
|
142
|
+
return notes.trim() || null;
|
|
143
|
+
}
|
|
86
144
|
/**
|
|
87
145
|
* Extract text strings from a slide XML document.
|
|
88
146
|
* Finds all `<a:t>` elements and returns their text content.
|
|
@@ -1441,20 +1441,34 @@ class MagicBytesStrategy {
|
|
|
1441
1441
|
if (this.isPDF(input)) {
|
|
1442
1442
|
return this.result("pdf", "application/pdf", 95);
|
|
1443
1443
|
}
|
|
1444
|
-
//
|
|
1444
|
+
// ISO-BMFF ("ftyp" at offset 4): MP4 video, QuickTime MOV, or M4A/M4B/M4P
|
|
1445
|
+
// audio all share this box — disambiguate by the major brand at offset 8-11,
|
|
1446
|
+
// otherwise an .m4a audio file is misrouted to the video pipeline.
|
|
1445
1447
|
if (input.length >= 8 &&
|
|
1446
1448
|
input[4] === 0x66 &&
|
|
1447
1449
|
input[5] === 0x74 &&
|
|
1448
1450
|
input[6] === 0x79 &&
|
|
1449
1451
|
input[7] === 0x70) {
|
|
1452
|
+
const brand = input.length >= 12 ? input.toString("latin1", 8, 12) : "";
|
|
1453
|
+
if (/^(M4A|M4B|M4P|F4A|F4B)/.test(brand)) {
|
|
1454
|
+
return this.result("audio", "audio/mp4", 95);
|
|
1455
|
+
}
|
|
1456
|
+
if (brand.startsWith("qt")) {
|
|
1457
|
+
return this.result("video", "video/quicktime", 95);
|
|
1458
|
+
}
|
|
1450
1459
|
return this.result("video", "video/mp4", 95);
|
|
1451
1460
|
}
|
|
1452
|
-
// MKV/WebM
|
|
1461
|
+
// EBML container (MKV/WebM) — both share the 0x1A45DFA3 header; the DocType
|
|
1462
|
+
// string in the header disambiguates WebM from generic Matroska.
|
|
1453
1463
|
if (input.length >= 4 &&
|
|
1454
1464
|
input[0] === 0x1a &&
|
|
1455
1465
|
input[1] === 0x45 &&
|
|
1456
1466
|
input[2] === 0xdf &&
|
|
1457
1467
|
input[3] === 0xa3) {
|
|
1468
|
+
const head = input.toString("latin1", 0, Math.min(input.length, 64));
|
|
1469
|
+
if (head.includes("webm")) {
|
|
1470
|
+
return this.result("video", "video/webm", 92);
|
|
1471
|
+
}
|
|
1458
1472
|
return this.result("video", "video/x-matroska", 90);
|
|
1459
1473
|
}
|
|
1460
1474
|
// AVI: "RIFF" + "AVI "
|
|
@@ -1488,7 +1502,13 @@ class MagicBytesStrategy {
|
|
|
1488
1502
|
input[2] === 0x33) {
|
|
1489
1503
|
return this.result("audio", "audio/mpeg", 95);
|
|
1490
1504
|
}
|
|
1491
|
-
//
|
|
1505
|
+
// AAC (ADTS): 12-bit syncword 0xFFF with the 2 layer bits == 00. This must be
|
|
1506
|
+
// checked before the MP3 sync word below, because an ADTS header also satisfies
|
|
1507
|
+
// the looser 11-bit MPEG sync mask and would otherwise be mislabeled audio/mpeg.
|
|
1508
|
+
if (input.length >= 2 && input[0] === 0xff && (input[1] & 0xf6) === 0xf0) {
|
|
1509
|
+
return this.result("audio", "audio/aac", 85);
|
|
1510
|
+
}
|
|
1511
|
+
// MP3: sync word (MPEG audio — layer bits are non-zero, unlike ADTS AAC above)
|
|
1492
1512
|
if (input.length >= 2 && input[0] === 0xff && (input[1] & 0xe0) === 0xe0) {
|
|
1493
1513
|
return this.result("audio", "audio/mpeg", 80);
|
|
1494
1514
|
}
|
|
@@ -42,6 +42,20 @@ export declare class PptxProcessor {
|
|
|
42
42
|
* @throws Error if the buffer is not a valid ZIP/PPTX file
|
|
43
43
|
*/
|
|
44
44
|
static extractText(content: Buffer): Promise<string | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Extract the speaker notes for one slide.
|
|
47
|
+
*
|
|
48
|
+
* Speaker notes live in a separate `ppt/notesSlides/notesSlideN.xml` part
|
|
49
|
+
* whose number is NOT tied to the slide number — the link is declared in the
|
|
50
|
+
* slide's own relationships file (`ppt/slides/_rels/slideN.xml.rels`) via a
|
|
51
|
+
* relationship of type `.../notesSlide`. We resolve that target, then pull the
|
|
52
|
+
* `<a:t>` runs from the notes part. Returns null when the slide has no notes.
|
|
53
|
+
*
|
|
54
|
+
* @param zip - The opened PPTX archive
|
|
55
|
+
* @param slideNumber - 1-indexed slide number
|
|
56
|
+
* @returns The notes text (runs joined by a space), or null when absent
|
|
57
|
+
*/
|
|
58
|
+
private static extractNotesForSlide;
|
|
45
59
|
/**
|
|
46
60
|
* Extract text strings from a slide XML document.
|
|
47
61
|
* Finds all `<a:t>` elements and returns their text content.
|
|
@@ -37,6 +37,14 @@ const TEXT_ELEMENT_REGEX = /<a:t[^>]*>([\s\S]*?)<\/a:t>/g;
|
|
|
37
37
|
* Matches entries like "ppt/slides/slide1.xml", "ppt/slides/slide12.xml".
|
|
38
38
|
*/
|
|
39
39
|
const SLIDE_ENTRY_REGEX = /^ppt\/slides\/slide(\d+)\.xml$/;
|
|
40
|
+
/**
|
|
41
|
+
* A slide's per-slide relationships file, e.g.
|
|
42
|
+
* "ppt/slides/_rels/slide1.xml.rels", which links the slide to its speaker
|
|
43
|
+
* notes part (among other relationships).
|
|
44
|
+
*/
|
|
45
|
+
const SLIDE_RELS_NAME = (n) => `ppt/slides/_rels/slide${n}.xml.rels`;
|
|
46
|
+
/** Matches a single `<Relationship .../>` tag inside a .rels document. */
|
|
47
|
+
const RELATIONSHIP_TAG_REGEX = /<Relationship\b[^>]*\/?>/g;
|
|
40
48
|
/**
|
|
41
49
|
* Static utility class for extracting text from PPTX files.
|
|
42
50
|
*
|
|
@@ -74,15 +82,65 @@ export class PptxProcessor {
|
|
|
74
82
|
parts.push(`Presentation: ${slides.length} slide(s)\n`);
|
|
75
83
|
for (const slide of slides) {
|
|
76
84
|
const texts = PptxProcessor.extractTextFromXml(slide.xml);
|
|
77
|
-
|
|
85
|
+
const notes = PptxProcessor.extractNotesForSlide(zip, slide.slideNumber);
|
|
86
|
+
// Emit a slide section when it has either body text or speaker notes.
|
|
87
|
+
if (texts.length > 0 || notes) {
|
|
78
88
|
parts.push(`### Slide ${slide.slideNumber}`);
|
|
79
|
-
|
|
89
|
+
if (texts.length > 0) {
|
|
90
|
+
parts.push(texts.join("\n"));
|
|
91
|
+
}
|
|
92
|
+
if (notes) {
|
|
93
|
+
parts.push(`**Speaker notes:** ${notes}`);
|
|
94
|
+
}
|
|
80
95
|
parts.push(""); // blank line between slides
|
|
81
96
|
}
|
|
82
97
|
}
|
|
83
98
|
const result = parts.join("\n").trim();
|
|
84
99
|
return result || null;
|
|
85
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Extract the speaker notes for one slide.
|
|
103
|
+
*
|
|
104
|
+
* Speaker notes live in a separate `ppt/notesSlides/notesSlideN.xml` part
|
|
105
|
+
* whose number is NOT tied to the slide number — the link is declared in the
|
|
106
|
+
* slide's own relationships file (`ppt/slides/_rels/slideN.xml.rels`) via a
|
|
107
|
+
* relationship of type `.../notesSlide`. We resolve that target, then pull the
|
|
108
|
+
* `<a:t>` runs from the notes part. Returns null when the slide has no notes.
|
|
109
|
+
*
|
|
110
|
+
* @param zip - The opened PPTX archive
|
|
111
|
+
* @param slideNumber - 1-indexed slide number
|
|
112
|
+
* @returns The notes text (runs joined by a space), or null when absent
|
|
113
|
+
*/
|
|
114
|
+
static extractNotesForSlide(zip, slideNumber) {
|
|
115
|
+
const relsEntry = zip.getEntry(SLIDE_RELS_NAME(slideNumber));
|
|
116
|
+
if (!relsEntry) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const relsXml = relsEntry.getData().toString("utf-8");
|
|
120
|
+
let notesTarget = null;
|
|
121
|
+
RELATIONSHIP_TAG_REGEX.lastIndex = 0;
|
|
122
|
+
for (let match = RELATIONSHIP_TAG_REGEX.exec(relsXml); match !== null; match = RELATIONSHIP_TAG_REGEX.exec(relsXml)) {
|
|
123
|
+
const tag = match[0];
|
|
124
|
+
if (/Type="[^"]*\/notesSlide"/.test(tag)) {
|
|
125
|
+
notesTarget = tag.match(/Target="([^"]+)"/)?.[1] ?? null;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!notesTarget) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
// Targets are relative to the slide's folder (ppt/slides/), e.g.
|
|
133
|
+
// "../notesSlides/notesSlide1.xml" → "ppt/notesSlides/notesSlide1.xml".
|
|
134
|
+
const normalized = notesTarget
|
|
135
|
+
.replace(/^\.\.\//, "ppt/")
|
|
136
|
+
.replace(/^\/+/, "");
|
|
137
|
+
const notesEntry = zip.getEntry(normalized);
|
|
138
|
+
if (!notesEntry) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const notes = PptxProcessor.extractTextFromXml(notesEntry.getData().toString("utf-8")).join(" ");
|
|
142
|
+
return notes.trim() || null;
|
|
143
|
+
}
|
|
86
144
|
/**
|
|
87
145
|
* Extract text strings from a slide XML document.
|
|
88
146
|
* Finds all `<a:t>` elements and returns their text content.
|
|
@@ -1441,20 +1441,34 @@ class MagicBytesStrategy {
|
|
|
1441
1441
|
if (this.isPDF(input)) {
|
|
1442
1442
|
return this.result("pdf", "application/pdf", 95);
|
|
1443
1443
|
}
|
|
1444
|
-
//
|
|
1444
|
+
// ISO-BMFF ("ftyp" at offset 4): MP4 video, QuickTime MOV, or M4A/M4B/M4P
|
|
1445
|
+
// audio all share this box — disambiguate by the major brand at offset 8-11,
|
|
1446
|
+
// otherwise an .m4a audio file is misrouted to the video pipeline.
|
|
1445
1447
|
if (input.length >= 8 &&
|
|
1446
1448
|
input[4] === 0x66 &&
|
|
1447
1449
|
input[5] === 0x74 &&
|
|
1448
1450
|
input[6] === 0x79 &&
|
|
1449
1451
|
input[7] === 0x70) {
|
|
1452
|
+
const brand = input.length >= 12 ? input.toString("latin1", 8, 12) : "";
|
|
1453
|
+
if (/^(M4A|M4B|M4P|F4A|F4B)/.test(brand)) {
|
|
1454
|
+
return this.result("audio", "audio/mp4", 95);
|
|
1455
|
+
}
|
|
1456
|
+
if (brand.startsWith("qt")) {
|
|
1457
|
+
return this.result("video", "video/quicktime", 95);
|
|
1458
|
+
}
|
|
1450
1459
|
return this.result("video", "video/mp4", 95);
|
|
1451
1460
|
}
|
|
1452
|
-
// MKV/WebM
|
|
1461
|
+
// EBML container (MKV/WebM) — both share the 0x1A45DFA3 header; the DocType
|
|
1462
|
+
// string in the header disambiguates WebM from generic Matroska.
|
|
1453
1463
|
if (input.length >= 4 &&
|
|
1454
1464
|
input[0] === 0x1a &&
|
|
1455
1465
|
input[1] === 0x45 &&
|
|
1456
1466
|
input[2] === 0xdf &&
|
|
1457
1467
|
input[3] === 0xa3) {
|
|
1468
|
+
const head = input.toString("latin1", 0, Math.min(input.length, 64));
|
|
1469
|
+
if (head.includes("webm")) {
|
|
1470
|
+
return this.result("video", "video/webm", 92);
|
|
1471
|
+
}
|
|
1458
1472
|
return this.result("video", "video/x-matroska", 90);
|
|
1459
1473
|
}
|
|
1460
1474
|
// AVI: "RIFF" + "AVI "
|
|
@@ -1488,7 +1502,13 @@ class MagicBytesStrategy {
|
|
|
1488
1502
|
input[2] === 0x33) {
|
|
1489
1503
|
return this.result("audio", "audio/mpeg", 95);
|
|
1490
1504
|
}
|
|
1491
|
-
//
|
|
1505
|
+
// AAC (ADTS): 12-bit syncword 0xFFF with the 2 layer bits == 00. This must be
|
|
1506
|
+
// checked before the MP3 sync word below, because an ADTS header also satisfies
|
|
1507
|
+
// the looser 11-bit MPEG sync mask and would otherwise be mislabeled audio/mpeg.
|
|
1508
|
+
if (input.length >= 2 && input[0] === 0xff && (input[1] & 0xf6) === 0xf0) {
|
|
1509
|
+
return this.result("audio", "audio/aac", 85);
|
|
1510
|
+
}
|
|
1511
|
+
// MP3: sync word (MPEG audio — layer bits are non-zero, unlike ADTS AAC above)
|
|
1492
1512
|
if (input.length >= 2 && input[0] === 0xff && (input[1] & 0xe0) === 0xe0) {
|
|
1493
1513
|
return this.result("audio", "audio/mpeg", 80);
|
|
1494
1514
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "9.88.
|
|
3
|
+
"version": "9.88.5",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (58+ servers), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
|
|
6
6
|
"author": {
|