Merge branch 'main' into main

This commit is contained in:
欣南科技
2025-02-15 21:13:50 +08:00
committed by GitHub
15 changed files with 369 additions and 211 deletions
+8 -3
View File
@@ -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))
+92
View File
@@ -0,0 +1,92 @@
import logging
import os
from aiohttp import web
from core.utils.util import get_local_ip, get_project_dir, check_password
from manager.api.prompt import PromptApi
from manager.api.auth import AuthApi
from aiohttp.web_middlewares import middleware
logger = logging.getLogger(__name__)
@middleware
async def cors_middleware(request, handler):
# 预检请求处理
if request.method == 'OPTIONS':
return web.Response(
status=204,
headers={
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization, Token',
'Access-Control-Max-Age': '86400',
}
)
try:
response = await handler(request)
except web.HTTPException as ex:
response = ex
# 添加CORS头到所有响应
cors_headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Expose-Headers': 'Content-Type, Authorization, Token',
'Vary': 'Origin' # 避免缓存问题
}
response.headers.update(cors_headers)
return response
class ConfigServer:
def __init__(self, config: dict):
self.config = config
self.app = web.Application(middlewares=[cors_middleware]) # 注册中间件
# 初始化接口处理器
self.prompt_handler = PromptApi(config)
self.auth_handler = AuthApi(config)
self.setup_routes()
def setup_routes(self):
# 注册prompt接口
self.app.router.add_get('/api/prompt', self.prompt_handler.get_prompt)
self.app.router.add_post('/api/prompt', self.prompt_handler.update_prompt)
# 注册auth接口
self.app.router.add_post('/api/login', self.auth_handler.login)
# 注册静态文件路由
static_dir = os.path.join(get_project_dir(), 'manager/static') # 获取static目录绝对路径
self.app.router.add_static(
prefix='/manager/', # 匹配前缀
path=static_dir, # 静态文件目录
name='static'
)
self.app.router.add_get('/manager', self.redirect_to_index)
async def redirect_to_index(self, _):
raise web.HTTPFound('/manager/')
async def start(self):
try:
http_config = self.config['manager']
if not http_config.get('enabled', False):
logger.info("HTTP server is disabled")
return
token = self.config['manager']['token']
if not check_password(token):
logger.info("您设置的后台密码太弱了,启动后台管理失败!")
return
runner = web.AppRunner(self.app)
await runner.setup()
site = web.TCPSite(runner, http_config['ip'], http_config['port'])
await site.start()
logger.info(
f"Config HTTP server is running at http://{get_local_ip()}:{http_config['port']}/manager/login.html")
return runner # 返回runner以便后续清理
except Exception as e:
logger.error(f"Failed to start HTTP server: {e}")
raise
+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>
<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>
+28
View File
@@ -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
View File
@@ -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>