mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 13:27:08 +00:00
236 lines
7 KiB
JavaScript
236 lines
7 KiB
JavaScript
import { Chaite } from 'chaite'
|
||
import common from '../../../lib/common/common.js'
|
||
import fetch from 'node-fetch'
|
||
|
||
/**
|
||
* 将e中的消息转换为chaite的UserMessage
|
||
*
|
||
* @param e
|
||
* @param {{
|
||
* handleReplyText: boolean,
|
||
* handleReplyImage: boolean,
|
||
* handleReplyFile: boolean,
|
||
* useRawMessage: boolean,
|
||
* handleAtMsg: boolean,
|
||
* excludeAtBot: boolean,
|
||
* toggleMode: 'at' | 'prefix',
|
||
* togglePrefix: string
|
||
* }} options
|
||
* @returns {Promise<import('chaite').UserMessage>}
|
||
*/
|
||
export async function intoUserMessage (e, options = {}) {
|
||
const {
|
||
handleReplyText = false,
|
||
handleReplyImage = true,
|
||
handleReplyFile = true,
|
||
useRawMessage = false,
|
||
handleAtMsg = true,
|
||
excludeAtBot = false,
|
||
toggleMode = 'at',
|
||
togglePrefix = null
|
||
} = options
|
||
const contents = []
|
||
let text = ''
|
||
if ((e.source || e.reply_id) && (handleReplyImage || handleReplyText || handleReplyFile)) {
|
||
let seq = e.isGroup ? (e.source?.seq || e.reply_id) : (e.source?.time || e.source?.time)
|
||
let reply
|
||
if (e.getReply && typeof e.getReply === 'function') {
|
||
reply = (await e.getReply()).message
|
||
} else {
|
||
reply = e.isGroup
|
||
? (await e.group.getChatHistory(seq, 1)).pop()?.message
|
||
: (await e.friend.getChatHistory(seq, 1)).pop()?.message
|
||
}
|
||
if (reply) {
|
||
for (let val of reply) {
|
||
if (val.type === 'image' && handleReplyImage) {
|
||
const res = await fetch(val.url)
|
||
if (res.ok) {
|
||
const mimeType = res.headers.get('content-type') || 'image/jpeg'
|
||
contents.push({
|
||
type: 'image',
|
||
image: Buffer.from(await res.arrayBuffer()).toString('base64'),
|
||
mimeType
|
||
})
|
||
} else {
|
||
logger.warn(`fetch image ${val.url} failed: ${res.status}`)
|
||
}
|
||
} else if (val.type === 'text' && handleReplyText) {
|
||
text = `本条消息对以下消息进行了引用回复:${val.text}\n\n本条消息内容:\n`
|
||
} else if (val.type === 'file' && handleReplyFile) {
|
||
let fileUrl = '获取失败'
|
||
if (e.group?.getFileUrl) {
|
||
fileUrl = await e.group.getFileUrl(val.fid)
|
||
} else if (e.friend?.getFileUrl) {
|
||
fileUrl = await e.friend.getFileUrl(val.fid)
|
||
}
|
||
text = `本条消息对一个文件进行了引用回复:该文件的下载地址为${fileUrl}\n\n本条消息内容:\n`
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (useRawMessage) {
|
||
text += e.raw_message
|
||
} else {
|
||
for (let val of e.message) {
|
||
switch (val.type) {
|
||
case 'at': {
|
||
if (handleAtMsg) {
|
||
const { qq, text: atCard } = val
|
||
if ((toggleMode === 'at' || excludeAtBot) && qq === e.bot.uin) {
|
||
break
|
||
}
|
||
text += ` @${atCard || qq} `
|
||
}
|
||
break
|
||
}
|
||
case 'text': {
|
||
text += val.text
|
||
break
|
||
}
|
||
default:
|
||
}
|
||
}
|
||
}
|
||
for (let element of e.message?.filter(element => element.type === 'image')) {
|
||
const res = await fetch(element.url)
|
||
if (res.ok) {
|
||
const mimeType = res.headers.get('content-type') || 'image/jpeg'
|
||
contents.push({
|
||
type: 'image',
|
||
image: Buffer.from(await res.arrayBuffer()).toString('base64'),
|
||
mimeType
|
||
})
|
||
} else {
|
||
logger.warn(`fetch image ${element.url} failed: ${res.status}`)
|
||
}
|
||
}
|
||
|
||
if (toggleMode === 'prefix') {
|
||
const regex = new RegExp(`^#?(图片)?${togglePrefix}[^gpt]`)
|
||
text = text.replace(regex, '')
|
||
}
|
||
if (text) {
|
||
contents.push({
|
||
type: 'text',
|
||
text
|
||
})
|
||
}
|
||
return {
|
||
role: 'user',
|
||
content: contents
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 找到本次对话使用的预设
|
||
* @param e
|
||
* @param {string} presetId
|
||
* @param {'at' | 'prefix'} toggleMode
|
||
* @param {string} togglePrefix
|
||
* @returns {Promise<import('chaite').ChatPreset | null>}
|
||
*/
|
||
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))
|
||
if (!isValidChat && prefixHitPresets.length === 0) {
|
||
return null
|
||
}
|
||
let preset
|
||
// 如果不是at且不满足通用前缀,查看是否满足其他预设
|
||
if (!isValidChat) {
|
||
// 找到其中prefix最长的
|
||
if (prefixHitPresets.length > 1) {
|
||
preset = prefixHitPresets.sort((a, b) => b.prefix.length - a.prefix.length)[0]
|
||
} else {
|
||
preset = prefixHitPresets[0]
|
||
}
|
||
} else {
|
||
// 命中at或通用前缀,直接走用户默认预设
|
||
preset = await manager.getInstance(presetId)
|
||
}
|
||
// 如果没找到再查一次
|
||
if (!preset) {
|
||
preset = await manager.getInstance(presetId)
|
||
}
|
||
return preset
|
||
}
|
||
|
||
/**
|
||
*
|
||
* @param e
|
||
* @param {'at' | 'prefix'} toggleMode
|
||
* @param {string} togglePrefix
|
||
* @returns {boolean}
|
||
*/
|
||
export function checkChatMsg (e, toggleMode, togglePrefix) {
|
||
if (toggleMode === 'at' && (e.atBot || e.isPrivate)) {
|
||
return true
|
||
}
|
||
const prefixReg = new RegExp(`^#?(图片)?${togglePrefix}[^gpt][sS]*`)
|
||
if (toggleMode === 'prefix' && e.msg.startsWith(prefixReg)) {
|
||
return true
|
||
}
|
||
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?.trim() || '')
|
||
break
|
||
}
|
||
case 'image': {
|
||
const imageContent = (/** @type {import('chaite').ImageContent} **/ content).image
|
||
if (imageContent.startsWith('http')) {
|
||
msgs.push(segment.image(imageContent))
|
||
} else if (!mageContent.startsWith('base64')) {
|
||
msgs.push(segment.image(imageContent))
|
||
} else {
|
||
msgs.push(segment.image(`base64://${imageContent}`))
|
||
}
|
||
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}`)
|
||
}
|
||
}
|
||
}
|
||
if (forward.length > 1) {
|
||
const newForward = [await common.makeForwardMsg(e, forward, '多次思考过程')]
|
||
return {
|
||
msgs: msgs.filter(i => !!i), newForward
|
||
}
|
||
}
|
||
return {
|
||
msgs: msgs.filter(i => !!i), forward
|
||
}
|
||
}
|