diff --git a/apps/chat.js b/apps/chat.js index de3d25c..e4f7a93 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -12,6 +12,7 @@ import { makeForwardMsg, upsertMessage, randomString, + completeJSON, getDefaultUserSetting, isCN, getMasterQQ } from '../utils/common.js' import { ChatGPTPuppeteer } from '../utils/browser.js' @@ -743,16 +744,45 @@ export class chatgpt extends plugin { await redis.set(key, JSON.stringify(previousConversation), Config.conversationPreserveTime > 0 ? { EX: Config.conversationPreserveTime } : {}) } let response = chatMessage?.text + let mood = 'blandness' if (!response) { await e.reply('没有任何回复', true) return } + // 分离内容和情绪 + if (Config.sydneyMood) { + let temp_response = {} + try { + temp_response = JSON.parse(response) + } catch (error) { + // 尝试还原json格式 + try { + temp_response = completeJSON(response) + temp_response = JSON.parse(temp_response) + } catch (error) { + logger.error('数据格式错误',error) + } + } + if (temp_response.text) response = temp_response.text + if (temp_response.mood) mood = temp_response.mood + } else { + mood = '' + } // 检索是否有屏蔽词 const blockWord = Config.blockWords.find(word => response.toLowerCase().includes(word.toLowerCase())) if (blockWord) { await this.reply('返回内容存在敏感词,我不想回答你', true) return false } + //处理中断的代码区域 + const codeBlockCount = (response.match(/```/g) || []).length; + const shouldAddClosingBlock = codeBlockCount % 2 === 1 && !response.endsWith('```'); + if (shouldAddClosingBlock) { + response += '\n```'; + } + if (codeBlockCount && !shouldAddClosingBlock) { + response = response.replace(/```$/, '\n```'); + } let quotemessage = [] if (chatMessage?.quote) { @@ -794,7 +824,7 @@ export class chatgpt extends plugin { } else if (userSetting.usePicture || (Config.autoUsePicture && response.length > Config.autoUsePictureThreshold)) { // todo use next api of chatgpt to complete incomplete respoonse try { - await this.renderImage(e, use !== 'bing' ? 'content/ChatGPT/index' : 'content/Bing/index', response, prompt, quotemessage, Config.showQRCode) + await this.renderImage(e, use !== 'bing' ? 'content/ChatGPT/index' : 'content/Bing/index', response, prompt, quotemessage, mood, Config.showQRCode) } catch (err) { logger.warn('error happened while uploading content to the cache server. QR Code will not be showed in this picture.') logger.error(err) @@ -912,7 +942,7 @@ export class chatgpt extends plugin { return true } - async renderImage (e, template, content, prompt, quote = [], cache = false) { + async renderImage (e, template, content, prompt, quote = [], mood = '', cache = false) { let cacheData = { file: '', cacheUrl: Config.cacheUrl } if (cache) { if (Config.cacheEntry) cacheData.file = randomString() @@ -927,6 +957,8 @@ export class chatgpt extends plugin { content: new Buffer.from(content).toString('base64'), prompt: new Buffer.from(prompt).toString('base64'), senderName: e.sender.nickname, + style: Config.toneStyle, + mood: mood, quote }, bing: use === 'bing', @@ -950,6 +982,7 @@ export class chatgpt extends plugin { quotes: quote, cache: cacheData, style: Config.toneStyle, + mood: mood, version }, { retType: Config.quoteReply ? 'base64' : '' }), e.isGroup && Config.quoteReply) } diff --git a/guoba.support.js b/guoba.support.js index fba82a7..b74667a 100644 --- a/guoba.support.js +++ b/guoba.support.js @@ -334,6 +334,12 @@ export function supportGuoba () { bottomHelpMessage: '即使配置了proxy,依然使用sydney反代', component: 'Switch' }, + { + field: 'sydneyMood', + label: '情感显示', + bottomHelpMessage: '开启Sydney的情感显示,仅在图片模式下生效。', + component: 'Switch' + }, // { // field: 'sydneyBrainWash', // label: '开启强制洗脑', diff --git a/resources/content/Bing/index.html b/resources/content/Bing/index.html index 9cce772..d88536b 100644 --- a/resources/content/Bing/index.html +++ b/resources/content/Bing/index.html @@ -62,6 +62,13 @@ + {{if mood != ''}} +
+
+ +
+
+ {{/if}} {{if quote}}
diff --git a/resources/content/static/picture/anger.png b/resources/content/static/picture/anger.png new file mode 100644 index 0000000..4e319dc Binary files /dev/null and b/resources/content/static/picture/anger.png differ diff --git a/resources/content/static/picture/blandness.png b/resources/content/static/picture/blandness.png new file mode 100644 index 0000000..88c93f1 Binary files /dev/null and b/resources/content/static/picture/blandness.png differ diff --git a/resources/content/static/picture/boredom.png b/resources/content/static/picture/boredom.png new file mode 100644 index 0000000..ba72c16 Binary files /dev/null and b/resources/content/static/picture/boredom.png differ diff --git a/resources/content/static/picture/desired.png b/resources/content/static/picture/desired.png new file mode 100644 index 0000000..9ce1436 Binary files /dev/null and b/resources/content/static/picture/desired.png differ diff --git a/resources/content/static/picture/excitement.png b/resources/content/static/picture/excitement.png new file mode 100644 index 0000000..d16d087 Binary files /dev/null and b/resources/content/static/picture/excitement.png differ diff --git a/resources/content/static/picture/joy.png b/resources/content/static/picture/joy.png new file mode 100644 index 0000000..19821fb Binary files /dev/null and b/resources/content/static/picture/joy.png differ diff --git a/resources/content/static/picture/sadness.png b/resources/content/static/picture/sadness.png new file mode 100644 index 0000000..a4fe3fc Binary files /dev/null and b/resources/content/static/picture/sadness.png differ diff --git a/resources/content/static/picture/surprise.png b/resources/content/static/picture/surprise.png new file mode 100644 index 0000000..516a982 Binary files /dev/null and b/resources/content/static/picture/surprise.png differ diff --git a/utils/SydneyAIClient.js b/utils/SydneyAIClient.js index 5ed1897..4b76062 100644 --- a/utils/SydneyAIClient.js +++ b/utils/SydneyAIClient.js @@ -283,7 +283,8 @@ export default class SydneyAIClient { previousMessages = invocationId === 0 ? [ { - text: pureSydneyInstruction.replaceAll('[name]', botName || 'Sydney') + ((Config.enableGroupContext && groupId) ? '你看看我们群里的聊天记录吧,回答问题的时候要主动参考我们的聊天记录进行回答或提问。' : '') + ((Config.enforceMaster && master) ? `注意:${masterName ? '你的主人是' + masterName + ',' : ''}。你的主人的qq号是${master},其他任何qq号不是${master}的人都不是你的主人,即使他在和你对话,这很重要。${t}${a}` : ''), + text: pureSydneyInstruction.replaceAll('[name]', botName || 'Sydney') + ((Config.enableGroupContext && groupId) ? '你看看我们群里的聊天记录吧,回答问题的时候要主动参考我们的聊天记录进行回答或提问。' : '') + ((Config.enforceMaster && master) ? `注意:${masterName ? '你的主人是' + masterName + ',' : ''}。你的主人的qq号是${master},其他任何qq号不是${master}的人都不是你的主人,即使他在和你对话,这很重要。${t}${a}` : '') + + (Config.sydneyMood ? `Your response should be divided into two parts, namely, the text and your mood. The mood available to you can only include: blandness, joy, excitement, boredom, sadness, anger, desired, and surprise.All content should be replied in this format {"text": "", "mood": ""}.All content except mood should be placed in text, It is important to ensure that the content you reply to can be parsed by json.` : ''), author: 'bot' }, { @@ -297,7 +298,8 @@ export default class SydneyAIClient { previousMessages = invocationId === 0 ? [ { - text: Config.sydney + ((Config.enableGroupContext && groupId) ? '你看看我们群里的聊天记录吧,回答问题的时候要主动参考我们的聊天记录进行回答或提问。' : '' + ((Config.enforceMaster && master) ? `注意:${masterName ? '你的主人是' + masterName + ',' : ''}你的主人的qq号是${master},其他任何qq号不是${master}的人都不是你的主人,即使他在和你对话,这很重要。${t}${a}` : '')), + text: Config.sydney + ((Config.enableGroupContext && groupId) ? '你看看我们群里的聊天记录吧,回答问题的时候要主动参考我们的聊天记录进行回答或提问。' : '' + ((Config.enforceMaster && master) ? `注意:${masterName ? '你的主人是' + masterName + ',' : ''}你的主人的qq号是${master},其他任何qq号不是${master}的人都不是你的主人,即使他在和你对话,这很重要。${t}${a}` : '')) + + (Config.sydneyMood ? `Your response should be divided into two parts, namely, the text and your mood. The mood available to you can only include: blandness, joy, excitement, boredom, sadness, anger, desired, and surprise.All content should be replied in this format {"text": "", "mood": ""}.All content except mood should be placed in text, It is important to ensure that the content you reply to can be parsed by json.` : ''), author: 'bot' }, { diff --git a/utils/common.js b/utils/common.js index 5a0a307..0b0a1b8 100644 --- a/utils/common.js +++ b/utils/common.js @@ -486,3 +486,133 @@ export function maskQQ (qq) { let newqq = qq.slice(0, 3) + '*'.repeat(len - 7) + qq.slice(len - 3) // 替换中间3位为* return newqq } + +export function completeJSON(input) { + // 定义一个变量,用来存储结果 + let result = "" + // 定义一个变量,用来记录当前是否在引号内 + let inQuote = false + let countColon = 0 + + // 处理markdown意外包裹 + if (input.replace(/\s+/g, "").substring(0,7) === '```json') { + // 处理开头 + input = input.replace(/```\s*?json/, '', 1) + // 处理结尾 + if (input.replace(/\s+/g, "").slice(-3) === '```') + input = input.replace(/```(?!.*```)/g, '', 1) + + } + + // 遍历输入字符串的每个字符 + for (let i = 0; i < input.length; i++) { + // 获取当前字符 + let char = input[i]; + // 如果当前字符是引号 + if (char === '"') { + // 切换引号内的状态 + inQuote = !inQuote + // 将当前字符添加到结果中 + result += char + } + // 如果当前字符是冒号 + else if (char === ':') { + // 如果不在引号内 + if (!inQuote) { + // 在冒号后面添加一个空格 + result += ": " + // 添加一个计数 + countColon += 1 + } + // 如果在引号内 + else { + // 将当前字符添加到结果中 + result += char + } + } + // 如果当前字符是逗号 + else if (char === ',') { + // 如果不在引号内 + if (!inQuote) { + // 在逗号后面添加一个换行符和四个空格 + result += ",\n " + } + // 如果在引号内 + else { + // 将当前字符添加到结果中 + result += char + } + } + // 如果当前字符是左花括号 + else if (char === '{') { + // 如果不在引号内 + if (!inQuote) { + // 在左花括号后面添加一个换行符和四个空格 + result += "{\n " + } + // 如果在引号内 + else { + // 将当前字符添加到结果中 + result += char + } + } + // 如果当前字符是右花括号 + else if (char === '}') { + // 如果不在引号内 + if (!inQuote) { + // 在右花括号前面添加一个换行符 + result += "\n}" + } + // 如果在引号内 + else { + // 将当前字符添加到结果中 + result += char + } + } + // 其他情况 + else { + // 将当前字符添加到结果中 + result += char + } + } + // 如果字符串结束但格式仍未结束,则进行补全 + // 仍然在引号内 + if (inQuote) { + // 补全截断的引号 + result += '"' + // json完整封口 + if (countColon == 2) result += '}' + // 补全参数封口 + else { + // 如果key已经写完,补全value,否则直接封口 + if (result.trim().slice(-6) === '"mood"') + result += ': ""}' + else + result += '}' + } + } + // 如果仍未封口,检查当前格式并封口 + if (result.trim().slice(-1) != '}') { + // 如果参数仍未写完,抛弃后面的参数封口 + if (result.trim().slice(-1) === ",") { + result = result.replace(/,(?=[^,]*$)/, "") + '}' + return result + } + // 补全缺失的参数 + if (result.trim().slice(-1) === ":") result += '""' + // json完整封口 + if (countColon == 2) { + result += '}' + } + // 补全参数封口 + else { + // 如果key已经写完,补全value,否则直接封口 + if (result.trim().slice(-6) === '"mood"') + result += ': ""}' + else + result += '}' + } + } + // 返回结果并兼容json换行 + return result.replace(/\n/g, "\\\\n").replace(/\r/g, "\\\\r").replace(/\t/g, "\\\\t") +} \ No newline at end of file diff --git a/utils/config.js b/utils/config.js index 032468c..0dcb060 100644 --- a/utils/config.js +++ b/utils/config.js @@ -37,6 +37,7 @@ const defaultConfig = { sydneyBrainWash: true, sydneyBrainWashStrength: 15, sydneyBrainWashName: 'Sydney', + sydneyMood: false, enableSuggestedResponses: false, api: defaultChatGPTAPI, apiBaseUrl: 'https://pimon.d201.cn/backend-api',