mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-25 00:23:53 +08:00
Merge branch 'main' into web-vue-modify-device
This commit is contained in:
+7
-1
@@ -171,7 +171,13 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
switch (param.getValueType().toLowerCase()) {
|
||||
case "number":
|
||||
try {
|
||||
current.put(lastKey, Double.parseDouble(value));
|
||||
double doubleValue = Double.parseDouble(value);
|
||||
// 如果数值是整数形式,则转换为Integer
|
||||
if (doubleValue == (int) doubleValue) {
|
||||
current.put(lastKey, (int) doubleValue);
|
||||
} else {
|
||||
current.put(lastKey, doubleValue);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
current.put(lastKey, value);
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ public class ShiroConfig {
|
||||
filterMap.put("/favicon.ico", "anon");
|
||||
filterMap.put("/user/captcha", "anon");
|
||||
filterMap.put("/user/login", "anon");
|
||||
filterMap.put("/user/pub-config", "anon");
|
||||
filterMap.put("/user/register", "anon");
|
||||
filterMap.put("/config/server-base", "anon");
|
||||
filterMap.put("/config/agent-models", "anon");
|
||||
|
||||
+10
@@ -1,6 +1,8 @@
|
||||
package xiaozhi.modules.security.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -111,4 +113,12 @@ public class LoginController {
|
||||
sysUserTokenService.changePassword(userId, passwordDTO);
|
||||
return new Result<>();
|
||||
}
|
||||
|
||||
@GetMapping("/pub-config")
|
||||
@Operation(summary = "公共配置")
|
||||
public Result<Map<String, Object>> pubConfig() {
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
config.put("version", "0.3.3");
|
||||
return new Result<Map<String, Object>>().ok(config);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1 @@
|
||||
# 暂时使用群主的接口
|
||||
# VUE_APP_API_BASE_URL=https://2662r3426b.vicp.fun/xiaozhi
|
||||
# 如果本地开发,请使用以下接口
|
||||
VUE_APP_API_BASE_URL=http://localhost:8002/xiaozhi
|
||||
VUE_APP_API_BASE_URL=/xiaozhi
|
||||
@@ -106,7 +106,7 @@ export default {
|
||||
},
|
||||
// 修改用户状态
|
||||
changeUserStatus(status, userIds, successCallback) {
|
||||
console.log(555,userIds)
|
||||
console.log(555, userIds)
|
||||
RequestService.sendRequest()
|
||||
.url(`${getServiceUrl()}/admin/users/changeStatus/${status}`)
|
||||
.method('put')
|
||||
@@ -122,4 +122,20 @@ export default {
|
||||
})
|
||||
}).send()
|
||||
},
|
||||
// 获取公共配置
|
||||
getPubConfig(callback) {
|
||||
RequestService.sendRequest()
|
||||
.url(`${getServiceUrl()}/user/pub-config`)
|
||||
.method('GET')
|
||||
.success((res) => {
|
||||
RequestService.clearRequestTime();
|
||||
callback(res);
|
||||
})
|
||||
.fail((err) => {
|
||||
console.error('获取公共配置失败:', err);
|
||||
RequestService.reAjaxFun(() => {
|
||||
this.getPubConfig(callback);
|
||||
});
|
||||
}).send();
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div class="copyright">
|
||||
©2025 xiaozhi-esp32-server v{{ version }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Api from '@/apis/api';
|
||||
|
||||
export default {
|
||||
name: 'VersionFooter',
|
||||
data() {
|
||||
return {
|
||||
version: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getSystemVersion();
|
||||
},
|
||||
methods: {
|
||||
getSystemVersion() {
|
||||
const storedVersion = sessionStorage.getItem('systemVersion');
|
||||
if (storedVersion) {
|
||||
this.version = storedVersion;
|
||||
return;
|
||||
}
|
||||
|
||||
Api.user.getPubConfig(({ data }) => {
|
||||
if (data.code === 0 && data.data.version) {
|
||||
this.version = data.data.version;
|
||||
sessionStorage.setItem('systemVersion', data.data.version);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -88,6 +88,7 @@
|
||||
|
||||
<AddDeviceDialog :visible.sync="addDeviceDialogVisible" :agent-id="currentAgentId"
|
||||
@refresh="fetchBindDevices(currentAgentId)" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -131,6 +132,7 @@ export default {
|
||||
},
|
||||
pageCount() {
|
||||
return Math.ceil(this.filteredDeviceList.length / this.pageSize);
|
||||
|
||||
},
|
||||
visiblePages() {
|
||||
const pages = [];
|
||||
@@ -523,4 +525,5 @@ export default {
|
||||
color: #5a64b5;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@@ -3,21 +3,15 @@
|
||||
<HeaderBar />
|
||||
|
||||
<div class="operation-bar">
|
||||
<h2 class="page-title">{{ modelTypeText }}</h2>
|
||||
<h2 class="page-title">{{ modelTypeText }}</h2>
|
||||
<div class="action-group">
|
||||
<div class="search-group">
|
||||
<el-input
|
||||
placeholder="请输入模型名称查询"
|
||||
v-model="search"
|
||||
class="search-input"
|
||||
clearable
|
||||
@keyup.enter.native="handleSearch"
|
||||
style="width: 240px"
|
||||
/>
|
||||
<el-button class="btn-search" @click="handleSearch">
|
||||
搜索
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="search-group">
|
||||
<el-input placeholder="请输入模型名称查询" v-model="search" class="search-input" clearable
|
||||
@keyup.enter.native="handleSearch" style="width: 240px" />
|
||||
<el-button class="btn-search" @click="handleSearch">
|
||||
搜索
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -93,9 +87,9 @@
|
||||
|
||||
<div class="table-footer">
|
||||
<div class="batch-actions">
|
||||
<el-button size="mini" type="primary" @click="selectAll" >
|
||||
<el-button size="mini" type="primary" @click="selectAll">
|
||||
{{ isAllSelected ?
|
||||
'取消全选' : '全选' }}
|
||||
'取消全选' : '全选' }}
|
||||
</el-button>
|
||||
<el-button type="success" size="mini" @click="addModel" class="add-btn">
|
||||
新增
|
||||
@@ -125,10 +119,6 @@
|
||||
<TtsModel :visible.sync="ttsDialogVisible" :ttsModelId="selectedTtsModelId" />
|
||||
<AddModelDialog :modelType="activeTab" :visible.sync="addDialogVisible" @confirm="handleAddConfirm" />
|
||||
</div>
|
||||
|
||||
<!-- <div class="copyright">-->
|
||||
<!-- ©2025 xiaozhi-esp32-server-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -639,7 +629,6 @@ export default {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
|
||||
.title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<div class="table_bottom">
|
||||
<div class="ctrl_btn">
|
||||
<el-button size="mini" type="primary" class="select-all-btn" @click="handleSelectAll">
|
||||
{{ isAllSelected ? '取消全选' : '全选' }}
|
||||
{{ isAllSelected ? '取消全选' : '全选' }}
|
||||
</el-button>
|
||||
<el-button size="mini" type="success" icon="el-icon-circle-check" @click="batchEnable">启用</el-button>
|
||||
<el-button size="mini" type="warning" @click="batchDisable"><i
|
||||
@@ -70,7 +70,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="copyright">©2025 xiaozhi-esp32-server</div> -->
|
||||
<view-password-dialog :visible.sync="showViewPassword" :password="currentPassword" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -35,11 +35,11 @@
|
||||
@deviceManage="handleDeviceManage" @delete="handleDeleteAgent" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="copyright">
|
||||
©2025 xiaozhi-esp32-server
|
||||
</div>
|
||||
<AddWisdomBodyDialog :visible.sync="addDeviceDialogVisible" @confirm="handleWisdomBodyAdded" />
|
||||
</el-main>
|
||||
<el-footer>
|
||||
<version-footer />
|
||||
</el-footer>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
@@ -49,10 +49,11 @@ import Api from '@/apis/api';
|
||||
import AddWisdomBodyDialog from '@/components/AddWisdomBodyDialog.vue';
|
||||
import DeviceItem from '@/components/DeviceItem.vue';
|
||||
import HeaderBar from '@/components/HeaderBar.vue';
|
||||
import VersionFooter from '@/components/VersionFooter.vue';
|
||||
|
||||
export default {
|
||||
name: 'HomePage',
|
||||
components: { DeviceItem, AddWisdomBodyDialog, HeaderBar },
|
||||
components: { DeviceItem, AddWisdomBodyDialog, HeaderBar, VersionFooter },
|
||||
data() {
|
||||
return {
|
||||
addDeviceDialogVisible: false,
|
||||
|
||||
@@ -51,9 +51,7 @@
|
||||
</div>
|
||||
</el-main>
|
||||
<el-footer>
|
||||
<div class="copyright">
|
||||
©2025 xiaozhi-esp32-server
|
||||
</div>
|
||||
<version-footer />
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</div>
|
||||
@@ -61,11 +59,14 @@
|
||||
|
||||
<script>
|
||||
import Api from '@/apis/api';
|
||||
import VersionFooter from '@/components/VersionFooter.vue';
|
||||
import { getUUID, goToPage, showDanger, showSuccess } from '@/utils';
|
||||
|
||||
|
||||
export default {
|
||||
name: 'login',
|
||||
components: {
|
||||
VersionFooter
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeName: "username",
|
||||
@@ -76,11 +77,13 @@ export default {
|
||||
captchaId: ''
|
||||
},
|
||||
captchaUuid: '',
|
||||
captchaUrl: ''
|
||||
captchaUrl: '',
|
||||
version: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchCaptcha();
|
||||
this.getSystemVersion();
|
||||
},
|
||||
methods: {
|
||||
fetchCaptcha() {
|
||||
@@ -144,6 +147,21 @@ export default {
|
||||
|
||||
goToRegister() {
|
||||
goToPage('/register')
|
||||
},
|
||||
|
||||
getSystemVersion() {
|
||||
const storedVersion = sessionStorage.getItem('systemVersion');
|
||||
if (storedVersion) {
|
||||
this.version = storedVersion;
|
||||
return;
|
||||
}
|
||||
|
||||
Api.user.getPubConfig(({ data }) => {
|
||||
if (data.code === 0 && data.data.version) {
|
||||
this.version = data.data.version;
|
||||
sessionStorage.setItem('systemVersion', data.data.version);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,9 +72,7 @@
|
||||
|
||||
<!-- 保持相同的页脚 -->
|
||||
<el-footer>
|
||||
<div class="copyright">
|
||||
©2025 xiaozhi-esp32-server
|
||||
</div>
|
||||
<version-footer />
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</div>
|
||||
@@ -82,10 +80,14 @@
|
||||
|
||||
<script>
|
||||
import Api from '@/apis/api';
|
||||
import VersionFooter from '@/components/VersionFooter.vue';
|
||||
import { getUUID, goToPage, showDanger, showSuccess } from '@/utils';
|
||||
|
||||
export default {
|
||||
name: 'register',
|
||||
components: {
|
||||
VersionFooter
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
|
||||
@@ -81,9 +81,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="copyright">-->
|
||||
<!-- ©2025 xiaozhi-esp32-server-->
|
||||
<!-- </div>-->
|
||||
|
||||
</el-main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -15,6 +15,10 @@ module.exports = defineConfig({
|
||||
devServer: {
|
||||
port: 8001, // 指定端口为 8001
|
||||
proxy: {
|
||||
'/xiaozhi': {
|
||||
target: 'http://127.0.0.1:8002',
|
||||
changeOrigin: true
|
||||
}
|
||||
},
|
||||
client: {
|
||||
overlay: false, // 不显示 webpack 错误覆盖层
|
||||
|
||||
@@ -3,7 +3,7 @@ import sys
|
||||
from loguru import logger
|
||||
from config.config_loader import load_config
|
||||
|
||||
SERVER_VERSION = "0.3.1"
|
||||
SERVER_VERSION = "0.3.3"
|
||||
|
||||
|
||||
def get_module_abbreviation(module_name, module_dict):
|
||||
@@ -25,6 +25,12 @@ def build_module_string(selected_module):
|
||||
)
|
||||
|
||||
|
||||
def formatter(record):
|
||||
"""为没有 tag 的日志添加默认值"""
|
||||
record["extra"].setdefault("tag", record["name"])
|
||||
return record["message"]
|
||||
|
||||
|
||||
def setup_logging():
|
||||
"""从配置文件中读取日志配置,并设置日志输出格式和级别"""
|
||||
config = load_config()
|
||||
@@ -56,9 +62,14 @@ def setup_logging():
|
||||
logger.remove()
|
||||
|
||||
# 输出到控制台
|
||||
logger.add(sys.stdout, format=log_format, level=log_level)
|
||||
logger.add(sys.stdout, format=log_format, level=log_level, filter=formatter)
|
||||
|
||||
# 输出到文件
|
||||
logger.add(os.path.join(log_dir, log_file), format=log_format_file, level=log_level)
|
||||
logger.add(
|
||||
os.path.join(log_dir, log_file),
|
||||
format=log_format_file,
|
||||
level=log_level,
|
||||
filter=formatter,
|
||||
)
|
||||
|
||||
return logger
|
||||
|
||||
@@ -204,7 +204,7 @@ class ConnectionHandler:
|
||||
self.headers.get("device-id", None),
|
||||
self.headers.get("client-id", None),
|
||||
)
|
||||
private_config["delete_audio"] = self.config["delete_audio"]
|
||||
private_config["delete_audio"] = bool(self.config.get("delete_audio", True))
|
||||
self.logger.bind(tag=TAG).info(f"获取差异化配置成功: {private_config}")
|
||||
except Exception as e:
|
||||
self.logger.bind(tag=TAG).error(f"获取差异化配置失败: {e}")
|
||||
@@ -335,10 +335,8 @@ class ConnectionHandler:
|
||||
|
||||
def change_system_prompt(self, prompt):
|
||||
self.prompt = prompt
|
||||
# 找到原来的role==system,替换原来的系统提示
|
||||
for m in self.dialogue.dialogue:
|
||||
if m.role == "system":
|
||||
m.content = prompt
|
||||
# 更新系统prompt至上下文
|
||||
self.dialogue.update_system_message(self.prompt)
|
||||
|
||||
def chat(self, query):
|
||||
|
||||
@@ -702,7 +700,7 @@ class ConnectionHandler:
|
||||
opus_datas, text_index, tts_file = [], 0, None
|
||||
try:
|
||||
self.logger.bind(tag=TAG).debug("正在处理TTS任务...")
|
||||
tts_timeout = self.config.get("tts_timeout", 10)
|
||||
tts_timeout = int(self.config.get("tts_timeout", 10))
|
||||
tts_file, text, text_index = future.result(timeout=tts_timeout)
|
||||
if text is None or len(text) <= 0:
|
||||
self.logger.bind(tag=TAG).error(
|
||||
|
||||
@@ -71,8 +71,8 @@ async def no_voice_close_connect(conn):
|
||||
conn.client_no_voice_last_time = time.time() * 1000
|
||||
else:
|
||||
no_voice_time = time.time() * 1000 - conn.client_no_voice_last_time
|
||||
close_connection_no_voice_time = conn.config.get(
|
||||
"close_connection_no_voice_time", 120
|
||||
close_connection_no_voice_time = int(
|
||||
conn.config.get("close_connection_no_voice_time", 120)
|
||||
)
|
||||
if (
|
||||
not conn.close_after_chat
|
||||
|
||||
@@ -58,7 +58,8 @@ class LLMProvider(LLMProviderBase):
|
||||
self.session_conversation_map[session_id] = (
|
||||
conversation_id # 更新映射
|
||||
)
|
||||
if event.get("answer"):
|
||||
# 过滤 message_replace 事件,此事件会全量推一次
|
||||
if event.get("event") != "message_replace" and event.get("answer"):
|
||||
yield event["answer"]
|
||||
elif self.mode == "workflows/run":
|
||||
for line in r.iter_lines():
|
||||
@@ -73,7 +74,8 @@ class LLMProvider(LLMProviderBase):
|
||||
for line in r.iter_lines():
|
||||
if line.startswith(b"data: "):
|
||||
event = json.loads(line[6:])
|
||||
if event.get("answer"):
|
||||
# 过滤 message_replace 事件,此事件会全量推一次
|
||||
if event.get("event") != "message_replace" and event.get("answer"):
|
||||
yield event["answer"]
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -89,19 +89,19 @@ class TTSProvider(TTSProviderBase):
|
||||
self.reference_audio = config.get("reference_audio", [])
|
||||
self.reference_text = config.get("reference_text", [])
|
||||
self.format = config.get("format", "wav")
|
||||
self.channels = config.get("channels", 1)
|
||||
self.rate = config.get("rate", 44100)
|
||||
self.channels = int(config.get("channels", 1))
|
||||
self.rate = int(config.get("rate", 44100))
|
||||
self.api_key = config.get("api_key", "YOUR_API_KEY")
|
||||
have_key = check_model_key("FishSpeech TTS", self.api_key)
|
||||
if not have_key:
|
||||
return
|
||||
self.normalize = config.get("normalize", True)
|
||||
self.max_new_tokens = config.get("max_new_tokens", 1024)
|
||||
self.chunk_length = config.get("chunk_length", 200)
|
||||
self.top_p = config.get("top_p", 0.7)
|
||||
self.repetition_penalty = config.get("repetition_penalty", 1.2)
|
||||
self.temperature = config.get("temperature", 0.7)
|
||||
self.streaming = config.get("streaming", False)
|
||||
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))
|
||||
self.repetition_penalty = float(config.get("repetition_penalty", 1.2))
|
||||
self.temperature = float(config.get("temperature", 0.7))
|
||||
self.streaming = bool(config.get("streaming", False))
|
||||
self.use_memory_cache = config.get("use_memory_cache", "on")
|
||||
self.seed = config.get("seed")
|
||||
self.api_url = config.get("api_url", "http://127.0.0.1:8080/v1/tts")
|
||||
|
||||
@@ -10,6 +10,7 @@ from core.providers.tts.base import TTSProviderBase
|
||||
TAG = __name__
|
||||
logger = setup_logging()
|
||||
|
||||
|
||||
class TTSProvider(TTSProviderBase):
|
||||
def __init__(self, config, delete_audio_file):
|
||||
super().__init__(config, delete_audio_file)
|
||||
@@ -18,23 +19,26 @@ class TTSProvider(TTSProviderBase):
|
||||
self.ref_audio_path = config.get("ref_audio_path")
|
||||
self.prompt_text = config.get("prompt_text")
|
||||
self.prompt_lang = config.get("prompt_lang", "zh")
|
||||
self.top_k = config.get("top_k", 5)
|
||||
self.top_p = config.get("top_p", 1)
|
||||
self.temperature = config.get("temperature", 1)
|
||||
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))
|
||||
self.text_split_method = config.get("text_split_method", "cut0")
|
||||
self.batch_size = config.get("batch_size", 1)
|
||||
self.batch_threshold = config.get("batch_threshold", 0.75)
|
||||
self.split_bucket = config.get("split_bucket", True)
|
||||
self.return_fragment = config.get("return_fragment", False)
|
||||
self.speed_factor = config.get("speed_factor", 1.0)
|
||||
self.streaming_mode = config.get("streaming_mode", False)
|
||||
self.seed = config.get("seed", -1)
|
||||
self.parallel_infer = config.get("parallel_infer", True)
|
||||
self.repetition_penalty = config.get("repetition_penalty", 1.35)
|
||||
self.batch_size = int(config.get("batch_size", 1))
|
||||
self.batch_threshold = float(config.get("batch_threshold", 0.75))
|
||||
self.split_bucket = bool(config.get("split_bucket", True))
|
||||
self.return_fragment = bool(config.get("return_fragment", False))
|
||||
self.speed_factor = float(config.get("speed_factor", 1.0))
|
||||
self.streaming_mode = bool(config.get("streaming_mode", False))
|
||||
self.seed = int(config.get("seed", -1))
|
||||
self.parallel_infer = bool(config.get("parallel_infer", True))
|
||||
self.repetition_penalty = float(config.get("repetition_penalty", 1.35))
|
||||
self.aux_ref_audio_paths = config.get("aux_ref_audio_paths", [])
|
||||
|
||||
def generate_filename(self, extension=".wav"):
|
||||
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}")
|
||||
return os.path.join(
|
||||
self.output_file,
|
||||
f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}",
|
||||
)
|
||||
|
||||
async def text_to_speak(self, text, output_file):
|
||||
request_json = {
|
||||
@@ -56,7 +60,7 @@ class TTSProvider(TTSProviderBase):
|
||||
"streaming_mode": self.streaming_mode,
|
||||
"seed": self.seed,
|
||||
"parallel_infer": self.parallel_infer,
|
||||
"repetition_penalty": self.repetition_penalty
|
||||
"repetition_penalty": self.repetition_penalty,
|
||||
}
|
||||
|
||||
resp = requests.post(self.url, json=request_json)
|
||||
@@ -64,4 +68,6 @@ class TTSProvider(TTSProviderBase):
|
||||
with open(output_file, "wb") as file:
|
||||
file.write(resp.content)
|
||||
else:
|
||||
logger.bind(tag=TAG).error(f"GPT_SoVITS_V2 TTS请求失败: {resp.status_code} - {resp.text}")
|
||||
logger.bind(tag=TAG).error(
|
||||
f"GPT_SoVITS_V2 TTS请求失败: {resp.status_code} - {resp.text}"
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ from core.providers.tts.base import TTSProviderBase
|
||||
TAG = __name__
|
||||
logger = setup_logging()
|
||||
|
||||
|
||||
class TTSProvider(TTSProviderBase):
|
||||
def __init__(self, config, delete_audio_file):
|
||||
super().__init__(config, delete_audio_file)
|
||||
@@ -16,18 +17,20 @@ class TTSProvider(TTSProviderBase):
|
||||
self.prompt_text = config.get("prompt_text")
|
||||
self.prompt_language = config.get("prompt_language")
|
||||
self.text_language = config.get("text_language", "audo")
|
||||
self.top_k = config.get("top_k", 15)
|
||||
self.top_p = config.get("top_p", 1.0)
|
||||
self.temperature = config.get("temperature", 1.0)
|
||||
self.cut_punc = config.get("cut_punc","")
|
||||
self.speed = config.get("speed", 1.0)
|
||||
self.inp_refs = config.get("inp_refs",[])
|
||||
self.sample_steps = config.get("sample_steps",32)
|
||||
self.if_sr = config.get("if_sr",False)
|
||||
|
||||
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))
|
||||
self.cut_punc = config.get("cut_punc", "")
|
||||
self.speed = float(config.get("speed", 1.0))
|
||||
self.inp_refs = config.get("inp_refs", [])
|
||||
self.sample_steps = int(config.get("sample_steps", 32))
|
||||
self.if_sr = bool(config.get("if_sr", False))
|
||||
|
||||
def generate_filename(self, extension=".wav"):
|
||||
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}")
|
||||
return os.path.join(
|
||||
self.output_file,
|
||||
f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}",
|
||||
)
|
||||
|
||||
async def text_to_speak(self, text, output_file):
|
||||
request_params = {
|
||||
@@ -51,4 +54,6 @@ class TTSProvider(TTSProviderBase):
|
||||
with open(output_file, "wb") as file:
|
||||
file.write(resp.content)
|
||||
else:
|
||||
logger.bind(tag=TAG).error(f"GPT_SoVITS_V3 TTS请求失败: {resp.status_code} - {resp.text}")
|
||||
logger.bind(tag=TAG).error(
|
||||
f"GPT_SoVITS_V3 TTS请求失败: {resp.status_code} - {resp.text}"
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ class TTSProvider(TTSProviderBase):
|
||||
else:
|
||||
self.voice = config.get("voice", "alloy")
|
||||
self.response_format = "wav"
|
||||
self.speed = config.get("speed", 1.0)
|
||||
self.speed = float(config.get("speed", 1.0))
|
||||
self.output_file = config.get("output_dir", "tmp/")
|
||||
check_model_key("TTS", self.api_key)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class TTSProvider(TTSProviderBase):
|
||||
self.voice = config.get("voice")
|
||||
self.response_format = config.get("response_format")
|
||||
self.sample_rate = config.get("sample_rate")
|
||||
self.speed = config.get("speed")
|
||||
self.speed = float(config.get("speed"))
|
||||
self.gain = config.get("gain")
|
||||
|
||||
self.host = "api.siliconflow.cn"
|
||||
|
||||
@@ -19,7 +19,7 @@ class TTSProvider(TTSProviderBase):
|
||||
if config.get("private_voice"):
|
||||
self.voice = config.get("private_voice")
|
||||
else:
|
||||
self.voice = config.get("voice")
|
||||
self.voice = int(config.get("voice"))
|
||||
self.api_url = "https://tts.tencentcloudapi.com" # 正确的API端点
|
||||
self.region = config.get("region")
|
||||
self.output_file = config.get("output_dir")
|
||||
|
||||
@@ -20,13 +20,13 @@ class TTSProvider(TTSProviderBase):
|
||||
self.voice_id = int(config.get("voice_id", 1695))
|
||||
self.token = config.get("token")
|
||||
self.to_lang = config.get("to_lang")
|
||||
self.volume_change_dB = config.get("volume_change_dB", 0)
|
||||
self.speed_factor = config.get("speed_factor", 1)
|
||||
self.stream = config.get("stream", False)
|
||||
self.volume_change_dB = int(config.get("volume_change_dB", 0))
|
||||
self.speed_factor = int(config.get("speed_factor", 1))
|
||||
self.stream = bool(config.get("stream", False))
|
||||
self.output_file = config.get("output_dir")
|
||||
self.pitch_factor = config.get("pitch_factor", 0)
|
||||
self.pitch_factor = int(config.get("pitch_factor", 0))
|
||||
self.format = config.get("format", "mp3")
|
||||
self.emotion = config.get("emotion", 1)
|
||||
self.emotion = int(config.get("emotion", 1))
|
||||
self.header = {"Content-Type": "application/json"}
|
||||
|
||||
def generate_filename(self, extension=".mp3"):
|
||||
|
||||
@@ -21,8 +21,8 @@ class VADProvider(VADProviderBase):
|
||||
(get_speech_timestamps, _, _, _, _) = self.utils
|
||||
|
||||
self.decoder = opuslib_next.Decoder(16000, 1)
|
||||
self.vad_threshold = config.get("threshold")
|
||||
self.silence_threshold_ms = config.get("min_silence_duration_ms")
|
||||
self.vad_threshold = float(config.get("threshold", 0.5))
|
||||
self.silence_threshold_ms = int(config.get("min_silence_duration_ms", 1000))
|
||||
|
||||
def is_vad(self, conn, opus_packet):
|
||||
try:
|
||||
|
||||
@@ -230,7 +230,7 @@ def initialize_modules(
|
||||
modules["tts"] = tts.create_instance(
|
||||
tts_type,
|
||||
config["TTS"][config["selected_module"]["TTS"]],
|
||||
config["delete_audio"],
|
||||
bool(config.get("delete_audio", True)),
|
||||
)
|
||||
logger.bind(tag=TAG).info(f"初始化组件: tts成功")
|
||||
|
||||
@@ -294,7 +294,7 @@ def initialize_modules(
|
||||
modules["asr"] = asr.create_instance(
|
||||
asr_type,
|
||||
config["ASR"][config["selected_module"]["ASR"]],
|
||||
config["delete_audio"],
|
||||
bool(config.get("delete_audio", True)),
|
||||
)
|
||||
logger.bind(tag=TAG).info(f"初始化组件: asr成功")
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class WebSocketServer:
|
||||
async def start(self):
|
||||
server_config = self.config["server"]
|
||||
host = server_config["ip"]
|
||||
port = server_config["port"]
|
||||
port = int(server_config.get("port", 8000))
|
||||
|
||||
self.logger.bind(tag=TAG).info(
|
||||
"Server is running at ws://{}:{}/xiaozhi/v1/", get_local_ip(), port
|
||||
|
||||
@@ -1,25 +1,17 @@
|
||||
# Docker安装全模块
|
||||
|
||||
# 1、安装mysql
|
||||
# |- 如果本机已经安装了MySQL,可以直接在数据库中创建名为`xiaozhi_esp32_server`的数据库。
|
||||
# |- 如果还没有MySQL,你可以通过docker安装mysql,执行以下一句话
|
||||
# |- docker run --name xiaozhi-esp32-server-db -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -e MYSQL_DATABASE=xiaozhi_esp32_server -e MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci" -d mysql:latest
|
||||
# |- 记得修改下方SPRING_DATASOURCE_DRUID_URL的IP,ip不能写127.0.0.1或localhost,否则容器无法访问,要写你电脑局域网ip
|
||||
|
||||
# 2、安装redis
|
||||
# |- 如果本机已经安装了Redis,看一下你安装的redis端口、密码,然后修改下方redis的地址和端口
|
||||
# |- 如果还没有Redis,你可以通过docker安装redis,执行以下一句话
|
||||
# |- docker run --name xiaozhi-esp32-server-redis -d -p 6379:6379 redis
|
||||
# |- 记得修改SPRING_DATA_REDIS_HOST的IP,ip不能写127.0.0.1或localhost,否则容器无法访问,要写你电脑局域网ip
|
||||
|
||||
|
||||
version: '3'
|
||||
services:
|
||||
# Server模块
|
||||
xiaozhi-esp32-server:
|
||||
image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest
|
||||
container_name: xiaozhi-esp32-server
|
||||
depends_on:
|
||||
- xiaozhi-esp32-server-db
|
||||
- xiaozhi-esp32-server-redis
|
||||
restart: always
|
||||
networks:
|
||||
- default
|
||||
ports:
|
||||
# ws服务端
|
||||
- "8000:8000"
|
||||
@@ -38,14 +30,55 @@ services:
|
||||
image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:web_latest
|
||||
container_name: xiaozhi-esp32-server-web
|
||||
restart: always
|
||||
networks:
|
||||
- default
|
||||
depends_on:
|
||||
xiaozhi-esp32-server-db:
|
||||
condition: service_healthy
|
||||
xiaozhi-esp32-server-redis:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
# 智控台
|
||||
- "8002:8002"
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
##记得改mysql和redis IP 密码
|
||||
- SPRING_DATASOURCE_DRUID_URL=jdbc:mysql://192.168.1.25:3306/xiaozhi_esp32_server?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
|
||||
- SPRING_DATASOURCE_DRUID_URL=jdbc:mysql://xiaozhi-esp32-server-db:3306/xiaozhi_esp32_server?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&connectTimeout=30000&socketTimeout=30000&autoReconnect=true&failOverReadOnly=false&maxReconnects=10
|
||||
- SPRING_DATASOURCE_DRUID_USERNAME=root
|
||||
- SPRING_DATASOURCE_DRUID_PASSWORD=123456
|
||||
- SPRING_DATA_REDIS_HOST=192.168.1.25
|
||||
- SPRING_DATA_REDIS_HOST=xiaozhi-esp32-server-redis
|
||||
- SPRING_DATA_REDIS_PORT=6379
|
||||
xiaozhi-esp32-server-db:
|
||||
image: mysql:latest
|
||||
container_name: xiaozhi-esp32-server-db
|
||||
healthcheck:
|
||||
test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
|
||||
timeout: 45s
|
||||
interval: 10s
|
||||
retries: 10
|
||||
restart: always
|
||||
networks:
|
||||
- default
|
||||
expose:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- ./mysql/data:/var/lib/mysql
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- MYSQL_ROOT_PASSWORD=123456
|
||||
- MYSQL_DATABASE=xiaozhi_esp32_server
|
||||
- MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci"
|
||||
xiaozhi-esp32-server-redis:
|
||||
image: redis
|
||||
expose:
|
||||
- 6379
|
||||
container_name: xiaozhi-esp32-server-redis
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
networks:
|
||||
- default
|
||||
networks:
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user