Last active
September 22, 2025 21:28
-
-
Save rexxar31/450ea0e10632a0677b4e9189ab89eb3f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Chat Assistant Test | |
| // @namespace test | |
| // @version 1.0.8 | |
| // @description Chat Assistant Front-End Interface | |
| // @match https://agents.moderationinterface.com/* | |
| // @require https://cdn.socket.io/4.7.5/socket.io.min.js | |
| // @updateURL https://gist.github.com/rexxar31/450ea0e10632a0677b4e9189ab89eb3f/raw/251a960052b5c4867944799d05eb97d76582bf87/ChatAssistant_duplicatecheck.user.js | |
| // @grant window.focus | |
| // @grant GM_xmlhttpRequest | |
| // @grant GM_addStyle | |
| // @grant GM_getValue | |
| // @grant GM_setValue | |
| // ==/UserScript== | |
| ;(function () { | |
| 'use strict' | |
| // Constants | |
| const CONFIG = { | |
| ui: { | |
| imageSelector: 'img.img-thumbnail[src*="cache.moderationinterface.com"]', | |
| blurAmount: '20px', | |
| hoverText: 'Hover to reveal', | |
| sessionSelector: | |
| 'span.badge.badge-secondary.float-right.ng-star-inserted', | |
| textareaSelector: '#chat-windows-message-textarea', | |
| }, | |
| server: { | |
| url: 'http://localhost:5000', // Central hub URL | |
| }, | |
| reactivationPhrases: [ | |
| '[please reactivate the user!]', | |
| '[Bitte User reaktivieren!]', | |
| ], | |
| } | |
| // Styles | |
| const STYLES = { | |
| imageBlur: ` | |
| :root { | |
| --blur-amount: ${CONFIG.ui.blurAmount}; | |
| --transition-speed: 0.3s; | |
| --hover-bg: rgba(0, 0, 0, 0.7); | |
| } | |
| .blurred-image { | |
| filter: blur(var(--blur-amount)); | |
| transition: filter var(--transition-speed) ease-in-out; | |
| } | |
| .blurred-image:hover { | |
| filter: blur(0); | |
| } | |
| .image-container { | |
| position: relative; | |
| display: inline-block; | |
| } | |
| .hover-text { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background-color: var(--hover-bg); | |
| color: white; | |
| padding: 5px 10px; | |
| border-radius: 5px; | |
| pointer-events: none; | |
| opacity: 1; | |
| transition: opacity var(--transition-speed) ease-in-out; | |
| } | |
| .image-container:hover .hover-text { | |
| opacity: 0; | |
| }`, | |
| notification: ` | |
| .notification-banner { | |
| position: fixed; | |
| top: 50px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background-color: #4CAF50; | |
| color: white; | |
| padding: 12px 24px; | |
| border-radius: 4px; | |
| z-index: 10000; | |
| box-shadow: 0 2px 5px rgba(0,0,0,0.2); | |
| animation: slideDown 0.5s ease-out, fadeOut 0.5s ease-in 2.5s forwards; | |
| }`, | |
| controls: ` | |
| .control-panel { | |
| position: fixed; | |
| top: 10px; | |
| left: 150px; | |
| z-index: 9999; | |
| display: flex; | |
| gap: 10px; | |
| align-items: center; | |
| background: white; | |
| padding: 10px; | |
| border-radius: 5px; | |
| box-shadow: 0 2px 5px rgba(0,0,0,0.2); | |
| }`, | |
| } | |
| class UIManager { | |
| constructor(chatAssistant) { | |
| this.chatAssistant = chatAssistant | |
| this.browserIdentifier = this.generateBrowserId() | |
| this.initializeStyles() | |
| this.initializeUI() | |
| this.applyImageBlur() | |
| this.observeImageChanges() | |
| } | |
| updateStatus(message) { | |
| this.statusTextbox.value = message | |
| } | |
| generateBrowserId() { | |
| let browserId = GM_getValue('browserId') | |
| if (!browserId) { | |
| browserId = 'browser_' + Math.random().toString(36).substr(2, 9) | |
| GM_setValue('browserId', browserId) | |
| } | |
| return browserId | |
| } | |
| initializeStyles() { | |
| GM_addStyle(STYLES.imageBlur) | |
| GM_addStyle(STYLES.notification) | |
| GM_addStyle(STYLES.controls) | |
| } | |
| initializeUI() { | |
| const controlPanel = document.createElement('div') | |
| controlPanel.className = 'control-panel' | |
| const apiSelect = this.createAPISelect() | |
| const regenerateButton = this.createButton('Regenerate', () => { | |
| this.chatAssistant.handleRegenerateClick() | |
| }) | |
| this.statusTextbox = document.createElement('input') | |
| this.statusTextbox.type = 'text' | |
| this.statusTextbox.className = 'status-textbox' | |
| this.statusTextbox.value = 'Waiting' | |
| this.statusTextbox.readOnly = true | |
| controlPanel.appendChild(apiSelect) | |
| controlPanel.appendChild(regenerateButton) | |
| controlPanel.appendChild(this.statusTextbox) | |
| document.body.appendChild(controlPanel) | |
| } | |
| createAPISelect() { | |
| const select = document.createElement('select') | |
| select.className = 'api-select' | |
| const options = [ | |
| { value: 'or-llama3.3', text: 'OR - Llama 3.3-70b' }, | |
| { value: 'or-llama4-maverick', text: 'OR - Llama 4 Maverick' }, | |
| { value: 'voidai-llama', text: 'Void - Llama 3.3-70B' }, | |
| { value: 'voidai-deepseekv3', text: 'Void - Deepseek V3' }, | |
| { value: 'zanity', text: 'Zanity - L3.3-70b' }, | |
| { value: 'zanity-l3.1-405b', text: 'Zanity - L3.1-401b' }, | |
| { value: 'deepInfra', text: 'DeepInfra' }, | |
| { value: 'klusterai', text: 'KlusterAI - LLama 3.3' }, | |
| ] | |
| options.forEach((opt) => { | |
| const option = document.createElement('option') | |
| option.value = opt.value | |
| option.textContent = opt.text | |
| select.appendChild(option) | |
| }) | |
| select.value = GM_getValue('selectedAPI') | |
| select.addEventListener('change', () => { | |
| GM_setValue('selectedAPI', select.value) | |
| this.showNotification( | |
| `Switched to ${select.options[select.selectedIndex].text} API`, | |
| ) | |
| }) | |
| return select | |
| } | |
| createButton(text, onClick) { | |
| const button = document.createElement('button') | |
| button.textContent = text | |
| button.className = 'control-button' | |
| button.addEventListener('click', onClick) | |
| return button | |
| } | |
| applyImageBlur() { | |
| const images = document.querySelectorAll(CONFIG.ui.imageSelector) | |
| images.forEach((img) => { | |
| if (img.closest('.image-container')) return | |
| const container = document.createElement('div') | |
| container.className = 'image-container' | |
| const hoverText = document.createElement('div') | |
| hoverText.className = 'hover-text' | |
| hoverText.textContent = CONFIG.ui.hoverText | |
| img.classList.add('blurred-image') | |
| img.parentNode.insertBefore(container, img) | |
| container.appendChild(img) | |
| container.appendChild(hoverText) | |
| }) | |
| } | |
| observeImageChanges() { | |
| new MutationObserver(() => this.applyImageBlur()).observe(document.body, { | |
| childList: true, | |
| subtree: true, | |
| }) | |
| } | |
| showNotification(message) { | |
| const notification = document.createElement('div') | |
| notification.className = 'notification-banner' | |
| notification.textContent = message | |
| document.body.appendChild(notification) | |
| setTimeout(() => notification.remove(), 3000) | |
| } | |
| } | |
| class MessageManager { | |
| constructor() { | |
| this.currentSessionId = null | |
| this.agentEnNumber = null | |
| this.lastMessageFingerprint = '' | |
| this.initMessageObserver() | |
| } | |
| getSubmitButton() { | |
| return document.querySelector('button.btn.btn-primary[type="submit"]') | |
| } | |
| getAgentEnNumber() { | |
| if (this.agentEnNumber) return this.agentEnNumber | |
| const enNumberElement = document.querySelector( | |
| '.nav-link .ng-star-inserted', | |
| ) | |
| this.agentEnNumber = | |
| enNumberElement?.textContent?.match(/\(([^)]+)\)/)?.[1] || 'unknown' | |
| return this.agentEnNumber | |
| } | |
| isAgentLikeMessage(panel) { | |
| const extensionIcon = panel.querySelector( | |
| '.timeline-badge.warning .material-icons, .timeline-badge.success .material-icons', | |
| ) | |
| return extensionIcon && extensionIcon.textContent.trim() === 'extension' | |
| } | |
| getMessages() { | |
| const panels = document.querySelectorAll( | |
| 'app-receive-message-item, app-sent-message-item', | |
| ) | |
| const messages = Array.from(panels) | |
| .slice(0, 5) | |
| .reduce((acc, panel) => { | |
| const messageBody = panel.querySelector('.timeline-body p') | |
| const messageText = messageBody?.textContent.trim() ?? '' | |
| // NEW: Skip messages containing "ALERT: Low Balance" | |
| if (messageText.includes('ALERT: Low Balance')) { | |
| return acc | |
| } | |
| const isClientMessage = | |
| panel.tagName.toLowerCase() === 'app-receive-message-item' | |
| const isAgentLikeMessage = this.isAgentLikeMessage(panel) | |
| const sender = isAgentLikeMessage | |
| ? 'agent' | |
| : isClientMessage | |
| ? 'client' | |
| : 'agent' | |
| const isAutomated = messageText.startsWith('Automated text:') | |
| acc.push({ | |
| text: isAutomated ? messageText.slice(15) : messageText, | |
| sender: isAutomated ? 'agent' : sender, | |
| }) | |
| return acc | |
| }, []) | |
| return messages.reverse() | |
| } | |
| getProfileInfo() { | |
| return { | |
| location: | |
| document.getElementById('moderator-city')?.value?.trim() || 'unknown', | |
| name: | |
| document.getElementById('moderator-name')?.value?.trim() || | |
| 'moderator', | |
| age: | |
| document.getElementById('moderator-age')?.value?.trim() || | |
| 'unspecified', | |
| customerAge: | |
| document.getElementById('customer-age')?.value?.trim() || | |
| 'unspecified', | |
| gender: this.detectGender(), | |
| countryCode: this.getCountryCode(), | |
| customerCustom: | |
| document.getElementById('customer-custom')?.value?.trim() || '', | |
| moderatorCustom: | |
| document.getElementById('moderator-custom')?.value?.trim() || '', | |
| customerDescription: | |
| document.getElementById('customer-description')?.value?.trim() || '', | |
| moderatorDescription: | |
| document.getElementById('moderator-description')?.value?.trim() || '', | |
| } | |
| } | |
| detectGender() { | |
| const serviceElement = document.querySelector('.alert.alert-light p') | |
| if (!serviceElement) return 'woman' | |
| const serviceText = serviceElement.textContent.toLowerCase() | |
| if (serviceText.includes('gay')) return 'gay' | |
| if (serviceText.includes('tran')) return 'trans' | |
| return 'woman' | |
| } | |
| initMessageObserver() { | |
| const target = document.body | |
| const observer = new MutationObserver(() => { | |
| const messages = this.getMessages() | |
| const newFingerprint = messages.map((m) => m.text).join('|') | |
| if (newFingerprint !== this.lastMessageFingerprint) { | |
| this.lastMessageFingerprint = newFingerprint | |
| this.chatAssistant.handleMessagesUpdate() | |
| } | |
| }) | |
| observer.observe(target, { childList: true, subtree: true }) | |
| } | |
| getCountryCode() { | |
| const element = document.querySelector('.alert.alert-light p') | |
| if (!element) return null | |
| const text = element.textContent | |
| if (text.includes('USA-EN')) return 'US' | |
| let match = text.match(/([A-Z]{2})-EN/) | |
| if (match) { | |
| const code = match[1] | |
| const lowerText = text.toLowerCase() | |
| switch (code) { | |
| case 'SA': | |
| return lowerText.includes('saudi arabia') ? 'SA' : 'US' | |
| case 'IR': | |
| return 'IE' | |
| default: | |
| return code | |
| } | |
| } | |
| match = text.match(/\[([A-Z]{2})_EN\]/) | |
| if (match) return match[1] | |
| match = text.match(/\b([A-Z]{2})\b/) | |
| if (match) return match[1] | |
| return null | |
| } | |
| } | |
| class ChatAssistant { | |
| constructor() { | |
| this.uiManager = new UIManager(this) | |
| this.messageManager = new MessageManager() | |
| this.currentSessionId = null | |
| this.socket = null | |
| this.initializeSocket() | |
| this.initializeObserver() | |
| } | |
| initializeSocket() { | |
| this.socket = io(CONFIG.server.url, { | |
| query: { | |
| browserId: this.uiManager.browserIdentifier, | |
| }, | |
| reconnection: true, | |
| reconnectionAttempts: Infinity, | |
| reconnectionDelay: 1000, | |
| }) | |
| this.socket.on('connect', () => { | |
| console.log('Connected to WebSocket server') | |
| this.uiManager.showNotification('Connected to Hub') | |
| }) | |
| this.socket.on('session_updated', (data) => { | |
| if ( | |
| data.sessionId === this.currentSessionId && | |
| data.action === 'send' | |
| ) { | |
| this.waitForTextareaAndInsert( | |
| data.response, | |
| this.uiManager.browserIdentifier, | |
| ) | |
| } | |
| }) | |
| this.socket.on('disconnect', () => { | |
| console.log('Disconnected from WebSocket server') | |
| this.uiManager.showNotification('Disconnected from Hub') | |
| }) | |
| } | |
| initializeObserver() { | |
| const observer = new MutationObserver(() => this.checkForNewAssignment()) | |
| observer.observe(document.body, { | |
| childList: true, | |
| subtree: true, | |
| attributes: true, | |
| }) | |
| this.checkForNewAssignment() | |
| } | |
| async checkForNewAssignment() { | |
| const sessionSpan = document.querySelector(CONFIG.ui.sessionSelector) | |
| if (!sessionSpan || sessionSpan.style.display === 'none') { | |
| this.currentSessionId = null | |
| this.uiManager.updateStatus('Waiting') | |
| return | |
| } | |
| const newSessionId = sessionSpan.textContent.split('#')[1]?.trim() | |
| if (newSessionId && newSessionId !== this.currentSessionId) { | |
| this.currentSessionId = newSessionId | |
| document.querySelector(CONFIG.ui.textareaSelector).value = '' | |
| await this.handleNewAssignment() | |
| } | |
| } | |
| async handleNewAssignment() { | |
| this.uiManager.updateStatus('Request Sent') | |
| const messages = this.messageManager.getMessages() | |
| const profileInfo = this.messageManager.getProfileInfo() | |
| const agentEnNumber = this.messageManager.getAgentEnNumber() | |
| const apiType = GM_getValue('selectedAPI') | |
| // Get the service element text | |
| const serviceElement = document.querySelector('.alert.alert-light p') | |
| const serviceText = serviceElement | |
| ? serviceElement.textContent.trim() | |
| : '' | |
| const requestData = { | |
| browserId: this.uiManager.browserIdentifier, | |
| sessionId: this.currentSessionId, | |
| messages, | |
| profileInfo, | |
| selectedAPI: apiType, | |
| agentEnNumber: this.messageManager.agentEnNumber, | |
| serviceText, | |
| } | |
| try { | |
| await this.sendToServer(requestData) | |
| this.uiManager.updateStatus('Waiting for hub...') | |
| } catch (error) { | |
| console.error('Server communication error:', error) | |
| this.uiManager.showNotification('Failed to queue request') | |
| } | |
| } | |
| async sendToServer(data) { | |
| return new Promise((resolve) => { | |
| GM_xmlhttpRequest({ | |
| method: 'POST', | |
| url: CONFIG.server.url + '/submit', | |
| headers: { 'Content-Type': 'application/json' }, | |
| data: JSON.stringify(data), | |
| onload: () => resolve(true), | |
| onerror: () => resolve(false), | |
| }) | |
| }) | |
| } | |
| async sendUpdateToServer(data) { | |
| return new Promise((resolve, reject) => { | |
| GM_xmlhttpRequest({ | |
| method: 'POST', | |
| url: CONFIG.server.url + '/update', | |
| headers: { 'Content-Type': 'application/json' }, | |
| data: JSON.stringify(data), | |
| onload: () => resolve(true), | |
| onerror: () => reject(false), | |
| }) | |
| }) | |
| } | |
| async handleRegenerateClick() { | |
| this.uiManager.updateStatus('Request Sent') | |
| const messages = this.messageManager.getMessages() | |
| const profileInfo = this.messageManager.getProfileInfo() | |
| const serviceElement = document.querySelector('.alert.alert-light p') | |
| const serviceText = serviceElement | |
| ? serviceElement.textContent.trim() | |
| : '' | |
| const agentEnNumber = this.messageManager.getAgentEnNumber() | |
| const requestData = { | |
| browserId: this.uiManager.browserIdentifier, | |
| sessionId: this.currentSessionId, | |
| messages, | |
| profileInfo, | |
| selectedAPI: GM_getValue('selectedAPI'), | |
| agentEnNumber: agentEnNumber, | |
| serviceText: serviceText, | |
| } | |
| try { | |
| await this.sendUpdateToServer(requestData) | |
| this.uiManager.updateStatus('Regeneration queued') | |
| } catch (error) { | |
| this.uiManager.showNotification('Failed to queue regeneration') | |
| } | |
| } | |
| waitForTextareaAndInsert(reply, browserId) { | |
| const maxAttempts = 100 | |
| let attempts = 0 | |
| const checkInterval = setInterval(() => { | |
| const textarea = document.querySelector(CONFIG.ui.textareaSelector) | |
| if (textarea) { | |
| clearInterval(checkInterval) | |
| textarea.value = reply + ' ' | |
| textarea.dispatchEvent(new Event('input', { bubbles: true })) | |
| textarea.setSelectionRange( | |
| textarea.value.length, | |
| textarea.value.length, | |
| ) | |
| textarea.dispatchEvent( | |
| new KeyboardEvent('keyup', { bubbles: true, key: ' ' }), | |
| ) | |
| textarea.focus() | |
| // Add 1-second delay before checking for duplicates and button state | |
| setTimeout(() => { | |
| const duplicateAlert = document.querySelector( | |
| '.alert.alert-danger.ng-star-inserted', | |
| ) | |
| if (duplicateAlert) { | |
| this.uiManager.showNotification( | |
| 'Duplicate detected! Please modify message and submit manually', | |
| ) | |
| return | |
| } | |
| // Start checking for enabled button state | |
| let buttonCheckAttempts = 0 | |
| const maxButtonChecks = 20 // 5 seconds total (250ms * 20) | |
| const checkButtonState = () => { | |
| const submitButton = this.messageManager.getSubmitButton() | |
| if (submitButton && !submitButton.disabled) { | |
| submitButton.click() | |
| this.uiManager.showNotification('Auto-submitted response!') | |
| return | |
| } | |
| if (buttonCheckAttempts >= maxButtonChecks) { | |
| this.uiManager.showNotification( | |
| 'Submit button not enabled after 5s - please submit manually', | |
| ) | |
| return | |
| } | |
| buttonCheckAttempts++ | |
| setTimeout(checkButtonState, 250) | |
| } | |
| checkButtonState() | |
| }, 1000) | |
| } else if (attempts >= maxAttempts) { | |
| clearInterval(checkInterval) | |
| this.uiManager.showNotification('Failed to find textarea') | |
| } | |
| attempts++ | |
| }, 100) | |
| } | |
| handleMessagesUpdate() { | |
| if (!this.currentSessionId) return | |
| const messages = this.messageManager.getMessages() | |
| const profileInfo = this.messageManager.getProfileInfo() | |
| const serviceElement = document.querySelector('.alert.alert-light p') | |
| const serviceText = serviceElement | |
| ? serviceElement.textContent.trim() | |
| : '' | |
| const requestData = { | |
| browserId: this.uiManager.browserIdentifier, | |
| sessionId: this.currentSessionId, | |
| messages, | |
| profileInfo, | |
| selectedAPI: GM_getValue('selectedAPI'), | |
| agentEnNumber: this.messageManager.getAgentEnNumber(), | |
| serviceText, | |
| } | |
| this.sendUpdateToServer(requestData) | |
| .then(() => | |
| this.uiManager.showNotification('Session updated with new messages'), | |
| ) | |
| .catch((error) => console.error('Update error:', error)) | |
| } | |
| } | |
| const chatAssistant = new ChatAssistant() | |
| window.chatAssistant = chatAssistant | |
| })() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment