From 655e788b7ac425365e9a2c465c682a48357b6682 Mon Sep 17 00:00:00 2001 From: samex Date: Fri, 4 Apr 2025 14:02:41 +0200 Subject: [PATCH] improvements --- background.js | 241 +++++++++++++++++++++++++++++++++++++++------- contentScript.css | 43 +++++++++ contentScript.js | 94 ++++++++++++++++++ manifest.json | 20 +--- popup.css | 100 ++++++++++++++++--- popup.html | 51 +++++++++- popup.js | 177 ++++++++++++++++++++++++++++++++++ 7 files changed, 657 insertions(+), 69 deletions(-) diff --git a/background.js b/background.js index 9ea1831..ddec65a 100644 --- a/background.js +++ b/background.js @@ -1,37 +1,215 @@ let originTabId = null; -// Create context menu +// Function to show loading overlay directly in the page +function showLoadingOverlay(tabId) { + chrome.scripting.executeScript({ + target: { tabId: tabId }, + function: () => { + // Create overlay elements if they don't exist + let overlay = document.getElementById('real-chatgpt-overlay'); + if (!overlay) { + // Create styles + const style = document.createElement('style'); + style.textContent = ` + #real-chatgpt-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.7); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 9999; + font-family: Arial, sans-serif; + } + #real-chatgpt-message { + color: white; + font-size: 18px; + margin-top: 20px; + padding: 10px 20px; + background-color: rgba(0, 0, 0, 0.5); + border-radius: 5px; + text-align: center; + max-width: 80%; + } + #real-chatgpt-message.completion { + background-color: #28a745; + font-weight: bold; + } + #real-chatgpt-spinner { + width: 50px; + height: 50px; + border: 5px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top-color: #fff; + animation: spin 1s ease-in-out infinite; + } + @keyframes spin { + to { transform: rotate(360deg); } + } + `; + document.head.appendChild(style); + + // Create overlay + overlay = document.createElement('div'); + overlay.id = 'real-chatgpt-overlay'; + + // Create spinner + const spinner = document.createElement('div'); + spinner.id = 'real-chatgpt-spinner'; + + // Create message + const message = document.createElement('div'); + message.id = 'real-chatgpt-message'; + message.textContent = 'Processing with ChatGPT...'; + + // Assemble overlay + overlay.appendChild(spinner); + overlay.appendChild(message); + document.body.appendChild(overlay); + } + } + }); +} + +// Function to show completion message +function showCompletionMessage(tabId) { + chrome.scripting.executeScript({ + target: { tabId: tabId }, + function: () => { + // Update existing overlay or create a new one + let overlay = document.getElementById('real-chatgpt-overlay'); + let spinner = document.getElementById('real-chatgpt-spinner'); + let message = document.getElementById('real-chatgpt-message'); + + if (!overlay) { + // If overlay doesn't exist, create it + const style = document.createElement('style'); + style.textContent = ` + #real-chatgpt-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.7); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 9999; + font-family: Arial, sans-serif; + } + #real-chatgpt-message { + color: white; + font-size: 18px; + margin-top: 20px; + padding: 10px 20px; + background-color: #28a745; + border-radius: 5px; + text-align: center; + max-width: 80%; + font-weight: bold; + } + `; + document.head.appendChild(style); + + overlay = document.createElement('div'); + overlay.id = 'real-chatgpt-overlay'; + + message = document.createElement('div'); + message.id = 'real-chatgpt-message'; + + overlay.appendChild(message); + document.body.appendChild(overlay); + } else if (spinner) { + // Hide spinner if it exists + spinner.style.display = 'none'; + } + + // Update message + if (message) { + message.textContent = 'Finished, please paste from clipboard'; + message.classList.add('completion'); + } + + // Remove overlay after 1 second + setTimeout(() => { + const overlay = document.getElementById('real-chatgpt-overlay'); + if (overlay) { + overlay.remove(); + } + }, 1000); + } + }); +} + +// Create context menus from settings or defaults +function createContextMenus(menuItems) { + // Remove all existing context menus + chrome.contextMenus.removeAll(); + + // Create new context menus from settings + menuItems.forEach(item => { + chrome.contextMenus.create({ + id: item.id, + title: item.title, + contexts: ["selection"], // Show only when text is selected + }); + }); +} + +// Initialize context menus on installation chrome.runtime.onInstalled.addListener(() => { - chrome.contextMenus.create({ - id: "improveMail", - title: "Verbessere Mail", - contexts: ["selection"], // Show only when text is selected + // Initialize storage with default values if not already set + chrome.storage.sync.get(['menuItems'], (result) => { + const menuItems = result.menuItems || defaultMenuItems; + + // Save to storage if not already set + if (!result.menuItems) { + chrome.storage.sync.set({ menuItems: defaultMenuItems }); + } + + // Create context menus + createContextMenus(menuItems); }); - chrome.contextMenus.create({ - id: "improveText", - title: "Verbessere Text", - contexts: ["selection"], // Show only when text is selected - }); - chrome.contextMenus.create({ - id: "seoText", - title: "Erstelle ein SEO-Text", - contexts: ["selection"], // Show only when text is selected - }); - chrome.contextMenus.create({ - id: "explainMe", - title: "Erkläre Text", - contexts: ["selection"], // Show only when text is selected +}); +// Handle context menu clicks +chrome.contextMenus.onClicked.addListener((info, tab) => { + // Get menu items from storage + chrome.storage.sync.get(['menuItems'], (result) => { + const menuItems = result.menuItems || defaultMenuItems; + const selectedItem = menuItems.find(item => item.id === info.menuItemId); + + if (selectedItem) { + handleMenuItemClick(info, tab, selectedItem.prompt, selectedItem.model); + } }); }); +// Listen for messages from popup.js +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.action === 'updateContextMenus') { + chrome.storage.sync.get(['menuItems'], (result) => { + const menuItems = result.menuItems || defaultMenuItems; + createContextMenus(menuItems); + }); + } +}); // Utility function to handle menu item click dynamically -function handleMenuItemClick(info, tab, prefixText) { +function handleMenuItemClick(info, tab, prefixText, model = 'gpt-4o') { originTabId = tab.id; if (info.selectionText) { + // Show loading indicator in the original tab + showLoadingOverlay(originTabId); + const selectedText = prefixText + info.selectionText; - chrome.tabs.create({ url: "https://chatgpt.com/?temporary-chat=true" }, (newTab) => { + chrome.tabs.create({ url: `https://chatgpt.com/?model=${model}&temporary-chat=true`, active: false }, (newTab) => { chrome.tabs.onUpdated.addListener(function listener(tabId, info) { if (tabId === newTab.id && info.status === "complete") { chrome.tabs.onUpdated.removeListener(listener); @@ -45,19 +223,7 @@ function handleMenuItemClick(info, tab, prefixText) { }); } } -// Handle context menu clicks -chrome.contextMenus.onClicked.addListener((info, tab) => { - const menuActions = { - explainMe: 'Erkläre mir folgenden Text, fasse es kurz und gut lesbar zusammen. Erstelle keine eigenen Kommentare, zeige rein nur den text als Ausgabe: ', - improveMail: 'Verbessere folgende Mail, erstelle keine eigenen Kommentare, zeige rein nur den text als ausgabe: ', - improveText: 'Verbessere folgenden Text, mach es nicht viel länger oder kürzer als es jetzt schon ist, erstelle keine eigenen Kommentare, zeige rein nur den text als Ausgabe: ', - seoText: 'Du bist der Weltbeste SEO-Text Author. Schreibe einen SEO-optimierten Text mit 400-500 Wörtern basierend auf dem folgenden Inhalt. Identifiziere das Haupt-Keyword und integriere es mit einer Keyword-Dichte von 2-3 %. Strukturierte den Text mit HTML-Elementen (h1, h2, h3, p). Hebe das Keyword mit dem strong-Tag hervor. Gib nur den HTML-Code aus, ohne HTML-, Head- oder Body-Tags und ohne Kommentare. Die Ausgabe sollte als reiner Text erscheinen, nicht in einem Codefeld. Hier ist der relevante Text/Keyword: ', - }; - if (menuActions[info.menuItemId]) { - handleMenuItemClick(info, tab, menuActions[info.menuItemId]); - } -}); // Listen for a message to close the current tab and switch back @@ -69,9 +235,14 @@ chrome.runtime.onMessage.addListener((message, sender) => { chrome.tabs.remove(currentTabId, () => { console.log("Closed current tab:", currentTabId); - // Switch back to the original tab + // Switch back to the original tab and show completion message if (originTabId) { - chrome.tabs.update(originTabId, { active: true }); + chrome.tabs.update(originTabId, { active: true }, () => { + // Show completion message in the original tab + setTimeout(() => { + showCompletionMessage(originTabId); + }, 100); // Small delay to ensure tab is active + }); } }); } diff --git a/contentScript.css b/contentScript.css index e69de29..183e366 100644 --- a/contentScript.css +++ b/contentScript.css @@ -0,0 +1,43 @@ +#real-chatgpt-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.7); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 9999; + font-family: Arial, sans-serif; +} + +#real-chatgpt-message { + color: white; + font-size: 18px; + margin-top: 20px; + padding: 10px 20px; + background-color: rgba(0, 0, 0, 0.5); + border-radius: 5px; + text-align: center; + max-width: 80%; +} + +#real-chatgpt-message.completion { + background-color: #28a745; + font-weight: bold; +} + +#real-chatgpt-spinner { + width: 50px; + height: 50px; + border: 5px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top-color: #fff; + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} diff --git a/contentScript.js b/contentScript.js index e69de29..6f26fcb 100644 --- a/contentScript.js +++ b/contentScript.js @@ -0,0 +1,94 @@ +// Create and manage overlay elements for loading and completion states +let overlay = null; +let messageElement = null; + +// Flag to indicate content script is ready +let isInitialized = false; + +// Initialize the content script +function initialize() { + if (isInitialized) return; + isInitialized = true; + + // Let the background script know we're ready + chrome.runtime.sendMessage({ action: 'contentScriptReady' }); + console.log('Real ChatGPT Extension: Content script initialized'); +} + +// Initialize immediately +initialize(); + +// Create the overlay element with loading spinner +function createOverlay() { + // Remove any existing overlay + removeOverlay(); + + // Create main overlay container + overlay = document.createElement('div'); + overlay.id = 'real-chatgpt-overlay'; + + // Create message container + messageElement = document.createElement('div'); + messageElement.id = 'real-chatgpt-message'; + + // Create loading spinner + const spinner = document.createElement('div'); + spinner.id = 'real-chatgpt-spinner'; + + // Add loading message + messageElement.textContent = 'Processing with ChatGPT...'; + + // Assemble the overlay + overlay.appendChild(spinner); + overlay.appendChild(messageElement); + document.body.appendChild(overlay); +} + +// Remove the overlay from the DOM +function removeOverlay() { + const existingOverlay = document.getElementById('real-chatgpt-overlay'); + if (existingOverlay) { + existingOverlay.remove(); + } +} + +// Show completion message and remove after delay +function showCompletionMessage() { + if (!overlay) { + createOverlay(); + } + + // Update the overlay to show completion message + const spinner = document.getElementById('real-chatgpt-spinner'); + if (spinner) { + spinner.style.display = 'none'; + } + + // Update message + if (messageElement) { + messageElement.textContent = 'Finished, please paste from clipboard'; + messageElement.classList.add('completion'); + } + + // Remove after 1 second + setTimeout(removeOverlay, 1000); +} + +// Listen for messages from the background script +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + // Make sure we're initialized + initialize(); + if (message.action === 'showLoading') { + createOverlay(); + sendResponse({ status: 'overlay_created' }); + return true; + } else if (message.action === 'showCompletion') { + showCompletionMessage(); + sendResponse({ status: 'completion_shown' }); + return true; + } else if (message.action === 'removeOverlay') { + removeOverlay(); + sendResponse({ status: 'overlay_removed' }); + return true; + } +}); diff --git a/manifest.json b/manifest.json index 96fed1c..f9e991c 100644 --- a/manifest.json +++ b/manifest.json @@ -7,7 +7,8 @@ "scripting", "activeTab", "tabs", - "clipboardWrite" + "clipboardWrite", + "storage" ], "host_permissions": [ "https://chatgpt.com/*" @@ -25,20 +26,5 @@ }, "background": { "service_worker": "background.js" - }, - "content_scripts": [ - { - "matches": [ - "http://*/*", - "https://*/*" - ], - "run_at": "document_end", - "js": [ - "contentScript.js" - ], - "css": [ - "contentScript.css" - ] - } - ] + } } \ No newline at end of file diff --git a/popup.css b/popup.css index c7cf5f5..a6c9e3b 100644 --- a/popup.css +++ b/popup.css @@ -4,31 +4,103 @@ } body { - width: 500px; - height: 200px; + background: #fff9d8; min-width: 500px; min-height: 200px; margin: 0; } .container { - background-color: #F2DE62; - width: 100%; - height: 100%; display: flex; flex-direction: column; align-items: flex-start; justify-content: space-evenly; - padding: 30px; + padding: 15px; + box-sizing: border-box; + border-radius: 10px; +} +.btn-primary { + background-color: #2196F3; + margin-right: 1rem; +} +.btn { + color: white; + font-weight: bold; +} +h1 { + font-size: 20px; +} +.menu-items { + margin-bottom: 10px; + width: 100%; +} +.menu-item { + border: 1px solid #ddd; + border-radius: 4px; + padding: 10px; + margin-bottom: 10px; + background-color: #f9f9f9; +} +.menu-item-header { + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + font-weight: bold; +} +.menu-item-content { + display: none; + margin-top: 1rem; +} +.menu-item.expanded .menu-item-content { + display: block; +} +input[type="text"], textarea, select { + width: 100%; + padding: 8px; + margin: 5px 0; + border: 1px solid #ddd; + border-radius: 4px; box-sizing: border-box; } - -h1 { - margin: 0; - font-family: 'Sora'; +textarea { + height: 80px; + resize: vertical; } - -p { - margin: 0; - font-family: 'Sora'; +.btn { + padding: 8px 12px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 13px; +} +.btn-primary { + background-color: #4285f4; + color: white; +} +.btn-danger { + background-color: #ea4335; + color: white; +} +.btn-secondary { + background-color: #f1f1f1; + color: #333; +} +.action-buttons { + display: flex; + justify-content: space-between; + margin-top: 10px; +} +.toggle-btn { + background: none; + border: none; + cursor: pointer; +} +.form-group { + margin-bottom: 10px; +} +.form-group label { + display: block; + margin-bottom: 5px; + font-weight: bold; } \ No newline at end of file diff --git a/popup.html b/popup.html index f3cf1ab..57eb154 100644 --- a/popup.html +++ b/popup.html @@ -3,14 +3,59 @@ - Chrome extension + Real ChatGPT Extension Settings
-

Great!

-

Your extension is ready
you can go to https://developer.chrome.com/docs/extensions/ to learn more about chrome extensions

+

Real ChatGPT Extension Settings

+ + + + + +
+ + +
+ + + \ No newline at end of file diff --git a/popup.js b/popup.js index e69de29..223d011 100644 --- a/popup.js +++ b/popup.js @@ -0,0 +1,177 @@ +document.addEventListener("DOMContentLoaded", () => { + // Default menu items + const defaultMenuItems = [ + { + id: "improveMail", + title: "Improve Email", + prompt: + "Improve the following email. Do not add any personal comments, only show the text as output:", + model: "gpt-4o", + }, + { + id: "improveText", + title: "Improve Text", + prompt: + "Improve the following text without making it much longer or shorter than it already is. Do not add any personal comments, only show the text as output:", + model: "gpt-4o", + }, + { + id: "seoText", + title: "Create an SEO Text", + prompt: + "You are the world’s best SEO text author. Write an SEO-optimized text of 400–500 words based on the following content. Identify the main keyword and integrate it with a keyword density of 2–3%. Structure the text with HTML elements (h1, h2, h3, p). Highlight the keyword with the strong tag. Only output the HTML code, without HTML, head, or body tags, and without comments. The output should appear as plain text, not in a code block. Here is the relevant text/keyword:", + model: "gpt-4o", + }, + { + id: "explainMe", + title: "Explain Text", + prompt: + "Explain the following text to me, summarize it briefly and in a readable way. Do not add any personal comments, only show the text as output:", + model: "gpt-4o", + }, + { + id: "fixCode", + title: "Fix Code", + prompt: + "Improve or fix the following code snippet. Provide the corrected code without adding any personal comments. Only output the corrected code:", + model: "gpt-4o", + }, + ]; + + // DOM Elements + const menuItemsContainer = document.getElementById("menuItems"); + const addMenuItemBtn = document.getElementById("addMenuItem"); + const saveSettingsBtn = document.getElementById("saveSettings"); + const resetSettingsBtn = document.getElementById("resetSettings"); + const menuItemTemplate = document.getElementById("menuItemTemplate"); + + // Load settings from storage + function loadSettings() { + chrome.storage.sync.get(["menuItems"], (result) => { + let menuItems = result.menuItems || defaultMenuItems; + renderMenuItems(menuItems); + }); + } + + // Save settings to storage + function saveSettings() { + const menuItems = []; + const menuItemElements = document.querySelectorAll(".menu-item"); + + menuItemElements.forEach((item) => { + const id = item.querySelector(".menu-id").value.trim(); + const title = item.querySelector(".menu-title").value.trim(); + const prompt = item.querySelector(".menu-prompt").value.trim(); + const model = item.querySelector(".menu-model").value; + + if (id && title && prompt) { + menuItems.push({ + id, + title, + prompt, + model, + }); + } + }); + + chrome.storage.sync.set({ menuItems }, () => { + // Update context menus + chrome.runtime.sendMessage({ action: "updateContextMenus" }); + + // Show save confirmation + const saveBtn = document.getElementById("saveSettings"); + const originalText = saveBtn.textContent; + saveBtn.textContent = "Saved!"; + saveBtn.disabled = true; + + setTimeout(() => { + saveBtn.textContent = originalText; + saveBtn.disabled = false; + }, 1500); + }); + } + + // Reset settings to default + function resetSettings() { + if (confirm("Are you sure you want to reset all settings to default?")) { + chrome.storage.sync.set({ menuItems: defaultMenuItems }, () => { + renderMenuItems(defaultMenuItems); + chrome.runtime.sendMessage({ action: "updateContextMenus" }); + }); + } + } + + // Render menu items in the UI + function renderMenuItems(menuItems) { + menuItemsContainer.innerHTML = ""; + + menuItems.forEach((item, index) => { + const menuItemElement = createMenuItemElement(item); + menuItemsContainer.appendChild(menuItemElement); + }); + } + + // Create a menu item element + function createMenuItemElement(item = {}) { + const fragment = document.importNode(menuItemTemplate.content, true); + const menuItem = fragment.querySelector(".menu-item"); + + // Set values if provided + const titleElement = menuItem.querySelector(".menu-item-title"); + const idInput = menuItem.querySelector(".menu-id"); + const titleInput = menuItem.querySelector(".menu-title"); + const promptInput = menuItem.querySelector(".menu-prompt"); + const modelSelect = menuItem.querySelector(".menu-model"); + + if (item.id) { + idInput.value = item.id; + titleElement.textContent = item.title || "Menu Item"; + } + + if (item.title) titleInput.value = item.title; + if (item.prompt) promptInput.value = item.prompt; + if (item.model) modelSelect.value = item.model; + + // Toggle expand/collapse + const toggleBtn = menuItem.querySelector(".menu-item-header"); + toggleBtn.addEventListener("click", () => { + menuItem.classList.toggle("expanded"); + toggleBtn.querySelector(".toggle-btn").textContent = + menuItem.classList.contains("expanded") ? "▲" : "▼"; + }); + + // Delete button + const deleteBtn = menuItem.querySelector(".delete-menu-item"); + deleteBtn.addEventListener("click", () => { + if (confirm("Are you sure you want to delete this menu item?")) { + menuItem.remove(); + } + }); + + // Update title when changed + titleInput.addEventListener("input", () => { + titleElement.textContent = titleInput.value || "Menu Item"; + }); + + return menuItem; + } + + // Add new menu item + function addMenuItem() { + const newItem = createMenuItemElement(); + menuItemsContainer.appendChild(newItem); + + // Expand the new item and scroll to it + newItem.classList.add("expanded"); + newItem.querySelector(".toggle-btn").textContent = "▲"; + newItem.scrollIntoView({ behavior: "smooth" }); + } + + // Event listeners + addMenuItemBtn.addEventListener("click", addMenuItem); + saveSettingsBtn.addEventListener("click", saveSettings); + resetSettingsBtn.addEventListener("click", resetSettings); + + // Initialize + loadSettings(); +});