Sexy.AI to SillyTavern origin

Sync between Sexy.AI and SillyTavern with improved mobile support

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

// ==UserScript==
// @name         Sexy.AI to SillyTavern origin
// @namespace    http://tampermonkey.net/
// @version      1.3
// @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
// ==/UserScript==
GM_addStyle(`
    .ai-button {
        display: block !important; /* Thay đổi từ inline-block sang block */
        position: fixed !important;
        bottom: 20px !important;
        right: 20px !important;
        z-index: 999999 !important; /* Tăng z-index */
        width: auto !important;
        min-width: 120px !important;
        min-height: 44px !important;
        padding: 12px 20px !important;
        background-color: #4CAF50 !important;
        color: white !important;
        border: none !important;
        border-radius: 5px !important;
        font-size: 18px !important;
        margin: 5px !important;
        opacity: 0.9;
        box-shadow: 0 2px 5px rgba(0,0,0,0.2) !important;
        visibility: visible !important;
        pointer-events: auto !important;
    }

    .button-container {
        position: fixed !important;
        bottom: 0 !important;
        left: 0 !important;
        right: 0 !important;
        padding: 10px !important;
        background: rgba(0,0,0,0.8) !important;
        z-index: 999999 !important;
        display: flex !important;
        justify-content: center !important;
        gap: 10px !important;
        visibility: visible !important;
        pointer-events: auto !important;
    }
`);
(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
       });
   }
})();

QingJ © 2025

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