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 -
+ 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: ` +
+ + xiaozhi-esp32-server +
+ ` +}; + +const AppFooter = { + template: ` +
+ © 2025 xiaozhi-esp32-server +
+ ` +}; + +// 初始化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 -
+ xiaozhi-esp32-server + - + +
+ 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