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 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
 
@@ -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 text = convertToMarkdown(responseContainers[i]);
98
- if (text.trim()) {
99
- messages.push({ role: "Model", content: text.trim() });
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
@@ -30,6 +30,7 @@ export { ChubParser } from "./chub.js";
30
30
 
31
31
  // Utilities
32
32
  export { convertToMarkdown, cleanMarkdown } from "../utils/html-to-markdown.js";
33
+ export { normalizeLatexMath, cleanLatexMath } from "../utils/latex-math.js";
33
34
 
34
35
  // Detection
35
36
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "decant-core",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "Shared AI chat platform parsers and detection for Covai browser extensions.",
5
5
  "type": "module",
6
6
  "main": "ai/index.js",
@@ -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
+ }