// *** Core Script - Export *** async function init() { if (window.location.pathname === '/auth/login') return; const buttonOuterHTMLFallback = ``; removeButtons(); if (window.buttonsInterval) { clearInterval(window.buttonsInterval); } if (window.innerWidth < 767) return; const chatConf = await invoke('get_app_conf') || {}; window.buttonsInterval = setInterval(() => { const actionsArea = document.querySelector("form>div>div"); if (!actionsArea) { return; } if (shouldAddButtons(actionsArea)) { let TryAgainButton = actionsArea.querySelector("button"); if (!TryAgainButton) { const parentNode = document.createElement("div"); parentNode.innerHTML = buttonOuterHTMLFallback; TryAgainButton = parentNode.querySelector("button"); } addActionsButtons(actionsArea, TryAgainButton, chatConf); } else if (shouldRemoveButtons()) { removeButtons(); } }, 1000); const Format = { PNG: "png", PDF: "pdf", }; function shouldRemoveButtons() { if (document.querySelector("form .text-2xl")) { return true; } return false; } function shouldAddButtons(actionsArea) { // first, check if there's a "Try Again" button and no other buttons const buttons = actionsArea.querySelectorAll("button"); const hasTryAgainButton = Array.from(buttons).some((button) => { return !/download-/.test(button.id); }); const stopBtn = buttons?.[0]?.innerText; if (/Stop generating/ig.test(stopBtn)) { return false; } if (buttons.length === 2 && (/Regenerate response/ig.test(stopBtn) || buttons[1].innerText === '')) { return true; } if (hasTryAgainButton && buttons.length === 1) { return true; } // otherwise, check if open screen is not visible const isOpenScreen = document.querySelector("h1.text-4xl"); if (isOpenScreen) { return false; } // check if the conversation is finished and there are no share buttons const finishedConversation = document.querySelector("form button>svg"); const hasShareButtons = actionsArea.querySelectorAll("button[share-ext]"); if (finishedConversation && !hasShareButtons.length) { return true; } return false; } function removeButtons() { const downloadButton = document.getElementById("download-png-button"); const downloadPdfButton = document.getElementById("download-pdf-button"); const downloadMdButton = document.getElementById("download-markdown-button"); if (downloadButton) { downloadButton.remove(); } if (downloadPdfButton) { downloadPdfButton.remove(); } if (downloadPdfButton) { downloadMdButton.remove(); } } function addActionsButtons(actionsArea, TryAgainButton) { const downloadButton = TryAgainButton.cloneNode(true); // Export markdown const exportMd = TryAgainButton.cloneNode(true); exportMd.id = "download-markdown-button"; downloadButton.setAttribute("share-ext", "true"); exportMd.title = "Export Markdown"; exportMd.innerHTML = setIcon('md'); exportMd.onclick = () => { exportMarkdown(); }; actionsArea.appendChild(exportMd); // Generate PNG downloadButton.id = "download-png-button"; downloadButton.setAttribute("share-ext", "true"); downloadButton.title = "Generate PNG"; downloadButton.innerHTML = setIcon('png'); downloadButton.onclick = () => { downloadThread(); }; actionsArea.appendChild(downloadButton); // Generate PDF const downloadPdfButton = TryAgainButton.cloneNode(true); downloadPdfButton.id = "download-pdf-button"; downloadButton.setAttribute("share-ext", "true"); downloadPdfButton.title = "Download PDF"; downloadPdfButton.innerHTML = setIcon('pdf'); downloadPdfButton.onclick = () => { downloadThread({ as: Format.PDF }); }; actionsArea.appendChild(downloadPdfButton); } async function exportMarkdown() { const content = Array.from(document.querySelectorAll('main .items-center>div')).map(i => { let j = i.cloneNode(true); if (/dark\:bg-gray-800/.test(i.getAttribute('class'))) { j.innerHTML = `
${i.innerHTML}`; } return j.innerHTML; }).join(''); const data = ExportMD.turndown(content); const { id, filename } = getName(); await invoke('save_file', { name: `notes/${id}.md`, content: data }); await invoke('download_list', { pathname: 'chat.notes.json', filename, id, dir: 'notes' }); } function downloadThread({ as = Format.PNG } = {}) { const elements = new Elements(); elements.fixLocation(); const pixelRatio = window.devicePixelRatio; const minRatio = as === Format.PDF ? 2 : 2.5; window.devicePixelRatio = Math.max(pixelRatio, minRatio); html2canvas(elements.thread, { letterRendering: true, }).then(async function (canvas) { elements.restoreLocation(); window.devicePixelRatio = pixelRatio; const imgData = canvas.toDataURL("image/png"); requestAnimationFrame(() => { if (as === Format.PDF) { return handlePdf(imgData, canvas, pixelRatio); } else { handleImg(imgData); } }); }); } async function handleImg(imgData) { const binaryData = atob(imgData.split("base64,")[1]); const data = []; for (let i = 0; i < binaryData.length; i++) { data.push(binaryData.charCodeAt(i)); } const { pathname, id, filename } = getName(); await invoke('download', { name: `download/img/${id}.png`, blob: data }); await invoke('download_list', { pathname, filename, id, dir: 'download' }); } async function handlePdf(imgData, canvas, pixelRatio) { const { jsPDF } = window.jspdf; const orientation = canvas.width > canvas.height ? "l" : "p"; var pdf = new jsPDF(orientation, "pt", [ canvas.width / pixelRatio, canvas.height / pixelRatio, ]); var pdfWidth = pdf.internal.pageSize.getWidth(); var pdfHeight = pdf.internal.pageSize.getHeight(); pdf.addImage(imgData, "PNG", 0, 0, pdfWidth, pdfHeight, '', 'FAST'); const { pathname, id, filename } = getName(); const data = pdf.__private__.getArrayBuffer(pdf.__private__.buildDocument()); await invoke('download', { name: `download/pdf/${id}.pdf`, blob: Array.from(new Uint8Array(data)) }); await invoke('download_list', { pathname, filename, id, dir: 'download' }); } function getName() { const id = window.crypto.getRandomValues(new Uint32Array(1))[0].toString(36); const name = document.querySelector('nav .overflow-y-auto a.hover\\:bg-gray-800')?.innerText?.trim() || ''; return { filename: name ? name : id, id, pathname: 'chat.download.json' }; } class Elements { constructor() { this.init(); } init() { // this.threadWrapper = document.querySelector(".cdfdFe"); this.spacer = document.querySelector("[class*='h-48'].w-full.flex-shrink-0"); this.thread = document.querySelector( "[class*='react-scroll-to-bottom']>[class*='react-scroll-to-bottom']>div" ); // fix: old chat https://github.com/lencx/ChatGPT/issues/185 if (!this.thread) { this.thread = document.querySelector("main .overflow-y-auto"); } // h-full overflow-y-auto this.positionForm = document.querySelector("form").parentNode; // this.styledThread = document.querySelector("main"); // this.threadContent = document.querySelector(".gAnhyd"); this.scroller = Array.from( document.querySelectorAll('[class*="react-scroll-to"]') ).filter((el) => el.classList.contains("h-full"))[0]; // fix: old chat if (!this.scroller) { this.scroller = document.querySelector('main .overflow-y-auto'); } this.hiddens = Array.from(document.querySelectorAll(".overflow-hidden")); this.images = Array.from(document.querySelectorAll("img[srcset]")); } fixLocation() { this.hiddens.forEach((el) => { el.classList.remove("overflow-hidden"); }); this.spacer.style.display = "none"; this.thread.style.maxWidth = "960px"; this.thread.style.marginInline = "auto"; this.positionForm.style.display = "none"; this.scroller.classList.remove("h-full"); this.scroller.style.minHeight = "100vh"; this.images.forEach((img) => { const srcset = img.getAttribute("srcset"); img.setAttribute("srcset_old", srcset); img.setAttribute("srcset", ""); }); //Fix to the text shifting down when generating the canvas document.body.style.lineHeight = "0.5"; } restoreLocation() { this.hiddens.forEach((el) => { el.classList.add("overflow-hidden"); }); this.spacer.style.display = null; this.thread.style.maxWidth = null; this.thread.style.marginInline = null; this.positionForm.style.display = null; this.scroller.classList.add("h-full"); this.scroller.style.minHeight = null; this.images.forEach((img) => { const srcset = img.getAttribute("srcset_old"); img.setAttribute("srcset", srcset); img.setAttribute("srcset_old", ""); }); document.body.style.lineHeight = null; } } function setIcon(type) { return { // link: ``, png: ``, pdf: ``, md: ``, }[type]; } } window.addEventListener('resize', init); if ( document.readyState === "complete" || document.readyState === "interactive" ) { init(); } else { document.addEventListener("DOMContentLoaded", init); }