Sync between Sexy.AI and SillyTavern

Enhanced integration between SillyTavern and Sexy.AI

目前為 2024-11-21 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Sync between Sexy.AI and SillyTavern
// @namespace    http://tampermonkey.net/
// @version      2.3
// @description  Enhanced integration between SillyTavern and Sexy.AI
// @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;
    }

    // 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 editMessageAndAddImage(messageNode, markdownUrls) {
            return new Promise((resolve, reject) => {
                try {
                    console.log('Starting edit process...');
                    const editButton = messageNode.querySelector('.mes_edit');
                    if (!editButton) {
                        throw new Error('Edit button not found');
                    }
                    editButton.click();

                    setTimeout(() => {
                        const textarea = document.getElementById('curEditTextarea');
                        if (!textarea) {
                            reject(new Error('Textarea not found'));
                            return;
                        }

                        textarea.value = textarea.value + '\n' + markdownUrls;
                        textarea.dispatchEvent(new Event('input', { bubbles: true }));

                        setTimeout(() => {
                            const confirmButton = messageNode.querySelector('.mes_edit_done');
                            if (!confirmButton) {
                                reject(new Error('Confirm button not found'));
                                return;
                            }
                            confirmButton.click();
                            resolve();
                        }, 500);
                    }, 500);

                } catch (error) {
                    reject(error);
                }
            });
        }

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

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

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

            const text = messageText.innerHTML;
            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 syncButton = createStyledButton('📥 Sync Image', async () => {
                    console.log('Sync button clicked');
                    try {
                        const markdownUrls = GM_getValue('sexyai_images', null);
                        if (!markdownUrls) return;

                        await editMessageAndAddImage(messageNode, markdownUrls);
                        GM_setValue('sexyai_images', null);

                        syncButton.style.backgroundColor = '#2196F3';
                        syncButton.textContent = '✓ Synced';
                        setTimeout(() => {
                            syncButton.style.backgroundColor = '#4CAF50';
                            syncButton.textContent = '📥 Sync Image';
                        }, 2000);
                    } catch (error) {
                        console.error('Error in sync process:', error);
                        syncButton.style.backgroundColor = '#f44336';
                        syncButton.textContent = '❌ Error';
                        setTimeout(() => {
                            syncButton.style.backgroundColor = '#4CAF50';
                            syncButton.textContent = '📥 Sync Image';
                        }, 2000);
                    }
                });

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

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

QingJ © 2025

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