From 379b08a310108378184c4dc545b987f116fd7731 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Fri, 30 Jun 2023 22:15:46 -0700 Subject: [PATCH 01/38] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20fix=20for=20arkose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 6 +- scripts/content/api.js | 30 - scripts/content/arkose.js | 19 + scripts/content/conversation.js | 5 +- scripts/content/conversationList.js | 586 ++-- scripts/content/global.js | 24 + scripts/content/initialize.js | 9 +- scripts/content/modelSwitcher.js | 9 +- scripts/content/settings.js | 2 +- .../api.js | 2443 +++++++++++++++++ 10 files changed, 2813 insertions(+), 320 deletions(-) create mode 100644 scripts/content/arkose.js create mode 100644 v2/35536E1E-65B4-4D96-9D97-6ADB7EFF8147/api.js diff --git a/manifest.json b/manifest.json index dea9b28..cf77a26 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "3.9.0", + "version": "4.0.0", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { @@ -109,7 +109,9 @@ "https://chat.openai.com/*" ], "resources": [ - "icons/*" + "icons/*", + "scripts/content/*", + "v2/*" ] } ] diff --git a/scripts/content/api.js b/scripts/content/api.js index 85b182f..1f72255 100644 --- a/scripts/content/api.js +++ b/scripts/content/api.js @@ -12,25 +12,6 @@ chrome.storage.local.get(['environment'], (result) => { const defaultHeaders = { 'content-type': 'application/json', }; -function arkose() { - return chrome.storage.local.get(['arkose']).then((res) => fetch('https://tcr9i.chat.openai.com/fc/gt2/public_key/35536E1E-65B4-4D96-9D97-6ADB7EFF8147', { - headers: { - accept: '*/*', - 'accept-language': 'en-US,en;q=0.9,fa;q=0.8', - 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', - 'sec-ch-ua': '"Google Chrome";v="114", "Chromium";v="114", "Not-A.Brand";v="24"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"macOS"', - 'sec-fetch-dest': 'empty', - 'sec-fetch-mode': 'cors', - 'sec-fetch-site': 'same-origin', - }, - referrer: 'https://tcr9i.chat.openai.com/v2/1.5.2/enforcement.64b3a4e29686f93d52816249ecbf9857.html', - referrerPolicy: 'strict-origin-when-cross-origin', - body: `${res.arkose}&rnd=${Math.random()}`, - method: 'POST', - }).then((response) => response.json())); -} function generateChat(message, conversationId, messageId, parentMessageId, token, saveHistory = true, role = 'user', action = 'next') { return chrome.storage.local.get(['settings', 'enabledPluginIds']).then((res) => chrome.storage.sync.get(['auth_token']).then((result) => { const payload = { @@ -657,17 +638,6 @@ function getSponsor(version) { }, }).then((res) => res.json()); } -function getArkose(version) { - return fetch(`${API_URL}/gptx/arkose/`, { - method: 'GET', - headers: { - ...defaultHeaders, - }, - }).then((res) => res.json()) - .then((data) => { - chrome.storage.local.set({ arkose: data.token }); - }); -} function getPrompts(pageNumber, searchTerm, sortBy = 'recent', language = 'all', category = 'all') { // get user id from sync storage return chrome.storage.sync.get(['openai_id']).then((result) => { diff --git a/scripts/content/arkose.js b/scripts/content/arkose.js new file mode 100644 index 0000000..ae2e94c --- /dev/null +++ b/scripts/content/arkose.js @@ -0,0 +1,19 @@ +window.useArkoseSetupEnforcement = function (e) { + // console.warn('arkoseSetupEnforcement', e); + e.setConfig({ + selector: '#enforcement-trigger', + onCompleted(x) { + // console.warn('onCompleted', x); + window.localStorage.setItem('arkoseToken', x.token); + }, + onError(x) { + // console.warn('onError', x); + }, + onFailed(x) { + // console.warn('onFailed', x); + }, + onShown(x) { + // console.warn('onShown', x); + }, + }); +}; diff --git a/scripts/content/conversation.js b/scripts/content/conversation.js index c15d448..7e97c09 100644 --- a/scripts/content/conversation.js +++ b/scripts/content/conversation.js @@ -1,6 +1,6 @@ /* eslint-disable no-restricted-globals */ // eslint-disable-next-line no-unused-vars -/* global getConversation, submitChat, openSubmitPromptModal, initializeRegenerateResponseButton, toggleTextAreaElement, rowAssistant, rowUser, copyRichText, messageFeedback, openFeedbackModal, refreshConversations, initializeStopGeneratingResponseButton, chatStreamIsClosed:true, generateInstructions, isGenerating:true, scrolUpDetected:true, addScrollDetector, generateSuggestions */ +/* global getConversation, submitChat, openSubmitPromptModal, initializeRegenerateResponseButton, toggleTextAreaElement, rowAssistant, rowUser, copyRichText, messageFeedback, openFeedbackModal, refreshConversations, initializeStopGeneratingResponseButton, chatStreamIsClosed:true, generateInstructions, isGenerating:true, scrolUpDetected:true, addScrollDetector, generateSuggestions, addArkoseScript */ function addPinNav(sortedNodes) { chrome.storage.local.get(['settings'], (res) => { @@ -40,6 +40,9 @@ function updateModel(modelSlug, pluginIds = []) { }) => { const allModels = [...models, ...unofficialModels, ...customModels]; const selectedModel = allModels.find((m) => m.slug === modelSlug); + if (selectedModel.slug.includes('gpt-4')) { + addArkoseScript(); + } const pluginsDropdownWrapper = document.querySelector('#plugins-dropdown-wrapper-navbar'); if (pluginsDropdownWrapper) { if (selectedModel.slug.includes('plugins')) { diff --git a/scripts/content/conversationList.js b/scripts/content/conversationList.js index 95e2ec1..143be8a 100644 --- a/scripts/content/conversationList.js +++ b/scripts/content/conversationList.js @@ -1,6 +1,6 @@ /* eslint-disable no-restricted-globals */ // eslint-disable-next-line no-unused-vars -/* global markdown, markdownitSup, initializeNavbar, generateInstructions, generateChat, SSE, formatDate, loadConversation, resetSelection, katex, texmath, rowUser, rowAssistant, updateOrCreateConversation, replaceTextAreaElemet, highlight, isGenerating:true, disableTextInput:true, generateTitle, debounce, initializeRegenerateResponseButton, initializeStopGeneratingResponseButton, toggleTextAreaElement, showNewChatPage, chatStreamIsClosed:true, addCopyCodeButtonsEventListeners, addScrollDetector, scrolUpDetected:true, Sortable, updateInputCounter, addUserPromptToHistory, getGPT4CounterMessageCapWindow, createFolder, getConversationElementClassList, notSelectedClassList, selectedClassList, conversationActions, addCheckboxToConversationElement, createConversation, deleteConversation, handleQueryParams, addScrollButtons, updateTotalCounter, isWindows, loadSharedConversation, createTemplateWordsModal, arkose */ +/* global markdown, markdownitSup, initializeNavbar, generateInstructions, generateChat, SSE, formatDate, loadConversation, resetSelection, katex, texmath, rowUser, rowAssistant, updateOrCreateConversation, replaceTextAreaElemet, highlight, isGenerating:true, disableTextInput:true, generateTitle, debounce, initializeRegenerateResponseButton, initializeStopGeneratingResponseButton, toggleTextAreaElement, showNewChatPage, chatStreamIsClosed:true, addCopyCodeButtonsEventListeners, addScrollDetector, scrolUpDetected:true, Sortable, updateInputCounter, addUserPromptToHistory, getGPT4CounterMessageCapWindow, createFolder, getConversationElementClassList, notSelectedClassList, selectedClassList, conversationActions, addCheckboxToConversationElement, createConversation, deleteConversation, handleQueryParams, addScrollButtons, updateTotalCounter, isWindows, loadSharedConversation, createTemplateWordsModal */ // Initial state let userChatIsActuallySaved = false; @@ -429,290 +429,304 @@ function updateNewChatButtonSynced() { }); } function submitChat(userInput, conversation, messageId, parentId, settings, models, continueGenerating = false, regenerateResponse = false) { - scrolUpDetected = false; - const curSubmitButton = document.querySelector('main').querySelector('form').querySelector('textarea ~ button'); - curSubmitButton.disabled = true; - curSubmitButton.innerHTML = ' '; - const syncDiv = document.getElementById('sync-div'); - if (syncDiv) syncDiv.style.opacity = '0.3'; - if (!regenerateResponse) initializeRegenerateResponseButton(); - chatStreamIsClosed = false; - let existingInnerHTML = ''; - let existingWordCount = 0; - let existingCharCount = 0; - if (continueGenerating) { - const incompleteAssistant = [...document.querySelectorAll('[id^="message-wrapper-"][data-role="assistant"]')].pop(); - existingInnerHTML = incompleteAssistant.querySelector('[id^=message-text-]').innerHTML; - existingWordCount = incompleteAssistant.querySelector('[id^=message-text-]').innerText.split(/[ /]/).length; - existingCharCount = incompleteAssistant.querySelector('[id^=message-text-]').innerText.length; - } - const saveHistory = conversation?.id ? conversation.saveHistory : settings.saveHistory; - arkose().then((arkoseRes) => generateChat(userInput, conversation?.id, messageId, parentId, arkoseRes.token, saveHistory, 'user', continueGenerating ? 'continue' : 'next').then((chatStream) => { - userChatIsActuallySaved = regenerateResponse || continueGenerating; - let userChatSavedLocally = regenerateResponse || continueGenerating; // false by default unless regenerateResponse is true - let assistantChatSavedLocally = false; - let finalMessage = ''; - let finalConversationId = ''; - let initialUserMessage = {}; - let systemMessage = {}; - chatStream.addEventListener('message', (e) => { - if (e.data === '[DONE]' || chatStreamIsClosed) { - const main = document.querySelector('main'); - const inputForm = main.querySelector('form'); - const submitButton = inputForm.querySelector('textarea ~ button'); - const textAreaElement = inputForm.querySelector('textarea'); - textAreaElement.focus(); - // submitButton.disabled = false; - submitButton.innerHTML = ''; - if (chatStreamIsClosed && e.data !== '[DONE]') { - const data = JSON.parse(e.data); - if (data.error) throw new Error(data.error); - const { conversation_id: conversationId, message } = data; - finalConversationId = conversationId; - finalMessage = message; - // reset splitter stuff - chunkNumber = 1; - totalChunks = 1; - remainingText = ''; - finalSummary = ''; - shouldSubmitFinalSummary = false; - // update rowAssistant? - } - const tempId = setInterval(() => { - if (userChatIsActuallySaved) { - clearInterval(tempId); - updateOrCreateConversation(finalConversationId, finalMessage, messageId, settings, true, chatStreamIsClosed).then(() => { - if (!chatStreamIsClosed) { - setTimeout(() => { - insertNextChunk(settings, finalMessage); - }, 700); - } - }); - } - }, 1000); - isGenerating = false; - chatStream.close(); - if (syncDiv) syncDiv.style.opacity = '1'; - toggleTextAreaElement(); - initializeStopGeneratingResponseButton(); - initializeRegenerateResponseButton(); - updateTotalCounter(); - } else if (e.event === 'ping') { - // console.error('PING RECEIVED', e); - } else { - try { - isGenerating = true; - if (finalMessage === '') { - const pluginDropdownButton = document.querySelector('#navbar-plugins-dropdown-button'); - if (pluginDropdownButton) { - pluginDropdownButton.disabled = true; - pluginDropdownButton.style.opacity = 0.75; - pluginDropdownButton.title = 'Changing plugins in the middle of the conversation is not allowed'; + // check window. localstorage every 200ms until arkoseToken is set + let arkoseToken; + const startTime = Date.now(); + const interval = setInterval(() => { + arkoseToken = window.localStorage.getItem('arkoseToken'); + if (Date.now() - startTime > 30000) { + clearInterval(interval); + return; + } + if (arkoseToken || !settings.selectedModel.slug.includes('gpt-4')) { + clearInterval(interval); + + scrolUpDetected = false; + const curSubmitButton = document.querySelector('main').querySelector('form').querySelector('textarea ~ button'); + curSubmitButton.disabled = true; + curSubmitButton.innerHTML = ' '; + const syncDiv = document.getElementById('sync-div'); + if (syncDiv) syncDiv.style.opacity = '0.3'; + if (!regenerateResponse) initializeRegenerateResponseButton(); + chatStreamIsClosed = false; + let existingInnerHTML = ''; + let existingWordCount = 0; + let existingCharCount = 0; + if (continueGenerating) { + const incompleteAssistant = [...document.querySelectorAll('[id^="message-wrapper-"][data-role="assistant"]')].pop(); + existingInnerHTML = incompleteAssistant.querySelector('[id^=message-text-]').innerHTML; + existingWordCount = incompleteAssistant.querySelector('[id^=message-text-]').innerText.split(/[ /]/).length; + existingCharCount = incompleteAssistant.querySelector('[id^=message-text-]').innerText.length; + } + const saveHistory = conversation?.id ? conversation.saveHistory : settings.saveHistory; + generateChat(userInput, conversation?.id, messageId, parentId, arkoseToken, saveHistory, 'user', continueGenerating ? 'continue' : 'next').then((chatStream) => { + userChatIsActuallySaved = regenerateResponse || continueGenerating; + let userChatSavedLocally = regenerateResponse || continueGenerating; // false by default unless regenerateResponse is true + let assistantChatSavedLocally = false; + let finalMessage = ''; + let finalConversationId = ''; + let initialUserMessage = {}; + let systemMessage = {}; + chatStream.addEventListener('message', (e) => { + if (e.data === '[DONE]' || chatStreamIsClosed) { + const main = document.querySelector('main'); + const inputForm = main.querySelector('form'); + const submitButton = inputForm.querySelector('textarea ~ button'); + const textAreaElement = inputForm.querySelector('textarea'); + textAreaElement.focus(); + // submitButton.disabled = false; + submitButton.innerHTML = ''; + if (chatStreamIsClosed && e.data !== '[DONE]') { + const data = JSON.parse(e.data); + if (data.error) throw new Error(data.error); + const { conversation_id: conversationId, message } = data; + finalConversationId = conversationId; + finalMessage = message; + // reset splitter stuff + chunkNumber = 1; + totalChunks = 1; + remainingText = ''; + finalSummary = ''; + shouldSubmitFinalSummary = false; + // update rowAssistant? } - initializeStopGeneratingResponseButton(); - // update gpt4 counter - if (!continueGenerating) { - chrome.storage.local.get(['gpt4Timestamps', 'settings', 'conversationLimit'], (result) => { - const { gpt4Timestamps } = result; - if (!result.settings.selectedModel.tags.includes('gpt4') && result.settings.selectedModel.slug !== 'gpt-4') return; - const now = new Date().getTime(); - const gpt4CounterElement = document.querySelector('#gpt4-counter'); - gpt4CounterElement.style.display = result.settings.showGpt4Counter ? 'block' : 'none'; - const messageCap = result?.conversationLimit?.message_cap || 25; - const messageCapWindow = result?.conversationLimit?.message_cap_window || 180; - if (gpt4Timestamps) { - gpt4Timestamps.push(now); - const hoursAgo = now - (messageCapWindow / 60) * 60 * 60 * 1000; - const gpt4TimestampsFiltered = gpt4Timestamps.filter((timestamp) => timestamp > hoursAgo); - chrome.storage.local.set({ gpt4Timestamps: gpt4TimestampsFiltered, capExpiresAt: '' }); - if (gpt4CounterElement) { - gpt4CounterElement.innerText = `GPT4 requests (last ${getGPT4CounterMessageCapWindow(messageCapWindow)}): ${gpt4TimestampsFiltered.length}/${messageCap}`; - } - } else { - chrome.storage.local.set({ gpt4Timestamps: [now] }); - if (gpt4CounterElement) { - gpt4CounterElement.innerText = `GPT4 requests (last ${getGPT4CounterMessageCapWindow(messageCapWindow)}): 1/${messageCap}`; + const tempId = setInterval(() => { + if (userChatIsActuallySaved) { + clearInterval(tempId); + updateOrCreateConversation(finalConversationId, finalMessage, messageId, settings, true, chatStreamIsClosed).then(() => { + if (!chatStreamIsClosed) { + setTimeout(() => { + insertNextChunk(settings, finalMessage); + }, 700); } + }); + } + }, 1000); + isGenerating = false; + chatStream.close(); + if (syncDiv) syncDiv.style.opacity = '1'; + toggleTextAreaElement(); + initializeStopGeneratingResponseButton(); + initializeRegenerateResponseButton(); + updateTotalCounter(); + } else if (e.event === 'ping') { + // console.error('PING RECEIVED', e); + } else { + try { + isGenerating = true; + if (finalMessage === '') { + const pluginDropdownButton = document.querySelector('#navbar-plugins-dropdown-button'); + if (pluginDropdownButton) { + pluginDropdownButton.disabled = true; + pluginDropdownButton.style.opacity = 0.75; + pluginDropdownButton.title = 'Changing plugins in the middle of the conversation is not allowed'; } - }); - } - } + initializeStopGeneratingResponseButton(); + // update gpt4 counter + if (!continueGenerating) { + chrome.storage.local.get(['gpt4Timestamps', 'settings', 'conversationLimit'], (result) => { + const { gpt4Timestamps } = result; + if (!result.settings.selectedModel.tags.includes('gpt4') && result.settings.selectedModel.slug !== 'gpt-4') return; + const now = new Date().getTime(); + const gpt4CounterElement = document.querySelector('#gpt4-counter'); + gpt4CounterElement.style.display = result.settings.showGpt4Counter ? 'block' : 'none'; + const messageCap = result?.conversationLimit?.message_cap || 25; + const messageCapWindow = result?.conversationLimit?.message_cap_window || 180; + if (gpt4Timestamps) { + gpt4Timestamps.push(now); + const hoursAgo = now - (messageCapWindow / 60) * 60 * 60 * 1000; + const gpt4TimestampsFiltered = gpt4Timestamps.filter((timestamp) => timestamp > hoursAgo); + chrome.storage.local.set({ gpt4Timestamps: gpt4TimestampsFiltered, capExpiresAt: '' }); + if (gpt4CounterElement) { + gpt4CounterElement.innerText = `GPT4 requests (last ${getGPT4CounterMessageCapWindow(messageCapWindow)}): ${gpt4TimestampsFiltered.length}/${messageCap}`; + } + } else { + chrome.storage.local.set({ gpt4Timestamps: [now] }); + if (gpt4CounterElement) { + gpt4CounterElement.innerText = `GPT4 requests (last ${getGPT4CounterMessageCapWindow(messageCapWindow)}): 1/${messageCap}`; + } + } + }); + } + } - const data = JSON.parse(e.data); + const data = JSON.parse(e.data); - if (data.error) throw new Error(data.error); - const { conversation_id: conversationId, message } = data; - const { role } = message.author; - const { recipient } = message; + if (data.error) throw new Error(data.error); + const { conversation_id: conversationId, message } = data; + const { role } = message.author; + const { recipient } = message; - finalConversationId = conversationId; - const { pathname } = new URL(window.location.toString()); - const urlConversationId = pathname.split('/').pop().replace(/[^a-z0-9-]/gi, ''); - if (pathname === '/') { // https://chat.openai.com/ - // only change url if there are any user messages. if user switch to new page while generating, don't change url when done generating - const anyUserMessageWrappers = document.querySelectorAll('[id^="message-wrapper-"][data-role="user"]').length > 0; - if (anyUserMessageWrappers) { - window.history.pushState({}, '', `https://chat.openai.com/c/${finalConversationId}`); - } - } - // save user chat locally - if (!conversation?.id) { - if (role === 'system') { - systemMessage = message; - return; - } - if (role === 'user') { - initialUserMessage = message; - initialUserMessage.metadata = { ...initialUserMessage.metadata, model_slug: settings.selectedModel.slug }; - // set forcerefresh=true when adding user chat, and set it to false when stream ends. This way if something goes wrong in between, the conversation will be refreshed later - updateOrCreateConversation(finalConversationId, initialUserMessage, parentId, settings, false, true, systemMessage); - return; - } - } else if (!userChatSavedLocally) { - const userMessage = { - id: messageId, - role: 'user', - content: { - content_type: 'text', - parts: [userInput], - }, - metadata: { model_slug: settings.selectedModel.slug }, - }; + finalConversationId = conversationId; + const { pathname } = new URL(window.location.toString()); + const urlConversationId = pathname.split('/').pop().replace(/[^a-z0-9-]/gi, ''); + if (pathname === '/') { // https://chat.openai.com/ + // only change url if there are any user messages. if user switch to new page while generating, don't change url when done generating + const anyUserMessageWrappers = document.querySelectorAll('[id^="message-wrapper-"][data-role="user"]').length > 0; + if (anyUserMessageWrappers) { + window.history.pushState({}, '', `https://chat.openai.com/c/${finalConversationId}`); + } + } + // save user chat locally + if (!conversation?.id) { + if (role === 'system') { + systemMessage = message; + return; + } + if (role === 'user') { + initialUserMessage = message; + initialUserMessage.metadata = { ...initialUserMessage.metadata, model_slug: settings.selectedModel.slug }; + // set forcerefresh=true when adding user chat, and set it to false when stream ends. This way if something goes wrong in between, the conversation will be refreshed later + updateOrCreateConversation(finalConversationId, initialUserMessage, parentId, settings, false, true, systemMessage); + return; + } + } else if (!userChatSavedLocally) { + const userMessage = { + id: messageId, + role: 'user', + content: { + content_type: 'text', + parts: [userInput], + }, + metadata: { model_slug: settings.selectedModel.slug }, + }; - // set forcerefresh=true when adding user chat, and set it to false when stream ends. This way if something goes wrong in between, the conversation will be refreshed later - updateOrCreateConversation(finalConversationId, userMessage, parentId, settings, false, true); - userChatSavedLocally = true; - } - if (!conversation?.id || userChatSavedLocally) { - // save assistant chat locally - finalMessage = message; - if (!assistantChatSavedLocally && message.author.role === 'assistant' && message.recipient === 'all') { - assistantChatSavedLocally = true; - const tempId = setInterval(() => { - if (userChatIsActuallySaved) { - clearInterval(tempId); - updateOrCreateConversation(finalConversationId, finalMessage, messageId, settings); + // set forcerefresh=true when adding user chat, and set it to false when stream ends. This way if something goes wrong in between, the conversation will be refreshed later + updateOrCreateConversation(finalConversationId, userMessage, parentId, settings, false, true); + userChatSavedLocally = true; + } + if (!conversation?.id || userChatSavedLocally) { + // save assistant chat locally + finalMessage = message; + if (!assistantChatSavedLocally && message.author.role === 'assistant' && message.recipient === 'all') { + assistantChatSavedLocally = true; + const tempId = setInterval(() => { + if (userChatIsActuallySaved) { + clearInterval(tempId); + updateOrCreateConversation(finalConversationId, finalMessage, messageId, settings); + } + }, 1000); } - }, 1000); - } - } + } - // if user switch conv while generating, dont show the assistant row until the user switch back to the original conv - if (finalConversationId !== urlConversationId) return; + // if user switch conv while generating, dont show the assistant row until the user switch back to the original conv + if (finalConversationId !== urlConversationId) return; - if (role !== 'assistant' && role !== 'user') return; - if (recipient !== 'all') return; + if (role !== 'assistant' && role !== 'user') return; + if (recipient !== 'all') return; - const lastRowAssistant = [...document.querySelectorAll('[id^="message-wrapper-"][data-role="assistant"]')].pop(); - const existingRowAssistant = continueGenerating ? lastRowAssistant : document.querySelector(`[id="message-wrapper-${message.id}"][data-role="assistant"]`); + const lastRowAssistant = [...document.querySelectorAll('[id^="message-wrapper-"][data-role="assistant"]')].pop(); + const existingRowAssistant = continueGenerating ? lastRowAssistant : document.querySelector(`[id="message-wrapper-${message.id}"][data-role="assistant"]`); - if (existingRowAssistant) { - if (!scrolUpDetected) { - document.querySelector('#conversation-bottom').scrollIntoView(); - } + if (existingRowAssistant) { + if (!scrolUpDetected) { + document.querySelector('#conversation-bottom').scrollIntoView(); + } - const existingRowAssistantTextWrapper = existingRowAssistant.querySelector('[id^=message-text-]'); + const existingRowAssistantTextWrapper = existingRowAssistant.querySelector('[id^=message-text-]'); - const resultCounter = existingRowAssistant.querySelector('[id^=result-counter-]'); - const searchValue = document.querySelector('#conversation-search')?.value; - let messageContentParts = searchValue ? highlight(finalMessage.content.parts.join('\n'), searchValue) : finalMessage.content.parts.join('\n'); - const { citations } = finalMessage.metadata; - if (citations?.length > 0) { - citations.reverse().forEach((citation, index) => { - const startIndex = citation.start_ix; - const endIndex = citation.end_ix; - const citationMetadata = citation.metadata; - const { url } = citationMetadata; - // number 1 with link to url - let citationText = `[^1^](${url})`; - if (endIndex === citations[index - 1]?.start_ix) { - citationText = ''; - } + const resultCounter = existingRowAssistant.querySelector('[id^=result-counter-]'); + const searchValue = document.querySelector('#conversation-search')?.value; + let messageContentParts = searchValue ? highlight(finalMessage.content.parts.join('\n'), searchValue) : finalMessage.content.parts.join('\n'); + const { citations } = finalMessage.metadata; + if (citations?.length > 0) { + citations.reverse().forEach((citation, index) => { + const startIndex = citation.start_ix; + const endIndex = citation.end_ix; + const citationMetadata = citation.metadata; + const { url } = citationMetadata; + // number 1 with link to url + let citationText = `[^1^](${url})`; + if (endIndex === citations[index - 1]?.start_ix) { + citationText = ''; + } - messageContentParts = messageContentParts.replace(messageContentParts.substring(startIndex, endIndex), citationText); - }); - } + messageContentParts = messageContentParts.replace(messageContentParts.substring(startIndex, endIndex), citationText); + }); + } - const messageContentPartsHTML = markdown('assistant') - .use(markdownitSup) - .use(texmath, { - engine: katex, - delimiters: 'brackets', - katexOptions: { macros: { '\\RR': '\\mathbb{R}' } }, - }).render(messageContentParts); - const wordCount = messageContentParts.split(/[ /]/).length + existingWordCount; - const charCount = messageContentParts.replace(/\n/g, '').length + existingCharCount; + const messageContentPartsHTML = markdown('assistant') + .use(markdownitSup) + .use(texmath, { + engine: katex, + delimiters: 'brackets', + katexOptions: { macros: { '\\RR': '\\mathbb{R}' } }, + }).render(messageContentParts); + const wordCount = messageContentParts.split(/[ /]/).length + existingWordCount; + const charCount = messageContentParts.replace(/\n/g, '').length + existingCharCount; - existingRowAssistantTextWrapper.innerHTML = `${existingInnerHTML}${messageContentPartsHTML}`; + existingRowAssistantTextWrapper.innerHTML = `${existingInnerHTML}${messageContentPartsHTML}`; - resultCounter.innerHTML = `${charCount} chars / ${wordCount} words`; - } else { - const lastMessageWrapper = [...document.querySelectorAll('[id^="message-wrapper-"]')].pop(); - if (lastMessageWrapper?.dataset?.role !== 'assistant') { - const existingRowUser = document.querySelector(`[id="message-wrapper-${messageId}"][data-role="user"]`); - if (existingRowUser) { - let threadCount = Object.keys(conversation).length > 0 ? conversation?.mapping[messageId]?.children?.length || 1 : 1; - if (regenerateResponse) threadCount += 1; - const assistantRow = rowAssistant(conversation, data, threadCount, threadCount, models, settings.customConversationWidth, settings.conversationWidth); - const conversationBottom = document.querySelector('#conversation-bottom'); - conversationBottom.insertAdjacentHTML('beforebegin', assistantRow); - if (!scrolUpDetected) { - conversationBottom.scrollIntoView(); + resultCounter.innerHTML = `${charCount} chars / ${wordCount} words`; + } else { + const lastMessageWrapper = [...document.querySelectorAll('[id^="message-wrapper-"]')].pop(); + if (lastMessageWrapper?.dataset?.role !== 'assistant') { + const existingRowUser = document.querySelector(`[id="message-wrapper-${messageId}"][data-role="user"]`); + if (existingRowUser) { + let threadCount = Object.keys(conversation).length > 0 ? conversation?.mapping[messageId]?.children?.length || 1 : 1; + if (regenerateResponse) threadCount += 1; + const assistantRow = rowAssistant(conversation, data, threadCount, threadCount, models, settings.customConversationWidth, settings.conversationWidth); + const conversationBottom = document.querySelector('#conversation-bottom'); + conversationBottom.insertAdjacentHTML('beforebegin', assistantRow); + if (!scrolUpDetected) { + conversationBottom.scrollIntoView(); + } + } } } + // addCopyCodeButtonsEventListeners(); + } catch (err) { + syncDiv.style.opacity = '1'; + // if (err.message === 'Unexpected end of JSON input') { + // } } } - // addCopyCodeButtonsEventListeners(); - } catch (err) { + }); + chatStream.addEventListener('error', (err) => { + isGenerating = false; + chunkNumber = 1; + totalChunks = 1; + remainingText = ''; + finalSummary = ''; + shouldSubmitFinalSummary = false; syncDiv.style.opacity = '1'; - // if (err.message === 'Unexpected end of JSON input') { - // } - } - } - }); - chatStream.addEventListener('error', (err) => { - isGenerating = false; - chunkNumber = 1; - totalChunks = 1; - remainingText = ''; - finalSummary = ''; - shouldSubmitFinalSummary = false; - syncDiv.style.opacity = '1'; - const main = document.querySelector('main'); - const inputForm = main.querySelector('form'); - const submitButton = inputForm.querySelector('textarea ~ button'); - // submitButton.disabled = false; - submitButton.innerHTML = ''; - console.warn(err); - if (err.data) { - const error = JSON.parse(err.data); - const errorCode = error?.detail?.code; - let errorMessage = typeof error.detail === 'string' ? error.detail : error.detail.message; - if (errorCode === 'model_cap_exceeded') { - // seconds until cap is cleared - const clearsIn = error?.detail?.clears_in; - const date = new Date(); - date.setSeconds(date.getSeconds() + clearsIn); - // print expire hour minute from local time - const hour = date.getHours(); - const minute = date.getMinutes(); - const ampm = hour >= 12 ? 'pm' : 'am'; - const hour12 = hour % 12; - const hour12Display = hour12 || 12; - const minuteDisplay = minute < 10 ? `0${minute}` : minute; - const capExpiresAt = `${hour12Display}:${minuteDisplay}${ampm}`; - chrome.storage.local.set({ capExpiresAt }); - errorMessage = `You've reached the current usage cap for this model. You can continue with the default model now, or try again after ${capExpiresAt}.`; - } else { - chrome.storage.local.set({ capExpiresAt: '' }); - } - const conversationBottom = document.querySelector('#conversation-bottom'); - const errorMessageElement = `
${errorMessage}
`; - conversationBottom.insertAdjacentHTML('beforebegin', errorMessageElement); - conversationBottom.scrollIntoView({ behavior: 'smooth' }); - } - }); - })); + const main = document.querySelector('main'); + const inputForm = main.querySelector('form'); + const submitButton = inputForm.querySelector('textarea ~ button'); + // submitButton.disabled = false; + submitButton.innerHTML = ''; + console.warn(err); + if (err.data) { + const error = JSON.parse(err.data); + const errorCode = error?.detail?.code; + let errorMessage = typeof error.detail === 'string' ? error.detail : error.detail.message; + if (errorCode === 'model_cap_exceeded') { + // seconds until cap is cleared + const clearsIn = error?.detail?.clears_in; + const date = new Date(); + date.setSeconds(date.getSeconds() + clearsIn); + // print expire hour minute from local time + const hour = date.getHours(); + const minute = date.getMinutes(); + const ampm = hour >= 12 ? 'pm' : 'am'; + const hour12 = hour % 12; + const hour12Display = hour12 || 12; + const minuteDisplay = minute < 10 ? `0${minute}` : minute; + const capExpiresAt = `${hour12Display}:${minuteDisplay}${ampm}`; + chrome.storage.local.set({ capExpiresAt }); + errorMessage = `You've reached the current usage cap for this model. You can continue with the default model now, or try again after ${capExpiresAt}.`; + } else { + chrome.storage.local.set({ capExpiresAt: '' }); + } + const conversationBottom = document.querySelector('#conversation-bottom'); + const errorMessageElement = `
${errorMessage}
`; + conversationBottom.insertAdjacentHTML('beforebegin', errorMessageElement); + conversationBottom.scrollIntoView({ behavior: 'smooth' }); + } + }); + }); + } + }, 200); } function submitFinalSummary() { if (!shouldSubmitFinalSummary) return; @@ -791,12 +805,16 @@ function overrideSubmitForm() { const inputForm = main.querySelector('form'); if (!inputForm) return; inputForm.addEventListener('submit', (e) => { + window.localStorage.removeItem('arkoseToken'); const textAreaElement = inputForm.querySelector('textarea'); e.preventDefault(); e.stopPropagation(); if (isGenerating) return; // get all words wrapped in {{ and }} chrome.storage.local.get(['settings'], ({ settings }) => { + if (settings.selectedModel.slug.includes('gpt-4')) { + inputForm.querySelector('#enforcement-trigger').click(); + } const templateWords = textAreaElement.value.match(/{{(.*?)}}/g); if (settings.promptTemplate && templateWords?.length > 0) { // open template words modal and wait for user to select a word. the when user submit, submit the input form with the replacement @@ -964,27 +982,33 @@ ${settings.autoSplitChunkPrompt}`; const submitButtonClone = submitButton.cloneNode(true); submitButtonClone.type = 'button'; submitButtonClone.addEventListener('click', () => { - const textAreaElement = inputForm.querySelector('textarea'); - if (isGenerating) return; - const templateWords = textAreaElement.value.match(/{{(.*?)}}/g); - if (templateWords?.length > 0) { - // open template words modal and wait for user to select a word. the when user submit, submit the input form with the replacement - createTemplateWordsModal(templateWords); - const firstTemplateWordInput = document.querySelector('[id^=template-input-]'); - if (firstTemplateWordInput) { - firstTemplateWordInput.focus(); - setTimeout(() => { - firstTemplateWordInput.value = ''; - }, 100); + window.localStorage.removeItem('arkoseToken'); + chrome.storage.local.get(['settings'], ({ settings }) => { + if (settings.selectedModel.slug.includes('gpt-4')) { + inputForm.querySelector('#enforcement-trigger').click(); } - } else { - if (textAreaElement.value.trim().length === 0) return; - textAreaElement.style.height = '24px'; - addUserPromptToHistory(textAreaElement.value.trim()); - inputForm.dispatchEvent(new Event('submit', { cancelable: true })); - } + const textAreaElement = inputForm.querySelector('textarea'); + if (isGenerating) return; + const templateWords = textAreaElement.value.match(/{{(.*?)}}/g); + if (templateWords?.length > 0) { + // open template words modal and wait for user to select a word. the when user submit, submit the input form with the replacement + createTemplateWordsModal(templateWords); + const firstTemplateWordInput = document.querySelector('[id^=template-input-]'); + if (firstTemplateWordInput) { + firstTemplateWordInput.focus(); + setTimeout(() => { + firstTemplateWordInput.value = ''; + }, 100); + } + } else { + if (textAreaElement.value.trim().length === 0) return; + textAreaElement.style.height = '24px'; + addUserPromptToHistory(textAreaElement.value.trim()); + inputForm.dispatchEvent(new Event('submit', { cancelable: true })); + } + }); + submitButton.parentNode.replaceChild(submitButtonClone, submitButton); }); - submitButton.parentNode.replaceChild(submitButtonClone, submitButton); } function setBackButtonDetection() { diff --git a/scripts/content/global.js b/scripts/content/global.js index f5ceec5..dc9d95e 100644 --- a/scripts/content/global.js +++ b/scripts/content/global.js @@ -317,6 +317,30 @@ function handleQueryParams(query) { }); } } +function addArkoseCallback() { + const script = document.createElement('script'); + script.setAttribute('type', 'text/javascript'); + script.setAttribute('src', chrome.runtime.getURL('scripts/content/arkose.js')); + document.body.appendChild(script); +} +function addArkoseScript() { + // check if a script element with src including api.js and chrome-extension exists + const arkoseScript = document.querySelector('script[src*="chrome-extension"][src*="api.js"]'); + if (arkoseScript) return; + const arkoseApiScript = document.createElement('script'); + arkoseApiScript.async = !0; + arkoseApiScript.defer = !0; + arkoseApiScript.setAttribute('type', 'text/javascript'); + arkoseApiScript.setAttribute('data-status', 'loading'); + arkoseApiScript.setAttribute('data-callback', 'useArkoseSetupEnforcement'); + arkoseApiScript.setAttribute('src', chrome.runtime.getURL('v2/35536E1E-65B4-4D96-9D97-6ADB7EFF8147/api.js')); + document.body.appendChild(arkoseApiScript); +} +function addEnforcementTriggerElement() { + const main = document.querySelector('main'); + const inputForm = main.querySelector('form'); + inputForm.firstChild.insertAdjacentHTML('beforeend', ''); +} function replaceTextAreaElemet(settings) { const main = document.querySelector('main'); if (!main) { return false; } diff --git a/scripts/content/initialize.js b/scripts/content/initialize.js index bd2d2ca..6f0c6a1 100644 --- a/scripts/content/initialize.js +++ b/scripts/content/initialize.js @@ -1,14 +1,15 @@ -/* global getAccount, getModels, getConversationLimit, initializeStorage, cleanNav, initializeContinue, initializeExport, initializeSettings, initializePromptHistory, initializePromptLibrary, initializeNewsletter, initializeAutoSave, addNavToggleButton, initializeAnnouncement, initializeReleaseNote, initializeReplaceDeleteConversationButton, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, updateNewChatButtonNotSynced, addAsyncInputEvents, initializeContentMessageListeners, registerShortkeys, addDevIndicator, addExpandButton, openLinksInNewTab, getArkose */ +/* global getAccount, getModels, getConversationLimit, initializeStorage, cleanNav, initializeContinue, initializeExport, initializeSettings, initializePromptHistory, initializePromptLibrary, initializeNewsletter, initializeAutoSave, addNavToggleButton, initializeAnnouncement, initializeReleaseNote, initializeReplaceDeleteConversationButton, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, updateNewChatButtonNotSynced, addAsyncInputEvents, initializeContentMessageListeners, registerShortkeys, addDevIndicator, addExpandButton, openLinksInNewTab, addEnforcementTriggerElement, addArkoseCallback */ // eslint-disable-next-line no-unused-vars function initialize() { const historyButton = document.querySelector('a[id="my-prompt-history-button"]'); if (window.location.pathname.startsWith('/share/') && !window.location.pathname.endsWith('/continue')) return; + if (!historyButton) { setTimeout(() => { initializeStorage().then(() => { registerShortkeys(); - getArkose(); + addEnforcementTriggerElement(); getAccount(); getModels(); getConversationLimit(); @@ -26,11 +27,13 @@ function initialize() { initializePromptHistory(); addExpandButton(); addDevIndicator(); + addArkoseCallback(); + setTimeout(() => { chrome.storage.local.get(['settings'], (result) => { const { settings } = result; if ((typeof settings?.autoSync === 'undefined' || settings?.autoSync) && !window.location.pathname.startsWith('/share/')) { - // if (typeof settings?.autoSync === 'undefined' || settings?.autoSync) { + // if (typeof settings?.autoSync === 'undefined' || settings?.autoSync) { initializeAutoSave(); } else { initializeCopyAndCounter(); diff --git a/scripts/content/modelSwitcher.js b/scripts/content/modelSwitcher.js index 0a9e236..017dd70 100644 --- a/scripts/content/modelSwitcher.js +++ b/scripts/content/modelSwitcher.js @@ -1,7 +1,10 @@ /* eslint-disable no-unused-vars */ -/* global getInstalledPlugins */ +/* global getInstalledPlugins, addArkoseScript */ // eslint-disable-next-line no-unused-vars function modelSwitcher(models, selectedModel, idPrefix, customModels, forceDark = false) { + if (selectedModel.slug.includes('gpt-4')) { + addArkoseScript(); + } return `'); } function replaceTextAreaElemet(settings) { diff --git a/scripts/content/initialize.js b/scripts/content/initialize.js index 6f0c6a1..9751a91 100644 --- a/scripts/content/initialize.js +++ b/scripts/content/initialize.js @@ -9,7 +9,6 @@ function initialize() { setTimeout(() => { initializeStorage().then(() => { registerShortkeys(); - addEnforcementTriggerElement(); getAccount(); getModels(); getConversationLimit(); @@ -27,6 +26,7 @@ function initialize() { initializePromptHistory(); addExpandButton(); addDevIndicator(); + addEnforcementTriggerElement(); addArkoseCallback(); setTimeout(() => { diff --git a/scripts/content/pluginsDropdown.js b/scripts/content/pluginsDropdown.js index 13de73f..391e30f 100644 --- a/scripts/content/pluginsDropdown.js +++ b/scripts/content/pluginsDropdown.js @@ -55,7 +55,7 @@ function createPluginsDropDown(installedPlugins, enabledPluginIds, idPrefix, for `} - `).join('')} `; + `).join('')}`; } function addPluginsDropdownEventListener(idPrefix, forceDark = false) { diff --git a/scripts/content/promptLibrary.js b/scripts/content/promptLibrary.js index 248ae6a..5ea4395 100644 --- a/scripts/content/promptLibrary.js +++ b/scripts/content/promptLibrary.js @@ -200,7 +200,7 @@ function promptLibraryListComponent(libraryData, loading = false) { libraryItem.appendChild(libraryItemText); const libraryItemCategories = document.createElement('div'); - libraryItemCategories.id = `library-item-categories-${libraryPrompt.id} `; + libraryItemCategories.id = `library-item-categories-${libraryPrompt.id}`; libraryItemCategories.style = 'display:flex; justify-content: flex-start; align-items:center; width: 100%; margin-top: 12px;'; // categories libraryPrompt.categories.forEach((category) => { @@ -223,18 +223,18 @@ function promptLibraryListComponent(libraryData, loading = false) { libraryItem.appendChild(libraryItemCategories); const libraryItemFooter = document.createElement('div'); - libraryItemFooter.id = `library-item-footer-${libraryPrompt.id} `; + libraryItemFooter.id = `library-item-footer-${libraryPrompt.id}`; libraryItemFooter.style = 'display:flex; justify-content: space-between; align-items:flex-end; width: 100%; white-space: break-spaces; overflow-wrap: break-word;margin-top: 8px'; // created by url const libraryItemInfoWrapper = document.createElement('span'); - libraryItemInfoWrapper.id = `library-item-created-by-${libraryPrompt.id} `; + libraryItemInfoWrapper.id = `library-item-created-by-${libraryPrompt.id}`; libraryItemInfoWrapper.style = 'display: flex; align-items:flex-end; justify-content: start; color: lightslategray; font-size:0.8em; width: 100%; white-space: break-spaces; overflow-wrap: break-word;'; const libraryItemCreatedBy = document.createElement('span'); - libraryItemCreatedBy.id = `library-item-created-by-${libraryPrompt.id} `; + libraryItemCreatedBy.id = `library-item-created-by-${libraryPrompt.id}`; libraryItemCreatedBy.textContent = 'by '; libraryItemInfoWrapper.appendChild(libraryItemCreatedBy); const libraryItemCreatedByUrl = document.createElement('a'); - libraryItemCreatedByUrl.id = `library-item-created-by-url-${libraryPrompt.id} `; + libraryItemCreatedByUrl.id = `library-item-created-by-url-${libraryPrompt.id}`; libraryItemCreatedByUrl.style = 'color: #919dd4; text-decoration:underline;max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;'; libraryItemCreatedByUrl.innerHTML = highlight(libraryPrompt.created_by?.nickname, promptLibrarySearchTerm); libraryItemCreatedByUrl.href = libraryPrompt.created_by?.url; @@ -253,7 +253,7 @@ function promptLibraryListComponent(libraryData, loading = false) { }); libraryItemInfoWrapper.appendChild(libraryItemCreatedByUrl); const libraryItemUseCount = document.createElement('span'); - libraryItemUseCount.id = `library-item-use-count-${libraryPrompt.id} `; + libraryItemUseCount.id = `library-item-use-count-${libraryPrompt.id}`; libraryItemUseCount.title = `used ${libraryPrompt.num_used} times`; libraryItemUseCount.style = 'display:flex;color: lightslategray; margin-left: 24px;'; libraryItemUseCount.innerHTML = ` ${libraryPrompt.num_used}`; @@ -262,7 +262,7 @@ function promptLibraryListComponent(libraryData, loading = false) { // votes const libraryItemVoteCount = document.createElement('span'); - libraryItemVoteCount.id = `library-item-vote-count-${libraryPrompt.id} `; + libraryItemVoteCount.id = `library-item-vote-count-${libraryPrompt.id}`; libraryItemVoteCount.title = `upvoted ${libraryPrompt.votes} times`; libraryItemVoteCount.style = 'display:flex;color: lightslategray; margin-left: 24px;'; libraryItemVoteCount.innerHTML = ` ${libraryPrompt.votes}`; @@ -271,7 +271,7 @@ function promptLibraryListComponent(libraryData, loading = false) { // Share const libraryItemShareButton = document.createElement('span'); - libraryItemShareButton.id = `library-item-share-button-${libraryPrompt.id} `; + libraryItemShareButton.id = `library-item-share-button-${libraryPrompt.id}`; libraryItemShareButton.title = 'Share this prompt'; libraryItemShareButton.style = 'display:flex;color: lightslategray; margin-left: 24px;position: relative;bottom:3px;cursor: pointer;'; libraryItemShareButton.innerHTML = ''; @@ -346,7 +346,7 @@ function promptLibraryListComponent(libraryData, loading = false) { if (e.target.textContent === 'Confirm') { deletePrompt(libraryPrompt.id); // remove the item from the list - const deletedLibraryItem = document.querySelector(`#library-item-${libraryPrompt.id} `); + const deletedLibraryItem = document.querySelector(`#library-item-${libraryPrompt.id}`); deletedLibraryItem.style.display = 'none'; } else { e.target.textContent = 'Confirm'; @@ -531,7 +531,7 @@ function promptLibraryModalContent(libraryData) { const pageNumberElement = document.createElement('span'); pageNumberElement.id = 'library-page-number'; pageNumberElement.style = 'color: lightslategray; font-size:0.8em; width: 100%; text-align: center;'; - pageNumberElement.textContent = `Page ${promptLibraryPageNumber} of ${promptLibraryMaxPageNumber} `; + pageNumberElement.textContent = `Page ${promptLibraryPageNumber} of ${promptLibraryMaxPageNumber}`; pageButtonsWrapper.appendChild(pageNumberElement); const previousPageButton = document.createElement('button'); @@ -557,7 +557,7 @@ function promptLibraryModalContent(libraryData) { previousPageButton.style.opacity = '1'; } fetchPrompts(promptLibraryPageNumber); - pageNumberElement.textContent = `Page ${promptLibraryPageNumber} of ${promptLibraryMaxPageNumber} `; + pageNumberElement.textContent = `Page ${promptLibraryPageNumber} of ${promptLibraryMaxPageNumber}`; }); pageButtonsWrapper.appendChild(previousPageButton); const nextPageButton = document.createElement('button'); @@ -576,7 +576,7 @@ function promptLibraryModalContent(libraryData) { nextPageButton.style.opacity = '0.5'; } fetchPrompts(promptLibraryPageNumber); - pageNumberElement.textContent = `Page ${promptLibraryPageNumber} of ${promptLibraryMaxPageNumber} `; + pageNumberElement.textContent = `Page ${promptLibraryPageNumber} of ${promptLibraryMaxPageNumber}`; }); pageButtonsWrapper.appendChild(nextPageButton); @@ -607,7 +607,7 @@ function updatePageButtons() { nextPageButton.style.opacity = 1; } const pageNumberElement = document.querySelector('span[id="library-page-number"]'); - pageNumberElement.textContent = `Page ${promptLibraryPageNumber} of ${promptLibraryMaxPageNumber} `; + pageNumberElement.textContent = `Page ${promptLibraryPageNumber} of ${promptLibraryMaxPageNumber}`; } function promptLibraryModalActions() { // add actionbar at the bottom of the content diff --git a/scripts/content/regenerateResponse.js b/scripts/content/regenerateResponse.js index d4f1944..52b989e 100644 --- a/scripts/content/regenerateResponse.js +++ b/scripts/content/regenerateResponse.js @@ -1,6 +1,6 @@ /* eslint-disable no-restricted-globals */ // eslint-disable-next-line no-unused-vars -/* global canSubmitPrompt, submitChat, toggleTextAreaElement, isGenerating:true */ +/* global canSubmitPrompt, submitChat, toggleTextAreaElement, isGenerating:true, addEnforcementTriggerElement */ function toggleOriginalRegenerateResponseButton() { const allMessageWrapper = document.querySelectorAll('[id^="message-wrapper-"]'); const lastMessageWrapperElement = allMessageWrapper[allMessageWrapper.length - 1]; @@ -55,7 +55,14 @@ function toggleOriginalRegenerateResponseButton() { newRegenerateResponseButton.classList = `btn flex justify-center gap-2 ${textAreaElementWrapper.style.display === 'none' ? 'btn-primary' : 'btn-neutral'} border-0 md:border`; newRegenerateResponseButton.innerHTML = ' Regenerate response'; newRegenerateResponseButton.addEventListener('click', () => { + window.localStorage.removeItem('arkoseToken'); chrome.storage.local.get(['conversations', 'settings', 'models'], (result) => { + if (result.settings.selectedModel.slug.includes('gpt-4')) { + if (!inputForm.querySelector('#enforcement-trigger')) { + addEnforcementTriggerElement(); + } + inputForm.querySelector('#enforcement-trigger').click(); + } const { pathname } = new URL(window.location.toString()); const conversationId = pathname.split('/').pop().replace(/[^a-z0-9-]/gi, ''); if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(conversationId)) return; @@ -94,7 +101,14 @@ function toggleOriginalRegenerateResponseButton() { newContinueGeneratingButton.classList = `btn flex justify-center gap-2 ${textAreaElementWrapper.style.display === 'none' ? 'btn-primary' : 'btn-neutral'} border-0 md:border`; newContinueGeneratingButton.innerHTML = ' Continue generating'; newContinueGeneratingButton.addEventListener('click', () => { + window.localStorage.removeItem('arkoseToken'); chrome.storage.local.get(['conversations', 'settings', 'models'], (result) => { + if (result.settings.selectedModel.slug.includes('gpt-4')) { + if (!inputForm.querySelector('#enforcement-trigger')) { + addEnforcementTriggerElement(); + } + inputForm.querySelector('#enforcement-trigger').click(); + } const { pathname } = new URL(window.location.toString()); const conversationId = pathname.split('/').pop().replace(/[^a-z0-9-]/gi, ''); if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(conversationId)) return; diff --git a/scripts/content/shareModal.js b/scripts/content/shareModal.js index cf1d498..166441f 100644 --- a/scripts/content/shareModal.js +++ b/scripts/content/shareModal.js @@ -257,7 +257,7 @@ function shareModal(conversation, shareData, name) { - `; +`; } function addShareModalEventListener(shareData, name) { const shareModalWrapper = document.getElementById('share-modal-wrapper'); From b1c1bc3aea5880ac8884b913e15a02530457f619 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Fri, 7 Jul 2023 22:33:23 -0700 Subject: [PATCH 03/38] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20plugin=20sea?= =?UTF-8?q?rch,=20bug=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/content/conversation.js | 5 ++-- scripts/content/conversationList.js | 2 +- scripts/content/pluginStore.js | 36 ++++++++++++++++++++++++++--- scripts/content/settings.js | 4 ++-- scripts/styles/global.css | 3 ++- 6 files changed, 42 insertions(+), 10 deletions(-) diff --git a/manifest.json b/manifest.json index fb73f47..eed772f 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "4.0.1", + "version": "4.0.2", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/content/conversation.js b/scripts/content/conversation.js index 9b49ff9..6965889 100644 --- a/scripts/content/conversation.js +++ b/scripts/content/conversation.js @@ -189,7 +189,8 @@ function loadConversation(conversationId, searchValue = '', focusOnInput = true) if (message.recipient === 'all' && (message.role === 'assistant' || message.author?.role === 'assistant')) { let nextMessage = sortedNodes[i + 1]?.message; while (nextMessage && nextMessage.recipient === 'all' && (nextMessage.role === 'assistant' || nextMessage.author?.role === 'assistant')) { - message.content.parts = [`${message.content.parts.join('')}${nextMessage.content.parts.join('')}`]; + message.content.parts.push(...nextMessage.content.parts); + // message.content.parts = [`${message.content.parts.join('')}${nextMessage.content.parts.join('')}`]; i += 1; nextMessage = sortedNodes[i + 1]?.message; } @@ -313,7 +314,7 @@ function addConversationsEventListeners(conversationId) { textArea.spellcheck = false; textArea.id = `message-text-${messageId}`; textArea.addEventListener('input', (e) => { - e.target.style.height = `${e.target.scrollHeight} px`; + e.target.style.height = `${e.target.scrollHeight}px`; }); oldElement.parentElement.replaceChild(textArea, oldElement); textArea.focus(); diff --git a/scripts/content/conversationList.js b/scripts/content/conversationList.js index 8385af0..ee2c849 100644 --- a/scripts/content/conversationList.js +++ b/scripts/content/conversationList.js @@ -1013,8 +1013,8 @@ ${settings.autoSplitChunkPrompt}`; inputForm.dispatchEvent(new Event('submit', { cancelable: true })); } }); - submitButton.parentNode.replaceChild(submitButtonClone, submitButton); }); + submitButton.parentNode.replaceChild(submitButtonClone, submitButton); } function setBackButtonDetection() { diff --git a/scripts/content/pluginStore.js b/scripts/content/pluginStore.js index 361df98..790ab6c 100644 --- a/scripts/content/pluginStore.js +++ b/scripts/content/pluginStore.js @@ -87,6 +87,20 @@ function initializePluginStoreModal(plugins) { Installed +
+
+ + + + +
+
+ +
+ +
+
+
`).join('')} -
${value}
`; }, }); - +function watchError() { + const targetNode = document.body; + const config = { childList: true, subtree: true }; + const callback = (mutationsList, observer) => { + mutationsList.forEach((mutation) => { + if (mutation.type === 'childList') { + // see if there is an h2 containing "Oops, an error occurred!" + const error = document.querySelector('h2')?.textContent === 'Oops, an error occurred!'; + if (error) { + chrome.storage.local.get('settings', ({ settings }) => { + chrome.storage.local.set({ + settings: { + ...settings, + autoSync: false, + }, + }, () => { + refreshPage(); + }); + }); + } + } + }); + }; + const observer = new MutationObserver(callback); + observer.observe(targetNode, config); +} +function showAutoSyncToast() { + chrome.storage.local.get('settings', ({ settings }) => { + const { autoSync } = settings; + if (autoSync) { + toast('Auto-sync is Enabled'); + } else { + toast('Auto-sync is Disabled'); + } + }); +} function escapeHtml(html) { return html .replace(/&/g, '&') @@ -161,7 +197,7 @@ function addScrollButtons() { scrollButtonWrapper.style = 'bottom: 6rem;right: 3rem;width: 2rem;height: 4rem;flex-wrap:wrap;'; const scrollUpButton = document.createElement('button'); scrollUpButton.id = 'scroll-up-button'; - scrollUpButton.innerHTML = ''; + scrollUpButton.innerHTML = ''; scrollUpButton.className = 'flex items-center justify-center border-black/10 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 hover:bg-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-900 dark:hover:text-gray-200 text-xs font-sans cursor-pointer rounded-t-md z-10'; scrollUpButton.style = 'width: 2rem;height: 2rem;border: 1px solid;'; scrollUpButton.addEventListener('click', () => { @@ -172,7 +208,7 @@ function addScrollButtons() { const scrollDownButton = document.createElement('button'); scrollDownButton.id = 'scroll-down-button'; - scrollDownButton.innerHTML = ''; + scrollDownButton.innerHTML = ''; scrollDownButton.className = 'flex items-center justify-center border-black/10 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 hover:bg-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-900 dark:hover:text-gray-200 text-xs font-sans cursor-pointer rounded-b-md z-10'; scrollDownButton.style = 'width: 2rem;height: 2rem;border: 1px solid; border-top: none;'; scrollDownButton.addEventListener('click', () => { @@ -186,6 +222,7 @@ function addScrollButtons() { scrollButtonWrapper.appendChild(scrollDownButton); document.body.appendChild(scrollButtonWrapper); } + function addNavToggleButton() { chrome.storage.local.get(['settings'], (result) => { const { settings } = result; @@ -199,15 +236,16 @@ function addNavToggleButton() { const navToggleButton = document.createElement('div'); navToggleButton.id = 'nav-toggle-button'; - navToggleButton.className = 'absolute flex items-center justify-center bg-gray-900 text-gray-200 text-xs font-sans cursor-pointer rounded-r-md z-50'; + navToggleButton.title = 'Hide/Show Sidebar (CMD/CTRL + ALT + H)'; + navToggleButton.className = 'absolute flex items-center justify-center bg-gray-900 text-gray-200 text-xs font-sans cursor-pointer rounded-r-md'; if (settings?.navOpen || settings?.navOpen === undefined) { - navToggleButton.style = 'width:16px;height:40px;right:-16px;bottom:0px;font-size:20px'; + navToggleButton.style = 'width:16px;height:40px;right:-16px;bottom:0px;font-size:20px;z-index: 100;'; navToggleButton.innerHTML = '‹'; } else { sidebar.style.marginLeft = '-260px'; mainContent.classList.replace('md:pl-[260px]', 'md:pl-0'); - navToggleButton.style = 'width:40px;height:40px;right:-40px;bottom:0px;font-size:20px'; + navToggleButton.style = 'width:40px;height:40px;right:-40px;bottom:0px;font-size:20px;z-index: 100;'; navToggleButton.innerHTML = '›'; } navToggleButton.addEventListener('click', () => { @@ -225,14 +263,14 @@ function addNavToggleButton() { const main = nav?.nextElementSibling; nav.style.marginLeft = '0px'; main.classList.replace('md:pl-0', 'md:pl-[260px]'); - curNavToggleBtn.style = 'width:16px;height:40px;right:-16px;bottom:0px;font-size:20px'; + curNavToggleBtn.style = 'width:16px;height:40px;right:-16px;bottom:0px;font-size:20px;z-index: 100;'; curNavToggleBtn.innerHTML = '‹'; } else { const nav = document.querySelector('.w-\\[260px\\]').parentElement; const main = nav?.nextElementSibling; nav.style.marginLeft = '-260px'; main.classList.replace('md:pl-[260px]', 'md:pl-0'); - curNavToggleBtn.style = 'width:40px;height:40px;right:-40px;bottom:0px;font-size:20px'; + curNavToggleBtn.style = 'width:40px;height:40px;right:-40px;bottom:0px;font-size:20px;z-index: 100;'; curNavToggleBtn.innerHTML = '›'; } }); @@ -242,10 +280,7 @@ function addNavToggleButton() { }); } function toggleTextAreaElement(forceShow = false) { - const main = document.querySelector('main'); - if (!main) return; - const inputForm = main.querySelector('form'); - const textAreaElement = inputForm.querySelector('textarea'); + const textAreaElement = document.querySelector('main form textarea'); if (!textAreaElement) return; const textAreaParent = textAreaElement.parentElement; const allMessageWrapper = document.querySelectorAll('[id^="message-wrapper-"]'); @@ -266,14 +301,34 @@ function toggleTextAreaElement(forceShow = false) { } function showNewChatPage() { // chatStreamIsClosed = true; - chrome.storage.local.get(['conversationsAreSynced', 'account'], (result) => { + chrome.storage.local.get(['conversationsAreSynced', 'account', 'settings'], (result) => { const pluginDropdownButton = document.querySelector('#navbar-plugins-dropdown-button'); if (pluginDropdownButton) { pluginDropdownButton.disabled = false; pluginDropdownButton.style.opacity = 1; pluginDropdownButton.title = ''; } - const { conversationsAreSynced, account } = result; + + const { conversationsAreSynced, account, settings } = result; + const { + selectedLanguage, selectedTone, selectedWritingStyle, autoClick, + } = settings; + chrome.storage.local.set({ + settings: { + ...settings, + autoClick: false, + selectedLanguage: languageList.find((language) => language.code === 'default'), + selectedTone: toneList.find((tone) => tone.code === 'default'), + selectedWritingStyle: writingStyleList.find((writingStyle) => writingStyle.code === 'default'), + }, + }, () => { + document.querySelector('#language-list-dropdown').querySelectorAll('li')[0].click(); + document.querySelector('#tone-list-dropdown').querySelectorAll('li')[0].click(); + document.querySelector('#writing-style-list-dropdown').querySelectorAll('li')[0].click(); + document.querySelector('#auto-click-button').classList.replace('btn-primary', 'btn-neutral'); + }); + runningPromptChainSteps = undefined; + runningPromptChainIndex = 0; document.title = 'New Page'; const planName = account?.account_plan?.subscription_plan || account?.accounts?.default?.entitlement?.subscription_plan || 'chatgptfreeplan'; if (!conversationsAreSynced) return; @@ -345,9 +400,8 @@ function addEnforcementTriggerElement() { inputForm.firstChild.insertAdjacentHTML('beforeend', ''); } function replaceTextAreaElemet(settings) { - const main = document.querySelector('main'); - if (!main) { return false; } - const inputForm = main.querySelector('form'); + const inputForm = document.querySelector('main form'); + if (!inputForm) { return false; } if (settings.customConversationWidth) { inputForm.style = `${inputForm.style.cssText}; max-width:${settings.conversationWidth}%;`; } @@ -362,13 +416,13 @@ function replaceTextAreaElemet(settings) { let textAreaElement = inputForm.querySelector('textarea'); if (!textAreaElement) { - const textAreaElementWrapperHTML = '
'; + const textAreaElementWrapperHTML = '
'; // insert text area element wrapper in input form first child at the end inputForm.firstChild.insertAdjacentHTML('beforeend', textAreaElementWrapperHTML); textAreaElement = inputForm.querySelector('textarea'); } const newTextAreaElement = textAreaElement.cloneNode(true); - newTextAreaElement.id = 'gptx-textarea'; + newTextAreaElement.id = 'prompt-textarea'; newTextAreaElement.dir = 'auto'; // auto resize textarea height up to 200px newTextAreaElement.style.height = 'auto'; @@ -378,7 +432,8 @@ function replaceTextAreaElemet(settings) { newTextAreaElement.style.paddingRight = '40px'; newTextAreaElement.style.overflowY = 'hidden'; - newTextAreaElement.addEventListener('keydown', textAreaElementKeydownEventListener); + newTextAreaElement.addEventListener('keydown', textAreaElementKeydownEventListenerASync); + // also async newTextAreaElement.addEventListener('keydown', (event) => { if (event.key === 'Enter' && event.which === 13 && !event.shiftKey) { disableTextInput = true; @@ -439,10 +494,7 @@ function getGPT4CounterMessageCapWindow(messageCapWindow) { return r < 2 ? 'week' : ''.concat(r, ' weeks'); } function addGpt4Counter() { - const main = document.querySelector('main'); - if (!main) return; - const inputForm = main.querySelector('form'); - const textAreaElement = inputForm.querySelector('textarea'); + const textAreaElement = document.querySelector('main form textarea'); if (!textAreaElement) return; // add input char/word counter const existingGpt4CounterElement = document.querySelector('#gpt4-counter'); @@ -494,12 +546,7 @@ function updateInputCounter(text) { } } function canSubmitPrompt() { - const main = document.querySelector('main'); - if (!main) { return false; } - const inputForm = main.querySelector('form'); - const textAreaElement = inputForm.querySelector('textarea'); - if (!textAreaElement) { return false; } - const submitButton = inputForm.querySelector('textarea ~ button'); + const submitButton = document.querySelector('main form textarea ~ button'); if (!submitButton) { return false; } // if submit button not contained and svg element retur false const submitSVG = submitButton.querySelector('svg');// (...) @@ -528,86 +575,7 @@ function addActionButtonWrapperAboveInput() { const textAreaElementWrapper = textAreaElement.parentElement.parentElement; textAreaElementWrapper.insertBefore(actionButtonWrapper, textAreaElement.parentElement); } -function showPluginStore() { - chrome.storage.local.get(['allPlugins'], (result) => { - const { allPlugins } = result; - const popularPlugins = allPlugins.filter((plugin) => plugin.categories.map((c) => c.id).includes('most_popular')); - const pluginStoreModal = initializePluginStoreModal(popularPlugins); - const pluginStoreWrapper = document.createElement('div'); - pluginStoreWrapper.id = 'plugin-store-wrapper'; - pluginStoreWrapper.classList = 'absolute inset-0 z-10'; - pluginStoreWrapper.innerHTML = pluginStoreModal; - document.body.appendChild(pluginStoreWrapper); - addPluginStoreEventListener(popularPlugins); - }); -} -function registerShortkeys() { - document.addEventListener('keydown', (e) => { - if (e.metaKey || (isWindows() && e.ctrlKey)) { - if (e.key === 'f' || e.key === 'F') { - const searchbox = document.querySelector('#conversation-search'); - if (searchbox && searchbox !== document.activeElement) { - searchbox.scrollIntoView(); - searchbox.focus(); - e.preventDefault(); - } - } - } - // esc - if (e.keyCode === 27) { - if (document.querySelector('[id*=close-button]')) { - document.querySelector('[id*=close-button]').click(); - } else { - const stopGeneratingResponseButton = document.querySelector('#stop-generating-response-button'); - if (stopGeneratingResponseButton) { - e.preventDefault(); - stopGeneratingResponseButton.click(); - } - } - } - // cmnd + shift + p - if ((e.metaKey || (isWindows() && e.ctrlKey)) && e.shiftKey && e.keyCode === 80) { - e.preventDefault(); - showPluginStore(); - } - // alt + shift + n - if (e.altKey && e.shiftKey && e.keyCode === 78) { - e.preventDefault(); - showNewChatPage(); - } - // home key - if (e.keyCode === 36) { - // if active element is not the textarea, scroll to top - if (document.activeElement.tagName !== 'TEXTAREA') { - e.preventDefault(); - document.querySelector('#scroll-up-button').click(); - } - } - // end key - if (e.keyCode === 35) { - if (document.activeElement.tagName !== 'TEXTAREA') { - e.preventDefault(); - document.querySelector('#scroll-down-button').click(); - } - } - // cmd/ctrl + shift + s - if ((e.metaKey || (isWindows() && e.ctrlKey)) && e.shiftKey && e.keyCode === 83) { - if (!document.querySelector('#modal-settings')) { - // open settings - e.preventDefault(); - document.querySelector('#settings-button')?.click(); - } - } - // cmd/ctrl + shift + l - if ((e.metaKey || (isWindows() && e.ctrlKey)) && e.shiftKey && e.keyCode === 76) { - if (!document.querySelector('#modal-newsletter-archive')) { - // open newsletter - e.preventDefault(); - document.querySelector('#newsletter-button')?.click(); - } - } - }); -} + function formatDate(date) { // if date is today show hh:mm. if older than today just show date yyyy-mm-dd const today = new Date(); @@ -666,7 +634,6 @@ function addButtonToNavFooter(title, onClick) { // add the setting button to the nav parrent navFooter.appendChild(button); } - function addExpandButton() { const nav = document.querySelector('nav'); if (nav) { @@ -819,10 +786,7 @@ function removeUnusedButtons() { function updateNewChatButtonNotSynced() { chrome.storage.local.get(['selectedConversations'], (result) => { const { selectedConversations } = result; - const main = document.querySelector('main'); - if (!main) return; - const inputForm = main.querySelector('form'); - const textAreaElement = inputForm.querySelector('textarea'); + const textAreaElement = document.querySelector('main form textarea'); const nav = document.querySelector('nav'); const newChatButton = nav?.querySelector('a'); newChatButton.classList = 'flex py-3 px-3 w-full items-center gap-3 transition-colors duration-200 text-white cursor-pointer text-sm rounded-md border border-white/20 hover:bg-gray-500/10 mb-1 flex-shrink-0'; @@ -840,13 +804,22 @@ function updateNewChatButtonNotSynced() { } }); } +function removeMarkTagsInsideBackticks(input) { + const pattern = /(`{1,3})([^`]*?)\1/gs; + return input.replace(pattern, (match, backticks, codeBlock) => { + const codeWithoutMarkTags = codeBlock.replace(/<\/?mark>/gi, ''); + return backticks + codeWithoutMarkTags + backticks; + }); +} function highlight(text, searchTerm) { if (!text) return ''; if (text.trim().length === 0) return ''; if (searchTerm.trim().length === 0) return text; // escape special characters searchTerm = searchTerm.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); - return text.replace(new RegExp(searchTerm, 'gi'), (match) => `${match}`); + const newText = text.replace(new RegExp(searchTerm, 'gi'), (match) => `${match}`); + // remove any and tags that are inside tags + return removeMarkTagsInsideBackticks(newText); } function highlightBracket(text) { @@ -879,19 +852,19 @@ function highlightHTML(text, elementId) { element.innerHTML = innerHTML; } -function toast(text, type = 'info') { +function toast(html, type = 'info', duration = 4000) { // show toast that text is copied to clipboard const element = document.createElement('div'); element.style = 'position:fixed;right:24px;top:24px;border-radius:4px;background-color:#19c37d;padding:8px 16px;z-index:100001;'; if (type === 'error') { element.style.backgroundColor = '#ef4146'; } - element.textContent = text; + element.innerHTML = html; document.body.appendChild(element); setTimeout( () => { element.remove(); }, - 4000, + duration, ); } diff --git a/scripts/content/initialize.js b/scripts/content/initialize.js index 9751a91..035fbea 100644 --- a/scripts/content/initialize.js +++ b/scripts/content/initialize.js @@ -1,4 +1,4 @@ -/* global getAccount, getModels, getConversationLimit, initializeStorage, cleanNav, initializeContinue, initializeExport, initializeSettings, initializePromptHistory, initializePromptLibrary, initializeNewsletter, initializeAutoSave, addNavToggleButton, initializeAnnouncement, initializeReleaseNote, initializeReplaceDeleteConversationButton, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, updateNewChatButtonNotSynced, addAsyncInputEvents, initializeContentMessageListeners, registerShortkeys, addDevIndicator, addExpandButton, openLinksInNewTab, addEnforcementTriggerElement, addArkoseCallback */ +/* global getAccount, getModels, getConversationLimit, initializeStorage, cleanNav, initializeContinue, initializeExport, initializeSettings, initializePromptHistory, initializePromptLibrary, initializeNewsletter, initializeAutoSave, addNavToggleButton, initializeAnnouncement, initializeReleaseNote, initializeReplaceDeleteConversationButton, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, updateNewChatButtonNotSynced, addAsyncInputEvents, initializeContentMessageListeners, addDevIndicator, addExpandButton, openLinksInNewTab, addEnforcementTriggerElement, initializeKeyboardShortcuts, addArkoseCallback, addQuickAccessMenuEventListener, watchError, showAutoSyncToast */ // eslint-disable-next-line no-unused-vars function initialize() { @@ -8,13 +8,14 @@ function initialize() { if (!historyButton) { setTimeout(() => { initializeStorage().then(() => { - registerShortkeys(); + // watchError(); getAccount(); getModels(); getConversationLimit(); openLinksInNewTab(); addNavToggleButton(); initializeContentMessageListeners(); + addQuickAccessMenuEventListener(); cleanNav(); initializeContinue(); initializeNewsletter(); @@ -26,9 +27,10 @@ function initialize() { initializePromptHistory(); addExpandButton(); addDevIndicator(); + initializeKeyboardShortcuts(); addEnforcementTriggerElement(); addArkoseCallback(); - + // showAutoSyncToast(); setTimeout(() => { chrome.storage.local.get(['settings'], (result) => { const { settings } = result; diff --git a/scripts/content/keyboardShortcuts.js b/scripts/content/keyboardShortcuts.js new file mode 100644 index 0000000..da72e27 --- /dev/null +++ b/scripts/content/keyboardShortcuts.js @@ -0,0 +1,230 @@ +/* global isWindows, createModal, settingsModalActions, initializePluginStoreModal, addPluginStoreEventListener, showNewChatPage, createPromptChainListModal */ + +// eslint-disable-next-line no-unused-vars +function createKeyboardShortcutsModal(version) { + const bodyContent = keyboardShortcutsModalContent(version); + const actionsBarContent = keyboardShortcutsModalActions(); + createModal('Keyboard Shortcuts', 'Some shortkeys only work when Auto-Sync is ON. Having issues? see our FAQ', bodyContent, actionsBarContent); +} + +function keyboardShortcutsModalContent() { + // create newsletterList modal content + const content = document.createElement('div'); + content.id = 'modal-content-keyboard-shortcuts-list'; + content.style = 'overflow-y: hidden;position: relative;height:100%; width:100%'; + content.classList = 'markdown prose-invert'; + const logoWatermark = document.createElement('img'); + logoWatermark.src = chrome.runtime.getURL('icons/logo.png'); + logoWatermark.style = 'position: fixed; top: 50%; right: 50%; width: 400px; height: 400px; opacity: 0.07; transform: translate(50%, -50%);box-shadow:none !important;'; + content.appendChild(logoWatermark); + const keyboardShortcutsText = document.createElement('div'); + keyboardShortcutsText.style = 'display: flex; flex-direction: column; justify-content: start; align-items: start;overflow-y: scroll; height: 100%; width: 100%; white-space: break-spaces; overflow-wrap: break-word;padding: 16px;position: relative;z-index:10;color: #fff;'; + keyboardShortcutsText.innerHTML = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ShortcutAction
CTRL/CMD + FSearch Chats (To use browser search, press “CTRL/CMD + F” twice)
CTRL/CMD + SHIFT + SOpen Settings
CTRL/CMD + SHIFT + POpen Plugin Store
CTRL/CMD + SHIFT + LOpen Newsletter Archive
CTRL/CMD + SHIFT + X (or SHIFT + Click on New Prompt Chain button)Open Prompt Chain List
CTRL/CMD + SHIFT + COpen New Prompt Chain Modal
CTRL/CMD + SHIFT + KOpen Keyboard Shortcut modal
CTRL/CMD + ALT + HHide/show the sidebar
CTRL/CMD + SHIFT + Click on the new folder iconReset the order of chats from newest to oldest (removes all folders)
CTRL/CMD + SHIFT + Click on the sync button in the bottom-left cornerReset Auto Sync
ALT + SHIFT + NOpen New Chat Page
HOMEScroll to top
ENDScroll to bottom
ESCClose modals or stop generating
+ `; + content.appendChild(keyboardShortcutsText); + return content; +} + +function keyboardShortcutsModalActions() { + return settingsModalActions(); +} +function showPluginStore() { + chrome.storage.local.get(['allPlugins'], (result) => { + const { allPlugins } = result; + const popularPlugins = allPlugins.filter((plugin) => plugin.categories.map((c) => c.id).includes('most_popular')); + const pluginStoreModal = initializePluginStoreModal(popularPlugins); + const pluginStoreWrapper = document.createElement('div'); + pluginStoreWrapper.id = 'plugin-store-wrapper'; + pluginStoreWrapper.classList = 'absolute inset-0 z-10'; + pluginStoreWrapper.innerHTML = pluginStoreModal; + document.body.appendChild(pluginStoreWrapper); + addPluginStoreEventListener(popularPlugins); + }); +} +function registerShortkeys() { + chrome.storage.local.get(['settings'], (result) => { + const { settings } = result; + const autoSync = typeof settings?.autoSync === 'undefined' || settings?.autoSync; + document.addEventListener('keydown', (e) => { + if (autoSync && (e.metaKey || (isWindows() && e.ctrlKey))) { + if (e.key === 'f' || e.key === 'F') { + const searchbox = document.querySelector('#conversation-search'); + if (searchbox && searchbox !== document.activeElement) { + searchbox.scrollIntoView(); + searchbox.focus(); + e.preventDefault(); + } + } + } + // esc + if (e.keyCode === 27) { + if (document.querySelector('[id*=close-button]')) { + document.querySelector('[id*=close-button]').click(); + } else if (document.querySelector('[id*=cancel-button]')) { + document.querySelector('[id*=cancel-button]').click(); + } else if (document.querySelector('#quick-access-menu')) { + document.querySelector('#quick-access-menu').remove(); + document.querySelector('main form textarea').focus(); + } else { + const stopGeneratingResponseButton = document.querySelector('#stop-generating-response-button'); + if (stopGeneratingResponseButton) { + e.preventDefault(); + stopGeneratingResponseButton.click(); + } + } + } + // cmnd + shift + p + if ((e.metaKey || (isWindows() && e.ctrlKey)) && e.shiftKey && e.keyCode === 80) { + e.preventDefault(); + showPluginStore(); + } + // cmnd + shift + k + if ((e.metaKey || (isWindows() && e.ctrlKey)) && e.shiftKey && e.keyCode === 75) { + e.preventDefault(); + createKeyboardShortcutsModal(); + } + // cmnd + shift + c + if (autoSync && (e.metaKey || (isWindows() && e.ctrlKey)) && e.shiftKey && e.keyCode === 67) { + e.preventDefault(); + const promptChainCreateModal = document.querySelector('#new-prompt-chain-modal'); + const promptChainCreateButton = document.querySelector('#prompt-chain-create-button'); + if (!promptChainCreateModal && promptChainCreateButton) { + promptChainCreateButton.click(); + } + } + // cmnd + shift + x + if (autoSync && (e.metaKey || (isWindows() && e.ctrlKey)) && e.shiftKey && e.keyCode === 88) { + e.preventDefault(); + const promptChainListModal = document.querySelector('#modal-prompt-chains'); + if (!promptChainListModal) { + createPromptChainListModal(); + } + } + // alt + shift + n + if (e.altKey && e.shiftKey && e.keyCode === 78) { + e.preventDefault(); + showNewChatPage(); + } + // cmd/ctrl + alt + h + if ((e.metaKey || (isWindows() && e.ctrlKey)) && e.altKey && e.keyCode === 72) { + e.preventDefault(); + const navToggleButton = document.querySelector('#nav-toggle-button'); + if (navToggleButton) { + navToggleButton.click(); + } + } + // home key + if (e.keyCode === 36) { + // if active element is not the textarea, scroll to top + if (document.activeElement.tagName !== 'TEXTAREA') { + e.preventDefault(); + document.querySelector('#scroll-up-button').click(); + } + } + // end key + if (e.keyCode === 35) { + if (document.activeElement.tagName !== 'TEXTAREA') { + e.preventDefault(); + document.querySelector('#scroll-down-button').click(); + } + } + // cmd/ctrl + shift + s + if ((e.metaKey || (isWindows() && e.ctrlKey)) && e.shiftKey && e.keyCode === 83) { + if (!document.querySelector('#modal-settings')) { + // open settings + e.preventDefault(); + document.querySelector('#settings-button')?.click(); + } + } + // cmd/ctrl + shift + l + if ((e.metaKey || (isWindows() && e.ctrlKey)) && e.shiftKey && e.keyCode === 76) { + if (!document.querySelector('#modal-newsletter-archive')) { + // open newsletter + e.preventDefault(); + document.querySelector('#newsletter-button')?.click(); + } + } + }); + }); +} +function addKeyboardShortcutsModalButton() { + const existingKeyboardShortcutsModalButton = document.getElementById('keyboard-shortcuts-modal-button'); + if (existingKeyboardShortcutsModalButton) existingKeyboardShortcutsModalButton.remove(); + + const keyboardShortcutsModalButton = document.createElement('button'); + keyboardShortcutsModalButton.id = 'keyboard-shortcuts-modal-button'; + keyboardShortcutsModalButton.innerHTML = ''; + keyboardShortcutsModalButton.className = 'absolute flex items-center justify-center bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-100 hover:bg-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-900 dark:hover:text-gray-200 text-xs font-sans cursor-pointer rounded-md z-10'; + keyboardShortcutsModalButton.style = 'bottom: 3rem;right: 3rem;width: 2rem;height: 2rem;flex-wrap:wrap;border: 1px solid;'; + keyboardShortcutsModalButton.addEventListener('click', () => { + createKeyboardShortcutsModal(); + }); + document.body.appendChild(keyboardShortcutsModalButton); +} +// eslint-disable-next-line no-unused-vars +function initializeKeyboardShortcuts() { + registerShortkeys(); + addKeyboardShortcutsModalButton(); +} diff --git a/scripts/content/modelSwitcher.js b/scripts/content/modelSwitcher.js index 017dd70..bba7978 100644 --- a/scripts/content/modelSwitcher.js +++ b/scripts/content/modelSwitcher.js @@ -121,6 +121,14 @@ function addModelSwitcherEventListener(idPrefix, forceDark = false) { } else { pluginsDropdownWrapper.style.display = 'none'; } + const submitButton = document.querySelector('main form textarea ~ button'); + if (submitButton && !submitButton.disabled) { + if (selectedModel.slug.startsWith('gpt-4')) { + submitButton.style.backgroundColor = '#AB68FF'; + } else { + submitButton.style.backgroundColor = '#19C37D'; + } + } } chrome.storage.local.set({ settings: { ...settings, selectedModel } }, () => { addArkoseScript(); diff --git a/scripts/content/newsletterList.js b/scripts/content/newsletterList.js index 50d64f4..d610474 100644 --- a/scripts/content/newsletterList.js +++ b/scripts/content/newsletterList.js @@ -74,6 +74,7 @@ function addNewsletterButton() { const newsletterButton = document.createElement('a'); newsletterButton.classList = 'flex py-3 px-3 items-center gap-3 rounded-md hover:bg-gray-500/10 transition-colors duration-200 text-white cursor-pointer text-sm'; newsletterButton.textContent = 'Newsletter Archive'; + newsletterButton.title = 'CMD/CTRL + SHIFT + L'; const newsletterButtonIcon = document.createElement('img'); newsletterButtonIcon.style = 'width: 16px; height: 16px;'; diff --git a/scripts/content/pluginsDropdown.js b/scripts/content/pluginsDropdown.js index 391e30f..25db505 100644 --- a/scripts/content/pluginsDropdown.js +++ b/scripts/content/pluginsDropdown.js @@ -3,12 +3,18 @@ // eslint-disable-next-line no-unused-vars function pluginsDropdown(installedPlugins, enabledPluginIds, idPrefix, forceDark = false) { const enabledPlugins = installedPlugins?.filter((plugin) => enabledPluginIds?.includes(plugin.id)); + const sortedPlugins = enabledPlugins?.sort((a, b) => { + const textA = a.manifest.name_for_human.toUpperCase(); + const textB = b.manifest.name_for_human.toUpperCase(); + // eslint-disable-next-line no-nested-ternary + return (textA < textB) ? -1 : (textA > textB) ? 1 : 0; + }); return ``; + newPromptChainModalContent.appendChild(modalTitle); + const modalSubtitle = document.createElement('div'); + modalSubtitle.style = 'color:lightslategray;font-size:0.875rem; margin-bottom: 16px;padding: 0 16px;'; + modalSubtitle.textContent = 'Prompt chains are a series of prompts that can be run automatically in sequence. You can create a new prompt chain from any existing conversation. You can add, remove, edit, or reorder prompts in the chain.'; + newPromptChainModalContent.appendChild(modalSubtitle); + const promptInputListWrapper = document.createElement('div'); + promptInputListWrapper.id = 'prompt-chain-input-list-wrapper'; + promptInputListWrapper.style = 'width:100%;display:flex;flex-direction:column;align-items:start;justify-content:start;overflow-y: scroll;padding: 0 16px;scrollBehavior: smooth;'; + + const promptInputNameInput = document.createElement('input'); + promptInputNameInput.id = 'prompt-chain-name-input'; + promptInputNameInput.style = 'width: calc(100% - 40px); min-height: 40px; border-radius: 4px; border: 1px solid #565869; background-color: #2d2d3a; color: #eee; padding: 4px 8px; font-size: 14px; margin: 0 24px 16px 16px;'; + promptInputNameInput.placeholder = 'Prompt chain name'; + promptInputNameInput.value = promptChainName; + promptInputNameInput.addEventListener('input', () => { + promptInputNameInput.style.border = '1px solid #565869'; + promptChainName = promptInputNameInput.value; + }); + newPromptChainModalContent.appendChild(promptInputNameInput); + + promptChainSteps.forEach((_prompt, index) => { + const promptRow = createPromptChainStep(promptChainSteps, index); + promptInputListWrapper.appendChild(promptRow); + }); + // modal action wrapper + const sortable = Sortable.create(promptInputListWrapper, { + handle: '#prompt-chain-drag-handle', + multiDrag: true, + selectedClass: 'multi-drag-selected', + }); + newPromptChainModalContent.appendChild(promptInputListWrapper); + + const newPromptChainModalActionWrapper = document.createElement('div'); + newPromptChainModalActionWrapper.style = 'display:flex;align-items:center;justify-content:space-between;width:100%;margin-top:auto;padding:0 16px;'; + newPromptChainModalContent.appendChild(newPromptChainModalActionWrapper); + const leftSection = document.createElement('div'); + leftSection.style = 'display:flex;align-items:center;justify-content:flex-start;'; + newPromptChainModalActionWrapper.appendChild(leftSection); + + const addStepButton = document.createElement('button'); + addStepButton.classList = 'btn flex justify-center gap-2 btn-primary border-0 md:border'; + addStepButton.style = 'margin-top:16px;'; + addStepButton.id = 'prompt-chain-add-step-button'; + addStepButton.textContent = '+ Add New Step'; + addStepButton.addEventListener('click', () => { + promptChainSteps.push(''); + const promptRow = createPromptChainStep(promptChainSteps, promptChainSteps.length - 1); + const curPromptInputListWrapper = document.getElementById('prompt-chain-input-list-wrapper'); + curPromptInputListWrapper.appendChild(promptRow); + promptRow.scrollIntoView({ behavior: 'smooth', block: 'end' }); + setTimeout(() => { + promptRow.querySelector('textarea').focus(); + }, 500); + }); + leftSection.appendChild(addStepButton); + const rightSection = document.createElement('div'); + rightSection.style = 'display:flex;align-items:center;justify-content:flex-end;'; + newPromptChainModalActionWrapper.appendChild(rightSection); + // add cancel button + const cancelButton = document.createElement('button'); + cancelButton.classList = 'btn flex justify-center gap-2 btn-dark border-0 md:border mr-2'; + cancelButton.style = 'margin-top:16px;margin-left:8px;'; + cancelButton.textContent = 'Cancel'; + cancelButton.id = 'prompt-chain-cancel-button'; + cancelButton.addEventListener('click', () => { + newPromptChainModal.remove(); + }); + rightSection.appendChild(cancelButton); + + // add submit button + const submitButton = document.createElement('button'); + submitButton.classList = 'btn flex justify-center gap-2 btn-primary border-0 md:border'; + submitButton.style = 'margin-top:16px;margin-left:8px;margin-right:8px;'; + submitButton.id = 'prompt-chain-submit-button'; + submitButton.textContent = isNew ? 'Save Prompt Chain' : 'Update Prompt Chain'; + submitButton.addEventListener('click', () => { + if (!promptChainName) { + const curPromptInputNameInput = document.getElementById('prompt-chain-name-input'); + curPromptInputNameInput.focus(); + curPromptInputNameInput.style.border = '1px solid #ff4a4a'; + toast('Please enter a prompt chain name', 'error'); + return; + } + chrome.storage.local.get(['promptChains'], (res) => { + const allPromptChains = res.promptChains || []; + const nonEmptySteps = []; + document.querySelectorAll('[id^=prompt-chain-input-]').forEach((step) => { + if (step.value) nonEmptySteps.push(step.value); + }); + if (nonEmptySteps.length === 0) { + toast('Please add at least one step to the prompt chain', 'error'); + return; + } + if (isNew) { + // add to the beginning of the array + allPromptChains.unshift({ title: promptChainName, steps: nonEmptySteps }); + } else { + allPromptChains[chainIndex] = { title: promptChainName, steps: nonEmptySteps }; + } + chrome.storage.local.set({ promptChains: allPromptChains }, () => { + const modalBodyPromptChains = document.getElementById('modal-body-prompt-chains'); + modalBodyPromptChains.innerHTML = ''; + const newContent = promptChainListModalContent(); + modalBodyPromptChains.appendChild(newContent); + }); + newPromptChainModal.remove(); + toast(`Prompt Chain is ${isNew ? 'saved' : 'updated'}!`); + }); + }); + rightSection.appendChild(submitButton); + + newPromptChainModal.appendChild(newPromptChainModalContent); + document.body.appendChild(newPromptChainModal); +} + +function addPromptChainCreateButton() { + const existingPromptChainCreateButton = document.getElementById('prompt-chain-create-button'); + if (existingPromptChainCreateButton) existingPromptChainCreateButton.remove(); + + const promptChainCreateButton = document.createElement('button'); + promptChainCreateButton.id = 'prompt-chain-create-button'; + promptChainCreateButton.title = 'Create a new prompt chain (CMD/CTRL + SHIFT + C)'; + promptChainCreateButton.innerHTML = ''; + promptChainCreateButton.className = 'absolute flex items-center justify-center bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-100 hover:bg-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-900 dark:hover:text-gray-200 text-xs font-sans cursor-pointer rounded-md z-10'; + promptChainCreateButton.style = 'bottom: 11rem;right: 3rem;width: 2rem;height: 2rem;flex-wrap:wrap;border: solid 1px;'; + const plusIcon = document.createElement('div'); + plusIcon.classList = 'bg-gray-100 dark:bg-gray-700'; + plusIcon.style = 'width: 1rem;height: 1rem;border: solid 1px;border-radius: 50%;border-color: currentColor;position: absolute;top: 0;right: 0;transform: translate(50%, -50%);'; + plusIcon.textContent = '+'; + promptChainCreateButton.appendChild(plusIcon); + promptChainCreateButton.addEventListener('click', (e) => { + // if shift key is pressed + if (e.shiftKey) { + // open prompt chain list modal + createPromptChainListModal(); + return; + } + // get all visible user messages + const allUserMessageWrappers = document.querySelectorAll('[id^="message-wrapper-"][data-role="user"]'); + const allUserMessages = []; + allUserMessageWrappers.forEach((userMessageWrapper) => { + const userMessage = userMessageWrapper.querySelector('[id^="message-text-"]'); + if (userMessage) allUserMessages.push(userMessage.textContent); + }); + const promptChainName = document.querySelector('[id^="conversation-top"]')?.textContent || ''; + createNewPromptChainModal(promptChainName, allUserMessages); + addNewPromptModalEventListener(); + }); + document.body.appendChild(promptChainCreateButton); +} +function addNewPromptModalEventListener() { + const seeAllPromptsButton = document.getElementById('see-all-prompt-chains'); + seeAllPromptsButton.addEventListener('click', () => { + const newPromptChainModal = document.getElementById('new-prompt-chain-modal'); + newPromptChainModal.remove(); + const existingPromptChainListModal = document.getElementById('modal-prompt-chains'); + if (existingPromptChainListModal) return; + createPromptChainListModal(); + }); +} +function runPromptChain(promptChainSteps, newChat = true) { + if (newChat) { + document.querySelector('[id="new-chat-button"]').click(); + // wait for new chat to open + setTimeout(() => { + runPromptChain(promptChainSteps, false); + }, 1000); + return; + } + const textAreaElement = document.querySelector('main form textarea'); + const submitButtonElement = document.querySelector('main form textarea ~ button'); + // eslint-disable-next-line prefer-destructuring + textAreaElement.value = promptChainSteps[0]; + runningPromptChainSteps = promptChainSteps; + runningPromptChainIndex = 0; + textAreaElement.focus(); + textAreaElement.dispatchEvent(new Event('input', { bubbles: true })); + textAreaElement.dispatchEvent(new Event('change', { bubbles: true })); + setTimeout(() => { + submitButtonElement.click(); + }, 300); +} +function insertNextChain(promptChainSteps, promptChainIndex) { + const textAreaElement = document.querySelector('main form textarea'); + const submitButtonElement = document.querySelector('main form textarea ~ button'); + textAreaElement.value = promptChainSteps[promptChainIndex]; + runningPromptChainSteps = promptChainSteps; + runningPromptChainIndex = promptChainIndex; + textAreaElement.focus(); + textAreaElement.dispatchEvent(new Event('input', { bubbles: true })); + textAreaElement.dispatchEvent(new Event('change', { bubbles: true })); + setTimeout(() => { + submitButtonElement.click(); + }, 300); +} +function initializePromptChain() { + addPromptChainCreateButton(); +} diff --git a/scripts/content/promptHistory.js b/scripts/content/promptHistory.js index 8521390..82a42b0 100644 --- a/scripts/content/promptHistory.js +++ b/scripts/content/promptHistory.js @@ -1,4 +1,4 @@ -/* global highlight,openSubmitPromptModal, updateInputCounter, addButtonToNavFooter,createModal, disableTextInput:true, isGenerating, addInputCounter, toast */ +/* global highlight,openSubmitPromptModal, updateInputCounter, addButtonToNavFooter,createModal, disableTextInput:true, isGenerating, addInputCounter, toast, quickAccessMenu , updateQuickAccessMenuItems */ function createPromptHistoryModal() { chrome.storage.local.get(['userInputValueHistory', 'settings'], (result) => { const { userInputValueHistory, settings } = result; @@ -142,7 +142,9 @@ function promptHistoryList(userInputValueHistory, historyFilter) { textAreaElement.dispatchEvent(new Event('change', { bubbles: true })); // if alt key is pressed, submit the form if (event.shiftKey) { - submitButton.click(); + setTimeout(() => { + submitButton.click(); + }, 300); } // click on modal close button document.querySelector('button[id="modal-close-button-my-prompt-history"]').click(); @@ -500,9 +502,19 @@ function textAreaElementInputEventListener(event) { const submitButton = inputForm.querySelector('textarea ~ button'); if (submitButton) { if (event.target.value.trim().length > 0) { - submitButton.disabled = false; + chrome.storage.local.get(['settings'], (result) => { + const { settings } = result; + const { selectedModel } = settings; + submitButton.disabled = false; + if (selectedModel.slug.startsWith('gpt-4')) { + submitButton.style.backgroundColor = '#AB68FF'; + } else { + submitButton.style.backgroundColor = '#19C37D'; + } + }); } else { submitButton.disabled = true; + submitButton.style.backgroundColor = 'transparent'; } } updateInputCounter(event.target.value); @@ -535,10 +547,12 @@ function textAreaElementInputEventListener(event) { }); } // Add keyboard event listener to text area -function textAreaElementKeydownEventListener(event) { +function textAreaElementKeydownEventListenerSync(event) { const textAreaElement = event.target; if (event.key === 'Enter' && event.which === 13 && !event.shiftKey) { + event.preventDefault(); + event.stopPropagation(); updateInputCounter(''); chrome.storage.local.get(['textInputValue'], (result) => { const textInputValue = result.textInputValue || ''; @@ -619,6 +633,144 @@ function textAreaElementKeydownEventListener(event) { }); } } +// eslint-disable-next-line no-unused-vars +function textAreaElementKeydownEventListenerASync(event) { + const textAreaElement = event.target; + + if (event.key === 'Enter' && event.which === 13 && !event.shiftKey) { + event.preventDefault(); + event.stopPropagation(); + updateInputCounter(''); + chrome.storage.local.get(['textInputValue'], (result) => { + const textInputValue = result.textInputValue || ''; + if (textInputValue === '') return; + const templateWords = textAreaElement.value.match(/{{(.*?)}}/g); + if (!templateWords) { + textAreaElement.style.height = '24px'; + } + addUserPromptToHistory(textInputValue); + }); + } + // if press up arrow key, get last input value from local storage history + if (event.key === 'ArrowUp') { + const quickAccessMenu = document.querySelector('#quick-access-menu'); + if (quickAccessMenu && quickAccessMenu.style.display !== 'none') { + event.preventDefault(); + return; + } + // check if cursor is at position 0 + if (textAreaElement.selectionStart !== 0) return; + // if there is text in the field save that first + chrome.storage.local.get(['userInputValueHistoryIndex', 'settings', 'userInputValueHistory'], (result) => { + const { settings } = result; + if (settings && !settings.promptHistory) return; + const userInputValueHistory = result.userInputValueHistory || []; + if (userInputValueHistory.length === 0) return; + let userInputValueHistoryIndex = result.userInputValueHistoryIndex || 0; + userInputValueHistoryIndex = Math.max(userInputValueHistoryIndex - 1, 0); + const lastInputValue = userInputValueHistory[userInputValueHistoryIndex]; + + chrome.storage.local.set({ userInputValueHistoryIndex }, () => { + if (lastInputValue) { + // textAreaElement.style.height = `${lastInputValue.inputValue.split('\\n').length * 24}px`; + textAreaElement.value = lastInputValue.inputValue; + textAreaElement.dispatchEvent(new Event('input', { bubbles: true })); + } + }); + }); + } + // if press down arrow key, get next input value from local storage history + if (event.key === 'ArrowDown') { + const quickAccessMenu = document.querySelector('#quick-access-menu'); + if (quickAccessMenu && quickAccessMenu.style.display !== 'none') { + event.preventDefault(); + return; + } + // check if cursor is at position end + if (textAreaElement.selectionStart !== textAreaElement.value.length) return; + // if there is text in the field save that first + chrome.storage.local.get(['userInputValueHistoryIndex', 'settings', 'userInputValueHistory', 'unsavedUserInput'], (result) => { + const { settings } = result; + if (settings && !settings.promptHistory) return; + let userInputValueHistoryIndex = result.userInputValueHistoryIndex || 0; + const userInputValueHistory = result.userInputValueHistory || []; + if (userInputValueHistory.length === 0) return; + userInputValueHistoryIndex = Math.min(userInputValueHistoryIndex + 1, userInputValueHistory.length); + chrome.storage.local.set({ userInputValueHistoryIndex }, () => { + const nextInputValue = userInputValueHistory[userInputValueHistoryIndex]; + if (nextInputValue) { + textAreaElement.style.height = `${nextInputValue.inputValue.split('\\n').length * 24}px`; + textAreaElement.value = nextInputValue.inputValue; + } else if (userInputValueHistory[userInputValueHistory.length - 1].inputValue !== '') { + const unsavedUserInput = result.unsavedUserInput || ''; + if (textAreaElement.value !== unsavedUserInput) { + textAreaElement.value = unsavedUserInput; + } + } + textAreaElement.dispatchEvent(new Event('input', { bubbles: true })); + }); + }); + } + // space key + if (event.keyCode === 32) { + const quickAccessMenuElement = document.querySelector('#quick-access-menu'); + if (quickAccessMenuElement) { + quickAccessMenuElement.remove(); + } + chrome.storage.local.get(['customPrompts'], (res) => { + // find any word that starts with @ and ends with space + // if the word is in customPrompts titles, replace it with the prompt.text + const customPrompts = res.customPrompts || []; + const textAreaValue = textAreaElement.value; + const words = textAreaValue.split(/[\s\n]+/); + const lastWord = words[words.length - 2]; + if (lastWord.startsWith('@')) { + const prompt = customPrompts.find((p) => p.title.toLowerCase() === lastWord.substring(1).toLowerCase()); + if (prompt) { + textAreaElement.value = textAreaValue.substring(0, textAreaValue.length - (lastWord.length + 1)) + prompt.text; + textAreaElement.dispatchEvent(new Event('input', { bubbles: true })); + } + } + }); + } + // timeout to capture last entered/removed character + setTimeout(() => { + updateQuickAccessMenuItems(); + }, 100); + // @ + if (event.keyCode === 50) { + // open the dropdown with custom prompts + quickAccessMenu('@'); + } + // # + if (event.keyCode === 51) { + // open the dropdown with prompt chains + quickAccessMenu('#'); + } + + if (event.key === 'Backspace') { + const cursorPosition = textAreaElement.selectionStart; + // @ + const previousAtPosition = textAreaElement.value.lastIndexOf('@', cursorPosition); + const previousHashtagPosition = textAreaElement.value.lastIndexOf('#', cursorPosition); + const previousTrigger = previousAtPosition > previousHashtagPosition ? '@' : '#'; + const previousTriggerPosition = Math.max(previousAtPosition, previousHashtagPosition); + + if (previousTriggerPosition > -1 && cursorPosition - 1 > previousTriggerPosition && textAreaElement.value.lastIndexOf(' ', cursorPosition) < previousTriggerPosition) { + const quickAccessMenuElement = document.querySelector('#quick-access-menu'); + if (!quickAccessMenuElement) { + // get the word between the previous trigger and the cursor + quickAccessMenu(previousTrigger); + } + } else { + const quickAccessMenuElement = document.querySelector('#quick-access-menu'); + if (quickAccessMenuElement) { + quickAccessMenuElement.remove(); + } + } + } +} + // eslint-disable-next-line no-unused-vars function initializePromptHistory() { addButtonToNavFooter('My Prompt History', () => createPromptHistoryModal()); @@ -654,6 +806,6 @@ function addAsyncInputEvents() { if (textAreaElement) { textAreaElement.addEventListener('input', textAreaElementInputEventListener); - textAreaElement.addEventListener('keydown', textAreaElementKeydownEventListener); + textAreaElement.addEventListener('keydown', textAreaElementKeydownEventListenerSync); } } diff --git a/scripts/content/promptLibrary.js b/scripts/content/promptLibrary.js index 5ea4395..7afa0bf 100644 --- a/scripts/content/promptLibrary.js +++ b/scripts/content/promptLibrary.js @@ -105,59 +105,59 @@ function promptLibraryListComponent(libraryData, loading = false) { // libraryItemActionWrapper.classList = 'invisible group-hover:visible'; libraryItemActionWrapper.style = 'position:absolute; top: 12px; right:4px; display: flex; justify-content: flex-end; align-items: center;'; // thumbs up - const libraryItemThumbsUp = document.createElement('span'); - libraryItemThumbsUp.id = `library-item-thumbs-up-${libraryPrompt.id}`; - libraryItemThumbsUp.title = 'Upvote this prompt'; - libraryItemThumbsUp.style = 'color: lightslategray; font-size:1.2em; margin-right: 8px; cursor: pointer;'; - libraryItemThumbsUp.innerHTML = ' '; - libraryItemThumbsUp.addEventListener('mouseenter', () => { - libraryItemThumbsUp.style.color = '#eee'; - }); - libraryItemThumbsUp.addEventListener('mouseleave', () => { - libraryItemThumbsUp.style.color = 'lightslategray'; - }); - libraryItemThumbsUp.addEventListener('click', () => { - vote(libraryPrompt.id, 'up').then((data) => { - if (data.status === 'success') { - toast('Prompt upvoted'); - const curUpvoteCount = document.getElementById(`prompt-upvotes-count-${libraryPrompt.id}`); - curUpvoteCount.textContent = parseInt(curUpvoteCount.textContent, 10) + 1; - } - if (data.status === 'same user') { - toast('You have already voted for this prompt'); - } - }); - const curLibraryItemActionWrapper = document.getElementById(`library-item-action-wrapper-${libraryPrompt.id}`); - curLibraryItemActionWrapper.style.opacity = '0.3'; - curLibraryItemActionWrapper.style.pointerEvents = 'none'; - }); - // thumbs down - const libraryItemThumbsDown = document.createElement('span'); - libraryItemThumbsDown.id = `library-item-thumbs-down-${libraryPrompt.id}`; - libraryItemThumbsDown.title = 'Downvote this prompt'; - libraryItemThumbsDown.style = 'color: lightslategray; font-size:1.2em; margin-right: 12px; cursor: pointer;'; - libraryItemThumbsDown.innerHTML = ' '; - libraryItemThumbsDown.addEventListener('mouseenter', () => { - libraryItemThumbsDown.style.color = '#eee'; - }); - libraryItemThumbsDown.addEventListener('mouseleave', () => { - libraryItemThumbsDown.style.color = 'lightslategray'; - }); - libraryItemThumbsDown.addEventListener('click', () => { - vote(libraryPrompt.id, 'down').then((data) => { - if (data.status === 'success') { - toast('Prompt downvoted'); - const curUpvoteCount = document.getElementById(`prompt-upvotes-count-${libraryPrompt.id}`); - curUpvoteCount.textContent = parseInt(curUpvoteCount.textContent, 10) - 1; - } - if (data.status === 'same user') { - toast('You have already voted for this prompt'); - } - }); - const curLibraryItemActionWrapper = document.getElementById(`library-item-action-wrapper-${libraryPrompt.id}`); - curLibraryItemActionWrapper.style.opacity = '0.3'; - curLibraryItemActionWrapper.style.pointerEvents = 'none'; - }); + // const libraryItemThumbsUp = document.createElement('span'); + // libraryItemThumbsUp.id = `library-item-thumbs-up-${libraryPrompt.id}`; + // libraryItemThumbsUp.title = 'Upvote this prompt'; + // libraryItemThumbsUp.style = 'color: lightslategray; font-size:1.2em; margin-right: 8px; cursor: pointer;'; + // libraryItemThumbsUp.innerHTML = ' '; + // libraryItemThumbsUp.addEventListener('mouseenter', () => { + // libraryItemThumbsUp.style.color = '#eee'; + // }); + // libraryItemThumbsUp.addEventListener('mouseleave', () => { + // libraryItemThumbsUp.style.color = 'lightslategray'; + // }); + // libraryItemThumbsUp.addEventListener('click', () => { + // vote(libraryPrompt.id, 'up').then((data) => { + // if (data.status === 'success') { + // toast('Prompt upvoted'); + // const curUpvoteCount = document.getElementById(`prompt-upvotes-count-${libraryPrompt.id}`); + // curUpvoteCount.textContent = parseInt(curUpvoteCount.textContent, 10) + 1; + // } + // if (data.status === 'same user') { + // toast('You have already voted for this prompt'); + // } + // }); + // const curLibraryItemActionWrapper = document.getElementById(`library-item-action-wrapper-${libraryPrompt.id}`); + // curLibraryItemActionWrapper.style.opacity = '0.3'; + // curLibraryItemActionWrapper.style.pointerEvents = 'none'; + // }); + // // thumbs down + // const libraryItemThumbsDown = document.createElement('span'); + // libraryItemThumbsDown.id = `library-item-thumbs-down-${libraryPrompt.id}`; + // libraryItemThumbsDown.title = 'Downvote this prompt'; + // libraryItemThumbsDown.style = 'color: lightslategray; font-size:1.2em; margin-right: 12px; cursor: pointer;'; + // libraryItemThumbsDown.innerHTML = ' '; + // libraryItemThumbsDown.addEventListener('mouseenter', () => { + // libraryItemThumbsDown.style.color = '#eee'; + // }); + // libraryItemThumbsDown.addEventListener('mouseleave', () => { + // libraryItemThumbsDown.style.color = 'lightslategray'; + // }); + // libraryItemThumbsDown.addEventListener('click', () => { + // vote(libraryPrompt.id, 'down').then((data) => { + // if (data.status === 'success') { + // toast('Prompt downvoted'); + // const curUpvoteCount = document.getElementById(`prompt-upvotes-count-${libraryPrompt.id}`); + // curUpvoteCount.textContent = parseInt(curUpvoteCount.textContent, 10) - 1; + // } + // if (data.status === 'same user') { + // toast('You have already voted for this prompt'); + // } + // }); + // const curLibraryItemActionWrapper = document.getElementById(`library-item-action-wrapper-${libraryPrompt.id}`); + // curLibraryItemActionWrapper.style.opacity = '0.3'; + // curLibraryItemActionWrapper.style.pointerEvents = 'none'; + // }); // flag const libraryItemFlag = document.createElement('span'); libraryItemFlag.id = `library-item-flag-${libraryPrompt.id}`; @@ -175,8 +175,8 @@ function promptLibraryListComponent(libraryData, loading = false) { openReportPromptModal(libraryPrompt); }); - libraryItemActionWrapper.appendChild(libraryItemThumbsUp); - libraryItemActionWrapper.appendChild(libraryItemThumbsDown); + // libraryItemActionWrapper.appendChild(libraryItemThumbsUp); + // libraryItemActionWrapper.appendChild(libraryItemThumbsDown); libraryItemActionWrapper.appendChild(libraryItemFlag); libraryItem.appendChild(libraryItemActionWrapper); @@ -227,7 +227,7 @@ function promptLibraryListComponent(libraryData, loading = false) { libraryItemFooter.style = 'display:flex; justify-content: space-between; align-items:flex-end; width: 100%; white-space: break-spaces; overflow-wrap: break-word;margin-top: 8px'; // created by url const libraryItemInfoWrapper = document.createElement('span'); - libraryItemInfoWrapper.id = `library-item-created-by-${libraryPrompt.id}`; + libraryItemInfoWrapper.id = `library-item-info-wrapper-${libraryPrompt.id}`; libraryItemInfoWrapper.style = 'display: flex; align-items:flex-end; justify-content: start; color: lightslategray; font-size:0.8em; width: 100%; white-space: break-spaces; overflow-wrap: break-word;'; const libraryItemCreatedBy = document.createElement('span'); libraryItemCreatedBy.id = `library-item-created-by-${libraryPrompt.id}`; @@ -258,17 +258,72 @@ function promptLibraryListComponent(libraryData, loading = false) { libraryItemUseCount.style = 'display:flex;color: lightslategray; margin-left: 24px;'; libraryItemUseCount.innerHTML = ` ${libraryPrompt.num_used}`; libraryItemInfoWrapper.appendChild(libraryItemUseCount); - libraryItemFooter.appendChild(libraryItemInfoWrapper); + + // thumbs up + const libraryItemThumbsUp = document.createElement('span'); + libraryItemThumbsUp.id = `library-item-thumbs-up-${libraryPrompt.id}`; + libraryItemThumbsUp.title = 'Upvote this prompt'; + libraryItemThumbsUp.style = 'color: lightslategray; font-size:1.2em; margin-left: 24px; position:relative; bottom:4px; cursor: pointer;'; + libraryItemThumbsUp.innerHTML = ' '; + libraryItemThumbsUp.addEventListener('mouseenter', () => { + libraryItemThumbsUp.style.color = '#eee'; + }); + libraryItemThumbsUp.addEventListener('mouseleave', () => { + libraryItemThumbsUp.style.color = 'lightslategray'; + }); + libraryItemThumbsUp.addEventListener('click', () => { + vote(libraryPrompt.id, 'up').then((data) => { + if (data.status === 'success') { + toast('Prompt upvoted'); + const curUpvoteCount = document.getElementById(`prompt-upvotes-count-${libraryPrompt.id}`); + curUpvoteCount.textContent = parseInt(curUpvoteCount.textContent, 10) + 1; + } + if (data.status === 'same user') { + toast('You have already voted for this prompt'); + } + }); + const curLibraryItemActionWrapper = document.getElementById(`library-item-action-wrapper-${libraryPrompt.id}`); + curLibraryItemActionWrapper.style.opacity = '0.3'; + curLibraryItemActionWrapper.style.pointerEvents = 'none'; + }); + libraryItemInfoWrapper.appendChild(libraryItemThumbsUp); // votes const libraryItemVoteCount = document.createElement('span'); libraryItemVoteCount.id = `library-item-vote-count-${libraryPrompt.id}`; libraryItemVoteCount.title = `upvoted ${libraryPrompt.votes} times`; - libraryItemVoteCount.style = 'display:flex;color: lightslategray; margin-left: 24px;'; - libraryItemVoteCount.innerHTML = ` ${libraryPrompt.votes}`; + libraryItemVoteCount.style = 'display:flex;color: lightslategray; margin: 0 8px;'; + libraryItemVoteCount.innerHTML = `${libraryPrompt.votes}`; libraryItemInfoWrapper.appendChild(libraryItemVoteCount); - libraryItemFooter.appendChild(libraryItemInfoWrapper); + // thumbs down + const libraryItemThumbsDown = document.createElement('span'); + libraryItemThumbsDown.id = `library-item-thumbs-down-${libraryPrompt.id}`; + libraryItemThumbsDown.title = 'Downvote this prompt'; + libraryItemThumbsDown.style = 'color: lightslategray; font-size:1.2em; cursor: pointer;'; + libraryItemThumbsDown.innerHTML = ' '; + libraryItemThumbsDown.addEventListener('mouseenter', () => { + libraryItemThumbsDown.style.color = '#eee'; + }); + libraryItemThumbsDown.addEventListener('mouseleave', () => { + libraryItemThumbsDown.style.color = 'lightslategray'; + }); + libraryItemThumbsDown.addEventListener('click', () => { + vote(libraryPrompt.id, 'down').then((data) => { + if (data.status === 'success') { + toast('Prompt downvoted'); + const curUpvoteCount = document.getElementById(`prompt-upvotes-count-${libraryPrompt.id}`); + curUpvoteCount.textContent = parseInt(curUpvoteCount.textContent, 10) - 1; + } + if (data.status === 'same user') { + toast('You have already voted for this prompt'); + } + }); + const curLibraryItemActionWrapper = document.getElementById(`library-item-action-wrapper-${libraryPrompt.id}`); + curLibraryItemActionWrapper.style.opacity = '0.3'; + curLibraryItemActionWrapper.style.pointerEvents = 'none'; + }); + libraryItemInfoWrapper.appendChild(libraryItemThumbsDown); // Share const libraryItemShareButton = document.createElement('span'); libraryItemShareButton.id = `library-item-share-button-${libraryPrompt.id}`; @@ -292,6 +347,7 @@ function promptLibraryListComponent(libraryData, loading = false) { libraryItemShareButton.style.color = 'lightslategray'; }); libraryItemInfoWrapper.appendChild(libraryItemShareButton); + libraryItemFooter.appendChild(libraryItemInfoWrapper); // library item action buttons wrapper @@ -324,7 +380,9 @@ function promptLibraryListComponent(libraryData, loading = false) { textAreaElement.dispatchEvent(new Event('input', { bubbles: true })); textAreaElement.dispatchEvent(new Event('change', { bubbles: true })); if (event.shiftKey || libraryPrompt.hide_full_prompt) { - submitButton.click(); + setTimeout(() => { + submitButton.click(); + }, 300); } document.querySelector('button[id="modal-close-button-community-prompts"]').click(); incrementUseCount(libraryPrompt.id); diff --git a/scripts/content/quickAccessMenu.js b/scripts/content/quickAccessMenu.js new file mode 100644 index 0000000..310b0bc --- /dev/null +++ b/scripts/content/quickAccessMenu.js @@ -0,0 +1,246 @@ +/* eslint-disable no-unused-vars */ +/* global defaultPrompts, createSettingsModal, createPromptChainListModal, runPromptChain */ +function addQuickAccessMenuEventListener() { + document.addEventListener('selectionchange', () => { + // bsckspace does not trigger selectionchange + const textAreaElement = document.querySelector('main form textarea'); + if (textAreaElement !== document.activeElement) return; + + const quickAccessMenuElement = document.querySelector('#quick-access-menu'); + const cursorPosition = textAreaElement.selectionStart; + const textAreaValue = textAreaElement.value; + const previousAtPosition = textAreaValue.lastIndexOf('@', cursorPosition - 1); + const previousHashtagPosition = textAreaValue.lastIndexOf('#', cursorPosition - 1); + if (cursorPosition === 0 || (previousAtPosition === -1 && previousHashtagPosition === -1)) { + if (quickAccessMenuElement) quickAccessMenuElement.remove(); + return; + } + // whichever is closer to the cursor + const previousTrigger = previousAtPosition > previousHashtagPosition ? '@' : '#'; + const previousTriggerPosition = Math.max(previousAtPosition, previousHashtagPosition); + // get the word between the previous trigger and the cursor + if (!quickAccessMenuElement && previousTriggerPosition !== -1 && cursorPosition > previousTriggerPosition && textAreaValue.lastIndexOf(' ', cursorPosition - 1) < previousTriggerPosition) { + quickAccessMenu(previousTrigger); + } else if (quickAccessMenuElement && (previousTriggerPosition === -1 || textAreaValue.lastIndexOf(' ', cursorPosition - 1) > previousTriggerPosition)) { + quickAccessMenuElement.remove(); + } + }); + document.body.addEventListener('click', (e) => { + const quickAccessMenuElement = document.querySelector('#quick-access-menu'); + const textAreaElement = document.querySelector('main form textarea'); + if (!quickAccessMenuElement) return; + if (textAreaElement?.contains(e.target)) { + setTimeout(() => { + updateQuickAccessMenuItems(); + }, 100); + e.stopPropagation(); + const cursorPosition = textAreaElement.selectionStart; + const textAreaValue = textAreaElement.value; + + const previousAtPosition = textAreaValue.lastIndexOf('@', cursorPosition - 1); + const previousHashtagPosition = textAreaValue.lastIndexOf('#', cursorPosition - 1); + if (cursorPosition === 0 || (previousAtPosition === -1 && previousHashtagPosition === -1)) { + if (quickAccessMenuElement) quickAccessMenuElement.remove(); + return; + } + // whichever is closer to the cursor + const previousTrigger = previousAtPosition > previousHashtagPosition ? '@' : '#'; + const previousTriggerPosition = Math.max(previousAtPosition, previousHashtagPosition); + + // if there is a space between previoustriggerpos and cur cursor position + if (!quickAccessMenuElement && previousTriggerPosition !== -1 && cursorPosition > previousTriggerPosition && textAreaValue.lastIndexOf(' ', cursorPosition - 1) < previousTriggerPosition) { + quickAccessMenu(previousTrigger); + } else if (previousTriggerPosition === -1 || textAreaValue.lastIndexOf(' ', cursorPosition - 1) > previousTriggerPosition) { + quickAccessMenuElement.remove(); + } + } else if (!quickAccessMenuElement.contains(e.target)) { + quickAccessMenuElement.remove(); + } + }); + document.body.addEventListener('keydown', (event) => { + const menu = document.querySelector('#quick-access-menu'); + if (!menu) return; + const menuContent = menu.querySelector('#quick-access-menu-content'); + if (event.key === 'ArrowUp') { + // rotate focus between quick-access-menu-item s where style.display:block + const menuItems = menuContent.querySelectorAll('[id^=quick-access-menu-item-]:not([style*="display: none"])'); + if (menuItems.length > 0) { + if (!menu.contains(document.activeElement)) { + menu.focus(); + menuItems[menuItems.length - 1].focus(); + } else { + const currentFocusIndex = Array.from(menuItems).indexOf(document.activeElement); + if (currentFocusIndex === 0) { + setTimeout(() => { + menuContent.scrollTop = menuContent.scrollHeight; + }, 100); + menuItems[menuItems.length - 1].focus({ preventScroll: true }); + } else if (currentFocusIndex > 0) { + menuItems[currentFocusIndex - 1].focus(); + } + } + } + } + if (event.key === 'ArrowDown') { + // rotate focus to between quick-access-menu-item s + const menuItems = menuContent.querySelectorAll('[id^=quick-access-menu-item-]:not([style*="display: none"])'); + if (menuItems.length > 0) { + if (!menu.contains(document.activeElement)) { + menu.focus(); + menuItems[0].focus(); + } else { + const currentFocusIndex = Array.from(menuItems).indexOf(document.activeElement); + if (currentFocusIndex === menuItems.length - 1) { + setTimeout(() => { + menuContent.scrollTop = 0; + }, 100); + menuItems[0].focus({ preventScroll: true }); + } else if (currentFocusIndex < menuItems.length - 1) { + menuItems[currentFocusIndex + 1].focus(); + } + } + } + } + if (event.key === 'Backspace') { + if (document.activeElement !== document.querySelector('main form textarea')) { + event.preventDefault(); + } + document.querySelector('main form textarea').focus(); + } + }); +} +function updateQuickAccessMenuItems() { + // find the closest trigger + const textAreaElement = document.querySelector('main form textarea'); + const quickAccessMenuElement = document.querySelector('#quick-access-menu'); + if (!textAreaElement || !quickAccessMenuElement) return; + const cursorPosition = textAreaElement.selectionStart; + const textAreaValue = textAreaElement.value; + const previousAtPosition = textAreaValue.lastIndexOf('@', cursorPosition - 1); + const previousHashtagPosition = textAreaValue.lastIndexOf('#', cursorPosition - 1); + if (cursorPosition === 0 || (previousAtPosition === -1 && previousHashtagPosition === -1)) { + return; + } + let nextSpacePos = textAreaValue.indexOf(' ', cursorPosition); + if (nextSpacePos === -1) nextSpacePos = textAreaValue.length; + const previousTriggerPosition = Math.max(previousAtPosition, previousHashtagPosition); + + const triggerWord = textAreaValue.substring(previousTriggerPosition + 1, nextSpacePos); + + const menuItems = quickAccessMenuElement.querySelectorAll('button'); + menuItems.forEach((item) => { + const itemText = item.textContent; + + if (itemText.toLowerCase().includes(triggerWord.toLowerCase())) { + item.style.display = 'block'; + } else { + item.style.display = 'none'; + } + }); +} +function quickAccessMenu(trigger) { + const existingMenu = document.querySelector('#quick-access-menu'); + if (existingMenu) { + existingMenu.remove(); + return; + } + const menu = document.createElement('div'); + menu.id = 'quick-access-menu'; + menu.classList = 'absolute flex flex-col gap-2 bg-white dark:bg-gray-800 border border-white/20 rounded shadow-xs'; + menu.style = 'height: 300px; top:-300px; left:0; width:100%; z-index: 1000;'; + const menuHeader = document.createElement('div'); + menuHeader.classList = 'flex justify-between items-center p-2 border-b dark:border-white/20 border-gray-900/50'; + const menuTitle = document.createElement('h3'); + menuTitle.classList = 'text-lg font-bold'; + menuHeader.appendChild(menuTitle); + const menuHeaderButton = document.createElement('button'); + menuHeaderButton.classList = 'btn flex justify-center gap-2 btn-primary border-0 md:border'; + menuHeaderButton.type = 'button'; + menuHeader.appendChild(menuHeaderButton); + menu.appendChild(menuHeader); + if (trigger === '@') { + menuTitle.textContent = 'Custom Prompts (@)'; + menuHeaderButton.id = 'see-all-custom-prompts'; + menuHeaderButton.textContent = '+ Add More'; + menuHeaderButton.addEventListener('click', () => { + menu.remove(); + createSettingsModal(3); + }); + menu.appendChild(loadCustomPrompts()); + } + if (trigger === '#') { + menuTitle.textContent = 'Prompt Chains (#)'; + menuHeaderButton.id = 'see-all-prompt-chains'; + menuHeaderButton.textContent = 'See All Prompt Chains'; + menuHeaderButton.addEventListener('click', () => { + menu.remove(); + createPromptChainListModal(); + }); + menu.appendChild(loadPromptChains()); + } + const textAreaElement = document.querySelector('main form textarea'); + textAreaElement.parentElement.appendChild(menu); +} +function loadCustomPrompts() { + const menuContent = document.createElement('div'); + menuContent.id = 'quick-access-menu-content'; + menuContent.classList = 'flex flex-col gap-2'; + menuContent.style = 'overflow-y: scroll;height: 100%; width: 100%;padding:1px;'; + chrome.storage.local.get(['customPrompts'], (result) => { + let { customPrompts } = result; + if (!customPrompts) customPrompts = defaultPrompts; + const sortedCustomPrompts = customPrompts.sort((a, b) => a.title.localeCompare(b.title)); + for (let i = 0; i < sortedCustomPrompts.length; i += 1) { + const prompt = sortedCustomPrompts[i]; + const promptElement = document.createElement('button'); + promptElement.id = `quick-access-menu-item-${i}`; + promptElement.classList = 'btn w-full text-left focus:outline focus:ring-2 focus:ring-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700'; + promptElement.innerHTML = `${prompt.title}${prompt.text}`; + promptElement.addEventListener('click', () => { + const inputForm = document.querySelector('main form'); + if (!inputForm) return; + const textAreaElement = inputForm.querySelector('textarea'); + if (!textAreaElement) return; + document.querySelector('#quick-access-menu').remove(); + // find the neeares previous @ position + const textAreaValue = textAreaElement.value; + const cursorPosition = textAreaElement.selectionStart; + const previousAtPosition = textAreaValue.lastIndexOf('@', cursorPosition); + const newText = textAreaValue.substring(0, previousAtPosition) + prompt.text + textAreaValue.substring(cursorPosition); + textAreaElement.value = newText; + textAreaElement.focus(); + }); + menuContent.appendChild(promptElement); + } + }); + return menuContent; +} +function loadPromptChains() { + const menuContent = document.createElement('div'); + menuContent.id = 'quick-access-menu-content'; + menuContent.classList = 'flex flex-col gap-2'; + menuContent.style = 'overflow-y: scroll;height: 100%; width: 100%;padding:1px;'; + chrome.storage.local.get(['promptChains'], (result) => { + const { promptChains } = result; + const sortedPromptChains = promptChains.sort((a, b) => a.title.localeCompare(b.title)); + if (!promptChains) { + const noPromptChains = document.createElement('div'); + noPromptChains.classList = 'text-center text-gray-500'; + noPromptChains.textContent = 'You haven\'t created any prompt chain yet.'; + menuContent.appendChild(noPromptChains); + return; + } + for (let i = 0; i < sortedPromptChains.length; i += 1) { + const prompt = sortedPromptChains[i]; + const promptElement = document.createElement('button'); + promptElement.id = `quick-access-menu-item-${i}`; + promptElement.classList = 'btn w-full text-left focus:outline focus:ring-2 focus:ring-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700'; + promptElement.innerHTML = `${prompt.title}`; + promptElement.addEventListener('click', () => { + runPromptChain(prompt.steps, false); + }); + menuContent.appendChild(promptElement); + } + }); + return menuContent; +} diff --git a/scripts/content/regenerateResponse.js b/scripts/content/regenerateResponse.js index 52b989e..bf24fb3 100644 --- a/scripts/content/regenerateResponse.js +++ b/scripts/content/regenerateResponse.js @@ -5,9 +5,7 @@ function toggleOriginalRegenerateResponseButton() { const allMessageWrapper = document.querySelectorAll('[id^="message-wrapper-"]'); const lastMessageWrapperElement = allMessageWrapper[allMessageWrapper.length - 1]; const anyUserMessageWrappers = document.querySelectorAll('[id^="message-wrapper-"][data-role="user"]').length > 0; - const main = document.querySelector('main'); - if (!main) return; - const inputForm = main.querySelector('form'); + const inputForm = document.querySelector('main form'); if (!inputForm) return; const submitButton = inputForm.querySelector('textarea ~ button'); if (!submitButton) return; @@ -52,7 +50,7 @@ function toggleOriginalRegenerateResponseButton() { const newRegenerateResponseButton = document.createElement('button'); newRegenerateResponseButton.id = 'regenerate-response-button'; newRegenerateResponseButton.type = 'button'; - newRegenerateResponseButton.classList = `btn flex justify-center gap-2 ${textAreaElementWrapper.style.display === 'none' ? 'btn-primary' : 'btn-neutral'} border-0 md:border`; + newRegenerateResponseButton.classList = `btn flex justify-center gap-2 ${textAreaElementWrapper.style.display === 'none' ? 'btn-primary' : 'btn-neutral'} border`; newRegenerateResponseButton.innerHTML = ' Regenerate response'; newRegenerateResponseButton.addEventListener('click', () => { window.localStorage.removeItem('arkoseToken'); @@ -98,10 +96,9 @@ function toggleOriginalRegenerateResponseButton() { const newContinueGeneratingButton = document.createElement('button'); newContinueGeneratingButton.id = 'continue-generating-button'; newContinueGeneratingButton.type = 'button'; - newContinueGeneratingButton.classList = `btn flex justify-center gap-2 ${textAreaElementWrapper.style.display === 'none' ? 'btn-primary' : 'btn-neutral'} border-0 md:border`; + newContinueGeneratingButton.classList = `btn flex justify-center gap-2 ${textAreaElementWrapper.style.display === 'none' ? 'btn-primary' : 'btn-neutral'} border`; newContinueGeneratingButton.innerHTML = ' Continue generating'; newContinueGeneratingButton.addEventListener('click', () => { - window.localStorage.removeItem('arkoseToken'); chrome.storage.local.get(['conversations', 'settings', 'models'], (result) => { if (result.settings.selectedModel.slug.includes('gpt-4')) { if (!inputForm.querySelector('#enforcement-trigger')) { diff --git a/scripts/content/rowUser.js b/scripts/content/rowUser.js index 1a1ae95..dad69f3 100644 --- a/scripts/content/rowUser.js +++ b/scripts/content/rowUser.js @@ -17,8 +17,8 @@ function rowUser(conversation, node, childIndex, childCount, name, avatar, custo return `
- -
+ +
Learn More)', 'promptTemplate', true); leftContent.appendChild(promptTemplateSwitch); // conversation width @@ -168,8 +168,8 @@ function generalTabContent() { const importExportWrapper = document.createElement('div'); importExportWrapper.style = 'display: flex; flex-direction: row; flex-wrap: wrap; justify-content: start; align-items: center; width: 100%; margin: 8px 0; color:white;'; const importExportLabel = document.createElement('div'); - importExportLabel.style = 'display: flex; flex-direction: column; justify-content: start; align-items: start; width: 100%; margin: 8px 0;'; - importExportLabel.textContent = 'Import / Export Settings, Custom Prompts, and Folders'; + importExportLabel.style = 'width: 100%; margin: 8px 0;'; + importExportLabel.innerHTML = 'Import / Export Settings, Custom Prompts, and Folders (Learn More)'; importExportWrapper.appendChild(importExportLabel); const importExportButtonWrapper = document.createElement('div'); @@ -447,6 +447,9 @@ function autoSyncTabContent() { chrome.storage.local.get(['settings'], (result) => { const { autoSync } = result.settings; + const quickSyncSwitch = createSwitch('Quick Sync', 'OFF: Sync All Conversations, ON: Sync only the last 100 conversations (Best performance)', 'quickSync', false, resetSync, 'Experimental - Requires Auto-Sync', !autoSync); + content.appendChild(quickSyncSwitch); + const conversationTimestampSwitch = createSwitch('Conversation Timestamp', 'OFF: Created time, ON: Last updated time', 'conversationTimestamp', false, reloadConversationList, 'Requires Auto-Sync', !autoSync); content.appendChild(conversationTimestampSwitch); @@ -461,6 +464,18 @@ function autoSyncTabContent() { }); return content; } +function resetSync() { + chrome.storage.sync.set({ + conversationsOrder: [], + }, () => { + chrome.storage.local.set({ + conversations: {}, + conversationsAreSynced: false, + }, () => { + refreshPage(); + }); + }); +} function reloadConversationList() { loadConversationList(true); } @@ -495,7 +510,7 @@ function modelsTabContent() { modelSwitcherRow.appendChild(modelSwitcherWrapper); content.appendChild(modelSwitcherRow); const betaTag = document.createElement('span'); - betaTag.style = 'background-color: #ff9800; color: white; padding: 2px 4px; border-radius: 8px; font-size: 0.6em;margin-top:8px;'; + betaTag.style = 'background-color: #ff9800; color: black; padding: 2px 4px; border-radius: 8px; font-size: 0.7em;margin-top:8px;'; betaTag.textContent = 'Requires Auto-Sync'; content.appendChild(betaTag); chrome.storage.local.get(['settings', 'models', 'unofficialModels', 'customModels'], (result) => { @@ -523,7 +538,7 @@ function modelsTabContent() { newCustomModelInputWrapper.style = 'display: flex; flex-direction: row; justify-content: start; align-items: start; width: 100%; margin: 8px 0;'; const newCustomModelWrapperTitle = document.createElement('div'); newCustomModelWrapperTitle.style = 'width: 100%; margin: 8px 0;color: #eee;'; - newCustomModelWrapperTitle.innerHTML = 'Add a Custom ModelExperimental'; + newCustomModelWrapperTitle.innerHTML = 'Add a Custom ModelExperimental'; const newCustomModelSlug = document.createElement('input'); newCustomModelSlug.style = 'width: 160px; height: 34px; border-radius: 4px; border: 1px solid #565869; background-color: #0b0d0e;margin-right:8px; color: #eee; padding: 0 8px; font-size: 14px;'; @@ -662,7 +677,7 @@ function customPromptTabContent() { const helperText = document.createElement('div'); helperText.style = 'color: #999; font-size: 12px; margin: 8px 0;'; - helperText.textContent = 'Tip: You can use @promptTitle anywhere in your prompt input to replace it with the prompt text. For this feature to work make sure you don\'t have any space in the prompt title. Smart replace is not case sensitive.'; + helperText.textContent = 'Tip: You can use @promptTitle anywhere in your prompt input to automatically replace it with the prompt text. For this feature to work make sure you don\'t have any space in the prompt title. Smart replace is not case sensitive.'; const repeatedNameError = document.createElement('div'); repeatedNameError.id = 'repeated-name-error'; @@ -903,7 +918,7 @@ function exportTabContent() { exportNamingFormatLabel.style = 'display: flex; flex-direction: row; justify-content: start; align-items: center; width: 100%; margin: 8px 0; color:white; opacity: 0.5;'; exportNamingFormatLabel.textContent = 'Export naming format'; const betaTag = document.createElement('span'); - betaTag.style = 'background-color: #ff9800; color: white; padding: 2px 4px; border-radius: 8px; margin-left: 8px; font-size: 0.6em;'; + betaTag.style = 'background-color: #ff9800; color: black; padding: 2px 4px; border-radius: 8px; margin-left: 8px; font-size: 0.7em;'; betaTag.textContent = 'Coming soon'; content.appendChild(exportModeSwitchWrapper); content.appendChild(exportNamingFormatLabel); @@ -920,8 +935,8 @@ function splitterTabContent() { const { autoSync } = result.settings; const splitterSwitchWrapper = document.createElement('div'); splitterSwitchWrapper.style = 'display: flex; gap:16px; justify-content: start; align-items: start; width: 100%; margin: 8px 0;'; - const autoSplitSwitch = createSwitch('Auto Split', 'Automatically split long prompts into smaller chunks', 'autoSplit', true, null, 'Requires Auto-Sync', !autoSync); - const autoSummarizeSwitch = createSwitch('Auto Summarize', 'Automatically summarize each chunk after auto split', 'autoSummarize', false, updateAutoSplitPrompt, 'Requires Auto-Sync', !autoSync); + const autoSplitSwitch = createSwitch('Auto Split', 'Automatically split long prompts into smaller chunks (Learn More)', 'autoSplit', true, null, 'Requires Auto-Sync', !autoSync); + const autoSummarizeSwitch = createSwitch('Auto Summarize', 'Automatically summarize each chunk after auto split (Learn More)', 'autoSummarize', false, updateAutoSplitPrompt, 'Requires Auto-Sync', !autoSync); const autoSplitChunkSizeLabel = document.createElement('div'); autoSplitChunkSizeLabel.style = 'display: flex; flex-direction: row; justify-content: start; align-items: center; width: 100%; margin: 8px 0; color:white;'; @@ -1047,7 +1062,7 @@ function createSwitch(title, subtitle, settingsKey, defaultValue, callback = nul input.type = 'checkbox'; input.disabled = disabled; const betaTag = document.createElement('span'); - betaTag.style = 'background-color: #ff9800; color: white; padding: 2px 4px; border-radius: 8px; margin-left: 8px; font-size: 0.6em;'; + betaTag.style = 'background-color: #ff9800; color: black; padding: 2px 4px; border-radius: 8px; font-size: 0.7em;border:'; betaTag.textContent = tag; const helper = document.createElement('div'); helper.style = 'font-size: 12px; color: #999;'; @@ -1211,6 +1226,7 @@ function addSettingsButton() { const settingsButton = document.createElement('a'); settingsButton.classList = 'flex py-3 px-3 items-center gap-3 rounded-md hover:bg-gray-500/10 transition-colors duration-200 text-white cursor-pointer text-sm'; settingsButton.textContent = 'Settings'; + settingsButton.title = 'CMD/CTRL + SHIFT + S'; const settingsButtonIcon = document.createElement('img'); settingsButtonIcon.style = 'width: 16px; height: 16px;'; @@ -1248,6 +1264,8 @@ function initializeSettings() { settings: { ...result.settings, autoSync: result.settings?.autoSync !== undefined ? result.settings.autoSync : true, + quickSync: result.settings?.quickSync !== undefined ? result.settings.quickSync : false, + quickSyncCount: result.settings?.quickSyncCount !== undefined ? result.settings.quickSyncCount : 100, safeMode: result.settings?.safeMode !== undefined ? result.settings.safeMode : true, promptHistory: result.settings?.promptHistory !== undefined ? result.settings.promptHistory : true, copyMode: result.settings?.copyMode !== undefined ? result.settings.copyMode : false, @@ -1274,7 +1292,7 @@ Let's begin: `, autoSplitChunkPrompt: result.settings?.autoSplitChunkPrompt !== undefined ? result.settings?.autoSplitChunkPrompt : `Reply with OK: [CHUNK x/TOTAL] Don't reply with anything else!`, - conversationTimestamp: result.settings?.conversationTimestamp !== undefined ? result.settings.conversationTimestamp : false, + conversationTimestamp: result.settings?.conversationTimestamp !== undefined ? result.settings.conversationTimestamp : true, autoHideTopNav: result.settings?.autoHideTopNav !== undefined ? result.settings.autoHideTopNav : false, navOpen: result.settings?.navOpen !== undefined ? result.settings.navOpen : true, showPinNav: result.settings?.showPinNav !== undefined ? result.settings.showPinNav : true, diff --git a/scripts/content/stopGeneratingResponse.js b/scripts/content/stopGeneratingResponse.js index b60b321..5ddc724 100644 --- a/scripts/content/stopGeneratingResponse.js +++ b/scripts/content/stopGeneratingResponse.js @@ -2,15 +2,11 @@ // eslint-disable-next-line no-unused-vars /* global isGenerating, chatStreamIsClosed:true */ function toggleStopGeneratingResponseButton() { - const main = document.querySelector('main'); - if (!main) return; - const inputForm = main.querySelector('form'); - if (!inputForm) return; - const submitButton = inputForm.querySelector('textarea ~ button'); + const textAreaElement = document.querySelector('main form textarea'); + if (!textAreaElement) return; + const submitButton = document.querySelector('main form textarea ~ button'); if (!submitButton) return; - const textAreaElement = inputForm.querySelector('textarea'); - if (!textAreaElement) return; const textAreaElementWrapper = textAreaElement.parentNode; const nodeBeforetTextAreaElement = textAreaElementWrapper.previousSibling; if (!nodeBeforetTextAreaElement) return; @@ -29,7 +25,7 @@ function toggleStopGeneratingResponseButton() { const newStopGeneratingResponseButton = document.createElement('button'); newStopGeneratingResponseButton.id = 'stop-generating-response-button'; newStopGeneratingResponseButton.type = 'button'; - newStopGeneratingResponseButton.classList = 'btn flex justify-center gap-2 btn-neutral border-0 md:border'; + newStopGeneratingResponseButton.classList = 'btn flex justify-center gap-2 btn-neutral border'; newStopGeneratingResponseButton.innerHTML = 'Stop generating'; newStopGeneratingResponseButton.addEventListener('click', () => { chatStreamIsClosed = true; diff --git a/scripts/content/templateWordsModal.js b/scripts/content/templateWordsModal.js index 2b76ae2..4d5e226 100644 --- a/scripts/content/templateWordsModal.js +++ b/scripts/content/templateWordsModal.js @@ -1,8 +1,9 @@ /* global createModal */ // eslint-disable-next-line no-unused-vars function createTemplateWordsModal(templateWords) { - const bodyContent = templateWordsModalContent(templateWords); - const actionsBarContent = templateWordsModalActions(templateWords); + const uniqueTemplateWords = [...new Set(templateWords)]; + const bodyContent = templateWordsModalContent(uniqueTemplateWords); + const actionsBarContent = templateWordsModalActions(uniqueTemplateWords); createModal('Template words', 'Please replace the template words', bodyContent, actionsBarContent, true); } @@ -39,10 +40,8 @@ function templateWordsModalActions(templateWords) { submitButton.innerHTML = 'Submit'; submitButton.id = 'modal-submit-button'; - submitButton.addEventListener('click', () => { - const main = document.querySelector('main'); - const inputForm = main.querySelector('form'); - const textAreaElement = inputForm.querySelector('textarea'); + submitButton.addEventListener('click', (e) => { + const textAreaElement = document.querySelector('main form textarea'); // replace template words in text area value with the input values associated with them let newValue = textAreaElement.value; templateWords.forEach((templateWord) => { @@ -54,6 +53,10 @@ function templateWordsModalActions(templateWords) { if (document.querySelector('[id*=close-button]')) { document.querySelector('[id*=close-button]').click(); } + if (!e.shiftKey) { + const chatSubmitButton = document.querySelector('main form textarea ~ button'); + chatSubmitButton.click(); + } }); actionBar.appendChild(submitButton); return actionBar; diff --git a/scripts/content/timestamp.js b/scripts/content/timestamp.js index 80b1341..be34e7e 100644 --- a/scripts/content/timestamp.js +++ b/scripts/content/timestamp.js @@ -79,9 +79,9 @@ function updateTimestamp(conversationList) { timestamp.style = 'font-size: 10px; color: lightslategray; position: absolute; bottom: 0px; left: 40px;'; const conversation = conversations[index]; button.id = `conversation-button-${conversation.id}`; - const createTime = conversation.create_time; + const updateTime = conversation.update_time; // convert create time from GMT to local time - timestamp.innerHTML = formatDate(new Date(createTime)); + timestamp.innerHTML = formatDate(new Date(updateTime)); button.appendChild(timestamp); // add checkbox const checkboxWrapper = document.createElement('div'); diff --git a/scripts/styles/global.css b/scripts/styles/global.css index ae4191c..c76ae7f 100644 --- a/scripts/styles/global.css +++ b/scripts/styles/global.css @@ -33,6 +33,17 @@ input[type="checkbox"]:checked:focus { /* sortable */ .sortable-ghost { background-color: #ffd70050 !important; + opacity: 0.5 !important; +} +.sortable-drag{ + border: solid 1px #ffd700 !important; + border-radius: 4px !important; +} +.multi-drag-selected #prompt-chain-drag-handle{ + background-color: #ffd70050 !important; +} +.multi-drag-selected [id^="conversation-button-"]{ + background-color: #ffd70050 !important; } /* tailwind custom*/ .hover\:pr-20:hover { @@ -66,7 +77,12 @@ input[type="checkbox"]:checked:focus { border-bottom-left-radius: .375rem; border-bottom-right-radius: .375rem } - +ul.hidden { + display: none !important; +} +#fabCon{ + display: none !important; +} /* toggle switch */ .switch { position: relative; From f6146ad9a100512b6cbe32cd36756e4974f36c5f Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Mon, 17 Jul 2023 12:20:05 -0700 Subject: [PATCH 05/38] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20quick=20sync?= =?UTF-8?q?=20removing=20folders?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/content/settings.js | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/manifest.json b/manifest.json index 586ac4d..76e9aec 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.0.0", + "version": "5.0.1", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/content/settings.js b/scripts/content/settings.js index 05eec30..4d8db17 100644 --- a/scripts/content/settings.js +++ b/scripts/content/settings.js @@ -465,15 +465,11 @@ function autoSyncTabContent() { return content; } function resetSync() { - chrome.storage.sync.set({ - conversationsOrder: [], + chrome.storage.local.set({ + conversations: {}, + conversationsAreSynced: false, }, () => { - chrome.storage.local.set({ - conversations: {}, - conversationsAreSynced: false, - }, () => { - refreshPage(); - }); + refreshPage(); }); } function reloadConversationList() { From d254272ae4bf3fba941653557acbbc83c152c30f Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Mon, 17 Jul 2023 23:47:58 -0700 Subject: [PATCH 06/38] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20import=20prom?= =?UTF-8?q?pt,=20add=20buyme=20coffee?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/content/promptHistory.js | 3 ++- scripts/content/settings.js | 18 +++++++++++++++++- scripts/styles/global.css | 26 ++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/manifest.json b/manifest.json index 76e9aec..61f7f08 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.0.1", + "version": "5.0.2", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/content/promptHistory.js b/scripts/content/promptHistory.js index 82a42b0..039ec7c 100644 --- a/scripts/content/promptHistory.js +++ b/scripts/content/promptHistory.js @@ -401,9 +401,10 @@ function historyModalActions() { reader.onload = (e) => { const importedHistory = JSON.parse(e.target.result); const existingHistory = result.userInputValueHistory; + // only add new items importedHistory.forEach((importedItem) => { - const existingItem = existingHistory.find((item) => item.text === importedItem.text); + const existingItem = existingHistory.find((item) => item.inputValue === importedItem.inputValue); if (!existingItem) { existingHistory.push(importedItem); } diff --git a/scripts/content/settings.js b/scripts/content/settings.js index 4d8db17..cf06f64 100644 --- a/scripts/content/settings.js +++ b/scripts/content/settings.js @@ -1111,7 +1111,7 @@ function refreshPage() { function settingsModalActions() { // add actionbar at the bottom of the content const actionBar = document.createElement('div'); - actionBar.style = 'display: flex; flex-direction: row; justify-content: start; align-items: end; margin-top: 8px;'; + actionBar.style = 'display: flex; flex-direction: row; justify-content: start; align-items: end; margin-top: 8px;width:100%;'; const logo = document.createElement('img'); logo.src = chrome.runtime.getURL('icons/logo.png'); logo.style = 'width: 40px; height: 40px;'; @@ -1210,6 +1210,22 @@ function settingsModalActions() { textWrapper.appendChild(madeBy); actionBar.appendChild(textWrapper); + + const buyMeAPizza = document.createElement('a'); + buyMeAPizza.classList = 'flex py-3 px-3 items-center gap-3 rounded-md bg-gold hover:bg-gold-dark transition-colors duration-200 text-black cursor-pointer text-sm ml-auto font-bold'; + buyMeAPizza.textContent = '🍕 Buy me a pizza'; + // make the button shake every 5 seconds + setInterval(() => { + buyMeAPizza.classList.add('animate-shake'); + setTimeout(() => { + buyMeAPizza.classList.remove('animate-shake'); + }, 1000); + }, 7000); + + buyMeAPizza.href = 'https://www.buymeacoffee.com/ezii'; + buyMeAPizza.target = '_blank'; + + actionBar.appendChild(buyMeAPizza); return actionBar; } function addSettingsButton() { diff --git a/scripts/styles/global.css b/scripts/styles/global.css index c76ae7f..f415c4b 100644 --- a/scripts/styles/global.css +++ b/scripts/styles/global.css @@ -52,6 +52,12 @@ input[type="checkbox"]:checked:focus { .border-gold { border-color: gold; } +.bg-gold { + background-color: gold; +} +.hover\:bg-gold-dark:hover { + background-color: #f1cb00; +} .pr-20 { padding-right: 5rem; } @@ -77,6 +83,26 @@ input[type="checkbox"]:checked:focus { border-bottom-left-radius: .375rem; border-bottom-right-radius: .375rem } +/* Make a button shake */ +.animate-shake{ + animation: shake 0.5s; + animation-iteration-count: infinite; +} +/* shake animation */ +@keyframes shake { + 0% { transform: translate(1px, 1px) rotate(0deg); } + 10% { transform: translate(-1px, -2px) rotate(-1deg); } + 20% { transform: translate(-3px, 0px) rotate(1deg); } + 30% { transform: translate(3px, 2px) rotate(0deg); } + 40% { transform: translate(1px, -1px) rotate(1deg); } + 50% { transform: translate(-1px, 2px) rotate(-1deg); } + 60% { transform: translate(-3px, 1px) rotate(0deg); } + 70% { transform: translate(3px, 1px) rotate(-1deg); } + 80% { transform: translate(-1px, -1px) rotate(1deg); } + 90% { transform: translate(1px, 2px) rotate(0deg); } + 100% { transform: translate(1px, -2px) rotate(-1deg); } +} + ul.hidden { display: none !important; } From 65ef9fe89e237239289019d6f8b7192d43585c47 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Wed, 19 Jul 2023 17:43:31 -0700 Subject: [PATCH 07/38] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20message=20cap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/content/api.js | 5 +++-- scripts/content/conversationList.js | 2 +- scripts/content/global.js | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/manifest.json b/manifest.json index 61f7f08..d44f55a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.0.2", + "version": "5.0.3", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/content/api.js b/scripts/content/api.js index 9fe69f2..371f164 100644 --- a/scripts/content/api.js +++ b/scripts/content/api.js @@ -141,10 +141,11 @@ function getModels() { }); } function getConversationLimit() { - return fetch('https://chat.openai.com/public-api/conversation_limit', { + return chrome.storage.sync.get(['auth_token']).then((result) => fetch('https://chat.openai.com/public-api/conversation_limit', { method: 'GET', headers: { ...defaultHeaders, + Authorization: result.auth_token, }, }).then((response) => response.json()) .then((data) => { @@ -153,7 +154,7 @@ function getConversationLimit() { conversationLimit: data, }); } - }); + })); } function messageFeedback(conversationId, messageId, rating, text = '') { const data = { diff --git a/scripts/content/conversationList.js b/scripts/content/conversationList.js index 39b4c22..56af0d8 100644 --- a/scripts/content/conversationList.js +++ b/scripts/content/conversationList.js @@ -546,7 +546,7 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode const now = new Date().getTime(); const gpt4CounterElement = document.querySelector('#gpt4-counter'); gpt4CounterElement.style.display = result.settings.showGpt4Counter ? 'block' : 'none'; - const messageCap = result?.conversationLimit?.message_cap || 25; + const messageCap = result?.conversationLimit?.message_cap || 50; const messageCapWindow = result?.conversationLimit?.message_cap_window || 180; if (gpt4Timestamps) { gpt4Timestamps.push(now); diff --git a/scripts/content/global.js b/scripts/content/global.js index 87b310d..c8cba7c 100644 --- a/scripts/content/global.js +++ b/scripts/content/global.js @@ -507,8 +507,8 @@ function addGpt4Counter() { if (!result.models.find((model) => model.slug === 'gpt-4')) return; gpt4CounterElement.style.display = result.settings.showGpt4Counter ? 'block' : 'none'; const gpt4Timestamps = result.gpt4Timestamps || []; - const messageCap = result?.conversationLimit?.message_cap || 24; - const messageCapWindow = result?.conversationLimit?.message_cap_window || 181; + const messageCap = result?.conversationLimit?.message_cap || 50; + const messageCapWindow = result?.conversationLimit?.message_cap_window || 180; const now = new Date().getTime(); const gpt4counter = gpt4Timestamps.filter((timestamp) => now - timestamp < (messageCapWindow / 60) * 60 * 60 * 1000).length; const capExpiresAtTimeString = result.capExpiresAt ? `(Cap Expires At: ${result.capExpiresAt})` : ''; From d45b1290e40abc0fa61aa1201a580b6489cf3766 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Mon, 24 Jul 2023 21:51:23 -0700 Subject: [PATCH 08/38] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20custom=20instructi?= =?UTF-8?q?on=20profiles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + manifest.json | 3 +- scripts/content/api.js | 41 +++ scripts/content/autoSave.js | 3 +- scripts/content/continue.js | 2 +- scripts/content/conversation.js | 11 +- scripts/content/conversationList.js | 237 +++++++++-------- scripts/content/customInstructions.js | 370 ++++++++++++++++++++++++++ scripts/content/dropdown.js | 2 +- scripts/content/global.js | 18 +- scripts/content/initialize.js | 3 +- scripts/content/modelSwitcher.js | 2 +- scripts/content/newChatPage.js | 91 +++++-- scripts/content/pluginStore.js | 2 +- scripts/content/pluginsDropdown.js | 2 +- scripts/content/promptHistory.js | 8 +- scripts/content/settings.js | 77 +++++- 17 files changed, 707 insertions(+), 167 deletions(-) create mode 100644 scripts/content/customInstructions.js diff --git a/README.md b/README.md index 7cf64bc..8127eea 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,8 @@ https://user-images.githubusercontent.com/574142/232172841-50f1b114-ef47-4533-a6 ## Utilities for ChatGPT +👥 Custom Instruction Profiles: Easily create and save multiple custom instruction profiles and quickly access them with a click of a button + ✂️ Auto Splitter: Automatically split your long input into smaller chunks and send them to ChatGPT one by one. 🗒 Auto summarize: Using the power of auto-splitter, your long text will be summarized into a shorter version so you can ask question diff --git a/manifest.json b/manifest.json index d44f55a..0123a83 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.0.3", + "version": "5.0.4", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { @@ -56,6 +56,7 @@ "scripts/content/keyboardShortcuts.js", "scripts/content/modelSwitcher.js", "scripts/content/enableMFA.js", + "scripts/content/customInstructions.js", "scripts/content/pluginsDropdown.js", "scripts/content/dropdown.js", "scripts/content/templateWordsModal.js", diff --git a/scripts/content/api.js b/scripts/content/api.js index 371f164..75ac214 100644 --- a/scripts/content/api.js +++ b/scripts/content/api.js @@ -113,6 +113,47 @@ function getAccount() { // "system_message" // ] // } +function setUserSystemMessage(aboutUser, aboutModel, enabled) { + const data = { + about_user_message: aboutUser, + about_model_message: aboutModel, + enabled, + }; + return chrome.storage.sync.get(['auth_token']).then((result) => fetch('https://chat.openai.com/backend-api/user_system_messages', { + method: 'POST', + headers: { + ...defaultHeaders, + Authorization: result.auth_token, + }, + body: JSON.stringify(data), + }).then((res) => res.json())); +} +function getUserSystemMessage() { + return chrome.storage.sync.get(['auth_token']).then((result) => fetch('https://chat.openai.com/backend-api/user_system_messages', { + method: 'GET', + headers: { + ...defaultHeaders, + Authorization: result.auth_token, + }, + }).then((res) => res.json())) + .then((data) => { + chrome.storage.local.get(['customInstructionProfiles'], (result) => { + const { customInstructionProfiles } = result; + + const newCustomInstructionProfiles = customInstructionProfiles.map((p) => { + if (p.isSelected) { + return { ...p, isSelected: false }; + } + if (p.aboutModel === data.about_model_message && p.aboutUser === data.about_user_message) { + return { ...p, isSelected: true }; + } + return p; + }); + chrome.storage.local.set({ customInstructionProfiles: newCustomInstructionProfiles }); + }); + return data; + }); +} function getModels() { return chrome.storage.sync.get(['auth_token']).then((result) => fetch('https://chat.openai.com/backend-api/models', { method: 'GET', diff --git a/scripts/content/autoSave.js b/scripts/content/autoSave.js index 69768fd..628a0c9 100644 --- a/scripts/content/autoSave.js +++ b/scripts/content/autoSave.js @@ -138,7 +138,8 @@ function updateOrCreateConversation(conversationId, message, parentId, settings, const mapping = Object.values(existingConversation.mapping); if (generateTitle && existingConversation.title === 'New chat' && mapping.length < 5 && mapping.filter((m) => m.message?.author.role === 'assistant').length === 1) { // only one assistant message if (settings.saveHistory) { - generateTitleForConversation(existingConversation.id, message.id); + const systemMessage = mapping.find((m) => m.message?.author.role === 'system'); + generateTitleForConversation(existingConversation.id, message.id, systemMessage?.message?.metadata?.user_context_message_data); } } else if (settings.conversationTimestamp) { // === updated // move cnversationelemnt after searchbox diff --git a/scripts/content/continue.js b/scripts/content/continue.js index bf305b8..d8475ab 100644 --- a/scripts/content/continue.js +++ b/scripts/content/continue.js @@ -20,7 +20,7 @@ function promptDropdown() { const dropdownItem = document.createElement('li'); dropdownItem.id = `continue-conversation-dropdown-item-${promptTitle}`; dropdownItem.dir = 'auto'; - dropdownItem.classList = 'text-gray-900 relative cursor-pointer select-none border-b p-2 last:border-0 border-gray-100 dark:border-white/20 hover:bg-gray-600'; + dropdownItem.classList = 'text-gray-900 relative cursor-pointer select-none border-b p-2 last:border-0 border-gray-100 dark:border-white/20 hover:bg-gray-500/10'; const dropdownOption = document.createElement('span'); dropdownOption.classList = 'font-semibold flex h-6 items-center gap-1 truncate text-gray-800 dark:text-gray-100'; dropdownOption.style = 'text-transform: capitalize;'; diff --git a/scripts/content/conversation.js b/scripts/content/conversation.js index 621bb74..72e5d9d 100644 --- a/scripts/content/conversation.js +++ b/scripts/content/conversation.js @@ -71,9 +71,9 @@ function updateModel(modelSlug, fullConversation) { enabledPluginIds: pluginIds || enabledPluginIds, }, () => { // get the li with id of the language code and click it - document.querySelector('#language-list-dropdown').querySelector(`li#language-selector-option-${languageCode}`).click(); - document.querySelector('#tone-list-dropdown').querySelector(`li#tone-selector-option-${toneCode}`).click(); - document.querySelector('#writing-style-list-dropdown').querySelector(`li#writing-style-selector-option-${writingStyleCode}`).click(); + document.querySelector(`#language-list-dropdown li#language-selector-option-${languageCode}`)?.click(); + document.querySelector(`#tone-list-dropdown li#tone-selector-option-${toneCode}`)?.click(); + document.querySelector(`#writing-style-list-dropdown li#writing-style-selector-option-${writingStyleCode}`)?.click(); }); }); } @@ -191,7 +191,10 @@ function loadConversation(conversationId, searchValue = '', focusOnInput = true) } sortedNodes.reverse(); //-------- - let messageDiv = `
${folderName ? `${folderName}    ›    ` : ''}${fullConversation.title}
`; + const systemMessage = sortedNodes.find((node) => node?.message?.author?.role === 'system'); + const customInstrucionProfile = systemMessage?.message?.metadata?.user_context_message_data || undefined; + + let messageDiv = `
${folderName ? `${folderName}    ›    ` : ''}${fullConversation.title}${customInstrucionProfile ? `   •   Custom instructions: On  ` : ''}
`; if (fullConversation.archived) { messageDiv = '
This is an archived chat. You can read archived chats, but you cannot continue them.
'; } diff --git a/scripts/content/conversationList.js b/scripts/content/conversationList.js index 56af0d8..654b473 100644 --- a/scripts/content/conversationList.js +++ b/scripts/content/conversationList.js @@ -311,7 +311,7 @@ function prependConversation(conversation) { conversationList.scrollTop = 0; } // eslint-disable-next-line no-unused-vars -function generateTitleForConversation(conversationId, messageId) { +function generateTitleForConversation(conversationId, messageId, profile) { setTimeout(() => { generateTitle(conversationId, messageId).then((data) => { const { title } = data; @@ -330,6 +330,10 @@ function generateTitleForConversation(conversationId, messageId) { if (topDiv) topDiv.innerHTML += c; }, i * 50); }); + // at the end, add sss + setTimeout(() => { + if (topDiv) topDiv.innerHTML += `   •   Custom instructions: On  `; + }, title.length * 50); chrome.storage.local.get('conversations', (result) => { const { conversations } = result; @@ -554,12 +558,12 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode const gpt4TimestampsFiltered = gpt4Timestamps.filter((timestamp) => timestamp > hoursAgo); chrome.storage.local.set({ gpt4Timestamps: gpt4TimestampsFiltered, capExpiresAt: '' }); if (gpt4CounterElement) { - gpt4CounterElement.innerText = `GPT4 requests (last ${getGPT4CounterMessageCapWindow(messageCapWindow)}): ${gpt4TimestampsFiltered.length}/${messageCap}`; + gpt4CounterElement.innerText = `GPT-4 requests (last ${getGPT4CounterMessageCapWindow(messageCapWindow)}): ${gpt4TimestampsFiltered.length}/${messageCap}`; } } else { chrome.storage.local.set({ gpt4Timestamps: [now] }); if (gpt4CounterElement) { - gpt4CounterElement.innerText = `GPT4 requests (last ${getGPT4CounterMessageCapWindow(messageCapWindow)}): 1/${messageCap}`; + gpt4CounterElement.innerText = `GPT-4 requests (last ${getGPT4CounterMessageCapWindow(messageCapWindow)}): 1/${messageCap}`; } } }); @@ -829,7 +833,9 @@ function overrideSubmitForm() { e.stopPropagation(); if (isGenerating) return; // get all words wrapped in {{ and }} - chrome.storage.local.get(['settings'], ({ settings }) => { + chrome.storage.local.get(['settings', 'conversations', 'models'], ({ + settings, conversations, models, + }) => { if (settings.selectedModel.slug.includes('gpt-4')) { if (!inputForm.querySelector('#enforcement-trigger')) { addEnforcementTriggerElement(); @@ -853,135 +859,132 @@ function overrideSubmitForm() { const conversationId = pathname.split('/').pop().replace(/[^a-z0-9-]/gi, ''); const anyUserMessageWrappers = document.querySelectorAll('[id^="message-wrapper-"][data-role="user"]').length > 0; if (/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(conversationId) && anyUserMessageWrappers) { - chrome.storage.local.get(['conversations', 'models']).then((res) => { - const { conversations, models } = res; - const conversation = conversations[conversationId]; - chrome.storage.sync.get(['name', 'avatar'], (result) => { - let text = textAreaElement.value.trim(); - if (chunkNumber === 1) { - finalSummary = ''; - if (settings.autoSplit && text.length > settings.autoSplitLimit) { - totalChunks = Math.ceil(text.length / settings.autoSplitLimit); - const lastNewLineIndexBeforeLimit = settings.autoSplitLimit > text.length ? settings.autoSplitLimit : getLastIndexOf(text, settings.autoSplitLimit); - remainingText = text.substring(lastNewLineIndexBeforeLimit); - text = `${settings.autoSplitInitialPrompt}[START CHUNK ${chunkNumber}/${totalChunks}] + const conversation = conversations[conversationId]; + chrome.storage.sync.get(['name', 'avatar'], (result) => { + let text = textAreaElement.value.trim(); + if (chunkNumber === 1) { + finalSummary = ''; + if (settings.autoSplit && text.length > settings.autoSplitLimit) { + totalChunks = Math.ceil(text.length / settings.autoSplitLimit); + const lastNewLineIndexBeforeLimit = settings.autoSplitLimit > text.length ? settings.autoSplitLimit : getLastIndexOf(text, settings.autoSplitLimit); + remainingText = text.substring(lastNewLineIndexBeforeLimit); + text = `${settings.autoSplitInitialPrompt}[START CHUNK ${chunkNumber}/${totalChunks}] ${text.substring(0, lastNewLineIndexBeforeLimit)} [END CHUNK ${chunkNumber}/${totalChunks}] ${settings.autoSplitChunkPrompt}`; - chunkNumber += 1; - } else { - text = generateInstructions(conversation, settings, textAreaElement.value.trim()); - } - } else if (chunkNumber === totalChunks) { - if (totalChunks > 1 && settings.autoSummarize) shouldSubmitFinalSummary = true; - chunkNumber = 1; - totalChunks = 1; - remainingText = ''; - } else { chunkNumber += 1; - const lastNewLineIndexBeforeLimit = settings.autoSplitLimit > remainingText.length ? settings.autoSplitLimit : getLastIndexOf(remainingText, settings.autoSplitLimit); - remainingText = remainingText.slice(lastNewLineIndexBeforeLimit); - } - const messageId = self.crypto.randomUUID(); - const allMessages = document.querySelectorAll('[id^="message-wrapper-"]'); - const lastMessage = allMessages[allMessages.length - 1]; - const parentId = lastMessage?.id?.split('message-wrapper-')[1] || self.crypto.randomUUID(); - const conversationBottom = document.querySelector('#conversation-bottom'); - if (text && settings.useCustomInstruction) { - text += settings.customInstruction; + } else { + text = generateInstructions(conversation, settings, textAreaElement.value.trim()); } - const node = { message: { id: messageId, content: { parts: [text] } } }; - const userRow = rowUser(conversation, node, 1, 1, result.name, result.avatar, settings.customConversationWidth, settings.conversationWidth); + } else if (chunkNumber === totalChunks) { + if (totalChunks > 1 && settings.autoSummarize) shouldSubmitFinalSummary = true; + chunkNumber = 1; + totalChunks = 1; + remainingText = ''; + } else { + chunkNumber += 1; + const lastNewLineIndexBeforeLimit = settings.autoSplitLimit > remainingText.length ? settings.autoSplitLimit : getLastIndexOf(remainingText, settings.autoSplitLimit); + remainingText = remainingText.slice(lastNewLineIndexBeforeLimit); + } + const messageId = self.crypto.randomUUID(); + const allMessages = document.querySelectorAll('[id^="message-wrapper-"]'); + const lastMessage = allMessages[allMessages.length - 1]; + const parentId = lastMessage?.id?.split('message-wrapper-')[1] || self.crypto.randomUUID(); + const conversationBottom = document.querySelector('#conversation-bottom'); + if (text && settings.useCustomInstruction) { + text += settings.customInstruction; + } + const node = { message: { id: messageId, content: { parts: [text] } } }; + const userRow = rowUser(conversation, node, 1, 1, result.name, result.avatar, settings.customConversationWidth, settings.conversationWidth); + // if last message data-role !== user, insert user row before conversation bottom + if (lastMessage?.dataset?.role !== 'user') { conversationBottom.insertAdjacentHTML('beforebegin', userRow); - conversationBottom.scrollIntoView({ behavior: 'smooth' }); - if (text) { - isGenerating = true; - submitChat(text, conversation, messageId, parentId, settings, models); - textAreaElement.value = ''; - updateInputCounter(''); - } - }); + } + conversationBottom.scrollIntoView({ behavior: 'smooth' }); + if (text) { + isGenerating = true; + submitChat(text, conversation, messageId, parentId, settings, models); + textAreaElement.value = ''; + updateInputCounter(''); + } }); } else { - chrome.storage.local.get(['models']).then((res) => { - const { models } = res; - chrome.storage.sync.get(['name', 'avatar'], (result) => { - let text = textAreaElement.value.trim(); - if (chunkNumber === 1) { - finalSummary = ''; - if (settings.autoSplit && text.length > settings.autoSplitLimit) { - totalChunks = Math.ceil(text.length / settings.autoSplitLimit); - const lastNewLineIndexBeforeLimit = settings.autoSplitLimit > text.length ? settings.autoSplitLimit : getLastIndexOf(text, settings.autoSplitLimit); - remainingText = text.substring(lastNewLineIndexBeforeLimit); - text = `${settings.autoSplitInitialPrompt}[START CHUNK ${chunkNumber}/${totalChunks}] + chrome.storage.sync.get(['name', 'avatar'], (result) => { + let text = textAreaElement.value.trim(); + if (chunkNumber === 1) { + finalSummary = ''; + if (settings.autoSplit && text.length > settings.autoSplitLimit) { + totalChunks = Math.ceil(text.length / settings.autoSplitLimit); + const lastNewLineIndexBeforeLimit = settings.autoSplitLimit > text.length ? settings.autoSplitLimit : getLastIndexOf(text, settings.autoSplitLimit); + remainingText = text.substring(lastNewLineIndexBeforeLimit); + text = `${settings.autoSplitInitialPrompt}[START CHUNK ${chunkNumber}/${totalChunks}] ${text.substring(0, lastNewLineIndexBeforeLimit)} [END CHUNK ${chunkNumber}/${totalChunks}] ${settings.autoSplitChunkPrompt}`; - chunkNumber += 1; - } else { - text = generateInstructions({}, settings, textAreaElement.value.trim()); - } - } else if (chunkNumber === totalChunks) { - if (totalChunks > 1 && settings.autoSummarize) shouldSubmitFinalSummary = true; - chunkNumber = 1; - totalChunks = 1; - remainingText = ''; - } else { chunkNumber += 1; - const lastNewLineIndexBeforeLimit = settings.autoSplitLimit > remainingText.length ? settings.autoSplitLimit : getLastIndexOf(remainingText, settings.autoSplitLimit); - remainingText = remainingText.slice(lastNewLineIndexBeforeLimit); + } else { + text = generateInstructions({}, settings, textAreaElement.value.trim()); } + } else if (chunkNumber === totalChunks) { + if (totalChunks > 1 && settings.autoSummarize) shouldSubmitFinalSummary = true; + chunkNumber = 1; + totalChunks = 1; + remainingText = ''; + } else { + chunkNumber += 1; + const lastNewLineIndexBeforeLimit = settings.autoSplitLimit > remainingText.length ? settings.autoSplitLimit : getLastIndexOf(remainingText, settings.autoSplitLimit); + remainingText = remainingText.slice(lastNewLineIndexBeforeLimit); + } - const messageId = self.crypto.randomUUID(); - if (text && settings.useCustomInstruction) { - text += settings.customInstruction; - } - const node = { message: { id: messageId, content: { parts: [text] } } }; - const allMessages = document.querySelectorAll('[id^="message-wrapper-"]'); - const lastMessage = allMessages[allMessages.length - 1]; - const parentId = lastMessage?.id?.split('message-wrapper-')[1] || self.crypto.randomUUID(); - // remove main first child - const contentWrapper = main.querySelector('.flex-1.overflow-hidden'); - main.removeChild(contentWrapper); + const messageId = self.crypto.randomUUID(); + if (text && settings.useCustomInstruction) { + text += settings.customInstruction; + } + const node = { message: { id: messageId, content: { parts: [text] } } }; + const allMessages = document.querySelectorAll('[id^="message-wrapper-"]'); + const lastMessage = allMessages[allMessages.length - 1]; + const parentId = lastMessage?.id?.split('message-wrapper-')[1] || self.crypto.randomUUID(); + // remove main first child + const contentWrapper = main.querySelector('.flex-1.overflow-hidden'); + main.removeChild(contentWrapper); - const outerDiv = document.createElement('div'); - outerDiv.classList = 'flex-1 overflow-hidden'; - const innerDiv = document.createElement('div'); - innerDiv.classList = 'h-full overflow-y-auto'; - innerDiv.style = 'scroll-behavior: smooth;'; - innerDiv.id = 'conversation-inner-div'; - addScrollDetector(innerDiv); - const conversationDiv = document.createElement('div'); - conversationDiv.classList = 'flex flex-col items-center text-sm h-full dark:bg-gray-800'; - const userRow = rowUser({}, node, 1, 1, result.name, result.avatar, settings.customConversationWidth, settings.conversationWidth); - conversationDiv.innerHTML = userRow; - const topDiv = '
New chat
'; - conversationDiv.insertAdjacentHTML('afterbegin', topDiv); - const bottomDiv = document.createElement('div'); - bottomDiv.id = 'conversation-bottom'; - bottomDiv.classList = 'w-full h-32 md:h-48 flex-shrink-0'; - conversationDiv.appendChild(bottomDiv); - const bottomDivContent = document.createElement('div'); - bottomDivContent.classList = 'relative text-base gap-4 md:gap-6 m-auto md:max-w-2xl lg:max-w-2xl xl:max-w-3xl flex lg:px-0'; - if (settings.customConversationWidth) { - bottomDivContent.style = `max-width: ${settings.conversationWidth}%`; - } - bottomDiv.appendChild(bottomDivContent); - const totalCounter = document.createElement('div'); - totalCounter.id = 'total-counter'; - totalCounter.style = 'position: absolute; top: 0px; right: 0px; font-size: 10px; color: rgb(153, 153, 153); opacity: 0.8;'; - bottomDivContent.appendChild(totalCounter); + const outerDiv = document.createElement('div'); + outerDiv.classList = 'flex-1 overflow-hidden'; + const innerDiv = document.createElement('div'); + innerDiv.classList = 'h-full overflow-y-auto'; + innerDiv.style = 'scroll-behavior: smooth;'; + innerDiv.id = 'conversation-inner-div'; + addScrollDetector(innerDiv); + const conversationDiv = document.createElement('div'); + conversationDiv.classList = 'flex flex-col items-center text-sm h-full dark:bg-gray-800'; + const userRow = rowUser({}, node, 1, 1, result.name, result.avatar, settings.customConversationWidth, settings.conversationWidth); + conversationDiv.innerHTML = userRow; + const topDiv = '
New chat
'; + conversationDiv.insertAdjacentHTML('afterbegin', topDiv); + const bottomDiv = document.createElement('div'); + bottomDiv.id = 'conversation-bottom'; + bottomDiv.classList = 'w-full h-32 md:h-48 flex-shrink-0'; + conversationDiv.appendChild(bottomDiv); + const bottomDivContent = document.createElement('div'); + bottomDivContent.classList = 'relative text-base gap-4 md:gap-6 m-auto md:max-w-2xl lg:max-w-2xl xl:max-w-3xl flex lg:px-0'; + if (settings.customConversationWidth) { + bottomDivContent.style = `max-width: ${settings.conversationWidth}%`; + } + bottomDiv.appendChild(bottomDivContent); + const totalCounter = document.createElement('div'); + totalCounter.id = 'total-counter'; + totalCounter.style = 'position: absolute; top: 0px; right: 0px; font-size: 10px; color: rgb(153, 153, 153); opacity: 0.8;'; + bottomDivContent.appendChild(totalCounter); - innerDiv.appendChild(conversationDiv); - outerDiv.appendChild(innerDiv); - main.prepend(outerDiv); - if (text) { - isGenerating = true; - submitChat(text, {}, messageId, parentId, settings, models); - textAreaElement.value = ''; - updateInputCounter(''); - } - }); + innerDiv.appendChild(conversationDiv); + outerDiv.appendChild(innerDiv); + main.prepend(outerDiv); + if (text) { + isGenerating = true; + submitChat(text, {}, messageId, parentId, settings, models); + textAreaElement.value = ''; + updateInputCounter(''); + } }); } } diff --git a/scripts/content/customInstructions.js b/scripts/content/customInstructions.js new file mode 100644 index 0000000..0ef9886 --- /dev/null +++ b/scripts/content/customInstructions.js @@ -0,0 +1,370 @@ +/* eslint-disable no-restricted-globals */ +/* global toast, setUserSystemMessage, customInstructionSettingsElement */ + +function checkmarkIcon(placement, profileId) { + const checkmark = document.createElement('span'); + checkmark.id = `custom-instructions-profile-dropdown-checkmark-${placement}-${profileId}`; + checkmark.classList = 'absolute inset-y-0 right-0 flex items-center pr-2 text-gray-800 dark:text-gray-100'; + checkmark.innerHTML = ''; + return checkmark; +} +function trashIcon(placement, profileId) { + const trash = document.createElement('span'); + trash.id = `custom-instructions-profile-dropdown-trash-${placement}-${profileId}`; + trash.classList = 'absolute inset-y-0 right-0 flex items-center pr-2 text-gray-800 dark:text-gray-100'; + trash.innerHTML = ''; + trash.addEventListener('click', (e) => { + e.stopPropagation(); + chrome.storage.local.get(['customInstructionProfiles'], (res) => { + const { customInstructionProfiles: cip } = res; + const newCip = cip.filter((p) => p.id !== profileId); + chrome.storage.local.set({ customInstructionProfiles: newCip }, () => { + document.querySelectorAll(`[id^="custom-instructions-profile-dropdown-item-"][id$="-${profileId}"]`).forEach((el) => el.remove()); + reloadCustomInstructionSettings(); + }); + }); + }); + return trash; +} +function profileDropdown(customInstructionProfiles, placement) { + const dropdown = document.createElement('ul'); + dropdown.id = `custom-instructions-profile-dropdown-list-${placement}`; + dropdown.style = 'max-height:300px;overflow-y:scroll;width:200px;top:46px;right:0;z-index:200;'; + dropdown.classList = 'hidden absolute z-10 right-0 mt-1 overflow-auto rounded py-1 text-base ring-1 ring-opacity-5 focus:outline-none bg-white dark:bg-gray-800 dark:ring-white/20 dark:last:border-0 sm:text-sm -translate-x-1/4'; + dropdown.setAttribute('role', 'menu'); + dropdown.setAttribute('aria-orientation', 'vertical'); + dropdown.setAttribute('aria-labelledby', `custom-instructions-profile-dropdown-button-${placement}`); + dropdown.setAttribute('tabindex', '-1'); + const newCustomInstructionProfiles = [...customInstructionProfiles, { + name: '+ Add new profile', aboutUser: '', aboutModel: '', isSelected: false, + }]; + for (let i = 0; i < newCustomInstructionProfiles.length; i += 1) { + const profileId = newCustomInstructionProfiles[i].id; + const profileName = newCustomInstructionProfiles[i].name; + const profileAboutUser = newCustomInstructionProfiles[i].aboutUser; + const profileAboutModel = newCustomInstructionProfiles[i].aboutModel; + const { isSelected } = newCustomInstructionProfiles[i]; + const dropdownItem = document.createElement('li'); + dropdownItem.id = `custom-instructions-profile-dropdown-item-${placement}-${profileId}`; + dropdownItem.dir = 'auto'; + dropdownItem.classList = 'text-gray-900 relative cursor-pointer select-none border-b p-2 last:border-0 border-gray-100 dark:border-white/20 hover:bg-gray-600'; + const dropdownOption = document.createElement('span'); + dropdownOption.classList = 'font-semibold flex h-6 items-center gap-1 truncate text-gray-800 dark:text-gray-100'; + dropdownOption.innerText = profileName; + dropdownOption.title = profileName; + dropdownItem.appendChild(dropdownOption); + dropdownItem.setAttribute('role', 'option'); + dropdownItem.setAttribute('tabindex', '-1'); + + if (isSelected) { + const checkmark = checkmarkIcon(placement, profileId); + dropdownItem.appendChild(checkmark); + } else if (i !== newCustomInstructionProfiles.length - 1) { + const trash = trashIcon(placement, profileId); + dropdownItem.appendChild(trash); + } + dropdownItem.addEventListener('mousemove', () => { + dropdownItem.classList.add('bg-gray-600'); + }); + dropdownItem.addEventListener('mouseleave', () => { + dropdownItem.classList.remove('bg-gray-600'); + }); + dropdownItem.addEventListener('click', () => { + const customInstructionsDialog = document.querySelector('[role="dialog"][data-state="open"][tabindex="-1"]'); + if (placement === 'new-page') { + setUserSystemMessage(profileAboutUser, profileAboutModel, true); + } + if (profileName === '+ Add new profile' && !customInstructionsDialog) { + const userMenu = document.querySelector('#user-menu'); + userMenu.querySelector('button').click(); + // div element that contains the text "Custom instructions" + setTimeout(() => { + const menuButtons = userMenu.querySelectorAll('nav a'); + const customInstructionsButton = [...menuButtons].find((b) => b.textContent === 'Custom instructions'); + customInstructionsButton.click(); + + setTimeout(() => { + const cusstomInstructionsProfileDropdown = document.querySelector('#custom-instructions-profile-dropdown-list-settings'); + cusstomInstructionsProfileDropdown.lastChild.click(); + }, 1000); + }, 300); + return; + } + if (customInstructionsDialog) { + const nameInput = document.querySelector('#custom-instructions-name-input'); + nameInput.value = profileName !== '+ Add new profile' ? profileName : ''; + const textAreaFields = document.querySelectorAll('[role="dialog"][data-state="open"][tabindex="-1"] textarea'); + const aboutUserInput = textAreaFields[0]; + const aboutModelInput = textAreaFields[1]; + aboutUserInput.value = profileAboutUser; + aboutUserInput.dispatchEvent(new Event('input', { bubbles: true })); + aboutUserInput.dispatchEvent(new Event('change', { bubbles: true })); + aboutModelInput.value = profileAboutModel; + aboutModelInput.dispatchEvent(new Event('input', { bubbles: true })); + aboutModelInput.dispatchEvent(new Event('change', { bubbles: true })); + nameInput.focus(); + } + + chrome.storage.local.get(['customInstructionProfiles'], (result) => { + const { customInstructionProfiles: oldCip } = result; + const previousSelectedProfile = oldCip.find((p) => p.isSelected); + + setTimeout(() => { + const newCip = oldCip.map((p) => { + if (p.id === profileId) { + return { ...p, isSelected: true }; + } + return { ...p, isSelected: false }; + }); + + chrome.storage.local.set({ customInstructionProfiles: newCip }, () => { + const selectedProfileTitle = document.querySelector(`#custom-instructions-selected-profile-title-${placement}`); + selectedProfileTitle.textContent = profileName; + // remove the old checkmark from the previous selected profile + const oldCheckmark = document.querySelector(`[id^=custom-instructions-profile-dropdown-checkmark-${placement}-`); + if (oldCheckmark) oldCheckmark.remove(); + // add the new trash icon to the previous selected profile + if (previousSelectedProfile) { + const previousSelectedProfileDropdownItem = document.querySelector(`#custom-instructions-profile-dropdown-item-${placement}-${previousSelectedProfile?.id}`); + const newTrash = trashIcon(placement, previousSelectedProfile?.id); + previousSelectedProfileDropdownItem.appendChild(newTrash); + } + + // remove the old trash icon from the new selected profile + const oldTrash = dropdownItem.querySelector(`#custom-instructions-profile-dropdown-trash-${placement}-${profileId}`); + if (oldTrash) oldTrash.remove(); + + // add the new checkmark to the new selected profile + const newCheckmark = checkmarkIcon(placement, profileId); + dropdownItem.appendChild(newCheckmark); + + // hide the dropdown + const customInstructionsProfileDropdown = document.querySelector(`#custom-instructions-profile-dropdown-list-${placement}`); + customInstructionsProfileDropdown.classList.replace('block', 'hidden'); + }); + }, 100); + }); + }); + dropdown.appendChild(dropdownItem); + } + + document.addEventListener('click', (e) => { + const customInstructionsProfileDropdown = document.querySelector(`#custom-instructions-profile-dropdown-list-${placement}`); + const cl = customInstructionsProfileDropdown?.classList; + if (cl?.contains('block') && !e.target.closest(`#custom-instructions-profile-dropdown-button-${placement}`)) { + customInstructionsProfileDropdown.classList.replace('block', 'hidden'); + } + }); + return dropdown; +} +function profileDropdownButton(customInstructionProfiles, placement) { + const selectedProfile = customInstructionProfiles.find((p) => p.isSelected); + const button = document.createElement('button'); + button.id = `custom-instructions-profile-dropdown-button-${placement}`; + button.title = 'Change the custom instructions profile'; + button.className = 'w-full relative cursor-pointer rounded-md border bg-white border-gray-300 pt-1 pl-3 pr-10 text-left focus:border-green-600 focus:outline-none focus:ring-1 focus:ring-green-600 dark:border-white/20 dark:bg-gray-800 sm:text-sm'; + button.type = 'button'; + const label = document.createElement('label'); + label.className = 'block text-xs text-gray-700 dark:text-gray-500'; + label.textContent = 'Profile'; + button.appendChild(label); + const span = document.createElement('span'); + span.className = 'inline-flex w-full truncate font-semibold text-gray-800 dark:text-gray-100'; + button.appendChild(span); + const span2 = document.createElement('span'); + span2.className = 'flex h-6 items-center gap-1 truncate'; + span.appendChild(span2); + const span3 = document.createElement('span'); + span3.className = 'font-semibold truncate'; + span3.id = `custom-instructions-selected-profile-title-${placement}`; + span3.textContent = selectedProfile?.name || customInstructionProfiles[0]?.name || 'No saved profile'; + span2.appendChild(span3); + const span4 = document.createElement('span'); + span4.className = 'pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2'; + button.appendChild(span4); + span4.innerHTML = ''; + button.addEventListener('click', () => { + const customInstructionsProfileDropdown = document.querySelector(`#custom-instructions-profile-dropdown-list-${placement}`); + const cl = customInstructionsProfileDropdown.classList; + if (cl.contains('block')) { + customInstructionsProfileDropdown.classList.replace('block', 'hidden'); + } else { + customInstructionsProfileDropdown.classList.replace('hidden', 'block'); + } + }); + return button; +} +function upgradeCustomInstructions() { + // observe the body and wait for the custom instructions dialog to be added + // there should be a div with role="dialog" and a h2 with text "Custom instructions" + const targetNode = document.body; + const config = { childList: true, subtree: true }; + const callback = (mutationsList) => { + mutationsList.forEach((mutation) => { + if (mutation.type === 'childList') { + setTimeout(() => { + chrome.storage.local.get(['customInstructionProfiles'], (result) => { + const customInstructionsDialog = document.querySelector('[role="dialog"][data-state="open"][tabindex="-1"]'); + if (!customInstructionsDialog) return; + const customInstructionsDialogHeader = customInstructionsDialog.querySelector('h2'); + const existingProfileButtonWrapper = customInstructionsDialog.querySelector('#custom-instructions-profile-button-wrapper-settings'); + const textAreaFields = customInstructionsDialog.querySelectorAll('textarea'); + if (textAreaFields.length > 0 && !existingProfileButtonWrapper && customInstructionsDialog && customInstructionsDialogHeader.textContent === 'Custom instructions') { + const aboutUser = textAreaFields[0]?.value; + const aboutModel = textAreaFields[1]?.value; + const { customInstructionProfiles } = result; + let newCustomInstructionProfiles = customInstructionProfiles; + let selectedProfile = customInstructionProfiles.find((p) => p.isSelected); + + if (!selectedProfile || (selectedProfile.aboutUser !== aboutUser || selectedProfile.aboutModel !== aboutModel)) { + newCustomInstructionProfiles = customInstructionProfiles.map((p) => { + if (p.aboutModel === aboutModel && p.aboutUser === aboutUser) { + selectedProfile = { ...p, isSelected: true }; + return { ...p, isSelected: true }; + } + if (p.isSelected) { + selectedProfile = undefined; + return { ...p, isSelected: false }; + } + return p; + }); + chrome.storage.local.set({ customInstructionProfiles: newCustomInstructionProfiles }); + } + // header = first child + const header = customInstructionsDialog.firstChild; + const profileButtonWrapper = document.createElement('div'); + profileButtonWrapper.style = 'position:relative;width: 200px;'; + profileButtonWrapper.id = 'custom-instructions-profile-button-wrapper-settings'; + profileButtonWrapper.appendChild(profileDropdown(newCustomInstructionProfiles, 'settings')); + profileButtonWrapper.appendChild(profileDropdownButton(newCustomInstructionProfiles, 'settings')); + header.appendChild(profileButtonWrapper); + // body = second child + const body = customInstructionsDialog.children[1]; + const nameLabel = document.createElement('label'); + nameLabel.className = 'block text-xs text-gray-700 dark:text-gray-500 mb-2 text-gray-600'; + nameLabel.textContent = 'Name'; + const nameInput = document.createElement('input'); + nameInput.id = 'custom-instructions-name-input'; + nameInput.placeholder = 'Profile Name'; + nameInput.value = selectedProfile?.name || ''; + nameInput.classList = 'w-full rounded p-2 mb-6 border dark:bg-gray-800 bg-white border-gray-100 focus:border-brand-green focus:ring-0 focus-visible:ring-0 bg-gray-50 outline-none focus-visible:outline-none'; + if (textAreaFields[0].disabled) { + nameInput.disabled = true; + nameInput.classList.add('text-gray-300'); + } + nameInput.addEventListener('input', () => { + const curSelectedProfileName = selectedProfile?.name || ''; + const allButtons = body.querySelectorAll('button'); + const saveButton = [...allButtons].find((b) => b.textContent === 'Save'); + const curTextAreaFields = document.querySelectorAll('[role="dialog"][data-state="open"][tabindex="-1"] textarea'); + const curAboutUserInput = curTextAreaFields[0]; + const curAboutModelInput = curTextAreaFields[1]; + if (nameInput.value === '' || (nameInput.value === curSelectedProfileName && curAboutUserInput.value === selectedProfile?.aboutUser && curAboutModelInput.value === selectedProfile?.aboutModel)) { + saveButton.disabled = true; + saveButton.classList.add('opacity-50', 'cursor-not-allowed'); + } else if (saveButton.disabled) { + saveButton.disabled = false; + saveButton.classList.remove('opacity-50', 'cursor-not-allowed'); + } + }); + // add the input field to the beginning of the body + body.insertBefore(nameInput, body.firstChild); + body.insertBefore(nameLabel, body.firstChild); + // add a change listener to the text area fields + textAreaFields.forEach((t) => { + t.addEventListener('input', () => { + setTimeout(() => { + const curNameInput = document.querySelector('#custom-instructions-name-input'); + if (curNameInput.value === '') { + saveButton.disabled = true; + saveButton.classList.add('opacity-50', 'cursor-not-allowed'); + } else if (curNameInput.value !== selectedProfile?.name) { + saveButton.disabled = false; + saveButton.classList.remove('opacity-50', 'cursor-not-allowed'); + } + }, 10); + }); + }); + // find a button inside body that has text "Save" + const allButtons = body.querySelectorAll('button'); + const toggleButton = [...allButtons].find((b) => b.role === 'switch'); + const saveButton = [...allButtons].find((b) => b.textContent === 'Save'); + // add a chane listener to the toggle button + toggleButton.addEventListener('click', () => { + const currState = toggleButton.getAttribute('aria-checked'); + + // when toggle off nameinput shoud be disabled + const curNameInput = document.querySelector('#custom-instructions-name-input'); + if (currState === 'true') { + curNameInput.disabled = true; + curNameInput.classList.add('text-gray-300'); + } else { + curNameInput.disabled = false; + curNameInput.classList.remove('text-gray-300'); + } + setTimeout(() => { + if (curNameInput.value === '') { + saveButton.disabled = true; + saveButton.classList.add('opacity-50', 'cursor-not-allowed'); + } else if (curNameInput.value !== selectedProfile?.name) { + saveButton.disabled = false; + saveButton.classList.remove('opacity-50', 'cursor-not-allowed'); + } + }, 10); + }); + // add a click listener to the save button + saveButton.addEventListener('click', () => { + chrome.storage.local.get(['customInstructionProfiles'], (res) => { + const { customInstructionProfiles: cip } = res; + const curNameInput = document.querySelector('#custom-instructions-name-input'); + const curTextAreaFields = document.querySelectorAll('[role="dialog"][data-state="open"][tabindex="-1"] textarea'); + const curAboutUserInput = curTextAreaFields[0]; + const curAboutModelInput = curTextAreaFields[1]; + const curSelectedProfile = cip.find((p) => p.isSelected); + + if (!curSelectedProfile) { + const newCip = [...cip, { + name: curNameInput.value, aboutUser: curAboutUserInput.value, aboutModel: curAboutModelInput.value, isSelected: true, id: self.crypto.randomUUID(), + }]; + chrome.storage.local.set({ customInstructionProfiles: newCip }, () => { + toast('Profile saved'); + reloadCustomInstructionSettings(); + }); + } else { + const newCip = cip.map((p) => { + if (p.isSelected) { + return { + ...p, name: curNameInput.value, aboutUser: curAboutUserInput.value, aboutModel: curAboutModelInput.value, + }; + } + return p; + }); + chrome.storage.local.set({ customInstructionProfiles: newCip }, () => { + toast('Profile updated'); + reloadCustomInstructionSettings(); + }); + } + }); + }); + } + }); + }, 200); + } + }); + }; + const observer = new MutationObserver(callback); + observer.observe(targetNode, config); +} +function reloadCustomInstructionSettings() { + const existingCustomInstructionSettings = document.querySelector('#custom-instruction-settings'); + + if (existingCustomInstructionSettings) { + existingCustomInstructionSettings.remove(); + const newPageSettings = document.querySelector('#new-page-settings'); + setTimeout(() => { + const customInstructionSettings = customInstructionSettingsElement(); + // prepend the custom instruction settings to the new page settings + newPageSettings.insertBefore(customInstructionSettings, newPageSettings.firstChild); + }, 500); + } +} diff --git a/scripts/content/dropdown.js b/scripts/content/dropdown.js index 5d52449..50d8939 100644 --- a/scripts/content/dropdown.js +++ b/scripts/content/dropdown.js @@ -21,7 +21,7 @@ function dropdown(title, options, selectedOption, side = 'right', forceDark = fa ${option.name}
${option.code === selectedOption.code ? ` - + ` : ''} diff --git a/scripts/content/global.js b/scripts/content/global.js index c8cba7c..28d326c 100644 --- a/scripts/content/global.js +++ b/scripts/content/global.js @@ -1,5 +1,5 @@ // eslint-disable-next-line no-unused-vars -/* global markdownit, hljs, resetSelection, getPrompt, newChatPage, initializeRegenerateResponseButton, notSelectedClassList, textAreaElementInputEventListener, textAreaElementKeydownEventListenerASync, languageList, writingStyleList, toneList, refreshPage, runningPromptChainSteps:true, runningPromptChainIndex:true */ +/* global markdownit, hljs, resetSelection, getPrompt, newChatPage, initializeRegenerateResponseButton, notSelectedClassList, textAreaElementInputEventListener, textAreaElementKeydownEventListenerSync, languageList, writingStyleList, toneList, refreshPage, runningPromptChainSteps:true, runningPromptChainIndex:true, dropdown */ /* eslint-disable no-unused-vars */ // Gloab variables // const { version } = chrome.runtime.getManifest(); @@ -322,9 +322,9 @@ function showNewChatPage() { selectedWritingStyle: writingStyleList.find((writingStyle) => writingStyle.code === 'default'), }, }, () => { - document.querySelector('#language-list-dropdown').querySelectorAll('li')[0].click(); - document.querySelector('#tone-list-dropdown').querySelectorAll('li')[0].click(); - document.querySelector('#writing-style-list-dropdown').querySelectorAll('li')[0].click(); + document.querySelectorAll('#language-list-dropdown li')?.[0]?.click(); + document.querySelectorAll('#tone-list-dropdown li')?.[0]?.click(); + document.querySelectorAll('#writing-style-list-dropdown li')?.[0]?.click(); document.querySelector('#auto-click-button').classList.replace('btn-primary', 'btn-neutral'); }); runningPromptChainSteps = undefined; @@ -399,6 +399,7 @@ function addEnforcementTriggerElement() { if (inputForm.querySelector('#enforcement-trigger')) return; inputForm.firstChild.insertAdjacentHTML('beforeend', ''); } + function replaceTextAreaElemet(settings) { const inputForm = document.querySelector('main form'); if (!inputForm) { return false; } @@ -432,7 +433,7 @@ function replaceTextAreaElemet(settings) { newTextAreaElement.style.paddingRight = '40px'; newTextAreaElement.style.overflowY = 'hidden'; - newTextAreaElement.addEventListener('keydown', textAreaElementKeydownEventListenerASync); + newTextAreaElement.addEventListener('keydown', textAreaElementKeydownEventListenerSync); // also async newTextAreaElement.addEventListener('keydown', (event) => { if (event.key === 'Enter' && event.which === 13 && !event.shiftKey) { @@ -513,9 +514,9 @@ function addGpt4Counter() { const gpt4counter = gpt4Timestamps.filter((timestamp) => now - timestamp < (messageCapWindow / 60) * 60 * 60 * 1000).length; const capExpiresAtTimeString = result.capExpiresAt ? `(Cap Expires At: ${result.capExpiresAt})` : ''; if (gpt4counter) { - gpt4CounterElement.innerText = `GPT4 requests (last ${getGPT4CounterMessageCapWindow(messageCapWindow)}): ${gpt4counter}/${messageCap} ${capExpiresAtTimeString}`; + gpt4CounterElement.innerText = `GPT-4 requests (last ${getGPT4CounterMessageCapWindow(messageCapWindow)}): ${gpt4counter}/${messageCap} ${capExpiresAtTimeString}`; } else { - gpt4CounterElement.innerText = `GPT4 requests (last ${getGPT4CounterMessageCapWindow(messageCapWindow)}): 0/${messageCap}`; + gpt4CounterElement.innerText = `GPT-4 requests (last ${getGPT4CounterMessageCapWindow(messageCapWindow)}): 0/${messageCap}`; } }); @@ -854,7 +855,10 @@ function highlightHTML(text, elementId) { function toast(html, type = 'info', duration = 4000) { // show toast that text is copied to clipboard + const existingToast = document.querySelector('#gptx-toast'); + if (existingToast) existingToast.remove(); const element = document.createElement('div'); + element.id = 'gptx-toast'; element.style = 'position:fixed;right:24px;top:24px;border-radius:4px;background-color:#19c37d;padding:8px 16px;z-index:100001;'; if (type === 'error') { element.style.backgroundColor = '#ef4146'; diff --git a/scripts/content/initialize.js b/scripts/content/initialize.js index 035fbea..d752d02 100644 --- a/scripts/content/initialize.js +++ b/scripts/content/initialize.js @@ -1,4 +1,4 @@ -/* global getAccount, getModels, getConversationLimit, initializeStorage, cleanNav, initializeContinue, initializeExport, initializeSettings, initializePromptHistory, initializePromptLibrary, initializeNewsletter, initializeAutoSave, addNavToggleButton, initializeAnnouncement, initializeReleaseNote, initializeReplaceDeleteConversationButton, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, updateNewChatButtonNotSynced, addAsyncInputEvents, initializeContentMessageListeners, addDevIndicator, addExpandButton, openLinksInNewTab, addEnforcementTriggerElement, initializeKeyboardShortcuts, addArkoseCallback, addQuickAccessMenuEventListener, watchError, showAutoSyncToast */ +/* global getAccount, getModels, getConversationLimit, initializeStorage, cleanNav, initializeContinue, initializeExport, initializeSettings, initializePromptHistory, initializePromptLibrary, initializeNewsletter, initializeAutoSave, addNavToggleButton, initializeAnnouncement, initializeReleaseNote, initializeReplaceDeleteConversationButton, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, updateNewChatButtonNotSynced, addAsyncInputEvents, initializeContentMessageListeners, addDevIndicator, addExpandButton, openLinksInNewTab, addEnforcementTriggerElement, initializeKeyboardShortcuts, addArkoseCallback, addQuickAccessMenuEventListener, upgradeCustomInstructions, watchError, showAutoSyncToast */ // eslint-disable-next-line no-unused-vars function initialize() { @@ -17,6 +17,7 @@ function initialize() { initializeContentMessageListeners(); addQuickAccessMenuEventListener(); cleanNav(); + upgradeCustomInstructions(); initializeContinue(); initializeNewsletter(); initializeExport(); diff --git a/scripts/content/modelSwitcher.js b/scripts/content/modelSwitcher.js index bba7978..f4fc62f 100644 --- a/scripts/content/modelSwitcher.js +++ b/scripts/content/modelSwitcher.js @@ -31,7 +31,7 @@ function createModelListDropDown(models, selectedModel, idPrefix, customModels, ${model.description}
${model.slug === selectedModel.slug ? ` - + ` : ''} diff --git a/scripts/content/newChatPage.js b/scripts/content/newChatPage.js index 302bc68..dcd2e72 100644 --- a/scripts/content/newChatPage.js +++ b/scripts/content/newChatPage.js @@ -1,4 +1,4 @@ -/* global createSwitch */ +/* global createSwitch, getUserSystemMessage, setUserSystemMessage, profileDropdown, profileDropdownButton */ // eslint-disable-next-line no-unused-vars function newChatPage(planName) { const outerDiv = document.createElement('div'); @@ -22,28 +22,22 @@ function newChatPage(planName) { content.classList = 'flex items-center justify-center text-center gap-3.5'; body.appendChild(content); - // const systemMessageWrapper = document.createElement('div'); - // systemMessageWrapper.classList = 'w-full flex flex-col items-start justify-center border border-gray-500 rounded-md p-4'; - // content.appendChild(systemMessageWrapper); - - // const systemMessageLabel = document.createElement('div'); - // systemMessageLabel.classList = 'text-gray-500 text-sm font-semibold'; - // systemMessageLabel.textContent = 'System Message'; - // systemMessageWrapper.appendChild(systemMessageLabel); - - // const systemMessageInput = document.createElement('textarea'); - // systemMessageInput.classList = 'w-full h-32 border-0 rounded-md p-2 mt-2 bg-gray-700 focus:ring-0 focus-visible:ring-0 resize-none'; - // systemMessageInput.placeholder = 'Enter your system message here'; - // systemMessageWrapper.appendChild(systemMessageInput); - const settings = document.createElement('div'); - settings.classList = 'flex flex-col items-start justify-center border border-gray-500 rounded-md p-4'; - settings.style = 'width: 400px;'; + settings.id = 'new-page-settings'; + settings.classList = 'flex flex-col items-start justify-end border border-gray-500 rounded-md p-4'; + settings.style = 'width: 600px;min-height:260px;'; content.appendChild(settings); - const saveHistorySwitch = createSwitch('Chat History & Training', `
Save new chats to your history and allow them to be used to - improve ChatGPT via model training. Unsaved chats will be - deleted from our systems within 30 days. Learn more
`, 'saveHistory', true); + const customInstructionSettings = customInstructionSettingsElement(); + settings.appendChild(customInstructionSettings); + + // divider + const divider = document.createElement('div'); + divider.classList = 'border border-gray-500'; + divider.style = 'width: 70%; height: 1px; background-color: #e5e7eb; margin: 16px auto;'; + settings.appendChild(divider); + + const saveHistorySwitch = createSwitch('Chat History & Training', '
Save new chats to your history and allow them to be used to improve ChatGPT via model training. Unsaved chats will be deleted from our systems within 30 days. Learn more
', 'saveHistory', true); settings.appendChild(saveHistorySwitch); const bottom = document.createElement('div'); @@ -52,3 +46,60 @@ function newChatPage(planName) { return outerDiv; } +function customInstructionSettingsElement() { + const customInstructionSettings = document.createElement('div'); + customInstructionSettings.id = 'custom-instruction-settings'; + customInstructionSettings.classList = 'flex items-start justify-between w-full'; + const customInstructionSettingsLeft = document.createElement('div'); + customInstructionSettingsLeft.style = 'width: 60%;'; + const customInstructionSettingsRight = document.createElement('div'); + customInstructionSettingsRight.style = 'width: 40%;display:flex;justify-content:flex-end;'; + customInstructionSettings.appendChild(customInstructionSettingsLeft); + customInstructionSettings.appendChild(customInstructionSettingsRight); + getUserSystemMessage().then((systemMessage) => { + const customInstructionSwitch = createSwitch('Custom Instruction', '
Learn more about Custom instructions and how they’re used to help ChatGPT provide better responses.
', null, systemMessage.enabled, (checked) => setUserSystemMessageCallback(checked, systemMessage)); + customInstructionSettingsLeft.appendChild(customInstructionSwitch); + chrome.storage.local.get(['customInstructionProfiles'], (result) => { + const { customInstructionProfiles } = result; + let newCustomInstructionProfiles = customInstructionProfiles; + const selectedProfile = customInstructionProfiles.find((p) => p.isSelected); + + if (!selectedProfile || (selectedProfile.aboutUser !== systemMessage.about_user_message || selectedProfile.aboutModel !== systemMessage.about_model_message)) { + newCustomInstructionProfiles = customInstructionProfiles.map((p) => { + if (p.aboutModel === systemMessage.about_model_message && p.aboutUser === systemMessage.about_user_message) { + return { ...p, isSelected: true }; + } + if (p.isSelected) { + return { ...p, isSelected: false }; + } + return p; + }); + + chrome.storage.local.set({ customInstructionProfiles: newCustomInstructionProfiles }); + } + + const profileButtonWrapper = document.createElement('div'); + profileButtonWrapper.style = 'position:relative;width: 200px;margin-top:8px;'; + profileButtonWrapper.id = 'custom-instructions-profile-button-wrapper-new-page'; + if (!systemMessage.enabled) { + profileButtonWrapper.style.pointerEvents = 'none'; + profileButtonWrapper.style.opacity = '0.5'; + } + profileButtonWrapper.appendChild(profileDropdown(newCustomInstructionProfiles, 'new-page')); + profileButtonWrapper.appendChild(profileDropdownButton(newCustomInstructionProfiles, 'new-page')); + customInstructionSettingsRight.appendChild(profileButtonWrapper); + }); + }); + return customInstructionSettings; +} +function setUserSystemMessageCallback(checked, systemMessage) { + const profileButtonWrapper = document.getElementById('custom-instructions-profile-button-wrapper-new-page'); + if (checked) { + profileButtonWrapper.style.pointerEvents = 'unset'; + profileButtonWrapper.style.opacity = '1'; + } else { + profileButtonWrapper.style.pointerEvents = 'none'; + profileButtonWrapper.style.opacity = '0.5'; + } + setUserSystemMessage(systemMessage.about_user_message, systemMessage.about_model_message, checked); +} diff --git a/scripts/content/pluginStore.js b/scripts/content/pluginStore.js index 790ab6c..1aa226a 100644 --- a/scripts/content/pluginStore.js +++ b/scripts/content/pluginStore.js @@ -89,7 +89,7 @@ function initializePluginStoreModal(plugins) {
- + diff --git a/scripts/content/pluginsDropdown.js b/scripts/content/pluginsDropdown.js index 25db505..948a5d2 100644 --- a/scripts/content/pluginsDropdown.js +++ b/scripts/content/pluginsDropdown.js @@ -30,7 +30,7 @@ function pluginsDropdown(installedPlugins, enabledPluginIds, idPrefix, forceDark
  • Plugin store (CMD/CTRL + SHIFT + P)
    - + diff --git a/scripts/content/promptHistory.js b/scripts/content/promptHistory.js index 039ec7c..70dd99e 100644 --- a/scripts/content/promptHistory.js +++ b/scripts/content/promptHistory.js @@ -548,12 +548,10 @@ function textAreaElementInputEventListener(event) { }); } // Add keyboard event listener to text area -function textAreaElementKeydownEventListenerSync(event) { +function textAreaElementKeydownEventListenerASync(event) { const textAreaElement = event.target; if (event.key === 'Enter' && event.which === 13 && !event.shiftKey) { - event.preventDefault(); - event.stopPropagation(); updateInputCounter(''); chrome.storage.local.get(['textInputValue'], (result) => { const textInputValue = result.textInputValue || ''; @@ -635,7 +633,7 @@ function textAreaElementKeydownEventListenerSync(event) { } } // eslint-disable-next-line no-unused-vars -function textAreaElementKeydownEventListenerASync(event) { +function textAreaElementKeydownEventListenerSync(event) { const textAreaElement = event.target; if (event.key === 'Enter' && event.which === 13 && !event.shiftKey) { @@ -807,6 +805,6 @@ function addAsyncInputEvents() { if (textAreaElement) { textAreaElement.addEventListener('input', textAreaElementInputEventListener); - textAreaElement.addEventListener('keydown', textAreaElementKeydownEventListenerSync); + textAreaElement.addEventListener('keydown', textAreaElementKeydownEventListenerASync); } } diff --git a/scripts/content/settings.js b/scripts/content/settings.js index cf06f64..b74f945 100644 --- a/scripts/content/settings.js +++ b/scripts/content/settings.js @@ -32,12 +32,14 @@ function selectedTabContent(selectedTab) { return splitterTabContent(); case 6: return newsletterTabContent(); + case 7: + return supportersTabContent(); default: return generalTabContent(); } } function settingsModalContent(initialTab = 0) { - const settingsTabs = ['General', 'Auto Sync', 'models', 'Custom Prompts', 'Export', 'Splitter', 'Newsletter']; + const settingsTabs = ['General', 'Auto Sync', 'models', 'Custom Prompts', 'Export', 'Splitter', 'Newsletter', 'Supporters']; let activeTab = initialTab; // create history modal content const content = document.createElement('div'); @@ -192,10 +194,10 @@ function generalTabContent() { } const importedData = JSON.parse(e.target.result); const { - settings, customModels, customPrompts, conversationsOrder, + settings, customModels, customPrompts, conversationsOrder, customInstructionProfiles, } = importedData; chrome.storage.local.set({ - settings, customModels, customPrompts, + settings, customModels, customPrompts, customInstructionProfiles, }, () => { chrome.storage.sync.set({ conversationsOrder, @@ -218,11 +220,11 @@ function generalTabContent() { chrome.storage.sync.get(['conversationsOrder'], (res) => { chrome.storage.local.get(['settings', 'customModels', 'customPrompts'], (result) => { const { - settings, customModels, customPrompts, + settings, customModels, customPrompts, customInstructionProfiles, } = result; const { conversationsOrder } = res; const data = { - settings, customModels, customPrompts, conversationsOrder, + settings, customModels, customPrompts, conversationsOrder, customInstructionProfiles, }; const element = document.createElement('a'); element.setAttribute('href', `data:text/plain;charset=utf-8,${encodeURIComponent(JSON.stringify(data))}`); @@ -1045,6 +1047,68 @@ function newsletterTabContent() { // content.appendChild(sendNewsletterToEmailSwitch); return content; } +function supportersTabContent() { + const content = document.createElement('div'); + content.id = 'settings-modal-tab-content'; + content.style = 'display: flex; flex-direction:column; justify-content: start; align-items: start;overflow-y: scroll; width:100%; padding: 16px; margin-width:100%; height: 100%;gap:16px;'; + + const goldSupporter = document.createElement('a'); + goldSupporter.href = 'https://buy.stripe.com/dR6g2A7subOigE09AF'; + goldSupporter.target = '_blank'; + goldSupporter.classList = 'h-64 w-full rounded bg-gray-700 text-gray-300 p-2 flex justify-center items-center text-4xl'; + goldSupporter.textContent = 'Gold'; + + const silverSupporterwrapper = document.createElement('div'); + silverSupporterwrapper.style = 'display: flex; flex-direction: row; justify-content: start; align-items: start; width: 100%; margin: 8px 0;gap:16px;'; + + const silverSupporter1 = document.createElement('a'); + silverSupporter1.href = 'https://buy.stripe.com/dR6bMk5km5pU87u5ko'; + silverSupporter1.target = '_blank'; + silverSupporter1.classList = 'h-32 rounded bg-gray-700 text-gray-300 p-2 flex justify-center items-center text-2xl'; + silverSupporter1.style = 'width: 50%;'; + silverSupporter1.textContent = 'Silver'; + silverSupporterwrapper.appendChild(silverSupporter1); + + const silverSupporter2 = document.createElement('a'); + silverSupporter2.href = 'https://buy.stripe.com/dR6bMk5km5pU87u5ko'; + silverSupporter2.target = '_blank'; + silverSupporter2.classList = 'h-32 rounded bg-gray-700 text-gray-300 p-2 flex justify-center items-center text-2xl'; + silverSupporter2.style = 'width: 50%;'; + silverSupporter2.textContent = 'Silver'; + silverSupporterwrapper.appendChild(silverSupporter2); + + const bronzeSupporterwrapper = document.createElement('div'); + bronzeSupporterwrapper.style = 'display: flex; flex-direction: row; justify-content: start; align-items: start; width: 100%; margin: 8px 0;gap:16px;'; + + const bronzeSupporter1 = document.createElement('a'); + bronzeSupporter1.href = 'https://buy.stripe.com/6oE17G4gibOifzW5kn'; + bronzeSupporter1.target = '_blank'; + bronzeSupporter1.classList = 'h-16 rounded bg-gray-700 text-gray-300 p-2 flex justify-center items-center text-xl'; + bronzeSupporter1.style = 'width: 33.33%;'; + bronzeSupporter1.textContent = 'Bronze'; + bronzeSupporterwrapper.appendChild(bronzeSupporter1); + + const bronzeSupporter2 = document.createElement('a'); + bronzeSupporter2.href = 'https://buy.stripe.com/6oE17G4gibOifzW5kn'; + bronzeSupporter2.target = '_blank'; + bronzeSupporter2.classList = 'h-16 rounded bg-gray-700 text-gray-300 p-2 flex justify-center items-center text-xl'; + bronzeSupporter2.style = 'width: 33.33%;'; + bronzeSupporter2.textContent = 'Bronze'; + bronzeSupporterwrapper.appendChild(bronzeSupporter2); + + const bronzeSupporter3 = document.createElement('a'); + bronzeSupporter3.href = 'https://buy.stripe.com/6oE17G4gibOifzW5kn'; + bronzeSupporter3.target = '_blank'; + bronzeSupporter3.classList = 'h-16 rounded bg-gray-700 text-gray-300 p-2 flex justify-center items-center text-xl'; + bronzeSupporter3.style = 'width: 33.33%;'; + bronzeSupporter3.textContent = 'Bronze'; + bronzeSupporterwrapper.appendChild(bronzeSupporter3); + + content.appendChild(goldSupporter); + content.appendChild(silverSupporterwrapper); + content.appendChild(bronzeSupporterwrapper); + return content; +} function createSwitch(title, subtitle, settingsKey, defaultValue, callback = null, tag = '', disabled = false) { const switchWrapper = document.createElement('div'); switchWrapper.style = 'display: flex; flex-direction: column; justify-content: start; align-items: start; width: 100%; margin: 8px 0;'; @@ -1258,7 +1322,7 @@ function addSettingsButton() { function initializeSettings() { // get dark mode from html tag class="dark" // create setting storage - chrome.storage.local.get(['settings', 'presetPrompts', 'selectedConversations', 'customPrompts'], (result) => { + chrome.storage.local.get(['settings', 'presetPrompts', 'selectedConversations', 'customPrompts', 'customInstructionProfiles'], (result) => { let newCustomPrompts = Array.isArray(result.customPrompts) ? result.customPrompts : [ @@ -1319,6 +1383,7 @@ Don't reply with anything else!`, selectedPromptLanguage: result.settings?.selectedPromptLanguage || { name: 'Select', code: 'select' }, }, presetPrompts: {}, + customInstructionProfiles: result.customInstructionProfiles !== undefined ? result.customInstructionProfiles : [], customPrompts: newCustomPrompts, }, () => addSettingsButton()); }); From 2172d5c6f866cfe71491a9a981f8f648cdbebc58 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Wed, 26 Jul 2023 17:23:56 -0700 Subject: [PATCH 09/38] =?UTF-8?q?fix:=20=F0=9F=90=9B=20don't=20show=20CI?= =?UTF-8?q?=20to=20free=20users?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/content/customInstructions.js | 285 +++++++++++++------------- scripts/content/newChatPage.js | 29 +-- 3 files changed, 163 insertions(+), 153 deletions(-) diff --git a/manifest.json b/manifest.json index 0123a83..9aadd01 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.0.4", + "version": "5.0.5", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/content/customInstructions.js b/scripts/content/customInstructions.js index 0ef9886..6878e0d 100644 --- a/scripts/content/customInstructions.js +++ b/scripts/content/customInstructions.js @@ -197,84 +197,116 @@ function profileDropdownButton(customInstructionProfiles, placement) { function upgradeCustomInstructions() { // observe the body and wait for the custom instructions dialog to be added // there should be a div with role="dialog" and a h2 with text "Custom instructions" - const targetNode = document.body; - const config = { childList: true, subtree: true }; - const callback = (mutationsList) => { - mutationsList.forEach((mutation) => { - if (mutation.type === 'childList') { - setTimeout(() => { - chrome.storage.local.get(['customInstructionProfiles'], (result) => { - const customInstructionsDialog = document.querySelector('[role="dialog"][data-state="open"][tabindex="-1"]'); - if (!customInstructionsDialog) return; - const customInstructionsDialogHeader = customInstructionsDialog.querySelector('h2'); - const existingProfileButtonWrapper = customInstructionsDialog.querySelector('#custom-instructions-profile-button-wrapper-settings'); - const textAreaFields = customInstructionsDialog.querySelectorAll('textarea'); - if (textAreaFields.length > 0 && !existingProfileButtonWrapper && customInstructionsDialog && customInstructionsDialogHeader.textContent === 'Custom instructions') { - const aboutUser = textAreaFields[0]?.value; - const aboutModel = textAreaFields[1]?.value; - const { customInstructionProfiles } = result; - let newCustomInstructionProfiles = customInstructionProfiles; - let selectedProfile = customInstructionProfiles.find((p) => p.isSelected); + chrome.storage.local.get(['account'], (r) => { + const { account } = r; + const isPaid = account?.account_plan?.is_paid_subscription_active || account?.accounts?.default?.entitlement?.has_active_subscription || false; + if (!isPaid) return; + const targetNode = document.body; + const config = { childList: true, subtree: true }; + const callback = (mutationsList) => { + mutationsList.forEach((mutation) => { + if (mutation.type === 'childList') { + setTimeout(() => { + chrome.storage.local.get(['customInstructionProfiles'], (result) => { + const customInstructionsDialog = document.querySelector('[role="dialog"][data-state="open"][tabindex="-1"]'); + if (!customInstructionsDialog) return; + const customInstructionsDialogHeader = customInstructionsDialog.querySelector('h2'); + const existingProfileButtonWrapper = customInstructionsDialog.querySelector('#custom-instructions-profile-button-wrapper-settings'); + const textAreaFields = customInstructionsDialog.querySelectorAll('textarea'); + if (textAreaFields.length > 0 && !existingProfileButtonWrapper && customInstructionsDialog && customInstructionsDialogHeader.textContent === 'Custom instructions') { + const aboutUser = textAreaFields[0]?.value; + const aboutModel = textAreaFields[1]?.value; + const { customInstructionProfiles } = result; + let newCustomInstructionProfiles = customInstructionProfiles; + let selectedProfile = customInstructionProfiles.find((p) => p.isSelected); - if (!selectedProfile || (selectedProfile.aboutUser !== aboutUser || selectedProfile.aboutModel !== aboutModel)) { - newCustomInstructionProfiles = customInstructionProfiles.map((p) => { - if (p.aboutModel === aboutModel && p.aboutUser === aboutUser) { - selectedProfile = { ...p, isSelected: true }; - return { ...p, isSelected: true }; - } - if (p.isSelected) { - selectedProfile = undefined; - return { ...p, isSelected: false }; + if (!selectedProfile || (selectedProfile.aboutUser !== aboutUser || selectedProfile.aboutModel !== aboutModel)) { + newCustomInstructionProfiles = customInstructionProfiles.map((p) => { + if (p.aboutModel === aboutModel && p.aboutUser === aboutUser) { + selectedProfile = { ...p, isSelected: true }; + return { ...p, isSelected: true }; + } + if (p.isSelected) { + selectedProfile = undefined; + return { ...p, isSelected: false }; + } + return p; + }); + chrome.storage.local.set({ customInstructionProfiles: newCustomInstructionProfiles }); + } + // header = first child + const header = customInstructionsDialog.firstChild; + const profileButtonWrapper = document.createElement('div'); + profileButtonWrapper.style = 'position:relative;width: 200px;'; + profileButtonWrapper.id = 'custom-instructions-profile-button-wrapper-settings'; + profileButtonWrapper.appendChild(profileDropdown(newCustomInstructionProfiles, 'settings')); + profileButtonWrapper.appendChild(profileDropdownButton(newCustomInstructionProfiles, 'settings')); + header.appendChild(profileButtonWrapper); + // body = second child + const body = customInstructionsDialog.children[1]; + const nameLabel = document.createElement('label'); + nameLabel.className = 'block text-xs text-gray-700 dark:text-gray-500 mb-2 text-gray-600'; + nameLabel.textContent = 'Name'; + const nameInput = document.createElement('input'); + nameInput.id = 'custom-instructions-name-input'; + nameInput.placeholder = 'Profile Name'; + nameInput.value = selectedProfile?.name || ''; + nameInput.classList = 'w-full rounded p-2 mb-6 border dark:bg-gray-800 bg-white border-gray-100 focus:border-brand-green focus:ring-0 focus-visible:ring-0 bg-gray-50 outline-none focus-visible:outline-none'; + if (textAreaFields[0].disabled) { + nameInput.disabled = true; + nameInput.classList.add('text-gray-300'); + } + nameInput.addEventListener('input', () => { + const curSelectedProfileName = selectedProfile?.name || ''; + const allButtons = body.querySelectorAll('button'); + const saveButton = [...allButtons].find((b) => b.textContent === 'Save'); + const curTextAreaFields = document.querySelectorAll('[role="dialog"][data-state="open"][tabindex="-1"] textarea'); + const curAboutUserInput = curTextAreaFields[0]; + const curAboutModelInput = curTextAreaFields[1]; + if (nameInput.value === '' || (nameInput.value === curSelectedProfileName && curAboutUserInput.value === selectedProfile?.aboutUser && curAboutModelInput.value === selectedProfile?.aboutModel)) { + saveButton.disabled = true; + saveButton.classList.add('opacity-50', 'cursor-not-allowed'); + } else if (saveButton.disabled) { + saveButton.disabled = false; + saveButton.classList.remove('opacity-50', 'cursor-not-allowed'); } - return p; }); - chrome.storage.local.set({ customInstructionProfiles: newCustomInstructionProfiles }); - } - // header = first child - const header = customInstructionsDialog.firstChild; - const profileButtonWrapper = document.createElement('div'); - profileButtonWrapper.style = 'position:relative;width: 200px;'; - profileButtonWrapper.id = 'custom-instructions-profile-button-wrapper-settings'; - profileButtonWrapper.appendChild(profileDropdown(newCustomInstructionProfiles, 'settings')); - profileButtonWrapper.appendChild(profileDropdownButton(newCustomInstructionProfiles, 'settings')); - header.appendChild(profileButtonWrapper); - // body = second child - const body = customInstructionsDialog.children[1]; - const nameLabel = document.createElement('label'); - nameLabel.className = 'block text-xs text-gray-700 dark:text-gray-500 mb-2 text-gray-600'; - nameLabel.textContent = 'Name'; - const nameInput = document.createElement('input'); - nameInput.id = 'custom-instructions-name-input'; - nameInput.placeholder = 'Profile Name'; - nameInput.value = selectedProfile?.name || ''; - nameInput.classList = 'w-full rounded p-2 mb-6 border dark:bg-gray-800 bg-white border-gray-100 focus:border-brand-green focus:ring-0 focus-visible:ring-0 bg-gray-50 outline-none focus-visible:outline-none'; - if (textAreaFields[0].disabled) { - nameInput.disabled = true; - nameInput.classList.add('text-gray-300'); - } - nameInput.addEventListener('input', () => { - const curSelectedProfileName = selectedProfile?.name || ''; + // add the input field to the beginning of the body + body.insertBefore(nameInput, body.firstChild); + body.insertBefore(nameLabel, body.firstChild); + // add a change listener to the text area fields + textAreaFields.forEach((t) => { + t.addEventListener('input', () => { + setTimeout(() => { + const curNameInput = document.querySelector('#custom-instructions-name-input'); + if (curNameInput.value === '') { + saveButton.disabled = true; + saveButton.classList.add('opacity-50', 'cursor-not-allowed'); + } else if (curNameInput.value !== selectedProfile?.name) { + saveButton.disabled = false; + saveButton.classList.remove('opacity-50', 'cursor-not-allowed'); + } + }, 10); + }); + }); + // find a button inside body that has text "Save" const allButtons = body.querySelectorAll('button'); + const toggleButton = [...allButtons].find((b) => b.role === 'switch'); const saveButton = [...allButtons].find((b) => b.textContent === 'Save'); - const curTextAreaFields = document.querySelectorAll('[role="dialog"][data-state="open"][tabindex="-1"] textarea'); - const curAboutUserInput = curTextAreaFields[0]; - const curAboutModelInput = curTextAreaFields[1]; - if (nameInput.value === '' || (nameInput.value === curSelectedProfileName && curAboutUserInput.value === selectedProfile?.aboutUser && curAboutModelInput.value === selectedProfile?.aboutModel)) { - saveButton.disabled = true; - saveButton.classList.add('opacity-50', 'cursor-not-allowed'); - } else if (saveButton.disabled) { - saveButton.disabled = false; - saveButton.classList.remove('opacity-50', 'cursor-not-allowed'); - } - }); - // add the input field to the beginning of the body - body.insertBefore(nameInput, body.firstChild); - body.insertBefore(nameLabel, body.firstChild); - // add a change listener to the text area fields - textAreaFields.forEach((t) => { - t.addEventListener('input', () => { + // add a chane listener to the toggle button + toggleButton.addEventListener('click', () => { + const currState = toggleButton.getAttribute('aria-checked'); + + // when toggle off nameinput shoud be disabled + const curNameInput = document.querySelector('#custom-instructions-name-input'); + if (currState === 'true') { + curNameInput.disabled = true; + curNameInput.classList.add('text-gray-300'); + } else { + curNameInput.disabled = false; + curNameInput.classList.remove('text-gray-300'); + } setTimeout(() => { - const curNameInput = document.querySelector('#custom-instructions-name-input'); if (curNameInput.value === '') { saveButton.disabled = true; saveButton.classList.add('opacity-50', 'cursor-not-allowed'); @@ -284,76 +316,49 @@ function upgradeCustomInstructions() { } }, 10); }); - }); - // find a button inside body that has text "Save" - const allButtons = body.querySelectorAll('button'); - const toggleButton = [...allButtons].find((b) => b.role === 'switch'); - const saveButton = [...allButtons].find((b) => b.textContent === 'Save'); - // add a chane listener to the toggle button - toggleButton.addEventListener('click', () => { - const currState = toggleButton.getAttribute('aria-checked'); - - // when toggle off nameinput shoud be disabled - const curNameInput = document.querySelector('#custom-instructions-name-input'); - if (currState === 'true') { - curNameInput.disabled = true; - curNameInput.classList.add('text-gray-300'); - } else { - curNameInput.disabled = false; - curNameInput.classList.remove('text-gray-300'); - } - setTimeout(() => { - if (curNameInput.value === '') { - saveButton.disabled = true; - saveButton.classList.add('opacity-50', 'cursor-not-allowed'); - } else if (curNameInput.value !== selectedProfile?.name) { - saveButton.disabled = false; - saveButton.classList.remove('opacity-50', 'cursor-not-allowed'); - } - }, 10); - }); - // add a click listener to the save button - saveButton.addEventListener('click', () => { - chrome.storage.local.get(['customInstructionProfiles'], (res) => { - const { customInstructionProfiles: cip } = res; - const curNameInput = document.querySelector('#custom-instructions-name-input'); - const curTextAreaFields = document.querySelectorAll('[role="dialog"][data-state="open"][tabindex="-1"] textarea'); - const curAboutUserInput = curTextAreaFields[0]; - const curAboutModelInput = curTextAreaFields[1]; - const curSelectedProfile = cip.find((p) => p.isSelected); + // add a click listener to the save button + saveButton.addEventListener('click', () => { + chrome.storage.local.get(['customInstructionProfiles'], (res) => { + const { customInstructionProfiles: cip } = res; + const curNameInput = document.querySelector('#custom-instructions-name-input'); + const curTextAreaFields = document.querySelectorAll('[role="dialog"][data-state="open"][tabindex="-1"] textarea'); + const curAboutUserInput = curTextAreaFields[0]; + const curAboutModelInput = curTextAreaFields[1]; + const curSelectedProfile = cip.find((p) => p.isSelected); - if (!curSelectedProfile) { - const newCip = [...cip, { - name: curNameInput.value, aboutUser: curAboutUserInput.value, aboutModel: curAboutModelInput.value, isSelected: true, id: self.crypto.randomUUID(), - }]; - chrome.storage.local.set({ customInstructionProfiles: newCip }, () => { - toast('Profile saved'); - reloadCustomInstructionSettings(); - }); - } else { - const newCip = cip.map((p) => { - if (p.isSelected) { - return { - ...p, name: curNameInput.value, aboutUser: curAboutUserInput.value, aboutModel: curAboutModelInput.value, - }; - } - return p; - }); - chrome.storage.local.set({ customInstructionProfiles: newCip }, () => { - toast('Profile updated'); - reloadCustomInstructionSettings(); - }); - } + if (!curSelectedProfile) { + const newCip = [...cip, { + name: curNameInput.value, aboutUser: curAboutUserInput.value, aboutModel: curAboutModelInput.value, isSelected: true, id: self.crypto.randomUUID(), + }]; + chrome.storage.local.set({ customInstructionProfiles: newCip }, () => { + toast('Profile saved'); + reloadCustomInstructionSettings(); + }); + } else { + const newCip = cip.map((p) => { + if (p.isSelected) { + return { + ...p, name: curNameInput.value, aboutUser: curAboutUserInput.value, aboutModel: curAboutModelInput.value, + }; + } + return p; + }); + chrome.storage.local.set({ customInstructionProfiles: newCip }, () => { + toast('Profile updated'); + reloadCustomInstructionSettings(); + }); + } + }); }); - }); - } - }); - }, 200); - } - }); - }; - const observer = new MutationObserver(callback); - observer.observe(targetNode, config); + } + }); + }, 200); + } + }); + }; + const observer = new MutationObserver(callback); + observer.observe(targetNode, config); + }); } function reloadCustomInstructionSettings() { const existingCustomInstructionSettings = document.querySelector('#custom-instruction-settings'); diff --git a/scripts/content/newChatPage.js b/scripts/content/newChatPage.js index dcd2e72..7749963 100644 --- a/scripts/content/newChatPage.js +++ b/scripts/content/newChatPage.js @@ -25,21 +25,26 @@ function newChatPage(planName) { const settings = document.createElement('div'); settings.id = 'new-page-settings'; settings.classList = 'flex flex-col items-start justify-end border border-gray-500 rounded-md p-4'; - settings.style = 'width: 600px;min-height:260px;'; + settings.style = 'width: 600px;'; content.appendChild(settings); + chrome.storage.local.get(['account'], (r) => { + const { account } = r; + const isPaid = account?.account_plan?.is_paid_subscription_active || account?.accounts?.default?.entitlement?.has_active_subscription || false; + if (isPaid) { + settings.style.minHeight = '260px'; + const customInstructionSettings = customInstructionSettingsElement(); + settings.appendChild(customInstructionSettings); - const customInstructionSettings = customInstructionSettingsElement(); - settings.appendChild(customInstructionSettings); - - // divider - const divider = document.createElement('div'); - divider.classList = 'border border-gray-500'; - divider.style = 'width: 70%; height: 1px; background-color: #e5e7eb; margin: 16px auto;'; - settings.appendChild(divider); - - const saveHistorySwitch = createSwitch('Chat History & Training', '
    Save new chats to your history and allow them to be used to improve ChatGPT via model training. Unsaved chats will be deleted from our systems within 30 days. Learn more
    ', 'saveHistory', true); - settings.appendChild(saveHistorySwitch); + // divider + const divider = document.createElement('div'); + divider.classList = 'border border-gray-500'; + divider.style = 'width: 70%; height: 1px; background-color: #e5e7eb; margin: 16px auto;'; + settings.appendChild(divider); + } + const saveHistorySwitch = createSwitch('Chat History & Training', '
    Save new chats to your history and allow them to be used to improve ChatGPT via model training. Unsaved chats will be deleted from our systems within 30 days. Learn more
    ', 'saveHistory', true); + settings.appendChild(saveHistorySwitch); + }); const bottom = document.createElement('div'); bottom.classList = 'w-full h-32 md:h-48 flex-shrink-0'; innerDiv.appendChild(bottom); From 1c12b700bd1852135f7bcdcd9fe76520b6d1cae1 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Mon, 31 Jul 2023 21:47:14 -0700 Subject: [PATCH 10/38] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20for=20input?= =?UTF-8?q?=20action=20buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/content/continue.js | 42 ++++++++++----- scripts/content/conversation.js | 6 +-- scripts/content/conversationList.js | 7 +-- scripts/content/export.js | 40 +++++++++++---- scripts/content/global.js | 8 +-- scripts/content/initialize.js | 2 +- scripts/content/regenerateResponse.js | 62 +++++++++-------------- scripts/content/settings.js | 2 +- scripts/content/stopGeneratingResponse.js | 11 ++-- scripts/styles/global.css | 3 -- 11 files changed, 100 insertions(+), 85 deletions(-) diff --git a/manifest.json b/manifest.json index 9aadd01..79dca6b 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.0.5", + "version": "5.0.6", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/content/continue.js b/scripts/content/continue.js index d8475ab..986a0e5 100644 --- a/scripts/content/continue.js +++ b/scripts/content/continue.js @@ -183,24 +183,42 @@ function addContinueButton() { } if (canSubmit) { const inputForm = document.querySelector('main form'); + const inputFormFirstChild = inputForm.firstChild; + const textAreaElement = inputForm.querySelector('textarea'); if (!textAreaElement) return; - const textAreaElementWrapper = textAreaElement.parentNode; - let nodeBeforetTextAreaElement = textAreaElementWrapper.previousSibling; - if (!nodeBeforetTextAreaElement) { - // create a new div + + let inputFormActionWrapper = settings.autoSync + ? inputForm.querySelector('#input-form-action-wrapper') + : inputForm.firstChild.firstChild.firstChild; + if (!settings.autoSync) { + const growElement = inputFormActionWrapper.querySelector('.grow'); + if (growElement) { + growElement.remove(); + } + } + if (!inputFormActionWrapper) { + if (!inputFormFirstChild.firstChild.contains(textAreaElement)) { + inputFormFirstChild.firstChild.remove(); + } + // create new div const newDiv = document.createElement('div'); - newDiv.classList = 'h-full flex ml-1 md:w-full md:m-auto md:mb-2 gap-0 md:gap-2 justify-center'; + newDiv.id = 'input-form-action-wrapper'; + newDiv.classList = 'h-full flex ml-1 md:w-full md:m-auto md:mb-4 gap-0 md:gap-2 justify-center'; // prepent inputform with new div - inputForm.firstChild.prepend(newDiv); - nodeBeforetTextAreaElement = newDiv; + inputFormActionWrapper = newDiv; + inputFormFirstChild.prepend(inputFormActionWrapper); + } + inputFormActionWrapper.style.minHeight = '38px'; + const existingContinueButton = document.querySelector('#continue-conversation-button-wrapper'); + const allMessageWrapper = document.querySelectorAll('[id^="message-wrapper-"]'); + let lastMessageWrapperElement; + if (allMessageWrapper.length > 0) { + lastMessageWrapperElement = allMessageWrapper[allMessageWrapper.length - 1]; } - if (nodeBeforetTextAreaElement.classList.length === 0) { - nodeBeforetTextAreaElement.classList = 'h-full flex ml-1 md:w-full md:m-auto md:mb-2 gap-0 md:gap-2 justify-center'; - nodeBeforetTextAreaElement.firstChild.classList = ''; + if (!settings.autoSync || !lastMessageWrapperElement || lastMessageWrapperElement.dataset.role !== 'user') { + if (!existingContinueButton) inputFormActionWrapper.appendChild(continueButtonWrapper); } - nodeBeforetTextAreaElement.style.minHeight = '38px'; - nodeBeforetTextAreaElement.appendChild(continueButtonWrapper); } }, 200); }); diff --git a/scripts/content/conversation.js b/scripts/content/conversation.js index 72e5d9d..4f1bcea 100644 --- a/scripts/content/conversation.js +++ b/scripts/content/conversation.js @@ -1,6 +1,6 @@ /* eslint-disable no-restricted-globals */ // eslint-disable-next-line no-unused-vars -/* global getConversation, submitChat, openSubmitPromptModal, initializeRegenerateResponseButton, toggleTextAreaElement, rowAssistant, rowUser, copyRichText, messageFeedback, openFeedbackModal, refreshConversations, initializeStopGeneratingResponseButton, chatStreamIsClosed:true, generateInstructions, isGenerating:true, scrolUpDetected:true, addScrollDetector, generateSuggestions, addArkoseScript, addEnforcementTriggerElement, languageList, writingStyleList, toneList */ +/* global getConversation, submitChat, openSubmitPromptModal, initializeRegenerateResponseButton, showHideTextAreaElement, rowAssistant, rowUser, copyRichText, messageFeedback, openFeedbackModal, refreshConversations, initializeStopGeneratingResponseButton, chatStreamIsClosed:true, generateInstructions, isGenerating:true, scrolUpDetected:true, addScrollDetector, generateSuggestions, addArkoseScript, addEnforcementTriggerElement, languageList, writingStyleList, toneList */ function addPinNav(sortedNodes) { chrome.storage.local.get(['settings'], (res) => { @@ -127,7 +127,7 @@ function loadConversationFromNode(conversationId, newMessageId, oldMessageId, se // inser messageDiv html above conversation bottom conversationBottom.insertAdjacentHTML('beforebegin', messageDiv); - toggleTextAreaElement(); + showHideTextAreaElement(); addConversationsEventListeners(fullConversation.id); initializeRegenerateResponseButton(); initializeStopGeneratingResponseButton(); @@ -253,7 +253,7 @@ function loadConversation(conversationId, searchValue = '', focusOnInput = true) searchElement.scrollIntoView(); } } - toggleTextAreaElement(); + showHideTextAreaElement(); addConversationsEventListeners(fullConversation.id); initializeRegenerateResponseButton(); initializeStopGeneratingResponseButton(); diff --git a/scripts/content/conversationList.js b/scripts/content/conversationList.js index 654b473..b1de9f4 100644 --- a/scripts/content/conversationList.js +++ b/scripts/content/conversationList.js @@ -1,6 +1,6 @@ /* eslint-disable no-restricted-globals */ // eslint-disable-next-line no-unused-vars -/* global markdown, markdownitSup, initializeNavbar, generateInstructions, generateChat, SSE, formatDate, loadConversation, resetSelection, katex, texmath, rowUser, rowAssistant, updateOrCreateConversation, replaceTextAreaElemet, highlight, isGenerating:true, disableTextInput:true, generateTitle, debounce, initializeRegenerateResponseButton, initializeStopGeneratingResponseButton, toggleTextAreaElement, showNewChatPage, chatStreamIsClosed:true, addCopyCodeButtonsEventListeners, addScrollDetector, scrolUpDetected:true, Sortable, updateInputCounter, addUserPromptToHistory, getGPT4CounterMessageCapWindow, createFolder, getConversationElementClassList, notSelectedClassList, selectedClassList, conversationActions, addCheckboxToConversationElement, createConversation, deleteConversation, handleQueryParams, addScrollButtons, updateTotalCounter, isWindows, loadSharedConversation, createTemplateWordsModal, addEnforcementTriggerElement, initializePromptChain, insertNextChain, runningPromptChainSteps:true, runningPromptChainIndex:true */ +/* global markdown, markdownitSup, initializeNavbar, generateInstructions, generateChat, SSE, formatDate, loadConversation, resetSelection, katex, texmath, rowUser, rowAssistant, updateOrCreateConversation, replaceTextAreaElemet, highlight, isGenerating:true, disableTextInput:true, generateTitle, debounce, initializeRegenerateResponseButton, initializeStopGeneratingResponseButton, showHideTextAreaElement, showNewChatPage, chatStreamIsClosed:true, addCopyCodeButtonsEventListeners, addScrollDetector, scrolUpDetected:true, Sortable, updateInputCounter, addUserPromptToHistory, getGPT4CounterMessageCapWindow, createFolder, getConversationElementClassList, notSelectedClassList, selectedClassList, conversationActions, addCheckboxToConversationElement, createConversation, deleteConversation, handleQueryParams, addScrollButtons, updateTotalCounter, isWindows, loadSharedConversation, createTemplateWordsModal, addEnforcementTriggerElement, initializePromptChain, insertNextChain, runningPromptChainSteps:true, runningPromptChainIndex:true */ // Initial state let userChatIsActuallySaved = false; @@ -525,7 +525,7 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode isGenerating = false; chatStream.close(); if (syncDiv) syncDiv.style.opacity = '1'; - toggleTextAreaElement(); + showHideTextAreaElement(); initializeStopGeneratingResponseButton(); initializeRegenerateResponseButton(); updateTotalCounter(); @@ -736,10 +736,11 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode chrome.storage.local.set({ capExpiresAt }); errorMessage = `You've reached the current usage cap for this model. You can continue with the default model now, or try again after ${capExpiresAt}.`; } else { + showHideTextAreaElement(); chrome.storage.local.set({ capExpiresAt: '' }); } const conversationBottom = document.querySelector('#conversation-bottom'); - const errorMessageElement = `
    ${errorMessage}
    `; + const errorMessageElement = `
    ${errorMessage}
    `; conversationBottom.insertAdjacentHTML('beforebegin', errorMessageElement); conversationBottom.scrollIntoView({ behavior: 'smooth' }); } diff --git a/scripts/content/export.js b/scripts/content/export.js index befe1da..fffd36f 100644 --- a/scripts/content/export.js +++ b/scripts/content/export.js @@ -127,8 +127,8 @@ function addExportButton() { if (!textAreaElement) return; const canSubmit = canSubmitPrompt(); - const otherExportButton = document.querySelector('#export-button'); - if (otherExportButton) otherExportButton.remove(); + const existingExportButton = document.querySelector('#export-button'); + if (existingExportButton) existingExportButton.remove(); const lastExportButton = document.querySelector('#export-conversation-button'); if ((!canSubmit || assistantChats.length === 0) && lastExportButton) { lastExportButton.remove(); @@ -190,15 +190,33 @@ function addExportButton() { }); if (canSubmit) { - const textAreaElementWrapper = textAreaElement.parentNode; - const nodeBeforetTextAreaElement = textAreaElementWrapper.previousSibling; - if (!nodeBeforetTextAreaElement) return; - if (nodeBeforetTextAreaElement.classList.length === 0) { - nodeBeforetTextAreaElement.classList = 'h-full flex ml-1 md:w-full md:m-auto md:mb-2 gap-0 md:gap-2 justify-center'; - nodeBeforetTextAreaElement.firstChild.classList = ''; - } - nodeBeforetTextAreaElement.style.minHeight = '38px'; - nodeBeforetTextAreaElement.appendChild(exportButton); + const inputForm = document.querySelector('main form'); + const inputFormFirstChild = inputForm.firstChild; + chrome.storage.local.get('settings', ({ settings }) => { + let inputFormActionWrapper = settings.autoSync + ? inputForm.querySelector('#input-form-action-wrapper') + : inputForm.firstChild.firstChild.firstChild; + if (!settings.autoSync) { + const growElement = inputFormActionWrapper.querySelector('.grow'); + if (growElement) { + growElement.remove(); + } + } + if (!inputFormActionWrapper) { + if (!inputFormFirstChild.firstChild.contains(textAreaElement)) { + inputFormFirstChild.firstChild.remove(); + } + // create new div + const newDiv = document.createElement('div'); + newDiv.id = 'input-form-action-wrapper'; + newDiv.classList = 'h-full flex ml-1 md:w-full md:m-auto md:mb-4 gap-0 md:gap-2 justify-center'; + // prepent inputform with new div + inputFormFirstChild.prepend(newDiv); + inputFormActionWrapper = newDiv; + } + inputFormActionWrapper.style.minHeight = '38px'; + if (!existingExportButton) inputFormActionWrapper.appendChild(exportButton); + }); } } diff --git a/scripts/content/global.js b/scripts/content/global.js index 28d326c..3b2380b 100644 --- a/scripts/content/global.js +++ b/scripts/content/global.js @@ -279,7 +279,7 @@ function addNavToggleButton() { sidebar.appendChild(navToggleButton); }); } -function toggleTextAreaElement(forceShow = false) { +function showHideTextAreaElement(forceShow = false) { const textAreaElement = document.querySelector('main form textarea'); if (!textAreaElement) return; const textAreaParent = textAreaElement.parentElement; @@ -325,7 +325,7 @@ function showNewChatPage() { document.querySelectorAll('#language-list-dropdown li')?.[0]?.click(); document.querySelectorAll('#tone-list-dropdown li')?.[0]?.click(); document.querySelectorAll('#writing-style-list-dropdown li')?.[0]?.click(); - document.querySelector('#auto-click-button').classList.replace('btn-primary', 'btn-neutral'); + document.querySelector('#auto-click-button')?.classList?.replace('btn-primary', 'btn-neutral'); }); runningPromptChainSteps = undefined; runningPromptChainIndex = 0; @@ -352,7 +352,7 @@ function showNewChatPage() { const textAreaElement = inputForm.querySelector('textarea'); textAreaElement.focus(); } - toggleTextAreaElement(); + showHideTextAreaElement(); initializeRegenerateResponseButton();// basically just hide the button, so conversationId is not needed handleQueryParams(search); }); @@ -558,7 +558,7 @@ function canSubmitPrompt() { } function addActionButtonWrapperAboveInput() { const regenerateResponseButton = Array.from(document.querySelectorAll('form button')).find( - (button) => button.textContent === 'Regenerate response', + (button) => button.textContent === 'Regenerate', ); if (regenerateResponseButton) regenerateResponseButton.style.zIndex = 10; const existingActionButtonWrapper = document.querySelector('#action-button-wrapper'); diff --git a/scripts/content/initialize.js b/scripts/content/initialize.js index d752d02..0c070f8 100644 --- a/scripts/content/initialize.js +++ b/scripts/content/initialize.js @@ -18,9 +18,9 @@ function initialize() { addQuickAccessMenuEventListener(); cleanNav(); upgradeCustomInstructions(); + initializeExport(); initializeContinue(); initializeNewsletter(); - initializeExport(); initializeSettings(); initializeAnnouncement(); initializeReleaseNote(); diff --git a/scripts/content/regenerateResponse.js b/scripts/content/regenerateResponse.js index bf24fb3..1480eb5 100644 --- a/scripts/content/regenerateResponse.js +++ b/scripts/content/regenerateResponse.js @@ -1,6 +1,6 @@ /* eslint-disable no-restricted-globals */ // eslint-disable-next-line no-unused-vars -/* global canSubmitPrompt, submitChat, toggleTextAreaElement, isGenerating:true, addEnforcementTriggerElement */ +/* global canSubmitPrompt, submitChat, showHideTextAreaElement, isGenerating:true, addEnforcementTriggerElement */ function toggleOriginalRegenerateResponseButton() { const allMessageWrapper = document.querySelectorAll('[id^="message-wrapper-"]'); const lastMessageWrapperElement = allMessageWrapper[allMessageWrapper.length - 1]; @@ -13,20 +13,14 @@ function toggleOriginalRegenerateResponseButton() { const textAreaElement = inputForm.querySelector('textarea'); if (!textAreaElement) return; - const textAreaElementWrapper = textAreaElement.parentNode; - const nodeBeforetTextAreaElement = textAreaElementWrapper.previousSibling; - // find all button without id - if (!nodeBeforetTextAreaElement) return; - if (nodeBeforetTextAreaElement.classList.length === 0) { - nodeBeforetTextAreaElement.classList = 'h-full flex ml-1 md:w-full md:m-auto md:mb-2 gap-0 md:gap-2 justify-center'; - nodeBeforetTextAreaElement.firstChild.classList = ''; - } - const allButtons = Array.from(nodeBeforetTextAreaElement.querySelectorAll('button:not([id])')); - const originalRegenerateResponseButton = allButtons.find((button) => button.textContent.toLowerCase() === 'regenerate response'); + const inputFormActionWrapper = inputForm.querySelector('#input-form-action-wrapper'); + + const allButtons = Array.from(inputFormActionWrapper.querySelectorAll('button:not([id])')); + const originalRegenerateResponseButton = allButtons.find((button) => button.textContent.toLowerCase() === 'regenerate'); if (originalRegenerateResponseButton) { originalRegenerateResponseButton.remove(); } - const originalContinueGeneratingButton = allButtons.find((button) => button.textContent.toLowerCase() === 'continue generating'); + const originalContinueGeneratingButton = allButtons.find((button) => button.textContent.toLowerCase() === 'continue'); if (originalContinueGeneratingButton) { originalContinueGeneratingButton.remove(); } @@ -39,10 +33,8 @@ function toggleOriginalRegenerateResponseButton() { if (existingContinueGeneratingButton) { existingContinueGeneratingButton.remove(); } - const existingErrorMessage = nodeBeforetTextAreaElement.querySelector('span'); - if (existingErrorMessage && existingErrorMessage.textContent === 'There was an error generating a response') { - nodeBeforetTextAreaElement.style.flexWrap = 'unset'; - existingErrorMessage.remove(); + if (inputForm.textContent.includes('There was an error generating a response')) { + inputForm.querySelector('#input-form-error').remove(); } if (!canSubmit) return; if (!anyUserMessageWrappers) return; @@ -50,8 +42,8 @@ function toggleOriginalRegenerateResponseButton() { const newRegenerateResponseButton = document.createElement('button'); newRegenerateResponseButton.id = 'regenerate-response-button'; newRegenerateResponseButton.type = 'button'; - newRegenerateResponseButton.classList = `btn flex justify-center gap-2 ${textAreaElementWrapper.style.display === 'none' ? 'btn-primary' : 'btn-neutral'} border`; - newRegenerateResponseButton.innerHTML = ' Regenerate response'; + newRegenerateResponseButton.classList = `btn flex justify-center gap-2 ${textAreaElement.parentElement.style.display === 'none' ? 'btn-primary' : 'btn-neutral'} border`; + newRegenerateResponseButton.innerHTML = ' Regenerate'; newRegenerateResponseButton.addEventListener('click', () => { window.localStorage.removeItem('arkoseToken'); chrome.storage.local.get(['conversations', 'settings', 'models'], (result) => { @@ -77,17 +69,11 @@ function toggleOriginalRegenerateResponseButton() { const parentId = lastUserMessage.parent; newRegenerateResponseButton.remove(); - const curMain = document.querySelector('main'); - const curInputForm = curMain.querySelector('form'); - const curTextAreaElement = curInputForm.querySelector('textarea'); - const curTextAreaElementWrapper = curTextAreaElement.parentNode; - const curNodeBeforetTextAreaElement = curTextAreaElementWrapper.previousSibling; - const errorMessage = curNodeBeforetTextAreaElement.querySelector('span'); - if (errorMessage && errorMessage.textContent === 'There was an error generating a response') { - nodeBeforetTextAreaElement.style.flexWrap = 'unset'; - errorMessage.remove(); + const curInputForm = document.querySelector('main form'); + if (curInputForm.textContent.includes('There was an error generating a response')) { + inputForm.firstChild.firstChild.remove(); } - toggleTextAreaElement(true); + showHideTextAreaElement(true); isGenerating = true; submitChat(newMessage, conversation, lastUserChatMessageId, parentId, result.settings, result.models, false, true); }); @@ -96,8 +82,8 @@ function toggleOriginalRegenerateResponseButton() { const newContinueGeneratingButton = document.createElement('button'); newContinueGeneratingButton.id = 'continue-generating-button'; newContinueGeneratingButton.type = 'button'; - newContinueGeneratingButton.classList = `btn flex justify-center gap-2 ${textAreaElementWrapper.style.display === 'none' ? 'btn-primary' : 'btn-neutral'} border`; - newContinueGeneratingButton.innerHTML = ' Continue generating'; + newContinueGeneratingButton.classList = `btn flex justify-center gap-2 ${textAreaElement.parentElement.style.display === 'none' ? 'btn-primary' : 'btn-neutral'} border`; + newContinueGeneratingButton.innerHTML = ' Continue'; newContinueGeneratingButton.addEventListener('click', () => { chrome.storage.local.get(['conversations', 'settings', 'models'], (result) => { if (result.settings.selectedModel.slug.includes('gpt-4')) { @@ -114,23 +100,23 @@ function toggleOriginalRegenerateResponseButton() { newContinueGeneratingButton.remove(); - toggleTextAreaElement(true); + showHideTextAreaElement(true); isGenerating = true; submitChat(null, conversation, conversation.current_node, conversation.current_node, result.settings, result.models, true); }); }); // handle error message - const erroMessageHTML = 'There was an error generating a response'; + const erroMessageHTML = '
    There was an error generating a response
    '; if (lastMessageWrapperElement.dataset.role === 'user') { - nodeBeforetTextAreaElement.style.flexWrap = 'wrap'; - nodeBeforetTextAreaElement.insertAdjacentHTML('afterbegin', erroMessageHTML); + if (!inputForm.textContent.includes('There was an error generating a response')) { + inputForm.firstChild.insertAdjacentHTML('afterbegin', erroMessageHTML); + } + inputFormActionWrapper.appendChild(newRegenerateResponseButton); } else { - nodeBeforetTextAreaElement.style.flexWrap = 'unset'; + inputFormActionWrapper.appendChild(newRegenerateResponseButton); + inputFormActionWrapper.appendChild(newContinueGeneratingButton); } - - nodeBeforetTextAreaElement.appendChild(newRegenerateResponseButton); - nodeBeforetTextAreaElement.appendChild(newContinueGeneratingButton); } // eslint-disable-next-line no-unused-vars diff --git a/scripts/content/settings.js b/scripts/content/settings.js index b74f945..ee986a5 100644 --- a/scripts/content/settings.js +++ b/scripts/content/settings.js @@ -39,7 +39,7 @@ function selectedTabContent(selectedTab) { } } function settingsModalContent(initialTab = 0) { - const settingsTabs = ['General', 'Auto Sync', 'models', 'Custom Prompts', 'Export', 'Splitter', 'Newsletter', 'Supporters']; + const settingsTabs = ['General', 'Auto Sync', 'Models', 'Custom Prompts', 'Export', 'Splitter', 'Newsletter', 'Supporters']; let activeTab = initialTab; // create history modal content const content = document.createElement('div'); diff --git a/scripts/content/stopGeneratingResponse.js b/scripts/content/stopGeneratingResponse.js index 5ddc724..23eb4c2 100644 --- a/scripts/content/stopGeneratingResponse.js +++ b/scripts/content/stopGeneratingResponse.js @@ -7,13 +7,6 @@ function toggleStopGeneratingResponseButton() { const submitButton = document.querySelector('main form textarea ~ button'); if (!submitButton) return; - const textAreaElementWrapper = textAreaElement.parentNode; - const nodeBeforetTextAreaElement = textAreaElementWrapper.previousSibling; - if (!nodeBeforetTextAreaElement) return; - if (nodeBeforetTextAreaElement.classList.length === 0) { - nodeBeforetTextAreaElement.classList = 'h-full flex ml-1 md:w-full md:m-auto md:mb-2 gap-0 md:gap-2 justify-center'; - nodeBeforetTextAreaElement.firstChild.classList = ''; - } const existingStopGeneratingResponseButton = document.querySelector('#stop-generating-response-button'); if (existingStopGeneratingResponseButton && !isGenerating) { existingStopGeneratingResponseButton.remove(); @@ -32,7 +25,9 @@ function toggleStopGeneratingResponseButton() { newStopGeneratingResponseButton.remove(); }); - nodeBeforetTextAreaElement.appendChild(newStopGeneratingResponseButton); + const inputForm = document.querySelector('main form'); + const inputFormActionWrapper = inputForm.querySelector('#input-form-action-wrapper'); + inputFormActionWrapper.appendChild(newStopGeneratingResponseButton); } // eslint-disable-next-line no-unused-vars diff --git a/scripts/styles/global.css b/scripts/styles/global.css index f415c4b..4daaf46 100644 --- a/scripts/styles/global.css +++ b/scripts/styles/global.css @@ -270,21 +270,18 @@ input:checked + .slider:before { } /* ezgif images have transparent background */ -#modal-content-newsletter img:not([src*="ezgif"]), #modal-content-general img, [id^="modal-content-release-note"] img { border-radius: 8px; box-shadow: 0 0 8px rgb(162, 154, 106); } -#modal-content-newsletter video, #modal-content-general video, [id^="modal-content-release-note"] video { border-radius: 8px; box-shadow: 0 0 8px rgb(162, 154, 106); } -#modal-content-newsletter iframe, #modal-content-general iframe, [id^="modal-content-release-note"] iframe { border-radius: 16px; From fa44350c6905acf8a2450911daefffaf9b9ddef3 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Tue, 1 Aug 2023 00:53:15 -0700 Subject: [PATCH 11/38] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20column=20styl?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/content/copyAndCounter.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 79dca6b..38e2f6c 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.0.6", + "version": "5.0.7", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/content/copyAndCounter.js b/scripts/content/copyAndCounter.js index b43617d..999d7f1 100644 --- a/scripts/content/copyAndCounter.js +++ b/scripts/content/copyAndCounter.js @@ -163,6 +163,7 @@ function updateCounters() { const assistantChats = allAsistantChats(); for (let i = 0; i < assistantChats.length; i += 1) { const resultElement = assistantChats[i]; + resultElement.parentElement.classList.add('flex-col'); addActionWrapperToResult(resultElement, i); updateCounterForResult(resultElement, i); addCopyButtonToResult(resultElement, i); @@ -172,6 +173,7 @@ function updateCounterEventListeners() { const assistantChats = allAsistantChats(); for (let i = 0; i < assistantChats.length; i += 1) { const resultElement = assistantChats[i]; + resultElement.parentElement.classList.add('flex-col'); addActionWrapperToResult(resultElement, i); updateCounterForResult(resultElement, i); addCopyButtonToResult(resultElement, i); From 17efe2fb59bd94478db7d8d849e1b523b1960ac1 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Wed, 2 Aug 2023 12:48:36 -0700 Subject: [PATCH 12/38] =?UTF-8?q?fix:=20=F0=9F=90=9B=20remove=20mfa=20chec?= =?UTF-8?q?k=20for=20plugins=20since=20openai=20removed=20it=20too?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/background/login.js | 4 +- scripts/content/conversationList.js | 1 + scripts/content/pluginStore.js | 63 +++++++++++------------------ 4 files changed, 27 insertions(+), 43 deletions(-) diff --git a/manifest.json b/manifest.json index 38e2f6c..8c757fe 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.0.7", + "version": "5.0.8", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/background/login.js b/scripts/background/login.js index 3fd6972..72fe615 100644 --- a/scripts/background/login.js +++ b/scripts/background/login.js @@ -51,7 +51,7 @@ function registerUser(data) { /* eslint-disable no-unused-vars */ chrome.webRequest.onBeforeSendHeaders.addListener( (details) => { - chrome.storage.sync.get(['user_id', 'openai_id', 'version', 'avatar', 'lastUserSync', 'mfa'], (result) => { + chrome.storage.sync.get(['user_id', 'openai_id', 'version', 'avatar', 'lastUserSync'], (result) => { // or conditionor const { version } = chrome.runtime.getManifest(); @@ -60,8 +60,6 @@ chrome.webRequest.onBeforeSendHeaders.addListener( || !result.avatar || !result.user_id || !result.openai_id - || typeof result.mfa === 'undefined' - || !result.mfa || result.version !== version; if (shouldRegister && details.url === 'https://chat.openai.com/api/auth/session') { const requestHeaders = details.requestHeaders.reduce((acc, header) => { diff --git a/scripts/content/conversationList.js b/scripts/content/conversationList.js index b1de9f4..34ca0d2 100644 --- a/scripts/content/conversationList.js +++ b/scripts/content/conversationList.js @@ -715,6 +715,7 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode const submitButton = inputForm.querySelector('textarea ~ button'); // submitButton.disabled = false; submitButton.innerHTML = ''; + // eslint-disable-next-line no-console console.warn(err); if (err.data) { const error = JSON.parse(err.data); diff --git a/scripts/content/pluginStore.js b/scripts/content/pluginStore.js index 1aa226a..5410b4d 100644 --- a/scripts/content/pluginStore.js +++ b/scripts/content/pluginStore.js @@ -1,5 +1,5 @@ /* eslint-disable no-unused-vars */ -/* global installPlugin, uninstallPlugin, showEnableMFA, pluginsDropdown, addPluginsDropdownEventListener */ +/* global installPlugin, uninstallPlugin, pluginsDropdown, addPluginsDropdownEventListener */ let currentPluginStorePage = 1; function initializePluginStoreModal(plugins) { const pageSize = 8; @@ -286,13 +286,6 @@ function addPluginStoreEventListener(plugins) { pluginStoreWrapper.addEventListener('click', (event) => { // if outside plugin-store-dialog close it const pluginStoreDialog = document.getElementById('plugin-store-dialog'); - const enableMFADialog = document.getElementById('enable-mfa-dialog'); - if (enableMFADialog && !pluginStoreDialog && !enableMFADialog?.contains(event.target)) { - pluginStoreWrapper.innerHTML = initializePluginStoreModal(plugins); - addPluginStoreEventListener(plugins); - - return; - } if (pluginStoreDialog && !pluginStoreDialog?.contains(event.target)) { currentPluginStorePage = 1; pluginStoreWrapper.remove(); @@ -442,39 +435,31 @@ function addInstallButtonEventListener(plugins) { e.stopPropagation(); button.disabled = true; if (button.innerText.toLowerCase() === 'install') { - chrome.storage.sync.get(['mfa'], (syncRes) => { - const { mfa } = syncRes; - const plugin = plugins.find((p) => p.id === pluginId); - if (plugin.oauth_client_id) { - if (mfa) { - const url = `${plugin.manifest.auth.client_url}?response_type=code&client_id=${plugin.oauth_client_id}&redirect_uri=https://chat.openai.com/aip/${plugin.id}/oauth/callback&scope=${plugin.manifest.auth.scope}`; - window.open(url, '_self'); - } else { - const pluginStoreWrapper = document.getElementById('plugin-store-wrapper'); - pluginStoreWrapper.innerHTML = showEnableMFA(); - } - } else { - button.classList = 'btn relative btn-light bg-green-100 hover:bg-green-100'; - button.innerHTML = 'Installing '; + const plugin = plugins.find((p) => p.id === pluginId); + if (plugin.oauth_client_id) { + const url = `${plugin.manifest.auth.client_url}?response_type=code&client_id=${plugin.oauth_client_id}&redirect_uri=https://chat.openai.com/aip/${plugin.id}/oauth/callback&scope=${plugin.manifest.auth.scope}`; + window.open(url, '_self'); + } else { + button.classList = 'btn relative btn-light bg-green-100 hover:bg-green-100'; + button.innerHTML = 'Installing '; - installPlugin(pluginId).then((res) => { - chrome.storage.local.get(['allPlugins', 'installedPlugins', 'enabledPluginIds'], (result) => { - button.disabled = false; - button.classList = 'btn relative btn-light hover:bg-gray-200'; - button.innerHTML = 'Uninstall '; - const { allPlugins, installedPlugins, enabledPluginIds } = result; - const allPluginIndex = allPlugins.findIndex((p) => p.id === pluginId); - allPlugins[allPluginIndex] = res; - const newInstalledPlugins = installedPlugins.map((p) => p.id).includes(res.id) ? installedPlugins : [...installedPlugins, res]; - chrome.storage.local.set({ allPlugins, installedPlugins: newInstalledPlugins }); - const idPrefix = 'navbar'; - const pluginsDropdownWrapper = document.getElementById(`plugins-dropdown-wrapper-${idPrefix}`); - pluginsDropdownWrapper.innerHTML = pluginsDropdown(newInstalledPlugins, enabledPluginIds, idPrefix); - addPluginsDropdownEventListener(idPrefix); - }); + installPlugin(pluginId).then((res) => { + chrome.storage.local.get(['allPlugins', 'installedPlugins', 'enabledPluginIds'], (result) => { + button.disabled = false; + button.classList = 'btn relative btn-light hover:bg-gray-200'; + button.innerHTML = 'Uninstall '; + const { allPlugins, installedPlugins, enabledPluginIds } = result; + const allPluginIndex = allPlugins.findIndex((p) => p.id === pluginId); + allPlugins[allPluginIndex] = res; + const newInstalledPlugins = installedPlugins.map((p) => p.id).includes(res.id) ? installedPlugins : [...installedPlugins, res]; + chrome.storage.local.set({ allPlugins, installedPlugins: newInstalledPlugins }); + const idPrefix = 'navbar'; + const pluginsDropdownWrapper = document.getElementById(`plugins-dropdown-wrapper-${idPrefix}`); + pluginsDropdownWrapper.innerHTML = pluginsDropdown(newInstalledPlugins, enabledPluginIds, idPrefix); + addPluginsDropdownEventListener(idPrefix); }); - } - }); + }); + } } else { button.classList = 'btn relative btn-light bg-green-100 hover:bg-green-100'; button.innerHTML = 'Unnstalling '; From d0c04cac422ed599754f504caf6918e40ea7446f Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Fri, 4 Aug 2023 21:23:54 -0700 Subject: [PATCH 13/38] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20support=20fo?= =?UTF-8?q?r=20example=20prompts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/content/api.js | 40 ++++++++++++++++- scripts/content/continue.js | 2 +- scripts/content/conversation.js | 7 +-- scripts/content/conversationList.js | 33 ++++++++------ scripts/content/export.js | 2 +- scripts/content/global.js | 62 +++++++++++++++++++++++---- scripts/content/keyboardShortcuts.js | 2 +- scripts/content/quickAccessMenu.js | 2 + scripts/content/regenerateResponse.js | 2 +- scripts/content/settings.js | 4 ++ 11 files changed, 126 insertions(+), 32 deletions(-) diff --git a/manifest.json b/manifest.json index 8c757fe..17ee677 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.0.8", + "version": "5.0.9", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/content/api.js b/scripts/content/api.js index 75ac214..b004f55 100644 --- a/scripts/content/api.js +++ b/scripts/content/api.js @@ -7,12 +7,49 @@ chrome.storage.local.get(['environment'], (result) => { API_URL = 'https://dev.wfh.team:8000'; } }); +let lastPromptSuggestions = []; // get auth token from sync storage const defaultHeaders = { 'content-type': 'application/json', }; -function generateChat(message, conversationId, messageId, parentMessageId, token, saveHistory = true, role = 'user', action = 'next') { +function getExamplePrompts(offset = 0, limit = 4) { + const url = new URL('https://chat.openai.com/backend-api/prompt_library/'); + const params = { offset, limit }; + url.search = new URLSearchParams(params).toString(); + return chrome.storage.sync.get(['auth_token']).then((result) => fetch(url, { + method: 'GET', + headers: { + ...defaultHeaders, + Authorization: result.auth_token, + }, + }).then((response) => response.json())) + .then((data) => { + lastPromptSuggestions = data.items.map((item) => item.prompt); + return data; + }); +} + +function generateSuggestions(conversationId, messageId, model, numSuggestions = 2) { + const payload = { + message_id: messageId, + model, + num_suggestions: numSuggestions, + }; + return chrome.storage.sync.get(['auth_token']).then((result) => fetch(`https://chat.openai.com/backend-api/conversation/${conversationId}/experimental/generate_suggestions`, { + method: 'POST', + headers: { + ...defaultHeaders, + Authorization: result.auth_token, + }, + body: JSON.stringify(payload), + }).then((response) => response.json())) + .then((data) => { + lastPromptSuggestions = data.suggestions; + return data; + }); +} +function generateChat(message, conversationId, messageId, parentMessageId, token, suggestions = [], saveHistory = true, role = 'user', action = 'next') { return chrome.storage.local.get(['settings', 'enabledPluginIds']).then((res) => chrome.storage.sync.get(['auth_token']).then((result) => { const payload = { action, @@ -20,6 +57,7 @@ function generateChat(message, conversationId, messageId, parentMessageId, token model: res.settings.selectedModel.slug, parent_message_id: parentMessageId, history_and_training_disabled: !saveHistory, + suggestions, timezone_offset_min: new Date().getTimezoneOffset(), }; if (action === 'next') { diff --git a/scripts/content/continue.js b/scripts/content/continue.js index 986a0e5..8a7c9c8 100644 --- a/scripts/content/continue.js +++ b/scripts/content/continue.js @@ -204,7 +204,7 @@ function addContinueButton() { // create new div const newDiv = document.createElement('div'); newDiv.id = 'input-form-action-wrapper'; - newDiv.classList = 'h-full flex ml-1 md:w-full md:m-auto md:mb-4 gap-0 md:gap-2 justify-center'; + newDiv.classList = 'h-full flex ml-1 md:w-full md:m-auto md:mb-4 gap-0 md:gap-2 justify-center items-end'; // prepent inputform with new div inputFormActionWrapper = newDiv; inputFormFirstChild.prepend(inputFormActionWrapper); diff --git a/scripts/content/conversation.js b/scripts/content/conversation.js index 4f1bcea..8b2bcda 100644 --- a/scripts/content/conversation.js +++ b/scripts/content/conversation.js @@ -1,6 +1,6 @@ /* eslint-disable no-restricted-globals */ // eslint-disable-next-line no-unused-vars -/* global getConversation, submitChat, openSubmitPromptModal, initializeRegenerateResponseButton, showHideTextAreaElement, rowAssistant, rowUser, copyRichText, messageFeedback, openFeedbackModal, refreshConversations, initializeStopGeneratingResponseButton, chatStreamIsClosed:true, generateInstructions, isGenerating:true, scrolUpDetected:true, addScrollDetector, generateSuggestions, addArkoseScript, addEnforcementTriggerElement, languageList, writingStyleList, toneList */ +/* global getConversation, submitChat, openSubmitPromptModal, initializeRegenerateResponseButton, showHideTextAreaElement, rowAssistant, rowUser, copyRichText, messageFeedback, openFeedbackModal, refreshConversations, initializeStopGeneratingResponseButton, chatStreamIsClosed:true, generateInstructions, isGenerating:true, scrolUpDetected:true, addScrollDetector, addArkoseScript, addEnforcementTriggerElement, languageList, writingStyleList, toneList */ function addPinNav(sortedNodes) { chrome.storage.local.get(['settings'], (res) => { @@ -78,7 +78,6 @@ function updateModel(modelSlug, fullConversation) { }); } function loadConversationFromNode(conversationId, newMessageId, oldMessageId, searchValue = '') { - // chatStreamIsClosed = true; chrome.storage.sync.get(['name', 'avatar'], (result) => { chrome.storage.local.get(['conversations', 'settings', 'models'], (res) => { const fullConversation = res.conversations?.[conversationId]; @@ -140,7 +139,9 @@ function loadConversationFromNode(conversationId, newMessageId, oldMessageId, se // eslint-disable-next-line no-unused-vars function loadConversation(conversationId, searchValue = '', focusOnInput = true) { - // chatStreamIsClosed = true; + // = true; + const suggestionsWrapper = document.querySelector('#suggestions-wrapper'); + if (suggestionsWrapper) suggestionsWrapper.remove(); scrolUpDetected = false; chrome.storage.sync.get(['name', 'avatar', 'conversationsOrder'], (result) => { chrome.storage.local.get(['conversations', 'settings', 'models'], (res) => { diff --git a/scripts/content/conversationList.js b/scripts/content/conversationList.js index 34ca0d2..26276b3 100644 --- a/scripts/content/conversationList.js +++ b/scripts/content/conversationList.js @@ -1,6 +1,6 @@ /* eslint-disable no-restricted-globals */ // eslint-disable-next-line no-unused-vars -/* global markdown, markdownitSup, initializeNavbar, generateInstructions, generateChat, SSE, formatDate, loadConversation, resetSelection, katex, texmath, rowUser, rowAssistant, updateOrCreateConversation, replaceTextAreaElemet, highlight, isGenerating:true, disableTextInput:true, generateTitle, debounce, initializeRegenerateResponseButton, initializeStopGeneratingResponseButton, showHideTextAreaElement, showNewChatPage, chatStreamIsClosed:true, addCopyCodeButtonsEventListeners, addScrollDetector, scrolUpDetected:true, Sortable, updateInputCounter, addUserPromptToHistory, getGPT4CounterMessageCapWindow, createFolder, getConversationElementClassList, notSelectedClassList, selectedClassList, conversationActions, addCheckboxToConversationElement, createConversation, deleteConversation, handleQueryParams, addScrollButtons, updateTotalCounter, isWindows, loadSharedConversation, createTemplateWordsModal, addEnforcementTriggerElement, initializePromptChain, insertNextChain, runningPromptChainSteps:true, runningPromptChainIndex:true */ +/* global markdown, markdownitSup, initializeNavbar, generateInstructions, generateChat, SSE, formatDate, loadConversation, resetSelection, katex, texmath, rowUser, rowAssistant, updateOrCreateConversation, replaceTextAreaElemet, highlight, isGenerating:true, disableTextInput:true, generateTitle, debounce, initializeRegenerateResponseButton, initializeStopGeneratingResponseButton, showHideTextAreaElement, showNewChatPage, chatStreamIsClosed:true, addCopyCodeButtonsEventListeners, addScrollDetector, scrolUpDetected:true, Sortable, updateInputCounter, addUserPromptToHistory, getGPT4CounterMessageCapWindow, createFolder, getConversationElementClassList, notSelectedClassList, selectedClassList, conversationActions, addCheckboxToConversationElement, createConversation, deleteConversation, handleQueryParams, addScrollButtons, updateTotalCounter, isWindows, loadSharedConversation, createTemplateWordsModal, addEnforcementTriggerElement, initializePromptChain, insertNextChain, runningPromptChainSteps:true, runningPromptChainIndex:true, lastPromptSuggestions, generateSuggestions */ // Initial state let userChatIsActuallySaved = false; @@ -113,7 +113,6 @@ function createSearchBox() { searchbox.classList = 'w-full px-4 py-2 mr-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-gray-800 conversation-search'; searchbox.addEventListener('keydown', (event) => { if (event.key === 'ArrowDown') { - // chatStreamIsClosed = true; const focusedConversation = document.querySelector('.selected'); if (focusedConversation) { const nextConversation = focusedConversation.nextElementSibling; @@ -124,7 +123,6 @@ function createSearchBox() { } } if (event.key === 'ArrowUp') { - // chatStreamIsClosed = true; const focusedConversation = document.querySelector('.selected'); if (focusedConversation) { const previousConversation = focusedConversation.previousElementSibling; @@ -136,7 +134,6 @@ function createSearchBox() { } }); searchbox.addEventListener('input', debounce((event) => { - // chatStreamIsClosed = true; const searchValue = event.target.value.toLowerCase(); chrome.storage.sync.get(['conversationsOrder'], (syncResult) => { chrome.storage.local.get(['conversations'], (result) => { @@ -315,6 +312,11 @@ function generateTitleForConversation(conversationId, messageId, profile) { setTimeout(() => { generateTitle(conversationId, messageId).then((data) => { const { title } = data; + chrome.storage.local.get('conversations', (result) => { + const { conversations } = result; + conversations[conversationId].title = title; + chrome.storage.local.set({ conversations }); + }); document.title = title; const conversationElement = document.querySelector(`#conversation-button-${conversationId}`); conversationElement.classList.add('animate-flash'); @@ -334,12 +336,6 @@ function generateTitleForConversation(conversationId, messageId, profile) { setTimeout(() => { if (topDiv) topDiv.innerHTML += `   •   Custom instructions: On  `; }, title.length * 50); - - chrome.storage.local.get('conversations', (result) => { - const { conversations } = result; - conversations[conversationId].title = title; - chrome.storage.local.set({ conversations }); - }); }); }, 500);// a little delay to make sure gen title still works even if user stops the generation } @@ -463,8 +459,10 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode existingWordCount = incompleteAssistant.querySelector('[id^=message-text-]').innerText.split(/[ /]/).length; existingCharCount = incompleteAssistant.querySelector('[id^=message-text-]').innerText.length; } + const suggestionsWrapper = document.querySelector('#suggestions-wrapper'); + if (suggestionsWrapper) suggestionsWrapper.remove(); const saveHistory = conversation?.id ? conversation.saveHistory : settings.saveHistory; - generateChat(userInput, conversation?.id, messageId, parentId, arkoseToken, saveHistory, 'user', continueGenerating ? 'continue' : 'next').then((chatStream) => { + generateChat(userInput, conversation?.id, messageId, parentId, arkoseToken, lastPromptSuggestions, saveHistory, 'user', continueGenerating ? 'continue' : 'next').then((chatStream) => { userChatIsActuallySaved = regenerateResponse || continueGenerating; let userChatSavedLocally = regenerateResponse || continueGenerating; // false by default unless regenerateResponse is true let assistantChatSavedLocally = false; @@ -495,11 +493,14 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode shouldSubmitFinalSummary = false; // update rowAssistant? } + // since we are closing the chat stream, but the following function has a delay + const tmpChatStreamIsClosed = chatStreamIsClosed; const tempId = setInterval(() => { if (userChatIsActuallySaved) { clearInterval(tempId); - updateOrCreateConversation(finalConversationId, finalMessage, messageId, settings, true, chatStreamIsClosed).then(() => { - if (!chatStreamIsClosed) { // if not clicked on stop generating button + // don't generate title if tmpChatStreamIsClosed + updateOrCreateConversation(finalConversationId, finalMessage, messageId, settings, !tmpChatStreamIsClosed, tmpChatStreamIsClosed).then(() => { + if (!tmpChatStreamIsClosed) { // if not clicked on stop generating button chrome.storage.local.get(['account'], (result) => { const { account } = result; const isPaid = account?.account_plan?.is_paid_subscription_active || account?.accounts?.default?.entitlement?.has_active_subscription || false; @@ -523,17 +524,21 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode } }, 1000); isGenerating = false; + chatStreamIsClosed = false; chatStream.close(); if (syncDiv) syncDiv.style.opacity = '1'; showHideTextAreaElement(); initializeStopGeneratingResponseButton(); initializeRegenerateResponseButton(); updateTotalCounter(); + // generateSuggestions(finalConversationId, messageId, settings.selectedModel.slug); } else if (e.event === 'ping') { // console.error('PING RECEIVED', e); } else { try { - isGenerating = true; + if (chatStream.readyState !== 2) { + isGenerating = true; + } if (finalMessage === '') { const pluginDropdownButton = document.querySelector('#navbar-plugins-dropdown-button'); if (pluginDropdownButton) { diff --git a/scripts/content/export.js b/scripts/content/export.js index fffd36f..75fa25e 100644 --- a/scripts/content/export.js +++ b/scripts/content/export.js @@ -209,7 +209,7 @@ function addExportButton() { // create new div const newDiv = document.createElement('div'); newDiv.id = 'input-form-action-wrapper'; - newDiv.classList = 'h-full flex ml-1 md:w-full md:m-auto md:mb-4 gap-0 md:gap-2 justify-center'; + newDiv.classList = 'h-full flex ml-1 md:w-full md:m-auto md:mb-4 gap-0 md:gap-2 justify-center items-end'; // prepent inputform with new div inputFormFirstChild.prepend(newDiv); inputFormActionWrapper = newDiv; diff --git a/scripts/content/global.js b/scripts/content/global.js index 3b2380b..6f62e59 100644 --- a/scripts/content/global.js +++ b/scripts/content/global.js @@ -1,5 +1,5 @@ // eslint-disable-next-line no-unused-vars -/* global markdownit, hljs, resetSelection, getPrompt, newChatPage, initializeRegenerateResponseButton, notSelectedClassList, textAreaElementInputEventListener, textAreaElementKeydownEventListenerSync, languageList, writingStyleList, toneList, refreshPage, runningPromptChainSteps:true, runningPromptChainIndex:true, dropdown */ +/* global markdownit, hljs, resetSelection, getPrompt, newChatPage, initializeRegenerateResponseButton, notSelectedClassList, textAreaElementInputEventListener, textAreaElementKeydownEventListenerSync, languageList, writingStyleList, toneList, refreshPage, runningPromptChainSteps:true, runningPromptChainIndex:true, dropdown, getExamplePrompts */ /* eslint-disable no-unused-vars */ // Gloab variables // const { version } = chrome.runtime.getManifest(); @@ -17,7 +17,6 @@ let disableTextInput = false;// to prevent input from showing extra line right b let chatStreamIsClosed = false; // to force close the chat stream // eslint-disable-next-line prefer-const let shiftKeyPressed = false; - // chrome.storage.local.get(['environment'], (result) => { // if (result.environment === 'development') { // chrome.storage.onChanged.addListener((changes, namespace) => { @@ -311,7 +310,7 @@ function showNewChatPage() { const { conversationsAreSynced, account, settings } = result; const { - selectedLanguage, selectedTone, selectedWritingStyle, autoClick, + selectedLanguage, selectedTone, selectedWritingStyle, autoClick, showExamplePrompts, } = settings; chrome.storage.local.set({ settings: { @@ -345,18 +344,63 @@ function showNewChatPage() { if (pinNav) { pinNav.remove(); } - const { pathname, href, search } = new URL(window.location.toString()); - if (href !== 'https://chat.openai.com') { - window.history.replaceState({}, '', 'https://chat.openai.com'); - const inputForm = main.querySelector('form'); - const textAreaElement = inputForm.querySelector('textarea'); - textAreaElement.focus(); + const { href, search } = new URL(window.location.toString()); + if (href !== 'https://chat.openai.com/') { + window.history.replaceState({}, '', 'https://chat.openai.com/'); } + const inputForm = main.querySelector('form'); + const textAreaElement = inputForm.querySelector('textarea'); + textAreaElement.focus(); showHideTextAreaElement(); + if (showExamplePrompts) loadExamplePrompts(); initializeRegenerateResponseButton();// basically just hide the button, so conversationId is not needed handleQueryParams(search); }); } +function suggestionButton(suggestion) { + const button = document.createElement('button'); + button.className = 'btn relative btn-neutral group w-full whitespace-nowrap rounded-xl text-left text-gray-700 shadow-[0px_1px_6px_0px_rgba(0,0,0,0.02)] dark:text-gray-300 md:whitespace-normal'; + button.style = 'width: 49%;'; + button.innerHTML = `
    ${suggestion.title}
    ${suggestion.description}
    `; + button.addEventListener('click', () => { + const textAreaElement = document.querySelector('main form textarea'); + textAreaElement.value = suggestion.prompt; + // remove all suggestion buttons + const suggestionsWrapper = document.querySelector('#suggestions-wrapper'); + if (suggestionsWrapper) suggestionsWrapper.remove(); + // click the submit button + textAreaElement.focus(); + textAreaElement.dispatchEvent(new Event('input', { bubbles: true })); + textAreaElement.dispatchEvent(new Event('change', { bubbles: true })); + setTimeout(() => { + const submitButton = document.querySelector('main form textarea ~ button'); + submitButton.click(); + }, 100); + }); + return button; +} +function loadExamplePrompts() { + getExamplePrompts().then((examplePrompts) => { + setTimeout(() => { + const existingSuggestionsWrapper = document.querySelector('#suggestions-wrapper'); + if (existingSuggestionsWrapper) existingSuggestionsWrapper.remove(); + const suggestionsWrapper = document.createElement('div'); + suggestionsWrapper.id = 'suggestions-wrapper'; + suggestionsWrapper.className = 'flex flex-wrap w-full gap-3'; + suggestionsWrapper.style = 'z-index:1000;'; + examplePrompts.items.forEach((examplePrompt) => { + const examplePromptButton = suggestionButton(examplePrompt); + suggestionsWrapper.appendChild(examplePromptButton); + }); + // check if still on new chat page + const { href } = new URL(window.location.toString()); + if (href === 'https://chat.openai.com/') { + const inputFormActionWrapper = document.querySelector('#input-form-action-wrapper'); + if (inputFormActionWrapper) inputFormActionWrapper.appendChild(suggestionsWrapper); + } + }, 1000); + }); +} function handleQueryParams(query) { const urlParams = new URLSearchParams(query); const promptId = urlParams.get('pid'); diff --git a/scripts/content/keyboardShortcuts.js b/scripts/content/keyboardShortcuts.js index da72e27..79fb5a5 100644 --- a/scripts/content/keyboardShortcuts.js +++ b/scripts/content/keyboardShortcuts.js @@ -193,8 +193,8 @@ function registerShortkeys() { // cmd/ctrl + shift + s if ((e.metaKey || (isWindows() && e.ctrlKey)) && e.shiftKey && e.keyCode === 83) { if (!document.querySelector('#modal-settings')) { - // open settings e.preventDefault(); + // open settings document.querySelector('#settings-button')?.click(); } } diff --git a/scripts/content/quickAccessMenu.js b/scripts/content/quickAccessMenu.js index 310b0bc..5772222 100644 --- a/scripts/content/quickAccessMenu.js +++ b/scripts/content/quickAccessMenu.js @@ -209,6 +209,8 @@ function loadCustomPrompts() { const newText = textAreaValue.substring(0, previousAtPosition) + prompt.text + textAreaValue.substring(cursorPosition); textAreaElement.value = newText; textAreaElement.focus(); + textAreaElement.dispatchEvent(new Event('input', { bubbles: true })); + textAreaElement.dispatchEvent(new Event('change', { bubbles: true })); }); menuContent.appendChild(promptElement); } diff --git a/scripts/content/regenerateResponse.js b/scripts/content/regenerateResponse.js index 1480eb5..65df6aa 100644 --- a/scripts/content/regenerateResponse.js +++ b/scripts/content/regenerateResponse.js @@ -14,7 +14,7 @@ function toggleOriginalRegenerateResponseButton() { const textAreaElement = inputForm.querySelector('textarea'); if (!textAreaElement) return; const inputFormActionWrapper = inputForm.querySelector('#input-form-action-wrapper'); - + if (!inputFormActionWrapper) return; const allButtons = Array.from(inputFormActionWrapper.querySelectorAll('button:not([id])')); const originalRegenerateResponseButton = allButtons.find((button) => button.textContent.toLowerCase() === 'regenerate'); if (originalRegenerateResponseButton) { diff --git a/scripts/content/settings.js b/scripts/content/settings.js index ee986a5..d337ca1 100644 --- a/scripts/content/settings.js +++ b/scripts/content/settings.js @@ -452,6 +452,9 @@ function autoSyncTabContent() { const quickSyncSwitch = createSwitch('Quick Sync', 'OFF: Sync All Conversations, ON: Sync only the last 100 conversations (Best performance)', 'quickSync', false, resetSync, 'Experimental - Requires Auto-Sync', !autoSync); content.appendChild(quickSyncSwitch); + const showExamplePromptsSwitch = createSwitch('Show Example Prompts', 'Show the example prompts when starting a new chat', 'showExamplePrompts', false, null, 'Requires Auto-Sync', !autoSync); + content.appendChild(showExamplePromptsSwitch); + const conversationTimestampSwitch = createSwitch('Conversation Timestamp', 'OFF: Created time, ON: Last updated time', 'conversationTimestamp', false, reloadConversationList, 'Requires Auto-Sync', !autoSync); content.appendChild(conversationTimestampSwitch); @@ -1346,6 +1349,7 @@ function initializeSettings() { promptHistory: result.settings?.promptHistory !== undefined ? result.settings.promptHistory : true, copyMode: result.settings?.copyMode !== undefined ? result.settings.copyMode : false, hideBottomSidebar: result.settings?.hideBottomSidebar !== undefined ? result.settings.hideBottomSidebar : false, + showExamplePrompts: result.settings?.showExamplePrompts !== undefined ? result.settings.showExamplePrompts : false, hideNewsletter: result.settings?.hideNewsletter !== undefined ? result.settings.hideNewsletter : false, customInstruction: result.settings?.customInstruction !== undefined ? result.settings.customInstruction : '', useCustomInstruction: result.settings?.useCustomInstruction !== undefined ? result.settings.useCustomInstruction : false, From 687cc28f3a7f385bf6fd8f2124566fa54416070b Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Sat, 5 Aug 2023 23:09:17 -0700 Subject: [PATCH 14/38] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20bug=20fixes=20and?= =?UTF-8?q?=20improvements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/content/autoSave.js | 6 ++++-- scripts/content/clearConversations.js | 2 +- scripts/content/conversation.js | 3 ++- scripts/content/conversationList.js | 7 +++++-- scripts/content/export.js | 4 +++- scripts/content/global.js | 21 ++++++++++++--------- scripts/content/keyboardShortcuts.js | 4 ++++ scripts/content/modelSwitcher.js | 10 +++++++++- scripts/content/promptChain.js | 4 ++-- scripts/content/quickAccessMenu.js | 5 +++-- scripts/content/regenerateResponse.js | 6 +++++- scripts/content/rowAssistant.js | 5 +++-- scripts/content/settings.js | 10 +++++++--- scripts/content/shareModal.js | 7 ++++--- scripts/styles/global.css | 17 ++++++++++++++++- 16 files changed, 81 insertions(+), 32 deletions(-) diff --git a/manifest.json b/manifest.json index 17ee677..25d720d 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.0.9", + "version": "5.1.0", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/content/autoSave.js b/scripts/content/autoSave.js index 628a0c9..a21b775 100644 --- a/scripts/content/autoSave.js +++ b/scripts/content/autoSave.js @@ -1,7 +1,7 @@ /* eslint-disable no-unused-vars */ /* eslint-disable no-restricted-globals */ // eslint-disable-next-line no-unused-vars -/* global updateNewChatButtonNotSynced, getAllConversations, getConversation, loadConversationList, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, addConversationsEventListeners, isGenerating, prependConversation, generateTitleForConversation, canSubmitPrompt, formatDate, userChatIsActuallySaved:true, addAsyncInputEvents, addSyncBanner, insertNextChunk, isWindows */ +/* global updateNewChatButtonNotSynced, getAllConversations, getConversation, loadConversationList, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, addConversationsEventListeners, isGenerating, prependConversation, generateTitleForConversation, canSubmitPrompt, formatDate, userChatIsActuallySaved:true, addAsyncInputEvents, addSyncBanner, isWindows */ /* eslint-disable no-await-in-loop, */ let localConversations = {}; let autoSaveTimeoutId; @@ -101,6 +101,8 @@ async function updateConversationInStorage(conv) { } function updateOrCreateConversation(conversationId, message, parentId, settings, generateTitle = false, forceRefresh = false, newSystemMessage = {}) { + if (!message) return; + // eslint-disable-next-line consistent-return return chrome.storage.local.get(['conversations', 'enabledPluginIds']).then((result) => { const existingConversation = result.conversations?.[conversationId]; if (existingConversation) { @@ -187,7 +189,7 @@ function updateOrCreateConversation(conversationId, message, parentId, settings, moderation_results: [], }; if (settings.selectedModel.slug.includes('plugins')) { - newConversation.plugin_ids = result.enabledPluginIds; + newConversation.pluginIds = result.enabledPluginIds; } return chrome.storage.local.set({ conversations: { diff --git a/scripts/content/clearConversations.js b/scripts/content/clearConversations.js index 9d8ea99..ddbf451 100644 --- a/scripts/content/clearConversations.js +++ b/scripts/content/clearConversations.js @@ -165,7 +165,7 @@ function replaceDeleteConversationButton() { if (conversationOrderIndex !== -1) { conversationsOrder.splice(conversationOrderIndex, 1); } else { // if not found, look into folders - const conversationFolder = conversationsOrder.find((f) => (f.id !== 'trash') && (f.conversationIds.includes(convId?.slice(0, 5)))); + const conversationFolder = conversationsOrder.find((f) => (f.id !== 'trash') && f.conversationIds && f.conversationIds.includes(convId?.slice(0, 5))); if (conversationFolder) { conversationOrderIndex = conversationFolder.conversationIds.findIndex((id) => id === convId?.slice(0, 5)); conversationFolder.conversationIds.splice(conversationOrderIndex, 1); diff --git a/scripts/content/conversation.js b/scripts/content/conversation.js index 8b2bcda..c91592c 100644 --- a/scripts/content/conversation.js +++ b/scripts/content/conversation.js @@ -34,7 +34,8 @@ function addPinNav(sortedNodes) { }); } function updateModel(modelSlug, fullConversation) { - const pluginIds = fullConversation.pluginIds || []; + const pluginIds = fullConversation.plugin_ids || []; + const { languageCode, toneCode, writingStyleCode } = fullConversation; if (!modelSlug) return; diff --git a/scripts/content/conversationList.js b/scripts/content/conversationList.js index 26276b3..b98b3cb 100644 --- a/scripts/content/conversationList.js +++ b/scripts/content/conversationList.js @@ -670,6 +670,7 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode }); } + messageContentParts = messageContentParts.replace(/[^n}]\n\\/g, '\n\n\\'); const messageContentPartsHTML = markdown('assistant') .use(markdownitSup) .use(texmath, { @@ -871,7 +872,7 @@ function overrideSubmitForm() { let text = textAreaElement.value.trim(); if (chunkNumber === 1) { finalSummary = ''; - if (settings.autoSplit && text.length > settings.autoSplitLimit) { + if (settings.autoSplit && text.length > settings.autoSplitLimit && !runningPromptChainSteps) { totalChunks = Math.ceil(text.length / settings.autoSplitLimit); const lastNewLineIndexBeforeLimit = settings.autoSplitLimit > text.length ? settings.autoSplitLimit : getLastIndexOf(text, settings.autoSplitLimit); remainingText = text.substring(lastNewLineIndexBeforeLimit); @@ -912,6 +913,7 @@ ${settings.autoSplitChunkPrompt}`; isGenerating = true; submitChat(text, conversation, messageId, parentId, settings, models); textAreaElement.value = ''; + textAreaElement.style.height = '24px'; updateInputCounter(''); } }); @@ -990,6 +992,7 @@ ${settings.autoSplitChunkPrompt}`; isGenerating = true; submitChat(text, {}, messageId, parentId, settings, models); textAreaElement.value = ''; + textAreaElement.style.height = '24px'; updateInputCounter(''); } }); @@ -1035,8 +1038,8 @@ ${settings.autoSplitChunkPrompt}`; }, 100); } } else { - if (textAreaElement.value.trim().length === 0) return; textAreaElement.style.height = '24px'; + if (textAreaElement.value.trim().length === 0) return; addUserPromptToHistory(textAreaElement.value.trim()); inputForm.dispatchEvent(new Event('submit', { cancelable: true })); } diff --git a/scripts/content/export.js b/scripts/content/export.js index 75fa25e..62374ae 100644 --- a/scripts/content/export.js +++ b/scripts/content/export.js @@ -357,7 +357,9 @@ function openExportAllModal() { exportAllModal.style = 'position:fixed;top:0px;left:0px;width:100%;height:100%;background-color:rgba(0,0,0,0.5);z-index:1000;display:flex;align-items:center;justify-content:center;color:lightslategray;'; exportAllModal.id = 'export-all-modal'; exportAllModal.addEventListener('click', (e) => { - if (e.target.id === 'export-all-modal') { + // export-all-modal-progress-bar-fill + const exportAllModalProgressBarFill = document.getElementById('export-all-modal-progress-bar-fill'); + if (e.target.id === 'export-all-modal' && (exportAllModalProgressBarFill.style.width === '0%' || exportAllModalProgressBarFill.style.width === '100%')) { exportAllModal.remove(); } }); diff --git a/scripts/content/global.js b/scripts/content/global.js index 6f62e59..7d7dd5c 100644 --- a/scripts/content/global.js +++ b/scripts/content/global.js @@ -94,7 +94,7 @@ function initializeStorage() { } // eslint-disable-next-line new-cap const markdown = (role, searchValue = '') => new markdownit({ - html: role === 'assistant' && searchValue !== '', + html: role === 'assistant' && searchValue === '', linkify: true, highlight(str, _lang) { const { language, value } = hljs.highlightAuto(str); @@ -230,7 +230,8 @@ function addNavToggleButton() { if (!sidebar) return; if (!mainContent) return; // add transition to nav and main - sidebar.style = `${sidebar.style.cssText};transition:margin-left 0.3s ease-in-out;position:relative;overflow:unset`; + sidebar.id = 'sidebar'; + sidebar.style = `${sidebar.style.cssText};width:260px !important;visibility:visible !important;transition:margin-left 0.3s ease-in-out;position:relative;overflow:unset`; mainContent.style.transition = 'padding-left 0.3s ease-in-out'; const navToggleButton = document.createElement('div'); @@ -310,20 +311,22 @@ function showNewChatPage() { const { conversationsAreSynced, account, settings } = result; const { - selectedLanguage, selectedTone, selectedWritingStyle, autoClick, showExamplePrompts, + selectedLanguage, selectedTone, selectedWritingStyle, autoClick, showExamplePrompts, autoResetTopNav, } = settings; chrome.storage.local.set({ settings: { ...settings, autoClick: false, - selectedLanguage: languageList.find((language) => language.code === 'default'), - selectedTone: toneList.find((tone) => tone.code === 'default'), - selectedWritingStyle: writingStyleList.find((writingStyle) => writingStyle.code === 'default'), + selectedLanguage: autoResetTopNav ? languageList.find((language) => language.code === 'default') : selectedLanguage, + selectedTone: autoResetTopNav ? toneList.find((tone) => tone.code === 'default') : selectedTone, + selectedWritingStyle: autoResetTopNav ? writingStyleList.find((writingStyle) => writingStyle.code === 'default') : selectedWritingStyle, }, }, () => { - document.querySelectorAll('#language-list-dropdown li')?.[0]?.click(); - document.querySelectorAll('#tone-list-dropdown li')?.[0]?.click(); - document.querySelectorAll('#writing-style-list-dropdown li')?.[0]?.click(); + if (autoResetTopNav) { + document.querySelectorAll('#language-list-dropdown li')?.[0]?.click(); + document.querySelectorAll('#tone-list-dropdown li')?.[0]?.click(); + document.querySelectorAll('#writing-style-list-dropdown li')?.[0]?.click(); + } document.querySelector('#auto-click-button')?.classList?.replace('btn-primary', 'btn-neutral'); }); runningPromptChainSteps = undefined; diff --git a/scripts/content/keyboardShortcuts.js b/scripts/content/keyboardShortcuts.js index 79fb5a5..03d0890 100644 --- a/scripts/content/keyboardShortcuts.js +++ b/scripts/content/keyboardShortcuts.js @@ -197,6 +197,10 @@ function registerShortkeys() { // open settings document.querySelector('#settings-button')?.click(); } + setTimeout(() => { + window.localStorage.setItem('UiState.isNavigationCollapsed.1', 'false'); + document.querySelector('button[aria-label*=sidebar]')?.remove(); + }, 300); } // cmd/ctrl + shift + l if ((e.metaKey || (isWindows() && e.ctrlKey)) && e.shiftKey && e.keyCode === 76) { diff --git a/scripts/content/modelSwitcher.js b/scripts/content/modelSwitcher.js index f4fc62f..a7a8e48 100644 --- a/scripts/content/modelSwitcher.js +++ b/scripts/content/modelSwitcher.js @@ -1,5 +1,5 @@ /* eslint-disable no-unused-vars */ -/* global getInstalledPlugins, addArkoseScript */ +/* global getInstalledPlugins, addArkoseScript, initializeRegenerateResponseButton */ // eslint-disable-next-line no-unused-vars function modelSwitcher(models, selectedModel, idPrefix, customModels, forceDark = false) { if (selectedModel.slug.includes('gpt-4')) { @@ -114,6 +114,14 @@ function addModelSwitcherEventListener(idPrefix, forceDark = false) { const modelSlug = option.id.split(`${idPrefix}-model-switcher-option-`)[1]; const selectedModel = allModels.find((m) => m.slug === modelSlug); const pluginsDropdownWrapper = document.querySelector(`#plugins-dropdown-wrapper-${idPrefix}`); + const continueGeneratingButton = document.querySelector('#continue-generating-button'); + if (selectedModel.slug.includes('plugins')) { + if (continueGeneratingButton) { + continueGeneratingButton.remove(); + } + } else if (!continueGeneratingButton) { + initializeRegenerateResponseButton(); + } if (pluginsDropdownWrapper) { if (selectedModel.slug.includes('plugins')) { getInstalledPlugins(); diff --git a/scripts/content/promptChain.js b/scripts/content/promptChain.js index 96f0b75..8a09c91 100644 --- a/scripts/content/promptChain.js +++ b/scripts/content/promptChain.js @@ -30,7 +30,7 @@ function promptChainListModalContent() { const promptChainName = document.createElement('div'); promptChainName.classList = 'flex-1 truncate relative mr-2'; promptChainName.innerHTML = `
    ${promptChain.title}
    `; - promptChainName.title = promptChain.title; + promptChainName.title = promptChain.steps.map((step, i) => `Step ${i + 1}:\n${step}`).join('\n\n'); promptChainElement.appendChild(promptChainName); promptChainElement.addEventListener('click', () => { // document.getElementById('modal-close-button-prompt-chains').click(); @@ -162,7 +162,7 @@ function createNewPromptChainModal(promptChainName, promptChainSteps, chainIndex newPromptChainModalContent.id = 'new-prompt-chain-modal-content'; const modalTitle = document.createElement('div'); modalTitle.style = 'display:flex; align-items:center; justify-content:space-between;color:white;font-size:1.25rem; font-weight: bold; margin-bottom: 16px;padding: 0 16px;width:100%;'; - modalTitle.innerHTML = `${isNew ? 'Create prompt chain' : 'Edit prompt chain'} `; + modalTitle.innerHTML = `${isNew ? 'Create prompt chain' : 'Edit prompt chain'} `; newPromptChainModalContent.appendChild(modalTitle); const modalSubtitle = document.createElement('div'); modalSubtitle.style = 'color:lightslategray;font-size:0.875rem; margin-bottom: 16px;padding: 0 16px;'; diff --git a/scripts/content/quickAccessMenu.js b/scripts/content/quickAccessMenu.js index 5772222..fcbd522 100644 --- a/scripts/content/quickAccessMenu.js +++ b/scripts/content/quickAccessMenu.js @@ -195,7 +195,7 @@ function loadCustomPrompts() { const promptElement = document.createElement('button'); promptElement.id = `quick-access-menu-item-${i}`; promptElement.classList = 'btn w-full text-left focus:outline focus:ring-2 focus:ring-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700'; - promptElement.innerHTML = `${prompt.title}${prompt.text}`; + promptElement.innerHTML = `${prompt.title}${prompt.text}`; promptElement.addEventListener('click', () => { const inputForm = document.querySelector('main form'); if (!inputForm) return; @@ -237,7 +237,8 @@ function loadPromptChains() { const promptElement = document.createElement('button'); promptElement.id = `quick-access-menu-item-${i}`; promptElement.classList = 'btn w-full text-left focus:outline focus:ring-2 focus:ring-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700'; - promptElement.innerHTML = `${prompt.title}`; + promptElement.innerHTML = `${prompt.title}Step 1: ${prompt.steps[0]}`; + // also trigger on enter promptElement.addEventListener('click', () => { runPromptChain(prompt.steps, false); }); diff --git a/scripts/content/regenerateResponse.js b/scripts/content/regenerateResponse.js index 65df6aa..0033a8a 100644 --- a/scripts/content/regenerateResponse.js +++ b/scripts/content/regenerateResponse.js @@ -115,7 +115,11 @@ function toggleOriginalRegenerateResponseButton() { inputFormActionWrapper.appendChild(newRegenerateResponseButton); } else { inputFormActionWrapper.appendChild(newRegenerateResponseButton); - inputFormActionWrapper.appendChild(newContinueGeneratingButton); + chrome.storage.local.get(['settings'], (result) => { + if (!result.settings.selectedModel.slug.includes('plugins')) { + inputFormActionWrapper.appendChild(newContinueGeneratingButton); + } + }); } } diff --git a/scripts/content/rowAssistant.js b/scripts/content/rowAssistant.js index 0bf8e5e..ce13ce0 100644 --- a/scripts/content/rowAssistant.js +++ b/scripts/content/rowAssistant.js @@ -8,7 +8,6 @@ function rowAssistant(conversation, node, childIndex, childCount, models, custom const modelTitle = models.find((m) => m.slug === modelSlug)?.title; let messageContentParts = highlight(message.content.parts.join('\n'), searchValue); - // if citations array is not mpty, replace text from start_ix to end_ix position with citation if (citations?.length > 0) { citations.reverse().forEach((citation, index) => { @@ -25,11 +24,13 @@ function rowAssistant(conversation, node, childIndex, childCount, models, custom messageContentParts = messageContentParts.replace(messageContentParts.substring(startIndex, endIndex), citationText); }); } + // replace single \n\\ with \n\n\\ + messageContentParts = messageContentParts.replace(/[^n}]\n\\/g, '\n\n\\'); const messageContentPartsHTML = markdown('assistant', searchValue) .use(markdownitSup) .use(texmath, { engine: katex, - delimiters: 'brackets', + delimiters: ['brackets'], katexOptions: { macros: { '\\RR': '\\mathbb{R}' } }, }).render(messageContentParts); diff --git a/scripts/content/settings.js b/scripts/content/settings.js index d337ca1..fb9dd29 100644 --- a/scripts/content/settings.js +++ b/scripts/content/settings.js @@ -149,7 +149,7 @@ function generalTabContent() { document.querySelector('#conversation-bottom').firstChild.style.maxWidth = `${newValue}%`; } document.querySelector('main').querySelector('form').style.maxWidth = `${newValue}%`; - chrome.storage.local.set({ settings: { ...result.settings, conversationWidth: newValue } }); + chrome.storage.local.set({ settings: { ...result.settings, conversationWidth: newValue, customConversationWidth: true } }); }); conversationWidthInput.addEventListener('input', () => { const curConversationWidthInput = document.querySelector('#conversation-width-input'); @@ -162,7 +162,7 @@ function generalTabContent() { document.querySelector('#conversation-bottom').firstChild.style.maxWidth = `${newValue}%`; } document.querySelector('main').querySelector('form').style.maxWidth = `${newValue}%`; - chrome.storage.local.set({ settings: { ...result.settings, conversationWidth: newValue } }); + chrome.storage.local.set({ settings: { ...result.settings, conversationWidth: newValue, customConversationWidth: true } }); }); }); leftContent.appendChild(conversationWidthInput); @@ -218,7 +218,7 @@ function generalTabContent() { exportButton.textContent = 'Export'; exportButton.addEventListener('click', () => { chrome.storage.sync.get(['conversationsOrder'], (res) => { - chrome.storage.local.get(['settings', 'customModels', 'customPrompts'], (result) => { + chrome.storage.local.get(['settings', 'customModels', 'customPrompts', 'customInstructionProfiles'], (result) => { const { settings, customModels, customPrompts, customInstructionProfiles, } = result; @@ -466,6 +466,9 @@ function autoSyncTabContent() { const autoHideTopNav = createSwitch('Auto hide Top Navbar', 'Automatically hide the navbar at the top of the page when move the mouse out of it.', 'autoHideTopNav', true, toggleTopNav, 'Requires Auto-Sync', !autoSync); content.appendChild(autoHideTopNav); + + const autoResetTopNav = createSwitch('Auto Reset Top Navbar', 'Automatically reset the tone, writing style, and language to default when switching to new chats', 'autoResetTopNav', false, toggleTopNav, 'Requires Auto-Sync', !autoSync); + content.appendChild(autoResetTopNav); }); return content; } @@ -1348,6 +1351,7 @@ function initializeSettings() { safeMode: result.settings?.safeMode !== undefined ? result.settings.safeMode : true, promptHistory: result.settings?.promptHistory !== undefined ? result.settings.promptHistory : true, copyMode: result.settings?.copyMode !== undefined ? result.settings.copyMode : false, + autoResetTopNav: result.settings?.autoResetTopNav !== undefined ? result.settings.hideBottomSidebar : false, hideBottomSidebar: result.settings?.hideBottomSidebar !== undefined ? result.settings.hideBottomSidebar : false, showExamplePrompts: result.settings?.showExamplePrompts !== undefined ? result.settings.showExamplePrompts : false, hideNewsletter: result.settings?.hideNewsletter !== undefined ? result.settings.hideNewsletter : false, diff --git a/scripts/content/shareModal.js b/scripts/content/shareModal.js index 166441f..67456d5 100644 --- a/scripts/content/shareModal.js +++ b/scripts/content/shareModal.js @@ -458,7 +458,7 @@ function userRow(message) {
  • `; } function assistantRow(message) { - let messageContent = message.content.parts.join('\n'); + let messageContentParts = message.content.parts.join('\n'); // if citations array is not mpty, replace text from start_ix to end_ix position with citation if (message.metadata.citations?.length > 0) { @@ -473,16 +473,17 @@ function assistantRow(message) { citationText = ''; } - messageContent = messageContent.replace(messageContent.substring(startIndex, endIndex), citationText); + messageContentParts = messageContentParts.replace(messageContentParts.substring(startIndex, endIndex), citationText); }); } + messageContentParts = messageContentParts.replace(/[^n}]\n\\/g, '\n\n\\'); const messageContentPartsHTML = markdown('assistant') .use(markdownitSup) .use(texmath, { engine: katex, delimiters: 'brackets', katexOptions: { macros: { '\\RR': '\\mathbb{R}' } }, - }).render(messageContent); + }).render(messageContentParts); const avatarColor = (message.metadata.model_slug?.includes('plugins') || message.metadata.model_slug?.startsWith('gpt-4')) ? 'rgb(171, 104, 255)' : 'rgb(25, 195, 125)'; return `
    eq { +.markdown ol li eq, +.markdown ul li eq { margin: 0; } + +.markdown ol li p, +.markdown ul li p { + white-space: pre-wrap; +} +.prose :where(p):not(:where([class~=not-prose] *)), +.prose :where(ol):not(:where([class~=not-prose] *)), +.prose :where(ul):not(:where([class~=not-prose] *)) { + margin-bottom: 0 !important; +} .markdown ol, .markdown ul { white-space: initial; } From 447e960e1224ae675498907bd6e02445ddf3f9a3 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Wed, 9 Aug 2023 10:51:27 -0700 Subject: [PATCH 15/38] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20a=20bug=20wit?= =?UTF-8?q?h=20custom=20instruction=20profiles=20and=20folders?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 + manifest.json | 2 +- scripts/content/customInstructions.js | 285 +++++++++++++------------- scripts/content/folderElement.js | 1 + scripts/content/newChatPage.js | 27 +-- 5 files changed, 164 insertions(+), 162 deletions(-) diff --git a/README.md b/README.md index 8127eea..22c75a2 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,17 @@ with 5. Firefox will prompt you to confirm the installation of the addon. Click Install. 6. The addon will be installed and will appear in the list of installed addons on the Add-ons page. +## Star History + + + + + + Star History Chart + + + + ## FAQ Read our [FAQ document](https://ezi.notion.site/Superpower-ChatGPT-FAQ-9d43a8a1c31745c893a4080029d2eb24) for more information about Superpower ChatGPT diff --git a/manifest.json b/manifest.json index 25d720d..5236fe0 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.1.0", + "version": "5.1.1", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/content/customInstructions.js b/scripts/content/customInstructions.js index 6878e0d..0ef9886 100644 --- a/scripts/content/customInstructions.js +++ b/scripts/content/customInstructions.js @@ -197,116 +197,84 @@ function profileDropdownButton(customInstructionProfiles, placement) { function upgradeCustomInstructions() { // observe the body and wait for the custom instructions dialog to be added // there should be a div with role="dialog" and a h2 with text "Custom instructions" - chrome.storage.local.get(['account'], (r) => { - const { account } = r; - const isPaid = account?.account_plan?.is_paid_subscription_active || account?.accounts?.default?.entitlement?.has_active_subscription || false; - if (!isPaid) return; - const targetNode = document.body; - const config = { childList: true, subtree: true }; - const callback = (mutationsList) => { - mutationsList.forEach((mutation) => { - if (mutation.type === 'childList') { - setTimeout(() => { - chrome.storage.local.get(['customInstructionProfiles'], (result) => { - const customInstructionsDialog = document.querySelector('[role="dialog"][data-state="open"][tabindex="-1"]'); - if (!customInstructionsDialog) return; - const customInstructionsDialogHeader = customInstructionsDialog.querySelector('h2'); - const existingProfileButtonWrapper = customInstructionsDialog.querySelector('#custom-instructions-profile-button-wrapper-settings'); - const textAreaFields = customInstructionsDialog.querySelectorAll('textarea'); - if (textAreaFields.length > 0 && !existingProfileButtonWrapper && customInstructionsDialog && customInstructionsDialogHeader.textContent === 'Custom instructions') { - const aboutUser = textAreaFields[0]?.value; - const aboutModel = textAreaFields[1]?.value; - const { customInstructionProfiles } = result; - let newCustomInstructionProfiles = customInstructionProfiles; - let selectedProfile = customInstructionProfiles.find((p) => p.isSelected); + const targetNode = document.body; + const config = { childList: true, subtree: true }; + const callback = (mutationsList) => { + mutationsList.forEach((mutation) => { + if (mutation.type === 'childList') { + setTimeout(() => { + chrome.storage.local.get(['customInstructionProfiles'], (result) => { + const customInstructionsDialog = document.querySelector('[role="dialog"][data-state="open"][tabindex="-1"]'); + if (!customInstructionsDialog) return; + const customInstructionsDialogHeader = customInstructionsDialog.querySelector('h2'); + const existingProfileButtonWrapper = customInstructionsDialog.querySelector('#custom-instructions-profile-button-wrapper-settings'); + const textAreaFields = customInstructionsDialog.querySelectorAll('textarea'); + if (textAreaFields.length > 0 && !existingProfileButtonWrapper && customInstructionsDialog && customInstructionsDialogHeader.textContent === 'Custom instructions') { + const aboutUser = textAreaFields[0]?.value; + const aboutModel = textAreaFields[1]?.value; + const { customInstructionProfiles } = result; + let newCustomInstructionProfiles = customInstructionProfiles; + let selectedProfile = customInstructionProfiles.find((p) => p.isSelected); - if (!selectedProfile || (selectedProfile.aboutUser !== aboutUser || selectedProfile.aboutModel !== aboutModel)) { - newCustomInstructionProfiles = customInstructionProfiles.map((p) => { - if (p.aboutModel === aboutModel && p.aboutUser === aboutUser) { - selectedProfile = { ...p, isSelected: true }; - return { ...p, isSelected: true }; - } - if (p.isSelected) { - selectedProfile = undefined; - return { ...p, isSelected: false }; - } - return p; - }); - chrome.storage.local.set({ customInstructionProfiles: newCustomInstructionProfiles }); - } - // header = first child - const header = customInstructionsDialog.firstChild; - const profileButtonWrapper = document.createElement('div'); - profileButtonWrapper.style = 'position:relative;width: 200px;'; - profileButtonWrapper.id = 'custom-instructions-profile-button-wrapper-settings'; - profileButtonWrapper.appendChild(profileDropdown(newCustomInstructionProfiles, 'settings')); - profileButtonWrapper.appendChild(profileDropdownButton(newCustomInstructionProfiles, 'settings')); - header.appendChild(profileButtonWrapper); - // body = second child - const body = customInstructionsDialog.children[1]; - const nameLabel = document.createElement('label'); - nameLabel.className = 'block text-xs text-gray-700 dark:text-gray-500 mb-2 text-gray-600'; - nameLabel.textContent = 'Name'; - const nameInput = document.createElement('input'); - nameInput.id = 'custom-instructions-name-input'; - nameInput.placeholder = 'Profile Name'; - nameInput.value = selectedProfile?.name || ''; - nameInput.classList = 'w-full rounded p-2 mb-6 border dark:bg-gray-800 bg-white border-gray-100 focus:border-brand-green focus:ring-0 focus-visible:ring-0 bg-gray-50 outline-none focus-visible:outline-none'; - if (textAreaFields[0].disabled) { - nameInput.disabled = true; - nameInput.classList.add('text-gray-300'); - } - nameInput.addEventListener('input', () => { - const curSelectedProfileName = selectedProfile?.name || ''; - const allButtons = body.querySelectorAll('button'); - const saveButton = [...allButtons].find((b) => b.textContent === 'Save'); - const curTextAreaFields = document.querySelectorAll('[role="dialog"][data-state="open"][tabindex="-1"] textarea'); - const curAboutUserInput = curTextAreaFields[0]; - const curAboutModelInput = curTextAreaFields[1]; - if (nameInput.value === '' || (nameInput.value === curSelectedProfileName && curAboutUserInput.value === selectedProfile?.aboutUser && curAboutModelInput.value === selectedProfile?.aboutModel)) { - saveButton.disabled = true; - saveButton.classList.add('opacity-50', 'cursor-not-allowed'); - } else if (saveButton.disabled) { - saveButton.disabled = false; - saveButton.classList.remove('opacity-50', 'cursor-not-allowed'); + if (!selectedProfile || (selectedProfile.aboutUser !== aboutUser || selectedProfile.aboutModel !== aboutModel)) { + newCustomInstructionProfiles = customInstructionProfiles.map((p) => { + if (p.aboutModel === aboutModel && p.aboutUser === aboutUser) { + selectedProfile = { ...p, isSelected: true }; + return { ...p, isSelected: true }; } + if (p.isSelected) { + selectedProfile = undefined; + return { ...p, isSelected: false }; + } + return p; }); - // add the input field to the beginning of the body - body.insertBefore(nameInput, body.firstChild); - body.insertBefore(nameLabel, body.firstChild); - // add a change listener to the text area fields - textAreaFields.forEach((t) => { - t.addEventListener('input', () => { - setTimeout(() => { - const curNameInput = document.querySelector('#custom-instructions-name-input'); - if (curNameInput.value === '') { - saveButton.disabled = true; - saveButton.classList.add('opacity-50', 'cursor-not-allowed'); - } else if (curNameInput.value !== selectedProfile?.name) { - saveButton.disabled = false; - saveButton.classList.remove('opacity-50', 'cursor-not-allowed'); - } - }, 10); - }); - }); - // find a button inside body that has text "Save" + chrome.storage.local.set({ customInstructionProfiles: newCustomInstructionProfiles }); + } + // header = first child + const header = customInstructionsDialog.firstChild; + const profileButtonWrapper = document.createElement('div'); + profileButtonWrapper.style = 'position:relative;width: 200px;'; + profileButtonWrapper.id = 'custom-instructions-profile-button-wrapper-settings'; + profileButtonWrapper.appendChild(profileDropdown(newCustomInstructionProfiles, 'settings')); + profileButtonWrapper.appendChild(profileDropdownButton(newCustomInstructionProfiles, 'settings')); + header.appendChild(profileButtonWrapper); + // body = second child + const body = customInstructionsDialog.children[1]; + const nameLabel = document.createElement('label'); + nameLabel.className = 'block text-xs text-gray-700 dark:text-gray-500 mb-2 text-gray-600'; + nameLabel.textContent = 'Name'; + const nameInput = document.createElement('input'); + nameInput.id = 'custom-instructions-name-input'; + nameInput.placeholder = 'Profile Name'; + nameInput.value = selectedProfile?.name || ''; + nameInput.classList = 'w-full rounded p-2 mb-6 border dark:bg-gray-800 bg-white border-gray-100 focus:border-brand-green focus:ring-0 focus-visible:ring-0 bg-gray-50 outline-none focus-visible:outline-none'; + if (textAreaFields[0].disabled) { + nameInput.disabled = true; + nameInput.classList.add('text-gray-300'); + } + nameInput.addEventListener('input', () => { + const curSelectedProfileName = selectedProfile?.name || ''; const allButtons = body.querySelectorAll('button'); - const toggleButton = [...allButtons].find((b) => b.role === 'switch'); const saveButton = [...allButtons].find((b) => b.textContent === 'Save'); - // add a chane listener to the toggle button - toggleButton.addEventListener('click', () => { - const currState = toggleButton.getAttribute('aria-checked'); - - // when toggle off nameinput shoud be disabled - const curNameInput = document.querySelector('#custom-instructions-name-input'); - if (currState === 'true') { - curNameInput.disabled = true; - curNameInput.classList.add('text-gray-300'); - } else { - curNameInput.disabled = false; - curNameInput.classList.remove('text-gray-300'); - } + const curTextAreaFields = document.querySelectorAll('[role="dialog"][data-state="open"][tabindex="-1"] textarea'); + const curAboutUserInput = curTextAreaFields[0]; + const curAboutModelInput = curTextAreaFields[1]; + if (nameInput.value === '' || (nameInput.value === curSelectedProfileName && curAboutUserInput.value === selectedProfile?.aboutUser && curAboutModelInput.value === selectedProfile?.aboutModel)) { + saveButton.disabled = true; + saveButton.classList.add('opacity-50', 'cursor-not-allowed'); + } else if (saveButton.disabled) { + saveButton.disabled = false; + saveButton.classList.remove('opacity-50', 'cursor-not-allowed'); + } + }); + // add the input field to the beginning of the body + body.insertBefore(nameInput, body.firstChild); + body.insertBefore(nameLabel, body.firstChild); + // add a change listener to the text area fields + textAreaFields.forEach((t) => { + t.addEventListener('input', () => { setTimeout(() => { + const curNameInput = document.querySelector('#custom-instructions-name-input'); if (curNameInput.value === '') { saveButton.disabled = true; saveButton.classList.add('opacity-50', 'cursor-not-allowed'); @@ -316,49 +284,76 @@ function upgradeCustomInstructions() { } }, 10); }); - // add a click listener to the save button - saveButton.addEventListener('click', () => { - chrome.storage.local.get(['customInstructionProfiles'], (res) => { - const { customInstructionProfiles: cip } = res; - const curNameInput = document.querySelector('#custom-instructions-name-input'); - const curTextAreaFields = document.querySelectorAll('[role="dialog"][data-state="open"][tabindex="-1"] textarea'); - const curAboutUserInput = curTextAreaFields[0]; - const curAboutModelInput = curTextAreaFields[1]; - const curSelectedProfile = cip.find((p) => p.isSelected); + }); + // find a button inside body that has text "Save" + const allButtons = body.querySelectorAll('button'); + const toggleButton = [...allButtons].find((b) => b.role === 'switch'); + const saveButton = [...allButtons].find((b) => b.textContent === 'Save'); + // add a chane listener to the toggle button + toggleButton.addEventListener('click', () => { + const currState = toggleButton.getAttribute('aria-checked'); - if (!curSelectedProfile) { - const newCip = [...cip, { - name: curNameInput.value, aboutUser: curAboutUserInput.value, aboutModel: curAboutModelInput.value, isSelected: true, id: self.crypto.randomUUID(), - }]; - chrome.storage.local.set({ customInstructionProfiles: newCip }, () => { - toast('Profile saved'); - reloadCustomInstructionSettings(); - }); - } else { - const newCip = cip.map((p) => { - if (p.isSelected) { - return { - ...p, name: curNameInput.value, aboutUser: curAboutUserInput.value, aboutModel: curAboutModelInput.value, - }; - } - return p; - }); - chrome.storage.local.set({ customInstructionProfiles: newCip }, () => { - toast('Profile updated'); - reloadCustomInstructionSettings(); - }); - } - }); + // when toggle off nameinput shoud be disabled + const curNameInput = document.querySelector('#custom-instructions-name-input'); + if (currState === 'true') { + curNameInput.disabled = true; + curNameInput.classList.add('text-gray-300'); + } else { + curNameInput.disabled = false; + curNameInput.classList.remove('text-gray-300'); + } + setTimeout(() => { + if (curNameInput.value === '') { + saveButton.disabled = true; + saveButton.classList.add('opacity-50', 'cursor-not-allowed'); + } else if (curNameInput.value !== selectedProfile?.name) { + saveButton.disabled = false; + saveButton.classList.remove('opacity-50', 'cursor-not-allowed'); + } + }, 10); + }); + // add a click listener to the save button + saveButton.addEventListener('click', () => { + chrome.storage.local.get(['customInstructionProfiles'], (res) => { + const { customInstructionProfiles: cip } = res; + const curNameInput = document.querySelector('#custom-instructions-name-input'); + const curTextAreaFields = document.querySelectorAll('[role="dialog"][data-state="open"][tabindex="-1"] textarea'); + const curAboutUserInput = curTextAreaFields[0]; + const curAboutModelInput = curTextAreaFields[1]; + const curSelectedProfile = cip.find((p) => p.isSelected); + + if (!curSelectedProfile) { + const newCip = [...cip, { + name: curNameInput.value, aboutUser: curAboutUserInput.value, aboutModel: curAboutModelInput.value, isSelected: true, id: self.crypto.randomUUID(), + }]; + chrome.storage.local.set({ customInstructionProfiles: newCip }, () => { + toast('Profile saved'); + reloadCustomInstructionSettings(); + }); + } else { + const newCip = cip.map((p) => { + if (p.isSelected) { + return { + ...p, name: curNameInput.value, aboutUser: curAboutUserInput.value, aboutModel: curAboutModelInput.value, + }; + } + return p; + }); + chrome.storage.local.set({ customInstructionProfiles: newCip }, () => { + toast('Profile updated'); + reloadCustomInstructionSettings(); + }); + } }); - } - }); - }, 200); - } - }); - }; - const observer = new MutationObserver(callback); - observer.observe(targetNode, config); - }); + }); + } + }); + }, 200); + } + }); + }; + const observer = new MutationObserver(callback); + observer.observe(targetNode, config); } function reloadCustomInstructionSettings() { const existingCustomInstructionSettings = document.querySelector('#custom-instruction-settings'); diff --git a/scripts/content/folderElement.js b/scripts/content/folderElement.js index 54f24e9..5fa3c8f 100644 --- a/scripts/content/folderElement.js +++ b/scripts/content/folderElement.js @@ -413,6 +413,7 @@ function deleteFolder(folder) { for (let i = 0; i < selectedConversationIds.length; i += 1) { const conv = Object.values(conversations).find((c) => c.id?.slice(0, 5) === selectedConversationIds[i]); + if (!conv) continue; promises.push(deleteConversation(conv.id).then((data) => { if (data.success) { successfullyDeletedConvIds.push(conv.id); diff --git a/scripts/content/newChatPage.js b/scripts/content/newChatPage.js index 7749963..a678d3c 100644 --- a/scripts/content/newChatPage.js +++ b/scripts/content/newChatPage.js @@ -27,24 +27,19 @@ function newChatPage(planName) { settings.classList = 'flex flex-col items-start justify-end border border-gray-500 rounded-md p-4'; settings.style = 'width: 600px;'; content.appendChild(settings); - chrome.storage.local.get(['account'], (r) => { - const { account } = r; - const isPaid = account?.account_plan?.is_paid_subscription_active || account?.accounts?.default?.entitlement?.has_active_subscription || false; - if (isPaid) { - settings.style.minHeight = '260px'; - const customInstructionSettings = customInstructionSettingsElement(); - settings.appendChild(customInstructionSettings); + settings.style.minHeight = '260px'; + // custom instruction settings + const customInstructionSettings = customInstructionSettingsElement(); + settings.appendChild(customInstructionSettings); - // divider - const divider = document.createElement('div'); - divider.classList = 'border border-gray-500'; - divider.style = 'width: 70%; height: 1px; background-color: #e5e7eb; margin: 16px auto;'; - settings.appendChild(divider); - } + // divider + const divider = document.createElement('div'); + divider.classList = 'border border-gray-500'; + divider.style = 'width: 70%; height: 1px; background-color: #e5e7eb; margin: 16px auto;'; + settings.appendChild(divider); - const saveHistorySwitch = createSwitch('Chat History & Training', '
    Save new chats to your history and allow them to be used to improve ChatGPT via model training. Unsaved chats will be deleted from our systems within 30 days. Learn more
    ', 'saveHistory', true); - settings.appendChild(saveHistorySwitch); - }); + const saveHistorySwitch = createSwitch('Chat History & Training', '
    Save new chats to your history and allow them to be used to improve ChatGPT via model training. Unsaved chats will be deleted from our systems within 30 days. Learn more
    ', 'saveHistory', true); + settings.appendChild(saveHistorySwitch); const bottom = document.createElement('div'); bottom.classList = 'w-full h-32 md:h-48 flex-shrink-0'; innerDiv.appendChild(bottom); From 1433129d10b91f8027806a34b3540422b85794f8 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Wed, 9 Aug 2023 22:37:56 -0700 Subject: [PATCH 16/38] =?UTF-8?q?test:=20=F0=9F=92=8D=20improve=20custom?= =?UTF-8?q?=20instruction=20profiles=20and=20enable=20for=20free?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/content/customInstructions.js | 47 ++++++++++++++++----------- scripts/content/newChatPage.js | 4 +-- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/manifest.json b/manifest.json index 5236fe0..e478551 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.1.1", + "version": "5.1.2", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/content/customInstructions.js b/scripts/content/customInstructions.js index 0ef9886..0559480 100644 --- a/scripts/content/customInstructions.js +++ b/scripts/content/customInstructions.js @@ -1,5 +1,5 @@ /* eslint-disable no-restricted-globals */ -/* global toast, setUserSystemMessage, customInstructionSettingsElement */ +/* global toast, setUserSystemMessage, customInstructionSettingsElement, getUserSystemMessage */ function checkmarkIcon(placement, profileId) { const checkmark = document.createElement('span'); @@ -50,6 +50,7 @@ function profileDropdown(customInstructionProfiles, placement) { dropdownItem.classList = 'text-gray-900 relative cursor-pointer select-none border-b p-2 last:border-0 border-gray-100 dark:border-white/20 hover:bg-gray-600'; const dropdownOption = document.createElement('span'); dropdownOption.classList = 'font-semibold flex h-6 items-center gap-1 truncate text-gray-800 dark:text-gray-100'; + dropdownOption.style = 'white-space: nowrap; overflow: hidden; text-overflow: ellipsis;display:block;margin-right: 24px; text-align:left'; dropdownOption.innerText = profileName; dropdownOption.title = profileName; dropdownItem.appendChild(dropdownOption); @@ -210,26 +211,34 @@ function upgradeCustomInstructions() { const existingProfileButtonWrapper = customInstructionsDialog.querySelector('#custom-instructions-profile-button-wrapper-settings'); const textAreaFields = customInstructionsDialog.querySelectorAll('textarea'); if (textAreaFields.length > 0 && !existingProfileButtonWrapper && customInstructionsDialog && customInstructionsDialogHeader.textContent === 'Custom instructions') { - const aboutUser = textAreaFields[0]?.value; - const aboutModel = textAreaFields[1]?.value; + // const aboutUser = textAreaFields[0]?.value; + // const aboutModel = textAreaFields[1]?.value; const { customInstructionProfiles } = result; - let newCustomInstructionProfiles = customInstructionProfiles; - let selectedProfile = customInstructionProfiles.find((p) => p.isSelected); - - if (!selectedProfile || (selectedProfile.aboutUser !== aboutUser || selectedProfile.aboutModel !== aboutModel)) { - newCustomInstructionProfiles = customInstructionProfiles.map((p) => { - if (p.aboutModel === aboutModel && p.aboutUser === aboutUser) { - selectedProfile = { ...p, isSelected: true }; - return { ...p, isSelected: true }; - } - if (p.isSelected) { - selectedProfile = undefined; - return { ...p, isSelected: false }; - } - return p; - }); - chrome.storage.local.set({ customInstructionProfiles: newCustomInstructionProfiles }); + const newCustomInstructionProfiles = customInstructionProfiles; + const selectedProfile = customInstructionProfiles.find((p) => p.isSelected); + if (selectedProfile) { + textAreaFields[0].value = selectedProfile.aboutUser; + textAreaFields[0].dispatchEvent(new Event('input', { bubbles: true })); + textAreaFields[0].dispatchEvent(new Event('change', { bubbles: true })); + textAreaFields[1].value = selectedProfile.aboutModel; + textAreaFields[1].dispatchEvent(new Event('input', { bubbles: true })); + textAreaFields[1].dispatchEvent(new Event('change', { bubbles: true })); } + // if (!selectedProfile || selectedProfile.aboutUser.replace(/[^a-zA-Z]/g, '') !== aboutUser.replace(/[^a-zA-Z]/g, '') || selectedProfile.aboutModel.replace(/[^a-zA-Z]/g, '') !== aboutModel.replace(/[^a-zA-Z]/g, '')) { + // newCustomInstructionProfiles = customInstructionProfiles.map((p) => { + // if (p.aboutModel.replace(/[^a-zA-Z]/g, '') === aboutModel.replace(/[^a-zA-Z]/g, '') && p.aboutUser.replace(/[^a-zA-Z]/g, '') === aboutUser.replace(/[^a-zA-Z]/g, '')) { + // selectedProfile = { ...p, isSelected: true }; + // return { ...p, isSelected: true }; + // } + // if (p.isSelected) { + // selectedProfile = undefined; + // return { ...p, isSelected: false }; + // } + // return p; + // }); + // chrome.storage.local.set({ customInstructionProfiles: newCustomInstructionProfiles }); + // } + // header = first child const header = customInstructionsDialog.firstChild; const profileButtonWrapper = document.createElement('div'); diff --git a/scripts/content/newChatPage.js b/scripts/content/newChatPage.js index a678d3c..2cf34e6 100644 --- a/scripts/content/newChatPage.js +++ b/scripts/content/newChatPage.js @@ -64,9 +64,9 @@ function customInstructionSettingsElement() { let newCustomInstructionProfiles = customInstructionProfiles; const selectedProfile = customInstructionProfiles.find((p) => p.isSelected); - if (!selectedProfile || (selectedProfile.aboutUser !== systemMessage.about_user_message || selectedProfile.aboutModel !== systemMessage.about_model_message)) { + if (!selectedProfile || selectedProfile.aboutUser.replace(/[^a-zA-Z]/g, '') !== systemMessage.about_user_message.replace(/[^a-zA-Z]/g, '') || selectedProfile.aboutModel.replace(/[^a-zA-Z]/g, '') !== systemMessage.about_model_message.replace(/[^a-zA-Z]/g, '')) { newCustomInstructionProfiles = customInstructionProfiles.map((p) => { - if (p.aboutModel === systemMessage.about_model_message && p.aboutUser === systemMessage.about_user_message) { + if (p.aboutModel.replace(/[^a-zA-Z]/g, '') === systemMessage.about_model_message.replace(/[^a-zA-Z]/g, '') && p.aboutUser.replace(/[^a-zA-Z]/g, '') === systemMessage.about_user_message.replace(/[^a-zA-Z]/g, '')) { return { ...p, isSelected: true }; } if (p.isSelected) { From 2e98489cc257dd430a049cde1f5a64efcfac33a5 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Thu, 10 Aug 2023 09:30:59 -0700 Subject: [PATCH 17/38] =?UTF-8?q?test:=20=F0=9F=92=8D=20update=20manifest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/content/api.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/content/api.js b/scripts/content/api.js index b004f55..fa07543 100644 --- a/scripts/content/api.js +++ b/scripts/content/api.js @@ -9,6 +9,7 @@ chrome.storage.local.get(['environment'], (result) => { }); let lastPromptSuggestions = []; + // get auth token from sync storage const defaultHeaders = { 'content-type': 'application/json', From aad8c07f40b5af8a12af9629884a268363ec80be Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Tue, 15 Aug 2023 23:10:37 -0700 Subject: [PATCH 18/38] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20the=20chat=20?= =?UTF-8?q?input=20not=20staying=20at=20the=20bottom=20of=20the=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/content/conversation.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index e478551..671b0a6 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.1.2", + "version": "5.1.3", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/content/conversation.js b/scripts/content/conversation.js index c91592c..cf4efe2 100644 --- a/scripts/content/conversation.js +++ b/scripts/content/conversation.js @@ -239,7 +239,7 @@ function loadConversation(conversationId, searchValue = '', focusOnInput = true) outerDiv.appendChild(innerDiv); const contentWrapper = main.querySelector('.flex-1.overflow-hidden'); contentWrapper.remove(); - main.prepend(outerDiv); + main.firstChild.prepend(outerDiv); if (!searchValue) { if (focusOnInput) { const inputForm = main.querySelector('form'); From e6f6b3b33010cccea62e01d10711c586ccc0283f Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Tue, 15 Aug 2023 23:24:02 -0700 Subject: [PATCH 19/38] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20the=20issue?= =?UTF-8?q?=20with=20new=20chats=20not=20being=20submitted?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/content/conversationList.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.json b/manifest.json index 671b0a6..d9b1675 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.1.3", + "version": "5.1.4", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/content/conversationList.js b/scripts/content/conversationList.js index b98b3cb..44923a5 100644 --- a/scripts/content/conversationList.js +++ b/scripts/content/conversationList.js @@ -955,7 +955,7 @@ ${settings.autoSplitChunkPrompt}`; const parentId = lastMessage?.id?.split('message-wrapper-')[1] || self.crypto.randomUUID(); // remove main first child const contentWrapper = main.querySelector('.flex-1.overflow-hidden'); - main.removeChild(contentWrapper); + main.firstChild.removeChild(contentWrapper); const outerDiv = document.createElement('div'); outerDiv.classList = 'flex-1 overflow-hidden'; @@ -987,7 +987,7 @@ ${settings.autoSplitChunkPrompt}`; innerDiv.appendChild(conversationDiv); outerDiv.appendChild(innerDiv); - main.prepend(outerDiv); + main.firstChild.prepend(outerDiv); if (text) { isGenerating = true; submitChat(text, {}, messageId, parentId, settings, models); From f737efe1d7ca4a8ccd45727aa46281577c537bff Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Sun, 20 Aug 2023 19:50:03 -0700 Subject: [PATCH 20/38] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20auto-sync=20shorke?= =?UTF-8?q?y?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/content/conversation.js | 2 +- scripts/content/conversationList.js | 4 ++-- scripts/content/keyboardShortcuts.js | 16 ++++++++++++++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index d9b1675..9c7d360 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.1.4", + "version": "5.1.5", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/content/conversation.js b/scripts/content/conversation.js index cf4efe2..b76ea87 100644 --- a/scripts/content/conversation.js +++ b/scripts/content/conversation.js @@ -196,7 +196,7 @@ function loadConversation(conversationId, searchValue = '', focusOnInput = true) const systemMessage = sortedNodes.find((node) => node?.message?.author?.role === 'system'); const customInstrucionProfile = systemMessage?.message?.metadata?.user_context_message_data || undefined; - let messageDiv = `
    ${folderName ? `${folderName}    ›    ` : ''}${fullConversation.title}${customInstrucionProfile ? `   •   Custom instructions: On  ` : ''}
    `; + let messageDiv = `
    ${folderName ? `${folderName}    ›    ` : ''}${fullConversation.title}${customInstrucionProfile ? `  ` : ''}
    `; if (fullConversation.archived) { messageDiv = '
    This is an archived chat. You can read archived chats, but you cannot continue them.
    '; } diff --git a/scripts/content/conversationList.js b/scripts/content/conversationList.js index 44923a5..6cdb441 100644 --- a/scripts/content/conversationList.js +++ b/scripts/content/conversationList.js @@ -334,7 +334,7 @@ function generateTitleForConversation(conversationId, messageId, profile) { }); // at the end, add sss setTimeout(() => { - if (topDiv) topDiv.innerHTML += `   •   Custom instructions: On  `; + if (topDiv) topDiv.innerHTML += `  `; }, title.length * 50); }); }, 500);// a little delay to make sure gen title still works even if user stops the generation @@ -1027,7 +1027,7 @@ ${settings.autoSplitChunkPrompt}`; const textAreaElement = inputForm.querySelector('textarea'); if (isGenerating) return; const templateWords = textAreaElement.value.match(/{{(.*?)}}/g); - if (templateWords?.length > 0) { + if (settings.promptTemplate && templateWords?.length > 0) { // open template words modal and wait for user to select a word. the when user submit, submit the input form with the replacement createTemplateWordsModal(templateWords); const firstTemplateWordInput = document.querySelector('[id^=template-input-]'); diff --git a/scripts/content/keyboardShortcuts.js b/scripts/content/keyboardShortcuts.js index 03d0890..f41477a 100644 --- a/scripts/content/keyboardShortcuts.js +++ b/scripts/content/keyboardShortcuts.js @@ -57,6 +57,10 @@ function keyboardShortcutsModalContent() { CTRL/CMD + ALT + H Hide/show the sidebar + + CTRL/CMD + ALT + A + Enable/disable auto-sync + CTRL/CMD + SHIFT + Click on the new folder icon Reset the order of chats from newest to oldest (removes all folders) @@ -167,6 +171,18 @@ function registerShortkeys() { e.preventDefault(); showNewChatPage(); } + // cmd/ctrl + alt + A + if ((e.metaKey || (isWindows() && e.ctrlKey)) && e.altKey && e.keyCode === 65) { + e.preventDefault(); + if (autoSync) { + settings.autoSync = false; + } else { + settings.autoSync = true; + } + chrome.storage.local.set({ settings }, () => { + window.location.reload(); + }); + } // cmd/ctrl + alt + h if ((e.metaKey || (isWindows() && e.ctrlKey)) && e.altKey && e.keyCode === 72) { e.preventDefault(); From 7c757e76a71b9442675b10e771445a391a7397a0 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Tue, 22 Aug 2023 18:27:22 -0700 Subject: [PATCH 21/38] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20custom=20inst?= =?UTF-8?q?ruction=20not=20getting=20saved=20correctly=20in=20Fi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/content/customInstructions.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index 9c7d360..942f4ea 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.1.5", + "version": "5.1.6", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/content/customInstructions.js b/scripts/content/customInstructions.js index 0559480..5a3d4f8 100644 --- a/scripts/content/customInstructions.js +++ b/scripts/content/customInstructions.js @@ -296,7 +296,7 @@ function upgradeCustomInstructions() { }); // find a button inside body that has text "Save" const allButtons = body.querySelectorAll('button'); - const toggleButton = [...allButtons].find((b) => b.role === 'switch'); + const toggleButton = [...allButtons].find((b) => b.getAttribute('role') === 'switch'); const saveButton = [...allButtons].find((b) => b.textContent === 'Save'); // add a chane listener to the toggle button toggleButton.addEventListener('click', () => { From 36b67e6ed504ecc16477882bd1a23135a5dff0cb Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Mon, 28 Aug 2023 01:47:46 -0700 Subject: [PATCH 22/38] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20auto-scroll=20sett?= =?UTF-8?q?ings=20+=20firefox=20splitter=20fix=20+=20bug=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 5 +- scripts/background/initialize.js | 2 + scripts/content/conversationList.js | 8 +- scripts/content/copyAndCounter.js | 22 +++--- scripts/content/customInstructions.js | 108 +++++++++++++------------- scripts/content/settings.js | 7 +- 6 files changed, 84 insertions(+), 68 deletions(-) diff --git a/manifest.json b/manifest.json index 942f4ea..8726eb4 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.1.6", + "version": "5.1.7", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { @@ -101,7 +101,8 @@ ], "matches": [ "https://chat.openai.com/*" - ] + ], + "run_at": "document_end" } ], "background": { diff --git a/scripts/background/initialize.js b/scripts/background/initialize.js index 9c9acdd..78a609d 100644 --- a/scripts/background/initialize.js +++ b/scripts/background/initialize.js @@ -28,6 +28,8 @@ chrome.runtime.onInstalled.addListener((detail) => { chrome.tabs.create({ url: 'https://ezi.notion.site/Superpower-ChatGPT-FAQ-9d43a8a1c31745c893a4080029d2eb24' }); chrome.tabs.create({ url: 'https://superpowerdaily.com' }); chrome.tabs.create({ url: 'https://chat.openai.com', active: true }); + } else { + chrome.tabs.create({ url: 'https://superpowerdaily.com' }); } }); chrome.action.onClicked.addListener((tab) => { diff --git a/scripts/content/conversationList.js b/scripts/content/conversationList.js index 6cdb441..f2e6cc7 100644 --- a/scripts/content/conversationList.js +++ b/scripts/content/conversationList.js @@ -644,7 +644,7 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode const existingRowAssistant = continueGenerating ? lastRowAssistant : document.querySelector(`[id="message-wrapper-${message.id}"][data-role="assistant"]`); if (existingRowAssistant) { - if (!scrolUpDetected) { + if (!scrolUpDetected && settings.autoScroll) { document.querySelector('#conversation-bottom').scrollIntoView(); } @@ -694,7 +694,7 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode const assistantRow = rowAssistant(conversation, data, threadCount, threadCount, models, settings.customConversationWidth, settings.conversationWidth); const conversationBottom = document.querySelector('#conversation-bottom'); conversationBottom.insertAdjacentHTML('beforebegin', assistantRow); - if (!scrolUpDetected) { + if (!scrolUpDetected && settings.autoScroll) { conversationBottom.scrollIntoView(); } } @@ -709,6 +709,10 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode } }); chatStream.addEventListener('error', (err) => { + // Firefox returns error when closing chat stream + const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; + // if firefox and no error data, do nothing + if (isFirefox && !err.data) return; isGenerating = false; chunkNumber = 1; totalChunks = 1; diff --git a/scripts/content/copyAndCounter.js b/scripts/content/copyAndCounter.js index 999d7f1..8500cc5 100644 --- a/scripts/content/copyAndCounter.js +++ b/scripts/content/copyAndCounter.js @@ -27,48 +27,48 @@ function addCopyButtonToResult(resultElement, index) { const copyHtmlButton = document.createElement('button'); copyHtmlButton.textContent = 'HTML'; copyHtmlButton.id = `result-html-copy-button-${index}`; - copyHtmlButton.style = 'border-radius:4px;border:1px solid lightslategray;padding:4px;width:64px;'; + copyHtmlButton.style = 'border-radius:4px;border:1px solid lightslategray;padding:4px;width:64px;background-color:#444554;'; copyHtmlButton.addEventListener('mouseover', () => { copyHtmlButton.style = 'border-radius:4px;border:1px solid lightslategray;padding:4px;width:64px;background-color:#3b3b43;color:white;'; }); copyHtmlButton.addEventListener('mouseout', () => { - copyHtmlButton.style = 'border-radius:4px;border:1px solid lightslategray;padding:4px;width:64px;'; + copyHtmlButton.style = 'border-radius:4px;border:1px solid lightslategray;padding:4px;width:64px;background-color:#3b3b43;'; }); const copyMarkdownButton = document.createElement('button'); copyMarkdownButton.textContent = 'Markdown'; copyMarkdownButton.id = `result-markdown-copy-button-${index}`; - copyMarkdownButton.style = 'border-radius:4px;border:1px solid lightslategray;padding:4px;width:64px;'; + copyMarkdownButton.style = 'border-radius:4px;border:1px solid lightslategray;padding:4px;width:64px;background-color:#444554;'; copyMarkdownButton.addEventListener('mouseover', () => { copyMarkdownButton.style = 'border-radius:4px;border:1px solid lightslategray;padding:4px;width:64px;background-color:#3b3b43;color:white;'; }); copyMarkdownButton.addEventListener('mouseout', () => { - copyMarkdownButton.style = 'border-radius:4px;border:1px solid lightslategray;padding:4px;width:64px;'; + copyMarkdownButton.style = 'border-radius:4px;border:1px solid lightslategray;padding:4px;width:64px;background-color:#3b3b43;'; }); const copyMenu = document.createElement('div'); - copyMenu.style = 'font-size:10px;position:absolute;right:0;bottom:49px;display:none;width:64px;'; + copyMenu.style = 'font-size:10px;position:absolute;right:0;bottom:49px;display:none;width:64px;background-color:#3b3b43;'; copyMenu.appendChild(copyMarkdownButton); copyMenu.appendChild(copyHtmlButton); const copyButton = document.createElement('button'); copyButton.textContent = 'Copy'; copyButton.id = `result-copy-button-${index}`; - copyButton.style = 'border-radius:4px;border:1px solid lightslategray;padding:4px;position:absolute;right:0;width:64px;'; + copyButton.style = 'border-radius:4px;border:1px solid lightslategray;padding:4px;position:absolute;right:0;width:64px;background-color:#444554;'; // add hover style to button copyButton.addEventListener('mouseover', () => { copyButton.style = 'border-radius:4px;border:1px solid lightslategray;padding:4px;position:absolute;right:0;width:64px;background-color:#3b3b43;color:white;'; - copyMenu.style = 'font-size:10px;position:absolute;right:0;bottom:49px;width:64px;'; + copyMenu.style = 'font-size:10px;position:absolute;right:0;bottom:49px;width:64px;background-color:#444554;'; }); copyButton.addEventListener('mouseout', () => { - copyButton.style = 'border-radius:4px;border:1px solid lightslategray;padding:4px;position:absolute;right:0;width:64px;'; - copyMenu.style = 'font-size:10px;position:absolute;right:0;bottom:49px;display:none;width:64px;'; + copyButton.style = 'border-radius:4px;border:1px solid lightslategray;padding:4px;position:absolute;right:0;width:64px;background-color:#444554;'; + copyMenu.style = 'font-size:10px;position:absolute;right:0;bottom:49px;display:none;width:64px;background-color:#444554;'; }); copyMenu.addEventListener('mouseover', () => { - copyMenu.style = 'font-size:10px;position:absolute;right:0;bottom:49px;width:64px;'; + copyMenu.style = 'font-size:10px;position:absolute;right:0;bottom:49px;width:64px;background-color:#444554;'; }); copyMenu.addEventListener('mouseout', () => { - copyMenu.style = 'font-size:10px;position:absolute;right:0;bottom:49px;display:none;width:64px;'; + copyMenu.style = 'font-size:10px;position:absolute;right:0;bottom:49px;display:none;width:64px;background-color:#444554;'; }); copyButton.addEventListener('click', () => { chrome.storage.local.get(['settings'], (result) => { diff --git a/scripts/content/customInstructions.js b/scripts/content/customInstructions.js index 5a3d4f8..d6d6ce2 100644 --- a/scripts/content/customInstructions.js +++ b/scripts/content/customInstructions.js @@ -299,65 +299,69 @@ function upgradeCustomInstructions() { const toggleButton = [...allButtons].find((b) => b.getAttribute('role') === 'switch'); const saveButton = [...allButtons].find((b) => b.textContent === 'Save'); // add a chane listener to the toggle button - toggleButton.addEventListener('click', () => { - const currState = toggleButton.getAttribute('aria-checked'); + if (toggleButton) { + toggleButton.addEventListener('click', () => { + const currState = toggleButton.getAttribute('aria-checked'); - // when toggle off nameinput shoud be disabled - const curNameInput = document.querySelector('#custom-instructions-name-input'); - if (currState === 'true') { - curNameInput.disabled = true; - curNameInput.classList.add('text-gray-300'); - } else { - curNameInput.disabled = false; - curNameInput.classList.remove('text-gray-300'); - } - setTimeout(() => { - if (curNameInput.value === '') { - saveButton.disabled = true; - saveButton.classList.add('opacity-50', 'cursor-not-allowed'); - } else if (curNameInput.value !== selectedProfile?.name) { - saveButton.disabled = false; - saveButton.classList.remove('opacity-50', 'cursor-not-allowed'); - } - }, 10); - }); - // add a click listener to the save button - saveButton.addEventListener('click', () => { - chrome.storage.local.get(['customInstructionProfiles'], (res) => { - const { customInstructionProfiles: cip } = res; + // when toggle off nameinput shoud be disabled const curNameInput = document.querySelector('#custom-instructions-name-input'); - const curTextAreaFields = document.querySelectorAll('[role="dialog"][data-state="open"][tabindex="-1"] textarea'); - const curAboutUserInput = curTextAreaFields[0]; - const curAboutModelInput = curTextAreaFields[1]; - const curSelectedProfile = cip.find((p) => p.isSelected); - - if (!curSelectedProfile) { - const newCip = [...cip, { - name: curNameInput.value, aboutUser: curAboutUserInput.value, aboutModel: curAboutModelInput.value, isSelected: true, id: self.crypto.randomUUID(), - }]; - chrome.storage.local.set({ customInstructionProfiles: newCip }, () => { - toast('Profile saved'); - reloadCustomInstructionSettings(); - }); + if (currState === 'true') { + curNameInput.disabled = true; + curNameInput.classList.add('text-gray-300'); } else { - const newCip = cip.map((p) => { - if (p.isSelected) { - return { - ...p, name: curNameInput.value, aboutUser: curAboutUserInput.value, aboutModel: curAboutModelInput.value, - }; - } - return p; - }); - chrome.storage.local.set({ customInstructionProfiles: newCip }, () => { - toast('Profile updated'); - reloadCustomInstructionSettings(); - }); + curNameInput.disabled = false; + curNameInput.classList.remove('text-gray-300'); } + setTimeout(() => { + if (curNameInput.value === '') { + saveButton.disabled = true; + saveButton.classList.add('opacity-50', 'cursor-not-allowed'); + } else if (curNameInput.value !== selectedProfile?.name) { + saveButton.disabled = false; + saveButton.classList.remove('opacity-50', 'cursor-not-allowed'); + } + }, 10); }); - }); + } + if (saveButton) { + // add a click listener to the save button + saveButton.addEventListener('click', () => { + chrome.storage.local.get(['customInstructionProfiles'], (res) => { + const { customInstructionProfiles: cip } = res; + const curNameInput = document.querySelector('#custom-instructions-name-input'); + const curTextAreaFields = document.querySelectorAll('[role="dialog"][data-state="open"][tabindex="-1"] textarea'); + const curAboutUserInput = curTextAreaFields[0]; + const curAboutModelInput = curTextAreaFields[1]; + const curSelectedProfile = cip.find((p) => p.isSelected); + + if (!curSelectedProfile) { + const newCip = [...cip, { + name: curNameInput.value, aboutUser: curAboutUserInput.value, aboutModel: curAboutModelInput.value, isSelected: true, id: self.crypto.randomUUID(), + }]; + chrome.storage.local.set({ customInstructionProfiles: newCip }, () => { + toast('Profile saved'); + reloadCustomInstructionSettings(); + }); + } else { + const newCip = cip.map((p) => { + if (p.isSelected) { + return { + ...p, name: curNameInput.value, aboutUser: curAboutUserInput.value, aboutModel: curAboutModelInput.value, + }; + } + return p; + }); + chrome.storage.local.set({ customInstructionProfiles: newCip }, () => { + toast('Profile updated'); + reloadCustomInstructionSettings(); + }); + } + }); + }); + } } }); - }, 200); + }, 500); } }); }; diff --git a/scripts/content/settings.js b/scripts/content/settings.js index fb9dd29..54b1af4 100644 --- a/scripts/content/settings.js +++ b/scripts/content/settings.js @@ -123,6 +123,10 @@ function generalTabContent() { const copyModeSwitch = createSwitch('Copy mode', 'OFF: only copy response / ON: copy both request and response', 'copyMode', false); leftContent.appendChild(copyModeSwitch); + // auto scroll + const autoScrollSwitch = createSwitch('Auto Scroll', 'Automatically scroll down while responding', 'autoScroll', true); + leftContent.appendChild(autoScrollSwitch); + // prompt template const promptTemplateSwitch = createSwitch('Prompt Template', 'Enable/disable the doube {{curly}} brackets replacement (Learn More)', 'promptTemplate', true); leftContent.appendChild(promptTemplateSwitch); @@ -372,7 +376,7 @@ function generalTabContent() { linkWrapper.appendChild(updatesLink); // add link for feedback email const feedbackLink = document.createElement('a'); - feedbackLink.href = 'mailto:m4rkobay@gmail.com?subject=Superpower ChatGPT Feature Request&body=Hi Marko,%0DReporting a bug? Any of the following information would help me figure it out faster: %0D- What version of the extension do you have? (You can find that at the bottom of the "settings" menu) %0D- What browser are you using? %0D- Do you see any errors in the console log? %0D- Do you have a plus account? %0D- How many conversations do you have approximately? %0D- Do you have the Auto Sync feature ON? %0D- Are all of your conversations synced? %0D- Do you see the "settings" menu on the sidebar? %0D- Does your issue go away if you turn the Auto Sync OFF in the settings menu? %0D- Does this issue happen to all prompts? Or only the first prompt? %0D- Are you using any other ChatGPT extensions at the same time? %0D- Can you email me a screenshot or video of the ChatGPT page when the bug happens? (with the extension installed)%0DThanks!'; + feedbackLink.href = 'mailto:saeed@superpowerdaily.com?subject=Superpower ChatGPT Feature Request&body=Hi Marko,%0DReporting a bug? Any of the following information would help me figure it out faster: %0D- What version of the extension do you have? (You can find that at the bottom of the "settings" menu) %0D- What browser are you using? %0D- Do you see any errors in the console log? %0D- Do you have a plus account? %0D- How many conversations do you have approximately? %0D- Do you have the Auto Sync feature ON? %0D- Are all of your conversations synced? %0D- Do you see the "settings" menu on the sidebar? %0D- Does your issue go away if you turn the Auto Sync OFF in the settings menu? %0D- Does this issue happen to all prompts? Or only the first prompt? %0D- Are you using any other ChatGPT extensions at the same time? %0D- Can you email me a screenshot or video of the ChatGPT page when the bug happens? (with the extension installed)%0DThanks!'; feedbackLink.target = '_blank'; feedbackLink.textContent = 'Feature Request ➜'; feedbackLink.style = 'color: #999; font-size: 12px; margin: 8px 0;min-width: 25%;text-align:center;padding-right: 8px;'; @@ -1345,6 +1349,7 @@ function initializeSettings() { chrome.storage.local.set({ settings: { ...result.settings, + autoScroll: result.settings?.autoScroll !== undefined ? result.settings.autoScroll : true, autoSync: result.settings?.autoSync !== undefined ? result.settings.autoSync : true, quickSync: result.settings?.quickSync !== undefined ? result.settings.quickSync : false, quickSyncCount: result.settings?.quickSyncCount !== undefined ? result.settings.quickSyncCount : 100, From 635f7098e4cffe8e723799da148edd2e94ae45a2 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Sat, 16 Sep 2023 16:53:50 -0700 Subject: [PATCH 23/38] =?UTF-8?q?feat:=20=F0=9F=8E=B8=205.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- icons/new-prompt-chain.png | Bin 0 -> 4452 bytes manifest.json | 2 +- scripts/.DS_Store | Bin 6148 -> 6148 bytes scripts/content/addToPromptLibrary.js | 6 +-- scripts/content/api.js | 1 - scripts/content/autoSave.js | 44 +++++++++++++--- scripts/content/clearConversations.js | 14 ++++-- scripts/content/continue.js | 3 +- scripts/content/conversation.js | 30 ++++++++--- scripts/content/conversationList.js | 21 +++++--- scripts/content/copyAndCounter.js | 36 ++++++++------ scripts/content/customInstructions.js | 14 ++++-- scripts/content/export.js | 29 ++++++++--- scripts/content/folderElement.js | 48 +++++++++++------- scripts/content/global.js | 52 +++++++++++-------- scripts/content/keyboardShortcuts.js | 18 +++++-- scripts/content/pluginStore.js | 4 +- scripts/content/pluginsDropdown.js | 4 +- scripts/content/promptHistory.js | 23 +++++---- scripts/content/promptLibrary.js | 14 +++++- scripts/content/rowAssistant.js | 10 ++-- scripts/content/settings.js | 69 +++++++++++++++++++++++--- scripts/content/shareModal.js | 14 ++++-- 23 files changed, 323 insertions(+), 133 deletions(-) create mode 100644 icons/new-prompt-chain.png diff --git a/icons/new-prompt-chain.png b/icons/new-prompt-chain.png new file mode 100644 index 0000000000000000000000000000000000000000..d3e7d5ac97607136c0ac20d16223f7590b754628 GIT binary patch literal 4452 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 { }); let lastPromptSuggestions = []; - // get auth token from sync storage const defaultHeaders = { 'content-type': 'application/json', diff --git a/scripts/content/autoSave.js b/scripts/content/autoSave.js index a21b775..6779ba2 100644 --- a/scripts/content/autoSave.js +++ b/scripts/content/autoSave.js @@ -1,7 +1,7 @@ /* eslint-disable no-unused-vars */ /* eslint-disable no-restricted-globals */ // eslint-disable-next-line no-unused-vars -/* global updateNewChatButtonNotSynced, getAllConversations, getConversation, loadConversationList, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, addConversationsEventListeners, isGenerating, prependConversation, generateTitleForConversation, canSubmitPrompt, formatDate, userChatIsActuallySaved:true, addAsyncInputEvents, addSyncBanner, isWindows */ +/* global updateNewChatButtonNotSynced, getAllConversations, getConversation, loadConversationList, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, addConversationsEventListeners, isGenerating, prependConversation, generateTitleForConversation, canSubmitPrompt, formatDate, userChatIsActuallySaved:true, addAsyncInputEvents, addSyncBanner, isWindows, toast */ /* eslint-disable no-await-in-loop, */ let localConversations = {}; let autoSaveTimeoutId; @@ -138,9 +138,9 @@ function updateOrCreateConversation(conversationId, message, parentId, settings, userChatIsActuallySaved = true; addConversationsEventListeners(existingConversation.id); const mapping = Object.values(existingConversation.mapping); - if (generateTitle && existingConversation.title === 'New chat' && mapping.length < 5 && mapping.filter((m) => m.message?.author.role === 'assistant').length === 1) { // only one assistant message + if (generateTitle && existingConversation.title === 'New chat' && mapping.length < 5 && mapping.filter((m) => m.message?.role === 'assistant' || m.message?.author.role === 'assistant').length === 1) { // only one assistant message if (settings.saveHistory) { - const systemMessage = mapping.find((m) => m.message?.author.role === 'system'); + const systemMessage = mapping.find((m) => m.message?.role === 'system' || m.message?.author.role === 'system'); generateTitleForConversation(existingConversation.id, message.id, systemMessage?.message?.metadata?.user_context_message_data); } } else if (settings.conversationTimestamp) { // === updated @@ -149,10 +149,23 @@ function updateOrCreateConversation(conversationId, message, parentId, settings, const conversationCreateTime = formatDate(new Date()); const conversationTimestampElement = conversationElement.querySelector('#timestamp'); conversationTimestampElement.innerHTML = conversationCreateTime; - // const searchBoxWrapper = document.querySelector('#conversation-search-wrapper'); - // if (conversationElement && searchBoxWrapper) { - // searchBoxWrapper.after(conversationElement); - // } + const searchBoxWrapper = document.querySelector('#conversation-search-wrapper'); + if (conversationElement && searchBoxWrapper) { + // update conversationorder + chrome.storage.sync.get(['conversationsOrder'], (res) => { + const { conversationsOrder } = res; + const conversationIndex = conversationsOrder.findIndex((conv) => conv === conversationId?.slice(0, 5)); + if (conversationIndex !== -1) { + // this guarantees we only move if conversation is not in a folder + searchBoxWrapper.after(conversationElement); + conversationsOrder.splice(conversationIndex, 1); + conversationsOrder.unshift(conversationId); + chrome.storage.sync.set({ + conversationsOrder, + }); + } + }); + } } }, ); @@ -284,6 +297,9 @@ function initializeAutoSave(skipInputFormReload = false, forceRefreshIds = []) { const forceRefresh = true; getAllConversations(forceRefresh).then((remoteConversations) => { + if (remoteConversations.length > 500) { + toast('Looks like you have over 500 conversation in your history. For best performance, please consider deleting some conversations to keep your history under 200 conversations!', 'warning', 10000); + } chrome.storage.sync.get(['conversationsOrder'], (res) => { const { conversationsOrder } = res; chrome.storage.local.get(['conversations', 'account', 'settings'], (result) => { @@ -510,7 +526,19 @@ function initializeAutoSave(skipInputFormReload = false, forceRefreshIds = []) { } const existingSyncBanner = document.querySelector('#sync-nav-wrapper'); if (existingSyncBanner) { - window.location.reload(); + if (settings.autoRefreshAfterSync) { + window.location.reload(); + } else { + // replace the content with "sync is complete" and a refresh button + const pageRefreshButton = document.createElement('button'); + pageRefreshButton.style = 'color: gold; cursor: pointer; border: solid 1px gold; padding: 8px; border-radius: 4px; margin-left: 16px;'; + pageRefreshButton.innerText = 'Refresh page'; + pageRefreshButton.addEventListener('click', () => { + window.location.reload(); + }); + existingSyncBanner.firstChild.innerHTML = 'Sync is complete! Refresh the page to see your changes.'; + existingSyncBanner.firstChild.appendChild(pageRefreshButton); + } } else { loadConversationList(skipInputFormReload); } diff --git a/scripts/content/clearConversations.js b/scripts/content/clearConversations.js index ddbf451..7f042da 100644 --- a/scripts/content/clearConversations.js +++ b/scripts/content/clearConversations.js @@ -1,4 +1,5 @@ /* global deleteConversation, deleteAllConversations, resetSelection, showNewChatPage, notSelectedClassList, emptyFolderElement */ +let deleteButtonTimeout; function replaceDeleteConversationButton() { const nav = document.querySelector('nav'); if (!nav) return; @@ -28,7 +29,7 @@ function replaceDeleteConversationButton() { const trashFolder = conversationsOrder?.find((folder) => folder.id === 'trash'); const visibleConversations = conversationsAreSynced && conversations && settings.autoSync ? Object.values(conversations).filter((conversation) => !conversation.archived && !conversation.skipped) : nav.querySelector('div.flex-col.flex-1').querySelector('div').querySelectorAll('a'); - if (e.target.textContent === 'Confirm Delete') { + if (e.target.textContent === 'Yes! for god\'s sake!') { e.target.innerHTML = 'Delete All'; e.target.style.removeProperty('background-color'); e.target.style.color = 'white'; @@ -197,10 +198,17 @@ function replaceDeleteConversationButton() { } } } else if (visibleConversations.length > 0) { - e.target.innerHTML = 'Confirm Delete'; + clearTimeout(deleteButtonTimeout); + const titleMap = { + 'Delete All': 'Confirm Delete All', + 'Confirm Delete All': 'Yes! Yes! Delete All!', + 'Yes! Yes! Delete All!': 'Yes! for god\'s sake!', + }; + + e.target.innerHTML = `${titleMap[e.target.textContent] || 'Delete All'}`; e.target.style.backgroundColor = '#864e6140'; e.target.style.color = '#ff4a4a'; - setTimeout(() => { + deleteButtonTimeout = setTimeout(() => { chrome.storage.local.get(['selectedConversations'], (res) => { const selectedConvs = res.selectedConversations; e.target.innerHTML = `Delete ${selectedConvs.length === 0 ? 'All' : `${selectedConvs.length} Selected`}`; diff --git a/scripts/content/continue.js b/scripts/content/continue.js index 8a7c9c8..8cc5512 100644 --- a/scripts/content/continue.js +++ b/scripts/content/continue.js @@ -90,7 +90,7 @@ function addContinueButton() { if (syncDiv) syncDiv.style.opacity = '1'; const continueButtonWrapper = document.createElement('div'); - continueButtonWrapper.style = 'position:absolute;left:0;display:flex;z-index:200'; + continueButtonWrapper.style = 'position:absolute;left:0;z-index:200;display:none;'; continueButtonWrapper.id = 'continue-conversation-button-wrapper'; const continueButtonDropdown = document.createElement('button'); @@ -174,6 +174,7 @@ function addContinueButton() { chrome.storage.local.get('settings', ({ settings }) => { setTimeout(() => { + continueButtonWrapper.style.display = settings.showCustomPromptsButton ? 'flex' : 'none'; continueButtonWrapper.appendChild(shiftClickText); continueButtonWrapper.appendChild(continueButtonDropdown); continueButtonWrapper.appendChild(promptDropdown()); diff --git a/scripts/content/conversation.js b/scripts/content/conversation.js index b76ea87..a970ea9 100644 --- a/scripts/content/conversation.js +++ b/scripts/content/conversation.js @@ -113,7 +113,7 @@ function loadConversationFromNode(conversationId, newMessageId, oldMessageId, se nextMessage = sortedNodes[i + 1]?.message; } sortedNodes[i].message = message; - messageDiv += rowAssistant(fullConversation, sortedNodes[i], threadIndex, threadCount, res.models, settings.customConversationWidth, settings.conversationWidth, searchValue); + messageDiv += rowAssistant(fullConversation, sortedNodes[i], threadIndex, threadCount, res.models, settings.customConversationWidth, settings.conversationWidth, settings.showMessageTimestamp, settings.showWordCount, searchValue); } } const conversationBottom = document.querySelector('#conversation-bottom'); @@ -193,7 +193,7 @@ function loadConversation(conversationId, searchValue = '', focusOnInput = true) } sortedNodes.reverse(); //-------- - const systemMessage = sortedNodes.find((node) => node?.message?.author?.role === 'system'); + const systemMessage = sortedNodes.find((node) => node?.message?.role === 'system' || node?.message?.author?.role === 'system'); const customInstrucionProfile = systemMessage?.message?.metadata?.user_context_message_data || undefined; let messageDiv = `
    ${folderName ? `${folderName}    ›    ` : ''}${fullConversation.title}${customInstrucionProfile ? `  ` : ''}
    `; @@ -217,7 +217,7 @@ function loadConversation(conversationId, searchValue = '', focusOnInput = true) nextMessage = sortedNodes[i + 1]?.message; } sortedNodes[i].message = message; - messageDiv += rowAssistant(fullConversation, sortedNodes[i], threadIndex, threadCount, res.models, settings.customConversationWidth, settings.conversationWidth, searchValue); + messageDiv += rowAssistant(fullConversation, sortedNodes[i], threadIndex, threadCount, res.models, settings.customConversationWidth, settings.conversationWidth, settings.showMessageTimestamp, settings.showWordCount, searchValue); } } conversationDiv.innerHTML = messageDiv; @@ -374,7 +374,7 @@ function addConversationsEventListeners(conversationId) { messageWrapper.id = `message-wrapper-${newMessageId}`; const parent = messageWrapper.previousElementSibling; // default parentId to root message - let parentId = Object.values(conversation.mapping).find((m) => m?.message?.author?.role === 'system')?.id; + let parentId = Object.values(conversation.mapping).find((m) => m?.message?.role === 'system' || m?.message?.author?.role === 'system')?.id; if (parent && parent.id.startsWith('message-wrapper-')) { parentId = parent.id.split('message-wrapper-').pop(); @@ -501,7 +501,13 @@ function addConversationsEventListeners(conversationId) { button.addEventListener('click', () => { chrome.storage.local.get(['conversations', 'settings'], (result) => { const conversation = result.conversations[conversationId]; - const parentId = conversation.mapping[messageId].parent; + // while parent is not user, keep going up + let parentId = conversation.mapping[messageId].parent; + let parentRole = conversation.mapping[parentId].message.author.role || conversation.mapping[parentId].message.role; + while (parentRole !== 'user') { + parentId = conversation.mapping[parentId].parent; + parentRole = conversation.mapping[parentId].message.author.role || conversation.mapping[parentId].message.role; + } const parentMessage = conversation.mapping[parentId].message.content.parts.join('\n'); const codeHeaders = document.querySelectorAll('#code-header'); // hide all code headers @@ -526,7 +532,12 @@ function addConversationsEventListeners(conversationId) { htmlButton.addEventListener('click', () => { chrome.storage.local.get(['conversations', 'settings'], (result) => { const conversation = result.conversations[conversationId]; - const parentId = conversation.mapping[messageId].parent; + let parentId = conversation.mapping[messageId].parent; + let parentRole = conversation.mapping[parentId].message.author.role || conversation.mapping[parentId].message.role; + while (parentRole !== 'user') { + parentId = conversation.mapping[parentId].parent; + parentRole = conversation.mapping[parentId].message.author.role || conversation.mapping[parentId].message.role; + } const parentMessage = conversation.mapping[parentId].message.content.parts.join('\n'); const newElement = element.cloneNode(true); if (result.settings.copyMode) { @@ -547,7 +558,12 @@ function addConversationsEventListeners(conversationId) { chrome.storage.local.get(['settings'], (result) => { getConversation(conversationId).then((conversation) => { const { message } = conversation.mapping[messageId]; - const parentId = conversation.mapping[messageId].parent; + let parentId = conversation.mapping[messageId].parent; + let parentRole = conversation.mapping[parentId].message.author.role || conversation.mapping[parentId].message.role; + while (parentRole !== 'user') { + parentId = conversation.mapping[parentId].parent; + parentRole = conversation.mapping[parentId].message.author.role || conversation.mapping[parentId].message.role; + } const parentMessage = conversation.mapping[parentId].message.content.parts.join('\n'); const text = `${result.settings.copyMode ? `##USER:\n${parentMessage}\n\n##ASSISTANT:\n` : ''}${message.content.parts.join('\n')}`; navigator.clipboard.writeText(text); diff --git a/scripts/content/conversationList.js b/scripts/content/conversationList.js index f2e6cc7..3baef38 100644 --- a/scripts/content/conversationList.js +++ b/scripts/content/conversationList.js @@ -608,12 +608,16 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode } else if (!userChatSavedLocally) { const userMessage = { id: messageId, - role: 'user', + author: { + role: 'user', + metadata: {}, + }, content: { content_type: 'text', parts: [userInput], }, metadata: { model_slug: settings.selectedModel.slug }, + recipient: recipient || 'all', }; // set forcerefresh=true when adding user chat, and set it to false when stream ends. This way if something goes wrong in between, the conversation will be refreshed later @@ -623,7 +627,7 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode if (!conversation?.id || userChatSavedLocally) { // save assistant chat locally finalMessage = message; - if (!assistantChatSavedLocally && message.author.role === 'assistant' && message.recipient === 'all') { + if (!assistantChatSavedLocally && (message.role === 'assistant' || message.author.role === 'assistant') && message.recipient === 'all') { assistantChatSavedLocally = true; const tempId = setInterval(() => { if (userChatIsActuallySaved) { @@ -682,8 +686,9 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode const charCount = messageContentParts.replace(/\n/g, '').length + existingCharCount; existingRowAssistantTextWrapper.innerHTML = `${existingInnerHTML}${messageContentPartsHTML}`; - - resultCounter.innerHTML = `${charCount} chars / ${wordCount} words`; + if (resultCounter) { + resultCounter.innerHTML = `${charCount} chars / ${wordCount} words`; + } } else { const lastMessageWrapper = [...document.querySelectorAll('[id^="message-wrapper-"]')].pop(); if (lastMessageWrapper?.dataset?.role !== 'assistant') { @@ -691,7 +696,7 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode if (existingRowUser) { let threadCount = Object.keys(conversation).length > 0 ? conversation?.mapping[messageId]?.children?.length || 1 : 1; if (regenerateResponse) threadCount += 1; - const assistantRow = rowAssistant(conversation, data, threadCount, threadCount, models, settings.customConversationWidth, settings.conversationWidth); + const assistantRow = rowAssistant(conversation, data, threadCount, threadCount, models, settings.customConversationWidth, settings.conversationWidth, settings.showMessageTimestamp, settings.showWordCount); const conversationBottom = document.querySelector('#conversation-bottom'); conversationBottom.insertAdjacentHTML('beforebegin', assistantRow); if (!scrolUpDetected && settings.autoScroll) { @@ -917,7 +922,7 @@ ${settings.autoSplitChunkPrompt}`; isGenerating = true; submitChat(text, conversation, messageId, parentId, settings, models); textAreaElement.value = ''; - textAreaElement.style.height = '24px'; + textAreaElement.style.height = '56px'; updateInputCounter(''); } }); @@ -996,7 +1001,7 @@ ${settings.autoSplitChunkPrompt}`; isGenerating = true; submitChat(text, {}, messageId, parentId, settings, models); textAreaElement.value = ''; - textAreaElement.style.height = '24px'; + textAreaElement.style.height = '56px'; updateInputCounter(''); } }); @@ -1042,7 +1047,7 @@ ${settings.autoSplitChunkPrompt}`; }, 100); } } else { - textAreaElement.style.height = '24px'; + textAreaElement.style.height = '56px'; if (textAreaElement.value.trim().length === 0) return; addUserPromptToHistory(textAreaElement.value.trim()); inputForm.dispatchEvent(new Event('submit', { cancelable: true })); diff --git a/scripts/content/copyAndCounter.js b/scripts/content/copyAndCounter.js index 8500cc5..bae20b4 100644 --- a/scripts/content/copyAndCounter.js +++ b/scripts/content/copyAndCounter.js @@ -138,25 +138,29 @@ function addCopyButtonToResult(resultElement, index) { actionWrapper.appendChild(copyButton); } function updateCounterForResult(resultElement, index) { - const prevCounter = document.querySelector(`#result-counter-${index}`); - let prevCounterText = ''; - if (prevCounter) { - prevCounterText = prevCounter.innerText; - } - const resultText = resultElement.innerText; - const wordCount = resultText.split(/[ /]/).length; // +1 because of the "/" in the counter - const charCount = resultText.length; - const counterElement = document.createElement('div'); - counterElement.textContent = `${Math.max(charCount, 0)} chars / ${Math.max(wordCount, 0)} words`; - if (prevCounterText !== counterElement.textContent) { + chrome.storage.local.get(['settings'], (result) => { + const { showWordCount } = result.settings; + if (!showWordCount) return; + const prevCounter = document.querySelector(`#result-counter-${index}`); + let prevCounterText = ''; if (prevCounter) { - prevCounter.remove(); + prevCounterText = prevCounter.innerText; } - counterElement.id = `result-counter-${index}`; - const actionWrapper = document.querySelector(`#result-action-wrapper-${index}`); + const resultText = resultElement.innerText; + const wordCount = resultText.split(/[ /]/).length; // +1 because of the "/" in the counter + const charCount = resultText.length; + const counterElement = document.createElement('div'); + counterElement.textContent = `${Math.max(charCount, 0)} chars / ${Math.max(wordCount, 0)} words`; + if (prevCounterText !== counterElement.textContent) { + if (prevCounter) { + prevCounter.remove(); + } + counterElement.id = `result-counter-${index}`; + const actionWrapper = document.querySelector(`#result-action-wrapper-${index}`); - actionWrapper.appendChild(counterElement); - } + actionWrapper.appendChild(counterElement); + } + }); } function updateCounters() { diff --git a/scripts/content/customInstructions.js b/scripts/content/customInstructions.js index d6d6ce2..ba98820 100644 --- a/scripts/content/customInstructions.js +++ b/scripts/content/customInstructions.js @@ -283,6 +283,8 @@ function upgradeCustomInstructions() { textAreaFields.forEach((t) => { t.addEventListener('input', () => { setTimeout(() => { + const allButtons = body.querySelectorAll('button'); + const saveButton = [...allButtons].find((b) => b.textContent === 'Save'); const curNameInput = document.querySelector('#custom-instructions-name-input'); if (curNameInput.value === '') { saveButton.disabled = true; @@ -313,12 +315,14 @@ function upgradeCustomInstructions() { curNameInput.classList.remove('text-gray-300'); } setTimeout(() => { + const curAllButtons = body.querySelectorAll('button'); + const curSaveButton = [...curAllButtons].find((b) => b.textContent === 'Save'); if (curNameInput.value === '') { - saveButton.disabled = true; - saveButton.classList.add('opacity-50', 'cursor-not-allowed'); + curSaveButton.disabled = true; + curSaveButton.classList.add('opacity-50', 'cursor-not-allowed'); } else if (curNameInput.value !== selectedProfile?.name) { - saveButton.disabled = false; - saveButton.classList.remove('opacity-50', 'cursor-not-allowed'); + curSaveButton.disabled = false; + curSaveButton.classList.remove('opacity-50', 'cursor-not-allowed'); } }, 10); }); @@ -361,7 +365,7 @@ function upgradeCustomInstructions() { } } }); - }, 500); + }, 100); } }); }; diff --git a/scripts/content/export.js b/scripts/content/export.js index 62374ae..7793d9a 100644 --- a/scripts/content/export.js +++ b/scripts/content/export.js @@ -37,7 +37,11 @@ function getSingelConversation(conversationId, title) { } // download as .txt file if (title.toLowerCase() === 'text') { - const conversationText = messages.reverse().filter((m) => ['user', 'assistant'].includes(m.role) || ['user', 'assistant'].includes(m.author?.role)).map((m) => `${exportMode === 'both' ? `>> ${m.role ? m.role.toUpperCase() : m.author?.role.toUpperCase()}: ` : ''}${m.content.parts.join('\n').replace(/## Instructions[\s\S]*## End Instructions\n\n/, '')}`).join('\n\n'); + const conversationText = messages.reverse().filter((m) => { + const role = m?.author?.role || m?.role; + const recipient = m?.recipient; + return role === 'user' || (recipient === 'all' && role === 'assistant'); + }).map((m) => `${exportMode === 'both' ? `>> ${m.role ? m.role.toUpperCase() : m.author?.role.toUpperCase()}: ` : ''}${m.content?.parts?.join('\n').replace(/## Instructions[\s\S]*## End Instructions\n\n/, '')}`).join('\n\n'); const element = document.createElement('a'); element.setAttribute('href', `data:text/plain;charset=utf-8,${encodeURIComponent(conversationText)}`); element.setAttribute('download', `${filePrefix}-${conversationTitle}.${fileFormatConverter(title.toLowerCase())}`); @@ -50,7 +54,11 @@ function getSingelConversation(conversationId, title) { } // download as .md file if (title.toLowerCase() === 'markdown') { - const conversationMarkdown = messages.reverse().filter((m) => ['user', 'assistant'].includes(m.role) || ['user', 'assistant'].includes(m.author?.role)).map((m) => `${exportMode === 'both' ? `## ${m.role ? m.role.toUpperCase() : m.author?.role.toUpperCase()}\n` : ''}${m.content.parts.join('\n').replace(/## Instructions[\s\S]*## End Instructions\n\n/, '')}`).join('\n\n'); + const conversationMarkdown = messages.reverse().filter((m) => { + const role = m?.author?.role || m?.role; + const recipient = m?.recipient; + return role === 'user' || (recipient === 'all' && role === 'assistant'); + }).map((m) => `${exportMode === 'both' ? `## ${m.role ? m.role.toUpperCase() : m.author?.role.toUpperCase()}\n` : ''}${m.content?.parts?.join('\n').replace(/## Instructions[\s\S]*## End Instructions\n\n/, '')}`).join('\n\n'); const element = document.createElement('a'); element.setAttribute('href', `data:text/plain;charset=utf-8,${encodeURIComponent(conversationMarkdown)}`); // add timestamp to conversation title to make file name @@ -141,8 +149,8 @@ function addExportButton() { exportButton.id = 'export-conversation-button'; exportButton.type = 'button'; exportButton.textContent = 'Export'; - exportButton.classList.add('btn', 'flex', 'justify-center', 'gap-2', 'btn-neutral', 'border'); - exportButton.style = 'position:absolute;right:0px;width:104px;'; + exportButton.classList.add('btn', 'justify-center', 'gap-2', 'btn-neutral', 'border'); + exportButton.style = 'position:absolute;right:0px;width:104px;display:none;'; // add icon const exportButtonIcon = document.createElement('img'); exportButtonIcon.style = 'height:20px;'; @@ -215,6 +223,7 @@ function addExportButton() { inputFormActionWrapper = newDiv; } inputFormActionWrapper.style.minHeight = '38px'; + exportButton.style.display = settings.showExportButton ? 'flex' : 'none'; if (!existingExportButton) inputFormActionWrapper.appendChild(exportButton); }); } @@ -280,7 +289,11 @@ function exportAllConversations(exportFormat) { } // download as .txt file if (exportFormat === 'text') { - const conversationText = messages.reverse().filter((m) => ['user', 'assistant'].includes(m.role) || ['user', 'assistant'].includes(m.author?.role)).map((m) => `${exportMode === 'both' ? `>> ${m.role ? m.role.toUpperCase() : m.author?.role.toUpperCase()}: ` : ''}${m.content?.parts?.join('\n').replace(/## Instructions[\s\S]*## End Instructions\n\n/, '')}`)?.join('\n\n'); + const conversationText = messages.reverse().filter((m) => { + const role = m?.author?.role || m?.role; + const recipient = m?.recipient; + return role === 'user' || (recipient === 'all' && role === 'assistant'); + }).map((m) => `${exportMode === 'both' ? `>> ${m.role ? m.role.toUpperCase() : m.author?.role.toUpperCase()}: ` : ''}${m.content?.parts?.join('\n').replace(/## Instructions[\s\S]*## End Instructions\n\n/, '')}`)?.join('\n\n'); zip.file(`${folderName}/${filePrefix}-${conversationTitle}.${fileFormatConverter(exportFormat)}`, conversationText); } // download as .json file @@ -290,7 +303,11 @@ function exportAllConversations(exportFormat) { } // download as .md file if (exportFormat === 'markdown') { - const conversationMarkdown = messages.reverse().filter((m) => ['user', 'assistant'].includes(m.role) || ['user', 'assistant'].includes(m.author?.role)).map((m) => `${exportMode === 'both' ? `## ${m.role ? m.role.toUpperCase() : m.author?.role.toUpperCase()}\n` : ''}${m.content?.parts?.join('\n').replace(/## Instructions[\s\S]*## End Instructions\n\n/, '')}`)?.join('\n\n'); + const conversationMarkdown = messages.reverse().filter((m) => { + const role = m?.author?.role || m?.role; + const recipient = m?.recipient; + return role === 'user' || (recipient === 'all' && role === 'assistant'); + }).map((m) => `${exportMode === 'both' ? `## ${m.role ? m.role.toUpperCase() : m.author?.role.toUpperCase()}\n` : ''}${m.content?.parts?.join('\n').replace(/## Instructions[\s\S]*## End Instructions\n\n/, '')}`)?.join('\n\n'); zip.file(`${folderName}/${filePrefix}-${conversationTitle}.${fileFormatConverter(exportFormat)}`, conversationMarkdown); } diff --git a/scripts/content/folderElement.js b/scripts/content/folderElement.js index 5fa3c8f..9e5bbf2 100644 --- a/scripts/content/folderElement.js +++ b/scripts/content/folderElement.js @@ -18,12 +18,39 @@ function createFolder(folder, conversationTimestamp, conversations = [], isNewFo curFolderElement?.click(); } }); + // folder element const folderElement = document.createElement('div'); folderElement.id = `folder-${folderId}`; folderElement.classList = 'flex py-3 px-3 pr-3 w-full items-center gap-3 relative rounded-md hover:bg-[#2A2B32] cursor-pointer break-all hover:pr-20 group'; folderElement.style.backgroundColor = folder.color || 'transparent'; - // eslint-disable-next-line no-loop-func - + folderElement.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + // get closet element with id starting with conversation-button + chrome.storage.sync.get(['conversationsOrder'], (result) => { + const { conversationsOrder } = result; + const folderElementId = e.srcElement.closest('[id^="folder-"]').id.split('folder-')[1]; + const curFolderIcon = document.querySelector(`#folder-${folderElementId} img`); + curFolderIcon.src = chrome.runtime.getURL(`${curFolderIcon.dataset.isOpen === 'false' ? 'icons/folder-open.png' : 'icons/folder.png'}`); + curFolderIcon.dataset.isOpen = curFolderIcon.dataset.isOpen === 'false' ? 'true' : 'false'; + const curFolderContent = document.querySelector(`#folder-content-${folderElementId}`); + curFolderContent.style.display = folderContent.style.display === 'none' ? 'block' : 'none'; + conversationsOrder.find((c) => c.id === folderElementId).isOpen = curFolderIcon.dataset.isOpen === 'true'; + chrome.storage.sync.set({ conversationsOrder }); + }); + }); + folderElement.addEventListener('dragenter', (e) => { + // if folder content is not visible, show it + const curFolderId = e.srcElement.id.split('folder-')[1]; + const curFolderContent = document.querySelector(`#folder-content-${curFolderId}`); + if (curFolderContent.style.display === 'none') { + curFolderContent.style.display = 'block'; + const curFolderIcon = document.querySelector(`#folder-${curFolderId} img`); + curFolderIcon.src = chrome.runtime.getURL('icons/folder-open.png'); + curFolderIcon.dataset.isOpen = 'true'; + } + }); + // folder icon const folderIcon = document.createElement('img'); folderIcon.classList = 'w-4 h-4'; folderIcon.src = folder.isOpen ? chrome.runtime.getURL('icons/folder-open.png') : chrome.runtime.getURL('icons/folder.png'); @@ -37,6 +64,7 @@ function createFolder(folder, conversationTimestamp, conversations = [], isNewFo folderElement.title = folder.name; folderElement.appendChild(folderTitle); + // folder content const folderContent = document.createElement('div'); folderContent.id = `folder-content-${folderId}`; folderContent.classList = 'w-full border-l border-gray-500'; @@ -57,22 +85,6 @@ function createFolder(folder, conversationTimestamp, conversations = [], isNewFo folderContent.appendChild(emptyFolderElement(folderId)); } - folderElement.addEventListener('click', (e) => { - e.preventDefault(); - e.stopPropagation(); - // get closet element with id starting with conversation-button - chrome.storage.sync.get(['conversationsOrder'], (result) => { - const { conversationsOrder } = result; - const folderElementId = e.srcElement.closest('[id^="folder-"]').id.split('folder-')[1]; - const curFolderIcon = document.querySelector(`#folder-${folderElementId} img`); - curFolderIcon.src = chrome.runtime.getURL(`${curFolderIcon.dataset.isOpen === 'false' ? 'icons/folder-open.png' : 'icons/folder.png'}`); - curFolderIcon.dataset.isOpen = curFolderIcon.dataset.isOpen === 'false' ? 'true' : 'false'; - const curFolderContent = document.querySelector(`#folder-content-${folderElementId}`); - curFolderContent.style.display = folderContent.style.display === 'none' ? 'block' : 'none'; - conversationsOrder.find((c) => c.id === folderElementId).isOpen = curFolderIcon.dataset.isOpen === 'true'; - chrome.storage.sync.set({ conversationsOrder }); - }); - }); // action icons folderElement.appendChild(folderActions(folderId)); diff --git a/scripts/content/global.js b/scripts/content/global.js index 7d7dd5c..f3af8e1 100644 --- a/scripts/content/global.js +++ b/scripts/content/global.js @@ -280,24 +280,26 @@ function addNavToggleButton() { }); } function showHideTextAreaElement(forceShow = false) { - const textAreaElement = document.querySelector('main form textarea'); - if (!textAreaElement) return; - const textAreaParent = textAreaElement.parentElement; - const allMessageWrapper = document.querySelectorAll('[id^="message-wrapper-"]'); - const continueButton = document.querySelector('#continue-conversation-button-wrapper'); - if (allMessageWrapper.length > 0) { - const lastMessageWrapperElement = allMessageWrapper[allMessageWrapper.length - 1]; - - if (!forceShow && lastMessageWrapperElement && lastMessageWrapperElement.dataset.role === 'user') { - textAreaParent.style.display = 'none'; - if (continueButton) continueButton.style.display = 'none'; + chrome.storage.local.get('settings', ({ settings }) => { + const textAreaElement = document.querySelector('main form textarea'); + if (!textAreaElement) return; + const textAreaParent = textAreaElement.parentElement; + const allMessageWrapper = document.querySelectorAll('[id^="message-wrapper-"]'); + const continueButton = document.querySelector('#continue-conversation-button-wrapper'); + if (allMessageWrapper.length > 0) { + const lastMessageWrapperElement = allMessageWrapper[allMessageWrapper.length - 1]; + + if (!forceShow && lastMessageWrapperElement && lastMessageWrapperElement.dataset.role === 'user') { + textAreaParent.style.display = 'none'; + if (continueButton) continueButton.style.display = 'none'; + } else { + textAreaParent.style = ''; + if (continueButton && settings.showCustomPromptsButton) continueButton.style.display = 'flex'; + } } else { textAreaParent.style = ''; - if (continueButton) continueButton.style.display = 'flex'; } - } else { - textAreaParent.style = ''; - } + }); } function showNewChatPage() { // chatStreamIsClosed = true; @@ -464,7 +466,7 @@ function replaceTextAreaElemet(settings) { let textAreaElement = inputForm.querySelector('textarea'); if (!textAreaElement) { - const textAreaElementWrapperHTML = '
    '; + const textAreaElementWrapperHTML = '
    '; // insert text area element wrapper in input form first child at the end inputForm.firstChild.insertAdjacentHTML('beforeend', textAreaElementWrapperHTML); textAreaElement = inputForm.querySelector('textarea'); @@ -474,16 +476,16 @@ function replaceTextAreaElemet(settings) { newTextAreaElement.dir = 'auto'; // auto resize textarea height up to 200px newTextAreaElement.style.height = 'auto'; - newTextAreaElement.style.height = `${newTextAreaElement.scrollHeight}px`; + newTextAreaElement.style.height = `${newTextAreaElement.scrollHeight || '56'}px`; newTextAreaElement.style.maxHeight = '200px'; - newTextAreaElement.style.minHeight = '24px'; + newTextAreaElement.style.minHeight = '56px'; newTextAreaElement.style.paddingRight = '40px'; newTextAreaElement.style.overflowY = 'hidden'; newTextAreaElement.addEventListener('keydown', textAreaElementKeydownEventListenerSync); // also async newTextAreaElement.addEventListener('keydown', (event) => { - if (event.key === 'Enter' && event.which === 13 && !event.shiftKey) { + if (event.key === 'Enter' && event.which === 13 && !event.shiftKey && !isGenerating) { disableTextInput = true; if (newTextAreaElement.value.trim().length === 0) { event.preventDefault(); @@ -495,6 +497,7 @@ function replaceTextAreaElemet(settings) { } }); newTextAreaElement.addEventListener('input', textAreaElementInputEventListener); + // newTextAreaElement.addEventListener('paste', textAreaElementInputEventListener); textAreaElement.replaceWith(newTextAreaElement); addInputCounter(); @@ -555,10 +558,13 @@ function addGpt4Counter() { if (!result.models.find((model) => model.slug === 'gpt-4')) return; gpt4CounterElement.style.display = result.settings.showGpt4Counter ? 'block' : 'none'; const gpt4Timestamps = result.gpt4Timestamps || []; + const messageCap = result?.conversationLimit?.message_cap || 50; const messageCapWindow = result?.conversationLimit?.message_cap_window || 180; const now = new Date().getTime(); - const gpt4counter = gpt4Timestamps.filter((timestamp) => now - timestamp < (messageCapWindow / 60) * 60 * 60 * 1000).length; + const timestampsInCapWindow = gpt4Timestamps.filter((timestamp) => now - timestamp < (messageCapWindow / 60) * 60 * 60 * 1000); + // const resetTimeText = timestampsInCapWindow.length > 0 ? `New message available at: ${new Date(timestampsInCapWindow[0] + (messageCapWindow / 60) * 60 * 60 * 1000).toLocaleString()}` : ''; + const gpt4counter = timestampsInCapWindow.length; const capExpiresAtTimeString = result.capExpiresAt ? `(Cap Expires At: ${result.capExpiresAt})` : ''; if (gpt4counter) { gpt4CounterElement.innerText = `GPT-4 requests (last ${getGPT4CounterMessageCapWindow(messageCapWindow)}): ${gpt4counter}/${messageCap} ${capExpiresAtTimeString}`; @@ -575,6 +581,7 @@ function addGpt4Counter() { } }); } + function updateInputCounter(text) { const curInputCounterElement = document.querySelector('#gptx-input-counter'); if (curInputCounterElement) { @@ -906,10 +913,13 @@ function toast(html, type = 'info', duration = 4000) { if (existingToast) existingToast.remove(); const element = document.createElement('div'); element.id = 'gptx-toast'; - element.style = 'position:fixed;right:24px;top:24px;border-radius:4px;background-color:#19c37d;padding:8px 16px;z-index:100001;'; + element.style = 'position:fixed;right:24px;top:24px;border-radius:4px;background-color:#19c37d;padding:8px 16px;z-index:100001;max-width:600px;'; if (type === 'error') { element.style.backgroundColor = '#ef4146'; } + if (type === 'warning') { + element.style.backgroundColor = '#e06c2b'; + } element.innerHTML = html; document.body.appendChild(element); setTimeout( diff --git a/scripts/content/keyboardShortcuts.js b/scripts/content/keyboardShortcuts.js index f41477a..239fea3 100644 --- a/scripts/content/keyboardShortcuts.js +++ b/scripts/content/keyboardShortcuts.js @@ -1,4 +1,4 @@ -/* global isWindows, createModal, settingsModalActions, initializePluginStoreModal, addPluginStoreEventListener, showNewChatPage, createPromptChainListModal */ +/* global isWindows, createModal, settingsModalActions, initializePluginStoreModal, addPluginStoreEventListener, showNewChatPage, createPromptChainListModal, toast */ // eslint-disable-next-line no-unused-vars function createKeyboardShortcutsModal(version) { @@ -19,6 +19,8 @@ function keyboardShortcutsModalContent() { content.appendChild(logoWatermark); const keyboardShortcutsText = document.createElement('div'); keyboardShortcutsText.style = 'display: flex; flex-direction: column; justify-content: start; align-items: start;overflow-y: scroll; height: 100%; width: 100%; white-space: break-spaces; overflow-wrap: break-word;padding: 16px;position: relative;z-index:10;color: #fff;'; + const refreshButton = document.getElementById('sync-refresh-button').innerHTML; + keyboardShortcutsText.innerHTML = ` @@ -42,7 +44,7 @@ function keyboardShortcutsModalContent() { - + @@ -62,11 +64,11 @@ function keyboardShortcutsModalContent() { - + - + @@ -183,6 +185,14 @@ function registerShortkeys() { window.location.reload(); }); } + // cmd/ctrl + alt + S + if ((e.metaKey || (isWindows() && e.ctrlKey)) && e.altKey && e.keyCode === 83) { + e.preventDefault(); + settings.autoSplit = !settings.autoSplit; + settings.autoSummarize = false; + toast(`Auto-split is now ${settings.autoSplit ? 'ON' : 'OFF'}`); + chrome.storage.local.set({ settings }); + } // cmd/ctrl + alt + h if ((e.metaKey || (isWindows() && e.ctrlKey)) && e.altKey && e.keyCode === 72) { e.preventDefault(); diff --git a/scripts/content/pluginStore.js b/scripts/content/pluginStore.js index 5410b4d..ec1c17b 100644 --- a/scripts/content/pluginStore.js +++ b/scripts/content/pluginStore.js @@ -11,7 +11,7 @@ function initializePluginStoreModal(plugins) { style="pointer-events: auto;" >
    - + - + + + + + - + @@ -81,11 +85,11 @@ function keyboardShortcutsModalContent() { - + - +
    Open Newsletter Archive
    CTRL/CMD + SHIFT + X (or SHIFT + Click on New Prompt Chain button)CTRL/CMD + SHIFT + X (or SHIFT + Click on New Prompt Chain button ) Open Prompt Chain List
    Enable/disable auto-sync
    CTRL/CMD + SHIFT + Click on the new folder iconCTRL/CMD + SHIFT + Click on the new folder icon Reset the order of chats from newest to oldest (removes all folders)
    CTRL/CMD + SHIFT + Click on the sync button in the bottom-left cornerCTRL/CMD + SHIFT + Click on the sync button in the bottom-left corner ${refreshButton} Reset Auto Sync
    CTRL/CMD + SHIFT + KOpen Keyboard Shortcut modalOpen Keyboard Shortcut Modal
    CTRL/CMD + ALT + HHide/show the sidebarHide/Show the Sidebar
    CTRL/CMD + ALT + SEnable/Disable Auto Splitter
    CTRL/CMD + ALT + AEnable/disable auto-syncEnable/Disable Auto-Sync
    CTRL/CMD + SHIFT + Click on the new folder icon
    ENDScroll to bottomScroll to Bottom
    ESCClose modals or stop generatingClose Modals/Stop Generating
    `; diff --git a/scripts/content/modelSwitcher.js b/scripts/content/modelSwitcher.js index a7a8e48..2859017 100644 --- a/scripts/content/modelSwitcher.js +++ b/scripts/content/modelSwitcher.js @@ -1,10 +1,13 @@ /* eslint-disable no-unused-vars */ /* global getInstalledPlugins, addArkoseScript, initializeRegenerateResponseButton */ // eslint-disable-next-line no-unused-vars -function modelSwitcher(models, selectedModel, idPrefix, customModels, forceDark = false) { +function modelSwitcher(models, selectedModel, idPrefix, customModels, autoSync, forceDark = false) { if (selectedModel.slug.includes('gpt-4')) { addArkoseScript(); } + if (selectedModel.slug === 'gpt-4-code-interpreter' && autoSync) { + showAutoSyncWarning('Uploading files with Advanced Data Analysis model requires Auto Sync to be OFF. Please turn off Auto Sync if you need to upload a file. You can turn Auto Sync back ON (CMD/CTRL+ALT+A) again after submitting your file.'); + } return `'); + setTimeout(() => { + // check if a script element with src including api.js and chrome-extension exists + const arkoseScript = document.querySelector('script[src*="chrome-extension"][src*="api.js"]'); + if (arkoseScript) return; + const arkoseApiScript = document.createElement('script'); + arkoseApiScript.async = !0; + arkoseApiScript.defer = !0; + arkoseApiScript.setAttribute('type', 'text/javascript'); + arkoseApiScript.setAttribute('data-status', 'loading'); + arkoseApiScript.setAttribute('data-callback', 'useArkoseSetupEnforcement'); + arkoseApiScript.setAttribute('src', chrome.runtime.getURL('v2/35536E1E-65B4-4D96-9D97-6ADB7EFF8147/api.js')); + document.body.appendChild(arkoseApiScript); + }, 500); +} +function arkoseTrigger(trigger = true) { + window.localStorage.removeItem('arkoseToken'); + const inputForm = document.querySelector('main form'); + if (!inputForm) return; + if (!inputForm.querySelector('#enforcement-trigger')) { + inputForm.firstChild.insertAdjacentHTML('beforeend', ''); + } + if (trigger) { + inputForm.querySelector('#enforcement-trigger').click(); + } } function replaceTextAreaElemet(settings) { diff --git a/scripts/content/initialize.js b/scripts/content/initialize.js index 20790f8..56d255e 100644 --- a/scripts/content/initialize.js +++ b/scripts/content/initialize.js @@ -1,4 +1,4 @@ -/* global navigation, initializeStorage, cleanNav, initializeContinue, initializeExport, initializeSettings, initializePromptHistory, initializePromptLibrary, initializeNewsletter, initializeAutoSave, addNavToggleButton, initializeAnnouncement, initializeReleaseNote, initializeReplaceDeleteConversationButton, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, updateNewChatButtonNotSynced, addAsyncInputEvents, addDevIndicator, addExpandButton, openLinksInNewTab, addEnforcementTriggerElement, initializeKeyboardShortcuts, addArkoseCallback, addQuickAccessMenuEventListener, upgradeCustomInstructions, watchError, showAutoSyncToast, addAutoSyncToggleButton */ +/* global navigation, initializeStorage, cleanNav, initializeContinue, initializeExport, initializeSettings, initializePromptHistory, initializePromptLibrary, initializeNewsletter, initializeAutoSave, addNavToggleButton, initializeAnnouncement, initializeReleaseNote, initializeReplaceDeleteConversationButton, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, updateNewChatButtonNotSynced, addAsyncInputEvents, addDevIndicator, addExpandButton, openLinksInNewTab, arkoseTrigger, initializeKeyboardShortcuts, addArkoseCallback, addQuickAccessMenuEventListener, upgradeCustomInstructions, watchError, showAutoSyncToast, addAutoSyncToggleButton */ // eslint-disable-next-line no-unused-vars function initialize() { @@ -25,7 +25,7 @@ function initialize() { addExpandButton(); addDevIndicator(); initializeKeyboardShortcuts(); - addEnforcementTriggerElement(); + arkoseTrigger(false); addArkoseCallback(); // showAutoSyncToast(); setTimeout(() => { diff --git a/scripts/content/modelSwitcher.js b/scripts/content/modelSwitcher.js index 2859017..f3b0ab4 100644 --- a/scripts/content/modelSwitcher.js +++ b/scripts/content/modelSwitcher.js @@ -1,10 +1,7 @@ /* eslint-disable no-unused-vars */ -/* global getInstalledPlugins, addArkoseScript, initializeRegenerateResponseButton */ +/* global getInstalledPlugins, initializeRegenerateResponseButton, arkoseTrigger */ // eslint-disable-next-line no-unused-vars function modelSwitcher(models, selectedModel, idPrefix, customModels, autoSync, forceDark = false) { - if (selectedModel.slug.includes('gpt-4')) { - addArkoseScript(); - } if (selectedModel.slug === 'gpt-4-code-interpreter' && autoSync) { showAutoSyncWarning('Uploading files with Advanced Data Analysis model requires Auto Sync to be OFF. Please turn off Auto Sync if you need to upload a file. You can turn Auto Sync back ON (CMD/CTRL+ALT+A) again after submitting your file.'); } @@ -197,7 +194,7 @@ function addModelSwitcherEventListener(idPrefix, forceDark = false) { } const submitButton = document.querySelector('main form textarea ~ button'); if (submitButton && !submitButton.disabled) { - if (selectedModel.slug.startsWith('gpt-4')) { + if (selectedModel.slug.includes('gpt-4')) { submitButton.style.backgroundColor = '#AB68FF'; } else { submitButton.style.backgroundColor = '#19C37D'; @@ -205,7 +202,13 @@ function addModelSwitcherEventListener(idPrefix, forceDark = false) { } } chrome.storage.local.set({ settings: { ...settings, selectedModel } }, () => { - addArkoseScript(); + if (selectedModel.slug.includes('gpt-4') && !selectedModel.tags.includes('Unofficial')) { + const arkoseIframeWrapper = document.querySelector('[class="arkose-35536E1E-65B4-4D96-9D97-6ADB7EFF8147-wrapper"]'); + if (!arkoseIframeWrapper) { + window.location.reload(); + } + arkoseTrigger(); + } }); }); }); diff --git a/scripts/content/promptChain.js b/scripts/content/promptChain.js index 8a09c91..5a08be5 100644 --- a/scripts/content/promptChain.js +++ b/scripts/content/promptChain.js @@ -191,6 +191,7 @@ function createNewPromptChainModal(promptChainName, promptChainSteps, chainIndex const sortable = Sortable.create(promptInputListWrapper, { handle: '#prompt-chain-drag-handle', multiDrag: true, + direction: 'vertical', selectedClass: 'multi-drag-selected', }); newPromptChainModalContent.appendChild(promptInputListWrapper); diff --git a/scripts/content/promptHistory.js b/scripts/content/promptHistory.js index b72a342..af32b6c 100644 --- a/scripts/content/promptHistory.js +++ b/scripts/content/promptHistory.js @@ -507,7 +507,7 @@ function textAreaElementInputEventListener(event) { const { settings } = result; const { selectedModel } = settings; submitButton.disabled = false; - if (selectedModel.slug.startsWith('gpt-4')) { + if (selectedModel.slug.includes('gpt-4')) { submitButton.style.backgroundColor = '#AB68FF'; } else { submitButton.style.backgroundColor = '#19C37D'; diff --git a/scripts/content/quickAccessMenu.js b/scripts/content/quickAccessMenu.js index fcbd522..acef99f 100644 --- a/scripts/content/quickAccessMenu.js +++ b/scripts/content/quickAccessMenu.js @@ -195,7 +195,7 @@ function loadCustomPrompts() { const promptElement = document.createElement('button'); promptElement.id = `quick-access-menu-item-${i}`; promptElement.classList = 'btn w-full text-left focus:outline focus:ring-2 focus:ring-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700'; - promptElement.innerHTML = `${prompt.title}${prompt.text}`; + promptElement.innerHTML = `${prompt.title}${prompt.text}`; promptElement.addEventListener('click', () => { const inputForm = document.querySelector('main form'); if (!inputForm) return; diff --git a/scripts/content/regenerateResponse.js b/scripts/content/regenerateResponse.js index 0033a8a..d585fb8 100644 --- a/scripts/content/regenerateResponse.js +++ b/scripts/content/regenerateResponse.js @@ -1,6 +1,6 @@ /* eslint-disable no-restricted-globals */ // eslint-disable-next-line no-unused-vars -/* global canSubmitPrompt, submitChat, showHideTextAreaElement, isGenerating:true, addEnforcementTriggerElement */ +/* global canSubmitPrompt, submitChat, showHideTextAreaElement, isGenerating:true, arkoseTrigger */ function toggleOriginalRegenerateResponseButton() { const allMessageWrapper = document.querySelectorAll('[id^="message-wrapper-"]'); const lastMessageWrapperElement = allMessageWrapper[allMessageWrapper.length - 1]; @@ -45,13 +45,9 @@ function toggleOriginalRegenerateResponseButton() { newRegenerateResponseButton.classList = `btn flex justify-center gap-2 ${textAreaElement.parentElement.style.display === 'none' ? 'btn-primary' : 'btn-neutral'} border`; newRegenerateResponseButton.innerHTML = ' Regenerate'; newRegenerateResponseButton.addEventListener('click', () => { - window.localStorage.removeItem('arkoseToken'); chrome.storage.local.get(['conversations', 'settings', 'models'], (result) => { if (result.settings.selectedModel.slug.includes('gpt-4')) { - if (!inputForm.querySelector('#enforcement-trigger')) { - addEnforcementTriggerElement(); - } - inputForm.querySelector('#enforcement-trigger').click(); + arkoseTrigger(); } const { pathname } = new URL(window.location.toString()); const conversationId = pathname.split('/').pop().replace(/[^a-z0-9-]/gi, ''); @@ -87,10 +83,7 @@ function toggleOriginalRegenerateResponseButton() { newContinueGeneratingButton.addEventListener('click', () => { chrome.storage.local.get(['conversations', 'settings', 'models'], (result) => { if (result.settings.selectedModel.slug.includes('gpt-4')) { - if (!inputForm.querySelector('#enforcement-trigger')) { - addEnforcementTriggerElement(); - } - inputForm.querySelector('#enforcement-trigger').click(); + arkoseTrigger(); } const { pathname } = new URL(window.location.toString()); const conversationId = pathname.split('/').pop().replace(/[^a-z0-9-]/gi, ''); diff --git a/scripts/content/rowAssistant.js b/scripts/content/rowAssistant.js index 77f3b61..e82f19b 100644 --- a/scripts/content/rowAssistant.js +++ b/scripts/content/rowAssistant.js @@ -41,7 +41,7 @@ function rowAssistant(conversation, node, childIndex, childCount, models, custom const charCount = messageContentParts.replace(/\n/g, '').length; // eslint-disable-next-line no-nested-ternary - const avatarColor = (modelSlug?.includes('plugins') || modelSlug?.startsWith('gpt-4')) ? 'rgb(171, 104, 255)' : 'rgb(25, 195, 125)'; + const avatarColor = (modelSlug?.includes('plugins') || modelSlug?.includes('gpt-4')) ? 'rgb(171, 104, 255)' : 'rgb(25, 195, 125)'; return `
    diff --git a/scripts/content/shareModal.js b/scripts/content/shareModal.js index 33d2570..2a64072 100644 --- a/scripts/content/shareModal.js +++ b/scripts/content/shareModal.js @@ -488,7 +488,7 @@ function assistantRow(message) { delimiters: 'brackets', katexOptions: { macros: { '\\RR': '\\mathbb{R}' } }, }).render(messageContentParts); - const avatarColor = (message.metadata.model_slug?.includes('plugins') || message.metadata.model_slug?.startsWith('gpt-4')) ? 'rgb(171, 104, 255)' : 'rgb(25, 195, 125)'; + const avatarColor = (message.metadata.model_slug?.includes('plugins') || message.metadata.model_slug?.includes('gpt-4')) ? 'rgb(171, 104, 255)' : 'rgb(25, 195, 125)'; return `
    Date: Fri, 22 Sep 2023 23:08:32 -0700 Subject: [PATCH 30/38] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20sound=20for?= =?UTF-8?q?=20chat=20end=20event,=20add=20not=20installed=20tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 1 + scripts/content/conversationList.js | 13 ++++++++++- scripts/content/global.js | 10 +++++++++ scripts/content/initialize.js | 3 ++- scripts/content/modelSwitcher.js | 5 +++++ scripts/content/pluginStore.js | 32 ++++++++++++++++++++++++++++ scripts/content/settings.js | 4 ++++ sounds/beep.mp3 | Bin 0 -> 9244 bytes 8 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 sounds/beep.mp3 diff --git a/manifest.json b/manifest.json index e892de2..9c324ee 100644 --- a/manifest.json +++ b/manifest.json @@ -123,6 +123,7 @@ ], "resources": [ "icons/*", + "sounds/*", "scripts/content/*", "scripts/interceptor/*", "v2/*" diff --git a/scripts/content/conversationList.js b/scripts/content/conversationList.js index cf38b4f..fc976f7 100644 --- a/scripts/content/conversationList.js +++ b/scripts/content/conversationList.js @@ -1,6 +1,6 @@ /* eslint-disable no-restricted-globals */ // eslint-disable-next-line no-unused-vars -/* global markdown, markdownitSup, initializeNavbar, generateInstructions, generateChat, SSE, formatDate, loadConversation, resetSelection, katex, texmath, rowUser, rowAssistant, updateOrCreateConversation, replaceTextAreaElemet, highlight, isGenerating:true, disableTextInput:true, generateTitle, debounce, initializeRegenerateResponseButton, initializeStopGeneratingResponseButton, showHideTextAreaElement, showNewChatPage, chatStreamIsClosed:true, addCopyCodeButtonsEventListeners, addScrollDetector, scrolUpDetected:true, Sortable, updateInputCounter, addUserPromptToHistory, getGPT4CounterMessageCapWindow, createFolder, getConversationElementClassList, notSelectedClassList, selectedClassList, conversationActions, addCheckboxToConversationElement, createConversation, deleteConversation, handleQueryParams, addScrollButtons, updateTotalCounter, isWindows, loadSharedConversation, createTemplateWordsModal, arkoseTrigger, initializePromptChain, insertNextChain, runningPromptChainSteps:true, runningPromptChainIndex:true, lastPromptSuggestions, generateSuggestions */ +/* global markdown, markdownitSup, initializeNavbar, generateInstructions, generateChat, SSE, formatDate, loadConversation, resetSelection, katex, texmath, rowUser, rowAssistant, updateOrCreateConversation, replaceTextAreaElemet, highlight, isGenerating:true, disableTextInput:true, generateTitle, debounce, initializeRegenerateResponseButton, initializeStopGeneratingResponseButton, showHideTextAreaElement, showNewChatPage, chatStreamIsClosed:true, addCopyCodeButtonsEventListeners, addScrollDetector, scrolUpDetected:true, Sortable, updateInputCounter, addUserPromptToHistory, getGPT4CounterMessageCapWindow, createFolder, getConversationElementClassList, notSelectedClassList, selectedClassList, conversationActions, addCheckboxToConversationElement, createConversation, deleteConversation, handleQueryParams, addScrollButtons, updateTotalCounter, isWindows, loadSharedConversation, createTemplateWordsModal, arkoseTrigger, initializePromptChain, insertNextChain, runningPromptChainSteps:true, runningPromptChainIndex:true, lastPromptSuggestions, generateSuggestions, playSound */ // Initial state let userChatIsActuallySaved = false; @@ -457,6 +457,11 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode remainingText = ''; finalSummary = ''; shouldSubmitFinalSummary = false; + // remove the last user message + const lastMessageWrapper = [...document.querySelectorAll('[id^="message-wrapper-"]')].pop(); + if (lastMessageWrapper?.dataset?.role !== 'assistant') { + lastMessageWrapper.remove(); + } const syncDiv = document.getElementById('sync-div'); syncDiv.style.opacity = '1'; const main = document.querySelector('main'); @@ -558,6 +563,9 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode initializeStopGeneratingResponseButton(); initializeRegenerateResponseButton(); updateTotalCounter(); + if (settings.chatEndedSound) { + playSound('beep'); + } // generateSuggestions(finalConversationId, messageId, settings.selectedModel.slug); } else if (e.event === 'ping') { // console.error('PING RECEIVED', e); @@ -745,6 +753,9 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; // if firefox and no error data, do nothing if (isFirefox && !err.data) return; + if (settings.chatEndedSound) { + playSound('beep'); + } isGenerating = false; chunkNumber = 1; totalChunks = 1; diff --git a/scripts/content/global.js b/scripts/content/global.js index bc2d022..a33727b 100644 --- a/scripts/content/global.js +++ b/scripts/content/global.js @@ -143,6 +143,16 @@ const markdown = (role, searchValue = '') => new markdownit({ return `
    ${language}
    ${value}
    `; }, }); +function addSounds() { + const audio = document.createElement('audio'); + audio.id = 'beep-sound'; + audio.src = chrome.runtime.getURL('sounds/beep.mp3'); + document.body.appendChild(audio); +} +function playSound(sound) { + const audio = document.getElementById(`${sound}-sound`); + audio.play(); +} function watchError() { const targetNode = document.body; const config = { childList: true, subtree: true }; diff --git a/scripts/content/initialize.js b/scripts/content/initialize.js index 56d255e..6d22225 100644 --- a/scripts/content/initialize.js +++ b/scripts/content/initialize.js @@ -1,4 +1,4 @@ -/* global navigation, initializeStorage, cleanNav, initializeContinue, initializeExport, initializeSettings, initializePromptHistory, initializePromptLibrary, initializeNewsletter, initializeAutoSave, addNavToggleButton, initializeAnnouncement, initializeReleaseNote, initializeReplaceDeleteConversationButton, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, updateNewChatButtonNotSynced, addAsyncInputEvents, addDevIndicator, addExpandButton, openLinksInNewTab, arkoseTrigger, initializeKeyboardShortcuts, addArkoseCallback, addQuickAccessMenuEventListener, upgradeCustomInstructions, watchError, showAutoSyncToast, addAutoSyncToggleButton */ +/* global navigation, initializeStorage, cleanNav, initializeContinue, initializeExport, initializeSettings, initializePromptHistory, initializePromptLibrary, initializeNewsletter, initializeAutoSave, addNavToggleButton, initializeAnnouncement, initializeReleaseNote, initializeReplaceDeleteConversationButton, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, updateNewChatButtonNotSynced, addAsyncInputEvents, addDevIndicator, addExpandButton, openLinksInNewTab, arkoseTrigger, initializeKeyboardShortcuts, addArkoseCallback, addQuickAccessMenuEventListener, upgradeCustomInstructions, watchError, showAutoSyncToast, addAutoSyncToggleButton, addSounds */ // eslint-disable-next-line no-unused-vars function initialize() { @@ -25,6 +25,7 @@ function initialize() { addExpandButton(); addDevIndicator(); initializeKeyboardShortcuts(); + addSounds(); arkoseTrigger(false); addArkoseCallback(); // showAutoSyncToast(); diff --git a/scripts/content/modelSwitcher.js b/scripts/content/modelSwitcher.js index f3b0ab4..d60f775 100644 --- a/scripts/content/modelSwitcher.js +++ b/scripts/content/modelSwitcher.js @@ -209,6 +209,11 @@ function addModelSwitcherEventListener(idPrefix, forceDark = false) { } arkoseTrigger(); } + // focus on input + const textInput = document.querySelector('main form textarea'); + if (textInput) { + textInput.focus(); + } }); }); }); diff --git a/scripts/content/pluginStore.js b/scripts/content/pluginStore.js index 8533532..1eda9af 100644 --- a/scripts/content/pluginStore.js +++ b/scripts/content/pluginStore.js @@ -87,6 +87,14 @@ function initializePluginStoreModal(plugins) { Installed
    +
    @@ -352,6 +360,18 @@ function addPluginStoreEventListener(plugins) { addInstallButtonEventListener(filteredPlugins); addPaginationEventListener(filteredPlugins); }); + } else if (filterType === 'not-installed') { + chrome.storage.local.get(['installedPlugins'], (res) => { + const { installedPlugins } = res; + filteredPlugins = allPlugins.filter((plugin) => !installedPlugins.map((p) => p.id).includes(plugin.id)); + if (searchValue.trim() !== '') { + filteredPlugins = filteredPlugins.filter((plugin) => `${plugin.manifest.name_for_human} ${plugin.manifest.description_for_human}`.toLowerCase().includes(searchValue.toLowerCase())); + } + pluginListWrapper.innerHTML = renderPluginList(filteredPlugins); + pluginStorePaginationWrapper.innerHTML = renderPageNumbers(filteredPlugins); + addInstallButtonEventListener(filteredPlugins); + addPaginationEventListener(filteredPlugins); + }); } else { if (searchValue.trim() !== '') { filteredPlugins = filteredPlugins.filter((plugin) => `${plugin.manifest.name_for_human} ${plugin.manifest.description_for_human}`.toLowerCase().includes(searchValue.toLowerCase())); @@ -457,6 +477,18 @@ function addInstallButtonEventListener(plugins) { const pluginsDropdownWrapper = document.getElementById(`plugins-dropdown-wrapper-${idPrefix}`); pluginsDropdownWrapper.innerHTML = pluginsDropdown(newInstalledPlugins, enabledPluginIds, idPrefix); addPluginsDropdownEventListener(idPrefix); + const selectedFilter = document.querySelector('[id^="plugin-filter-"].btn-light'); + const filter = selectedFilter ? selectedFilter.id.split('plugin-filter-')[1] : 'all'; + if (filter === 'not-installed') { + document.querySelector(`#${pluginId}`).remove(); + const pluginListWrapper = document.getElementById('plugin-list-wrapper'); + const pluginStorePaginationWrapper = document.getElementById('plugin-store-pagination-wrapper'); + const notIinstalledPlugins = allPlugins.filter((p1) => !newInstalledPlugins.map((p2) => p2.id).includes(p1.id)); + pluginListWrapper.innerHTML = renderPluginList(notIinstalledPlugins); + pluginStorePaginationWrapper.innerHTML = renderPageNumbers(notIinstalledPlugins); + addInstallButtonEventListener(notIinstalledPlugins); + addPaginationEventListener(notIinstalledPlugins); + } }); }); } diff --git a/scripts/content/settings.js b/scripts/content/settings.js index 60dc4db..7ced6b9 100644 --- a/scripts/content/settings.js +++ b/scripts/content/settings.js @@ -479,6 +479,9 @@ function autoSyncTabContent() { const autoResetTopNav = createSwitch('Auto Reset Top Navbar', 'Automatically reset the tone, writing style, and language to default when switching to new chats', 'autoResetTopNav', false, toggleTopNav, 'Requires Auto-Sync', !autoSync); content.appendChild(autoResetTopNav); + + const chatEndedSoundSwitch = createSwitch('Sound Alarm', 'Play a sound when the chat ends', 'chatEndedSound', false, null, 'Requires Auto-Sync', !autoSync); + content.appendChild(chatEndedSoundSwitch); }); return content; } @@ -1439,6 +1442,7 @@ function initializeSettings() { showExportButton: result.settings?.showExportButton !== undefined ? result.settings.showExportButton : true, showWordCount: result.settings?.showWordCount !== undefined ? result.settings.showWordCount : true, hideNewsletter: result.settings?.hideNewsletter !== undefined ? result.settings.hideNewsletter : false, + chatEndedSound: result.settings?.chatEndedSound !== undefined ? result.settings.chatEndedSound : false, customInstruction: result.settings?.customInstruction !== undefined ? result.settings.customInstruction : '', useCustomInstruction: result.settings?.useCustomInstruction !== undefined ? result.settings.useCustomInstruction : false, customConversationWidth: result.settings?.customConversationWidth !== undefined ? result.settings.customConversationWidth : false, diff --git a/sounds/beep.mp3 b/sounds/beep.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a6853a7d99d049ce172e500abcc4203c05c8a713 GIT binary patch literal 9244 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 literal 0 HcmV?d00001 From ba834cc87b20ba8fe5dc1efc7bdfb759ed634e54 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Sun, 24 Sep 2023 16:36:48 -0700 Subject: [PATCH 31/38] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20submit=20butt?= =?UTF-8?q?on=20+=20performance=20improvement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/.DS_Store | Bin 8196 -> 8196 bytes scripts/content/addToPromptLibrary.js | 2 +- scripts/content/continue.js | 5 +++-- scripts/content/customInstructions.js | 4 ++-- scripts/content/export.js | 2 +- scripts/content/global.js | 2 +- scripts/content/modelSwitcher.js | 2 +- scripts/content/regenerateResponse.js | 8 +++----- 9 files changed, 13 insertions(+), 14 deletions(-) diff --git a/manifest.json b/manifest.json index 9c324ee..06becaf 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.6.0", + "version": "5.6.1", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/.DS_Store b/scripts/.DS_Store index 8ee4f7b2365df6bcc6619a0496943ef7b9a292be..d74f9215ce3f62ac6f92f2c311010e47f5e96ea4 100644 GIT binary patch delta 48 ycmZp1XmQwZO^_-1@Z=kUa%^iQUY`h#pZrHq0mf7kQeqNdm|P$vwK+j(2`>QR`4R#E delta 48 ycmZp1XmQwZO^_*U{p1^ha%^w5J-s { addSubmitButtonToAllUserInputs(); }); - observer.observe(main.parentElement.parentElement, { childList: true, subtree: true }); + observer.observe(main, { childList: true, subtree: true }); } diff --git a/scripts/content/continue.js b/scripts/content/continue.js index de491b2..353b4b2 100644 --- a/scripts/content/continue.js +++ b/scripts/content/continue.js @@ -52,7 +52,8 @@ function promptDropdown() { textAreaElement.dispatchEvent(new Event('input', { bubbles: true })); textAreaElement.dispatchEvent(new Event('change', { bubbles: true })); if (e.shiftKey) return; - const curSubmitButton = document.querySelector('main').querySelector('form').querySelector('textarea ~ button'); + const curSubmitButton = document.querySelector('main form textarea ~ button'); + setTimeout(() => { curSubmitButton.click(); }, 300); @@ -247,5 +248,5 @@ function initializeContinue() { addContinueButton(); }, 500); }); - observer.observe(main.parentElement.parentElement, { childList: true, subtree: true }); + observer.observe(main, { childList: true, subtree: true }); } diff --git a/scripts/content/customInstructions.js b/scripts/content/customInstructions.js index a6e0165..02567e7 100644 --- a/scripts/content/customInstructions.js +++ b/scripts/content/customInstructions.js @@ -207,9 +207,9 @@ function upgradeCustomInstructions() { mutationsList.forEach((mutation) => { if (mutation.type === 'childList') { setTimeout(() => { + const customInstructionsDialog = document.querySelector('[role="dialog"][data-state="open"][tabindex="-1"]'); + if (!customInstructionsDialog) return; chrome.storage.local.get(['customInstructionProfiles'], (result) => { - const customInstructionsDialog = document.querySelector('[role="dialog"][data-state="open"][tabindex="-1"]'); - if (!customInstructionsDialog) return; const customInstructionsDialogHeader = customInstructionsDialog.querySelector('h2'); const existingProfileButtonWrapper = customInstructionsDialog.querySelector('#custom-instructions-profile-button-wrapper-settings'); const textAreaFields = customInstructionsDialog.querySelectorAll('textarea'); diff --git a/scripts/content/export.js b/scripts/content/export.js index 97041b7..f490e74 100644 --- a/scripts/content/export.js +++ b/scripts/content/export.js @@ -599,7 +599,7 @@ function initializeExport() { addExportButton(); }, 500); }); - observer.observe(main.parentElement.parentElement, { childList: true, subtree: true }); + observer.observe(main, { childList: true, subtree: true }); addExportAllButton(); } diff --git a/scripts/content/global.js b/scripts/content/global.js index a33727b..1051a3e 100644 --- a/scripts/content/global.js +++ b/scripts/content/global.js @@ -531,7 +531,7 @@ function replaceTextAreaElemet(settings) { let textAreaElement = inputForm.querySelector('textarea'); if (!textAreaElement) { - const textAreaElementWrapperHTML = '
    '; + const textAreaElementWrapperHTML = '
    '; // insert text area element wrapper in input form first child at the end inputForm.firstChild.insertAdjacentHTML('beforeend', textAreaElementWrapperHTML); textAreaElement = inputForm.querySelector('textarea'); diff --git a/scripts/content/modelSwitcher.js b/scripts/content/modelSwitcher.js index d60f775..b4ece3d 100644 --- a/scripts/content/modelSwitcher.js +++ b/scripts/content/modelSwitcher.js @@ -189,7 +189,7 @@ function addModelSwitcherEventListener(idPrefix, forceDark = false) { const textInput = document.querySelector('main form textarea'); if (selectedModel.slug !== 'gpt-4-code-interpreter' && textInput) { textInput.style.paddingLeft = '1rem'; - const uploadButton = textInput.parentElement.querySelector('[data-state=closed]'); + const uploadButton = textInput.parentElement.querySelector('button[aria-label="Attach files"]'); if (uploadButton) uploadButton.remove(); } const submitButton = document.querySelector('main form textarea ~ button'); diff --git a/scripts/content/regenerateResponse.js b/scripts/content/regenerateResponse.js index d585fb8..425af7f 100644 --- a/scripts/content/regenerateResponse.js +++ b/scripts/content/regenerateResponse.js @@ -108,11 +108,9 @@ function toggleOriginalRegenerateResponseButton() { inputFormActionWrapper.appendChild(newRegenerateResponseButton); } else { inputFormActionWrapper.appendChild(newRegenerateResponseButton); - chrome.storage.local.get(['settings'], (result) => { - if (!result.settings.selectedModel.slug.includes('plugins')) { - inputFormActionWrapper.appendChild(newContinueGeneratingButton); - } - }); + if (document.querySelector('#plugins-dropdown-wrapper-navbar').style.display === 'none') { + inputFormActionWrapper.appendChild(newContinueGeneratingButton); + } } } From cd881ec64f50f216285755ca49536ec4bf4e979d Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Sun, 24 Sep 2023 20:21:03 -0700 Subject: [PATCH 32/38] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20improve=20arkose?= =?UTF-8?q?=20trigger=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/content/api.js | 2 +- scripts/content/conversation.js | 5 +--- scripts/content/conversationList.js | 8 +----- scripts/content/global.js | 35 +++++++++++++++++++-------- scripts/content/initialize.js | 3 +-- scripts/content/modelSwitcher.js | 7 +++--- scripts/content/promptHistory.js | 6 ++--- scripts/content/regenerateResponse.js | 4 +-- 9 files changed, 38 insertions(+), 34 deletions(-) diff --git a/manifest.json b/manifest.json index 06becaf..4a166d2 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.6.1", + "version": "5.6.2", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/content/api.js b/scripts/content/api.js index b69fb1d..dc80f81 100644 --- a/scripts/content/api.js +++ b/scripts/content/api.js @@ -51,7 +51,7 @@ function generateChat(message, conversationId, messageId, parentMessageId, token return chrome.storage.local.get(['settings', 'enabledPluginIds', 'installedPlugins']).then((res) => chrome.storage.sync.get(['accessToken']).then((result) => { const payload = { action, - arkose_token: res.settings.selectedModel.slug.includes('gpt-4') && !res.settings.selectedModel.tags.includes('Unofficial') ? token : null, + arkose_token: res.settings.selectedModel.tags.includes('gpt4') && !res.settings.selectedModel.tags.includes('Unofficial') ? token : null, model: res.settings.selectedModel.slug, parent_message_id: parentMessageId, history_and_training_disabled: !saveHistory, diff --git a/scripts/content/conversation.js b/scripts/content/conversation.js index 7a53b97..5e8a7a0 100644 --- a/scripts/content/conversation.js +++ b/scripts/content/conversation.js @@ -75,9 +75,6 @@ function updateModel(modelSlug, fullConversation) { document.querySelector(`#language-list-dropdown li#language-selector-option-${languageCode}`)?.click(); document.querySelector(`#tone-list-dropdown li#tone-selector-option-${toneCode}`)?.click(); document.querySelector(`#writing-style-list-dropdown li#writing-style-selector-option-${writingStyleCode}`)?.click(); - if (selectedModel.slug.includes('gpt-4')) { - arkoseTrigger(); - } }); }); } @@ -352,7 +349,7 @@ function addConversationsEventListeners(conversationId) { saveButton.classList = 'btn flex justify-center gap-2 btn-primary mr-2'; saveButton.innerText = 'Save & Submit'; saveButton.addEventListener('click', () => { - if (result.settings.selectedModel.slug.includes('gpt-4')) { + if (result.settings.selectedModel.tags.includes('gpt4')) { arkoseTrigger(); } let newMessage = textArea.value; diff --git a/scripts/content/conversationList.js b/scripts/content/conversationList.js index fc976f7..4490801 100644 --- a/scripts/content/conversationList.js +++ b/scripts/content/conversationList.js @@ -471,7 +471,7 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode submitButton.innerHTML = ''; return; } - if (arkoseToken || !settings.selectedModel.slug.includes('gpt-4') || settings.selectedModel.tags.includes('Unofficial')) { + if (arkoseToken || !settings.selectedModel.tags.includes('gpt4') || settings.selectedModel.tags.includes('Unofficial')) { clearInterval(interval); scrolUpDetected = false; const curSubmitButton = document.querySelector('main').querySelector('form').querySelector('textarea ~ button'); @@ -890,9 +890,6 @@ function overrideSubmitForm() { chrome.storage.local.get(['settings', 'conversations', 'models'], ({ settings, conversations, models, }) => { - if (settings.selectedModel.slug.includes('gpt-4')) { - arkoseTrigger(); - } const templateWords = textAreaElement.value.match(/{{(.*?)}}/g); if (settings.promptTemplate && templateWords?.length > 0) { // open template words modal and wait for user to select a word. the when user submit, submit the input form with the replacement @@ -1060,9 +1057,6 @@ ${settings.autoSplitChunkPrompt}`; submitButtonClone.type = 'button'; submitButtonClone.addEventListener('click', () => { chrome.storage.local.get(['settings'], ({ settings }) => { - if (settings.selectedModel.slug.includes('gpt-4')) { - arkoseTrigger(); - } const textAreaElement = inputForm.querySelector('textarea'); if (isGenerating) return; const templateWords = textAreaElement.value.match(/{{(.*?)}}/g); diff --git a/scripts/content/global.js b/scripts/content/global.js index 1051a3e..b53d067 100644 --- a/scripts/content/global.js +++ b/scripts/content/global.js @@ -17,6 +17,8 @@ let disableTextInput = false;// to prevent input from showing extra line right b let chatStreamIsClosed = false; // to force close the chat stream // eslint-disable-next-line prefer-const let shiftKeyPressed = false; +// eslint-disable-next-line prefer-const +let textAreaElementOldValue = ''; // chrome.storage.local.get(['environment'], (result) => { // if (result.environment === 'development') { // chrome.storage.onChanged.addListener((changes, namespace) => { @@ -502,16 +504,18 @@ function addArkoseScript() { document.body.appendChild(arkoseApiScript); }, 500); } -function arkoseTrigger(trigger = true) { - window.localStorage.removeItem('arkoseToken'); - const inputForm = document.querySelector('main form'); - if (!inputForm) return; - if (!inputForm.querySelector('#enforcement-trigger')) { - inputForm.firstChild.insertAdjacentHTML('beforeend', ''); - } - if (trigger) { - inputForm.querySelector('#enforcement-trigger').click(); - } +function arkoseTrigger() { + chrome.storage.local.get('settings', ({ settings }) => { + if (settings.selectedModel.tags.includes('gpt4')) { + window.localStorage.removeItem('arkoseToken'); + const inputForm = document.querySelector('main form'); + if (!inputForm) return; + if (!inputForm.querySelector('#enforcement-trigger')) { + inputForm.firstChild.insertAdjacentHTML('beforeend', ''); + } + inputForm.querySelector('#enforcement-trigger').click(); + } + }); } function replaceTextAreaElemet(settings) { @@ -548,6 +552,17 @@ function replaceTextAreaElemet(settings) { newTextAreaElement.style.paddingRight = '40px'; newTextAreaElement.style.overflowY = 'hidden'; + // keydown is triggered before input event and before value is changed. + newTextAreaElement.addEventListener('input', (event) => { + // console.warn('input event', 'old: ', textAreaElementOldValue, 'new: ', newTextAreaElement.value); + if (textAreaElementOldValue === '' && newTextAreaElement.value !== textAreaElementOldValue) { + textAreaElementOldValue = newTextAreaElement.value; + arkoseTrigger(); + } else if (newTextAreaElement.value !== textAreaElementOldValue) { + textAreaElementOldValue = newTextAreaElement.value; + } + }); + newTextAreaElement.addEventListener('keydown', textAreaElementKeydownEventListenerSync); // also async newTextAreaElement.addEventListener('keydown', (event) => { diff --git a/scripts/content/initialize.js b/scripts/content/initialize.js index 6d22225..e2f0fee 100644 --- a/scripts/content/initialize.js +++ b/scripts/content/initialize.js @@ -1,4 +1,4 @@ -/* global navigation, initializeStorage, cleanNav, initializeContinue, initializeExport, initializeSettings, initializePromptHistory, initializePromptLibrary, initializeNewsletter, initializeAutoSave, addNavToggleButton, initializeAnnouncement, initializeReleaseNote, initializeReplaceDeleteConversationButton, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, updateNewChatButtonNotSynced, addAsyncInputEvents, addDevIndicator, addExpandButton, openLinksInNewTab, arkoseTrigger, initializeKeyboardShortcuts, addArkoseCallback, addQuickAccessMenuEventListener, upgradeCustomInstructions, watchError, showAutoSyncToast, addAutoSyncToggleButton, addSounds */ +/* global navigation, initializeStorage, cleanNav, initializeContinue, initializeExport, initializeSettings, initializePromptHistory, initializePromptLibrary, initializeNewsletter, initializeAutoSave, addNavToggleButton, initializeAnnouncement, initializeReleaseNote, initializeReplaceDeleteConversationButton, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, updateNewChatButtonNotSynced, addAsyncInputEvents, addDevIndicator, addExpandButton, openLinksInNewTab, initializeKeyboardShortcuts, addArkoseCallback, addQuickAccessMenuEventListener, upgradeCustomInstructions, watchError, showAutoSyncToast, addAutoSyncToggleButton, addSounds */ // eslint-disable-next-line no-unused-vars function initialize() { @@ -26,7 +26,6 @@ function initialize() { addDevIndicator(); initializeKeyboardShortcuts(); addSounds(); - arkoseTrigger(false); addArkoseCallback(); // showAutoSyncToast(); setTimeout(() => { diff --git a/scripts/content/modelSwitcher.js b/scripts/content/modelSwitcher.js index b4ece3d..75bf266 100644 --- a/scripts/content/modelSwitcher.js +++ b/scripts/content/modelSwitcher.js @@ -1,5 +1,5 @@ /* eslint-disable no-unused-vars */ -/* global getInstalledPlugins, initializeRegenerateResponseButton, arkoseTrigger */ +/* global getInstalledPlugins, initializeRegenerateResponseButton */ // eslint-disable-next-line no-unused-vars function modelSwitcher(models, selectedModel, idPrefix, customModels, autoSync, forceDark = false) { if (selectedModel.slug === 'gpt-4-code-interpreter' && autoSync) { @@ -194,7 +194,7 @@ function addModelSwitcherEventListener(idPrefix, forceDark = false) { } const submitButton = document.querySelector('main form textarea ~ button'); if (submitButton && !submitButton.disabled) { - if (selectedModel.slug.includes('gpt-4')) { + if (selectedModel.tags.includes('gpt4')) { submitButton.style.backgroundColor = '#AB68FF'; } else { submitButton.style.backgroundColor = '#19C37D'; @@ -202,12 +202,11 @@ function addModelSwitcherEventListener(idPrefix, forceDark = false) { } } chrome.storage.local.set({ settings: { ...settings, selectedModel } }, () => { - if (selectedModel.slug.includes('gpt-4') && !selectedModel.tags.includes('Unofficial')) { + if (selectedModel.tags.includes('gpt4') && !selectedModel.tags.includes('Unofficial')) { const arkoseIframeWrapper = document.querySelector('[class="arkose-35536E1E-65B4-4D96-9D97-6ADB7EFF8147-wrapper"]'); if (!arkoseIframeWrapper) { window.location.reload(); } - arkoseTrigger(); } // focus on input const textInput = document.querySelector('main form textarea'); diff --git a/scripts/content/promptHistory.js b/scripts/content/promptHistory.js index af32b6c..e52de44 100644 --- a/scripts/content/promptHistory.js +++ b/scripts/content/promptHistory.js @@ -507,7 +507,7 @@ function textAreaElementInputEventListener(event) { const { settings } = result; const { selectedModel } = settings; submitButton.disabled = false; - if (selectedModel.slug.includes('gpt-4')) { + if (selectedModel.tags.includes('gpt4')) { submitButton.style.backgroundColor = '#AB68FF'; } else { submitButton.style.backgroundColor = '#19C37D'; @@ -549,7 +549,7 @@ function textAreaElementInputEventListener(event) { }); } // Add keyboard event listener to text area -function textAreaElementKeydownEventListenerASync(event) { +function textAreaElementKeydownEventListenerAsync(event) { const textAreaElement = event.target; if (event.key === 'Enter' && event.which === 13 && !event.shiftKey && !isGenerating) { @@ -806,6 +806,6 @@ function addAsyncInputEvents() { if (textAreaElement) { textAreaElement.addEventListener('input', textAreaElementInputEventListener); - textAreaElement.addEventListener('keydown', textAreaElementKeydownEventListenerASync); + textAreaElement.addEventListener('keydown', textAreaElementKeydownEventListenerAsync); } } diff --git a/scripts/content/regenerateResponse.js b/scripts/content/regenerateResponse.js index 425af7f..ab6e999 100644 --- a/scripts/content/regenerateResponse.js +++ b/scripts/content/regenerateResponse.js @@ -46,7 +46,7 @@ function toggleOriginalRegenerateResponseButton() { newRegenerateResponseButton.innerHTML = ' Regenerate'; newRegenerateResponseButton.addEventListener('click', () => { chrome.storage.local.get(['conversations', 'settings', 'models'], (result) => { - if (result.settings.selectedModel.slug.includes('gpt-4')) { + if (result.settings.selectedModel.tags.includes('gpt4')) { arkoseTrigger(); } const { pathname } = new URL(window.location.toString()); @@ -82,7 +82,7 @@ function toggleOriginalRegenerateResponseButton() { newContinueGeneratingButton.innerHTML = ' Continue'; newContinueGeneratingButton.addEventListener('click', () => { chrome.storage.local.get(['conversations', 'settings', 'models'], (result) => { - if (result.settings.selectedModel.slug.includes('gpt-4')) { + if (result.settings.selectedModel.tags.includes('gpt4')) { arkoseTrigger(); } const { pathname } = new URL(window.location.toString()); From 2e2440c1d5296e22e85380ae6123cd54a883d05d Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Mon, 25 Sep 2023 12:21:43 -0700 Subject: [PATCH 33/38] =?UTF-8?q?fix:=20=F0=9F=90=9B=20bug=20fixes=20and?= =?UTF-8?q?=20performance=20improvement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/.DS_Store | Bin 8196 -> 8196 bytes scripts/content/addToPromptLibrary.js | 2 +- scripts/content/autoSave.js | 2 +- scripts/content/categoryList.js | 21 +++++++++++++++++++++ scripts/content/conversationList.js | 2 +- scripts/content/global.js | 4 ++++ sounds/beep.mp3 | Bin 9244 -> 81120 bytes 8 files changed, 29 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index 4a166d2..0c3e283 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.6.2", + "version": "5.6.3", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/.DS_Store b/scripts/.DS_Store index d74f9215ce3f62ac6f92f2c311010e47f5e96ea4..b1b449d8dd70a562efeb8a149389f8e91ea813ff 100644 GIT binary patch delta 15 WcmZp1XmQwZO^_+oWAhC`9X { diff --git a/scripts/content/global.js b/scripts/content/global.js index b53d067..0434e1f 100644 --- a/scripts/content/global.js +++ b/scripts/content/global.js @@ -521,6 +521,9 @@ function arkoseTrigger() { function replaceTextAreaElemet(settings) { const inputForm = document.querySelector('main form'); if (!inputForm) { return false; } + if (!inputForm.querySelector('#enforcement-trigger')) { + inputForm.firstChild.insertAdjacentHTML('beforeend', ''); + } if (settings.customConversationWidth) { inputForm.style = `${inputForm.style.cssText}; max-width:${settings.conversationWidth}%;`; } @@ -568,6 +571,7 @@ function replaceTextAreaElemet(settings) { newTextAreaElement.addEventListener('keydown', (event) => { if (event.key === 'Enter' && event.which === 13 && !event.shiftKey && !isGenerating) { disableTextInput = true; + textAreaElementOldValue = ''; if (newTextAreaElement.value.trim().length === 0) { event.preventDefault(); event.stopPropagation(); diff --git a/sounds/beep.mp3 b/sounds/beep.mp3 index a6853a7d99d049ce172e500abcc4203c05c8a713..5e42648bd2cd007ab1aeb4289516d3c4ed282958 100644 GIT binary patch literal 81120 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 NutZ^eVIhT1=TACP!8iZ_ literal 9244 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 From 52f8ea55ce032a4fbb90aca3d2301a734e50f606 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Mon, 25 Sep 2023 16:50:27 -0700 Subject: [PATCH 34/38] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20gpt-4=20bug?= =?UTF-8?q?=20when=20auto-sync=20is=20off?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/.DS_Store | Bin 8196 -> 8196 bytes scripts/content/autoSave.js | 4 ++-- scripts/content/conversation.js | 14 +++++++------- scripts/content/conversationList.js | 2 +- scripts/content/initialize.js | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/manifest.json b/manifest.json index 0c3e283..1ab6382 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.6.3", + "version": "5.6.4", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/.DS_Store b/scripts/.DS_Store index b1b449d8dd70a562efeb8a149389f8e91ea813ff..f87c65f230db68856bfc35a6794b8afde989cf51 100644 GIT binary patch delta 15 WcmZp1XmQwZO^_+eaq|s99X (m.message?.role === 'assistant' || m.message?.author.role === 'assistant') && m.message.recipient === 'all').length === 1) { // only one assistant message + if (generateTitle && existingConversation.title === 'New chat' && mapping.length < 5 && mapping.filter((m) => (m.message?.role === 'assistant' || m.message?.author?.role === 'assistant') && m.message.recipient === 'all').length === 1) { // only one assistant message if (settings.saveHistory) { - const systemMessage = mapping.find((m) => m.message?.role === 'system' || m.message?.author.role === 'system'); + const systemMessage = mapping.find((m) => m.message?.role === 'system' || m.message?.author?.role === 'system'); generateTitleForConversation(existingConversation.id, message.id, systemMessage?.message?.metadata?.user_context_message_data); } } else if (settings.conversationTimestamp) { // === updated diff --git a/scripts/content/conversation.js b/scripts/content/conversation.js index 5e8a7a0..938ebba 100644 --- a/scripts/content/conversation.js +++ b/scripts/content/conversation.js @@ -175,7 +175,7 @@ function loadConversation(conversationId, searchValue = '', focusOnInput = true) let currentNodeId = fullConversation.current_node; // mapping: {id: message, id: message } // remove all the nodes that are not user or assistant - // (node) => ['user', 'assistant'].includes(node.author.role) && node.recipient === 'all'); + // (node) => ['user', 'assistant'].includes(node.author?.role) && node.recipient === 'all'); while (currentNodeId) { const currentNode = fullConversation.mapping[currentNodeId]; @@ -500,10 +500,10 @@ function addConversationsEventListeners(conversationId) { const conversation = result.conversations[conversationId]; // while parent is not user, keep going up let parentId = conversation.mapping[messageId].parent; - let parentRole = conversation.mapping[parentId].message.author.role || conversation.mapping[parentId].message.role; + let parentRole = conversation.mapping[parentId].message.author?.role || conversation.mapping[parentId].message.role; while (parentRole !== 'user') { parentId = conversation.mapping[parentId].parent; - parentRole = conversation.mapping[parentId].message.author.role || conversation.mapping[parentId].message.role; + parentRole = conversation.mapping[parentId].message.author?.role || conversation.mapping[parentId].message.role; } const parentMessage = conversation.mapping[parentId].message.content.parts.join('\n'); const codeHeaders = document.querySelectorAll('#code-header'); @@ -530,10 +530,10 @@ function addConversationsEventListeners(conversationId) { chrome.storage.local.get(['conversations', 'settings'], (result) => { const conversation = result.conversations[conversationId]; let parentId = conversation.mapping[messageId].parent; - let parentRole = conversation.mapping[parentId].message.author.role || conversation.mapping[parentId].message.role; + let parentRole = conversation.mapping[parentId].message.author?.role || conversation.mapping[parentId].message.role; while (parentRole !== 'user') { parentId = conversation.mapping[parentId].parent; - parentRole = conversation.mapping[parentId].message.author.role || conversation.mapping[parentId].message.role; + parentRole = conversation.mapping[parentId].message.author?.role || conversation.mapping[parentId].message.role; } const parentMessage = conversation.mapping[parentId].message.content.parts.join('\n'); const newElement = element.cloneNode(true); @@ -556,10 +556,10 @@ function addConversationsEventListeners(conversationId) { getConversation(conversationId).then((conversation) => { const { message } = conversation.mapping[messageId]; let parentId = conversation.mapping[messageId].parent; - let parentRole = conversation.mapping[parentId].message.author.role || conversation.mapping[parentId].message.role; + let parentRole = conversation.mapping[parentId].message.author?.role || conversation.mapping[parentId].message.role; while (parentRole !== 'user') { parentId = conversation.mapping[parentId].parent; - parentRole = conversation.mapping[parentId].message.author.role || conversation.mapping[parentId].message.role; + parentRole = conversation.mapping[parentId].message.author?.role || conversation.mapping[parentId].message.role; } const parentMessage = conversation.mapping[parentId].message.content.parts.join('\n'); const text = `${result.settings.copyMode ? `##USER:\n${parentMessage}\n\n##ASSISTANT:\n` : ''}${message.content.parts.join('\n')}`; diff --git a/scripts/content/conversationList.js b/scripts/content/conversationList.js index d455a0c..cb802b9 100644 --- a/scripts/content/conversationList.js +++ b/scripts/content/conversationList.js @@ -662,7 +662,7 @@ function submitChat(userInput, conversation, messageId, parentId, settings, mode if (!conversation?.id || userChatSavedLocally) { // save assistant chat locally finalMessage = message; - if (!assistantChatSavedLocally && (message.role === 'assistant' || message.author.role === 'assistant') && message.recipient === 'all') { + if (!assistantChatSavedLocally && (message.role === 'assistant' || message.author?.role === 'assistant') && message.recipient === 'all') { assistantChatSavedLocally = true; const tempId = setInterval(() => { if (userChatIsActuallySaved) { diff --git a/scripts/content/initialize.js b/scripts/content/initialize.js index e2f0fee..71b48df 100644 --- a/scripts/content/initialize.js +++ b/scripts/content/initialize.js @@ -26,7 +26,6 @@ function initialize() { addDevIndicator(); initializeKeyboardShortcuts(); addSounds(); - addArkoseCallback(); // showAutoSyncToast(); setTimeout(() => { chrome.storage.local.get(['settings'], (result) => { @@ -34,6 +33,7 @@ function initialize() { if ((typeof settings?.autoSync === 'undefined' || settings?.autoSync) && !window.location.pathname.startsWith('/share/')) { // if (typeof settings?.autoSync === 'undefined' || settings?.autoSync) { initializeAutoSave(); + addArkoseCallback(); } else { addAutoSyncToggleButton(); initializeCopyAndCounter(); From d7b2007a01f40fe5826b5f5ec571a389dd6c9fc2 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Mon, 25 Sep 2023 22:05:34 -0700 Subject: [PATCH 35/38] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20Disable=20Drag=20w?= =?UTF-8?q?hen=20editing=20conversation/forlder=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/.DS_Store | Bin 8196 -> 8196 bytes scripts/content/conversationList.js | 26 +++++++++++++------------- scripts/content/folderElement.js | 3 ++- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/manifest.json b/manifest.json index 1ab6382..d71268c 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.6.4", + "version": "5.6.5", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/.DS_Store b/scripts/.DS_Store index f87c65f230db68856bfc35a6794b8afde989cf51..2e885494afc83d2732d1c1cdb4e16f676cc385fe 100644 GIT binary patch delta 14 VcmZp1XmQwZU63(w^9?~AJ^(Ay1xWw^ delta 14 VcmZp1XmQwZU69dn^9?~AJ^(9n1vvl! diff --git a/scripts/content/conversationList.js b/scripts/content/conversationList.js index cb802b9..91c1e48 100644 --- a/scripts/content/conversationList.js +++ b/scripts/content/conversationList.js @@ -37,7 +37,7 @@ function removeOriginalConversationList() { }, direction: 'vertical', invertSwap: true, - draggable: '[id^="conversation-button-"], [id^="wrapper-folder-"]:not([id="wrapper-folder-trash"]', + draggable: '[id^="conversation-button-"]:not(:has([id^=conversation-rename-])), [id^="wrapper-folder-"]:not([id="wrapper-folder-trash"]):not(:has([id^=rename-folder-])):not(:has([id^=conversation-rename-]))', onEnd: (event) => { const { item, to, oldDraggableIndex, newDraggableIndex, @@ -894,13 +894,13 @@ function overrideSubmitForm() { if (settings.promptTemplate && templateWords?.length > 0) { // open template words modal and wait for user to select a word. the when user submit, submit the input form with the replacement createTemplateWordsModal(templateWords); - const firstTemplateWordInput = document.querySelector('[id^=template-input-]'); - if (firstTemplateWordInput) { - firstTemplateWordInput.focus(); - setTimeout(() => { + setTimeout(() => { + const firstTemplateWordInput = document.querySelector('[id^=template-input-]'); + if (firstTemplateWordInput) { + firstTemplateWordInput.focus(); firstTemplateWordInput.value = ''; - }, 100); - } + } + }, 100); } else { const { pathname } = new URL(window.location.toString()); // const isSharedConversation = pathname.startsWith('/share/') && window.location.href.endsWith('/continue'); @@ -1063,13 +1063,13 @@ ${settings.autoSplitChunkPrompt}`; if (settings.promptTemplate && templateWords?.length > 0) { // open template words modal and wait for user to select a word. the when user submit, submit the input form with the replacement createTemplateWordsModal(templateWords); - const firstTemplateWordInput = document.querySelector('[id^=template-input-]'); - if (firstTemplateWordInput) { - firstTemplateWordInput.focus(); - setTimeout(() => { + setTimeout(() => { + const firstTemplateWordInput = document.querySelector('[id^=template-input-]'); + if (firstTemplateWordInput) { + firstTemplateWordInput.focus(); firstTemplateWordInput.value = ''; - }, 100); - } + } + }, 100); } else { textAreaElement.style.height = '56px'; if (textAreaElement.value.trim().length === 0) return; diff --git a/scripts/content/folderElement.js b/scripts/content/folderElement.js index ed4b928..a20fbfc 100644 --- a/scripts/content/folderElement.js +++ b/scripts/content/folderElement.js @@ -98,9 +98,10 @@ function createFolder(folder, conversationTimestamp, conversations = [], isNewFo // add checkbox // addCheckboxToConversationElement(conversationElement, conversation); const sortable = Sortable.create(folderContent, { - draggable: '[id^="conversation-button-"]', + draggable: '[id^="conversation-button-"]:not(:has([id^=conversation-rename-]))', direction: 'vertical', invertSwap: true, + disabled: false, // multiDrag: true, // selectedClass: 'multi-drag-selected', // handle: '[id^="checkbox-wrapper-"], [id^="conversation-button-"]', From 35c6a67839cb383c9e7ea24f9d01e561571f37ef Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Tue, 26 Sep 2023 09:34:55 -0700 Subject: [PATCH 36/38] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20fix=20for=20auth?= =?UTF-8?q?=20token=20to=20be=20renewed=20correctly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- scripts/.DS_Store | Bin 8196 -> 8196 bytes scripts/background/initialize.js | 6 ++++-- scripts/content/addToPromptLibrary.js | 1 + scripts/content/arkose.js | 9 ++++++--- scripts/content/customInstructions.js | 1 + scripts/content/global.js | 4 +++- scripts/content/initialize.js | 2 +- scripts/content/promptLibrary.js | 2 +- scripts/content/settings.js | 1 + scripts/interceptor/interceptor.js | 1 + 11 files changed, 20 insertions(+), 9 deletions(-) diff --git a/manifest.json b/manifest.json index d71268c..7e2134c 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Superpower ChatGPT", - "version": "5.6.5", + "version": "5.6.6", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzG6ZR+j3lpHF0XrDLIXdrk25idObfq+RK7WM+pIaQmDO2nM5Y+SZJJbFwyxjWX+3V6XOgS5v9Lpnqg46OJ/W9Q5i23Usx1MXgaJBTlEFz0XG+PYK6BElhc9itS7m6oCLknin97a533tusXmm8zW7kaDGy8vycMDY6Ffbqa3sn0PqZ8bXUlAjgO91dQcB8EtlT906hwhZjtfEYvp2hdxYkRFxfuaR1WMLkxttVXv506RXJowxq0LO3aqj83QeJoXkQF1wbzCxYO1VpVGEmYIQxIKw/csusZNZs8gwJrIWtOzhMgDNOFzXNeZl0ASgoj2M9UsZp+Dunn57VT8tQyaE6QIDAQAB", "description": "ChatGPT with superpowers! Sync/search history locally, create folders, export all chats, pin messages, access thousands of prompts", "icons": { diff --git a/scripts/.DS_Store b/scripts/.DS_Store index 2e885494afc83d2732d1c1cdb4e16f676cc385fe..c42d0885c33ddec68a1890e4e7d2c79df2c5e5fb 100644 GIT binary patch delta 15 WcmZp1XmQwZO^_*@aq|s99X_K delta 15 WcmZp1XmQwZO^_)oaPtj89X { if (request.setUninstallURL) { chrome.runtime.setUninstallURL(`${API_URL}/gptx/uninstall?p=${request.userId}`); @@ -90,19 +91,20 @@ function registerUser(data) { }); } chrome.runtime.onMessage.addListener( + // eslint-disable-next-line no-unused-vars (request, sender, sendResponse) => { if (request.authReceived) { const data = request.detail; chrome.storage.sync.get(['user_id', 'openai_id', 'version', 'avatar', 'lastUserSync', 'accessToken'], (result) => { // or conditionor const { version } = chrome.runtime.getManifest(); - const shouldRegister = !result.lastUserSync || result.lastUserSync < Date.now() - 1000 * 60 * 60 * 24 || !result.avatar || !result.user_id || !result.openai_id || !result.accessToken + || result.accessToken !== `Bearer ${data.accessToken}` || result.version !== version; if (result.openai_id !== data.user.id) { @@ -120,7 +122,7 @@ chrome.runtime.onMessage.addListener( readNewsletterIds, promptChains, userInputValueHistory, - }, () => registerUser(data)); + }); }); }); // remove any key from syncstorage except the following keys: lastSeenAnnouncementId, lastSeenNewsletterId, lastSeenReleaseNoteVersion diff --git a/scripts/content/addToPromptLibrary.js b/scripts/content/addToPromptLibrary.js index 5b7bac7..f50fe83 100644 --- a/scripts/content/addToPromptLibrary.js +++ b/scripts/content/addToPromptLibrary.js @@ -83,6 +83,7 @@ function validateFields() { return valid; } +// eslint-disable-next-line no-unused-vars function openSubmitPromptModal(text = '', modelSlug = '', promptId = null, title = '', categories = [], language = '', refreshPromptLibrary = false, hideFullPrompt = false) { selectedCategories = categories; const submitPromptModal = document.createElement('div'); diff --git a/scripts/content/arkose.js b/scripts/content/arkose.js index 73019b2..6fe85ee 100644 --- a/scripts/content/arkose.js +++ b/scripts/content/arkose.js @@ -1,3 +1,6 @@ +/* eslint-disable no-console */ +/* eslint-disable no-unused-vars */ +// eslint-disable-next-line func-names window.useArkoseSetupEnforcement = function (e) { // console.warn('arkoseSetupEnforcement', e); e.setConfig({ @@ -6,13 +9,13 @@ window.useArkoseSetupEnforcement = function (e) { window.localStorage.setItem('arkoseToken', x.token); }, onError(x) { - // console.warn('onError', x); + console.warn('onError', x); }, onFailed(x) { - // console.warn('onFailed', x); + console.warn('onFailed', x); }, onShown(x) { - // console.warn('onShown', x); + console.warn('onShown', x); }, }); }; diff --git a/scripts/content/customInstructions.js b/scripts/content/customInstructions.js index 02567e7..8bb4274 100644 --- a/scripts/content/customInstructions.js +++ b/scripts/content/customInstructions.js @@ -198,6 +198,7 @@ function profileDropdownButton(customInstructionProfiles, placement) { }); return button; } +// eslint-disable-next-line no-unused-vars function upgradeCustomInstructions() { // observe the body and wait for the custom instructions dialog to be added // there should be a div with role="dialog" and a h2 with text "Custom instructions" diff --git a/scripts/content/global.js b/scripts/content/global.js index 0434e1f..bbb19b9 100644 --- a/scripts/content/global.js +++ b/scripts/content/global.js @@ -69,6 +69,7 @@ function initializeStorage() { // if conversationIds are not strings (they are objects), get the id property flattenedConversationIds.forEach((conversationId, index) => { if (typeof conversationId !== 'string') { + // eslint-disable-next-line no-console console.warn('Bad type. Please contact the developer!'); } }); @@ -76,12 +77,13 @@ function initializeStorage() { // if there are duplicates, remove them const uniqueConversationIds = [...new Set(flattenedConversationIds)]; if (uniqueConversationIds.length !== flattenedConversationIds.length) { + // eslint-disable-next-line no-console console.warn('Not unique. Please contact the developer!'); } } }); return chrome.storage.local.get(['selectedConversations', 'conversationsOrder', 'customModels', 'conversations']).then((result) => { - const localConversationsOrder = result.conversationsOrder || []; + const localConversationsOrder = (result.conversationsOrder || []).filter((conversationOrder) => typeof conversationOrder !== 'string' || conversationOrder.length > 6); const allConversationKeys = Object.keys(result.conversations || []); return chrome.storage.sync.get(['conversationsOrder']).then((res) => { const syncConversationsOrder = res.conversationsOrder || []; diff --git a/scripts/content/initialize.js b/scripts/content/initialize.js index 71b48df..78e5d07 100644 --- a/scripts/content/initialize.js +++ b/scripts/content/initialize.js @@ -1,4 +1,4 @@ -/* global navigation, initializeStorage, cleanNav, initializeContinue, initializeExport, initializeSettings, initializePromptHistory, initializePromptLibrary, initializeNewsletter, initializeAutoSave, addNavToggleButton, initializeAnnouncement, initializeReleaseNote, initializeReplaceDeleteConversationButton, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, updateNewChatButtonNotSynced, addAsyncInputEvents, addDevIndicator, addExpandButton, openLinksInNewTab, initializeKeyboardShortcuts, addArkoseCallback, addQuickAccessMenuEventListener, upgradeCustomInstructions, watchError, showAutoSyncToast, addAutoSyncToggleButton, addSounds */ +/* global navigation, initializeStorage, cleanNav, initializeContinue, initializeExport, initializeSettings, initializePromptHistory, initializePromptLibrary, initializeNewsletter, initializeAutoSave, addNavToggleButton, initializeAnnouncement, initializeReleaseNote, initializeReplaceDeleteConversationButton, initializeCopyAndCounter, initializeAddToPromptLibrary, initializeTimestamp, updateNewChatButtonNotSynced, addAsyncInputEvents, addDevIndicator, addExpandButton, openLinksInNewTab, initializeKeyboardShortcuts, addArkoseCallback, addQuickAccessMenuEventListener, upgradeCustomInstructions, addAutoSyncToggleButton, addSounds */ // eslint-disable-next-line no-unused-vars function initialize() { diff --git a/scripts/content/promptLibrary.js b/scripts/content/promptLibrary.js index cec8ba7..072090c 100644 --- a/scripts/content/promptLibrary.js +++ b/scripts/content/promptLibrary.js @@ -399,7 +399,7 @@ function promptLibraryListComponent(libraryData, loading = false) { }); chrome.storage.sync.get(['user_id'], (res) => { - if (res.user_id === libraryPrompt.created_by.id) { + if (res.user_id === libraryPrompt?.created_by?.id) { libraryItemActionButtons.appendChild(libraryItemDeleteButton); libraryItemActionButtons.appendChild(libraryItemEditButton); } diff --git a/scripts/content/settings.js b/scripts/content/settings.js index 7ced6b9..fe6ea71 100644 --- a/scripts/content/settings.js +++ b/scripts/content/settings.js @@ -533,6 +533,7 @@ function sortConversationsByTimestamp(conversationsOrder, conversations, byUpdat const newConversationsOrder = [...folders, ...conversationIds, trash]; return newConversationsOrder; } +// eslint-disable-next-line no-unused-vars function toggleKeepFoldersAtTheTop(isChecked) { chrome.storage.local.get(['settings'], (result) => { const { settings } = result; diff --git a/scripts/interceptor/interceptor.js b/scripts/interceptor/interceptor.js index a4b8c56..f706fbb 100644 --- a/scripts/interceptor/interceptor.js +++ b/scripts/interceptor/interceptor.js @@ -1,6 +1,7 @@ // intercept fetch requests const originalFetch = window.fetch; +// eslint-disable-next-line func-names window.fetch = async function (...args) { const input = args[0]; let url; From c47a2eb822c932f2f18a8ec1e1038ffd669a3cc4 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Wed, 15 Nov 2023 01:38:58 -0800 Subject: [PATCH 37/38] Update README.md Signed-off-by: Saeed Ezzati --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a97d1e..96864cb 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,11 @@ [link-chrome]: https://chrome.google.com/webstore/detail/superpower-chatgpt/amhmeenmapldpjdedekalnfifgnpfnkc "Chrome Web Store" [link-firefox]: https://addons.mozilla.org/en-US/firefox/addon/superpower-chatgpt "Firefox Addons" +

    + +
    + + # Superpower ChatGPT ⚡️ A browser extension to add the missing features like **Folders**, **Search**, and **Community Prompts** to ChatGPT @@ -15,9 +20,14 @@ A browser extension to add the missing features like **Folders**, **Search**, an [![Mozilla Add-on](https://img.shields.io/amo/users/superpower-chatgpt.svg)]([link-firefox]) [![Mozilla Add-on](https://img.shields.io/amo/rating/superpower-chatgpt.svg)]([link-firefox]) -[![Discord](https://img.shields.io/discord/1060110102188797992?color=green&label=Discord&logo=discord)](https://discord.gg/superpower-chatgpt-1083455984489476220) + [![Twitter Follow](https://img.shields.io/twitter/follow/eeeziii?label=follow%20me&style=social)](https://twitter.com/intent/user?screen_name=eeeziii) +#### To **report a bug** or make a **feature request** please join our Discord + +[![Discord](https://img.shields.io/discord/1060110102188797992?color=green&label=Discord&logo=discord)](https://discord.gg/superpower-chatgpt-1083455984489476220) + +#### Please get the latest and most up to date version of the extension with all the new features from below links
    [Chrome][link-chrome] From d10a491cd1e45104ee31b8c43816dafb2ccd4ed4 Mon Sep 17 00:00:00 2001 From: Saeed Ezzati Date: Tue, 18 Jun 2024 15:16:25 -0700 Subject: [PATCH 38/38] Update README.md Signed-off-by: Saeed Ezzati --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 96864cb..a5a0aea 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ A browser extension to add the missing features like **Folders**, **Search**, an
    -https://user-images.githubusercontent.com/574142/232172841-50f1b114-ef47-4533-a6e6-fd630e7b30a2.mov +https://github.com/saeedezzati/superpower-chatgpt/assets/574142/b4a6d746-b7ea-4c49-ac08-ecaca5f74264 # Features