decant-core 1.0.0 → 1.1.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/README.md +20 -20
- package/ai/base.js +4 -4
- package/ai/chatgpt.js +356 -216
- package/ai/chatgpt_helper.js +46 -28
- package/ai/chatgpt_scroll_collector.js +36 -33
- package/ai/chub.js +90 -0
- package/ai/claude.js +119 -92
- package/ai/claude_react_reader.js +8 -6
- package/ai/copilot.js +158 -124
- package/ai/deepseek.js +36 -27
- package/ai/gemini.js +384 -230
- package/ai/gemini_cloud_assist.js +31 -22
- package/ai/google_ai_studio.js +45 -27
- package/ai/google_search_ai.js +32 -23
- package/ai/index.js +33 -19
- package/ai/joyland.js +85 -0
- package/ai/lumo.js +39 -28
- package/ai/meta.js +30 -24
- package/ai/mistral.js +21 -16
- package/ai/notebooklm.js +50 -34
- package/ai/perplexity.js +22 -19
- package/ai/qwen.js +29 -25
- package/ai/z_ai.js +34 -28
- package/detection/detect-platform.js +27 -19
- package/detection/domains.js +32 -24
- package/lib/turndown.js +352 -183
- package/package.json +24 -5
- package/utils/html-to-markdown.js +148 -112
- package/web/article.js +286 -0
- package/web/index.js +6 -0
- package/web/scoring.js +76 -0
package/ai/lumo.js
CHANGED
|
@@ -1,43 +1,49 @@
|
|
|
1
|
-
import { ChatParser } from
|
|
2
|
-
import { convertToMarkdown } from
|
|
1
|
+
import { ChatParser } from "./base.js";
|
|
2
|
+
import { convertToMarkdown } from "../utils/html-to-markdown.js";
|
|
3
3
|
|
|
4
4
|
export class LumoParser extends ChatParser {
|
|
5
|
-
name =
|
|
5
|
+
name = "Lumo";
|
|
6
6
|
isAvailable(url) {
|
|
7
|
-
return url.includes(
|
|
7
|
+
return url.includes("lumo.proton.me");
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
async parse() {
|
|
11
11
|
// Extract Title
|
|
12
|
-
let title =
|
|
13
|
-
const titleBtn = document.querySelector(
|
|
12
|
+
let title = "";
|
|
13
|
+
const titleBtn = document.querySelector(
|
|
14
|
+
".conversation-header-title-view button",
|
|
15
|
+
);
|
|
14
16
|
if (titleBtn && titleBtn.textContent) {
|
|
15
17
|
title = titleBtn.textContent.trim();
|
|
16
18
|
} else if (document.title) {
|
|
17
|
-
title = document.title.replace(/\s*-\s*Lumo.*$/i,
|
|
19
|
+
title = document.title.replace(/\s*-\s*Lumo.*$/i, "").trim();
|
|
18
20
|
}
|
|
19
|
-
title = title ||
|
|
21
|
+
title = title || "Lumo Conversation";
|
|
20
22
|
|
|
21
23
|
const messages = [];
|
|
22
|
-
const messageElements = document.querySelectorAll(
|
|
24
|
+
const messageElements = document.querySelectorAll(
|
|
25
|
+
".lumo-chat-item[data-message-role]",
|
|
26
|
+
);
|
|
23
27
|
|
|
24
28
|
for (const el of messageElements) {
|
|
25
|
-
const roleAttr = el.getAttribute(
|
|
29
|
+
const roleAttr = el.getAttribute("data-message-role");
|
|
26
30
|
|
|
27
|
-
if (roleAttr ===
|
|
31
|
+
if (roleAttr === "user") {
|
|
28
32
|
const contentEl =
|
|
29
|
-
el.querySelector(
|
|
33
|
+
el.querySelector(".lumo-markdown") ||
|
|
34
|
+
el.querySelector(".user-msg-container") ||
|
|
35
|
+
el;
|
|
30
36
|
const text = convertToMarkdown(contentEl);
|
|
31
37
|
if (text.trim()) {
|
|
32
38
|
messages.push({
|
|
33
|
-
role:
|
|
39
|
+
role: "User",
|
|
34
40
|
content: text.trim(),
|
|
35
41
|
});
|
|
36
42
|
}
|
|
37
|
-
} else if (roleAttr ===
|
|
43
|
+
} else if (roleAttr === "assistant") {
|
|
38
44
|
const contentEl =
|
|
39
|
-
el.querySelector(
|
|
40
|
-
el.querySelector(
|
|
45
|
+
el.querySelector(".progressive-markdown-content") ||
|
|
46
|
+
el.querySelector(".assistant-msg-container") ||
|
|
41
47
|
el;
|
|
42
48
|
|
|
43
49
|
const ownerDoc = el.ownerDocument || document;
|
|
@@ -45,26 +51,27 @@ export class LumoParser extends ChatParser {
|
|
|
45
51
|
|
|
46
52
|
// Preprocess Lumo code blocks to standard <pre><code class="language-xyz">...</code></pre>
|
|
47
53
|
contentElClone
|
|
48
|
-
.querySelectorAll(
|
|
54
|
+
.querySelectorAll(".lumo-syntax-highlighter, .lumo-code-block")
|
|
49
55
|
.forEach((codeBlock) => {
|
|
50
|
-
const codeEl = codeBlock.querySelector(
|
|
56
|
+
const codeEl = codeBlock.querySelector("code");
|
|
51
57
|
if (codeEl) {
|
|
52
|
-
let language =
|
|
53
|
-
const match = (codeEl.className ||
|
|
58
|
+
let language = "";
|
|
59
|
+
const match = (codeEl.className || "").match(/language-([^\s]+)/);
|
|
54
60
|
if (match) {
|
|
55
61
|
language = match[1];
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
const codeText = codeEl.textContent ||
|
|
59
|
-
const pre = ownerDoc.createElement(
|
|
60
|
-
const code = ownerDoc.createElement(
|
|
64
|
+
const codeText = codeEl.textContent || "";
|
|
65
|
+
const pre = ownerDoc.createElement("pre");
|
|
66
|
+
const code = ownerDoc.createElement("code");
|
|
61
67
|
if (language) {
|
|
62
68
|
code.className = `language-${language}`;
|
|
63
69
|
}
|
|
64
70
|
code.textContent = codeText;
|
|
65
71
|
pre.appendChild(code);
|
|
66
72
|
|
|
67
|
-
const parentToReplace =
|
|
73
|
+
const parentToReplace =
|
|
74
|
+
codeBlock.closest(".message-container") || codeBlock;
|
|
68
75
|
if (parentToReplace && parentToReplace.parentNode) {
|
|
69
76
|
parentToReplace.parentNode.replaceChild(pre, parentToReplace);
|
|
70
77
|
}
|
|
@@ -73,7 +80,9 @@ export class LumoParser extends ChatParser {
|
|
|
73
80
|
|
|
74
81
|
// Clean up UI toolbar and avatar elements from assistant clone
|
|
75
82
|
contentElClone
|
|
76
|
-
.querySelectorAll(
|
|
83
|
+
.querySelectorAll(
|
|
84
|
+
".action-toolbar, .lumo-no-copy, .lumo-avatar, button",
|
|
85
|
+
)
|
|
77
86
|
.forEach((noCopy) => {
|
|
78
87
|
noCopy.remove();
|
|
79
88
|
});
|
|
@@ -81,7 +90,7 @@ export class LumoParser extends ChatParser {
|
|
|
81
90
|
const markdown = convertToMarkdown(contentElClone);
|
|
82
91
|
if (markdown.trim()) {
|
|
83
92
|
messages.push({
|
|
84
|
-
role:
|
|
93
|
+
role: "Lumo",
|
|
85
94
|
content: markdown.trim(),
|
|
86
95
|
});
|
|
87
96
|
}
|
|
@@ -89,9 +98,11 @@ export class LumoParser extends ChatParser {
|
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
const currentUrl =
|
|
92
|
-
typeof window !==
|
|
101
|
+
typeof window !== "undefined" && window.location
|
|
102
|
+
? window.location.href || ""
|
|
103
|
+
: "";
|
|
93
104
|
const metadata = {
|
|
94
|
-
Source:
|
|
105
|
+
Source: "Lumo",
|
|
95
106
|
Date: new Date().toLocaleString(),
|
|
96
107
|
Link: currentUrl,
|
|
97
108
|
};
|
package/ai/meta.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
import { ChatParser } from
|
|
2
|
-
import { convertToMarkdown } from
|
|
1
|
+
import { ChatParser } from "./base.js";
|
|
2
|
+
import { convertToMarkdown } from "../utils/html-to-markdown.js";
|
|
3
3
|
|
|
4
4
|
export class MetaParser extends ChatParser {
|
|
5
|
-
name =
|
|
5
|
+
name = "Meta AI";
|
|
6
6
|
isAvailable(url) {
|
|
7
|
-
return url.includes(
|
|
7
|
+
return url.includes("meta.ai");
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
async parse() {
|
|
11
11
|
// Try to get the conversation title from the input field or the header button
|
|
12
|
-
const titleInput = document.querySelector(
|
|
13
|
-
|
|
12
|
+
const titleInput = document.querySelector(
|
|
13
|
+
'input[placeholder="Conversation title"]',
|
|
14
|
+
);
|
|
15
|
+
const titleButton = document.querySelector(
|
|
16
|
+
'[data-slot="button"] span.truncate',
|
|
17
|
+
);
|
|
14
18
|
const title =
|
|
15
19
|
titleInput && titleInput.value
|
|
16
20
|
? titleInput.value
|
|
17
21
|
: titleButton
|
|
18
22
|
? titleButton.innerText
|
|
19
|
-
:
|
|
23
|
+
: "Meta AI Session";
|
|
20
24
|
|
|
21
25
|
const messages = [];
|
|
22
26
|
|
|
@@ -40,45 +44,45 @@ export class MetaParser extends ChatParser {
|
|
|
40
44
|
});
|
|
41
45
|
|
|
42
46
|
uniqueElements.forEach((el) => {
|
|
43
|
-
let role =
|
|
44
|
-
let content =
|
|
47
|
+
let role = "Unknown";
|
|
48
|
+
let content = "";
|
|
45
49
|
|
|
46
50
|
// Check if the element itself identifies as user or assistant
|
|
47
51
|
const isUser =
|
|
48
52
|
el.matches('[data-message-type="user"]') ||
|
|
49
|
-
(el.getAttribute(
|
|
50
|
-
el.getAttribute(
|
|
53
|
+
(el.getAttribute("data-message-id") &&
|
|
54
|
+
el.getAttribute("data-message-id").endsWith("_user"));
|
|
51
55
|
const isAssistant =
|
|
52
56
|
el.matches('[data-testid="assistant-message"]') ||
|
|
53
|
-
(el.getAttribute(
|
|
54
|
-
el.getAttribute(
|
|
57
|
+
(el.getAttribute("data-message-id") &&
|
|
58
|
+
el.getAttribute("data-message-id").endsWith("_assistant"));
|
|
55
59
|
|
|
56
60
|
if (isUser) {
|
|
57
|
-
role =
|
|
61
|
+
role = "User";
|
|
58
62
|
// User text is usually in a span with text-response class or simply pre-wrap
|
|
59
63
|
const textEl =
|
|
60
64
|
el.querySelector('[data-slot="text"].text-response') ||
|
|
61
|
-
el.querySelector(
|
|
65
|
+
el.querySelector(".whitespace-pre-wrap");
|
|
62
66
|
if (textEl) {
|
|
63
67
|
content = convertToMarkdown(textEl);
|
|
64
68
|
} else {
|
|
65
69
|
content = convertToMarkdown(el);
|
|
66
70
|
}
|
|
67
71
|
} else if (isAssistant) {
|
|
68
|
-
role =
|
|
72
|
+
role = "Meta AI";
|
|
69
73
|
// Assistant content is typically styled in markdown-content or prose
|
|
70
74
|
const contentEl =
|
|
71
|
-
el.querySelector(
|
|
72
|
-
el.querySelector(
|
|
73
|
-
el.querySelector(
|
|
75
|
+
el.querySelector(".markdown-content") ||
|
|
76
|
+
el.querySelector(".ur-markdown") ||
|
|
77
|
+
el.querySelector(".prose");
|
|
74
78
|
if (contentEl) {
|
|
75
79
|
const clone = contentEl.cloneNode(true);
|
|
76
80
|
|
|
77
81
|
// Remove noise elements (like citation pills, edit buttons, thinking status, etc)
|
|
78
82
|
const noiseSelectors = [
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
"button",
|
|
84
|
+
".ur-citation-pill",
|
|
85
|
+
"svg",
|
|
82
86
|
'[data-testid="citation-pill"]',
|
|
83
87
|
'[data-testid="thinking-status"]',
|
|
84
88
|
];
|
|
@@ -98,9 +102,11 @@ export class MetaParser extends ChatParser {
|
|
|
98
102
|
});
|
|
99
103
|
|
|
100
104
|
const currentUrl =
|
|
101
|
-
typeof window !==
|
|
105
|
+
typeof window !== "undefined" && window.location
|
|
106
|
+
? window.location.href || ""
|
|
107
|
+
: "";
|
|
102
108
|
const metadata = {
|
|
103
|
-
Source:
|
|
109
|
+
Source: "Meta AI",
|
|
104
110
|
Date: new Date().toLocaleString(),
|
|
105
111
|
Link: currentUrl,
|
|
106
112
|
};
|
package/ai/mistral.js
CHANGED
|
@@ -1,43 +1,46 @@
|
|
|
1
|
-
import { ChatParser } from
|
|
2
|
-
import { convertToMarkdown } from
|
|
1
|
+
import { ChatParser } from "./base.js";
|
|
2
|
+
import { convertToMarkdown } from "../utils/html-to-markdown.js";
|
|
3
3
|
|
|
4
4
|
export class MistralParser extends ChatParser {
|
|
5
|
-
name =
|
|
5
|
+
name = "Mistral";
|
|
6
6
|
isAvailable(url) {
|
|
7
|
-
return url.includes(
|
|
7
|
+
return url.includes("chat.mistral.ai");
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
async parse() {
|
|
11
11
|
// Extract Title
|
|
12
|
-
const titleElement = document.querySelector(
|
|
13
|
-
let title =
|
|
12
|
+
const titleElement = document.querySelector("span.truncate.text-sm");
|
|
13
|
+
let title = "Mistral Conversation";
|
|
14
14
|
if (titleElement && titleElement.innerText) {
|
|
15
15
|
title = titleElement.innerText.trim();
|
|
16
16
|
} else if (document.title) {
|
|
17
|
-
title = document.title.replace(
|
|
17
|
+
title = document.title.replace(" - Mistral", "").trim();
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const messages = [];
|
|
21
|
-
const messageElements = document.querySelectorAll(
|
|
21
|
+
const messageElements = document.querySelectorAll(
|
|
22
|
+
"[data-message-author-role]",
|
|
23
|
+
);
|
|
22
24
|
|
|
23
25
|
for (const el of messageElements) {
|
|
24
|
-
const role = el.getAttribute(
|
|
26
|
+
const role = el.getAttribute("data-message-author-role");
|
|
25
27
|
|
|
26
|
-
if (role ===
|
|
28
|
+
if (role === "user") {
|
|
27
29
|
const contentEl =
|
|
28
|
-
el.querySelector(
|
|
30
|
+
el.querySelector(".select-text") ||
|
|
31
|
+
el.querySelector(".whitespace-pre-wrap");
|
|
29
32
|
if (contentEl) {
|
|
30
33
|
messages.push({
|
|
31
|
-
role:
|
|
34
|
+
role: "User",
|
|
32
35
|
content: contentEl.innerText.trim(),
|
|
33
36
|
});
|
|
34
37
|
}
|
|
35
|
-
} else if (role ===
|
|
38
|
+
} else if (role === "assistant") {
|
|
36
39
|
const answerEl = el.querySelector('[data-message-part-type="answer"]');
|
|
37
40
|
if (answerEl) {
|
|
38
41
|
const markdown = convertToMarkdown(answerEl.innerHTML);
|
|
39
42
|
messages.push({
|
|
40
|
-
role:
|
|
43
|
+
role: "Mistral",
|
|
41
44
|
content: markdown,
|
|
42
45
|
});
|
|
43
46
|
}
|
|
@@ -45,9 +48,11 @@ export class MistralParser extends ChatParser {
|
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
const currentUrl =
|
|
48
|
-
typeof window !==
|
|
51
|
+
typeof window !== "undefined" && window.location
|
|
52
|
+
? window.location.href || ""
|
|
53
|
+
: "";
|
|
49
54
|
const metadata = {
|
|
50
|
-
Source:
|
|
55
|
+
Source: "Mistral",
|
|
51
56
|
Date: new Date().toLocaleString(),
|
|
52
57
|
Link: currentUrl,
|
|
53
58
|
};
|
package/ai/notebooklm.js
CHANGED
|
@@ -1,84 +1,90 @@
|
|
|
1
|
-
import { ChatParser } from
|
|
2
|
-
import { convertToMarkdown } from
|
|
1
|
+
import { ChatParser } from "./base.js";
|
|
2
|
+
import { convertToMarkdown } from "../utils/html-to-markdown.js";
|
|
3
3
|
|
|
4
4
|
export class NotebookLMParser extends ChatParser {
|
|
5
|
-
name =
|
|
5
|
+
name = "NotebookLM";
|
|
6
6
|
isAvailable(url) {
|
|
7
|
-
return
|
|
7
|
+
return (
|
|
8
|
+
url.includes("notebooklm.google.com") ||
|
|
9
|
+
url.includes("notebook.google.com")
|
|
10
|
+
);
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
async parse() {
|
|
11
|
-
let title =
|
|
14
|
+
let title = "";
|
|
12
15
|
|
|
13
16
|
// Title extraction
|
|
14
17
|
const titleEl = document.querySelector(
|
|
15
18
|
'.title-container .title, .notebook-title-input, input[aria-label*="title" i], h1.notebook-title',
|
|
16
19
|
);
|
|
17
20
|
if (titleEl) {
|
|
18
|
-
title = (titleEl.value || titleEl.textContent ||
|
|
21
|
+
title = (titleEl.value || titleEl.textContent || "").trim();
|
|
19
22
|
}
|
|
20
23
|
if (!title) {
|
|
21
|
-
title = (document.title ||
|
|
22
|
-
.replace(/\s*-\s*(Gemini Notebook|NotebookLM)$/i,
|
|
24
|
+
title = (document.title || "Gemini Notebook")
|
|
25
|
+
.replace(/\s*-\s*(Gemini Notebook|NotebookLM)$/i, "")
|
|
23
26
|
.trim();
|
|
24
27
|
}
|
|
25
|
-
title = title ||
|
|
28
|
+
title = title || "NotebookLM Conversation";
|
|
26
29
|
|
|
27
30
|
const messages = [];
|
|
28
31
|
|
|
29
32
|
// Selectors for turn pairs or individual message cards in NotebookLM
|
|
30
33
|
const messagePairs = Array.from(
|
|
31
|
-
document.querySelectorAll(
|
|
34
|
+
document.querySelectorAll(
|
|
35
|
+
".chat-message-pair, chat-message, .message-pair-container",
|
|
36
|
+
),
|
|
32
37
|
);
|
|
33
38
|
|
|
34
39
|
// Filter to top-level containers
|
|
35
40
|
const topContainers = messagePairs.filter(
|
|
36
|
-
(el) =>
|
|
41
|
+
(el) =>
|
|
42
|
+
!messagePairs.some((parent) => parent !== el && parent.contains(el)),
|
|
37
43
|
);
|
|
38
44
|
|
|
39
45
|
if (topContainers.length > 0) {
|
|
40
46
|
topContainers.forEach((pair) => {
|
|
41
47
|
// User message
|
|
42
48
|
const userContainer = pair.querySelector(
|
|
43
|
-
|
|
49
|
+
".from-user-container, .from-user-message-card-content",
|
|
44
50
|
);
|
|
45
51
|
|
|
46
52
|
// Model / Assistant response
|
|
47
53
|
const aiContainer = pair.querySelector(
|
|
48
|
-
|
|
54
|
+
".to-user-container, .to-user-message-inner-content, labs-tailwind-doc-viewer",
|
|
49
55
|
);
|
|
50
56
|
|
|
51
57
|
if (userContainer) {
|
|
52
58
|
const contentEl =
|
|
53
59
|
userContainer.querySelector(
|
|
54
|
-
|
|
60
|
+
".message-text-content, .md3-body-text, .from-user-message-inner-content",
|
|
55
61
|
) || userContainer;
|
|
56
62
|
|
|
57
63
|
const clone = contentEl.cloneNode(true);
|
|
58
64
|
// Clean up noise buttons & icons
|
|
59
65
|
clone
|
|
60
|
-
.querySelectorAll(
|
|
66
|
+
.querySelectorAll("button, mat-icon, .mat-mdc-button-touch-target")
|
|
61
67
|
.forEach((el) => el.remove());
|
|
62
68
|
|
|
63
69
|
const userText = convertToMarkdown(clone).trim();
|
|
64
70
|
if (userText) {
|
|
65
|
-
messages.push({ role:
|
|
71
|
+
messages.push({ role: "User", content: userText });
|
|
66
72
|
}
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
if (aiContainer) {
|
|
70
76
|
const contentEl =
|
|
71
77
|
aiContainer.querySelector(
|
|
72
|
-
|
|
78
|
+
"labs-tailwind-doc-viewer, .message-text-content, .to-user-message-inner-content",
|
|
73
79
|
) || aiContainer;
|
|
74
80
|
|
|
75
81
|
const clone = contentEl.cloneNode(true);
|
|
76
82
|
// Format citation markers (e.g., button.citation-marker -> <span class="citation-ref">[1]</span>)
|
|
77
|
-
clone.querySelectorAll(
|
|
83
|
+
clone.querySelectorAll("button.citation-marker").forEach((btn) => {
|
|
78
84
|
const citeNum = btn.textContent.trim();
|
|
79
|
-
if (citeNum && typeof document !==
|
|
80
|
-
const spanNode = document.createElement(
|
|
81
|
-
spanNode.className =
|
|
85
|
+
if (citeNum && typeof document !== "undefined") {
|
|
86
|
+
const spanNode = document.createElement("span");
|
|
87
|
+
spanNode.className = "citation-ref";
|
|
82
88
|
spanNode.textContent = ` [${citeNum}]`;
|
|
83
89
|
btn.replaceWith(spanNode);
|
|
84
90
|
}
|
|
@@ -86,12 +92,14 @@ export class NotebookLMParser extends ChatParser {
|
|
|
86
92
|
|
|
87
93
|
// Clean up other UI noise (thumbs up/down, action buttons)
|
|
88
94
|
clone
|
|
89
|
-
.querySelectorAll(
|
|
95
|
+
.querySelectorAll(
|
|
96
|
+
"button, mat-icon, .mat-mdc-button-touch-target, .feedback-actions",
|
|
97
|
+
)
|
|
90
98
|
.forEach((el) => el.remove());
|
|
91
99
|
|
|
92
100
|
const aiText = convertToMarkdown(clone).trim();
|
|
93
101
|
if (aiText) {
|
|
94
|
-
messages.push({ role:
|
|
102
|
+
messages.push({ role: "NotebookLM", content: aiText });
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
105
|
});
|
|
@@ -99,38 +107,46 @@ export class NotebookLMParser extends ChatParser {
|
|
|
99
107
|
|
|
100
108
|
// Direct fallback if topContainers loop yielded no messages
|
|
101
109
|
if (messages.length === 0) {
|
|
102
|
-
const userContainers = Array.from(
|
|
103
|
-
|
|
110
|
+
const userContainers = Array.from(
|
|
111
|
+
document.querySelectorAll(".from-user-container"),
|
|
112
|
+
);
|
|
113
|
+
const aiContainers = Array.from(
|
|
114
|
+
document.querySelectorAll(".to-user-container"),
|
|
115
|
+
);
|
|
104
116
|
|
|
105
117
|
const maxLen = Math.max(userContainers.length, aiContainers.length);
|
|
106
118
|
for (let i = 0; i < maxLen; i++) {
|
|
107
119
|
if (userContainers[i]) {
|
|
108
120
|
const text = convertToMarkdown(userContainers[i]).trim();
|
|
109
|
-
if (text) messages.push({ role:
|
|
121
|
+
if (text) messages.push({ role: "User", content: text });
|
|
110
122
|
}
|
|
111
123
|
if (aiContainers[i]) {
|
|
112
124
|
const clone = aiContainers[i].cloneNode(true);
|
|
113
|
-
clone.querySelectorAll(
|
|
125
|
+
clone.querySelectorAll("button.citation-marker").forEach((btn) => {
|
|
114
126
|
const citeNum = btn.textContent.trim();
|
|
115
|
-
if (citeNum && typeof document !==
|
|
116
|
-
const spanNode = document.createElement(
|
|
117
|
-
spanNode.className =
|
|
127
|
+
if (citeNum && typeof document !== "undefined") {
|
|
128
|
+
const spanNode = document.createElement("span");
|
|
129
|
+
spanNode.className = "citation-ref";
|
|
118
130
|
spanNode.textContent = ` [${citeNum}]`;
|
|
119
131
|
btn.replaceWith(spanNode);
|
|
120
132
|
}
|
|
121
133
|
});
|
|
122
|
-
clone
|
|
134
|
+
clone
|
|
135
|
+
.querySelectorAll("button, mat-icon")
|
|
136
|
+
.forEach((el) => el.remove());
|
|
123
137
|
|
|
124
138
|
const text = convertToMarkdown(clone).trim();
|
|
125
|
-
if (text) messages.push({ role:
|
|
139
|
+
if (text) messages.push({ role: "NotebookLM", content: text });
|
|
126
140
|
}
|
|
127
141
|
}
|
|
128
142
|
}
|
|
129
143
|
|
|
130
144
|
const currentUrl =
|
|
131
|
-
typeof window !==
|
|
145
|
+
typeof window !== "undefined" && window.location
|
|
146
|
+
? window.location.href || ""
|
|
147
|
+
: "";
|
|
132
148
|
const metadata = {
|
|
133
|
-
Source:
|
|
149
|
+
Source: "NotebookLM",
|
|
134
150
|
Date: new Date().toLocaleString(),
|
|
135
151
|
Link: currentUrl,
|
|
136
152
|
};
|
package/ai/perplexity.js
CHANGED
|
@@ -1,52 +1,53 @@
|
|
|
1
|
-
import { ChatParser } from
|
|
2
|
-
import { convertToMarkdown } from
|
|
1
|
+
import { ChatParser } from "./base.js";
|
|
2
|
+
import { convertToMarkdown } from "../utils/html-to-markdown.js";
|
|
3
3
|
|
|
4
4
|
export class PerplexityParser extends ChatParser {
|
|
5
|
-
name =
|
|
5
|
+
name = "Perplexity";
|
|
6
6
|
isAvailable(url) {
|
|
7
|
-
return url.includes(
|
|
7
|
+
return url.includes("perplexity.ai");
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
async parse() {
|
|
11
11
|
const rawTitle =
|
|
12
|
-
document.querySelector(
|
|
13
|
-
document.querySelector(
|
|
12
|
+
document.querySelector(".share-title-section h1")?.textContent ||
|
|
13
|
+
document.querySelector("h1")?.textContent ||
|
|
14
14
|
document.title ||
|
|
15
|
-
|
|
16
|
-
const title = rawTitle.trim().replace(/\s+/g,
|
|
15
|
+
"Perplexity Search";
|
|
16
|
+
const title = rawTitle.trim().replace(/\s+/g, " ");
|
|
17
17
|
|
|
18
18
|
const messages = [];
|
|
19
19
|
|
|
20
20
|
// Perplexity Container
|
|
21
|
-
const threadContainer =
|
|
21
|
+
const threadContainer =
|
|
22
|
+
document.querySelector(".max-w-threadContentWidth") || document.body;
|
|
22
23
|
|
|
23
24
|
// Candidates for User Messages
|
|
24
25
|
const userSelectors = [
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
"h1.group\\/query",
|
|
27
|
+
".group\\/query",
|
|
28
|
+
".whitespace-pre-line.text-pretty",
|
|
28
29
|
'[data-testid="search-bar-input"]', // fallback for input? typically input is not the message display
|
|
29
30
|
];
|
|
30
31
|
|
|
31
32
|
// Candidates for Assistant Messages
|
|
32
|
-
const assistantSelectors = ['div[id^="markdown-content-"]',
|
|
33
|
+
const assistantSelectors = ['div[id^="markdown-content-"]', ".prose"];
|
|
33
34
|
|
|
34
35
|
// Strategy: Iterate children of thread container or find all matches in document
|
|
35
36
|
// Thread container is better to preserve order
|
|
36
37
|
|
|
37
38
|
// Let's select all potential message blocks within the thread container
|
|
38
|
-
const selectorString = [...userSelectors, ...assistantSelectors].join(
|
|
39
|
+
const selectorString = [...userSelectors, ...assistantSelectors].join(", ");
|
|
39
40
|
const elements = threadContainer.querySelectorAll(selectorString);
|
|
40
41
|
|
|
41
42
|
// Helper to determine role
|
|
42
43
|
const getRole = (el) => {
|
|
43
44
|
for (const s of userSelectors) {
|
|
44
|
-
if (el.matches(s)) return
|
|
45
|
+
if (el.matches(s)) return "User";
|
|
45
46
|
}
|
|
46
47
|
for (const s of assistantSelectors) {
|
|
47
|
-
if (el.matches(s)) return
|
|
48
|
+
if (el.matches(s)) return "Perplexity";
|
|
48
49
|
}
|
|
49
|
-
return
|
|
50
|
+
return "Unknown";
|
|
50
51
|
};
|
|
51
52
|
|
|
52
53
|
const seenText = new Set();
|
|
@@ -73,9 +74,11 @@ export class PerplexityParser extends ChatParser {
|
|
|
73
74
|
});
|
|
74
75
|
|
|
75
76
|
const currentUrl =
|
|
76
|
-
typeof window !==
|
|
77
|
+
typeof window !== "undefined" && window.location
|
|
78
|
+
? window.location.href || ""
|
|
79
|
+
: "";
|
|
77
80
|
const metadata = {
|
|
78
|
-
Source:
|
|
81
|
+
Source: "Perplexity",
|
|
79
82
|
Date: new Date().toLocaleString(),
|
|
80
83
|
Link: currentUrl,
|
|
81
84
|
};
|