From aa71968a91f8f8c93fb4853fdbe266d226a3fb95 Mon Sep 17 00:00:00 2001
From: hrz <1710360675@qq.com>
Date: Sat, 15 Feb 2025 18:54:29 +0800
Subject: [PATCH 1/6] =?UTF-8?q?update:=E6=8B=86=E5=88=86=E9=A1=B5=E9=9D=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
manager/static/css/common.css | 66 +++++++++++++++++++++
manager/static/images/favicon.ico | Bin 0 -> 1150 bytes
manager/static/index.html | 70 +++--------------------
manager/static/js/common.js | 28 +++++++++
manager/static/login.html | 92 ++++--------------------------
5 files changed, 115 insertions(+), 141 deletions(-)
create mode 100644 manager/static/css/common.css
create mode 100644 manager/static/images/favicon.ico
diff --git a/manager/static/css/common.css b/manager/static/css/common.css
new file mode 100644
index 00000000..ac0a6498
--- /dev/null
+++ b/manager/static/css/common.css
@@ -0,0 +1,66 @@
+/* common.css */
+/* 基础样式 */
+body {
+ margin: 0;
+ padding: 0;
+ background: linear-gradient(135deg, #1a1f25 0%, #0d1117 100%);
+ min-height: 100vh;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+}
+
+/* 头部组件 */
+.app-header {
+ background: rgba(13, 17, 23, 0.8);
+ backdrop-filter: blur(10px);
+ color: #fff;
+ padding: 1.5rem 2rem;
+ font-size: 1.4rem;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ position: relative;
+ z-index: 1;
+}
+
+.header-logo {
+ width: 32px;
+ height: 32px;
+ background: linear-gradient(45deg, #00c6fb 0%, #005bea 100%);
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: bold;
+}
+
+/* 页脚组件 */
+.app-footer {
+ text-align: center;
+ color: rgba(255, 255, 255, 0.6);
+ font-size: 13px;
+ padding: 20px;
+ position: fixed;
+ bottom: 0;
+ width: 100%;
+ z-index: 1;
+}
+
+/* 背景动画 */
+.animated-bg {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: -1;
+ background: linear-gradient(-45deg, #1a1f25, #0d1117, #162030, #1c1c1c);
+ background-size: 400% 400%;
+ animation: gradient 15s ease infinite;
+}
+
+@keyframes gradient {
+ 0% { background-position: 0% 50% }
+ 50% { background-position: 100% 50% }
+ 100% { background-position: 0% 50% }
+}
\ No newline at end of file
diff --git a/manager/static/images/favicon.ico b/manager/static/images/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..2e4ba64eab3093e6d7394eeda180acafbaccf02e
GIT binary patch
literal 1150
zcmb7^+e;L29K|Q-4^Ve^_Tu#^iqKy2#e$%Rs2-!o!k``sq6jV0u6ENdR%V%%Wh$1T
zW~*stmIn1uBtfPbfl5KJ*V*0Kon7~KI&(=&=%HDDvpc_;&+nY?cg}AGVF^DO83NA*
z!tSMlAPR!8oP$%G(dYRk2)wuWP=*Si4CX`W&qt&$50NK%2zTcq+?flxBL{MO4nl3&
z2;I*{@U8>FI}QXI?Fii9sJ9zzE+WosM0&Cje*6dffNZNBp=K*tJA$`u2;3COS~XUx
zvr#t>(UJ9tz1s=p<)#H)m2Km?Pd7su-U0bh2}13~hzyj{*9J&+c1V|Pnr>`tKafr!
z>3L7@E9lvw>B=32NP0g3$rvKfN}-G%gnG
#Kf5$GC4ED%xsd!}o&VYk|0O&8=dF6Ly7G~j>C*0{6&F(SEK;Faq!qXR{CG3{
zTDW$Fem&)c|AHO9bE01FLp!2x_OON|(sMz?r~5eik&cI{KZkJdHb}LZP+l4Q7bkqx
zHuz49#yfJ*$7-a4(@00X^uG>r_eQQ2VoxRzxV4hCnh+T__(}3-Z17fD{(8UhsaDOO
zGFnDI$F$$E4-M3|vR3jh41U51?
K?Oon>lGILee*>aY)6H_&Eltei{5VBK>9|JZSLaPI!)r@Kl%=
zWzvS&$41S!dWycSg}>UO_1RlRen97ADqktWeaK|wnVD{2o~|*cEhVh8oLP^&6URf*8K
-
+
+
@@ -8,39 +8,11 @@
+
+
小智-server
-
+
xiaozhi-esp32-server
配置角色:
@@ -123,9 +73,7 @@
注意:保存配置后,需要重启设备,新的配置才会生效。
-
+
diff --git a/manager/static/js/common.js b/manager/static/js/common.js
index 0f888e11..790e6b43 100644
--- a/manager/static/js/common.js
+++ b/manager/static/js/common.js
@@ -37,4 +37,32 @@ function get(api, fn) {
}
fn(data);
});
+}
+
+// 注册全局组件
+const AppHeader = {
+ template: `
+
+ `
+};
+
+const AppFooter = {
+ template: `
+
+ `
+};
+
+// 初始化Vue应用的通用配置
+function createVueApp(options) {
+ const app = Vue.createApp({
+ ...options,
+ components: { AppHeader, AppFooter }
+ });
+ app.use(ElementPlus);
+ return app;
}
\ No newline at end of file
diff --git a/manager/static/login.html b/manager/static/login.html
index 0a622146..6621a134 100644
--- a/manager/static/login.html
+++ b/manager/static/login.html
@@ -1,46 +1,18 @@
-
-
+
+
+
+
小智-server
-
+
xiaozhi-esp32-server
+
安全验证
@@ -152,10 +84,10 @@
-
+
+
+
From 2baba4394660b9844fe622b279ed4bb5e21621e8 Mon Sep 17 00:00:00 2001
From: hrz <1710360675@qq.com>
Date: Sat, 15 Feb 2025 19:38:32 +0800
Subject: [PATCH 2/6] =?UTF-8?q?update:=E5=AE=8C=E6=88=90=E5=9C=A8web?=
=?UTF-8?q?=E7=AB=AF=EF=BC=8C=E4=BF=AE=E6=94=B9=E6=8F=90=E7=A4=BA=E8=AF=8D?=
=?UTF-8?q?=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
config/settings.py | 22 ++++++++++++++++++----
core/connection.py | 2 +-
manager/api/prompt.py | 11 ++++++++---
manager/static/css/common.css | 29 ++++++++++++++++++++++++++---
manager/static/index.html | 33 +++++++++++++++++++++++++++------
requirements.txt | 3 ++-
6 files changed, 82 insertions(+), 18 deletions(-)
diff --git a/config/settings.py b/config/settings.py
index 9bdab1fe..d1f5a984 100644
--- a/config/settings.py
+++ b/config/settings.py
@@ -1,15 +1,29 @@
import os
import argparse
+from ruamel.yaml import YAML
from core.utils.util import read_config, get_project_dir
+def get_config_file():
+ default_config_file = "config.yaml"
+ # 判断是否存在私有的配置文件
+ if os.path.exists(get_project_dir() + "." + default_config_file):
+ default_config_file = "." + default_config_file
+ return default_config_file
+
+
def load_config():
"""加载配置文件"""
parser = argparse.ArgumentParser(description="Server configuration")
- default_config_file = "config.yaml"
- # 判断是否存在私有的配置文件
- if os.path.exists(get_project_dir() + "." + default_config_file):
- default_config_file = "." + default_config_file
+ default_config_file = get_config_file()
parser.add_argument("--config_path", type=str, default=default_config_file)
args = parser.parse_args()
return read_config(args.config_path)
+
+
+def update_config(config):
+ yaml = YAML()
+ yaml.preserve_quotes = True
+ """将配置保存到YAML文件"""
+ with open(get_config_file(), 'w') as f:
+ yaml.dump(config, f)
diff --git a/core/connection.py b/core/connection.py
index c1cbe4b8..972e9ae5 100644
--- a/core/connection.py
+++ b/core/connection.py
@@ -15,7 +15,7 @@ from core.handle.textHandle import handleTextMessage
from core.utils.util import get_string_no_punctuation_or_emoji
from concurrent.futures import ThreadPoolExecutor, TimeoutError
from core.handle.audioHandle import handleAudioMessage, sendAudioMessage
-from .auth import AuthMiddleware, AuthenticationError
+from core.auth import AuthMiddleware, AuthenticationError
class ConnectionHandler:
diff --git a/manager/api/prompt.py b/manager/api/prompt.py
index a9ce8238..1fe5aa08 100644
--- a/manager/api/prompt.py
+++ b/manager/api/prompt.py
@@ -1,5 +1,7 @@
import logging
from aiohttp import web
+from config.settings import update_config
+from ruamel.yaml.scalarstring import PreservedScalarString
from manager.api.auth import verify_token
from manager.api.response import response_unauthorized, response_success, response_error
@@ -28,10 +30,13 @@ class PromptApi:
if 'prompt' not in data:
return response_success()
- self.config['prompt'] = data['prompt']
- # TODO 保存到配置文件
- return response_success()
+ # 使用PreservedScalarString保留多行文本格式
+ self.config['prompt'] = PreservedScalarString(data['prompt'])
+ # 保存到配置文件
+ update_config(self.config)
+
+ return response_success()
except Exception as e:
logger.error(f"Failed to update prompt: {e}")
return response_error(str(e))
diff --git a/manager/static/css/common.css b/manager/static/css/common.css
index ac0a6498..145ef688 100644
--- a/manager/static/css/common.css
+++ b/manager/static/css/common.css
@@ -60,7 +60,30 @@ body {
}
@keyframes gradient {
- 0% { background-position: 0% 50% }
- 50% { background-position: 100% 50% }
- 100% { background-position: 0% 50% }
+ 0% {
+ background-position: 0% 50%
+ }
+ 50% {
+ background-position: 100% 50%
+ }
+ 100% {
+ background-position: 0% 50%
+ }
+}
+
+.user-menu {
+ position: fixed;
+ top: 23px;
+ right: 32px;
+ z-index: 1000;
+ cursor: pointer;
+}
+
+.user-info {
+ align-items: center;
+}
+
+.user-name {
+ font-size: 14px;
+ color: #606266;
}
\ No newline at end of file
diff --git a/manager/static/index.html b/manager/static/index.html
index 8da76c5f..2e087027 100644
--- a/manager/static/index.html
+++ b/manager/static/index.html
@@ -51,15 +51,26 @@
xiaozhi-esp32-server
-
-
配置角色:
-
cc:ba:97:11:a6:ac
+
+
角色介绍
@@ -67,7 +78,7 @@
保存配置
- 重置
+ 重置
注意:保存配置后,需要重启设备,新的配置才会生效。
@@ -121,7 +132,17 @@
});
}
});
- }
+ },
+ // 新增退出处理方法
+ handleCommand(command) {
+ if (command === 'logout') {
+ this.handleLogout();
+ }
+ },
+ handleLogout() {
+ localStorage.removeItem('token');
+ location.href = 'login.html';
+ },
},
mounted() {
this.loadPrompt();
diff --git a/requirements.txt b/requirements.txt
index 77523f3e..c0fa17ef 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -11,4 +11,5 @@ openai==1.61.0
google-generativeai==0.8.4
edge_tts==7.0.0
httpx==0.27.2
-aiohttp==3.9.3
\ No newline at end of file
+aiohttp==3.9.3
+ruamel.yaml==0.18.10
\ No newline at end of file
From 2fe4f44529c9b6c0cde7f75e700b4b72ba520a37 Mon Sep 17 00:00:00 2001
From: hrz <1710360675@qq.com>
Date: Sat, 15 Feb 2025 20:31:55 +0800
Subject: [PATCH 3/6] =?UTF-8?q?update=EF=BC=9A=E4=BC=98=E5=8C=96http=5Fser?=
=?UTF-8?q?ver=E4=BB=A3=E7=A0=81=E8=B7=AF=E5=BE=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app.py | 4 +-
core/{server.py => websocket_server.py} | 0
{core => manager}/http_server.py | 0
manager/static/css/common.css | 12 ------
static/index.html | 53 -------------------------
5 files changed, 2 insertions(+), 67 deletions(-)
rename core/{server.py => websocket_server.py} (100%)
rename {core => manager}/http_server.py (100%)
delete mode 100644 static/index.html
diff --git a/app.py b/app.py
index 2e1dbeb0..befb5808 100644
--- a/app.py
+++ b/app.py
@@ -1,8 +1,8 @@
import asyncio
from config.logger import setup_logging
from config.settings import load_config
-from core.server import WebSocketServer
-from core.http_server import ConfigServer
+from core.websocket_server import WebSocketServer
+from manager.http_server import ConfigServer
async def main():
setup_logging() # 最先初始化日志
diff --git a/core/server.py b/core/websocket_server.py
similarity index 100%
rename from core/server.py
rename to core/websocket_server.py
diff --git a/core/http_server.py b/manager/http_server.py
similarity index 100%
rename from core/http_server.py
rename to manager/http_server.py
diff --git a/manager/static/css/common.css b/manager/static/css/common.css
index 145ef688..b6f7287e 100644
--- a/manager/static/css/common.css
+++ b/manager/static/css/common.css
@@ -56,20 +56,8 @@ body {
z-index: -1;
background: linear-gradient(-45deg, #1a1f25, #0d1117, #162030, #1c1c1c);
background-size: 400% 400%;
- animation: gradient 15s ease infinite;
}
-@keyframes gradient {
- 0% {
- background-position: 0% 50%
- }
- 50% {
- background-position: 100% 50%
- }
- 100% {
- background-position: 0% 50%
- }
-}
.user-menu {
position: fixed;
diff --git a/static/index.html b/static/index.html
deleted file mode 100644
index 8c32c57c..00000000
--- a/static/index.html
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
小智提示词配置
-
-
-
-
-
小智提示词配置
-
-
-
-
-
-
-
-
\ No newline at end of file
From 9feb64486d017c4c8284afbb2246a913596e3421 Mon Sep 17 00:00:00 2001
From: fofo
Date: Sat, 15 Feb 2025 20:33:05 +0800
Subject: [PATCH 4/6] add tts provider for fish-speech
---
config.yaml | 21 +++++
core/providers/tts/fishspeech.py | 152 +++++++++++++++++++++++++++++++
2 files changed, 173 insertions(+)
create mode 100644 core/providers/tts/fishspeech.py
diff --git a/config.yaml b/config.yaml
index 749b5234..bd080254 100644
--- a/config.yaml
+++ b/config.yaml
@@ -137,3 +137,24 @@ TTS:
output_file: tmp/
access_token: 你的硅基流动API密钥
response_format: wav
+ FishSpeech:
+ # 定义TTS API类型
+ type: fishspeech
+ output_file: tmp/
+ response_format: wav
+ reference_id: null
+ reference_audio: ["/tmp/test.wav",]
+ reference_text: ["你弄来这些吟词宴曲来看,还是这些混话来欺负我。",]
+ normalize: true
+ max_new_tokens: 1024
+ chunk_length: 200
+ top_p: 0.7
+ repetition_penalty: 1.2
+ temperature: 0.7
+ streaming: false
+ use_memory_cache: "on"
+ seed: null
+ channels: 1
+ rate: 44100
+ api_key: "YOUR_API_KEY"
+ api_url: "http://127.0.0.1:8080/v1/tts"
\ No newline at end of file
diff --git a/core/providers/tts/fishspeech.py b/core/providers/tts/fishspeech.py
new file mode 100644
index 00000000..c7121498
--- /dev/null
+++ b/core/providers/tts/fishspeech.py
@@ -0,0 +1,152 @@
+
+import base64
+import os
+import uuid
+import requests
+import ormsgpack
+from pathlib import Path
+from pydantic import BaseModel, Field, conint, model_validator
+from typing_extensions import Annotated
+from datetime import datetime
+from typing import Literal
+# from base import TTSProviderBase
+from core.providers.tts.base import TTSProviderBase
+
+
+class ServeReferenceAudio(BaseModel):
+ audio: bytes
+ text: str
+
+ @model_validator(mode="before")
+ def decode_audio(cls, values):
+ audio = values.get("audio")
+ if (
+ isinstance(audio, str) and len(audio) > 255
+ ): # Check if audio is a string (Base64)
+ try:
+ values["audio"] = base64.b64decode(audio)
+ except Exception as e:
+ # If the audio is not a valid base64 string, we will just ignore it and let the server handle it
+ pass
+ return values
+
+ def __repr__(self) -> str:
+ return f"ServeReferenceAudio(text={self.text!r}, audio_size={len(self.audio)})"
+
+class ServeTTSRequest(BaseModel):
+ text: str
+ chunk_length: Annotated[int, conint(ge=100, le=300, strict=True)] = 200
+ # Audio format
+ format: Literal["wav", "pcm", "mp3"] = "wav"
+ # References audios for in-context learning
+ references: list[ServeReferenceAudio] = []
+ # Reference id
+ # For example, if you want use https://fish.audio/m/7f92f8afb8ec43bf81429cc1c9199cb1/
+ # Just pass 7f92f8afb8ec43bf81429cc1c9199cb1
+ reference_id: str | None = None
+ seed: int | None = None
+ use_memory_cache: Literal["on", "off"] = "off"
+ # Normalize text for en & zh, this increase stability for numbers
+ normalize: bool = True
+ # not usually used below
+ streaming: bool = False
+ max_new_tokens: int = 1024
+ top_p: Annotated[float, Field(ge=0.1, le=1.0, strict=True)] = 0.7
+ repetition_penalty: Annotated[float, Field(ge=0.9, le=2.0, strict=True)] = 1.2
+ temperature: Annotated[float, Field(ge=0.1, le=1.0, strict=True)] = 0.7
+
+ class Config:
+ # Allow arbitrary types for pytorch related types
+ arbitrary_types_allowed = True
+
+
+def audio_to_bytes(file_path):
+ if not file_path or not Path(file_path).exists():
+ return None
+ with open(file_path, "rb") as wav_file:
+ wav = wav_file.read()
+ return wav
+
+def read_ref_text(ref_text):
+ path = Path(ref_text)
+ if path.exists() and path.is_file():
+ with path.open("r", encoding="utf-8") as file:
+ return file.read()
+ return ref_text
+
+class TTSProvider(TTSProviderBase):
+
+ def __init__(self, config, delete_audio_file):
+ super().__init__(config, delete_audio_file)
+
+ self.reference_id = config.get("reference_id")
+ 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.api_key = config.get("api_key","YOUR_API_KEY")
+ 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.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")
+
+ 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):
+ # Prepare reference data
+ byte_audios = [audio_to_bytes(ref_audio) for ref_audio in self.reference_audio]
+ ref_texts = [read_ref_text(ref_text) for ref_text in self.reference_text]
+
+ data = {
+ "text": text,
+ "references": [
+ ServeReferenceAudio(
+ audio=audio if audio else b"", text=text
+ )
+ for text, audio in zip(ref_texts, byte_audios)
+ ],
+ "reference_id": self.reference_id,
+ "normalize": self.normalize,
+ "format": self.format,
+ "max_new_tokens": self.max_new_tokens,
+ "chunk_length": self.chunk_length,
+ "top_p": self.top_p,
+ "repetition_penalty": self.repetition_penalty,
+ "temperature": self.temperature,
+ "streaming": self.streaming,
+ "use_memory_cache": self.use_memory_cache,
+ "seed": self.seed,
+ }
+
+ pydantic_data = ServeTTSRequest(**data)
+
+ response = requests.post(
+ self.api_url,
+ data=ormsgpack.packb(pydantic_data, option=ormsgpack.OPT_SERIALIZE_PYDANTIC),
+ headers={
+ "Authorization": f"Bearer {self.api_key}",
+ "Content-Type": "application/msgpack",
+ },
+ )
+
+ if response.status_code == 200:
+ audio_content = response.content
+
+ with open(output_file, "wb") as audio_file:
+ audio_file.write(audio_content)
+
+
+
+ else:
+ print(f"Request failed with status code {response.status_code}")
+ print(response.json())
+
+
From 1293f2a6eae4a66cdff48ac2e55f78340ebe4c03 Mon Sep 17 00:00:00 2001
From: fofo
Date: Sat, 15 Feb 2025 20:37:01 +0800
Subject: [PATCH 5/6] add ormsgpack==1.7.0 for fish-speech
---
requirements.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index 77523f3e..fb127028 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -11,4 +11,5 @@ openai==1.61.0
google-generativeai==0.8.4
edge_tts==7.0.0
httpx==0.27.2
-aiohttp==3.9.3
\ No newline at end of file
+aiohttp==3.9.3
+ormsgpack==1.7.0
From b560160035fbe7fb439b1b68e94ca42025a5889d Mon Sep 17 00:00:00 2001
From: hrz <1710360675@qq.com>
Date: Sat, 15 Feb 2025 21:11:37 +0800
Subject: [PATCH 6/6] =?UTF-8?q?update:FishSpeech=E9=83=A8=E7=BD=B2?=
=?UTF-8?q?=E6=96=B9=E5=BC=8F=E6=96=B9=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
config.yaml | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/config.yaml b/config.yaml
index bd080254..1c521b0b 100644
--- a/config.yaml
+++ b/config.yaml
@@ -1,3 +1,7 @@
+# 如果您是一名开发者,建议阅读以下内容。如果不是开发者,可以忽略这部分内容。
+# 在开发中,建议将【config.yaml】复制一份,改成【.config.yaml】。 系统会优先读取【.config.yaml】文件的配置。
+# 这样做,可以避免在提交代码的时候,错误地提交密钥信息,保护您的密钥安全。
+
# 服务器基础配置(Basic server configuration)
server:
# 服务器监听地址和端口(Server listening address and port)
@@ -139,6 +143,13 @@ TTS:
response_format: wav
FishSpeech:
# 定义TTS API类型
+ #启动tts方法:
+ #python -m tools.api_server
+ #--listen 0.0.0.0:8080
+ #--llama-checkpoint-path "checkpoints/fish-speech-1.5"
+ #--decoder-checkpoint-path "checkpoints/fish-speech-1.5/firefly-gan-vq-fsq-8x1024-21hz-generator.pth"
+ #--decoder-config-name firefly_gan_vq
+ #--compile
type: fishspeech
output_file: tmp/
response_format: wav