decant-core 1.2.3 → 1.2.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/ai/chatgpt.js +3 -0
- package/ai/google_search_ai.js +68 -3
- package/ai/index.js +1 -0
- package/package.json +1 -1
- package/utils/latex-math.js +81 -0
package/ai/chatgpt.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ChatParser } from "./base.js";
|
|
2
2
|
import { convertToMarkdown } from "../utils/html-to-markdown.js";
|
|
3
|
+
import { normalizeLatexMath } from "../utils/latex-math.js";
|
|
3
4
|
import {
|
|
4
5
|
collectMountedTurnMessages,
|
|
5
6
|
findChatGPTScrollRoot,
|
|
@@ -534,6 +535,8 @@ function cleanMarkdownFromApi(text, citeMap, imageGroupMap) {
|
|
|
534
535
|
},
|
|
535
536
|
);
|
|
536
537
|
|
|
538
|
+
text = normalizeLatexMath(text);
|
|
539
|
+
|
|
537
540
|
return text;
|
|
538
541
|
}
|
|
539
542
|
|
package/ai/google_search_ai.js
CHANGED
|
@@ -1,6 +1,70 @@
|
|
|
1
1
|
import { ChatParser } from "./base.js";
|
|
2
2
|
import { convertToMarkdown } from "../utils/html-to-markdown.js";
|
|
3
3
|
|
|
4
|
+
export function sanitizeResponseContainer(container) {
|
|
5
|
+
if (!container) return container;
|
|
6
|
+
const clone =
|
|
7
|
+
typeof container.cloneNode === "function"
|
|
8
|
+
? container.cloneNode(true)
|
|
9
|
+
: container;
|
|
10
|
+
|
|
11
|
+
// 1. Remove script, style, and noscript elements to prevent inline script leakage
|
|
12
|
+
const unwanted = clone.querySelectorAll
|
|
13
|
+
? clone.querySelectorAll("script, style, noscript")
|
|
14
|
+
: [];
|
|
15
|
+
unwanted.forEach((el) => el.remove());
|
|
16
|
+
|
|
17
|
+
// 2. Remove base64 inline images to prevent megabyte-scale text walls in markdown exports
|
|
18
|
+
const images = clone.querySelectorAll ? clone.querySelectorAll("img") : [];
|
|
19
|
+
images.forEach((img) => {
|
|
20
|
+
const src = img.getAttribute("src") || "";
|
|
21
|
+
if (src.startsWith("data:image/")) {
|
|
22
|
+
img.remove();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// 3. Unwrap Google tracking redirects (/goto?url=..., /url?q=...) to direct URLs
|
|
27
|
+
const links = clone.querySelectorAll ? clone.querySelectorAll("a[href]") : [];
|
|
28
|
+
links.forEach((a) => {
|
|
29
|
+
const href = a.getAttribute("href") || "";
|
|
30
|
+
if (
|
|
31
|
+
href.startsWith("/goto?") ||
|
|
32
|
+
href.startsWith("/url?") ||
|
|
33
|
+
href.includes("google.com/url?") ||
|
|
34
|
+
href.includes("google.com/goto?")
|
|
35
|
+
) {
|
|
36
|
+
try {
|
|
37
|
+
const parsed = new URL(href, "https://www.google.com");
|
|
38
|
+
const target =
|
|
39
|
+
parsed.searchParams.get("url") || parsed.searchParams.get("q");
|
|
40
|
+
if (
|
|
41
|
+
target &&
|
|
42
|
+
(target.startsWith("http://") || target.startsWith("https://"))
|
|
43
|
+
) {
|
|
44
|
+
a.setAttribute("href", target);
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// Keep original href if URL parsing fails
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 4. Remove empty link shells left behind by stripped images/tracking icons
|
|
52
|
+
if (!a.textContent.trim() && !a.querySelector("img, svg")) {
|
|
53
|
+
a.remove();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return clone;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function cleanMarkdownSpacing(markdown) {
|
|
61
|
+
if (!markdown) return "";
|
|
62
|
+
return markdown
|
|
63
|
+
.replace(/[ \t]+$/gm, "")
|
|
64
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
65
|
+
.trim();
|
|
66
|
+
}
|
|
67
|
+
|
|
4
68
|
export class GoogleSearchAIParser extends ChatParser {
|
|
5
69
|
name = "Google Search AI";
|
|
6
70
|
isAvailable(url) {
|
|
@@ -94,9 +158,10 @@ export class GoogleSearchAIParser extends ChatParser {
|
|
|
94
158
|
const minLength = Math.min(queries.length, responseContainers.length);
|
|
95
159
|
for (let i = 0; i < minLength; i++) {
|
|
96
160
|
messages.push({ role: "User", content: queries[i].trim() });
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
161
|
+
const cleanContainer = sanitizeResponseContainer(responseContainers[i]);
|
|
162
|
+
const text = cleanMarkdownSpacing(convertToMarkdown(cleanContainer));
|
|
163
|
+
if (text) {
|
|
164
|
+
messages.push({ role: "Model", content: text });
|
|
100
165
|
}
|
|
101
166
|
}
|
|
102
167
|
|
package/ai/index.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clean up LaTeX math formulas by unescaping common markdown parser artifacts.
|
|
3
|
+
* @param {string} latex - Raw LaTeX math string
|
|
4
|
+
* @returns {string} Cleaned LaTeX math
|
|
5
|
+
*/
|
|
6
|
+
export function cleanLatexMath(latex) {
|
|
7
|
+
if (!latex || typeof latex !== "string") return "";
|
|
8
|
+
return latex
|
|
9
|
+
.replace(/\\\\([a-zA-Z]+)/g, "\\$1")
|
|
10
|
+
.replace(/\\([_\][*])/g, "$1");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Standardize LaTeX math delimiters in markdown text.
|
|
15
|
+
* Converts LaTeX bracket delimiters (\[...\] and \(...\)) to standard markdown
|
|
16
|
+
* math delimiters ($$...$$ and $...$) while protecting code blocks and inline code.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} text - Input text/markdown
|
|
19
|
+
* @returns {string} Text with normalized LaTeX math delimiters
|
|
20
|
+
*/
|
|
21
|
+
export function normalizeLatexMath(text) {
|
|
22
|
+
if (!text || typeof text !== "string") return "";
|
|
23
|
+
|
|
24
|
+
const placeholders = [];
|
|
25
|
+
let tokenCounter = 0;
|
|
26
|
+
|
|
27
|
+
// 1. Protect fenced code blocks (``` ... ``` or ~~~ ... ~~~)
|
|
28
|
+
let processed = text.replace(/(```[\s\S]*?```|~~~[\s\S]*?~~~)/g, (match) => {
|
|
29
|
+
const id = `@@MATH_CODE_BLOCK_${tokenCounter++}@@`;
|
|
30
|
+
placeholders.push({ id, content: match });
|
|
31
|
+
return id;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// 2. Protect inline code (`...`)
|
|
35
|
+
processed = processed.replace(/`([^`\n]+?)`/g, (match) => {
|
|
36
|
+
const id = `@@MATH_INLINE_CODE_${tokenCounter++}@@`;
|
|
37
|
+
placeholders.push({ id, content: match });
|
|
38
|
+
return id;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// 3. Protect existing display math ($$ ... $$) and clean any escaped LaTeX syntax
|
|
42
|
+
processed = processed.replace(/\$\$([\s\S]*?)\$\$/g, (match, math) => {
|
|
43
|
+
const id = `@@MATH_DISPLAY_${tokenCounter++}@@`;
|
|
44
|
+
placeholders.push({ id, content: `$$${cleanLatexMath(math)}$$` });
|
|
45
|
+
return id;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// 4. Protect existing inline math ($ ... $) and clean any escaped LaTeX syntax
|
|
49
|
+
processed = processed.replace(/\$([^$\n]+?)\$/g, (match, math) => {
|
|
50
|
+
const id = `@@MATH_INLINE_${tokenCounter++}@@`;
|
|
51
|
+
placeholders.push({ id, content: `$${cleanLatexMath(math)}$` });
|
|
52
|
+
return id;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// 5. Convert display math: \[ ... \] or \\[ ... \\]
|
|
56
|
+
processed = processed.replace(
|
|
57
|
+
/(?:\\{1,2}\[)([\s\S]+?)(?:\\{1,2}\])/g,
|
|
58
|
+
(match, math) => {
|
|
59
|
+
return `$$${cleanLatexMath(math).trim()}$$`;
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// 6. Convert inline math: \( ... \) or \\( ... \\)
|
|
64
|
+
processed = processed.replace(
|
|
65
|
+
/(?:\\{1,2}\()([\s\S]+?)(?:\\{1,2}\))/g,
|
|
66
|
+
(match, math) => {
|
|
67
|
+
return `$${cleanLatexMath(math).trim()}$`;
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// 7. Collapse excessive blank lines outside protected code
|
|
72
|
+
processed = processed.replace(/\n{3,}/g, "\n\n");
|
|
73
|
+
|
|
74
|
+
// 8. Restore protected items in reverse order
|
|
75
|
+
for (let i = placeholders.length - 1; i >= 0; i--) {
|
|
76
|
+
const { id, content } = placeholders[i];
|
|
77
|
+
processed = processed.replace(id, () => content);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return processed;
|
|
81
|
+
}
|