chatgpt-plugin/client/ChatGLM4Client.js
2025-03-27 21:55:14 +08:00

191 lines
5.8 KiB
JavaScript

import { BaseClient } from './BaseClient.js'
import https from 'https'
import { Config } from '../utils/config.js'
import { createParser } from 'eventsource-parser'
const BASEURL = 'https://chatglm.cn/chatglm/backend-api/assistant/stream'
export class ChatGLM4Client extends BaseClient {
constructor (props) {
super(props)
this.baseUrl = props.baseUrl || BASEURL
this.supportFunction = false
this.debug = props.debug
this._refreshToken = props.refreshToken
this.if_plus_model = props.if_plus_model || false
}
async getAccessToken (refreshToken = this._refreshToken) {
if (redis) {
let lastToken = await redis.get('CHATGPT:CHATGLM4_ACCESS_TOKEN')
if (lastToken) {
this._accessToken = lastToken
// todo check token through user info endpoint
return
}
}
let res = await fetch('https://chatglm.cn/chatglm/backend-api/v1/user/refresh', {
method: 'POST',
body: '{}',
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
Origin: 'https://www.chatglm.cn',
Referer: 'https://www.chatglm.cn/main/detail',
Authorization: `Bearer ${refreshToken}`
}
})
let tokenRsp = await res.json()
let token = tokenRsp?.result?.accessToken
if (token) {
this._accessToken = token
redis && await redis.set('CHATGPT:CHATGLM4_ACCESS_TOKEN', token, { EX: 7000 })
// accessToken will expire in 2 hours
}
}
// todo https://chatglm.cn/chatglm/backend-api/v3/user/info query remain times
/**
*
* @param text
* @param {{conversationId: string?, stream: boolean?, onProgress: function?, image: string?}} opt
* @returns {Promise<{conversationId: string?, parentMessageId: string?, text: string, id: string, image: string?}>}
*/
async sendMessage (text, opt = {}) {
await this.getAccessToken()
if (!this._accessToken) {
throw new Error('accessToken for www.chatglm.cn not set')
}
let { conversationId, onProgress } = opt
const body = {
assistant_id: '65940acff94777010aa6b796', // chatglm4
conversation_id: conversationId || '',
meta_data: {
is_test: false,
input_question_type: 'xxxx',
if_plus_model: this.if_plus_model,
channel: ''
},
messages: [
{
role: 'user',
content: [
{
type: 'text',
text
}
]
}
]
}
let conversationResponse
let statusCode
let messageId
let image
let requestP = new Promise((resolve, reject) => {
let option = {
method: 'POST',
headers: {
accept: 'text/event-stream',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
authorization: `Bearer ${this._accessToken}`,
'content-type': 'application/json',
referer: 'https://www.chatglm.cn/main/alltoolsdetail',
origin: 'https://www.chatglm.cn',
'X-App-Platform': 'pc',
'X-App-Version': '0.0.1',
'App-Name': 'chatglm',
Host: 'chatglm.cn'
},
referrer: 'https://www.chatglm.cn/main/alltoolsdetail',
timeout: 60000
}
const req = https.request(BASEURL, option, (res) => {
statusCode = res.statusCode
let response
function onMessage (data) {
try {
const convoResponseEvent = JSON.parse(data)
conversationResponse = convoResponseEvent
if (convoResponseEvent.conversation_id) {
conversationId = convoResponseEvent.conversation_id
}
if (convoResponseEvent.id) {
messageId = convoResponseEvent.id
}
const partialResponse =
convoResponseEvent?.parts?.[0]
if (partialResponse) {
if (Config.debug) {
logger.info(JSON.stringify(convoResponseEvent))
}
response = partialResponse
if (onProgress && typeof onProgress === 'function') {
onProgress(partialResponse)
}
}
let content = partialResponse?.content[0]
if (content?.type === 'image' && content?.status === 'finish') {
image = content.image[0].image_url
}
if (convoResponseEvent.status === 'finish') {
resolve({
error: null,
response,
conversationId,
messageId,
conversationResponse,
image
})
}
} catch (err) {
console.warn('fetchSSE onMessage unexpected error', err)
reject(err)
}
}
const parser = createParser((event) => {
if (event.type === 'event') {
onMessage(event.data)
}
})
const errBody = []
res.on('data', (chunk) => {
if (statusCode === 200) {
let str = chunk.toString()
parser.feed(str)
}
errBody.push(chunk)
})
// const body = []
// res.on('data', (chunk) => body.push(chunk))
res.on('end', () => {
const resString = Buffer.concat(errBody).toString()
reject(resString)
})
})
req.on('error', (err) => {
reject(err)
})
req.on('timeout', () => {
req.destroy()
reject(new Error('Request time out'))
})
req.write(JSON.stringify(body))
req.end()
})
const res = await requestP
return {
text: res?.response?.content[0]?.text,
conversationId: res.conversationId,
id: res.messageId,
image,
raw: res?.response
}
}
}