From 7d5eff1d0c4cb19716eb962bc8db912e37d7ac4e Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Fri, 6 Feb 2026 14:11:20 +0530 Subject: [PATCH] feat: add download logic for all data types --- src/components/run/RunContent.tsx | 574 ++++++++++++++++++++++++------ 1 file changed, 464 insertions(+), 110 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index 3a2ea069..c5d8704d 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -68,6 +68,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const [screenshotKeys, setScreenshotKeys] = useState([]); const [screenshotKeyMap, setScreenshotKeyMap] = useState>({}); const [currentScreenshotIndex, setCurrentScreenshotIndex] = useState(0); + const [currentSearchScreenshotTab, setCurrentSearchScreenshotTab] = useState(0); const [currentSchemaIndex, setCurrentSchemaIndex] = useState(0); const [legacyData, setLegacyData] = useState([]); @@ -517,18 +518,88 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe }, 100); }; + const downloadText = (content: string, filename: string) => { + const blob = new Blob([content], { type: 'text/plain;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + + const link = document.createElement("a"); + link.href = url; + link.setAttribute("download", filename); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + setTimeout(() => { + URL.revokeObjectURL(url); + }, 100); + }; + + const downloadHTML = (content: string, filename: string) => { + const blob = new Blob([content], { type: 'text/html;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + + const link = document.createElement("a"); + link.href = url; + link.setAttribute("download", filename); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + setTimeout(() => { + URL.revokeObjectURL(url); + }, 100); + }; + const downloadAllCrawlsAsZip = async (crawlDataArray: any[], zipFilename: string) => { const zip = new JSZip(); - crawlDataArray.forEach((item, index) => { + for (let index = 0; index < crawlDataArray.length; index++) { + const item = crawlDataArray[index]; const url = item?.metadata?.url || item?.url || ''; - const filename = url - ? url.replace(/^https?:\/\//, '').replace(/\//g, '_').replace(/[^a-zA-Z0-9_.-]/g, '_') + '.json' - : `crawl_url_${index + 1}.json`; + const folderName = url + ? url.replace(/^https?:\/\//, '').replace(/\//g, '_').replace(/[^a-zA-Z0-9_.-]/g, '_') + : `page_${index + 1}`; + + const pageFolder = zip.folder(folderName); + if (!pageFolder) continue; - const jsonContent = JSON.stringify(item, null, 2); - zip.file(filename, jsonContent); - }); + pageFolder.file('metadata.json', JSON.stringify(item, null, 2)); + + if (item.text) { + const textContent = typeof item.text === 'object' ? JSON.stringify(item.text, null, 2) : String(item.text); + pageFolder.file('content.txt', textContent); + } + + if (item.html) { + const htmlContent = typeof item.html === 'object' ? JSON.stringify(item.html, null, 2) : String(item.html); + pageFolder.file('content.html', htmlContent); + } + + if (item.markdown) { + const mdContent = typeof item.markdown === 'object' ? JSON.stringify(item.markdown, null, 2) : String(item.markdown); + pageFolder.file('content.md', mdContent); + } + + if (item.links && Array.isArray(item.links)) { + const uniqueLinks = Array.from(new Set(item.links)); + pageFolder.file('links.txt', uniqueLinks.join('\n')); + } + + const screenshots = [ + { id: item.screenshotVisible, name: 'screenshot_visible.png' }, + { id: item.screenshotFullpage, name: 'screenshot_full_page.png' } + ]; + + for (const screenshot of screenshots) { + if (screenshot.id && row.binaryOutput && row.binaryOutput[screenshot.id]) { + const binaryData = row.binaryOutput[screenshot.id].data; + if (binaryData && !binaryData.startsWith('http')) { + const base64Data = binaryData.replace(/^data:image\/\w+;base64,/, ""); + pageFolder.file(screenshot.name, base64Data, { base64: true }); + } + } + } + } const blob = await zip.generateAsync({ type: 'blob' }); const url = URL.createObjectURL(blob); @@ -831,7 +902,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe onClick={() => downloadMarkdown(markdownContent, 'output.md')} sx={{ color: '#FF00C3', textTransform: 'none' }} > - Download + Download Markdown @@ -866,7 +937,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe }} sx={{ color: '#FF00C3', textTransform: 'none' }} > - Download + Download HTML @@ -930,6 +1001,39 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe /> )} + + + )} @@ -1267,6 +1371,18 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe + + + @@ -1303,6 +1419,19 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe : crawlData[0][currentCrawlIndex].text} + + + )} @@ -1340,6 +1469,69 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe : crawlData[0][currentCrawlIndex].html} + + + + + + )} + + {crawlData[0][currentCrawlIndex].markdown && ( + + }> + + + Markdown + + + + + + + {typeof crawlData[0][currentCrawlIndex].markdown === 'object' + ? JSON.stringify(crawlData[0][currentCrawlIndex].markdown, null, 2) + : crawlData[0][currentCrawlIndex].markdown} + + + + + )} @@ -1350,7 +1542,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe ) || []; return validLinks.length > 0 && ( - + }> @@ -1359,13 +1551,29 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe - - {validLinks.map((link: string, idx: number) => ( - - {link} - - ))} + + + {(Array.from(new Set(validLinks)) as string[]).map((link: string, idx: number) => ( + + {link} + + ))} + + + + ); @@ -1374,28 +1582,20 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe @@ -1525,6 +1715,19 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe + + + @@ -1559,6 +1762,20 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe {searchData[currentSearchIndex].text} + + + )} @@ -1596,12 +1813,26 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe : searchData[currentSearchIndex].html} + + + )} {searchData[currentSearchIndex].markdown && ( - + }> @@ -1624,7 +1855,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe whiteSpace: 'pre-wrap', wordBreak: 'break-word', fontFamily: 'monospace', - fontSize: '0.75rem', + fontSize: '0.875rem', m: 0 }} > @@ -1633,6 +1864,20 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe : searchData[currentSearchIndex].markdown} + + + )} @@ -1652,61 +1897,153 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe - - {validLinks.map((link: string, linkIdx: number) => { - return ( - - - {link} - - - ); - })} - - - + + + {(Array.from(new Set(validLinks)) as string[]).map((link: string, idx: number) => ( + + {link} + + ))} + + + + + + + ); })()} - + {(searchData[currentSearchIndex].screenshotVisible || searchData[currentSearchIndex].screenshotFullpage) && ( + + }> + + + Screenshots + + + + + {(() => { + const tabs: { key: string; label: string; value: string }[] = []; + if (searchData[currentSearchIndex].screenshotVisible) + tabs.push({ key: 'visible', label: 'Screenshot (Visible)', value: searchData[currentSearchIndex].screenshotVisible }); + if (searchData[currentSearchIndex].screenshotFullpage) + tabs.push({ key: 'fullpage', label: 'Screenshot (Full Page)', value: searchData[currentSearchIndex].screenshotFullpage }); + + // Ensure activeTab is valid for current tabs array + const activeTab = Math.min(currentSearchScreenshotTab, tabs.length - 1); + + const getImageSrc = (val: string) => { + if (val.startsWith('http')) return val; + if (row.binaryOutput && row.binaryOutput[val]) { + const binaryData = row.binaryOutput[val].data || row.binaryOutput[val]; + return typeof binaryData === 'string' && binaryData.startsWith('http') + ? binaryData + : typeof binaryData === 'string' && binaryData.startsWith('data:') + ? binaryData + : `data:image/png;base64,${binaryData}`; + } + return `data:image/png;base64,${val}`; + }; + + return ( + <> + {tabs.length > 1 && ( + + {tabs.map((tab, idx) => ( + setCurrentSearchScreenshotTab(idx)} + sx={{ + px: 3, py: 1, + cursor: 'pointer', + backgroundColor: activeTab === idx ? (darkMode ? '#121111ff' : '#e9ecef') : 'transparent', + borderBottom: activeTab === idx ? '3px solid #FF00C3' : 'none', + color: darkMode ? '#fff' : '#000', + }} + > + {tab.label} + + ))} + + )} + {tabs.length > 0 && ( + <> + = 0 ? activeTab : 0].value)} + alt={tabs[activeTab >= 0 ? activeTab : 0].label} + style={{ maxWidth: '100%', borderRadius: '4px', border: '1px solid rgba(255,255,255,0.1)' }} + /> + + + + + )} + + ); + })()} + + + )} + + + + @@ -1779,26 +2116,23 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe - + + )} @@ -1874,6 +2208,26 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe /> )} + + + )}