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.
Files changed (2) hide show
  1. package/ai/gemini.js +543 -880
  2. 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 url.includes("gemini.google.com");
11
+ return (
12
+ typeof url === "string" &&
13
+ (url.includes("gemini.google.com") || url.includes("bard.google.com"))
14
+ );
8
15
  }
9
16
 
10
- async parse() {
11
- console.log("[Gemini Parser] ========== STARTING PARSE() ==========");
17
+ getPlatformName() {
18
+ return "Gemini";
19
+ }
12
20
 
21
+ getConversationId(url) {
13
22
  try {
14
- //1. Title Extraction - Enhanced for Deep Research
15
- let title = "";
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
- const isInsideMessage = (el) => {
18
- return !!el.closest(
19
- "user-query, model-response, .conversation-container, message-content, .query-text, .markdown",
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
- // 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();
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
- const isGeneric =
35
- !cleanedDocTitle ||
36
- cleanedDocTitle.toLowerCase() === "new chat" ||
37
- cleanedDocTitle.toLowerCase() === "help" ||
38
- cleanedDocTitle.toLowerCase() === "settings";
85
+ const mode = options.parserMode || "auto";
39
86
 
40
- if (cleanedDocTitle && !isGeneric && cleanedDocTitle.length > 2) {
41
- title = cleanedDocTitle;
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] Title set from Strategy 1 (document.title):",
44
- title,
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
- navText &&
64
- navText.length > 2 &&
65
- !navText.toLowerCase().includes("new chat")
105
+ apiResult &&
106
+ apiResult.messages &&
107
+ apiResult.messages.length > 0
66
108
  ) {
67
- title = navText;
68
109
  console.log(
69
- "[Gemini Parser] Title set from Strategy 2 (active navigation):",
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
- // 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"]',
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
- // 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
- }
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
- // 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
- );
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
- 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
- }
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
- if (!title || title.length < 2) {
162
- title = "Gemini Conversation";
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
- console.log("[Gemini Parser] Final title:", title);
270
+ convertApiItemsToMessages(items, options = {}) {
271
+ const messages = [];
166
272
 
167
- const messages = [];
168
- const seenTexts = new Set();
273
+ for (const item of items) {
274
+ if (!Array.isArray(item)) continue;
169
275
 
170
- console.log("[Gemini Parser] Starting content extraction...");
171
- let extractionAttempted = false;
276
+ const userText = this.findUserTextInApiItem(item);
277
+ if (userText) {
278
+ messages.push({
279
+ role: "User",
280
+ content: userText.trim(),
281
+ });
282
+ }
172
283
 
173
- // 2. Content Extraction - Enhanced for Deep Research
174
- // Try multiple strategies to extract content
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
- // 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",
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
- console.log(
184
- "[Gemini Parser] Found conversation containers:",
185
- conversationContainers.length,
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 (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
- }
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
- // 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
- }
383
+ return "Gemini Conversation";
384
+ }
350
385
 
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
- }
386
+ isInsideMessage(el) {
387
+ return !!el.closest?.(
388
+ "user-query, model-response, .conversation-container, message-content, .query-text, .markdown",
389
+ );
390
+ }
393
391
 
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
- }
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
- // 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
- }
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
- // 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;
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
- // 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
- );
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
- if (deepResearchPanel) {
530
- console.log("[Gemini Parser] Processing Deep Research panel...");
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
- // 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);
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
- // 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
- );
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
- // 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
- }
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
- // 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
- );
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
- 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
- }
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
- 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 = {
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
- // 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
- });
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
- // 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
- }
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
- 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);
658
+ const panelText = (
659
+ panelElement.innerText ||
660
+ panelElement.textContent ||
661
+ ""
662
+ ).trim();
951
663
  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
- }
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
  }