// 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; } // Static object containing all artifact scraping functionality const ArtifactScraper = { // Open the artifact list menu openArtifactList() { return new Promise((resolve, reject) => { // 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) { reject(new Error('Global artifact menu button not found')); return; } console.log('Opening artifact list menu...'); let hasResolved = false; // 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' || node.querySelector('[role="menu"]')) ); if (menuAdded) { console.log('Artifact list menu detected as opened'); observer.disconnect(); if (!hasResolved) { hasResolved = true; resolve(true); } } } }); }); observer.observe(document.body, { childList: true, subtree: true }); // Focus and send Enter key event to open menu menuButton.focus(); const enterEvent = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', bubbles: true, cancelable: true }); menuButton.dispatchEvent(enterEvent); // Fallback timeout setTimeout(() => { if (!hasResolved) { observer.disconnect(); hasResolved = true; reject(new Error('Failed to open artifact list menu - timeout')); } }, 3000); }); }, // Parse the artifact list and return filename -> isSelected hash getArtifactListItems() { const menuItems = document.querySelectorAll('li[role="none"] div[role="menuitem"]'); const artifactHash = {}; if (menuItems.length === 0) { console.log('No artifact list items found'); return artifactHash; } menuItems.forEach((item, index) => { const filenameDiv = item.querySelector('div.line-clamp-2'); if (filenameDiv) { const filename = filenameDiv.textContent.trim(); const isSelected = item.classList.contains('border-accent-secondary-100') || item.getAttribute('class').includes('border-accent-secondary-100'); artifactHash[filename] = isSelected; console.log(`Found artifact: ${filename} (${isSelected ? 'selected' : 'unselected'})`); } }); return artifactHash; }, // Select a specific artifact list item selectArtifactListItem(filename) { return new Promise((resolve, reject) => { const menuItems = document.querySelectorAll('li[role="none"] div[role="menuitem"]'); let targetItem = null; // Find the matching artifact by filename menuItems.forEach(item => { const filenameDiv = item.querySelector('div.line-clamp-2'); if (filenameDiv && filenameDiv.textContent.trim() === filename) { targetItem = item; } }); if (!targetItem) { reject(new Error(`Could not find artifact item for: ${filename}`)); return; } console.log(`Selecting artifact: ${filename}`); // Focus and send Enter key event to select the item targetItem.focus(); const enterEvent = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', bubbles: true, cancelable: true }); targetItem.dispatchEvent(enterEvent); // Wait for artifact to load setTimeout(() => { resolve(true); }, 1000); }); }, // Get artifact content and handle selection if needed getArtifact(filename, isCurrentlySelected) { return new Promise(async (resolve, reject) => { // If not currently selected, we need to select it if (!isCurrentlySelected) { // Open menu if not already open const menuOpen = document.querySelector('li[role="none"] div[role="menuitem"]'); if (!menuOpen) { try { await this.openArtifactList(); } catch (error) { reject(new Error(`Failed to open menu to select: ${filename} - ${error.message}`)); return; } } // Select the artifact try { await this.selectArtifactListItem(filename); } catch (error) { reject(new Error(`Failed to select artifact: ${filename} - ${error.message}`)); return; } } // Extract content from the code block using the updated selector const codeBlock = document.querySelector('div.right-0 code'); if (!codeBlock) { reject(new Error(`No code block found for artifact: ${filename}`)); return; } const content = codeBlock.textContent || codeBlock.innerText || ''; console.log(`Extracted content for ${filename}: ${content.length} characters`); resolve(content); }); }, // Main collection method async collectArtifacts() { console.log('Starting artifact collection...'); // Initialize empty collection const artifactCollection = {}; // Open artifact list await this.openArtifactList(); // Get list of artifacts const artifactList = this.getArtifactListItems(); if (Object.keys(artifactList).length === 0) { console.log('No artifacts found in list'); return; } console.log(`Found ${Object.keys(artifactList).length} artifacts to collect`); // Iterate through artifacts and collect content for (const [filename, isSelected] of Object.entries(artifactList)) { console.log(`Processing artifact: ${filename}`); try { const content = await this.getArtifact(filename, isSelected); artifactCollection[filename] = content; console.log(`✓ Collected: ${filename}`); } catch (error) { console.log(`✗ Failed to collect: ${filename} - ${error.message}`); } // Small delay between artifacts await new Promise(resolve => setTimeout(resolve, 500)); } // Log final collection console.log('=== ARTIFACT COLLECTION COMPLETE ==='); console.log(`Collected ${Object.keys(artifactCollection).length} artifacts:`); console.log(artifactCollection); return artifactCollection; } }; // Main function async function main() { console.log('Running artifact collector'); if (!isProjectChat()) { return; } await ArtifactScraper.collectArtifacts(); } // Run the main function main(); })();