Merge branch 'main' into dev-manager-api-local

This commit is contained in:
pengzhisheng
2025-03-09 02:24:58 +08:00
17 changed files with 246 additions and 4943 deletions
+7
View File
@@ -0,0 +1,7 @@
.git
__pycache__
*.pyc
.env
Dockerfile
tmp/
data/
+22 -8
View File
@@ -7,30 +7,31 @@ on:
jobs:
release:
name: Release Docker image
name: Release Docker images
runs-on: ubuntu-latest
permissions:
packages: write
contents: write
id-token: write
issues: write
steps:
- name: Check Disk Space
run: |
df -h
docker system df
- name: Clean up Docker resources
run: |
docker system prune -af
docker builder prune -af
- name: Check out the repo
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to the GitHub Container Registry
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
@@ -42,13 +43,26 @@ jobs:
run: |
echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
- name: Build and push Docker image
id: build_push
# 构建 xiaozhi-server 镜像
- name: Build and push xiaozhi-server
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile-server
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ env.VERSION }}
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:server_${{ env.VERSION }}
ghcr.io/${{ github.repository }}:server_latest
platforms: linux/amd64,linux/arm64
# 构建 manager-api 镜像
- name: Build and push manager-web
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile-web
push: true
tags: |
ghcr.io/${{ github.repository }}:web_${{ env.VERSION }}
ghcr.io/${{ github.repository }}:web_latest
platforms: linux/amd64,linux/arm64
Executable → Regular
+1 -1
View File
@@ -23,7 +23,7 @@ RUN apt-get update && \
COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
# 复制应用代码
COPY main/xiaozhi-server/ .
COPY main/xiaozhi-server .
# 启动应用
CMD ["python", "app.py"]
+40
View File
@@ -0,0 +1,40 @@
# 第一阶段:构建Vue前端
FROM node:18 as web-builder
WORKDIR /app
COPY main/manager-web/package*.json ./
RUN npm install
COPY main/manager-web .
RUN npm run build
# 第二阶段:构建Java后端
FROM maven:3-eclipse-temurin-21-alpine as api-builder
WORKDIR /app
COPY main/manager-api/pom.xml .
COPY main/manager-api/src ./src
RUN mvn clean package -Dmaven.test.skip=true
# 第三阶段:构建最终镜像
FROM eclipse-temurin:21-jdk-jammy
# 安装Nginx并清理缓存
RUN apt-get update && \
apt-get install -y nginx && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# 配置Nginx
COPY docs/docker/nginx.conf /etc/nginx/conf.d/default.conf
# 复制前端构建产物
COPY --from=web-builder /app/dist /usr/share/nginx/html
# 复制Java后端JAR包
COPY --from=api-builder /app/target/xiaozhi-esp32-api.jar /app/xiaozhi-esp32-api.jar
# 暴露端口
EXPOSE 8002
# 启动脚本
COPY docs/docker/start.sh /start.sh
RUN chmod +x /start.sh
CMD ["/start.sh"]
+26
View File
@@ -0,0 +1,26 @@
server {
listen 8002;
server_name localhost;
# 静态资源服务(Vue项目)
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
# API反向代理(Java项目)
location /xiaozhi-esp32-api/ {
proxy_pass http://127.0.0.1:8003;
proxy_set_header Host $host;
proxy_cookie_path /api/ /;
proxy_set_header Referer $http_referer;
proxy_set_header Cookie $http_cookie;
proxy_connect_timeout 10;
proxy_send_timeout 10;
proxy_read_timeout 10;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# 启动Java后端(docker内监听8003端口)
java -jar /app/xiaozhi-esp32-api.jar \
--server.port=8003 \
--spring.datasource.druid.url=${SPRING_DATASOURCE_DRUID_URL} \
--spring.datasource.druid.username=${SPRING_DATASOURCE_DRUID_USERNAME} \
--spring.datasource.druid.password=${SPRING_DATASOURCE_DRUID_PASSWORD} \
--spring.data.redis.host=${SPRING_DATA_REDIS_HOST} \
--spring.data.redis.port=${SPRING_DATA_REDIS_PORT} &
# 启动Nginx(前台运行保持容器存活)
nginx -g 'daemon off;'
@@ -35,8 +35,8 @@ public interface ErrorCode {
int REDIS_ERROR = 10027;
int JOB_ERROR = 10028;
int INVALID_SYMBOL = 10029;
// 密码相关错误码
int PASSWORD_LENGTH_ERROR = 10030;
int PASSWORD_WEAK_ERROR = 10031;
int DEL_MYSELF_ERROR = 10032;
-13
View File
@@ -1,13 +0,0 @@
.git
__pycache__
*.pyc
.env
Dockerfile
../docs/
tmp/
data/
LICENSE
README.md
README_en.md
manager/static
manager/static/webui/
-16
View File
@@ -1,16 +0,0 @@
# 第一阶段:构建 Python 依赖
FROM kalicyh/poetry:v3.10_xiaozhi AS builder
WORKDIR /opt/xiaozhi-esp32-server
# 同时拷贝本地环境.venv
COPY . .
# 检查是否有缺失
RUN poetry install --no-root
# 设置虚拟环境路径
ENV PATH="/app/.venv/bin:$PATH"
# 启动应用
ENTRYPOINT ["poetry", "run", "python"]
CMD ["app.py"]
+17 -1
View File
@@ -37,7 +37,8 @@ log:
data_dir: data
iot:
Speaker:
volume: 100
# 设置esp32的音量,范围0-100
volume: 80
xiaozhi:
type: hello
version: 1
@@ -258,6 +259,21 @@ TTS:
parallel_infer: true
repetition_penalty: 1.35
aux_ref_audio_paths: []
GPT_SOVITS_V3:
type: gpt_sovits_v3
url: "http://127.0.0.1:9880/tts"
output_file: tmp/
text_lang: "auto"
ref_audio_path: "caixukun.wav"
prompt_lang: "zh"
prompt_text: ""
top_k: 5
top_p: 1
temperature: 1
sample_steps: 16
media_type: "wav"
streaming_mode: false
threshold: 30
MinimaxTTS:
# Minimax语音合成服务,需要先在minimax平台创建账户充值,并获取登录信息
# 平台地址:https://platform.minimaxi.com/
@@ -109,7 +109,48 @@ async def handleIotDescriptors(conn, descriptors):
default_iot_volume = conn.config["iot"]["Speaker"]["volume"]
logger.bind(tag=TAG).info(f"服务端设置音量为{default_iot_volume}")
await send_iot_conn(conn, "Speaker", "SetVolume", {"volume": default_iot_volume})
async def handleIotStatus(conn, states):
"""
处理物联网状态
示例: [{
"name":"Speaker",
"state":{
"volume":100
}
}]
states: 状态列表
"""
for state in states:
for key, value in conn.iot_descriptors.items():
if key == state["name"]:
for property_item in value.properties:
# properties为字典列表, 记录各种属性
for k, v in state["state"].items():
# state为字典, 记录各种属性的值, 是需要记录的信息
if property_item["name"] == k:
# 检查一下属性是不是相同的
if type(v) != type(property_item["value"]):
logger.bind(tag=TAG).error(f"属性{property_item['name']}的值类型不匹配")
break
else:
property_item["value"] = v
logger.bind(tag=TAG).info(f"物联网状态更新: {key} , {property_item['name']} = {v}")
break
break
async def get_iot_status(conn, name, property_name):
"""
获取物联网状态
name: 设备名称 "Speaker"
property_name: 属性名称 "volume"
返回值: 属性值, 实际的属性有int, bool和str三种类型
"""
for key, value in conn.iot_descriptors.items():
if key == name:
for property_item in value.properties:
if property_item["name"] == property_name:
return property_item["value"]
return None
async def send_iot_conn(conn, name, method_name, parameters):
"""
@@ -3,7 +3,7 @@ import json
from core.handle.abortHandle import handleAbortMessage
from core.handle.helloHandle import handleHelloMessage
from core.handle.receiveAudioHandle import startToChat
from core.handle.iotHandle import handleIotDescriptors
from core.handle.iotHandle import handleIotDescriptors, handleIotStatus
TAG = __name__
logger = setup_logging()
@@ -40,5 +40,7 @@ async def handleTextMessage(conn, message):
elif msg_json["type"] == "iot":
if "descriptors" in msg_json:
await handleIotDescriptors(conn, msg_json["descriptors"])
if "states" in msg_json:
await handleIotStatus(conn, msg_json["states"])
except json.JSONDecodeError:
await conn.websocket.send(message)
@@ -0,0 +1,52 @@
import os
import uuid
import requests
from config.logger import setup_logging
from datetime import datetime
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)
self.url = config.get("url")
self.text_lang = config.get("text_lang", "audo")
self.ref_audio_path = config.get("ref_audio_path")
self.prompt_lang = config.get("prompt_lang")
self.prompt_text = config.get("prompt_text")
self.top_k = config.get("top_k", 5)
self.top_p = config.get("top_p", 1)
self.temperature = config.get("temperature", 1)
self.sample_steps = config.get("sample_steps", 16)
self.media_type = config.get("media_type", "wav")
self.streaming_mode = config.get("streaming_mode", False)
self.threshold = config.get("threshold", 30)
def generate_filename(self, extension=".wav"):
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 = {
"text": text,
"text_lang": self.text_lang,
"ref_audio_path": self.ref_audio_path,
"prompt_lang": self.prompt_lang,
"prompt_text": self.prompt_text,
"top_k": self.top_k,
"top_p": self.top_p,
"temperature": self.temperature,
"sample_steps": self.sample_steps,
"media_type": self.media_type,
"streaming_mode": self.streaming_mode,
"threshold": self.threshold,
}
resp = requests.get(self.url, params=request_params)
if resp.status_code == 200:
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}")
+1 -1
View File
@@ -75,7 +75,7 @@ def get_string_no_punctuation_or_emoji(s):
def remove_punctuation_and_length(text):
# 全角符号和半角符号的Unicode范围
full_width_punctuations = '!"#$%&'()*+,-。/:;<=>?@[\]^_`{|}~'
half_width_punctuations = '!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~'
half_width_punctuations = r'!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~'
space = ' ' # 半角空格
full_width_space = ' ' # 全角空格
+22 -4
View File
@@ -1,7 +1,12 @@
# 如果本机已经安装了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
# 如果你的mysql账号和密码有修改过,记得修改下方数据库的账号和密码
# 记得修改下方数据库的IPip不能写127.0.0.1或localhost,否则容器无法访问,要写你电脑局域网ip
version: '3'
services:
xiaozhi-esp32-server:
image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:latest
image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest
container_name: xiaozhi-esp32-server
restart: always
security_opt:
@@ -11,10 +16,23 @@ services:
ports:
# ws服务端
- "8000:8000"
# 管理后台
- "8002:8002"
volumes:
# 配置文件目录
- ./data:/opt/xiaozhi-esp32-server/data
# 模型文件挂接,很重要
- ./models/SenseVoiceSmall/model.pt:/opt/xiaozhi-esp32-server/models/SenseVoiceSmall/model.pt
- ./models/SenseVoiceSmall/model.pt:/opt/xiaozhi-esp32-server/models/SenseVoiceSmall/model.pt
# #智控台还没开发好,还不能完全使用,会报很多错误,如果是非技术人员,请不要启用智控台服务
# xiaozhi-esp32-server-web:
# image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:web_latest
# container_name: xiaozhi-esp32-server-web
# restart: always
# ports:
# - "8002:8002"
# environment:
# - TZ=Asia/Shanghai
# ##记得改mysql和redis IP 密码
# - SPRING_DATASOURCE_DRUID_URL=jdbc:mysql://192.168.1.20:3306/xiaozhi_esp32_server?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
# - SPRING_DATASOURCE_DRUID_USERNAME=root
# - SPRING_DATASOURCE_DRUID_PASSWORD=123456
# - SPRING_DATA_REDIS_HOST=192.168.1.20
# - SPRING_DATA_REDIS_PORT=6379
-4854
View File
File diff suppressed because it is too large Load Diff
-42
View File
@@ -1,42 +0,0 @@
[tool.poetry]
name = "xiaozhi-esp32-server"
version = "0.1.0"
description = ""
authors = ["kalicyh <34980061+kaliCYH@users.noreply.github.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.10"
pyyml = "0.0.2"
torch = "2.2.2"
silero-vad = "5.1.2"
websockets = "14.2"
numpy = "1.26.4"
pydub = "0.25.1"
funasr = "1.2.3"
torchaudio = "2.2.2"
openai = "1.61.0"
google-generativeai = "0.8.4"
edge-tts = "7.0.0"
httpx = "0.27.2"
aiohttp = "3.9.3"
aiohttp-cors = "0.7.0"
ormsgpack = "1.7.0"
ruamel-yaml = "0.18.10"
setuptools = "^75.8.0"
loguru = "^0.7.3"
opuslib-next = "^1.1.2"
fastapi = {extras = ["all"], version = "^0.115.8"}
uvicorn = "^0.34.0"
pyjwt = "^2.10.1"
python-jose = {extras = ["cryptography"], version = "^3.3.0"}
bcrypt = "^4.2.1"
sqlalchemy = "^2.0.38"
pymysql = "^1.1.1"
asyncpg = "^0.30.0"
onnxruntime = "1.19.2"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"