diff --git a/README.md b/README.md
index 9f6cfc6..bd56202 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ Deep ChatGPT integrations in your browser, completely for free.
[![license][license-image]][license-url]
[![release][release-image]][release-url]
-[][release-url]
+[][release-url]
[![verfiy][verify-image]][verify-url]
English | [Indonesia](README_IN.md) | [简体中文](README_ZH.md)
diff --git a/README_IN.md b/README_IN.md
index 7b40276..f159dda 100644
--- a/README_IN.md
+++ b/README_IN.md
@@ -10,7 +10,7 @@ Integrasi Deep ChatGPT di browser Anda, sepenuhnya gratis.
[![license][license-image]][license-url]
[![release][release-image]][release-url]
-[][release-url]
+[][release-url]
[![verfiy][verify-image]][verify-url]
[Inggris](README.md) | Indonesia | [简体中文](README_ZH.md)
diff --git a/README_ZH.md b/README_ZH.md
index 7776653..ea88180 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -10,7 +10,7 @@
[![license][license-image]][license-url]
[![release][release-image]][release-url]
-[][release-url]
+[][release-url]
[![verfiy][verify-image]][verify-url]
[English](README.md) | [Indonesia](README_IN.md) | 简体中文
diff --git a/package-lock.json b/package-lock.json
index 77bcd0b..68cf1ad 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"@nem035/gpt-3-encoder": "^1.1.7",
"@picocss/pico": "^1.5.7",
"@primer/octicons-react": "^18.2.0",
+ "azure-openai": "^0.9.4",
"countries-list": "^2.6.1",
"eventsource-parser": "^0.1.0",
"expiry-map": "^2.0.0",
@@ -2751,8 +2752,7 @@
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
- "dev": true
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/available-typed-arrays": {
"version": "1.0.5",
@@ -2763,6 +2763,23 @@
"node": ">= 0.4"
}
},
+ "node_modules/axios": {
+ "version": "0.26.1",
+ "resolved": "https://registry.npmmirror.com/axios/-/axios-0.26.1.tgz",
+ "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
+ "dependencies": {
+ "follow-redirects": "^1.14.8"
+ }
+ },
+ "node_modules/azure-openai": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmmirror.com/azure-openai/-/azure-openai-0.9.4.tgz",
+ "integrity": "sha512-7uii4ZInxzu2zjLg45PdvgOaw3ps18tEAw0Yux9mo8anX4PwnCMSS9xdlKNiNQyyEKPogvAcxH2PIufHXFLx6Q==",
+ "dependencies": {
+ "axios": "^0.26.0",
+ "form-data": "^4.0.0"
+ }
+ },
"node_modules/babel-loader": {
"version": "9.1.2",
"resolved": "https://registry.npmmirror.com/babel-loader/-/babel-loader-9.1.2.tgz",
@@ -3081,7 +3098,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "dev": true,
"dependencies": {
"delayed-stream": "~1.0.0"
},
@@ -3581,7 +3597,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
- "dev": true,
"engines": {
"node": ">=0.4.0"
}
@@ -4349,6 +4364,19 @@
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
"dev": true
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz",
+ "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
"node_modules/for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.3.tgz",
@@ -4362,7 +4390,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
- "dev": true,
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
@@ -6295,7 +6322,6 @@
"version": "1.52.0",
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -6304,7 +6330,6 @@
"version": "2.1.35",
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "dev": true,
"dependencies": {
"mime-db": "1.52.0"
},
diff --git a/package.json b/package.json
index 1c7b54c..f7c3016 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"@nem035/gpt-3-encoder": "^1.1.7",
"@picocss/pico": "^1.5.7",
"@primer/octicons-react": "^18.2.0",
+ "azure-openai": "^0.9.4",
"countries-list": "^2.6.1",
"eventsource-parser": "^0.1.0",
"expiry-map": "^2.0.0",
diff --git a/src/background/apis/azure-openai-api.mjs b/src/background/apis/azure-openai-api.mjs
new file mode 100644
index 0000000..c8e27f9
--- /dev/null
+++ b/src/background/apis/azure-openai-api.mjs
@@ -0,0 +1,75 @@
+import { Configuration, OpenAIApi } from 'azure-openai'
+import { getUserConfig, maxResponseTokenLength } from '../../config/index.mjs'
+import { getChatSystemPromptBase, pushRecord, setAbortController } from './shared.mjs'
+import { getConversationPairs } from '../../utils/get-conversation-pairs'
+
+/**
+ * @param {Runtime.Port} port
+ * @param {string} question
+ * @param {Session} session
+ */
+export async function generateAnswersWithAzureOpenaiApi(port, question, session) {
+ const { controller, messageListener } = setAbortController(port)
+ const config = await getUserConfig()
+
+ const prompt = getConversationPairs(session.conversationRecords, false)
+ prompt.unshift({ role: 'system', content: await getChatSystemPromptBase() })
+ prompt.push({ role: 'user', content: question })
+
+ const openAiApi = new OpenAIApi(
+ new Configuration({
+ apiKey: config.azureApiKey,
+ azure: {
+ apiKey: config.azureApiKey,
+ endpoint: config.azureEndpoint,
+ deploymentName: config.azureDeploymentName,
+ },
+ }),
+ )
+
+ let answer = ''
+ const response = await openAiApi
+ .createChatCompletion(
+ {
+ messages: prompt,
+ stream: true,
+ max_tokens: maxResponseTokenLength,
+ },
+ {
+ signal: controller.signal,
+ responseType: 'stream',
+ },
+ )
+ .catch((err) => {
+ port.onMessage.removeListener(messageListener)
+ throw err
+ })
+ for await (const chunk of response.data) {
+ const lines = chunk
+ .toString('utf8')
+ .split('\n')
+ .filter((line) => line.trim().startsWith('data: '))
+
+ for (const line of lines) {
+ const message = line.replace(/^data: /, '')
+ console.debug('sse message', message)
+ if (message === '[DONE]') {
+ pushRecord(session, question, answer)
+ console.debug('conversation history', { content: session.conversationRecords })
+ port.postMessage({ answer: null, done: true, session: session })
+ break
+ }
+ let data
+ try {
+ data = JSON.parse(message)
+ } catch (error) {
+ console.debug('json error', error)
+ continue
+ }
+ answer += data.choices[0].text
+ port.postMessage({ answer: answer, done: false, session: null })
+ }
+ }
+
+ port.onMessage.removeListener(messageListener)
+}
diff --git a/src/background/apis/bing-web.mjs b/src/background/apis/bing-web.mjs
index b456dfb..3236c0a 100644
--- a/src/background/apis/bing-web.mjs
+++ b/src/background/apis/bing-web.mjs
@@ -7,16 +7,8 @@ import { pushRecord, setAbortController } from './shared.mjs'
* @param {string} question
* @param {Session} session
* @param {string} accessToken
- * @param {string} modelName
*/
-export async function generateAnswersWithBingWebApi(
- port,
- question,
- session,
- accessToken,
- // eslint-disable-next-line
- modelName,
-) {
+export async function generateAnswersWithBingWebApi(port, question, session, accessToken) {
const { controller, messageListener } = setAbortController(port)
const bingAIClient = new BingAIClient({ userToken: accessToken })
diff --git a/src/background/index.mjs b/src/background/index.mjs
index 23464b4..4891ea1 100644
--- a/src/background/index.mjs
+++ b/src/background/index.mjs
@@ -11,7 +11,9 @@ import {
generateAnswersWithGptCompletionApi,
} from './apis/openai-api'
import { generateAnswersWithCustomApi } from './apis/custom-api.mjs'
+import { generateAnswersWithAzureOpenaiApi } from './apis/azure-openai-api.mjs'
import {
+ azureOpenAiApiModelKeys,
bingWebModelKeys,
chatgptApiModelKeys,
chatgptWebModelKeys,
@@ -81,13 +83,7 @@ Browser.runtime.onConnect.addListener((port) => {
await generateAnswersWithChatgptWebApi(port, session.question, session, accessToken)
} else if (bingWebModelKeys.includes(session.modelName)) {
const accessToken = await getBingAccessToken()
- await generateAnswersWithBingWebApi(
- port,
- session.question,
- session,
- accessToken,
- session.modelName,
- )
+ await generateAnswersWithBingWebApi(port, session.question, session, accessToken)
} else if (gptApiModelKeys.includes(session.modelName)) {
await generateAnswersWithGptCompletionApi(
port,
@@ -112,6 +108,8 @@ Browser.runtime.onConnect.addListener((port) => {
'',
config.customModelName,
)
+ } else if (azureOpenAiApiModelKeys.includes(session.modelName)) {
+ await generateAnswersWithAzureOpenaiApi(port, session.question, session)
}
} catch (err) {
console.error(err)
diff --git a/src/config/index.mjs b/src/config/index.mjs
index 6a02304..f0d50b0 100644
--- a/src/config/index.mjs
+++ b/src/config/index.mjs
@@ -19,6 +19,7 @@ export const Models = {
chatgptApi4_32k: { value: 'gpt-4-32k', desc: 'ChatGPT (GPT-4-32k)' },
gptApiDavinci: { value: 'text-davinci-003', desc: 'GPT-3.5' },
customModel: { value: '', desc: 'Custom Model' },
+ azureOpenAi: { value: '', desc: 'ChatGPT (Azure)' },
}
export const chatgptWebModelKeys = ['chatgptFree35', 'chatgptPlus4']
@@ -26,6 +27,7 @@ export const bingWebModelKeys = ['bingFree4']
export const gptApiModelKeys = ['gptApiDavinci']
export const chatgptApiModelKeys = ['chatgptApi35', 'chatgptApi4_8k', 'chatgptApi4_32k']
export const customApiModelKeys = ['customModel']
+export const azureOpenAiApiModelKeys = ['azureOpenAi']
export const TriggerMode = {
always: 'Always',
@@ -60,13 +62,21 @@ export const defaultConfig = {
themeMode: 'auto',
/** @type {keyof Models}*/
modelName: 'chatgptFree35',
- apiKey: '',
- /** @type {keyof ModelMode}*/
- modelMode: 'balanced',
+
preferredLanguage: getNavigatorLanguage(),
insertAtTop: isMobile(),
lockWhenAnswer: false,
autoRegenAfterSwitchModel: false,
+
+ apiKey: '', // openai ApiKey
+
+ azureApiKey: '',
+ azureEndpoint: '',
+ azureDeploymentName: '',
+
+ /** @type {keyof ModelMode}*/
+ modelMode: 'balanced',
+
customModelApiUrl: 'http://localhost:8000/chat/completions',
customModelName: 'llama-7b-int4',
@@ -154,6 +164,10 @@ export function isUsingCustomModel(configOrSession) {
return customApiModelKeys.includes(configOrSession.modelName)
}
+export function isUsingAzureOpenAi(configOrSession) {
+ return azureOpenAiApiModelKeys.includes(configOrSession.modelName)
+}
+
export async function getPreferredLanguageKey() {
const config = await getUserConfig()
if (config.preferredLanguage === 'auto') return config.userLanguage
@@ -166,12 +180,6 @@ export async function getPreferredLanguageKey() {
*/
export async function getUserConfig() {
const options = await Browser.storage.local.get(Object.keys(defaultConfig))
-
- // version compatibility
- if (options.modelName === 'chatgptFree') options.modelName = 'chatgptFree35'
- else if (options.modelName === 'chatgptApi') options.modelName = 'chatgptApi35'
- else if (options.modelName === 'gptDavinci') options.modelName = 'gptApiDavinci'
-
return defaults(options, defaultConfig)
}
diff --git a/src/popup/Popup.jsx b/src/popup/Popup.jsx
index 605b164..4c26d2e 100644
--- a/src/popup/Popup.jsx
+++ b/src/popup/Popup.jsx
@@ -5,6 +5,7 @@ import {
getPreferredLanguageKey,
getUserConfig,
isUsingApiKey,
+ isUsingAzureOpenAi,
isUsingCustomModel,
isUsingMultiModeModel,
ModelMode,
@@ -85,7 +86,10 @@ function GeneralPart({ config, updateConfig }) {
)}
+ {isUsingAzureOpenAi(config) && (
+ {
+ const apiKey = e.target.value
+ updateConfig({ azureApiKey: apiKey })
+ }}
+ />
+ )}
{isUsingCustomModel(config) && (
)}
+ {isUsingAzureOpenAi(config) && (
+ {
+ const endpoint = e.target.value
+ updateConfig({ azureEndpoint: endpoint })
+ }}
+ />
+ )}
+ {isUsingAzureOpenAi(config) && (
+ {
+ const deploymentName = e.target.value
+ updateConfig({ azureDeploymentName: deploymentName })
+ }}
+ />
+ )}