- // ==UserScript==
- // @name 导出DeepSeek回答为图片 | Export DeepSeek Answer to Image
- // @namespace http://github.com/byronleeeee/exportDeepseek
- // @version 1.0
- // @description 将DeepSeek的回答导出为一张图片
- // @author ByronLeeeee
- // @match *://chat.deepseek.com/*
- // @grant none
- // @require https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
- // @license MIT
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- // i18n translations
- const i18n = {
- 'zh': {
- exportBtn: '导出AI回答为图片',
- notFound: 'AI回答区域未找到!',
- selectScheme: '选择配色方案:',
- footer: '回答来自DeepSeek,仅供参考',
- lastAnswer: '最新回答',
- colorSchemes: [
- { name: '白色-蓝色', top: '#FFFFFF', bottom: '#4D6BFE', textTop: '#000000', textBottom: '#FFFFFF' },
- { name: '黑色-金色', top: '#121212', bottom: '#FFD700', textTop: '#FFFFFF', textBottom: '#000000' },
- { name: '浅灰-青色', top: '#F5F5F5', bottom: '#008080', textTop: '#000000', textBottom: '#FFFFFF' },
- { name: '深灰-紫色', top: '#333333', bottom: '#800080', textTop: '#FFFFFF', textBottom: '#FFFFFF' }
- ],
- error: '生成图片失败,请查看控制台了解详情。',
- cancel: '取消',
- export: '导出'
- },
- 'en': {
- exportBtn: 'Export AI Answer to Image',
- notFound: 'AI answer div not found!',
- selectScheme: 'Select a color scheme:',
- footer: 'Answer from DeepSeek, for reference only',
- lastAnswer: 'Last Answer',
- colorSchemes: [
- { name: 'White-Blue', top: '#FFFFFF', bottom: '#4D6BFE', textTop: '#000000', textBottom: '#FFFFFF' },
- { name: 'Black-Gold', top: '#121212', bottom: '#FFD700', textTop: '#FFFFFF', textBottom: '#000000' },
- { name: 'Light Gray-Teal', top: '#F5F5F5', bottom: '#008080', textTop: '#000000', textBottom: '#FFFFFF' },
- { name: 'Dark Gray-Purple', top: '#333333', bottom: '#800080', textTop: '#FFFFFF', textBottom: '#FFFFFF' }
- ],
- error: 'Failed to generate image. Check console for details.',
- cancel: 'Cancel',
- export: 'Export'
- }
- };
-
- const userLang = (navigator.language || navigator.userLanguage).split('-')[0];
- const lang = i18n[userLang] ? userLang : 'en';
- const texts = i18n[lang];
-
- // Add styles with dark mode support
- function addStyles() {
- const style = document.createElement('style');
- style.textContent = `
- .ai-export-fab {
- position: fixed;
- bottom: 24px;
- right: 24px;
- width: 56px;
- height: 56px;
- border-radius: 50%;
- background-color: #4D6BFE;
- color: white;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- box-shadow: 0 4px 8px rgba(0,0,0,0.2);
- z-index: 9999;
- transition: all 0.3s ease;
- }
- .ai-export-fab:hover {
- transform: scale(1.05);
- box-shadow: 0 6px 12px rgba(0,0,0,0.3);
- }
- .ai-export-fab-icon {
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .ai-export-tooltip {
- position: absolute;
- background: rgba(0,0,0,0.7);
- color: white;
- padding: 5px 10px;
- border-radius: 4px;
- font-size: 12px;
- white-space: nowrap;
- right: 70px;
- opacity: 0;
- transition: opacity 0.3s;
- pointer-events: none;
- }
- .ai-export-fab:hover .ai-export-tooltip {
- opacity: 1;
- }
- .ai-color-scheme-modal {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0,0,0,0.5);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 10000;
- }
- .ai-modal-content {
- background-color: white;
- border-radius: 8px;
- padding: 20px;
- width: 300px;
- max-width: 90%;
- color: #000000; /* Default for light mode */
- }
- body.dark .ai-modal-content {
- background-color: #1e1e1e; /* Dark mode background */
- color: #ffffff; /* Dark mode text */
- }
- .ai-modal-header {
- font-size: 18px;
- font-weight: bold;
- margin-bottom: 15px;
- }
- .ai-scheme-options {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 10px;
- margin-bottom: 15px;
- }
- .ai-scheme-option {
- border: 2px solid transparent;
- border-radius: 6px;
- overflow: hidden;
- cursor: pointer;
- transition: all 0.2s;
- }
- .ai-scheme-option:hover {
- transform: translateY(-2px);
- }
- .ai-scheme-option.selected {
- border-color: #4D6BFE;
- }
- .ai-scheme-preview {
- display: flex;
- flex-direction: column;
- height: 100px;
- }
- .ai-scheme-top {
- flex: 3;
- display: flex;
- align-items: center;
- justify-content: center;
- font-weight: bold;
- }
- .ai-scheme-bottom {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 12px;
- }
- .ai-modal-buttons {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
- }
- .ai-modal-button {
- padding: 8px 16px;
- border-radius: 4px;
- border: none;
- cursor: pointer;
- font-weight: bold;
- }
- .ai-modal-button.primary {
- background-color: #4D6BFE;
- color: white;
- }
- .ai-modal-button.secondary {
- background-color: #f1f1f1;
- color: #333;
- }
- body.dark .ai-modal-button.secondary {
- background-color: #333333;
- color: #ffffff;
- }
- .ai-export-button {
- position: absolute;
- top: 10px;
- right: 10px;
- background-color: #4D6BFE;
- color: white;
- border: none;
- border-radius: 4px;
- padding: 4px 8px;
- font-size: 12px;
- cursor: pointer;
- opacity: 0;
- transition: opacity 0.2s;
- z-index: 100;
- }
- ._4f9bf79:hover .ai-export-button {
- opacity: 1;
- }
- `;
- document.head.appendChild(style);
- }
-
- // Create FAB
- function createFAB() {
- const fab = document.createElement('div');
- fab.innerHTML = `
- <div class="ai-export-fab">
- <div class="ai-export-fab-icon">
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
- <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
- <polyline points="7 10 12 15 17 10"></polyline>
- <line x1="12" y1="15" x2="12" y2="3"></line>
- </svg>
- </div>
- <span class="ai-export-tooltip">${texts.exportBtn} (${texts.lastAnswer})</span>
- </div>
- `;
- document.body.appendChild(fab);
-
- fab.querySelector('.ai-export-fab').addEventListener('click', async () => {
- const aiDivs = document.querySelectorAll('div._4f9bf79._43c05b5');
- if (!aiDivs.length) {
- alert(texts.notFound);
- return;
- }
- showColorSchemeModal(aiDivs[aiDivs.length - 1]);
- });
- }
-
- // Add export buttons to each AI answer
- function addExportButtonsToAnswers() {
- const observer = new MutationObserver(() => {
- const aiDivs = document.querySelectorAll('div._4f9bf79._43c05b5');
- aiDivs.forEach(aiDiv => {
- if (!aiDiv.querySelector('.ai-export-button')) {
- const exportButton = document.createElement('button');
- exportButton.className = 'ai-export-button';
- exportButton.textContent = texts.exportBtn;
- exportButton.addEventListener('click', () => {
- showColorSchemeModal(aiDiv);
- });
-
- const parentDiv = aiDiv.closest('.ds-relative') || aiDiv;
- parentDiv.style.position = 'relative';
- parentDiv.appendChild(exportButton);
- }
- });
- });
-
- observer.observe(document.body, { childList: true, subtree: true });
-
- const aiDivs = document.querySelectorAll('div._4f9bf79._43c05b5');
- aiDivs.forEach(aiDiv => {
- if (!aiDiv.querySelector('.ai-export-button')) {
- const exportButton = document.createElement('button');
- exportButton.className = 'ai-export-button';
- exportButton.textContent = texts.exportBtn;
- exportButton.addEventListener('click', () => {
- showColorSchemeModal(aiDiv);
- });
-
- const parentDiv = aiDiv.closest('.ds-relative') || aiDiv;
- parentDiv.style.position = 'relative';
- parentDiv.appendChild(exportButton);
- }
- });
- }
-
- // Show color scheme modal
- function showColorSchemeModal(aiDiv) {
- const modal = document.createElement('div');
- modal.className = 'ai-color-scheme-modal';
- let selectedSchemeIndex = 0;
-
- modal.innerHTML = `
- <div class="ai-modal-content">
- <div class="ai-modal-header">${texts.selectScheme}</div>
- <div class="ai-scheme-options">
- ${texts.colorSchemes.map((scheme, index) => `
- <div class="ai-scheme-option ${index === 0 ? 'selected' : ''}" data-index="${index}">
- <div class="ai-scheme-preview">
- <div class="ai-scheme-top" style="background-color:${scheme.top};color:${scheme.textTop}">AI</div>
- <div class="ai-scheme-bottom" style="background-color:${scheme.bottom};color:${scheme.textBottom}">DeepSeek</div>
- </div>
- </div>
- `).join('')}
- </div>
- <div class="ai-modal-buttons">
- <button class="ai-modal-button secondary" id="ai-cancel-btn">${texts.cancel}</button>
- <button class="ai-modal-button primary" id="ai-export-btn">${texts.export}</button>
- </div>
- </div>
- `;
-
- document.body.appendChild(modal);
-
- const schemeOptions = modal.querySelectorAll('.ai-scheme-option');
- schemeOptions.forEach(option => {
- option.addEventListener('click', () => {
- schemeOptions.forEach(opt => opt.classList.remove('selected'));
- option.classList.add('selected');
- selectedSchemeIndex = parseInt(option.dataset.index);
- });
- });
-
- modal.querySelector('#ai-cancel-btn').addEventListener('click', () => {
- document.body.removeChild(modal);
- });
-
- modal.querySelector('#ai-export-btn').addEventListener('click', async () => {
- document.body.removeChild(modal);
- await generateAndDownloadImage(aiDiv, texts.colorSchemes[selectedSchemeIndex], texts);
- });
- }
-
- // Generate and download image (fixed typo)
- async function generateAndDownloadImage(aiDiv, colorScheme, texts) {
- const clonedDiv = aiDiv.cloneNode(true);
- const buttonDiv = clonedDiv.querySelector('.ds-flex[style*="margin-top"]');
- if (buttonDiv) buttonDiv.remove();
-
- const container = document.createElement('div');
- container.style.width = '600px';
- container.style.borderRadius = '10px';
- container.style.overflow = 'hidden';
- container.style.boxShadow = '0 4px 8px rgba(0,0,0,0.1)';
- container.style.display = 'flex';
- container.style.flexDirection = 'column';
-
- const topSection = document.createElement('div');
- topSection.style.padding = '20px';
- topSection.style.backgroundColor = colorScheme.top;
- topSection.style.color = colorScheme.textTop;
-
- fixTextContrast(clonedDiv, colorScheme);
- topSection.appendChild(clonedDiv);
-
- const bottomSection = document.createElement('div');
- bottomSection.textContent = texts.footer;
- bottomSection.style.padding = '10px';
- bottomSection.style.textAlign = 'center';
- bottomSection.style.fontSize = '14px';
- bottomSection.style.fontFamily = 'Arial, sans-serif';
- bottomSection.style.backgroundColor = colorScheme.bottom;
- bottomSection.style.color = colorScheme.textBottom;
-
- container.appendChild(topSection);
- container.appendChild(bottomSection);
-
- container.style.position = 'absolute';
- container.style.left = '-9999px';
- document.body.appendChild(container);
-
- try {
- const canvas = await html2canvas(container, {
- scale: 2,
- backgroundColor: null,
- useCORS: true,
- logging: false
- });
-
- const link = document.createElement('a');
- link.download = `ai_answer_${new Date().toISOString().split('T')[0]}.png`;
- link.href = canvas.toDataURL('image/png');
- link.click();
- } catch (error) {
- console.error('Error generating image:', error);
- alert(texts.error);
- }
-
- document.body.removeChild(container);
- }
-
- // Fix text contrast
- function fixTextContrast(element, colorScheme) {
- if (colorScheme.textTop === '#FFFFFF') {
- const allTextElements = element.querySelectorAll('*');
- allTextElements.forEach(el => {
- if (el.childNodes && Array.from(el.childNodes).some(node =>
- node.nodeType === Node.TEXT_NODE && node.textContent.trim().length > 0)) {
- const computedStyle = window.getComputedStyle(el);
- const currentColor = computedStyle.color;
- const isDarkColor = currentColor === 'rgb(0, 0, 0)' || currentColor === '#000000' || currentColor === 'black';
- const isCloseToBlack = currentColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/) &&
- parseInt(RegExp.$1) < 100 &&
- parseInt(RegExp.$2) < 100 &&
- parseInt(RegExp.$3) < 100;
- if (isDarkColor || isCloseToBlack) {
- el.style.color = '#FFFFFF';
- }
- }
- });
-
- const codeElements = element.querySelectorAll('pre, code');
- codeElements.forEach(codeEl => {
- if (codeEl.tagName === 'PRE') {
- codeEl.style.backgroundColor = '#2A2A2A';
- codeEl.style.border = '1px solid #444';
- codeEl.style.color = '#E0E0E0';
- }
- if (codeEl.tagName === 'CODE' && codeEl.parentElement.tagName !== 'PRE') {
- codeEl.style.backgroundColor = '#3A3A3A';
- codeEl.style.color = '#E0E0E0';
- codeEl.style.padding = '2px 4px';
- codeEl.style.borderRadius = '3px';
- }
- const syntaxElements = codeEl.querySelectorAll('span');
- syntaxElements.forEach(span => {
- const spanColor = window.getComputedStyle(span).color;
- if (spanColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/) &&
- parseInt(RegExp.$1) < 100 &&
- parseInt(RegExp.$2) < 100 &&
- parseInt(RegExp.$3) < 100) {
- const r = parseInt(RegExp.$1);
- const g = parseInt(RegExp.$2);
- const b = parseInt(RegExp.$3);
- if (r > g && r > b) {
- span.style.color = '#FF9090';
- } else if (g > r && g > b) {
- span.style.color = '#90FF90';
- } else if (b > r && b > g) {
- span.style.color = '#9090FF';
- } else {
- span.style.color = '#E0E0E0';
- }
- }
- });
- });
- }
- }
-
- // Initialize
- function init() {
- addStyles();
- createFAB();
- addExportButtonsToAnswers();
- }
-
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', init);
- } else {
- init();
- }
- })();