Initial commit
This commit is contained in:
commit
c778b61eb1
13 changed files with 4025 additions and 0 deletions
17
LICENSE
Normal file
17
LICENSE
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
General Bots Chrome Extension
|
||||||
|
Copyright (C) 2025 pragmatismo.com.br
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
50
README.md
Normal file
50
README.md
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# General Bots Chrome Extension
|
||||||
|
|
||||||
|
A professional-grade Chrome extension developed by [pragmatismo.com.br](https://pragmatismo.com.br) that enhances WhatsApp Web with server-side message processing capabilities and UI improvements.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Message Interception: Captures messages before they're sent
|
||||||
|
- Server Processing: Sends message content to your server for processing
|
||||||
|
- Message Replacement: Updates the message with processed content before sending
|
||||||
|
- UI Enhancement: Option to hide the contact list for more chat space
|
||||||
|
- User-friendly Settings: Simple configuration through the extension popup
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Developer Mode Installation
|
||||||
|
|
||||||
|
1. Clone or download this repository
|
||||||
|
2. Open Chrome and navigate to `chrome://extensions/`
|
||||||
|
3. Enable "Developer mode" in the top-right corner
|
||||||
|
4. Click "Load unpacked" and select the extension directory
|
||||||
|
|
||||||
|
### Chrome Web Store Installation
|
||||||
|
|
||||||
|
(Coming soon)
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
1. Click the General Bots icon in your Chrome toolbar
|
||||||
|
2. Enter your processing server URL
|
||||||
|
3. Toggle message processing on/off
|
||||||
|
4. Toggle contact list visibility
|
||||||
|
|
||||||
|
## Server API Requirements
|
||||||
|
|
||||||
|
Your server endpoint should:
|
||||||
|
|
||||||
|
1. Accept POST requests with JSON payload: `{ "text": "message content", "timestamp": 1621234567890 }`
|
||||||
|
2. Return JSON response: `{ "processedText": "updated message content" }`
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the [GNU Affero General Public License](LICENSE) - see the LICENSE file for details.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
For support or questions, please contact [pragmatismo.com.br](https://pragmatismo.com.br).
|
||||||
461
background.js
Normal file
461
background.js
Normal file
|
|
@ -0,0 +1,461 @@
|
||||||
|
// General Bots - Background Service Worker
|
||||||
|
// Handles authentication, LLM communication, and message processing
|
||||||
|
|
||||||
|
// Default configuration
|
||||||
|
const DEFAULT_CONFIG = {
|
||||||
|
serverUrl: "https://api.generalbots.com",
|
||||||
|
gbServerUrl: "https://api.pragmatismo.com.br",
|
||||||
|
enableProcessing: true,
|
||||||
|
hideContacts: false,
|
||||||
|
autoMode: false,
|
||||||
|
grammarCorrection: true,
|
||||||
|
whatsappNumber: "",
|
||||||
|
authToken: "",
|
||||||
|
instanceId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize extension on install
|
||||||
|
chrome.runtime.onInstalled.addListener(async (details) => {
|
||||||
|
console.log("General Bots: Extension installed/updated", details.reason);
|
||||||
|
|
||||||
|
// Set default settings on installation
|
||||||
|
const existing = await chrome.storage.sync.get(DEFAULT_CONFIG);
|
||||||
|
await chrome.storage.sync.set({ ...DEFAULT_CONFIG, ...existing });
|
||||||
|
|
||||||
|
// Create context menu items
|
||||||
|
chrome.contextMenus?.create({
|
||||||
|
id: "gb-correct-grammar",
|
||||||
|
title: "Correct Grammar with AI",
|
||||||
|
contexts: ["selection"],
|
||||||
|
});
|
||||||
|
|
||||||
|
chrome.contextMenus?.create({
|
||||||
|
id: "gb-translate",
|
||||||
|
title: "Translate with AI",
|
||||||
|
contexts: ["selection"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for tab updates
|
||||||
|
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||||
|
if (
|
||||||
|
changeInfo.status === "complete" &&
|
||||||
|
tab.url?.includes("web.whatsapp.com")
|
||||||
|
) {
|
||||||
|
console.log("General Bots: WhatsApp Web detected, initializing...");
|
||||||
|
|
||||||
|
// Notify content script that tab is ready
|
||||||
|
chrome.tabs.sendMessage(tabId, { action: "tabReady" }).catch(() => {
|
||||||
|
// Content script may not be loaded yet, that's okay
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for auto-authentication
|
||||||
|
checkAutoAuth(tabId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle messages from content script and popup
|
||||||
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
|
console.log("General Bots: Received message", message.action);
|
||||||
|
|
||||||
|
switch (message.action) {
|
||||||
|
case "processText":
|
||||||
|
handleProcessText(message.text, message.options)
|
||||||
|
.then(sendResponse)
|
||||||
|
.catch((err) => sendResponse({ error: err.message }));
|
||||||
|
return true; // Will respond asynchronously
|
||||||
|
|
||||||
|
case "correctGrammar":
|
||||||
|
handleGrammarCorrection(message.text)
|
||||||
|
.then(sendResponse)
|
||||||
|
.catch((err) => sendResponse({ error: err.message }));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "authenticate":
|
||||||
|
handleAuthentication(message.whatsappNumber)
|
||||||
|
.then(sendResponse)
|
||||||
|
.catch((err) => sendResponse({ error: err.message }));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "getAuthStatus":
|
||||||
|
getAuthStatus()
|
||||||
|
.then(sendResponse)
|
||||||
|
.catch((err) => sendResponse({ error: err.message }));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "generateAutoReply":
|
||||||
|
handleAutoReply(message.context, message.lastMessages)
|
||||||
|
.then(sendResponse)
|
||||||
|
.catch((err) => sendResponse({ error: err.message }));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "getSettings":
|
||||||
|
chrome.storage.sync.get(DEFAULT_CONFIG).then(sendResponse);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "saveSettings":
|
||||||
|
chrome.storage.sync.set(message.settings).then(() => {
|
||||||
|
broadcastSettingsUpdate(message.settings);
|
||||||
|
sendResponse({ success: true });
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "showNotification":
|
||||||
|
showNotification(message.title, message.message, message.type);
|
||||||
|
sendResponse({ success: true });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Context menu click handler
|
||||||
|
chrome.contextMenus?.onClicked.addListener(async (info, tab) => {
|
||||||
|
if (!info.selectionText) return;
|
||||||
|
|
||||||
|
switch (info.menuItemId) {
|
||||||
|
case "gb-correct-grammar":
|
||||||
|
const corrected = await handleGrammarCorrection(info.selectionText);
|
||||||
|
if (corrected.processedText && tab?.id) {
|
||||||
|
chrome.tabs.sendMessage(tab.id, {
|
||||||
|
action: "replaceSelection",
|
||||||
|
text: corrected.processedText,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "gb-translate":
|
||||||
|
// Could implement translation here
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process text through LLM
|
||||||
|
async function handleProcessText(text, options = {}) {
|
||||||
|
const settings = await chrome.storage.sync.get(DEFAULT_CONFIG);
|
||||||
|
|
||||||
|
if (!settings.enableProcessing) {
|
||||||
|
return { processedText: text, changed: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${settings.serverUrl}/api/v1/llm/process`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${settings.authToken}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
text,
|
||||||
|
instanceId: settings.instanceId,
|
||||||
|
options: {
|
||||||
|
grammarCorrection: settings.grammarCorrection,
|
||||||
|
...options,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Server error: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return {
|
||||||
|
processedText: data.processedText || text,
|
||||||
|
changed: data.processedText !== text,
|
||||||
|
corrections: data.corrections || [],
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("General Bots: Process text error", error);
|
||||||
|
// Fallback - return original text
|
||||||
|
return { processedText: text, changed: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grammar correction specifically
|
||||||
|
async function handleGrammarCorrection(text) {
|
||||||
|
const settings = await chrome.storage.sync.get(DEFAULT_CONFIG);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${settings.serverUrl}/api/v1/llm/grammar`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${settings.authToken}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
text,
|
||||||
|
instanceId: settings.instanceId,
|
||||||
|
language: "auto",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Server error: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return {
|
||||||
|
processedText: data.correctedText || text,
|
||||||
|
original: text,
|
||||||
|
corrections: data.corrections || [],
|
||||||
|
language: data.detectedLanguage,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("General Bots: Grammar correction error", error);
|
||||||
|
return { processedText: text, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-reply generation
|
||||||
|
async function handleAutoReply(context, lastMessages = []) {
|
||||||
|
const settings = await chrome.storage.sync.get(DEFAULT_CONFIG);
|
||||||
|
|
||||||
|
if (!settings.autoMode) {
|
||||||
|
return { reply: null, autoModeDisabled: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${settings.serverUrl}/api/v1/llm/auto-reply`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${settings.authToken}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
context,
|
||||||
|
lastMessages,
|
||||||
|
instanceId: settings.instanceId,
|
||||||
|
whatsappNumber: settings.whatsappNumber,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Server error: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return {
|
||||||
|
reply: data.suggestedReply,
|
||||||
|
confidence: data.confidence,
|
||||||
|
autoSend: data.autoSend && settings.autoMode,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("General Bots: Auto-reply error", error);
|
||||||
|
return { reply: null, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authentication with General Bots via WhatsApp
|
||||||
|
async function handleAuthentication(whatsappNumber) {
|
||||||
|
const settings = await chrome.storage.sync.get(DEFAULT_CONFIG);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Request authentication via General Bots WhatsApp bot
|
||||||
|
const response = await fetch(
|
||||||
|
`${settings.gbServerUrl}/api/v1/auth/whatsapp/request`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
whatsappNumber,
|
||||||
|
extensionId: chrome.runtime.id,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Authentication request failed: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Save pending auth state
|
||||||
|
await chrome.storage.sync.set({
|
||||||
|
whatsappNumber,
|
||||||
|
authPending: true,
|
||||||
|
authRequestId: data.requestId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show notification to user
|
||||||
|
showNotification(
|
||||||
|
"Authentication Requested",
|
||||||
|
"Check your WhatsApp for a message from General Bots to complete authentication.",
|
||||||
|
"info",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start polling for auth completion
|
||||||
|
pollAuthCompletion(data.requestId);
|
||||||
|
|
||||||
|
return { success: true, requestId: data.requestId };
|
||||||
|
} catch (error) {
|
||||||
|
console.error("General Bots: Authentication error", error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll for authentication completion
|
||||||
|
async function pollAuthCompletion(requestId, attempts = 0) {
|
||||||
|
if (attempts > 60) {
|
||||||
|
// 5 minutes max
|
||||||
|
await chrome.storage.sync.set({ authPending: false });
|
||||||
|
showNotification("Authentication Timeout", "Please try again.", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = await chrome.storage.sync.get(DEFAULT_CONFIG);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${settings.gbServerUrl}/api/v1/auth/whatsapp/status/${requestId}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.status === "completed") {
|
||||||
|
await chrome.storage.sync.set({
|
||||||
|
authToken: data.token,
|
||||||
|
instanceId: data.instanceId,
|
||||||
|
authPending: false,
|
||||||
|
authenticated: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
showNotification(
|
||||||
|
"Authentication Complete",
|
||||||
|
"You are now connected to General Bots!",
|
||||||
|
"success",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Broadcast to all tabs
|
||||||
|
broadcastSettingsUpdate({ authenticated: true });
|
||||||
|
return;
|
||||||
|
} else if (data.status === "failed") {
|
||||||
|
await chrome.storage.sync.set({ authPending: false });
|
||||||
|
showNotification(
|
||||||
|
"Authentication Failed",
|
||||||
|
data.message || "Please try again.",
|
||||||
|
"error",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("General Bots: Poll auth error", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue polling
|
||||||
|
setTimeout(() => pollAuthCompletion(requestId, attempts + 1), 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check auth status
|
||||||
|
async function getAuthStatus() {
|
||||||
|
const settings = await chrome.storage.sync.get([
|
||||||
|
"authToken",
|
||||||
|
"authenticated",
|
||||||
|
"whatsappNumber",
|
||||||
|
"instanceId",
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!settings.authToken) {
|
||||||
|
return { authenticated: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token is still valid
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${DEFAULT_CONFIG.gbServerUrl}/api/v1/auth/verify`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${settings.authToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return {
|
||||||
|
authenticated: true,
|
||||||
|
whatsappNumber: settings.whatsappNumber,
|
||||||
|
instanceId: settings.instanceId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("General Bots: Verify auth error", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token invalid, clear auth
|
||||||
|
await chrome.storage.sync.set({
|
||||||
|
authToken: "",
|
||||||
|
authenticated: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { authenticated: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for auto-authentication when WhatsApp loads
|
||||||
|
async function checkAutoAuth(tabId) {
|
||||||
|
const settings = await chrome.storage.sync.get([
|
||||||
|
"authenticated",
|
||||||
|
"autoMode",
|
||||||
|
"whatsappNumber",
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (settings.authenticated && settings.autoMode) {
|
||||||
|
// Notify content script that auto mode is active
|
||||||
|
setTimeout(() => {
|
||||||
|
chrome.tabs
|
||||||
|
.sendMessage(tabId, {
|
||||||
|
action: "enableAutoMode",
|
||||||
|
whatsappNumber: settings.whatsappNumber,
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast settings update to all WhatsApp tabs
|
||||||
|
async function broadcastSettingsUpdate(settings) {
|
||||||
|
const tabs = await chrome.tabs.query({ url: "https://web.whatsapp.com/*" });
|
||||||
|
|
||||||
|
for (const tab of tabs) {
|
||||||
|
chrome.tabs
|
||||||
|
.sendMessage(tab.id, {
|
||||||
|
action: "settingsUpdated",
|
||||||
|
settings,
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show browser notification
|
||||||
|
function showNotification(title, message, type = "info") {
|
||||||
|
const iconPath = type === "error" ? "icons/icon48.png" : "icons/icon48.png";
|
||||||
|
|
||||||
|
chrome.notifications?.create({
|
||||||
|
type: "basic",
|
||||||
|
iconUrl: iconPath,
|
||||||
|
title: `General Bots - ${title}`,
|
||||||
|
message: message,
|
||||||
|
priority: type === "error" ? 2 : 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alarm for periodic tasks
|
||||||
|
chrome.alarms?.create("checkAuth", { periodInMinutes: 30 });
|
||||||
|
|
||||||
|
chrome.alarms?.onAlarm.addListener(async (alarm) => {
|
||||||
|
if (alarm.name === "checkAuth") {
|
||||||
|
const status = await getAuthStatus();
|
||||||
|
if (!status.authenticated) {
|
||||||
|
console.log("General Bots: Auth token expired or invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("General Bots: Background service worker initialized");
|
||||||
719
content.js
Normal file
719
content.js
Normal file
|
|
@ -0,0 +1,719 @@
|
||||||
|
// General Bots - Content Script for WhatsApp Web
|
||||||
|
// Provides grammar correction, auto-mode, contact hiding, and LLM integration
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Global settings
|
||||||
|
let settings = {
|
||||||
|
serverUrl: "https://api.generalbots.com",
|
||||||
|
enableProcessing: true,
|
||||||
|
hideContacts: false,
|
||||||
|
autoMode: false,
|
||||||
|
grammarCorrection: true,
|
||||||
|
whatsappNumber: "",
|
||||||
|
authenticated: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// State management
|
||||||
|
const state = {
|
||||||
|
initialized: false,
|
||||||
|
currentContact: null,
|
||||||
|
autoModeContacts: new Set(),
|
||||||
|
originalMessages: new Map(),
|
||||||
|
processingQueue: [],
|
||||||
|
isProcessing: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Selectors for WhatsApp Web elements
|
||||||
|
const SELECTORS = {
|
||||||
|
contactList: "#pane-side",
|
||||||
|
chatContainer: ".copyable-area",
|
||||||
|
messageInput: 'div[contenteditable="true"][data-tab="10"]',
|
||||||
|
sendButton: 'button[data-tab="11"]',
|
||||||
|
messageOut: ".message-out",
|
||||||
|
messageIn: ".message-in",
|
||||||
|
messageText: ".selectable-text",
|
||||||
|
contactName: "header ._amig span",
|
||||||
|
chatHeader: "header._amid",
|
||||||
|
conversationPanel: "#main",
|
||||||
|
searchBox: 'div[data-tab="3"]',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize the extension
|
||||||
|
async function init() {
|
||||||
|
if (state.initialized) return;
|
||||||
|
|
||||||
|
console.log("General Bots: Initializing content script...");
|
||||||
|
|
||||||
|
// Load settings from storage
|
||||||
|
await loadSettings();
|
||||||
|
|
||||||
|
// Apply initial UI modifications
|
||||||
|
applyUIModifications();
|
||||||
|
|
||||||
|
// Setup observers and listeners
|
||||||
|
setupInputListener();
|
||||||
|
setupMessageObserver();
|
||||||
|
setupContactObserver();
|
||||||
|
|
||||||
|
// Inject custom UI elements
|
||||||
|
injectControlPanel();
|
||||||
|
|
||||||
|
state.initialized = true;
|
||||||
|
console.log("General Bots: Content script initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load settings from chrome storage
|
||||||
|
async function loadSettings() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
chrome.storage.sync.get(settings, (items) => {
|
||||||
|
settings = { ...settings, ...items };
|
||||||
|
console.log("General Bots: Settings loaded", settings);
|
||||||
|
resolve(settings);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply UI modifications based on settings
|
||||||
|
function applyUIModifications() {
|
||||||
|
applyContactVisibility();
|
||||||
|
applyAutoModeIndicator();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide/show contact list
|
||||||
|
function applyContactVisibility() {
|
||||||
|
const contactList = document.querySelector(SELECTORS.contactList);
|
||||||
|
if (contactList) {
|
||||||
|
const parent = contactList.parentElement;
|
||||||
|
if (settings.hideContacts) {
|
||||||
|
parent.classList.add("gb-hide-contacts");
|
||||||
|
parent.style.cssText =
|
||||||
|
"width: 0 !important; min-width: 0 !important; overflow: hidden !important;";
|
||||||
|
} else {
|
||||||
|
parent.classList.remove("gb-hide-contacts");
|
||||||
|
parent.style.cssText = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show auto-mode indicator
|
||||||
|
function applyAutoModeIndicator() {
|
||||||
|
let indicator = document.getElementById("gb-auto-mode-indicator");
|
||||||
|
|
||||||
|
if (settings.autoMode) {
|
||||||
|
if (!indicator) {
|
||||||
|
indicator = document.createElement("div");
|
||||||
|
indicator.id = "gb-auto-mode-indicator";
|
||||||
|
indicator.className = "gb-auto-indicator";
|
||||||
|
indicator.innerHTML = `
|
||||||
|
<span class="gb-auto-dot"></span>
|
||||||
|
<span>Auto Mode Active</span>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(indicator);
|
||||||
|
}
|
||||||
|
indicator.style.display = "flex";
|
||||||
|
} else if (indicator) {
|
||||||
|
indicator.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup input field listener for message processing
|
||||||
|
function setupInputListener() {
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
const inputField = document.querySelector(SELECTORS.messageInput);
|
||||||
|
if (inputField && !inputField.getAttribute("gb-monitored")) {
|
||||||
|
setupFieldMonitoring(inputField);
|
||||||
|
inputField.setAttribute("gb-monitored", "true");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check immediately
|
||||||
|
const inputField = document.querySelector(SELECTORS.messageInput);
|
||||||
|
if (inputField && !inputField.getAttribute("gb-monitored")) {
|
||||||
|
setupFieldMonitoring(inputField);
|
||||||
|
inputField.setAttribute("gb-monitored", "true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monitor input field for messages
|
||||||
|
function setupFieldMonitoring(inputField) {
|
||||||
|
console.log("General Bots: Setting up input field monitoring");
|
||||||
|
|
||||||
|
// Listen for keydown events
|
||||||
|
inputField.addEventListener("keydown", async (event) => {
|
||||||
|
if (event.key === "Enter" && !event.shiftKey) {
|
||||||
|
const originalText = inputField.textContent.trim();
|
||||||
|
|
||||||
|
if (originalText.length > 0 && settings.enableProcessing) {
|
||||||
|
if (settings.grammarCorrection) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
try {
|
||||||
|
showProcessingIndicator(inputField);
|
||||||
|
const result = await processMessageWithLLM(originalText);
|
||||||
|
|
||||||
|
if (
|
||||||
|
result.processedText &&
|
||||||
|
result.processedText !== originalText
|
||||||
|
) {
|
||||||
|
// Show correction preview
|
||||||
|
const shouldSend = await showCorrectionPreview(
|
||||||
|
originalText,
|
||||||
|
result.processedText,
|
||||||
|
inputField,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shouldSend) {
|
||||||
|
setInputText(inputField, result.processedText);
|
||||||
|
// Store original for reference
|
||||||
|
state.originalMessages.set(Date.now(), {
|
||||||
|
original: originalText,
|
||||||
|
corrected: result.processedText,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideProcessingIndicator();
|
||||||
|
// Send the message
|
||||||
|
simulateEnterPress(inputField);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("General Bots: Error processing message", error);
|
||||||
|
hideProcessingIndicator();
|
||||||
|
simulateEnterPress(inputField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add visual indicator that field is being monitored
|
||||||
|
inputField.classList.add("gb-monitored-input");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process message through LLM for grammar correction
|
||||||
|
async function processMessageWithLLM(text) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
chrome.runtime.sendMessage(
|
||||||
|
{
|
||||||
|
action: "correctGrammar",
|
||||||
|
text: text,
|
||||||
|
},
|
||||||
|
(response) => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
console.error(
|
||||||
|
"General Bots: Runtime error",
|
||||||
|
chrome.runtime.lastError,
|
||||||
|
);
|
||||||
|
resolve({ processedText: text });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(response || { processedText: text });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show correction preview modal
|
||||||
|
async function showCorrectionPreview(original, corrected, inputField) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// If texts are very similar, auto-accept
|
||||||
|
if (levenshteinDistance(original, corrected) < 3) {
|
||||||
|
resolve(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = document.createElement("div");
|
||||||
|
modal.className = "gb-correction-modal";
|
||||||
|
modal.innerHTML = `
|
||||||
|
<div class="gb-correction-content">
|
||||||
|
<div class="gb-correction-header">
|
||||||
|
<span class="gb-correction-icon">✨</span>
|
||||||
|
<span>Grammar Correction</span>
|
||||||
|
</div>
|
||||||
|
<div class="gb-correction-body">
|
||||||
|
<div class="gb-text-compare">
|
||||||
|
<div class="gb-original">
|
||||||
|
<label>Original:</label>
|
||||||
|
<p>${escapeHtml(original)}</p>
|
||||||
|
</div>
|
||||||
|
<div class="gb-corrected">
|
||||||
|
<label>Corrected:</label>
|
||||||
|
<p>${escapeHtml(corrected)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="gb-correction-actions">
|
||||||
|
<button class="gb-btn gb-btn-secondary" id="gb-reject">Keep Original</button>
|
||||||
|
<button class="gb-btn gb-btn-primary" id="gb-accept">Use Corrected</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
|
||||||
|
// Auto-close after 5 seconds with corrected text
|
||||||
|
const autoClose = setTimeout(() => {
|
||||||
|
modal.remove();
|
||||||
|
resolve(true);
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
document.getElementById("gb-accept").addEventListener("click", () => {
|
||||||
|
clearTimeout(autoClose);
|
||||||
|
modal.remove();
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("gb-reject").addEventListener("click", () => {
|
||||||
|
clearTimeout(autoClose);
|
||||||
|
modal.remove();
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup observer for incoming messages (for auto-reply)
|
||||||
|
function setupMessageObserver() {
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
if (!settings.autoMode) return;
|
||||||
|
|
||||||
|
for (const mutation of mutations) {
|
||||||
|
for (const node of mutation.addedNodes) {
|
||||||
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||||
|
const incomingMsg = node.querySelector
|
||||||
|
? node.querySelector(SELECTORS.messageIn)
|
||||||
|
: null;
|
||||||
|
if (
|
||||||
|
incomingMsg ||
|
||||||
|
(node.classList && node.classList.contains("message-in"))
|
||||||
|
) {
|
||||||
|
handleIncomingMessage(incomingMsg || node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start observing when chat container is available
|
||||||
|
const waitForChat = setInterval(() => {
|
||||||
|
const chatContainer = document.querySelector(SELECTORS.chatContainer);
|
||||||
|
if (chatContainer) {
|
||||||
|
clearInterval(waitForChat);
|
||||||
|
observer.observe(chatContainer, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
console.log("General Bots: Message observer started");
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle incoming message for auto-reply
|
||||||
|
async function handleIncomingMessage(messageElement) {
|
||||||
|
if (!settings.autoMode || !settings.authenticated) return;
|
||||||
|
|
||||||
|
const currentContact = getCurrentContactName();
|
||||||
|
if (!currentContact) return;
|
||||||
|
|
||||||
|
// Check if auto-mode is enabled for this contact
|
||||||
|
if (!state.autoModeContacts.has(currentContact)) return;
|
||||||
|
|
||||||
|
const messageText = messageElement.querySelector(SELECTORS.messageText);
|
||||||
|
if (!messageText) return;
|
||||||
|
|
||||||
|
const text = messageText.textContent.trim();
|
||||||
|
if (!text) return;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"General Bots: Processing incoming message for auto-reply",
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get conversation context
|
||||||
|
const context = getConversationContext();
|
||||||
|
|
||||||
|
// Request auto-reply from LLM
|
||||||
|
chrome.runtime.sendMessage(
|
||||||
|
{
|
||||||
|
action: "generateAutoReply",
|
||||||
|
context: {
|
||||||
|
contact: currentContact,
|
||||||
|
lastMessage: text,
|
||||||
|
},
|
||||||
|
lastMessages: context,
|
||||||
|
},
|
||||||
|
async (response) => {
|
||||||
|
if (response && response.reply && response.autoSend) {
|
||||||
|
await sendAutoReply(response.reply);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get conversation context (last few messages)
|
||||||
|
function getConversationContext() {
|
||||||
|
const messages = [];
|
||||||
|
const messageElements = document.querySelectorAll(
|
||||||
|
`${SELECTORS.messageIn}, ${SELECTORS.messageOut}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const recentMessages = Array.from(messageElements).slice(-10);
|
||||||
|
|
||||||
|
for (const msg of recentMessages) {
|
||||||
|
const textEl = msg.querySelector(SELECTORS.messageText);
|
||||||
|
if (textEl) {
|
||||||
|
messages.push({
|
||||||
|
type: msg.classList.contains("message-out") ? "sent" : "received",
|
||||||
|
text: textEl.textContent.trim(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send auto-reply
|
||||||
|
async function sendAutoReply(text) {
|
||||||
|
const inputField = document.querySelector(SELECTORS.messageInput);
|
||||||
|
if (!inputField) return;
|
||||||
|
|
||||||
|
setInputText(inputField, text);
|
||||||
|
|
||||||
|
// Small delay before sending
|
||||||
|
await new Promise((r) => setTimeout(r, 500));
|
||||||
|
|
||||||
|
simulateEnterPress(inputField);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup contact observer for auto-mode toggle per contact
|
||||||
|
function setupContactObserver() {
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
const header = document.querySelector(SELECTORS.chatHeader);
|
||||||
|
if (header && !header.querySelector(".gb-contact-controls")) {
|
||||||
|
injectContactControls(header);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject control buttons for current contact
|
||||||
|
function injectContactControls(header) {
|
||||||
|
const contactName = getCurrentContactName();
|
||||||
|
if (!contactName) return;
|
||||||
|
|
||||||
|
const controls = document.createElement("div");
|
||||||
|
controls.className = "gb-contact-controls";
|
||||||
|
|
||||||
|
const isAutoEnabled = state.autoModeContacts.has(contactName);
|
||||||
|
|
||||||
|
controls.innerHTML = `
|
||||||
|
<button class="gb-contact-btn ${isAutoEnabled ? "active" : ""}"
|
||||||
|
id="gb-toggle-auto"
|
||||||
|
title="Toggle Auto Mode for this contact">
|
||||||
|
<span class="gb-icon">🤖</span>
|
||||||
|
<span class="gb-label">${isAutoEnabled ? "Auto ON" : "Auto OFF"}</span>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
header.appendChild(controls);
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("gb-toggle-auto")
|
||||||
|
.addEventListener("click", function () {
|
||||||
|
if (state.autoModeContacts.has(contactName)) {
|
||||||
|
state.autoModeContacts.delete(contactName);
|
||||||
|
this.classList.remove("active");
|
||||||
|
this.querySelector(".gb-label").textContent = "Auto OFF";
|
||||||
|
} else {
|
||||||
|
state.autoModeContacts.add(contactName);
|
||||||
|
this.classList.add("active");
|
||||||
|
this.querySelector(".gb-label").textContent = "Auto ON";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject main control panel
|
||||||
|
function injectControlPanel() {
|
||||||
|
const panel = document.createElement("div");
|
||||||
|
panel.id = "gb-control-panel";
|
||||||
|
panel.className = "gb-panel";
|
||||||
|
panel.innerHTML = `
|
||||||
|
<div class="gb-panel-header">
|
||||||
|
<img src="${chrome.runtime.getURL("icons/icon48.png")}" alt="GB" class="gb-panel-logo">
|
||||||
|
<span>General Bots</span>
|
||||||
|
<button class="gb-panel-toggle" id="gb-panel-toggle">−</button>
|
||||||
|
</div>
|
||||||
|
<div class="gb-panel-body" id="gb-panel-body">
|
||||||
|
<div class="gb-status ${settings.authenticated ? "connected" : "disconnected"}">
|
||||||
|
<span class="gb-status-dot"></span>
|
||||||
|
<span>${settings.authenticated ? "Connected" : "Not Connected"}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gb-controls">
|
||||||
|
<label class="gb-switch-label">
|
||||||
|
<span>Grammar Correction</span>
|
||||||
|
<input type="checkbox" id="gb-grammar-toggle" ${settings.grammarCorrection ? "checked" : ""}>
|
||||||
|
<span class="gb-switch"></span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="gb-switch-label">
|
||||||
|
<span>Hide Contacts</span>
|
||||||
|
<input type="checkbox" id="gb-contacts-toggle" ${settings.hideContacts ? "checked" : ""}>
|
||||||
|
<span class="gb-switch"></span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="gb-switch-label">
|
||||||
|
<span>Auto Mode</span>
|
||||||
|
<input type="checkbox" id="gb-auto-toggle" ${settings.autoMode ? "checked" : ""}>
|
||||||
|
<span class="gb-switch"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${
|
||||||
|
!settings.authenticated
|
||||||
|
? `
|
||||||
|
<div class="gb-auth-section">
|
||||||
|
<p>Connect with your General Bots account:</p>
|
||||||
|
<input type="text" id="gb-whatsapp-number" placeholder="Your WhatsApp number" value="${settings.whatsappNumber}">
|
||||||
|
<button class="gb-btn gb-btn-primary" id="gb-auth-btn">Authenticate</button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(panel);
|
||||||
|
|
||||||
|
// Setup panel event listeners
|
||||||
|
setupPanelListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup control panel event listeners
|
||||||
|
function setupPanelListeners() {
|
||||||
|
// Panel toggle
|
||||||
|
document
|
||||||
|
.getElementById("gb-panel-toggle")
|
||||||
|
?.addEventListener("click", function () {
|
||||||
|
const body = document.getElementById("gb-panel-body");
|
||||||
|
if (body.style.display === "none") {
|
||||||
|
body.style.display = "block";
|
||||||
|
this.textContent = "−";
|
||||||
|
} else {
|
||||||
|
body.style.display = "none";
|
||||||
|
this.textContent = "+";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Grammar toggle
|
||||||
|
document
|
||||||
|
.getElementById("gb-grammar-toggle")
|
||||||
|
?.addEventListener("change", function () {
|
||||||
|
settings.grammarCorrection = this.checked;
|
||||||
|
saveSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Contacts toggle
|
||||||
|
document
|
||||||
|
.getElementById("gb-contacts-toggle")
|
||||||
|
?.addEventListener("change", function () {
|
||||||
|
settings.hideContacts = this.checked;
|
||||||
|
applyContactVisibility();
|
||||||
|
saveSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto mode toggle
|
||||||
|
document
|
||||||
|
.getElementById("gb-auto-toggle")
|
||||||
|
?.addEventListener("change", function () {
|
||||||
|
settings.autoMode = this.checked;
|
||||||
|
applyAutoModeIndicator();
|
||||||
|
saveSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auth button
|
||||||
|
document
|
||||||
|
.getElementById("gb-auth-btn")
|
||||||
|
?.addEventListener("click", async function () {
|
||||||
|
const numberInput = document.getElementById("gb-whatsapp-number");
|
||||||
|
const number = numberInput?.value.trim();
|
||||||
|
|
||||||
|
if (!number) {
|
||||||
|
alert("Please enter your WhatsApp number");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.disabled = true;
|
||||||
|
this.textContent = "Authenticating...";
|
||||||
|
|
||||||
|
chrome.runtime.sendMessage(
|
||||||
|
{
|
||||||
|
action: "authenticate",
|
||||||
|
whatsappNumber: number,
|
||||||
|
},
|
||||||
|
(response) => {
|
||||||
|
if (response.success) {
|
||||||
|
this.textContent = "Check WhatsApp";
|
||||||
|
} else {
|
||||||
|
this.textContent = "Authenticate";
|
||||||
|
this.disabled = false;
|
||||||
|
alert(
|
||||||
|
"Authentication failed: " + (response.error || "Unknown error"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save settings to storage
|
||||||
|
function saveSettings() {
|
||||||
|
chrome.storage.sync.set(settings);
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
action: "saveSettings",
|
||||||
|
settings: settings,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current contact name
|
||||||
|
function getCurrentContactName() {
|
||||||
|
const nameEl = document.querySelector(SELECTORS.contactName);
|
||||||
|
return nameEl ? nameEl.textContent.trim() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set text in input field
|
||||||
|
function setInputText(inputField, text) {
|
||||||
|
inputField.textContent = text;
|
||||||
|
inputField.dispatchEvent(new InputEvent("input", { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate Enter key press
|
||||||
|
function simulateEnterPress(element) {
|
||||||
|
const enterEvent = new KeyboardEvent("keydown", {
|
||||||
|
key: "Enter",
|
||||||
|
code: "Enter",
|
||||||
|
keyCode: 13,
|
||||||
|
which: 13,
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
});
|
||||||
|
element.dispatchEvent(enterEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show processing indicator
|
||||||
|
function showProcessingIndicator(inputField) {
|
||||||
|
let indicator = document.getElementById("gb-processing");
|
||||||
|
if (!indicator) {
|
||||||
|
indicator = document.createElement("div");
|
||||||
|
indicator.id = "gb-processing";
|
||||||
|
indicator.className = "gb-processing-indicator";
|
||||||
|
indicator.innerHTML = `
|
||||||
|
<div class="gb-spinner"></div>
|
||||||
|
<span>Processing with AI...</span>
|
||||||
|
`;
|
||||||
|
inputField.parentElement.appendChild(indicator);
|
||||||
|
}
|
||||||
|
indicator.style.display = "flex";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide processing indicator
|
||||||
|
function hideProcessingIndicator() {
|
||||||
|
const indicator = document.getElementById("gb-processing");
|
||||||
|
if (indicator) {
|
||||||
|
indicator.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility: Escape HTML
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility: Levenshtein distance for text comparison
|
||||||
|
function levenshteinDistance(str1, str2) {
|
||||||
|
const m = str1.length;
|
||||||
|
const n = str2.length;
|
||||||
|
const dp = Array(m + 1)
|
||||||
|
.fill(null)
|
||||||
|
.map(() => Array(n + 1).fill(0));
|
||||||
|
|
||||||
|
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
||||||
|
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
||||||
|
|
||||||
|
for (let i = 1; i <= m; i++) {
|
||||||
|
for (let j = 1; j <= n; j++) {
|
||||||
|
if (str1[i - 1] === str2[j - 1]) {
|
||||||
|
dp[i][j] = dp[i - 1][j - 1];
|
||||||
|
} else {
|
||||||
|
dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[m][n];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for messages from background script
|
||||||
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
|
switch (message.action) {
|
||||||
|
case "tabReady":
|
||||||
|
init();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "settingsUpdated":
|
||||||
|
settings = { ...settings, ...message.settings };
|
||||||
|
applyUIModifications();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "enableAutoMode":
|
||||||
|
settings.autoMode = true;
|
||||||
|
settings.whatsappNumber = message.whatsappNumber;
|
||||||
|
applyAutoModeIndicator();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "replaceSelection":
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (selection.rangeCount > 0) {
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
range.deleteContents();
|
||||||
|
range.insertNode(document.createTextNode(message.text));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "authCompleted":
|
||||||
|
settings.authenticated = true;
|
||||||
|
// Refresh control panel
|
||||||
|
const panel = document.getElementById("gb-control-panel");
|
||||||
|
if (panel) {
|
||||||
|
panel.remove();
|
||||||
|
injectControlPanel();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendResponse({ success: true });
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize on DOM ready
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also try to initialize after a delay (WhatsApp Web loads dynamically)
|
||||||
|
setTimeout(init, 2000);
|
||||||
|
})();
|
||||||
BIN
icons/icon128.png
Normal file
BIN
icons/icon128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 339 B |
BIN
icons/icon16.png
Normal file
BIN
icons/icon16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 339 B |
BIN
icons/icon48.png
Normal file
BIN
icons/icon48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 339 B |
44
manifest.json
Normal file
44
manifest.json
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "General Bots",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"description": "AI-powered browser assistant with grammar correction, auto-reply, and WhatsApp integration",
|
||||||
|
"author": "pragmatismo.com.br",
|
||||||
|
"icons": {
|
||||||
|
"16": "icons/icon16.png",
|
||||||
|
"48": "icons/icon48.png",
|
||||||
|
"128": "icons/icon128.png"
|
||||||
|
},
|
||||||
|
"permissions": ["storage", "tabs", "activeTab", "notifications", "identity"],
|
||||||
|
"host_permissions": [
|
||||||
|
"https://web.whatsapp.com/*",
|
||||||
|
"https://*.pragmatismo.com.br/*",
|
||||||
|
"https://api.generalbots.com/*"
|
||||||
|
],
|
||||||
|
"action": {
|
||||||
|
"default_popup": "popup.html",
|
||||||
|
"default_icon": {
|
||||||
|
"16": "icons/icon16.png",
|
||||||
|
"48": "icons/icon48.png",
|
||||||
|
"128": "icons/icon128.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": ["https://web.whatsapp.com/*"],
|
||||||
|
"js": ["content.js"],
|
||||||
|
"css": ["styles.css"],
|
||||||
|
"run_at": "document_end"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"background": {
|
||||||
|
"service_worker": "background.js"
|
||||||
|
},
|
||||||
|
"web_accessible_resources": [
|
||||||
|
{
|
||||||
|
"resources": ["icons/*", "styles.css"],
|
||||||
|
"matches": ["https://web.whatsapp.com/*"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options_page": "options.html"
|
||||||
|
}
|
||||||
1135
options.html
Normal file
1135
options.html
Normal file
File diff suppressed because it is too large
Load diff
465
popup.css
Normal file
465
popup.css
Normal file
|
|
@ -0,0 +1,465 @@
|
||||||
|
/* General Bots - Popup Styles */
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--primary: #3b82f6;
|
||||||
|
--primary-hover: #2563eb;
|
||||||
|
--secondary: #64748b;
|
||||||
|
--success: #22c55e;
|
||||||
|
--warning: #f59e0b;
|
||||||
|
--error: #ef4444;
|
||||||
|
--bg-dark: #1e293b;
|
||||||
|
--bg-darker: #0f172a;
|
||||||
|
--bg-card: #334155;
|
||||||
|
--text-primary: #f1f5f9;
|
||||||
|
--text-secondary: #94a3b8;
|
||||||
|
--border: #475569;
|
||||||
|
--radius: 8px;
|
||||||
|
--radius-lg: 12px;
|
||||||
|
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||||
|
--transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
width: 360px;
|
||||||
|
min-height: 480px;
|
||||||
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
|
||||||
|
Cantarell, sans-serif;
|
||||||
|
background: var(--bg-dark);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
background: linear-gradient(135deg, var(--primary), var(--primary-hover));
|
||||||
|
}
|
||||||
|
|
||||||
|
.header img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .version {
|
||||||
|
font-size: 11px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status Bar */
|
||||||
|
.status-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: var(--bg-darker);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--secondary);
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-bar.connected .status-dot {
|
||||||
|
background: var(--success);
|
||||||
|
box-shadow: 0 0 8px var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-bar.disconnected .status-dot {
|
||||||
|
background: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sections */
|
||||||
|
.section {
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section h3 {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section .description {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Auth Section */
|
||||||
|
.auth-section {
|
||||||
|
background: rgba(59, 130, 246, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: var(--bg-darker);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 14px;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group input.error {
|
||||||
|
border-color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group small {
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group small.error-text {
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Settings */
|
||||||
|
.settings-section {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px 0;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-hint {
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-small {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 10px;
|
||||||
|
background: var(--bg-darker);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-small:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle Switch */
|
||||||
|
.switch {
|
||||||
|
position: relative;
|
||||||
|
width: 44px;
|
||||||
|
height: 24px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: var(--secondary);
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider.round {
|
||||||
|
border-radius: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
left: 3px;
|
||||||
|
bottom: 3px;
|
||||||
|
background: white;
|
||||||
|
transition: var(--transition);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider {
|
||||||
|
background: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider:before {
|
||||||
|
transform: translateX(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus + .slider {
|
||||||
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stats Section */
|
||||||
|
.stats-section {
|
||||||
|
background: var(--bg-darker);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
text-align: center;
|
||||||
|
padding: 12px 8px;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
display: block;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actions */
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--primary);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:disabled {
|
||||||
|
background: var(--secondary);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--bg-card);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: var(--bg-darker);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background: var(--success) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-error {
|
||||||
|
background: var(--error) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
padding: 12px 20px;
|
||||||
|
text-align: center;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer .copyright {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a {
|
||||||
|
color: var(--primary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Notification */
|
||||||
|
.popup-notification {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 20px;
|
||||||
|
right: 20px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
animation: slideUp 0.3s ease;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-notification.success {
|
||||||
|
background: var(--success);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-notification.error {
|
||||||
|
background: var(--error);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-notification.warning {
|
||||||
|
background: var(--warning);
|
||||||
|
color: var(--bg-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-notification.fade-out {
|
||||||
|
animation: fadeOut 0.3s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
transform: translateY(20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOut {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--bg-darker);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--secondary);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--text-secondary);
|
||||||
|
}
|
||||||
175
popup.html
Normal file
175
popup.html
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>General Bots</title>
|
||||||
|
<link rel="stylesheet" href="popup.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<img src="icons/icon48.png" alt="General Bots Logo" />
|
||||||
|
<div class="header-text">
|
||||||
|
<h1>General Bots</h1>
|
||||||
|
<p class="version">v2.0.0</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Connection Status -->
|
||||||
|
<div class="status-bar" id="status-bar">
|
||||||
|
<span class="status-dot"></span>
|
||||||
|
<span class="status-text">Checking connection...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Authentication Section -->
|
||||||
|
<div class="section auth-section" id="auth-section">
|
||||||
|
<h3>🔐 Connect to General Bots</h3>
|
||||||
|
<p class="description">
|
||||||
|
Authenticate using your WhatsApp number to enable AI
|
||||||
|
features.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="whatsapp-number">WhatsApp Number</label>
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
id="whatsapp-number"
|
||||||
|
placeholder="+55 11 99999-9999"
|
||||||
|
/>
|
||||||
|
<small>Include country code</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-primary" id="auth-btn">
|
||||||
|
<span class="btn-icon">🤖</span>
|
||||||
|
Authenticate via WhatsApp
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Settings Section -->
|
||||||
|
<div class="section settings-section">
|
||||||
|
<h3>⚙️ Settings</h3>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">Server URL</span>
|
||||||
|
<span class="setting-hint"
|
||||||
|
>General Bots API endpoint</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="server-url"
|
||||||
|
class="input-small"
|
||||||
|
placeholder="https://api.generalbots.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item toggle">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">✨ Grammar Correction</span>
|
||||||
|
<span class="setting-hint"
|
||||||
|
>Fix spelling & grammar with AI</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<label class="switch">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="grammar-correction"
|
||||||
|
checked
|
||||||
|
/>
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item toggle">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">📝 Enable Processing</span>
|
||||||
|
<span class="setting-hint"
|
||||||
|
>Process messages before sending</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="enable-processing" checked />
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item toggle">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">👁️ Hide Contacts</span>
|
||||||
|
<span class="setting-hint"
|
||||||
|
>Hide contact list for privacy</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="hide-contacts" />
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item toggle">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">🤖 Auto Mode</span>
|
||||||
|
<span class="setting-hint"
|
||||||
|
>Enable automatic replies</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="auto-mode" />
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stats Section -->
|
||||||
|
<div class="section stats-section" id="stats-section">
|
||||||
|
<h3>📊 Statistics</h3>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-value" id="messages-processed"
|
||||||
|
>0</span
|
||||||
|
>
|
||||||
|
<span class="stat-label">Messages Processed</span>
|
||||||
|
</div>
|
||||||
|
<div class="</h3>stat-item">
|
||||||
|
<span class="stat-value" id="corrections-made">0</span>
|
||||||
|
<span class="stat-label">Corrections Made</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-value" id="auto-replies">0</span>
|
||||||
|
<span class="stat-label">Auto Replies</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="actions">
|
||||||
|
<button class="btn btn-primary" id="save-settings">
|
||||||
|
<span class="btn-icon">💾</span>
|
||||||
|
Save Settings
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" id="open-options">
|
||||||
|
<span class="btn-icon">⚙️</span>
|
||||||
|
Advanced
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p class="copyright">
|
||||||
|
©
|
||||||
|
<a href="https://pragmatismo.com.br" target="_blank"
|
||||||
|
>pragmatismo.com.br</a
|
||||||
|
>
|
||||||
|
•
|
||||||
|
<a
|
||||||
|
href="https://github.com/GeneralBots/BotServer"
|
||||||
|
target="_blank"
|
||||||
|
>AGPL License</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="popup.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
293
popup.js
Normal file
293
popup.js
Normal file
|
|
@ -0,0 +1,293 @@
|
||||||
|
// General Bots - Popup Script
|
||||||
|
// Handles settings management, authentication, and UI updates
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", async function () {
|
||||||
|
// Default settings
|
||||||
|
const DEFAULT_SETTINGS = {
|
||||||
|
serverUrl: "https://api.generalbots.com",
|
||||||
|
gbServerUrl: "https://api.pragmatismo.com.br",
|
||||||
|
enableProcessing: true,
|
||||||
|
hideContacts: false,
|
||||||
|
autoMode: false,
|
||||||
|
grammarCorrection: true,
|
||||||
|
whatsappNumber: "",
|
||||||
|
authToken: "",
|
||||||
|
instanceId: "",
|
||||||
|
authenticated: false,
|
||||||
|
stats: {
|
||||||
|
messagesProcessed: 0,
|
||||||
|
correctionsMade: 0,
|
||||||
|
autoReplies: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load settings and update UI
|
||||||
|
await loadSettings();
|
||||||
|
await checkAuthStatus();
|
||||||
|
loadStats();
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
setupEventListeners();
|
||||||
|
|
||||||
|
// Load saved settings
|
||||||
|
async function loadSettings() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
chrome.storage.sync.get(DEFAULT_SETTINGS, function (items) {
|
||||||
|
// Update form fields
|
||||||
|
document.getElementById("server-url").value =
|
||||||
|
items.serverUrl || DEFAULT_SETTINGS.serverUrl;
|
||||||
|
document.getElementById("whatsapp-number").value =
|
||||||
|
items.whatsappNumber || "";
|
||||||
|
document.getElementById("grammar-correction").checked =
|
||||||
|
items.grammarCorrection;
|
||||||
|
document.getElementById("enable-processing").checked =
|
||||||
|
items.enableProcessing;
|
||||||
|
document.getElementById("hide-contacts").checked = items.hideContacts;
|
||||||
|
document.getElementById("auto-mode").checked = items.autoMode;
|
||||||
|
|
||||||
|
resolve(items);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check authentication status
|
||||||
|
async function checkAuthStatus() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
chrome.runtime.sendMessage({ action: "getAuthStatus" }, (response) => {
|
||||||
|
const statusBar = document.getElementById("status-bar");
|
||||||
|
const authSection = document.getElementById("auth-section");
|
||||||
|
const statusDot = statusBar.querySelector(".status-dot");
|
||||||
|
const statusText = statusBar.querySelector(".status-text");
|
||||||
|
|
||||||
|
if (response && response.authenticated) {
|
||||||
|
statusBar.classList.add("connected");
|
||||||
|
statusBar.classList.remove("disconnected");
|
||||||
|
statusDot.style.background = "#22c55e";
|
||||||
|
statusText.textContent = `Connected (${response.whatsappNumber || "Authenticated"})`;
|
||||||
|
authSection.style.display = "none";
|
||||||
|
} else {
|
||||||
|
statusBar.classList.add("disconnected");
|
||||||
|
statusBar.classList.remove("connected");
|
||||||
|
statusDot.style.background = "#ef4444";
|
||||||
|
statusText.textContent = "Not connected";
|
||||||
|
authSection.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(response);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load statistics
|
||||||
|
function loadStats() {
|
||||||
|
chrome.storage.local.get(["stats"], function (result) {
|
||||||
|
const stats = result.stats || DEFAULT_SETTINGS.stats;
|
||||||
|
document.getElementById("messages-processed").textContent =
|
||||||
|
stats.messagesProcessed || 0;
|
||||||
|
document.getElementById("corrections-made").textContent =
|
||||||
|
stats.correctionsMade || 0;
|
||||||
|
document.getElementById("auto-replies").textContent =
|
||||||
|
stats.autoReplies || 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup event listeners
|
||||||
|
function setupEventListeners() {
|
||||||
|
// Save settings button
|
||||||
|
document
|
||||||
|
.getElementById("save-settings")
|
||||||
|
.addEventListener("click", saveSettings);
|
||||||
|
|
||||||
|
// Auth button
|
||||||
|
document
|
||||||
|
.getElementById("auth-btn")
|
||||||
|
.addEventListener("click", handleAuthentication);
|
||||||
|
|
||||||
|
// Open options page
|
||||||
|
document.getElementById("open-options").addEventListener("click", () => {
|
||||||
|
chrome.runtime.openOptionsPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle switches - save immediately on change
|
||||||
|
const toggles = [
|
||||||
|
"grammar-correction",
|
||||||
|
"enable-processing",
|
||||||
|
"hide-contacts",
|
||||||
|
"auto-mode",
|
||||||
|
];
|
||||||
|
toggles.forEach((id) => {
|
||||||
|
document.getElementById(id).addEventListener("change", function () {
|
||||||
|
saveSettings(true); // silent save
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save settings
|
||||||
|
async function saveSettings(silent = false) {
|
||||||
|
const settings = {
|
||||||
|
serverUrl: document.getElementById("server-url").value.trim(),
|
||||||
|
grammarCorrection: document.getElementById("grammar-correction").checked,
|
||||||
|
enableProcessing: document.getElementById("enable-processing").checked,
|
||||||
|
hideContacts: document.getElementById("hide-contacts").checked,
|
||||||
|
autoMode: document.getElementById("auto-mode").checked,
|
||||||
|
whatsappNumber: document.getElementById("whatsapp-number").value.trim(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
chrome.storage.sync.set(settings, function () {
|
||||||
|
// Notify content script about settings change
|
||||||
|
chrome.tabs.query(
|
||||||
|
{ url: "https://web.whatsapp.com/*" },
|
||||||
|
function (tabs) {
|
||||||
|
tabs.forEach((tab) => {
|
||||||
|
chrome.tabs.sendMessage(tab.id, {
|
||||||
|
action: "settingsUpdated",
|
||||||
|
settings: settings,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!silent) {
|
||||||
|
showFeedback("save-settings", "Saved!", "success");
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(settings);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle authentication
|
||||||
|
async function handleAuthentication() {
|
||||||
|
const numberInput = document.getElementById("whatsapp-number");
|
||||||
|
const authBtn = document.getElementById("auth-btn");
|
||||||
|
const number = numberInput.value.trim();
|
||||||
|
|
||||||
|
// Validate number
|
||||||
|
if (!number) {
|
||||||
|
showError(numberInput, "Please enter your WhatsApp number");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean number (remove spaces, dashes)
|
||||||
|
const cleanNumber = number.replace(/[\s\-\(\)]/g, "");
|
||||||
|
|
||||||
|
// Basic validation
|
||||||
|
if (!/^\+?[0-9]{10,15}$/.test(cleanNumber)) {
|
||||||
|
showError(numberInput, "Invalid phone number format");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update button state
|
||||||
|
authBtn.disabled = true;
|
||||||
|
authBtn.innerHTML = '<span class="btn-icon">⏳</span> Sending request...';
|
||||||
|
|
||||||
|
// Send auth request
|
||||||
|
chrome.runtime.sendMessage(
|
||||||
|
{
|
||||||
|
action: "authenticate",
|
||||||
|
whatsappNumber: cleanNumber,
|
||||||
|
},
|
||||||
|
(response) => {
|
||||||
|
if (response && response.success) {
|
||||||
|
authBtn.innerHTML =
|
||||||
|
'<span class="btn-icon">📱</span> Check your WhatsApp';
|
||||||
|
authBtn.classList.add("btn-success");
|
||||||
|
|
||||||
|
// Start polling for auth completion
|
||||||
|
pollAuthStatus();
|
||||||
|
} else {
|
||||||
|
authBtn.disabled = false;
|
||||||
|
authBtn.innerHTML =
|
||||||
|
'<span class="btn-icon">🤖</span> Authenticate via WhatsApp';
|
||||||
|
|
||||||
|
const errorMsg = response?.error || "Authentication failed";
|
||||||
|
showNotification(errorMsg, "error");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll for authentication status
|
||||||
|
function pollAuthStatus(attempts = 0) {
|
||||||
|
if (attempts > 60) {
|
||||||
|
// 5 minutes max
|
||||||
|
showNotification("Authentication timed out. Please try again.", "error");
|
||||||
|
resetAuthButton();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
const response = await checkAuthStatus();
|
||||||
|
|
||||||
|
if (response && response.authenticated) {
|
||||||
|
showNotification("Successfully connected!", "success");
|
||||||
|
resetAuthButton();
|
||||||
|
} else {
|
||||||
|
pollAuthStatus(attempts + 1);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset auth button
|
||||||
|
function resetAuthButton() {
|
||||||
|
const authBtn = document.getElementById("auth-btn");
|
||||||
|
authBtn.disabled = false;
|
||||||
|
authBtn.classList.remove("btn-success");
|
||||||
|
authBtn.innerHTML =
|
||||||
|
'<span class="btn-icon">🤖</span> Authenticate via WhatsApp';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error on input
|
||||||
|
function showError(input, message) {
|
||||||
|
input.classList.add("error");
|
||||||
|
const small = input.parentElement.querySelector("small");
|
||||||
|
if (small) {
|
||||||
|
small.textContent = message;
|
||||||
|
small.classList.add("error-text");
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
input.classList.remove("error");
|
||||||
|
if (small) {
|
||||||
|
small.textContent = "Include country code";
|
||||||
|
small.classList.remove("error-text");
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show feedback on button
|
||||||
|
function showFeedback(buttonId, message, type = "success") {
|
||||||
|
const button = document.getElementById(buttonId);
|
||||||
|
const originalHTML = button.innerHTML;
|
||||||
|
const originalDisabled = button.disabled;
|
||||||
|
|
||||||
|
button.disabled = true;
|
||||||
|
button.innerHTML = `<span class="btn-icon">${type === "success" ? "✓" : "✗"}</span> ${message}`;
|
||||||
|
button.classList.add(`btn-${type}`);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
button.innerHTML = originalHTML;
|
||||||
|
button.disabled = originalDisabled;
|
||||||
|
button.classList.remove(`btn-${type}`);
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show notification
|
||||||
|
function showNotification(message, type = "info") {
|
||||||
|
// Remove existing notification
|
||||||
|
const existing = document.querySelector(".popup-notification");
|
||||||
|
if (existing) existing.remove();
|
||||||
|
|
||||||
|
const notification = document.createElement("div");
|
||||||
|
notification.className = `popup-notification ${type}`;
|
||||||
|
notification.textContent = message;
|
||||||
|
|
||||||
|
document.body.appendChild(notification);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.classList.add("fade-out");
|
||||||
|
setTimeout(() => notification.remove(), 300);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
666
styles.css
Normal file
666
styles.css
Normal file
|
|
@ -0,0 +1,666 @@
|
||||||
|
/* General Bots - WhatsApp Web Extension Styles */
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
CSS Variables
|
||||||
|
============================================ */
|
||||||
|
:root {
|
||||||
|
--gb-primary: #3b82f6;
|
||||||
|
--gb-primary-hover: #2563eb;
|
||||||
|
--gb-secondary: #64748b;
|
||||||
|
--gb-success: #22c55e;
|
||||||
|
--gb-warning: #f59e0b;
|
||||||
|
--gb-error: #ef4444;
|
||||||
|
--gb-bg-dark: #1e293b;
|
||||||
|
--gb-bg-light: #f8fafc;
|
||||||
|
--gb-text-light: #f1f5f9;
|
||||||
|
--gb-text-dark: #1e293b;
|
||||||
|
--gb-border: #334155;
|
||||||
|
--gb-shadow:
|
||||||
|
0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||||
|
--gb-shadow-lg:
|
||||||
|
0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||||
|
--gb-radius: 8px;
|
||||||
|
--gb-radius-lg: 12px;
|
||||||
|
--gb-transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Hide Contacts Mode
|
||||||
|
============================================ */
|
||||||
|
.gb-hide-contacts {
|
||||||
|
width: 0 !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
opacity: 0 !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
transition: var(--gb-transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Control Panel
|
||||||
|
============================================ */
|
||||||
|
#gb-control-panel {
|
||||||
|
position: fixed;
|
||||||
|
top: 80px;
|
||||||
|
right: 20px;
|
||||||
|
width: 280px;
|
||||||
|
background: var(--gb-bg-dark);
|
||||||
|
border-radius: var(--gb-radius-lg);
|
||||||
|
box-shadow: var(--gb-shadow-lg);
|
||||||
|
z-index: 99999;
|
||||||
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
|
||||||
|
Cantarell, sans-serif;
|
||||||
|
color: var(--gb-text-light);
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--gb-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-panel-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
var(--gb-primary),
|
||||||
|
var(--gb-primary-hover)
|
||||||
|
);
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-panel-logo {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-panel-header span {
|
||||||
|
flex: 1;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-panel-toggle {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
transition: var(--gb-transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-panel-toggle:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-panel-body {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status indicator */
|
||||||
|
.gb-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: var(--gb-radius);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-status-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--gb-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-status.connected .gb-status-dot {
|
||||||
|
background: var(--gb-success);
|
||||||
|
box-shadow: 0 0 8px var(--gb-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-status.disconnected .gb-status-dot {
|
||||||
|
background: var(--gb-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Controls */
|
||||||
|
.gb-controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-switch-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-switch-label input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-switch {
|
||||||
|
position: relative;
|
||||||
|
width: 44px;
|
||||||
|
height: 24px;
|
||||||
|
background: var(--gb-secondary);
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: var(--gb-transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-switch::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: var(--gb-transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-switch-label input:checked + .gb-switch {
|
||||||
|
background: var(--gb-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-switch-label input:checked + .gb-switch::after {
|
||||||
|
left: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Auth section */
|
||||||
|
.gb-auth-section {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid var(--gb-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-auth-section p {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--gb-secondary);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-auth-section input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border: 1px solid var(--gb-border);
|
||||||
|
border-radius: var(--gb-radius);
|
||||||
|
color: var(--gb-text-light);
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-auth-section input::placeholder {
|
||||||
|
color: var(--gb-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-auth-section input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--gb-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Buttons
|
||||||
|
============================================ */
|
||||||
|
.gb-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--gb-radius);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--gb-transition);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-btn-primary {
|
||||||
|
background: var(--gb-primary);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-btn-primary:hover {
|
||||||
|
background: var(--gb-primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-btn-primary:disabled {
|
||||||
|
background: var(--gb-secondary);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-btn-secondary {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: var(--gb-text-light);
|
||||||
|
border: 1px solid var(--gb-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-btn-secondary:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Correction Modal
|
||||||
|
============================================ */
|
||||||
|
.gb-correction-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 999999;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
animation: gb-fade-in 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gb-fade-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-correction-content {
|
||||||
|
background: var(--gb-bg-dark);
|
||||||
|
border-radius: var(--gb-radius-lg);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 500px;
|
||||||
|
box-shadow: var(--gb-shadow-lg);
|
||||||
|
border: 1px solid var(--gb-border);
|
||||||
|
animation: gb-slide-up 0.3s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gb-slide-up {
|
||||||
|
from {
|
||||||
|
transform: translateY(20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-correction-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
var(--gb-primary),
|
||||||
|
var(--gb-primary-hover)
|
||||||
|
);
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-correction-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-correction-body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-text-compare {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-text-compare label {
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: var(--gb-secondary);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-text-compare p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: var(--gb-radius);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--gb-text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-original p {
|
||||||
|
border-left: 3px solid var(--gb-error);
|
||||||
|
text-decoration: line-through;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-corrected p {
|
||||||
|
border-left: 3px solid var(--gb-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-correction-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
border-top: 1px solid var(--gb-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-correction-actions .gb-btn {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Auto Mode Indicator
|
||||||
|
============================================ */
|
||||||
|
.gb-auto-indicator {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: var(--gb-success);
|
||||||
|
color: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
box-shadow: var(--gb-shadow);
|
||||||
|
z-index: 99999;
|
||||||
|
animation: gb-pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gb-pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-auto-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: gb-blink 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gb-blink {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Contact Controls
|
||||||
|
============================================ */
|
||||||
|
.gb-contact-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-contact-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 20px;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--gb-transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-contact-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-contact-btn.active {
|
||||||
|
background: var(--gb-success);
|
||||||
|
border-color: var(--gb-success);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-contact-btn .gb-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Processing Indicator
|
||||||
|
============================================ */
|
||||||
|
.gb-processing-indicator {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
background: var(--gb-bg-dark);
|
||||||
|
border-radius: var(--gb-radius) var(--gb-radius) 0 0;
|
||||||
|
color: var(--gb-text-light);
|
||||||
|
font-size: 13px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-spinner {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 2px solid var(--gb-border);
|
||||||
|
border-top-color: var(--gb-primary);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: gb-spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gb-spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Processed Message Indicator
|
||||||
|
============================================ */
|
||||||
|
.gb-processed-indicator {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--gb-success);
|
||||||
|
margin-top: 4px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
background: rgba(34, 197, 94, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-processed-message {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-processed-message::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: -4px;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 3px;
|
||||||
|
background: var(--gb-success);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Monitored Input Indicator
|
||||||
|
============================================ */
|
||||||
|
.gb-monitored-input {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-monitored-input::after {
|
||||||
|
content: "✨";
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Tooltip
|
||||||
|
============================================ */
|
||||||
|
.gb-tooltip {
|
||||||
|
position: absolute;
|
||||||
|
background: var(--gb-bg-dark);
|
||||||
|
color: var(--gb-text-light);
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: var(--gb-radius);
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 100000;
|
||||||
|
box-shadow: var(--gb-shadow);
|
||||||
|
pointer-events: none;
|
||||||
|
animation: gb-fade-in 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-tooltip::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
bottom: -6px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
border-left: 6px solid transparent;
|
||||||
|
border-right: 6px solid transparent;
|
||||||
|
border-top: 6px solid var(--gb-bg-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Notification Toast
|
||||||
|
============================================ */
|
||||||
|
.gb-toast {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 14px 20px;
|
||||||
|
background: var(--gb-bg-dark);
|
||||||
|
border-radius: var(--gb-radius);
|
||||||
|
box-shadow: var(--gb-shadow-lg);
|
||||||
|
color: var(--gb-text-light);
|
||||||
|
font-size: 14px;
|
||||||
|
z-index: 999999;
|
||||||
|
animation: gb-slide-in 0.3s ease;
|
||||||
|
border-left: 4px solid var(--gb-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-toast.success {
|
||||||
|
border-left-color: var(--gb-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-toast.error {
|
||||||
|
border-left-color: var(--gb-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-toast.warning {
|
||||||
|
border-left-color: var(--gb-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gb-slide-in {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-toast-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--gb-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-toast-close:hover {
|
||||||
|
color: var(--gb-text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Dark Mode Overrides for WhatsApp
|
||||||
|
============================================ */
|
||||||
|
[data-theme="dark"] #gb-control-panel,
|
||||||
|
.dark #gb-control-panel {
|
||||||
|
background: #0b141a;
|
||||||
|
border-color: #222d34;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .gb-panel-body,
|
||||||
|
.dark .gb-panel-body {
|
||||||
|
background: #0b141a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Responsive Design
|
||||||
|
============================================ */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
#gb-control-panel {
|
||||||
|
width: 260px;
|
||||||
|
right: 10px;
|
||||||
|
top: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-correction-content {
|
||||||
|
width: 95%;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gb-auto-indicator {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue