decant-core 1.2.0 → 1.2.2
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/gemini.js +569 -877
- package/package.json +1 -1
package/ai/gemini.js
CHANGED
|
@@ -1,1012 +1,704 @@
|
|
|
1
1
|
import { ChatParser } from "./base.js";
|
|
2
2
|
import { convertToMarkdown } from "../utils/html-to-markdown.js";
|
|
3
3
|
|
|
4
|
+
const GEMINI_RPC_ID = "hNvQHb";
|
|
5
|
+
const DEFAULT_BARD_PATH = "/_/BardChatUi";
|
|
6
|
+
|
|
7
|
+
function isValidMessageText(str, convoId = "") {
|
|
8
|
+
if (typeof str !== "string") return false;
|
|
9
|
+
const trimmed = str.trim();
|
|
10
|
+
if (!trimmed) return false;
|
|
11
|
+
if (/^(?:c_|rc_|r_)[a-zA-Z0-9_-]+$/.test(trimmed)) return false;
|
|
12
|
+
if (convoId && (trimmed === convoId || trimmed === `c_${convoId}`))
|
|
13
|
+
return false;
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
4
17
|
export class GeminiParser extends ChatParser {
|
|
5
18
|
name = "Gemini";
|
|
19
|
+
|
|
6
20
|
isAvailable(url) {
|
|
7
|
-
return
|
|
21
|
+
return (
|
|
22
|
+
typeof url === "string" &&
|
|
23
|
+
(url.includes("gemini.google.com") || url.includes("bard.google.com"))
|
|
24
|
+
);
|
|
8
25
|
}
|
|
9
26
|
|
|
10
|
-
|
|
11
|
-
|
|
27
|
+
getPlatformName() {
|
|
28
|
+
return "Gemini";
|
|
29
|
+
}
|
|
12
30
|
|
|
31
|
+
getConversationId(url) {
|
|
13
32
|
try {
|
|
14
|
-
|
|
15
|
-
|
|
33
|
+
const targetUrl =
|
|
34
|
+
url ||
|
|
35
|
+
(typeof window !== "undefined" && window.location
|
|
36
|
+
? window.location.href
|
|
37
|
+
: "");
|
|
38
|
+
if (!targetUrl) return null;
|
|
39
|
+
const parsed = new URL(
|
|
40
|
+
targetUrl,
|
|
41
|
+
typeof location !== "undefined"
|
|
42
|
+
? location.origin
|
|
43
|
+
: "https://gemini.google.com",
|
|
44
|
+
);
|
|
45
|
+
const match = parsed.pathname.match(/\/(?:app|share)\/([a-zA-Z0-9_-]+)/);
|
|
46
|
+
return match ? match[1] : null;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
16
51
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
52
|
+
getGlobalData() {
|
|
53
|
+
try {
|
|
54
|
+
// 1. Try direct window access if present
|
|
55
|
+
if (
|
|
56
|
+
typeof window !== "undefined" &&
|
|
57
|
+
window.WIZ_global_data &&
|
|
58
|
+
typeof window.WIZ_global_data === "object"
|
|
59
|
+
) {
|
|
60
|
+
return window.WIZ_global_data;
|
|
61
|
+
}
|
|
22
62
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
.
|
|
28
|
-
.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
63
|
+
// 2. Try parsing inline script tags for WIZ_global_data
|
|
64
|
+
if (typeof document !== "undefined" && document.querySelectorAll) {
|
|
65
|
+
const scripts = document.querySelectorAll("script");
|
|
66
|
+
for (let i = 0; i < scripts.length; i++) {
|
|
67
|
+
const content = scripts[i].textContent || "";
|
|
68
|
+
if (content.includes("WIZ_global_data")) {
|
|
69
|
+
const match = content.match(
|
|
70
|
+
/window\.WIZ_global_data\s*=\s*(\{[\s\S]*?\});/,
|
|
71
|
+
);
|
|
72
|
+
if (match && match[1]) {
|
|
73
|
+
try {
|
|
74
|
+
return JSON.parse(match[1]);
|
|
75
|
+
} catch {
|
|
76
|
+
// Continue to next script
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.warn("[Gemini Parser] Error reading global data:", e);
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
33
87
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
88
|
+
async parse(options = {}) {
|
|
89
|
+
console.log("[Gemini Parser] ========== STARTING PARSE() ==========");
|
|
90
|
+
const currentUrl =
|
|
91
|
+
typeof window !== "undefined" && window.location
|
|
92
|
+
? window.location.href || ""
|
|
93
|
+
: "";
|
|
39
94
|
|
|
40
|
-
|
|
41
|
-
|
|
95
|
+
const mode = options.parserMode || "auto";
|
|
96
|
+
|
|
97
|
+
// 1. Prefer DOM extraction first when on a live page with conversation containers
|
|
98
|
+
if (typeof document !== "undefined" && document.querySelector) {
|
|
99
|
+
const hasDomMessages = document.querySelector(
|
|
100
|
+
".conversation-container, user-query, model-response, deep-research-immersive-panel",
|
|
101
|
+
);
|
|
102
|
+
if (hasDomMessages && mode !== "api") {
|
|
103
|
+
const domResult = this.parseFromDom(currentUrl, options);
|
|
104
|
+
if (domResult && domResult.messages && domResult.messages.length > 0) {
|
|
42
105
|
console.log(
|
|
43
|
-
|
|
44
|
-
title,
|
|
106
|
+
`[Gemini Parser] Successfully parsed ${domResult.messages.length} messages from DOM`,
|
|
45
107
|
);
|
|
108
|
+
return domResult;
|
|
46
109
|
}
|
|
47
110
|
}
|
|
111
|
+
}
|
|
48
112
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
113
|
+
// 2. Attempt API / RPC extraction if DOM parsing didn't find messages or mode is API
|
|
114
|
+
if (mode !== "dom" && typeof fetch === "function") {
|
|
115
|
+
try {
|
|
116
|
+
const convoId = this.getConversationId(currentUrl);
|
|
117
|
+
const globalData = this.getGlobalData();
|
|
118
|
+
|
|
119
|
+
if (convoId && globalData && globalData.SNlM0e && globalData.FdrFJe) {
|
|
120
|
+
console.log(
|
|
121
|
+
"[Gemini Parser] Attempting API extraction for convo:",
|
|
122
|
+
convoId,
|
|
123
|
+
);
|
|
124
|
+
const apiResult = await this.fetchFromApi(
|
|
125
|
+
convoId,
|
|
126
|
+
globalData,
|
|
127
|
+
currentUrl,
|
|
128
|
+
options,
|
|
129
|
+
);
|
|
62
130
|
if (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
131
|
+
apiResult &&
|
|
132
|
+
apiResult.messages &&
|
|
133
|
+
apiResult.messages.length > 0 &&
|
|
134
|
+
apiResult.messages.some((m) =>
|
|
135
|
+
isValidMessageText(m.content, convoId),
|
|
136
|
+
)
|
|
66
137
|
) {
|
|
67
|
-
title = navText;
|
|
68
138
|
console.log(
|
|
69
|
-
|
|
70
|
-
title,
|
|
139
|
+
`[Gemini Parser] Successfully parsed ${apiResult.messages.length} messages via API`,
|
|
71
140
|
);
|
|
141
|
+
return apiResult;
|
|
72
142
|
}
|
|
73
143
|
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.warn(
|
|
146
|
+
"[Gemini Parser] API extraction failed, falling back to DOM:",
|
|
147
|
+
err,
|
|
148
|
+
);
|
|
74
149
|
}
|
|
150
|
+
}
|
|
75
151
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
152
|
+
// Fall back to robust DOM parsing
|
|
153
|
+
return this.parseFromDom(currentUrl, options);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async fetchFromApi(convoId, globalData, currentUrl, options = {}) {
|
|
157
|
+
const fSid = globalData.FdrFJe || "";
|
|
158
|
+
const bl = globalData.cfb2h || "";
|
|
159
|
+
const prefix = globalData.Im6cmf || DEFAULT_BARD_PATH;
|
|
160
|
+
const atToken = globalData.SNlM0e || "";
|
|
161
|
+
|
|
162
|
+
const reqId = String(Math.floor(9e6 * Math.random()) + 1e6);
|
|
163
|
+
const sourcePath =
|
|
164
|
+
typeof window !== "undefined" && window.location
|
|
165
|
+
? window.location.pathname
|
|
166
|
+
: `/app/${convoId}`;
|
|
167
|
+
|
|
168
|
+
const endpoint =
|
|
169
|
+
`https://gemini.google.com${prefix}/data/batchexecute` +
|
|
170
|
+
`?rpcids=${encodeURIComponent(GEMINI_RPC_ID)}` +
|
|
171
|
+
`&source-path=${encodeURIComponent(sourcePath)}` +
|
|
172
|
+
`&bl=${encodeURIComponent(bl)}` +
|
|
173
|
+
`&f.sid=${encodeURIComponent(fSid)}` +
|
|
174
|
+
`&hl=en` +
|
|
175
|
+
`&_reqid=${encodeURIComponent(reqId)}` +
|
|
176
|
+
`&rt=c`;
|
|
177
|
+
|
|
178
|
+
const allItems = [];
|
|
179
|
+
let cursor = null;
|
|
180
|
+
let pageCount = 0;
|
|
181
|
+
|
|
182
|
+
while (pageCount < 50) {
|
|
183
|
+
pageCount++;
|
|
184
|
+
const payloadArg = JSON.stringify([
|
|
185
|
+
`c_${convoId}`,
|
|
186
|
+
100,
|
|
187
|
+
cursor,
|
|
188
|
+
1,
|
|
189
|
+
[0],
|
|
190
|
+
[4],
|
|
191
|
+
null,
|
|
192
|
+
1,
|
|
193
|
+
]);
|
|
194
|
+
|
|
195
|
+
const formParams = new URLSearchParams();
|
|
196
|
+
formParams.append(
|
|
197
|
+
"f.req",
|
|
198
|
+
JSON.stringify([[[GEMINI_RPC_ID, payloadArg, null, "generic"]]]),
|
|
199
|
+
);
|
|
200
|
+
formParams.append("at", atToken);
|
|
201
|
+
|
|
202
|
+
const resp = await fetch(endpoint, {
|
|
203
|
+
method: "POST",
|
|
204
|
+
credentials: "include",
|
|
205
|
+
headers: {
|
|
206
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
207
|
+
},
|
|
208
|
+
body: formParams.toString(),
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
if (!resp.ok) {
|
|
212
|
+
throw new Error(
|
|
213
|
+
`Gemini RPC request failed: ${resp.status} ${resp.statusText}`,
|
|
83
214
|
);
|
|
84
|
-
if (deepResearchTitle && !isInsideMessage(deepResearchTitle)) {
|
|
85
|
-
const text = deepResearchTitle.innerText.trim();
|
|
86
|
-
console.log("[Gemini Parser] Found potential title:", text);
|
|
87
|
-
if (
|
|
88
|
-
text.length > 5 &&
|
|
89
|
-
!text.includes("Gemini") &&
|
|
90
|
-
!text.includes("Help") &&
|
|
91
|
-
!text.includes("Settings")
|
|
92
|
-
) {
|
|
93
|
-
title = text;
|
|
94
|
-
console.log("[Gemini Parser] Title set from Strategy 3:", title);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
215
|
}
|
|
98
216
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
"main h1, main h2, article h1, article h2, .content h1, .content h2",
|
|
106
|
-
);
|
|
107
|
-
console.log(
|
|
108
|
-
"[Gemini Parser] Found content titles:",
|
|
109
|
-
contentTitles.length,
|
|
110
|
-
);
|
|
111
|
-
for (const el of contentTitles) {
|
|
112
|
-
if (isInsideMessage(el)) continue;
|
|
113
|
-
const text = el.innerText.trim();
|
|
114
|
-
if (
|
|
115
|
-
text.length > 5 &&
|
|
116
|
-
!text.includes("Gemini") &&
|
|
117
|
-
!text.includes("Help") &&
|
|
118
|
-
!text.includes("Settings") &&
|
|
119
|
-
!text.includes("Prompt:") &&
|
|
120
|
-
!text.includes("Response:")
|
|
121
|
-
) {
|
|
122
|
-
title = text;
|
|
123
|
-
console.log("[Gemini Parser] Title set from Strategy 4:", title);
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
217
|
+
const rawText = await resp.text();
|
|
218
|
+
const parsedBatch = this.parseBatchExecuteLines(rawText);
|
|
219
|
+
const rpcEntry = this.findRpcEntry(parsedBatch.arrays, GEMINI_RPC_ID);
|
|
220
|
+
|
|
221
|
+
if (!rpcEntry || !rpcEntry[2]) {
|
|
222
|
+
break;
|
|
127
223
|
}
|
|
128
224
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
"[Gemini Parser] Strategy 5: Looking for title in top bar/sidebar...",
|
|
133
|
-
);
|
|
134
|
-
const possibleHeaders = document.querySelectorAll(
|
|
135
|
-
'h1, button[aria-haspopup="true"], button[aria-expanded]',
|
|
136
|
-
);
|
|
137
|
-
console.log(
|
|
138
|
-
"[Gemini Parser] Found possible headers:",
|
|
139
|
-
possibleHeaders.length,
|
|
140
|
-
);
|
|
225
|
+
const payload = JSON.parse(rpcEntry[2]);
|
|
226
|
+
const items = Array.isArray(payload[0]) ? payload[0] : [];
|
|
227
|
+
const continueCursor = payload[1] || null;
|
|
141
228
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
229
|
+
if (items.length > 0) {
|
|
230
|
+
// Items are in reverse chronological order from API
|
|
231
|
+
allItems.unshift(...items.slice().reverse());
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!continueCursor || items.length < 100) {
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
cursor = continueCursor;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (allItems.length === 0) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const messages = this.convertApiItemsToMessages(allItems, options);
|
|
245
|
+
let title = this.extractTitleFromPage();
|
|
246
|
+
if (!title || title === "Gemini Conversation") {
|
|
247
|
+
const firstUserMsg = messages.find((m) => m.role === "User");
|
|
248
|
+
if (firstUserMsg && firstUserMsg.content) {
|
|
249
|
+
title = firstUserMsg.content.slice(0, 50).split("\n")[0].trim();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
title: title || "Gemini Conversation",
|
|
255
|
+
messages,
|
|
256
|
+
url: currentUrl,
|
|
257
|
+
metadata: {
|
|
258
|
+
Source: "Gemini",
|
|
259
|
+
Date: new Date().toLocaleString(),
|
|
260
|
+
Link: currentUrl,
|
|
261
|
+
Method: "API",
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
parseBatchExecuteLines(raw) {
|
|
267
|
+
const cleaned = String(raw || "").replace(/^\)\]\}'\s*\n/, "");
|
|
268
|
+
const lines = cleaned
|
|
269
|
+
.split("\n")
|
|
270
|
+
.map((l) => l.trim())
|
|
271
|
+
.filter(Boolean);
|
|
272
|
+
const arrays = [];
|
|
273
|
+
for (const line of lines) {
|
|
274
|
+
if (!/^\d+$/.test(line) && line.startsWith("[") && line.endsWith("]")) {
|
|
275
|
+
try {
|
|
276
|
+
arrays.push(JSON.parse(line));
|
|
277
|
+
} catch {
|
|
278
|
+
// Ignore non-JSON lines
|
|
158
279
|
}
|
|
159
280
|
}
|
|
281
|
+
}
|
|
282
|
+
return { arrays, rawData: cleaned };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
findRpcEntry(arrays, rpcId, envelope = "wrb.fr") {
|
|
286
|
+
if (!Array.isArray(arrays)) return null;
|
|
287
|
+
if (arrays[0] === envelope && arrays[1] === rpcId && arrays[2]) {
|
|
288
|
+
return arrays;
|
|
289
|
+
}
|
|
290
|
+
for (const item of arrays) {
|
|
291
|
+
if (Array.isArray(item)) {
|
|
292
|
+
const found = this.findRpcEntry(item, rpcId, envelope);
|
|
293
|
+
if (found) return found;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
convertApiItemsToMessages(items, options = {}) {
|
|
300
|
+
const messages = [];
|
|
301
|
+
|
|
302
|
+
for (const item of items) {
|
|
303
|
+
if (!Array.isArray(item)) continue;
|
|
304
|
+
|
|
305
|
+
const userText = this.findUserTextInApiItem(item);
|
|
306
|
+
if (userText) {
|
|
307
|
+
messages.push({
|
|
308
|
+
role: "User",
|
|
309
|
+
content: userText.trim(),
|
|
310
|
+
});
|
|
311
|
+
}
|
|
160
312
|
|
|
161
|
-
|
|
162
|
-
|
|
313
|
+
const modelText = this.findModelTextInApiItem(item, options);
|
|
314
|
+
if (modelText) {
|
|
315
|
+
messages.push({
|
|
316
|
+
role: "Model",
|
|
317
|
+
content: modelText.trim(),
|
|
318
|
+
});
|
|
163
319
|
}
|
|
320
|
+
}
|
|
164
321
|
|
|
165
|
-
|
|
322
|
+
return messages;
|
|
323
|
+
}
|
|
166
324
|
|
|
167
|
-
|
|
168
|
-
|
|
325
|
+
findUserTextInApiItem(item) {
|
|
326
|
+
try {
|
|
327
|
+
if (typeof item[2]?.[0] === "string") return item[2][0];
|
|
328
|
+
if (typeof item[1]?.[0] === "string" && !Array.isArray(item[1][0]))
|
|
329
|
+
return item[1][0];
|
|
330
|
+
if (typeof item[0]?.[0] === "string") return item[0][0];
|
|
331
|
+
} catch {
|
|
332
|
+
// Fall through
|
|
333
|
+
}
|
|
334
|
+
return "";
|
|
335
|
+
}
|
|
169
336
|
|
|
170
|
-
|
|
171
|
-
|
|
337
|
+
findModelTextInApiItem(item) {
|
|
338
|
+
try {
|
|
339
|
+
if (Array.isArray(item[1])) {
|
|
340
|
+
const candidate = item[1][0];
|
|
341
|
+
if (typeof candidate === "string") return candidate;
|
|
342
|
+
if (Array.isArray(candidate)) {
|
|
343
|
+
if (typeof candidate[1]?.[0] === "string") return candidate[1][0];
|
|
344
|
+
if (typeof candidate[0] === "string") return candidate[0];
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} catch {
|
|
348
|
+
// Fall through
|
|
349
|
+
}
|
|
350
|
+
return "";
|
|
351
|
+
}
|
|
172
352
|
|
|
173
|
-
|
|
174
|
-
|
|
353
|
+
extractTitleFromPage() {
|
|
354
|
+
if (typeof document !== "undefined" && document.title) {
|
|
355
|
+
const cleanedDocTitle = document.title
|
|
356
|
+
.replace(/Google/g, "")
|
|
357
|
+
.replace(/Gemini/g, "")
|
|
358
|
+
.replace(/Advanced/g, "")
|
|
359
|
+
.replace(/- /g, "")
|
|
360
|
+
.replace(/—/g, "")
|
|
361
|
+
.trim();
|
|
362
|
+
|
|
363
|
+
const isGeneric =
|
|
364
|
+
!cleanedDocTitle ||
|
|
365
|
+
cleanedDocTitle.toLowerCase() === "new chat" ||
|
|
366
|
+
cleanedDocTitle.toLowerCase() === "help" ||
|
|
367
|
+
cleanedDocTitle.toLowerCase() === "settings";
|
|
368
|
+
|
|
369
|
+
if (cleanedDocTitle && !isGeneric && cleanedDocTitle.length > 2) {
|
|
370
|
+
return cleanedDocTitle;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
175
373
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
"
|
|
179
|
-
);
|
|
180
|
-
const conversationContainers = document.querySelectorAll(
|
|
181
|
-
".conversation-container",
|
|
374
|
+
if (typeof document !== "undefined" && document.querySelector) {
|
|
375
|
+
const activeNav = document.querySelector(
|
|
376
|
+
'a[aria-current="page"], .selected',
|
|
182
377
|
);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
378
|
+
if (activeNav) {
|
|
379
|
+
const navText = (activeNav.textContent || activeNav.innerText || "")
|
|
380
|
+
.replace(/more_vert/g, "")
|
|
381
|
+
.replace(/\n/g, " ")
|
|
382
|
+
.trim();
|
|
383
|
+
if (
|
|
384
|
+
navText &&
|
|
385
|
+
navText.length > 2 &&
|
|
386
|
+
!navText.toLowerCase().includes("new chat")
|
|
387
|
+
) {
|
|
388
|
+
return navText;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const deepResearchTitle = document.querySelector(
|
|
393
|
+
'h1, .title, .conversation-title, [data-testid="title"]',
|
|
186
394
|
);
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
'.cdk-visually-hidden, [class*="screen-reader"]',
|
|
204
|
-
)
|
|
205
|
-
.forEach((el) => el.remove());
|
|
206
|
-
const userText = clone.innerText.trim();
|
|
207
|
-
if (userText && !seenTexts.has(userText)) {
|
|
208
|
-
seenTexts.add(userText);
|
|
209
|
-
messages.push({
|
|
210
|
-
role: "User",
|
|
211
|
-
content: userText,
|
|
212
|
-
});
|
|
213
|
-
console.log(
|
|
214
|
-
"[Gemini Parser] Added user message:",
|
|
215
|
-
userText.substring(0, 50) + "...",
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
395
|
+
if (deepResearchTitle && !this.isInsideMessage(deepResearchTitle)) {
|
|
396
|
+
const text = (
|
|
397
|
+
deepResearchTitle.textContent ||
|
|
398
|
+
deepResearchTitle.innerText ||
|
|
399
|
+
""
|
|
400
|
+
).trim();
|
|
401
|
+
if (
|
|
402
|
+
text.length > 5 &&
|
|
403
|
+
!text.includes("Gemini") &&
|
|
404
|
+
!text.includes("Help") &&
|
|
405
|
+
!text.includes("Settings")
|
|
406
|
+
) {
|
|
407
|
+
return text;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
220
411
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (modelResponse) {
|
|
224
|
-
console.log("[Gemini Parser] Found model response...");
|
|
225
|
-
const messageContent =
|
|
226
|
-
modelResponse.querySelector("message-content");
|
|
227
|
-
if (messageContent) {
|
|
228
|
-
console.log("[Gemini Parser] Found message content...");
|
|
229
|
-
const markdownDiv = messageContent.querySelector(
|
|
230
|
-
".markdown.markdown-main-panel, .markdown",
|
|
231
|
-
);
|
|
232
|
-
if (markdownDiv) {
|
|
233
|
-
console.log("[Gemini Parser] Found markdown div...");
|
|
234
|
-
// Clone to avoid modifying the original DOM
|
|
235
|
-
const clone = markdownDiv.cloneNode(true);
|
|
236
|
-
|
|
237
|
-
// Remove UI elements that shouldn't be in the export
|
|
238
|
-
clone
|
|
239
|
-
.querySelectorAll(
|
|
240
|
-
"button, .thoughts-container, .thoughts-wrapper, model-thoughts, .table-footer, .hide-from-message-actions",
|
|
241
|
-
)
|
|
242
|
-
.forEach((el) => el.remove());
|
|
243
|
-
|
|
244
|
-
// Remove response-element wrappers (they contain export buttons)
|
|
245
|
-
clone.querySelectorAll("response-element").forEach((el) => {
|
|
246
|
-
// Keep the table but remove the wrapper
|
|
247
|
-
while (el.firstChild) {
|
|
248
|
-
el.parentNode.insertBefore(el.firstChild, el);
|
|
249
|
-
}
|
|
250
|
-
el.remove();
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
// Convert to markdown
|
|
254
|
-
const text = convertToMarkdown(clone);
|
|
255
|
-
console.log(
|
|
256
|
-
"[Gemini Parser] Converted to markdown, length:",
|
|
257
|
-
text.length,
|
|
258
|
-
);
|
|
259
|
-
console.log("[Gemini Parser] Full markdown content:");
|
|
260
|
-
console.log(text);
|
|
261
|
-
console.log("[Gemini Parser] End of markdown content");
|
|
262
|
-
|
|
263
|
-
if (text && text.trim() && !seenTexts.has(text.trim())) {
|
|
264
|
-
seenTexts.add(text.trim());
|
|
265
|
-
messages.push({
|
|
266
|
-
role: "Model",
|
|
267
|
-
content: text.trim(),
|
|
268
|
-
});
|
|
269
|
-
console.log(
|
|
270
|
-
"[Gemini Parser] Added model message:",
|
|
271
|
-
text.substring(0, 50) + "...",
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
} else {
|
|
275
|
-
console.log(
|
|
276
|
-
"[Gemini Parser] No markdown div found, trying comprehensive content extraction...",
|
|
277
|
-
);
|
|
278
|
-
|
|
279
|
-
// Strategy 1: Look for content in nested elements within message-content
|
|
280
|
-
const nestedSelectors = [
|
|
281
|
-
'div[class*="content"]',
|
|
282
|
-
'div[class*="research"]',
|
|
283
|
-
'div[class*="report"]',
|
|
284
|
-
'div[class*="analysis"]',
|
|
285
|
-
'div[class*="section"]',
|
|
286
|
-
'div[class*="paragraph"]',
|
|
287
|
-
"p",
|
|
288
|
-
"article",
|
|
289
|
-
"section",
|
|
290
|
-
];
|
|
291
|
-
|
|
292
|
-
let foundContent = false;
|
|
293
|
-
for (const selector of nestedSelectors) {
|
|
294
|
-
const nestedElements =
|
|
295
|
-
messageContent.querySelectorAll(selector);
|
|
296
|
-
console.log(
|
|
297
|
-
"[Gemini Parser] Looking for nested elements with selector:",
|
|
298
|
-
selector,
|
|
299
|
-
"found:",
|
|
300
|
-
nestedElements.length,
|
|
301
|
-
);
|
|
302
|
-
|
|
303
|
-
nestedElements.forEach((element) => {
|
|
304
|
-
const text = element.innerText.trim();
|
|
305
|
-
if (text.length > 100) {
|
|
306
|
-
console.log(
|
|
307
|
-
"[Gemini Parser] Found nested content, length:",
|
|
308
|
-
text.length,
|
|
309
|
-
);
|
|
310
|
-
console.log(
|
|
311
|
-
"[Gemini Parser] Nested content preview:",
|
|
312
|
-
text.substring(0, 200) + "...",
|
|
313
|
-
);
|
|
314
|
-
|
|
315
|
-
const isDeepResearch =
|
|
316
|
-
text.includes("research") ||
|
|
317
|
-
text.includes("analysis") ||
|
|
318
|
-
text.includes("findings") ||
|
|
319
|
-
text.includes("cost") ||
|
|
320
|
-
text.includes("sweetener") ||
|
|
321
|
-
text.includes("projection") ||
|
|
322
|
-
text.includes("historical") ||
|
|
323
|
-
text.includes("economic") ||
|
|
324
|
-
text.includes("market") ||
|
|
325
|
-
text.includes("price") ||
|
|
326
|
-
text.includes("industry");
|
|
327
|
-
|
|
328
|
-
console.log(
|
|
329
|
-
"[Gemini Parser] Is Deep Research content:",
|
|
330
|
-
isDeepResearch,
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
if (!seenTexts.has(text)) {
|
|
334
|
-
seenTexts.add(text);
|
|
335
|
-
messages.push({
|
|
336
|
-
role: "Model",
|
|
337
|
-
content: text,
|
|
338
|
-
});
|
|
339
|
-
console.log(
|
|
340
|
-
"[Gemini Parser] Added nested content, Deep Research:",
|
|
341
|
-
isDeepResearch,
|
|
342
|
-
);
|
|
343
|
-
foundContent = true;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
if (foundContent) break;
|
|
349
|
-
}
|
|
412
|
+
return "Gemini Conversation";
|
|
413
|
+
}
|
|
350
414
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const parentContainer = modelResponse.parentElement;
|
|
357
|
-
if (parentContainer) {
|
|
358
|
-
const siblings = parentContainer.children;
|
|
359
|
-
console.log(
|
|
360
|
-
"[Gemini Parser] Checking siblings, count:",
|
|
361
|
-
siblings.length,
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
Array.from(siblings).forEach((sibling) => {
|
|
365
|
-
if (sibling !== modelResponse) {
|
|
366
|
-
const text = sibling.innerText.trim();
|
|
367
|
-
if (text.length > 200) {
|
|
368
|
-
console.log(
|
|
369
|
-
"[Gemini Parser] Found sibling content, length:",
|
|
370
|
-
text.length,
|
|
371
|
-
);
|
|
372
|
-
console.log(
|
|
373
|
-
"[Gemini Parser] Sibling content preview:",
|
|
374
|
-
text.substring(0, 200) + "...",
|
|
375
|
-
);
|
|
376
|
-
|
|
377
|
-
if (!seenTexts.has(text)) {
|
|
378
|
-
seenTexts.add(text);
|
|
379
|
-
messages.push({
|
|
380
|
-
role: "Model",
|
|
381
|
-
content: text,
|
|
382
|
-
});
|
|
383
|
-
console.log(
|
|
384
|
-
"[Gemini Parser] Added sibling content",
|
|
385
|
-
);
|
|
386
|
-
foundContent = true;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
}
|
|
415
|
+
isInsideMessage(el) {
|
|
416
|
+
return !!el.closest?.(
|
|
417
|
+
"user-query, model-response, .conversation-container, message-content, .query-text, .markdown",
|
|
418
|
+
);
|
|
419
|
+
}
|
|
393
420
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const containerText = container.innerText.trim();
|
|
400
|
-
console.log(
|
|
401
|
-
"[Gemini Parser] Full container text length:",
|
|
402
|
-
containerText.length,
|
|
403
|
-
);
|
|
404
|
-
|
|
405
|
-
if (containerText.length > 500) {
|
|
406
|
-
console.log(
|
|
407
|
-
"[Gemini Parser] Full container content preview:",
|
|
408
|
-
containerText.substring(0, 200) + "...",
|
|
409
|
-
);
|
|
410
|
-
|
|
411
|
-
if (!seenTexts.has(containerText)) {
|
|
412
|
-
seenTexts.add(containerText);
|
|
413
|
-
messages.push({
|
|
414
|
-
role: "Model",
|
|
415
|
-
content: containerText,
|
|
416
|
-
});
|
|
417
|
-
console.log(
|
|
418
|
-
"[Gemini Parser] Added full container content",
|
|
419
|
-
);
|
|
420
|
-
foundContent = true;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
421
|
+
parseFromDom(currentUrl) {
|
|
422
|
+
console.log("[Gemini Parser] Running DOM content extraction...");
|
|
423
|
+
const title = this.extractTitleFromPage();
|
|
424
|
+
const messages = [];
|
|
425
|
+
const seenTexts = new Set();
|
|
424
426
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
// Check if this looks like Deep Research content
|
|
439
|
-
const isDeepResearch =
|
|
440
|
-
allText.includes("research") ||
|
|
441
|
-
allText.includes("analysis") ||
|
|
442
|
-
allText.includes("findings") ||
|
|
443
|
-
allText.includes("cost") ||
|
|
444
|
-
allText.includes("sweetener") ||
|
|
445
|
-
allText.includes("projection") ||
|
|
446
|
-
allText.includes("historical");
|
|
447
|
-
|
|
448
|
-
console.log(
|
|
449
|
-
"[Gemini Parser] Is Deep Research content:",
|
|
450
|
-
isDeepResearch,
|
|
451
|
-
);
|
|
452
|
-
|
|
453
|
-
if (!seenTexts.has(allText)) {
|
|
454
|
-
seenTexts.add(allText);
|
|
455
|
-
messages.push({
|
|
456
|
-
role: "Model",
|
|
457
|
-
content: allText,
|
|
458
|
-
});
|
|
459
|
-
console.log(
|
|
460
|
-
"[Gemini Parser] Added last resort model message, Deep Research:",
|
|
461
|
-
isDeepResearch,
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
} else {
|
|
468
|
-
console.log(
|
|
469
|
-
"[Gemini Parser] No message-content found, trying direct model-response text...",
|
|
470
|
-
);
|
|
471
|
-
// Fallback: get text directly from model-response
|
|
472
|
-
const directText = modelResponse.innerText.trim();
|
|
473
|
-
console.log(
|
|
474
|
-
"[Gemini Parser] Direct model response text length:",
|
|
475
|
-
directText.length,
|
|
476
|
-
);
|
|
477
|
-
|
|
478
|
-
if (directText && directText.length > 50) {
|
|
479
|
-
const isDeepResearch =
|
|
480
|
-
directText.includes("research") ||
|
|
481
|
-
directText.includes("analysis") ||
|
|
482
|
-
directText.includes("findings") ||
|
|
483
|
-
directText.includes("cost") ||
|
|
484
|
-
directText.includes("sweetener") ||
|
|
485
|
-
directText.includes("projection") ||
|
|
486
|
-
directText.includes("historical");
|
|
487
|
-
|
|
488
|
-
console.log(
|
|
489
|
-
"[Gemini Parser] Direct text is Deep Research:",
|
|
490
|
-
isDeepResearch,
|
|
491
|
-
);
|
|
492
|
-
|
|
493
|
-
if (!seenTexts.has(directText)) {
|
|
494
|
-
seenTexts.add(directText);
|
|
495
|
-
messages.push({
|
|
496
|
-
role: "Model",
|
|
497
|
-
content: directText,
|
|
498
|
-
});
|
|
499
|
-
console.log(
|
|
500
|
-
"[Gemini Parser] Added direct model message, Deep Research:",
|
|
501
|
-
isDeepResearch,
|
|
502
|
-
);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
});
|
|
508
|
-
}
|
|
427
|
+
if (typeof document === "undefined" || !document.querySelectorAll) {
|
|
428
|
+
return {
|
|
429
|
+
title,
|
|
430
|
+
messages: [],
|
|
431
|
+
url: currentUrl,
|
|
432
|
+
metadata: {
|
|
433
|
+
Source: "Gemini",
|
|
434
|
+
Date: new Date().toLocaleString(),
|
|
435
|
+
Link: currentUrl,
|
|
436
|
+
Method: "DOM",
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
}
|
|
509
440
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
);
|
|
515
|
-
extractionAttempted = true;
|
|
441
|
+
// Strategy 1: Conversation containers or individual query/response tags
|
|
442
|
+
const conversationContainers = document.querySelectorAll(
|
|
443
|
+
".conversation-container, user-query, model-response",
|
|
444
|
+
);
|
|
516
445
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
446
|
+
if (conversationContainers.length > 0) {
|
|
447
|
+
const parentContainers = document.querySelectorAll(
|
|
448
|
+
".conversation-container",
|
|
449
|
+
);
|
|
450
|
+
const targetContainers =
|
|
451
|
+
parentContainers.length > 0 ? parentContainers : [document.body];
|
|
452
|
+
|
|
453
|
+
targetContainers.forEach((container) => {
|
|
454
|
+
// 1. Extract User Queries
|
|
455
|
+
const userQueries =
|
|
456
|
+
container.tagName === "USER-QUERY"
|
|
457
|
+
? [container]
|
|
458
|
+
: container.querySelectorAll("user-query, .user-query-container");
|
|
459
|
+
|
|
460
|
+
userQueries.forEach((userQuery) => {
|
|
461
|
+
const queryTextEl =
|
|
462
|
+
userQuery.querySelector(".query-text") ||
|
|
463
|
+
userQuery.querySelector("user-query-content") ||
|
|
464
|
+
userQuery;
|
|
465
|
+
|
|
466
|
+
if (queryTextEl) {
|
|
467
|
+
const clone = queryTextEl.cloneNode(true);
|
|
468
|
+
clone
|
|
469
|
+
.querySelectorAll(
|
|
470
|
+
'.cdk-visually-hidden, [class*="screen-reader"], h5.cdk-visually-hidden, user-query-file-carousel',
|
|
471
|
+
)
|
|
472
|
+
.forEach((el) => el.remove());
|
|
473
|
+
|
|
474
|
+
// Extract file attachments if any
|
|
475
|
+
const attachments = [];
|
|
476
|
+
userQuery
|
|
477
|
+
.querySelectorAll("user-query-file-preview")
|
|
478
|
+
.forEach((fp) => {
|
|
479
|
+
const fileName = fp.textContent?.trim();
|
|
480
|
+
if (fileName) attachments.push(fileName);
|
|
481
|
+
});
|
|
528
482
|
|
|
529
|
-
|
|
530
|
-
|
|
483
|
+
// Use innerText if available, with textContent fallback so detached nodes are never blank
|
|
484
|
+
let userText = (
|
|
485
|
+
clone.innerText !== undefined && clone.innerText !== ""
|
|
486
|
+
? clone.innerText
|
|
487
|
+
: clone.textContent || ""
|
|
488
|
+
).trim();
|
|
489
|
+
// Clean out leading "You said" if still present
|
|
490
|
+
userText = userText.replace(/^You said\s*/i, "").trim();
|
|
491
|
+
|
|
492
|
+
if (attachments.length > 0) {
|
|
493
|
+
userText +=
|
|
494
|
+
`\n\n**Attachments:**\n` +
|
|
495
|
+
attachments.map((a) => `- ${a}`).join("\n");
|
|
496
|
+
}
|
|
531
497
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
console.log("[Gemini Parser] Found panel title:", titleText);
|
|
539
|
-
if (titleText.length > 5 && !titleText.includes("Gemini")) {
|
|
540
|
-
title = titleText;
|
|
541
|
-
console.log("[Gemini Parser] Title updated from panel:", title);
|
|
498
|
+
if (userText && !seenTexts.has(userText)) {
|
|
499
|
+
seenTexts.add(userText);
|
|
500
|
+
messages.push({
|
|
501
|
+
role: "User",
|
|
502
|
+
content: userText,
|
|
503
|
+
});
|
|
542
504
|
}
|
|
543
505
|
}
|
|
506
|
+
});
|
|
544
507
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
508
|
+
// 2. Extract Model Responses
|
|
509
|
+
const modelResponses =
|
|
510
|
+
container.tagName === "MODEL-RESPONSE"
|
|
511
|
+
? [container]
|
|
512
|
+
: container.querySelectorAll("model-response");
|
|
513
|
+
|
|
514
|
+
modelResponses.forEach((modelResponse) => {
|
|
515
|
+
const messageContent =
|
|
516
|
+
modelResponse.querySelector("message-content") || modelResponse;
|
|
517
|
+
const markdownDiv =
|
|
518
|
+
messageContent.querySelector(
|
|
519
|
+
".markdown.markdown-main-panel, .markdown",
|
|
520
|
+
) || messageContent;
|
|
521
|
+
|
|
522
|
+
if (markdownDiv) {
|
|
523
|
+
const clone = markdownDiv.cloneNode(true);
|
|
524
|
+
|
|
525
|
+
// Remove UI buttons, thought overlays, and interactive toolbars
|
|
526
|
+
clone
|
|
527
|
+
.querySelectorAll(
|
|
528
|
+
"button, .thoughts-container, .thoughts-wrapper, model-thoughts, .table-footer, .hide-from-message-actions, message-actions, election-info-disclaimer, finance-info-disclaimer, .sources-list",
|
|
529
|
+
)
|
|
530
|
+
.forEach((el) => el.remove());
|
|
531
|
+
|
|
532
|
+
// Unwrap response-element wrappers
|
|
533
|
+
clone.querySelectorAll("response-element").forEach((el) => {
|
|
534
|
+
while (el.firstChild) {
|
|
535
|
+
el.parentNode.insertBefore(el.firstChild, el);
|
|
570
536
|
}
|
|
537
|
+
el.remove();
|
|
571
538
|
});
|
|
572
|
-
} else {
|
|
573
|
-
console.log(
|
|
574
|
-
"[Gemini Parser] No panel content found, trying fallback...",
|
|
575
|
-
);
|
|
576
|
-
}
|
|
577
|
-
} else {
|
|
578
|
-
console.log("[Gemini Parser] No Deep Research panel found");
|
|
579
|
-
}
|
|
580
539
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
".content",
|
|
590
|
-
".main-content",
|
|
591
|
-
'[role="main"]',
|
|
592
|
-
".conversation-content",
|
|
593
|
-
".chat-content",
|
|
594
|
-
".message-content",
|
|
595
|
-
];
|
|
596
|
-
|
|
597
|
-
let contentFound = false;
|
|
598
|
-
|
|
599
|
-
for (const selector of contentSelectors) {
|
|
600
|
-
console.log("[Gemini Parser] Trying selector:", selector);
|
|
601
|
-
const contentElement = document.querySelector(selector);
|
|
602
|
-
if (contentElement) {
|
|
603
|
-
console.log(
|
|
604
|
-
"[Gemini Parser] Found content element:",
|
|
605
|
-
!!contentElement,
|
|
606
|
-
);
|
|
607
|
-
// Extract all text content from the main content area
|
|
608
|
-
const textContent = contentElement.innerText.trim();
|
|
609
|
-
console.log(
|
|
610
|
-
"[Gemini Parser] Text content length:",
|
|
611
|
-
textContent.length,
|
|
612
|
-
);
|
|
613
|
-
|
|
614
|
-
if (textContent && textContent.length > 100) {
|
|
615
|
-
// Try to identify user prompts and responses
|
|
616
|
-
const sections =
|
|
617
|
-
this.extractDeepResearchSections(contentElement);
|
|
618
|
-
console.log(
|
|
619
|
-
"[Gemini Parser] Extracted sections:",
|
|
620
|
-
sections.length,
|
|
621
|
-
);
|
|
622
|
-
|
|
623
|
-
if (sections.length > 0) {
|
|
624
|
-
sections.forEach((section) => {
|
|
625
|
-
if (section.content && !seenTexts.has(section.content)) {
|
|
626
|
-
seenTexts.add(section.content);
|
|
627
|
-
messages.push({
|
|
628
|
-
role: section.role,
|
|
629
|
-
content: section.content,
|
|
630
|
-
});
|
|
631
|
-
console.log(
|
|
632
|
-
"[Gemini Parser] Added fallback section, role:",
|
|
633
|
-
section.role,
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
});
|
|
637
|
-
contentFound = true;
|
|
638
|
-
break;
|
|
639
|
-
} else {
|
|
640
|
-
// If we can't parse sections, treat the whole content as a response
|
|
641
|
-
console.log(
|
|
642
|
-
"[Gemini Parser] Treating whole content as response...",
|
|
643
|
-
);
|
|
644
|
-
const markdown = convertToMarkdown(contentElement);
|
|
645
|
-
if (
|
|
646
|
-
markdown &&
|
|
647
|
-
markdown.trim() &&
|
|
648
|
-
!seenTexts.has(markdown.trim())
|
|
649
|
-
) {
|
|
650
|
-
seenTexts.add(markdown.trim());
|
|
651
|
-
messages.push({
|
|
652
|
-
role: "Model",
|
|
653
|
-
content: markdown.trim(),
|
|
654
|
-
});
|
|
655
|
-
console.log("[Gemini Parser] Added fallback message");
|
|
656
|
-
contentFound = true;
|
|
657
|
-
break;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
}
|
|
540
|
+
const text = convertToMarkdown(clone);
|
|
541
|
+
const trimmed = text.trim();
|
|
542
|
+
if (trimmed && !seenTexts.has(trimmed)) {
|
|
543
|
+
seenTexts.add(trimmed);
|
|
544
|
+
messages.push({
|
|
545
|
+
role: "Model",
|
|
546
|
+
content: trimmed,
|
|
547
|
+
});
|
|
661
548
|
}
|
|
662
549
|
}
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
}
|
|
663
553
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
554
|
+
// Strategy 2: Deep Research immersive panel structure fallback
|
|
555
|
+
if (messages.length === 0) {
|
|
556
|
+
const deepResearchPanel = document.querySelector(
|
|
557
|
+
"deep-research-immersive-panel",
|
|
558
|
+
);
|
|
559
|
+
if (deepResearchPanel) {
|
|
560
|
+
const panelContent =
|
|
561
|
+
this.extractDeepResearchPanelContent(deepResearchPanel);
|
|
562
|
+
panelContent.forEach((section) => {
|
|
563
|
+
if (section.content && !seenTexts.has(section.content)) {
|
|
564
|
+
seenTexts.add(section.content);
|
|
565
|
+
messages.push({
|
|
566
|
+
role: section.role,
|
|
567
|
+
content: section.content,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
674
573
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
"[Gemini Parser] Last resort - treating as single response...",
|
|
701
|
-
);
|
|
702
|
-
messages.push({
|
|
703
|
-
role: "Model",
|
|
704
|
-
content: bodyContent,
|
|
705
|
-
});
|
|
706
|
-
console.log("[Gemini Parser] Added last resort message");
|
|
707
|
-
}
|
|
574
|
+
// Strategy 3: General content container fallback
|
|
575
|
+
if (messages.length === 0) {
|
|
576
|
+
const contentSelectors = [
|
|
577
|
+
"main",
|
|
578
|
+
"article",
|
|
579
|
+
".content",
|
|
580
|
+
".main-content",
|
|
581
|
+
'[role="main"]',
|
|
582
|
+
".chat-window-content",
|
|
583
|
+
];
|
|
584
|
+
|
|
585
|
+
for (const selector of contentSelectors) {
|
|
586
|
+
const el = document.querySelector(selector);
|
|
587
|
+
if (el) {
|
|
588
|
+
const text = (el.textContent || "").trim();
|
|
589
|
+
if (text.length > 100) {
|
|
590
|
+
const sections = this.extractDeepResearchSections(el);
|
|
591
|
+
if (sections.length > 0) {
|
|
592
|
+
sections.forEach((s) => {
|
|
593
|
+
if (s.content && !seenTexts.has(s.content)) {
|
|
594
|
+
seenTexts.add(s.content);
|
|
595
|
+
messages.push(s);
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
break;
|
|
708
599
|
}
|
|
709
600
|
}
|
|
710
601
|
}
|
|
711
602
|
}
|
|
603
|
+
}
|
|
712
604
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
605
|
+
console.log(
|
|
606
|
+
`[Gemini Parser] Total DOM messages extracted: ${messages.length}`,
|
|
607
|
+
);
|
|
608
|
+
return {
|
|
609
|
+
title,
|
|
610
|
+
messages,
|
|
611
|
+
url: currentUrl,
|
|
612
|
+
metadata: {
|
|
721
613
|
Source: "Gemini",
|
|
722
614
|
Date: new Date().toLocaleString(),
|
|
723
615
|
Link: currentUrl,
|
|
724
616
|
Method: "DOM",
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
return {
|
|
728
|
-
title: title,
|
|
729
|
-
messages: messages,
|
|
730
|
-
url: currentUrl,
|
|
731
|
-
metadata: metadata,
|
|
732
|
-
};
|
|
733
|
-
} catch (error) {
|
|
734
|
-
console.error("[Gemini Parser] Error during parsing:", error);
|
|
735
|
-
const currentUrl =
|
|
736
|
-
typeof window !== "undefined" && window.location
|
|
737
|
-
? window.location.href || ""
|
|
738
|
-
: "";
|
|
739
|
-
return {
|
|
740
|
-
title: "Gemini Conversation",
|
|
741
|
-
messages: [],
|
|
742
|
-
url: currentUrl,
|
|
743
|
-
metadata: {
|
|
744
|
-
Source: "Gemini",
|
|
745
|
-
Date: new Date().toLocaleString(),
|
|
746
|
-
Link: currentUrl,
|
|
747
|
-
Method: "DOM",
|
|
748
|
-
},
|
|
749
|
-
};
|
|
750
|
-
}
|
|
617
|
+
},
|
|
618
|
+
};
|
|
751
619
|
}
|
|
752
620
|
|
|
753
|
-
// Helper method to extract sections from Deep Research content
|
|
754
621
|
extractDeepResearchSections(contentElement) {
|
|
755
|
-
console.log("[Gemini Parser] Extracting Deep Research sections...");
|
|
756
622
|
const sections = [];
|
|
757
|
-
const text = contentElement.innerText || "";
|
|
623
|
+
const text = contentElement.innerText || contentElement.textContent || "";
|
|
758
624
|
|
|
759
|
-
// Look for common Deep Research patterns
|
|
760
625
|
const patterns = [
|
|
761
|
-
// Pattern 1: "Prompt:" and "Response:" sections
|
|
762
626
|
{
|
|
763
627
|
promptRegex:
|
|
764
628
|
/(?:Prompt|You said)[:\s]*\n*([\s\S]*?)(?=\n\s*(?:Response|I've completed|Generating|Start research)|$)/i,
|
|
765
629
|
responseRegex:
|
|
766
630
|
/(?:Response|I've completed|Generating|Start research)[:\s]*\n*([\s\S]*?)(?=\n\s*(?:Prompt|You said)|$)/i,
|
|
767
631
|
},
|
|
768
|
-
// Pattern 2: Question/Answer format
|
|
769
632
|
{
|
|
770
633
|
promptRegex:
|
|
771
634
|
/(?:Question|Q)[:\s]*\n*([\s\S]*?)(?=\n\s*(?:Answer|A|Response)|$)/i,
|
|
772
635
|
responseRegex:
|
|
773
636
|
/(?:Answer|A|Response)[:\s]*\n*([\s\S]*?)(?=\n\s*(?:Question|Q)|$)/i,
|
|
774
637
|
},
|
|
775
|
-
// Pattern 3: Look for research plan and results
|
|
776
|
-
{
|
|
777
|
-
promptRegex:
|
|
778
|
-
/(?:Research plan|Research query|What is|How has|What's the projection)[:\s]*\n*([\s\S]*?)(?=\n\s*(?:I've completed|Research|Analysis|Results)|$)/i,
|
|
779
|
-
responseRegex:
|
|
780
|
-
/(?:I've completed|Research|Analysis|Results|Findings)[:\s]*\n*([\s\S]*?)(?=\n\s*(?:Research plan|Research query|What is|How has)|$)/i,
|
|
781
|
-
},
|
|
782
638
|
];
|
|
783
639
|
|
|
784
|
-
// Try each pattern
|
|
785
640
|
for (const pattern of patterns) {
|
|
786
|
-
console.log("[Gemini Parser] Trying pattern...");
|
|
787
641
|
const promptMatches = text.match(pattern.promptRegex);
|
|
788
642
|
const responseMatches = text.match(pattern.responseRegex);
|
|
789
643
|
|
|
790
644
|
if (promptMatches && promptMatches[1]) {
|
|
791
645
|
const promptContent = promptMatches[1].trim();
|
|
792
|
-
console.log(
|
|
793
|
-
"[Gemini Parser] Found prompt content, length:",
|
|
794
|
-
promptContent.length,
|
|
795
|
-
);
|
|
796
646
|
if (promptContent.length > 20) {
|
|
797
647
|
sections.push({
|
|
798
648
|
role: "User",
|
|
799
649
|
content: promptContent,
|
|
800
650
|
});
|
|
801
|
-
console.log("[Gemini Parser] Added prompt section");
|
|
802
651
|
}
|
|
803
652
|
}
|
|
804
653
|
|
|
805
654
|
if (responseMatches && responseMatches[1]) {
|
|
806
655
|
const responseContent = responseMatches[1].trim();
|
|
807
|
-
console.log(
|
|
808
|
-
"[Gemini Parser] Found response content, length:",
|
|
809
|
-
responseContent.length,
|
|
810
|
-
);
|
|
811
656
|
if (responseContent.length > 50) {
|
|
812
657
|
sections.push({
|
|
813
658
|
role: "Model",
|
|
814
659
|
content: responseContent,
|
|
815
660
|
});
|
|
816
|
-
console.log("[Gemini Parser] Added response section");
|
|
817
661
|
}
|
|
818
662
|
}
|
|
819
663
|
|
|
820
|
-
|
|
821
|
-
if (sections.length > 0) {
|
|
822
|
-
console.log("[Gemini Parser] Found sections using pattern matching");
|
|
823
|
-
return sections;
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
// If no structured sections found, try to extract based on HTML structure
|
|
828
|
-
console.log("[Gemini Parser] Trying HTML structure extraction...");
|
|
829
|
-
const userElements = contentElement.querySelectorAll(
|
|
830
|
-
'.user-query, .prompt, .question, [data-role="user"]',
|
|
831
|
-
);
|
|
832
|
-
console.log("[Gemini Parser] Found user elements:", userElements.length);
|
|
833
|
-
userElements.forEach((el) => {
|
|
834
|
-
const clone = el.cloneNode(true);
|
|
835
|
-
clone
|
|
836
|
-
.querySelectorAll('.cdk-visually-hidden, [class*="screen-reader"]')
|
|
837
|
-
.forEach((subEl) => subEl.remove());
|
|
838
|
-
const content = clone.innerText.trim();
|
|
839
|
-
if (content.length > 20) {
|
|
840
|
-
sections.push({
|
|
841
|
-
role: "User",
|
|
842
|
-
content: content,
|
|
843
|
-
});
|
|
844
|
-
console.log("[Gemini Parser] Added user element from HTML structure");
|
|
845
|
-
}
|
|
846
|
-
});
|
|
847
|
-
|
|
848
|
-
// Look for elements that might contain responses
|
|
849
|
-
const responseElements = contentElement.querySelectorAll(
|
|
850
|
-
'.model-response, .response, .answer, [data-role="model"], .research-content',
|
|
851
|
-
);
|
|
852
|
-
console.log(
|
|
853
|
-
"[Gemini Parser] Found response elements:",
|
|
854
|
-
responseElements.length,
|
|
855
|
-
);
|
|
856
|
-
responseElements.forEach((el) => {
|
|
857
|
-
const content = el.innerText.trim();
|
|
858
|
-
if (content.length > 50) {
|
|
859
|
-
sections.push({
|
|
860
|
-
role: "Model",
|
|
861
|
-
content: content,
|
|
862
|
-
});
|
|
863
|
-
console.log(
|
|
864
|
-
"[Gemini Parser] Added response element from HTML structure",
|
|
865
|
-
);
|
|
866
|
-
}
|
|
867
|
-
});
|
|
868
|
-
|
|
869
|
-
// If still no sections, try to split by common delimiters
|
|
870
|
-
if (sections.length === 0) {
|
|
871
|
-
console.log("[Gemini Parser] Trying delimiter splitting...");
|
|
872
|
-
const delimiterPatterns = [
|
|
873
|
-
/\n\s*You said\s*\n/i,
|
|
874
|
-
/\n\s*Response\s*\n/i,
|
|
875
|
-
/\n\s*Prompt\s*\n/i,
|
|
876
|
-
/\n\s*I've completed\s*\n/i,
|
|
877
|
-
];
|
|
878
|
-
|
|
879
|
-
let parts = [text];
|
|
880
|
-
delimiterPatterns.forEach((pattern) => {
|
|
881
|
-
parts = parts.flatMap((part) => part.split(pattern));
|
|
882
|
-
});
|
|
883
|
-
|
|
884
|
-
parts.forEach((part, index) => {
|
|
885
|
-
const trimmedPart = part.trim();
|
|
886
|
-
if (trimmedPart.length > 50) {
|
|
887
|
-
// Alternate between User and Model roles
|
|
888
|
-
const role = index % 2 === 0 ? "User" : "Model";
|
|
889
|
-
sections.push({
|
|
890
|
-
role: role,
|
|
891
|
-
content: trimmedPart,
|
|
892
|
-
});
|
|
893
|
-
console.log("[Gemini Parser] Added delimiter section, role:", role);
|
|
894
|
-
}
|
|
895
|
-
});
|
|
664
|
+
if (sections.length > 0) return sections;
|
|
896
665
|
}
|
|
897
666
|
|
|
898
|
-
console.log("[Gemini Parser] Total sections extracted:", sections.length);
|
|
899
667
|
return sections;
|
|
900
668
|
}
|
|
901
669
|
|
|
902
|
-
// Helper method to extract content from Deep Research immersive panel
|
|
903
670
|
extractDeepResearchPanelContent(panelElement) {
|
|
904
|
-
console.log("[Gemini Parser] Looking for Deep Research panel content...");
|
|
905
671
|
const sections = [];
|
|
906
|
-
|
|
907
|
-
console.log("[Gemini Parser] Panel element found:", !!panelElement);
|
|
908
|
-
console.log(
|
|
909
|
-
"[Gemini Parser] Panel innerText length:",
|
|
910
|
-
panelElement.innerText ? panelElement.innerText.length : 0,
|
|
911
|
-
);
|
|
912
|
-
|
|
913
672
|
try {
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
".
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
console.log("[Gemini Parser] Trying selector:", selector);
|
|
927
|
-
const contentElements = panelElement.querySelectorAll(selector);
|
|
928
|
-
console.log("[Gemini Parser] Found elements:", contentElements.length);
|
|
929
|
-
contentElements.forEach((element) => {
|
|
930
|
-
const text = element.innerText.trim();
|
|
931
|
-
if (text.length > 100) {
|
|
932
|
-
sections.push({
|
|
933
|
-
role: "Model",
|
|
934
|
-
content: text,
|
|
935
|
-
});
|
|
936
|
-
console.log(
|
|
937
|
-
"[Gemini Parser] Added panel content via selector:",
|
|
938
|
-
selector,
|
|
939
|
-
);
|
|
940
|
-
}
|
|
941
|
-
});
|
|
942
|
-
}
|
|
673
|
+
const contentElements = panelElement.querySelectorAll(
|
|
674
|
+
".markdown, .content, .research-content, .panel-content",
|
|
675
|
+
);
|
|
676
|
+
contentElements.forEach((element) => {
|
|
677
|
+
const text = (element.innerText || element.textContent || "").trim();
|
|
678
|
+
if (text.length > 100) {
|
|
679
|
+
sections.push({
|
|
680
|
+
role: "Model",
|
|
681
|
+
content: text,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
});
|
|
943
685
|
|
|
944
|
-
// If no structured content found, extract all text from panel
|
|
945
686
|
if (sections.length === 0) {
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
687
|
+
const panelText = (
|
|
688
|
+
panelElement.innerText ||
|
|
689
|
+
panelElement.textContent ||
|
|
690
|
+
""
|
|
691
|
+
).trim();
|
|
951
692
|
if (panelText.length > 200) {
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
parts.forEach((part) => {
|
|
956
|
-
if (part.length > 50) {
|
|
957
|
-
sections.push({
|
|
958
|
-
role: "Model",
|
|
959
|
-
content: part,
|
|
960
|
-
});
|
|
961
|
-
console.log("[Gemini Parser] Added panel text part");
|
|
962
|
-
}
|
|
693
|
+
sections.push({
|
|
694
|
+
role: "Model",
|
|
695
|
+
content: panelText,
|
|
963
696
|
});
|
|
964
697
|
}
|
|
965
698
|
}
|
|
966
699
|
} catch (error) {
|
|
967
700
|
console.error("[Gemini Parser] Error extracting panel content:", error);
|
|
968
701
|
}
|
|
969
|
-
|
|
970
|
-
console.log(
|
|
971
|
-
"[Gemini Parser] Panel content extraction complete, sections:",
|
|
972
|
-
sections.length,
|
|
973
|
-
);
|
|
974
|
-
return sections;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
// Helper method to split text into logical sections
|
|
978
|
-
splitIntoSections(text) {
|
|
979
|
-
console.log("[Gemini Parser] Splitting text into sections...");
|
|
980
|
-
const sections = [];
|
|
981
|
-
|
|
982
|
-
// Try to split by common delimiters
|
|
983
|
-
const delimiters = [
|
|
984
|
-
/\n\n+/g, // Double newlines
|
|
985
|
-
/\n(?=[A-Z])/g, // Newline followed by capital letter
|
|
986
|
-
/\.\s+/g, // Period followed by space
|
|
987
|
-
];
|
|
988
|
-
|
|
989
|
-
let parts = [text];
|
|
990
|
-
delimiters.forEach((delimiter) => {
|
|
991
|
-
parts = parts.flatMap((part) => part.split(delimiter));
|
|
992
|
-
});
|
|
993
|
-
|
|
994
|
-
// Filter and clean sections
|
|
995
|
-
parts.forEach((part) => {
|
|
996
|
-
const cleaned = part.trim();
|
|
997
|
-
if (cleaned.length > 50 && !cleaned.match(/^\d+$/)) {
|
|
998
|
-
sections.push(cleaned);
|
|
999
|
-
console.log(
|
|
1000
|
-
"[Gemini Parser] Added split section, length:",
|
|
1001
|
-
cleaned.length,
|
|
1002
|
-
);
|
|
1003
|
-
}
|
|
1004
|
-
});
|
|
1005
|
-
|
|
1006
|
-
console.log(
|
|
1007
|
-
"[Gemini Parser] Text splitting complete, sections:",
|
|
1008
|
-
sections.length,
|
|
1009
|
-
);
|
|
1010
702
|
return sections;
|
|
1011
703
|
}
|
|
1012
704
|
}
|