decant-core 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ai/lumo.js CHANGED
@@ -1,43 +1,49 @@
1
- import { ChatParser } from './base.js';
2
- import { convertToMarkdown } from '../utils/html-to-markdown.js';
1
+ import { ChatParser } from "./base.js";
2
+ import { convertToMarkdown } from "../utils/html-to-markdown.js";
3
3
 
4
4
  export class LumoParser extends ChatParser {
5
- name = 'Lumo';
5
+ name = "Lumo";
6
6
  isAvailable(url) {
7
- return url.includes('lumo.proton.me');
7
+ return url.includes("lumo.proton.me");
8
8
  }
9
9
 
10
10
  async parse() {
11
11
  // Extract Title
12
- let title = '';
13
- const titleBtn = document.querySelector('.conversation-header-title-view button');
12
+ let title = "";
13
+ const titleBtn = document.querySelector(
14
+ ".conversation-header-title-view button",
15
+ );
14
16
  if (titleBtn && titleBtn.textContent) {
15
17
  title = titleBtn.textContent.trim();
16
18
  } else if (document.title) {
17
- title = document.title.replace(/\s*-\s*Lumo.*$/i, '').trim();
19
+ title = document.title.replace(/\s*-\s*Lumo.*$/i, "").trim();
18
20
  }
19
- title = title || 'Lumo Conversation';
21
+ title = title || "Lumo Conversation";
20
22
 
21
23
  const messages = [];
22
- const messageElements = document.querySelectorAll('.lumo-chat-item[data-message-role]');
24
+ const messageElements = document.querySelectorAll(
25
+ ".lumo-chat-item[data-message-role]",
26
+ );
23
27
 
24
28
  for (const el of messageElements) {
25
- const roleAttr = el.getAttribute('data-message-role');
29
+ const roleAttr = el.getAttribute("data-message-role");
26
30
 
27
- if (roleAttr === 'user') {
31
+ if (roleAttr === "user") {
28
32
  const contentEl =
29
- el.querySelector('.lumo-markdown') || el.querySelector('.user-msg-container') || el;
33
+ el.querySelector(".lumo-markdown") ||
34
+ el.querySelector(".user-msg-container") ||
35
+ el;
30
36
  const text = convertToMarkdown(contentEl);
31
37
  if (text.trim()) {
32
38
  messages.push({
33
- role: 'User',
39
+ role: "User",
34
40
  content: text.trim(),
35
41
  });
36
42
  }
37
- } else if (roleAttr === 'assistant') {
43
+ } else if (roleAttr === "assistant") {
38
44
  const contentEl =
39
- el.querySelector('.progressive-markdown-content') ||
40
- el.querySelector('.assistant-msg-container') ||
45
+ el.querySelector(".progressive-markdown-content") ||
46
+ el.querySelector(".assistant-msg-container") ||
41
47
  el;
42
48
 
43
49
  const ownerDoc = el.ownerDocument || document;
@@ -45,26 +51,27 @@ export class LumoParser extends ChatParser {
45
51
 
46
52
  // Preprocess Lumo code blocks to standard <pre><code class="language-xyz">...</code></pre>
47
53
  contentElClone
48
- .querySelectorAll('.lumo-syntax-highlighter, .lumo-code-block')
54
+ .querySelectorAll(".lumo-syntax-highlighter, .lumo-code-block")
49
55
  .forEach((codeBlock) => {
50
- const codeEl = codeBlock.querySelector('code');
56
+ const codeEl = codeBlock.querySelector("code");
51
57
  if (codeEl) {
52
- let language = '';
53
- const match = (codeEl.className || '').match(/language-([^\s]+)/);
58
+ let language = "";
59
+ const match = (codeEl.className || "").match(/language-([^\s]+)/);
54
60
  if (match) {
55
61
  language = match[1];
56
62
  }
57
63
 
58
- const codeText = codeEl.textContent || '';
59
- const pre = ownerDoc.createElement('pre');
60
- const code = ownerDoc.createElement('code');
64
+ const codeText = codeEl.textContent || "";
65
+ const pre = ownerDoc.createElement("pre");
66
+ const code = ownerDoc.createElement("code");
61
67
  if (language) {
62
68
  code.className = `language-${language}`;
63
69
  }
64
70
  code.textContent = codeText;
65
71
  pre.appendChild(code);
66
72
 
67
- const parentToReplace = codeBlock.closest('.message-container') || codeBlock;
73
+ const parentToReplace =
74
+ codeBlock.closest(".message-container") || codeBlock;
68
75
  if (parentToReplace && parentToReplace.parentNode) {
69
76
  parentToReplace.parentNode.replaceChild(pre, parentToReplace);
70
77
  }
@@ -73,7 +80,9 @@ export class LumoParser extends ChatParser {
73
80
 
74
81
  // Clean up UI toolbar and avatar elements from assistant clone
75
82
  contentElClone
76
- .querySelectorAll('.action-toolbar, .lumo-no-copy, .lumo-avatar, button')
83
+ .querySelectorAll(
84
+ ".action-toolbar, .lumo-no-copy, .lumo-avatar, button",
85
+ )
77
86
  .forEach((noCopy) => {
78
87
  noCopy.remove();
79
88
  });
@@ -81,7 +90,7 @@ export class LumoParser extends ChatParser {
81
90
  const markdown = convertToMarkdown(contentElClone);
82
91
  if (markdown.trim()) {
83
92
  messages.push({
84
- role: 'Lumo',
93
+ role: "Lumo",
85
94
  content: markdown.trim(),
86
95
  });
87
96
  }
@@ -89,9 +98,11 @@ export class LumoParser extends ChatParser {
89
98
  }
90
99
 
91
100
  const currentUrl =
92
- typeof window !== 'undefined' && window.location ? window.location.href || '' : '';
101
+ typeof window !== "undefined" && window.location
102
+ ? window.location.href || ""
103
+ : "";
93
104
  const metadata = {
94
- Source: 'Lumo',
105
+ Source: "Lumo",
95
106
  Date: new Date().toLocaleString(),
96
107
  Link: currentUrl,
97
108
  };
package/ai/meta.js CHANGED
@@ -1,22 +1,26 @@
1
- import { ChatParser } from './base.js';
2
- import { convertToMarkdown } from '../utils/html-to-markdown.js';
1
+ import { ChatParser } from "./base.js";
2
+ import { convertToMarkdown } from "../utils/html-to-markdown.js";
3
3
 
4
4
  export class MetaParser extends ChatParser {
5
- name = 'Meta AI';
5
+ name = "Meta AI";
6
6
  isAvailable(url) {
7
- return url.includes('meta.ai');
7
+ return url.includes("meta.ai");
8
8
  }
9
9
 
10
10
  async parse() {
11
11
  // Try to get the conversation title from the input field or the header button
12
- const titleInput = document.querySelector('input[placeholder="Conversation title"]');
13
- const titleButton = document.querySelector('[data-slot="button"] span.truncate');
12
+ const titleInput = document.querySelector(
13
+ 'input[placeholder="Conversation title"]',
14
+ );
15
+ const titleButton = document.querySelector(
16
+ '[data-slot="button"] span.truncate',
17
+ );
14
18
  const title =
15
19
  titleInput && titleInput.value
16
20
  ? titleInput.value
17
21
  : titleButton
18
22
  ? titleButton.innerText
19
- : 'Meta AI Session';
23
+ : "Meta AI Session";
20
24
 
21
25
  const messages = [];
22
26
 
@@ -40,45 +44,45 @@ export class MetaParser extends ChatParser {
40
44
  });
41
45
 
42
46
  uniqueElements.forEach((el) => {
43
- let role = 'Unknown';
44
- let content = '';
47
+ let role = "Unknown";
48
+ let content = "";
45
49
 
46
50
  // Check if the element itself identifies as user or assistant
47
51
  const isUser =
48
52
  el.matches('[data-message-type="user"]') ||
49
- (el.getAttribute('data-message-id') &&
50
- el.getAttribute('data-message-id').endsWith('_user'));
53
+ (el.getAttribute("data-message-id") &&
54
+ el.getAttribute("data-message-id").endsWith("_user"));
51
55
  const isAssistant =
52
56
  el.matches('[data-testid="assistant-message"]') ||
53
- (el.getAttribute('data-message-id') &&
54
- el.getAttribute('data-message-id').endsWith('_assistant'));
57
+ (el.getAttribute("data-message-id") &&
58
+ el.getAttribute("data-message-id").endsWith("_assistant"));
55
59
 
56
60
  if (isUser) {
57
- role = 'User';
61
+ role = "User";
58
62
  // User text is usually in a span with text-response class or simply pre-wrap
59
63
  const textEl =
60
64
  el.querySelector('[data-slot="text"].text-response') ||
61
- el.querySelector('.whitespace-pre-wrap');
65
+ el.querySelector(".whitespace-pre-wrap");
62
66
  if (textEl) {
63
67
  content = convertToMarkdown(textEl);
64
68
  } else {
65
69
  content = convertToMarkdown(el);
66
70
  }
67
71
  } else if (isAssistant) {
68
- role = 'Meta AI';
72
+ role = "Meta AI";
69
73
  // Assistant content is typically styled in markdown-content or prose
70
74
  const contentEl =
71
- el.querySelector('.markdown-content') ||
72
- el.querySelector('.ur-markdown') ||
73
- el.querySelector('.prose');
75
+ el.querySelector(".markdown-content") ||
76
+ el.querySelector(".ur-markdown") ||
77
+ el.querySelector(".prose");
74
78
  if (contentEl) {
75
79
  const clone = contentEl.cloneNode(true);
76
80
 
77
81
  // Remove noise elements (like citation pills, edit buttons, thinking status, etc)
78
82
  const noiseSelectors = [
79
- 'button',
80
- '.ur-citation-pill',
81
- 'svg',
83
+ "button",
84
+ ".ur-citation-pill",
85
+ "svg",
82
86
  '[data-testid="citation-pill"]',
83
87
  '[data-testid="thinking-status"]',
84
88
  ];
@@ -98,9 +102,11 @@ export class MetaParser extends ChatParser {
98
102
  });
99
103
 
100
104
  const currentUrl =
101
- typeof window !== 'undefined' && window.location ? window.location.href || '' : '';
105
+ typeof window !== "undefined" && window.location
106
+ ? window.location.href || ""
107
+ : "";
102
108
  const metadata = {
103
- Source: 'Meta AI',
109
+ Source: "Meta AI",
104
110
  Date: new Date().toLocaleString(),
105
111
  Link: currentUrl,
106
112
  };
package/ai/mistral.js CHANGED
@@ -1,43 +1,46 @@
1
- import { ChatParser } from './base.js';
2
- import { convertToMarkdown } from '../utils/html-to-markdown.js';
1
+ import { ChatParser } from "./base.js";
2
+ import { convertToMarkdown } from "../utils/html-to-markdown.js";
3
3
 
4
4
  export class MistralParser extends ChatParser {
5
- name = 'Mistral';
5
+ name = "Mistral";
6
6
  isAvailable(url) {
7
- return url.includes('chat.mistral.ai');
7
+ return url.includes("chat.mistral.ai");
8
8
  }
9
9
 
10
10
  async parse() {
11
11
  // Extract Title
12
- const titleElement = document.querySelector('span.truncate.text-sm');
13
- let title = 'Mistral Conversation';
12
+ const titleElement = document.querySelector("span.truncate.text-sm");
13
+ let title = "Mistral Conversation";
14
14
  if (titleElement && titleElement.innerText) {
15
15
  title = titleElement.innerText.trim();
16
16
  } else if (document.title) {
17
- title = document.title.replace(' - Mistral', '').trim();
17
+ title = document.title.replace(" - Mistral", "").trim();
18
18
  }
19
19
 
20
20
  const messages = [];
21
- const messageElements = document.querySelectorAll('[data-message-author-role]');
21
+ const messageElements = document.querySelectorAll(
22
+ "[data-message-author-role]",
23
+ );
22
24
 
23
25
  for (const el of messageElements) {
24
- const role = el.getAttribute('data-message-author-role');
26
+ const role = el.getAttribute("data-message-author-role");
25
27
 
26
- if (role === 'user') {
28
+ if (role === "user") {
27
29
  const contentEl =
28
- el.querySelector('.select-text') || el.querySelector('.whitespace-pre-wrap');
30
+ el.querySelector(".select-text") ||
31
+ el.querySelector(".whitespace-pre-wrap");
29
32
  if (contentEl) {
30
33
  messages.push({
31
- role: 'User',
34
+ role: "User",
32
35
  content: contentEl.innerText.trim(),
33
36
  });
34
37
  }
35
- } else if (role === 'assistant') {
38
+ } else if (role === "assistant") {
36
39
  const answerEl = el.querySelector('[data-message-part-type="answer"]');
37
40
  if (answerEl) {
38
41
  const markdown = convertToMarkdown(answerEl.innerHTML);
39
42
  messages.push({
40
- role: 'Mistral',
43
+ role: "Mistral",
41
44
  content: markdown,
42
45
  });
43
46
  }
@@ -45,9 +48,11 @@ export class MistralParser extends ChatParser {
45
48
  }
46
49
 
47
50
  const currentUrl =
48
- typeof window !== 'undefined' && window.location ? window.location.href || '' : '';
51
+ typeof window !== "undefined" && window.location
52
+ ? window.location.href || ""
53
+ : "";
49
54
  const metadata = {
50
- Source: 'Mistral',
55
+ Source: "Mistral",
51
56
  Date: new Date().toLocaleString(),
52
57
  Link: currentUrl,
53
58
  };
package/ai/notebooklm.js CHANGED
@@ -1,84 +1,90 @@
1
- import { ChatParser } from './base.js';
2
- import { convertToMarkdown } from '../utils/html-to-markdown.js';
1
+ import { ChatParser } from "./base.js";
2
+ import { convertToMarkdown } from "../utils/html-to-markdown.js";
3
3
 
4
4
  export class NotebookLMParser extends ChatParser {
5
- name = 'NotebookLM';
5
+ name = "NotebookLM";
6
6
  isAvailable(url) {
7
- return url.includes('notebooklm.google.com') || url.includes('notebook.google.com');
7
+ return (
8
+ url.includes("notebooklm.google.com") ||
9
+ url.includes("notebook.google.com")
10
+ );
8
11
  }
9
12
 
10
13
  async parse() {
11
- let title = '';
14
+ let title = "";
12
15
 
13
16
  // Title extraction
14
17
  const titleEl = document.querySelector(
15
18
  '.title-container .title, .notebook-title-input, input[aria-label*="title" i], h1.notebook-title',
16
19
  );
17
20
  if (titleEl) {
18
- title = (titleEl.value || titleEl.textContent || '').trim();
21
+ title = (titleEl.value || titleEl.textContent || "").trim();
19
22
  }
20
23
  if (!title) {
21
- title = (document.title || 'Gemini Notebook')
22
- .replace(/\s*-\s*(Gemini Notebook|NotebookLM)$/i, '')
24
+ title = (document.title || "Gemini Notebook")
25
+ .replace(/\s*-\s*(Gemini Notebook|NotebookLM)$/i, "")
23
26
  .trim();
24
27
  }
25
- title = title || 'NotebookLM Conversation';
28
+ title = title || "NotebookLM Conversation";
26
29
 
27
30
  const messages = [];
28
31
 
29
32
  // Selectors for turn pairs or individual message cards in NotebookLM
30
33
  const messagePairs = Array.from(
31
- document.querySelectorAll('.chat-message-pair, chat-message, .message-pair-container'),
34
+ document.querySelectorAll(
35
+ ".chat-message-pair, chat-message, .message-pair-container",
36
+ ),
32
37
  );
33
38
 
34
39
  // Filter to top-level containers
35
40
  const topContainers = messagePairs.filter(
36
- (el) => !messagePairs.some((parent) => parent !== el && parent.contains(el)),
41
+ (el) =>
42
+ !messagePairs.some((parent) => parent !== el && parent.contains(el)),
37
43
  );
38
44
 
39
45
  if (topContainers.length > 0) {
40
46
  topContainers.forEach((pair) => {
41
47
  // User message
42
48
  const userContainer = pair.querySelector(
43
- '.from-user-container, .from-user-message-card-content',
49
+ ".from-user-container, .from-user-message-card-content",
44
50
  );
45
51
 
46
52
  // Model / Assistant response
47
53
  const aiContainer = pair.querySelector(
48
- '.to-user-container, .to-user-message-inner-content, labs-tailwind-doc-viewer',
54
+ ".to-user-container, .to-user-message-inner-content, labs-tailwind-doc-viewer",
49
55
  );
50
56
 
51
57
  if (userContainer) {
52
58
  const contentEl =
53
59
  userContainer.querySelector(
54
- '.message-text-content, .md3-body-text, .from-user-message-inner-content',
60
+ ".message-text-content, .md3-body-text, .from-user-message-inner-content",
55
61
  ) || userContainer;
56
62
 
57
63
  const clone = contentEl.cloneNode(true);
58
64
  // Clean up noise buttons & icons
59
65
  clone
60
- .querySelectorAll('button, mat-icon, .mat-mdc-button-touch-target')
66
+ .querySelectorAll("button, mat-icon, .mat-mdc-button-touch-target")
61
67
  .forEach((el) => el.remove());
62
68
 
63
69
  const userText = convertToMarkdown(clone).trim();
64
70
  if (userText) {
65
- messages.push({ role: 'User', content: userText });
71
+ messages.push({ role: "User", content: userText });
66
72
  }
67
73
  }
68
74
 
69
75
  if (aiContainer) {
70
76
  const contentEl =
71
77
  aiContainer.querySelector(
72
- 'labs-tailwind-doc-viewer, .message-text-content, .to-user-message-inner-content',
78
+ "labs-tailwind-doc-viewer, .message-text-content, .to-user-message-inner-content",
73
79
  ) || aiContainer;
74
80
 
75
81
  const clone = contentEl.cloneNode(true);
76
82
  // Format citation markers (e.g., button.citation-marker -> <span class="citation-ref">[1]</span>)
77
- clone.querySelectorAll('button.citation-marker').forEach((btn) => {
83
+ clone.querySelectorAll("button.citation-marker").forEach((btn) => {
78
84
  const citeNum = btn.textContent.trim();
79
- if (citeNum && typeof document !== 'undefined') {
80
- const spanNode = document.createElement('span');
81
- spanNode.className = 'citation-ref';
85
+ if (citeNum && typeof document !== "undefined") {
86
+ const spanNode = document.createElement("span");
87
+ spanNode.className = "citation-ref";
82
88
  spanNode.textContent = ` [${citeNum}]`;
83
89
  btn.replaceWith(spanNode);
84
90
  }
@@ -86,12 +92,14 @@ export class NotebookLMParser extends ChatParser {
86
92
 
87
93
  // Clean up other UI noise (thumbs up/down, action buttons)
88
94
  clone
89
- .querySelectorAll('button, mat-icon, .mat-mdc-button-touch-target, .feedback-actions')
95
+ .querySelectorAll(
96
+ "button, mat-icon, .mat-mdc-button-touch-target, .feedback-actions",
97
+ )
90
98
  .forEach((el) => el.remove());
91
99
 
92
100
  const aiText = convertToMarkdown(clone).trim();
93
101
  if (aiText) {
94
- messages.push({ role: 'NotebookLM', content: aiText });
102
+ messages.push({ role: "NotebookLM", content: aiText });
95
103
  }
96
104
  }
97
105
  });
@@ -99,38 +107,46 @@ export class NotebookLMParser extends ChatParser {
99
107
 
100
108
  // Direct fallback if topContainers loop yielded no messages
101
109
  if (messages.length === 0) {
102
- const userContainers = Array.from(document.querySelectorAll('.from-user-container'));
103
- const aiContainers = Array.from(document.querySelectorAll('.to-user-container'));
110
+ const userContainers = Array.from(
111
+ document.querySelectorAll(".from-user-container"),
112
+ );
113
+ const aiContainers = Array.from(
114
+ document.querySelectorAll(".to-user-container"),
115
+ );
104
116
 
105
117
  const maxLen = Math.max(userContainers.length, aiContainers.length);
106
118
  for (let i = 0; i < maxLen; i++) {
107
119
  if (userContainers[i]) {
108
120
  const text = convertToMarkdown(userContainers[i]).trim();
109
- if (text) messages.push({ role: 'User', content: text });
121
+ if (text) messages.push({ role: "User", content: text });
110
122
  }
111
123
  if (aiContainers[i]) {
112
124
  const clone = aiContainers[i].cloneNode(true);
113
- clone.querySelectorAll('button.citation-marker').forEach((btn) => {
125
+ clone.querySelectorAll("button.citation-marker").forEach((btn) => {
114
126
  const citeNum = btn.textContent.trim();
115
- if (citeNum && typeof document !== 'undefined') {
116
- const spanNode = document.createElement('span');
117
- spanNode.className = 'citation-ref';
127
+ if (citeNum && typeof document !== "undefined") {
128
+ const spanNode = document.createElement("span");
129
+ spanNode.className = "citation-ref";
118
130
  spanNode.textContent = ` [${citeNum}]`;
119
131
  btn.replaceWith(spanNode);
120
132
  }
121
133
  });
122
- clone.querySelectorAll('button, mat-icon').forEach((el) => el.remove());
134
+ clone
135
+ .querySelectorAll("button, mat-icon")
136
+ .forEach((el) => el.remove());
123
137
 
124
138
  const text = convertToMarkdown(clone).trim();
125
- if (text) messages.push({ role: 'NotebookLM', content: text });
139
+ if (text) messages.push({ role: "NotebookLM", content: text });
126
140
  }
127
141
  }
128
142
  }
129
143
 
130
144
  const currentUrl =
131
- typeof window !== 'undefined' && window.location ? window.location.href || '' : '';
145
+ typeof window !== "undefined" && window.location
146
+ ? window.location.href || ""
147
+ : "";
132
148
  const metadata = {
133
- Source: 'NotebookLM',
149
+ Source: "NotebookLM",
134
150
  Date: new Date().toLocaleString(),
135
151
  Link: currentUrl,
136
152
  };
package/ai/perplexity.js CHANGED
@@ -1,52 +1,53 @@
1
- import { ChatParser } from './base.js';
2
- import { convertToMarkdown } from '../utils/html-to-markdown.js';
1
+ import { ChatParser } from "./base.js";
2
+ import { convertToMarkdown } from "../utils/html-to-markdown.js";
3
3
 
4
4
  export class PerplexityParser extends ChatParser {
5
- name = 'Perplexity';
5
+ name = "Perplexity";
6
6
  isAvailable(url) {
7
- return url.includes('perplexity.ai');
7
+ return url.includes("perplexity.ai");
8
8
  }
9
9
 
10
10
  async parse() {
11
11
  const rawTitle =
12
- document.querySelector('.share-title-section h1')?.textContent ||
13
- document.querySelector('h1')?.textContent ||
12
+ document.querySelector(".share-title-section h1")?.textContent ||
13
+ document.querySelector("h1")?.textContent ||
14
14
  document.title ||
15
- 'Perplexity Search';
16
- const title = rawTitle.trim().replace(/\s+/g, ' ');
15
+ "Perplexity Search";
16
+ const title = rawTitle.trim().replace(/\s+/g, " ");
17
17
 
18
18
  const messages = [];
19
19
 
20
20
  // Perplexity Container
21
- const threadContainer = document.querySelector('.max-w-threadContentWidth') || document.body;
21
+ const threadContainer =
22
+ document.querySelector(".max-w-threadContentWidth") || document.body;
22
23
 
23
24
  // Candidates for User Messages
24
25
  const userSelectors = [
25
- 'h1.group\\/query',
26
- '.group\\/query',
27
- '.whitespace-pre-line.text-pretty',
26
+ "h1.group\\/query",
27
+ ".group\\/query",
28
+ ".whitespace-pre-line.text-pretty",
28
29
  '[data-testid="search-bar-input"]', // fallback for input? typically input is not the message display
29
30
  ];
30
31
 
31
32
  // Candidates for Assistant Messages
32
- const assistantSelectors = ['div[id^="markdown-content-"]', '.prose'];
33
+ const assistantSelectors = ['div[id^="markdown-content-"]', ".prose"];
33
34
 
34
35
  // Strategy: Iterate children of thread container or find all matches in document
35
36
  // Thread container is better to preserve order
36
37
 
37
38
  // Let's select all potential message blocks within the thread container
38
- const selectorString = [...userSelectors, ...assistantSelectors].join(', ');
39
+ const selectorString = [...userSelectors, ...assistantSelectors].join(", ");
39
40
  const elements = threadContainer.querySelectorAll(selectorString);
40
41
 
41
42
  // Helper to determine role
42
43
  const getRole = (el) => {
43
44
  for (const s of userSelectors) {
44
- if (el.matches(s)) return 'User';
45
+ if (el.matches(s)) return "User";
45
46
  }
46
47
  for (const s of assistantSelectors) {
47
- if (el.matches(s)) return 'Perplexity';
48
+ if (el.matches(s)) return "Perplexity";
48
49
  }
49
- return 'Unknown';
50
+ return "Unknown";
50
51
  };
51
52
 
52
53
  const seenText = new Set();
@@ -73,9 +74,11 @@ export class PerplexityParser extends ChatParser {
73
74
  });
74
75
 
75
76
  const currentUrl =
76
- typeof window !== 'undefined' && window.location ? window.location.href || '' : '';
77
+ typeof window !== "undefined" && window.location
78
+ ? window.location.href || ""
79
+ : "";
77
80
  const metadata = {
78
- Source: 'Perplexity',
81
+ Source: "Perplexity",
79
82
  Date: new Date().toLocaleString(),
80
83
  Link: currentUrl,
81
84
  };