Export chat history from ChatGPT and Grok websites to normal format as Markdown, which can be opened via typora exactly.
Size
26.7 KB
Version
1.1.2
Created
Oct 9, 2025
Updated
14 days ago
1// ==UserScript==
2// @name Export ChatGPT/Gemini/Grok conversations as Markdown
3// @name:zh-CN Export ChatGPT/Gemini/Grok conversations as Markdown
4// @name:zh-TW Export ChatGPT/Gemini/Grok conversations as Markdown
5// @name:ar Export ChatGPT/Gemini/Grok conversations as Markdown
6// @name:bg Export ChatGPT/Gemini/Grok conversations as Markdown
7// @name:cs Export ChatGPT/Gemini/Grok conversations as Markdown
8// @name:da Export ChatGPT/Gemini/Grok conversations as Markdown
9// @name:de Export ChatGPT/Gemini/Grok conversations as Markdown
10// @name:el Export ChatGPT/Gemini/Grok conversations as Markdown
11// @name:en Export ChatGPT/Gemini/Grok conversations as Markdown
12// @name:eo Export ChatGPT/Gemini/Grok conversations as Markdown
13// @name:es Export ChatGPT/Gemini/Grok conversations as Markdown
14// @name:es-419 Export ChatGPT/Gemini/Grok conversations as Markdown
15// @name:fi Export ChatGPT/Gemini/Grok conversations as Markdown
16// @name:fr Export ChatGPT/Gemini/Grok conversations as Markdown
17// @name:fr-CA Export ChatGPT/Gemini/Grok conversations as Markdown
18// @name:he Export ChatGPT/Gemini/Grok conversations as Markdown
19// @name:hr Export ChatGPT/Gemini/Grok conversations as Markdown
20// @name:hu Export ChatGPT/Gemini/Grok conversations as Markdown
21// @name:id Export ChatGPT/Gemini/Grok conversations as Markdown
22// @name:it Export ChatGPT/Gemini/Grok conversations as Markdown
23// @name:ja Export ChatGPT/Gemini/Grok conversations as Markdown
24// @name:ka Export ChatGPT/Gemini/Grok conversations as Markdown
25// @name:ko Export ChatGPT/Gemini/Grok conversations as Markdown
26// @name:nb Export ChatGPT/Gemini/Grok conversations as Markdown
27// @name:nl Export ChatGPT/Gemini/Grok conversations as Markdown
28// @name:pl Export ChatGPT/Gemini/Grok conversations as Markdown
29// @name:pt-BR Export ChatGPT/Gemini/Grok conversations as Markdown
30// @name:ro Export ChatGPT/Gemini/Grok conversations as Markdown
31// @name:ru Export ChatGPT/Gemini/Grok conversations as Markdown
32// @name:sv Export ChatGPT/Gemini/Grok conversations as Markdown
33// @name:th Export ChatGPT/Gemini/Grok conversations as Markdown
34// @name:tr Export ChatGPT/Gemini/Grok conversations as Markdown
35// @name:uk Export ChatGPT/Gemini/Grok conversations as Markdown
36// @name:ug Export ChatGPT/Gemini/Grok conversations as Markdown
37// @name:vi Export ChatGPT/Gemini/Grok conversations as Markdown
38// @description Export chat history from ChatGPT and Grok websites to normal format as Markdown, which can be opened via typora exactly.
39// @description:zh-CN 将 ChatGPT 和 Grok 网站的聊天记录导出为普通 Markdown 格式,可在 Typora 中准确打开。
40// @description:zh-TW 將 ChatGPT 和 Grok 網站的聊天記錄導出為普通 Markdown 格式,可於 Typora 中精確打開。
41// @description:ar تصدير محفوظات الدردشة من موقعي ChatGPT و Grok إلى تنسيق Markdown عادي يمكن فتحه عبر Typora بدقة.
42// @description:bg Експортиране на историята на чатовете от уебсайтовете ChatGPT и Grok в обикновен Markdown формат, който може да се отвори точно чрез Typora.
43// @description:cs Exportovat historii chatu z webů ChatGPT a Grok do obyčejného formátu Markdown, který lze přesně otevřít v Typora.
44// @description:da Eksportér chat‑historik fra ChatGPT- og Grok‑websteder til normalt Markdown‑format, som kan åbnes præcist i Typora.
45// @description:de Exportiere den Chatverlauf von den ChatGPT‑ und Grok‑Websites in ein normales Markdown‑Format, das exakt in Typora geöffnet werden kann.
46// @description:el Εξαγωγή του ιστορικού συνομιλίας από τις ιστοσελίδες ChatGPT και Grok σε απλό μορφότυπο Markdown, που μπορεί να ανοιχτεί ακριβώς με το Typora.
47// @description:en Export chat history from ChatGPT and Grok websites to normal format as Markdown, which can be opened via typora exactly.
48// @description:eo Eksporti babilhistorion de ChatGPT- kaj Grok‑retejoj al norma Markdown‑formato, kiu povas esti malfermita ĝuste en Typora.
49// @description:es Exportar el historial de chat de los sitios web ChatGPT y Grok a un formato normal Markdown, que se pueda abrir exactamente con Typora.
50// @description:es-419 Exportar el historial de chat de los sitios web ChatGPT y Grok a un formato normal Markdown, que se pueda abrir exactamente con Typora.
51// @description:fi Vie ChatGPT:n ja Grok-verkkosivustojen chätähistoria tavalliseen Markdown‑muotoon, jonka Typora avaa täsmälleen oikein.
52// @description:fr Exporter l’historique de discussion des sites ChatGPT et Grok vers un format Markdown normal, pouvant être ouvert exactement avec Typora.
53// @description:fr-CA Exporter l’historique des conversations des sites ChatGPT et Grok dans un format Markdown standard, pouvant être ouvert précisément avec Typora.
54// @description:he ייצוא היסטוריית הצ'אט מאתרי ChatGPT ו‑Grok לפורמט Markdown רגיל, שניתן לפתוח ב‑Typora בדיוק.
55// @description:hr Izvezi povijest razgovora s web‑mjesta ChatGPT i Grok u obični Markdown format, koji se točno može otvoriti u Typora.
56// @description:hu A ChatGPT és Grok webhelyek csevegési előzményeinek exportálása normál Markdown formátumba, amely pontosan megnyitható a Typora segítségével.
57// @description:id Ekspor riwayat obrolan dari situs web ChatGPT dan Grok ke format Markdown biasa, yang bisa dibuka persis dengan Typora.
58// @description:it Esporta la cronologia della chat dai siti ChatGPT e Grok in formato Markdown normale, che può essere aperto esattamente con Typora.
59// @description:ja ChatGPT と Grok のウェブサイトからチャット履歴を通常の Markdown 形式にエクスポートし、Typora で正確に開けるようにします。
60// @description:ka ChatGPT და Grok ვებსაიტების ჩატის ისტორიის ექსპორტი ნორმალურ Markdown ფორმატში, რომელიც Typora-ში ზუსტად იხსნება.
61// @description:ko ChatGPT 및 Grok 웹사이트의 채팅 기록을 일반 Markdown 형식으로 내보내 Typora에서 정확하게 열 수 있습니다.
62// @description:nb Eksporter chattehistorikk fra ChatGPT- og Grok-nettsteder til vanlig Markdown-format, som kan åpnes nøyaktig i Typora.
63// @description:nl Exporteer de chatgeschiedenis van de ChatGPT- en Grok-websites naar normaal Markdown‑formaat, dat precies met Typora geopend kan worden.
64// @description:pl Eksportuj historię czatów ze stron ChatGPT i Grok do zwykłego formatu Markdown, który można dokładnie otworzyć w Typora.
65// @description:pt-BR Exporte o histórico de conversa dos sites ChatGPT e Grok para um formato Markdown normal, que possa ser aberto exatamente via Typora.
66// @description:ro Exportă istoricul conversației de pe site‑urile ChatGPT și Grok într‑un format Markdown normal, care poate fi deschis exact în Typora.
67// @description:ru Экспорт истории чата с сайтов ChatGPT и Grok в обычный формат Markdown, который можно точно открыть в Typora.
68// @description:sv Exportera chatthistorik från ChatGPT- och Grok-webbplatser till vanligt Markdown-format, som kan öppnas exakt i Typora.
69// @description:th ส่งออกประวัติแชทจากเว็บไซต์ ChatGPT และ Grok ไปยังรูปแบบ Markdown ปกติ ซึ่งสามารถเปิดได้อย่างถูกต้องผ่าน Typora
70// @description:tr ChatGPT ve Grok web sitelerinden sohbet geçmişini normal Markdown formatına aktarın; Typora ile tam olarak açılabilir.
71// @description:uk Експортувати історію чату з веб‑сайтів ChatGPT і Grok у звичайний формат Markdown, який точно відкривається в Typora.
72// @description:ug ChatGPT ۋە Grok تور بەتلىرىدىكى چەت تارىخنى ئادەتتىكى Markdown فورماتىغا چىقارسىڭىز بولىدۇ، بۇنى Typora قوشۇلمىسىدە تولۇق ئاچىشقا بولىدۇ.
73// @description:vi Xuất lịch sử trò chuyện từ các trang ChatGPT và Grok sang định dạng Markdown bình thường, có thể mở chính xác bằng Typora.
74// @namespace Elior_Chatgpt_XX
75// @version 1.1.2
76// @author Elior
77// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAkZJREFUWEfNV8tSwzAMtPtFlKFMbm2/rO2XEW4ZygBfVMO6lkdWLFtpe6CXwMSWVqvVI94t+D29DDvv3YFd2aW/Rzy9d+94fn1MR6tZbzm43gxvzjlyZrkCMCcLkCaA59fhGEIRsck5P9QDogK4JeoGuvH7PO1r72cAUp5B+cN/Ibj9z+cU9UK/GYD1ZggP98wMfp+nwmfxj5H2MQR3okigE9hfoJUiHRmARXAkKKRptbpWxeXiRoBJ97coxR4YLswMoEc9XaoArQqsFxClIgLoHf7rAdGJOJdTwRpUZIUz5b3TBB1tRgCt6LlyCYBwgM5Ya1LRQUtXYMG3oodzAES+0dXIGC4KwzkN3B7d11jAexUARZnYKaKJF6/UxjQk2g8QILVfqo4EXCvtUUaSKxZRsmhmAOggVQNXPk9RAqfq4CYAMgVKhXB2mgCq9Ij8aSnIjOE8aYXrQDA5a7JgoApAaCCXFoyj+UhhcV1w9feEqKYgtdc4PLjiNbqTUwRTsCUDERToIiQAeLJWm7chGKZWnCdbqgzOwF0AKH9wwMsrBLfl5YdGpKWgpQGA8635L/PHh0itEfGeQVpplWEE0GnFvXaa54HsGbDbW25iK04A1KWTZsHSQdTbLYhN0zjWFsuKk9rUrC5YxTg2juTYCyBA2fNJlLVqqXmvLiS9VEhDVIZUHXInaOyV9ZWMLvQ2o3sX1uZSalHuPQBMazljYvHnmJV2fu7/fppxlIaltRa8+jlmZkBaZR8h2/Su+DxPAyx/tFj08gu12CtmhuPs2AAAAABJRU5ErkJggg==
78// @match *://chatgpt.com/*
79// @match *://grok.com/*
80// @match *://gemini.google.com/*
81// @license MIT
82// @run-at document-idle
83// @grant GM_registerMenuCommand
84// @grant GM_openInTab
85// @grant GM.openInTab
86// @grant GM_addStyle
87// @grant GM_setValue
88// @grant GM_getValue
89// @grant GM_xmlhttpRequest
90// @downloadURL https://update.greasyfork.org/scripts/543471/Export%20ChatGPTGeminiGrok%20conversations%20as%20Markdown.user.js
91// @updateURL https://update.greasyfork.org/scripts/543471/Export%20ChatGPTGeminiGrok%20conversations%20as%20Markdown.meta.js
92// ==/UserScript==
93(function () {
94 'use strict';
95
96
97 /*!
98 * Copyright (c) 2024 - 2025, Elior. All rights reserved.
99 *
100 * Permission is hereby granted, free of charge, to any person obtaining a copy
101 * of this software and associated documentation files (the "Software"), to deal
102 * in the Software without restriction, including without limitation the rights
103 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
104 * copies of the Software, and to permit persons to whom the Software is
105 * furnished to do so, subject to the following conditions:
106 *
107 * The above copyright notice and this permission notice shall be included in
108 * all copies or substantial portions of the Software.
109 *
110 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
111 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
112 *
113 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
114 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
115 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
116 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
117 * SOFTWARE.
118 */
119
120
121 const CommonUtil = {
122 onPageLoad: function(callback) {
123 if (document.readyState === 'complete') {
124 callback();
125 } else {
126 window.addEventListener('DOMContentLoaded', callback, { once: true });
127 window.addEventListener('load', callback, { once: true });
128 }
129 },
130 addStyle: function(style) {
131 GM_addStyle(style);
132 },
133 createElement: function(tag, options = {}) {
134 const element = document.createElement(tag);
135 if (options.text) {
136 element.textContent = options.text;
137 }
138 if (options.html) {
139 element.innerHTML = options.html;
140 }
141 if (options.style) {
142 Object.assign(element.style, options.style);
143 }
144 if (options.className) {
145 element.className = options.className;
146 }
147 if (options.attributes) {
148 for (let [key, value] of Object.entries(options.attributes)) {
149 element.setAttribute(key, value);
150 }
151 }
152 if (options.childrens) {
153 options.childrens.forEach((child) => {
154 element.appendChild(child);
155 });
156 }
157 return element;
158 },
159 openInTab: function(url, options = { 'active': true, 'insert': true, 'setParent': true }) {
160 GM.openInTab(url, options);
161 },
162 waitForElementByInterval: function(selector, target = document.body, allowEmpty = true, delay = 10, maxDelay = 10 * 1e3) {
163 return new Promise((resolve) => {
164 let totalDelay = 0;
165 let element = target.querySelector(selector);
166 let result = allowEmpty ? !!element : !!element && !!element.innerHTML;
167 if (result) {
168 resolve(element);
169 }
170 const elementInterval = setInterval(() => {
171 if (totalDelay >= maxDelay) {
172 clearInterval(elementInterval);
173 resolve(null);
174 }
175 element = target.querySelector(selector);
176 result = allowEmpty ? !!element : !!element && !!element.innerHTML;
177 if (result) {
178 clearInterval(elementInterval);
179 resolve(element);
180 } else {
181 totalDelay += delay;
182 }
183 }, delay);
184 });
185 }
186 };
187
188 const HtmlToMarkdown = {
189 to: function(html, platform) {
190 const parser = new DOMParser();
191 const doc = parser.parseFromString(html, 'text/html');
192 const isChatGPT = platform === 'chatGPT', isGemini = platform === 'gemini', isGrok = platform === 'grok';
193 if (!isGemini) {
194 doc.querySelectorAll('span.katex-html').forEach((element) => element.remove());
195 }
196 doc.querySelectorAll('mrow').forEach((mrow) => mrow.remove());
197 doc.querySelectorAll('annotation[encoding="application/x-tex"]').forEach((element) => {
198 if (element.closest('.katex-display')) {
199 const latex = element.textContent;
200 const trimmedLatex = latex.trim();
201 element.replaceWith(`
202$$
203${trimmedLatex}
204$$
205`);
206 } else {
207 const latex = element.textContent;
208 const trimmedLatex = latex.trim();
209 element.replaceWith(`$${trimmedLatex}$`);
210 }
211 });
212 doc.querySelectorAll('strong, b').forEach((bold) => {
213 const markdownBold = `**${bold.textContent}**`;
214 bold.parentNode.replaceChild(document.createTextNode(markdownBold), bold);
215 });
216 doc.querySelectorAll('em, i').forEach((italic) => {
217 const markdownItalic = `*${italic.textContent}*`;
218 italic.parentNode.replaceChild(document.createTextNode(markdownItalic), italic);
219 });
220 doc.querySelectorAll('p code').forEach((code) => {
221 const markdownCode = `\`${code.textContent}\``;
222 code.parentNode.replaceChild(document.createTextNode(markdownCode), code);
223 });
224 doc.querySelectorAll('a').forEach((link) => {
225 const markdownLink = `[${link.textContent}](${link.href})`;
226 link.parentNode.replaceChild(document.createTextNode(markdownLink), link);
227 });
228 doc.querySelectorAll('img').forEach((img) => {
229 const markdownImage = ``;
230 img.parentNode.replaceChild(document.createTextNode(markdownImage), img);
231 });
232 if (isChatGPT) {
233 doc.querySelectorAll('pre').forEach((pre) => {
234 const codeType = pre.querySelector('div > div:first-child')?.textContent || '';
235 const markdownCode = pre.querySelector('div > div:nth-child(3) > code')?.textContent || pre.textContent;
236 pre.innerHTML = `
237\`\`\`${codeType}
238${markdownCode}
239\`\`\``;
240 });
241 } else if (isGrok) {
242 doc.querySelectorAll('div.not-prose').forEach((div) => {
243 const codeType = div.querySelector('div > div > span')?.textContent || '';
244 const markdownCode = div.querySelector('div > div:nth-child(3) > code')?.textContent || div.textContent;
245 div.innerHTML = `
246\`\`\`${codeType}
247${markdownCode}
248\`\`\``;
249 });
250 } else if (isGemini) {
251 doc.querySelectorAll('code-block').forEach((div) => {
252 const codeType = div.querySelector('div > div > span')?.textContent || '';
253 const markdownCode = div.querySelector('div > div:nth-child(2) > div > pre')?.textContent || div.textContent;
254 div.innerHTML = `
255\`\`\`${codeType}
256${markdownCode}
257\`\`\``;
258 });
259 }
260 doc.querySelectorAll('ul').forEach((ul) => {
261 let markdown2 = '';
262 ul.querySelectorAll(':scope > li').forEach((li) => {
263 markdown2 += `- ${li.textContent.trim()}
264`;
265 });
266 ul.parentNode.replaceChild(document.createTextNode('\n' + markdown2.trim()), ul);
267 });
268 doc.querySelectorAll('ol').forEach((ol) => {
269 let markdown2 = '';
270 ol.querySelectorAll(':scope > li').forEach((li, index) => {
271 markdown2 += `${index + 1}. ${li.textContent.trim()}
272`;
273 });
274 ol.parentNode.replaceChild(document.createTextNode('\n' + markdown2.trim()), ol);
275 });
276 for (let i = 1; i <= 6; i++) {
277 doc.querySelectorAll(`h${i}`).forEach((header) => {
278 const markdownHeader = `
279${'#'.repeat(i)} ${header.textContent}
280`;
281 header.parentNode.replaceChild(document.createTextNode(markdownHeader), header);
282 });
283 }
284 doc.querySelectorAll('p').forEach((p) => {
285 const markdownParagraph = '\n' + p.textContent + '\n';
286 p.parentNode.replaceChild(document.createTextNode(markdownParagraph), p);
287 });
288 doc.querySelectorAll('table').forEach((table) => {
289 let markdown2 = '';
290 table.querySelectorAll('thead tr').forEach((tr) => {
291 tr.querySelectorAll('th').forEach((th) => {
292 markdown2 += `| ${th.textContent} `;
293 });
294 markdown2 += '|\n';
295 tr.querySelectorAll('th').forEach(() => {
296 markdown2 += '| ---- ';
297 });
298 markdown2 += '|\n';
299 });
300 table.querySelectorAll('tbody tr').forEach((tr) => {
301 tr.querySelectorAll('td').forEach((td) => {
302 markdown2 += `| ${td.textContent} `;
303 });
304 markdown2 += '|\n';
305 });
306 table.parentNode.replaceChild(document.createTextNode('\n' + markdown2.trim() + '\n'), table);
307 });
308 let markdown = doc.body.innerHTML.replace(/<[^>]*>/g, '');
309 markdown = markdown.replaceAll(/- >/g, '- $\\gt$');
310 markdown = markdown.replaceAll(/>/g, '>');
311 markdown = markdown.replaceAll(/</g, '<');
312 markdown = markdown.replaceAll(/≥/g, '>=');
313 markdown = markdown.replaceAll(/≤/g, '<=');
314 markdown = markdown.replaceAll(/≠/g, '\\neq');
315 return markdown.trim();
316 }
317 };
318
319 const Download = {
320 start: function(data, filename, type) {
321 var file = new Blob([data], { type });
322 if (window.navigator.msSaveOrOpenBlob) {
323 window.navigator.msSaveOrOpenBlob(file, filename);
324 } else {
325 var a = document.createElement('a'), url = URL.createObjectURL(file);
326 a.href = url;
327 a.download = filename;
328 document.body.appendChild(a);
329 a.click();
330 setTimeout(function() {
331 document.body.removeChild(a);
332 window.URL.revokeObjectURL(url);
333 }, 0);
334 }
335 }
336 };
337
338 const Chat = {
339 sanitizeFilename: function(input, replacement = '_') {
340 const illegalRe = /[\/\\\?\%\*\:\|"<>\.]/g;
341 const controlRe = /[\x00-\x1f\x80-\x9f]/g;
342 const reservedRe = /^\.+$/;
343 const windowsReservedRe = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
344 let name = input.replace(illegalRe, replacement).replace(controlRe, replacement).replace(/\s+/g, ' ').trim();
345 if (reservedRe.test(name))
346 name = 'file';
347 if (windowsReservedRe.test(name))
348 name = `file_${name}`;
349 return name || 'untitled';
350 },
351 getConversationElements: function() {
352 const currentUrl = window.location.href;
353 const result = [];
354 let platform = '';
355 let title = '';
356 if (currentUrl.includes('openai.com') || currentUrl.includes('chatgpt.com')) {
357 platform = 'chatGPT';
358 title = document.querySelector('#history a[data-active]')?.textContent;
359 result.push(...document.querySelectorAll('div[data-message-id]'));
360 } else if (currentUrl.includes('grok.com')) {
361 platform = 'grok';
362 result.push(...document.querySelectorAll('div.message-bubble'));
363 } else if (currentUrl.includes('gemini.google.com')) {
364 platform = 'gemini';
365 title = document.querySelector('conversations-list div.selected')?.textContent;
366 const userQueries = document.querySelectorAll('user-query-content');
367 const modelResponses = document.querySelectorAll('model-response');
368 for (let i = 0; i < userQueries.length; i++) {
369 if (i < modelResponses.length) {
370 result.push(userQueries[i]);
371 result.push(modelResponses[i]);
372 } else {
373 result.push(userQueries[i]);
374 }
375 }
376 }
377 return { 'result': result, 'platform': platform, 'title': title };
378 },
379 exportChatAsMarkdown: function() {
380 let markdownContent = '';
381 const { result, platform, title } = this.getConversationElements();
382 const filename = (this.sanitizeFilename(title) || 'chat_export') + '.md';
383 for (let i = 0; i < result.length; i += 2) {
384 if (!result[i + 1])
385 break;
386 let userText = result[i].textContent.trim();
387 let answerHtml = result[i + 1].innerHTML.trim();
388 userText = HtmlToMarkdown.to(userText, platform);
389 answerHtml = HtmlToMarkdown.to(answerHtml, platform);
390 markdownContent += `
391# Q:
392${userText}
393# A:
394${answerHtml}`;
395 }
396 markdownContent = markdownContent.replace(/&/g, '&');
397 if (markdownContent) {
398 Download.start(markdownContent, filename, 'text/markdown');
399 }
400 },
401 exportChatAsPDF: function() {
402 console.log('Exporting chat as PDF...');
403 const { result, title } = this.getConversationElements();
404
405 if (result.length === 0) {
406 console.error('No conversation elements found');
407 return;
408 }
409
410 // Create a new window with the conversation content
411 const printWindow = window.open('', '_blank');
412
413 let htmlContent = `
414<!DOCTYPE html>
415<html>
416<head>
417 <title>${title || 'Chat Export'}</title>
418 <style>
419 body {
420 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
421 max-width: 800px;
422 margin: 40px auto;
423 padding: 20px;
424 line-height: 1.6;
425 color: #333;
426 }
427 h1 {
428 color: #2c3e50;
429 border-bottom: 2px solid #3498db;
430 padding-bottom: 10px;
431 margin-top: 30px;
432 }
433 .question {
434 background-color: #f8f9fa;
435 padding: 15px;
436 border-left: 4px solid #3498db;
437 margin: 20px 0;
438 }
439 .answer {
440 background-color: #ffffff;
441 padding: 15px;
442 border-left: 4px solid #2ecc71;
443 margin: 20px 0;
444 }
445 pre {
446 background-color: #f4f4f4;
447 padding: 10px;
448 border-radius: 5px;
449 overflow-x: auto;
450 }
451 code {
452 background-color: #f4f4f4;
453 padding: 2px 6px;
454 border-radius: 3px;
455 font-family: 'Courier New', monospace;
456 }
457 @media print {
458 body {
459 margin: 0;
460 padding: 20px;
461 }
462 }
463 </style>
464</head>
465<body>
466 <h1>${title || 'Chat Export'}</h1>
467`;
468
469 for (let i = 0; i < result.length; i += 2) {
470 if (!result[i + 1]) break;
471
472 const userText = result[i].innerHTML.trim();
473 const answerHtml = result[i + 1].innerHTML.trim();
474
475 htmlContent += `
476 <div class="question">
477 <h2>Question:</h2>
478 ${userText}
479 </div>
480 <div class="answer">
481 <h2>Answer:</h2>
482 ${answerHtml}
483 </div>
484`;
485 }
486
487 htmlContent += `
488</body>
489</html>
490`;
491
492 printWindow.document.write(htmlContent);
493 printWindow.document.close();
494
495 // Wait for content to load, then trigger print dialog
496 printWindow.onload = function() {
497 setTimeout(() => {
498 printWindow.print();
499 }, 250);
500 };
501 }
502 };
503
504 var css_248z = '.chat-gpt-document-block{align-items:center;border:1px solid #e5e5e5;border-radius:35px;display:flex;font-size:15px;justify-content:center;left:50%;padding:5px 15px;position:fixed;top:9px;transform:translateX(-50%);z-index:99999999999!important;gap:10px}.chat-gpt-document-icon-sm{margin-right:5px}.chat-gpt-document-btn-content{align-items:center;display:flex}.chat-export-btn{background:transparent;border:none;cursor:pointer;padding:8px 12px;border-radius:20px;font-size:14px;display:flex;align-items:center;gap:5px;transition:background-color 0.2s}.chat-export-btn:hover{background-color:#f0f0f0}';
505
506 const Export = {
507 addStyle: function() {
508 CommonUtil.addStyle(css_248z);
509 },
510 createSvgIcon: function() {
511 const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
512 svg.setAttribute('class', 'chat-gpt-document-icon-sm');
513 svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
514 svg.setAttribute('fill', 'none');
515 svg.setAttribute('viewBox', '0 0 24 24');
516 svg.setAttribute('width', '16');
517 svg.setAttribute('height', '16');
518 svg.setAttribute('stroke-width', '1.5');
519 svg.setAttribute('stroke', 'currentColor');
520 const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
521 path.setAttribute('stroke-linecap', 'round');
522 path.setAttribute('stroke-linejoin', 'round');
523 path.setAttribute('d', 'M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m.75 12 3 3m0 0 3-3m-3 3v-6m-1.5-9H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z');
524 svg.appendChild(path);
525 return svg;
526 },
527 generateHtml: function() {
528 const outerDiv = CommonUtil.createElement('div', {
529 className: 'chat-gpt-document-block'
530 });
531
532 // Create Markdown export button
533 const markdownBtn = CommonUtil.createElement('button', {
534 className: 'chat-export-btn',
535 childrens: [
536 this.createSvgIcon(),
537 CommonUtil.createElement('span', {
538 text: 'Export as Markdown'
539 })
540 ]
541 });
542
543 // Create PDF export button
544 const pdfBtn = CommonUtil.createElement('button', {
545 className: 'chat-export-btn',
546 childrens: [
547 this.createSvgIcon(),
548 CommonUtil.createElement('span', {
549 text: 'Export as PDF'
550 })
551 ]
552 });
553
554 outerDiv.appendChild(markdownBtn);
555 outerDiv.appendChild(pdfBtn);
556
557 (document.body || document.documentElement).appendChild(outerDiv);
558
559 markdownBtn.addEventListener('click', function() {
560 Chat.exportChatAsMarkdown();
561 });
562
563 pdfBtn.addEventListener('click', function() {
564 Chat.exportChatAsPDF();
565 });
566 },
567 start: function() {
568 this.addStyle();
569 this.generateHtml();
570 }
571 };
572
573 (() => {
574 if (typeof trustedTypes !== 'undefined' && trustedTypes.defaultPolicy === null) {
575 let s = (s2) => s2;
576 trustedTypes.createPolicy('default', { createHTML: s, createScriptURL: s, createScript: s });
577 }
578 })();
579
580 Export.start();
581
582}());