Sync between Sexy.AI and SillyTavern

Enhanced integration between SillyTavern and Sexy.AI

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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