mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 05:17:10 +00:00
feat: support claude api fix #659
This commit is contained in:
parent
8b2493a4bf
commit
79ab6cbd40
17 changed files with 859 additions and 563 deletions
|
|
@ -177,8 +177,11 @@ export class ChatGPTButtonHandler extends plugin {
|
|||
if (Config.chatglmRefreshToken) {
|
||||
buttons[buttons[0].length >= 4 ? 1 : 0].push(createButtonBase('ChatGLM4', '#glm4', false))
|
||||
}
|
||||
if (Config.claudeAISessionKey) {
|
||||
buttons[buttons[0].length >= 4 ? 1 : 0].push(createButtonBase('Claude', '#claude.ai', false))
|
||||
// 两个claude只显示一个 优先API
|
||||
if (Config.claudeApiKey) {
|
||||
buttons[buttons[0].length >= 4 ? 1 : 0].push(createButtonBase('Claude', '#claude', false))
|
||||
} else if (Config.claudeAISessionKey) {
|
||||
buttons[buttons[0].length >= 4 ? 1 : 0].push(createButtonBase('Claude.ai', '#claude.ai', false))
|
||||
}
|
||||
rows.push({
|
||||
buttons: buttons[0]
|
||||
|
|
|
|||
502
apps/chat.js
502
apps/chat.js
|
|
@ -8,7 +8,6 @@ import SydneyAIClient from '../utils/SydneyAIClient.js'
|
|||
import { PoeClient } from '../utils/poe/index.js'
|
||||
import AzureTTS from '../utils/tts/microsoft-azure.js'
|
||||
import VoiceVoxTTS from '../utils/tts/voicevox.js'
|
||||
import Version from '../utils/version.js'
|
||||
import {
|
||||
completeJSON,
|
||||
extractContentFromFile,
|
||||
|
|
@ -20,7 +19,6 @@ import {
|
|||
getImg,
|
||||
getMasterQQ,
|
||||
getMaxModelTokens,
|
||||
getMessageById,
|
||||
getOrDownloadFile,
|
||||
getUin,
|
||||
getUserData,
|
||||
|
|
@ -30,9 +28,9 @@ import {
|
|||
makeForwardMsg,
|
||||
randomString,
|
||||
render,
|
||||
renderUrl,
|
||||
upsertMessage
|
||||
renderUrl
|
||||
} from '../utils/common.js'
|
||||
|
||||
import { ChatGPTPuppeteer } from '../utils/browser.js'
|
||||
import { KeyvFile } from 'keyv-file'
|
||||
import { OfficialChatGPTClient } from '../utils/message.js'
|
||||
|
|
@ -41,8 +39,7 @@ import { deleteConversation, getConversations, getLatestMessageIdByConversationI
|
|||
import { convertSpeaker, speakers } from '../utils/tts.js'
|
||||
import ChatGLMClient from '../utils/chatglm.js'
|
||||
import { convertFaces } from '../utils/face.js'
|
||||
import { SlackClaudeClient } from '../utils/slack/slackClient.js'
|
||||
import { getPromptByName } from '../utils/prompts.js'
|
||||
import { originalValues, ConversationManager } from '../model/conversation.js'
|
||||
import BingDrawClient from '../utils/BingDraw.js'
|
||||
import XinghuoClient from '../utils/xinghuo/xinghuo.js'
|
||||
import Bard from '../utils/bard.js'
|
||||
|
|
@ -82,6 +79,8 @@ import { CustomGoogleGeminiClient } from '../client/CustomGoogleGeminiClient.js'
|
|||
import { resizeAndCropImage } from '../utils/dalle.js'
|
||||
import fs from 'fs'
|
||||
import { ChatGLM4Client } from '../client/ChatGLM4Client.js'
|
||||
import { ClaudeAPIClient } from '../client/ClaudeAPIClient.js'
|
||||
import { getMessageById, upsertMessage } from '../utils/history.js'
|
||||
|
||||
const roleMap = {
|
||||
owner: 'group owner',
|
||||
|
|
@ -107,8 +106,6 @@ try {
|
|||
let version = Config.version
|
||||
let proxy = getProxy()
|
||||
|
||||
const originalValues = ['星火', '通义千问', '克劳德', '克劳德2', '必应', 'api', 'API', 'api3', 'API3', 'glm', '巴德', '双子星', '双子座', '智谱']
|
||||
const correspondingValues = ['xh', 'qwen', 'claude', 'claude2', 'bing', 'api', 'api', 'api3', 'api3', 'chatglm', 'bard', 'gemini', 'gemini', 'chatglm4']
|
||||
/**
|
||||
* 每个对话保留的时长。单个对话内ai是保留上下文的。超时后销毁对话,再次对话创建新的对话。
|
||||
* 单位:秒
|
||||
|
|
@ -167,10 +164,6 @@ export class chatgpt extends plugin {
|
|||
/** 执行方法 */
|
||||
fnc: 'bing'
|
||||
},
|
||||
{
|
||||
reg: '^#claude开启新对话',
|
||||
fnc: 'newClaudeConversation'
|
||||
},
|
||||
{
|
||||
/** 命令正则匹配 */
|
||||
reg: '^#claude(2|3|.ai)[sS]*',
|
||||
|
|
@ -339,360 +332,13 @@ export class chatgpt extends plugin {
|
|||
* @returns {Promise<void>}
|
||||
*/
|
||||
async destroyConversations (e) {
|
||||
const userData = await getUserData(e.user_id)
|
||||
const match = e.msg.trim().match('^#?(.*)(结束|新开|摧毁|毁灭|完结)对话')
|
||||
console.log(match[1])
|
||||
let use
|
||||
if (match[1] && match[1] != 'chatgpt') {
|
||||
use = correspondingValues[originalValues.indexOf(match[1])]
|
||||
} else {
|
||||
use = (userData.mode === 'default' ? null : userData.mode) || await redis.get('CHATGPT:USE')
|
||||
}
|
||||
console.log(use)
|
||||
await redis.del(`CHATGPT:WRONG_EMOTION:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||
if (use === 'claude') {
|
||||
// let client = new SlackClaudeClient({
|
||||
// slackUserToken: Config.slackUserToken,
|
||||
// slackChannelId: Config.slackChannelId
|
||||
// })
|
||||
// await client.endConversation()
|
||||
await redis.del(`CHATGPT:SLACK_CONVERSATION:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||
await this.reply('claude对话已结束')
|
||||
return
|
||||
}
|
||||
if (use === 'claude2') {
|
||||
await redis.del(`CHATGPT:CLAUDE2_CONVERSATION:${e.sender.user_id}`)
|
||||
await this.reply('claude2对话已结束')
|
||||
return
|
||||
}
|
||||
if (use === 'xh') {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_XH:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||
await this.reply('星火对话已结束')
|
||||
return
|
||||
}
|
||||
if (use === 'bard') {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_BARD:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||
await this.reply('Bard对话已结束')
|
||||
return
|
||||
}
|
||||
let ats = e.message.filter(m => m.type === 'at')
|
||||
const isAtMode = Config.toggleMode === 'at'
|
||||
if (isAtMode) ats = ats.filter(item => item.qq !== getUin(e))
|
||||
if (ats.length === 0) {
|
||||
if (use === 'api3') {
|
||||
await redis.del(`CHATGPT:QQ_CONVERSATION:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
|
||||
} else if (use === 'bing') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||
if (!c) {
|
||||
await this.reply('当前没有开启对话', true)
|
||||
return
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_BING:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||
}
|
||||
const conversation = {
|
||||
store: new KeyvFile({ filename: 'cache.json' }),
|
||||
namespace: Config.toneStyle
|
||||
}
|
||||
let Keyv
|
||||
try {
|
||||
Keyv = (await import('keyv')).default
|
||||
} catch (err) {
|
||||
await this.reply('依赖keyv未安装,请执行pnpm install keyv', true)
|
||||
}
|
||||
const conversationsCache = new Keyv(conversation)
|
||||
logger.info(`SydneyUser_${e.sender.user_id}`, await conversationsCache.get(`SydneyUser_${e.sender.user_id}`))
|
||||
await conversationsCache.delete(`SydneyUser_${e.sender.user_id}`)
|
||||
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
|
||||
} else if (use === 'chatglm') {
|
||||
const conversation = {
|
||||
store: new KeyvFile({ filename: 'cache.json' }),
|
||||
namespace: 'chatglm_6b'
|
||||
}
|
||||
let Keyv
|
||||
try {
|
||||
Keyv = (await import('keyv')).default
|
||||
} catch (err) {
|
||||
await this.reply('依赖keyv未安装,请执行pnpm install keyv', true)
|
||||
}
|
||||
const conversationsCache = new Keyv(conversation)
|
||||
logger.info(`ChatGLMUser_${e.sender.user_id}`, await conversationsCache.get(`ChatGLMUser_${e.sender.user_id}`))
|
||||
await conversationsCache.delete(`ChatGLMUser_${e.sender.user_id}`)
|
||||
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
|
||||
} else if (use === 'api') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`)
|
||||
if (!c) {
|
||||
await this.reply('当前没有开启对话', true)
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`)
|
||||
await this.reply('已结束当前对话,请@我进行聊天以开启新的对话', true)
|
||||
}
|
||||
} else if (use === 'qwen') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_QWEN:${e.sender.user_id}`)
|
||||
if (!c) {
|
||||
await this.reply('当前没有开启对话', true)
|
||||
} else {
|
||||
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 === 'chatglm4') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_CHATGLM4:${e.sender.user_id}`)
|
||||
if (!c) {
|
||||
await this.reply('当前没有开启对话', true)
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_CHATGLM4:${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) {
|
||||
await this.reply('当前没有开启对话', true)
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_BING:${e.sender.user_id}`)
|
||||
await this.reply('已结束当前对话,请@我进行聊天以开启新的对话', true)
|
||||
}
|
||||
} else if (use === 'browser') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_BROWSER:${e.sender.user_id}`)
|
||||
if (!c) {
|
||||
await this.reply('当前没有开启对话', true)
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_BROWSER:${e.sender.user_id}`)
|
||||
await this.reply('已结束当前对话,请@我进行聊天以开启新的对话', true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let at = ats[0]
|
||||
let qq = at.qq
|
||||
let atUser = _.trimStart(at.text, '@')
|
||||
if (use === 'api3') {
|
||||
await redis.del(`CHATGPT:QQ_CONVERSATION:${qq}`)
|
||||
await this.reply(`${atUser}已退出TA当前的对话,TA仍可以@我进行聊天以开启新的对话`, true)
|
||||
} else if (use === 'bing') {
|
||||
const conversation = {
|
||||
store: new KeyvFile({ filename: 'cache.json' }),
|
||||
namespace: Config.toneStyle
|
||||
}
|
||||
let Keyv
|
||||
try {
|
||||
Keyv = (await import('keyv')).default
|
||||
} catch (err) {
|
||||
await this.reply('依赖keyv未安装,请执行pnpm install keyv', true)
|
||||
}
|
||||
const conversationsCache = new Keyv(conversation)
|
||||
await conversationsCache.delete(`SydneyUser_${qq}`)
|
||||
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
|
||||
} else if (use === 'chatglm') {
|
||||
const conversation = {
|
||||
store: new KeyvFile({ filename: 'cache.json' }),
|
||||
namespace: 'chatglm_6b'
|
||||
}
|
||||
let Keyv
|
||||
try {
|
||||
Keyv = (await import('keyv')).default
|
||||
} catch (err) {
|
||||
await this.reply('依赖keyv未安装,请执行pnpm install keyv', true)
|
||||
}
|
||||
const conversationsCache = new Keyv(conversation)
|
||||
logger.info(`ChatGLMUser_${e.sender.user_id}`, await conversationsCache.get(`ChatGLMUser_${e.sender.user_id}`))
|
||||
await conversationsCache.delete(`ChatGLMUser_${qq}`)
|
||||
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
|
||||
} else if (use === 'api') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS:${qq}`)
|
||||
if (!c) {
|
||||
await this.reply(`当前${atUser}没有开启对话`, true)
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS:${qq}`)
|
||||
await this.reply(`已结束${atUser}的对话,TA仍可以@我进行聊天以开启新的对话`, true)
|
||||
}
|
||||
} else if (use === 'qwen') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_QWEN:${qq}`)
|
||||
if (!c) {
|
||||
await this.reply(`当前${atUser}没有开启对话`, true)
|
||||
} else {
|
||||
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 === 'chatglm4') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_CHATGLM4:${qq}`)
|
||||
if (!c) {
|
||||
await this.reply(`当前${atUser}没有开启对话`, true)
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_CHATGLM4:${qq}`)
|
||||
await this.reply(`已结束${atUser}的对话,TA仍可以@我进行聊天以开启新的对话`, true)
|
||||
}
|
||||
} else if (use === 'bing') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${qq}`)
|
||||
if (!c) {
|
||||
await this.reply(`当前${atUser}没有开启对话`, true)
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_BING:${qq}`)
|
||||
await this.reply(`已结束${atUser}的对话,TA仍可以@我进行聊天以开启新的对话`, true)
|
||||
}
|
||||
} else if (use === 'browser') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_BROWSER:${qq}`)
|
||||
if (!c) {
|
||||
await this.reply(`当前${atUser}没有开启对话`, true)
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_BROWSER:${qq}`)
|
||||
await this.reply(`已结束${atUser}的对话,TA仍可以@我进行聊天以开启新的对话`, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
let manager = new ConversationManager(e)
|
||||
await manager.endConversation.bind(this)(e)
|
||||
}
|
||||
|
||||
async endAllConversations (e) {
|
||||
const match = e.msg.trim().match('^#?(.*)(结束|新开|摧毁|毁灭|完结)全部对话')
|
||||
console.log(match[1])
|
||||
let use
|
||||
if (match[1] && match[1] != 'chatgpt') {
|
||||
use = correspondingValues[originalValues.indexOf(match[1])]
|
||||
} else {
|
||||
use = await redis.get('CHATGPT:USE') || 'api'
|
||||
}
|
||||
console.log(use)
|
||||
let deleted = 0
|
||||
switch (use) {
|
||||
case 'claude': {
|
||||
let cs = await redis.keys('CHATGPT:SLACK_CONVERSATION:*')
|
||||
let we = await redis.keys('CHATGPT:WRONG_EMOTION:*')
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
await redis.del(cs[i])
|
||||
if (Config.debug) {
|
||||
logger.info('delete slack conversation of qq: ' + cs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
for (const element of we) {
|
||||
await redis.del(element)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'xh': {
|
||||
let cs = await redis.keys('CHATGPT:CONVERSATIONS_XH:*')
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
await redis.del(cs[i])
|
||||
if (Config.debug) {
|
||||
logger.info('delete xh conversation of qq: ' + cs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'bard': {
|
||||
let cs = await redis.keys('CHATGPT:CONVERSATIONS_BARD:*')
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
await redis.del(cs[i])
|
||||
if (Config.debug) {
|
||||
logger.info('delete bard conversation of qq: ' + cs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'bing': {
|
||||
let cs = await redis.keys('CHATGPT:CONVERSATIONS_BING:*')
|
||||
let we = await redis.keys('CHATGPT:WRONG_EMOTION:*')
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
await redis.del(cs[i])
|
||||
if (Config.debug) {
|
||||
logger.info('delete bing conversation of qq: ' + cs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
for (const element of we) {
|
||||
await redis.del(element)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'api': {
|
||||
let cs = await redis.keys('CHATGPT:CONVERSATIONS:*')
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
await redis.del(cs[i])
|
||||
if (Config.debug) {
|
||||
logger.info('delete api conversation of qq: ' + cs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'api3': {
|
||||
let qcs = await redis.keys('CHATGPT:QQ_CONVERSATION:*')
|
||||
for (let i = 0; i < qcs.length; i++) {
|
||||
await redis.del(qcs[i])
|
||||
// todo clean last message id
|
||||
if (Config.debug) {
|
||||
logger.info('delete conversation bind: ' + qcs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'chatglm': {
|
||||
let qcs = await redis.keys('CHATGPT:CONVERSATIONS_CHATGLM:*')
|
||||
for (let i = 0; i < qcs.length; i++) {
|
||||
await redis.del(qcs[i])
|
||||
// todo clean last message id
|
||||
if (Config.debug) {
|
||||
logger.info('delete chatglm conversation bind: ' + qcs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'qwen': {
|
||||
let qcs = await redis.keys('CHATGPT:CONVERSATIONS_QWEN:*')
|
||||
for (let i = 0; i < qcs.length; i++) {
|
||||
await redis.del(qcs[i])
|
||||
// todo clean last message id
|
||||
if (Config.debug) {
|
||||
logger.info('delete qwen conversation bind: ' + qcs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
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
|
||||
}
|
||||
case 'chatglm4': {
|
||||
let qcs = await redis.keys('CHATGPT:CONVERSATIONS_CHATGLM4:*')
|
||||
for (let i = 0; i < qcs.length; i++) {
|
||||
await redis.del(qcs[i])
|
||||
// todo clean last message id
|
||||
if (Config.debug) {
|
||||
logger.info('delete chatglm4 conversation bind: ' + qcs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
await this.reply(`结束了${deleted}个用户的对话。`, true)
|
||||
let manager = new ConversationManager(e)
|
||||
await manager.endAllConversations.bind(this)(e)
|
||||
}
|
||||
|
||||
async deleteConversation (e) {
|
||||
|
|
@ -1132,7 +778,7 @@ export class chatgpt extends plugin {
|
|||
num: 0
|
||||
}
|
||||
}
|
||||
} else if (use !== 'poe' && use !== 'claude') {
|
||||
} else if (use !== 'poe') {
|
||||
switch (use) {
|
||||
case 'api': {
|
||||
key = `CHATGPT:CONVERSATIONS:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
|
||||
|
|
@ -1174,6 +820,10 @@ export class chatgpt extends plugin {
|
|||
key = `CHATGPT:CONVERSATIONS_GEMINI:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
|
||||
break
|
||||
}
|
||||
case 'claude': {
|
||||
key = `CHATGPT:CONVERSATIONS_CLAUDE:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
|
||||
break
|
||||
}
|
||||
case 'chatglm4': {
|
||||
key = `CHATGPT:CONVERSATIONS_CHATGLM4:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
|
||||
break
|
||||
|
|
@ -1224,7 +874,7 @@ export class chatgpt extends plugin {
|
|||
// 字数超限直接返回
|
||||
return false
|
||||
}
|
||||
if (use !== 'api3' && use !== 'poe' && use !== 'claude') {
|
||||
if (use !== 'api3' && use !== 'poe') {
|
||||
previousConversation.conversation = {
|
||||
conversationId: chatMessage.conversationId
|
||||
}
|
||||
|
|
@ -1258,7 +908,7 @@ export class chatgpt extends plugin {
|
|||
}
|
||||
let response = chatMessage?.text?.replace('\n\n\n', '\n')
|
||||
// 过滤无法正常显示的emoji
|
||||
if (use === 'claude') response = response.replace(/:[a-zA-Z_]+:/g, '')
|
||||
// if (use === 'claude') response = response.replace(/:[a-zA-Z_]+:/g, '')
|
||||
let mood = 'blandness'
|
||||
if (!response) {
|
||||
await this.reply('没有任何回复', true)
|
||||
|
|
@ -1347,7 +997,7 @@ export class chatgpt extends plugin {
|
|||
})
|
||||
}
|
||||
// 处理内容和引用中的图片
|
||||
const regex = /\b((?:https?|ftp|file):\/\/[-a-zA-Z0-9+&@#\/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#\/%=~_|])/g
|
||||
const regex = /\b((?:https?|ftp|file):\/\/[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|])/g
|
||||
let responseUrls = response.match(regex)
|
||||
let imgUrls = []
|
||||
if (responseUrls) {
|
||||
|
|
@ -1439,10 +1089,6 @@ export class chatgpt extends plugin {
|
|||
this.reply('当前对话超过上限,已重置对话', false, { at: true })
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_BING:${e.sender.user_id}`)
|
||||
return false
|
||||
} else if (response === 'Unexpected message author.') {
|
||||
this.reply('无法回答当前话题,已重置对话', false, { at: true })
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_BING:${e.sender.user_id}`)
|
||||
return false
|
||||
} else if (response === 'Throttled: Request is throttled.') {
|
||||
this.reply('今日对话已达上限')
|
||||
return false
|
||||
|
|
@ -1476,7 +1122,7 @@ export class chatgpt extends plugin {
|
|||
}
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
if (use !== 'bing') {
|
||||
if (use === 'api3') {
|
||||
// 异常了也要腾地方(todo 大概率后面的也会异常,要不要一口气全杀了)
|
||||
await redis.lPop('CHATGPT:CHAT_QUEUE', 0)
|
||||
}
|
||||
|
|
@ -1484,11 +1130,11 @@ export class chatgpt extends plugin {
|
|||
await this.destroyConversations(err)
|
||||
await this.reply('当前对话异常,已经清除,请重试', true, { recallMsg: e.isGroup ? 10 : 0 })
|
||||
} else {
|
||||
if (err.length < 200) {
|
||||
await this.reply(`出现错误:${err}`, true, { recallMsg: e.isGroup ? 10 : 0 })
|
||||
let errorMessage = err?.message || err?.data?.message || (typeof (err) === 'object' ? JSON.stringify(err) : err) || '未能确认错误类型!'
|
||||
if (errorMessage.length < 200) {
|
||||
await this.reply(`出现错误:${errorMessage}`, true, { recallMsg: e.isGroup ? 10 : 0 })
|
||||
} else {
|
||||
// 这里是否还需要上传到缓存服务器呐?多半是代理服务器的问题,本地也修不了,应该不用吧。
|
||||
await this.renderImage(e, use, `通信异常,错误信息如下 \n \`\`\`${err?.message || err?.data?.message || (typeof (err) === 'object' ? JSON.stringify(err) : err) || '未能确认错误类型!'}\`\`\``, prompt)
|
||||
await this.renderImage(e, use, `出现异常,错误信息如下 \n \`\`\`${errorMessage}\`\`\``, prompt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1535,6 +1181,9 @@ export class chatgpt extends plugin {
|
|||
}
|
||||
|
||||
async cacheContent (e, use, content, prompt, quote = [], mood = '', suggest = '', imgUrls = []) {
|
||||
if (!Config.enableToolbox) {
|
||||
return
|
||||
}
|
||||
let cacheData = { file: '', status: '' }
|
||||
cacheData.file = randomString()
|
||||
const cacheresOption = {
|
||||
|
|
@ -1544,8 +1193,8 @@ export class chatgpt extends plugin {
|
|||
},
|
||||
body: JSON.stringify({
|
||||
content: {
|
||||
content: new Buffer.from(content).toString('base64'),
|
||||
prompt: new Buffer.from(prompt).toString('base64'),
|
||||
content: Buffer.from(content).toString('base64'),
|
||||
prompt: Buffer.from(prompt).toString('base64'),
|
||||
senderName: e.sender.nickname,
|
||||
style: Config.toneStyle,
|
||||
mood,
|
||||
|
|
@ -1576,7 +1225,7 @@ export class chatgpt extends plugin {
|
|||
|
||||
async renderImage (e, use, content, prompt, quote = [], mood = '', suggest = '', imgUrls = []) {
|
||||
let cacheData = await this.cacheContent(e, use, content, prompt, quote, mood, suggest, imgUrls)
|
||||
const template = use !== 'bing' ? 'content/ChatGPT/index' : 'content/Bing/index'
|
||||
// const template = use !== 'bing' ? 'content/ChatGPT/index' : 'content/Bing/index'
|
||||
if (cacheData.error || cacheData.status != 200) { await this.reply(`出现错误:${cacheData.error || 'server error ' + cacheData.status}`, true) } else { await this.reply(await renderUrl(e, (Config.viewHost ? `${Config.viewHost}/` : `http://127.0.0.1:${Config.serverPort || 3321}/`) + `page/${cacheData.file}?qr=${Config.showQRCode ? 'true' : 'false'}`, { retType: Config.quoteReply ? 'base64' : '', Viewport: { width: parseInt(Config.chatViewWidth), height: parseInt(parseInt(Config.chatViewWidth) * 0.56) }, func: (parseFloat(Config.live2d) && !Config.viewHost) ? 'window.Live2d == true' : '', deviceScaleFactor: parseFloat(Config.cloudDPR) }), e.isGroup && Config.quoteReply) }
|
||||
}
|
||||
|
||||
|
|
@ -1902,28 +1551,43 @@ export class chatgpt extends plugin {
|
|||
text: response.data
|
||||
}
|
||||
} else if (use === 'claude') {
|
||||
let client = new SlackClaudeClient({
|
||||
slackUserToken: Config.slackUserToken,
|
||||
slackChannelId: Config.slackChannelId
|
||||
// slack已经不可用,移除
|
||||
// let client = new SlackClaudeClient({
|
||||
// slackUserToken: Config.slackUserToken,
|
||||
// slackChannelId: Config.slackChannelId
|
||||
// })
|
||||
// let conversationId = await redis.get(`CHATGPT:SLACK_CONVERSATION:${e.sender.user_id}`)
|
||||
// if (!conversationId) {
|
||||
// // 如果是新对话
|
||||
// if (Config.slackClaudeEnableGlobalPreset && (useCast?.slack || Config.slackClaudeGlobalPreset)) {
|
||||
// // 先发送设定
|
||||
// let prompt = (useCast?.slack || Config.slackClaudeGlobalPreset)
|
||||
// let emotion = await AzureTTS.getEmotionPrompt(e)
|
||||
// if (emotion) {
|
||||
// prompt = prompt + '\n' + emotion
|
||||
// }
|
||||
// await client.sendMessage(prompt, e)
|
||||
// logger.info('claudeFirst:', prompt)
|
||||
// }
|
||||
// }
|
||||
// let text = await client.sendMessage(prompt, e)
|
||||
// return {
|
||||
// text
|
||||
// }
|
||||
const client = new ClaudeAPIClient({
|
||||
key: Config.claudeApiKey,
|
||||
model: Config.claudeApiModel || 'claude-3-sonnet-20240229',
|
||||
debug: true,
|
||||
baseUrl: Config.claudeApiBaseUrl
|
||||
// temperature: Config.claudeApiTemperature || 0.5
|
||||
})
|
||||
let conversationId = await redis.get(`CHATGPT:SLACK_CONVERSATION:${e.sender.user_id}`)
|
||||
if (!conversationId) {
|
||||
// 如果是新对话
|
||||
if (Config.slackClaudeEnableGlobalPreset && (useCast?.slack || Config.slackClaudeGlobalPreset)) {
|
||||
// 先发送设定
|
||||
let prompt = (useCast?.slack || Config.slackClaudeGlobalPreset)
|
||||
let emotion = await AzureTTS.getEmotionPrompt(e)
|
||||
if (emotion) {
|
||||
prompt = prompt + '\n' + emotion
|
||||
}
|
||||
await client.sendMessage(prompt, e)
|
||||
logger.info('claudeFirst:', prompt)
|
||||
}
|
||||
}
|
||||
let text = await client.sendMessage(prompt, e)
|
||||
return {
|
||||
text
|
||||
}
|
||||
let rsp = await client.sendMessage(prompt, {
|
||||
stream: false,
|
||||
parentMessageId: conversation.parentMessageId,
|
||||
conversationId: conversation.conversationId,
|
||||
system: Config.claudeSystemPrompt
|
||||
})
|
||||
return rsp
|
||||
} else if (use === 'claude2') {
|
||||
let { conversationId } = conversation
|
||||
let client = new ClaudeAIClient({
|
||||
|
|
@ -2518,44 +2182,6 @@ export class chatgpt extends plugin {
|
|||
}
|
||||
}
|
||||
|
||||
async newClaudeConversation (e) {
|
||||
let presetName = e.msg.replace(/^#claude开启新对话/, '').trim()
|
||||
let client = new SlackClaudeClient({
|
||||
slackUserToken: Config.slackUserToken,
|
||||
slackChannelId: Config.slackChannelId
|
||||
})
|
||||
let response
|
||||
if (!presetName || presetName === '空' || presetName === '无设定') {
|
||||
let conversationId = await redis.get(`CHATGPT:SLACK_CONVERSATION:${e.sender.user_id}`)
|
||||
if (conversationId) {
|
||||
// 如果有对话进行中,先删除
|
||||
logger.info('开启Claude新对话,但旧对话未结束,自动结束上一次对话')
|
||||
await redis.del(`CHATGPT:SLACK_CONVERSATION:${e.sender.user_id}`)
|
||||
await redis.del(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`)
|
||||
}
|
||||
response = await client.sendMessage('', e)
|
||||
await this.reply(response, true)
|
||||
} else {
|
||||
let preset = getPromptByName(presetName)
|
||||
if (!preset) {
|
||||
await this.reply('没有这个设定', true)
|
||||
} else {
|
||||
let conversationId = await redis.get(`CHATGPT:SLACK_CONVERSATION:${e.sender.user_id}`)
|
||||
if (conversationId) {
|
||||
// 如果有对话进行中,先删除
|
||||
logger.info('开启Claude新对话,但旧对话未结束,自动结束上一次对话')
|
||||
await redis.del(`CHATGPT:SLACK_CONVERSATION:${e.sender.user_id}`)
|
||||
await redis.del(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`)
|
||||
}
|
||||
logger.info('send preset: ' + preset.content)
|
||||
response = await client.sendMessage(preset.content, e) +
|
||||
await client.sendMessage(await AzureTTS.getEmotionPrompt(e), e)
|
||||
await this.reply(response, true)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async newxhBotConversation (e) {
|
||||
let botId = e.msg.replace(/^#星火助手/, '').trim()
|
||||
if (Config.xhmode != 'web') {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import VoiceVoxTTS, { supportConfigurations as voxRoleList } from '../utils/tts/
|
|||
import { supportConfigurations as azureRoleList } from '../utils/tts/microsoft-azure.js'
|
||||
import fetch from 'node-fetch'
|
||||
import { newFetch } from '../utils/proxy.js'
|
||||
import { createServer, runServer, stopServer } from '../server/index.js'
|
||||
|
||||
export class ChatgptManagement extends plugin {
|
||||
constructor (e) {
|
||||
|
|
@ -180,6 +181,11 @@ export class ChatgptManagement extends plugin {
|
|||
fnc: 'setAPIKey',
|
||||
permission: 'master'
|
||||
},
|
||||
{
|
||||
reg: '^#chatgpt设置(claude|Claude)(Key|key)$',
|
||||
fnc: 'setClaudeKey',
|
||||
permission: 'master'
|
||||
},
|
||||
{
|
||||
reg: '^#chatgpt设置(Gemini|gemini)(Key|key)$',
|
||||
fnc: 'setGeminiKey',
|
||||
|
|
@ -316,6 +322,11 @@ export class ChatgptManagement extends plugin {
|
|||
fnc: 'setXinghuoModel',
|
||||
permission: 'master'
|
||||
},
|
||||
{
|
||||
reg: '^#chatgpt设置(claude|Claude)模型$',
|
||||
fnc: 'setClaudeModel',
|
||||
permission: 'master'
|
||||
},
|
||||
{
|
||||
reg: '^#chatgpt必应(禁用|禁止|关闭|启用|开启)搜索$',
|
||||
fnc: 'switchBingSearch',
|
||||
|
|
@ -330,6 +341,11 @@ export class ChatgptManagement extends plugin {
|
|||
reg: '^#chatgpt(开启|关闭)(api|API)流$',
|
||||
fnc: 'switchStream',
|
||||
permission: 'master'
|
||||
},
|
||||
{
|
||||
reg: '^#chatgpt(开启|关闭)(工具箱|后台服务)$',
|
||||
fnc: 'switchToolbox',
|
||||
permission: 'master'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
@ -1255,6 +1271,25 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
|||
this.finish('saveAPIKey')
|
||||
}
|
||||
|
||||
async setClaudeKey (e) {
|
||||
this.setContext('saveClaudeKey')
|
||||
await this.reply('请发送Claude API Key', true)
|
||||
return false
|
||||
}
|
||||
|
||||
async saveClaudeKey () {
|
||||
if (!this.e.msg) return
|
||||
let token = this.e.msg
|
||||
if (!token.startsWith('sk-ant')) {
|
||||
await this.reply('Claude API Key格式错误。如果是格式特殊的非官方Key请前往锅巴或工具箱手动设置', true)
|
||||
this.finish('saveClaudeKey')
|
||||
return
|
||||
}
|
||||
Config.claudeKey = token
|
||||
await this.reply('Claude API Key设置成功', true)
|
||||
this.finish('saveClaudeKey')
|
||||
}
|
||||
|
||||
async setGeminiKey (e) {
|
||||
this.setContext('saveGeminiKey')
|
||||
await this.reply('请发送Gemini API Key.获取地址:https://makersuite.google.com/app/apikey', true)
|
||||
|
|
@ -1675,6 +1710,20 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
|||
this.finish('saveAPIModel')
|
||||
}
|
||||
|
||||
async setClaudeModel (e) {
|
||||
this.setContext('saveClaudeModel')
|
||||
await this.reply('请发送Claude模型,官方推荐模型:\nclaude-3-opus-20240229\nclaude-3-sonnet-20240229', true)
|
||||
return false
|
||||
}
|
||||
|
||||
async saveClaudeModel () {
|
||||
if (!this.e.msg) return
|
||||
let token = this.e.msg
|
||||
Config.claudeApiModel = token
|
||||
await this.reply('Claude模型设置成功', true)
|
||||
this.finish('saveClaudeModel')
|
||||
}
|
||||
|
||||
async setOpenAiBaseUrl (e) {
|
||||
this.setContext('saveOpenAiBaseUrl')
|
||||
await this.reply('请发送API反代', true)
|
||||
|
|
@ -1775,4 +1824,25 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
|||
await this.reply('好的,已经关闭API流式输出')
|
||||
}
|
||||
}
|
||||
|
||||
async switchToolbox (e) {
|
||||
if (e.msg.includes('开启')) {
|
||||
if (Config.enableToolbox) {
|
||||
await this.reply('已经开启了')
|
||||
return
|
||||
}
|
||||
Config.enableToolbox = true
|
||||
await this.reply('开启中', true)
|
||||
await runServer()
|
||||
await this.reply('好的,已经打开工具箱')
|
||||
} else {
|
||||
if (!Config.enableToolbox) {
|
||||
await this.reply('已经是关闭的了')
|
||||
return
|
||||
}
|
||||
Config.enableToolbox = false
|
||||
await stopServer()
|
||||
await this.reply('好的,已经关闭工具箱')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import plugin from '../../../lib/plugins/plugin.js'
|
||||
import {Config} from '../utils/config.js'
|
||||
import { Config } from '../utils/config.js'
|
||||
|
||||
export class ChatGPTMarkdownHandler extends plugin {
|
||||
constructor () {
|
||||
|
|
|
|||
|
|
@ -66,21 +66,6 @@ export class help extends plugin {
|
|||
fnc: 'helpPrompt',
|
||||
permission: 'master'
|
||||
}
|
||||
// {
|
||||
// reg: '^#(chatgpt|ChatGPT)(开启|关闭)洗脑$',
|
||||
// fnc: 'setSydneyBrainWash',
|
||||
// permission: 'master'
|
||||
// },
|
||||
// {
|
||||
// reg: '^#(chatgpt|ChatGPT)(设置)?洗脑强度',
|
||||
// fnc: 'setSydneyBrainWashStrength',
|
||||
// permission: 'master'
|
||||
// },
|
||||
// {
|
||||
// reg: '^#(chatgpt|ChatGPT)(设置)?洗脑名称',
|
||||
// fnc: 'setSydneyBrainWashName',
|
||||
// permission: 'master'
|
||||
// }
|
||||
]
|
||||
})
|
||||
}
|
||||
|
|
@ -152,7 +137,7 @@ export class help extends plugin {
|
|||
const keyMap = {
|
||||
api: 'promptPrefixOverride',
|
||||
bing: 'sydney',
|
||||
claude: 'slackClaudeGlobalPreset',
|
||||
claude: 'claudeSystemPrompt',
|
||||
qwen: 'promptPrefixOverride',
|
||||
gemini: 'geminiPrompt',
|
||||
xh: 'xhPrompt'
|
||||
|
|
|
|||
188
client/ClaudeAPIClient.js
Normal file
188
client/ClaudeAPIClient.js
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
import crypto from 'crypto'
|
||||
import { newFetch } from '../utils/proxy.js'
|
||||
import _ from 'lodash'
|
||||
import { getMessageById, upsertMessage } from '../utils/history.js'
|
||||
import { BaseClient } from './BaseClient.js'
|
||||
|
||||
const BASEURL = 'https://api.anthropic.com'
|
||||
|
||||
/**
|
||||
* @typedef {Object} Content
|
||||
* @property {string} model
|
||||
* @property {string} system
|
||||
* @property {number} max_tokens
|
||||
* @property {boolean} stream
|
||||
* @property {Array<{
|
||||
* role: 'user'|'assistant',
|
||||
* content: string|Array<{
|
||||
* type: 'text'|'image',
|
||||
* text?: string,
|
||||
* source?: {
|
||||
* type: 'base64',
|
||||
* media_type: 'image/jpeg'|'image/png'|'image/gif'|'image/webp',
|
||||
* data: string
|
||||
* }
|
||||
* }>
|
||||
* }>} messages
|
||||
*
|
||||
* Claude消息的基本格式
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ClaudeResponse
|
||||
* @property {string} id
|
||||
* @property {string} type
|
||||
* @property {number} role
|
||||
* @property {number} model
|
||||
* @property {number} stop_reason
|
||||
* @property {number} stop_sequence
|
||||
* @property {number} role
|
||||
* @property {boolean} stream
|
||||
* @property {Array<{
|
||||
* type: string,
|
||||
* text: string
|
||||
* }>} content
|
||||
* @property {Array<{
|
||||
* input_tokens: number,
|
||||
* output_tokens: number,
|
||||
* }>} usage
|
||||
*
|
||||
* Claude响应的基本格式
|
||||
*/
|
||||
|
||||
export class ClaudeAPIClient extends BaseClient {
|
||||
constructor (props) {
|
||||
if (!props.upsertMessage) {
|
||||
props.upsertMessage = async function umGemini (message) {
|
||||
return await upsertMessage(message, 'Claude')
|
||||
}
|
||||
}
|
||||
if (!props.getMessageById) {
|
||||
props.getMessageById = async function umGemini (message) {
|
||||
return await getMessageById(message, 'Claude')
|
||||
}
|
||||
}
|
||||
super(props)
|
||||
this.model = props.model
|
||||
this.key = props.key
|
||||
if (!this.key) {
|
||||
throw new Error('no claude API key')
|
||||
}
|
||||
this.baseUrl = props.baseUrl || BASEURL
|
||||
this.supportFunction = false
|
||||
this.debug = props.debug
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param text
|
||||
* @param {{conversationId: string?, parentMessageId: string?, stream: boolean?, onProgress: function?, functionResponse: FunctionResponse?, system: string?, image: string?, model: string?}} opt
|
||||
* @returns {Promise<{conversationId: string?, parentMessageId: string, text: string, id: string}>}
|
||||
*/
|
||||
async sendMessage (text, opt = {}) {
|
||||
let history = await this.getHistory(opt.parentMessageId)
|
||||
/**
|
||||
* 发送的body
|
||||
* @type {Content}
|
||||
* @see https://docs.anthropic.com/claude/reference/messages_post
|
||||
*/
|
||||
let body = {}
|
||||
if (opt.system) {
|
||||
body.system = opt.system
|
||||
}
|
||||
const idThis = crypto.randomUUID()
|
||||
const idModel = crypto.randomUUID()
|
||||
/**
|
||||
* @type {Array<{
|
||||
* role: 'user'|'assistant',
|
||||
* content: string|Array<{
|
||||
* type: 'text'|'image',
|
||||
* text?: string,
|
||||
* source?: {
|
||||
* type: 'base64',
|
||||
* media_type: 'image/jpeg'|'image/png'|'image/gif'|'image/webp',
|
||||
* data: string
|
||||
* }
|
||||
* }>
|
||||
* }>}
|
||||
*/
|
||||
let thisContent = [{ type: 'text', text }]
|
||||
if (opt.image) {
|
||||
thisContent.push({
|
||||
type: 'image',
|
||||
source: {
|
||||
type: 'base64',
|
||||
media_type: 'image/jpeg',
|
||||
data: opt.image
|
||||
}
|
||||
})
|
||||
}
|
||||
const thisMessage = {
|
||||
role: 'user',
|
||||
content: thisContent,
|
||||
id: idThis,
|
||||
parentMessageId: opt.parentMessageId || undefined
|
||||
}
|
||||
history.push(_.cloneDeep(thisMessage))
|
||||
let messages = history.map(h => { return { role: h.role, content: h.content } })
|
||||
body = Object.assign(body, {
|
||||
model: opt.model || this.model || 'claude-3-opus-20240229',
|
||||
max_tokens: opt.max_tokens || 1024,
|
||||
messages,
|
||||
stream: false
|
||||
})
|
||||
let url = `${this.baseUrl}/v1/messages`
|
||||
let result = await newFetch(url, {
|
||||
headers: {
|
||||
'anthropic-version': '2023-06-01',
|
||||
'x-api-key': this.key,
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
if (result.status !== 200) {
|
||||
throw new Error(await result.text())
|
||||
}
|
||||
/**
|
||||
* @type {ClaudeResponse}
|
||||
*/
|
||||
let response = await result.json()
|
||||
if (this.debug) {
|
||||
console.log(JSON.stringify(response))
|
||||
}
|
||||
await this.upsertMessage(thisMessage)
|
||||
const respMessage = Object.assign(response, {
|
||||
id: idModel,
|
||||
parentMessageId: idThis
|
||||
})
|
||||
await this.upsertMessage(respMessage)
|
||||
return {
|
||||
text: response.content[0].text,
|
||||
conversationId: '',
|
||||
parentMessageId: idThis,
|
||||
id: idModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { BaseClient } from './BaseClient.js'
|
||||
|
||||
import { getMessageById, upsertMessage } from '../utils/common.js'
|
||||
import { getMessageById, upsertMessage } from '../utils/history.js'
|
||||
import crypto from 'crypto'
|
||||
let GoogleGenerativeAI, HarmBlockThreshold, HarmCategory
|
||||
try {
|
||||
|
|
|
|||
27
client/test/ClaudeApiClientTest.js
Normal file
27
client/test/ClaudeApiClientTest.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// import { ClaudeAPIClient } from '../ClaudeAPIClient.js'
|
||||
//
|
||||
// async function test () {
|
||||
// const client = new ClaudeAPIClient({
|
||||
// key: 'sk-ant-api03-**************************************',
|
||||
// model: 'claude-3-opus-20240229',
|
||||
// debug: true,
|
||||
// // baseUrl: 'http://claude-api.ikechan8370.com'
|
||||
// })
|
||||
// let rsp = await client.sendMessage('你好')
|
||||
// console.log(rsp)
|
||||
// }
|
||||
// global.store = {}
|
||||
// global.redis = {
|
||||
// set: (key, val) => {
|
||||
// global.store[key] = val
|
||||
// },
|
||||
// get: (key) => {
|
||||
// return global.store[key]
|
||||
// }
|
||||
// }
|
||||
// global.logger = {
|
||||
// info: console.log,
|
||||
// warn: console.warn,
|
||||
// error: console.error
|
||||
// }
|
||||
// test()
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { SlackCozeClient } from '../CozeSlackClient.js'
|
||||
import fs from 'fs'
|
||||
global.store = {}
|
||||
// global.store = {}
|
||||
|
||||
// global.redis = {
|
||||
// set: (key, val) => {
|
||||
|
|
|
|||
|
|
@ -57,6 +57,12 @@ export function supportGuoba () {
|
|||
bottomHelpMessage: '将输出更多调试信息,如果不希望控制台刷屏的话,可以关闭',
|
||||
component: 'Switch'
|
||||
},
|
||||
{
|
||||
field: 'enableToolbox',
|
||||
label: '开启工具箱',
|
||||
bottomHelpMessage: '独立的后台管理面板(默认3321端口),与锅巴类似。工具箱会有额外占用,启动速度稍慢,酌情开启。修改后需重启生效!!!',
|
||||
component: 'Switch'
|
||||
},
|
||||
{
|
||||
field: 'enableMd',
|
||||
label: 'QQ开启markdown',
|
||||
|
|
@ -334,50 +340,44 @@ export function supportGuoba () {
|
|||
component: 'Input'
|
||||
},
|
||||
{
|
||||
label: '以下为Slack Claude方式的配置',
|
||||
label: '以下为Claude API方式的配置',
|
||||
component: 'Divider'
|
||||
},
|
||||
{
|
||||
field: 'slackUserToken',
|
||||
label: 'Slack用户Token',
|
||||
bottomHelpMessage: 'slackUserToken,在OAuth&Permissions页面获取。需要具有channels:history, chat:write, groups:history, im:history, mpim:history 这几个scope',
|
||||
field: 'claudeApiKey',
|
||||
label: 'claude API Key',
|
||||
bottomHelpMessage: '前往 https://console.anthropic.com/settings/keys 注册和生成',
|
||||
component: 'InputPassword'
|
||||
},
|
||||
{
|
||||
field: 'claudeApiModel',
|
||||
label: 'claude API 模型',
|
||||
bottomHelpMessage: '如 claude-3-sonnet-20240229 或 claude-3-opus-20240229',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'slackBotUserToken',
|
||||
label: 'Slack Bot Token',
|
||||
bottomHelpMessage: 'slackBotUserToken,在OAuth&Permissions页面获取。需要channels:history,groups:history,im:history 这几个scope',
|
||||
field: 'claudeApiBaseUrl',
|
||||
label: 'claude API 反代',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'slackClaudeUserId',
|
||||
label: 'Slack成员id',
|
||||
bottomHelpMessage: '在Slack中点击Claude头像查看详情,其中的成员ID复制过来',
|
||||
component: 'Input'
|
||||
field: 'claudeApiMaxToken',
|
||||
label: 'claude 最大回复token数',
|
||||
component: 'InputNumber'
|
||||
},
|
||||
{
|
||||
field: 'slackSigningSecret',
|
||||
label: 'Slack签名密钥',
|
||||
bottomHelpMessage: 'Signing Secret。在Basic Information页面获取',
|
||||
component: 'Input'
|
||||
field: 'claudeApiTemperature',
|
||||
label: 'claude 温度',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
min: 0,
|
||||
max: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'slackClaudeSpecifiedChannel',
|
||||
label: 'Slack指定频道',
|
||||
bottomHelpMessage: '为空时,将为每个qq号建立私有频道。若填写了,对话将发生在本频道。和其他人公用workspace时建议用这个',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'slackClaudeEnableGlobalPreset',
|
||||
label: 'Claude使用全局设定',
|
||||
bottomHelpMessage: '开启后,所有人每次发起新对话时,会先发送设定过去再开始对话,达到类似Bing自设定的效果。',
|
||||
component: 'Switch'
|
||||
},
|
||||
{
|
||||
field: 'slackClaudeGlobalPreset',
|
||||
label: 'Slack全局设定',
|
||||
bottomHelpMessage: '若启用全局设定,每个人都会默认使用这里的设定。',
|
||||
component: 'Input'
|
||||
field: 'claudeSystemPrompt',
|
||||
label: 'claude 设定',
|
||||
component: 'InputTextArea'
|
||||
},
|
||||
{
|
||||
label: '以下为Claude2方式的配置',
|
||||
|
|
|
|||
25
index.js
25
index.js
|
|
@ -1,9 +1,16 @@
|
|||
import fs from 'node:fs'
|
||||
import { Config } from './utils/config.js'
|
||||
import { createServer } from './server/index.js'
|
||||
import { createServer, runServer } from './server/index.js'
|
||||
|
||||
logger.info('**************************************')
|
||||
logger.info('chatgpt-plugin加载中')
|
||||
|
||||
if (!global.segment) {
|
||||
global.segment = (await import('oicq')).segment
|
||||
try {
|
||||
global.segment = (await import('icqq')).segment
|
||||
} catch (err) {
|
||||
global.segment = (await import('oicq')).segment
|
||||
}
|
||||
}
|
||||
|
||||
const files = fs.readdirSync('./plugins/chatgpt-plugin/apps').filter(file => file.endsWith('.js'))
|
||||
|
|
@ -19,7 +26,6 @@ ret = await Promise.allSettled(ret)
|
|||
let apps = {}
|
||||
for (let i in files) {
|
||||
let name = files[i].replace('.js', '')
|
||||
|
||||
if (ret[i].status !== 'fulfilled') {
|
||||
logger.error(`载入插件错误:${logger.red(name)}`)
|
||||
logger.error(ret[i].reason)
|
||||
|
|
@ -27,13 +33,22 @@ for (let i in files) {
|
|||
}
|
||||
apps[name] = ret[i].value[Object.keys(ret[i].value)[0]]
|
||||
}
|
||||
global.chatgpt = {
|
||||
|
||||
}
|
||||
// 启动服务器
|
||||
await createServer()
|
||||
logger.info('**************************************')
|
||||
if (Config.enableToolbox) {
|
||||
logger.info('开启工具箱配置项,工具箱启动中')
|
||||
await createServer()
|
||||
await runServer()
|
||||
logger.info('工具箱启动成功')
|
||||
} else {
|
||||
logger.info('提示:当前配置未开启chatgpt工具箱,可通过锅巴或`#chatgpt开启工具箱`指令开启')
|
||||
}
|
||||
logger.info('chatgpt-plugin加载成功')
|
||||
logger.info(`当前版本${Config.version}`)
|
||||
logger.info('仓库地址 https://github.com/ikechan8370/chatgpt-plugin')
|
||||
logger.info('文档地址 https://www.yunzai.chat')
|
||||
logger.info('插件群号 559567232')
|
||||
logger.info('**************************************')
|
||||
|
||||
|
|
|
|||
362
model/conversation.js
Normal file
362
model/conversation.js
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
import { getUin, getUserData } from '../utils/common.js'
|
||||
import { Config } from '../utils/config.js'
|
||||
import { KeyvFile } from 'keyv-file'
|
||||
import _ from 'lodash'
|
||||
|
||||
export const originalValues = ['星火', '通义千问', '克劳德', '克劳德2', '必应', 'api', 'API', 'api3', 'API3', 'glm', '巴德', '双子星', '双子座', '智谱']
|
||||
export const correspondingValues = ['xh', 'qwen', 'claude', 'claude2', 'bing', 'api', 'api', 'api3', 'api3', 'chatglm', 'bard', 'gemini', 'gemini', 'chatglm4']
|
||||
|
||||
export class ConversationManager {
|
||||
async endConversation (e) {
|
||||
const userData = await getUserData(e.user_id)
|
||||
const match = e.msg.trim().match('^#?(.*)(结束|新开|摧毁|毁灭|完结)对话')
|
||||
console.log(match[1])
|
||||
let use
|
||||
if (match[1] && match[1] != 'chatgpt') {
|
||||
use = correspondingValues[originalValues.indexOf(match[1])]
|
||||
} else {
|
||||
use = (userData.mode === 'default' ? null : userData.mode) || await redis.get('CHATGPT:USE')
|
||||
}
|
||||
console.log(use)
|
||||
await redis.del(`CHATGPT:WRONG_EMOTION:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||
// fast implementation
|
||||
if (use === 'claude') {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_CLAUDE:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||
await this.reply('claude对话已结束')
|
||||
return
|
||||
}
|
||||
if (use === 'claude2') {
|
||||
await redis.del(`CHATGPT:CLAUDE2_CONVERSATION:${e.sender.user_id}`)
|
||||
await this.reply('claude.ai对话已结束')
|
||||
return
|
||||
}
|
||||
if (use === 'xh') {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_XH:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||
await this.reply('星火对话已结束')
|
||||
return
|
||||
}
|
||||
if (use === 'bard') {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_BARD:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||
await this.reply('Bard对话已结束')
|
||||
return
|
||||
}
|
||||
let ats = e.message.filter(m => m.type === 'at')
|
||||
const isAtMode = Config.toggleMode === 'at'
|
||||
if (isAtMode) ats = ats.filter(item => item.qq !== getUin(e))
|
||||
if (ats.length === 0) {
|
||||
if (use === 'api3') {
|
||||
await redis.del(`CHATGPT:QQ_CONVERSATION:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
|
||||
} else if (use === 'bing') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||
if (!c) {
|
||||
await this.reply('当前没有开启对话', true)
|
||||
return
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_BING:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`)
|
||||
}
|
||||
const conversation = {
|
||||
store: new KeyvFile({ filename: 'cache.json' }),
|
||||
namespace: Config.toneStyle
|
||||
}
|
||||
let Keyv
|
||||
try {
|
||||
Keyv = (await import('keyv')).default
|
||||
} catch (err) {
|
||||
await this.reply('依赖keyv未安装,请执行pnpm install keyv', true)
|
||||
}
|
||||
const conversationsCache = new Keyv(conversation)
|
||||
logger.info(`SydneyUser_${e.sender.user_id}`, await conversationsCache.get(`SydneyUser_${e.sender.user_id}`))
|
||||
await conversationsCache.delete(`SydneyUser_${e.sender.user_id}`)
|
||||
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
|
||||
} else if (use === 'chatglm') {
|
||||
const conversation = {
|
||||
store: new KeyvFile({ filename: 'cache.json' }),
|
||||
namespace: 'chatglm_6b'
|
||||
}
|
||||
let Keyv
|
||||
try {
|
||||
Keyv = (await import('keyv')).default
|
||||
} catch (err) {
|
||||
await this.reply('依赖keyv未安装,请执行pnpm install keyv', true)
|
||||
}
|
||||
const conversationsCache = new Keyv(conversation)
|
||||
logger.info(`ChatGLMUser_${e.sender.user_id}`, await conversationsCache.get(`ChatGLMUser_${e.sender.user_id}`))
|
||||
await conversationsCache.delete(`ChatGLMUser_${e.sender.user_id}`)
|
||||
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
|
||||
} else if (use === 'api') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`)
|
||||
if (!c) {
|
||||
await this.reply('当前没有开启对话', true)
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`)
|
||||
await this.reply('已结束当前对话,请@我进行聊天以开启新的对话', true)
|
||||
}
|
||||
} else if (use === 'qwen') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_QWEN:${e.sender.user_id}`)
|
||||
if (!c) {
|
||||
await this.reply('当前没有开启对话', true)
|
||||
} else {
|
||||
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 === 'chatglm4') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_CHATGLM4:${e.sender.user_id}`)
|
||||
if (!c) {
|
||||
await this.reply('当前没有开启对话', true)
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_CHATGLM4:${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) {
|
||||
await this.reply('当前没有开启对话', true)
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_BING:${e.sender.user_id}`)
|
||||
await this.reply('已结束当前对话,请@我进行聊天以开启新的对话', true)
|
||||
}
|
||||
} else if (use === 'browser') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_BROWSER:${e.sender.user_id}`)
|
||||
if (!c) {
|
||||
await this.reply('当前没有开启对话', true)
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_BROWSER:${e.sender.user_id}`)
|
||||
await this.reply('已结束当前对话,请@我进行聊天以开启新的对话', true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let at = ats[0]
|
||||
let qq = at.qq
|
||||
let atUser = _.trimStart(at.text, '@')
|
||||
if (use === 'api3') {
|
||||
await redis.del(`CHATGPT:QQ_CONVERSATION:${qq}`)
|
||||
await this.reply(`${atUser}已退出TA当前的对话,TA仍可以@我进行聊天以开启新的对话`, true)
|
||||
} else if (use === 'bing') {
|
||||
const conversation = {
|
||||
store: new KeyvFile({ filename: 'cache.json' }),
|
||||
namespace: Config.toneStyle
|
||||
}
|
||||
let Keyv
|
||||
try {
|
||||
Keyv = (await import('keyv')).default
|
||||
} catch (err) {
|
||||
await this.reply('依赖keyv未安装,请执行pnpm install keyv', true)
|
||||
}
|
||||
const conversationsCache = new Keyv(conversation)
|
||||
await conversationsCache.delete(`SydneyUser_${qq}`)
|
||||
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
|
||||
} else if (use === 'chatglm') {
|
||||
const conversation = {
|
||||
store: new KeyvFile({ filename: 'cache.json' }),
|
||||
namespace: 'chatglm_6b'
|
||||
}
|
||||
let Keyv
|
||||
try {
|
||||
Keyv = (await import('keyv')).default
|
||||
} catch (err) {
|
||||
await this.reply('依赖keyv未安装,请执行pnpm install keyv', true)
|
||||
}
|
||||
const conversationsCache = new Keyv(conversation)
|
||||
logger.info(`ChatGLMUser_${e.sender.user_id}`, await conversationsCache.get(`ChatGLMUser_${e.sender.user_id}`))
|
||||
await conversationsCache.delete(`ChatGLMUser_${qq}`)
|
||||
await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true)
|
||||
} else if (use === 'api') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS:${qq}`)
|
||||
if (!c) {
|
||||
await this.reply(`当前${atUser}没有开启对话`, true)
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS:${qq}`)
|
||||
await this.reply(`已结束${atUser}的对话,TA仍可以@我进行聊天以开启新的对话`, true)
|
||||
}
|
||||
} else if (use === 'qwen') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_QWEN:${qq}`)
|
||||
if (!c) {
|
||||
await this.reply(`当前${atUser}没有开启对话`, true)
|
||||
} else {
|
||||
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 === 'chatglm4') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_CHATGLM4:${qq}`)
|
||||
if (!c) {
|
||||
await this.reply(`当前${atUser}没有开启对话`, true)
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_CHATGLM4:${qq}`)
|
||||
await this.reply(`已结束${atUser}的对话,TA仍可以@我进行聊天以开启新的对话`, true)
|
||||
}
|
||||
} else if (use === 'bing') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${qq}`)
|
||||
if (!c) {
|
||||
await this.reply(`当前${atUser}没有开启对话`, true)
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_BING:${qq}`)
|
||||
await this.reply(`已结束${atUser}的对话,TA仍可以@我进行聊天以开启新的对话`, true)
|
||||
}
|
||||
} else if (use === 'browser') {
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_BROWSER:${qq}`)
|
||||
if (!c) {
|
||||
await this.reply(`当前${atUser}没有开启对话`, true)
|
||||
} else {
|
||||
await redis.del(`CHATGPT:CONVERSATIONS_BROWSER:${qq}`)
|
||||
await this.reply(`已结束${atUser}的对话,TA仍可以@我进行聊天以开启新的对话`, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async endAllConversations (e) {
|
||||
const match = e.msg.trim().match('^#?(.*)(结束|新开|摧毁|毁灭|完结)全部对话')
|
||||
console.log(match[1])
|
||||
let use
|
||||
if (match[1] && match[1] != 'chatgpt') {
|
||||
use = correspondingValues[originalValues.indexOf(match[1])]
|
||||
} else {
|
||||
use = await redis.get('CHATGPT:USE') || 'api'
|
||||
}
|
||||
console.log(use)
|
||||
let deleted = 0
|
||||
switch (use) {
|
||||
case 'claude': {
|
||||
let cs = await redis.keys('CHATGPT:CONVERSATIONS_CLAUDE:*')
|
||||
let we = await redis.keys('CHATGPT:WRONG_EMOTION:*')
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
await redis.del(cs[i])
|
||||
if (Config.debug) {
|
||||
logger.info('delete claude conversation of qq: ' + cs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
for (const element of we) {
|
||||
await redis.del(element)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'xh': {
|
||||
let cs = await redis.keys('CHATGPT:CONVERSATIONS_XH:*')
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
await redis.del(cs[i])
|
||||
if (Config.debug) {
|
||||
logger.info('delete xh conversation of qq: ' + cs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'bard': {
|
||||
let cs = await redis.keys('CHATGPT:CONVERSATIONS_BARD:*')
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
await redis.del(cs[i])
|
||||
if (Config.debug) {
|
||||
logger.info('delete bard conversation of qq: ' + cs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'bing': {
|
||||
let cs = await redis.keys('CHATGPT:CONVERSATIONS_BING:*')
|
||||
let we = await redis.keys('CHATGPT:WRONG_EMOTION:*')
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
await redis.del(cs[i])
|
||||
if (Config.debug) {
|
||||
logger.info('delete bing conversation of qq: ' + cs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
for (const element of we) {
|
||||
await redis.del(element)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'api': {
|
||||
let cs = await redis.keys('CHATGPT:CONVERSATIONS:*')
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
await redis.del(cs[i])
|
||||
if (Config.debug) {
|
||||
logger.info('delete api conversation of qq: ' + cs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'api3': {
|
||||
let qcs = await redis.keys('CHATGPT:QQ_CONVERSATION:*')
|
||||
for (let i = 0; i < qcs.length; i++) {
|
||||
await redis.del(qcs[i])
|
||||
// todo clean last message id
|
||||
if (Config.debug) {
|
||||
logger.info('delete conversation bind: ' + qcs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'chatglm': {
|
||||
let qcs = await redis.keys('CHATGPT:CONVERSATIONS_CHATGLM:*')
|
||||
for (let i = 0; i < qcs.length; i++) {
|
||||
await redis.del(qcs[i])
|
||||
// todo clean last message id
|
||||
if (Config.debug) {
|
||||
logger.info('delete chatglm conversation bind: ' + qcs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'qwen': {
|
||||
let qcs = await redis.keys('CHATGPT:CONVERSATIONS_QWEN:*')
|
||||
for (let i = 0; i < qcs.length; i++) {
|
||||
await redis.del(qcs[i])
|
||||
// todo clean last message id
|
||||
if (Config.debug) {
|
||||
logger.info('delete qwen conversation bind: ' + qcs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
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
|
||||
}
|
||||
case 'chatglm4': {
|
||||
let qcs = await redis.keys('CHATGPT:CONVERSATIONS_CHATGLM4:*')
|
||||
for (let i = 0; i < qcs.length; i++) {
|
||||
await redis.del(qcs[i])
|
||||
// todo clean last message id
|
||||
if (Config.debug) {
|
||||
logger.info('delete chatglm4 conversation bind: ' + qcs[i])
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
await this.reply(`结束了${deleted}个用户的对话。`, true)
|
||||
}
|
||||
}
|
||||
113
server/index.js
113
server/index.js
|
|
@ -20,39 +20,9 @@ import Guoba from './modules/guoba.js'
|
|||
import SettingView from './modules/setting_view.js'
|
||||
|
||||
const __dirname = path.resolve()
|
||||
const server = fastify({
|
||||
logger: Config.debug
|
||||
})
|
||||
|
||||
async function setUserData(qq, data) {
|
||||
const dir = 'resources/ChatGPTCache/user'
|
||||
const filename = `${qq}.json`
|
||||
const filepath = path.join(dir, filename)
|
||||
fs.mkdirSync(dir, { recursive: true })
|
||||
fs.writeFileSync(filepath, JSON.stringify(data))
|
||||
}
|
||||
|
||||
await server.register(cors, {
|
||||
origin: '*'
|
||||
})
|
||||
await server.register(fstatic, {
|
||||
root: path.join(__dirname, 'plugins/chatgpt-plugin/server/static/')
|
||||
})
|
||||
await server.register(websocket, {
|
||||
cors: true,
|
||||
options: {
|
||||
maxPayload: 1048576
|
||||
}
|
||||
})
|
||||
await server.register(fastifyCookie)
|
||||
await server.register(webRoute)
|
||||
await server.register(webUser)
|
||||
await server.register(SettingView)
|
||||
await server.register(webPrompt)
|
||||
await server.register(Guoba)
|
||||
|
||||
// 无法访问端口的情况下创建与media的通讯
|
||||
async function mediaLink() {
|
||||
async function mediaLink () {
|
||||
const ip = await getPublicIP()
|
||||
const testServer = await fetch(`${Config.cloudTranscode}/check`,
|
||||
{
|
||||
|
|
@ -74,7 +44,7 @@ async function mediaLink() {
|
|||
ws.send(JSON.stringify({
|
||||
command: 'register',
|
||||
region: getUin(),
|
||||
type: 'server',
|
||||
type: 'server'
|
||||
}))
|
||||
})
|
||||
ws.on('message', async (message) => {
|
||||
|
|
@ -108,14 +78,13 @@ async function mediaLink() {
|
|||
if (data.qq && data.passwd) {
|
||||
const token = randomString(32)
|
||||
if (data.qq == getUin() && await redis.get('CHATGPT:ADMIN_PASSWD') == data.passwd) {
|
||||
AddUser({ user: data.qq, token: token, autho: 'admin' })
|
||||
ws.send(JSON.stringify({ command: data.command, state: true, autho: 'admin', token: token, region: getUin(), type: 'server' }))
|
||||
|
||||
AddUser({ user: data.qq, token, autho: 'admin' })
|
||||
ws.send(JSON.stringify({ command: data.command, state: true, autho: 'admin', token, region: getUin(), type: 'server' }))
|
||||
} else {
|
||||
const user = await getUserData(data.qq)
|
||||
if (user.passwd != '' && user.passwd === data.passwd) {
|
||||
AddUser({ user: data.qq, token: token, autho: 'user' })
|
||||
ws.send(JSON.stringify({ command: data.command, state: true, autho: 'user', token: token, region: getUin(), type: 'server' }))
|
||||
AddUser({ user: data.qq, token, autho: 'user' })
|
||||
ws.send(JSON.stringify({ command: data.command, state: true, autho: 'user', token, region: getUin(), type: 'server' }))
|
||||
} else {
|
||||
ws.send(JSON.stringify({ command: data.command, state: false, error: `用户名密码错误,如果忘记密码请私聊机器人输入 ${data.qq == getUin() ? '#修改管理密码' : '#修改用户密码'} 进行修改`, region: getUin(), type: 'server' }))
|
||||
}
|
||||
|
|
@ -141,7 +110,6 @@ async function mediaLink() {
|
|||
console.log(error)
|
||||
}
|
||||
})
|
||||
|
||||
} else {
|
||||
console.log('本地服务网络正常,无需开启通讯')
|
||||
}
|
||||
|
|
@ -152,7 +120,38 @@ async function mediaLink() {
|
|||
// 未完工,暂不开启这个功能
|
||||
// mediaLink()
|
||||
|
||||
export async function createServer() {
|
||||
export async function createServer () {
|
||||
let server = fastify({
|
||||
logger: Config.debug
|
||||
})
|
||||
|
||||
async function setUserData (qq, data) {
|
||||
const dir = 'resources/ChatGPTCache/user'
|
||||
const filename = `${qq}.json`
|
||||
const filepath = path.join(dir, filename)
|
||||
fs.mkdirSync(dir, { recursive: true })
|
||||
fs.writeFileSync(filepath, JSON.stringify(data))
|
||||
}
|
||||
|
||||
await server.register(cors, {
|
||||
origin: '*'
|
||||
})
|
||||
await server.register(fstatic, {
|
||||
root: path.join(__dirname, 'plugins/chatgpt-plugin/server/static/')
|
||||
})
|
||||
await server.register(websocket, {
|
||||
cors: true,
|
||||
options: {
|
||||
maxPayload: 1048576
|
||||
}
|
||||
})
|
||||
await server.register(fastifyCookie)
|
||||
await server.register(webRoute)
|
||||
await server.register(webUser)
|
||||
await server.register(SettingView)
|
||||
await server.register(webPrompt)
|
||||
await server.register(Guoba)
|
||||
|
||||
// 页面数据获取
|
||||
server.post('/page', async (request, reply) => {
|
||||
const body = request.body || {}
|
||||
|
|
@ -316,7 +315,7 @@ export async function createServer() {
|
|||
Bot.sendPrivateMsg(parseInt(data.id), data.message, data.quotable)
|
||||
}
|
||||
}
|
||||
await connection.socket.send(JSON.stringify({ command: data.command, state: true, }))
|
||||
await connection.socket.send(JSON.stringify({ command: data.command, state: true }))
|
||||
} else {
|
||||
await connection.socket.send(JSON.stringify({ command: data.command, state: false, error: '参数不足' }))
|
||||
}
|
||||
|
|
@ -370,7 +369,7 @@ export async function createServer() {
|
|||
seq: e.seq,
|
||||
rand: e.rand,
|
||||
message: e.message,
|
||||
user_name: e.sender.nickname,
|
||||
user_name: e.sender.nickname
|
||||
},
|
||||
read: true
|
||||
}
|
||||
|
|
@ -380,12 +379,12 @@ export async function createServer() {
|
|||
|
||||
break
|
||||
default:
|
||||
await connection.socket.send(JSON.stringify({ "data": data }))
|
||||
await connection.socket.send(JSON.stringify({ data }))
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
await connection.socket.send(JSON.stringify({ "error": error.message }))
|
||||
await connection.socket.send(JSON.stringify({ error: error.message }))
|
||||
}
|
||||
})
|
||||
connection.socket.on('close', () => {
|
||||
|
|
@ -395,7 +394,7 @@ export async function createServer() {
|
|||
})
|
||||
return request
|
||||
}
|
||||
Bot.on("message", e => {
|
||||
Bot.on('message', e => {
|
||||
const messageData = {
|
||||
notice: 'clientMessage',
|
||||
message: e.message,
|
||||
|
|
@ -411,7 +410,7 @@ export async function createServer() {
|
|||
seq: e.seq,
|
||||
rand: e.rand,
|
||||
message: e.message,
|
||||
user_name: e.sender.nickname,
|
||||
user_name: e.sender.nickname
|
||||
}
|
||||
}
|
||||
if (clients) {
|
||||
|
|
@ -486,10 +485,10 @@ export async function createServer() {
|
|||
for (let [keyPath, value] of Object.entries(chatdata)) {
|
||||
if (keyPath === 'blockWords' || keyPath === 'promptBlockWords' || keyPath === 'initiativeChatGroups') { value = value.toString().split(/[,,;;\|]/) }
|
||||
if (Config[keyPath] != value) {
|
||||
//检查云服务api
|
||||
// 检查云服务api
|
||||
if (keyPath === 'cloudTranscode') {
|
||||
const referer = request.headers.referer;
|
||||
const origin = referer.match(/(https?:\/\/[^/]+)/)[1];
|
||||
const referer = request.headers.referer
|
||||
const origin = referer.match(/(https?:\/\/[^/]+)/)[1]
|
||||
const checkCloud = await fetch(`${value}/check`,
|
||||
{
|
||||
method: 'POST',
|
||||
|
|
@ -562,7 +561,7 @@ export async function createServer() {
|
|||
// 系统服务测试
|
||||
server.post('/serverTest', async (request, reply) => {
|
||||
let serverState = {
|
||||
cache: false, //待移除
|
||||
cache: false, // 待移除
|
||||
cloud: false
|
||||
}
|
||||
if (Config.cloudTranscode) {
|
||||
|
|
@ -575,6 +574,15 @@ export async function createServer() {
|
|||
return reply
|
||||
})
|
||||
|
||||
global.chatgpt.server = server
|
||||
return server
|
||||
}
|
||||
|
||||
export async function runServer () {
|
||||
let server = global.chatgpt.server
|
||||
if (!server) {
|
||||
server = await createServer()
|
||||
}
|
||||
server.listen({
|
||||
port: Config.serverPort || 3321,
|
||||
host: '::'
|
||||
|
|
@ -586,3 +594,10 @@ export async function createServer() {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function stopServer () {
|
||||
let server = global.chatgpt.server
|
||||
if (server) {
|
||||
await server.close()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,21 +74,6 @@ export function randomString (length = 5) {
|
|||
return str.substr(0, length)
|
||||
}
|
||||
|
||||
export async function upsertMessage (message, suffix = '') {
|
||||
if (suffix) {
|
||||
suffix = '_' + suffix
|
||||
}
|
||||
await redis.set(`CHATGPT:MESSAGE${suffix}:${message.id}`, JSON.stringify(message))
|
||||
}
|
||||
|
||||
export async function getMessageById (id, suffix = '') {
|
||||
if (suffix) {
|
||||
suffix = '_' + suffix
|
||||
}
|
||||
let messageStr = await redis.get(`CHATGPT:MESSAGE${suffix}:${id}`)
|
||||
return JSON.parse(messageStr)
|
||||
}
|
||||
|
||||
export async function tryTimes (promiseFn, maxTries = 10) {
|
||||
try {
|
||||
return await promiseFn()
|
||||
|
|
|
|||
|
|
@ -178,9 +178,17 @@ const defaultConfig = {
|
|||
chatglmRefreshToken: '',
|
||||
sunoSessToken: '',
|
||||
sunoClientToken: '',
|
||||
|
||||
claudeApiKey: '',
|
||||
claudeApiBaseUrl: 'http://claude-api.ikechan8370.com',
|
||||
claudeApiMaxToken: 1024,
|
||||
claudeApiTemperature: 0.8,
|
||||
claudeApiModel: '', // claude-3-opus-20240229 claude-3-sonnet-20240229
|
||||
claudeSystemPrompt: '', // claude api 设定
|
||||
translateSource: 'openai',
|
||||
enableMd: false, // 第三方md,非QQBot。需要适配器实现segment.markdown和segment.button方可使用,否则不建议开启,会造成各种错误
|
||||
version: 'v2.7.10'
|
||||
enableToolbox: false, // 默认关闭工具箱节省占用和加速启动
|
||||
version: 'v2.8.0'
|
||||
}
|
||||
const _path = process.cwd()
|
||||
let config = {}
|
||||
|
|
|
|||
14
utils/history.js
Normal file
14
utils/history.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
export async function upsertMessage (message, suffix = '') {
|
||||
if (suffix) {
|
||||
suffix = '_' + suffix
|
||||
}
|
||||
await redis.set(`CHATGPT:MESSAGE${suffix}:${message.id}`, JSON.stringify(message))
|
||||
}
|
||||
|
||||
export async function getMessageById (id, suffix = '') {
|
||||
if (suffix) {
|
||||
suffix = '_' + suffix
|
||||
}
|
||||
let messageStr = await redis.get(`CHATGPT:MESSAGE${suffix}:${id}`)
|
||||
return JSON.parse(messageStr)
|
||||
}
|
||||
|
|
@ -5,9 +5,7 @@ import { ChatGPTAPI } from './openai/chatgpt-api.js'
|
|||
import { newFetch } from './proxy.js'
|
||||
import { CustomGoogleGeminiClient } from '../client/CustomGoogleGeminiClient.js'
|
||||
import XinghuoClient from './xinghuo/xinghuo.js'
|
||||
import {getImg, getMessageById, upsertMessage} from './common.js'
|
||||
import {QwenApi} from "./alibaba/qwen-api.js";
|
||||
import {v4 as uuid} from "uuid";
|
||||
import { QwenApi } from './alibaba/qwen-api.js'
|
||||
|
||||
// 代码参考:https://github.com/yeyang52/yenai-plugin/blob/b50b11338adfa5a4ef93912eefd2f1f704e8b990/model/api/funApi.js#L25
|
||||
export const translateLangSupports = [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue