Skip to content

Commit

Permalink
Add ChatGLM API
Browse files Browse the repository at this point in the history
  • Loading branch information
AceLam committed Nov 20, 2023
1 parent d9a6f23 commit ea90c23
Show file tree
Hide file tree
Showing 9 changed files with 4,517 additions and 2,313 deletions.
8 changes: 8 additions & 0 deletions build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, callback) {
concatenateModules: !isAnalyzing,
},
plugins: [
new webpack.ProvidePlugin({
process: 'process/browser.js',
Buffer: ['buffer', 'Buffer'],
}),
new ProgressBarPlugin({
format: ' build [:bar] :percent (:elapsed seconds)',
clear: false,
Expand Down Expand Up @@ -97,6 +101,10 @@ async function runWebpack(isWithoutKatex, isWithoutTiktoken, callback) {
extensions: ['.jsx', '.mjs', '.js'],
alias: {
parse5: path.resolve(__dirname, 'node_modules/parse5'),
util: path.resolve(__dirname, 'node_modules/util'),
buffer: path.resolve(__dirname, 'node_modules/buffer'),
stream: 'stream-browserify',
crypto: 'crypto-browserify',
},
},
module: {
Expand Down
6,624 changes: 4,312 additions & 2,312 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
},
Expand Down
4 changes: 4 additions & 0 deletions src/background/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)) {
Expand Down
8 changes: 8 additions & 0 deletions src/config/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -71,6 +72,7 @@ export const poeWebModelKeys = [
* @type {Object.<string,Model>}
*/
export const Models = {
chatglmTurbo: { value: 'chatglm_turbo', desc: 'ChatGLM (ChatGLM-Turbo)' },
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)' },
Expand Down Expand Up @@ -152,6 +154,7 @@ export const defaultConfig = {
poeCustomBotName: '',

claudeApiKey: '',
chatglmApiKey: '',

customApiKey: '',

Expand Down Expand Up @@ -181,6 +184,7 @@ export const defaultConfig = {

alwaysCreateNewConversationWindow: false,
activeApiModes: [
'chatglmTurbo',
// 'claude2Api',
'chatgptFree35',
//'chatgptFree35Mobile',
Expand Down Expand Up @@ -290,6 +294,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'
}
Expand Down
13 changes: 13 additions & 0 deletions src/popup/sections/GeneralPart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { openUrl } from '../../utils/index.mjs'
import {
isUsingApiKey,
isUsingAzureOpenAi,
isUsingChatGLMApi,
isUsingClaude2Api,
isUsingCustomModel,
isUsingCustomNameOnlyModel,
Expand Down Expand Up @@ -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
Expand Down
115 changes: 115 additions & 0 deletions src/services/apis/chatglm-api.mjs
Original file line number Diff line number Diff line change
@@ -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 changes: 8 additions & 1 deletion src/utils/eventsource-parser.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ function createParser(onParse) {
let eventId
let eventName
let data
let extra
reset()
return {
feed,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -120,6 +123,10 @@ function createParser(onParse) {
value: retry,
})
}
} else {
const str = `{"${field}":${value}}`
extra = extra ?? []
extra.push(JSON.parse(str))
}
}
}
Expand Down
43 changes: 43 additions & 0 deletions src/utils/jwt-token-generator.mjs
Original file line number Diff line number Diff line change
@@ -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 }

0 comments on commit ea90c23

Please sign in to comment.