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.
@@ -1,60 +1,67 @@
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 GeminiCloudAssistParser extends ChatParser {
5
- name = 'Gemini Cloud Assist';
5
+ name = "Gemini Cloud Assist";
6
6
  isAvailable(url) {
7
- return url.includes('console.cloud.google.com/gemini');
7
+ return url.includes("console.cloud.google.com/gemini");
8
8
  }
9
9
 
10
10
  async parse() {
11
11
  const activeItem = document.querySelector(
12
- 'mat-list-item.mdc-list-item--activated span.cfc-flex-grow-content',
12
+ "mat-list-item.mdc-list-item--activated span.cfc-flex-grow-content",
13
13
  );
14
- let title = activeItem?.textContent?.trim() || '';
14
+ let title = activeItem?.textContent?.trim() || "";
15
15
 
16
16
  // Fallback if no active item
17
17
  if (!title) {
18
- const firstUserMsg = document.querySelector('.aic-user-message-text')?.textContent?.trim();
18
+ const firstUserMsg = document
19
+ .querySelector(".aic-user-message-text")
20
+ ?.textContent?.trim();
19
21
  if (firstUserMsg) {
20
- title = firstUserMsg.length > 50 ? firstUserMsg.substring(0, 50) + '...' : firstUserMsg;
22
+ title =
23
+ firstUserMsg.length > 50
24
+ ? firstUserMsg.substring(0, 50) + "..."
25
+ : firstUserMsg;
21
26
  }
22
27
  }
23
28
 
24
29
  if (!title) {
25
- title = document.title || 'Gemini Cloud Assist';
30
+ title = document.title || "Gemini Cloud Assist";
26
31
  }
27
32
 
28
33
  // Clean up title whitespace
29
- title = title.replace(/\s+/g, ' ').trim();
34
+ title = title.replace(/\s+/g, " ").trim();
30
35
 
31
36
  const messages = [];
32
37
 
33
38
  // Find all turns inside aic-conversation or standard turn containers
34
- const turns = document.querySelectorAll('aic-conversation .aic-turn, .aic-turn');
39
+ const turns = document.querySelectorAll(
40
+ "aic-conversation .aic-turn, .aic-turn",
41
+ );
35
42
 
36
43
  turns.forEach((turn) => {
37
44
  // 1. Process user message
38
- const userEl = turn.querySelector('.aic-user-message');
45
+ const userEl = turn.querySelector(".aic-user-message");
39
46
  if (userEl) {
40
- const textEl = userEl.querySelector('.aic-user-message-text') || userEl;
47
+ const textEl = userEl.querySelector(".aic-user-message-text") || userEl;
41
48
  let text = convertToMarkdown(textEl).trim();
42
49
  if (text) {
43
50
  messages.push({
44
- role: 'User',
51
+ role: "User",
45
52
  content: text,
46
53
  });
47
54
  }
48
55
  }
49
56
 
50
57
  // 2. Process assistant message
51
- const agentEl = turn.querySelector('aic-agent-entry');
58
+ const agentEl = turn.querySelector("aic-agent-entry");
52
59
  if (agentEl) {
53
60
  // Prefer the actual markdown renderer content if present, to avoid feedback actions/buttons noise
54
61
  const markdownEl =
55
- agentEl.querySelector('.ai-markdown-artifact-renderer') ||
56
- agentEl.querySelector('.aic-markdown-renderer-container') ||
57
- agentEl.querySelector('.aic-agent-content') ||
62
+ agentEl.querySelector(".ai-markdown-artifact-renderer") ||
63
+ agentEl.querySelector(".aic-markdown-renderer-container") ||
64
+ agentEl.querySelector(".aic-agent-content") ||
58
65
  agentEl;
59
66
 
60
67
  // Clone the element to safely strip unwanted components
@@ -62,7 +69,7 @@ export class GeminiCloudAssistParser extends ChatParser {
62
69
  // Remove thumb up/down feedback buttons or loader/expander elements
63
70
  clone
64
71
  .querySelectorAll(
65
- 'button, aic-feedback-actions, .aic-agent-thoughts-container, aic-loading-indicator',
72
+ "button, aic-feedback-actions, .aic-agent-thoughts-container, aic-loading-indicator",
66
73
  )
67
74
  .forEach((el) => {
68
75
  el.remove();
@@ -71,7 +78,7 @@ export class GeminiCloudAssistParser extends ChatParser {
71
78
  let text = convertToMarkdown(clone).trim();
72
79
  if (text) {
73
80
  messages.push({
74
- role: 'Model',
81
+ role: "Model",
75
82
  content: text,
76
83
  });
77
84
  }
@@ -79,9 +86,11 @@ export class GeminiCloudAssistParser extends ChatParser {
79
86
  });
80
87
 
81
88
  const currentUrl =
82
- typeof window !== 'undefined' && window.location ? window.location.href || '' : '';
89
+ typeof window !== "undefined" && window.location
90
+ ? window.location.href || ""
91
+ : "";
83
92
  const metadata = {
84
- Source: 'Gemini Cloud Assist',
93
+ Source: "Gemini Cloud Assist",
85
94
  Date: new Date().toLocaleString(),
86
95
  Link: currentUrl,
87
96
  };
@@ -1,14 +1,14 @@
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 GoogleAIStudioParser extends ChatParser {
5
- name = 'Google AI Studio';
5
+ name = "Google AI Studio";
6
6
  isAvailable(url) {
7
- return url.includes('aistudio.google.com');
7
+ return url.includes("aistudio.google.com");
8
8
  }
9
9
 
10
10
  async parse() {
11
- let title = '';
11
+ let title = "";
12
12
 
13
13
  // Title extraction strategies
14
14
  const titleInput = document.querySelector(
@@ -18,51 +18,59 @@ export class GoogleAIStudioParser extends ChatParser {
18
18
  title = titleInput.value.trim();
19
19
  }
20
20
  if (!title) {
21
- const headerTitle = document.querySelector('.title-text, h1, .prompt-name');
21
+ const headerTitle = document.querySelector(
22
+ ".title-text, h1, .prompt-name",
23
+ );
22
24
  if (headerTitle) {
23
25
  title = headerTitle.textContent.trim();
24
26
  }
25
27
  }
26
28
  if (!title) {
27
- title = document.title || 'Google AI Studio Chat';
29
+ title = document.title || "Google AI Studio Chat";
28
30
  }
29
- title = title.replace(/\s+/g, ' ').trim();
31
+ title = title.replace(/\s+/g, " ").trim();
30
32
 
31
33
  const messages = [];
32
34
 
33
35
  // Selectors for chat turns
34
36
  const allTurnElements = Array.from(
35
- document.querySelectorAll('ms-chat-turn, .chat-turn-container, ms-prompt-editor'),
37
+ document.querySelectorAll(
38
+ "ms-chat-turn, .chat-turn-container, ms-prompt-editor",
39
+ ),
36
40
  );
37
41
 
38
42
  // Filter to top-level turn elements only (avoiding child elements nested in parent turn containers)
39
43
  const turnElements = allTurnElements.filter(
40
- (el) => !allTurnElements.some((parent) => parent !== el && parent.contains(el)),
44
+ (el) =>
45
+ !allTurnElements.some((parent) => parent !== el && parent.contains(el)),
41
46
  );
42
47
 
43
48
  if (turnElements.length > 0) {
44
49
  turnElements.forEach((turn) => {
45
50
  // User prompt element
46
51
  const userEl =
47
- turn.querySelector('.user-prompt, .user-prompt-container, .user-message') ||
48
- (turn.tagName === 'MS-PROMPT-EDITOR' ? turn : null);
52
+ turn.querySelector(
53
+ ".user-prompt, .user-prompt-container, .user-message",
54
+ ) || (turn.tagName === "MS-PROMPT-EDITOR" ? turn : null);
49
55
 
50
56
  // Model output element
51
57
  const aiEl = turn.querySelector(
52
- '.model-response-text, .model-output, ms-model-output, .model-prompt-container',
58
+ ".model-response-text, .model-output, ms-model-output, .model-prompt-container",
53
59
  );
54
60
 
55
61
  if (userEl) {
56
- const contentEl = userEl.querySelector('.turn-content, .prompt-text, textarea') || userEl;
62
+ const contentEl =
63
+ userEl.querySelector(".turn-content, .prompt-text, textarea") ||
64
+ userEl;
57
65
  let text;
58
- if (contentEl.tagName === 'TEXTAREA') {
59
- text = contentEl.value || contentEl.textContent || '';
66
+ if (contentEl.tagName === "TEXTAREA") {
67
+ text = contentEl.value || contentEl.textContent || "";
60
68
  } else {
61
69
  text = convertToMarkdown(contentEl);
62
70
  }
63
71
  text = text.trim();
64
72
  if (text) {
65
- messages.push({ role: 'User', content: text });
73
+ messages.push({ role: "User", content: text });
66
74
  }
67
75
  }
68
76
 
@@ -71,13 +79,17 @@ export class GoogleAIStudioParser extends ChatParser {
71
79
  // Remove noise elements like copy buttons or action menus
72
80
  clone
73
81
  .querySelectorAll(
74
- 'button, .mat-expansion-panel-header, .author-label, .timestamp, ms-prompt-options-menu, .navigator-container',
82
+ "button, .mat-expansion-panel-header, .author-label, .timestamp, ms-prompt-options-menu, .navigator-container",
75
83
  )
76
84
  .forEach((el) => el.remove());
77
85
 
78
86
  let text = convertToMarkdown(clone).trim();
79
- if (text && text !== 'Thinking...' && !text.includes('model_thought_output')) {
80
- messages.push({ role: 'Google AI Studio', content: text });
87
+ if (
88
+ text &&
89
+ text !== "Thinking..." &&
90
+ !text.includes("model_thought_output")
91
+ ) {
92
+ messages.push({ role: "Google AI Studio", content: text });
81
93
  }
82
94
  }
83
95
  });
@@ -85,28 +97,34 @@ export class GoogleAIStudioParser extends ChatParser {
85
97
 
86
98
  // Fallback if turnElements linear loop yielded no messages
87
99
  if (messages.length === 0) {
88
- const userPrompts = document.querySelectorAll('.user-prompt, .user-prompt-container');
100
+ const userPrompts = document.querySelectorAll(
101
+ ".user-prompt, .user-prompt-container",
102
+ );
89
103
  const modelOutputs = document.querySelectorAll(
90
- '.model-response-text, ms-model-output, .model-output',
104
+ ".model-response-text, ms-model-output, .model-output",
91
105
  );
92
106
 
93
107
  const maxLen = Math.max(userPrompts.length, modelOutputs.length);
94
108
  for (let i = 0; i < maxLen; i++) {
95
109
  if (userPrompts[i]) {
96
- const text = (userPrompts[i].value || convertToMarkdown(userPrompts[i])).trim();
97
- if (text) messages.push({ role: 'User', content: text });
110
+ const text = (
111
+ userPrompts[i].value || convertToMarkdown(userPrompts[i])
112
+ ).trim();
113
+ if (text) messages.push({ role: "User", content: text });
98
114
  }
99
115
  if (modelOutputs[i]) {
100
116
  const text = convertToMarkdown(modelOutputs[i]).trim();
101
- if (text) messages.push({ role: 'Google AI Studio', content: text });
117
+ if (text) messages.push({ role: "Google AI Studio", content: text });
102
118
  }
103
119
  }
104
120
  }
105
121
 
106
122
  const currentUrl =
107
- typeof window !== 'undefined' && window.location ? window.location.href || '' : '';
123
+ typeof window !== "undefined" && window.location
124
+ ? window.location.href || ""
125
+ : "";
108
126
  const metadata = {
109
- Source: 'Google AI Studio',
127
+ Source: "Google AI Studio",
110
128
  Date: new Date().toLocaleString(),
111
129
  Link: currentUrl,
112
130
  };
@@ -1,29 +1,29 @@
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 GoogleSearchAIParser extends ChatParser {
5
- name = 'Google Search AI';
5
+ name = "Google Search AI";
6
6
  isAvailable(url) {
7
7
  return /google\.[a-z.]+\/search/.test(url);
8
8
  }
9
9
 
10
10
  async parse() {
11
- const title = document.title || 'Google Search AI Overview';
11
+ const title = document.title || "Google Search AI Overview";
12
12
  const messages = [];
13
13
 
14
14
  // 1. Extract the user's queries
15
15
  const queries = [];
16
- const queryElements = document.querySelectorAll('.sUKAcb');
16
+ const queryElements = document.querySelectorAll(".sUKAcb");
17
17
  queryElements.forEach((el) => {
18
18
  // Clone the element to avoid mutating the live DOM
19
19
  const clone = el.cloneNode(true);
20
- const prefixEl = clone.querySelector('.iMqumd');
20
+ const prefixEl = clone.querySelector(".iMqumd");
21
21
  if (prefixEl) {
22
22
  prefixEl.remove();
23
23
  }
24
- let text = clone.textContent || '';
24
+ let text = clone.textContent || "";
25
25
  // Fallback regex cleanup if needed
26
- text = text.replace(/^\s*You\s+said:\s*/i, '').trim();
26
+ text = text.replace(/^\s*You\s+said:\s*/i, "").trim();
27
27
  if (text) {
28
28
  queries.push(text);
29
29
  }
@@ -31,29 +31,29 @@ export class GoogleSearchAIParser extends ChatParser {
31
31
 
32
32
  // Fallback to single-turn query extraction if no turn query elements are found
33
33
  if (queries.length === 0) {
34
- let userQuery = '';
35
- const uqEl = document.querySelector('[data-uq]');
34
+ let userQuery = "";
35
+ const uqEl = document.querySelector("[data-uq]");
36
36
  if (uqEl) {
37
- userQuery = uqEl.getAttribute('data-uq');
37
+ userQuery = uqEl.getAttribute("data-uq");
38
38
  }
39
39
  if (!userQuery) {
40
- const qEl = document.querySelector('[data-q]');
40
+ const qEl = document.querySelector("[data-q]");
41
41
  if (qEl) {
42
- userQuery = qEl.getAttribute('data-q');
42
+ userQuery = qEl.getAttribute("data-q");
43
43
  }
44
44
  }
45
45
  if (!userQuery) {
46
46
  try {
47
47
  const url = new URL(window.location.href);
48
- userQuery = url.searchParams.get('q');
48
+ userQuery = url.searchParams.get("q");
49
49
  } catch {
50
50
  // Ignore invalid URLs
51
51
  }
52
52
  }
53
53
  if (!userQuery) {
54
54
  const textarea =
55
- document.querySelector('textarea.gLFyf') ||
56
- document.querySelector('textarea.ITIRGe') ||
55
+ document.querySelector("textarea.gLFyf") ||
56
+ document.querySelector("textarea.ITIRGe") ||
57
57
  document.querySelector('input[name="q"]');
58
58
  if (textarea) {
59
59
  userQuery = textarea.value || textarea.textContent;
@@ -71,7 +71,9 @@ export class GoogleSearchAIParser extends ChatParser {
71
71
  turnElements.forEach((turnEl) => {
72
72
  const resEl =
73
73
  turnEl.querySelector('[data-container-id="main-col"]') ||
74
- turnEl.querySelector('[data-container-id="model-response-placeholder"]');
74
+ turnEl.querySelector(
75
+ '[data-container-id="model-response-placeholder"]',
76
+ );
75
77
  if (resEl) {
76
78
  responseContainers.push(resEl);
77
79
  }
@@ -80,7 +82,9 @@ export class GoogleSearchAIParser extends ChatParser {
80
82
  // Fallback for pages without data-scope-id="turn"
81
83
  const resEl =
82
84
  document.querySelector('[data-container-id="main-col"]') ||
83
- document.querySelector('[data-container-id="model-response-placeholder"]');
85
+ document.querySelector(
86
+ '[data-container-id="model-response-placeholder"]',
87
+ );
84
88
  if (resEl) {
85
89
  responseContainers.push(resEl);
86
90
  }
@@ -89,22 +93,27 @@ export class GoogleSearchAIParser extends ChatParser {
89
93
  // Pair queries and responses in order
90
94
  const minLength = Math.min(queries.length, responseContainers.length);
91
95
  for (let i = 0; i < minLength; i++) {
92
- messages.push({ role: 'User', content: queries[i].trim() });
96
+ messages.push({ role: "User", content: queries[i].trim() });
93
97
  const text = convertToMarkdown(responseContainers[i]);
94
98
  if (text.trim()) {
95
- messages.push({ role: 'Model', content: text.trim() });
99
+ messages.push({ role: "Model", content: text.trim() });
96
100
  }
97
101
  }
98
102
 
99
103
  // If there is a trailing user query with no response container (yet)
100
104
  if (queries.length > responseContainers.length) {
101
- messages.push({ role: 'User', content: queries[queries.length - 1].trim() });
105
+ messages.push({
106
+ role: "User",
107
+ content: queries[queries.length - 1].trim(),
108
+ });
102
109
  }
103
110
 
104
111
  const currentUrl =
105
- typeof window !== 'undefined' && window.location ? window.location.href || '' : '';
112
+ typeof window !== "undefined" && window.location
113
+ ? window.location.href || ""
114
+ : "";
106
115
  const metadata = {
107
- Source: 'Google Search AI',
116
+ Source: "Google Search AI",
108
117
  Date: new Date().toLocaleString(),
109
118
  Link: currentUrl,
110
119
  };
package/ai/index.js CHANGED
@@ -7,28 +7,34 @@
7
7
  */
8
8
 
9
9
  // Base class
10
- export { ChatParser } from './base.js';
10
+ export { ChatParser } from "./base.js";
11
11
 
12
12
  // Individual parsers
13
- export { ChatGPTParser } from './chatgpt.js';
14
- export { ClaudeParser } from './claude.js';
15
- export { GeminiParser } from './gemini.js';
16
- export { CopilotParser } from './copilot.js';
17
- export { DeepSeekParser } from './deepseek.js';
18
- export { MetaParser } from './meta.js';
19
- export { MistralParser } from './mistral.js';
20
- export { PerplexityParser } from './perplexity.js';
21
- export { QwenParser } from './qwen.js';
22
- export { LumoParser } from './lumo.js';
23
- export { ZAiParser } from './z_ai.js';
24
- export { GoogleAIStudioParser } from './google_ai_studio.js';
25
- export { NotebookLMParser } from './notebooklm.js';
26
- export { GoogleSearchAIParser } from './google_search_ai.js';
27
- export { GeminiCloudAssistParser } from './gemini_cloud_assist.js';
13
+ export { ChatGPTParser } from "./chatgpt.js";
14
+ export { ClaudeParser } from "./claude.js";
15
+ export { GeminiParser } from "./gemini.js";
16
+ export { CopilotParser } from "./copilot.js";
17
+ export { DeepSeekParser } from "./deepseek.js";
18
+ export { MetaParser } from "./meta.js";
19
+ export { MistralParser } from "./mistral.js";
20
+ export { PerplexityParser } from "./perplexity.js";
21
+ export { QwenParser } from "./qwen.js";
22
+ export { LumoParser } from "./lumo.js";
23
+ export { ZAiParser } from "./z_ai.js";
24
+ export { GoogleAIStudioParser } from "./google_ai_studio.js";
25
+ export { NotebookLMParser } from "./notebooklm.js";
26
+ export { GoogleSearchAIParser } from "./google_search_ai.js";
27
+ export { GeminiCloudAssistParser } from "./gemini_cloud_assist.js";
28
+ export { JoylandParser } from "./joyland.js";
29
+ export { ChubParser } from "./chub.js";
28
30
 
29
31
  // Utilities
30
- export { convertToMarkdown, cleanMarkdown } from '../utils/html-to-markdown.js';
32
+ export { convertToMarkdown, cleanMarkdown } from "../utils/html-to-markdown.js";
31
33
 
32
34
  // Detection
33
- export { detectPlatform, isAiChatUrl, parsers } from '../detection/detect-platform.js';
34
- export { AI_CHAT_DOMAINS } from '../detection/domains.js';
35
+ export {
36
+ detectPlatform,
37
+ isAiChatUrl,
38
+ parsers,
39
+ } from "../detection/detect-platform.js";
40
+ export { AI_CHAT_DOMAINS } from "../detection/domains.js";
package/ai/joyland.js ADDED
@@ -0,0 +1,85 @@
1
+ import { ChatParser } from "./base.js";
2
+ import { convertToMarkdown } from "../utils/html-to-markdown.js";
3
+
4
+ export class JoylandParser extends ChatParser {
5
+ name = "Joyland";
6
+
7
+ isAvailable(url) {
8
+ return url.includes("joyland.ai");
9
+ }
10
+
11
+ async parse() {
12
+ // Extract Character Name & Creator
13
+ const botNameEl =
14
+ document.querySelector(".bot-name-text") ||
15
+ document.querySelector(".bot-name");
16
+ let botName = botNameEl
17
+ ? botNameEl.textContent.trim().replace(/\s+/g, " ")
18
+ : "";
19
+ if (
20
+ botName.endsWith("1") &&
21
+ botNameEl?.parentElement?.querySelector(".bot-name-text")
22
+ ) {
23
+ const cleanEl = botNameEl.parentElement.querySelector(".bot-name-text");
24
+ if (cleanEl) botName = cleanEl.textContent.trim();
25
+ }
26
+
27
+ const creatorEl = document.querySelector(".creator-name .name");
28
+ const creator = creatorEl
29
+ ? creatorEl.textContent.trim().replace(/\s+/g, " ")
30
+ : "";
31
+
32
+ // Extract Title
33
+ let title = "";
34
+ if (botName) {
35
+ title = `${botName} - Joyland Chat`;
36
+ } else if (document.title) {
37
+ title = document.title.replace(/\s*-\s*Joyland.*$/i, "").trim();
38
+ }
39
+ title = title || "Joyland Conversation";
40
+
41
+ const messages = [];
42
+ const items = document.querySelectorAll(".chat-list-item");
43
+
44
+ for (const item of items) {
45
+ const isUser = !!item.querySelector(".user-text-box, .user-text");
46
+ const isRobot = !!item.querySelector(".robot-text-box, .robot-text");
47
+ if (!isUser && !isRobot) continue;
48
+
49
+ const rawContentEl =
50
+ item.querySelector(".markdown-body") ||
51
+ item.querySelector(".body-text") ||
52
+ item;
53
+ const contentElClone = rawContentEl.cloneNode(true);
54
+
55
+ // Remove UI noise elements (audio player, ratings, refresh buttons, etc.)
56
+ contentElClone
57
+ .querySelectorAll(
58
+ ".audio-style-box, .audio-style, .rate-container, .rate, .refresh, button, .click-gif, svg",
59
+ )
60
+ .forEach((el) => el.remove());
61
+
62
+ const markdown = convertToMarkdown(contentElClone);
63
+ if (markdown.trim()) {
64
+ messages.push({
65
+ role: isUser ? "User" : "Joyland",
66
+ content: markdown.trim(),
67
+ });
68
+ }
69
+ }
70
+
71
+ const currentUrl =
72
+ typeof window !== "undefined" && window.location
73
+ ? window.location.href || ""
74
+ : "";
75
+ const metadata = {
76
+ Source: "Joyland",
77
+ Date: new Date().toLocaleString(),
78
+ Link: currentUrl,
79
+ };
80
+ if (botName) metadata.Character = botName;
81
+ if (creator) metadata.Creator = creator;
82
+
83
+ return { title, messages, url: currentUrl, metadata };
84
+ }
85
+ }