decant-core 1.2.0 → 1.2.1
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 +543 -880
- package/package.json +1 -1
package/ai/gemini.js
CHANGED
|
@@ -1,1012 +1,675 @@
|
|
|
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
|
+
|
|
4
7
|
export class GeminiParser extends ChatParser {
|
|
5
8
|
name = "Gemini";
|
|
9
|
+
|
|
6
10
|
isAvailable(url) {
|
|
7
|
-
return
|
|
11
|
+
return (
|
|
12
|
+
typeof url === "string" &&
|
|
13
|
+
(url.includes("gemini.google.com") || url.includes("bard.google.com"))
|
|
14
|
+
);
|
|
8
15
|
}
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
getPlatformName() {
|
|
18
|
+
return "Gemini";
|
|
19
|
+
}
|
|
12
20
|
|
|
21
|
+
getConversationId(url) {
|
|
13
22
|
try {
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
const targetUrl =
|
|
24
|
+
url ||
|
|
25
|
+
(typeof window !== "undefined" && window.location
|
|
26
|
+
? window.location.href
|
|
27
|
+
: "");
|
|
28
|
+
if (!targetUrl) return null;
|
|
29
|
+
const parsed = new URL(
|
|
30
|
+
targetUrl,
|
|
31
|
+
typeof location !== "undefined"
|
|
32
|
+
? location.origin
|
|
33
|
+
: "https://gemini.google.com",
|
|
34
|
+
);
|
|
35
|
+
const match = parsed.pathname.match(/\/(?:app|share)\/([a-zA-Z0-9_-]+)/);
|
|
36
|
+
return match ? match[1] : null;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
16
41
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
42
|
+
getGlobalData() {
|
|
43
|
+
try {
|
|
44
|
+
// 1. Try direct window access if present
|
|
45
|
+
if (
|
|
46
|
+
typeof window !== "undefined" &&
|
|
47
|
+
window.WIZ_global_data &&
|
|
48
|
+
typeof window.WIZ_global_data === "object"
|
|
49
|
+
) {
|
|
50
|
+
return window.WIZ_global_data;
|
|
51
|
+
}
|
|
22
52
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
.
|
|
28
|
-
.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
53
|
+
// 2. Try parsing inline script tags for WIZ_global_data
|
|
54
|
+
if (typeof document !== "undefined" && document.querySelectorAll) {
|
|
55
|
+
const scripts = document.querySelectorAll("script");
|
|
56
|
+
for (let i = 0; i < scripts.length; i++) {
|
|
57
|
+
const content = scripts[i].textContent || "";
|
|
58
|
+
if (content.includes("WIZ_global_data")) {
|
|
59
|
+
const match = content.match(
|
|
60
|
+
/window\.WIZ_global_data\s*=\s*(\{[\s\S]*?\});/,
|
|
61
|
+
);
|
|
62
|
+
if (match && match[1]) {
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(match[1]);
|
|
65
|
+
} catch {
|
|
66
|
+
// Continue to next script
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {
|
|
73
|
+
console.warn("[Gemini Parser] Error reading global data:", e);
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async parse(options = {}) {
|
|
79
|
+
console.log("[Gemini Parser] ========== STARTING PARSE() ==========");
|
|
80
|
+
const currentUrl =
|
|
81
|
+
typeof window !== "undefined" && window.location
|
|
82
|
+
? window.location.href || ""
|
|
83
|
+
: "";
|
|
33
84
|
|
|
34
|
-
|
|
35
|
-
!cleanedDocTitle ||
|
|
36
|
-
cleanedDocTitle.toLowerCase() === "new chat" ||
|
|
37
|
-
cleanedDocTitle.toLowerCase() === "help" ||
|
|
38
|
-
cleanedDocTitle.toLowerCase() === "settings";
|
|
85
|
+
const mode = options.parserMode || "auto";
|
|
39
86
|
|
|
40
|
-
|
|
41
|
-
|
|
87
|
+
// Attempt API / RPC extraction first when in auto mode and in real browser
|
|
88
|
+
if (mode !== "dom" && typeof fetch === "function") {
|
|
89
|
+
try {
|
|
90
|
+
const convoId = this.getConversationId(currentUrl);
|
|
91
|
+
const globalData = this.getGlobalData();
|
|
92
|
+
|
|
93
|
+
if (convoId && globalData && globalData.SNlM0e && globalData.FdrFJe) {
|
|
42
94
|
console.log(
|
|
43
|
-
"[Gemini Parser]
|
|
44
|
-
|
|
95
|
+
"[Gemini Parser] Attempting API extraction for convo:",
|
|
96
|
+
convoId,
|
|
97
|
+
);
|
|
98
|
+
const apiResult = await this.fetchFromApi(
|
|
99
|
+
convoId,
|
|
100
|
+
globalData,
|
|
101
|
+
currentUrl,
|
|
102
|
+
options,
|
|
45
103
|
);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Strategy 2: Looking for active navigation
|
|
50
|
-
if (!title) {
|
|
51
|
-
console.log(
|
|
52
|
-
"[Gemini Parser] Strategy 2: Looking for active navigation...",
|
|
53
|
-
);
|
|
54
|
-
const activeNav = document.querySelector(
|
|
55
|
-
'a[aria-current="page"], .selected',
|
|
56
|
-
);
|
|
57
|
-
if (activeNav) {
|
|
58
|
-
const navText = activeNav.innerText
|
|
59
|
-
.replace(/more_vert/g, "")
|
|
60
|
-
.replace(/\n/g, " ")
|
|
61
|
-
.trim();
|
|
62
104
|
if (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
105
|
+
apiResult &&
|
|
106
|
+
apiResult.messages &&
|
|
107
|
+
apiResult.messages.length > 0
|
|
66
108
|
) {
|
|
67
|
-
title = navText;
|
|
68
109
|
console.log(
|
|
69
|
-
|
|
70
|
-
title,
|
|
110
|
+
`[Gemini Parser] Successfully parsed ${apiResult.messages.length} messages via API`,
|
|
71
111
|
);
|
|
112
|
+
return apiResult;
|
|
72
113
|
}
|
|
73
114
|
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.warn(
|
|
117
|
+
"[Gemini Parser] API extraction failed, falling back to DOM:",
|
|
118
|
+
err,
|
|
119
|
+
);
|
|
74
120
|
}
|
|
121
|
+
}
|
|
75
122
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
123
|
+
// Fall back to robust DOM parsing
|
|
124
|
+
return this.parseFromDom(currentUrl, options);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async fetchFromApi(convoId, globalData, currentUrl, options = {}) {
|
|
128
|
+
const fSid = globalData.FdrFJe || "";
|
|
129
|
+
const bl = globalData.cfb2h || "";
|
|
130
|
+
const prefix = globalData.Im6cmf || DEFAULT_BARD_PATH;
|
|
131
|
+
const atToken = globalData.SNlM0e || "";
|
|
132
|
+
|
|
133
|
+
const reqId = String(Math.floor(9e6 * Math.random()) + 1e6);
|
|
134
|
+
const sourcePath =
|
|
135
|
+
typeof window !== "undefined" && window.location
|
|
136
|
+
? window.location.pathname
|
|
137
|
+
: `/app/${convoId}`;
|
|
138
|
+
|
|
139
|
+
const endpoint =
|
|
140
|
+
`https://gemini.google.com${prefix}/data/batchexecute` +
|
|
141
|
+
`?rpcids=${encodeURIComponent(GEMINI_RPC_ID)}` +
|
|
142
|
+
`&source-path=${encodeURIComponent(sourcePath)}` +
|
|
143
|
+
`&bl=${encodeURIComponent(bl)}` +
|
|
144
|
+
`&f.sid=${encodeURIComponent(fSid)}` +
|
|
145
|
+
`&hl=en` +
|
|
146
|
+
`&_reqid=${encodeURIComponent(reqId)}` +
|
|
147
|
+
`&rt=c`;
|
|
148
|
+
|
|
149
|
+
const allItems = [];
|
|
150
|
+
let cursor = null;
|
|
151
|
+
let pageCount = 0;
|
|
152
|
+
|
|
153
|
+
while (pageCount < 50) {
|
|
154
|
+
pageCount++;
|
|
155
|
+
const payloadArg = JSON.stringify([
|
|
156
|
+
`c_${convoId}`,
|
|
157
|
+
100,
|
|
158
|
+
cursor,
|
|
159
|
+
1,
|
|
160
|
+
[0],
|
|
161
|
+
[4],
|
|
162
|
+
null,
|
|
163
|
+
1,
|
|
164
|
+
]);
|
|
165
|
+
|
|
166
|
+
const formParams = new URLSearchParams();
|
|
167
|
+
formParams.append(
|
|
168
|
+
"f.req",
|
|
169
|
+
JSON.stringify([[[GEMINI_RPC_ID, payloadArg, null, "generic"]]]),
|
|
170
|
+
);
|
|
171
|
+
formParams.append("at", atToken);
|
|
172
|
+
|
|
173
|
+
const resp = await fetch(endpoint, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
credentials: "include",
|
|
176
|
+
headers: {
|
|
177
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
178
|
+
},
|
|
179
|
+
body: formParams.toString(),
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (!resp.ok) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`Gemini RPC request failed: ${resp.status} ${resp.statusText}`,
|
|
83
185
|
);
|
|
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
186
|
}
|
|
98
187
|
|
|
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
|
-
}
|
|
188
|
+
const rawText = await resp.text();
|
|
189
|
+
const parsedBatch = this.parseBatchExecuteLines(rawText);
|
|
190
|
+
const rpcEntry = this.findRpcEntry(parsedBatch.arrays, GEMINI_RPC_ID);
|
|
191
|
+
|
|
192
|
+
if (!rpcEntry || !rpcEntry[2]) {
|
|
193
|
+
break;
|
|
127
194
|
}
|
|
128
195
|
|
|
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
|
-
);
|
|
196
|
+
const payload = JSON.parse(rpcEntry[2]);
|
|
197
|
+
const items = Array.isArray(payload[0]) ? payload[0] : [];
|
|
198
|
+
const continueCursor = payload[1] || null;
|
|
141
199
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
200
|
+
if (items.length > 0) {
|
|
201
|
+
// Items are in reverse chronological order from API
|
|
202
|
+
allItems.unshift(...items.slice().reverse());
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!continueCursor || items.length < 100) {
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
cursor = continueCursor;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (allItems.length === 0) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const messages = this.convertApiItemsToMessages(allItems, options);
|
|
216
|
+
let title = this.extractTitleFromPage();
|
|
217
|
+
if (!title || title === "Gemini Conversation") {
|
|
218
|
+
const firstUserMsg = messages.find((m) => m.role === "User");
|
|
219
|
+
if (firstUserMsg && firstUserMsg.content) {
|
|
220
|
+
title = firstUserMsg.content.slice(0, 50).split("\n")[0].trim();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
title: title || "Gemini Conversation",
|
|
226
|
+
messages,
|
|
227
|
+
url: currentUrl,
|
|
228
|
+
metadata: {
|
|
229
|
+
Source: "Gemini",
|
|
230
|
+
Date: new Date().toLocaleString(),
|
|
231
|
+
Link: currentUrl,
|
|
232
|
+
Method: "API",
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
parseBatchExecuteLines(raw) {
|
|
238
|
+
const cleaned = String(raw || "").replace(/^\)\]\}'\s*\n/, "");
|
|
239
|
+
const lines = cleaned
|
|
240
|
+
.split("\n")
|
|
241
|
+
.map((l) => l.trim())
|
|
242
|
+
.filter(Boolean);
|
|
243
|
+
const arrays = [];
|
|
244
|
+
for (const line of lines) {
|
|
245
|
+
if (!/^\d+$/.test(line) && line.startsWith("[") && line.endsWith("]")) {
|
|
246
|
+
try {
|
|
247
|
+
arrays.push(JSON.parse(line));
|
|
248
|
+
} catch {
|
|
249
|
+
// Ignore non-JSON lines
|
|
158
250
|
}
|
|
159
251
|
}
|
|
252
|
+
}
|
|
253
|
+
return { arrays, rawData: cleaned };
|
|
254
|
+
}
|
|
160
255
|
|
|
161
|
-
|
|
162
|
-
|
|
256
|
+
findRpcEntry(arrays, rpcId, envelope = "wrb.fr") {
|
|
257
|
+
if (!Array.isArray(arrays)) return null;
|
|
258
|
+
if (arrays[0] === envelope && arrays[1] === rpcId && arrays[2]) {
|
|
259
|
+
return arrays;
|
|
260
|
+
}
|
|
261
|
+
for (const item of arrays) {
|
|
262
|
+
if (Array.isArray(item)) {
|
|
263
|
+
const found = this.findRpcEntry(item, rpcId, envelope);
|
|
264
|
+
if (found) return found;
|
|
163
265
|
}
|
|
266
|
+
}
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
164
269
|
|
|
165
|
-
|
|
270
|
+
convertApiItemsToMessages(items, options = {}) {
|
|
271
|
+
const messages = [];
|
|
166
272
|
|
|
167
|
-
|
|
168
|
-
|
|
273
|
+
for (const item of items) {
|
|
274
|
+
if (!Array.isArray(item)) continue;
|
|
169
275
|
|
|
170
|
-
|
|
171
|
-
|
|
276
|
+
const userText = this.findUserTextInApiItem(item);
|
|
277
|
+
if (userText) {
|
|
278
|
+
messages.push({
|
|
279
|
+
role: "User",
|
|
280
|
+
content: userText.trim(),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
172
283
|
|
|
173
|
-
|
|
174
|
-
|
|
284
|
+
const modelText = this.findModelTextInApiItem(item, options);
|
|
285
|
+
if (modelText) {
|
|
286
|
+
messages.push({
|
|
287
|
+
role: "Model",
|
|
288
|
+
content: modelText.trim(),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
175
292
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
293
|
+
return messages;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
findUserTextInApiItem(item) {
|
|
297
|
+
try {
|
|
298
|
+
if (typeof item[2]?.[0] === "string") return item[2][0];
|
|
299
|
+
if (typeof item[1]?.[0] === "string" && !Array.isArray(item[1][0]))
|
|
300
|
+
return item[1][0];
|
|
301
|
+
if (typeof item[0]?.[0] === "string") return item[0][0];
|
|
302
|
+
} catch {
|
|
303
|
+
// Fall through
|
|
304
|
+
}
|
|
305
|
+
return "";
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
findModelTextInApiItem(item) {
|
|
309
|
+
try {
|
|
310
|
+
if (Array.isArray(item[1])) {
|
|
311
|
+
const candidate = item[1][0];
|
|
312
|
+
if (typeof candidate === "string") return candidate;
|
|
313
|
+
if (Array.isArray(candidate)) {
|
|
314
|
+
if (typeof candidate[1]?.[0] === "string") return candidate[1][0];
|
|
315
|
+
if (typeof candidate[0] === "string") return candidate[0];
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
} catch {
|
|
319
|
+
// Fall through
|
|
320
|
+
}
|
|
321
|
+
return "";
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
extractTitleFromPage() {
|
|
325
|
+
if (typeof document !== "undefined" && document.title) {
|
|
326
|
+
const cleanedDocTitle = document.title
|
|
327
|
+
.replace(/Google/g, "")
|
|
328
|
+
.replace(/Gemini/g, "")
|
|
329
|
+
.replace(/Advanced/g, "")
|
|
330
|
+
.replace(/- /g, "")
|
|
331
|
+
.replace(/—/g, "")
|
|
332
|
+
.trim();
|
|
333
|
+
|
|
334
|
+
const isGeneric =
|
|
335
|
+
!cleanedDocTitle ||
|
|
336
|
+
cleanedDocTitle.toLowerCase() === "new chat" ||
|
|
337
|
+
cleanedDocTitle.toLowerCase() === "help" ||
|
|
338
|
+
cleanedDocTitle.toLowerCase() === "settings";
|
|
339
|
+
|
|
340
|
+
if (cleanedDocTitle && !isGeneric && cleanedDocTitle.length > 2) {
|
|
341
|
+
return cleanedDocTitle;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (typeof document !== "undefined" && document.querySelector) {
|
|
346
|
+
const activeNav = document.querySelector(
|
|
347
|
+
'a[aria-current="page"], .selected',
|
|
182
348
|
);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
349
|
+
if (activeNav) {
|
|
350
|
+
const navText = (activeNav.textContent || activeNav.innerText || "")
|
|
351
|
+
.replace(/more_vert/g, "")
|
|
352
|
+
.replace(/\n/g, " ")
|
|
353
|
+
.trim();
|
|
354
|
+
if (
|
|
355
|
+
navText &&
|
|
356
|
+
navText.length > 2 &&
|
|
357
|
+
!navText.toLowerCase().includes("new chat")
|
|
358
|
+
) {
|
|
359
|
+
return navText;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const deepResearchTitle = document.querySelector(
|
|
364
|
+
'h1, .title, .conversation-title, [data-testid="title"]',
|
|
186
365
|
);
|
|
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
|
-
}
|
|
366
|
+
if (deepResearchTitle && !this.isInsideMessage(deepResearchTitle)) {
|
|
367
|
+
const text = (
|
|
368
|
+
deepResearchTitle.textContent ||
|
|
369
|
+
deepResearchTitle.innerText ||
|
|
370
|
+
""
|
|
371
|
+
).trim();
|
|
372
|
+
if (
|
|
373
|
+
text.length > 5 &&
|
|
374
|
+
!text.includes("Gemini") &&
|
|
375
|
+
!text.includes("Help") &&
|
|
376
|
+
!text.includes("Settings")
|
|
377
|
+
) {
|
|
378
|
+
return text;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
220
382
|
|
|
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
|
-
}
|
|
383
|
+
return "Gemini Conversation";
|
|
384
|
+
}
|
|
350
385
|
|
|
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
|
-
}
|
|
386
|
+
isInsideMessage(el) {
|
|
387
|
+
return !!el.closest?.(
|
|
388
|
+
"user-query, model-response, .conversation-container, message-content, .query-text, .markdown",
|
|
389
|
+
);
|
|
390
|
+
}
|
|
393
391
|
|
|
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
|
-
}
|
|
392
|
+
parseFromDom(currentUrl) {
|
|
393
|
+
console.log("[Gemini Parser] Running DOM content extraction...");
|
|
394
|
+
const title = this.extractTitleFromPage();
|
|
395
|
+
const messages = [];
|
|
396
|
+
const seenTexts = new Set();
|
|
424
397
|
|
|
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
|
-
}
|
|
398
|
+
if (typeof document === "undefined" || !document.querySelectorAll) {
|
|
399
|
+
return {
|
|
400
|
+
title,
|
|
401
|
+
messages: [],
|
|
402
|
+
url: currentUrl,
|
|
403
|
+
metadata: {
|
|
404
|
+
Source: "Gemini",
|
|
405
|
+
Date: new Date().toLocaleString(),
|
|
406
|
+
Link: currentUrl,
|
|
407
|
+
Method: "DOM",
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
}
|
|
509
411
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
);
|
|
515
|
-
extractionAttempted = true;
|
|
412
|
+
// Strategy 1: Conversation containers or individual query/response tags
|
|
413
|
+
const conversationContainers = document.querySelectorAll(
|
|
414
|
+
".conversation-container, user-query, model-response",
|
|
415
|
+
);
|
|
516
416
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
417
|
+
if (conversationContainers.length > 0) {
|
|
418
|
+
const parentContainers = document.querySelectorAll(
|
|
419
|
+
".conversation-container",
|
|
420
|
+
);
|
|
421
|
+
const targetContainers =
|
|
422
|
+
parentContainers.length > 0 ? parentContainers : [document.body];
|
|
423
|
+
|
|
424
|
+
targetContainers.forEach((container) => {
|
|
425
|
+
// 1. Extract User Queries
|
|
426
|
+
const userQueries =
|
|
427
|
+
container.tagName === "USER-QUERY"
|
|
428
|
+
? [container]
|
|
429
|
+
: container.querySelectorAll("user-query, .user-query-container");
|
|
430
|
+
|
|
431
|
+
userQueries.forEach((userQuery) => {
|
|
432
|
+
const queryTextEl =
|
|
433
|
+
userQuery.querySelector(".query-text") ||
|
|
434
|
+
userQuery.querySelector("user-query-content") ||
|
|
435
|
+
userQuery;
|
|
436
|
+
|
|
437
|
+
if (queryTextEl) {
|
|
438
|
+
const clone = queryTextEl.cloneNode(true);
|
|
439
|
+
clone
|
|
440
|
+
.querySelectorAll(
|
|
441
|
+
'.cdk-visually-hidden, [class*="screen-reader"], h5.cdk-visually-hidden, user-query-file-carousel',
|
|
442
|
+
)
|
|
443
|
+
.forEach((el) => el.remove());
|
|
444
|
+
|
|
445
|
+
// Extract file attachments if any
|
|
446
|
+
const attachments = [];
|
|
447
|
+
userQuery
|
|
448
|
+
.querySelectorAll("user-query-file-preview")
|
|
449
|
+
.forEach((fp) => {
|
|
450
|
+
const fileName = fp.textContent?.trim();
|
|
451
|
+
if (fileName) attachments.push(fileName);
|
|
452
|
+
});
|
|
528
453
|
|
|
529
|
-
|
|
530
|
-
|
|
454
|
+
// Use innerText if available, with textContent fallback so detached nodes are never blank
|
|
455
|
+
let userText = (
|
|
456
|
+
clone.innerText !== undefined && clone.innerText !== ""
|
|
457
|
+
? clone.innerText
|
|
458
|
+
: clone.textContent || ""
|
|
459
|
+
).trim();
|
|
460
|
+
// Clean out leading "You said" if still present
|
|
461
|
+
userText = userText.replace(/^You said\s*/i, "").trim();
|
|
462
|
+
|
|
463
|
+
if (attachments.length > 0) {
|
|
464
|
+
userText +=
|
|
465
|
+
`\n\n**Attachments:**\n` +
|
|
466
|
+
attachments.map((a) => `- ${a}`).join("\n");
|
|
467
|
+
}
|
|
531
468
|
|
|
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);
|
|
469
|
+
if (userText && !seenTexts.has(userText)) {
|
|
470
|
+
seenTexts.add(userText);
|
|
471
|
+
messages.push({
|
|
472
|
+
role: "User",
|
|
473
|
+
content: userText,
|
|
474
|
+
});
|
|
542
475
|
}
|
|
543
476
|
}
|
|
477
|
+
});
|
|
544
478
|
|
|
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
|
-
|
|
479
|
+
// 2. Extract Model Responses
|
|
480
|
+
const modelResponses =
|
|
481
|
+
container.tagName === "MODEL-RESPONSE"
|
|
482
|
+
? [container]
|
|
483
|
+
: container.querySelectorAll("model-response");
|
|
484
|
+
|
|
485
|
+
modelResponses.forEach((modelResponse) => {
|
|
486
|
+
const messageContent =
|
|
487
|
+
modelResponse.querySelector("message-content") || modelResponse;
|
|
488
|
+
const markdownDiv =
|
|
489
|
+
messageContent.querySelector(
|
|
490
|
+
".markdown.markdown-main-panel, .markdown",
|
|
491
|
+
) || messageContent;
|
|
492
|
+
|
|
493
|
+
if (markdownDiv) {
|
|
494
|
+
const clone = markdownDiv.cloneNode(true);
|
|
495
|
+
|
|
496
|
+
// Remove UI buttons, thought overlays, and interactive toolbars
|
|
497
|
+
clone
|
|
498
|
+
.querySelectorAll(
|
|
499
|
+
"button, .thoughts-container, .thoughts-wrapper, model-thoughts, .table-footer, .hide-from-message-actions, message-actions, election-info-disclaimer, finance-info-disclaimer, .sources-list",
|
|
500
|
+
)
|
|
501
|
+
.forEach((el) => el.remove());
|
|
502
|
+
|
|
503
|
+
// Unwrap response-element wrappers
|
|
504
|
+
clone.querySelectorAll("response-element").forEach((el) => {
|
|
505
|
+
while (el.firstChild) {
|
|
506
|
+
el.parentNode.insertBefore(el.firstChild, el);
|
|
570
507
|
}
|
|
508
|
+
el.remove();
|
|
571
509
|
});
|
|
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
510
|
|
|
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
|
-
}
|
|
511
|
+
const text = convertToMarkdown(clone);
|
|
512
|
+
const trimmed = text.trim();
|
|
513
|
+
if (trimmed && !seenTexts.has(trimmed)) {
|
|
514
|
+
seenTexts.add(trimmed);
|
|
515
|
+
messages.push({
|
|
516
|
+
role: "Model",
|
|
517
|
+
content: trimmed,
|
|
518
|
+
});
|
|
661
519
|
}
|
|
662
520
|
}
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
}
|
|
663
524
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
525
|
+
// Strategy 2: Deep Research immersive panel structure fallback
|
|
526
|
+
if (messages.length === 0) {
|
|
527
|
+
const deepResearchPanel = document.querySelector(
|
|
528
|
+
"deep-research-immersive-panel",
|
|
529
|
+
);
|
|
530
|
+
if (deepResearchPanel) {
|
|
531
|
+
const panelContent =
|
|
532
|
+
this.extractDeepResearchPanelContent(deepResearchPanel);
|
|
533
|
+
panelContent.forEach((section) => {
|
|
534
|
+
if (section.content && !seenTexts.has(section.content)) {
|
|
535
|
+
seenTexts.add(section.content);
|
|
536
|
+
messages.push({
|
|
537
|
+
role: section.role,
|
|
538
|
+
content: section.content,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
}
|
|
674
544
|
|
|
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
|
-
}
|
|
545
|
+
// Strategy 3: General content container fallback
|
|
546
|
+
if (messages.length === 0) {
|
|
547
|
+
const contentSelectors = [
|
|
548
|
+
"main",
|
|
549
|
+
"article",
|
|
550
|
+
".content",
|
|
551
|
+
".main-content",
|
|
552
|
+
'[role="main"]',
|
|
553
|
+
".chat-window-content",
|
|
554
|
+
];
|
|
555
|
+
|
|
556
|
+
for (const selector of contentSelectors) {
|
|
557
|
+
const el = document.querySelector(selector);
|
|
558
|
+
if (el) {
|
|
559
|
+
const text = (el.textContent || "").trim();
|
|
560
|
+
if (text.length > 100) {
|
|
561
|
+
const sections = this.extractDeepResearchSections(el);
|
|
562
|
+
if (sections.length > 0) {
|
|
563
|
+
sections.forEach((s) => {
|
|
564
|
+
if (s.content && !seenTexts.has(s.content)) {
|
|
565
|
+
seenTexts.add(s.content);
|
|
566
|
+
messages.push(s);
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
break;
|
|
708
570
|
}
|
|
709
571
|
}
|
|
710
572
|
}
|
|
711
573
|
}
|
|
574
|
+
}
|
|
712
575
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
576
|
+
console.log(
|
|
577
|
+
`[Gemini Parser] Total DOM messages extracted: ${messages.length}`,
|
|
578
|
+
);
|
|
579
|
+
return {
|
|
580
|
+
title,
|
|
581
|
+
messages,
|
|
582
|
+
url: currentUrl,
|
|
583
|
+
metadata: {
|
|
721
584
|
Source: "Gemini",
|
|
722
585
|
Date: new Date().toLocaleString(),
|
|
723
586
|
Link: currentUrl,
|
|
724
587
|
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
|
-
}
|
|
588
|
+
},
|
|
589
|
+
};
|
|
751
590
|
}
|
|
752
591
|
|
|
753
|
-
// Helper method to extract sections from Deep Research content
|
|
754
592
|
extractDeepResearchSections(contentElement) {
|
|
755
|
-
console.log("[Gemini Parser] Extracting Deep Research sections...");
|
|
756
593
|
const sections = [];
|
|
757
|
-
const text = contentElement.innerText || "";
|
|
594
|
+
const text = contentElement.innerText || contentElement.textContent || "";
|
|
758
595
|
|
|
759
|
-
// Look for common Deep Research patterns
|
|
760
596
|
const patterns = [
|
|
761
|
-
// Pattern 1: "Prompt:" and "Response:" sections
|
|
762
597
|
{
|
|
763
598
|
promptRegex:
|
|
764
599
|
/(?:Prompt|You said)[:\s]*\n*([\s\S]*?)(?=\n\s*(?:Response|I've completed|Generating|Start research)|$)/i,
|
|
765
600
|
responseRegex:
|
|
766
601
|
/(?:Response|I've completed|Generating|Start research)[:\s]*\n*([\s\S]*?)(?=\n\s*(?:Prompt|You said)|$)/i,
|
|
767
602
|
},
|
|
768
|
-
// Pattern 2: Question/Answer format
|
|
769
603
|
{
|
|
770
604
|
promptRegex:
|
|
771
605
|
/(?:Question|Q)[:\s]*\n*([\s\S]*?)(?=\n\s*(?:Answer|A|Response)|$)/i,
|
|
772
606
|
responseRegex:
|
|
773
607
|
/(?:Answer|A|Response)[:\s]*\n*([\s\S]*?)(?=\n\s*(?:Question|Q)|$)/i,
|
|
774
608
|
},
|
|
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
609
|
];
|
|
783
610
|
|
|
784
|
-
// Try each pattern
|
|
785
611
|
for (const pattern of patterns) {
|
|
786
|
-
console.log("[Gemini Parser] Trying pattern...");
|
|
787
612
|
const promptMatches = text.match(pattern.promptRegex);
|
|
788
613
|
const responseMatches = text.match(pattern.responseRegex);
|
|
789
614
|
|
|
790
615
|
if (promptMatches && promptMatches[1]) {
|
|
791
616
|
const promptContent = promptMatches[1].trim();
|
|
792
|
-
console.log(
|
|
793
|
-
"[Gemini Parser] Found prompt content, length:",
|
|
794
|
-
promptContent.length,
|
|
795
|
-
);
|
|
796
617
|
if (promptContent.length > 20) {
|
|
797
618
|
sections.push({
|
|
798
619
|
role: "User",
|
|
799
620
|
content: promptContent,
|
|
800
621
|
});
|
|
801
|
-
console.log("[Gemini Parser] Added prompt section");
|
|
802
622
|
}
|
|
803
623
|
}
|
|
804
624
|
|
|
805
625
|
if (responseMatches && responseMatches[1]) {
|
|
806
626
|
const responseContent = responseMatches[1].trim();
|
|
807
|
-
console.log(
|
|
808
|
-
"[Gemini Parser] Found response content, length:",
|
|
809
|
-
responseContent.length,
|
|
810
|
-
);
|
|
811
627
|
if (responseContent.length > 50) {
|
|
812
628
|
sections.push({
|
|
813
629
|
role: "Model",
|
|
814
630
|
content: responseContent,
|
|
815
631
|
});
|
|
816
|
-
console.log("[Gemini Parser] Added response section");
|
|
817
632
|
}
|
|
818
633
|
}
|
|
819
634
|
|
|
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
|
-
});
|
|
635
|
+
if (sections.length > 0) return sections;
|
|
896
636
|
}
|
|
897
637
|
|
|
898
|
-
console.log("[Gemini Parser] Total sections extracted:", sections.length);
|
|
899
638
|
return sections;
|
|
900
639
|
}
|
|
901
640
|
|
|
902
|
-
// Helper method to extract content from Deep Research immersive panel
|
|
903
641
|
extractDeepResearchPanelContent(panelElement) {
|
|
904
|
-
console.log("[Gemini Parser] Looking for Deep Research panel content...");
|
|
905
642
|
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
643
|
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
|
-
}
|
|
644
|
+
const contentElements = panelElement.querySelectorAll(
|
|
645
|
+
".markdown, .content, .research-content, .panel-content",
|
|
646
|
+
);
|
|
647
|
+
contentElements.forEach((element) => {
|
|
648
|
+
const text = (element.innerText || element.textContent || "").trim();
|
|
649
|
+
if (text.length > 100) {
|
|
650
|
+
sections.push({
|
|
651
|
+
role: "Model",
|
|
652
|
+
content: text,
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
});
|
|
943
656
|
|
|
944
|
-
// If no structured content found, extract all text from panel
|
|
945
657
|
if (sections.length === 0) {
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
658
|
+
const panelText = (
|
|
659
|
+
panelElement.innerText ||
|
|
660
|
+
panelElement.textContent ||
|
|
661
|
+
""
|
|
662
|
+
).trim();
|
|
951
663
|
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
|
-
}
|
|
664
|
+
sections.push({
|
|
665
|
+
role: "Model",
|
|
666
|
+
content: panelText,
|
|
963
667
|
});
|
|
964
668
|
}
|
|
965
669
|
}
|
|
966
670
|
} catch (error) {
|
|
967
671
|
console.error("[Gemini Parser] Error extracting panel content:", error);
|
|
968
672
|
}
|
|
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
673
|
return sections;
|
|
1011
674
|
}
|
|
1012
675
|
}
|