mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
Merge branch 'xinnan-tech:main' into main
This commit is contained in:
@@ -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() # 最先初始化日志
|
||||
|
||||
+18
-4
@@ -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)
|
||||
|
||||
+1
-1
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/* 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%;
|
||||
}
|
||||
|
||||
|
||||
.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;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
+35
-66
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!doctype html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
||||
@@ -8,39 +8,11 @@
|
||||
<script src="js/element-plus2.9.4.js"></script>
|
||||
<script src="js/icons-vue2.3.1.js"></script>
|
||||
<script src="js/common.js"></script>
|
||||
<link rel="icon" href="images/favicon.ico" />
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<title>小智-server</title>
|
||||
<style>
|
||||
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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
/* 页面特有样式 */
|
||||
.content-container {
|
||||
max-width: 1200px;
|
||||
margin: 20px auto;
|
||||
@@ -56,15 +28,6 @@
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.role-id {
|
||||
color: #333;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.role-description {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.character-count {
|
||||
text-align: right;
|
||||
color: #999;
|
||||
@@ -81,35 +44,33 @@
|
||||
font-size: 12px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 13px;
|
||||
padding: 20px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="animated-bg"></div>
|
||||
<div class="header">
|
||||
<div class="header-logo">AI</div>
|
||||
xiaozhi-esp32-server
|
||||
<app-header>xiaozhi-esp32-server</app-header>
|
||||
|
||||
<div class="user-menu">
|
||||
<el-dropdown @command="handleCommand">
|
||||
<div class="user-info">
|
||||
<el-avatar :size="32" src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"/>
|
||||
<span class="user-name">管理员</span>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="logout">退出</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
|
||||
<div class="content-container">
|
||||
<div class="role-title">配置角色:</div>
|
||||
<div class="role-id">cc:ba:97:11:a6:ac</div>
|
||||
|
||||
<div class="role-description">角色介绍</div>
|
||||
<el-input
|
||||
type="textarea"
|
||||
v-model="form.prompt"
|
||||
:rows="6"
|
||||
:rows="10"
|
||||
resize="vertical"
|
||||
@input="updateCharCount"
|
||||
></el-input>
|
||||
@@ -117,15 +78,13 @@
|
||||
|
||||
<div class="button-group">
|
||||
<el-button type="primary" @click="savePrompt">保存配置</el-button>
|
||||
<el-button @click="resetPrompt">重置</el-button>
|
||||
<el-button @click="loadPrompt">重置</el-button>
|
||||
</div>
|
||||
|
||||
<div class="note">注意:保存配置后,需要重启设备,新的配置才会生效。</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
© 2025 xiaozhi-esp32-server
|
||||
</div>
|
||||
<app-footer></app-footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -173,14 +132,24 @@
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
// 新增退出处理方法
|
||||
handleCommand(command) {
|
||||
if (command === 'logout') {
|
||||
this.handleLogout();
|
||||
}
|
||||
},
|
||||
handleLogout() {
|
||||
localStorage.removeItem('token');
|
||||
location.href = 'login.html';
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadPrompt();
|
||||
}
|
||||
};
|
||||
const app = Vue.createApp(App);
|
||||
app.use(ElementPlus);
|
||||
|
||||
const app = createVueApp(App);
|
||||
app.mount("#app");
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -37,4 +37,32 @@ function get(api, fn) {
|
||||
}
|
||||
fn(data);
|
||||
});
|
||||
}
|
||||
|
||||
// 注册全局组件
|
||||
const AppHeader = {
|
||||
template: `
|
||||
<header class="app-header">
|
||||
<div class="header-logo">AI</div>
|
||||
<slot>xiaozhi-esp32-server</slot>
|
||||
</header>
|
||||
`
|
||||
};
|
||||
|
||||
const AppFooter = {
|
||||
template: `
|
||||
<footer class="app-footer">
|
||||
<slot>© 2025 xiaozhi-esp32-server</slot>
|
||||
</footer>
|
||||
`
|
||||
};
|
||||
|
||||
// 初始化Vue应用的通用配置
|
||||
function createVueApp(options) {
|
||||
const app = Vue.createApp({
|
||||
...options,
|
||||
components: { AppHeader, AppFooter }
|
||||
});
|
||||
app.use(ElementPlus);
|
||||
return app;
|
||||
}
|
||||
+12
-80
@@ -1,46 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!doctype html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="icon" href="images/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
||||
<script src="js/vue3.5.13.js"></script>
|
||||
<link rel="stylesheet" href="css/element-plus2.9.4.css">
|
||||
<script src="js/element-plus2.9.4.js"></script>
|
||||
<script src="js/icons-vue2.3.1.js"></script>
|
||||
<script src="js/common.js"></script>
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<title>小智-server</title>
|
||||
<style>
|
||||
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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
/* 页面特有样式 */
|
||||
.login-container {
|
||||
max-width: 440px;
|
||||
margin: 60px auto;
|
||||
@@ -60,10 +32,6 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.custom-input :deep(.el-input__wrapper) {
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
||||
@@ -88,49 +56,13 @@
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 15px rgba(0, 198, 251, 0.3) !important;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 13px;
|
||||
padding: 20px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-position: 0% 50%
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="animated-bg"></div>
|
||||
<div class="header">
|
||||
<div class="header-logo">AI</div>
|
||||
xiaozhi-esp32-server
|
||||
</div>
|
||||
<app-header>xiaozhi-esp32-server</app-header>
|
||||
|
||||
<div class="login-container">
|
||||
<h2 class="login-title">安全验证</h2>
|
||||
<div class="login-form">
|
||||
@@ -152,10 +84,10 @@
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
© 2025 xiaozhi-esp32-server
|
||||
</div>
|
||||
|
||||
<app-footer></app-footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const {ElMessage} = ElementPlus;
|
||||
const App = {
|
||||
@@ -198,8 +130,8 @@
|
||||
}
|
||||
}
|
||||
};
|
||||
const app = Vue.createApp(App);
|
||||
app.use(ElementPlus);
|
||||
|
||||
const app = createVueApp(App);
|
||||
app.mount("#app");
|
||||
</script>
|
||||
</body>
|
||||
|
||||
+2
-1
@@ -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
|
||||
aiohttp==3.9.3
|
||||
ruamel.yaml==0.18.10
|
||||
@@ -1,53 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>小智提示词配置</title>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body { max-width: 800px; margin: 20px auto; padding: 0 20px; font-family: Arial, sans-serif; }
|
||||
textarea { width: 100%; height: 300px; margin: 20px 0; padding: 10px; }
|
||||
button { padding: 10px 20px; font-size: 16px; }
|
||||
.success { color: green; }
|
||||
.error { color: red; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>小智提示词配置</h1>
|
||||
<textarea id="prompt"></textarea>
|
||||
<div>
|
||||
<button onclick="updatePrompt()">更新提示词</button>
|
||||
<span id="status"></span>
|
||||
</div>
|
||||
<script>
|
||||
// 加载当前prompt
|
||||
fetch('/api/prompt')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('prompt').value = data.prompt;
|
||||
});
|
||||
|
||||
function updatePrompt() {
|
||||
const prompt = document.getElementById('prompt').value;
|
||||
fetch('/api/prompt', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({prompt: prompt})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const status = document.getElementById('status');
|
||||
if(data.success) {
|
||||
status.textContent = '更新成功!';
|
||||
status.className = 'success';
|
||||
} else {
|
||||
status.textContent = '更新失败: ' + data.error;
|
||||
status.className = 'error';
|
||||
}
|
||||
setTimeout(() => status.textContent = '', 3000);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user