diff --git a/build.mjs b/build.mjs
index 2d2a3c7..0baf44f 100644
--- a/build.mjs
+++ b/build.mjs
@@ -249,6 +249,7 @@ async function copyFiles(entryPoints, targetDir) {
async function finishOutput(outputDirSuffix) {
const commonFiles = [
{ src: 'src/logo.png', dst: 'logo.png' },
+ { src: 'src/rules.json', dst: 'rules.json' },
{ src: 'build/shared.js', dst: 'shared.js' },
{ src: 'build/content-script.css', dst: 'content-script.css' }, // shared
diff --git a/src/background/index.mjs b/src/background/index.mjs
index 5879714..e17820f 100644
--- a/src/background/index.mjs
+++ b/src/background/index.mjs
@@ -83,7 +83,7 @@ async function executeApi(session, port, config) {
const accessToken = await getChatGptAccessToken()
await generateAnswersWithChatgptWebApi(port, session.question, session, accessToken)
}
- } else if (bingWebModelKeys.some((n) => session.modelName.includes(n))) {
+ } else if (bingWebModelKeys.includes(session.modelName)) {
const accessToken = await getBingAccessToken()
if (session.modelName.includes('bingFreeSydney'))
await generateAnswersWithBingWebApi(port, session.question, session, accessToken, true)
@@ -192,9 +192,49 @@ Browser.runtime.onMessage.addListener(async (message, sender) => {
}
break
}
+ case 'FETCH': {
+ if (message.data.input.includes('bing.com')) {
+ const accessToken = await getBingAccessToken()
+ await setUserConfig({ bingAccessToken: accessToken })
+ }
+
+ try {
+ const response = await fetch(message.data.input, message.data.init)
+ const text = await response.text()
+ return [
+ {
+ body: text,
+ status: response.status,
+ statusText: response.statusText,
+ },
+ null,
+ ]
+ } catch (error) {
+ return [null, error]
+ }
+ }
}
})
+Browser.webRequest.onBeforeSendHeaders.addListener(
+ (details) => {
+ const headers = details.requestHeaders
+ for (let i = 0; i < headers.length; i++) {
+ if (headers[i].name === 'Origin') {
+ headers[i].value = 'https://www.bing.com'
+ } else if (headers[i].name === 'Referer') {
+ headers[i].value = 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx'
+ }
+ }
+ return { requestHeaders: headers }
+ },
+ {
+ urls: ['wss://sydney.bing.com/*', 'https://www.bing.com/*'],
+ types: ['xmlhttprequest', 'websocket'],
+ },
+ ['requestHeaders'],
+)
+
registerPortListener(async (session, port, config) => await executeApi(session, port, config))
registerCommands()
refreshMenu()
diff --git a/src/components/ConversationCard/index.jsx b/src/components/ConversationCard/index.jsx
index d0241fe..16d3d41 100644
--- a/src/components/ConversationCard/index.jsx
+++ b/src/components/ConversationCard/index.jsx
@@ -5,17 +5,17 @@ import InputBox from '../InputBox'
import ConversationItem from '../ConversationItem'
import { createElementAtPosition, isFirefox, isMobile, isSafari } from '../../utils'
import {
- LinkExternalIcon,
ArchiveIcon,
DesktopDownloadIcon,
+ LinkExternalIcon,
MoveToBottomIcon,
} from '@primer/octicons-react'
-import { WindowDesktop, XLg, Pin } from 'react-bootstrap-icons'
+import { Pin, WindowDesktop, XLg } from 'react-bootstrap-icons'
import FileSaver from 'file-saver'
import { render } from 'preact'
import FloatingToolbar from '../FloatingToolbar'
import { useClampWindowSize } from '../../hooks/use-clamp-window-size'
-import { ModelMode, Models } from '../../config/index.mjs'
+import { bingWebModelKeys, getUserConfig, ModelMode, Models } from '../../config/index.mjs'
import { useTranslation } from 'react-i18next'
import DeleteButton from '../DeleteButton'
import { useConfig } from '../../hooks/use-config.mjs'
@@ -23,6 +23,7 @@ import { createSession } from '../../services/local-session.mjs'
import { v4 as uuidv4 } from 'uuid'
import { initSession } from '../../services/init-session.mjs'
import { findLastIndex } from 'lodash-es'
+import { generateAnswersWithBingWebApi } from '../../services/apis/bing-web.mjs'
const logo = Browser.runtime.getURL('logo.png')
@@ -48,6 +49,7 @@ function ConversationCard(props) {
const windowSize = useClampWindowSize([750, 1500], [250, 1100])
const bodyRef = useRef(null)
const [completeDraggable, setCompleteDraggable] = useState(false)
+ const useForegroundFetch = bingWebModelKeys.includes(session.modelName)
/**
* @type {[ConversationItemData[], (conversationItemData: ConversationItemData[]) => void]}
@@ -96,12 +98,12 @@ function ConversationCard(props) {
}
}, [conversationItemData])
- useEffect(() => {
+ useEffect(async () => {
// when the page is responsive, session may accumulate redundant data and needs to be cleared after remounting and before making a new request
if (props.question) {
const newSession = initSession({ question: props.question })
setSession(newSession)
- port.postMessage({ session: newSession })
+ await postMessage({ session: newSession })
}
}, [props.question]) // usually only triggered once
@@ -125,6 +127,100 @@ function ConversationCard(props) {
})
}
+ const portMessageListener = (msg) => {
+ if (msg.answer) {
+ updateAnswer(msg.answer, false, 'answer')
+ }
+ if (msg.session) {
+ if (msg.done) msg.session = { ...msg.session, isRetry: false }
+ setSession(msg.session)
+ }
+ if (msg.done) {
+ updateAnswer('', true, 'answer', true)
+ setIsReady(true)
+ }
+ if (msg.error) {
+ switch (msg.error) {
+ case 'UNAUTHORIZED':
+ updateAnswer(
+ `${t('UNAUTHORIZED')}
${t('Please login at https://chat.openai.com first')}${
+ isSafari() ? `
${t('Then open https://chat.openai.com/api/auth/session')}` : ''
+ }
${t('And refresh this page or type you question again')}` +
+ `
${t(
+ 'Consider creating an api key at https://platform.openai.com/account/api-keys',
+ )}`,
+ false,
+ 'error',
+ )
+ break
+ case 'CLOUDFLARE':
+ updateAnswer(
+ `${t('OpenAI Security Check Required')}
${
+ isSafari()
+ ? t('Please open https://chat.openai.com/api/auth/session')
+ : t('Please open https://chat.openai.com')
+ }
${t('And refresh this page or type you question again')}` +
+ `
${t(
+ 'Consider creating an api key at https://platform.openai.com/account/api-keys',
+ )}`,
+ false,
+ 'error',
+ )
+ break
+ default:
+ if (conversationItemData[conversationItemData.length - 1].content.includes('gpt-loading'))
+ updateAnswer(msg.error, false, 'error')
+ else
+ setConversationItemData([
+ ...conversationItemData,
+ new ConversationItemData('error', msg.error),
+ ])
+ break
+ }
+ setIsReady(true)
+ }
+ }
+
+ const foregroundMessageListeners = useRef([])
+
+ /**
+ * @param {Session|undefined} session
+ * @param {boolean|undefined} stop
+ */
+ const postMessage = async ({ session, stop }) => {
+ if (useForegroundFetch) {
+ foregroundMessageListeners.current.forEach((listener) => listener({ session, stop }))
+ if (session) {
+ const fakePort = {
+ postMessage: (msg) => {
+ portMessageListener(msg)
+ },
+ onMessage: {
+ addListener: (listener) => {
+ foregroundMessageListeners.current.push(listener)
+ },
+ removeListener: (listener) => {
+ foregroundMessageListeners.current.splice(
+ foregroundMessageListeners.current.indexOf(listener),
+ 1,
+ )
+ },
+ },
+ onDisconnect: {
+ addListener: () => {},
+ removeListener: () => {},
+ },
+ }
+ const bingToken = (await getUserConfig()).bingAccessToken
+ if (session.modelName.includes('bingFreeSydney'))
+ await generateAnswersWithBingWebApi(fakePort, session.question, session, bingToken, true)
+ else await generateAnswersWithBingWebApi(fakePort, session.question, session, bingToken)
+ }
+ } else {
+ port.postMessage({ session, stop })
+ }
+ }
+
useEffect(() => {
const portListener = () => {
setPort(Browser.runtime.connect())
@@ -146,68 +242,17 @@ function ConversationCard(props) {
}
}, [port])
useEffect(() => {
- const listener = (msg) => {
- if (msg.answer) {
- updateAnswer(msg.answer, false, 'answer')
+ if (useForegroundFetch) {
+ return () => {}
+ } else {
+ port.onMessage.addListener(portMessageListener)
+ return () => {
+ port.onMessage.removeListener(portMessageListener)
}
- if (msg.session) {
- if (msg.done) msg.session = { ...msg.session, isRetry: false }
- setSession(msg.session)
- }
- if (msg.done) {
- updateAnswer('', true, 'answer', true)
- setIsReady(true)
- }
- if (msg.error) {
- switch (msg.error) {
- case 'UNAUTHORIZED':
- updateAnswer(
- `${t('UNAUTHORIZED')}
${t('Please login at https://chat.openai.com first')}${
- isSafari() ? `
${t('Then open https://chat.openai.com/api/auth/session')}` : ''
- }
${t('And refresh this page or type you question again')}` +
- `
${t(
- 'Consider creating an api key at https://platform.openai.com/account/api-keys',
- )}`,
- false,
- 'error',
- )
- break
- case 'CLOUDFLARE':
- updateAnswer(
- `${t('OpenAI Security Check Required')}
${
- isSafari()
- ? t('Please open https://chat.openai.com/api/auth/session')
- : t('Please open https://chat.openai.com')
- }
${t('And refresh this page or type you question again')}` +
- `
${t(
- 'Consider creating an api key at https://platform.openai.com/account/api-keys',
- )}`,
- false,
- 'error',
- )
- break
- default:
- if (
- conversationItemData[conversationItemData.length - 1].content.includes('gpt-loading')
- )
- updateAnswer(msg.error, false, 'error')
- else
- setConversationItemData([
- ...conversationItemData,
- new ConversationItemData('error', msg.error),
- ])
- break
- }
- setIsReady(true)
- }
- }
- port.onMessage.addListener(listener)
- return () => {
- port.onMessage.removeListener(listener)
}
}, [conversationItemData])
- const getRetryFn = (session) => () => {
+ const getRetryFn = (session) => async () => {
updateAnswer(`
${t('Waiting for response...')}
`, false, 'answer') setIsReady(false) @@ -223,8 +268,8 @@ function ConversationCard(props) { const newSession = { ...session, isRetry: true } setSession(newSession) try { - port.postMessage({ stop: true }) - port.postMessage({ session: newSession }) + await postMessage({ stop: true }) + await postMessage({ session: newSession }) } catch (e) { updateAnswer(e, false, 'error') } @@ -348,8 +393,8 @@ function ConversationCard(props) {