Greasy Fork 还支持 简体中文。

Sync between Sexy.AI and SillyTavern old

Enhanced integration between SillyTavern and Sexy.AI with translation support

Verze ze dne 21. 11. 2024. Zobrazit nejnovější verzi.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         Sync between Sexy.AI and SillyTavern old
// @namespace    http://tampermonkey.net/
// @version      2.7
// @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: 160%; /* Tăng gấp đôi từ 80% */
        max-height: 90vh; /* Tăng lên để ảnh có thể lớn hơn */
        object-fit: contain;
    `;

    // Tạo nút đóng
    const closeButton = document.createElement('button');
    closeButton.textContent = '×';
    closeButton.style.cssText = `
        position: absolute;
        top: -15px;
        left: -15px; /* Đổi từ right sang left vì đã đặt ảnh bên phải */
        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
        });
    }
})();