fix: 调试设定和伪人

This commit is contained in:
ikechan8370 2025-03-18 22:11:31 +08:00
parent efb5a8f174
commit 3c77da5373
11 changed files with 293 additions and 34 deletions

View file

@ -2,6 +2,7 @@ import ChatGPTConfig from '../config/config.js'
import { Chaite } from 'chaite'
import { intoUserMessage, toYunzai } from '../utils/message.js'
import common from '../../../lib/common/common.js'
import { getGroupContextPrompt } from '../utils/group.js'
export class bym extends plugin {
constructor () {
@ -9,11 +10,12 @@ export class bym extends plugin {
name: 'ChatGPT-Plugin伪人模式',
dsc: 'ChatGPT-Plugin伪人模式',
event: 'message',
priority: -150,
priority: 6000,
rule: [
{
reg: '^#chatgpt伪人模式$',
fnc: 'bym'
reg: '^[^#][sS]*',
fnc: 'bym',
log: false
}
]
})
@ -23,11 +25,19 @@ export class bym extends plugin {
if (!ChatGPTConfig.bym.enable) {
return false
}
let prob = ChatGPTConfig.bym.probability
if (ChatGPTConfig.bym.hit.find(keyword => e.msg?.includes(keyword))) {
prob = 1
}
if (Math.random() > prob) {
return false
}
logger.info('伪人模式触发')
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)))
.find(item => item.keywords.find(keyword => e.msg?.includes(keyword)))
if (option) {
presetId = option.presetId
}
@ -49,6 +59,12 @@ export class bym extends plugin {
}
preset.sendMessageOption.systemOverride = ChatGPTConfig.bym.presetPrefix + preset.sendMessageOption.systemOverride
}
if (ChatGPTConfig.bym.temperature >= 0) {
preset.sendMessageOption.temperature = ChatGPTConfig.bym.temperature
}
if (ChatGPTConfig.bym.maxTokens > 0) {
preset.sendMessageOption.maxTokens = ChatGPTConfig.bym.maxTokens
}
const userMessage = await intoUserMessage(e, {
handleReplyText: true,
handleReplyImage: true,
@ -71,6 +87,10 @@ export class bym extends plugin {
this.reply(forwardElement)
}
}
if (ChatGPTConfig.llm.enableGroupContext && e.isGroup) {
const contextPrompt = await getGroupContextPrompt(e, ChatGPTConfig.llm.groupContextLength)
preset.sendMessageOption.systemOverride = preset.sendMessageOption.systemOverride ? preset.sendMessageOption.systemOverride + '\n' + contextPrompt : contextPrompt
}
// 发送
const response = await Chaite.getInstance().sendMessage(userMessage, e, {
...preset.sendMessageOption,

View file

@ -1,6 +1,8 @@
import Config from '../config/config.js'
import { Chaite, SendMessageOption } from 'chaite'
import { getPreset, intoUserMessage, toYunzai } from '../utils/message.js'
import { YunzaiUserState } from '../models/chaite/user_state_storage.js'
import { getGroupContextPrompt, getGroupHistory } from '../utils/group.js'
export class Chat extends plugin {
constructor () {
@ -8,19 +10,34 @@ export class Chat extends plugin {
name: 'ChatGPT-Plugin对话',
dsc: 'ChatGPT-Plugin对话',
event: 'message',
priority: 0,
priority: 500,
rule: [
{
reg: '^[^#][sS]*',
fnc: 'chat',
log: false
},
{
reg: '#hi',
fnc: 'history'
}
]
})
}
async chat (e) {
const state = await Chaite.getInstance().getUserStateStorage().getItem(e.sender.user_id + '')
let state = await Chaite.getInstance().getUserStateStorage().getItem(e.sender.user_id + '')
if (!state) {
state = new YunzaiUserState(e.sender.user_id, e.sender.nickname, e.sender.card)
await Chaite.getInstance().getUserStateStorage().setItem(e.sender.user_id + '', state)
}
const preset = await getPreset(e, state?.settings.preset || Config.llm.defaultChatPresetId, Config.basic.toggleMode, Config.basic.togglePrefix)
if (!preset) {
logger.debug('不满足对话触发条件或未找到预设,不进入对话')
return false
} else {
logger.info('进入对话, prompt: ' + e.msg)
}
const sendMessageOptions = SendMessageOption.create(state?.settings)
sendMessageOptions.onMessageWithToolCall = async content => {
const { msgs, forward } = await toYunzai(e, [content])
@ -31,11 +48,6 @@ export class Chat extends plugin {
this.reply(forwardElement)
}
}
const preset = await getPreset(e, state?.settings.preset || Config.llm.defaultChatPresetId, Config.basic.toggleMode, Config.basic.togglePrefix)
if (!preset) {
logger.debug('不满足对话触发条件或未找到预设,不进入对话')
return false
}
const userMessage = await intoUserMessage(e, {
handleReplyText: false,
handleReplyImage: true,
@ -45,10 +57,17 @@ export class Chat extends plugin {
toggleMode: Config.basic.toggleMode,
togglePrefix: Config.basic.togglePrefix
})
if (Config.llm.enableGroupContext && e.isGroup) {
const contextPrompt = await getGroupContextPrompt(e, Config.llm.groupContextLength)
sendMessageOptions.systemOverride = sendMessageOptions.systemOverride ? sendMessageOptions.systemOverride + '\n' + contextPrompt : (preset.sendMessageOption.systemOverride + contextPrompt)
}
const response = await Chaite.getInstance().sendMessage(userMessage, e, {
...sendMessageOptions,
chatPreset: preset
})
// 更新当前聊天进度
state.current.messageId = response.id
await Chaite.getInstance().getUserStateStorage().setItem(e.sender.user_id + '', state)
const { msgs, forward } = await toYunzai(e, response.contents)
if (msgs.length > 0) {
await e.reply(msgs, true)
@ -57,4 +76,9 @@ export class Chat extends plugin {
this.reply(forwardElement)
}
}
async history (e) {
const history = await getGroupHistory(e, 10)
e.reply(JSON.stringify(history))
}
}

View file

@ -1,6 +1,7 @@
import ChatGPTConfig from '../config/config.js'
import { createCRUDCommandRules, createSwitchCommandRules } from '../utils/command.js'
import { Chaite } from 'chaite'
import * as crypto from 'node:crypto'
export class ChatGPTManagement extends plugin {
constructor () {
@ -17,7 +18,7 @@ export class ChatGPTManagement extends plugin {
permission: 'master'
},
{
reg: `^${cmdPrefix}结束(全部)?对话$`,
reg: `^(${cmdPrefix})?#?结束(全部)?对话$`,
fnc: 'destroyConversation'
},
{
@ -27,18 +28,22 @@ export class ChatGPTManagement extends plugin {
}
]
})
this.initCommand(cmdPrefix)
if (!Chaite.getInstance()) {
const waitForChaite = async () => {
while (!Chaite.getInstance()) {
await new Promise(resolve => setTimeout(resolve, 1000))
}
return Chaite.getInstance()
}
waitForChaite().then(() => {
this.initCommand(cmdPrefix)
})
} else {
this.initCommand(cmdPrefix)
}
}
async initCommand (cmdPrefix) {
const waitForChaite = async () => {
while (!Chaite.getInstance()) {
await new Promise(resolve => setTimeout(resolve, 1000))
}
return Chaite.getInstance()
}
await waitForChaite()
initCommand (cmdPrefix) {
this.rule.push(...[
...createCRUDCommandRules.bind(this)(cmdPrefix, '渠道', 'channels'),
...createCRUDCommandRules.bind(this)(cmdPrefix, '预设', 'presets'),
@ -94,7 +99,11 @@ export class ChatGPTManagement extends plugin {
this.reply(`已结束${num}个用户的对话`)
} else {
const state = await Chaite.getInstance().getUserStateStorage().getItem(e.sender.user_id + '')
state.current.conversationId = ''
if (!state) {
this.reply('当前未开启对话')
return false
}
state.current.conversationId = crypto.randomUUID()
state.current.messageId = ''
await Chaite.getInstance().getUserStateStorage().setItem(e.sender.user_id + '', state)
this.reply('已结束当前对话')

View file

@ -24,7 +24,7 @@ class ChatGPTConfig {
// 是否开启调试模式
debug: false,
// 一般命令的开头
commandPrefix: '^#chatgpt'
commandPrefix: '#chatgpt'
}
/**
@ -80,7 +80,12 @@ class ChatGPTConfig {
* promptBlockWords: string[],
* responseBlockWords: string[],
* blockStrategy: 'full' | 'mask',
* blockWordMask: string
* blockWordMask: string,
* enableGroupContext: boolean,
* groupContextLength: number,
* groupContextTemplatePrefix: string,
* groupContextTemplateMessage: string,
* groupContextTemplateSuffix: string
* }}
*/
llm = {
@ -105,7 +110,21 @@ class ChatGPTConfig {
// 触发屏蔽词的策略,完全屏蔽或仅屏蔽关键词
blockStrategy: 'full',
// 如果blockStrategy为mask屏蔽词的替换字符
blockWordMask: '***'
blockWordMask: '***',
// 是否开启群组上下文
enableGroupContext: false,
// 群组上下文长度
groupContextLength: 20,
// 用于组装群聊上下文提示词的模板前缀
groupContextTemplatePrefix: 'Latest several messages in the group chat:\n' +
'| sender.card | sender.nickname | sender.user_id | sender.role | sender.title | time | messageId | raw_message |\n' +
'|---|---|---|---|---|---|---|---|',
// 用于组装群聊上下文提示词的模板内容部分每一条消息作为message仿照示例填写
// eslint-disable-next-line no-template-curly-in-string
groupContextTemplateMessage: '| ${message.sender.card} | ${message.sender.nickname} | ${message.sender.user_id} | ${message.sender.role} | ${message.sender.title} | ${message.time} | ${message.messageId} | ${message.raw_message} |',
// 用于组装群聊上下文提示词的模板后缀
groupContextTemplateSuffix: '\n'
}
/**

View file

@ -37,13 +37,13 @@ export class LowDBHistoryManager extends AbstractHistoryManager {
const message = await this.collection.findOne({ id: currentId })
if (!message) break
messages.unshift(message)
currentId = message.parentMessageId
currentId = message.parentId
}
return messages
} else if (conversationId) {
return this.collection.find({ conversationId })
}
return this.collection.findAll()
return []
}
async deleteConversation (conversationId) {

View file

@ -1,4 +1,22 @@
import { ChaiteStorage } from 'chaite'
import * as crypto from 'node:crypto'
/**
* 继承UserState
*/
export class YunzaiUserState {
constructor (userId, nickname, card, conversationId = crypto.randomUUID()) {
this.userId = userId
this.nickname = nickname
this.card = card
this.conversations = []
this.settings = {}
this.current = {
conversationId,
messageId: ''
}
}
}
/**
* @extends {ChaiteStorage<import('chaite').UserState>}
@ -35,9 +53,12 @@ export class LowDBUserStateStorage extends ChaiteStorage {
*/
async setItem (id, state) {
if (id) {
await this.collection.updateById(id, state)
return id
if (await this.getItem(id)) {
await this.collection.updateById(id, state)
return id
}
}
state.id = id
const result = await this.collection.insert(state)
return result.id
}

View file

@ -4,7 +4,7 @@
"type": "module",
"author": "ikechan8370",
"dependencies": {
"chaite": "^1.2.1",
"chaite": "^1.2.3",
"js-yaml": "^4.1.0",
"keyv": "^5.3.1",
"keyv-file": "^5.1.2",

10
utils/bot.js Normal file
View file

@ -0,0 +1,10 @@
/**
* 获取机器人框架
* @returns {'trss'|'miao'}
*/
export function getBotFramework () {
if (Bot.bots) {
return 'trss'
}
return 'miao'
}

View file

@ -1,6 +1,7 @@
import { Chaite } from 'chaite'
import common from '../../../lib/common/common.js'
import ChatGPTConfig from '../config/config.js'
import { getBotFramework } from './bot.js'
/**
* 模板
* @param cmdPrefix
@ -33,7 +34,7 @@ export function createCRUDCommandRules (cmdPrefix, name, variable, detail = true
const manager = getManagerByName(upperVariable)
if (detail) {
rules.push({
reg: cmdPrefix + `${name}详情$`,
reg: cmdPrefix + `${name}详情`,
fnc: `detail${upperVariable}`
})
this[`detail${upperVariable}`] = async function (e) {
@ -130,6 +131,11 @@ export function createCRUDCommandRules (cmdPrefix, name, variable, detail = true
}
}
}
if (getBotFramework() === 'trss') {
rules.forEach(rule => {
rule.reg = new RegExp(rule.reg)
})
}
return rules
}
@ -161,8 +167,12 @@ const switchCommandPreset = {
}
export function createSwitchCommandRules (cmdPrefix, name, variable, preset = 0) {
const upperVariable = variable.charAt(0).toUpperCase() + variable.slice(1)
return {
const rule = {
reg: cmdPrefix + `(${switchCommandPreset[preset][0]}|${switchCommandPreset[preset][1]})${name}$`,
fnc: `switch${upperVariable}`
}
if (getBotFramework() === 'trss') {
rule.reg = new RegExp(rule.reg)
}
return rule
}

146
utils/group.js Normal file
View file

@ -0,0 +1,146 @@
import { getBotFramework } from './bot.js'
import ChatGPTConfig from '../config/config.js'
export class GroupContextCollector {
/**
* 获取群组上下文
* @param {*} bot bot实例
* @param {string} groupId 群号
* @param {number} start 起始seq
* @param {number} length 往前数几条
* @returns {Promise<Array<*>>}
*/
async collect (bot = Bot, groupId, start = 0, length = 20) {
throw new Error('Method not implemented.')
}
}
export class ICQQGroupContextCollector extends GroupContextCollector {
/**
* 获取群组上下文
* @param {*} bot
* @param {string} groupId
* @param {number} start
* @param {number} length
* @returns {Promise<Array<*>>}
*/
async collect (bot = Bot, groupId, start = 0, length = 20) {
const group = bot.pickGroup(groupId)
let latestChats = await group.getChatHistory(start, 1)
if (latestChats.length > 0) {
let latestChat = latestChats[0]
if (latestChat) {
let seq = latestChat.seq || latestChat.message_id
let chats = []
while (chats.length < length) {
let chatHistory = await group.getChatHistory(seq, 20)
if (!chatHistory || chatHistory.length === 0) {
break
}
chats.push(...chatHistory.reverse())
if (seq === chatHistory[chatHistory.length - 1].seq || seq === chatHistory[chatHistory.length - 1].message_id) {
break
}
seq = chatHistory[chatHistory.length - 1].seq || chatHistory[chatHistory.length - 1].message_id
}
chats = chats.slice(0, length).reverse()
try {
let mm = bot.gml
for (const chat of chats) {
let sender = mm.get(chat.sender.user_id)
if (sender) {
chat.sender = sender
}
}
} catch (err) {
logger.warn(err)
}
// console.log(chats)
return chats
}
}
// }
return []
}
}
export class TRSSGroupContextCollector extends GroupContextCollector {
/**
* 获取群组上下文
* @param {*} bot
* @param {string} groupId
* @param {number} start
* @param {number} length
* @returns {Promise<Array<*>>}
*/
async collect (bot = Bot, groupId, start = 0, length = 20) {
const group = bot.pickGroup(groupId)
let chats = await group.getChatHistory(start, length)
try {
let mm = bot.gml
for (const chat of chats) {
let sender = mm.get(chat.sender.user_id)
if (sender) {
chat.sender = sender
}
}
} catch (err) {
logger.warn(err)
}
return chats
}
}
/**
* 获取群组上下文
* @param e
* @param length
* @returns {Promise<Array<*>>}
*/
export async function getGroupHistory (e, length = 20) {
if (getBotFramework() === 'trss') {
const collector = new TRSSGroupContextCollector()
return await collector.collect(e.bot, e.group_id, 0, length)
}
return await new ICQQGroupContextCollector().collect(e.bot, e.group_id, 0, length)
}
/**
* 获取构建群聊聊天记录的prompt
* @param e event
* @param {number} length 长度
* @returns {Promise<string>}
*/
export async function getGroupContextPrompt (e, length) {
const {
groupContextTemplatePrefix,
groupContextTemplateMessage,
groupContextTemplateSuffix
} = ChatGPTConfig.llm
const chats = await getGroupHistory(e, length)
const rows = chats.map(chat => {
const sender = chat.sender || {}
return groupContextTemplateMessage
// eslint-disable-next-line no-template-curly-in-string
.replace('${message.sender.card}', sender.card || '-')
// eslint-disable-next-line no-template-curly-in-string
.replace('${message.sender.nickname}', sender.nickname || '-')
// eslint-disable-next-line no-template-curly-in-string
.replace('${message.sender.user_id}', sender.user_id || '-')
// eslint-disable-next-line no-template-curly-in-string
.replace('${message.sender.role}', sender.role || '-')
// eslint-disable-next-line no-template-curly-in-string
.replace('${message.sender.title}', sender.title || '-')
// eslint-disable-next-line no-template-curly-in-string
.replace('${message.time}', chat.time || '-')
// eslint-disable-next-line no-template-curly-in-string
.replace('${message.messageId}', chat.messageId || '-')
// eslint-disable-next-line no-template-curly-in-string
.replace('${message.raw_message}', chat.raw_message || '-')
}).join('\n')
return [
groupContextTemplatePrefix,
rows,
groupContextTemplateSuffix
].join('\n')
}

View file

@ -103,7 +103,7 @@ export async function getPreset (e, presetId, toggleMode, togglePrefix) {
const isValidChat = checkChatMsg(e, toggleMode, togglePrefix)
const manager = Chaite.getInstance().getChatPresetManager()
const presets = await manager.getAllPresets()
const prefixHitPresets = presets.filter(p => e.msg.startsWith(p.prefix))
const prefixHitPresets = presets.filter(p => e.msg?.startsWith(p.prefix))
if (!isValidChat && prefixHitPresets.length === 0) {
return null
}