Merge branch 'xinnan-tech:main' into main

This commit is contained in:
fofo
2025-02-15 20:34:24 +08:00
committed by GitHub
13 changed files with 183 additions and 210 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
import asyncio import asyncio
from config.logger import setup_logging from config.logger import setup_logging
from config.settings import load_config from config.settings import load_config
from core.server import WebSocketServer from core.websocket_server import WebSocketServer
from core.http_server import ConfigServer from manager.http_server import ConfigServer
async def main(): async def main():
setup_logging() # 最先初始化日志 setup_logging() # 最先初始化日志
+18 -4
View File
@@ -1,15 +1,29 @@
import os import os
import argparse import argparse
from ruamel.yaml import YAML
from core.utils.util import read_config, get_project_dir 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(): def load_config():
"""加载配置文件""" """加载配置文件"""
parser = argparse.ArgumentParser(description="Server configuration") parser = argparse.ArgumentParser(description="Server configuration")
default_config_file = "config.yaml" default_config_file = get_config_file()
# 判断是否存在私有的配置文件
if os.path.exists(get_project_dir() + "." + default_config_file):
default_config_file = "." + default_config_file
parser.add_argument("--config_path", type=str, default=default_config_file) parser.add_argument("--config_path", type=str, default=default_config_file)
args = parser.parse_args() args = parser.parse_args()
return read_config(args.config_path) 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
View File
@@ -15,7 +15,7 @@ from core.handle.textHandle import handleTextMessage
from core.utils.util import get_string_no_punctuation_or_emoji from core.utils.util import get_string_no_punctuation_or_emoji
from concurrent.futures import ThreadPoolExecutor, TimeoutError from concurrent.futures import ThreadPoolExecutor, TimeoutError
from core.handle.audioHandle import handleAudioMessage, sendAudioMessage from core.handle.audioHandle import handleAudioMessage, sendAudioMessage
from .auth import AuthMiddleware, AuthenticationError from core.auth import AuthMiddleware, AuthenticationError
class ConnectionHandler: class ConnectionHandler:
+8 -3
View File
@@ -1,5 +1,7 @@
import logging import logging
from aiohttp import web 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.auth import verify_token
from manager.api.response import response_unauthorized, response_success, response_error from manager.api.response import response_unauthorized, response_success, response_error
@@ -28,10 +30,13 @@ class PromptApi:
if 'prompt' not in data: if 'prompt' not in data:
return response_success() return response_success()
self.config['prompt'] = data['prompt'] # 使用PreservedScalarString保留多行文本格式
# TODO 保存到配置文件 self.config['prompt'] = PreservedScalarString(data['prompt'])
return response_success()
# 保存到配置文件
update_config(self.config)
return response_success()
except Exception as e: except Exception as e:
logger.error(f"Failed to update prompt: {e}") logger.error(f"Failed to update prompt: {e}")
return response_error(str(e)) return response_error(str(e))
+77
View File
@@ -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
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html> <!doctype html>
<html> <html lang="zh">
<head> <head>
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0"/> <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/element-plus2.9.4.js"></script>
<script src="js/icons-vue2.3.1.js"></script> <script src="js/icons-vue2.3.1.js"></script>
<script src="js/common.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> <title>小智-server</title>
<style> <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 { .content-container {
max-width: 1200px; max-width: 1200px;
margin: 20px auto; margin: 20px auto;
@@ -56,15 +28,6 @@
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.role-id {
color: #333;
margin-bottom: 1rem;
}
.role-description {
margin: 1rem 0;
}
.character-count { .character-count {
text-align: right; text-align: right;
color: #999; color: #999;
@@ -81,35 +44,33 @@
font-size: 12px; font-size: 12px;
margin-top: 1rem; 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> </style>
</head> </head>
<body> <body>
<div id="app"> <div id="app">
<div class="animated-bg"></div> <div class="animated-bg"></div>
<div class="header"> <app-header>xiaozhi-esp32-server</app-header>
<div class="header-logo">AI</div>
xiaozhi-esp32-server <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>
<div class="content-container"> <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> <div class="role-description">角色介绍</div>
<el-input <el-input
type="textarea" type="textarea"
v-model="form.prompt" v-model="form.prompt"
:rows="6" :rows="10"
resize="vertical" resize="vertical"
@input="updateCharCount" @input="updateCharCount"
></el-input> ></el-input>
@@ -117,15 +78,13 @@
<div class="button-group"> <div class="button-group">
<el-button type="primary" @click="savePrompt">保存配置</el-button> <el-button type="primary" @click="savePrompt">保存配置</el-button>
<el-button @click="resetPrompt">重置</el-button> <el-button @click="loadPrompt">重置</el-button>
</div> </div>
<div class="note">注意:保存配置后,需要重启设备,新的配置才会生效。</div> <div class="note">注意:保存配置后,需要重启设备,新的配置才会生效。</div>
</div> </div>
<div class="footer"> <app-footer></app-footer>
© 2025 xiaozhi-esp32-server
</div>
</div> </div>
<script> <script>
@@ -173,14 +132,24 @@
}); });
} }
}); });
} },
// 新增退出处理方法
handleCommand(command) {
if (command === 'logout') {
this.handleLogout();
}
},
handleLogout() {
localStorage.removeItem('token');
location.href = 'login.html';
},
}, },
mounted() { mounted() {
this.loadPrompt(); this.loadPrompt();
} }
}; };
const app = Vue.createApp(App);
app.use(ElementPlus); const app = createVueApp(App);
app.mount("#app"); app.mount("#app");
</script> </script>
</body> </body>
+28
View File
@@ -37,4 +37,32 @@ function get(api, fn) {
} }
fn(data); 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
View File
@@ -1,46 +1,18 @@
<!DOCTYPE html> <!doctype html>
<html> <html lang="zh">
<head> <head>
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<link rel="icon" href="images/favicon.ico" />
<meta name="viewport" content="width=device-width,initial-scale=1.0"/> <meta name="viewport" content="width=device-width,initial-scale=1.0"/>
<script src="js/vue3.5.13.js"></script> <script src="js/vue3.5.13.js"></script>
<link rel="stylesheet" href="css/element-plus2.9.4.css"> <link rel="stylesheet" href="css/element-plus2.9.4.css">
<script src="js/element-plus2.9.4.js"></script> <script src="js/element-plus2.9.4.js"></script>
<script src="js/icons-vue2.3.1.js"></script> <script src="js/icons-vue2.3.1.js"></script>
<script src="js/common.js"></script> <script src="js/common.js"></script>
<link rel="stylesheet" href="css/common.css">
<title>小智-server</title> <title>小智-server</title>
<style> <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 { .login-container {
max-width: 440px; max-width: 440px;
margin: 60px auto; margin: 60px auto;
@@ -60,10 +32,6 @@
text-align: center; text-align: center;
} }
.login-form {
margin-top: 20px;
}
.custom-input :deep(.el-input__wrapper) { .custom-input :deep(.el-input__wrapper) {
background: rgba(255, 255, 255, 0.05) !important; background: rgba(255, 255, 255, 0.05) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important; border: 1px solid rgba(255, 255, 255, 0.1) !important;
@@ -88,49 +56,13 @@
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0, 198, 251, 0.3) !important; 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> </style>
</head> </head>
<body> <body>
<div id="app"> <div id="app">
<div class="animated-bg"></div> <div class="animated-bg"></div>
<div class="header"> <app-header>xiaozhi-esp32-server</app-header>
<div class="header-logo">AI</div>
xiaozhi-esp32-server
</div>
<div class="login-container"> <div class="login-container">
<h2 class="login-title">安全验证</h2> <h2 class="login-title">安全验证</h2>
<div class="login-form"> <div class="login-form">
@@ -152,10 +84,10 @@
</el-form> </el-form>
</div> </div>
</div> </div>
<div class="footer">
© 2025 xiaozhi-esp32-server <app-footer></app-footer>
</div>
</div> </div>
<script> <script>
const {ElMessage} = ElementPlus; const {ElMessage} = ElementPlus;
const App = { const App = {
@@ -198,8 +130,8 @@
} }
} }
}; };
const app = Vue.createApp(App);
app.use(ElementPlus); const app = createVueApp(App);
app.mount("#app"); app.mount("#app");
</script> </script>
</body> </body>
+2 -1
View File
@@ -11,4 +11,5 @@ openai==1.61.0
google-generativeai==0.8.4 google-generativeai==0.8.4
edge_tts==7.0.0 edge_tts==7.0.0
httpx==0.27.2 httpx==0.27.2
aiohttp==3.9.3 aiohttp==3.9.3
ruamel.yaml==0.18.10
-53
View File
@@ -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>