// ==UserScript==
// @name Sync between Sexy.AI and SillyTavern old
// @namespace http://tampermonkey.net/
// @version 3.0
// @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) {
const button = document.createElement('button');
button.textContent = text;
button.style.cssText = `
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;
margin-left: 5px;
`;
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);
}
}
// 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);
}
}
});
promptButton.style.cssText += 'position: fixed; right: 20px; top: 80px;';
document.body.appendChild(promptButton);
}
document.addEventListener('click', (e) => {
if (e.target.tagName === 'IMG') {
// Lấy tất cả ảnh hiện tại
const allImages = document.querySelectorAll('img');
const latestImages = Array.from(allImages)
.slice(-4) // Lấy 4 ảnh cuối cùng
.map(img => ``);
console.log('Saving latest images:', latestImages);
GM_setValue('sexyai_images', latestImages.join('\n'));
// Visual feedback
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 = 'Images Synced';
document.body.appendChild(overlay);
setTimeout(() => {
overlay.style.opacity = '1';
setTimeout(() => {
overlay.style.opacity = '0';
setTimeout(() => overlay.remove(), 300);
}, 1500);
}, 0);
}
}, true);
}
else if (isSillyTavern) {
function showImageModal(imageUrl) {
const existingModal = document.querySelector('.image-modal-container');
if (existingModal) {
existingModal.remove();
}
const container = document.createElement('div');
container.className = 'image-modal-container';
container.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 10000;
background: rgba(0, 0, 0, 0.8);
padding: 10px;
border-radius: 10px;
max-width: 300px;
`;
// Tải trước tất cả các ảnh
const images = GM_getValue('sexyai_images', '').split('\n').filter(url => url);
const imageElements = images.map(url => {
const img = new Image();
img.src = url.replace('.replace(')', '');
img.style.cssText = `
width: 100%;
height: auto;
max-height: 400px;
object-fit: contain;
border-radius: 5px;
display: none;
`;
return img;
});
let currentIndex = images.findIndex(url => url === imageUrl);
if (currentIndex === -1) currentIndex = 0;
// Hiển thị ảnh hiện tại
imageElements[currentIndex].style.display = 'block';
// Thêm tất cả ảnh vào container
imageElements.forEach(img => container.appendChild(img));
const navigationContainer = document.createElement('div');
navigationContainer.style.cssText = `
display: flex;
justify-content: space-between;
margin-top: 10px;
`;
const prevButton = document.createElement('button');
const nextButton = document.createElement('button');
[prevButton, nextButton].forEach(button => {
button.style.cssText = `
background: #4CAF50;
border: none;
color: white;
padding: 5px 15px;
border-radius: 3px;
cursor: pointer;
margin: 0 5px;
font-size: 16px;
transition: background-color 0.2s;
`;
});
prevButton.textContent = '←';
nextButton.textContent = '→';
// Tối ưu hàm chuyển ảnh
function switchImage(newIndex) {
imageElements[currentIndex].style.display = 'none';
currentIndex = (newIndex + images.length) % images.length;
imageElements[currentIndex].style.display = 'block';
}
prevButton.onclick = () => switchImage(currentIndex - 1);
nextButton.onclick = () => switchImage(currentIndex + 1);
// Thêm phím tắt để điều hướng
document.addEventListener('keydown', function(e) {
if (container.isConnected) { // Chỉ xử lý khi modal đang hiển thị
if (e.key === 'ArrowLeft') {
prevButton.click();
} else if (e.key === 'ArrowRight') {
nextButton.click();
} else if (e.key === 'Escape') {
closeButton.click();
}
}
});
// Cải thiện nút đóng
const closeButton = document.createElement('button');
closeButton.textContent = '×';
closeButton.style.cssText = `
position: absolute;
top: -10px;
right: -10px;
background: #4CAF50;
border: none;
color: white;
width: 20px;
height: 20px;
border-radius: 50%;
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
transition: background-color 0.2s;
`;
closeButton.onmouseover = () => closeButton.style.backgroundColor = '#45a049';
closeButton.onmouseout = () => closeButton.style.backgroundColor = '#4CAF50';
closeButton.onclick = () => {
container.remove();
const showImageButton = document.querySelector('#show_image_button');
if (showImageButton) {
showImageButton.style.color = '';
showImageButton.style.opacity = '0.7';
}
};
container.appendChild(closeButton);
navigationContainer.appendChild(prevButton);
navigationContainer.appendChild(nextButton);
container.appendChild(navigationContainer);
document.body.appendChild(container);
// Tải trước ảnh kế tiếp
images.forEach((url, index) => {
if (index !== currentIndex) {
const preloadImg = new Image();
preloadImg.src = url.replace('.replace(')', '');
}
});
}
function addControlButtons() {
// Kiểm tra cả hai nút để tránh nhân bản
if (document.querySelector('#show_image_button') || document.querySelector('#send_prompt_button')) {
return; // Nếu đã tồn tại nút thì không thêm nữa
}
const extensionsButton = document.querySelector('#extensionsMenuButton');
const optionsButton = document.querySelector('#options_button');
if (extensionsButton && optionsButton) {
// Nút Show Image
const showImageButton = document.createElement('div');
showImageButton.id = 'show_image_button';
showImageButton.className = 'fa-solid fa-eye interactable';
showImageButton.title = 'Show/Hide Images';
showImageButton.style.cssText = `
display: flex;
cursor: pointer;
opacity: 0.7;
margin: 0 5px;
font-size: 18px;
transition: all 0.3s;
`;
showImageButton.tabIndex = "0";
// Nút Send Prompt
const sendPromptButton = document.createElement('div');
sendPromptButton.id = 'send_prompt_button';
sendPromptButton.className = 'fa-solid fa-paper-plane interactable';
sendPromptButton.title = 'Send Prompt';
sendPromptButton.style.cssText = `
display: flex;
cursor: pointer;
opacity: 0.7;
margin: 0 5px;
font-size: 18px;
transition: all 0.3s;
`;
sendPromptButton.tabIndex = "0";
// Logic xử lý sự kiện cho Show Image Button
let isShowingImages = false;
let currentImageIndex = 0;
let images = [];
showImageButton.addEventListener('click', () => {
isShowingImages = !isShowingImages;
showImageButton.style.color = isShowingImages ? 'var(--accent-color, #4CAF50)' : '';
showImageButton.style.opacity = isShowingImages ? '1' : '0.7';
if (isShowingImages) {
const markdownUrls = GM_getValue('sexyai_images', '').split('\n').filter(url => url);
images = markdownUrls;
if (images.length > 0) {
showImageModal(images[currentImageIndex]);
}
} else {
const existingModal = document.querySelector('.image-modal-container');
if (existingModal) {
existingModal.remove();
}
}
});
// Logic xử lý sự kiện cho Send Prompt Button
sendPromptButton.addEventListener('click', () => {
const messages = document.getElementsByClassName('mes');
const lastMessage = messages[messages.length - 1];
if (lastMessage) {
const text = extractOriginalText(lastMessage);
const match = text.match(/image###([^#]+)###/);
if (match) {
const prompt = match[1].trim();
GM_setValue('st_prompt', prompt);
sendPromptButton.style.color = 'var(--accent-color, #4CAF50)';
setTimeout(() => {
sendPromptButton.style.color = '';
}, 2000);
}
}
});
// Chèn các nút vào vị trí phù hợp
optionsButton.parentNode.insertBefore(showImageButton, optionsButton.nextSibling);
extensionsButton.parentNode.insertBefore(sendPromptButton, extensionsButton.nextSibling);
}
}
// Thay đổi cách gọi hàm addControlButtons
let checkInterval = setInterval(() => {
if (document.querySelector('#options_button') && document.querySelector('#extensionsMenuButton')) {
addControlButtons();
// Sau khi thêm nút thành công, clear interval
if (document.querySelector('#show_image_button') && document.querySelector('#send_prompt_button')) {
clearInterval(checkInterval);
}
}
}, 1000);
// Thêm một observer để xử lý trường hợp giao diện bị reload
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.removedNodes.length > 0) {
// Nếu các nút bị xóa, thêm lại chúng
if (!document.querySelector('#show_image_button') || !document.querySelector('#send_prompt_button')) {
addControlButtons();
}
}
}
});
// Bắt đầu quan sát DOM
observer.observe(document.body, {
childList: true,
subtree: true
});
}
})();