diff --git a/README.md b/README.md index ec523ae..c9bfe2d 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,9 @@ * 2023-09-10 支持来自claude.ai的claude-2模型 * 2023-10-19 支持读取文件,(目前适配必应模式和Claude2模式) * 2023-10-25 增加支持通义千问官方API +* 2023-12-01 持续优先适配Shamrock +* 2023-12-14 增加支持Gemini 官方API + ### 如果觉得这个插件有趣或者对你有帮助,请点一个star吧! ## 版本要求 diff --git a/apps/chat.js b/apps/chat.js index 099c960..3cc9423 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -4,7 +4,6 @@ import { Config, defaultOpenAIAPI } from '../utils/config.js' import { v4 as uuid } from 'uuid' import delay from 'delay' import { ChatGPTAPI } from '../utils/openai/chatgpt-api.js' -import { BingAIClient } from '@waylaidwanderer/chatgpt-api' import SydneyAIClient from '../utils/SydneyAIClient.js' import { PoeClient } from '../utils/poe/index.js' import AzureTTS from '../utils/tts/microsoft-azure.js' @@ -78,6 +77,7 @@ import { ClaudeAIClient } from '../utils/claude.ai/index.js' import { getProxy } from '../utils/proxy.js' import { QwenApi } from '../utils/alibaba/qwen-api.js' import { getChatHistoryGroup } from '../utils/chat.js' +import { GoogleGeminiClient } from '../client/GoogleGeminiClient.js' try { await import('@azure/openai') @@ -194,6 +194,12 @@ export class chatgpt extends plugin { /** 执行方法 */ fnc: 'qwen' }, + { + /** 命令正则匹配 */ + reg: '^#gemini[sS]*', + /** 执行方法 */ + fnc: 'gemini' + }, { /** 命令正则匹配 */ reg: toggleMode === 'at' ? '^[^#][sS]*' : '^#chat[^gpt][sS]*', @@ -397,6 +403,14 @@ export class chatgpt extends plugin { await redis.del(`CHATGPT:CONVERSATIONS_QWEN:${e.sender.user_id}`) await this.reply('已结束当前对话,请@我进行聊天以开启新的对话', true) } + } else if (use === 'gemini') { + let c = await redis.get(`CHATGPT:CONVERSATIONS_GEMINI:${e.sender.user_id}`) + if (!c) { + await this.reply('当前没有开启对话', true) + } else { + await redis.del(`CHATGPT:CONVERSATIONS_GEMINI:${e.sender.user_id}`) + await this.reply('已结束当前对话,请@我进行聊天以开启新的对话', true) + } } else if (use === 'bing') { let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${e.sender.user_id}`) if (!c) { @@ -466,6 +480,14 @@ export class chatgpt extends plugin { await redis.del(`CHATGPT:CONVERSATIONS_QWEN:${qq}`) await this.reply(`已结束${atUser}的对话,TA仍可以@我进行聊天以开启新的对话`, true) } + } else if (use === 'gemini') { + let c = await redis.get(`CHATGPT:CONVERSATIONS_GEMINI:${qq}`) + if (!c) { + await this.reply(`当前${atUser}没有开启对话`, true) + } else { + await redis.del(`CHATGPT:CONVERSATIONS_GEMINI:${qq}`) + await this.reply(`已结束${atUser}的对话,TA仍可以@我进行聊天以开启新的对话`, true) + } } else if (use === 'bing') { let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${qq}`) if (!c) { @@ -597,6 +619,18 @@ export class chatgpt extends plugin { } break } + case 'gemini': { + let qcs = await redis.keys('CHATGPT:CONVERSATIONS_GEMINI:*') + for (let i = 0; i < qcs.length; i++) { + await redis.del(qcs[i]) + // todo clean last message id + if (Config.debug) { + logger.info('delete gemini conversation bind: ' + qcs[i]) + } + deleted++ + } + break + } } await this.reply(`结束了${deleted}个用户的对话。`, true) } @@ -1092,6 +1126,10 @@ export class chatgpt extends plugin { key = `CHATGPT:CONVERSATIONS_QWEN:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}` break } + case 'gemini': { + key = `CHATGPT:CONVERSATIONS_GEMINI:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}` + break + } } let ctime = new Date() previousConversation = (key ? await redis.get(key) : null) || JSON.stringify({ @@ -1387,155 +1425,38 @@ export class chatgpt extends plugin { } async chatgpt1 (e) { - if (!Config.allowOtherMode) { - return false - } - let ats = e.message.filter(m => m.type === 'at') - if (!(e.atme || e.atBot) && ats.length > 0) { - if (Config.debug) { - logger.mark('艾特别人了,没艾特我,忽略#chat1') - } - return false - } - let prompt = _.replace(e.raw_message.trimStart(), '#chat1', '').trim() - if (prompt.length === 0) { - return false - } - await this.abstractChat(e, prompt, 'api') - return true + return await this.otherMode(e, 'api', '#chat1') } async chatgpt3 (e) { - if (!Config.allowOtherMode) { - return false - } - let ats = e.message.filter(m => m.type === 'at') - if (!(e.atme || e.atBot) && ats.length > 0) { - if (Config.debug) { - logger.mark('艾特别人了,没艾特我,忽略#chat3') - } - return false - } - let prompt = _.replace(e.raw_message.trimStart(), '#chat3', '').trim() - if (prompt.length === 0) { - return false - } - await this.abstractChat(e, prompt, 'api3') - return true + return await this.otherMode(e, 'api3', '#chat3') } async chatglm (e) { - if (!Config.allowOtherMode) { - return false - } - let ats = e.message.filter(m => m.type === 'at') - if (!(e.atme || e.atBot) && ats.length > 0) { - if (Config.debug) { - logger.mark('艾特别人了,没艾特我,忽略#chatglm') - } - return false - } - let prompt = _.replace(e.raw_message.trimStart(), '#chatglm', '').trim() - if (prompt.length === 0) { - return false - } - await this.abstractChat(e, prompt, 'chatglm') - return true + return await this.otherMode(e, 'chatglm') } async bing (e) { - if (!Config.allowOtherMode) { - return false - } - let ats = e.message.filter(m => m.type === 'at') - if (!(e.atme || e.atBot) && ats.length > 0) { - if (Config.debug) { - logger.mark('艾特别人了,没艾特我,忽略#bing') - } - return false - } - let prompt = _.replace(e.raw_message.trimStart(), '#bing', '').trim() - if (prompt.length === 0) { - return false - } - await this.abstractChat(e, prompt, 'bing') - return true + return await this.otherMode(e, 'bing') } async claude2 (e) { - if (!Config.allowOtherMode) { - return false - } - let ats = e.message.filter(m => m.type === 'at') - if (!(e.atme || e.atBot) && ats.length > 0) { - if (Config.debug) { - logger.mark('艾特别人了,没艾特我,忽略#claude2') - } - return false - } - let prompt = _.replace(e.raw_message.trimStart(), '#claude2', '').trim() - if (prompt.length === 0) { - return false - } - await this.abstractChat(e, prompt, 'claude2') - return true + return await this.otherMode(e, 'claude2') } async claude (e) { - if (!Config.allowOtherMode) { - return false - } - let ats = e.message.filter(m => m.type === 'at') - if (!(e.atme || e.atBot) && ats.length > 0) { - if (Config.debug) { - logger.mark('艾特别人了,没艾特我,忽略#claude') - } - return false - } - let prompt = _.replace(e.raw_message.trimStart(), '#claude', '').trim() - if (prompt.length === 0) { - return false - } - await this.abstractChat(e, prompt, 'claude') - return true + return await this.otherMode(e, 'claude') } async qwen (e) { - if (!Config.allowOtherMode) { - return false - } - let ats = e.message.filter(m => m.type === 'at') - if (!(e.atme || e.atBot) && ats.length > 0) { - if (Config.debug) { - logger.mark('艾特别人了,没艾特我,忽略#xh') - } - return false - } - let prompt = _.replace(e.raw_message.trimStart(), '#qwen', '').trim() - if (prompt.length === 0) { - return false - } - await this.abstractChat(e, prompt, 'qwen') - return true + return await this.otherMode(e, 'gemini') + } + async gemini (e) { + return await this.otherMode(e, 'gemini') } async xh (e) { - if (!Config.allowOtherMode) { - return false - } - let ats = e.message.filter(m => m.type === 'at') - if (!(e.atme || e.atBot) && ats.length > 0) { - if (Config.debug) { - logger.mark('艾特别人了,没艾特我,忽略#xh') - } - return false - } - let prompt = _.replace(e.raw_message.trimStart(), '#xh', '').trim() - if (prompt.length === 0) { - return false - } - await this.abstractChat(e, prompt, 'xh') - return true + return await this.otherMode(e, 'xh') } async cacheContent (e, use, content, prompt, quote = [], mood = '', suggest = '', imgUrls = []) { @@ -1639,73 +1560,55 @@ export class chatgpt extends plugin { } // 重新拿存储的token,因为可能之前有过期的被删了 let abtrs = await getAvailableBingToken(conversation, throttledTokens) - if (Config.toneStyle === 'Sydney' || Config.toneStyle === 'Custom') { - bingToken = abtrs.bingToken - // eslint-disable-next-line no-unused-vars - allThrottled = abtrs.allThrottled - if (bingToken?.indexOf('=') > -1) { - cookies = bingToken - } - if (!bingAIClient.opts) { - bingAIClient.opts = {} - } - bingAIClient.opts.userToken = bingToken - bingAIClient.opts.cookies = cookies - // opt.messageType = allThrottled ? 'Chat' : 'SearchQuery' - if (Config.enableGroupContext && e.isGroup && typeof e.group.getMemberMap === 'function') { - try { - opt.groupId = e.group_id - opt.qq = e.sender.user_id - opt.nickname = e.sender.card - opt.groupName = e.group.name || e.group_name - opt.botName = e.isGroup ? (e.group.pickMember(getUin(e)).card || e.group.pickMember(getUin(e)).nickname) : e.bot.nickname - let master = (await getMasterQQ())[0] - if (master && e.group) { - opt.masterName = e.group.pickMember(parseInt(master)).card || e.group.pickMember(parseInt(master)).nickname - } - if (master && !e.group) { - opt.masterName = e.bot.getFriendList().get(parseInt(master))?.nickname - } - opt.chats = await getChatHistoryGroup(e, Config.groupContextLength) - } catch (err) { - logger.warn('获取群聊聊天记录失败,本次对话不携带聊天记录', err) - } - } - let toSummaryFileContent - try { - if (e.source) { - let seq = e.isGroup ? e.source.seq : e.source.time - if (e.adapter === 'shamrock') { - seq = e.source.message_id - } - let msgs = e.isGroup ? await e.group.getChatHistory(seq, 1) : await e.friend.getChatHistory(seq, 1) - let sourceMsg = msgs[msgs.length - 1] - let fileMsgElem = sourceMsg.file || sourceMsg.message.find(msg => msg.type === 'file') - if (fileMsgElem) { - toSummaryFileContent = await extractContentFromFile(fileMsgElem, e) - } - } - } catch (err) { - logger.warn('读取文件内容出错, 忽略文件内容', err) - } - opt.toSummaryFileContent = toSummaryFileContent - } else { - // 重新创建client,因为token可能换到别的了 - if (bingToken?.indexOf('=') > -1) { - cookies = bingToken - } - let bingOption = { - userToken: abtrs.bingToken, // "_U" cookie from bing.com - cookies, - debug: Config.debug, - proxy: Config.proxy, - host: Config.sydneyReverseProxy - } - if (Config.proxy && Config.sydneyReverseProxy && !Config.sydneyForceUseReverse) { - delete bingOption.host - } - bingAIClient = new BingAIClient(bingOption) + bingToken = abtrs.bingToken + // eslint-disable-next-line no-unused-vars + allThrottled = abtrs.allThrottled + if (bingToken?.indexOf('=') > -1) { + cookies = bingToken } + if (!bingAIClient.opts) { + bingAIClient.opts = {} + } + bingAIClient.opts.userToken = bingToken + bingAIClient.opts.cookies = cookies + // opt.messageType = allThrottled ? 'Chat' : 'SearchQuery' + if (Config.enableGroupContext && e.isGroup && typeof e.group.getMemberMap === 'function') { + try { + opt.groupId = e.group_id + opt.qq = e.sender.user_id + opt.nickname = e.sender.card + opt.groupName = e.group.name || e.group_name + opt.botName = e.isGroup ? (e.group.pickMember(getUin(e)).card || e.group.pickMember(getUin(e)).nickname) : e.bot.nickname + let master = (await getMasterQQ())[0] + if (master && e.group) { + opt.masterName = e.group.pickMember(parseInt(master)).card || e.group.pickMember(parseInt(master)).nickname + } + if (master && !e.group) { + opt.masterName = e.bot.getFriendList().get(parseInt(master))?.nickname + } + opt.chats = await getChatHistoryGroup(e, Config.groupContextLength) + } catch (err) { + logger.warn('获取群聊聊天记录失败,本次对话不携带聊天记录', err) + } + } + let toSummaryFileContent + try { + if (e.source) { + let seq = e.isGroup ? e.source.seq : e.source.time + if (e.adapter === 'shamrock') { + seq = e.source.message_id + } + let msgs = e.isGroup ? await e.group.getChatHistory(seq, 1) : await e.friend.getChatHistory(seq, 1) + let sourceMsg = msgs[msgs.length - 1] + let fileMsgElem = sourceMsg.file || sourceMsg.message.find(msg => msg.type === 'file') + if (fileMsgElem) { + toSummaryFileContent = await extractContentFromFile(fileMsgElem, e) + } + } + } catch (err) { + logger.warn('读取文件内容出错, 忽略文件内容', err) + } + opt.toSummaryFileContent = toSummaryFileContent // 写入图片数据 if (Config.sydneyImageRecognition) { const image = await getImg(e) @@ -2136,6 +2039,26 @@ export class chatgpt extends plugin { images: response.images } } + case 'gemini': { + let client = new GoogleGeminiClient({ + e, + userId: e.sender.user_id, + key: Config.geminiKey, + model: Config.geminiModel + }) + let option = { + stream: false, + onProgress: (data) => { + if (Config.debug) { + logger.info(data) + } + }, + parentMessageId: conversation.parentMessageId, + conversationId: conversation.conversationId, + system: Config.geminiPrompt + } + return await client.sendMessage(prompt, option) + } default: { // openai api let completionParams = {} @@ -2703,6 +2626,25 @@ export class chatgpt extends plugin { } return await this.chatGPTApi.sendMessage(prompt, sendMessageOption) } + + async otherMode (e, mode, pattern = `#${mode}`) { + if (!Config.allowOtherMode) { + return false + } + let ats = e.message.filter(m => m.type === 'at') + if (!(e.atme || e.atBot) && ats.length > 0) { + if (Config.debug) { + logger.mark('艾特别人了,没艾特我,忽略' + pattern) + } + return false + } + let prompt = _.replace(e.raw_message.trimStart(), pattern, '').trim() + if (prompt.length === 0) { + return false + } + await this.abstractChat(e, prompt, mode) + return true + } } async function getAvailableBingToken (conversation, throttled = []) { diff --git a/apps/management.js b/apps/management.js index 689dd28..20ecb30 100644 --- a/apps/management.js +++ b/apps/management.js @@ -126,6 +126,11 @@ export class ChatgptManagement extends plugin { fnc: 'useClaudeAISolution', permission: 'master' }, + { + reg: '^#chatgpt切换(Gemini|gemini)$', + fnc: 'useGeminiSolution', + permission: 'master' + }, { reg: '^#chatgpt切换星火$', fnc: 'useXinghuoBasedSolution', @@ -184,6 +189,11 @@ export class ChatgptManagement extends plugin { fnc: 'setAPIKey', permission: 'master' }, + { + reg: '^#chatgpt设置(Gemini|gemini)(Key|key)$', + fnc: 'setGeminiKey', + permission: 'master' + }, { reg: '^#chatgpt设置(API|api)设定$', fnc: 'setAPIPromptPrefix', @@ -902,6 +912,16 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, } } + async useGeminiSolution () { + let use = await redis.get('CHATGPT:USE') + if (use !== 'gemini') { + await redis.set('CHATGPT:USE', 'gemini') + await this.reply('已切换到基于Google Gemini的解决方案') + } else { + await this.reply('当前已经是gemini模式了') + } + } + async useXinghuoBasedSolution () { let use = await redis.get('CHATGPT:USE') if (use !== 'xh') { @@ -1148,6 +1168,21 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, this.finish('saveAPIKey') } + async setGeminiKey (e) { + this.setContext('saveGeminiKey') + await this.reply('请发送Gemini API Key.获取地址:https://makersuite.google.com/app/apikey', true) + return false + } + + async saveGeminiKey () { + if (!this.e.msg) return + let token = this.e.msg + // todo + Config.geminiKey = token + await this.reply('请发送Gemini API Key设置成功', true) + this.finish('saveGeminiKey') + } + async setXinghuoToken () { this.setContext('saveXinghuoToken') await this.reply('请发送星火的ssoSessionId', true) diff --git a/apps/prompts.js b/apps/prompts.js index a2b2809..a78e4d3 100644 --- a/apps/prompts.js +++ b/apps/prompts.js @@ -158,7 +158,8 @@ export class help extends plugin { api: 'promptPrefixOverride', Custom: 'sydney', claude: 'slackClaudeGlobalPreset', - qwen: 'promptPrefixOverride' + qwen: 'promptPrefixOverride', + gemini: 'geminiPrompt' } if (keyMap[use]) { @@ -171,7 +172,7 @@ export class help extends plugin { await redis.set(`CHATGPT:PROMPT_USE_${use}`, promptName) await e.reply(`你当前正在使用${use}模式,已将该模式设定应用为"${promptName}"。更该设定后建议结束对话以使设定更好生效`, true) } else { - await e.reply(`你当前正在使用${use}模式,该模式不支持设定。支持设定的模式有:API、自定义、Claude`, true) + await e.reply(`你当前正在使用${use}模式,该模式不支持设定。支持设定的模式有:API、自定义、Claude、通义千问和Gemini`, true) } } diff --git a/client/BaseClient.js b/client/BaseClient.js index 46232df..5f7bf1f 100644 --- a/client/BaseClient.js +++ b/client/BaseClient.js @@ -16,11 +16,13 @@ export class BaseClient { this.maxToken = 4096 this.tools = [] const { - e, getMessageById, upsertMessage + e, getMessageById, upsertMessage, deleteMessageById, userId } = props this.e = e this.getMessageById = getMessageById this.upsertMessage = upsertMessage + this.deleteMessageById = deleteMessageById || (() => {}) + this.userId = userId } /** @@ -42,14 +44,23 @@ export class BaseClient { */ upsertMessage + /** + * delete a message with the id + * + * @type function + * @param {string} id + * @return {Promise} + */ + deleteMessageById + /** * Send prompt message with history and return response message \ * if function called, handled internally \ * override this method to implement logic of sending and receiving message * - * @param msg - * @param opt other options, optional fields: [conversationId, parentMessageId], if not set, random uuid instead - * @returns {Promise} required fields: [text, conversationId, parentMessageId, id] + * @param {string} msg + * @param {{conversationId: string?, parentMessageId: string?, stream: boolean?, onProgress: function?}} opt other options, optional fields: [conversationId, parentMessageId], if not set, random uuid instead + * @returns {Promise<{text, conversationId, parentMessageId, id}>} required fields: [text, conversationId, parentMessageId, id] */ async sendMessage (msg, opt = {}) { throw new Error('not implemented in abstract client') @@ -60,11 +71,12 @@ export class BaseClient { * override this method to implement logic of getting history * keyv with local file or redis recommended * - * @param userId such as qq number - * @param opt other options - * @returns {Promise} + * @param userId optional, such as qq number + * @param parentMessageId if blank, no history + * @param opt optional, other options + * @returns {Promise} */ - async getHistory (userId, opt = {}) { + async getHistory (parentMessageId, userId = this.userId, opt = {}) { throw new Error('not implemented in abstract client') } diff --git a/client/GoogleGeminiClient.js b/client/GoogleGeminiClient.js new file mode 100644 index 0000000..abe6d69 --- /dev/null +++ b/client/GoogleGeminiClient.js @@ -0,0 +1,155 @@ +import { BaseClient } from './BaseClient.js' + +import { getMessageById, upsertMessage } from '../utils/common.js' +import crypto from 'crypto' +let GoogleGenerativeAI, HarmBlockThreshold, HarmCategory +try { + const GenerativeAI = await import('@google/generative-ai') + GoogleGenerativeAI = GenerativeAI.GoogleGenerativeAI + HarmBlockThreshold = GenerativeAI.HarmBlockThreshold + HarmCategory = GenerativeAI.HarmCategory +} catch (err) { + console.warn('未安装@google/generative-ai,无法使用Gemini,请在chatgpt-plugin目录下执行pnpm i安装新依赖') +} +export class GoogleGeminiClient extends BaseClient { + constructor (props) { + if (!props.upsertMessage) { + props.upsertMessage = async function umGemini (message) { + return await upsertMessage(message, 'Gemini') + } + } + if (!props.getMessageById) { + props.getMessageById = async function umGemini (message) { + return await getMessageById(message, 'Gemini') + } + } + super(props) + this._key = props.key + this._client = new GoogleGenerativeAI(this._key) + this.model = this._client.getGenerativeModel({ model: props.model }) + this.supportFunction = false + } + + async getHistory (parentMessageId, userId = this.userId, opt = {}) { + const history = [] + let cursor = parentMessageId + if (!cursor) { + return history + } + do { + let parentMessage = await this.getMessageById(cursor) + if (!parentMessage) { + break + } else { + history.push(parentMessage) + cursor = parentMessage.parentMessageId + if (!cursor) { + break + } + } + } while (true) + return history.reverse() + } + + async sendMessage (text, opt) { + let history = await this.getHistory(opt.parentMessageId) + let systemMessage = opt.system + if (systemMessage) { + history = history.reverse() + history.push({ + role: 'model', + parts: 'ok' + }) + history.push({ + role: 'user', + parts: systemMessage + }) + history = history.reverse() + } + const idUser = crypto.randomUUID() + const idModel = crypto.randomUUID() + let responseText = '' + try { + const chat = this.model.startChat({ + history, + // [ + // { + // role: 'user', + // parts: 'Hello, I have 2 dogs in my house.' + // }, + // { + // role: 'model', + // parts: 'Great to meet you. What would you like to know?' + // } + // ], + generationConfig: { + // todo configuration + maxOutputTokens: 1000, + temperature: 0.9, + topP: 0.1, + topK: 16 + }, + safetySettings: [ + // todo configuration + { + category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, + threshold: HarmBlockThreshold.BLOCK_NONE + }, + { + category: HarmCategory.HARM_CATEGORY_HARASSMENT, + threshold: HarmBlockThreshold.BLOCK_NONE + }, + { + category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, + threshold: HarmBlockThreshold.BLOCK_NONE + }, + { + category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, + threshold: HarmBlockThreshold.BLOCK_NONE + } + ] + }) + if (opt.stream && (typeof opt.onProgress === 'function')) { + const result = await chat.sendMessageStream(text) + responseText = '' + for await (const chunk of result.stream) { + const chunkText = chunk.text() + responseText += chunkText + await opt.onProgress(responseText) + } + return { + text: responseText, + conversationId: '', + parentMessageId: idUser, + id: idModel + } + } + const result = await chat.sendMessage(text) + const response = await result.response + responseText = response.text() + return { + text: responseText, + conversationId: '', + parentMessageId: idUser, + id: idModel + } + } finally { + await this.upsertMessage({ + role: 'user', + parts: text, + id: idUser, + parentMessageId: opt.parentMessageId || undefined + }) + await this.upsertMessage({ + role: 'model', + parts: responseText, + id: idModel, + parentMessageId: idUser + }) + } + } + + async destroyHistory (conversationId, opt = {}) { + // todo clean history + } +} diff --git a/guoba.support.js b/guoba.support.js index 84d4256..53f0666 100644 --- a/guoba.support.js +++ b/guoba.support.js @@ -743,15 +743,36 @@ export function supportGuoba () { component: 'Switch' }, { - label: '以下为杂七杂八的配置', + label: '以下为Gemini方式的配置', component: 'Divider' }, { - field: '2captchaToken', - label: '验证码平台Token', - bottomHelpMessage: '可注册2captcha实现跳过验证码,收费服务但很便宜。否则可能会遇到验证码而卡住', + field: 'geminiKey', + label: 'API密钥', + bottomHelpMessage: '前往https://makersuite.google.com/app/apikey获取', component: 'InputPassword' }, + { + field: 'geminiModel', + label: '模型', + bottomHelpMessage: '目前仅支持gemini-pro', + component: 'Input' + }, + { + field: 'geminiPrompt', + label: '设定', + component: 'InputTextArea' + }, + { + label: '以下为杂七杂八的配置', + component: 'Divider' + }, + // { + // field: '2captchaToken', + // label: '验证码平台Token', + // bottomHelpMessage: '可注册2captcha实现跳过验证码,收费服务但很便宜。否则可能会遇到验证码而卡住', + // component: 'InputPassword' + // }, { field: 'ttsSpace', label: 'vits-uma-genshin-honkai语音转换API地址', diff --git a/package.json b/package.json index b593ce4..ebe0814 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,9 @@ "@fastify/cors": "^8.2.0", "@fastify/static": "^6.9.0", "@fastify/websocket": "^8.2.0", + "@google/generative-ai": "^0.1.1", "@slack/bolt": "^3.13.2", - "@waylaidwanderer/chatgpt-api": "^1.37.1", "asn1.js": "^5.0.0", - "chatgpt": "^5.2.4", - "crypto": "^1.0.1", "delay": "^6.0.0", "diff": "^5.1.0", "emoji-strip": "^1.0.1", @@ -24,6 +22,7 @@ "js-tiktoken": "^1.0.5", "keyv": "^4.5.3", "keyv-file": "^0.2.0", + "lodash": "^4.17.21", "microsoft-cognitiveservices-speech-sdk": "1.32.0", "node-fetch": "^3.3.1", "openai": "^3.2.1", @@ -35,18 +34,18 @@ "ws": "^8.13.0" }, "optionalDependencies": { - "xlsx": "^0.18.5", - "mammoth": "^1.6.0", - "pdfjs-dist": "^3.11.174", - "nodejs-pptx": "^1.2.4", "@node-rs/jieba": "^1.6.2", "cycletls": "^1.0.21", "jimp": "^0.22.7", + "mammoth": "^1.6.0", "node-silk": "^0.1.0", + "nodejs-pptx": "^1.2.4", + "pdfjs-dist": "^3.11.174", "puppeteer-extra": "^3.3.6", "puppeteer-extra-plugin-recaptcha": "^3.6.8", "puppeteer-extra-plugin-stealth": "^2.11.2", - "sharp": "^0.32.3" + "sharp": "^0.32.3", + "xlsx": "^0.18.5" }, "devDependencies": { "ts-node": "^10.9.1", diff --git a/patches/@google__generative-ai@0.1.1.patch b/patches/@google__generative-ai@0.1.1.patch new file mode 100644 index 0000000..cc0efee --- /dev/null +++ b/patches/@google__generative-ai@0.1.1.patch @@ -0,0 +1,26 @@ +diff --git a/dist/index.js b/dist/index.js +index c71c104e7b8ee70ed1b5a5141d04c98109fe6439..2dd8b1f93de0e502729cb91c9618bf80e8559e1e 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -152,7 +152,7 @@ class GoogleGenerativeAIResponseError extends GoogleGenerativeAIError { + * See the License for the specific language governing permissions and + * limitations under the License. + */ +-const BASE_URL = "https://generativelanguage.googleapis.com"; ++const BASE_URL = "https://gemini.ikechan8370.com"; + const API_VERSION = "v1"; + /** + * We can't `require` package.json if this runs on web. We will use rollup to +diff --git a/dist/index.mjs b/dist/index.mjs +index 402a0c7fa5b692dea07d2dfd83e0148f0a493ca2..c48ce6d612a8752a5161da574804e7a830700d2c 100644 +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -150,7 +150,7 @@ class GoogleGenerativeAIResponseError extends GoogleGenerativeAIError { + * See the License for the specific language governing permissions and + * limitations under the License. + */ +-const BASE_URL = "https://generativelanguage.googleapis.com"; ++const BASE_URL = "https://gemini.ikechan8370.com"; + const API_VERSION = "v1"; + /** + * We can't `require` package.json if this runs on web. We will use rollup to \ No newline at end of file diff --git a/utils/config.js b/utils/config.js index 7e94acc..e2d22c8 100644 --- a/utils/config.js +++ b/utils/config.js @@ -162,6 +162,9 @@ const defaultConfig = { qwenSeed: 0, qwenTemperature: 1, qwenEnableSearch: true, + geminiKey: '', + geminiModel: 'gemini-pro', + geminiPrompt: 'You are Gemini. Your answer shouldn\'t be too verbose. Prefer to answer in Chinese.', version: 'v2.7.8' } const _path = process.cwd() diff --git a/utils/randomMessage.js b/utils/randomMessage.js index bbba39b..eab3917 100644 --- a/utils/randomMessage.js +++ b/utils/randomMessage.js @@ -1,5 +1,5 @@ import { Config } from './config.js' -import { ChatGPTAPI } from 'chatgpt' +import { ChatGPTAPI } from './openai/chatgpt-api.js' import fetch from 'node-fetch' import { getProxy } from './proxy.js' let proxy = getProxy()