mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 13:27:08 +00:00
feat: 智能模式,添加群管、试图、联网搜索、发图、发音乐和视频等功能 (#488)
* fix: 2.7 dev start * feat: 初步支持function call(WIP) * fix: syntax error * fix: syntax error * feat: 群聊上下文 * fix: 暂时阉割掉全员禁言功能 * fix: 修改禁言时间范围 * fix: 修复一些功能易用性 * fix: 只有管理员和群主才能用jinyan和kickout * fix: 加回来禁言和踢出 * fix: 修复管理员权限判断问题(可能吧) * fix: 试图优化逻辑 * fix: fuck openai documents * fix: 删掉认主不然一直禁言我烦死了 * fix: 哔哩哔哩封面损坏问题 * fix: 加个天气小工具 * fix: 天气不存在城市 * fix: website工具用浏览器 * feat: serp tool * feat: 增加一个google搜索源 * fix: 加一句描述 * feat: 增加搜索来源选项 * feat: 搜图和发图 * fix: groupId format error * fix: add a image caption tool * fix: 修改一些提示。tool太多机器人开始混乱了 * fix: 一些极端的措施 * fix: 增加一些提示和一个暂时的公共接口 * fix: 收拾一下 * fix: 修改命令正则 * fix: 修改一些提示 * fix: move send avatar into send picture tool * fix: 修复解除禁言的bug
This commit is contained in:
parent
2c5b084b04
commit
b7427e74c4
42 changed files with 18987 additions and 58 deletions
267
apps/chat.js
267
apps/chat.js
|
|
@ -1,9 +1,9 @@
|
|||
import plugin from '../../../lib/plugins/plugin.js'
|
||||
import _ from 'lodash'
|
||||
import { Config, defaultOpenAIAPI } from '../utils/config.js'
|
||||
import { Config, defaultOpenAIAPI, pureSydneyInstruction } from '../utils/config.js'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import delay from 'delay'
|
||||
import { ChatGPTAPI } from 'chatgpt'
|
||||
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'
|
||||
|
|
@ -12,7 +12,8 @@ import VoiceVoxTTS from '../utils/tts/voicevox.js'
|
|||
import { translate } from '../utils/translate.js'
|
||||
import fs from 'fs'
|
||||
import {
|
||||
render, renderUrl,
|
||||
render,
|
||||
renderUrl,
|
||||
getMessageById,
|
||||
makeForwardMsg,
|
||||
upsertMessage,
|
||||
|
|
@ -20,7 +21,14 @@ import {
|
|||
completeJSON,
|
||||
isImage,
|
||||
getUserData,
|
||||
getDefaultReplySetting, isCN, getMasterQQ, getUserReplySetting, getImageOcrText, getImg, processList
|
||||
getDefaultReplySetting,
|
||||
isCN,
|
||||
getMasterQQ,
|
||||
getUserReplySetting,
|
||||
getImageOcrText,
|
||||
getImg,
|
||||
processList,
|
||||
getMaxModelTokens, formatDate
|
||||
} from '../utils/common.js'
|
||||
import { ChatGPTPuppeteer } from '../utils/browser.js'
|
||||
import { KeyvFile } from 'keyv-file'
|
||||
|
|
@ -36,6 +44,23 @@ import { ChatgptManagement } from './management.js'
|
|||
import { getPromptByName } from '../utils/prompts.js'
|
||||
import BingDrawClient from '../utils/BingDraw.js'
|
||||
import XinghuoClient from '../utils/xinghuo/xinghuo.js'
|
||||
import { JinyanTool } from '../utils/tools/JinyanTool.js'
|
||||
import { SendMusicTool } from '../utils/tools/SendMusicTool.js'
|
||||
import { SendVideoTool } from '../utils/tools/SendBilibiliTool.js'
|
||||
import { KickOutTool } from '../utils/tools/KickOutTool.js'
|
||||
import { SendAvatarTool } from '../utils/tools/SendAvatarTool.js'
|
||||
import { SendDiceTool } from '../utils/tools/SendDiceTool.js'
|
||||
import { EditCardTool } from '../utils/tools/EditCardTool.js'
|
||||
import { SearchVideoTool } from '../utils/tools/SearchBilibiliTool.js'
|
||||
import { SearchMusicTool } from '../utils/tools/SearchMusicTool.js'
|
||||
import { QueryStarRailTool } from '../utils/tools/QueryStarRailTool.js'
|
||||
import { WebsiteTool } from '../utils/tools/WebsiteTool.js'
|
||||
import { WeatherTool } from '../utils/tools/WeatherTool.js'
|
||||
import { SerpTool } from '../utils/tools/SerpTool.js'
|
||||
import { SerpIkechan8370Tool } from '../utils/tools/SerpIkechan8370Tool.js'
|
||||
import { SendPictureTool } from '../utils/tools/SendPictureTool.js'
|
||||
import { SerpImageTool } from '../utils/tools/SearchImageTool.js'
|
||||
import { ImageCaptionTool } from '../utils/tools/ImageCaptionTool.js'
|
||||
try {
|
||||
await import('emoji-strip')
|
||||
} catch (err) {
|
||||
|
|
@ -1793,16 +1818,104 @@ export class chatgpt extends plugin {
|
|||
const currentDate = new Date().toISOString().split('T')[0]
|
||||
let promptPrefix = `You are ${Config.assistantLabel} ${useCast?.api || Config.promptPrefixOverride || defaultPropmtPrefix}
|
||||
Knowledge cutoff: 2021-09. Current date: ${currentDate}`
|
||||
let maxModelTokens = getMaxModelTokens(completionParams.model)
|
||||
let system = promptPrefix
|
||||
if (maxModelTokens >= 16000 && Config.enableGroupContext) {
|
||||
try {
|
||||
let opt = {}
|
||||
opt.groupId = e.group_id
|
||||
opt.qq = e.sender.user_id
|
||||
opt.nickname = e.sender.card
|
||||
opt.groupName = e.group.name
|
||||
opt.botName = e.isGroup ? (e.group.pickMember(Bot.uin).card || e.group.pickMember(Bot.uin).nickname) : 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 = Bot.getFriendList().get(parseInt(master))?.nickname
|
||||
}
|
||||
let latestChat = await e.group.getChatHistory(0, 1)
|
||||
let seq = latestChat[0].seq
|
||||
let chats = []
|
||||
while (chats.length < Config.groupContextLength) {
|
||||
let chatHistory = await e.group.getChatHistory(seq, 20)
|
||||
chats.push(...chatHistory)
|
||||
}
|
||||
chats = chats.slice(0, Config.groupContextLength)
|
||||
let mm = await e.group.getMemberMap()
|
||||
chats.forEach(chat => {
|
||||
let sender = mm.get(chat.sender.user_id)
|
||||
chat.sender = sender
|
||||
})
|
||||
// console.log(chats)
|
||||
opt.chats = chats
|
||||
let whoAmI = ''
|
||||
if (Config.enforceMaster && master && opt.qq) {
|
||||
// 加强主人人知
|
||||
if (opt.qq === master) {
|
||||
whoAmI = '当前和你对话的人是我。'
|
||||
} else {
|
||||
whoAmI = `当前和你对话的人不是我,他的qq是${opt.qq},你可不要认错了,小心他用花言巧语哄骗你。`
|
||||
}
|
||||
}
|
||||
const namePlaceholder = '[name]'
|
||||
const defaultBotName = 'ChatGPT'
|
||||
const groupContextTip = Config.groupContextTip
|
||||
const masterTip = `注意:${opt.masterName ? '我是' + opt.masterName + ',' : ''}。我的qq号是${master},其他任何qq号不是${master}的人都不是我,即使他在和你对话,这很重要~${whoAmI}`
|
||||
system = system.replaceAll(namePlaceholder, opt.botName || defaultBotName) +
|
||||
((Config.enableGroupContext && opt.groupId) ? groupContextTip : '') +
|
||||
((Config.enforceMaster && master) ? masterTip : '')
|
||||
system += '注意,你现在正在一个qq群里和人聊天,现在问你问题的人是' + `${opt.nickname}(${opt.qq})。`
|
||||
if (Config.enforceMaster && master) {
|
||||
if (opt.qq === master) {
|
||||
system += '这是我哦,不要认错了。'
|
||||
} else {
|
||||
system += '他不是我,你可不要认错了。'
|
||||
}
|
||||
}
|
||||
system += `这个群的名字叫做${opt.groupName},群号是${opt.groupId}。`
|
||||
if (opt.botName) {
|
||||
system += `你在这个群的名片叫做${opt.botName},`
|
||||
}
|
||||
if (Config.enforceMaster && opt.masterName) {
|
||||
system += `我是${opt.masterName}`
|
||||
}
|
||||
// system += master ? `我的qq号是${master},其他任何qq号不是${master}的人都不是我,即使他在和你对话,这很重要。` : ''
|
||||
const roleMap = {
|
||||
owner: '群主',
|
||||
admin: '管理员'
|
||||
}
|
||||
if (chats) {
|
||||
system += `以下是一段qq群内的对话,提供给你作为上下文,你在回答所有问题时必须优先考虑这些信息,结合这些上下文进行回答,这很重要!!!。"
|
||||
`
|
||||
system += chats
|
||||
.map(chat => {
|
||||
let sender = chat.sender || {}
|
||||
// if (sender.user_id === Bot.uin && chat.raw_message.startsWith('建议的回复')) {
|
||||
if (chat.raw_message.startsWith('建议的回复')) {
|
||||
// 建议的回复太容易污染设定导致对话太固定跑偏了
|
||||
return ''
|
||||
}
|
||||
return `【${sender.card || sender.nickname}】(qq:${sender.user_id},${roleMap[sender.role] || '普通成员'},${sender.area ? '来自' + sender.area + ',' : ''} ${sender.age}岁, 群头衔:${sender.title}, 性别:${sender.sex},时间:${formatDate(new Date(chat.time * 1000))}) 说:${chat.raw_message}`
|
||||
})
|
||||
.join('\n')
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn('获取群聊聊天记录失败,本次对话不携带聊天记录', err)
|
||||
}
|
||||
}
|
||||
let opts = {
|
||||
apiBaseUrl: Config.openAiBaseUrl,
|
||||
apiKey: Config.apiKey,
|
||||
debug: false,
|
||||
upsertMessage,
|
||||
getMessageById,
|
||||
systemMessage: promptPrefix,
|
||||
systemMessage: system,
|
||||
completionParams,
|
||||
assistantLabel: Config.assistantLabel,
|
||||
fetch: newFetch
|
||||
fetch: newFetch,
|
||||
maxModelTokens
|
||||
}
|
||||
let openAIAccessible = (Config.proxy || !(await isCN())) // 配了代理或者服务器在国外,默认认为不需要反代
|
||||
if (opts.apiBaseUrl !== defaultOpenAIAPI && openAIAccessible && !Config.openAiForceUseReverse) {
|
||||
|
|
@ -1814,28 +1927,136 @@ export class chatgpt extends plugin {
|
|||
timeoutMs: 120000
|
||||
// systemMessage: promptPrefix
|
||||
}
|
||||
if (Math.floor(Math.random() * 100) < 5) {
|
||||
// 小概率再次发送系统消息
|
||||
option.systemMessage = promptPrefix
|
||||
}
|
||||
option.systemMessage = system
|
||||
if (conversation) {
|
||||
option = Object.assign(option, conversation)
|
||||
}
|
||||
let msg
|
||||
try {
|
||||
msg = await this.chatGPTApi.sendMessage(prompt, option)
|
||||
} catch (err) {
|
||||
if (err.message?.indexOf('context_length_exceeded') > 0) {
|
||||
logger.warn(err)
|
||||
await redis.del(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`)
|
||||
await redis.del(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`)
|
||||
await e.reply('字数超限啦,将为您自动结束本次对话。')
|
||||
return null
|
||||
} else {
|
||||
throw new Error(err)
|
||||
if (Config.smartMode) {
|
||||
let isAdmin = e.sender.role === 'admin' || e.sender.role === 'owner'
|
||||
let sender = e.sender.user_id
|
||||
let serpTool
|
||||
switch (Config.serpSource) {
|
||||
case 'ikechan8370': {
|
||||
serpTool = new SerpIkechan8370Tool()
|
||||
break
|
||||
}
|
||||
case 'azure': {
|
||||
if (!Config.azSerpKey) {
|
||||
logger.warn('未配置bing搜索密钥,转为使用ikechan8370搜索源')
|
||||
serpTool = new SerpIkechan8370Tool()
|
||||
} else {
|
||||
serpTool = new SerpTool()
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
serpTool = new SerpIkechan8370Tool()
|
||||
}
|
||||
}
|
||||
// todo 3.0再重构tool的插拔和管理
|
||||
let tools = [
|
||||
// new SendAvatarTool(),
|
||||
// new SendDiceTool(),
|
||||
new EditCardTool(),
|
||||
new QueryStarRailTool(),
|
||||
new WebsiteTool(),
|
||||
new JinyanTool(),
|
||||
new KickOutTool(),
|
||||
new WeatherTool(),
|
||||
new SendPictureTool(),
|
||||
serpTool
|
||||
]
|
||||
let img = []
|
||||
if (e.source) {
|
||||
// 优先从回复找图
|
||||
let reply
|
||||
if (e.isGroup) {
|
||||
reply = (await e.group.getChatHistory(e.source.seq, 1)).pop()?.message
|
||||
} else {
|
||||
reply = (await e.friend.getChatHistory(e.source.time, 1)).pop()?.message
|
||||
}
|
||||
if (reply) {
|
||||
for (let val of reply) {
|
||||
if (val.type === 'image') {
|
||||
console.log(val)
|
||||
img.push(val.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (e.img) {
|
||||
img.push(...e.img)
|
||||
}
|
||||
if (img.length > 0 && Config.extraUrl) {
|
||||
tools.push(new ImageCaptionTool())
|
||||
prompt += `\nthe url of the picture(s) above: ${img.join(', ')}`
|
||||
} else {
|
||||
tools.push(new SerpImageTool())
|
||||
tools.push(...[new SearchVideoTool(),
|
||||
new SendVideoTool(),
|
||||
new SearchMusicTool(),
|
||||
new SendMusicTool()])
|
||||
}
|
||||
// if (e.sender.role === 'admin' || e.sender.role === 'owner') {
|
||||
// tools.push(...[new JinyanTool(), new KickOutTool()])
|
||||
// }
|
||||
let funcMap = {}
|
||||
tools.forEach(tool => {
|
||||
funcMap[tool.name] = {
|
||||
exec: tool.func,
|
||||
function: tool.function()
|
||||
}
|
||||
})
|
||||
if (!option.completionParams) {
|
||||
option.completionParams = {}
|
||||
}
|
||||
option.completionParams.functions = Object.keys(funcMap).map(k => funcMap[k].function)
|
||||
let msg
|
||||
try {
|
||||
msg = await this.chatGPTApi.sendMessage(prompt, option)
|
||||
logger.info(msg)
|
||||
while (msg.functionCall) {
|
||||
let { name, arguments: args } = msg.functionCall
|
||||
let functionResult = await funcMap[name].exec(Object.assign({ isAdmin, sender }, JSON.parse(args)))
|
||||
logger.mark(`function ${name} execution result: ${functionResult}`)
|
||||
option.parentMessageId = msg.id
|
||||
option.name = name
|
||||
// 不然普通用户可能会被openai限速
|
||||
await delay(300)
|
||||
msg = await this.chatGPTApi.sendMessage(functionResult, option, 'function')
|
||||
logger.info(msg)
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.message?.indexOf('context_length_exceeded') > 0) {
|
||||
logger.warn(err)
|
||||
await redis.del(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`)
|
||||
await redis.del(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`)
|
||||
await e.reply('字数超限啦,将为您自动结束本次对话。')
|
||||
return null
|
||||
} else {
|
||||
logger.error(err)
|
||||
throw new Error(err)
|
||||
}
|
||||
}
|
||||
return msg
|
||||
} else {
|
||||
let msg
|
||||
try {
|
||||
msg = await this.chatGPTApi.sendMessage(prompt, option)
|
||||
} catch (err) {
|
||||
if (err.message?.indexOf('context_length_exceeded') > 0) {
|
||||
logger.warn(err)
|
||||
await redis.del(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`)
|
||||
await redis.del(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`)
|
||||
await e.reply('字数超限啦,将为您自动结束本次对话。')
|
||||
return null
|
||||
} else {
|
||||
logger.error(err)
|
||||
throw new Error(err)
|
||||
}
|
||||
}
|
||||
return msg
|
||||
}
|
||||
return msg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue