decant-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ai/gemini.js ADDED
@@ -0,0 +1,856 @@
1
+ import { ChatParser } from './base.js';
2
+ import { convertToMarkdown } from '../utils/html-to-markdown.js';
3
+
4
+ export class GeminiParser extends ChatParser {
5
+ name = 'Gemini';
6
+ isAvailable(url) {
7
+ return url.includes('gemini.google.com');
8
+ }
9
+
10
+ async parse() {
11
+ console.log('[Gemini Parser] ========== STARTING PARSE() ==========');
12
+
13
+ try {
14
+ //1. Title Extraction - Enhanced for Deep Research
15
+ let title = '';
16
+
17
+ const isInsideMessage = (el) => {
18
+ return !!el.closest(
19
+ 'user-query, model-response, .conversation-container, message-content, .query-text, .markdown',
20
+ );
21
+ };
22
+
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();
33
+
34
+ const isGeneric =
35
+ !cleanedDocTitle ||
36
+ cleanedDocTitle.toLowerCase() === 'new chat' ||
37
+ cleanedDocTitle.toLowerCase() === 'help' ||
38
+ cleanedDocTitle.toLowerCase() === 'settings';
39
+
40
+ if (cleanedDocTitle && !isGeneric && cleanedDocTitle.length > 2) {
41
+ title = cleanedDocTitle;
42
+ console.log('[Gemini Parser] Title set from Strategy 1 (document.title):', title);
43
+ }
44
+ }
45
+
46
+ // Strategy 2: Looking for active navigation
47
+ if (!title) {
48
+ console.log('[Gemini Parser] Strategy 2: Looking for active navigation...');
49
+ const activeNav = document.querySelector('a[aria-current="page"], .selected');
50
+ if (activeNav) {
51
+ const navText = activeNav.innerText
52
+ .replace(/more_vert/g, '')
53
+ .replace(/\n/g, ' ')
54
+ .trim();
55
+ if (navText && navText.length > 2 && !navText.toLowerCase().includes('new chat')) {
56
+ title = navText;
57
+ console.log('[Gemini Parser] Title set from Strategy 2 (active navigation):', title);
58
+ }
59
+ }
60
+ }
61
+
62
+ // Strategy 3: Check for Deep Research title patterns
63
+ if (!title) {
64
+ console.log('[Gemini Parser] Strategy 3: Looking for Deep Research title patterns...');
65
+ const deepResearchTitle = document.querySelector(
66
+ 'h1, .title, .conversation-title, [data-testid="title"]',
67
+ );
68
+ if (deepResearchTitle && !isInsideMessage(deepResearchTitle)) {
69
+ const text = deepResearchTitle.innerText.trim();
70
+ console.log('[Gemini Parser] Found potential title:', text);
71
+ if (
72
+ text.length > 5 &&
73
+ !text.includes('Gemini') &&
74
+ !text.includes('Help') &&
75
+ !text.includes('Settings')
76
+ ) {
77
+ title = text;
78
+ console.log('[Gemini Parser] Title set from Strategy 3:', title);
79
+ }
80
+ }
81
+ }
82
+
83
+ // Strategy 4: Look for title in page content (Deep Research reports often have titles in content)
84
+ if (!title) {
85
+ console.log('[Gemini Parser] Strategy 4: Looking for title in page content...');
86
+ const contentTitles = document.querySelectorAll(
87
+ 'main h1, main h2, article h1, article h2, .content h1, .content h2',
88
+ );
89
+ console.log('[Gemini Parser] Found content titles:', contentTitles.length);
90
+ for (const el of contentTitles) {
91
+ if (isInsideMessage(el)) continue;
92
+ const text = el.innerText.trim();
93
+ if (
94
+ text.length > 5 &&
95
+ !text.includes('Gemini') &&
96
+ !text.includes('Help') &&
97
+ !text.includes('Settings') &&
98
+ !text.includes('Prompt:') &&
99
+ !text.includes('Response:')
100
+ ) {
101
+ title = text;
102
+ console.log('[Gemini Parser] Title set from Strategy 4:', title);
103
+ break;
104
+ }
105
+ }
106
+ }
107
+
108
+ // Strategy 5: Top Bar or Sidebar (original logic)
109
+ if (!title) {
110
+ console.log('[Gemini Parser] Strategy 5: Looking for title in top bar/sidebar...');
111
+ const possibleHeaders = document.querySelectorAll(
112
+ 'h1, button[aria-haspopup="true"], button[aria-expanded]',
113
+ );
114
+ console.log('[Gemini Parser] Found possible headers:', possibleHeaders.length);
115
+
116
+ for (const el of possibleHeaders) {
117
+ if (isInsideMessage(el)) continue;
118
+ const text = el.innerText.trim();
119
+ if (
120
+ text.length > 5 &&
121
+ !text.includes('Gemini') &&
122
+ !text.includes('Help') &&
123
+ !text.includes('Settings')
124
+ ) {
125
+ const rect = el.getBoundingClientRect();
126
+ if (rect.top < 100 && rect.left > 50) {
127
+ title = text;
128
+ console.log('[Gemini Parser] Title set from Strategy 5:', title);
129
+ break;
130
+ }
131
+ }
132
+ }
133
+ }
134
+
135
+ if (!title || title.length < 2) {
136
+ title = 'Gemini Conversation';
137
+ }
138
+
139
+ console.log('[Gemini Parser] Final title:', title);
140
+
141
+ const messages = [];
142
+ const seenTexts = new Set();
143
+
144
+ console.log('[Gemini Parser] Starting content extraction...');
145
+ let extractionAttempted = false;
146
+
147
+ // 2. Content Extraction - Enhanced for Deep Research
148
+ // Try multiple strategies to extract content
149
+
150
+ // Strategy 1: Original conversation containers
151
+ console.log('[Gemini Parser] Strategy 1: Looking for conversation containers...');
152
+ const conversationContainers = document.querySelectorAll('.conversation-container');
153
+ console.log('[Gemini Parser] Found conversation containers:', conversationContainers.length);
154
+ if (conversationContainers.length > 0) {
155
+ console.log('[Gemini Parser] Processing conversation containers...');
156
+ extractionAttempted = true;
157
+ conversationContainers.forEach((container) => {
158
+ console.log('[Gemini Parser] Processing conversation container...');
159
+
160
+ // First, check for user query
161
+ const userQuery = container.querySelector('user-query');
162
+ if (userQuery) {
163
+ console.log('[Gemini Parser] Found user query...');
164
+ const queryText = userQuery.querySelector('.query-text');
165
+ if (queryText) {
166
+ console.log('[Gemini Parser] Found query text...');
167
+ const clone = queryText.cloneNode(true);
168
+ clone
169
+ .querySelectorAll('.cdk-visually-hidden, [class*="screen-reader"]')
170
+ .forEach((el) => el.remove());
171
+ const userText = clone.innerText.trim();
172
+ if (userText && !seenTexts.has(userText)) {
173
+ seenTexts.add(userText);
174
+ messages.push({
175
+ role: 'User',
176
+ content: userText,
177
+ });
178
+ console.log(
179
+ '[Gemini Parser] Added user message:',
180
+ userText.substring(0, 50) + '...',
181
+ );
182
+ }
183
+ }
184
+ }
185
+
186
+ // Then, check for model response
187
+ const modelResponse = container.querySelector('model-response');
188
+ if (modelResponse) {
189
+ console.log('[Gemini Parser] Found model response...');
190
+ const messageContent = modelResponse.querySelector('message-content');
191
+ if (messageContent) {
192
+ console.log('[Gemini Parser] Found message content...');
193
+ const markdownDiv = messageContent.querySelector(
194
+ '.markdown.markdown-main-panel, .markdown',
195
+ );
196
+ if (markdownDiv) {
197
+ console.log('[Gemini Parser] Found markdown div...');
198
+ // Clone to avoid modifying the original DOM
199
+ const clone = markdownDiv.cloneNode(true);
200
+
201
+ // Remove UI elements that shouldn't be in the export
202
+ clone
203
+ .querySelectorAll(
204
+ 'button, .thoughts-container, .thoughts-wrapper, model-thoughts, .table-footer, .hide-from-message-actions',
205
+ )
206
+ .forEach((el) => el.remove());
207
+
208
+ // Remove response-element wrappers (they contain export buttons)
209
+ clone.querySelectorAll('response-element').forEach((el) => {
210
+ // Keep the table but remove the wrapper
211
+ while (el.firstChild) {
212
+ el.parentNode.insertBefore(el.firstChild, el);
213
+ }
214
+ el.remove();
215
+ });
216
+
217
+ // Convert to markdown
218
+ const text = convertToMarkdown(clone);
219
+ console.log('[Gemini Parser] Converted to markdown, length:', text.length);
220
+ console.log('[Gemini Parser] Full markdown content:');
221
+ console.log(text);
222
+ console.log('[Gemini Parser] End of markdown content');
223
+
224
+ if (text && text.trim() && !seenTexts.has(text.trim())) {
225
+ seenTexts.add(text.trim());
226
+ messages.push({
227
+ role: 'Model',
228
+ content: text.trim(),
229
+ });
230
+ console.log(
231
+ '[Gemini Parser] Added model message:',
232
+ text.substring(0, 50) + '...',
233
+ );
234
+ }
235
+ } else {
236
+ console.log(
237
+ '[Gemini Parser] No markdown div found, trying comprehensive content extraction...',
238
+ );
239
+
240
+ // Strategy 1: Look for content in nested elements within message-content
241
+ const nestedSelectors = [
242
+ 'div[class*="content"]',
243
+ 'div[class*="research"]',
244
+ 'div[class*="report"]',
245
+ 'div[class*="analysis"]',
246
+ 'div[class*="section"]',
247
+ 'div[class*="paragraph"]',
248
+ 'p',
249
+ 'article',
250
+ 'section',
251
+ ];
252
+
253
+ let foundContent = false;
254
+ for (const selector of nestedSelectors) {
255
+ const nestedElements = messageContent.querySelectorAll(selector);
256
+ console.log(
257
+ '[Gemini Parser] Looking for nested elements with selector:',
258
+ selector,
259
+ 'found:',
260
+ nestedElements.length,
261
+ );
262
+
263
+ nestedElements.forEach((element) => {
264
+ const text = element.innerText.trim();
265
+ if (text.length > 100) {
266
+ console.log('[Gemini Parser] Found nested content, length:', text.length);
267
+ console.log(
268
+ '[Gemini Parser] Nested content preview:',
269
+ text.substring(0, 200) + '...',
270
+ );
271
+
272
+ const isDeepResearch =
273
+ text.includes('research') ||
274
+ text.includes('analysis') ||
275
+ text.includes('findings') ||
276
+ text.includes('cost') ||
277
+ text.includes('sweetener') ||
278
+ text.includes('projection') ||
279
+ text.includes('historical') ||
280
+ text.includes('economic') ||
281
+ text.includes('market') ||
282
+ text.includes('price') ||
283
+ text.includes('industry');
284
+
285
+ console.log('[Gemini Parser] Is Deep Research content:', isDeepResearch);
286
+
287
+ if (!seenTexts.has(text)) {
288
+ seenTexts.add(text);
289
+ messages.push({
290
+ role: 'Model',
291
+ content: text,
292
+ });
293
+ console.log(
294
+ '[Gemini Parser] Added nested content, Deep Research:',
295
+ isDeepResearch,
296
+ );
297
+ foundContent = true;
298
+ }
299
+ }
300
+ });
301
+
302
+ if (foundContent) break;
303
+ }
304
+
305
+ // Strategy 2: Look for content in parent/sibling elements of model-response
306
+ if (!foundContent) {
307
+ console.log('[Gemini Parser] Trying parent/sibling element extraction...');
308
+ const parentContainer = modelResponse.parentElement;
309
+ if (parentContainer) {
310
+ const siblings = parentContainer.children;
311
+ console.log('[Gemini Parser] Checking siblings, count:', siblings.length);
312
+
313
+ Array.from(siblings).forEach((sibling) => {
314
+ if (sibling !== modelResponse) {
315
+ const text = sibling.innerText.trim();
316
+ if (text.length > 200) {
317
+ console.log(
318
+ '[Gemini Parser] Found sibling content, length:',
319
+ text.length,
320
+ );
321
+ console.log(
322
+ '[Gemini Parser] Sibling content preview:',
323
+ text.substring(0, 200) + '...',
324
+ );
325
+
326
+ if (!seenTexts.has(text)) {
327
+ seenTexts.add(text);
328
+ messages.push({
329
+ role: 'Model',
330
+ content: text,
331
+ });
332
+ console.log('[Gemini Parser] Added sibling content');
333
+ foundContent = true;
334
+ }
335
+ }
336
+ }
337
+ });
338
+ }
339
+ }
340
+
341
+ // Strategy 3: Look for content in the entire conversation container (outside model-response)
342
+ if (!foundContent) {
343
+ console.log('[Gemini Parser] Trying full container content extraction...');
344
+ const containerText = container.innerText.trim();
345
+ console.log('[Gemini Parser] Full container text length:', containerText.length);
346
+
347
+ if (containerText.length > 500) {
348
+ console.log(
349
+ '[Gemini Parser] Full container content preview:',
350
+ containerText.substring(0, 200) + '...',
351
+ );
352
+
353
+ if (!seenTexts.has(containerText)) {
354
+ seenTexts.add(containerText);
355
+ messages.push({
356
+ role: 'Model',
357
+ content: containerText,
358
+ });
359
+ console.log('[Gemini Parser] Added full container content');
360
+ foundContent = true;
361
+ }
362
+ }
363
+ }
364
+
365
+ // Strategy 4: Last resort - get all text from message-content
366
+ if (!foundContent) {
367
+ const allText = messageContent.innerText.trim();
368
+ console.log('[Gemini Parser] Last resort text length:', allText.length);
369
+ console.log(
370
+ '[Gemini Parser] Last resort text preview:',
371
+ allText.substring(0, 200) + '...',
372
+ );
373
+
374
+ if (allText && allText.length > 50) {
375
+ // Check if this looks like Deep Research content
376
+ const isDeepResearch =
377
+ allText.includes('research') ||
378
+ allText.includes('analysis') ||
379
+ allText.includes('findings') ||
380
+ allText.includes('cost') ||
381
+ allText.includes('sweetener') ||
382
+ allText.includes('projection') ||
383
+ allText.includes('historical');
384
+
385
+ console.log('[Gemini Parser] Is Deep Research content:', isDeepResearch);
386
+
387
+ if (!seenTexts.has(allText)) {
388
+ seenTexts.add(allText);
389
+ messages.push({
390
+ role: 'Model',
391
+ content: allText,
392
+ });
393
+ console.log(
394
+ '[Gemini Parser] Added last resort model message, Deep Research:',
395
+ isDeepResearch,
396
+ );
397
+ }
398
+ }
399
+ }
400
+ }
401
+ } else {
402
+ console.log(
403
+ '[Gemini Parser] No message-content found, trying direct model-response text...',
404
+ );
405
+ // Fallback: get text directly from model-response
406
+ const directText = modelResponse.innerText.trim();
407
+ console.log('[Gemini Parser] Direct model response text length:', directText.length);
408
+
409
+ if (directText && directText.length > 50) {
410
+ const isDeepResearch =
411
+ directText.includes('research') ||
412
+ directText.includes('analysis') ||
413
+ directText.includes('findings') ||
414
+ directText.includes('cost') ||
415
+ directText.includes('sweetener') ||
416
+ directText.includes('projection') ||
417
+ directText.includes('historical');
418
+
419
+ console.log('[Gemini Parser] Direct text is Deep Research:', isDeepResearch);
420
+
421
+ if (!seenTexts.has(directText)) {
422
+ seenTexts.add(directText);
423
+ messages.push({
424
+ role: 'Model',
425
+ content: directText,
426
+ });
427
+ console.log(
428
+ '[Gemini Parser] Added direct model message, Deep Research:',
429
+ isDeepResearch,
430
+ );
431
+ }
432
+ }
433
+ }
434
+ }
435
+ });
436
+ }
437
+
438
+ // Strategy 2: Deep Research content extraction
439
+ if (!extractionAttempted) {
440
+ console.log(
441
+ '[Gemini Parser] Strategy 2: No conversation containers found, trying Deep Research panel...',
442
+ );
443
+ extractionAttempted = true;
444
+
445
+ // First try Deep Research immersive panel structure
446
+ console.log('[Gemini Parser] Looking for deep-research-immersive-panel...');
447
+ const deepResearchPanel = document.querySelector('deep-research-immersive-panel');
448
+ console.log('[Gemini Parser] Deep Research panel found:', !!deepResearchPanel);
449
+
450
+ if (deepResearchPanel) {
451
+ console.log('[Gemini Parser] Processing Deep Research panel...');
452
+
453
+ // Extract title from panel
454
+ const panelTitle = deepResearchPanel.querySelector('.title-text, h2, h1');
455
+ if (panelTitle && !title) {
456
+ const titleText = panelTitle.innerText.trim();
457
+ console.log('[Gemini Parser] Found panel title:', titleText);
458
+ if (titleText.length > 5 && !titleText.includes('Gemini')) {
459
+ title = titleText;
460
+ console.log('[Gemini Parser] Title updated from panel:', title);
461
+ }
462
+ }
463
+
464
+ // Extract content from panel
465
+ console.log('[Gemini Parser] Calling extractDeepResearchPanelContent...');
466
+ const panelContent = this.extractDeepResearchPanelContent(deepResearchPanel);
467
+ console.log('[Gemini Parser] Panel content extracted, sections:', panelContent.length);
468
+
469
+ if (panelContent.length > 0) {
470
+ panelContent.forEach((section) => {
471
+ if (section.content && !seenTexts.has(section.content)) {
472
+ seenTexts.add(section.content);
473
+ messages.push({
474
+ role: section.role,
475
+ content: section.content,
476
+ });
477
+ console.log(
478
+ '[Gemini Parser] Added panel section, role:',
479
+ section.role,
480
+ 'length:',
481
+ section.content.length,
482
+ );
483
+ }
484
+ });
485
+ } else {
486
+ console.log('[Gemini Parser] No panel content found, trying fallback...');
487
+ }
488
+ } else {
489
+ console.log('[Gemini Parser] No Deep Research panel found');
490
+ }
491
+
492
+ // Fallback: Look for content in main, article, or content areas
493
+ if (messages.length === 0) {
494
+ console.log('[Gemini Parser] Strategy 3: Trying fallback content extraction...');
495
+ const contentSelectors = [
496
+ 'main',
497
+ 'article',
498
+ '.content',
499
+ '.main-content',
500
+ '[role="main"]',
501
+ '.conversation-content',
502
+ '.chat-content',
503
+ '.message-content',
504
+ ];
505
+
506
+ let contentFound = false;
507
+
508
+ for (const selector of contentSelectors) {
509
+ console.log('[Gemini Parser] Trying selector:', selector);
510
+ const contentElement = document.querySelector(selector);
511
+ if (contentElement) {
512
+ console.log('[Gemini Parser] Found content element:', !!contentElement);
513
+ // Extract all text content from the main content area
514
+ const textContent = contentElement.innerText.trim();
515
+ console.log('[Gemini Parser] Text content length:', textContent.length);
516
+
517
+ if (textContent && textContent.length > 100) {
518
+ // Try to identify user prompts and responses
519
+ const sections = this.extractDeepResearchSections(contentElement);
520
+ console.log('[Gemini Parser] Extracted sections:', sections.length);
521
+
522
+ if (sections.length > 0) {
523
+ sections.forEach((section) => {
524
+ if (section.content && !seenTexts.has(section.content)) {
525
+ seenTexts.add(section.content);
526
+ messages.push({
527
+ role: section.role,
528
+ content: section.content,
529
+ });
530
+ console.log('[Gemini Parser] Added fallback section, role:', section.role);
531
+ }
532
+ });
533
+ contentFound = true;
534
+ break;
535
+ } else {
536
+ // If we can't parse sections, treat the whole content as a response
537
+ console.log('[Gemini Parser] Treating whole content as response...');
538
+ const markdown = convertToMarkdown(contentElement);
539
+ if (markdown && markdown.trim() && !seenTexts.has(markdown.trim())) {
540
+ seenTexts.add(markdown.trim());
541
+ messages.push({
542
+ role: 'Model',
543
+ content: markdown.trim(),
544
+ });
545
+ console.log('[Gemini Parser] Added fallback message');
546
+ contentFound = true;
547
+ break;
548
+ }
549
+ }
550
+ }
551
+ }
552
+ }
553
+
554
+ // Strategy 3: Fallback - look for any meaningful content
555
+ if (!contentFound) {
556
+ console.log('[Gemini Parser] Strategy 4: Final fallback - extracting body content...');
557
+ const bodyContent = document.body.innerText.trim();
558
+ console.log('[Gemini Parser] Body content length:', bodyContent.length);
559
+
560
+ if (bodyContent && bodyContent.length > 200) {
561
+ // Try to extract structured content from body
562
+ const sections = this.extractDeepResearchSections(document.body);
563
+ console.log('[Gemini Parser] Body sections extracted:', sections.length);
564
+
565
+ if (sections.length > 0) {
566
+ sections.forEach((section) => {
567
+ if (section.content && !seenTexts.has(section.content)) {
568
+ seenTexts.add(section.content);
569
+ messages.push({
570
+ role: section.role,
571
+ content: section.content,
572
+ });
573
+ console.log('[Gemini Parser] Added body section, role:', section.role);
574
+ }
575
+ });
576
+ } else {
577
+ // Last resort - treat as single response
578
+ console.log('[Gemini Parser] Last resort - treating as single response...');
579
+ messages.push({
580
+ role: 'Model',
581
+ content: bodyContent,
582
+ });
583
+ console.log('[Gemini Parser] Added last resort message');
584
+ }
585
+ }
586
+ }
587
+ }
588
+ }
589
+
590
+ console.log('[Gemini Parser] Total messages extracted:', messages.length);
591
+ console.log('[Gemini Parser] ========== FINISHING PARSE() ==========');
592
+
593
+ const currentUrl =
594
+ typeof window !== 'undefined' && window.location ? window.location.href || '' : '';
595
+ const metadata = {
596
+ Source: 'Gemini',
597
+ Date: new Date().toLocaleString(),
598
+ Link: currentUrl,
599
+ };
600
+
601
+ return {
602
+ title: title,
603
+ messages: messages,
604
+ url: currentUrl,
605
+ metadata: metadata,
606
+ };
607
+ } catch (error) {
608
+ console.error('[Gemini Parser] Error during parsing:', error);
609
+ const currentUrl =
610
+ typeof window !== 'undefined' && window.location ? window.location.href || '' : '';
611
+ return {
612
+ title: 'Gemini Conversation',
613
+ messages: [],
614
+ url: currentUrl,
615
+ metadata: {
616
+ Source: 'Gemini',
617
+ Date: new Date().toLocaleString(),
618
+ Link: currentUrl,
619
+ },
620
+ };
621
+ }
622
+ }
623
+
624
+ // Helper method to extract sections from Deep Research content
625
+ extractDeepResearchSections(contentElement) {
626
+ console.log('[Gemini Parser] Extracting Deep Research sections...');
627
+ const sections = [];
628
+ const text = contentElement.innerText || '';
629
+
630
+ // Look for common Deep Research patterns
631
+ const patterns = [
632
+ // Pattern 1: "Prompt:" and "Response:" sections
633
+ {
634
+ promptRegex:
635
+ /(?:Prompt|You said)[:\s]*\n*([\s\S]*?)(?=\n\s*(?:Response|I've completed|Generating|Start research)|$)/i,
636
+ responseRegex:
637
+ /(?:Response|I've completed|Generating|Start research)[:\s]*\n*([\s\S]*?)(?=\n\s*(?:Prompt|You said)|$)/i,
638
+ },
639
+ // Pattern 2: Question/Answer format
640
+ {
641
+ promptRegex: /(?:Question|Q)[:\s]*\n*([\s\S]*?)(?=\n\s*(?:Answer|A|Response)|$)/i,
642
+ responseRegex: /(?:Answer|A|Response)[:\s]*\n*([\s\S]*?)(?=\n\s*(?:Question|Q)|$)/i,
643
+ },
644
+ // Pattern 3: Look for research plan and results
645
+ {
646
+ promptRegex:
647
+ /(?: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,
648
+ responseRegex:
649
+ /(?:I've completed|Research|Analysis|Results|Findings)[:\s]*\n*([\s\S]*?)(?=\n\s*(?:Research plan|Research query|What is|How has)|$)/i,
650
+ },
651
+ ];
652
+
653
+ // Try each pattern
654
+ for (const pattern of patterns) {
655
+ console.log('[Gemini Parser] Trying pattern...');
656
+ const promptMatches = text.match(pattern.promptRegex);
657
+ const responseMatches = text.match(pattern.responseRegex);
658
+
659
+ if (promptMatches && promptMatches[1]) {
660
+ const promptContent = promptMatches[1].trim();
661
+ console.log('[Gemini Parser] Found prompt content, length:', promptContent.length);
662
+ if (promptContent.length > 20) {
663
+ sections.push({
664
+ role: 'User',
665
+ content: promptContent,
666
+ });
667
+ console.log('[Gemini Parser] Added prompt section');
668
+ }
669
+ }
670
+
671
+ if (responseMatches && responseMatches[1]) {
672
+ const responseContent = responseMatches[1].trim();
673
+ console.log('[Gemini Parser] Found response content, length:', responseContent.length);
674
+ if (responseContent.length > 50) {
675
+ sections.push({
676
+ role: 'Model',
677
+ content: responseContent,
678
+ });
679
+ console.log('[Gemini Parser] Added response section');
680
+ }
681
+ }
682
+
683
+ // If we found meaningful sections, stop trying other patterns
684
+ if (sections.length > 0) {
685
+ console.log('[Gemini Parser] Found sections using pattern matching');
686
+ return sections;
687
+ }
688
+ }
689
+
690
+ // If no structured sections found, try to extract based on HTML structure
691
+ console.log('[Gemini Parser] Trying HTML structure extraction...');
692
+ const userElements = contentElement.querySelectorAll(
693
+ '.user-query, .prompt, .question, [data-role="user"]',
694
+ );
695
+ console.log('[Gemini Parser] Found user elements:', userElements.length);
696
+ userElements.forEach((el) => {
697
+ const clone = el.cloneNode(true);
698
+ clone
699
+ .querySelectorAll('.cdk-visually-hidden, [class*="screen-reader"]')
700
+ .forEach((subEl) => subEl.remove());
701
+ const content = clone.innerText.trim();
702
+ if (content.length > 20) {
703
+ sections.push({
704
+ role: 'User',
705
+ content: content,
706
+ });
707
+ console.log('[Gemini Parser] Added user element from HTML structure');
708
+ }
709
+ });
710
+
711
+ // Look for elements that might contain responses
712
+ const responseElements = contentElement.querySelectorAll(
713
+ '.model-response, .response, .answer, [data-role="model"], .research-content',
714
+ );
715
+ console.log('[Gemini Parser] Found response elements:', responseElements.length);
716
+ responseElements.forEach((el) => {
717
+ const content = el.innerText.trim();
718
+ if (content.length > 50) {
719
+ sections.push({
720
+ role: 'Model',
721
+ content: content,
722
+ });
723
+ console.log('[Gemini Parser] Added response element from HTML structure');
724
+ }
725
+ });
726
+
727
+ // If still no sections, try to split by common delimiters
728
+ if (sections.length === 0) {
729
+ console.log('[Gemini Parser] Trying delimiter splitting...');
730
+ const delimiterPatterns = [
731
+ /\n\s*You said\s*\n/i,
732
+ /\n\s*Response\s*\n/i,
733
+ /\n\s*Prompt\s*\n/i,
734
+ /\n\s*I've completed\s*\n/i,
735
+ ];
736
+
737
+ let parts = [text];
738
+ delimiterPatterns.forEach((pattern) => {
739
+ parts = parts.flatMap((part) => part.split(pattern));
740
+ });
741
+
742
+ parts.forEach((part, index) => {
743
+ const trimmedPart = part.trim();
744
+ if (trimmedPart.length > 50) {
745
+ // Alternate between User and Model roles
746
+ const role = index % 2 === 0 ? 'User' : 'Model';
747
+ sections.push({
748
+ role: role,
749
+ content: trimmedPart,
750
+ });
751
+ console.log('[Gemini Parser] Added delimiter section, role:', role);
752
+ }
753
+ });
754
+ }
755
+
756
+ console.log('[Gemini Parser] Total sections extracted:', sections.length);
757
+ return sections;
758
+ }
759
+
760
+ // Helper method to extract content from Deep Research immersive panel
761
+ extractDeepResearchPanelContent(panelElement) {
762
+ console.log('[Gemini Parser] Looking for Deep Research panel content...');
763
+ const sections = [];
764
+
765
+ console.log('[Gemini Parser] Panel element found:', !!panelElement);
766
+ console.log(
767
+ '[Gemini Parser] Panel innerText length:',
768
+ panelElement.innerText ? panelElement.innerText.length : 0,
769
+ );
770
+
771
+ try {
772
+ // Look for content within panel
773
+ const contentSelectors = [
774
+ '.markdown',
775
+ '.content',
776
+ '.research-content',
777
+ '.panel-content',
778
+ 'div[class*="content"]',
779
+ 'div[class*="markdown"]',
780
+ 'div[class*="research"]',
781
+ ];
782
+
783
+ for (const selector of contentSelectors) {
784
+ console.log('[Gemini Parser] Trying selector:', selector);
785
+ const contentElements = panelElement.querySelectorAll(selector);
786
+ console.log('[Gemini Parser] Found elements:', contentElements.length);
787
+ contentElements.forEach((element) => {
788
+ const text = element.innerText.trim();
789
+ if (text.length > 100) {
790
+ sections.push({
791
+ role: 'Model',
792
+ content: text,
793
+ });
794
+ console.log('[Gemini Parser] Added panel content via selector:', selector);
795
+ }
796
+ });
797
+ }
798
+
799
+ // If no structured content found, extract all text from panel
800
+ if (sections.length === 0) {
801
+ console.log('[Gemini Parser] No structured content found, extracting all panel text...');
802
+ const panelText = panelElement.innerText.trim();
803
+ console.log('[Gemini Parser] Panel text length:', panelText.length);
804
+ if (panelText.length > 200) {
805
+ // Try to split into logical sections
806
+ const parts = this.splitIntoSections(panelText);
807
+ console.log('[Gemini Parser] Split into parts:', parts.length);
808
+ parts.forEach((part) => {
809
+ if (part.length > 50) {
810
+ sections.push({
811
+ role: 'Model',
812
+ content: part,
813
+ });
814
+ console.log('[Gemini Parser] Added panel text part');
815
+ }
816
+ });
817
+ }
818
+ }
819
+ } catch (error) {
820
+ console.error('[Gemini Parser] Error extracting panel content:', error);
821
+ }
822
+
823
+ console.log('[Gemini Parser] Panel content extraction complete, sections:', sections.length);
824
+ return sections;
825
+ }
826
+
827
+ // Helper method to split text into logical sections
828
+ splitIntoSections(text) {
829
+ console.log('[Gemini Parser] Splitting text into sections...');
830
+ const sections = [];
831
+
832
+ // Try to split by common delimiters
833
+ const delimiters = [
834
+ /\n\n+/g, // Double newlines
835
+ /\n(?=[A-Z])/g, // Newline followed by capital letter
836
+ /\.\s+/g, // Period followed by space
837
+ ];
838
+
839
+ let parts = [text];
840
+ delimiters.forEach((delimiter) => {
841
+ parts = parts.flatMap((part) => part.split(delimiter));
842
+ });
843
+
844
+ // Filter and clean sections
845
+ parts.forEach((part) => {
846
+ const cleaned = part.trim();
847
+ if (cleaned.length > 50 && !cleaned.match(/^\d+$/)) {
848
+ sections.push(cleaned);
849
+ console.log('[Gemini Parser] Added split section, length:', cleaned.length);
850
+ }
851
+ });
852
+
853
+ console.log('[Gemini Parser] Text splitting complete, sections:', sections.length);
854
+ return sections;
855
+ }
856
+ }