Sucessfully fetch filenames from triggered menu

This commit is contained in:
2025-07-11 10:40:33 -04:00
parent aae32c6370
commit 5bf5a3acb3

View File

@ -21,48 +21,163 @@
return true; return true;
} }
// Function to trigger context menu and extract filename // Function to get all artifact filenames from the global artifact menu
function getArtifactFilename(artifact) { function getAllArtifactFilenames() {
return new Promise((resolve) => { return new Promise((resolve) => {
// Look for context menu trigger button (usually three dots or similar) // Find the global artifact menu button (has the hamburger/list icon)
const contextMenuTrigger = artifact.querySelector('[data-testid*="menu"], [aria-label*="menu"], button[aria-haspopup="menu"]'); 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) { if (!menuButton) {
console.log('No context menu trigger found for artifact'); console.log('Global artifact menu button not found');
resolve(null); resolve([]);
return; return;
} }
// Function to handle menu opening console.log('Found global artifact menu button, attempting to trigger...');
function handleMenuOpen() {
// Wait a bit for menu to render let hasResolved = false;
// Function to extract filenames from the opened menu
function extractFilenames() {
setTimeout(() => { setTimeout(() => {
// Look for download or filename-related menu items if (hasResolved) return;
const menuItems = document.querySelectorAll('[role="menuitem"], [data-testid*="download"], [data-testid*="save"]');
let filename = null; const menuItems = document.querySelectorAll('li[role="none"] div[role="menuitem"]');
const filenames = [];
menuItems.forEach(item => { if (menuItems.length === 0) {
const text = item.textContent || item.getAttribute('aria-label') || ''; console.log('No menu items found - menu may not have opened');
if (!hasResolved) {
// Look for download options that might contain filename hasResolved = true;
if (text.toLowerCase().includes('download') || text.toLowerCase().includes('save')) { resolve([]);
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();
} }
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 or pressing escape // Close the menu by clicking elsewhere
document.body.click(); document.body.click();
resolve(filename); if (!hasResolved) {
}, 100); 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 // Listen for menu opening
@ -72,15 +187,13 @@
const addedNodes = Array.from(mutation.addedNodes); const addedNodes = Array.from(mutation.addedNodes);
const menuAdded = addedNodes.some(node => const menuAdded = addedNodes.some(node =>
node.nodeType === Node.ELEMENT_NODE && node.nodeType === Node.ELEMENT_NODE &&
(node.getAttribute('role') === 'menu' || (node.querySelector('li[role="none"] div[role="menuitem"]') ||
node.querySelector('[role="menu"]') || node.getAttribute('role') === 'menu')
node.classList?.contains('menu') ||
node.getAttribute('data-testid')?.includes('menu'))
); );
if (menuAdded) { if (menuAdded) {
observer.disconnect(); observer.disconnect();
handleMenuOpen(); clickArtifact();
} }
} }
}); });
@ -88,59 +201,69 @@
observer.observe(document.body, { childList: true, subtree: true }); observer.observe(document.body, { childList: true, subtree: true });
// Trigger the context menu // Click the menu button
contextMenuTrigger.click(); menuButton.click();
// Fallback timeout // Fallback timeout
setTimeout(() => { setTimeout(() => {
observer.disconnect(); observer.disconnect();
if (filename === null) { resolve(false);
resolve(null); }, 3000);
}
}, 2000);
}); });
} }
// Find and process artifacts // Find and process artifacts using the global menu approach
async function processArtifacts() { async function processArtifacts() {
if (!isProjectChat()) { if (!isProjectChat()) {
return; return;
} }
// Look for artifacts in the page console.log('Getting artifact list from global menu...');
// Try multiple selectors that might match artifacts
const artifacts = document.querySelectorAll('[data-testid*="artifact"], .artifact, [class*="artifact"], iframe[title*="artifact"]');
if (artifacts.length === 0) { // Get all available artifacts from the global menu
console.log('No artifacts found on this page'); const artifactInfos = await getAllArtifactFilenames();
if (artifactInfos.length === 0) {
console.log('No artifacts found in global menu');
return; 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 // Process each artifact
for (let i = 0; i < artifacts.length; i++) { for (let i = 0; i < artifactInfos.length; i++) {
const artifact = artifacts[i]; const artifactInfo = artifactInfos[i];
console.log(`Processing artifact ${i + 1}`); console.log(`Processing artifact: ${artifactInfo.filename}`);
try { try {
// Try to get the filename from context menu // Select this artifact if it's not already selected
const filename = await getArtifactFilename(artifact); const selected = await selectArtifact(artifactInfo);
if (filename) { if (!selected) {
console.log(`Artifact ${i + 1} filename: ${filename}`); console.log(`Failed to select artifact: ${artifactInfo.filename}`);
} else { continue;
console.log(`Artifact ${i + 1} filename: Could not determine filename`);
} }
// Extract artifact content - this will depend on the actual structure // Wait a moment for the artifact to fully load
const content = artifact.textContent || artifact.innerHTML; await new Promise(resolve => setTimeout(resolve, 1000));
console.log(`Artifact ${i + 1} content preview: ${content.substring(0, 100)}...`);
// 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 // TODO: Add download functionality here
// downloadFile(artifactInfo.filename, content);
} else {
console.log(`Could not find artifact content container for ${artifactInfo.filename}`);
}
} catch (error) { } catch (error) {
console.error(`Error processing artifact ${i + 1}:`, error); console.error(`Error processing artifact ${artifactInfo.filename}:`, error);
} }
// Small delay between processing artifacts // Small delay between processing artifacts