// 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 get all artifact filenames from the global artifact menu function getAllArtifactFilenames() { return new Promise((resolve) => { // Find the global artifact menu button (has the hamburger/list icon) const artifactMenuButton = document.querySelector('button[aria-haspopup="menu"] svg[viewBox="0 0 256 256"] path[d*="M80,64a8,8,0,0,1,8-8H216"]'); const menuButton = artifactMenuButton?.closest('button'); if (!menuButton) { console.log('Global artifact menu button not found'); resolve([]); return; } console.log('Found global artifact menu button, attempting to trigger...'); let hasResolved = false; // Function to extract filenames from the opened menu function extractFilenames() { setTimeout(() => { if (hasResolved) return; const menuItems = document.querySelectorAll('li[role="none"] div[role="menuitem"]'); const filenames = []; if (menuItems.length === 0) { console.log('No menu items found - menu may not have opened'); if (!hasResolved) { hasResolved = true; resolve([]); } return; } menuItems.forEach((item, index) => { // Get the filename from the first div (line-clamp-2 class) const filenameDiv = item.querySelector('div.line-clamp-2'); if (filenameDiv) { const filename = filenameDiv.textContent.trim(); // Check if this is the currently selected item const isSelected = item.classList.contains('border-accent-secondary-100') || item.getAttribute('class').includes('border-accent-secondary-100'); filenames.push({ filename: filename, isSelected: isSelected, element: item, index: index }); console.log(`Found artifact: ${filename} (${isSelected ? 'selected' : 'unselected'})`); } }); // Close the menu by clicking elsewhere document.body.click(); if (!hasResolved) { hasResolved = true; resolve(filenames); } }, 500); } // Listen for menu opening with more comprehensive detection 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.querySelector('li[role="none"] div[role="menuitem"]') || node.getAttribute('role') === 'menu' || node.querySelector('[role="menu"]')) ); if (menuAdded) { console.log('Menu detected as opened'); observer.disconnect(); extractFilenames(); } } }); }); observer.observe(document.body, { childList: true, subtree: true }); // Focus and send Enter key (this worked!) menuButton.focus(); const enterEvent = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', bubbles: true, cancelable: true }); menuButton.dispatchEvent(enterEvent); // Final fallback timeout - only if we haven't resolved yet setTimeout(() => { if (!hasResolved) { observer.disconnect(); console.log('Menu trigger timeout - no menu detected'); hasResolved = true; resolve([]); } }, 4000); }); } // Function to select a specific artifact by clicking on it in the menu function selectArtifact(artifactInfo) { return new Promise((resolve) => { if (artifactInfo.isSelected) { console.log(`${artifactInfo.filename} is already selected`); resolve(true); return; } // Find and click the global menu button again const artifactMenuButton = document.querySelector('button[aria-haspopup="menu"] svg[viewBox="0 0 256 256"] path[d*="M80,64a8,8,0,0,1,8-8H216"]'); const menuButton = artifactMenuButton?.closest('button'); if (!menuButton) { console.log('Could not find menu button to select artifact'); resolve(false); return; } // Function to click the specific artifact function clickArtifact() { setTimeout(() => { const menuItems = document.querySelectorAll('li[role="none"] div[role="menuitem"]'); // Find the matching artifact by filename let targetItem = null; menuItems.forEach(item => { const filenameDiv = item.querySelector('div.line-clamp-2'); if (filenameDiv && filenameDiv.textContent.trim() === artifactInfo.filename) { targetItem = item; } }); if (targetItem) { console.log(`Clicking on ${artifactInfo.filename}`); targetItem.click(); // Wait a moment for the artifact to load setTimeout(() => { resolve(true); }, 500); } else { console.log(`Could not find ${artifactInfo.filename} in menu`); document.body.click(); // Close menu resolve(false); } }, 200); } // 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.querySelector('li[role="none"] div[role="menuitem"]') || node.getAttribute('role') === 'menu') ); if (menuAdded) { observer.disconnect(); clickArtifact(); } } }); }); observer.observe(document.body, { childList: true, subtree: true }); // Click the menu button menuButton.click(); // Fallback timeout setTimeout(() => { observer.disconnect(); resolve(false); }, 3000); }); } // Find and process artifacts using the global menu approach async function processArtifacts() { if (!isProjectChat()) { return; } console.log('Getting artifact list from global menu...'); // Get all available artifacts from the global menu const artifactInfos = await getAllArtifactFilenames(); if (artifactInfos.length === 0) { console.log('No artifacts found in global menu'); return; } console.log(`Found ${artifactInfos.length} artifacts in global menu`); // Process each artifact for (let i = 0; i < artifactInfos.length; i++) { const artifactInfo = artifactInfos[i]; console.log(`Processing artifact: ${artifactInfo.filename}`); try { // Select this artifact if it's not already selected const selected = await selectArtifact(artifactInfo); if (!selected) { console.log(`Failed to select artifact: ${artifactInfo.filename}`); continue; } // Wait a moment for the artifact to fully load await new Promise(resolve => setTimeout(resolve, 1000)); // Now extract the content from the currently displayed artifact // This will depend on how artifacts are rendered in the UI const artifactContainer = document.querySelector('[data-testid*="artifact"], .artifact, iframe[title*="artifact"]'); if (artifactContainer) { const content = artifactContainer.textContent || artifactContainer.innerHTML; console.log(`${artifactInfo.filename} content preview: ${content.substring(0, 100)}...`); // TODO: Add download functionality here // downloadFile(artifactInfo.filename, content); } else { console.log(`Could not find artifact content container for ${artifactInfo.filename}`); } } catch (error) { console.error(`Error processing artifact ${artifactInfo.filename}:`, 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(); })();