// ==UserScript== // @name Sexy.AI to SillyTavern origin // @namespace http://tampermonkey.net/ // @version 1.11 // @description Sync between Sexy.AI and SillyTavern with improved mobile support // @author You // @match https://sexy.ai/workflow* // @match https://staticui.sexy.ai/* // @match http://ducninh.top:8000/* // @grant GM_setValue // @grant GM_getValue // @grant unsafeWindow // @grant GM_addStyle // @downloadURL none // ==/UserScript== (function() { 'use strict'; // Add CSS for mobile and desktop GM_addStyle(` .ai-button { display: inline-block !important; min-height: 44px !important; padding: 12px 20px !important; background-color: #4CAF50 !important; color: white !important; border: none !important; border-radius: 5px !important; cursor: pointer !important; font-size: 16px !important; margin: 10px 5px !important; opacity: 0.8; transition: opacity 0.3s; -webkit-tap-highlight-color: transparent; touch-action: manipulation; } .ai-button:active { opacity: 1; } .button-container { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; width: 100%; } @media (max-width: 768px) { .ai-button { padding: 15px 25px !important; font-size: 18px !important; min-width: 120px; } .button-container { position: fixed; bottom: 0; left: 0; right: 0; padding: 10px; background: rgba(0,0,0,0.8); z-index: 9999; justify-content: center; } .fixed-button { position: fixed !important; z-index: 9999 !important; bottom: 20px !important; right: 20px !important; } } `); function isMobileDevice() { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); } function createStyledButton(text, onClick, isFixed = false) { const button = document.createElement('button'); button.textContent = text; button.className = 'ai-button'; if(isFixed && isMobileDevice()) { button.classList.add('fixed-button'); } // Add touch events for mobile button.addEventListener('touchstart', (e) => { e.preventDefault(); button.style.opacity = '1'; }); button.addEventListener('touchend', (e) => { e.preventDefault(); button.style.opacity = '0.8'; onClick(); }); // Keep click event for desktop button.addEventListener('click', onClick); return button; } const isSexyAI = window.location.href.includes('staticui.sexy.ai'); const isSillyTavern = window.location.href.includes('ducninh.top:8000'); if (isSexyAI) { 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); alert('Prompt added!'); } else { console.error('Available inputs:', document.querySelectorAll('input, textarea')); alert('Input field not found.'); } } else { alert('No prompt found. Copy from SillyTavern first.'); } }, true); document.body.appendChild(promptButton); document.addEventListener('click', (e) => { if (e.target.tagName === 'IMG') { const markdownUrls = [`![alt-text](${e.target.src})`]; GM_setValue('sexyai_images', markdownUrls.join('\n')); alert('Image copied! Switch to SillyTavern tab.'); } }, true); } else if (isSillyTavern) { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.classList?.contains('mes')) { const messageText = node.querySelector('.mes_text'); if (messageText && !messageText.querySelector('.button-container')) { const buttonContainer = document.createElement('div'); buttonContainer.className = 'button-container'; const syncButton = createStyledButton('📥 Sync Image', () => { const markdownUrls = GM_getValue('sexyai_images', null); if (!markdownUrls) { alert('No images found. Click an image in Sexy.AI first.'); return; } const editButton = node.querySelector('.mes_edit'); if (editButton) { editButton.click(); setTimeout(() => { const textarea = document.getElementById('curEditTextarea'); if (textarea) { textarea.value = textarea.value + '\n' + markdownUrls; setTimeout(() => { const confirmButton = node.querySelector('.mes_edit_done'); if (confirmButton) { confirmButton.click(); GM_setValue('sexyai_images', null); alert('Images added successfully!'); } }, 100); } }, 100); } }); 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); alert('Prompt copied! Click "Get Prompt" in Sexy.AI tab'); } else { alert('No valid prompt found. Message should contain image###prompt###'); } }); buttonContainer.appendChild(syncButton); buttonContainer.appendChild(sendPromptButton); messageText.appendChild(buttonContainer); } } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); } })();