mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 21:37:11 +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 { SlackClaudeClient } from '../utils/slack/slackClient.js'
|
||||||
import { ChatgptManagement } from './management.js'
|
import { ChatgptManagement } from './management.js'
|
||||||
import { getPromptByName } from '../utils/prompts.js'
|
import { getPromptByName } from '../utils/prompts.js'
|
||||||
|
import Translate from '../utils/baiduTranslate.js'
|
||||||
|
import emojiStrip from 'emoji-strip'
|
||||||
try {
|
try {
|
||||||
await import('keyv')
|
await import('keyv')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -220,6 +222,7 @@ export class chatgpt extends plugin {
|
||||||
async destroyConversations (e) {
|
async destroyConversations (e) {
|
||||||
const userData = await getUserData(e.user_id)
|
const userData = await getUserData(e.user_id)
|
||||||
const use = (userData.mode === 'default' ? null : userData.mode) || await redis.get('CHATGPT:USE')
|
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') {
|
if (use === 'claude') {
|
||||||
// let client = new SlackClaudeClient({
|
// let client = new SlackClaudeClient({
|
||||||
// slackUserToken: Config.slackUserToken,
|
// slackUserToken: Config.slackUserToken,
|
||||||
|
|
@ -367,6 +370,7 @@ export class chatgpt extends plugin {
|
||||||
switch (use) {
|
switch (use) {
|
||||||
case 'claude': {
|
case 'claude': {
|
||||||
let cs = await redis.keys('CHATGPT:SLACK_CONVERSATION:*')
|
let cs = await redis.keys('CHATGPT:SLACK_CONVERSATION:*')
|
||||||
|
let we = await redis.keys('CHATGPT:WRONG_EMOTION:*')
|
||||||
for (let i = 0; i < cs.length; i++) {
|
for (let i = 0; i < cs.length; i++) {
|
||||||
await redis.del(cs[i])
|
await redis.del(cs[i])
|
||||||
if (Config.debug) {
|
if (Config.debug) {
|
||||||
|
|
@ -374,10 +378,14 @@ export class chatgpt extends plugin {
|
||||||
}
|
}
|
||||||
deleted++
|
deleted++
|
||||||
}
|
}
|
||||||
|
for (const element of we) {
|
||||||
|
await redis.del(element)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'bing': {
|
case 'bing': {
|
||||||
let cs = await redis.keys('CHATGPT:CONVERSATIONS_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++) {
|
for (let i = 0; i < cs.length; i++) {
|
||||||
await redis.del(cs[i])
|
await redis.del(cs[i])
|
||||||
if (Config.debug) {
|
if (Config.debug) {
|
||||||
|
|
@ -385,6 +393,9 @@ export class chatgpt extends plugin {
|
||||||
}
|
}
|
||||||
deleted++
|
deleted++
|
||||||
}
|
}
|
||||||
|
for (const element of we) {
|
||||||
|
await redis.del(element)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'api': {
|
case 'api': {
|
||||||
|
|
@ -519,7 +530,7 @@ export class chatgpt extends plugin {
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'azure':
|
case 'azure':
|
||||||
if (!Config.azureKey) {
|
if (!Config.azureTTSKey) {
|
||||||
await this.reply('您没有配置Azure Key,请前往锅巴面板进行配置')
|
await this.reply('您没有配置Azure Key,请前往锅巴面板进行配置')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -538,6 +549,7 @@ export class chatgpt extends plugin {
|
||||||
userSetting = JSON.parse(userSetting)
|
userSetting = JSON.parse(userSetting)
|
||||||
}
|
}
|
||||||
userSetting.useTTS = true
|
userSetting.useTTS = true
|
||||||
|
userSetting.usePicture = false
|
||||||
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
||||||
await this.reply('ChatGPT回复已转换为语音模式')
|
await this.reply('ChatGPT回复已转换为语音模式')
|
||||||
}
|
}
|
||||||
|
|
@ -611,7 +623,8 @@ export class chatgpt extends plugin {
|
||||||
userSetting.ttsRoleAzure = chosen[0].code
|
userSetting.ttsRoleAzure = chosen[0].code
|
||||||
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
await redis.set(`CHATGPT:USER:${e.sender.user_id}`, JSON.stringify(userSetting))
|
||||||
// Config.azureTTSSpeaker = chosen[0].code
|
// 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
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -845,6 +858,25 @@ export class chatgpt extends plugin {
|
||||||
await this.reply('我正在思考如何回复你,请稍等', true, { recallMsg: 8 })
|
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}`)
|
logger.info(`chatgpt prompt: ${prompt}`)
|
||||||
let previousConversation
|
let previousConversation
|
||||||
let conversation = {}
|
let conversation = {}
|
||||||
|
|
@ -955,7 +987,57 @@ export class chatgpt extends plugin {
|
||||||
await e.reply('没有任何回复', true)
|
await e.reply('没有任何回复', true)
|
||||||
return
|
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) {
|
if (Config.sydneyMood) {
|
||||||
let tempResponse = completeJSON(response)
|
let tempResponse = completeJSON(response)
|
||||||
if (tempResponse.text) response = tempResponse.text
|
if (tempResponse.text) response = tempResponse.text
|
||||||
|
|
@ -1013,6 +1095,9 @@ export class chatgpt extends plugin {
|
||||||
ttsRegex = ''
|
ttsRegex = ''
|
||||||
}
|
}
|
||||||
ttsResponse = response.replace(ttsRegex, '')
|
ttsResponse = response.replace(ttsRegex, '')
|
||||||
|
ttsResponse = emojiStrip(ttsResponse)
|
||||||
|
// 处理多行回复有时候只会读第一行和azure语音会读出一些标点符号的问题
|
||||||
|
ttsResponse = ttsResponse.replace(/[-:_;*;\n]/g, ',')
|
||||||
// 先把文字回复发出去,避免过久等待合成语音
|
// 先把文字回复发出去,避免过久等待合成语音
|
||||||
if (Config.alsoSendText || ttsResponse.length > Config.ttsAutoFallbackThreshold) {
|
if (Config.alsoSendText || ttsResponse.length > Config.ttsAutoFallbackThreshold) {
|
||||||
if (Config.ttsMode === 'vits-uma-genshin-honkai' && 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
|
let wav
|
||||||
if (Config.ttsMode === 'vits-uma-genshin-honkai' && Config.ttsSpace) {
|
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 {
|
try {
|
||||||
wav = await generateAudio(ttsResponse, speaker, '中日混合(中文用[ZH][ZH]包裹起来,日文用[JA][JA]包裹起来)')
|
wav = await generateAudio(ttsResponse, speaker, '中日混合(中文用[ZH][ZH]包裹起来,日文用[JA][JA]包裹起来)')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -1035,9 +1136,14 @@ export class chatgpt extends plugin {
|
||||||
await this.reply('合成语音发生错误~')
|
await this.reply('合成语音发生错误~')
|
||||||
}
|
}
|
||||||
} else if (Config.ttsMode === 'azure' && Config.azureTTSKey) {
|
} else if (Config.ttsMode === 'azure' && Config.azureTTSKey) {
|
||||||
|
let ssml = AzureTTS.generateSsml(ttsResponse, {
|
||||||
|
speaker,
|
||||||
|
emotion,
|
||||||
|
emotionDegree
|
||||||
|
})
|
||||||
wav = await AzureTTS.generateAudio(ttsResponse, {
|
wav = await AzureTTS.generateAudio(ttsResponse, {
|
||||||
speaker
|
speaker
|
||||||
})
|
}, await ssml)
|
||||||
} else if (Config.ttsMode === 'voicevox' && Config.voicevoxSpace) {
|
} else if (Config.ttsMode === 'voicevox' && Config.voicevoxSpace) {
|
||||||
wav = await VoiceVoxTTS.generateAudio(ttsResponse, {
|
wav = await VoiceVoxTTS.generateAudio(ttsResponse, {
|
||||||
speaker
|
speaker
|
||||||
|
|
@ -1573,7 +1679,11 @@ export class chatgpt extends plugin {
|
||||||
// 如果是新对话
|
// 如果是新对话
|
||||||
if (Config.slackClaudeEnableGlobalPreset && (useCast?.slack || Config.slackClaudeGlobalPreset)) {
|
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)
|
let text = await client.sendMessage(prompt, e)
|
||||||
|
|
@ -1624,6 +1734,7 @@ export class chatgpt extends plugin {
|
||||||
if (err.message?.indexOf('context_length_exceeded') > 0) {
|
if (err.message?.indexOf('context_length_exceeded') > 0) {
|
||||||
logger.warn(err)
|
logger.warn(err)
|
||||||
await redis.del(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`)
|
await redis.del(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`)
|
||||||
|
await redis.del(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`)
|
||||||
await e.reply('字数超限啦,将为您自动结束本次对话。')
|
await e.reply('字数超限啦,将为您自动结束本次对话。')
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1648,6 +1759,7 @@ export class chatgpt extends plugin {
|
||||||
// 如果有对话进行中,先删除
|
// 如果有对话进行中,先删除
|
||||||
logger.info('开启Claude新对话,但旧对话未结束,自动结束上一次对话')
|
logger.info('开启Claude新对话,但旧对话未结束,自动结束上一次对话')
|
||||||
await redis.del(`CHATGPT:SLACK_CONVERSATION:${e.sender.user_id}`)
|
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)
|
response = await client.sendMessage('', e)
|
||||||
await e.reply(response, true)
|
await e.reply(response, true)
|
||||||
|
|
@ -1661,9 +1773,11 @@ export class chatgpt extends plugin {
|
||||||
// 如果有对话进行中,先删除
|
// 如果有对话进行中,先删除
|
||||||
logger.info('开启Claude新对话,但旧对话未结束,自动结束上一次对话')
|
logger.info('开启Claude新对话,但旧对话未结束,自动结束上一次对话')
|
||||||
await redis.del(`CHATGPT:SLACK_CONVERSATION:${e.sender.user_id}`)
|
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)
|
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)
|
await e.reply(response, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ import fetch from 'node-fetch'
|
||||||
import { mkdirs } from '../utils/common.js'
|
import { mkdirs } from '../utils/common.js'
|
||||||
import uploadRecord from '../utils/uploadRecord.js'
|
import uploadRecord from '../utils/uploadRecord.js'
|
||||||
import { makeWordcloud } from '../utils/wordcloud/wordcloud.js'
|
import { makeWordcloud } from '../utils/wordcloud/wordcloud.js'
|
||||||
|
import Translate, { transMap } from '../utils/baiduTranslate.js'
|
||||||
|
import _ from 'lodash'
|
||||||
let useSilk = false
|
let useSilk = false
|
||||||
try {
|
try {
|
||||||
await import('node-silk')
|
await import('node-silk')
|
||||||
|
|
@ -20,7 +21,7 @@ export class Entertainment extends plugin {
|
||||||
constructor (e) {
|
constructor (e) {
|
||||||
super({
|
super({
|
||||||
name: 'ChatGPT-Plugin 娱乐小功能',
|
name: 'ChatGPT-Plugin 娱乐小功能',
|
||||||
dsc: '让你的聊天更有趣!现已支持主动打招呼和表情合成小功能!',
|
dsc: '让你的聊天更有趣!现已支持主动打招呼、表情合成、群聊词云统计与文本翻译小功能!',
|
||||||
event: 'message',
|
event: 'message',
|
||||||
priority: 500,
|
priority: 500,
|
||||||
rule: [
|
rule: [
|
||||||
|
|
@ -41,6 +42,10 @@ export class Entertainment extends plugin {
|
||||||
{
|
{
|
||||||
reg: '^#?(今日词云|群友在聊什么)$',
|
reg: '^#?(今日词云|群友在聊什么)$',
|
||||||
fnc: 'wordcloud'
|
fnc: 'wordcloud'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reg: '^#((?:寄批踢)?翻.*|chatgpt翻译帮助)',
|
||||||
|
fnc: 'translate'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
@ -55,6 +60,42 @@ export class Entertainment extends plugin {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async translate (e) {
|
||||||
|
if (e.msg.trim() === '#chatgpt翻译帮助') {
|
||||||
|
await this.reply('支持中、日、文(文言文)、英、俄、韩语言之间的文本翻译功能,"寄批踢"为可选前缀' +
|
||||||
|
'\n示例:1. #寄批踢翻英 你好' +
|
||||||
|
'\t2. #翻中 你好' +
|
||||||
|
'\t3. #寄批踢翻文 hello')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (_.isEmpty(Config.baiduTranslateAppId) || _.isEmpty(Config.baiduTranslateSecret)) {
|
||||||
|
this.reply('请检查翻译配置是否正确。')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const regExp = /(#(?:寄批踢)?翻(.))(.*)/
|
||||||
|
const msg = e.msg.trim()
|
||||||
|
const match = msg.match(regExp)
|
||||||
|
let result = ''
|
||||||
|
if (!(match[2] in transMap)) {
|
||||||
|
e.reply('输入格式有误或暂不支持该语言,' +
|
||||||
|
'\n当前支持:中、日、文(文言文)、英、俄、韩。', e.isGroup
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const PendingText = match[3]
|
||||||
|
try {
|
||||||
|
const translate = new Translate({
|
||||||
|
appid: Config.baiduTranslateAppId,
|
||||||
|
secret: Config.baiduTranslateSecret
|
||||||
|
})
|
||||||
|
result = await translate(PendingText, match[2])
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err)
|
||||||
|
result = err.message
|
||||||
|
}
|
||||||
|
await this.reply(result, e.isGroup)
|
||||||
|
}
|
||||||
|
|
||||||
async wordcloud (e) {
|
async wordcloud (e) {
|
||||||
if (e.isGroup) {
|
if (e.isGroup) {
|
||||||
let groupId = e.group_id
|
let groupId = e.group_id
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ export class history extends plugin {
|
||||||
rule: [
|
rule: [
|
||||||
{
|
{
|
||||||
reg: '^#(chatgpt|ChatGPT)(导出)?聊天记录$',
|
reg: '^#(chatgpt|ChatGPT)(导出)?聊天记录$',
|
||||||
fnc: 'history'
|
fnc: 'history',
|
||||||
|
permission: 'master'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,23 @@
|
||||||
import plugin from '../../../lib/plugins/plugin.js'
|
import plugin from '../../../lib/plugins/plugin.js'
|
||||||
import { Config } from '../utils/config.js'
|
import { Config } from '../utils/config.js'
|
||||||
import { exec } from 'child_process'
|
import { exec } from 'child_process'
|
||||||
import { checkPnpm, formatDuration, parseDuration, getPublicIP, renderUrl } from '../utils/common.js'
|
import {
|
||||||
|
checkPnpm,
|
||||||
|
formatDuration,
|
||||||
|
parseDuration,
|
||||||
|
getPublicIP,
|
||||||
|
renderUrl,
|
||||||
|
makeForwardMsg,
|
||||||
|
getDefaultReplySetting
|
||||||
|
} from '../utils/common.js'
|
||||||
import SydneyAIClient from '../utils/SydneyAIClient.js'
|
import SydneyAIClient from '../utils/SydneyAIClient.js'
|
||||||
import { convertSpeaker, speakers } from '../utils/tts.js'
|
import { convertSpeaker, speakers as vitsRoleList } from '../utils/tts.js'
|
||||||
import md5 from 'md5'
|
import md5 from 'md5'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import loader from '../../../lib/plugins/loader.js'
|
import loader from '../../../lib/plugins/loader.js'
|
||||||
|
import { supportConfigurations as voxRoleList } from '../utils/tts/voicevox.js'
|
||||||
|
import { supportConfigurations as azureRoleList } from '../utils/tts/microsoft-azure.js'
|
||||||
let isWhiteList = true
|
let isWhiteList = true
|
||||||
export class ChatgptManagement extends plugin {
|
export class ChatgptManagement extends plugin {
|
||||||
constructor (e) {
|
constructor (e) {
|
||||||
|
|
@ -212,11 +222,62 @@ export class ChatgptManagement extends plugin {
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt(对话|管理|娱乐|绘图|人物设定|聊天记录)?指令表(帮助)?',
|
reg: '^#chatgpt(对话|管理|娱乐|绘图|人物设定|聊天记录)?指令表(帮助)?',
|
||||||
fnc: 'commandHelp'
|
fnc: 'commandHelp'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reg: '^#语音切换.*',
|
||||||
|
fnc: 'ttsSwitch',
|
||||||
|
permission: 'master'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reg: '^#chatgpt角色列表$',
|
||||||
|
fnc: 'getTTSRoleList'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getTTSRoleList (e) {
|
||||||
|
let userReplySetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
|
||||||
|
userReplySetting = !userReplySetting
|
||||||
|
? getDefaultReplySetting()
|
||||||
|
: JSON.parse(userReplySetting)
|
||||||
|
if (!userReplySetting.useTTS) return
|
||||||
|
let ttsMode = Config.ttsMode
|
||||||
|
let roleList = []
|
||||||
|
if (ttsMode === 'vits-uma-genshin-honkai') {
|
||||||
|
const [firstHalf, secondHalf] = [vitsRoleList.slice(0, Math.floor(vitsRoleList.length / 2)).join('、'), vitsRoleList.slice(Math.floor(vitsRoleList.length / 2)).join('、')]
|
||||||
|
const [chunk1, chunk2] = [firstHalf.match(/[^、]+(?:、[^、]+){0,30}/g), secondHalf.match(/[^、]+(?:、[^、]+){0,30}/g)]
|
||||||
|
const list = [await makeForwardMsg(e, chunk1, `${Config.ttsMode}角色列表1`), await makeForwardMsg(e, chunk2, `${Config.ttsMode}角色列表2`)]
|
||||||
|
roleList = await makeForwardMsg(e, list, `${Config.ttsMode}角色列表`)
|
||||||
|
await this.reply(roleList)
|
||||||
|
return
|
||||||
|
} else if (ttsMode === 'voicevox') {
|
||||||
|
roleList = voxRoleList.map(item => item.name).join('、')
|
||||||
|
} else if (ttsMode === 'azure') {
|
||||||
|
roleList = azureRoleList.map(item => item.name).join('、')
|
||||||
|
}
|
||||||
|
if (roleList.length > 300) {
|
||||||
|
let chunks = roleList.match(/[^、]+(?:、[^、]+){0,30}/g)
|
||||||
|
roleList = await makeForwardMsg(e, chunks, `${Config.ttsMode}角色列表`)
|
||||||
|
}
|
||||||
|
await this.reply(roleList)
|
||||||
|
}
|
||||||
|
async ttsSwitch (e) {
|
||||||
|
let regExp = /#语音切换(.*)/
|
||||||
|
let ttsMode = e.msg.match(regExp)[1]
|
||||||
|
if (['vits', 'azure', 'voicevox'].includes(ttsMode)) {
|
||||||
|
if (ttsMode === 'vits') {
|
||||||
|
Config.ttsMode = 'vits-uma-genshin-honkai'
|
||||||
|
} else {
|
||||||
|
Config.ttsMode = ttsMode
|
||||||
|
}
|
||||||
|
await this.reply(`语音回复已切换至${Config.ttsMode}模式${Config.ttsMode === 'azure' ? ',建议重新开始对话以获得更好的对话效果!' : ''}`)
|
||||||
|
} else {
|
||||||
|
await this.reply('暂不支持此模式,当前支持vits,azure,voicevox。')
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
async commandHelp (e) {
|
async commandHelp (e) {
|
||||||
if (!this.e.isMaster) { return this.reply('你没有权限') }
|
if (!this.e.isMaster) { return this.reply('你没有权限') }
|
||||||
if (e.msg.trim() === '#chatgpt指令表帮助') {
|
if (e.msg.trim() === '#chatgpt指令表帮助') {
|
||||||
|
|
@ -267,8 +328,8 @@ export class ChatgptManagement extends plugin {
|
||||||
prompts.push(generatePrompt(plugin, commands))
|
prompts.push(generatePrompt(plugin, commands))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let msg = await makeForwardMsg(e, prompts, e.msg.slice(1))
|
||||||
await this.reply(prompts.join('\n'))
|
await this.reply(msg)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -479,7 +540,7 @@ export class ChatgptManagement extends plugin {
|
||||||
Config.defaultTTSRole = ''
|
Config.defaultTTSRole = ''
|
||||||
} else {
|
} else {
|
||||||
const ttsRole = convertSpeaker(speaker)
|
const ttsRole = convertSpeaker(speaker)
|
||||||
if (speakers.includes(ttsRole)) {
|
if (vitsRoleList.includes(ttsRole)) {
|
||||||
Config.defaultTTSRole = ttsRole
|
Config.defaultTTSRole = ttsRole
|
||||||
replyMsg = `ChatGPT默认语音角色已被设置为“${ttsRole}”`
|
replyMsg = `ChatGPT默认语音角色已被设置为“${ttsRole}”`
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import _ from 'lodash'
|
||||||
import { Config } from '../utils/config.js'
|
import { Config } from '../utils/config.js'
|
||||||
import { getMasterQQ, limitString, makeForwardMsg, maskQQ } from '../utils/common.js'
|
import { getMasterQQ, limitString, makeForwardMsg, maskQQ } from '../utils/common.js'
|
||||||
import { deleteOnePrompt, getPromptByName, readPrompts, saveOnePrompt } from '../utils/prompts.js'
|
import { deleteOnePrompt, getPromptByName, readPrompts, saveOnePrompt } from '../utils/prompts.js'
|
||||||
|
import AzureTTS from "../utils/tts/microsoft-azure.js";
|
||||||
export class help extends plugin {
|
export class help extends plugin {
|
||||||
constructor (e) {
|
constructor (e) {
|
||||||
super({
|
super({
|
||||||
|
|
@ -160,7 +161,12 @@ export class help extends plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyMap[use]) {
|
if (keyMap[use]) {
|
||||||
|
if (Config.ttsMode === 'azure') {
|
||||||
|
Config[keyMap[use]] = prompt.content + '\n' + await AzureTTS.getEmotionPrompt(e)
|
||||||
|
logger.warn(Config[keyMap[use]])
|
||||||
|
} else {
|
||||||
Config[keyMap[use]] = prompt.content
|
Config[keyMap[use]] = prompt.content
|
||||||
|
}
|
||||||
await redis.set(`CHATGPT:PROMPT_USE_${use}`, promptName)
|
await redis.set(`CHATGPT:PROMPT_USE_${use}`, promptName)
|
||||||
await e.reply(`你当前正在使用${use}模式,已将该模式设定应用为"${promptName}"。更该设定后建议结束对话以使设定更好生效`, true)
|
await e.reply(`你当前正在使用${use}模式,已将该模式设定应用为"${promptName}"。更该设定后建议结束对话以使设定更好生效`, true)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -72,5 +72,11 @@
|
||||||
"enablePrivateChat": false,
|
"enablePrivateChat": false,
|
||||||
"groupWhitelist": [],
|
"groupWhitelist": [],
|
||||||
"groupBlacklist": [],
|
"groupBlacklist": [],
|
||||||
"ttsRegex": "/匹配规则/匹配模式"
|
"ttsRegex": "/匹配规则/匹配模式",
|
||||||
|
"baiduTranslateAppId": "",
|
||||||
|
"baiduTranslateSecret": "",
|
||||||
|
"azureTTSSpeaker": "zh-CN-XiaochenNeural",
|
||||||
|
"azureTTSEmotion": false,
|
||||||
|
"enhanceAzureTTSEmotion": false,
|
||||||
|
"autoJapanese": false
|
||||||
}
|
}
|
||||||
|
|
@ -98,29 +98,16 @@ export function supportGuoba () {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'defaultTTSRole',
|
field: 'defaultTTSRole',
|
||||||
label: '语音模式默认角色(vits-uma-genshin-honkai)',
|
label: 'vits默认角色',
|
||||||
bottomHelpMessage: 'vits-uma-genshin-honkai语音模式下,未指定角色时使用的角色。若留空,将使用随机角色回复。若用户通过指令指定了角色,将忽略本设定',
|
bottomHelpMessage: 'vits-uma-genshin-honkai语音模式下,未指定角色时使用的角色。若留空,将使用随机角色回复。若用户通过指令指定了角色,将忽略本设定',
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
options: speakers.concat('随机').map(s => { return { label: s, value: s } })
|
options: speakers.concat('随机').map(s => { return { label: s, value: s } })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
field: 'voicevoxTTSSpeaker',
|
|
||||||
label: '语音模式默认角色(VoiceVox)',
|
|
||||||
bottomHelpMessage: 'VoiceVox语音模式下,未指定角色时使用的角色。若留空,将使用随机角色回复。若用户通过指令指定了角色,将忽略本设定',
|
|
||||||
component: 'Select',
|
|
||||||
componentProps: {
|
|
||||||
options: VoiceVoxTTS.supportConfigurations.map(item => {
|
|
||||||
return item.styles.map(style => {
|
|
||||||
return `${item.name}-${style.name}`
|
|
||||||
}).concat(item.name)
|
|
||||||
}).flat().concat('随机').map(s => { return { label: s, value: s } })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
field: 'azureTTSSpeaker',
|
field: 'azureTTSSpeaker',
|
||||||
label: '语音模式默认角色(微软Azure)',
|
label: 'Azure默认角色',
|
||||||
bottomHelpMessage: '微软Azure语音模式下,未指定角色时使用的角色。若用户通过指令指定了角色,将忽略本设定',
|
bottomHelpMessage: '微软Azure语音模式下,未指定角色时使用的角色。若用户通过指令指定了角色,将忽略本设定',
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
|
@ -132,6 +119,19 @@ export function supportGuoba () {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'voicevoxTTSSpeaker',
|
||||||
|
label: 'VoiceVox默认角色',
|
||||||
|
bottomHelpMessage: 'VoiceVox语音模式下,未指定角色时使用的角色。若留空,将使用随机角色回复。若用户通过指令指定了角色,将忽略本设定',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
options: VoiceVoxTTS.supportConfigurations.map(item => {
|
||||||
|
return item.styles.map(style => {
|
||||||
|
return `${item.name}-${style.name}`
|
||||||
|
}).concat(item.name)
|
||||||
|
}).flat().concat('随机').map(s => { return { label: s, value: s } })
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'ttsRegex',
|
field: 'ttsRegex',
|
||||||
label: '语音过滤正则表达式',
|
label: '语音过滤正则表达式',
|
||||||
|
|
@ -155,6 +155,14 @@ export function supportGuoba () {
|
||||||
bottomHelpMessage: '语音模式下,同时发送文字版,避免音质较低听不懂',
|
bottomHelpMessage: '语音模式下,同时发送文字版,避免音质较低听不懂',
|
||||||
component: 'Switch'
|
component: 'Switch'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'autoJapanese',
|
||||||
|
label: 'vits模式日语输出',
|
||||||
|
bottomHelpMessage: '使用vits语音时,将机器人的文字回复翻译成日文后获取语音。' +
|
||||||
|
'需要填写下方的翻译配置,配置文档:http://api.fanyi.baidu.com/doc/21 ' +
|
||||||
|
'填写配置后另外支持通过本插件使用文字翻译功能,发送"#chatgpt翻译帮助"查看使用方法。',
|
||||||
|
component: 'Switch'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'autoUsePicture',
|
field: 'autoUsePicture',
|
||||||
label: '长文本自动转图片',
|
label: '长文本自动转图片',
|
||||||
|
|
@ -557,6 +565,16 @@ export function supportGuoba () {
|
||||||
bottomHelpMessage: '可注册2captcha实现跳过验证码,收费服务但很便宜。否则可能会遇到验证码而卡住',
|
bottomHelpMessage: '可注册2captcha实现跳过验证码,收费服务但很便宜。否则可能会遇到验证码而卡住',
|
||||||
component: 'InputPassword'
|
component: 'InputPassword'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'baiduTranslateAppId',
|
||||||
|
label: '百度翻译应用ID',
|
||||||
|
component: 'Input'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'baiduTranslateSecret',
|
||||||
|
label: '百度翻译密钥',
|
||||||
|
component: 'Input'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'ttsSpace',
|
field: 'ttsSpace',
|
||||||
label: 'vits-uma-genshin-honkai语音转换API地址',
|
label: 'vits-uma-genshin-honkai语音转换API地址',
|
||||||
|
|
@ -580,6 +598,18 @@ export function supportGuoba () {
|
||||||
bottomHelpMessage: '例如japaneast',
|
bottomHelpMessage: '例如japaneast',
|
||||||
component: 'Input'
|
component: 'Input'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'azureTTSEmotion',
|
||||||
|
label: 'Azure情绪多样化',
|
||||||
|
bottomHelpMessage: '切换角色后使用"#chatgpt使用设定xxx"重新开始对话以更新不同角色的情绪配置。支持使用不同的说话风格回复,各个角色支持说话风格详情:https://speech.microsoft.com/portal/voicegallery',
|
||||||
|
component: 'Switch'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'enhanceAzureTTSEmotion',
|
||||||
|
label: 'Azure情绪纠正',
|
||||||
|
bottomHelpMessage: '当机器人未使用或使用了不支持的说话风格时,将在对话中提醒机器人。注意:bing模式开启此项后有概率增大触发抱歉的机率,且不要单独开启此项。',
|
||||||
|
component: 'Switch'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'huggingFaceReverseProxy',
|
field: 'huggingFaceReverseProxy',
|
||||||
label: '语音转换huggingface反代',
|
label: '语音转换huggingface反代',
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
"@slack/bolt": "^3.13.0",
|
"@slack/bolt": "^3.13.0",
|
||||||
"@waylaidwanderer/chatgpt-api": "^1.33.2",
|
"@waylaidwanderer/chatgpt-api": "^1.33.2",
|
||||||
"asn1.js": "^5.0.0",
|
"asn1.js": "^5.0.0",
|
||||||
|
"axios": "^1.3.6",
|
||||||
"chatgpt": "^5.1.1",
|
"chatgpt": "^5.1.1",
|
||||||
"delay": "^5.0.0",
|
"delay": "^5.0.0",
|
||||||
"diff": "^5.1.0",
|
"diff": "^5.1.0",
|
||||||
|
|
@ -18,13 +19,15 @@
|
||||||
"https-proxy-agent": "5.0.1",
|
"https-proxy-agent": "5.0.1",
|
||||||
"keyv": "^4.5.2",
|
"keyv": "^4.5.2",
|
||||||
"keyv-file": "^0.2.0",
|
"keyv-file": "^0.2.0",
|
||||||
|
"md5-node": "^1.0.1",
|
||||||
"microsoft-cognitiveservices-speech-sdk": "^1.27.0",
|
"microsoft-cognitiveservices-speech-sdk": "^1.27.0",
|
||||||
"node-fetch": "^3.3.1",
|
"node-fetch": "^3.3.1",
|
||||||
"openai": "^3.2.1",
|
"openai": "^3.2.1",
|
||||||
"random": "^4.1.0",
|
"random": "^4.1.0",
|
||||||
"undici": "^5.21.0",
|
"undici": "^5.21.0",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"ws": "^8.13.0"
|
"ws": "^8.13.0",
|
||||||
|
"emoji-strip": "^1.0.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@node-rs/jieba": "^1.6.2",
|
"@node-rs/jieba": "^1.6.2",
|
||||||
|
|
|
||||||
141
utils/baiduTranslate.js
Normal file
141
utils/baiduTranslate.js
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
import md5 from 'md5-node'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
// noinspection NonAsciiCharacters
|
||||||
|
export const transMap = { 中: 'zh', 日: 'jp', 文: 'wyw', 英: 'en', 俄: 'ru', 韩: 'kr' }
|
||||||
|
const errOr = {
|
||||||
|
52001: '请求超时,请重试。',
|
||||||
|
52002: '系统错误,请重试。',
|
||||||
|
52003: '未授权用户,请检查appid是否正确或者服务是否开通。',
|
||||||
|
54000: '必填参数为空,请检查是否少传参数。',
|
||||||
|
54001: '签名错误,请检查您的签名生成方法。',
|
||||||
|
54003: '访问频率受限,请降低您的调用频率,或进行身份认证后切换为高级版/尊享版。',
|
||||||
|
54004: '账户余额不足,请前往管理控制台为账户充值。',
|
||||||
|
54005: '长query请求频繁,请降低长query的发送频率,3s后再试。',
|
||||||
|
58000: '客户端IP非法,检查个人资料里填写的IP地址是否正确,可前往开发者信息-基本信息修改。',
|
||||||
|
58001: '译文语言方向不支持,检查译文语言是否在语言列表里。',
|
||||||
|
58002: '服务当前已关闭,请前往管理控制台开启服务。',
|
||||||
|
90107: '认证未通过或未生效,请前往我的认证查看认证进度。'
|
||||||
|
}
|
||||||
|
function Translate (config) {
|
||||||
|
this.requestNumber = 0 // 请求次数
|
||||||
|
this.config = {
|
||||||
|
showProgress: 1, // 是否显示进度
|
||||||
|
requestNumber: 1, // 最大请求次数
|
||||||
|
agreement: 'http', // 协议
|
||||||
|
...config
|
||||||
|
}
|
||||||
|
this.baiduApi = `${this.config.agreement}://api.fanyi.baidu.com/api/trans/vip/translate`
|
||||||
|
|
||||||
|
// 拼接url
|
||||||
|
this.createUrl = (domain, form) => {
|
||||||
|
let result = domain + '?'
|
||||||
|
for (let key in form) {
|
||||||
|
result += `${key}=${form[key]}&`
|
||||||
|
}
|
||||||
|
return result.slice(0, result.length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.translate = async (value, ...params) => {
|
||||||
|
let result = ''
|
||||||
|
let from = 'auto'
|
||||||
|
let to = 'en'
|
||||||
|
|
||||||
|
if (params.length === 1) {
|
||||||
|
to = transMap[params[0]] || to
|
||||||
|
} else if (params.length === 2) {
|
||||||
|
from = transMap[params[0]] || from
|
||||||
|
to = transMap[params[1]] || to
|
||||||
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const res = await this.requestApi(value, { from, to })
|
||||||
|
result = res[0].dst
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value) || Object.prototype.toString.call(value) === '[object Object]') {
|
||||||
|
result = await this._createObjValue(value, { from, to })
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
this.requestApi = (value, params) => {
|
||||||
|
if (this.requestNumber >= this.config.requestNumber) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.requestApi(value, params).then((res) => {
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.requestNumber++
|
||||||
|
|
||||||
|
const { appid, secret } = this.config
|
||||||
|
const q = value
|
||||||
|
const salt = Math.random()
|
||||||
|
const sign = md5(`${appid}${q}${salt}${secret}`)
|
||||||
|
|
||||||
|
const fromData = {
|
||||||
|
q: encodeURIComponent(q),
|
||||||
|
sign,
|
||||||
|
appid,
|
||||||
|
salt,
|
||||||
|
from: params.from || 'auto',
|
||||||
|
to: params.to || 'en'
|
||||||
|
}
|
||||||
|
|
||||||
|
const fanyiApi = this.createUrl(this.baiduApi, fromData)
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
axios
|
||||||
|
.get(fanyiApi)
|
||||||
|
.then(({ data: res }) => {
|
||||||
|
if (!res.error_code) {
|
||||||
|
const resList = res.trans_result
|
||||||
|
resolve(resList)
|
||||||
|
} else {
|
||||||
|
const errCode = res.error_code
|
||||||
|
if (errOr[errCode]) {
|
||||||
|
reject(new Error('翻译出错了~' + errOr[errCode]))
|
||||||
|
} else {
|
||||||
|
reject(new Error('翻译出错了~' + errCode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.requestNumber--
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 递归翻译数组或对象
|
||||||
|
this._createObjValue = async (value, parames) => {
|
||||||
|
let index = 0
|
||||||
|
const obj = Array.isArray(value) ? [] : {}
|
||||||
|
const strDatas = Array.isArray(value) ? value : Object.values(value)
|
||||||
|
const reqData = strDatas
|
||||||
|
.filter((item) => typeof item === 'string') // 过滤字符串
|
||||||
|
.join('\n')
|
||||||
|
const res = reqData ? await this.requestApi(reqData, parames) : []
|
||||||
|
for (let key in value) {
|
||||||
|
if (typeof value[key] === 'string') {
|
||||||
|
obj[key] = res[index].dst
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
Array.isArray(value[key]) ||
|
||||||
|
Object.prototype.toString.call(value[key]) === '[object Object]'
|
||||||
|
) {
|
||||||
|
obj[key] = await this.translate(value[key], parames) // 递归翻译
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.translate
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Translate
|
||||||
|
|
@ -111,8 +111,12 @@ const defaultConfig = {
|
||||||
azureTTSSpeaker: 'zh-CN-XiaochenNeural',
|
azureTTSSpeaker: 'zh-CN-XiaochenNeural',
|
||||||
voicevoxSpace: '',
|
voicevoxSpace: '',
|
||||||
voicevoxTTSSpeaker: '护士机器子T',
|
voicevoxTTSSpeaker: '护士机器子T',
|
||||||
|
baiduTranslateAppId: '',
|
||||||
|
baiduTranslateSecret: '',
|
||||||
|
azureTTSEmotion: false,
|
||||||
|
enhanceAzureTTSEmotion: false,
|
||||||
|
autoJapanese: false,
|
||||||
version: 'v2.5.8'
|
version: 'v2.5.8'
|
||||||
|
|
||||||
}
|
}
|
||||||
const _path = process.cwd()
|
const _path = process.cwd()
|
||||||
let config = {}
|
let config = {}
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,110 @@
|
||||||
|
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import { mkdirs } from '../common.js'
|
import { getDefaultReplySetting, mkdirs } from '../common.js'
|
||||||
import { Config } from '../config.js'
|
import { Config } from '../config.js'
|
||||||
|
|
||||||
let sdk
|
let sdk
|
||||||
try {
|
try {
|
||||||
sdk = (await import('microsoft-cognitiveservices-speech-sdk')).default
|
sdk = (await import('microsoft-cognitiveservices-speech-sdk')).default
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn('未安装microsoft-cognitiveservices-speech-sdk,无法使用微软Azure语音源')
|
logger.warn('未安装microsoft-cognitiveservices-speech-sdk,无法使用微软Azure语音源')
|
||||||
}
|
}
|
||||||
async function generateAudio (text, option = {}) {
|
async function generateAudio (text, option = {}, ssml = '') {
|
||||||
if (!sdk) {
|
if (!sdk) {
|
||||||
throw new Error('未安装microsoft-cognitiveservices-speech-sdk,无法使用微软Azure语音源')
|
throw new Error('未安装microsoft-cognitiveservices-speech-sdk,无法使用微软Azure语音源')
|
||||||
}
|
}
|
||||||
let subscriptionKey = Config.azureTTSKey
|
let subscriptionKey = Config.azureTTSKey
|
||||||
let serviceRegion = Config.azureTTSRegion
|
let serviceRegion = Config.azureTTSRegion
|
||||||
|
let speechConfig = sdk.SpeechConfig.fromSubscription(subscriptionKey, serviceRegion)
|
||||||
const _path = process.cwd()
|
const _path = process.cwd()
|
||||||
mkdirs(`${_path}/data/chatgpt/tts/azure`)
|
mkdirs(`${_path}/data/chatgpt/tts/azure`)
|
||||||
let filename = `${_path}/data/chatgpt/tts/azure/${crypto.randomUUID()}.wav`
|
let filename = `${_path}/data/chatgpt/tts/azure/${crypto.randomUUID()}.wav`
|
||||||
let audioConfig = sdk.AudioConfig.fromAudioFileOutput(filename)
|
let audioConfig = sdk.AudioConfig.fromAudioFileOutput(filename)
|
||||||
let speechConfig = sdk.SpeechConfig.fromSubscription(subscriptionKey, serviceRegion)
|
let synthesizer
|
||||||
// speechConfig.speechSynthesisLanguage = option?.language || 'zh-CN'
|
if (ssml) {
|
||||||
|
synthesizer = new sdk.SpeechSynthesizer(speechConfig, audioConfig)
|
||||||
|
await speakSsmlAsync(synthesizer, ssml)
|
||||||
|
} else {
|
||||||
|
speechConfig.speechSynthesisLanguage = option?.language || 'zh-CN'
|
||||||
logger.info('using speaker: ' + option?.speaker || 'zh-CN-YunyeNeural')
|
logger.info('using speaker: ' + option?.speaker || 'zh-CN-YunyeNeural')
|
||||||
speechConfig.speechSynthesisVoiceName = option?.speaker || 'zh-CN-YunyeNeural'
|
speechConfig.speechSynthesisVoiceName = option?.speaker || 'zh-CN-YunyeNeural'
|
||||||
let synthesizer = new sdk.SpeechSynthesizer(speechConfig, audioConfig)
|
synthesizer = new sdk.SpeechSynthesizer(speechConfig, audioConfig)
|
||||||
|
await speakTextAsync(synthesizer, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('synthesis finished.')
|
||||||
|
synthesizer.close()
|
||||||
|
synthesizer = undefined
|
||||||
|
return filename
|
||||||
|
}
|
||||||
|
|
||||||
|
async function speakTextAsync (synthesizer, text) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
synthesizer.speakTextAsync(text, result => {
|
synthesizer.speakTextAsync(text, result => {
|
||||||
if (result.reason === sdk.ResultReason.SynthesizingAudioCompleted) {
|
if (result.reason === sdk.ResultReason.SynthesizingAudioCompleted) {
|
||||||
console.log('synthesis finished.')
|
logger.info('speakTextAsync: true')
|
||||||
|
resolve()
|
||||||
} else {
|
} else {
|
||||||
console.error('Speech synthesis canceled, ' + result.errorDetails +
|
console.error('Speech synthesis canceled, ' + result.errorDetails +
|
||||||
'\nDid you update the subscription info?')
|
'\nDid you update the subscription info?')
|
||||||
|
reject(result.errorDetails)
|
||||||
}
|
}
|
||||||
synthesizer.close()
|
|
||||||
synthesizer = undefined
|
|
||||||
resolve(filename)
|
|
||||||
}, err => {
|
}, err => {
|
||||||
console.error('err - ' + err)
|
console.error('err - ' + err)
|
||||||
synthesizer.close()
|
|
||||||
synthesizer = undefined
|
|
||||||
reject(err)
|
reject(err)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const supportConfigurations = [
|
async function speakSsmlAsync (synthesizer, ssml) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
synthesizer.speakSsmlAsync(ssml, result => {
|
||||||
|
if (result.reason === sdk.ResultReason.SynthesizingAudioCompleted) {
|
||||||
|
logger.info('speakSsmlAsync: true')
|
||||||
|
resolve()
|
||||||
|
} else {
|
||||||
|
console.error('Speech synthesis canceled, ' + result.errorDetails +
|
||||||
|
'\nDid you update the subscription info?')
|
||||||
|
reject(result.errorDetails)
|
||||||
|
}
|
||||||
|
}, err => {
|
||||||
|
console.error('err - ' + err)
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async function generateSsml (text, option = {}) {
|
||||||
|
const voiceName = option.speaker || 'zh-CN-YunyeNeural'
|
||||||
|
const expressAs = option.emotion ? `<mstts:express-as style="${option.emotion}" styledegree="${option.emotionDegree || 1}">` : ''
|
||||||
|
return `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis"
|
||||||
|
xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="zh-CN">
|
||||||
|
<voice name="${voiceName}">
|
||||||
|
${expressAs}${text}${expressAs ? '</mstts:express-as>' : ''}
|
||||||
|
</voice>
|
||||||
|
</speak>`
|
||||||
|
}
|
||||||
|
async function getEmotionPrompt (e) {
|
||||||
|
if (!Config.azureTTSEmotion) return ''
|
||||||
|
let userReplySetting = await redis.get(`CHATGPT:USER:${e.sender.user_id}`)
|
||||||
|
userReplySetting = !userReplySetting
|
||||||
|
? getDefaultReplySetting()
|
||||||
|
: JSON.parse(userReplySetting)
|
||||||
|
let emotionPrompt = ''
|
||||||
|
let ttsRoleAzure = userReplySetting.ttsRoleAzure
|
||||||
|
const configuration = Config.ttsMode === 'azure' ? supportConfigurations.find(config => config.code === ttsRoleAzure) : ''
|
||||||
|
if (configuration !== '' && configuration.emotion) {
|
||||||
|
// 0-1 感觉没啥区别,说实话只有1和2听得出差别。。
|
||||||
|
emotionPrompt = `\n在回复的最开始使用[]在其中表示你这次回复的情绪风格和程度(1-2),最小单位0.1
|
||||||
|
\n例如:['angry',2]表示你极度愤怒
|
||||||
|
\n这是情绪参考值,禁止使用给出范围以外的词,且单次回复只需要给出一个情绪表示
|
||||||
|
\n${JSON.stringify(configuration.emotion)}
|
||||||
|
\n另外,不要在情绪[]前后使用回车换行,如果你明白上面的设定,请回复’好的,我明白了‘并在后续的对话中严格执行此设定。`
|
||||||
|
// logger.warn('emotionPrompt:', `${JSON.stringify(configuration.emotion)}`)
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return emotionPrompt
|
||||||
|
}
|
||||||
|
export const supportConfigurations = [
|
||||||
{
|
{
|
||||||
code: 'zh-CN-liaoning-XiaobeiNeural',
|
code: 'zh-CN-liaoning-XiaobeiNeural',
|
||||||
name: '晓北',
|
name: '晓北',
|
||||||
|
|
@ -78,35 +138,86 @@ const supportConfigurations = [
|
||||||
name: '晓晓',
|
name: '晓晓',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
emotion: {
|
||||||
|
affectionate: '温暖、亲切的语气',
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
assistant: '数字助理用的是热情而轻松的语气',
|
||||||
|
calm: '沉着冷静的态度说话。语气、音调和韵律统一',
|
||||||
|
chat: '表达轻松随意的语气',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
customerservice: '以友好热情的语气为客户提供支持',
|
||||||
|
disgruntled: '轻蔑、抱怨的语气,表现不悦和蔑视',
|
||||||
|
excited: '乐观、充满希望的语气,发生了美好的事情',
|
||||||
|
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
||||||
|
friendly: '愉快、怡人、温暖、真诚、关切的语气',
|
||||||
|
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
||||||
|
lyrical: '以优美又带感伤的方式表达情感',
|
||||||
|
newscast: '以正式专业的语气叙述新闻',
|
||||||
|
'poetry-reading': '读诗时带情感和节奏的语气',
|
||||||
|
sad: '表达悲伤语气',
|
||||||
|
serious: '严肃、命令的语气'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-YunxiNeural',
|
code: 'zh-CN-YunxiNeural',
|
||||||
name: '云希',
|
name: '云希',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文 (普通话,简体)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
emotion: {
|
||||||
|
angry: '表达生气和愤怒的语气',
|
||||||
|
assistant: '数字助理使用热情而轻松的语气',
|
||||||
|
chat: '表达轻松随意的语气',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
depressed: '表达沮丧、消沉的语气',
|
||||||
|
disgruntled: '表达不满、不悦的语气',
|
||||||
|
embarrassed: '表达尴尬、难为情的语气',
|
||||||
|
fearful: '表达害怕、恐惧的语气',
|
||||||
|
'narration-relaxed': '以轻松、自然的语气叙述',
|
||||||
|
newscast: '用于新闻播报,表现出庄重、严谨的语气',
|
||||||
|
sad: '表达悲伤、失落的语气',
|
||||||
|
serious: '表现出认真、严肃的语气'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-YunyangNeural',
|
code: 'zh-CN-YunyangNeural',
|
||||||
name: '云扬',
|
name: '云扬',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文 (普通话,简体)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
emotion: {
|
||||||
|
customerservice: '以亲切友好的语气为客户提供支持',
|
||||||
|
'narration-professional': '以专业、稳重的语气讲述',
|
||||||
|
'newscast-casual': '以轻松自然的语气播报新闻'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-YunyeNeural',
|
code: 'zh-CN-YunyeNeural',
|
||||||
name: '云野',
|
name: '云野',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
emotion: {
|
||||||
|
angry: '表达愤怒和不满的语气',
|
||||||
|
calm: '以冷静的态度说话,不带过多情绪',
|
||||||
|
cheerful: '表达快乐和积极的语气',
|
||||||
|
disgruntled: '表达不满和不满足的语气',
|
||||||
|
embarrassed: '表达不自在或难堪的语气',
|
||||||
|
fearful: '表达害怕和不安的语气',
|
||||||
|
sad: '表达悲伤和失落的语气',
|
||||||
|
serious: '以认真和严肃的态度说话'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaoshuangNeural',
|
code: 'zh-CN-XiaoshuangNeural',
|
||||||
name: '晓双',
|
name: '晓双',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
emotion: {
|
||||||
|
chat: '表达轻松随意的语气'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaoyouNeural',
|
code: 'zh-CN-XiaoyouNeural',
|
||||||
|
|
@ -141,56 +252,126 @@ const supportConfigurations = [
|
||||||
name: '晓墨',
|
name: '晓墨',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
emotion: {
|
||||||
|
affectionate: '温暖、亲切的语气',
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
calm: '沉着冷静的态度说话。语气、音调和韵律统一',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
depressed: '调低音调和音量来表达忧郁、沮丧的语气',
|
||||||
|
disgruntled: '轻蔑、抱怨的语气,表现不悦和蔑视',
|
||||||
|
embarrassed: '在说话者感到不舒适时表达不确定、犹豫的语气',
|
||||||
|
envious: '当你渴望别人拥有的东西时,表达一种钦佩的语气',
|
||||||
|
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
||||||
|
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
||||||
|
sad: '表达悲伤语气',
|
||||||
|
serious: '严肃、命令的语气'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaoxuanNeural',
|
code: 'zh-CN-XiaoxuanNeural',
|
||||||
name: '晓萱',
|
name: '晓萱',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
emotion: {
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
calm: '沉着冷静的态度说话。语气、音调和韵律统一',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
depressed: '调低音调和音量来表达忧郁、沮丧的语气',
|
||||||
|
disgruntled: '轻蔑、抱怨的语气,表现不悦和蔑视',
|
||||||
|
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
||||||
|
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
||||||
|
serious: '严肃、命令的语气'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaohanNeural',
|
code: 'zh-CN-XiaohanNeural',
|
||||||
name: '晓涵',
|
name: '晓涵',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
emotion: {
|
||||||
|
affectionate: '温暖、亲切的语气',
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
calm: '沉着冷静的态度说话。语气、音调和韵律统一',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
disgruntled: '轻蔑、抱怨的语气,表现不悦和蔑视',
|
||||||
|
embarrassed: '在说话者感到不舒适时表达不确定、犹豫的语气',
|
||||||
|
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
||||||
|
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
||||||
|
sad: '表达悲伤语气',
|
||||||
|
serious: '严肃、命令的语气'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaoruiNeural',
|
code: 'zh-CN-XiaoruiNeural',
|
||||||
name: '晓睿',
|
name: '晓睿',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
emotion: {
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
calm: '沉着冷静的态度说话。语气、音调和韵律统一',
|
||||||
|
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
||||||
|
sad: '表达悲伤语气'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaomengNeural',
|
code: 'zh-CN-XiaomengNeural',
|
||||||
name: '晓梦',
|
name: '晓梦',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
emotion: {
|
||||||
|
chat: '表达轻松随意的语气'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaoyiNeural',
|
code: 'zh-CN-XiaoyiNeural',
|
||||||
name: '晓伊',
|
name: '晓伊',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
emotion: {
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
affectionate: '温暖、亲切的语气',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
gentle: '温和、礼貌、愉快的语气,音调和音量较低',
|
||||||
|
sad: '表达悲伤语气',
|
||||||
|
serious: '严肃、命令的语气'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-XiaozhenNeural',
|
code: 'zh-CN-XiaozhenNeural',
|
||||||
name: '晓甄',
|
name: '晓甄',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '女'
|
gender: '女',
|
||||||
|
emotion: {
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
disgruntled: '轻蔑、抱怨的语气,表现不悦和蔑视',
|
||||||
|
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
||||||
|
sad: '表达悲伤语气',
|
||||||
|
serious: '严肃、命令的语气'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-YunfengNeural',
|
code: 'zh-CN-YunfengNeural',
|
||||||
name: '云枫',
|
name: '云枫',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
emotion: {
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
depressed: '调低音调和音量来表达忧郁、沮丧的语气',
|
||||||
|
disgruntled: '轻蔑、抱怨的语气,表现不悦和蔑视',
|
||||||
|
fearful: '恐惧、紧张的语气,说话人处于紧张和不安的状态',
|
||||||
|
sad: '表达悲伤语气',
|
||||||
|
serious: '严肃、命令的语气'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-YunhaoNeural',
|
code: 'zh-CN-YunhaoNeural',
|
||||||
|
|
@ -204,21 +385,44 @@ const supportConfigurations = [
|
||||||
name: '云健',
|
name: '云健',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文(普通话,简体)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
emotion: {
|
||||||
|
'narration-relaxed': '以轻松、自然的语气进行叙述',
|
||||||
|
'sports-commentary': '在解说体育比赛时,使用专业而自信的语气',
|
||||||
|
'sports-commentary-excited': '在解说激动人心的体育比赛时,使用兴奋和激动的语气'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-YunxiaNeural',
|
code: 'zh-CN-YunxiaNeural',
|
||||||
name: '云夏',
|
name: '云夏',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文 (普通话,简体)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
emotion: {
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
calm: '沉着冷静的态度说话。语气、音调和韵律统一',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
fearful: '表达害怕、紧张的语气',
|
||||||
|
sad: '表达悲伤和失落的语气'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-CN-YunzeNeural',
|
code: 'zh-CN-YunzeNeural',
|
||||||
name: '云泽',
|
name: '云泽',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(普通话,简体)',
|
languageDetail: '中文 (普通话,简体)',
|
||||||
gender: '男'
|
gender: '男',
|
||||||
|
emotion: {
|
||||||
|
angry: '用愤怒的语气表达强烈的不满和愤怒',
|
||||||
|
calm: '以冷静、沉着的语气说话,表现出稳重、深思熟虑的态度',
|
||||||
|
cheerful: '表达愉快和轻松的情绪',
|
||||||
|
depressed: '用沉闷、低落的语气表达消极、悲伤的情绪',
|
||||||
|
disgruntled: '表达不满和不高兴的情绪',
|
||||||
|
'documentary-narration': '用一种客观、中立的语气讲述事实和事件',
|
||||||
|
fearful: '表达害怕、不安的情绪',
|
||||||
|
sad: '用悲伤的语气表达悲伤和失落',
|
||||||
|
serious: '以严肃的语气和态度表现出对事情的重视和认真对待'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'zh-HK-HiuGaaiNeural',
|
code: 'zh-HK-HiuGaaiNeural',
|
||||||
|
|
@ -240,7 +444,480 @@ const supportConfigurations = [
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
languageDetail: '中文(粤语,繁体)',
|
languageDetail: '中文(粤语,繁体)',
|
||||||
gender: '男'
|
gender: '男'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-GB-AbbiNeural',
|
||||||
|
name: 'Abbi',
|
||||||
|
language: 'en-GB',
|
||||||
|
languageDetail: '英语(英国)',
|
||||||
|
gender: 'female'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-GB-AlfieNeural',
|
||||||
|
name: 'Alfie',
|
||||||
|
language: 'en-GB',
|
||||||
|
languageDetail: '英语(英国)',
|
||||||
|
gender: 'male'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-GB-BellaNeural',
|
||||||
|
name: 'Bella',
|
||||||
|
language: 'en-GB',
|
||||||
|
languageDetail: '英语(英国)',
|
||||||
|
gender: 'female'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-GB-ElliotNeural',
|
||||||
|
name: 'Elliot',
|
||||||
|
language: 'en-GB',
|
||||||
|
languageDetail: '英语(英国)',
|
||||||
|
gender: 'male'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-GB-EthanNeural',
|
||||||
|
name: 'Ethan',
|
||||||
|
language: 'en-GB',
|
||||||
|
languageDetail: '英语(英国)',
|
||||||
|
gender: 'male'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-GB-HollieNeural',
|
||||||
|
name: 'Hollie',
|
||||||
|
language: 'en-GB',
|
||||||
|
languageDetail: '英语(英国)',
|
||||||
|
gender: 'female'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-GB-LibbyNeural',
|
||||||
|
name: 'Libby',
|
||||||
|
language: 'en-GB',
|
||||||
|
languageDetail: '英语(英国)',
|
||||||
|
gender: 'female'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-GB-MaisieNeural',
|
||||||
|
name: 'Maisie',
|
||||||
|
language: 'en-GB',
|
||||||
|
languageDetail: '英语(英国)',
|
||||||
|
gender: 'female'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-GB-NoahNeural',
|
||||||
|
name: 'Noah',
|
||||||
|
language: 'en-GB',
|
||||||
|
languageDetail: '英语(英国)',
|
||||||
|
gender: 'male'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-GB-OliverNeural',
|
||||||
|
name: 'Oliver',
|
||||||
|
language: 'en-GB',
|
||||||
|
languageDetail: '英语(英国)',
|
||||||
|
gender: 'male'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-GB-OliviaNeural',
|
||||||
|
name: 'Olivia',
|
||||||
|
language: 'en-GB',
|
||||||
|
languageDetail: '英语(英国)',
|
||||||
|
gender: 'female'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-GB-RyanNeural',
|
||||||
|
name: 'Ryan',
|
||||||
|
language: 'en-GB',
|
||||||
|
languageDetail: '英语(英国)',
|
||||||
|
gender: 'male',
|
||||||
|
emotion: {
|
||||||
|
chat: '表达轻松随意的语气',
|
||||||
|
cheerful: '表达积极愉快的语气'
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-GB-SoniaNeural',
|
||||||
|
name: 'Sonia',
|
||||||
|
language: 'en-GB',
|
||||||
|
languageDetail: '英语(英国)',
|
||||||
|
gender: 'female',
|
||||||
|
emotion: {
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
sad: '表达悲伤语气'
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-GB-ThomasNeural',
|
||||||
|
name: 'Thomas',
|
||||||
|
language: 'en-GB',
|
||||||
|
languageDetail: '英语(英国)',
|
||||||
|
gender: 'male'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'ja-JP-AoiNeural',
|
||||||
|
name: '葵',
|
||||||
|
language: 'ja-JP',
|
||||||
|
languageDetail: '日语(日本)',
|
||||||
|
gender: '女'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'ja-JP-DaichiNeural',
|
||||||
|
name: '大地',
|
||||||
|
language: 'ja-JP',
|
||||||
|
languageDetail: '日语(日本)',
|
||||||
|
gender: '男'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'ja-JP-KeitaNeural',
|
||||||
|
name: '慶太',
|
||||||
|
language: 'ja-JP',
|
||||||
|
languageDetail: '日语(日本)',
|
||||||
|
gender: '男'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'ja-JP-MayuNeural',
|
||||||
|
name: '真由',
|
||||||
|
language: 'ja-JP',
|
||||||
|
languageDetail: '日语(日本)',
|
||||||
|
gender: '女'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'ja-JP-NanamiNeural',
|
||||||
|
name: '七海',
|
||||||
|
language: 'ja-JP',
|
||||||
|
languageDetail: '日语(日本)',
|
||||||
|
gender: '女',
|
||||||
|
emotion: {
|
||||||
|
chat: '表达轻松随意的语气',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
customerservice: '以友好热情的语气为客户提供支持'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'ja-JP-NaokiNeural',
|
||||||
|
name: '直樹',
|
||||||
|
language: 'ja-JP',
|
||||||
|
languageDetail: '日语(日本)',
|
||||||
|
gender: '男'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'ja-JP-ShioriNeural',
|
||||||
|
name: '栞',
|
||||||
|
language: 'ja-JP',
|
||||||
|
languageDetail: '日语(日本)',
|
||||||
|
gender: '女'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-AIGenerate1Neural1',
|
||||||
|
name: 'AI Generate 1',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: 'English (United States)',
|
||||||
|
gender: '男'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-AIGenerate2Neural1',
|
||||||
|
name: 'AI Generate 2',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: 'English (United States)',
|
||||||
|
gender: '女'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-AmberNeural',
|
||||||
|
name: 'Amber',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: 'English (United States)',
|
||||||
|
gender: '女'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-AnaNeural',
|
||||||
|
name: 'Ana',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: 'English (United States)',
|
||||||
|
gender: '女性、儿童'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-AriaNeural',
|
||||||
|
name: 'Aria',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: 'English (United States)',
|
||||||
|
gender: '女',
|
||||||
|
emotion: {
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
excited: '乐观、充满希望的语气,发生了美好的事情',
|
||||||
|
friendly: '愉快、怡人、温暖、真诚、关切的语气',
|
||||||
|
hopeful: '温暖且渴望的语气。像是会有好事发生',
|
||||||
|
sad: '表达悲伤语气',
|
||||||
|
shouting: '就像从遥远的地方说话或在外面说话',
|
||||||
|
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||||
|
unfriendly: '表达一种冷淡无情的语气',
|
||||||
|
whispering: '说话非常柔和,发出的声音小且温柔',
|
||||||
|
chat: '表达轻松随意的语气',
|
||||||
|
customerservice: '以友好热情的语气为客户提供支持',
|
||||||
|
empathetic: '表达关心和理解',
|
||||||
|
'narration-professional': '以专业、客观的语气朗读内容',
|
||||||
|
'newscast-casual': '以通用、随意的语气发布一般新闻',
|
||||||
|
'newscast-formal': '以正式、自信和权威的语气发布新闻'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-AshleyNeural',
|
||||||
|
name: 'Ashley',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: 'English (United States)',
|
||||||
|
gender: '女'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-BrandonNeural',
|
||||||
|
name: 'Brandon',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: 'English (United States)',
|
||||||
|
gender: '男'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-ChristopherNeural',
|
||||||
|
name: 'Christopher',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: 'English (United States)',
|
||||||
|
gender: '男'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-CoraNeural',
|
||||||
|
name: 'Cora',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: 'English (United States)',
|
||||||
|
gender: '女'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-DavisNeural',
|
||||||
|
name: 'Davis',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: 'English (United States)',
|
||||||
|
gender: '男',
|
||||||
|
emotion: {
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
excited: '乐观、充满希望的语气,发生了美好的事情',
|
||||||
|
friendly: '愉快、怡人、温暖、真诚、关切的语气',
|
||||||
|
hopeful: '温暖且渴望的语气。像是会有好事发生',
|
||||||
|
sad: '表达悲伤语气',
|
||||||
|
shouting: '就像从遥远的地方说话或在外面说话',
|
||||||
|
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||||
|
unfriendly: '表达一种冷淡无情的语气',
|
||||||
|
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-ElizabethNeural',
|
||||||
|
name: 'Elizabeth',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: 'English (United States)',
|
||||||
|
gender: '女'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-EricNeural',
|
||||||
|
name: 'Eric',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: 'English (United States)',
|
||||||
|
gender: '男'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-GuyNeural',
|
||||||
|
name: 'Guy',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: 'English (United States)',
|
||||||
|
gender: '男',
|
||||||
|
emotion: {
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
excited: '乐观、充满希望的语气,发生了美好的事情',
|
||||||
|
friendly: '愉快、怡人、温暖、真诚、关切的语气',
|
||||||
|
hopeful: '温暖且渴望的语气。像是会有好事发生',
|
||||||
|
sad: '表达悲伤语气',
|
||||||
|
shouting: '就像从遥远的地方说话或在外面说话',
|
||||||
|
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||||
|
unfriendly: '表达一种冷淡无情的语气',
|
||||||
|
whispering: '说话非常柔和,发出的声音小且温柔',
|
||||||
|
newscast: '以正式专业的语气叙述新闻'
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-JacobNeural',
|
||||||
|
name: 'Jacob',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: 'English (United States)',
|
||||||
|
gender: '男'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-JaneNeural',
|
||||||
|
name: 'Jane',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: 'English (United States)',
|
||||||
|
gender: '女',
|
||||||
|
emotion: {
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
excited: '乐观、充满希望的语气,发生了美好的事情',
|
||||||
|
friendly: '愉快、怡人、温暖、真诚、关切的语气',
|
||||||
|
hopeful: '温暖且渴望的语气。像是会有好事发生',
|
||||||
|
sad: '表达悲伤语气',
|
||||||
|
shouting: '就像从遥远的地方说话或在外面说话',
|
||||||
|
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||||
|
unfriendly: '表达一种冷淡无情的语气',
|
||||||
|
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-JasonNeural',
|
||||||
|
name: 'Jason',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: '英语(美国)',
|
||||||
|
gender: 'male',
|
||||||
|
emotion: {
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
excited: '乐观、充满希望的语气,发生了美好的事情',
|
||||||
|
friendly: '愉快、怡人、温暖、真诚、关切的语气',
|
||||||
|
hopeful: '温暖且渴望的语气。像是会有好事发生',
|
||||||
|
sad: '表达悲伤语气',
|
||||||
|
shouting: '就像从遥远的地方说话或在外面说话',
|
||||||
|
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||||
|
unfriendly: '表达一种冷淡无情的语气',
|
||||||
|
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-JennyMultilingualNeural3',
|
||||||
|
name: 'Jenny',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: '英语(美国)',
|
||||||
|
gender: 'female'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-JennyNeural',
|
||||||
|
name: 'Jenny',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: '英语(美国)',
|
||||||
|
gender: 'female',
|
||||||
|
emotion: {
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
excited: '乐观、充满希望的语气,发生了美好的事情',
|
||||||
|
friendly: '愉快、怡人、温暖、真诚、关切的语气',
|
||||||
|
hopeful: '温暖且渴望的语气。像是会有好事发生',
|
||||||
|
sad: '表达悲伤语气',
|
||||||
|
shouting: '就像从遥远的地方说话或在外面说话',
|
||||||
|
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||||
|
unfriendly: '表达一种冷淡无情的语气',
|
||||||
|
whispering: '说话非常柔和,发出的声音小且温柔',
|
||||||
|
assistant: '数字助理用的是热情而轻松的语气',
|
||||||
|
chat: '表达轻松随意的语气',
|
||||||
|
customerservice: '以友好热情的语气为客户提供支持',
|
||||||
|
newscast: '以正式专业的语气叙述新闻'
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-MichelleNeural',
|
||||||
|
name: 'Michelle',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: '英语(美国)',
|
||||||
|
gender: 'female'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-MonicaNeural',
|
||||||
|
name: 'Monica',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: '英语(美国)',
|
||||||
|
gender: 'female'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-NancyNeural',
|
||||||
|
name: 'Nancy',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: '英语(美国)',
|
||||||
|
gender: 'female',
|
||||||
|
emotion: {
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
excited: '乐观、充满希望的语气,发生了美好的事情',
|
||||||
|
friendly: '愉快、怡人、温暖、真诚、关切的语气',
|
||||||
|
hopeful: '温暖且渴望的语气。像是会有好事发生',
|
||||||
|
sad: '表达悲伤语气',
|
||||||
|
shouting: '就像从遥远的地方说话或在外面说话',
|
||||||
|
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||||
|
unfriendly: '表达一种冷淡无情的语气',
|
||||||
|
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-RogerNeural',
|
||||||
|
name: 'Roger',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: '英语(美国)',
|
||||||
|
gender: 'male'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-SaraNeural',
|
||||||
|
name: 'Sara',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: '英语(美国)',
|
||||||
|
gender: 'female',
|
||||||
|
emotion: {
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
excited: '乐观、充满希望的语气,发生了美好的事情',
|
||||||
|
friendly: '愉快、怡人、温暖、真诚、关切的语气',
|
||||||
|
hopeful: '温暖且渴望的语气。像是会有好事发生',
|
||||||
|
sad: '表达悲伤语气',
|
||||||
|
shouting: '就像从遥远的地方说话或在外面说话',
|
||||||
|
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||||
|
unfriendly: '表达一种冷淡无情的语气',
|
||||||
|
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-SteffanNeural',
|
||||||
|
name: 'Steffan',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: '英语(美国)',
|
||||||
|
gender: 'male'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-US-TonyNeural',
|
||||||
|
name: 'Tony',
|
||||||
|
language: 'en-US',
|
||||||
|
languageDetail: '英语(美国)',
|
||||||
|
gender: 'male',
|
||||||
|
emotion: {
|
||||||
|
angry: '生气和厌恶的语气',
|
||||||
|
cheerful: '表达积极愉快的语气',
|
||||||
|
excited: '乐观、充满希望的语气,发生了美好的事情',
|
||||||
|
friendly: '愉快、怡人、温暖、真诚、关切的语气',
|
||||||
|
hopeful: '温暖且渴望的语气。像是会有好事发生',
|
||||||
|
sad: '表达悲伤语气',
|
||||||
|
shouting: '就像从遥远的地方说话或在外面说话',
|
||||||
|
terrified: '非常害怕的语气,语速快且声音颤抖。不稳定的疯狂状态',
|
||||||
|
unfriendly: '表达一种冷淡无情的语气',
|
||||||
|
whispering: '说话非常柔和,发出的声音小且温柔'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-IN-NeerjaNeural',
|
||||||
|
name: 'Neerja',
|
||||||
|
language: 'en',
|
||||||
|
languageDetail: '英语(印度)',
|
||||||
|
gender: 'female'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'en-IN-PrabhatNeural',
|
||||||
|
name: 'Prabhat',
|
||||||
|
language: 'en',
|
||||||
|
languageDetail: '英语(印度)',
|
||||||
|
gender: 'male'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
export default { generateAudio, supportConfigurations }
|
export default { generateAudio, generateSsml, getEmotionPrompt, supportConfigurations }
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ async function generateAudio (text, options = {}) {
|
||||||
return Buffer.from(synthesisResponseData)
|
return Buffer.from(synthesisResponseData)
|
||||||
}
|
}
|
||||||
|
|
||||||
const supportConfigurations = [
|
export const supportConfigurations = [
|
||||||
{
|
{
|
||||||
supported_features: { permitted_synthesis_morphing: 'SELF_ONLY' },
|
supported_features: { permitted_synthesis_morphing: 'SELF_ONLY' },
|
||||||
name: '四国めたん',
|
name: '四国めたん',
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ import stream from 'stream'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import child_process from 'child_process'
|
import child_process from 'child_process'
|
||||||
import { Config } from './config.js'
|
import { Config } from './config.js'
|
||||||
import {mkdirs} from "./common.js";
|
import path from 'path'
|
||||||
|
import { mkdirs } from './common.js'
|
||||||
let module
|
let module
|
||||||
try {
|
try {
|
||||||
module = await import('oicq')
|
module = await import('oicq')
|
||||||
|
|
@ -248,9 +249,14 @@ async function getPttBuffer (file, ffmpeg = 'ffmpeg') {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function audioTrans (file, ffmpeg = 'ffmpeg') {
|
async function audioTrans (file, ffmpeg = 'ffmpeg') {
|
||||||
|
const tmpfile = path.join(TMP_DIR, uuid())
|
||||||
|
const cmd = IS_WIN
|
||||||
|
? `${ffmpeg} -i "${file}" -f s16le -ac 1 -ar 24000 "${tmpfile}"`
|
||||||
|
: `exec ${ffmpeg} -i "${file}" -f s16le -ac 1 -ar 24000 "${tmpfile}"`
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const tmpfile = TMP_DIR + '/' + (0, uuid)();
|
// 隐藏windows下调用ffmpeg的cmd弹窗
|
||||||
(0, child_process.exec)(`${ffmpeg} -i "${file}" -f s16le -ac 1 -ar 24000 "${tmpfile}"`, async (error, stdout, stderr) => {
|
const options = IS_WIN ? { windowsHide: true, stdio: 'ignore' } : {}
|
||||||
|
child_process.exec(cmd, options, async (error, stdout, stderr) => {
|
||||||
try {
|
try {
|
||||||
resolve(pcm2slk(fs.readFileSync(tmpfile)))
|
resolve(pcm2slk(fs.readFileSync(tmpfile)))
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue