导出DeepSeek回答为图片 | Export DeepSeek Answer to Image

将DeepSeek的回答导出为一张图片

目前為 2025-03-24 提交的版本,檢視 最新版本

// ==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();
    }
})();

QingJ © 2025

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