Greasy Fork

Greasy Fork is available in English.

Sync between Sexy.AI and SillyTavern old

Enhanced integration between SillyTavern and Sexy.AI with translation support

目前为 2024-11-21 提交的版本。查看 最新版本

// ==UserScript==
// @name         Sync between Sexy.AI and SillyTavern old
// @namespace    http://tampermonkey.net/
// @version      2.8
// @description  Enhanced integration between SillyTavern and Sexy.AI with translation support
// @author       You
// @match        https://sexy.ai/workflow*
// @match        https://staticui.sexy.ai/*
// @match        http://ducninh.top:8000/*
// @match        http://127.0.0.1:8000/*
// @match        http://*/*:8000/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        unsafeWindow
// ==/UserScript==

(function() {
    'use strict';

    console.log("Script started on URL:", window.location.href);

    const isSexyAI = window.location.href.includes('sexy.ai') || window.location.href.includes('staticui.sexy.ai');
    const isSillyTavern = window.location.href.includes(':8000');

    // Utility Functions
    function createStyledButton(text, onClick, isFixed = false) {
        const button = document.createElement('button');
        button.textContent = text;
        let baseStyle = `
            padding: 5px 8px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 3px;
            cursor: pointer;
            font-size: 12px;
            opacity: 0.8;
            transition: all 0.3s;
            z-index: 9999;
        `;

        if(isFixed) {
            baseStyle += `
                position: fixed;
                right: 20px;
            `;
        } else {
            baseStyle += `
                margin-left: 5px;
            `;
        }

        button.style.cssText = baseStyle;
        button.addEventListener('mouseover', () => button.style.opacity = '1');
        button.addEventListener('mouseout', () => button.style.opacity = '0.8');
        button.addEventListener('click', onClick);
        return button;
    }

    // Extract original text from message
    function extractOriginalText(messageNode) {
        // Try to get original text from data attribute first
        const originalText = messageNode.getAttribute('data-original-text');
        if (originalText) {
            return originalText;
        }

        // If no stored original text, get current text
        const messageText = messageNode.querySelector('.mes_text');
        if (!messageText) return '';

        // Clone the message text element to work with
        const clone = messageText.cloneNode(true);

        // Remove any button containers if they exist
        const buttonContainer = clone.querySelector('.button-container');
        if (buttonContainer) {
            buttonContainer.remove();
        }

        return clone.textContent.trim();
    }

    // Store original text before translation
    function storeOriginalText(messageNode) {
        const messageText = messageNode.querySelector('.mes_text');
        if (messageText && !messageNode.hasAttribute('data-original-text')) {
            messageNode.setAttribute('data-original-text', messageText.textContent);
        }
    }

function showImageModal(imageUrl) {
    // Tạo container cho ảnh
    const imgContainer = document.createElement('div');
    imgContainer.style.cssText = `
        position: fixed;
        top: 20px;
        right: 20px;
        z-index: 10000;
    `;

    // Tạo element ảnh
    const img = document.createElement('img');
    img.src = imageUrl.replace('![alt-text](', '').replace(')', '');
    img.style.cssText = `
        width: auto;
        height: auto;
        max-width: 100%;
        max-height: 80vh;
        object-fit: contain;
    `;

    // Tạo nút đóng
    const closeButton = document.createElement('button');
    closeButton.textContent = '×';
    closeButton.style.cssText = `
        position: absolute;
        top: -15px;
        left: -15px;
        background: #4CAF50;
        border: none;
        color: white;
        width: 25px;
        height: 25px;
        border-radius: 50%;
        font-size: 16px;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
    `;

    const closeModal = () => imgContainer.remove();
    closeButton.onclick = closeModal;

    imgContainer.appendChild(img);
    imgContainer.appendChild(closeButton);
    document.body.appendChild(imgContainer);
}

    // SexyAI Implementation
    if (isSexyAI) {
        if (window.location.href.includes('staticui.sexy.ai')) {
            const promptButton = createStyledButton('Get Prompt', () => {
                const prompt = GM_getValue('st_prompt', null);
                if (prompt) {
                    const positiveInput = document.querySelector('textarea') ||
                                        document.querySelector('input[type="text"]');

                    if (positiveInput) {
                        positiveInput.value = prompt;
                        const event = new Event('input', { bubbles: true });
                        positiveInput.dispatchEvent(event);
                        GM_setValue('st_prompt', null);
                        promptButton.style.backgroundColor = '#2196F3';
                        promptButton.textContent = 'Prompt Added';
                        setTimeout(() => {
                            promptButton.style.backgroundColor = '#4CAF50';
                            promptButton.textContent = 'Get Prompt';
                        }, 2000);
                    }
                }
            }, true);

            promptButton.style.top = '80px';
            document.body.appendChild(promptButton);
        }

        document.addEventListener('click', (e) => {
            if (e.target.tagName === 'IMG') {
                const markdownUrls = [`![alt-text](${e.target.src})`];
                console.log('Saving image URL:', markdownUrls[0]);
                GM_setValue('sexyai_images', markdownUrls.join('\n'));

                // Visual feedback on image click
                const overlay = document.createElement('div');
                overlay.style.cssText = `
                    position: fixed;
                    top: 10px;
                    right: 10px;
                    background-color: #4CAF50;
                    color: white;
                    padding: 8px;
                    border-radius: 4px;
                    z-index: 10000;
                    opacity: 0;
                    transition: opacity 0.3s;
                `;
                overlay.textContent = 'Image Copied';
                document.body.appendChild(overlay);

                setTimeout(() => {
                    overlay.style.opacity = '1';
                    setTimeout(() => {
                        overlay.style.opacity = '0';
                        setTimeout(() => overlay.remove(), 300);
                    }, 1500);
                }, 0);
            }
        }, true);

    } else if (isSillyTavern) {
        let isMonitoring = false;

        function addMonitorButton() {
            const extensionsButton = document.querySelector('#extensionsMenuButton');
            if (extensionsButton && !document.querySelector('#monitor_button')) {
                const monitorButton = document.createElement('div');
                monitorButton.id = 'monitor_button';
                monitorButton.className = 'fa-solid fa-eye menu_button';
                monitorButton.title = 'Monitor Messages';
                monitorButton.style.cssText = `
                    display: flex;
                    cursor: pointer;
                    opacity: 0.7;
                    margin: 0 5px;
                    font-size: 18px;
                    transition: all 0.3s;
                `;

                monitorButton.addEventListener('click', () => {
                    isMonitoring = !isMonitoring;
                    monitorButton.style.color = isMonitoring ? '#4CAF50' : '';
                    monitorButton.style.opacity = isMonitoring ? '1' : '0.7';
                    if (isMonitoring) {
                        processExistingMessages();
                    } else {
                        document.querySelectorAll('.button-container').forEach(container => {
                            container.remove();
                        });
                    }
                });

                extensionsButton.parentNode.insertBefore(monitorButton, extensionsButton.nextSibling);
            }
        }

        function addButtonsToMessage(messageNode) {
            if (!isMonitoring) return;
            if (!messageNode || !messageNode.querySelector) return;

            // Store original text before any translation happens
            storeOriginalText(messageNode);

            const messageText = messageNode.querySelector('.mes_text');
            if (!messageText) return;

            if (messageText.querySelector('.button-container')) return;

            // Get original text for processing
            const text = extractOriginalText(messageNode);
            const hasImagePrompt = text.match(/image###([^#]+)###/);

            if (hasImagePrompt) {
                const buttonContainer = document.createElement('div');
                buttonContainer.className = 'button-container';
                buttonContainer.style.display = 'flex';
                buttonContainer.style.gap = '5px';
                buttonContainer.style.marginTop = '5px';

                const showImageButton = createStyledButton('👁️ Show Image', async () => {
                    console.log('Show image button clicked');
                    const markdownUrls = GM_getValue('sexyai_images', null);
                    if (!markdownUrls) {
                        showImageButton.style.backgroundColor = '#f44336';
                        showImageButton.textContent = '❌ No Image';
                        setTimeout(() => {
                            showImageButton.style.backgroundColor = '#4CAF50';
                            showImageButton.textContent = '👁️ Show Image';
                        }, 2000);
                        return;
                    }

                    showImageModal(markdownUrls);

                    showImageButton.style.backgroundColor = '#2196F3';
                    showImageButton.textContent = '✓ Shown';
                    setTimeout(() => {
                        showImageButton.style.backgroundColor = '#4CAF50';
                        showImageButton.textContent = '👁️ Show Image';
                    }, 2000);
                });

                const sendPromptButton = createStyledButton('📤 Send Prompt', () => {
                    const text = extractOriginalText(messageNode);
                    const match = text.match(/image###([^#]+)###/);
                    if (match) {
                        const prompt = match[1].trim();
                        GM_setValue('st_prompt', prompt);

                        // Update visual text without affecting original stored text
                        const textNodes = Array.from(messageText.childNodes).filter(node => node.nodeType === Node.TEXT_NODE);
                        textNodes.forEach(node => {
                            node.textContent = node.textContent.replace(/(image###[^#]+)###/g, '');
                        });

                        sendPromptButton.style.backgroundColor = '#2196F3';
                        sendPromptButton.textContent = '✓ Sent';
                        setTimeout(() => {
                            sendPromptButton.style.backgroundColor = '#4CAF50';
                            sendPromptButton.textContent = '📤 Send Prompt';
                        }, 2000);
                    }
                });

                buttonContainer.appendChild(showImageButton);
                buttonContainer.appendChild(sendPromptButton);
                messageText.appendChild(buttonContainer);
            }
        }

        function processExistingMessages() {
            const messages = document.getElementsByClassName('mes');
            Array.from(messages).forEach(addButtonsToMessage);
        }

        setInterval(addMonitorButton, 2000);

        const observer = new MutationObserver((mutations) => {
            if (!isMonitoring) return;
            mutations.forEach((mutation) => {
                mutation.addedNodes.forEach((node) => {
                    if (node.nodeType === 1) {
                        if (node.classList?.contains('mes')) {
                            addButtonsToMessage(node);
                        }
                        const mesElements = node.getElementsByClassName('mes');
                        Array.from(mesElements).forEach(addButtonsToMessage);
                    }
                });
            });
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }
})();