mirror of
https://github.com/wassname/chatGPTBox.git
synced 2026-06-27 18:04:15 +08:00
Add ChatGLM API (#567)
* Add ChatGLM API * add minimal build * a small patch --------- Co-authored-by: josc146 <josStorer@outlook.com>
This commit is contained in:
@@ -18,7 +18,7 @@ async function deleteOldDir() {
|
||||
await fs.rm(outdir, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
async function runWebpack(isWithoutKatex, isWithoutTiktoken, callback) {
|
||||
async function runWebpack(isWithoutKatex, isWithoutTiktoken, minimal, callback) {
|
||||
const shared = [
|
||||
'preact',
|
||||
'webextension-polyfill',
|
||||
@@ -70,6 +70,12 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, callback) {
|
||||
concatenateModules: !isAnalyzing,
|
||||
},
|
||||
plugins: [
|
||||
minimal
|
||||
? undefined
|
||||
: new webpack.ProvidePlugin({
|
||||
process: 'process/browser.js',
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
}),
|
||||
new ProgressBarPlugin({
|
||||
format: ' build [:bar] :percent (:elapsed seconds)',
|
||||
clear: false,
|
||||
@@ -97,6 +103,14 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, callback) {
|
||||
extensions: ['.jsx', '.mjs', '.js'],
|
||||
alias: {
|
||||
parse5: path.resolve(__dirname, 'node_modules/parse5'),
|
||||
...(minimal
|
||||
? {}
|
||||
: {
|
||||
util: path.resolve(__dirname, 'node_modules/util'),
|
||||
buffer: path.resolve(__dirname, 'node_modules/buffer'),
|
||||
stream: 'stream-browserify',
|
||||
crypto: 'crypto-browserify',
|
||||
}),
|
||||
},
|
||||
},
|
||||
module: {
|
||||
@@ -206,7 +220,7 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, callback) {
|
||||
},
|
||||
}
|
||||
: {},
|
||||
isWithoutKatex && isWithoutTiktoken
|
||||
minimal
|
||||
? {
|
||||
test: /styles\.scss$/,
|
||||
loader: 'string-replace-loader',
|
||||
@@ -220,6 +234,32 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, callback) {
|
||||
},
|
||||
}
|
||||
: {},
|
||||
minimal
|
||||
? {
|
||||
test: /index\.mjs$/,
|
||||
loader: 'string-replace-loader',
|
||||
options: {
|
||||
multiple: [
|
||||
{
|
||||
search: 'import { generateAnswersWithChatGLMApi }',
|
||||
replace: '//',
|
||||
},
|
||||
{
|
||||
search: 'await generateAnswersWithChatGLMApi',
|
||||
replace: '//',
|
||||
},
|
||||
{
|
||||
search: 'chatglmTurbo',
|
||||
replace: '//',
|
||||
},
|
||||
{
|
||||
search: "'chatglmTurbo",
|
||||
replace: '//',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
: {},
|
||||
],
|
||||
},
|
||||
})
|
||||
@@ -305,6 +345,7 @@ async function build() {
|
||||
// )
|
||||
// await new Promise((r) => setTimeout(r, 5000))
|
||||
await runWebpack(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
generateWebpackCallback(() => finishOutput('-without-katex-and-tiktoken')),
|
||||
@@ -312,6 +353,7 @@ async function build() {
|
||||
await new Promise((r) => setTimeout(r, 10000))
|
||||
}
|
||||
await runWebpack(
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
generateWebpackCallback(() => finishOutput('')),
|
||||
|
||||
Generated
+4359
-2359
File diff suppressed because it is too large
Load Diff
@@ -22,19 +22,23 @@
|
||||
"@nem035/gpt-3-encoder": "^1.1.7",
|
||||
"@picocss/pico": "^1.5.9",
|
||||
"@primer/octicons-react": "^18.3.0",
|
||||
"buffer": "^6.0.3",
|
||||
"claude-ai": "^1.2.2",
|
||||
"countries-list": "^2.6.1",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"diff": "^5.1.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"github-markdown-css": "^5.2.0",
|
||||
"gpt-3-encoder": "^1.1.4",
|
||||
"graphql": "^16.6.0",
|
||||
"i18next": "^22.4.15",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"katex": "^0.16.6",
|
||||
"lodash-es": "^4.17.21",
|
||||
"md5": "^2.3.0",
|
||||
"parse5": "^6.0.1",
|
||||
"preact": "^10.13.2",
|
||||
"process": "^0.11.10",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "npm:@preact/compat@^17.1.2",
|
||||
"react-bootstrap-icons": "^1.10.3",
|
||||
@@ -50,6 +54,8 @@
|
||||
"remark-breaks": "^3.0.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-math": "^5.1.1",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"util": "^0.12.5",
|
||||
"uuid": "^9.0.0",
|
||||
"webextension-polyfill": "^0.10.0"
|
||||
},
|
||||
|
||||
@@ -12,10 +12,12 @@ import {
|
||||
import { generateAnswersWithCustomApi } from '../services/apis/custom-api.mjs'
|
||||
import { generateAnswersWithAzureOpenaiApi } from '../services/apis/azure-openai-api.mjs'
|
||||
import { generateAnswersWithClaudeApi } from '../services/apis/claude-api.mjs'
|
||||
import { generateAnswersWithChatGLMApi } from '../services/apis/chatglm-api.mjs'
|
||||
import { generateAnswersWithWaylaidwandererApi } from '../services/apis/waylaidwanderer-api.mjs'
|
||||
import {
|
||||
azureOpenAiApiModelKeys,
|
||||
claudeApiModelKeys,
|
||||
chatglmApiModelKeys,
|
||||
bardWebModelKeys,
|
||||
bingWebModelKeys,
|
||||
chatgptApiModelKeys,
|
||||
@@ -122,6 +124,8 @@ async function executeApi(session, port, config) {
|
||||
await generateAnswersWithAzureOpenaiApi(port, session.question, session)
|
||||
} else if (claudeApiModelKeys.includes(session.modelName)) {
|
||||
await generateAnswersWithClaudeApi(port, session.question, session)
|
||||
} else if (chatglmApiModelKeys.includes(session.modelName)) {
|
||||
await generateAnswersWithChatGLMApi(port, session.question, session, session.modelName)
|
||||
} else if (githubThirdPartyApiModelKeys.includes(session.modelName)) {
|
||||
await generateAnswersWithWaylaidwandererApi(port, session.question, session)
|
||||
} else if (poeWebModelKeys.includes(session.modelName)) {
|
||||
|
||||
+37
-28
@@ -45,6 +45,7 @@ export const chatgptApiModelKeys = [
|
||||
export const customApiModelKeys = ['customModel']
|
||||
export const azureOpenAiApiModelKeys = ['azureOpenAi']
|
||||
export const claudeApiModelKeys = ['claude2Api']
|
||||
export const chatglmApiModelKeys = ['chatglmTurbo']
|
||||
export const githubThirdPartyApiModelKeys = ['waylaidwandererApi']
|
||||
export const poeWebModelKeys = [
|
||||
'poeAiWebSage', //poe.com/Assistant
|
||||
@@ -72,18 +73,43 @@ export const poeWebModelKeys = [
|
||||
*/
|
||||
export const Models = {
|
||||
chatgptFree35: { value: 'text-davinci-002-render-sha', desc: 'ChatGPT (Web)' },
|
||||
chatgptFree35Mobile: { value: 'text-davinci-002-render-sha-mobile', desc: 'ChatGPT (Mobile)' },
|
||||
|
||||
chatgptPlus4: { value: 'gpt-4', desc: 'ChatGPT (Web, GPT-4)' },
|
||||
chatgptPlus4Browsing: { value: 'gpt-4-browsing', desc: 'ChatGPT (Web, GPT-4, Browsing)' },
|
||||
chatgptPlus4Mobile: { value: 'gpt-4-mobile', desc: 'ChatGPT (Mobile, GPT-4)' },
|
||||
|
||||
chatgptApi35: { value: 'gpt-3.5-turbo', desc: 'ChatGPT (GPT-3.5-turbo)' },
|
||||
chatgptApi35_16k: { value: 'gpt-3.5-turbo-16k', desc: 'ChatGPT (GPT-3.5-turbo-16k)' },
|
||||
chatgptApi35_1106: { value: 'gpt-3.5-turbo-1106', desc: 'ChatGPT (GPT-3.5-turbo 1106)' },
|
||||
|
||||
chatgptApi4_8k: { value: 'gpt-4', desc: 'ChatGPT (GPT-4-8k)' },
|
||||
chatgptApi4_32k: { value: 'gpt-4-32k', desc: 'ChatGPT (GPT-4-32k)' },
|
||||
chatgptApi4_128k_preview: {
|
||||
value: 'gpt-4-1106-preview',
|
||||
desc: 'ChatGPT (GPT-4-Turbo 128k Preview)',
|
||||
},
|
||||
|
||||
claude2WebFree: { value: 'claude-2', desc: 'Claude.ai (Web, Claude 2)' },
|
||||
claude2Api: { value: '', desc: 'Claude.ai (API, Claude 2)' },
|
||||
|
||||
bingFree4: { value: '', desc: 'Bing (Web, GPT-4)' },
|
||||
bingFreeSydney: { value: '', desc: 'Bing (Web, GPT-4, Sydney)' },
|
||||
|
||||
bardWebFree: { value: '', desc: 'Bard (Web)' },
|
||||
|
||||
chatglmTurbo: { value: 'chatglm_turbo', desc: 'ChatGLM (ChatGLM-Turbo)' },
|
||||
|
||||
chatgptFree35Mobile: { value: 'text-davinci-002-render-sha-mobile', desc: 'ChatGPT (Mobile)' },
|
||||
chatgptPlus4Mobile: { value: 'gpt-4-mobile', desc: 'ChatGPT (Mobile, GPT-4)' },
|
||||
|
||||
chatgptApi35_1106: { value: 'gpt-3.5-turbo-1106', desc: 'ChatGPT (GPT-3.5-turbo 1106)' },
|
||||
chatgptApi4_8k_0613: { value: 'gpt-4', desc: 'ChatGPT (GPT-4-8k 0613)' },
|
||||
chatgptApi4_32k_0613: { value: 'gpt-4-32k', desc: 'ChatGPT (GPT-4-32k 0613)' },
|
||||
|
||||
gptApiDavinci: { value: 'text-davinci-003', desc: 'GPT-3.5' },
|
||||
|
||||
customModel: { value: '', desc: 'Custom Model' },
|
||||
azureOpenAi: { value: '', desc: 'ChatGPT (Azure)' },
|
||||
waylaidwandererApi: { value: '', desc: 'Waylaidwanderer API (Github)' },
|
||||
|
||||
poeAiWebSage: { value: 'Assistant', desc: 'Poe AI (Web, Assistant)' },
|
||||
poeAiWebGPT4: { value: 'gpt-4', desc: 'Poe AI (Web, GPT-4)' },
|
||||
poeAiWebGPT4_32k: { value: 'gpt-4-32k', desc: 'Poe AI (Web, GPT-4-32k)' },
|
||||
@@ -94,21 +120,9 @@ export const Models = {
|
||||
poeAiWeb_Llama_2_7b: { value: 'Llama-2-7b', desc: 'Poe AI (Web, Llama-2-7b)' },
|
||||
poeAiWeb_Llama_2_13b: { value: 'Llama-2-13b', desc: 'Poe AI (Web, Llama-2-13b)' },
|
||||
poeAiWeb_Llama_2_70b: { value: 'Llama-2-70b', desc: 'Poe AI (Web, Llama-2-70b)' },
|
||||
chatgptApi4_8k: { value: 'gpt-4', desc: 'ChatGPT (GPT-4-8k)' },
|
||||
chatgptApi4_8k_0613: { value: 'gpt-4', desc: 'ChatGPT (GPT-4-8k 0613)' },
|
||||
chatgptApi4_32k: { value: 'gpt-4-32k', desc: 'ChatGPT (GPT-4-32k)' },
|
||||
chatgptApi4_32k_0613: { value: 'gpt-4-32k', desc: 'ChatGPT (GPT-4-32k 0613)' },
|
||||
chatgptApi4_128k_preview: {
|
||||
value: 'gpt-4-1106-preview',
|
||||
desc: 'ChatGPT (GPT-4-Turbo 128k Preview)',
|
||||
},
|
||||
gptApiDavinci: { value: 'text-davinci-003', desc: 'GPT-3.5' },
|
||||
customModel: { value: '', desc: 'Custom Model' },
|
||||
azureOpenAi: { value: '', desc: 'ChatGPT (Azure)' },
|
||||
waylaidwandererApi: { value: '', desc: 'Waylaidwanderer API (Github)' },
|
||||
poeAiWebCustom: { value: '', desc: 'Poe AI (Web, Custom)' },
|
||||
poeAiWebChatGpt: { value: 'chatgpt', desc: 'Poe AI (Web, ChatGPT)' },
|
||||
poeAiWebChatGpt_16k: { value: 'chatgpt-16k', desc: 'Poe AI (Web, ChatGPT-16k)' },
|
||||
poeAiWebCustom: { value: '', desc: 'Poe AI (Web, Custom)' },
|
||||
}
|
||||
|
||||
for (const modelName in Models) {
|
||||
@@ -152,6 +166,7 @@ export const defaultConfig = {
|
||||
poeCustomBotName: '',
|
||||
|
||||
claudeApiKey: '',
|
||||
chatglmApiKey: '',
|
||||
|
||||
customApiKey: '',
|
||||
|
||||
@@ -181,25 +196,15 @@ export const defaultConfig = {
|
||||
|
||||
alwaysCreateNewConversationWindow: false,
|
||||
activeApiModes: [
|
||||
// 'claude2Api',
|
||||
'chatgptFree35',
|
||||
//'chatgptFree35Mobile',
|
||||
'chatgptPlus4',
|
||||
// 'chatgptPlus4Mobile',
|
||||
'chatgptApi35',
|
||||
'chatgptApi35_16k',
|
||||
'chatgptApi4_8k',
|
||||
'claude2WebFree',
|
||||
'bingFree4',
|
||||
'bingFreeSydney',
|
||||
// 'poeAiWebSage', //poe.com/Assistant
|
||||
// 'poeAiWebGPT4',
|
||||
// 'poeAiWebGPT4_32k',
|
||||
// 'poeAiWebClaudePlus',
|
||||
// 'poeAiWebClaude100k',
|
||||
'chatgptApi4_8k',
|
||||
'chatglmTurbo',
|
||||
'customModel',
|
||||
'azureOpenAi',
|
||||
// 'poeAiWebCustom',
|
||||
],
|
||||
activeSelectionTools: ['translate', 'summary', 'polish', 'code', 'ask'],
|
||||
activeSiteAdapters: [
|
||||
@@ -290,6 +295,10 @@ export function isUsingCustomModel(configOrSession) {
|
||||
return customApiModelKeys.includes(configOrSession.modelName)
|
||||
}
|
||||
|
||||
export function isUsingChatGLMApi(configOrSession) {
|
||||
return chatglmApiModelKeys.includes(configOrSession.modelName)
|
||||
}
|
||||
|
||||
export function isUsingCustomNameOnlyModel(configOrSession) {
|
||||
return configOrSession.modelName === 'poeAiWebCustom'
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { openUrl } from '../../utils/index.mjs'
|
||||
import {
|
||||
isUsingApiKey,
|
||||
isUsingAzureOpenAi,
|
||||
isUsingChatGLMApi,
|
||||
isUsingClaude2Api,
|
||||
isUsingCustomModel,
|
||||
isUsingCustomNameOnlyModel,
|
||||
@@ -273,6 +274,18 @@ export function GeneralPart({ config, updateConfig }) {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isUsingChatGLMApi(config) && (
|
||||
<input
|
||||
type="password"
|
||||
style="width: 50%;"
|
||||
value={config.chatglmApiKey}
|
||||
placeholder={t('ChatGLM API Key')}
|
||||
onChange={(e) => {
|
||||
const apiKey = e.target.value
|
||||
updateConfig({ chatglmApiKey: apiKey })
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
{isUsingCustomModel(config) && (
|
||||
<input
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import { Models, getUserConfig } from '../../config/index.mjs'
|
||||
import { pushRecord, setAbortController } from './shared.mjs'
|
||||
import { isEmpty } from 'lodash-es'
|
||||
import { getToken } from '../../utils/jwt-token-generator.mjs'
|
||||
import { createParser } from '../../utils/eventsource-parser.mjs'
|
||||
|
||||
async function fetchSSE(resource, options) {
|
||||
const { onMessage, onStart, onEnd, onError, ...fetchOptions } = options
|
||||
const resp = await fetch(resource, fetchOptions).catch(async (err) => {
|
||||
await onError(err)
|
||||
})
|
||||
if (!resp) return
|
||||
if (!resp.ok) {
|
||||
await onError(resp)
|
||||
return
|
||||
}
|
||||
|
||||
const parser = createParser((event) => {
|
||||
if (event.type === 'event') {
|
||||
onMessage(event)
|
||||
}
|
||||
})
|
||||
|
||||
let hasStarted = false
|
||||
const reader = resp.body.getReader()
|
||||
let result
|
||||
while (!(result = await reader.read()).done) {
|
||||
const chunk = result.value
|
||||
if (!hasStarted) {
|
||||
hasStarted = true
|
||||
await onStart(new TextDecoder().decode(chunk))
|
||||
}
|
||||
parser.feed(chunk)
|
||||
}
|
||||
await onEnd()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Runtime.Port} port
|
||||
* @param {string} question
|
||||
* @param {Session} session
|
||||
* @param {string} modelName
|
||||
*/
|
||||
export async function generateAnswersWithChatGLMApi(port, question, session, modelName) {
|
||||
const { controller, messageListener, disconnectListener } = setAbortController(port)
|
||||
const config = await getUserConfig()
|
||||
|
||||
const prompt = []
|
||||
for (const record of session.conversationRecords.slice(-config.maxConversationContextLength)) {
|
||||
prompt.push({ role: 'user', content: record.question })
|
||||
prompt.push({ role: 'assistant', content: record.answer })
|
||||
}
|
||||
prompt.push({ role: 'user', content: question })
|
||||
|
||||
let answer = ''
|
||||
await fetchSSE(
|
||||
`https://open.bigmodel.cn/api/paas/v3/model-api/${Models[modelName].value}/sse-invoke`,
|
||||
{
|
||||
method: 'POST',
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
Accept: 'text/event-stream',
|
||||
Authorization: getToken(config.chatglmApiKey),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
prompt: prompt,
|
||||
// temperature: config.temperature,
|
||||
// top_t: 0.7,
|
||||
// request_id: string
|
||||
// incremental: true,
|
||||
// return_type: "json_string",
|
||||
// ref: {"enable": "true", "search_query": "history"},
|
||||
}),
|
||||
onMessage(event) {
|
||||
console.debug('sse event', event)
|
||||
|
||||
// Handle different types of events
|
||||
switch (event.event) {
|
||||
case 'add':
|
||||
// In the case of an "add" event, append the completion to the answer
|
||||
if (event.data) {
|
||||
answer += event.data
|
||||
port.postMessage({ answer: answer, done: false, session: null })
|
||||
}
|
||||
break
|
||||
case 'error':
|
||||
case 'interrupted':
|
||||
case 'finish':
|
||||
pushRecord(session, question, answer)
|
||||
console.debug('conversation history', { content: session.conversationRecords })
|
||||
port.postMessage({ answer: null, done: true, session: session })
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
async onStart() {},
|
||||
async onEnd() {
|
||||
port.postMessage({ done: true })
|
||||
port.onMessage.removeListener(messageListener)
|
||||
port.onDisconnect.removeListener(disconnectListener)
|
||||
},
|
||||
async onError(resp) {
|
||||
port.onMessage.removeListener(messageListener)
|
||||
port.onDisconnect.removeListener(disconnectListener)
|
||||
if (resp instanceof Error) throw resp
|
||||
const error = await resp.json().catch(() => ({}))
|
||||
throw new Error(
|
||||
!isEmpty(error) ? JSON.stringify(error) : `${resp.status} ${resp.statusText}`,
|
||||
)
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -9,6 +9,7 @@ function createParser(onParse) {
|
||||
let eventId
|
||||
let eventName
|
||||
let data
|
||||
let extra
|
||||
reset()
|
||||
return {
|
||||
feed,
|
||||
@@ -78,17 +79,19 @@ function createParser(onParse) {
|
||||
|
||||
function parseEventStreamLine(lineBuffer, index, fieldLength, lineLength) {
|
||||
if (lineLength === 0) {
|
||||
if (data.length > 0) {
|
||||
if (data.length > 0 || extra) {
|
||||
onParse({
|
||||
type: 'event',
|
||||
id: eventId,
|
||||
event: eventName || void 0,
|
||||
data: data.slice(0, -1),
|
||||
extra: extra || void 0,
|
||||
// remove trailing newline
|
||||
})
|
||||
|
||||
data = ''
|
||||
eventId = void 0
|
||||
extra = void 0
|
||||
}
|
||||
eventName = void 0
|
||||
return
|
||||
@@ -120,6 +123,10 @@ function createParser(onParse) {
|
||||
value: retry,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const str = `{"${field}":${value}}`
|
||||
extra = extra ?? []
|
||||
extra.push(JSON.parse(str))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
let jwtToken = null
|
||||
let tokenExpiration = null // Declare tokenExpiration in the module scope
|
||||
|
||||
function generateToken(apiKey, timeoutSeconds) {
|
||||
const parts = apiKey.split('.')
|
||||
if (parts.length !== 2) {
|
||||
throw new Error('Invalid API key')
|
||||
}
|
||||
|
||||
const ms = Date.now()
|
||||
const currentSeconds = Math.floor(ms / 1000)
|
||||
const [id, secret] = parts
|
||||
const payload = {
|
||||
api_key: id,
|
||||
exp: currentSeconds + timeoutSeconds,
|
||||
timestamp: currentSeconds,
|
||||
}
|
||||
|
||||
jwtToken = jwt.sign(payload, secret, {
|
||||
header: {
|
||||
alg: 'HS256',
|
||||
typ: 'JWT',
|
||||
sign_type: 'SIGN',
|
||||
},
|
||||
})
|
||||
tokenExpiration = ms + timeoutSeconds * 1000
|
||||
}
|
||||
|
||||
function shouldRegenerateToken() {
|
||||
const ms = Date.now()
|
||||
return !jwtToken || ms >= tokenExpiration
|
||||
}
|
||||
|
||||
function getToken(apiKey) {
|
||||
if (shouldRegenerateToken()) {
|
||||
generateToken(apiKey, 86400) // Hard-coded to regenerate the token every 24 hours
|
||||
}
|
||||
return jwtToken
|
||||
}
|
||||
|
||||
export { getToken }
|
||||
Reference in New Issue
Block a user