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/copilot.js
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
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 CopilotParser extends ChatParser {
|
|
5
|
-
name =
|
|
5
|
+
name = "Copilot";
|
|
6
6
|
isAvailable(url) {
|
|
7
7
|
return (
|
|
8
|
-
url.includes(
|
|
9
|
-
url.includes(
|
|
10
|
-
url.includes(
|
|
11
|
-
url.includes(
|
|
12
|
-
url.includes(
|
|
13
|
-
url.includes(
|
|
14
|
-
url.includes(
|
|
15
|
-
url.includes(
|
|
16
|
-
url.includes(
|
|
8
|
+
url.includes("copilot.microsoft.com") ||
|
|
9
|
+
url.includes("copilot.com") ||
|
|
10
|
+
url.includes("copilot.cloud.microsoft") ||
|
|
11
|
+
url.includes("m365.cloud.microsoft") ||
|
|
12
|
+
url.includes("m365.microsoft.com") ||
|
|
13
|
+
url.includes("bing.com/chat") ||
|
|
14
|
+
url.includes("bing.com/copilot") ||
|
|
15
|
+
url.includes("bing.com/copilotsearch") ||
|
|
16
|
+
url.includes("edgeservices.bing.com")
|
|
17
17
|
);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
async parse() {
|
|
21
|
-
let title =
|
|
21
|
+
let title = "Copilot Conversation";
|
|
22
22
|
if (document.title) {
|
|
23
23
|
const cleanTitle = document.title
|
|
24
|
-
.replace(/^Microsoft Copilot:\s*/i,
|
|
25
|
-
.replace(/\s*-\s*Microsoft Copilot$/i,
|
|
26
|
-
.replace(/^Copilot:\s*/i,
|
|
27
|
-
.replace(/\s*-\s*Copilot$/i,
|
|
28
|
-
.replace(/Your AI companion/i,
|
|
24
|
+
.replace(/^Microsoft Copilot:\s*/i, "")
|
|
25
|
+
.replace(/\s*-\s*Microsoft Copilot$/i, "")
|
|
26
|
+
.replace(/^Copilot:\s*/i, "")
|
|
27
|
+
.replace(/\s*-\s*Copilot$/i, "")
|
|
28
|
+
.replace(/Your AI companion/i, "")
|
|
29
29
|
.trim();
|
|
30
30
|
if (
|
|
31
31
|
cleanTitle &&
|
|
32
|
-
cleanTitle.toLowerCase() !==
|
|
33
|
-
cleanTitle.toLowerCase() !==
|
|
32
|
+
cleanTitle.toLowerCase() !== "microsoft copilot" &&
|
|
33
|
+
cleanTitle.toLowerCase() !== "copilot"
|
|
34
34
|
) {
|
|
35
35
|
title = cleanTitle;
|
|
36
36
|
}
|
|
@@ -60,28 +60,28 @@ export class CopilotParser extends ChatParser {
|
|
|
60
60
|
'[data-testid="regenerate-message-button-popover"]',
|
|
61
61
|
'[data-testid="chat-suggestion"]',
|
|
62
62
|
'[data-testid="loading-message"]',
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
".fai-CopilotMessage__actions",
|
|
64
|
+
".fai-SuggestionList",
|
|
65
|
+
".fai-UserMessage__accessibleHeading",
|
|
66
|
+
".fai-CopilotMessage__accessibleHeading",
|
|
67
67
|
'[class*="suggestedReplies"]',
|
|
68
68
|
'[class*="workingCard"]',
|
|
69
69
|
'[class*="WorkingCard"]',
|
|
70
70
|
'[class*="disclaimerText"]',
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
"cib-action-bar",
|
|
72
|
+
"cib-feedback-buttons",
|
|
73
|
+
"cib-message-actions",
|
|
74
|
+
".sr-only",
|
|
75
75
|
];
|
|
76
76
|
noiseSelectors.forEach((sel) => {
|
|
77
77
|
clone.querySelectorAll(sel).forEach((el) => el.remove());
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
// Transform data-url span buttons into standard anchor tags
|
|
81
|
-
clone.querySelectorAll(
|
|
82
|
-
const url = span.getAttribute(
|
|
83
|
-
if (url && !url.startsWith(
|
|
84
|
-
const a = document.createElement(
|
|
81
|
+
clone.querySelectorAll("span[data-url]").forEach((span) => {
|
|
82
|
+
const url = span.getAttribute("data-url");
|
|
83
|
+
if (url && !url.startsWith("ca://")) {
|
|
84
|
+
const a = document.createElement("a");
|
|
85
85
|
a.href = url;
|
|
86
86
|
a.textContent = span.textContent;
|
|
87
87
|
span.replaceWith(a);
|
|
@@ -89,22 +89,26 @@ export class CopilotParser extends ChatParser {
|
|
|
89
89
|
});
|
|
90
90
|
|
|
91
91
|
// Standardize code blocks with language labels
|
|
92
|
-
clone
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
92
|
+
clone
|
|
93
|
+
.querySelectorAll('div.rounded-xl, div[class*="code-block"]')
|
|
94
|
+
.forEach((block) => {
|
|
95
|
+
const langEl = block.querySelector(
|
|
96
|
+
'span.capitalize, [class*="language-"]',
|
|
97
|
+
);
|
|
98
|
+
const codeEl = block.querySelector("code, pre");
|
|
99
|
+
if (codeEl) {
|
|
100
|
+
const lang = langEl ? langEl.innerText.trim().toLowerCase() : "";
|
|
101
|
+
const codeText = codeEl.innerText || codeEl.textContent;
|
|
102
|
+
const newPre = document.createElement("pre");
|
|
103
|
+
const newCode = document.createElement("code");
|
|
104
|
+
if (lang) {
|
|
105
|
+
newCode.className = `language-${lang}`;
|
|
106
|
+
}
|
|
107
|
+
newCode.textContent = codeText;
|
|
108
|
+
newPre.appendChild(newCode);
|
|
109
|
+
block.replaceWith(newPre);
|
|
102
110
|
}
|
|
103
|
-
|
|
104
|
-
newPre.appendChild(newCode);
|
|
105
|
-
block.replaceWith(newPre);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
111
|
+
});
|
|
108
112
|
|
|
109
113
|
return clone.innerHTML;
|
|
110
114
|
};
|
|
@@ -121,7 +125,8 @@ export class CopilotParser extends ChatParser {
|
|
|
121
125
|
// Filter out nested matches
|
|
122
126
|
if (turnElements.length) {
|
|
123
127
|
turnElements = turnElements.filter(
|
|
124
|
-
(el) =>
|
|
128
|
+
(el) =>
|
|
129
|
+
!turnElements.some((other) => other !== el && other.contains(el)),
|
|
125
130
|
);
|
|
126
131
|
}
|
|
127
132
|
|
|
@@ -145,20 +150,23 @@ export class CopilotParser extends ChatParser {
|
|
|
145
150
|
|
|
146
151
|
if (turnElements.length) {
|
|
147
152
|
turnElements.forEach((node) => {
|
|
148
|
-
const dataContent = node.getAttribute(
|
|
149
|
-
const dataAuthor = (
|
|
150
|
-
|
|
151
|
-
|
|
153
|
+
const dataContent = node.getAttribute("data-content") || "";
|
|
154
|
+
const dataAuthor = (
|
|
155
|
+
node.getAttribute("data-message-author-role") || ""
|
|
156
|
+
).toLowerCase();
|
|
157
|
+
const className =
|
|
158
|
+
typeof node.className === "string" ? node.className : "";
|
|
159
|
+
const testId = node.getAttribute("data-testid") || "";
|
|
152
160
|
|
|
153
161
|
const isUser =
|
|
154
|
-
dataAuthor ===
|
|
155
|
-
testId ===
|
|
156
|
-
testId ===
|
|
157
|
-
testId ===
|
|
158
|
-
className.includes(
|
|
159
|
-
dataContent ===
|
|
160
|
-
className.includes(
|
|
161
|
-
testId ===
|
|
162
|
+
dataAuthor === "user" ||
|
|
163
|
+
testId === "chatQuestion" ||
|
|
164
|
+
testId === "chat-turn-user" ||
|
|
165
|
+
testId === "user-turn" ||
|
|
166
|
+
className.includes("UserMessage") ||
|
|
167
|
+
dataContent === "user-message" ||
|
|
168
|
+
className.includes("user-message") ||
|
|
169
|
+
testId === "user-message";
|
|
162
170
|
|
|
163
171
|
if (isUser) {
|
|
164
172
|
const targetNode =
|
|
@@ -167,24 +175,26 @@ export class CopilotParser extends ChatParser {
|
|
|
167
175
|
) || node;
|
|
168
176
|
const clone = targetNode.cloneNode(true);
|
|
169
177
|
clone
|
|
170
|
-
.querySelectorAll(
|
|
178
|
+
.querySelectorAll(
|
|
179
|
+
".fai-UserMessage__accessibleHeading, .sr-only, button",
|
|
180
|
+
)
|
|
171
181
|
.forEach((el) => el.remove());
|
|
172
182
|
const text = clone.innerText || clone.textContent;
|
|
173
183
|
if (text && text.trim()) {
|
|
174
|
-
messages.push({ role:
|
|
184
|
+
messages.push({ role: "User", content: text.trim() });
|
|
175
185
|
}
|
|
176
186
|
} else {
|
|
177
187
|
const targetNode =
|
|
178
188
|
node.querySelector('[data-testid="markdown-reply"]') ||
|
|
179
189
|
node.querySelector('[data-testid="ai-message-body"]') ||
|
|
180
190
|
node.querySelector('[data-testid="copilot-message-content"]') ||
|
|
181
|
-
node.querySelector(
|
|
191
|
+
node.querySelector(".fai-CopilotMessage__content") ||
|
|
182
192
|
node.querySelector('[class*="group/ai-message-item"]') ||
|
|
183
193
|
node;
|
|
184
194
|
const html = processAiElement(targetNode);
|
|
185
195
|
const markdown = convertToMarkdown(html);
|
|
186
196
|
if (markdown && markdown.trim()) {
|
|
187
|
-
messages.push({ role:
|
|
197
|
+
messages.push({ role: "Copilot", content: markdown });
|
|
188
198
|
}
|
|
189
199
|
}
|
|
190
200
|
});
|
|
@@ -193,39 +203,45 @@ export class CopilotParser extends ChatParser {
|
|
|
193
203
|
// Helper to extract messages from Shadow DOM cib-serp components (Classic Bing Chat / Copilot)
|
|
194
204
|
const extractFromCibSerp = (rootDoc = document) => {
|
|
195
205
|
try {
|
|
196
|
-
const cibSerp = rootDoc.querySelector(
|
|
206
|
+
const cibSerp = rootDoc.querySelector("cib-serp");
|
|
197
207
|
if (!cibSerp || !cibSerp.shadowRoot) return [];
|
|
198
|
-
const cibConversation =
|
|
208
|
+
const cibConversation =
|
|
209
|
+
cibSerp.shadowRoot.querySelector("cib-conversation");
|
|
199
210
|
if (!cibConversation || !cibConversation.shadowRoot) return [];
|
|
200
|
-
const cibTurns =
|
|
211
|
+
const cibTurns =
|
|
212
|
+
cibConversation.shadowRoot.querySelectorAll("cib-chat-turn");
|
|
201
213
|
const results = [];
|
|
202
214
|
|
|
203
215
|
cibTurns.forEach((turn) => {
|
|
204
216
|
const turnRoot = turn.shadowRoot || turn;
|
|
205
|
-
const msgGroups = turnRoot.querySelectorAll(
|
|
217
|
+
const msgGroups = turnRoot.querySelectorAll("cib-message-group");
|
|
206
218
|
msgGroups.forEach((group) => {
|
|
207
|
-
const source = (group.getAttribute(
|
|
208
|
-
const role = source ===
|
|
219
|
+
const source = (group.getAttribute("source") || "").toLowerCase();
|
|
220
|
+
const role = source === "user" ? "User" : "Copilot";
|
|
209
221
|
const groupRoot = group.shadowRoot || group;
|
|
210
|
-
const cibMessages = groupRoot.querySelectorAll(
|
|
222
|
+
const cibMessages = groupRoot.querySelectorAll("cib-message");
|
|
211
223
|
|
|
212
224
|
cibMessages.forEach((msg) => {
|
|
213
225
|
const msgRoot = msg.shadowRoot || msg;
|
|
214
|
-
if (role ===
|
|
226
|
+
if (role === "User") {
|
|
215
227
|
const text = msgRoot.textContent?.trim();
|
|
216
|
-
if (text) results.push({ role:
|
|
228
|
+
if (text) results.push({ role: "User", content: text });
|
|
217
229
|
} else {
|
|
218
|
-
const shared = msgRoot.querySelector(
|
|
230
|
+
const shared = msgRoot.querySelector("cib-shared") || msgRoot;
|
|
219
231
|
const html = processAiElement(shared);
|
|
220
232
|
const md = convertToMarkdown(html);
|
|
221
|
-
if (md && md.trim())
|
|
233
|
+
if (md && md.trim())
|
|
234
|
+
results.push({ role: "Copilot", content: md.trim() });
|
|
222
235
|
}
|
|
223
236
|
});
|
|
224
237
|
});
|
|
225
238
|
});
|
|
226
239
|
return results;
|
|
227
240
|
} catch (err) {
|
|
228
|
-
console.warn(
|
|
241
|
+
console.warn(
|
|
242
|
+
"[AI Exporter] Shadow DOM cib-serp extraction error:",
|
|
243
|
+
err,
|
|
244
|
+
);
|
|
229
245
|
return [];
|
|
230
246
|
}
|
|
231
247
|
};
|
|
@@ -240,21 +256,21 @@ export class CopilotParser extends ChatParser {
|
|
|
240
256
|
|
|
241
257
|
// Tier 5: Web components in light DOM (cib-chat-turn / cib-message-group)
|
|
242
258
|
if (!messages.length) {
|
|
243
|
-
const cibTurns = document.querySelectorAll(
|
|
259
|
+
const cibTurns = document.querySelectorAll("cib-chat-turn");
|
|
244
260
|
if (cibTurns.length) {
|
|
245
261
|
cibTurns.forEach((turn) => {
|
|
246
|
-
const source = (turn.getAttribute(
|
|
247
|
-
const role = source ===
|
|
248
|
-
if (role ===
|
|
262
|
+
const source = (turn.getAttribute("source") || "").toLowerCase();
|
|
263
|
+
const role = source === "user" ? "User" : "Copilot";
|
|
264
|
+
if (role === "User") {
|
|
249
265
|
const text = turn.innerText || turn.textContent;
|
|
250
266
|
if (text && text.trim()) {
|
|
251
|
-
messages.push({ role:
|
|
267
|
+
messages.push({ role: "User", content: text.trim() });
|
|
252
268
|
}
|
|
253
269
|
} else {
|
|
254
270
|
const html = processAiElement(turn);
|
|
255
271
|
const markdown = convertToMarkdown(html);
|
|
256
272
|
if (markdown && markdown.trim()) {
|
|
257
|
-
messages.push({ role:
|
|
273
|
+
messages.push({ role: "Copilot", content: markdown });
|
|
258
274
|
}
|
|
259
275
|
}
|
|
260
276
|
});
|
|
@@ -263,25 +279,25 @@ export class CopilotParser extends ChatParser {
|
|
|
263
279
|
|
|
264
280
|
// Tier 6: React [data-turn-id] layout
|
|
265
281
|
if (!messages.length) {
|
|
266
|
-
const turnNodes = document.querySelectorAll(
|
|
282
|
+
const turnNodes = document.querySelectorAll("[data-turn-id]");
|
|
267
283
|
turnNodes.forEach((node) => {
|
|
268
284
|
const roleAttr = (
|
|
269
|
-
node.getAttribute(
|
|
270
|
-
node.getAttribute(
|
|
271
|
-
|
|
285
|
+
node.getAttribute("data-turn-role") ||
|
|
286
|
+
node.getAttribute("data-author") ||
|
|
287
|
+
""
|
|
272
288
|
).toLowerCase();
|
|
273
|
-
const role = roleAttr.includes(
|
|
289
|
+
const role = roleAttr.includes("user") ? "User" : "Copilot";
|
|
274
290
|
|
|
275
|
-
if (role ===
|
|
291
|
+
if (role === "User") {
|
|
276
292
|
const text = node.innerText || node.textContent;
|
|
277
293
|
if (text && text.trim()) {
|
|
278
|
-
messages.push({ role:
|
|
294
|
+
messages.push({ role: "User", content: text.trim() });
|
|
279
295
|
}
|
|
280
296
|
} else {
|
|
281
297
|
const html = processAiElement(node);
|
|
282
298
|
const markdown = convertToMarkdown(html);
|
|
283
299
|
if (markdown && markdown.trim()) {
|
|
284
|
-
messages.push({ role:
|
|
300
|
+
messages.push({ role: "Copilot", content: markdown });
|
|
285
301
|
}
|
|
286
302
|
}
|
|
287
303
|
});
|
|
@@ -289,51 +305,58 @@ export class CopilotParser extends ChatParser {
|
|
|
289
305
|
|
|
290
306
|
// Tier 7: Universal role="article" scanner for Copilot
|
|
291
307
|
if (!messages.length) {
|
|
292
|
-
const articles = Array.from(
|
|
308
|
+
const articles = Array.from(
|
|
309
|
+
document.querySelectorAll('[role="article"]'),
|
|
310
|
+
);
|
|
293
311
|
articles.forEach((art) => {
|
|
294
312
|
const headingText = (
|
|
295
|
-
art.querySelector(
|
|
313
|
+
art.querySelector("h1, h2, h3, h4, h5, h6")?.textContent || ""
|
|
296
314
|
).toLowerCase();
|
|
297
315
|
const ariaLabel = (
|
|
298
|
-
art.getAttribute(
|
|
299
|
-
art.getAttribute(
|
|
300
|
-
|
|
316
|
+
art.getAttribute("aria-label") ||
|
|
317
|
+
art.getAttribute("aria-labelledby") ||
|
|
318
|
+
""
|
|
301
319
|
).toLowerCase();
|
|
302
|
-
const className =
|
|
320
|
+
const className =
|
|
321
|
+
typeof art.className === "string" ? art.className : "";
|
|
303
322
|
|
|
304
323
|
const isUser =
|
|
305
|
-
headingText.includes(
|
|
306
|
-
ariaLabel.includes(
|
|
307
|
-
ariaLabel.includes(
|
|
308
|
-
className.includes(
|
|
309
|
-
art.matches(
|
|
324
|
+
headingText.includes("you said") ||
|
|
325
|
+
ariaLabel.includes("user-message") ||
|
|
326
|
+
ariaLabel.includes("you said") ||
|
|
327
|
+
className.includes("UserMessage") ||
|
|
328
|
+
art.matches(
|
|
329
|
+
'[data-testid="chatQuestion"], [data-testid="user-message"]',
|
|
330
|
+
);
|
|
310
331
|
|
|
311
332
|
const isCopilot =
|
|
312
|
-
headingText.includes(
|
|
313
|
-
ariaLabel.includes(
|
|
314
|
-
ariaLabel.includes(
|
|
315
|
-
className.includes(
|
|
316
|
-
art.matches(
|
|
333
|
+
headingText.includes("copilot said") ||
|
|
334
|
+
ariaLabel.includes("copilot-message") ||
|
|
335
|
+
ariaLabel.includes("copilot said") ||
|
|
336
|
+
className.includes("CopilotMessage") ||
|
|
337
|
+
art.matches(
|
|
338
|
+
'[data-testid="copilot-message-div"], [data-testid="ai-message"]',
|
|
339
|
+
);
|
|
317
340
|
|
|
318
341
|
if (isUser) {
|
|
319
342
|
const clone = art.cloneNode(true);
|
|
320
343
|
clone
|
|
321
|
-
.querySelectorAll(
|
|
344
|
+
.querySelectorAll("h1, h2, h3, h4, h5, h6, .sr-only, button")
|
|
322
345
|
.forEach((el) => el.remove());
|
|
323
346
|
const text = clone.innerText || clone.textContent;
|
|
324
347
|
if (text && text.trim()) {
|
|
325
|
-
messages.push({ role:
|
|
348
|
+
messages.push({ role: "User", content: text.trim() });
|
|
326
349
|
}
|
|
327
350
|
} else if (isCopilot) {
|
|
328
351
|
const targetNode =
|
|
329
352
|
art.querySelector('[data-testid="markdown-reply"]') ||
|
|
330
|
-
art.querySelector(
|
|
353
|
+
art.querySelector(".fai-CopilotMessage__content") ||
|
|
331
354
|
art.querySelector('[data-testid="ai-message-body"]') ||
|
|
332
355
|
art;
|
|
333
356
|
const html = processAiElement(targetNode);
|
|
334
357
|
const markdown = convertToMarkdown(html);
|
|
335
358
|
if (markdown && markdown.trim()) {
|
|
336
|
-
messages.push({ role:
|
|
359
|
+
messages.push({ role: "Copilot", content: markdown });
|
|
337
360
|
}
|
|
338
361
|
}
|
|
339
362
|
});
|
|
@@ -341,10 +364,11 @@ export class CopilotParser extends ChatParser {
|
|
|
341
364
|
|
|
342
365
|
// Tier 8: Check embedded iframe documents (e.g. Edge Sidebar panel iframe)
|
|
343
366
|
if (!messages.length) {
|
|
344
|
-
const iframes = Array.from(document.querySelectorAll(
|
|
367
|
+
const iframes = Array.from(document.querySelectorAll("iframe"));
|
|
345
368
|
for (const iframe of iframes) {
|
|
346
369
|
try {
|
|
347
|
-
const iframeDoc =
|
|
370
|
+
const iframeDoc =
|
|
371
|
+
iframe.contentDocument || iframe.contentWindow?.document;
|
|
348
372
|
if (iframeDoc) {
|
|
349
373
|
const iframeCib = extractFromCibSerp(iframeDoc);
|
|
350
374
|
if (iframeCib.length) {
|
|
@@ -358,19 +382,20 @@ export class CopilotParser extends ChatParser {
|
|
|
358
382
|
);
|
|
359
383
|
if (iframeTurns.length) {
|
|
360
384
|
iframeTurns.forEach((node) => {
|
|
361
|
-
const dataContent = node.getAttribute(
|
|
362
|
-
const testId = node.getAttribute(
|
|
385
|
+
const dataContent = node.getAttribute("data-content") || "";
|
|
386
|
+
const testId = node.getAttribute("data-testid") || "";
|
|
363
387
|
const isUser =
|
|
364
|
-
testId ===
|
|
365
|
-
dataContent ===
|
|
366
|
-
testId ===
|
|
388
|
+
testId === "chatQuestion" ||
|
|
389
|
+
dataContent === "user-message" ||
|
|
390
|
+
testId === "user-message";
|
|
367
391
|
if (isUser) {
|
|
368
392
|
const targetNode =
|
|
369
393
|
node.querySelector(
|
|
370
394
|
'[data-testid="chatOutput"], .fai-UserMessage__message, [data-content="user-message"]',
|
|
371
395
|
) || node;
|
|
372
396
|
const text = targetNode.innerText || targetNode.textContent;
|
|
373
|
-
if (text && text.trim())
|
|
397
|
+
if (text && text.trim())
|
|
398
|
+
messages.push({ role: "User", content: text.trim() });
|
|
374
399
|
} else {
|
|
375
400
|
const targetNode =
|
|
376
401
|
node.querySelector('[data-testid="markdown-reply"]') ||
|
|
@@ -379,7 +404,7 @@ export class CopilotParser extends ChatParser {
|
|
|
379
404
|
const html = processAiElement(targetNode);
|
|
380
405
|
const markdown = convertToMarkdown(html);
|
|
381
406
|
if (markdown && markdown.trim()) {
|
|
382
|
-
messages.push({ role:
|
|
407
|
+
messages.push({ role: "Copilot", content: markdown });
|
|
383
408
|
}
|
|
384
409
|
}
|
|
385
410
|
});
|
|
@@ -393,17 +418,26 @@ export class CopilotParser extends ChatParser {
|
|
|
393
418
|
}
|
|
394
419
|
|
|
395
420
|
// Fallback title generation if default title is generic
|
|
396
|
-
if (
|
|
397
|
-
|
|
421
|
+
if (
|
|
422
|
+
title === "Copilot Conversation" &&
|
|
423
|
+
messages.length > 0 &&
|
|
424
|
+
messages[0].role === "User"
|
|
425
|
+
) {
|
|
426
|
+
const firstPrompt = messages[0].content.split("\n")[0].trim();
|
|
398
427
|
if (firstPrompt) {
|
|
399
|
-
title =
|
|
428
|
+
title =
|
|
429
|
+
firstPrompt.length > 40
|
|
430
|
+
? `${firstPrompt.slice(0, 40)}...`
|
|
431
|
+
: firstPrompt;
|
|
400
432
|
}
|
|
401
433
|
}
|
|
402
434
|
|
|
403
435
|
const currentUrl =
|
|
404
|
-
typeof window !==
|
|
436
|
+
typeof window !== "undefined" && window.location
|
|
437
|
+
? window.location.href || ""
|
|
438
|
+
: "";
|
|
405
439
|
const metadata = {
|
|
406
|
-
Source:
|
|
440
|
+
Source: "Copilot",
|
|
407
441
|
Date: new Date().toLocaleString(),
|
|
408
442
|
Link: currentUrl,
|
|
409
443
|
};
|
package/ai/deepseek.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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
|
function getUserToken() {
|
|
5
5
|
try {
|
|
6
|
-
if (typeof localStorage ===
|
|
7
|
-
const raw = localStorage.getItem(
|
|
6
|
+
if (typeof localStorage === "undefined") return null;
|
|
7
|
+
const raw = localStorage.getItem("userToken");
|
|
8
8
|
if (!raw) return null;
|
|
9
9
|
try {
|
|
10
10
|
const parsed = JSON.parse(raw);
|
|
@@ -19,8 +19,8 @@ function getUserToken() {
|
|
|
19
19
|
|
|
20
20
|
function getConversationId() {
|
|
21
21
|
try {
|
|
22
|
-
if (typeof window ===
|
|
23
|
-
const path = window.location.pathname || window.location.href ||
|
|
22
|
+
if (typeof window === "undefined" || !window.location) return null;
|
|
23
|
+
const path = window.location.pathname || window.location.href || "";
|
|
24
24
|
return (
|
|
25
25
|
path.match(/\/chat\/s\/([a-f0-9-]+)/)?.[1] ??
|
|
26
26
|
path.match(/\/a\/chat\/s\/([a-f0-9-]+)/)?.[1] ??
|
|
@@ -34,10 +34,10 @@ function getConversationId() {
|
|
|
34
34
|
async function fetchDeepSeekConversation(sessionId, token) {
|
|
35
35
|
const url = `https://chat.deepseek.com/api/v0/chat/history_messages?chat_session_id=${sessionId}&cache_version=0`;
|
|
36
36
|
const response = await fetch(url, {
|
|
37
|
-
method:
|
|
38
|
-
credentials:
|
|
37
|
+
method: "GET",
|
|
38
|
+
credentials: "include",
|
|
39
39
|
headers: {
|
|
40
|
-
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
41
|
Authorization: `Bearer ${token}`,
|
|
42
42
|
},
|
|
43
43
|
});
|
|
@@ -74,27 +74,29 @@ async function fetchDeepSeekConversation(sessionId, token) {
|
|
|
74
74
|
|
|
75
75
|
return branch
|
|
76
76
|
.map((msgNode) => {
|
|
77
|
-
const isUser = msgNode.role ===
|
|
78
|
-
const role = isUser ?
|
|
79
|
-
const content = msgNode.content || msgNode.text ||
|
|
77
|
+
const isUser = msgNode.role === "USER" || msgNode.role === "user";
|
|
78
|
+
const role = isUser ? "User" : "DeepSeek";
|
|
79
|
+
const content = msgNode.content || msgNode.text || "";
|
|
80
80
|
return { role, content: content.trim() };
|
|
81
81
|
})
|
|
82
82
|
.filter((msg) => msg.content.length > 0);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
export class DeepSeekParser extends ChatParser {
|
|
86
|
-
name =
|
|
86
|
+
name = "DeepSeek";
|
|
87
87
|
isAvailable(url) {
|
|
88
|
-
return url.includes(
|
|
88
|
+
return url.includes("chat.deepseek.com");
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
async parse() {
|
|
92
|
-
const title = document.title ||
|
|
92
|
+
const title = document.title || "DeepSeek Chat";
|
|
93
93
|
|
|
94
94
|
const currentUrl =
|
|
95
|
-
typeof window !==
|
|
95
|
+
typeof window !== "undefined" && window.location
|
|
96
|
+
? window.location.href || ""
|
|
97
|
+
: "";
|
|
96
98
|
const metadata = {
|
|
97
|
-
Source:
|
|
99
|
+
Source: "DeepSeek",
|
|
98
100
|
Date: new Date().toLocaleString(),
|
|
99
101
|
Link: currentUrl,
|
|
100
102
|
};
|
|
@@ -110,25 +112,30 @@ export class DeepSeekParser extends ChatParser {
|
|
|
110
112
|
}
|
|
111
113
|
}
|
|
112
114
|
} catch (e) {
|
|
113
|
-
console.warn(
|
|
115
|
+
console.warn(
|
|
116
|
+
"[AI Exporter] DeepSeek API fetch failed, falling back to DOM:",
|
|
117
|
+
e,
|
|
118
|
+
);
|
|
114
119
|
}
|
|
115
120
|
|
|
116
121
|
// Secondary: DOM Fallback
|
|
117
122
|
const messages = [];
|
|
118
123
|
|
|
119
124
|
// Selectors from research
|
|
120
|
-
const userSelector =
|
|
121
|
-
const assistantSelector =
|
|
125
|
+
const userSelector = ".fbb737a4";
|
|
126
|
+
const assistantSelector = ".ds-markdown";
|
|
122
127
|
|
|
123
128
|
// We'll traverse the DOM to find these in order
|
|
124
|
-
const allElements = document.querySelectorAll(
|
|
129
|
+
const allElements = document.querySelectorAll(
|
|
130
|
+
`${userSelector}, ${assistantSelector}`,
|
|
131
|
+
);
|
|
125
132
|
|
|
126
133
|
allElements.forEach((el) => {
|
|
127
|
-
let role =
|
|
134
|
+
let role = "Unknown";
|
|
128
135
|
if (el.matches(userSelector)) {
|
|
129
|
-
role =
|
|
136
|
+
role = "User";
|
|
130
137
|
} else if (el.matches(assistantSelector)) {
|
|
131
|
-
role =
|
|
138
|
+
role = "DeepSeek";
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
const text = convertToMarkdown(el);
|
|
@@ -139,10 +146,12 @@ export class DeepSeekParser extends ChatParser {
|
|
|
139
146
|
|
|
140
147
|
// Fallback if the specific classes fail (e.g. class name rotation)
|
|
141
148
|
if (messages.length === 0) {
|
|
142
|
-
const messageRows = document.querySelectorAll(
|
|
149
|
+
const messageRows = document.querySelectorAll(
|
|
150
|
+
".ds-message-row, .message-row",
|
|
151
|
+
);
|
|
143
152
|
messageRows.forEach((row) => {
|
|
144
|
-
const isUser = row.classList.contains(
|
|
145
|
-
const role = isUser ?
|
|
153
|
+
const isUser = row.classList.contains("ds-user-message");
|
|
154
|
+
const role = isUser ? "User" : "DeepSeek";
|
|
146
155
|
const text = convertToMarkdown(row);
|
|
147
156
|
if (text.trim()) {
|
|
148
157
|
messages.push({ role, content: text.trim() });
|