decant-core 1.3.0 → 1.4.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/claude.js +318 -50
- package/ai/google_search_ai.js +8 -0
- package/package.json +3 -3
package/ai/claude.js
CHANGED
|
@@ -70,6 +70,39 @@ function getCurrentBranch(data) {
|
|
|
70
70
|
return branch;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
const EXT_TO_LANG = {
|
|
74
|
+
py: "python",
|
|
75
|
+
js: "javascript",
|
|
76
|
+
jsx: "jsx",
|
|
77
|
+
ts: "typescript",
|
|
78
|
+
tsx: "tsx",
|
|
79
|
+
md: "markdown",
|
|
80
|
+
html: "html",
|
|
81
|
+
css: "css",
|
|
82
|
+
json: "json",
|
|
83
|
+
sh: "bash",
|
|
84
|
+
bash: "bash",
|
|
85
|
+
yml: "yaml",
|
|
86
|
+
yaml: "yaml",
|
|
87
|
+
sql: "sql",
|
|
88
|
+
java: "java",
|
|
89
|
+
rb: "ruby",
|
|
90
|
+
go: "go",
|
|
91
|
+
rs: "rust",
|
|
92
|
+
c: "c",
|
|
93
|
+
cpp: "cpp",
|
|
94
|
+
txt: "text",
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const MIME_TO_LANG = {
|
|
98
|
+
"application/vnd.ant.react": "jsx",
|
|
99
|
+
"text/html": "html",
|
|
100
|
+
"image/svg+xml": "svg",
|
|
101
|
+
"application/vnd.ant.mermaid": "mermaid",
|
|
102
|
+
"text/markdown": "markdown",
|
|
103
|
+
"application/vnd.ant.code": "text",
|
|
104
|
+
};
|
|
105
|
+
|
|
73
106
|
function extractArtifactsFromText(text) {
|
|
74
107
|
const artifactRegex = /<antArtifact[^>]*>([\s\S]*?)<\/antArtifact>/g;
|
|
75
108
|
const artifacts = [];
|
|
@@ -90,53 +123,139 @@ function extractArtifactsFromText(text) {
|
|
|
90
123
|
return artifacts;
|
|
91
124
|
}
|
|
92
125
|
|
|
93
|
-
function
|
|
126
|
+
function collectArtifacts(messages) {
|
|
127
|
+
const artifacts = new Map();
|
|
128
|
+
for (const m of messages) {
|
|
129
|
+
if (!Array.isArray(m?.content)) continue;
|
|
130
|
+
for (const block of m.content) {
|
|
131
|
+
if (block.type !== "tool_use" || block.name !== "artifacts") continue;
|
|
132
|
+
const input = block.input || {};
|
|
133
|
+
const id = input.id || "__artifact__";
|
|
134
|
+
let a = artifacts.get(id);
|
|
135
|
+
if (!a) {
|
|
136
|
+
a = { content: "" };
|
|
137
|
+
artifacts.set(id, a);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (input.command === "update") {
|
|
141
|
+
if (
|
|
142
|
+
typeof input.old_str === "string" &&
|
|
143
|
+
typeof input.new_str === "string"
|
|
144
|
+
) {
|
|
145
|
+
if (a.content.includes(input.old_str)) {
|
|
146
|
+
a.content = a.content.replace(input.old_str, () => input.new_str);
|
|
147
|
+
} else {
|
|
148
|
+
console.warn(
|
|
149
|
+
`[AI Exporter] Artifact "${a.title || id}": update could not be applied (source text not found).`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} else if (typeof input.content === "string") {
|
|
154
|
+
a.content = input.content;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (input.title) a.title = input.title;
|
|
158
|
+
if (input.type) a.type = input.type;
|
|
159
|
+
if (input.language) a.language = input.language;
|
|
160
|
+
a.lastBlock = block;
|
|
161
|
+
if (input.version_uuid) a.lastVersionUuid = input.version_uuid;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return artifacts;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function extractArtifacts(message, foldedArtifacts = new Map()) {
|
|
94
168
|
const artifacts = [];
|
|
95
169
|
if (message.content && Array.isArray(message.content)) {
|
|
96
170
|
for (const content of message.content) {
|
|
97
|
-
if (
|
|
98
|
-
content.
|
|
99
|
-
(content.name === "artifacts"
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
171
|
+
if (content.type === "tool_use") {
|
|
172
|
+
const input = content.input || {};
|
|
173
|
+
if (content.name === "artifacts") {
|
|
174
|
+
const id = input.id || "__artifact__";
|
|
175
|
+
const folded = foldedArtifacts.get(id);
|
|
176
|
+
|
|
177
|
+
// Only emit at the final edit block for this artifact
|
|
178
|
+
if (folded && folded.lastBlock && folded.lastBlock !== content) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (
|
|
182
|
+
folded &&
|
|
183
|
+
folded.lastVersionUuid &&
|
|
184
|
+
input.version_uuid &&
|
|
185
|
+
input.version_uuid !== folded.lastVersionUuid
|
|
186
|
+
) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const title = input.title || (folded && folded.title) || "Artifact";
|
|
191
|
+
const lang =
|
|
192
|
+
input.language ||
|
|
193
|
+
(folded && folded.language) ||
|
|
194
|
+
MIME_TO_LANG[input.type || (folded && folded.type)] ||
|
|
195
|
+
"text";
|
|
196
|
+
const code =
|
|
197
|
+
(folded && folded.content) || input.content || input.new_str || "";
|
|
198
|
+
if (code) {
|
|
199
|
+
artifacts.push({
|
|
200
|
+
title,
|
|
201
|
+
language: lang,
|
|
202
|
+
content: code.trim(),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
} else if (content.name === "create_file") {
|
|
206
|
+
let code = "";
|
|
207
|
+
let filename = "file";
|
|
208
|
+
let lang = "";
|
|
209
|
+
|
|
210
|
+
if (typeof input.file_text === "string" && input.file_text) {
|
|
211
|
+
code = input.file_text.trim();
|
|
212
|
+
filename = String(input.path || "file")
|
|
213
|
+
.split("/")
|
|
214
|
+
.pop();
|
|
215
|
+
const ext = filename.includes(".")
|
|
216
|
+
? filename.split(".").pop().toLowerCase()
|
|
217
|
+
: "";
|
|
218
|
+
lang = EXT_TO_LANG[ext] || ext || "text";
|
|
219
|
+
} else if (content.display_content) {
|
|
220
|
+
const displayContent = content.display_content;
|
|
221
|
+
if (displayContent.type === "code_block" && displayContent.code) {
|
|
222
|
+
filename = displayContent.filename || "artifact";
|
|
223
|
+
code = displayContent.code.trim();
|
|
224
|
+
lang = displayContent.language || "text";
|
|
225
|
+
} else if (
|
|
226
|
+
displayContent.type === "json_block" &&
|
|
227
|
+
displayContent.json_block
|
|
228
|
+
) {
|
|
229
|
+
try {
|
|
230
|
+
const data = JSON.parse(displayContent.json_block);
|
|
231
|
+
if (data.filename) {
|
|
232
|
+
filename = data.filename;
|
|
233
|
+
code = (data.code || "").trim();
|
|
234
|
+
lang = data.language || "text";
|
|
235
|
+
}
|
|
236
|
+
} catch (e) {
|
|
237
|
+
console.warn(
|
|
238
|
+
"[AI Exporter] Failed to parse tool use artifact json:",
|
|
239
|
+
e,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
131
242
|
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (code) {
|
|
246
|
+
const title = filename
|
|
247
|
+
.split("/")
|
|
248
|
+
.pop()
|
|
249
|
+
.replace(/\.[^.]+$/, "");
|
|
250
|
+
artifacts.push({
|
|
251
|
+
title: title || "Artifact",
|
|
252
|
+
language: lang,
|
|
253
|
+
content: code,
|
|
254
|
+
});
|
|
137
255
|
}
|
|
138
256
|
}
|
|
139
257
|
}
|
|
258
|
+
|
|
140
259
|
if (content.text) {
|
|
141
260
|
artifacts.push(...extractArtifactsFromText(content.text));
|
|
142
261
|
}
|
|
@@ -148,6 +267,68 @@ function extractArtifacts(message) {
|
|
|
148
267
|
return artifacts;
|
|
149
268
|
}
|
|
150
269
|
|
|
270
|
+
function unrollInteractiveElements(root, doc) {
|
|
271
|
+
if (!root || !doc) return;
|
|
272
|
+
|
|
273
|
+
// Group slide titles by their common card container
|
|
274
|
+
const titleEls = Array.from(root.querySelectorAll(".text-title"));
|
|
275
|
+
if (titleEls.length > 0) {
|
|
276
|
+
const containerMap = new Map();
|
|
277
|
+
for (const titleEl of titleEls) {
|
|
278
|
+
let container = titleEl.parentElement;
|
|
279
|
+
while (container && container !== root) {
|
|
280
|
+
if (
|
|
281
|
+
container.classList.contains("@container") ||
|
|
282
|
+
container.classList.contains("bg-surface-2") ||
|
|
283
|
+
container.querySelector(
|
|
284
|
+
'[aria-label*="Go to step"], [aria-current="step"]',
|
|
285
|
+
)
|
|
286
|
+
) {
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
container = container.parentElement;
|
|
290
|
+
}
|
|
291
|
+
if (container && container !== root) {
|
|
292
|
+
if (!containerMap.has(container)) {
|
|
293
|
+
containerMap.set(container, []);
|
|
294
|
+
}
|
|
295
|
+
containerMap.get(container).push(titleEl);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
for (const [container, titles] of containerMap.entries()) {
|
|
300
|
+
const slides = [];
|
|
301
|
+
titles.forEach((titleEl, idx) => {
|
|
302
|
+
const title = titleEl.textContent.trim();
|
|
303
|
+
const bodyEl =
|
|
304
|
+
titleEl.nextElementSibling ||
|
|
305
|
+
titleEl.parentElement.querySelector(".text-body");
|
|
306
|
+
const body = bodyEl ? bodyEl.textContent.trim() : "";
|
|
307
|
+
if (title) slides.push({ index: idx + 1, title, body });
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
if (slides.length > 0) {
|
|
311
|
+
const replacement = doc.createElement("div");
|
|
312
|
+
replacement.className = "unrolled-interactive-steps";
|
|
313
|
+
slides.forEach((slide) => {
|
|
314
|
+
const h3 = doc.createElement("h3");
|
|
315
|
+
h3.textContent = `${slide.index}. ${slide.title}`;
|
|
316
|
+
replacement.appendChild(h3);
|
|
317
|
+
if (slide.body) {
|
|
318
|
+
const p = doc.createElement("p");
|
|
319
|
+
p.textContent = slide.body;
|
|
320
|
+
replacement.appendChild(p);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
container.replaceWith(replacement);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Remove any remaining buttons
|
|
329
|
+
root.querySelectorAll("button").forEach((btn) => btn.remove());
|
|
330
|
+
}
|
|
331
|
+
|
|
151
332
|
export class ClaudeParser extends ChatParser {
|
|
152
333
|
name = "Claude";
|
|
153
334
|
constructor() {
|
|
@@ -189,6 +370,31 @@ export class ClaudeParser extends ChatParser {
|
|
|
189
370
|
}
|
|
190
371
|
|
|
191
372
|
const branch = getCurrentBranch(data);
|
|
373
|
+
const foldedArtifacts = collectArtifacts(branch);
|
|
374
|
+
|
|
375
|
+
const toolResultMap = new Map();
|
|
376
|
+
for (const msg of branch) {
|
|
377
|
+
if (Array.isArray(msg?.content)) {
|
|
378
|
+
for (const block of msg.content) {
|
|
379
|
+
if (block.type === "tool_result") {
|
|
380
|
+
const toolUseId = block.tool_use_id || block.id;
|
|
381
|
+
let answers = block.toolUseResult?.answers || {};
|
|
382
|
+
if (
|
|
383
|
+
(!answers || Object.keys(answers).length === 0) &&
|
|
384
|
+
typeof block.content === "string"
|
|
385
|
+
) {
|
|
386
|
+
try {
|
|
387
|
+
const parsed = JSON.parse(block.content);
|
|
388
|
+
answers = parsed.answers || parsed;
|
|
389
|
+
} catch {
|
|
390
|
+
answers = { result: block.content };
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
toolResultMap.set(toolUseId, answers);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
192
398
|
|
|
193
399
|
const convTitle = data.name || title;
|
|
194
400
|
|
|
@@ -209,6 +415,47 @@ export class ClaudeParser extends ChatParser {
|
|
|
209
415
|
if (cleanText) {
|
|
210
416
|
contentStr += `${cleanText}\n\n`;
|
|
211
417
|
}
|
|
418
|
+
} else if (block.type === "tool_use") {
|
|
419
|
+
const input = block.input || {};
|
|
420
|
+
if (
|
|
421
|
+
block.name === "visualize:show_widget" &&
|
|
422
|
+
input.widget_code
|
|
423
|
+
) {
|
|
424
|
+
const widgetTitle = input.title || "Interactive Widget";
|
|
425
|
+
contentStr += `> **Interactive Widget: ${widgetTitle}**\n\n\`\`\`jsx\n${input.widget_code.trim()}\n\`\`\`\n\n`;
|
|
426
|
+
} else if (block.name === "repl" && input.code) {
|
|
427
|
+
contentStr += `**Analyzed data**\n\n\`\`\`javascript\n${input.code.trim()}\n\`\`\`\n\n`;
|
|
428
|
+
} else if (
|
|
429
|
+
block.name === "AskUserQuestion" &&
|
|
430
|
+
Array.isArray(input.questions) &&
|
|
431
|
+
input.questions.length > 0
|
|
432
|
+
) {
|
|
433
|
+
const qCount = input.questions.length;
|
|
434
|
+
const countLabel =
|
|
435
|
+
qCount === 1
|
|
436
|
+
? "Asked 1 question"
|
|
437
|
+
: `Asked ${qCount} questions`;
|
|
438
|
+
let qStr = `> **${countLabel}:**\n`;
|
|
439
|
+
const answers = toolResultMap.get(block.id) || {};
|
|
440
|
+
for (const q of input.questions) {
|
|
441
|
+
const qText = q.question || q.text || "";
|
|
442
|
+
const ans = answers[qText];
|
|
443
|
+
let ansStr = "";
|
|
444
|
+
if (typeof ans === "string") {
|
|
445
|
+
ansStr = ans;
|
|
446
|
+
} else if (Array.isArray(ans)) {
|
|
447
|
+
ansStr = ans.join(", ");
|
|
448
|
+
} else if (ans && typeof ans === "object") {
|
|
449
|
+
ansStr = JSON.stringify(ans);
|
|
450
|
+
} else if (ans != null) {
|
|
451
|
+
ansStr = String(ans);
|
|
452
|
+
}
|
|
453
|
+
qStr += ansStr
|
|
454
|
+
? `> - **${qText}** — ${ansStr}\n`
|
|
455
|
+
: `> - ${qText}\n`;
|
|
456
|
+
}
|
|
457
|
+
contentStr += `${qStr}\n`;
|
|
458
|
+
}
|
|
212
459
|
}
|
|
213
460
|
}
|
|
214
461
|
} else if (message.text) {
|
|
@@ -245,13 +492,37 @@ export class ClaudeParser extends ChatParser {
|
|
|
245
492
|
}
|
|
246
493
|
}
|
|
247
494
|
|
|
495
|
+
// Append files (images/documents)
|
|
496
|
+
if (message.files && Array.isArray(message.files)) {
|
|
497
|
+
for (const file of message.files) {
|
|
498
|
+
const name = file.file_name || "file";
|
|
499
|
+
if (
|
|
500
|
+
file.file_kind === "image" &&
|
|
501
|
+
(file.preview_url || file.preview_asset?.url)
|
|
502
|
+
) {
|
|
503
|
+
const url = file.preview_url || file.preview_asset.url;
|
|
504
|
+
contentStr += `\n\n**Attachment: ${name}**\n\n\n\n`;
|
|
505
|
+
} else if (
|
|
506
|
+
file.file_kind === "document" &&
|
|
507
|
+
file.document_asset?.url
|
|
508
|
+
) {
|
|
509
|
+
const url = file.document_asset.url;
|
|
510
|
+
const pages = file.document_asset.page_count;
|
|
511
|
+
const pageInfo = pages
|
|
512
|
+
? ` · ${pages} page${pages === 1 ? "" : "s"}`
|
|
513
|
+
: "";
|
|
514
|
+
contentStr += `\n\n**Attachment: [${name}](${url})** _(document${pageInfo})_\n\n`;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
248
519
|
contentStr = contentStr.trim();
|
|
249
520
|
if (contentStr) {
|
|
250
521
|
messages.push({ role, content: contentStr });
|
|
251
522
|
}
|
|
252
523
|
|
|
253
524
|
// Extract and push artifacts
|
|
254
|
-
const artifacts = extractArtifacts(message);
|
|
525
|
+
const artifacts = extractArtifacts(message, foldedArtifacts);
|
|
255
526
|
for (const artifact of artifacts) {
|
|
256
527
|
let artContent = "";
|
|
257
528
|
const artTitle = artifact.title || "Artifact";
|
|
@@ -356,13 +627,10 @@ export class ClaudeParser extends ChatParser {
|
|
|
356
627
|
return !overlapsWithError;
|
|
357
628
|
});
|
|
358
629
|
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
? -1
|
|
364
|
-
: 1;
|
|
365
|
-
});
|
|
630
|
+
const combinedSet = new Set([...strictCandidates, ...validFallbacks]);
|
|
631
|
+
const allElements = Array.from(
|
|
632
|
+
document.querySelectorAll(`${strictSelectors}, ${fallbackSelectors}`),
|
|
633
|
+
).filter((el) => combinedSet.has(el));
|
|
366
634
|
|
|
367
635
|
const artifactElements = document.querySelectorAll(".artifact-block-cell");
|
|
368
636
|
const artifactMap = new Map();
|
|
@@ -375,7 +643,7 @@ export class ClaudeParser extends ChatParser {
|
|
|
375
643
|
if (el.matches('[data-testid="user-message"]')) {
|
|
376
644
|
role = "User";
|
|
377
645
|
const clone = el.cloneNode(true);
|
|
378
|
-
clone.
|
|
646
|
+
unrollInteractiveElements(clone, el.ownerDocument || document);
|
|
379
647
|
content = convertToMarkdown(clone);
|
|
380
648
|
} else if (
|
|
381
649
|
el.matches(".font-claude-message") ||
|
|
@@ -384,7 +652,7 @@ export class ClaudeParser extends ChatParser {
|
|
|
384
652
|
) {
|
|
385
653
|
role = "Claude";
|
|
386
654
|
const clone = el.cloneNode(true);
|
|
387
|
-
clone.
|
|
655
|
+
unrollInteractiveElements(clone, el.ownerDocument || document);
|
|
388
656
|
content = convertToMarkdown(clone);
|
|
389
657
|
} else if (el.matches(".artifact-block-cell")) {
|
|
390
658
|
role = "Claude Artifact";
|
package/ai/google_search_ai.js
CHANGED
|
@@ -61,6 +61,14 @@ export function sanitizeResponseContainer(container) {
|
|
|
61
61
|
export function cleanMarkdownSpacing(markdown) {
|
|
62
62
|
if (!markdown) return "";
|
|
63
63
|
return markdown
|
|
64
|
+
.replace(
|
|
65
|
+
/(?:google|sn)\.\\?_setImageSrc\s*\(\s*['"][^'"]*['"]\s*,\s*['"][^'"]*['"]\s*\);?/gi,
|
|
66
|
+
"",
|
|
67
|
+
)
|
|
68
|
+
.replace(
|
|
69
|
+
/data:image(?:\\)?\/[a-zA-Z0-9+.-]+;base64,[A-Za-z0-9+/=\\]+/gi,
|
|
70
|
+
"",
|
|
71
|
+
)
|
|
64
72
|
.replace(/[ \t]+$/gm, "")
|
|
65
73
|
.replace(/\n{3,}/g, "\n\n")
|
|
66
74
|
.trim();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "decant-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Shared AI chat platform parsers and detection for Covai browser extensions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "ai/index.js",
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"access": "public"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@extractus/article-extractor": "^9.0.
|
|
55
|
+
"@extractus/article-extractor": "^9.0.1",
|
|
56
56
|
"@mozilla/readability": "^0.6.0",
|
|
57
|
-
"defuddle": "^0.19.
|
|
57
|
+
"defuddle": "^0.19.3",
|
|
58
58
|
"turndown": "^7.2.4",
|
|
59
59
|
"turndown-plugin-gfm": "^1.0.2"
|
|
60
60
|
},
|