mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 13:27:08 +00:00
为azure语音模式添加说话风格配置,支持vits语音模式中文回答转日语语音输出,添加翻译功能,支持指令切换语音模式,支持查看当前语音模式下的支持角色列表 (#404)
* feat: add support for ‘greeting’ and ‘global reply mode’ commands, improve variable naming and remove unnecessary backend output. * feat: Add support for black and white lists, global reply mode and voice role settings, private chat switch, and active greeting configuration. Refactor some variable names and comment out redundant code for better readability and reduced backend output. * feat: 为新功能完善了帮助面板 * docs: 完善了‘打招呼’的帮助说明 * Commit Type: feat, bugfix Add functionality to view plugin command table, fix bug in blacklist/whitelist, and fix bug where chat mode can still be used in private messaging when disabled. * Commit Type: feat, bugfix Add functionality to view plugin command table, fix bug in blacklist/whitelist, and fix bug where chat mode can still be used in private messaging when disabled. * refactor: Remove redundant log output. * Refactor: optimize code logic * Fix: 修复绘图指令表被抢指令的bug。 * Refactor:1. Add support for automatically translating replies to Japanese and generating voice messages in VITS voice mode (please monitor remaining quota after enabling). 2. Add translation function. 3. Add emotion configuration for Azure voice mode, allowing the robot to select appropriate emotional styles for replies. * Refactor:Handle the issue of exceeding character setting limit caused by adding emotion configuration. * Fix: fix bugs * Refactor: Added error feedback to translation service * Refactor: Added support for viewing the list of supported roles for each language mode, and fixed some bugs in the emotion switching feature of the auzre mode. * Refactor: Optimized some command feedback and added owner restriction to chat record export function. * Refactor: Optimized feedback when viewing role list to avoid excessive messages. * Refactor: Optimized feedback when configuring multi-emotion mode. * Feature: Added help instructions for translation feature. * chore: Adjust help instructions for mood settings * Fix: Fixed issue where only first line of multi-line replies were being read and Azure voice was pronouncing punctuation marks. * Fix: Fixed bug where switching to Azure voice mode prompted for missing key and restricted ability to view voice role list to only when in voice mode. --------- Co-authored-by: Sean <1519059137@qq.com> Co-authored-by: ikechan8370 <geyinchibuaa@gmail.com>
This commit is contained in:
parent
bb90c3c3e7
commit
b687d45897
13 changed files with 1165 additions and 75 deletions
126
apps/chat.js
126
apps/chat.js
|
|
@ -33,6 +33,8 @@ import uploadRecord from '../utils/uploadRecord.js'
|
|||
import { SlackClaudeClient } from '../utils/slack/slackClient.js'
|
||||
import { ChatgptManagement } from './management.js'
|
||||
import { getPromptByName } from '../utils/prompts.js'
|
||||
import Translate from '../utils/baiduTranslate.js'
|
||||
import emojiStrip from 'emoji-strip'
|
||||
try {
|
||||
await import('keyv')
|
||||
} catch (err) {
|
||||
|
|
@ -220,6 +222,7 @@ export class chatgpt extends plugin {
|
|||
async destroyConversations (e) {
|
||||
const userData = await getUserData(e.user_id)
|
||||
const use = (userData.mode === 'default' ? null : userData.mode) || await redis.get('CHATGPT:USE')
|
||||
await redis.del(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`)
|
||||
if (use === 'claude') {
|
||||
// let client = new SlackClaudeClient({
|
||||
// slackUserToken: Config.slackUserToken,
|
||||
|
|
@ -367,6 +370,7 @@ export class chatgpt extends plugin {
|
|||
switch (use) {
|
||||
case 'claude': {
|
||||
let cs = await redis.keys('CHATGPT:SLACK_CONVERSATION:*')
|
||||
let we = await redis.keys('CHATGPT:WRONG_EMOTION:*')
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
await redis.del(cs[i])
|
||||
if (Config.debug) {
|
||||
|
|
@ -374,10 +378,14 @@ export class chatgpt extends plugin {
|
|||
}
|
||||
deleted++
|
||||
}
|
||||
for (const element of we) {
|
||||
await redis.del(element)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'bing': {
|
||||
let cs = await redis.keys('CHATGPT:CONVERSATIONS_BING:*')
|
||||
let we = await redis.keys('CHATGPT:WRONG_EMOTION:*')
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
await redis.del(cs[i])
|
||||
if (Config.debug) {
|
||||
|
|
@ -385,6 +393,9 @@ export class chatgpt extends plugin {
|
|||
}
|
||||
deleted++
|
||||
}
|
||||
for (const element of we) {
|
||||
await redis.del(element)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'api': {
|
||||
|
|
@ -519,7 +530,7 @@ export class chatgpt extends plugin {
|
|||
}
|
||||
break
|
||||
case 'azure':
|
||||
if (!Config.azureKey) {
|
||||
if (!Config.azureTTSKey) {
|
||||
await this.reply('您没有配置Azure Key,请前往锅巴面板进行配置')
|
||||
return
|
||||
}
|
||||
|
|
@ -538,6 +549,7 @@ export class chatgpt extends plugin {
|
|||
userSetting = JSON.parse(userSetting)
|
||||
}
|
||||
userSetting.useTTS = true
|
||||
userSetting.usePicture = false
|
||||
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
||||
await this.reply('ChatGPT回复已转换为语音模式')
|
||||
}
|
||||
|
|
@ -611,7 +623,8 @@ export class chatgpt extends plugin {
|
|||
userSetting.ttsRoleAzure = chosen[0].code
|
||||
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
||||
// Config.azureTTSSpeaker = chosen[0].code
|
||||
await this.reply(`您的默认语音角色已被设置为”${speaker}-${chosen[0].gender}-${chosen[0].languageDetail}“`)
|
||||
const supportEmotion = AzureTTS.supportConfigurations.find(config => config.name === speaker)?.emotion
|
||||
await this.reply(`您的默认语音角色已被设置为 ${speaker}-${chosen[0].gender}-${chosen[0].languageDetail} ${supportEmotion && Config.azureTTSEmotion ? ',此角色支持多情绪配置,建议重新使用设定并结束对话以获得最佳体验!' : ''}`)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
@ -845,6 +858,25 @@ export class chatgpt extends plugin {
|
|||
await this.reply('我正在思考如何回复你,请稍等', true, { recallMsg: 8 })
|
||||
}
|
||||
}
|
||||
const emotionFlag = await redis.get(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`)
|
||||
let userReplySetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
|
||||
userReplySetting = !userReplySetting
|
||||
? getDefaultReplySetting()
|
||||
: JSON.parse(userReplySetting)
|
||||
// 图片模式就不管了,降低抱歉概率
|
||||
if (Config.ttsMode === 'azure' && Config.enhanceAzureTTSEmotion && userReplySetting.useTTS === true && await AzureTTS.getEmotionPrompt(e)) {
|
||||
switch (emotionFlag) {
|
||||
case '1':
|
||||
prompt += '(上一次回复没有添加情绪,请确保接下来的对话正确使用情绪和情绪格式,回复时忽略此内容。)'
|
||||
break
|
||||
case '2':
|
||||
prompt += '(不要使用给出情绪范围的词和错误的情绪格式,请确保接下来的对话正确选择情绪,回复时忽略此内容。)'
|
||||
break
|
||||
case '3':
|
||||
prompt += '(不要给出多个情绪[]项,请确保接下来的对话给且只给出一个正确情绪项,回复时忽略此内容。)'
|
||||
break
|
||||
}
|
||||
}
|
||||
logger.info(`chatgpt prompt: ${prompt}`)
|
||||
let previousConversation
|
||||
let conversation = {}
|
||||
|
|
@ -955,7 +987,57 @@ export class chatgpt extends plugin {
|
|||
await e.reply('没有任何回复', true)
|
||||
return
|
||||
}
|
||||
// 分离内容和情绪
|
||||
let emotion, emotionDegree
|
||||
if (Config.ttsMode === 'azure' && (use === 'claude' || use === 'bing') && await AzureTTS.getEmotionPrompt(e)) {
|
||||
let ttsRoleAzure = userReplySetting.ttsRoleAzure
|
||||
const emotionReg = /\[\s*['`’‘]?(\w+)[`’‘']?\s*[,,、]\s*([\d.]+)\s*\]/
|
||||
const emotionTimes = response.match(/\[\s*['`’‘]?(\w+)[`’‘']?\s*[,,、]\s*([\d.]+)\s*\]/g)
|
||||
const emotionMatch = response.match(emotionReg)
|
||||
if (emotionMatch) {
|
||||
const [startIndex, endIndex] = [
|
||||
emotionMatch.index,
|
||||
emotionMatch.index + emotionMatch[0].length - 1
|
||||
]
|
||||
const ttsArr =
|
||||
response.length / 2 < endIndex
|
||||
? [response.substring(startIndex), response.substring(0, startIndex)]
|
||||
: [
|
||||
response.substring(0, endIndex + 1),
|
||||
response.substring(endIndex + 1)
|
||||
]
|
||||
const match = ttsArr[0].match(emotionReg)
|
||||
response = ttsArr[1].replace(/\n/, '').trim()
|
||||
if (match) {
|
||||
[emotion, emotionDegree] = [match[1], match[2]]
|
||||
const configuration = AzureTTS.supportConfigurations.find(
|
||||
(config) => config.code === ttsRoleAzure
|
||||
)
|
||||
const supportedEmotions =
|
||||
configuration.emotion && Object.keys(configuration.emotion)
|
||||
if (supportedEmotions && supportedEmotions.includes(emotion)) {
|
||||
logger.warn(`角色 ${ttsRoleAzure} 支持 ${emotion} 情绪.`)
|
||||
await redis.set(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`, '0')
|
||||
} else {
|
||||
logger.warn(`角色 ${ttsRoleAzure} 不支持 ${emotion} 情绪.`)
|
||||
await redis.set(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`, '2')
|
||||
}
|
||||
logger.info(`情绪: ${emotion}, 程度: ${emotionDegree}`)
|
||||
if (emotionTimes.length > 1) {
|
||||
logger.warn('回复包含多个情绪项')
|
||||
// 处理包含多个情绪项的情况,后续可以考虑实现单次回复多情绪的配置
|
||||
response = response.replace(/\[\s*['`’‘]?(\w+)[`’‘']?\s*[,,、]\s*([\d.]+)\s*\]/g, '').trim()
|
||||
await redis.set(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`, '3')
|
||||
}
|
||||
} else {
|
||||
// 使用了正则匹配外的奇奇怪怪的符号
|
||||
logger.warn('情绪格式错误')
|
||||
await redis.set(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`, '2')
|
||||
}
|
||||
} else {
|
||||
logger.warn('回复不包含情绪')
|
||||
await redis.set(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`, '1')
|
||||
}
|
||||
}
|
||||
if (Config.sydneyMood) {
|
||||
let tempResponse = completeJSON(response)
|
||||
if (tempResponse.text) response = tempResponse.text
|
||||
|
|
@ -1013,6 +1095,9 @@ export class chatgpt extends plugin {
|
|||
ttsRegex = ''
|
||||
}
|
||||
ttsResponse = response.replace(ttsRegex, '')
|
||||
ttsResponse = emojiStrip(ttsResponse)
|
||||
// 处理多行回复有时候只会读第一行和azure语音会读出一些标点符号的问题
|
||||
ttsResponse = ttsResponse.replace(/[-:_;*;\n]/g, ',')
|
||||
// 先把文字回复发出去,避免过久等待合成语音
|
||||
if (Config.alsoSendText || ttsResponse.length > Config.ttsAutoFallbackThreshold) {
|
||||
if (Config.ttsMode === 'vits-uma-genshin-honkai' && ttsResponse.length > Config.ttsAutoFallbackThreshold) {
|
||||
|
|
@ -1028,6 +1113,22 @@ export class chatgpt extends plugin {
|
|||
}
|
||||
let wav
|
||||
if (Config.ttsMode === 'vits-uma-genshin-honkai' && Config.ttsSpace) {
|
||||
if (Config.autoJapanese && (_.isEmpty(Config.baiduTranslateAppId) || _.isEmpty(Config.baiduTranslateSecret))) {
|
||||
await this.reply('请检查翻译配置是否正确。')
|
||||
return false
|
||||
}
|
||||
if (Config.autoJapanese) {
|
||||
try {
|
||||
const translate = new Translate({
|
||||
appid: Config.baiduTranslateAppId,
|
||||
secret: Config.baiduTranslateSecret
|
||||
})
|
||||
ttsResponse = await translate(ttsResponse, '日')
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
await this.reply(err.message + '\n将使用原始文本合成语音...')
|
||||
}
|
||||
}
|
||||
try {
|
||||
wav = await generateAudio(ttsResponse, speaker, '中日混合(中文用[ZH][ZH]包裹起来,日文用[JA][JA]包裹起来)')
|
||||
} catch (err) {
|
||||
|
|
@ -1035,9 +1136,14 @@ export class chatgpt extends plugin {
|
|||
await this.reply('合成语音发生错误~')
|
||||
}
|
||||
} else if (Config.ttsMode === 'azure' && Config.azureTTSKey) {
|
||||
let ssml = AzureTTS.generateSsml(ttsResponse, {
|
||||
speaker,
|
||||
emotion,
|
||||
emotionDegree
|
||||
})
|
||||
wav = await AzureTTS.generateAudio(ttsResponse, {
|
||||
speaker
|
||||
})
|
||||
}, await ssml)
|
||||
} else if (Config.ttsMode === 'voicevox' && Config.voicevoxSpace) {
|
||||
wav = await VoiceVoxTTS.generateAudio(ttsResponse, {
|
||||
speaker
|
||||
|
|
@ -1573,7 +1679,11 @@ export class chatgpt extends plugin {
|
|||
// 如果是新对话
|
||||
if (Config.slackClaudeEnableGlobalPreset && (useCast?.slack || Config.slackClaudeGlobalPreset)) {
|
||||
// 先发送设定
|
||||
await client.sendMessage(useCast?.slack || Config.slackClaudeGlobalPreset, e)
|
||||
let prompt = (useCast?.slack || Config.slackClaudeGlobalPreset)
|
||||
await client.sendMessage(prompt, e)
|
||||
// 处理可能由情绪参数导致的设定超限问题
|
||||
await client.sendMessage(await AzureTTS.getEmotionPrompt(e), e)
|
||||
logger.info('claudeFirst:', prompt)
|
||||
}
|
||||
}
|
||||
let text = await client.sendMessage(prompt, e)
|
||||
|
|
@ -1624,6 +1734,7 @@ export class chatgpt extends plugin {
|
|||
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 {
|
||||
|
|
@ -1648,6 +1759,7 @@ export class chatgpt extends plugin {
|
|||
// 如果有对话进行中,先删除
|
||||
logger.info('开启Claude新对话,但旧对话未结束,自动结束上一次对话')
|
||||
await redis.del(`CHATGPT:SLACK_CONVERSATION:${e.sender.user_id}`)
|
||||
await redis.del(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`)
|
||||
}
|
||||
response = await client.sendMessage('', e)
|
||||
await e.reply(response, true)
|
||||
|
|
@ -1661,9 +1773,11 @@ export class chatgpt extends plugin {
|
|||
// 如果有对话进行中,先删除
|
||||
logger.info('开启Claude新对话,但旧对话未结束,自动结束上一次对话')
|
||||
await redis.del(`CHATGPT:SLACK_CONVERSATION:${e.sender.user_id}`)
|
||||
await redis.del(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`)
|
||||
}
|
||||
logger.info('send preset: ' + preset.content)
|
||||
response = await client.sendMessage(preset.content, e)
|
||||
response = await client.sendMessage(preset.content, e) +
|
||||
await client.sendMessage(await AzureTTS.getEmotionPrompt(e), e)
|
||||
await e.reply(response, true)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue