mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 13:27:08 +00:00
Feat: 添加语音云转码功能,系统优化和故障修复 (#378)
* 修复后台API反代地址未能正确显示的问题 * 更新渲染页面配置 * 添加个人聊天模式配置 * 将用户数据获取改到common中 * 修复错误的渲染页面参数 * 修复bug * 添加Live2D * 修复渲染页面错误 * 修复渲染传入值 * 更新渲染 * 修复图表渲染bug * 调整live2d模型大小 * 修复live2d无法关闭问题 * 修复错误的传值 * 修复ai命名 * 更新渲染 * 添加用户独立设定 * 更新渲染配置适配个人设置 * 修复合并导致的渲染文件异常删除 * 修复用户数据缺失问题 * 修复旧版本数据缺失问题 * 修复bing参数不存在问题,兼容miao的截图 * 修复受限token重试时不被排除的问题 * 修复个人模式下结束对话的模式错误 * 更新渲染页面,将预览版转为正式版 * 修复传统渲染无法调用截图功能的问题 * 文字模式也进行一次缓存 * 更新README * Update README.md * 更新渲染 * 更新渲染页面 * 添加版本信息 * 遗漏参数 * 丢失引用 * 补充路由 * 添加云转码功能 * 判断node-silk是否正常合成 * 云转码提示 * 修复图片渲染出错
This commit is contained in:
parent
26b4534cfb
commit
55f060dc5f
17 changed files with 169 additions and 99 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -2,6 +2,8 @@ config/*
|
|||
!config/config.example.js
|
||||
!config/config.example.json
|
||||
!config/config.md
|
||||
server/static/live2dw/*
|
||||
!server/static/live2dw/live2d-widget-model-default
|
||||
prompts/*
|
||||
!prompts/.gitkeep
|
||||
node_modules/
|
||||
|
|
|
|||
23
README.md
23
README.md
|
|
@ -75,14 +75,24 @@ pnpm i
|
|||
> 4.2更新:必应站起来了,必应天下第一。建议都用必应,别用API/API3了。浏览器模式除非极其特殊的需求否则强烈建议不使用,已经不维护了。
|
||||
|
||||
3. 修改配置
|
||||
**本插件配置项比较多,强烈建议使用[锅巴面板](https://github.com/guoba-yunzai/Guoba-Plugin)修改**
|
||||
**本插件配置项比较多,强烈建议使用后台面板或[锅巴面板](https://github.com/guoba-yunzai/Guoba-Plugin)修改**
|
||||
|
||||
复制`plugins/chatgpt-plugin/config/config.example.json`并将其改名为`config.json`\
|
||||
编辑`plugins/chatgpt-plugin/config/config.json`文件,修改必要配置项 \
|
||||
**请勿直接修改config.example.json**
|
||||
|
||||
4. 重启Yunzai-Bot
|
||||
如通过锅巴面板升级可以热加载,无需重启。
|
||||
4. 后台面板使用
|
||||
初次使用请先私聊机器人 `#设置管理密码` 进登录密码设置
|
||||
私聊 `#chatgpt系统管理` 后机器人会回复系统管理页面网址,在此网址输入机器人QQ号和刚刚设置的管理密码点击登录即可进入后台管理系统
|
||||
如果忘记密码,再次私聊输入 `#设置管理密码` 后可重新设置密码
|
||||
|
||||
用户同样可私聊机器人 `#设置用户密码` 进行账号注册和密码设置
|
||||
用户设置密码后,所有聊天信息将记录在用户缓存数据下,同时用户可通过私聊机器人 `#chatgpt用户配置` 登录后台用户配置面板,查看自己的聊天数据和自定义机器人对自己的回复参数
|
||||
|
||||
如果后台面板访问出现 time out 请检查机器人启动时是否有报错,服务器端口是否开放,可尝试ping一下服务器ip看能否直接ping通。
|
||||
|
||||
5. 重启Yunzai-Bot
|
||||
如通过后台面板或锅巴面板升级可以热加载,无需重启。
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -212,7 +222,7 @@ pnpm i
|
|||
|
||||
## TODO
|
||||
|
||||
* 预设分群组和个人
|
||||
* 预设分群组和个人(已初步实现个人,在用户面板设置)
|
||||
|
||||
## 其他
|
||||
|
||||
|
|
@ -248,6 +258,10 @@ pnpm i
|
|||
|
||||
能。克隆下来安装依赖直接运行即可。
|
||||
|
||||
6. 系统后台无法进入怎么办?
|
||||
|
||||
多数情况下是由于服务器未开放3321端口导致,请根据服务器系统和服务器供应商配置,开放3321端口后再试。
|
||||
|
||||
## 交流群
|
||||
|
||||
* QQ 559567232 [问题交流]
|
||||
|
|
@ -267,6 +281,7 @@ pnpm i
|
|||
|
||||
图片以及Bing模式支持 @HalcyonAlcedo
|
||||
* https://github.com/HalcyonAlcedo/ChatGPT-Plugin-PageCache
|
||||
* https://github.com/HalcyonAlcedo/cache-web
|
||||
|
||||
语音vits模型来自于
|
||||
* https://huggingface.co/spaces/sayashi/vits-uma-genshin-honkai
|
||||
|
|
|
|||
40
apps/chat.js
40
apps/chat.js
|
|
@ -44,13 +44,6 @@ if (Config.proxy) {
|
|||
console.warn('未安装https-proxy-agent,请在插件目录下执行pnpm add https-proxy-agent')
|
||||
}
|
||||
}
|
||||
let useSilk = false
|
||||
try {
|
||||
await import('node-silk')
|
||||
useSilk = true
|
||||
} catch (e) {
|
||||
useSilk = false
|
||||
}
|
||||
/**
|
||||
* 每个对话保留的时长。单个对话内ai是保留上下文的。超时后销毁对话,再次对话创建新的对话。
|
||||
* 单位:秒
|
||||
|
|
@ -73,6 +66,8 @@ const newFetch = (url, options = {}) => {
|
|||
|
||||
return fetch(url, mergedOptions)
|
||||
}
|
||||
// 后台地址
|
||||
const viewHost = Config.viewHost ? `${Config.viewHost}/` : `http://127.0.0.1:${Config.serverPort || 3321}/`
|
||||
export class chatgpt extends plugin {
|
||||
constructor () {
|
||||
let toggleMode = Config.toggleMode
|
||||
|
|
@ -893,6 +888,8 @@ export class chatgpt extends plugin {
|
|||
if (quote.imageLink) imgUrls.push(quote.imageLink)
|
||||
}
|
||||
if (useTTS) {
|
||||
// 缓存数据
|
||||
this.cacheContent(e, use, response, prompt, quotemessage, mood, chatMessage.suggestedResponses, imgUrls)
|
||||
// 处理tts输入文本
|
||||
let ttsResponse, ttsRegex
|
||||
const regex = /^\/(.*)\/([gimuy]*)$/
|
||||
|
|
@ -921,15 +918,16 @@ export class chatgpt extends plugin {
|
|||
if (Config.ttsSpace && ttsResponse.length <= Config.ttsAutoFallbackThreshold) {
|
||||
try {
|
||||
let wav = await generateAudio(ttsResponse, speaker, '中日混合(中文用[ZH][ZH]包裹起来,日文用[JA][JA]包裹起来)')
|
||||
if (useSilk) {
|
||||
try {
|
||||
let sendable = await uploadRecord(wav)
|
||||
if (sendable) {
|
||||
await e.reply(sendable)
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
} else {
|
||||
//如果合成失败,尝试使用ffmpeg合成
|
||||
await e.reply(segment.record(wav))
|
||||
}
|
||||
} else {
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
await e.reply(segment.record(wav))
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
@ -951,6 +949,7 @@ export class chatgpt extends plugin {
|
|||
this.reply(`建议的回复:\n${chatMessage.suggestedResponses}`)
|
||||
}
|
||||
} else {
|
||||
this.cacheContent(e, use, response, prompt, quotemessage, mood, chatMessage.suggestedResponses, imgUrls)
|
||||
await this.reply(await convertFaces(response, Config.enableRobotAt, e), e.isGroup)
|
||||
if (quotemessage.length > 0) {
|
||||
this.reply(await makeForwardMsg(this.e, quotemessage.map(msg => `${msg.text} - ${msg.url}`)))
|
||||
|
|
@ -1071,10 +1070,8 @@ export class chatgpt extends plugin {
|
|||
return true
|
||||
}
|
||||
|
||||
async renderImage (e, use, content, prompt, quote = [], mood = '', suggest = '', imgUrls = []) {
|
||||
let cacheData = { file: '', cacheUrl: Config.cacheUrl }
|
||||
const template = use !== 'bing' ? 'content/ChatGPT/index' : 'content/Bing/index'
|
||||
if (!Config.oldview) {
|
||||
async cacheContent (e, use, content, prompt, quote = [], mood = '', suggest = '', imgUrls = []) {
|
||||
let cacheData = { file: '', cacheUrl: Config.cacheUrl, status: '' }
|
||||
cacheData.file = randomString()
|
||||
const cacheresOption = {
|
||||
method: 'POST',
|
||||
|
|
@ -1102,12 +1099,21 @@ export class chatgpt extends plugin {
|
|||
qq: e.sender.user_id
|
||||
})
|
||||
}
|
||||
const viewHost = Config.viewHost ? `${Config.viewHost}/` : `http://127.0.0.1:${Config.serverPort || 3321}/`
|
||||
const cacheres = await fetch(viewHost + 'cache', cacheresOption)
|
||||
if (cacheres.ok) {
|
||||
cacheData = Object.assign({}, cacheData, await cacheres.json())
|
||||
} else {
|
||||
cacheData.error = '渲染服务器出错!'
|
||||
}
|
||||
if (cacheData.error || cacheres.status != 200) { await this.reply(`出现错误:${cacheData.error || 'server error ' + cacheres.status}`, true) } else { await e.reply(await renderUrl(e, viewHost + `page/${cacheData.file}?qr=${Config.showQRCode ? 'true' : 'false'}`, { retType: Config.quoteReply ? 'base64' : '', Viewport: { width: Config.chatViewWidth, height: parseInt(Config.chatViewWidth * 0.56) } }), e.isGroup && Config.quoteReply) }
|
||||
cacheData.status = cacheres.status
|
||||
return cacheData
|
||||
}
|
||||
|
||||
async renderImage (e, use, content, prompt, quote = [], mood = '', suggest = '', imgUrls = []) {
|
||||
let cacheData = await this.cacheContent(e, use, content, prompt, quote, mood, suggest, imgUrls)
|
||||
const template = use !== 'bing' ? 'content/ChatGPT/index' : 'content/Bing/index'
|
||||
if (!Config.oldview) {
|
||||
if (cacheData.error || cacheData.status != 200) { await this.reply(`出现错误:${cacheData.error || 'server error ' + cacheData.status}`, true) } else { await e.reply(await renderUrl(e, viewHost + `page/${cacheData.file}?qr=${Config.showQRCode ? 'true' : 'false'}`, { retType: Config.quoteReply ? 'base64' : '', Viewport: { width: Config.chatViewWidth, height: parseInt(Config.chatViewWidth * 0.56) } }), e.isGroup && Config.quoteReply) }
|
||||
} else {
|
||||
if (Config.cacheEntry) cacheData.file = randomString()
|
||||
const cacheresOption = {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import plugin from '../../../lib/plugins/plugin.js'
|
||||
import { Config } from '../utils/config.js'
|
||||
import { exec } from 'child_process'
|
||||
import { checkPnpm, formatDuration, parseDuration, getPublicIP } from '../utils/common.js'
|
||||
import { checkPnpm, formatDuration, parseDuration, getPublicIP, renderUrl } from '../utils/common.js'
|
||||
import SydneyAIClient from '../utils/SydneyAIClient.js'
|
||||
import { convertSpeaker, speakers } from '../utils/tts.js'
|
||||
import md5 from 'md5'
|
||||
|
|
@ -110,6 +110,10 @@ export class ChatgptManagement extends plugin {
|
|||
reg: '^#chatgpt(强制)?更新$',
|
||||
fnc: 'updateChatGPTPlugin'
|
||||
},
|
||||
{
|
||||
reg: '^#chatgpt版本(信息)',
|
||||
fnc: 'versionChatGPTPlugin'
|
||||
},
|
||||
{
|
||||
reg: '^#chatgpt(本群)?(群\\d+)?闭嘴',
|
||||
fnc: 'shutUp',
|
||||
|
|
@ -828,6 +832,10 @@ export class ChatgptManagement extends plugin {
|
|||
return true
|
||||
}
|
||||
|
||||
async versionChatGPTPlugin (e) {
|
||||
await renderUrl(e, `http://127.0.0.1:${Config.serverPort || 3321}/version`, { Viewport: { width: 800, height: 600 } })
|
||||
}
|
||||
|
||||
async modeHelp () {
|
||||
let mode = await redis.get('CHATGPT:USE')
|
||||
const modeMap = {
|
||||
|
|
|
|||
|
|
@ -519,6 +519,12 @@ export function supportGuoba () {
|
|||
bottomHelpMessage: '没有就空着',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'cloudTranscode',
|
||||
label: '云转码API地址',
|
||||
bottomHelpMessage: '目前只支持node-silk语音转码,如果本地无法安装node-silk可填写http://alcedogroup.com:3031',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'noiseScale',
|
||||
label: 'noiseScale',
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@ export async function createServer() {
|
|||
const stream = fs.createReadStream('plugins/chatgpt-plugin/server/static/index.html')
|
||||
reply.type('text/html').send(stream)
|
||||
})
|
||||
await server.get('/version', (request, reply) => {
|
||||
const stream = fs.createReadStream('plugins/chatgpt-plugin/server/static/index.html')
|
||||
reply.type('text/html').send(stream)
|
||||
})
|
||||
await server.get('/auth/*', (request, reply) => {
|
||||
const stream = fs.createReadStream('plugins/chatgpt-plugin/server/static/index.html')
|
||||
reply.type('text/html').send(stream)
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@
|
|||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
-->
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="shortcut icon" href="/favicon.ico"/><link rel="apple-touch-icon" sizes="76x76" href="/apple-icon.png"/><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css"/><script src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.6.3/mermaid.min.js"></script><title>ChatGPT-Plugin</title><script defer="defer" type="module" src="/js/chunk-vendors.59d1ab94.js"></script><script defer="defer" type="module" src="/js/app.e532d95e.js"></script><link href="/css/chunk-vendors.0ede84b4.css" rel="stylesheet"><link href="/css/app.db850df4.css" rel="stylesheet"><script defer="defer" src="/js/chunk-vendors-legacy.40504512.js" nomodule></script><script defer="defer" src="/js/app-legacy.1aff05a1.js" nomodule></script></head><body class="text-blueGray-700 antialiased"><noscript><strong>We're sorry but vue-notus doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="shortcut icon" href="/favicon.ico"/><link rel="apple-touch-icon" sizes="76x76" href="/apple-icon.png"/><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css"/><script src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.6.3/mermaid.min.js"></script><title>ChatGPT-Plugin</title><script defer="defer" type="module" src="/js/chunk-vendors.59d1ab94.js"></script><script defer="defer" type="module" src="/js/app.e96e0e6a.js"></script><link href="/css/chunk-vendors.0ede84b4.css" rel="stylesheet"><link href="/css/app.db850df4.css" rel="stylesheet"><script defer="defer" src="/js/chunk-vendors-legacy.40504512.js" nomodule></script><script defer="defer" src="/js/app-legacy.5df11576.js" nomodule></script></head><body class="text-blueGray-700 antialiased"><noscript><strong>We're sorry but vue-notus doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
21
server/static/js/app-legacy.5df11576.js
Normal file
21
server/static/js/app-legacy.5df11576.js
Normal file
File diff suppressed because one or more lines are too long
1
server/static/js/app-legacy.5df11576.js.map
Normal file
1
server/static/js/app-legacy.5df11576.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
21
server/static/js/app.e96e0e6a.js
Normal file
21
server/static/js/app.e96e0e6a.js
Normal file
File diff suppressed because one or more lines are too long
1
server/static/js/app.e96e0e6a.js.map
Normal file
1
server/static/js/app.e96e0e6a.js.map
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -102,6 +102,7 @@ const defaultConfig = {
|
|||
slackClaudeEnableGlobalPreset: true,
|
||||
slackClaudeGlobalPreset: '',
|
||||
slackClaudeSpecifiedChannel: '',
|
||||
cloudTranscode: '',
|
||||
version: 'v2.5.6'
|
||||
}
|
||||
const _path = process.cwd()
|
||||
|
|
|
|||
|
|
@ -22,12 +22,40 @@ let pcm2slk
|
|||
try {
|
||||
pcm2slk = (await import('node-silk')).pcm2slk
|
||||
} catch (e) {
|
||||
if (Config.cloudTranscode) {
|
||||
logger.warn('未安装node-silk,将尝试使用云转码服务进行合成')
|
||||
} else {
|
||||
Config.debug && logger.error(e)
|
||||
logger.warn('未安装node-silk,如ffmpeg不支持amr编码请安装node-silk以支持语音模式')
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadRecord (recordUrl) {
|
||||
const result = await getPttBuffer(recordUrl, Bot.config.ffmpeg_path)
|
||||
let result
|
||||
if (pcm2slk) {
|
||||
result = await getPttBuffer(recordUrl, Bot.config.ffmpeg_path)
|
||||
} else if (Config.cloudTranscode) {
|
||||
const resultOption = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({recordUrl: recordUrl})
|
||||
}
|
||||
const resultres = await fetch(`${Config.cloudTranscode}/audio`, resultOption)
|
||||
if (!resultres.ok) {
|
||||
return false
|
||||
}
|
||||
result = await resultres.json()
|
||||
if (result.error) {
|
||||
logger.error('云转码API报错:' + result.error)
|
||||
return false
|
||||
}
|
||||
result.buffer = Buffer.from(result.buffer.data)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!result.buffer) {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue