mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-27 01:23:55 +08:00
Merge branch 'main' of https://github.com/xinnan-tech/xiaozhi-esp32-server
This commit is contained in:
+36
-10
@@ -1,12 +1,31 @@
|
|||||||
# 第一阶段:构建依赖
|
# 第一阶段:前端构建
|
||||||
|
FROM node:18 as frontend-builder
|
||||||
|
|
||||||
|
WORKDIR /app/ZhiKongTaiWeb
|
||||||
|
|
||||||
|
COPY ZhiKongTaiWeb/package*.json ./
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY ZhiKongTaiWeb .
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# 第二阶段:构建Python依赖
|
||||||
FROM python:3.10-slim as builder
|
FROM python:3.10-slim as builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
|
|
||||||
# 安装构建依赖
|
# 使用清华源加速apt安装
|
||||||
RUN apt-get update && \
|
RUN rm -rf /etc/apt/sources.list.d/* && \
|
||||||
|
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm main contrib non-free non-free-firmware" > /etc/apt/sources.list && \
|
||||||
|
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-updates main contrib non-free non-free-firmware" >> /etc/apt/sources.list && \
|
||||||
|
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-backports main contrib non-free non-free-firmware" >> /etc/apt/sources.list && \
|
||||||
|
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian-security bookworm-security main contrib non-free non-free-firmware" >> /etc/apt/sources.list && \
|
||||||
|
apt-get clean && \
|
||||||
|
apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
gcc \
|
gcc \
|
||||||
libopus-dev \
|
libopus-dev \
|
||||||
@@ -16,24 +35,31 @@ RUN apt-get update && \
|
|||||||
# 安装Python依赖到虚拟环境
|
# 安装Python依赖到虚拟环境
|
||||||
RUN python -m venv /opt/venv
|
RUN python -m venv /opt/venv
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
RUN pip install --no-cache-dir -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
|
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||||
|
|
||||||
# 第二阶段:生产镜像
|
# 第三阶段:生产镜像
|
||||||
FROM python:3.10-slim
|
FROM python:3.10-slim
|
||||||
|
|
||||||
WORKDIR /opt/xiaozhi-esp32-server
|
WORKDIR /opt/xiaozhi-esp32-server
|
||||||
|
|
||||||
# 从构建阶段复制虚拟环境
|
# 使用清华源加速apt安装
|
||||||
COPY --from=builder /opt/venv /opt/venv
|
RUN rm -rf /etc/apt/sources.list.d/* && \
|
||||||
|
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm main contrib non-free non-free-firmware" > /etc/apt/sources.list && \
|
||||||
# 安装运行时依赖
|
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-updates main contrib non-free non-free-firmware" >> /etc/apt/sources.list && \
|
||||||
RUN apt-get update && \
|
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-backports main contrib non-free non-free-firmware" >> /etc/apt/sources.list && \
|
||||||
|
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian-security bookworm-security main contrib non-free non-free-firmware" >> /etc/apt/sources.list && \
|
||||||
|
apt-get clean && \
|
||||||
|
apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
libopus0 \
|
libopus0 \
|
||||||
ffmpeg && \
|
ffmpeg && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 从构建阶段复制虚拟环境和前端构建产物
|
||||||
|
COPY --from=builder /opt/venv /opt/venv
|
||||||
|
COPY --from=frontend-builder /app/ZhiKongTaiWeb/dist /opt/xiaozhi-esp32-server/manager/static
|
||||||
|
|
||||||
# 设置虚拟环境路径
|
# 设置虚拟环境路径
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
VITE_API_BASE_URL=http://localhost:8002
|
VITE_API_BASE_URL=''
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
VITE_API_BASE_URL=http://localhost:8002
|
VITE_API_BASE_URL='' # 空字符串表示使用当前域名和端口
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
import Login from './components/Login.vue';
|
import Login from './components/Login.vue';
|
||||||
import Registration from './components/Registration.vue';
|
import Registration from './components/Registration.vue';
|
||||||
import panel from './components/panel.vue';
|
import panel from './components/panel.vue';
|
||||||
import MainPage from './components/main.vue';
|
import MainPage from './components/Main.vue';
|
||||||
import RoleSetting from './components/RoleSetting.vue';
|
import RoleSetting from './components/RoleSetting.vue';
|
||||||
import Footer from './components/Footer.vue';
|
import Footer from './components/Footer.vue';
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { API_BASE_URL } from '../config/api';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -34,7 +36,7 @@
|
|||||||
|
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/login`, {
|
const response = await fetch(`${API_BASE_URL}/api/login`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { API_BASE_URL } from '../config/api';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -47,7 +49,7 @@ export default {
|
|||||||
|
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/register`, {
|
const response = await fetch(`${API_BASE_URL}/api/register`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="model-description">
|
<div class="model-description">
|
||||||
除了“qwen-turbo”,其他模型通常会增加约 1 秒的延迟。改变模型后,建议清空记忆体,以免影响体验。
|
除了"qwen-turbo",其他模型通常会增加约 1 秒的延迟。改变模型后,建议清空记忆体,以免影响体验。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,6 +109,7 @@
|
|||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import NavBar from './NavBar.vue';
|
import NavBar from './NavBar.vue';
|
||||||
import RoleTemplates from '../utils/RoleTemplates';
|
import RoleTemplates from '../utils/RoleTemplates';
|
||||||
|
import { API_BASE_URL } from '../config/api';
|
||||||
|
|
||||||
const roleTemplates = RoleTemplates.getTemplates();
|
const roleTemplates = RoleTemplates.getTemplates();
|
||||||
|
|
||||||
@@ -120,7 +121,7 @@ const activeMemoryTab = ref('recent');
|
|||||||
const memoryContent = ref('');
|
const memoryContent = ref('');
|
||||||
const selectedModel = ref('qianwen');
|
const selectedModel = ref('qianwen');
|
||||||
|
|
||||||
const baseUrl = import.meta.env.VITE_API_BASE_URL;
|
const baseUrl = API_BASE_URL;
|
||||||
const moduleOptions = ref({
|
const moduleOptions = ref({
|
||||||
LLM: [],
|
LLM: [],
|
||||||
TTS: [],
|
TTS: [],
|
||||||
|
|||||||
@@ -41,10 +41,11 @@
|
|||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import NavBar from './NavBar.vue';
|
import NavBar from './NavBar.vue';
|
||||||
import DeviceCard from './DeviceCard.vue';
|
import DeviceCard from './DeviceCard.vue';
|
||||||
|
import { API_BASE_URL } from '../config/api';
|
||||||
|
|
||||||
const emit = defineEmits(['show-role', 'go-home']);
|
const emit = defineEmits(['show-role', 'go-home']);
|
||||||
|
|
||||||
const baseUrl = import.meta.env.VITE_API_BASE_URL;
|
const baseUrl = API_BASE_URL;
|
||||||
const devices = ref([]);
|
const devices = ref([]);
|
||||||
|
|
||||||
const formatLastActivity = (timestamp) => {
|
const formatLastActivity = (timestamp) => {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// 获取当前运行环境的基础 URL
|
||||||
|
const getBaseUrl = () => {
|
||||||
|
// 如果是开发环境,使用环境变量中的地址
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
return import.meta.env.VITE_API_BASE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生产环境使用当前域名和端口
|
||||||
|
const protocol = window.location.protocol;
|
||||||
|
const hostname = window.location.hostname;
|
||||||
|
const port = window.location.port;
|
||||||
|
|
||||||
|
return `${protocol}//${hostname}${port ? `:${port}` : ''}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const API_BASE_URL = getBaseUrl();
|
||||||
@@ -3,6 +3,9 @@ from config.logger import setup_logging
|
|||||||
from config.settings import load_config
|
from config.settings import load_config
|
||||||
from core.websocket_server import WebSocketServer
|
from core.websocket_server import WebSocketServer
|
||||||
from manager.http_server import ConfigServer
|
from manager.http_server import ConfigServer
|
||||||
|
from core.webui import WebUI
|
||||||
|
from aiohttp import web
|
||||||
|
from core.utils.util import get_local_ip
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
setup_logging() # 最先初始化日志
|
setup_logging() # 最先初始化日志
|
||||||
@@ -21,6 +24,20 @@ async def main():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to start HTTP server: {e}")
|
print(f"Failed to start HTTP server: {e}")
|
||||||
|
|
||||||
|
# 启动 WebUI 服务器
|
||||||
|
webui_runner = None
|
||||||
|
try:
|
||||||
|
webui = WebUI()
|
||||||
|
runner = web.AppRunner(webui.app)
|
||||||
|
await runner.setup()
|
||||||
|
site = web.TCPSite(runner, '0.0.0.0', 8002)
|
||||||
|
await site.start()
|
||||||
|
webui_runner = runner
|
||||||
|
local_ip = get_local_ip()
|
||||||
|
print(f"WebUI server is running at http://{local_ip}:8002")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to start WebUI server: {e}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 等待 WebSocket 服务器运行
|
# 等待 WebSocket 服务器运行
|
||||||
await ws_task
|
await ws_task
|
||||||
@@ -28,6 +45,9 @@ async def main():
|
|||||||
# 清理 HTTP 服务器
|
# 清理 HTTP 服务器
|
||||||
if http_runner:
|
if http_runner:
|
||||||
await http_runner.cleanup()
|
await http_runner.cleanup()
|
||||||
|
# 清理 WebUI 服务器
|
||||||
|
if webui_runner:
|
||||||
|
await webui_runner.cleanup()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import sys
|
|||||||
import logging
|
import logging
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from aiohttp_cors import setup as cors_setup, ResourceOptions
|
from aiohttp_cors import setup as cors_setup, ResourceOptions
|
||||||
|
from core.utils.util import get_local_ip
|
||||||
|
|
||||||
# 添加项目根目录到Python路径
|
# 添加项目根目录到Python路径
|
||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
@@ -68,6 +69,7 @@ class WebUI:
|
|||||||
|
|
||||||
# 添加静态文件服务
|
# 添加静态文件服务
|
||||||
self.app.router.add_static('/assets/', path=os.path.join(self.static_path, 'assets'))
|
self.app.router.add_static('/assets/', path=os.path.join(self.static_path, 'assets'))
|
||||||
|
# 所有未匹配的路由都返回 index.html
|
||||||
self.app.router.add_get('/{tail:.*}', self.handle_static_files)
|
self.app.router.add_get('/{tail:.*}', self.handle_static_files)
|
||||||
|
|
||||||
async def handle_static_files(self, request):
|
async def handle_static_files(self, request):
|
||||||
@@ -79,6 +81,8 @@ class WebUI:
|
|||||||
|
|
||||||
def run(self, host='0.0.0.0', port=8002):
|
def run(self, host='0.0.0.0', port=8002):
|
||||||
"""运行服务器"""
|
"""运行服务器"""
|
||||||
|
local_ip = get_local_ip()
|
||||||
|
logger.info(f"WebUI server is running at http://{local_ip}:{port}")
|
||||||
web.run_app(self.app, host=host, port=port)
|
web.run_app(self.app, host=host, port=port)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,12 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# 添加项目根目录到Python路径
|
|
||||||
project_root = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
sys.path.append(project_root)
|
|
||||||
|
|
||||||
from core.webui import WebUI
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
webui = WebUI()
|
|
||||||
webui.run()
|
|
||||||
Reference in New Issue
Block a user