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/LICENSE +661 -0
- package/README.md +116 -0
- package/ai/base.js +26 -0
- package/ai/chatgpt.js +973 -0
- package/ai/chatgpt_helper.js +137 -0
- package/ai/chatgpt_scroll_collector.js +193 -0
- package/ai/claude.js +410 -0
- package/ai/claude_react_reader.js +59 -0
- package/ai/copilot.js +413 -0
- package/ai/deepseek.js +155 -0
- package/ai/gemini.js +856 -0
- package/ai/gemini_cloud_assist.js +91 -0
- package/ai/google_ai_studio.js +116 -0
- package/ai/google_search_ai.js +114 -0
- package/ai/index.js +34 -0
- package/ai/lumo.js +101 -0
- package/ai/meta.js +110 -0
- package/ai/mistral.js +57 -0
- package/ai/notebooklm.js +140 -0
- package/ai/perplexity.js +85 -0
- package/ai/qwen.js +101 -0
- package/ai/z_ai.js +101 -0
- package/detection/detect-platform.js +100 -0
- package/detection/domains.js +45 -0
- package/lib/turndown.js +803 -0
- package/package.json +56 -0
- package/utils/html-to-markdown.js +374 -0
package/ai/claude.js
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { ChatParser } from './base.js';
|
|
2
|
+
import { convertToMarkdown } from '../utils/html-to-markdown.js';
|
|
3
|
+
|
|
4
|
+
async function getOrganizationId() {
|
|
5
|
+
try {
|
|
6
|
+
const response = await fetch('https://claude.ai/api/organizations', {
|
|
7
|
+
credentials: 'include',
|
|
8
|
+
headers: {
|
|
9
|
+
Accept: 'application/json',
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
if (!response.ok) return null;
|
|
13
|
+
const orgs = await response.json();
|
|
14
|
+
if (Array.isArray(orgs) && orgs.length > 0) {
|
|
15
|
+
const chatOrg = orgs.find((org) => org.capabilities && org.capabilities.includes('chat'));
|
|
16
|
+
return chatOrg ? chatOrg.uuid : orgs[0].uuid;
|
|
17
|
+
}
|
|
18
|
+
} catch (e) {
|
|
19
|
+
console.error('[AI Exporter] Failed to detect org ID:', e);
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getConversationId() {
|
|
25
|
+
try {
|
|
26
|
+
if (typeof window === 'undefined' || !window.location) return null;
|
|
27
|
+
return window.location.pathname.match(/\/chat\/([^/?#]+)/)?.[1] ?? null;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function fetchConversation(orgId, conversationId) {
|
|
34
|
+
const url = `https://claude.ai/api/organizations/${orgId}/chat_conversations/${conversationId}?tree=True&rendering_mode=messages&render_all_tools=true`;
|
|
35
|
+
const response = await fetch(url, {
|
|
36
|
+
credentials: 'include',
|
|
37
|
+
headers: {
|
|
38
|
+
Accept: 'application/json',
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new Error(`Failed to fetch Claude conversation: ${response.status}`);
|
|
43
|
+
}
|
|
44
|
+
return response.json();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getCurrentBranch(data) {
|
|
48
|
+
if (!data.chat_messages || !data.current_leaf_message_uuid) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
const messageMap = new Map();
|
|
52
|
+
data.chat_messages.forEach((msg) => {
|
|
53
|
+
if (msg && msg.uuid) {
|
|
54
|
+
messageMap.set(msg.uuid, msg);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const branch = [];
|
|
59
|
+
let currentUuid = data.current_leaf_message_uuid;
|
|
60
|
+
while (currentUuid && messageMap.has(currentUuid)) {
|
|
61
|
+
const message = messageMap.get(currentUuid);
|
|
62
|
+
branch.unshift(message);
|
|
63
|
+
currentUuid = message.parent_message_uuid;
|
|
64
|
+
if (!messageMap.has(currentUuid)) {
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return branch;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function extractArtifactsFromText(text) {
|
|
72
|
+
const artifactRegex = /<antArtifact[^>]*>([\s\S]*?)<\/antArtifact>/g;
|
|
73
|
+
const artifacts = [];
|
|
74
|
+
let match;
|
|
75
|
+
while ((match = artifactRegex.exec(text)) !== null) {
|
|
76
|
+
const fullTag = match[0];
|
|
77
|
+
const content = match[1];
|
|
78
|
+
|
|
79
|
+
const titleMatch = fullTag.match(/title="([^"]*)"/);
|
|
80
|
+
const languageMatch = fullTag.match(/language="([^"]*)"/);
|
|
81
|
+
|
|
82
|
+
artifacts.push({
|
|
83
|
+
title: titleMatch ? titleMatch[1] : 'Artifact',
|
|
84
|
+
language: languageMatch ? languageMatch[1] : 'text',
|
|
85
|
+
content: content.trim(),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return artifacts;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function extractArtifacts(message) {
|
|
92
|
+
const artifacts = [];
|
|
93
|
+
if (message.content && Array.isArray(message.content)) {
|
|
94
|
+
for (const content of message.content) {
|
|
95
|
+
if (
|
|
96
|
+
content.type === 'tool_use' &&
|
|
97
|
+
(content.name === 'artifacts' || content.name === 'create_file') &&
|
|
98
|
+
content.display_content
|
|
99
|
+
) {
|
|
100
|
+
const displayContent = content.display_content;
|
|
101
|
+
if (displayContent.type === 'code_block' && displayContent.code) {
|
|
102
|
+
const filename = displayContent.filename || 'artifact';
|
|
103
|
+
const title = filename
|
|
104
|
+
.split('/')
|
|
105
|
+
.pop()
|
|
106
|
+
.replace(/\.[^.]+$/, '');
|
|
107
|
+
artifacts.push({
|
|
108
|
+
title: title || 'Artifact',
|
|
109
|
+
language: displayContent.language || 'text',
|
|
110
|
+
content: displayContent.code.trim(),
|
|
111
|
+
});
|
|
112
|
+
} else if (displayContent.type === 'json_block' && displayContent.json_block) {
|
|
113
|
+
try {
|
|
114
|
+
const data = JSON.parse(displayContent.json_block);
|
|
115
|
+
if (data.filename) {
|
|
116
|
+
const filename = data.filename;
|
|
117
|
+
const title = filename
|
|
118
|
+
.split('/')
|
|
119
|
+
.pop()
|
|
120
|
+
.replace(/\.[^.]+$/, '');
|
|
121
|
+
artifacts.push({
|
|
122
|
+
title: title || 'Artifact',
|
|
123
|
+
language: data.language || 'text',
|
|
124
|
+
content: (data.code || '').trim(),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
} catch (e) {
|
|
128
|
+
console.warn('[AI Exporter] Failed to parse tool use artifact json:', e);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (content.text) {
|
|
133
|
+
artifacts.push(...extractArtifactsFromText(content.text));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (message.text) {
|
|
138
|
+
artifacts.push(...extractArtifactsFromText(message.text));
|
|
139
|
+
}
|
|
140
|
+
return artifacts;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export class ClaudeParser extends ChatParser {
|
|
144
|
+
name = 'Claude';
|
|
145
|
+
constructor() {
|
|
146
|
+
super();
|
|
147
|
+
this.lastFetch = null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
isAvailable(url) {
|
|
151
|
+
return url.includes('claude.ai');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async parse(options = {}) {
|
|
155
|
+
const title = document.title || 'Claude Chat';
|
|
156
|
+
const messages = [];
|
|
157
|
+
|
|
158
|
+
const conversationId = getConversationId();
|
|
159
|
+
const parserMode = options.parserMode || 'auto';
|
|
160
|
+
|
|
161
|
+
if (conversationId && parserMode !== 'prefer_dom') {
|
|
162
|
+
const orgId = await getOrganizationId();
|
|
163
|
+
if (orgId) {
|
|
164
|
+
try {
|
|
165
|
+
const now = Date.now();
|
|
166
|
+
let data;
|
|
167
|
+
|
|
168
|
+
if (
|
|
169
|
+
this.lastFetch &&
|
|
170
|
+
this.lastFetch.conversationId === conversationId &&
|
|
171
|
+
now - this.lastFetch.timestamp < 20000
|
|
172
|
+
) {
|
|
173
|
+
data = this.lastFetch.data;
|
|
174
|
+
} else {
|
|
175
|
+
data = await fetchConversation(orgId, conversationId);
|
|
176
|
+
this.lastFetch = {
|
|
177
|
+
conversationId,
|
|
178
|
+
timestamp: now,
|
|
179
|
+
data,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const branch = getCurrentBranch(data);
|
|
184
|
+
|
|
185
|
+
const convTitle = data.name || title;
|
|
186
|
+
|
|
187
|
+
for (const message of branch) {
|
|
188
|
+
const role = message.sender === 'human' ? 'User' : 'Claude';
|
|
189
|
+
|
|
190
|
+
let contentStr = '';
|
|
191
|
+
|
|
192
|
+
// Construct content
|
|
193
|
+
if (message.content && Array.isArray(message.content)) {
|
|
194
|
+
for (const block of message.content) {
|
|
195
|
+
if (block.type === 'thinking' && block.thinking) {
|
|
196
|
+
contentStr += `> **Thinking Process:**\n> \n> ${block.thinking.replace(/\n/g, '\n> ')}\n\n`;
|
|
197
|
+
} else if (block.type === 'text' && block.text) {
|
|
198
|
+
const cleanText = block.text
|
|
199
|
+
.replace(/<antArtifact[^>]*>[\s\S]*?<\/antArtifact>/g, '')
|
|
200
|
+
.trim();
|
|
201
|
+
if (cleanText) {
|
|
202
|
+
contentStr += `${cleanText}\n\n`;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} else if (message.text) {
|
|
207
|
+
const cleanText = message.text
|
|
208
|
+
.replace(/<antArtifact[^>]*>[\s\S]*?<\/antArtifact>/g, '')
|
|
209
|
+
.trim();
|
|
210
|
+
if (cleanText) {
|
|
211
|
+
contentStr += `${cleanText}\n\n`;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Append attachments (for user messages)
|
|
216
|
+
if (message.attachments && message.attachments.length > 0) {
|
|
217
|
+
for (const attachment of message.attachments) {
|
|
218
|
+
if (attachment.file_name) {
|
|
219
|
+
let header = `### Attachment: ${attachment.file_name}`;
|
|
220
|
+
const meta = [];
|
|
221
|
+
if (attachment.file_size) {
|
|
222
|
+
meta.push(`${(attachment.file_size / 1024).toFixed(1)} KB`);
|
|
223
|
+
}
|
|
224
|
+
if (attachment.file_type) {
|
|
225
|
+
meta.push(attachment.file_type);
|
|
226
|
+
}
|
|
227
|
+
if (meta.length > 0) {
|
|
228
|
+
header += ` _(${meta.join(', ')})_`;
|
|
229
|
+
}
|
|
230
|
+
contentStr += `\n\n${header}\n`;
|
|
231
|
+
if (attachment.extracted_content) {
|
|
232
|
+
contentStr += `\`\`\`\`\n${attachment.extracted_content}\n\`\`\`\`\n\n`;
|
|
233
|
+
}
|
|
234
|
+
} else if (attachment.extracted_content) {
|
|
235
|
+
contentStr += `\n\n### Pasted\n\`\`\`\`\n${attachment.extracted_content}\n\`\`\`\`\n\n`;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
contentStr = contentStr.trim();
|
|
241
|
+
if (contentStr) {
|
|
242
|
+
messages.push({ role, content: contentStr });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Extract and push artifacts
|
|
246
|
+
const artifacts = extractArtifacts(message);
|
|
247
|
+
for (const artifact of artifacts) {
|
|
248
|
+
let artContent = '';
|
|
249
|
+
const artTitle = artifact.title || 'Artifact';
|
|
250
|
+
const artText = artifact.content || '';
|
|
251
|
+
const artLang = artifact.language || 'text';
|
|
252
|
+
|
|
253
|
+
if (artLang === 'markdown' || artLang === 'text') {
|
|
254
|
+
const quotedContent = artText
|
|
255
|
+
.split('\n')
|
|
256
|
+
.map((line) => `> ${line}`)
|
|
257
|
+
.join('\n');
|
|
258
|
+
artContent = `\n\n> **Artifact: ${artTitle}**\n\n${quotedContent}\n\n`;
|
|
259
|
+
} else {
|
|
260
|
+
artContent = `\n\n> **Artifact: ${artTitle}**\n\`\`\`${artLang}\n${artText}\n\`\`\`\n\n`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
messages.push({
|
|
264
|
+
role: 'Claude Artifact',
|
|
265
|
+
content: artContent.trim(),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const currentUrl =
|
|
271
|
+
typeof window !== 'undefined' && window.location ? window.location.href || '' : '';
|
|
272
|
+
const metadata = {
|
|
273
|
+
Source: 'Claude',
|
|
274
|
+
Date: new Date().toLocaleString(),
|
|
275
|
+
Link: currentUrl,
|
|
276
|
+
Model: data.model || 'Claude',
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
return { title: convTitle, messages, url: currentUrl, metadata };
|
|
280
|
+
} catch (e) {
|
|
281
|
+
console.error('[AI Exporter] Claude API parse failed, falling back to DOM:', e);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Inject the React reader script if not already injected (DOM Fallback)
|
|
287
|
+
if (!document.getElementById('ai-export-claude-reader')) {
|
|
288
|
+
const script = document.createElement('script');
|
|
289
|
+
script.src = chrome.runtime.getURL('content/claude_react_reader.js');
|
|
290
|
+
script.id = 'ai-export-claude-reader';
|
|
291
|
+
script.onload = function () {
|
|
292
|
+
this.remove(); // Clean up script tag
|
|
293
|
+
};
|
|
294
|
+
(document.head || document.documentElement).appendChild(script);
|
|
295
|
+
// Give it a moment to initialize
|
|
296
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Helper to get artifact info
|
|
300
|
+
const getArtifactInfo = (index) => {
|
|
301
|
+
return new Promise((resolve) => {
|
|
302
|
+
const handler = (event) => {
|
|
303
|
+
if (event.data.type === 'RspAtftInfo' && event.data.idx === index) {
|
|
304
|
+
window.removeEventListener('message', handler);
|
|
305
|
+
resolve(event.data.atftInfo);
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
window.addEventListener('message', handler);
|
|
309
|
+
window.postMessage({ type: 'ReqAtftInfo', idx: index }, window.location.origin);
|
|
310
|
+
|
|
311
|
+
// Timeout fallback
|
|
312
|
+
setTimeout(() => {
|
|
313
|
+
window.removeEventListener('message', handler);
|
|
314
|
+
resolve(null);
|
|
315
|
+
}, 1000); // 1s timeout
|
|
316
|
+
});
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const strictSelectors = [
|
|
320
|
+
'[data-testid="user-message"]',
|
|
321
|
+
'.font-claude-message',
|
|
322
|
+
'.font-claude-response',
|
|
323
|
+
'.artifact-block-cell',
|
|
324
|
+
].join(', ');
|
|
325
|
+
|
|
326
|
+
const fallbackSelectors = ['div.font-serif'].join(', ');
|
|
327
|
+
|
|
328
|
+
const strictCandidates = Array.from(document.querySelectorAll(strictSelectors));
|
|
329
|
+
const fallbackCandidates = Array.from(document.querySelectorAll(fallbackSelectors));
|
|
330
|
+
|
|
331
|
+
const validFallbacks = fallbackCandidates.filter((fallback) => {
|
|
332
|
+
const overlapsWithError = strictCandidates.some(
|
|
333
|
+
(strict) => strict.contains(fallback) || fallback.contains(strict),
|
|
334
|
+
);
|
|
335
|
+
return !overlapsWithError;
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const combined = [...new Set([...strictCandidates, ...validFallbacks])];
|
|
339
|
+
|
|
340
|
+
const allElements = combined.sort((a, b) => {
|
|
341
|
+
return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const artifactElements = document.querySelectorAll('.artifact-block-cell');
|
|
345
|
+
const artifactMap = new Map();
|
|
346
|
+
artifactElements.forEach((el, index) => artifactMap.set(el, index));
|
|
347
|
+
|
|
348
|
+
for (const el of allElements) {
|
|
349
|
+
let role = 'Unknown';
|
|
350
|
+
let content = '';
|
|
351
|
+
|
|
352
|
+
if (el.matches('[data-testid="user-message"]')) {
|
|
353
|
+
role = 'User';
|
|
354
|
+
const clone = el.cloneNode(true);
|
|
355
|
+
clone.querySelectorAll('button').forEach((btn) => btn.remove());
|
|
356
|
+
content = convertToMarkdown(clone);
|
|
357
|
+
} else if (
|
|
358
|
+
el.matches('.font-claude-message') ||
|
|
359
|
+
el.matches('.font-claude-response') ||
|
|
360
|
+
el.matches('div.font-serif')
|
|
361
|
+
) {
|
|
362
|
+
role = 'Claude';
|
|
363
|
+
const clone = el.cloneNode(true);
|
|
364
|
+
clone.querySelectorAll('button').forEach((btn) => btn.remove());
|
|
365
|
+
content = convertToMarkdown(clone);
|
|
366
|
+
} else if (el.matches('.artifact-block-cell')) {
|
|
367
|
+
role = 'Claude Artifact';
|
|
368
|
+
|
|
369
|
+
const index = artifactMap.get(el);
|
|
370
|
+
if (index !== undefined) {
|
|
371
|
+
const info = await getArtifactInfo(index);
|
|
372
|
+
if (info) {
|
|
373
|
+
const artTitle = info.title || 'Artifact';
|
|
374
|
+
const artContent = info.content || '';
|
|
375
|
+
const artLang = info.language || 'text';
|
|
376
|
+
if (artLang === 'markdown' || artLang === 'text') {
|
|
377
|
+
const quotedContent = artContent
|
|
378
|
+
.split('\n')
|
|
379
|
+
.map((line) => `> ${line}`)
|
|
380
|
+
.join('\n');
|
|
381
|
+
content = `\n\n> **Artifact: ${artTitle}**\n\n${quotedContent}\n\n`;
|
|
382
|
+
} else {
|
|
383
|
+
content = `\n\n> **Artifact: ${artTitle}**\n\`\`\`${artLang}\n${artContent}\n\`\`\`\n\n`;
|
|
384
|
+
}
|
|
385
|
+
} else {
|
|
386
|
+
const header =
|
|
387
|
+
el.querySelector('.flex.items-center.gap-2') || el.querySelector('.font-bold');
|
|
388
|
+
const fallbackTitle = header ? header.innerText.split('\n')[0] : 'Unknown Artifact';
|
|
389
|
+
content = `\n> [Artifact: ${fallbackTitle} - content extraction failed]\n`;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (content) {
|
|
395
|
+
messages.push({ role, content });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const currentUrl =
|
|
400
|
+
typeof window !== 'undefined' && window.location ? window.location.href || '' : '';
|
|
401
|
+
const metadata = {
|
|
402
|
+
Source: 'Claude',
|
|
403
|
+
Date: new Date().toLocaleString(),
|
|
404
|
+
Link: currentUrl,
|
|
405
|
+
Model: 'Claude',
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
return { title, messages, url: currentUrl, metadata };
|
|
409
|
+
}
|
|
410
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
// Listen for messages from the content script
|
|
3
|
+
window.addEventListener('message', (event) => {
|
|
4
|
+
// Security check: ensure message is from same origin
|
|
5
|
+
if (event.origin !== window.location.origin) return;
|
|
6
|
+
|
|
7
|
+
if (event.data.type === 'ReqAtftInfo') {
|
|
8
|
+
const index = event.data.idx;
|
|
9
|
+
const artifacts = document.querySelectorAll('div.artifact-block-cell');
|
|
10
|
+
const artifactElement = artifacts[index];
|
|
11
|
+
|
|
12
|
+
let artifactInfo = null;
|
|
13
|
+
|
|
14
|
+
if (artifactElement) {
|
|
15
|
+
try {
|
|
16
|
+
// Try to find the React Fiber key
|
|
17
|
+
const key = Object.keys(artifactElement).find((k) => k.startsWith('__reactFiber'));
|
|
18
|
+
if (key) {
|
|
19
|
+
const fiber = artifactElement[key];
|
|
20
|
+
// Navigate React props structure to find the artifact data
|
|
21
|
+
// Path based on reference extension research:
|
|
22
|
+
// memoizedProps -> children -> flatMap(props.properties) -> find(id)
|
|
23
|
+
|
|
24
|
+
const children = fiber.memoizedProps?.children;
|
|
25
|
+
if (Array.isArray(children)) {
|
|
26
|
+
// Try to find the component with properties
|
|
27
|
+
const candidate = children
|
|
28
|
+
.flatMap((c) => {
|
|
29
|
+
// Sometimes structure varies, try to find props.properties
|
|
30
|
+
return c?.props?.properties ? [c.props] : [];
|
|
31
|
+
})
|
|
32
|
+
.find((p) => p.properties && p.properties.id);
|
|
33
|
+
|
|
34
|
+
if (candidate) {
|
|
35
|
+
artifactInfo = candidate.properties;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Fallback: Dump text content if React lookup fails, but mark as raw
|
|
40
|
+
if (!artifactInfo) {
|
|
41
|
+
// artifactInfo = { fallback: true, text: artifactElement.innerText };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error('[AI Export] Error reading React internals:', e);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
window.postMessage(
|
|
50
|
+
{
|
|
51
|
+
type: 'RspAtftInfo',
|
|
52
|
+
idx: index,
|
|
53
|
+
atftInfo: artifactInfo,
|
|
54
|
+
},
|
|
55
|
+
window.location.origin,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
})();
|