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.
Files changed (2) hide show
  1. package/ai/gemini.js +569 -877
  2. 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 url.includes("gemini.google.com");
21
+ return (
22
+ typeof url === "string" &&
23
+ (url.includes("gemini.google.com") || url.includes("bard.google.com"))
24
+ );
8
25
  }
9
26
 
10
- async parse() {
11
- console.log("[Gemini Parser] ========== STARTING PARSE() ==========");
27
+ getPlatformName() {
28
+ return "Gemini";
29
+ }
12
30
 
31
+ getConversationId(url) {
13
32
  try {
14
- //1. Title Extraction - Enhanced for Deep Research
15
- let title = "";
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
- const isInsideMessage = (el) => {
18
- return !!el.closest(
19
- "user-query, model-response, .conversation-container, message-content, .query-text, .markdown",
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
- // Strategy 1: Using document.title
24
- console.log("[Gemini Parser] Strategy 1: Looking at document.title...");
25
- if (document.title) {
26
- const cleanedDocTitle = document.title
27
- .replace(/Google/g, "")
28
- .replace(/Gemini/g, "")
29
- .replace(/Advanced/g, "")
30
- .replace(/- /g, "")
31
- .replace(/—/g, "")
32
- .trim();
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
- const isGeneric =
35
- !cleanedDocTitle ||
36
- cleanedDocTitle.toLowerCase() === "new chat" ||
37
- cleanedDocTitle.toLowerCase() === "help" ||
38
- cleanedDocTitle.toLowerCase() === "settings";
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
- if (cleanedDocTitle && !isGeneric && cleanedDocTitle.length > 2) {
41
- title = cleanedDocTitle;
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
- "[Gemini Parser] Title set from Strategy 1 (document.title):",
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
- // 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();
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
- navText &&
64
- navText.length > 2 &&
65
- !navText.toLowerCase().includes("new chat")
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
- "[Gemini Parser] Title set from Strategy 2 (active navigation):",
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
- // Strategy 3: Check for Deep Research title patterns
77
- if (!title) {
78
- console.log(
79
- "[Gemini Parser] Strategy 3: Looking for Deep Research title patterns...",
80
- );
81
- const deepResearchTitle = document.querySelector(
82
- 'h1, .title, .conversation-title, [data-testid="title"]',
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
- // Strategy 4: Look for title in page content (Deep Research reports often have titles in content)
100
- if (!title) {
101
- console.log(
102
- "[Gemini Parser] Strategy 4: Looking for title in page content...",
103
- );
104
- const contentTitles = document.querySelectorAll(
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
- // Strategy 5: Top Bar or Sidebar (original logic)
130
- if (!title) {
131
- console.log(
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
- for (const el of possibleHeaders) {
143
- if (isInsideMessage(el)) continue;
144
- const text = el.innerText.trim();
145
- if (
146
- text.length > 5 &&
147
- !text.includes("Gemini") &&
148
- !text.includes("Help") &&
149
- !text.includes("Settings")
150
- ) {
151
- const rect = el.getBoundingClientRect();
152
- if (rect.top < 100 && rect.left > 50) {
153
- title = text;
154
- console.log("[Gemini Parser] Title set from Strategy 5:", title);
155
- break;
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
- if (!title || title.length < 2) {
162
- title = "Gemini Conversation";
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
- console.log("[Gemini Parser] Final title:", title);
322
+ return messages;
323
+ }
166
324
 
167
- const messages = [];
168
- const seenTexts = new Set();
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
- console.log("[Gemini Parser] Starting content extraction...");
171
- let extractionAttempted = false;
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
- // 2. Content Extraction - Enhanced for Deep Research
174
- // Try multiple strategies to extract content
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
- // Strategy 1: Original conversation containers
177
- console.log(
178
- "[Gemini Parser] Strategy 1: Looking for conversation containers...",
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
- console.log(
184
- "[Gemini Parser] Found conversation containers:",
185
- conversationContainers.length,
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 (conversationContainers.length > 0) {
188
- console.log("[Gemini Parser] Processing conversation containers...");
189
- extractionAttempted = true;
190
- conversationContainers.forEach((container) => {
191
- console.log("[Gemini Parser] Processing conversation container...");
192
-
193
- // First, check for user query
194
- const userQuery = container.querySelector("user-query");
195
- if (userQuery) {
196
- console.log("[Gemini Parser] Found user query...");
197
- const queryText = userQuery.querySelector(".query-text");
198
- if (queryText) {
199
- console.log("[Gemini Parser] Found query text...");
200
- const clone = queryText.cloneNode(true);
201
- clone
202
- .querySelectorAll(
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
- // Then, check for model response
222
- const modelResponse = container.querySelector("model-response");
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
- // Strategy 2: Look for content in parent/sibling elements of model-response
352
- if (!foundContent) {
353
- console.log(
354
- "[Gemini Parser] Trying parent/sibling element extraction...",
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
- // Strategy 3: Look for content in the entire conversation container (outside model-response)
395
- if (!foundContent) {
396
- console.log(
397
- "[Gemini Parser] Trying full container content extraction...",
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
- // Strategy 4: Last resort - get all text from message-content
426
- if (!foundContent) {
427
- const allText = messageContent.innerText.trim();
428
- console.log(
429
- "[Gemini Parser] Last resort text length:",
430
- allText.length,
431
- );
432
- console.log(
433
- "[Gemini Parser] Last resort text preview:",
434
- allText.substring(0, 200) + "...",
435
- );
436
-
437
- if (allText && allText.length > 50) {
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
- // Strategy 2: Deep Research content extraction
511
- if (!extractionAttempted) {
512
- console.log(
513
- "[Gemini Parser] Strategy 2: No conversation containers found, trying Deep Research panel...",
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
- // First try Deep Research immersive panel structure
518
- console.log(
519
- "[Gemini Parser] Looking for deep-research-immersive-panel...",
520
- );
521
- const deepResearchPanel = document.querySelector(
522
- "deep-research-immersive-panel",
523
- );
524
- console.log(
525
- "[Gemini Parser] Deep Research panel found:",
526
- !!deepResearchPanel,
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
- if (deepResearchPanel) {
530
- console.log("[Gemini Parser] Processing Deep Research panel...");
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
- // Extract title from panel
533
- const panelTitle = deepResearchPanel.querySelector(
534
- ".title-text, h2, h1",
535
- );
536
- if (panelTitle && !title) {
537
- const titleText = panelTitle.innerText.trim();
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
- // Extract content from panel
546
- console.log(
547
- "[Gemini Parser] Calling extractDeepResearchPanelContent...",
548
- );
549
- const panelContent =
550
- this.extractDeepResearchPanelContent(deepResearchPanel);
551
- console.log(
552
- "[Gemini Parser] Panel content extracted, sections:",
553
- panelContent.length,
554
- );
555
-
556
- if (panelContent.length > 0) {
557
- panelContent.forEach((section) => {
558
- if (section.content && !seenTexts.has(section.content)) {
559
- seenTexts.add(section.content);
560
- messages.push({
561
- role: section.role,
562
- content: section.content,
563
- });
564
- console.log(
565
- "[Gemini Parser] Added panel section, role:",
566
- section.role,
567
- "length:",
568
- section.content.length,
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
- // Fallback: Look for content in main, article, or content areas
582
- if (messages.length === 0) {
583
- console.log(
584
- "[Gemini Parser] Strategy 3: Trying fallback content extraction...",
585
- );
586
- const contentSelectors = [
587
- "main",
588
- "article",
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
- // Strategy 3: Fallback - look for any meaningful content
665
- if (!contentFound) {
666
- console.log(
667
- "[Gemini Parser] Strategy 4: Final fallback - extracting body content...",
668
- );
669
- const bodyContent = document.body.innerText.trim();
670
- console.log(
671
- "[Gemini Parser] Body content length:",
672
- bodyContent.length,
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
- if (bodyContent && bodyContent.length > 200) {
676
- // Try to extract structured content from body
677
- const sections = this.extractDeepResearchSections(document.body);
678
- console.log(
679
- "[Gemini Parser] Body sections extracted:",
680
- sections.length,
681
- );
682
-
683
- if (sections.length > 0) {
684
- sections.forEach((section) => {
685
- if (section.content && !seenTexts.has(section.content)) {
686
- seenTexts.add(section.content);
687
- messages.push({
688
- role: section.role,
689
- content: section.content,
690
- });
691
- console.log(
692
- "[Gemini Parser] Added body section, role:",
693
- section.role,
694
- );
695
- }
696
- });
697
- } else {
698
- // Last resort - treat as single response
699
- console.log(
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
- console.log("[Gemini Parser] Total messages extracted:", messages.length);
714
- console.log("[Gemini Parser] ========== FINISHING PARSE() ==========");
715
-
716
- const currentUrl =
717
- typeof window !== "undefined" && window.location
718
- ? window.location.href || ""
719
- : "";
720
- const metadata = {
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
- // If we found meaningful sections, stop trying other patterns
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
- // Look for content within panel
915
- const contentSelectors = [
916
- ".markdown",
917
- ".content",
918
- ".research-content",
919
- ".panel-content",
920
- 'div[class*="content"]',
921
- 'div[class*="markdown"]',
922
- 'div[class*="research"]',
923
- ];
924
-
925
- for (const selector of contentSelectors) {
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
- console.log(
947
- "[Gemini Parser] No structured content found, extracting all panel text...",
948
- );
949
- const panelText = panelElement.innerText.trim();
950
- console.log("[Gemini Parser] Panel text length:", panelText.length);
687
+ const panelText = (
688
+ panelElement.innerText ||
689
+ panelElement.textContent ||
690
+ ""
691
+ ).trim();
951
692
  if (panelText.length > 200) {
952
- // Try to split into logical sections
953
- const parts = this.splitIntoSections(panelText);
954
- console.log("[Gemini Parser] Split into parts:", parts.length);
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
  }