This commit is contained in:
2025-07-11 10:01:18 -04:00
commit aae32c6370
11 changed files with 298 additions and 0 deletions

159
content.js Normal file
View File

@ -0,0 +1,159 @@
// Content script for artifact processing
(function() {
console.log('Content script loaded');
// Check if we're in a project chat by looking for the project link with forward slash
function isProjectChat() {
const projectLink = document.querySelector('a[href*="/project/"]');
if (!projectLink) {
console.log('Not in a project chat - no project link found');
return false;
}
// Additional check: look for the forward slash separator in the project breadcrumb
const breadcrumbWithSlash = projectLink.querySelector('span.opacity-50');
if (!breadcrumbWithSlash || !breadcrumbWithSlash.textContent.includes('/')) {
console.log('Not in a project chat - no forward slash separator found');
return false;
}
console.log('Confirmed we are in a project chat');
return true;
}
// Function to trigger context menu and extract filename
function getArtifactFilename(artifact) {
return new Promise((resolve) => {
// Look for context menu trigger button (usually three dots or similar)
const contextMenuTrigger = artifact.querySelector('[data-testid*="menu"], [aria-label*="menu"], button[aria-haspopup="menu"]');
if (!contextMenuTrigger) {
console.log('No context menu trigger found for artifact');
resolve(null);
return;
}
// Function to handle menu opening
function handleMenuOpen() {
// Wait a bit for menu to render
setTimeout(() => {
// Look for download or filename-related menu items
const menuItems = document.querySelectorAll('[role="menuitem"], [data-testid*="download"], [data-testid*="save"]');
let filename = null;
menuItems.forEach(item => {
const text = item.textContent || item.getAttribute('aria-label') || '';
// Look for download options that might contain filename
if (text.toLowerCase().includes('download') || text.toLowerCase().includes('save')) {
console.log('Found menu item:', text);
// Try to extract filename from the text
// Common patterns: "Download filename.ext", "Save as filename.ext", etc.
const filenameMatch = text.match(/(?:download|save(?:\s+as)?)\s+(.+\.\w+)/i);
if (filenameMatch) {
filename = filenameMatch[1].trim();
}
}
});
// Close the menu by clicking elsewhere or pressing escape
document.body.click();
resolve(filename);
}, 100);
}
// Listen for menu opening
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
const addedNodes = Array.from(mutation.addedNodes);
const menuAdded = addedNodes.some(node =>
node.nodeType === Node.ELEMENT_NODE &&
(node.getAttribute('role') === 'menu' ||
node.querySelector('[role="menu"]') ||
node.classList?.contains('menu') ||
node.getAttribute('data-testid')?.includes('menu'))
);
if (menuAdded) {
observer.disconnect();
handleMenuOpen();
}
}
});
});
observer.observe(document.body, { childList: true, subtree: true });
// Trigger the context menu
contextMenuTrigger.click();
// Fallback timeout
setTimeout(() => {
observer.disconnect();
if (filename === null) {
resolve(null);
}
}, 2000);
});
}
// Find and process artifacts
async function processArtifacts() {
if (!isProjectChat()) {
return;
}
// Look for artifacts in the page
// Try multiple selectors that might match artifacts
const artifacts = document.querySelectorAll('[data-testid*="artifact"], .artifact, [class*="artifact"], iframe[title*="artifact"]');
if (artifacts.length === 0) {
console.log('No artifacts found on this page');
return;
}
console.log(`Found ${artifacts.length} artifacts`);
// Process each artifact one by one to avoid conflicts
for (let i = 0; i < artifacts.length; i++) {
const artifact = artifacts[i];
console.log(`Processing artifact ${i + 1}`);
try {
// Try to get the filename from context menu
const filename = await getArtifactFilename(artifact);
if (filename) {
console.log(`Artifact ${i + 1} filename: ${filename}`);
} else {
console.log(`Artifact ${i + 1} filename: Could not determine filename`);
}
// Extract artifact content - this will depend on the actual structure
const content = artifact.textContent || artifact.innerHTML;
console.log(`Artifact ${i + 1} content preview: ${content.substring(0, 100)}...`);
// TODO: Add download functionality here
} catch (error) {
console.error(`Error processing artifact ${i + 1}:`, error);
}
// Small delay between processing artifacts
await new Promise(resolve => setTimeout(resolve, 500));
}
}
// Main function to run when content script is executed
async function main() {
console.log('Running artifact processor');
await processArtifacts();
}
// Run the main function
main();
})();