From 5bf5a3acb3886a25b9556c9c06e58cc79af2cb72 Mon Sep 17 00:00:00 2001 From: Lexical Bits Date: Fri, 11 Jul 2025 10:40:33 -0400 Subject: [PATCH] Sucessfully fetch filenames from triggered menu --- content.js | 247 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 185 insertions(+), 62 deletions(-) diff --git a/content.js b/content.js index 6725ef1..9586168 100644 --- a/content.js +++ b/content.js @@ -20,49 +20,164 @@ console.log('Confirmed we are in a project chat'); return true; } - - // Function to trigger context menu and extract filename - function getArtifactFilename(artifact) { + + // Function to get all artifact filenames from the global artifact menu + function getAllArtifactFilenames() { 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"]'); + // 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 (!contextMenuTrigger) { - console.log('No context menu trigger found for artifact'); - resolve(null); + if (!menuButton) { + console.log('Global artifact menu button not found'); + resolve([]); return; } - // Function to handle menu opening - function handleMenuOpen() { - // Wait a bit for menu to render + console.log('Found global artifact menu button, attempting to trigger...'); + + let hasResolved = false; + + // Function to extract filenames from the opened menu + function extractFilenames() { setTimeout(() => { - // Look for download or filename-related menu items - const menuItems = document.querySelectorAll('[role="menuitem"], [data-testid*="download"], [data-testid*="save"]'); + if (hasResolved) return; - let filename = null; + const menuItems = document.querySelectorAll('li[role="none"] div[role="menuitem"]'); + const filenames = []; - 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); + 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(); - // 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(); - } + // 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 or pressing escape + // Close the menu by clicking elsewhere document.body.click(); - resolve(filename); - }, 100); + 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 @@ -72,15 +187,13 @@ 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')) + (node.querySelector('li[role="none"] div[role="menuitem"]') || + node.getAttribute('role') === 'menu') ); if (menuAdded) { observer.disconnect(); - handleMenuOpen(); + clickArtifact(); } } }); @@ -88,59 +201,69 @@ observer.observe(document.body, { childList: true, subtree: true }); - // Trigger the context menu - contextMenuTrigger.click(); + // Click the menu button + menuButton.click(); // Fallback timeout setTimeout(() => { observer.disconnect(); - if (filename === null) { - resolve(null); - } - }, 2000); + resolve(false); + }, 3000); }); } - // Find and process artifacts + // Find and process artifacts using the global menu approach 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"]'); + console.log('Getting artifact list from global menu...'); - if (artifacts.length === 0) { - console.log('No artifacts found on this page'); + // 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 ${artifacts.length} artifacts`); + console.log(`Found ${artifactInfos.length} artifacts in global menu`); - // 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}`); + // Process each artifact + for (let i = 0; i < artifactInfos.length; i++) { + const artifactInfo = artifactInfos[i]; + console.log(`Processing artifact: ${artifactInfo.filename}`); try { - // Try to get the filename from context menu - const filename = await getArtifactFilename(artifact); + // Select this artifact if it's not already selected + const selected = await selectArtifact(artifactInfo); - if (filename) { - console.log(`Artifact ${i + 1} filename: ${filename}`); - } else { - console.log(`Artifact ${i + 1} filename: Could not determine filename`); + if (!selected) { + console.log(`Failed to select artifact: ${artifactInfo.filename}`); + continue; } - // 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)}...`); + // Wait a moment for the artifact to fully load + await new Promise(resolve => setTimeout(resolve, 1000)); - // TODO: Add download functionality here + // 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 ${i + 1}:`, error); + console.error(`Error processing artifact ${artifactInfo.filename}:`, error); } // Small delay between processing artifacts