导出 Google AI Studio 对话

在 Google AI Studio 页面显示一个悬浮按钮,点击即可导出当前对话为 MD 文件,包含文件名、导出时间和 AI 名称。

  1. // ==UserScript==
  2. // @name 导出 Google AI Studio 对话
  3. // @namespace https://aistudio.google.com/
  4. // @version 1.3
  5. // @description 在 Google AI Studio 页面显示一个悬浮按钮,点击即可导出当前对话为 MD 文件,包含文件名、导出时间和 AI 名称。
  6. // @author Gemini 2.0 Flash Thinking Experimental
  7. // @match https://aistudio.google.com/prompts/*
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11. (function() {
  12. 'use strict';
  13.  
  14. // 创建悬浮按钮
  15. const exportButton = document.createElement('button');
  16. exportButton.textContent = '导出对话';
  17. exportButton.style.position = 'fixed';
  18. exportButton.style.bottom = '20px';
  19. exportButton.style.right = '20px';
  20. exportButton.style.padding = '10px 15px';
  21. exportButton.style.backgroundColor = '#007bff';
  22. exportButton.style.color = 'white';
  23. exportButton.style.border = 'none';
  24. exportButton.style.borderRadius = '5px';
  25. exportButton.style.cursor = 'pointer';
  26. exportButton.style.zIndex = '1000'; // 确保按钮在其他元素之上
  27.  
  28. // 格式化日期时间
  29. function formatDateTime(date) {
  30. const year = date.getFullYear();
  31. const month = String(date.getMonth() + 1).padStart(2, '0');
  32. const day = String(date.getDate()).padStart(2, '0');
  33. const hour = String(date.getHours()).padStart(2, '0');
  34. const minute = String(date.getMinutes()).padStart(2, '0');
  35. return `${year}-${month}-${day} ${hour}:${minute}`;
  36. }
  37.  
  38. function getMessageContent(element) {
  39. let content = '';
  40.  
  41. function traverseNodes(node) {
  42. if (node.nodeType === Node.ELEMENT_NODE) {
  43. if (node.nodeName === 'P') {
  44. content += node.textContent + '\n';
  45. } else {
  46. node.childNodes.forEach(traverseNodes); // Recursively traverse child nodes
  47. }
  48. } else if (node.nodeType === Node.TEXT_NODE) {
  49. content += node.textContent;
  50. }
  51. }
  52.  
  53. element.childNodes.forEach(traverseNodes);
  54. return content.trim();
  55. }
  56.  
  57. // 添加按钮点击事件
  58. exportButton.addEventListener('click', function() {
  59. const now = new Date();
  60. const formattedDateTime = formatDateTime(now);
  61.  
  62. // 获取页面标题作为文件名
  63. const pageTitleElement = document.querySelector('.page-title');
  64. const baseFilename = pageTitleElement ? pageTitleElement.textContent.trim() : 'conversation';
  65. const filename = `${baseFilename} (${formattedDateTime}).md`;
  66.  
  67. // 获取 AI 名称
  68. const aiNameElement = document.querySelector('.model-option-content .gmat-body-medium');
  69. const aiName = aiNameElement ? aiNameElement.textContent.trim() : 'AI';
  70.  
  71. // 获取所有对话元素
  72. const userMessages = Array.from(document.querySelectorAll('.user-prompt-container'));
  73. const modelMessages = Array.from(document.querySelectorAll('.model-prompt-container'));
  74.  
  75. // 将所有消息合并到一个数组中,并标记类型
  76. const allMessages = [];
  77. userMessages.forEach(message => allMessages.push({ author: '我', element: message }));
  78. modelMessages.forEach(message => allMessages.push({ author: 'AI', element: message }));
  79.  
  80. // 按照元素在 DOM 中的出现顺序排序
  81. allMessages.sort((a, b) => {
  82. const parentA = a.element.parentNode;
  83. const parentB = b.element.parentNode;
  84. if (parentA === parentB) {
  85. return Array.from(parentA.children).indexOf(a.element) - Array.from(parentB.children).indexOf(b.element);
  86. }
  87. const treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, false);
  88. let indexA = -1;
  89. let indexB = -1;
  90. let currentIndex = 0;
  91. while (treeWalker.nextNode()) {
  92. const currentNode = treeWalker.currentNode;
  93. if (currentNode === a.element) {
  94. indexA = currentIndex;
  95. }
  96. if (currentNode === b.element) {
  97. indexB = currentIndex;
  98. }
  99. if (indexA !== -1 && indexB !== -1) {
  100. return indexA - indexB;
  101. }
  102. currentIndex++;
  103. }
  104. return 0;
  105. });
  106.  
  107. // 构建 Markdown 内容
  108. let markdownContent = '';
  109. if (pageTitleElement) {
  110. markdownContent += `# ${pageTitleElement.textContent.trim()}\n`;
  111. }
  112. markdownContent += `\n- 导出时间:${formattedDateTime}\n`;
  113. markdownContent += `- AI${aiName}\n`;
  114. markdownContent += `\n---\n`;
  115.  
  116. for (let i = 0; i < allMessages.length; i += 2) {
  117. const myMessage = allMessages[i];
  118. const aiMessage = allMessages[i + 1];
  119.  
  120. if (myMessage && myMessage.author === '我') {
  121. markdownContent += `\n**我**: ${getMessageContent(myMessage.element)}\n\n`;
  122. }
  123.  
  124. if (aiMessage && aiMessage.author === 'AI') {
  125. let aiContent = getMessageContent(aiMessage.element);
  126. if (aiContent.endsWith(' warning')) {
  127. aiContent = aiContent.slice(0, -' warning'.length);
  128. }
  129. markdownContent += `**AI**: ${aiContent}\n\n`;
  130. }
  131.  
  132. if (myMessage || aiMessage) {
  133. markdownContent += '---\n';
  134. }
  135. }
  136.  
  137. // 移除重复的分割线
  138. markdownContent = markdownContent.replace(/^---\n(?:---\n)+/gm, '');
  139.  
  140. // 移除两个或多个连续的换行
  141. markdownContent = markdownContent.replace(/\n{2,}/g, '\n\n');
  142.  
  143. // 创建 Blob 对象
  144. const blob = new Blob([markdownContent], { type: 'text/markdown;charset=utf-8' });
  145.  
  146. // 创建下载链接
  147. const url = URL.createObjectURL(blob);
  148. const downloadLink = document.createElement('a');
  149. downloadLink.href = url;
  150. downloadLink.download = filename; // 使用带时间戳的文件名
  151.  
  152. // 模拟点击下载链接
  153. document.body.appendChild(downloadLink);
  154. downloadLink.click();
  155. document.body.removeChild(downloadLink);
  156.  
  157. // 释放 URL 对象
  158. URL.revokeObjectURL(url);
  159. });
  160.  
  161. // 将按钮添加到页面
  162. document.body.appendChild(exportButton);
  163. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址