diff --git a/content.js b/content.js index 9586168..65a1faa 100644 --- a/content.js +++ b/content.js @@ -20,73 +20,24 @@ 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) => { + + // Step 4: Open the artifact list menu + function 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) { - console.log('Global artifact menu button not found'); - resolve([]); + reject(new Error('Global artifact menu button not found')); return; } - console.log('Found global artifact menu button, attempting to trigger...'); + console.log('Opening artifact list menu...'); 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 + // Listen for menu opening const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList') { @@ -99,9 +50,12 @@ ); if (menuAdded) { - console.log('Menu detected as opened'); + console.log('Artifact list menu detected as opened'); observer.disconnect(); - extractFilenames(); + if (!hasResolved) { + hasResolved = true; + resolve(true); + } } } }); @@ -109,7 +63,7 @@ observer.observe(document.body, { childList: true, subtree: true }); - // Focus and send Enter key (this worked!) + // Focus and send Enter key event to open menu menuButton.focus(); const enterEvent = new KeyboardEvent('keydown', { key: 'Enter', @@ -119,162 +73,174 @@ }); menuButton.dispatchEvent(enterEvent); - // Final fallback timeout - only if we haven't resolved yet + // Fallback timeout setTimeout(() => { if (!hasResolved) { observer.disconnect(); - console.log('Menu trigger timeout - no menu detected'); hasResolved = true; - resolve([]); + reject(new Error('Failed to open artifact list menu - timeout')); } - }, 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() { + // Step 5: Parse the artifact list and return filename -> isSelected hash + function 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; + } + + // Step 6: Select a specific artifact list item + function 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); + }); + } + + // Step 7: Get artifact content and handle selection if needed + function 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 openArtifactList(); + } catch (error) { + reject(new Error(`Failed to open menu to select: ${filename} - ${error.message}`)); + return; + } + } + + // Select the artifact + try { + await selectArtifactListItem(filename); + } catch (error) { + reject(new Error(`Failed to select artifact: ${filename} - ${error.message}`)); + return; + } + } + + // Extract content from the code block + 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); + }); + } + + // Step 2: Main collection method + async function collectArtifacts() { + console.log('Starting artifact collection...'); + + // Step 3: Initialize empty collection + const artifactCollection = {}; + + // Step 4: Open artifact list + const menuOpened = await openArtifactList(); + if (!menuOpened) { + console.log('Failed to open artifact list'); + return; + } + + // Step 5: Get list of artifacts + const artifactList = 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`); + + // Step 6-8: Iterate through artifacts and collect content + for (const [filename, isSelected] of Object.entries(artifactList)) { + console.log(`Processing artifact: ${filename}`); + + try { + const content = await 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)); + } + + // Step 9: Log final collection + console.log('=== ARTIFACT COLLECTION COMPLETE ==='); + console.log(`Collected ${Object.keys(artifactCollection).length} artifacts:`); + console.log(artifactCollection); + } + + // Step 1: Content script is loaded, start collection + async function main() { + console.log('Running artifact collector'); + 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(); + await collectArtifacts(); } // Run the main function