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 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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