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 14, 2025
Updated
9 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}());