diff --git a/src/_locales/en/main.json b/src/_locales/en/main.json index ddefd432..984a7167 100644 --- a/src/_locales/en/main.json +++ b/src/_locales/en/main.json @@ -104,5 +104,6 @@ "Max Conversation Length": "Max Conversation Length", "Always pin the floating window": "Always pin the floating window", "Export": "Export", - "Always Create New Conversation Window": "Always Create New Conversation Window" + "Always Create New Conversation Window": "Always Create New Conversation Window", + "Please keep this tab open. You can now use the web mode of ChatGPTBox": "Please keep this tab open. You can now use the web mode of ChatGPTBox" } diff --git a/src/_locales/zh-hans/main.json b/src/_locales/zh-hans/main.json index 14295602..a0caf2b1 100644 --- a/src/_locales/zh-hans/main.json +++ b/src/_locales/zh-hans/main.json @@ -104,5 +104,6 @@ "Max Conversation Length": "对话处理的最大长度", "Always pin the floating window": "总是固定浮动窗口", "Export": "导出", - "Always Create New Conversation Window": "总是创建新的对话窗口" + "Always Create New Conversation Window": "总是创建新的对话窗口", + "Please keep this tab open. You can now use the web mode of ChatGPTBox": "请保持这个页面打开, 现在你可以使用ChatGPTBox的网页版模式" } diff --git a/src/_locales/zh-hant/main.json b/src/_locales/zh-hant/main.json index 2887b9af..5aa45072 100644 --- a/src/_locales/zh-hant/main.json +++ b/src/_locales/zh-hant/main.json @@ -104,5 +104,6 @@ "Max Conversation Length": "對話處理的最大長度", "Always pin the floating window": "總是固定浮動視窗", "Export": "匯出", - "Always Create New Conversation Window": "總是建立新的對話視窗" + "Always Create New Conversation Window": "總是建立新的對話視窗", + "Please keep this tab open. You can now use the web mode of ChatGPTBox": "請保持這個頁面開啟, 現在妳可以使用ChatGPTBox的網頁版模式" } diff --git a/src/background/index.mjs b/src/background/index.mjs index 3028aad1..17bb5e17 100644 --- a/src/background/index.mjs +++ b/src/background/index.mjs @@ -1,5 +1,4 @@ import Browser from 'webextension-polyfill' -import { v4 as uuidv4 } from 'uuid' import { deleteConversation, generateAnswersWithChatgptWebApi, @@ -20,10 +19,12 @@ import { chatgptApiModelKeys, chatgptWebModelKeys, customApiModelKeys, + defaultConfig, githubThirdPartyApiModelKeys, gptApiModelKeys, Models, poeWebModelKeys, + setUserConfig, } from '../config/index.mjs' import '../_locales/i18n' import { openUrl } from '../utils/open-url' @@ -37,12 +38,27 @@ import { registerCommands } from './commands.mjs' async function executeApi(session, port, config) { if (chatgptWebModelKeys.includes(session.modelName)) { - const accessToken = await getChatGptAccessToken() - session.messageId = uuidv4() - if (session.parentMessageId == null) { - session.parentMessageId = uuidv4() + let tabId + if ( + config.chatgptTabId && + config.customChatGptWebApiUrl === defaultConfig.customChatGptWebApiUrl + ) { + const tab = await Browser.tabs.get(config.chatgptTabId).catch(() => {}) + if (tab) tabId = tab.id + } + if (tabId) { + const proxyPort = Browser.tabs.connect(tabId) + proxyPort.onMessage.addListener((msg) => { + port.postMessage(msg) + }) + port.onMessage.addListener((msg) => { + proxyPort.postMessage(msg) + }) + proxyPort.postMessage({ session }) + } else { + const accessToken = await getChatGptAccessToken() + await generateAnswersWithChatgptWebApi(port, session.question, session, accessToken) } - await generateAnswersWithChatgptWebApi(port, session.question, session, accessToken) } else if (bingWebModelKeys.includes(session.modelName)) { const accessToken = await getBingAccessToken() if (session.modelName === 'bingFreeSydney') @@ -84,18 +100,38 @@ async function executeApi(session, port, config) { } Browser.runtime.onMessage.addListener(async (message) => { - if (message.type === 'FEEDBACK') { - const token = await getChatGptAccessToken() - await sendMessageFeedback(token, message.data) - } else if (message.type === 'DELETE_CONVERSATION') { - const token = await getChatGptAccessToken() - const data = message.data - await deleteConversation(token, data.conversationId) - } else if (message.type === 'OPEN_URL') { - const data = message.data - openUrl(data.url) - } else if (message.type === 'REFRESH_MENU') { - refreshMenu() + let token + switch (message.type) { + case 'FEEDBACK': + token = await getChatGptAccessToken() + await sendMessageFeedback(token, message.data) + break + case 'DELETE_CONVERSATION': + token = await getChatGptAccessToken() + await deleteConversation(token, message.data.conversationId) + break + case 'OPEN_URL': + openUrl(message.data.url) + break + case 'REFRESH_MENU': + refreshMenu() + break + case 'PIN_TAB': + let tabId + if (message.data.tabId) tabId = message.data.tabId + else { + const currentTab = (await Browser.tabs.query({ active: true, currentWindow: true }))[0] + if (message.data.saveAsChatgptConfig) { + if (currentTab.url.includes('chat.openai.com')) tabId = currentTab.id + } else { + tabId = currentTab.id + } + } + if (tabId) { + await Browser.tabs.update(tabId, { pinned: true }) + if (message.data.saveAsChatgptConfig) await setUserConfig({ chatgptTabId: tabId }) + } + break } }) diff --git a/src/config/index.mjs b/src/config/index.mjs index 9fa38d59..22dea183 100644 --- a/src/config/index.mjs +++ b/src/config/index.mjs @@ -132,6 +132,7 @@ export const defaultConfig = { ], accessToken: '', tokenSavedOn: 0, + chatgptTabId: 0, // unchangeable diff --git a/src/content-script/index.jsx b/src/content-script/index.jsx index 3240ce58..10e031a2 100644 --- a/src/content-script/index.jsx +++ b/src/content-script/index.jsx @@ -5,7 +5,12 @@ import DecisionCard from '../components/DecisionCard' import { config as siteConfig } from './site-adapters' import { config as toolsConfig } from './selection-tools' import { config as menuConfig } from './menu-tools' -import { getPreferredLanguageKey, getUserConfig, setAccessToken } from '../config/index.mjs' +import { + chatgptWebModelKeys, + getPreferredLanguageKey, + getUserConfig, + setAccessToken, +} from '../config/index.mjs' import { createElementAtPosition, cropText, @@ -16,8 +21,10 @@ import FloatingToolbar from '../components/FloatingToolbar' import Browser from 'webextension-polyfill' import { getPreferredLanguage } from '../config/language.mjs' import '../_locales/i18n-react' -import { changeLanguage } from 'i18next' +import { changeLanguage, t } from 'i18next' import { initSession } from '../services/init-session.mjs' +import { getChatGptAccessToken, registerPortListener } from '../services/wrappers.mjs' +import { generateAnswersWithChatgptWebApi } from '../services/apis/chatgpt-web.mjs' /** * @param {SiteConfig} siteConfig @@ -293,6 +300,35 @@ async function overwriteAccessToken() { } } +async function prepareForForegroundRequests() { + if (location.hostname !== 'chat.openai.com') return + + const div = document.createElement('div') + div.innerText = t('Please keep this tab open. You can now use the web mode of ChatGPTBox') + div.style.position = 'fixed' + div.style.top = '25px' + div.style.right = '25px' + div.style.zIndex = '2147483647' + div.style.padding = '4px 10px' + div.style.border = '1px solid' + div.style.borderRadius = '4px' + document.body.append(div) + + await Browser.runtime.sendMessage({ + type: 'PIN_TAB', + data: { + saveAsChatgptConfig: true, + }, + }) + + registerPortListener(async (session, port) => { + if (chatgptWebModelKeys.includes(session.modelName)) { + const accessToken = await getChatGptAccessToken() + await generateAnswersWithChatgptWebApi(port, session.question, session, accessToken) + } + }) +} + async function run() { await getPreferredLanguageKey().then((lang) => { changeLanguage(lang) @@ -305,6 +341,7 @@ async function run() { }) await overwriteAccessToken() + await prepareForForegroundRequests() prepareForSelectionTools() prepareForSelectionToolsTouch() diff --git a/src/popup/Popup.jsx b/src/popup/Popup.jsx index b8accebe..5422c4c8 100644 --- a/src/popup/Popup.jsx +++ b/src/popup/Popup.jsx @@ -271,7 +271,7 @@ function GeneralPart({ config, updateConfig }) { lang, }, }) - .catch(() => ({})) + .catch(() => {}) }) }) }} diff --git a/src/services/apis/chatgpt-web.mjs b/src/services/apis/chatgpt-web.mjs index 34a5e345..55c2dd70 100644 --- a/src/services/apis/chatgpt-web.mjs +++ b/src/services/apis/chatgpt-web.mjs @@ -5,6 +5,7 @@ import { isEmpty } from 'lodash-es' import { chatgptWebModelKeys, getUserConfig, Models } from '../../config/index.mjs' import { pushRecord, setAbortController } from './shared.mjs' import Browser from 'webextension-polyfill' +import { v4 as uuidv4 } from 'uuid' async function request(token, method, path, data) { const apiUrl = (await getUserConfig()).customChatGptWebApiUrl @@ -54,6 +55,11 @@ export async function getModels(token) { * @param {string} accessToken */ export async function generateAnswersWithChatgptWebApi(port, question, session, accessToken) { + session.messageId = uuidv4() + if (session.parentMessageId == null) { + session.parentMessageId = uuidv4() + } + const { controller, messageListener } = setAbortController(port, null, () => { if (session.autoClean) deleteConversation(accessToken, session.conversationId) }) @@ -68,20 +74,23 @@ export async function generateAnswersWithChatgptWebApi(port, question, session, models && models.includes(selectedModel) ? selectedModel : Models[chatgptWebModelKeys[0]].value console.debug('usedModel', usedModel) - const cookie = (await Browser.cookies.getAll({ url: 'https://chat.openai.com/' })) - .map((cookie) => { - return `${cookie.name}=${cookie.value}` - }) - .join('; ') + let cookie + if (Browser.cookies && Browser.cookies.getAll) + cookie = (await Browser.cookies.getAll({ url: 'https://chat.openai.com/' })) + .map((cookie) => { + return `${cookie.name}=${cookie.value}` + }) + .join('; ') let answer = '' await fetchSSE(`${config.customChatGptWebApiUrl}${config.customChatGptWebApiPath}`, { method: 'POST', signal: controller.signal, + credentials: 'include', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${accessToken}`, - Cookie: cookie, + ...(cookie && { Cookie: cookie }), }, body: JSON.stringify({ action: 'next',