feat: bym

This commit is contained in:
ikechan8370 2025-03-17 16:19:12 +08:00
parent 6997d1e024
commit 9fcc25a726
5 changed files with 219 additions and 46 deletions

93
apps/bym.js Normal file
View file

@ -0,0 +1,93 @@
import ChatGPTConfig from '../config/config.js'
import { Chaite } from 'chaite'
import { intoUserMessage, toYunzai } from '../utils/message.js'
import common from '../../../lib/common/common.js'
export class bym extends plugin {
constructor () {
super({
name: 'ChatGPT-Plugin伪人模式',
dsc: 'ChatGPT-Plugin伪人模式',
event: 'message',
priority: -150,
rule: [
{
reg: '^#chatgpt伪人模式$',
fnc: 'bym'
}
]
})
}
async bym (e) {
if (!ChatGPTConfig.bym.enable) {
return false
}
let recall = false
let presetId = ChatGPTConfig.bym.defaultPreset
if (ChatGPTConfig.bym.presetMap && ChatGPTConfig.bym.presetMap.length > 0) {
const option = ChatGPTConfig.bym.presetMap.sort((a, b) => a.priority - b.priority)
.find(item => item.keywords.find(keyword => e.msg.includes(keyword)))
if (option) {
presetId = option.presetId
}
recall = !!option.recall
}
const presetManager = Chaite.getInstance().getChatPresetManager()
let preset = await presetManager.getInstance(presetId)
if (!preset) {
preset = await presetManager.getInstance(ChatGPTConfig.bym.defaultPreset)
}
if (!preset) {
logger.debug('未找到预设,请检查配置文件')
return false
}
if (ChatGPTConfig.bym.presetPrefix) {
if (!preset.sendMessageOption.systemOverride) {
preset.sendMessageOption.systemOverride = ''
}
preset.sendMessageOption.systemOverride = ChatGPTConfig.bym.presetPrefix + preset.sendMessageOption.systemOverride
}
const userMessage = await intoUserMessage(e, {
handleReplyText: true,
handleReplyImage: true,
useRawMessage: true,
handleAtMsg: true,
excludeAtBot: false,
toggleMode: ChatGPTConfig.basic.toggleMode,
togglePrefix: ChatGPTConfig.basic.togglePrefix
})
// 伪人不记录历史
preset.sendMessageOption.disableHistoryRead = true
preset.sendMessageOption.disableHistorySave = true
// 设置多轮调用回掉
preset.sendMessageOption.onMessageWithToolCall = async content => {
const { msgs, forward } = await toYunzai(e, [content])
if (msgs.length > 0) {
await e.reply(msgs)
}
for (let forwardElement of forward) {
this.reply(forwardElement)
}
}
// 发送
const response = await Chaite.getInstance().sendMessage(userMessage, e, {
...preset.sendMessageOption,
chatPreset: preset
})
const { msgs, forward } = await toYunzai(e, response.contents)
if (msgs.length > 0) {
// await e.reply(msgs, false, { recallMsg: recall })
for (let msg of msgs) {
await e.reply(msg, false, { recallMsg: recall ? 10 : 0 })
await common.sleep(Math.floor(Math.random() * 2000) + 1000)
}
}
if (ChatGPTConfig.bym.sendReasoning) {
for (let forwardElement of forward) {
await e.reply(forwardElement, false, { recallMsg: recall ? 10 : 0 })
}
}
}
}

View file

@ -1,6 +1,6 @@
import Config from '../config/config.js'
import { Chaite, SendMessageOption } from 'chaite'
import { getPreset, intoUserMessage } from '../utils/message.js'
import { getPreset, intoUserMessage, toYunzai } from '../utils/message.js'
export class Chat extends plugin {
constructor () {
@ -22,9 +22,18 @@ export class Chat extends plugin {
async chat (e) {
const state = await Chaite.getInstance().getUserStateStorage().getItem(e.sender.user_id + '')
const sendMessageOptions = SendMessageOption.create(state?.settings)
sendMessageOptions.onMessageWithToolCall = async content => {
const { msgs, forward } = await toYunzai(e, [content])
if (msgs.length > 0) {
await e.reply(msgs)
}
for (let forwardElement of forward) {
this.reply(forwardElement)
}
}
const preset = await getPreset(e, state?.settings.preset || Config.llm.defaultChatPresetId, Config.basic.toggleMode, Config.basic.togglePrefix)
if (!preset) {
logger.debug('未找到预设,不进入对话')
logger.debug('不满足对话触发条件或未找到预设,不进入对话')
return false
}
const userMessage = await intoUserMessage(e, {
@ -40,10 +49,12 @@ export class Chat extends plugin {
...sendMessageOptions,
chatPreset: preset
})
const responseText = response.contents
.filter(c => c.type === 'text')
.map(c => (/** @type {import('chaite').TextContent} **/ c).text)
.reduce((a, b) => a + b, '')
await this.reply(responseText)
const { msgs, forward } = await toYunzai(e, response.contents)
if (msgs.length > 0) {
await e.reply(msgs, true)
}
for (let forwardElement of forward) {
this.reply(forwardElement)
}
}
}

View file

@ -1,7 +1,6 @@
import ChatGPTConfig from '../config/config.js'
import { createCRUDCommandRules, createSwitchCommandRules } from '../utils/command.js'
import { Chaite } from 'chaite'
import { resolve } from 'eslint-plugin-promise/rules/lib/promise-statics.js'
export class ChatGPTManagement extends plugin {
constructor () {
@ -20,6 +19,11 @@ export class ChatGPTManagement extends plugin {
{
reg: `^${cmdPrefix}结束(全部)?对话$`,
fnc: 'destroyConversation'
},
{
reg: `^${cmdPrefix}(bym|伪人)设置默认预设`,
fnc: 'setDefaultBymPreset',
permission: 'master'
}
]
})
@ -49,7 +53,8 @@ export class ChatGPTManagement extends plugin {
...createCRUDCommandRules.bind(this)(cmdPrefix, '黑名单群', 'blackGroups', false),
...createCRUDCommandRules.bind(this)(cmdPrefix, '白名单群', 'whiteGroups', false),
...createCRUDCommandRules.bind(this)(cmdPrefix, '黑名单用户', 'blackUsers', false),
...createCRUDCommandRules.bind(this)(cmdPrefix, '白名单用户', 'whiteUsers', false)
...createCRUDCommandRules.bind(this)(cmdPrefix, '白名单用户', 'whiteUsers', false),
createSwitchCommandRules(cmdPrefix, '(伪人|bym)', 'bym')
])
}
@ -60,6 +65,17 @@ export class ChatGPTManagement extends plugin {
this.reply(`token: ${token}, 有效期300秒`, true)
}
async setDefaultBymPreset (e) {
const presetId = e.msg.replace(`${ChatGPTConfig.basic.commandPrefix}伪人设置默认预设`, '')
const preset = await Chaite.getInstance().getChatPresetManager().getInstance(presetId)
if (preset) {
ChatGPTConfig.bym.defaultPreset = presetId
this.reply(`伪人模式默认预设已切换为${presetId}(${preset.name})`)
} else {
this.reply(`未找到预设${presetId}`)
}
}
async destroyConversation (e) {
if (e.msg.includes('全部')) {
if (!e.isMaster) {

View file

@ -27,6 +27,47 @@ class ChatGPTConfig {
commandPrefix: '^#chatgpt'
}
/**
* 伪人模式基于框架实现因此机器人开启前缀后依然需要带上前缀
* @type {{
* enable: boolean,
* hit: string[],
* probability: number,
* defaultPreset: string,
* presetPrefix?: string,
* presetMap: Array<{
* keywords: string[],
* presetId: string,
* priority: number,
* recall?: boolean
* }>,
* maxTokens: number,
* temperature: number,
* sendReasoning: boolean
* }}
* }}
*/
bym = {
// 开关
enable: false,
// 伪人必定触发词
hit: ['bym'],
// 不包含伪人必定触发词时的概率
probability: 0.02,
// 伪人模式的默认预设
defaultPreset: '',
// 伪人模式的预设前缀会加在在所有其他预设前。例如此处可以用于配置通用的伪人发言风格随意、模仿群友等presetMap中专心配置角色设定即可
presetPrefix: '',
// 包含关键词与预设的对应关系。包含特定触发词使用特定的预设,按照优先级排序
presetMap: [],
// 如果大于0会覆盖preset中的maxToken用于控制伪人模式发言长度
maxTokens: 0,
// 如果大于等于0会覆盖preset中的temperature用于控制伪人模式发言随机性
temperature: -1,
// 是否发送思考内容
sendReasoning: false
}
/**
* 模型和对话相关配置
* @type {{
@ -119,43 +160,6 @@ class ChatGPTConfig {
constructor () {
this.version = '3.0.0'
this.basic = {
toggleMode: 'at',
togglePrefix: '#chat',
debug: false,
commandPrefix: '^#chatgpt'
}
this.llm = {
defaultModel: '',
embeddingModel: 'gemini-embedding-exp-03-07',
dimensions: 0,
defaultChatPresetId: '',
enableCustomPreset: false,
customPresetUserWhiteList: [],
customPresetUserBlackList: [],
promptBlockWords: [],
responseBlockWords: [],
blockStrategy: 'full',
blockWordMask: '***'
}
this.management = {
blackGroups: [],
whiteGroups: [],
blackUsers: [],
whiteUsers: [],
defaultRateLimit: 0
}
this.chaite = {
dataDir: 'data',
processorsDirPath: 'utils/processors',
toolsDirPath: 'utils/tools',
cloudBaseUrl: '',
cloudApiKey: '',
authKey: '',
host: '',
port: 48370
}
this.watcher = null
this.configPath = ''
}
@ -216,6 +220,7 @@ class ChatGPTConfig {
// 为所有嵌套对象创建Proxy
this.basic = createDeepProxy(this.basic, handler)
this.bym = createDeepProxy(this.bym, handler)
this.llm = createDeepProxy(this.llm, handler)
this.management = createDeepProxy(this.management, handler)
this.chaite = createDeepProxy(this.chaite, handler)
@ -248,6 +253,7 @@ class ChatGPTConfig {
const config = {
version: this.version,
basic: this.basic,
bym: this.bym,
llm: this.llm,
management: this.management,
chaite: this.chaite

View file

@ -1,4 +1,5 @@
import { Chaite } from 'chaite'
import common from '../../../lib/common/common.js'
/**
* 将e中的消息转换为chaite的UserMessage
@ -143,3 +144,49 @@ export function checkChatMsg (e, toggleMode, togglePrefix) {
}
return false
}
/**
* 模型响应转为机器人格式
* @param e
* @param {import('chaite').MessageContent[]} contents
* @returns {Promise<{ msgs: (import('icqq').TextElem | import('icqq').ImageElem | import('icqq').AtElem | import('icqq').PttElem | string)[], forward: *[]}>}
*/
export async function toYunzai (e, contents) {
/**
* 要发送的消息
* @type {(import('icqq').TextElem | import('icqq').ImageElem | import('icqq').AtElem | import('icqq').PttElem | string)[]}
*/
const msgs = []
/**
* 要转发的
* @type {*[]}
*/
const forward = []
for (let content of contents) {
switch (content.type) {
case 'text': {
msgs.push((/** @type {import('chaite').TextContent} **/ content).text)
break
}
case 'image': {
msgs.push(segment.image((/** @type {import('chaite').ImageContent} **/ content).image))
break
}
case 'audio': {
msgs.push(segment.record((/** @type {import('chaite').AudioContent} **/ content).data))
break
}
case 'reasoning': {
const reasoning = await common.makeForwardMsg(e, [(/** @type {import('chaite').ReasoningContent} **/ content).text], '思考过程')
forward.push(reasoning)
break
}
default: {
logger.warn(`不支持的类型 ${content.type}`)
}
}
}
return {
msgs, forward
}
}