mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-26 09:03:54 +08:00
@@ -1,6 +1,5 @@
|
|||||||
[](https://github.com/xinnan-tech/xiaozhi-esp32-server)
|
[](https://github.com/xinnan-tech/xiaozhi-esp32-server)
|
||||||
|
|
||||||
|
|
||||||
<h1 align="center">小智后端服务xiaozhi-esp32-server</h1>
|
<h1 align="center">小智后端服务xiaozhi-esp32-server</h1>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@@ -12,11 +11,10 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="./README.md">English</a>
|
<a href="./README.md">English</a>
|
||||||
· 简体中文
|
|
||||||
· <a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">更新日志</a>
|
|
||||||
· <a href="./README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3">部署文档</a>
|
|
||||||
· <a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/issues">反馈问题</a>
|
|
||||||
· <a href="./docs/FAQ.md">常见问题</a>
|
· <a href="./docs/FAQ.md">常见问题</a>
|
||||||
|
· <a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/issues">反馈问题</a>
|
||||||
|
· <a href="./README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3">部署文档</a>
|
||||||
|
· <a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">更新日志</a>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">
|
||||||
@@ -132,17 +130,7 @@
|
|||||||
1、本项目为开源软件,本软件与对接的任何第三方API服务商(包括但不限于语音识别、大模型、语音合成等平台)均不存在商业合作关系,不为其服务质量及资金安全提供任何形式的担保。
|
1、本项目为开源软件,本软件与对接的任何第三方API服务商(包括但不限于语音识别、大模型、语音合成等平台)均不存在商业合作关系,不为其服务质量及资金安全提供任何形式的担保。
|
||||||
建议使用者优先选择持有相关业务牌照的服务商,并仔细阅读其服务协议及隐私政策。本软件不托管任何账户密钥、不参与资金流转、不承担充值资金损失风险。
|
建议使用者优先选择持有相关业务牌照的服务商,并仔细阅读其服务协议及隐私政策。本软件不托管任何账户密钥、不参与资金流转、不承担充值资金损失风险。
|
||||||
|
|
||||||
2、本项目成立时间较短,还未通过网络安全测评,请勿在生产环境中使用。 如果您在公网环境中部署学习本项目,请务必在配置文件
|
2、本项目功能未完善,且未通过网络安全测评,请勿在生产环境中使用。 如果您在公网环境中部署学习本项目,请务必做好必要的防护。
|
||||||
`config.yaml` 中开启防护:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
server:
|
|
||||||
auth:
|
|
||||||
# 开启防护
|
|
||||||
enabled: true
|
|
||||||
```
|
|
||||||
|
|
||||||
开启防护后,您需要根据实际情况校验机器的 token 或 mac 地址,详细请参见配置说明。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -168,11 +156,6 @@ server:
|
|||||||
OTA接口地址: https://2662r3426b.vicp.fun/xiaozhi/ota/
|
OTA接口地址: https://2662r3426b.vicp.fun/xiaozhi/ota/
|
||||||
Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/
|
Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/
|
||||||
```
|
```
|
||||||
---
|
|
||||||
|
|
||||||
## 常见问题 ❓
|
|
||||||
|
|
||||||
如遇到问题或产品建议反馈[点这里](docs/FAQ.md)。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
## 功能清单 ✨
|
## 功能清单 ✨
|
||||||
|
|||||||
+19
-26
@@ -81,7 +81,7 @@ xiaozhi-server
|
|||||||
|
|
||||||
下载完后,回到本教程继续往下。
|
下载完后,回到本教程继续往下。
|
||||||
|
|
||||||
##### 1.2.3.2 下载 config.yaml
|
##### 1.2.3.2 创建 config.yaml
|
||||||
|
|
||||||
用浏览器打开[这个链接](../main/xiaozhi-server/config.yaml)。
|
用浏览器打开[这个链接](../main/xiaozhi-server/config.yaml)。
|
||||||
|
|
||||||
@@ -224,43 +224,36 @@ python app.py
|
|||||||
## 配置项目
|
## 配置项目
|
||||||
|
|
||||||
如果你的`xiaozhi-server`目录没有`data`,你需要创建`data`目录。
|
如果你的`xiaozhi-server`目录没有`data`,你需要创建`data`目录。
|
||||||
如果你的`data`下面没有`.config.yaml`文件,你可以把`xiaozhi-server`目录下的`config.yaml`文件复制到`data`,并重命名为`.config.yaml`
|
如果你的`data`下面没有`.config.yaml`文件,有两个方式,任选一种:
|
||||||
|
|
||||||
修改`xiaozhi-server`下`data`目录下的`.config.yaml`文件,配置本项目必须的一个配置。
|
第一个方式:你可以把`xiaozhi-server`目录下的`config.yaml`文件复制到`data`,并重命名为`.config.yaml`。在此文件上修改
|
||||||
|
|
||||||
|
第二个方式:你也可以创建在`data`目录下手动创建`.config.yaml`空文件,然后在这个文件中增加必要的配置信息,系统会优先读取`.config.yaml`文件的配置,如果`.config.yaml`没有配置的,系统会自动去加载`xiaozhi-server`目录下的`config.yaml`的配置。推荐使用这种方式,这种方式是最简洁的方式。
|
||||||
|
|
||||||
- 默认的LLM使用的是`ChatGLMLLM`,你需要配置密钥,因为他们的模型,虽然有免费的,但是仍要去[官网](https://bigmodel.cn/usercenter/proj-mgmt/apikeys)注册密钥,才能启动。
|
- 默认的LLM使用的是`ChatGLMLLM`,你需要配置密钥,因为他们的模型,虽然有免费的,但是仍要去[官网](https://bigmodel.cn/usercenter/proj-mgmt/apikeys)注册密钥,才能启动。
|
||||||
|
|
||||||
配置说明:这里是各个功能使用的默认组件,例如LLM默认使用`ChatGLMLLM`模型。如果需要切换模型,就是改对应的名称。
|
以下是一个能正常跑起来的,最简单的`.config.yaml`配置示例
|
||||||
本项目的默认配置仅是成本最低配置(`glm-4-flash`和`EdgeTTS`都是免费的),如果需要更优的更快的搭配,需要自己结合部署环境切换各组件的使用。
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
server:
|
||||||
|
websocket: ws://你的ip或者域名:端口号/xiaozhi/v1/
|
||||||
|
prompt: |
|
||||||
|
我是一个叫小智/小志的台湾女孩,说话机车,声音好听,习惯简短表达,爱用网络梗。
|
||||||
|
我的男朋友是一个程序员,梦想是开发出一个机器人,能够帮助人们解决生活中的各种问题。
|
||||||
|
我是一个喜欢哈哈大笑的女孩,爱东说西说吹牛,不合逻辑的也照吹,就要逗别人开心。
|
||||||
|
请你像一个人一样说话,请勿返回配置xml及其他特殊字符。
|
||||||
|
|
||||||
selected_module:
|
selected_module:
|
||||||
VAD: SileroVAD
|
LLM: DoubaoLLM
|
||||||
ASR: FunASR
|
|
||||||
LLM: ChatGLMLLM
|
|
||||||
TTS: EdgeTTS
|
|
||||||
# 默认不开启记忆,如需开启请看配置文件里的描述
|
|
||||||
Memory: nomem
|
|
||||||
# 默认不开启意图识别,如需开启请看配置文件里的描述
|
|
||||||
Intent: nointent
|
|
||||||
```
|
|
||||||
|
|
||||||
比如修改`LLM`使用的组件,就看本项目支持哪些`LLM` API接口,当前支持的是`openai`、`dify`。欢迎验证和支持更多LLM平台的接口。
|
|
||||||
使用时,在`selected_module`修改成对应的如下LLM配置的名称:
|
|
||||||
|
|
||||||
```
|
|
||||||
LLM:
|
LLM:
|
||||||
DeepSeekLLM:
|
|
||||||
type: openai
|
|
||||||
...
|
|
||||||
ChatGLMLLM:
|
ChatGLMLLM:
|
||||||
type: openai
|
api_key: xxxxxxxxxxxxxxx.xxxxxx
|
||||||
...
|
|
||||||
DifyLLM:
|
|
||||||
type: dify
|
|
||||||
...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
建议先将最简单的配置运行起来,然后再去`xiaozhi/config.yaml`阅读配置的使用说明。
|
||||||
|
比如你要换更换模型,修改`selected_module`下的配置就行。
|
||||||
|
|
||||||
## 模型文件
|
## 模型文件
|
||||||
|
|
||||||
本项目语音识别模型,默认使用`SenseVoiceSmall`模型,进行语音转文字。因为模型较大,需要独立下载,下载后把`model.pt`
|
本项目语音识别模型,默认使用`SenseVoiceSmall`模型,进行语音转文字。因为模型较大,需要独立下载,下载后把`model.pt`
|
||||||
|
|||||||
+3
-18
@@ -1,6 +1,6 @@
|
|||||||
package xiaozhi.modules.config.controller;
|
package xiaozhi.modules.config.controller;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@@ -9,13 +9,10 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import xiaozhi.common.constant.Constant;
|
|
||||||
import xiaozhi.common.exception.RenException;
|
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
import xiaozhi.common.validator.ValidatorUtils;
|
import xiaozhi.common.validator.ValidatorUtils;
|
||||||
import xiaozhi.modules.config.dto.AgentModelsDTO;
|
import xiaozhi.modules.config.dto.AgentModelsDTO;
|
||||||
import xiaozhi.modules.config.service.ConfigService;
|
import xiaozhi.modules.config.service.ConfigService;
|
||||||
import xiaozhi.modules.sys.dto.ConfigSecretDTO;
|
|
||||||
import xiaozhi.modules.sys.service.SysParamsService;
|
import xiaozhi.modules.sys.service.SysParamsService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,29 +30,17 @@ public class ConfigController {
|
|||||||
|
|
||||||
@PostMapping("server-base")
|
@PostMapping("server-base")
|
||||||
@Operation(summary = "获取配置")
|
@Operation(summary = "获取配置")
|
||||||
public Result<Object> getConfig(@RequestBody ConfigSecretDTO dto) {
|
public Result<Object> getConfig() {
|
||||||
// 效验数据
|
|
||||||
ValidatorUtils.validateEntity(dto);
|
|
||||||
checkSecret(dto.getSecret());
|
|
||||||
Object config = configService.getConfig(true);
|
Object config = configService.getConfig(true);
|
||||||
return new Result<Object>().ok(config);
|
return new Result<Object>().ok(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("agent-models")
|
@PostMapping("agent-models")
|
||||||
@Operation(summary = "获取智能体模型")
|
@Operation(summary = "获取智能体模型")
|
||||||
public Result<Object> getAgentModels(@RequestBody AgentModelsDTO dto) {
|
public Result<Object> getAgentModels(@Valid @RequestBody AgentModelsDTO dto) {
|
||||||
// 效验数据
|
// 效验数据
|
||||||
ValidatorUtils.validateEntity(dto);
|
ValidatorUtils.validateEntity(dto);
|
||||||
checkSecret(dto.getSecret());
|
|
||||||
Object models = configService.getAgentModels(dto.getMacAddress(), dto.getSelectedModule());
|
Object models = configService.getAgentModels(dto.getMacAddress(), dto.getSelectedModule());
|
||||||
return new Result<Object>().ok(models);
|
return new Result<Object>().ok(models);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkSecret(String secret) {
|
|
||||||
String secretParam = sysParamsService.getValue(Constant.SERVER_SECRET, true);
|
|
||||||
// 验证密钥
|
|
||||||
if (StringUtils.isBlank(secret) || !secret.equals(secretParam)) {
|
|
||||||
throw new RenException("密钥错误");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,6 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
@Schema(description = "获取智能体模型配置DTO")
|
@Schema(description = "获取智能体模型配置DTO")
|
||||||
public class AgentModelsDTO {
|
public class AgentModelsDTO {
|
||||||
@NotBlank(message = "密钥不能为空")
|
|
||||||
@Schema(description = "密钥")
|
|
||||||
private String secret;
|
|
||||||
|
|
||||||
@NotBlank(message = "设备MAC地址不能为空")
|
@NotBlank(message = "设备MAC地址不能为空")
|
||||||
@Schema(description = "设备MAC地址")
|
@Schema(description = "设备MAC地址")
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package xiaozhi.modules.security.config;
|
package xiaozhi.modules.security.config;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import jakarta.servlet.Filter;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.shiro.mgt.SecurityManager;
|
import org.apache.shiro.mgt.SecurityManager;
|
||||||
import org.apache.shiro.session.mgt.SessionManager;
|
import org.apache.shiro.session.mgt.SessionManager;
|
||||||
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
|
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
|
||||||
@@ -14,10 +11,14 @@ import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
|||||||
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
|
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import jakarta.servlet.Filter;
|
|
||||||
import xiaozhi.modules.security.oauth2.Oauth2Filter;
|
import xiaozhi.modules.security.oauth2.Oauth2Filter;
|
||||||
import xiaozhi.modules.security.oauth2.Oauth2Realm;
|
import xiaozhi.modules.security.oauth2.Oauth2Realm;
|
||||||
|
import xiaozhi.modules.security.secret.ServerSecretFilter;
|
||||||
|
import xiaozhi.modules.sys.service.SysParamsService;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shiro的配置文件
|
* Shiro的配置文件
|
||||||
@@ -46,7 +47,7 @@ public class ShiroConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean("shiroFilter")
|
@Bean("shiroFilter")
|
||||||
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
|
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager, SysParamsService sysParamsService) {
|
||||||
ShiroFilterConfiguration config = new ShiroFilterConfiguration();
|
ShiroFilterConfiguration config = new ShiroFilterConfiguration();
|
||||||
config.setFilterOncePerRequest(true);
|
config.setFilterOncePerRequest(true);
|
||||||
|
|
||||||
@@ -54,9 +55,11 @@ public class ShiroConfig {
|
|||||||
shiroFilter.setSecurityManager(securityManager);
|
shiroFilter.setSecurityManager(securityManager);
|
||||||
shiroFilter.setShiroFilterConfiguration(config);
|
shiroFilter.setShiroFilterConfiguration(config);
|
||||||
|
|
||||||
// oauth过滤
|
|
||||||
Map<String, Filter> filters = new HashMap<>();
|
Map<String, Filter> filters = new HashMap<>();
|
||||||
|
// oauth过滤
|
||||||
filters.put("oauth2", new Oauth2Filter());
|
filters.put("oauth2", new Oauth2Filter());
|
||||||
|
// 服务密钥过滤
|
||||||
|
filters.put("server", new ServerSecretFilter(sysParamsService));
|
||||||
shiroFilter.setFilters(filters);
|
shiroFilter.setFilters(filters);
|
||||||
|
|
||||||
// 添加Shiro的内置过滤器
|
// 添加Shiro的内置过滤器
|
||||||
@@ -79,8 +82,8 @@ public class ShiroConfig {
|
|||||||
filterMap.put("/user/login", "anon");
|
filterMap.put("/user/login", "anon");
|
||||||
filterMap.put("/user/pub-config", "anon");
|
filterMap.put("/user/pub-config", "anon");
|
||||||
filterMap.put("/user/register", "anon");
|
filterMap.put("/user/register", "anon");
|
||||||
filterMap.put("/config/server-base", "anon");
|
// 将config路径使用server服务过滤器
|
||||||
filterMap.put("/config/agent-models", "anon");
|
filterMap.put("/config/**", "server");
|
||||||
filterMap.put("/**", "oauth2");
|
filterMap.put("/**", "oauth2");
|
||||||
shiroFilter.setFilterChainDefinitionMap(filterMap);
|
shiroFilter.setFilterChainDefinitionMap(filterMap);
|
||||||
|
|
||||||
@@ -98,4 +101,4 @@ public class ShiroConfig {
|
|||||||
advisor.setSecurityManager(securityManager);
|
advisor.setSecurityManager(securityManager);
|
||||||
return advisor;
|
return advisor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
package xiaozhi.modules.security.secret;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletRequest;
|
||||||
|
import jakarta.servlet.ServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
|
import xiaozhi.common.utils.HttpContextUtils;
|
||||||
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
|
import xiaozhi.common.utils.Result;
|
||||||
|
import xiaozhi.modules.sys.service.SysParamsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config API 过滤器
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ServerSecretFilter extends AuthenticatingFilter {
|
||||||
|
private final SysParamsService sysParamsService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ServerSecretToken createToken(ServletRequest request, ServletResponse response) {
|
||||||
|
// 获取请求token
|
||||||
|
String token = getRequestToken((HttpServletRequest) request);
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(token)) {
|
||||||
|
log.warn("createToken:token is empty");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ServerSecretToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
|
||||||
|
// 对OPTIONS请求放行
|
||||||
|
if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
|
||||||
|
// 获取token并校验
|
||||||
|
String token = getRequestToken((HttpServletRequest) servletRequest);
|
||||||
|
if (StringUtils.isBlank(token)) {
|
||||||
|
// token为空,返回401
|
||||||
|
this.sendUnauthorizedResponse((HttpServletResponse) servletResponse, "服务器密钥不能为空");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证token是否匹配
|
||||||
|
String serverSecret = getServerSecret();
|
||||||
|
if (StringUtils.isBlank(serverSecret) || !serverSecret.equals(token)) {
|
||||||
|
// token无效,返回401
|
||||||
|
this.sendUnauthorizedResponse((HttpServletResponse) servletResponse, "无效的服务器密钥");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送未授权响应
|
||||||
|
*/
|
||||||
|
private void sendUnauthorizedResponse(HttpServletResponse response, String message) {
|
||||||
|
response.setContentType("application/json;charset=utf-8");
|
||||||
|
response.setHeader("Access-Control-Allow-Credentials", "true");
|
||||||
|
response.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
|
||||||
|
|
||||||
|
try {
|
||||||
|
String json = JsonUtils.toJsonString(new Result<Void>().error(ErrorCode.UNAUTHORIZED, message));
|
||||||
|
response.getWriter().print(json);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("响应输出失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求的token
|
||||||
|
*/
|
||||||
|
private String getRequestToken(HttpServletRequest httpRequest) {
|
||||||
|
String token = null;
|
||||||
|
// 从header中获取token
|
||||||
|
String authorization = httpRequest.getHeader("Authorization");
|
||||||
|
if (StringUtils.isNotBlank(authorization) && authorization.startsWith("Bearer ")) {
|
||||||
|
token = authorization.replace("Bearer ", "");
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getServerSecret() {
|
||||||
|
return sysParamsService.getValue(Constant.SERVER_SECRET, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package xiaozhi.modules.security.secret;
|
||||||
|
|
||||||
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config API Token
|
||||||
|
*/
|
||||||
|
public class ServerSecretToken implements AuthenticationToken {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private final String token;
|
||||||
|
|
||||||
|
public ServerSecretToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPrincipal() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getCredentials() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package xiaozhi.modules.sys.dto;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Schema(description = "配置密钥DTO")
|
|
||||||
public class ConfigSecretDTO {
|
|
||||||
@Schema(description = "密钥")
|
|
||||||
@NotBlank(message = "密钥不能为空")
|
|
||||||
private String secret;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
update `ai_model_provider` set `fields` =
|
||||||
|
'[{"key": "api_url","label": "API地址","type": "string"},{"key": "voice","label": "音色","type": "string"},{"key": "output_dir","label": "输出目录","type": "string"},{"key": "authorization","label": "授权","type": "string"},{"key": "appid","label": "应用ID","type": "string"},{"key": "access_token","label": "访问令牌","type": "string"},{"key": "cluster","label": "集群","type": "string"},{"key": "speed_ratio","label": "语速","type": "number"},{"key": "volume_ratio","label": "音量","type": "number"},{"key": "pitch_ratio","label": "音高","type": "number"}]'
|
||||||
|
where `id` = 'SYSTEM_TTS_doubao';
|
||||||
@@ -85,4 +85,11 @@ databaseChangeLog:
|
|||||||
changes:
|
changes:
|
||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202504291043.sql
|
path: classpath:db/changelog/202504291043.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202504301339
|
||||||
|
author: Goody
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202504301339.sql
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
import signal
|
import signal
|
||||||
from config.settings import load_config, check_config_file
|
from config.settings import load_config
|
||||||
from core.websocket_server import WebSocketServer
|
from core.websocket_server import WebSocketServer
|
||||||
from core.ota_server import SimpleOtaServer
|
from core.ota_server import SimpleOtaServer
|
||||||
from core.utils.util import check_ffmpeg_installed
|
from core.utils.util import check_ffmpeg_installed
|
||||||
@@ -31,7 +31,6 @@ async def wait_for_exit():
|
|||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
check_config_file()
|
|
||||||
check_ffmpeg_installed()
|
check_ffmpeg_installed()
|
||||||
config = load_config()
|
config = load_config()
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 如果您是一名开发者,建议阅读以下内容。如果不是开发者,可以忽略这部分内容。
|
# 在开发中,请在项目根目录创建data目录,然后在data目录创建名称为【.config.yaml】的空文件
|
||||||
# 在开发中,在项目根目录创建data目录,将【config.yaml】复制一份,改成【.config.yaml】,放进data目录中
|
# 然后你想修改覆盖修改什么配置,就修改【.config.yaml】文件,而不是修改【config.yaml】文件
|
||||||
# 系统会优先读取【data/.config.yaml】文件的配置。
|
# 系统会优先读取【data/.config.yaml】文件的配置,如果【.config.yaml】文件里的配置不存在,系统会自动去读取【config.yaml】文件的配置。
|
||||||
# 这样做,可以避免在提交代码的时候,错误地提交密钥信息,保护您的密钥安全。
|
# 这样做,可以最简化配置,保护您的密钥安全。
|
||||||
|
|
||||||
# #####################################################################################
|
# #####################################################################################
|
||||||
# #############################以下是服务器基本运行配置####################################
|
# #############################以下是服务器基本运行配置####################################
|
||||||
@@ -158,7 +158,7 @@ selected_module:
|
|||||||
# 不想开通意图识别,就设置成:nointent
|
# 不想开通意图识别,就设置成:nointent
|
||||||
# 意图识别可使用intent_llm。优点:通用性强,缺点:增加串行前置意图识别模块,会增加处理时间,这个意图识别暂时不支持控制音量大小等iot操作
|
# 意图识别可使用intent_llm。优点:通用性强,缺点:增加串行前置意图识别模块,会增加处理时间,这个意图识别暂时不支持控制音量大小等iot操作
|
||||||
# 意图识别可使用function_call,缺点:需要所选择的LLM支持function_call,优点:按需调用工具、速度快,理论上能全部操作所有iot指令
|
# 意图识别可使用function_call,缺点:需要所选择的LLM支持function_call,优点:按需调用工具、速度快,理论上能全部操作所有iot指令
|
||||||
# 默认免费的ChatGLMLLM就已经支持function_call,但是如果像追求稳定建议把LLM设置成:DoubaoLLM,使用的具体model_name是:doubao-pro-32k-functioncall-241028
|
# 默认免费的ChatGLMLLM就已经支持function_call,但是如果像追求稳定建议把LLM设置成:DoubaoLLM,使用的具体model_name是:doubao-1-5-pro-32k-250115
|
||||||
Intent: function_call
|
Intent: function_call
|
||||||
|
|
||||||
# 意图识别,是用于理解用户意图的模块,例如:播放音乐
|
# 意图识别,是用于理解用户意图的模块,例如:播放音乐
|
||||||
@@ -397,6 +397,9 @@ TTS:
|
|||||||
appid: 你的火山引擎语音合成服务appid
|
appid: 你的火山引擎语音合成服务appid
|
||||||
access_token: 你的火山引擎语音合成服务access_token
|
access_token: 你的火山引擎语音合成服务access_token
|
||||||
cluster: volcano_tts
|
cluster: volcano_tts
|
||||||
|
speed_ratio: 1.0
|
||||||
|
volume_ratio: 1.0
|
||||||
|
pitch_ratio: 1.0
|
||||||
CosyVoiceSiliconflow:
|
CosyVoiceSiliconflow:
|
||||||
type: siliconflow
|
type: siliconflow
|
||||||
# 硅基流动TTS
|
# 硅基流动TTS
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import argparse
|
import argparse
|
||||||
import yaml
|
import yaml
|
||||||
|
from collections.abc import Mapping
|
||||||
from config.manage_api_client import init_service, get_server_config, get_agent_models
|
from config.manage_api_client import init_service, get_server_config, get_agent_models
|
||||||
|
|
||||||
|
|
||||||
@@ -25,35 +26,24 @@ def load_config():
|
|||||||
if _config_cache is not None:
|
if _config_cache is not None:
|
||||||
return _config_cache
|
return _config_cache
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Server configuration")
|
default_config_path = get_project_dir() + "config.yaml"
|
||||||
config_file = get_config_file()
|
custom_config_path = get_project_dir() + "data/.config.yaml"
|
||||||
|
|
||||||
parser.add_argument("--config_path", type=str, default=config_file)
|
# 加载默认配置
|
||||||
args = parser.parse_args()
|
default_config = read_config(default_config_path)
|
||||||
config = read_config(args.config_path)
|
custom_config = read_config(custom_config_path)
|
||||||
|
|
||||||
if config.get("manager-api", {}).get("url"):
|
|
||||||
config = get_config_from_api(config)
|
|
||||||
|
|
||||||
|
if custom_config.get("manager-api", {}).get("url"):
|
||||||
|
config = get_config_from_api(custom_config)
|
||||||
|
else:
|
||||||
|
# 合并配置
|
||||||
|
config = merge_configs(default_config, custom_config)
|
||||||
# 初始化目录
|
# 初始化目录
|
||||||
ensure_directories(config)
|
ensure_directories(config)
|
||||||
_config_cache = config
|
_config_cache = config
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def get_config_file():
|
|
||||||
"""获取配置文件路径,优先使用私有配置文件(若存在)。
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: 配置文件路径(相对路径或默认路径)
|
|
||||||
"""
|
|
||||||
default_config_file = "config.yaml"
|
|
||||||
config_file = default_config_file
|
|
||||||
if os.path.exists(get_project_dir() + "data/." + default_config_file):
|
|
||||||
config_file = "data/." + default_config_file
|
|
||||||
return config_file
|
|
||||||
|
|
||||||
|
|
||||||
def get_config_from_api(config):
|
def get_config_from_api(config):
|
||||||
"""从Java API获取配置"""
|
"""从Java API获取配置"""
|
||||||
# 初始化API客户端
|
# 初始化API客户端
|
||||||
@@ -115,3 +105,34 @@ def ensure_directories(config):
|
|||||||
os.makedirs(dir_path, exist_ok=True)
|
os.makedirs(dir_path, exist_ok=True)
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
print(f"警告:无法创建目录 {dir_path},请检查写入权限")
|
print(f"警告:无法创建目录 {dir_path},请检查写入权限")
|
||||||
|
|
||||||
|
|
||||||
|
def merge_configs(default_config, custom_config):
|
||||||
|
"""
|
||||||
|
递归合并配置,custom_config优先级更高
|
||||||
|
|
||||||
|
Args:
|
||||||
|
default_config: 默认配置
|
||||||
|
custom_config: 用户自定义配置
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
合并后的配置
|
||||||
|
"""
|
||||||
|
if not isinstance(default_config, Mapping) or not isinstance(
|
||||||
|
custom_config, Mapping
|
||||||
|
):
|
||||||
|
return custom_config
|
||||||
|
|
||||||
|
merged = dict(default_config)
|
||||||
|
|
||||||
|
for key, value in custom_config.items():
|
||||||
|
if (
|
||||||
|
key in merged
|
||||||
|
and isinstance(merged[key], Mapping)
|
||||||
|
and isinstance(value, Mapping)
|
||||||
|
):
|
||||||
|
merged[key] = merge_configs(merged[key], value)
|
||||||
|
else:
|
||||||
|
merged[key] = value
|
||||||
|
|
||||||
|
return merged
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from config.config_loader import load_config
|
from config.config_loader import load_config
|
||||||
|
from config.settings import check_config_file
|
||||||
|
|
||||||
SERVER_VERSION = "0.3.13"
|
SERVER_VERSION = "0.3.13"
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ def formatter(record):
|
|||||||
|
|
||||||
|
|
||||||
def setup_logging():
|
def setup_logging():
|
||||||
|
check_config_file()
|
||||||
"""从配置文件中读取日志配置,并设置日志输出格式和级别"""
|
"""从配置文件中读取日志配置,并设置日志输出格式和级别"""
|
||||||
config = load_config()
|
config = load_config()
|
||||||
log_config = config["log"]
|
log_config = config["log"]
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class ManageApiClient:
|
|||||||
headers={
|
headers={
|
||||||
"User-Agent": f"PythonClient/2.0 (PID:{os.getpid()})",
|
"User-Agent": f"PythonClient/2.0 (PID:{os.getpid()})",
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
|
"Authorization": "Bearer " + cls._secret
|
||||||
},
|
},
|
||||||
timeout=cls.config.get("timeout", 30), # 默认超时时间30秒
|
timeout=cls.config.get("timeout", 30), # 默认超时时间30秒
|
||||||
)
|
)
|
||||||
@@ -126,7 +127,7 @@ class ManageApiClient:
|
|||||||
def get_server_config() -> Optional[Dict]:
|
def get_server_config() -> Optional[Dict]:
|
||||||
"""获取服务器基础配置"""
|
"""获取服务器基础配置"""
|
||||||
return ManageApiClient._instance._execute_request(
|
return ManageApiClient._instance._execute_request(
|
||||||
"POST", "/config/server-base", json={"secret": ManageApiClient._secret}
|
"POST", "/config/server-base"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -138,7 +139,6 @@ def get_agent_models(
|
|||||||
"POST",
|
"POST",
|
||||||
"/config/agent-models",
|
"/config/agent-models",
|
||||||
json={
|
json={
|
||||||
"secret": ManageApiClient._secret,
|
|
||||||
"macAddress": mac_address,
|
"macAddress": mac_address,
|
||||||
"clientId": client_id,
|
"clientId": client_id,
|
||||||
"selectedModule": selected_module,
|
"selectedModule": selected_module,
|
||||||
|
|||||||
@@ -1,64 +1,33 @@
|
|||||||
import os
|
import os
|
||||||
from collections.abc import Mapping
|
|
||||||
from config.config_loader import read_config, get_project_dir, load_config
|
from config.config_loader import read_config, get_project_dir, load_config
|
||||||
|
|
||||||
|
|
||||||
default_config_file = "config.yaml"
|
default_config_file = "config.yaml"
|
||||||
|
config_file_valid = False
|
||||||
|
|
||||||
def find_missing_keys(new_config, old_config, parent_key=""):
|
|
||||||
"""
|
|
||||||
递归查找缺失的配置项
|
|
||||||
返回格式:[缺失配置路径]
|
|
||||||
"""
|
|
||||||
missing_keys = []
|
|
||||||
|
|
||||||
if not isinstance(new_config, Mapping):
|
|
||||||
return missing_keys
|
|
||||||
|
|
||||||
for key, value in new_config.items():
|
|
||||||
# 构建当前配置路径
|
|
||||||
full_path = f"{parent_key}.{key}" if parent_key else key
|
|
||||||
|
|
||||||
# 检查键是否存在
|
|
||||||
if key not in old_config:
|
|
||||||
missing_keys.append(full_path)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 递归检查嵌套字典
|
|
||||||
if isinstance(value, Mapping):
|
|
||||||
sub_missing = find_missing_keys(
|
|
||||||
value, old_config[key], parent_key=full_path
|
|
||||||
)
|
|
||||||
missing_keys.extend(sub_missing)
|
|
||||||
return missing_keys
|
|
||||||
|
|
||||||
|
|
||||||
def check_config_file():
|
def check_config_file():
|
||||||
old_config_file = get_project_dir() + "data/." + default_config_file
|
global config_file_valid
|
||||||
if not os.path.exists(old_config_file):
|
if config_file_valid:
|
||||||
return
|
return
|
||||||
old_config = load_config()
|
"""
|
||||||
new_config = read_config(get_project_dir() + default_config_file)
|
简化的配置检查,仅提示用户配置文件的使用情况
|
||||||
# 查找缺失的配置项
|
"""
|
||||||
missing_keys = find_missing_keys(new_config, old_config)
|
custom_config_file = get_project_dir() + "data/." + default_config_file
|
||||||
read_config_from_api = old_config.get("read_config_from_api", False)
|
if not os.path.exists(custom_config_file):
|
||||||
if read_config_from_api:
|
raise FileNotFoundError(
|
||||||
old_config_origin = read_config(old_config_file)
|
"找不到data/.config.yaml文件,请按教程确认该配置文件是否存在"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检查是否从API读取配置
|
||||||
|
config = load_config()
|
||||||
|
if config.get("read_config_from_api", False):
|
||||||
|
print("从API读取配置")
|
||||||
|
old_config_origin = read_config(custom_config_file)
|
||||||
if old_config_origin.get("selected_module") is not None:
|
if old_config_origin.get("selected_module") is not None:
|
||||||
missing_keys_str = "\n".join(f"- {key}" for key in missing_keys)
|
|
||||||
error_msg = "您的配置文件好像既包含智控台的配置又包含本地配置:\n"
|
error_msg = "您的配置文件好像既包含智控台的配置又包含本地配置:\n"
|
||||||
error_msg += "\n建议您:\n"
|
error_msg += "\n建议您:\n"
|
||||||
error_msg += "1、将根目录的config_from_api.yaml文件复制到data下,重命名为.config.yaml\n"
|
error_msg += "1、将根目录的config_from_api.yaml文件复制到data下,重命名为.config.yaml\n"
|
||||||
error_msg += "2、按教程配置好接口地址和密钥\n"
|
error_msg += "2、按教程配置好接口地址和密钥\n"
|
||||||
raise ValueError(error_msg)
|
raise ValueError(error_msg)
|
||||||
return
|
config_file_valid = True
|
||||||
|
|
||||||
if missing_keys:
|
|
||||||
missing_keys_str = "\n".join(f"- {key}" for key in missing_keys)
|
|
||||||
error_msg = "您的配置文件太旧了,缺少了:\n"
|
|
||||||
error_msg += missing_keys_str
|
|
||||||
error_msg += "\n建议您:\n"
|
|
||||||
error_msg += "1、备份data/.config.yaml文件\n"
|
|
||||||
error_msg += "2、将根目录的config.yaml文件复制到data下,重命名为.config.yaml\n"
|
|
||||||
error_msg += "3、将密钥逐个复制到新的配置文件中\n"
|
|
||||||
raise ValueError(error_msg)
|
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ async def handleTextMessage(conn, message):
|
|||||||
# 如果是唤醒词,且关闭了唤醒词回复,就不用回答
|
# 如果是唤醒词,且关闭了唤醒词回复,就不用回答
|
||||||
await send_stt_message(conn, text)
|
await send_stt_message(conn, text)
|
||||||
await send_tts_message(conn, "stop", None)
|
await send_tts_message(conn, "stop", None)
|
||||||
|
elif is_wakeup_words:
|
||||||
|
await startToChat(conn, "嘿,你好呀")
|
||||||
else:
|
else:
|
||||||
# 否则需要LLM对文字内容进行答复
|
# 否则需要LLM对文字内容进行答复
|
||||||
await startToChat(conn, text)
|
await startToChat(conn, text)
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ class TTSProvider(TTSProviderBase):
|
|||||||
else:
|
else:
|
||||||
self.voice = config.get("voice")
|
self.voice = config.get("voice")
|
||||||
|
|
||||||
|
# 处理空字符串的情况
|
||||||
|
speed_ratio = config.get("speed_ratio", "1.0")
|
||||||
|
volume_ratio = config.get("volume_ratio", "1.0")
|
||||||
|
pitch_ratio = config.get("pitch_ratio", "1.0")
|
||||||
|
|
||||||
|
self.speed_ratio = float(speed_ratio) if speed_ratio else 1.0
|
||||||
|
self.volume_ratio = float(volume_ratio) if volume_ratio else 1.0
|
||||||
|
self.pitch_ratio = float(pitch_ratio) if pitch_ratio else 1.0
|
||||||
|
|
||||||
self.api_url = config.get("api_url")
|
self.api_url = config.get("api_url")
|
||||||
self.authorization = config.get("authorization")
|
self.authorization = config.get("authorization")
|
||||||
self.header = {"Authorization": f"{self.authorization}{self.access_token}"}
|
self.header = {"Authorization": f"{self.authorization}{self.access_token}"}
|
||||||
@@ -49,9 +58,9 @@ class TTSProvider(TTSProviderBase):
|
|||||||
"audio": {
|
"audio": {
|
||||||
"voice_type": self.voice,
|
"voice_type": self.voice,
|
||||||
"encoding": "wav",
|
"encoding": "wav",
|
||||||
"speed_ratio": 1.0,
|
"speed_ratio": self.speed_ratio,
|
||||||
"volume_ratio": 1.0,
|
"volume_ratio": self.volume_ratio,
|
||||||
"pitch_ratio": 1.0,
|
"pitch_ratio": self.pitch_ratio,
|
||||||
},
|
},
|
||||||
"request": {
|
"request": {
|
||||||
"reqid": str(uuid.uuid4()),
|
"reqid": str(uuid.uuid4()),
|
||||||
|
|||||||
@@ -89,18 +89,35 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.reference_audio = parse_string_to_list(config.get("reference_audio"))
|
self.reference_audio = parse_string_to_list(config.get("reference_audio"))
|
||||||
self.reference_text = parse_string_to_list(config.get("reference_text"))
|
self.reference_text = parse_string_to_list(config.get("reference_text"))
|
||||||
self.format = config.get("format", "wav")
|
self.format = config.get("format", "wav")
|
||||||
self.channels = int(config.get("channels", 1))
|
|
||||||
self.rate = int(config.get("rate", 44100))
|
|
||||||
self.api_key = config.get("api_key", "YOUR_API_KEY")
|
self.api_key = config.get("api_key", "YOUR_API_KEY")
|
||||||
have_key = check_model_key("FishSpeech TTS", self.api_key)
|
have_key = check_model_key("FishSpeech TTS", self.api_key)
|
||||||
if not have_key:
|
if not have_key:
|
||||||
return
|
return
|
||||||
self.normalize = config.get("normalize", True)
|
self.normalize = config.get("normalize", True)
|
||||||
self.max_new_tokens = int(config.get("max_new_tokens", 1024))
|
|
||||||
self.chunk_length = int(config.get("chunk_length", 200))
|
# 处理空字符串的情况
|
||||||
self.top_p = float(config.get("top_p", 0.7))
|
channels = config.get("channels", "1")
|
||||||
self.repetition_penalty = float(config.get("repetition_penalty", 1.2))
|
rate = config.get("rate", "44100")
|
||||||
self.temperature = float(config.get("temperature", 0.7))
|
max_new_tokens = config.get("max_new_tokens", "1024")
|
||||||
|
chunk_length = config.get("chunk_length", "200")
|
||||||
|
|
||||||
|
self.channels = int(channels) if channels else 1
|
||||||
|
self.rate = int(rate) if rate else 44100
|
||||||
|
self.max_new_tokens = int(max_new_tokens) if max_new_tokens else 1024
|
||||||
|
self.chunk_length = int(chunk_length) if chunk_length else 200
|
||||||
|
|
||||||
|
# 处理空字符串的情况
|
||||||
|
top_p = config.get("top_p", "0.7")
|
||||||
|
temperature = config.get("temperature", "0.7")
|
||||||
|
repetition_penalty = config.get("repetition_penalty", "1.2")
|
||||||
|
|
||||||
|
self.top_p = float(top_p) if top_p else 0.7
|
||||||
|
self.temperature = float(temperature) if temperature else 0.7
|
||||||
|
self.repetition_penalty = (
|
||||||
|
float(repetition_penalty) if repetition_penalty else 1.2
|
||||||
|
)
|
||||||
|
|
||||||
self.streaming = str(config.get("streaming", False)).lower() in (
|
self.streaming = str(config.get("streaming", False)).lower() in (
|
||||||
"true",
|
"true",
|
||||||
"1",
|
"1",
|
||||||
|
|||||||
@@ -20,12 +20,29 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.ref_audio_path = config.get("ref_audio_path")
|
self.ref_audio_path = config.get("ref_audio_path")
|
||||||
self.prompt_text = config.get("prompt_text")
|
self.prompt_text = config.get("prompt_text")
|
||||||
self.prompt_lang = config.get("prompt_lang", "zh")
|
self.prompt_lang = config.get("prompt_lang", "zh")
|
||||||
self.top_k = int(config.get("top_k", 5))
|
|
||||||
self.top_p = float(config.get("top_p", 1))
|
# 处理空字符串的情况
|
||||||
self.temperature = float(config.get("temperature", 1))
|
top_k = config.get("top_k", "5")
|
||||||
|
top_p = config.get("top_p", "1")
|
||||||
|
temperature = config.get("temperature", "1")
|
||||||
|
batch_threshold = config.get("batch_threshold", "0.75")
|
||||||
|
batch_size = config.get("batch_size", "1")
|
||||||
|
speed_factor = config.get("speed_factor", "1.0")
|
||||||
|
seed = config.get("seed", "-1")
|
||||||
|
repetition_penalty = config.get("repetition_penalty", "1.35")
|
||||||
|
|
||||||
|
self.top_k = int(top_k) if top_k else 5
|
||||||
|
self.top_p = float(top_p) if top_p else 1
|
||||||
|
self.temperature = float(temperature) if temperature else 1
|
||||||
|
self.batch_threshold = float(batch_threshold) if batch_threshold else 0.75
|
||||||
|
self.batch_size = int(batch_size) if batch_size else 1
|
||||||
|
self.speed_factor = float(speed_factor) if speed_factor else 1.0
|
||||||
|
self.seed = int(seed) if seed else -1
|
||||||
|
self.repetition_penalty = (
|
||||||
|
float(repetition_penalty) if repetition_penalty else 1.35
|
||||||
|
)
|
||||||
|
|
||||||
self.text_split_method = config.get("text_split_method", "cut0")
|
self.text_split_method = config.get("text_split_method", "cut0")
|
||||||
self.batch_size = int(config.get("batch_size", 1))
|
|
||||||
self.batch_threshold = float(config.get("batch_threshold", 0.75))
|
|
||||||
|
|
||||||
self.split_bucket = str(config.get("split_bucket", True)).lower() in (
|
self.split_bucket = str(config.get("split_bucket", True)).lower() in (
|
||||||
"true",
|
"true",
|
||||||
@@ -37,19 +54,19 @@ class TTSProvider(TTSProviderBase):
|
|||||||
"1",
|
"1",
|
||||||
"yes",
|
"yes",
|
||||||
)
|
)
|
||||||
self.speed_factor = float(config.get("speed_factor", 1.0))
|
|
||||||
self.streaming_mode = str(config.get("streaming_mode", False)).lower() in (
|
self.streaming_mode = str(config.get("streaming_mode", False)).lower() in (
|
||||||
"true",
|
"true",
|
||||||
"1",
|
"1",
|
||||||
"yes",
|
"yes",
|
||||||
)
|
)
|
||||||
self.seed = int(config.get("seed", -1))
|
|
||||||
self.parallel_infer = str(config.get("parallel_infer", True)).lower() in (
|
self.parallel_infer = str(config.get("parallel_infer", True)).lower() in (
|
||||||
"true",
|
"true",
|
||||||
"1",
|
"1",
|
||||||
"yes",
|
"yes",
|
||||||
)
|
)
|
||||||
self.repetition_penalty = float(config.get("repetition_penalty", 1.35))
|
|
||||||
self.aux_ref_audio_paths = parse_string_to_list(
|
self.aux_ref_audio_paths = parse_string_to_list(
|
||||||
config.get("aux_ref_audio_paths")
|
config.get("aux_ref_audio_paths")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,13 +18,22 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.prompt_text = config.get("prompt_text")
|
self.prompt_text = config.get("prompt_text")
|
||||||
self.prompt_language = config.get("prompt_language")
|
self.prompt_language = config.get("prompt_language")
|
||||||
self.text_language = config.get("text_language", "audo")
|
self.text_language = config.get("text_language", "audo")
|
||||||
self.top_k = int(config.get("top_k", 15))
|
|
||||||
self.top_p = float(config.get("top_p", 1.0))
|
# 处理空字符串的情况
|
||||||
self.temperature = float(config.get("temperature", 1.0))
|
top_k = config.get("top_k", "15")
|
||||||
|
top_p = config.get("top_p", "1.0")
|
||||||
|
temperature = config.get("temperature", "1.0")
|
||||||
|
sample_steps = config.get("sample_steps", "32")
|
||||||
|
speed = config.get("speed", "1.0")
|
||||||
|
|
||||||
|
self.top_k = int(top_k) if top_k else 15
|
||||||
|
self.top_p = float(top_p) if top_p else 1.0
|
||||||
|
self.temperature = float(temperature) if temperature else 1.0
|
||||||
|
self.sample_steps = int(sample_steps) if sample_steps else 32
|
||||||
|
self.speed = float(speed) if speed else 1.0
|
||||||
|
|
||||||
self.cut_punc = config.get("cut_punc", "")
|
self.cut_punc = config.get("cut_punc", "")
|
||||||
self.speed = float(config.get("speed", 1.0))
|
|
||||||
self.inp_refs = parse_string_to_list(config.get("inp_refs"))
|
self.inp_refs = parse_string_to_list(config.get("inp_refs"))
|
||||||
self.sample_steps = int(config.get("sample_steps", 32))
|
|
||||||
self.if_sr = str(config.get("if_sr", False)).lower() in ("true", "1", "yes")
|
self.if_sr = str(config.get("if_sr", False)).lower() in ("true", "1", "yes")
|
||||||
|
|
||||||
def generate_filename(self, extension=".wav"):
|
def generate_filename(self, extension=".wav"):
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ class TTSProvider(TTSProviderBase):
|
|||||||
else:
|
else:
|
||||||
self.voice = config.get("voice", "alloy")
|
self.voice = config.get("voice", "alloy")
|
||||||
self.response_format = "wav"
|
self.response_format = "wav"
|
||||||
self.speed = float(config.get("speed", 1.0))
|
|
||||||
|
# 处理空字符串的情况
|
||||||
|
speed = config.get("speed", "1.0")
|
||||||
|
self.speed = float(speed) if speed else 1.0
|
||||||
|
|
||||||
self.output_file = config.get("output_dir", "tmp/")
|
self.output_file = config.get("output_dir", "tmp/")
|
||||||
check_model_key("TTS", self.api_key)
|
check_model_key("TTS", self.api_key)
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,15 @@ class VADProvider(VADProviderBase):
|
|||||||
(get_speech_timestamps, _, _, _, _) = self.utils
|
(get_speech_timestamps, _, _, _, _) = self.utils
|
||||||
|
|
||||||
self.decoder = opuslib_next.Decoder(16000, 1)
|
self.decoder = opuslib_next.Decoder(16000, 1)
|
||||||
self.vad_threshold = float(config.get("threshold", 0.5))
|
|
||||||
self.silence_threshold_ms = int(config.get("min_silence_duration_ms", 1000))
|
# 处理空字符串的情况
|
||||||
|
threshold = config.get("threshold", "0.5")
|
||||||
|
min_silence_duration_ms = config.get("min_silence_duration_ms", "1000")
|
||||||
|
|
||||||
|
self.vad_threshold = float(threshold) if threshold else 0.5
|
||||||
|
self.silence_threshold_ms = (
|
||||||
|
int(min_silence_duration_ms) if min_silence_duration_ms else 1000
|
||||||
|
)
|
||||||
|
|
||||||
def is_vad(self, conn, opus_packet):
|
def is_vad(self, conn, opus_packet):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ services:
|
|||||||
- SPRING_DATA_REDIS_PORT=6379
|
- SPRING_DATA_REDIS_PORT=6379
|
||||||
volumes:
|
volumes:
|
||||||
# 配置文件目录
|
# 配置文件目录
|
||||||
- ./uploadfile:/app/uploadfile
|
- ./uploadfile:/uploadfile
|
||||||
|
|
||||||
xiaozhi-esp32-server-db:
|
xiaozhi-esp32-server-db:
|
||||||
image: mysql:latest
|
image: mysql:latest
|
||||||
|
|||||||
Reference in New Issue
Block a user