const ArtifactExporter = { parseFileMap(fileMap) { const lines = fileMap.split('\n').filter(line => line.trim()); const pathMapping = {}; let currentPath = ''; lines.forEach(line => { const trimmedLine = line.trim(); if (trimmedLine.endsWith('/')) { currentPath = trimmedLine; } else if (trimmedLine && !trimmedLine.startsWith('/')) { if (line.startsWith(' ')) { pathMapping[trimmedLine] = currentPath + trimmedLine; } else { pathMapping[trimmedLine] = trimmedLine; } } }); return pathMapping; }, extractFilenamesFromFileMap(fileMap) { const lines = fileMap.split('\n').filter(line => line.trim()); const filenames = []; lines.forEach(line => { const trimmedLine = line.trim(); if (trimmedLine.endsWith('/') || !trimmedLine || trimmedLine.startsWith('/')) { return; } const filename = trimmedLine.replace(/^\s+/, ''); if (filename) { filenames.push(filename); } }); return filenames.sort((a, b) => b.length - a.length); }, extractFilenameFromArtifactName(artifactName, sortedFilenames) { for (const filename of sortedFilenames) { const lowerArtifactName = artifactName.toLowerCase(); const lowerFilename = filename.toLowerCase(); if (lowerArtifactName.includes(lowerFilename)) { return filename; } } return artifactName; }, createFinalMapping(artifactCollection, pathMapping) { const finalMap = {}; const sortedFilenames = this.extractFilenamesFromFileMap(Object.keys(pathMapping).join('\n')); Object.entries(artifactCollection).forEach(([artifactName, content]) => { const extractedFilename = this.extractFilenameFromArtifactName(artifactName, sortedFilenames); const fullPath = pathMapping[extractedFilename] || extractedFilename; if (artifactName !== extractedFilename) { console.log(`Mapped artifact "${artifactName}" -> found filename "${extractedFilename}" -> "${fullPath}"`); } else { console.log(`Mapped artifact "${artifactName}" -> "${fullPath}"`); } finalMap[fullPath] = content; }); return finalMap; }, generateZipFilename() { const now = new Date(); const timestamp = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0'); return `${timestamp}_claude-artifacts.zip`; }, addFilesToZip(zip, finalMap) { Object.entries(finalMap).forEach(([fullPath, content]) => { const pathParts = fullPath.split('/'); if (pathParts.length > 1) { const filename = pathParts.pop(); const folderPath = pathParts.join('/'); const folder = zip.folder(folderPath); folder.file(filename, content); } else { zip.file(fullPath, content); } }); }, downloadZip(content, filename) { const url = URL.createObjectURL(content); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); console.log(`✓ Downloaded: ${filename}`); }, exportArtifacts(artifactCollection, fileMap) { // Validation if (!artifactCollection || Object.keys(artifactCollection).length === 0) { console.warn("No non-filemap artifacts were found. Ask the LLM to copy them from the project to this chat first."); return; } if (!fileMap) { console.warn("No filemap was found. Ask the LLM to generate one. See the README for a sample prompt."); return; } // Parse the file map into paths const pathMapping = this.parseFileMap(fileMap); // Create final map with full paths and contents const finalMap = this.createFinalMapping(artifactCollection, pathMapping); // Create zip file const zip = new JSZip(); // Add each file to the zip this.addFilesToZip(zip, finalMap); // Generate filename const zipFilename = this.generateZipFilename(); // Generate and download the zip zip.generateAsync({type: "blob"}) .then((content) => { this.downloadZip(content, zipFilename); }) .catch((error) => { console.error('Failed to generate zip:', error); }); } }; // No exports needed - will be in same scope after build