diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a59924..1545a4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## 更新日志 +### V1.6.60(2024-08-06) +1、添加有道QAnything 支持 +2、添加CozeV3 支持 +3、更新机器人头像 +4、增长二维码过期时间 + ### V1.6.59(2024-07-12) 1、Dify 和 FastGPT 添加备注参数,可以获取用户的备注信息,更加个性化 diff --git a/package.json b/package.json index 3ed2b8b..2434cd1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wechaty-web-panel", - "version": "1.6.59", + "version": "1.6.60", "description": "智能微秘书插件", "exports": { ".": { @@ -51,21 +51,21 @@ "devDependencies": { "@chatie/semver": "^0.4.7", "@chatie/tsconfig": "^4.6.3", + "@grpc/grpc-js": "1.9.14", + "@juzi/wechaty": "^1.0.87", + "@juzi/wechaty-puppet": "^1.0.78", + "@juzi/wechaty-puppet-service": "^1.0.87", "babel-eslint": "^10.1.0", "eslint": "^7.4.0", "eslint-config-prettier": "^6.11.0", "eslint-plugin-prettier": "^3.1.4", "npm-run-all": "^4.1.5", "prettier": "^2.0.5", - "@juzi/wechaty": "^1.0.87", - "@grpc/grpc-js": "1.9.14", - "@juzi/wechaty-puppet": "^1.0.78", - "@juzi/wechaty-puppet-service": "^1.0.87", "wechaty": "^1.20.2", - "wechaty-puppet-service": "^1.18.2", - "wechaty-puppet-wechat4u": "^1.14.12", + "wechaty-puppet": "^1.21.1", "wechaty-puppet-padlocal": "^1.20.1", - "wechaty-puppet": "^1.21.1" + "wechaty-puppet-service": "^1.18.2", + "wechaty-puppet-wechat4u": "^1.14.12" }, "readme": "README.md", "engines": { @@ -73,6 +73,7 @@ "npm": ">=7" }, "dependencies": { + "@coze/coze-js": "^0.1.2", "@dqbd/tiktoken": "^1.0.2", "axios": "^1.6.6", "baidu-aip-sdk": "^4.16.10", diff --git a/src/botInstance/cozev3.js b/src/botInstance/cozev3.js new file mode 100644 index 0000000..e868ed4 --- /dev/null +++ b/src/botInstance/cozev3.js @@ -0,0 +1,136 @@ +import { CozeV3Api } from "./sdk/cozev3.js"; +import { addAichatRecord } from "../db/aichatDb.js"; +import { getPromotInfo } from "../proxy/aibotk.js"; +import { ContentCensor } from "../lib/contentCensor.js"; +import { getPuppetEol, isWindowsPlatform } from '../const/puppet-type.js' +import dayjs from "dayjs"; +import { extractImageLinks } from '../lib/index.js' + + +class CozeV3Ai { + constructor(config = { + isAiAgent: false, // 是否为 ai agent 模式 + showDownloadUrl: false, // 显示文件下载链接 + token: '', // api 秘钥 + proxyPass: '', // 请求地址 + showQuestion: true, // 显示原文 + timeoutMs: 60, // 超时时间 s + promotId: '', + systemMessage: '', // 预设promotion + }) { + this.cozeV3Chat = null; + this.config = { showDownloadUrl: false, isAiAgent: false, ...config }; + this.contentCensor = null + this.chatOption = {}; + this.eol = '\n' + this.iswindows = false; + } + + + async init() { + this.eol = await getPuppetEol(); + this.iswindows = await isWindowsPlatform() + if(this.config.promotId) { + const promotInfo = await getPromotInfo(this.config.promotId) + if(promotInfo) { + this.config.systemMessage = promotInfo.promot + } + } + if(this.config.filter) { + this.contentCensor = new ContentCensor(this.config.filterConfig) + } + const baseOptions = { + baseUrl: this.config.proxyPass, + apiKey: this.config.token, + botId: this.config.botId, + stream: this.config.stream, + debug: !!this.config.debug, + systemMessage: this.config.systemMessage || '', + } + + console.log(`api请求地址:${this.config.proxyPass}`); + this.cozeV3Chat = new CozeV3Api({ + ...baseOptions, + }); + } + /** + * 重置apikey + * @return {Promise} + */ + reset () { + this.cozeV3Chat = null + } + + + async getReply({ content, inputs }, id, adminId = '', systemMessage = '') { + try { + if(!this.cozeV3Chat) { + console.log('启用Coze v3对话平台'); + await this.init() + } + if(this.config.filter) { + const censor = await this.contentCensor.checkText(content) + if(!censor) { + console.log(`问题:${content},包含违规词,已拦截`); + return [{ type: 1, content: '这个话题不适合讨论,换个话题吧。' }] + } + } + if(systemMessage || content === 'reset' || content === '重置') { + console.log('重新更新上下文对话'); + this.chatOption[id] = {} + if(content === 'reset' || content === '重置') { + return [{type: 1, content: '上下文已重置'}] + } + } + const { conversationId, text } = systemMessage ? await this.cozeV3Chat.sendMessage(content, { ...this.chatOption[id], variables: inputs, systemMessage, timeoutMs: this.config.timeoutMs * 1000 || 80 * 1000, user: id }) : await this.cozeV3Chat.sendMessage(content, { ...this.chatOption[id], variables: inputs, timeoutMs: this.config.timeoutMs * 1000 || 80 * 1000, user: id }); + if(this.config.filter) { + const censor = await this.contentCensor.checkText(text) + if(!censor) { + console.log(`回复: ${text},包含违规词,已拦截`); + return [{ type: 1, content: '这个话题不适合讨论,换个话题吧。' }] + } + } + if(this.config.record) { + void addAichatRecord({ contactId: id, adminId, input: content, output: text, time: dayjs().format('YYYY-MM-DD HH:mm:ss') }) + } + // 保存对话id 对于同一个用户的对话不更新conversationId + if(!this.chatOption[id]?.conversationId) { + this.chatOption[id] = { + conversationId + }; + } + let replys = [] + let message; + if(this.config.showQuestion) { + message = `${content}${this.eol}-----------${this.eol}` + (this.iswindows ? text.replaceAll('\n', this.eol) : text); + } else { + message = this.iswindows ? text.replaceAll('\n', this.eol) : text; + } + const imgs = extractImageLinks(message) + + while (message.length > 1500) { + replys.push(message.slice(0, 1500)); + message = message.slice(1500); + } + replys.push(message); + replys = replys.map(item=> { + return { + type: 1, + content: item.trim() + } + }) + + if(imgs.length) { + console.log('提取到内容中的图片', imgs) + replys = replys.concat(imgs) + } + + return replys + } catch (e) { + console.log('Coze V3 请求报错:'+ e); + return [] + } + } +} + +export default CozeV3Ai; diff --git a/src/botInstance/qany.js b/src/botInstance/qany.js new file mode 100644 index 0000000..0e0a01f --- /dev/null +++ b/src/botInstance/qany.js @@ -0,0 +1,139 @@ +import { QAnyApi } from './sdk/qanything.js' +import { addAichatRecord } from '../db/aichatDb.js' +import { ContentCensor } from '../lib/contentCensor.js' +import { getPuppetEol, isWindowsPlatform } from '../const/puppet-type.js' +import dayjs from 'dayjs' +import { extractImageLinks } from '../lib/index.js' + +class QAnyAi { + constructor(config = { + token: '', // api 秘钥 + botId: '', // botId + proxyPass: '', // 请求地址 + showQuestion: true, // 显示原文 + showSuggestions: false, // 显示建议问题 + timeoutMs: 180, // 超时时间 s + promotId: '', + systemMessage: '' // 预设promotion + }) { + this.qanyChat = null + this.config = { ...config } + this.contentCensor = null + this.chatOption = {} + this.eol = '\n' + this.iswindows = false + } + + + async init() { + this.eol = await getPuppetEol() + this.iswindows = await isWindowsPlatform() + if (this.config.filter) { + this.contentCensor = new ContentCensor(this.config.filterConfig) + } + const baseOptions = { + apiKey: this.config.token, + apiBaseUrl: this.config.proxyPass, + debug: !!this.config.debug, + botId: this.config.botId, + } + + console.log(`api请求地址:${this.config.proxyPass}`) + this.qanyChat = new QAnyApi({ + ...baseOptions, + }) + } + + /** + * 重置apikey + * @return {Promise} + */ + reset() { + this.qanyChat = null + } + + + async getReply(content, uid, adminId = '', systemMessage = '') { + try { + if (!this.qanyChat) { + console.log('启用QAnything对话平台') + await this.init() + } + if (this.config.filter) { + const censor = await this.contentCensor.checkText(content) + if (!censor) { + console.log(`问题:${content},包含违规词,已拦截`) + return [{ type: 1, content: '这个话题不适合讨论,换个话题吧。' }] + } + } + if (content === 'reset' || content === '重置') { + console.log('重新更新上下文对话') + this.chatOption[uid] = { + needHistory: false + } + if (content === 'reset' || content === '重置') { + return [{ type: 1, content: '上下文已重置' }] + } + } + const { text, id } = await this.qanyChat.sendMessage(content, { + ...this.chatOption[uid], + timeoutMs: this.config.timeoutMs * 1000 || 180 * 1000, + user: uid + }) + if (this.config.filter) { + const censor = await this.contentCensor.checkText(text) + if (!censor) { + console.log(`回复: ${text},包含违规词,已拦截`) + return [{ type: 1, content: '这个话题不适合讨论,换个话题吧。' }] + } + } + if (this.config.record) { + void addAichatRecord({ + contactId: uid, + adminId, + input: content, + output: text, + time: dayjs().format('YYYY-MM-DD HH:mm:ss') + }) + } + // 保存对话id 对于同一个用户的对话不更新conversationId + + this.chatOption[uid] = { + needHistory: true + } + let replys = [] + let message + if (this.config.showQuestion) { + message = `${content}${this.eol}-----------${this.eol}` + (this.iswindows ? text.replaceAll('\n', this.eol) : text) + } else { + message = this.iswindows ? text.replaceAll('\n', this.eol) : text + } + + const imgs = extractImageLinks(message) + + while (message.length > 1500) { + replys.push(message.slice(0, 1500)) + message = message.slice(1500) + } + replys.push(message) + replys = replys.map(item => { + return { + type: 1, + content: item.trim() + } + }) + + if (imgs.length) { + console.log('提取到内容中的图片', imgs) + replys = replys.concat(imgs) + } + + return replys + } catch (e) { + console.log('QAnything请求报错:' + e) + return [] + } + } +} + +export default QAnyAi diff --git a/src/botInstance/sdk/cozev3.js b/src/botInstance/sdk/cozev3.js new file mode 100644 index 0000000..4b17402 --- /dev/null +++ b/src/botInstance/sdk/cozev3.js @@ -0,0 +1,320 @@ +import axios from 'axios'; +const BASE_URL = 'https://api.coze.cn'; + +const routes = { + // 创建会话 + creatConversation: { + method: 'POST', url: () => '/v1/conversation/create', + }, + // 发起对话 + creatChat: { + method: 'POST', url: () => '/v3/chat', + }, + // 查看对话消息详情 + getChatList: { + method: 'GET', url: () => '/v3/chat/message/list', + }, + // 轮询接口状态 + retrieve: { + method: 'GET', url: () => '/v3/chat/retrieve', + }, + // 文件上传 + fileUpload: { + method: 'POST', + url: () => '/v1/files/upload', + }, +}; + +class CozeClient { + constructor({ apiKey, baseUrl = BASE_URL, debug = false, systemMessage = null, stream = false, botId }) { + this.apiKey = apiKey; + this.baseUrl = baseUrl; + this.debug = debug; + this.stream = stream; + this.botId = botId; + this.systemMessage = systemMessage; + } + + updateApiKey(apiKey) { + this.apiKey = apiKey; + } + + async sendUploadRequest( + method, + endpoint, + data = null, + params = null, + stream = false, + headerParams = {} + ) { + const headers = { + ...{ + Authorization: `Bearer ${this.apiKey}`, + 'Content-Type': 'application/json', + }, + ...headerParams, + }; + + const url = `${this.baseUrl}${endpoint}`; + const response = await axios({ + method, + url, + data, + params, + headers, + responseType: 'json', + }); + + return response; + } + + async sendRequest({ method, endpoint, data, params, stream = false, headerParams = {}, timeoutMs = 100 * 1000 }) { + const headers = { + ...{ + Authorization: `Bearer ${this.apiKey}`, + 'Content-Type': 'application/json', + }, + ...headerParams, + }; + + const url = `${this.baseUrl}${endpoint}`; + let response; + if (this.debug) { + console.log('request', url, { data, headers, params }); + } + if (!stream) { + response = await axios.request({ + method, + url, + data: data || {}, + params: params || {}, + headers, + timeout: timeoutMs, + }); + } else { + response = await axios({ + method, + url, + data, + params, + headers, + responseType: 'stream', + }); + } + + return response; + } + + async getConversationId({ messages, timeoutMs }) { + const res = await this.sendRequest({ method: routes.creatConversation.method, + endpoint: routes.creatConversation.url(), + data: { + messages, + }, + stream: false, + timeoutMs }); + console.log(`创建新的会话, 获取会话Id: ${res.data.data.id}`); + return res.data.data.id; + } + async retrieveStatus({conversationId, chatId, timeoutMs}) { + return new Promise(async (resolve, reject)=> { + let time = 0 + const checkRetry = async () => { + const res = await this.sendRequest({ method: routes.retrieve.method, + endpoint: routes.retrieve.url(), + data: {}, + params: { + conversation_id: conversationId, + chat_id: chatId + }, + stream: false, + timeoutMs }); + time = time + 1 + if(res.data.code!==0) { + console.log('coze v3 请求失败', res.data.msg) + resolve(false) + } + const finishStatus = ['completed', 'required_action', 'canceled', 'failed'] + if(finishStatus.includes(res.data.data.status)) { + resolve(true) + } else if(time < 180) { + let timer = setTimeout(()=> { + void checkRetry() + clearTimeout(timer) + timer = null + }, 1200) + } else { + resolve(false) + } + } + void checkRetry() + }) + + } + + fileUpload(data) { + return this.sendUploadRequest(routes.fileUpload.method, routes.fileUpload.url(), data, null, false, { + 'Content-Type': 'multipart/form-data', + }); + } +} + +class CozeV3Api extends CozeClient { + async sendMessage(query, { + systemMessage, user, needConversation = true, conversationId = null, timeoutMs = 100 * 1000, files = null, variables, + }) { + let messages = [ + { + role: 'user', + content: query, + content_type: 'text', + }, + ]; + if (!conversationId && needConversation) { + conversationId = await this.getConversationId({ + messages, + timeoutMs, + }); + messages = []; + } + const data = { + bot_id: this.botId, + custom_variables: { + ...variables, + }, + additional_messages: messages, + user_id: user, + stream: this.stream, + auto_save_history: true, + }; + if (systemMessage || this.systemMessage) { + data.custom_variables.systemMessage = systemMessage || this.systemMessage; + } + const params = {}; + if (conversationId && needConversation) { + params.conversation_id = conversationId; + } + const res = await this.sendRequest({ + method: routes.creatChat.method, + endpoint: routes.creatChat.url(), + data, + params, + stream: this.stream, + timeoutMs, + }); + + const asyncSSE = stream => { + return new Promise((resolve, reject) => { + const answers = []; + let chatId = ''; + const chunks = []; + try { + stream.on('data', data => { + const result = Buffer.concat(chunks); + const streams = new TextDecoder('utf-8').decode(result, { stream: true }); + console.log('思考中', streams); + chunks.push(data); + }); + stream.on('end', async () => { + const result = Buffer.concat(chunks); + console.log('思考完毕,准备回复'); + const streams = new TextDecoder('utf-8').decode(result, { stream: true }).split('\n'); + for (const stream of streams) { + let res = {}; + try { + res = JSON.parse(stream.substring(5)) || {}; + } catch (e) { + // console.log('json 解析错误,不影响输出', e) + } + if (res && res.chat_id) { + chatId = res.chat_id; + } + if (res && res.conversation_id) { + conversationId = res.conversation_id; + } + if (chatId && conversationId) { + break; + } + if (res.type === 'answer') { + console.log('得到回复结果', res.content); + chatId = res.chat_id; + conversationId = res.conversation_id; + answers.push(res.content); + } + } + const list = chatId ? await this.getConversationMessages({ conversationId, chatId, timeoutMs }) : []; + let lastMessage = list.filter(item => item.type === 'answer'); + if (lastMessage.length) { + lastMessage = lastMessage[0]; + } + if (this.debug) { + console.log('获取最后一条对话记录', lastMessage); + } + let answer = ''; + if (lastMessage && lastMessage.content) { + answer = lastMessage.content; + } else { + answer = answers.join(''); + } + + resolve({ text: answer, conversationId, id: lastMessage.id || '' }); + }); + } catch (e) { + resolve({ text: `AI agent 出错,${e}`, conversationId: '', files: [] }); + } + }); + }; + if (!this.stream) { + console.log('正在思考中,请耐心等待...'); + const checkStatus = await this.retrieveStatus({ conversationId: res.data.data.conversation_id, chatId: res.data.data.id, timeoutMs }) + if(checkStatus) { + const list = await this.getConversationMessages({ conversationId: res.data.data.conversation_id, chatId: res.data.data.id, timeoutMs }); + let lastMessage = list.filter(item => item.type === 'answer'); + if (lastMessage.length) { + lastMessage = lastMessage[0]; + } + if (this.debug) { + console.log('获取最后一条对话记录', lastMessage); + } + let lanswer = ''; + if (lastMessage && lastMessage.content) { + lanswer = lastMessage.content; + } + return { text: lanswer, conversationId: res.data.data.conversation_id, id: lastMessage.id || '' } + } + return { text: '', conversationId: '', id: '', files: [] } + } else { + console.log('进入流式输出模式,请耐心等待模型的思考'); + const result = await asyncSSE(res.data); + + return result; + } + } + + async getConversationMessages({ chatId, timeoutMs, conversationId }) { + try { + const res = await this.sendRequest({ + method: routes.getChatList.method, + endpoint: routes.getChatList.url(), + params: { + chat_id: chatId, + conversation_id: conversationId, + }, + data: {}, + stream: false, + timeoutMs, + }); + + if (res.data.code === 0) { + return res.data.data; + } + console.log(`获取coze v3对话详情报错: ${JSON.stringify(res.data)}`); + return []; + } catch (e) { + console.log(`获取coze v3对话详情报错: ${e}`); + return []; + } + } +} + +export { CozeV3Api }; \ No newline at end of file diff --git a/src/botInstance/sdk/qanything.js b/src/botInstance/sdk/qanything.js new file mode 100644 index 0000000..2befe26 --- /dev/null +++ b/src/botInstance/sdk/qanything.js @@ -0,0 +1,148 @@ +import axios from 'axios'; +const BASE_URL = 'https://openapi.youdao.com'; + +const routes = { + // 发起对话 + creatChat: { + method: 'POST', url: () => '/q_anything/api/bot/chat_stream', + } +}; + +class QAnyClient { + constructor({ apiKey, baseUrl = BASE_URL, debug = false, stream = true, botId }) { + this.apiKey = apiKey; + this.baseUrl = baseUrl || BASE_URL; + this.debug = debug; + this.stream = stream; + this.botId = botId; + this.history = [] + } + + updateApiKey(apiKey) { + this.apiKey = apiKey; + } + + async sendRequest({ method, endpoint, data, params, stream = false, headerParams = {}, timeoutMs = 100 * 1000 }) { + const headers = { + ...{ + Authorization: `${this.apiKey}`, + 'Content-Type': 'application/json', + }, + ...headerParams, + }; + + const url = `${this.baseUrl}${endpoint}`; + let response; + if (this.debug) { + console.log('request', url, { data, headers, params }); + } + if (!stream) { + response = await axios.request({ + method, + url, + data: data || {}, + params: params || {}, + headers, + timeout: timeoutMs, + }); + } else { + response = await axios({ + method, + url, + data, + params, + headers, + responseType: 'stream', + }); + } + + return response; + } +} + +class QAnyApi extends QAnyClient { + async sendMessage(query, { + needHistory, + user, timeoutMs = 100 * 1000, variables, + }) { + if(needHistory) { + this.history = this.history.slice(-2) + } else { + this.history = [] + } + const data = { + uuid: this.botId, + question: query, + history: this.history + }; + + const res = await this.sendRequest({ + method: routes.creatChat.method, + endpoint: routes.creatChat.url(), + data, + stream: this.stream, + timeoutMs, + }); + + const asyncSSE = stream => { + return new Promise((resolve, reject) => { + const answers = []; + let answer = ''; + let id = ''; + const chunks = []; + try { + stream.on('data', data => { + chunks.push(data); + }); + stream.on('end', async () => { + const result = Buffer.concat(chunks); + console.log('思考完毕,准备回复'); + const streams = new TextDecoder('utf-8').decode(result, { stream: true }).split('\n'); + for (const stream of streams) { + let res = {}; + try { + res = JSON.parse(stream.substring(5)) || {}; + } catch (e) { + // console.log('json 解析错误,不影响输出', stream) + try { + res = JSON.parse(stream) || {}; + } catch (e) { + // console.log('json 解析错误,不影响输出', stream) + } + } + + if(res.errorCode && res.errorCode!=='0') { + console.log('QAnything 请求报错', res.msg) + answer = res.msg + } else if(res.errorCode) { + if(res.result.response && !res.result.singleQAId) { + answers.push(res.result.response); + } + if(res.result.singleQAId) { + this.history = res.result.history + id = res.result.singleQAId + answer = res.result.response + } + } + } + if(!answer) { + answer = answers.join(''); + } + resolve({ text: answer, conversationId: '', id: id }); + }); + } catch (e) { + resolve({ text: `agent 出错,${e}`, conversationId: '', files: [] }); + } + }); + }; + console.log('进入流式输出模式,请耐心等待模型的思考'); + const result = await asyncSSE(res.data); + + return result; + + } +} + +export { + QAnyApi +}; \ No newline at end of file diff --git a/src/botInstance/unOfficialOpenAi.js b/src/botInstance/unOfficialOpenAi.js deleted file mode 100644 index 66f4eb3..0000000 --- a/src/botInstance/unOfficialOpenAi.js +++ /dev/null @@ -1,137 +0,0 @@ -import proxy from "https-proxy-agent"; -import nodeFetch from "node-fetch"; -import { ChatGPTUnofficialProxyAPI } from './sdk/chatGPT.js' -import { addAichatRecord } from "../db/aichatDb.js"; -import dayjs from "dayjs"; -import { getPromotInfo } from "../proxy/aibotk.js"; -import { getPuppetEol } from "../const/puppet-type.js"; - -class UnOfficialOpenAi { - constructor(config = { - token: '', // token - debug: true, // 开启调试 - proxyPass: '', // 反向代理地址 - proxyUrl: '', // 代理地址 - showQuestion: false, // 显示原文 - systemMessage: '', // 预设promotion - timeoutMs: 60 // 超时时间 s - }) { - this.chatGPT = null; - this.config = config - this.chatOption = {}; - this.eol = '\n' - } - - async init() { - this.eol = await getPuppetEol(); - if(this.config.promotId) { - const promotInfo = await getPromotInfo(this.config.promotId) - if(promotInfo) { - this.config.systemMessage = promotInfo.promot - } - } - const baseOptions = { - accessToken: this.config.token, - debug: this.config.debug, - apiReverseProxyUrl: 'https://bypass.churchless.tech/api/conversation' - } - - if(this.config.proxyUrl) { - console.log(`启用代理请求:${this.config.proxyUrl}`); - this.chatGPT = new ChatGPTUnofficialProxyAPI({ - ...baseOptions, - fetch: (url, options = {}) => { - const defaultOptions = { - agent: proxy(this.config.proxyUrl), - }; - - const mergedOptions = { - ...defaultOptions, - ...options, - }; - return nodeFetch(url, mergedOptions); - }, - }); - return - } else if(this.config.proxyPass) { - console.log(`启用反向代理:${this.config.proxyPass}`); - baseOptions.apiReverseProxyUrl = this.config.proxyPass - } - - this.chatGPT = new ChatGPTUnofficialProxyAPI({ - ...baseOptions, - fetch: (url, options = {}) => { - const mergedOptions = { - ...options, - }; - return nodeFetch(url, mergedOptions); - }, - }); - } - - /** - * 重置实例 - * @return {Promise} - */ - reset() { - this.chatGPT = null - } - - async getReply(content, uid, adminId = '', systemMessage = '') { - try { - if(!this.chatGPT) { - console.log('看到此消息说明已启用chatGPT 网页hook版'); - await this.init() - } - let question = this.config.systemMessage ? this.config.systemMessage + content : content; - if(systemMessage || content === 'reset' || content === '重置') { - question = systemMessage + content - console.log('重新更新上下文对话'); - this.chatOption[uid] = {} - if(content === 'reset' || content === '重置') { - return [{type: 1, content: '上下文已重置'}] - } - } - - const { conversationId, text, id } = systemMessage ? await this.chatGPT.sendMessage(question, { ...this.chatOption[uid], systemMessage, timeoutMs: this.config.timeoutMs * 1000 || 80 * 1000 }):await this.chatGPT.sendMessage(question, { ...this.chatOption[uid], timeoutMs: this.config.timeoutMs * 1000 || 80 * 1000 }); - if(this.config.record) { - void addAichatRecord({ - contactId: uid, - adminId, - input: content, - output: text, - time: dayjs().format('YYYY-MM-DD HH:mm:ss') - }) - } - this.chatOption[uid] = { - conversationId, - parentMessageId: id, - }; - let replys = [] - let message; - if(this.config.showQuestion) { - message = `${content}${this.eol}----------${this.eol}` + text.replaceAll('\n', this.eol); - } else { - message = text.replaceAll('\n', this.eol); - } - while (message.length > 1000) { - replys.push(message.slice(0, 1000)); - message = message.slice(1000); - } - replys.push(message); - replys = replys.map(item=> { - return { - type: 1, - content: item.trim() - } - }) - return replys - } catch (e) { - console.log('chat gpt报错:'+ e); - return [] - } - } -} - -export default UnOfficialOpenAi; - diff --git a/src/common/index.js b/src/common/index.js index 4f9945c..eebcf92 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -115,7 +115,7 @@ async function updateContactInfo(that, noCache = false) { res.push(obj) } await updateFriendInfo(res, 80) - console.log(`更新群列表完毕,共获取到${realContact.length}个好友信息`) + console.log(`更新好友列表完毕,共获取到${realContact.length}个好友信息`) } catch (e) { console.log('e', e) } diff --git a/src/handlers/on-login.js b/src/handlers/on-login.js index 0f36c30..513791b 100644 --- a/src/handlers/on-login.js +++ b/src/handlers/on-login.js @@ -41,7 +41,7 @@ async function onLogin(user) { file = await user.avatar() if(file) { const base = await file.toBase64() - avatarUrl = base ? await putqn(base, userId):'' + avatarUrl = base ? await putqn(base, userInfo.robotId):'' } else { console.log('头像未获取到,不影响项目正常使用') } diff --git a/src/index.js b/src/index.js index 7b3fd75..108b990 100644 --- a/src/index.js +++ b/src/index.js @@ -45,7 +45,7 @@ function WechatyWebPanelPlugin(config = { apiKey, apiSecret, scanTimes }) { ignoreMessages: config.ignoreMessages || [], // 需要忽略的事件 ['scan', 'login', 'logout', 'friendship', 'room-join', 'room-topic', 'room-leave', 'message', 'ready', 'heartbeat', 'error'] ignoreEvents: config.ignoreEvents || [], - scanTimes: config.scanTimes || 50 + scanTimes: config.scanTimes || 800 } globalConfig.setApikey(initConfig.apiKey) addAibotConfig(initConfig) diff --git a/src/proxy/aibotk.js b/src/proxy/aibotk.js index f52e30b..ca703aa 100644 --- a/src/proxy/aibotk.js +++ b/src/proxy/aibotk.js @@ -216,6 +216,12 @@ async function getConfig() { presence_penalty: 1, maxToken: null, modelMaxToken: null, + cozev3_token: '', + cozev3_botId: '', + cozev3_baseUrl: '', + qany_botId: '', + qany_token: '', + qany_baseUrl: '', dify_token: '', dify_baseUrl: '', difyAgent: false, diff --git a/src/proxy/bot/chatgpt-web.js b/src/proxy/bot/chatgpt-web.js deleted file mode 100644 index 9dd7eec..0000000 --- a/src/proxy/bot/chatgpt-web.js +++ /dev/null @@ -1,42 +0,0 @@ -import UnOfficialOpenAi from '../../botInstance/unOfficialOpenAi.js' -import { getPromotInfo } from "../aibotk.js"; - -let chatGPT = {} - -export function reset(adminId) { - if(!chatGPT[adminId]) return - chatGPT[adminId].reset(); - chatGPT[adminId] = null; -} - -export function resetAll() { - Object.keys(chatGPT).forEach(key => { - if(chatGPT[key]) { - chatGPT[key].reset() - } - }) - chatGPT = {} -} - -export async function getChatGPTWebReply(content, uid, adminId, config={ token: "", debug: false, proxyPass: "", proxyUrl: "", systemMessage:'', showQuestion: false, timeoutMs: 80, keywordSystemMessages: [] }) { - if (!config.token) { - console.log('请到智能微秘书平台配置openaiAccessToken参数方可使用') - return [{ type: 1, content: '请到平台配置Openai openaiAccessToken参数方可使用' }] - } - - if(!chatGPT[adminId]) { - chatGPT[adminId] = new UnOfficialOpenAi(config) - } - let systemMessage = '' - if(config.keywordSystemMessages && config.keywordSystemMessages.length) { - const finalSystemMsg = config.keywordSystemMessages.find(item=> content.startsWith(item.keyword)) - if(finalSystemMsg && finalSystemMsg.promotId) { - const promotInfo = await getPromotInfo(finalSystemMsg.promotId) - console.log(`触发关键词角色功能,使用对应预设角色:${promotInfo.name}`); - systemMessage = promotInfo.promot - content = content.replace(finalSystemMsg.keyword, '') - } - } - - return await chatGPT[adminId].getReply(content, uid, adminId, systemMessage) -} diff --git a/src/proxy/bot/cozev3.js b/src/proxy/bot/cozev3.js new file mode 100644 index 0000000..1d690ce --- /dev/null +++ b/src/proxy/bot/cozev3.js @@ -0,0 +1,40 @@ +import CozeV3Ai from "../../botInstance/cozev3.js"; +import { getPromotInfo } from "../aibotk.js"; + +let cozeV3Ai = {} + +export function reset(adminId) { + if(!cozeV3Ai[adminId]) return + cozeV3Ai[adminId].reset(); + cozeV3Ai[adminId] = null; +} + +export function resetAll() { + Object.keys(cozeV3Ai).forEach(key => { + if(cozeV3Ai[key]) { + cozeV3Ai[key].reset() + } + }) + cozeV3Ai = {} +} +export async function getCozeV3AiReply({ content, inputs }, uid, adminId, config = { token: "", botId : '', debug: false, proxyPass: "", proxyUrl: "", showQuestion: false, stream: false, timeoutMs: 180, systemMessage: "", keywordSystemMessages: [] }) { + if (!config.token || !config.botId) { + console.log('请到智能微秘书平台配置Coze的 api秘钥方可使用') + return [{ type: 1, content: '请到智能微秘书平台配置Coze的 api秘钥方可使用' }] + } + + if(!cozeV3Ai[adminId]) { + cozeV3Ai[adminId] = new CozeV3Ai(config) + } + let systemMessage = '' + if(config.keywordSystemMessages && config.keywordSystemMessages.length) { + const finalSystemMsg = config.keywordSystemMessages.find(item=> content.startsWith(item.keyword)) + if(finalSystemMsg && finalSystemMsg.promotId) { + const promotInfo = await getPromotInfo(finalSystemMsg.promotId) + console.log(`触发关键词角色功能,使用对应预设角色:${promotInfo.name}`); + systemMessage = promotInfo.promot + content = content.replace(finalSystemMsg.keyword, '') + } + } + return await cozeV3Ai[adminId].getReply({ content, inputs }, uid, adminId, systemMessage) +} diff --git a/src/proxy/bot/dispatch.js b/src/proxy/bot/dispatch.js index ec12707..b6bec22 100644 --- a/src/proxy/bot/dispatch.js +++ b/src/proxy/bot/dispatch.js @@ -1,7 +1,8 @@ import { getChatGPTReply } from "./chatgpt.js"; -import { getChatGPTWebReply } from "./chatgpt-web.js"; import { getDifyAiReply } from "./dify.js"; import { getCozeReply } from './coze.js' +import { getCozeV3AiReply } from './cozev3.js' +import { getQAnyReply } from './qany.js' import { updateChatRecord } from "../aibotk.js"; import globalConfig from '../../db/global.js' @@ -21,12 +22,6 @@ export async function dispatchBot({botType, userAlias, content, id, uid, uname, res = await getChatGPTReply({ content }, id, adminId, config, false) replys = res break - case 7: - // ChatGPT web - console.log('进入聊天'); - res = await getChatGPTWebReply(content, id, adminId, config) - replys = res - break case 8: // dify ai console.log('进入Dify聊天'); @@ -35,14 +30,28 @@ export async function dispatchBot({botType, userAlias, content, id, uid, uname, break case 9: // fastGPT api + console.log('进入FastGPT聊天'); res = await getChatGPTReply({ content, variables: { uid, ualias: userAlias, uname, roomId, roomName } }, id, adminId, config, true) replys = res break case 11: // coze api + console.log('进入Coze V2聊天'); res = await getCozeReply(content, id, adminId, config) replys = res break + case 12: + // coze v3 api + console.log('进入Coze V3聊天'); + res = await getCozeV3AiReply({ content, inputs: { uid, ualias: userAlias, uname, roomId, roomName } }, id, adminId, config) + replys = res + break + case 13: + // QAnything api + console.log('进入有道QAnything聊天'); + res = await getQAnyReply(content, id, adminId, config) + replys = res + break default: replys = [] diff --git a/src/proxy/bot/qany.js b/src/proxy/bot/qany.js new file mode 100644 index 0000000..353c4aa --- /dev/null +++ b/src/proxy/bot/qany.js @@ -0,0 +1,30 @@ +import QAnyAi from "../../botInstance/qany.js"; + +let chatQAny = {} + +export function reset(adminId) { + if(!chatQAny[adminId]) return + chatQAny[adminId].reset(); + chatQAny[adminId] = null; +} + +export function resetAll() { + Object.keys(chatQAny).forEach(key => { + if(chatQAny[key]) { + chatQAny[key].reset() + } + }) + chatQAny = {} +} +export async function getQAnyReply(content, uid, adminId, config = { token: "", debug: false, proxyPass: "", proxyUrl: "", timeoutMs: 180 }) { + if (!config.token || !config.botId) { + console.log('请到智能微秘书平台配置QAnything token 和 botId参数方可使用') + return [{ type: 1, content: '请到平台配置QAnything token 和 botId参数方可使用' }] + } + + if(!chatQAny[adminId]) { + chatQAny[adminId] = new QAnyAi(config) + } + + return await chatQAny[adminId].getReply(content, uid, adminId, '') +} diff --git a/src/proxy/cozeV3Ai.js b/src/proxy/cozeV3Ai.js new file mode 100644 index 0000000..436c482 --- /dev/null +++ b/src/proxy/cozeV3Ai.js @@ -0,0 +1,61 @@ +import { allConfig } from "../db/configDb.js"; +import CozeV3Ai from "../botInstance/cozev3.js"; + +let cozeV3Ai = null; +/** + * 重置实例 + */ +export function reset() { + if(cozeV3Ai) { + cozeV3Ai.reset(); + cozeV3Ai = null + } +} + +export async function getCozeV3Reply({ content, id, inputs}) { + const config = await allConfig() + if (!config.cozev3_token || !config.cozev3_botId) { + console.log('请到智能微秘书平台配置Coze token 和 botId参数方可使用') + return [{ type: 1, content: '请到平台配置Coze token 和 botId参数方可使用' }] + } + const chatConfig = { + token: config.cozev3_token, // token + botId: config.cozev3_botId, // botId + debug: config.openaiDebug, // 开启调试 + proxyPass: config.cozev3_baseUrl, // 反向代理地址 + showQuestion: config.showQuestion, // 显示原文 + timeoutMs: config.openaiTimeout, // 超时时间 s + systemMessage: config.openaiSystemMessage, // 预设promotion + filter: config.chatFilter, + stream: config?.stream || false, + filterConfig: { + type: 1, + appId: config.filterAppid, + apiKey: config.filterApiKey, + secretKey: config.filterSecretKey + } + } + if(!cozeV3Ai) { + cozeV3Ai = new CozeV3Ai(chatConfig) + } + return await cozeV3Ai.getReply({ content, inputs }, id) +} + + +export async function getCozeV3SimpleReply({content, id, inputs, config}) { + if (!config.token) { + console.log('请到智能微秘书平台配置聊天总结API Token参数方可使用') + return [{ type: 1, content: '请到平台配置API Token参数方可使用' }] + } + const chatConfig = { + token: config.token, // token + debug: config.debug, // 开启调试 + proxyPass: config.baseUrl, // 反向代理地址 + showQuestion: false, // 显示原文 + timeoutMs: config.timeout, // 超时时间 s + systemMessage: '', // 预设promotion + stream: false, + } + + return await new CozeV3Ai(chatConfig).getReply({content, inputs }, id) +} diff --git a/src/proxy/mqtt.js b/src/proxy/mqtt.js index 9d013af..e59bdf3 100644 --- a/src/proxy/mqtt.js +++ b/src/proxy/mqtt.js @@ -6,13 +6,14 @@ import { dispatchEventContent } from '../service/event-dispatch-service.js' import { sendTaskMessage, initMultiTask, sendMultiTaskMessage } from "../task/index.js"; import { delay, randomRange } from "../lib/index.js"; import { reset } from './bot/chatgpt.js' -import { reset as webReset } from './bot/chatgpt-web.js' import { reset as difyReset } from './bot/dify.js' import { reset as cozeReset } from './bot/coze.js' +import { reset as qanyReset } from './bot/qany.js' +import { reset as cozeV3Reset } from './bot/cozev3.js' import { initRssTask, sendRssTaskMessage } from "../task/rss.js"; import globalConfig from "../db/global.js"; import { resetScanTime } from '../handlers/on-scan.js' -import {clearHistory} from "../db/chatHistory.js"; +import { clearHistory } from "../db/chatHistory.js"; let mqttclient = null @@ -186,9 +187,10 @@ async function initMqtt(that) { if(content.event === 'update' || content.event === 'delete') { console.log('更新自定义对话配置,重置对话') reset(content.updateId) - webReset(content.updateId) difyReset(content.updateId) cozeReset(content.updateId) + qanyReset(content.updateId) + cozeV3Reset(content.updateId) } } else if(topic === `aibotk/${userId}/rssconfig`) { console.log('更新rss配置') diff --git a/src/proxy/qAnyAi.js b/src/proxy/qAnyAi.js new file mode 100644 index 0000000..4f97f24 --- /dev/null +++ b/src/proxy/qAnyAi.js @@ -0,0 +1,59 @@ +import { allConfig } from "../db/configDb.js"; +import QAnyAi from "../botInstance/qany.js"; + +let chatQAny = null; +/** + * 重置实例 + */ +export function reset() { + if(chatQAny) { + chatQAny.reset(); + chatQAny = null + } +} + +export async function getQAnyReply(content, uid) { + const config = await allConfig() + if (!config.qany_token || !config.qany_botId) { + console.log('请到智能微秘书平台配置Coze Token 和 botId参数方可使用') + return [{ type: 1, content: '请到平台配置Coze Token 和 botId 参数方可使用' }] + } + const chatConfig = { + token: config.qany_token, // token + debug: config.openaiDebug, // 开启调试 + botId: config.qany_botId, + proxyPass: config.qany_baseUrl, // 反向代理地址 + showQuestion: config.showQuestion, // 显示原文 + timeoutMs: config.openaiTimeout, // 超时时间 s + filter: config.chatFilter, + filterConfig: { + type: 1, + appId: config.filterAppid, + apiKey: config.filterApiKey, + secretKey: config.filterSecretKey + } + } + if(!chatQAny) { + chatQAny = new QAnyAi(chatConfig) + } + return await chatQAny.getReply(content, uid, '', '') +} + + +export async function getQAnySimpleReply({content, uid, config}) { + if (!config.token || !config.botId) { + console.log('请到智能微秘书平台配置聊天总结的QAnything Token 和 botId参数方可使用') + return [{ type: 1, content: '请到平台配置聊天总结的QAnything Token 和 botId 参数方可使用' }] + } + const chatConfig = { + token: config.token, // token + debug: config.debug, // 开启调试 + botId: config.botId, + proxyPass: config.baseUrl, // 反向代理地址 + showQuestion: false, // 显示原文 + showSuggestions: false, // 显示原文 + timeoutMs: config.timeout, // 超时时间 s + } + + return await new QAnyAi(chatConfig).getReply(content, uid, '', '') +} diff --git a/src/service/event-dispatch-service.js b/src/service/event-dispatch-service.js index 06c4b11..a0ea27e 100644 --- a/src/service/event-dispatch-service.js +++ b/src/service/event-dispatch-service.js @@ -6,9 +6,10 @@ import { updateContactAndRoom, updateContactOnly, updateRoomOnly } from '../comm import { getTencentOpenReply } from '../proxy/tencent-open.js' import { removeRecord } from "../db/roomDb.js"; import { getGptOfficialReply, reset as officialReset, getSimpleGptReply } from "../proxy/openAi.js"; -import { getGptUnOfficialReply, reset } from "../proxy/openAiHook.js"; import { getDifyReply, reset as difyReset, getDifySimpleReply } from "../proxy/difyAi.js"; +import { getCozeV3Reply, reset as cozeV3Reset, getCozeV3SimpleReply } from "../proxy/cozeV3Ai.js"; import { getCozeReply, reset as cozeReset, getCozeSimpleReply } from '../proxy/cozeAi.js' +import { getQAnyReply, reset as qanyReset, getQAnySimpleReply } from '../proxy/qAnyAi.js' import { outApi } from '../proxy/outapi.js' /** @@ -121,10 +122,11 @@ async function dispatchEventContent(that, eName, msg, name, id, avatar, room, ro await initTaskLocalSchedule(that) await initTimeSchedule(that) await initMultiTask(that) - reset(); officialReset(); difyReset(); cozeReset(); + qanyReset(); + cozeV3Reset(); content = '更新配置成功,请稍等一分钟后生效' break default: @@ -162,11 +164,6 @@ async function dispatchAiBot({ bot, msg, name, id, uid, uname, roomId, userAlias res = await getGptOfficialReply(msg, id, false) replys = res break - case 7: - // ChatGPT-hook - res = await getGptUnOfficialReply(msg, id) - replys = res - break case 8: // dify ai res = await getDifyReply({ content: msg, id, inputs: { uid, ualias: userAlias, uname, roomId, roomName } }) @@ -182,6 +179,16 @@ async function dispatchAiBot({ bot, msg, name, id, uid, uname, roomId, userAlias res = await getCozeReply(msg, id) replys = res break + case 12: + // coze v3 + res = await getCozeV3Reply({ content: msg, id, inputs: { uid, ualias: userAlias, uname, roomId, roomName } }) + replys = res + break + case 13: + // QAnything + res = await getQAnyReply(msg, id) + replys = res + break default: replys = [{ type: 1, content: '' }] break @@ -217,6 +224,11 @@ async function dispatchSummerBot({ content, id, uid, uname, roomId, roomName, us res = await getCozeSimpleReply({content, uid: id, config, isFastGPT:true}) replys = res break + case 12: + // coze v3 + res = await getCozeV3SimpleReply({content, id, inputs: { uid, uname, ualias: userAlias, roomId, roomName }, config}) + replys = res + break default: replys = [{ type: 1, content: '' }] break