diff --git a/.gitignore b/.gitignore
index 9586fb9f..8e8b0e02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -141,3 +141,5 @@ model.pt
tmp
.DS_Store
.config.yaml
+.secrets.yaml
+.private_config.yaml
diff --git a/Dockerfile b/Dockerfile
index cf709a71..f19d7a22 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -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
WORKDIR /app
COPY requirements.txt .
-# 安装构建依赖
-RUN apt-get update && \
+# 使用清华源加速apt安装
+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 \
gcc \
libopus-dev \
@@ -16,24 +35,31 @@ RUN apt-get update && \
# 安装Python依赖到虚拟环境
RUN python -m venv /opt/venv
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
WORKDIR /opt/xiaozhi-esp32-server
-# 从构建阶段复制虚拟环境
-COPY --from=builder /opt/venv /opt/venv
-
-# 安装运行时依赖
-RUN apt-get update && \
+# 使用清华源加速apt安装
+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 \
libopus0 \
ffmpeg && \
apt-get clean && \
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"
diff --git a/ZhiKongTaiWeb/.env.development b/ZhiKongTaiWeb/.env.development
new file mode 100644
index 00000000..296f5314
--- /dev/null
+++ b/ZhiKongTaiWeb/.env.development
@@ -0,0 +1 @@
+VITE_API_BASE_URL=''
diff --git a/ZhiKongTaiWeb/.env.production b/ZhiKongTaiWeb/.env.production
new file mode 100644
index 00000000..adc0b5df
--- /dev/null
+++ b/ZhiKongTaiWeb/.env.production
@@ -0,0 +1 @@
+VITE_API_BASE_URL='' # 空字符串表示使用当前域名和端口
diff --git a/ZhiKongTaiWeb/.gitignore b/ZhiKongTaiWeb/.gitignore
new file mode 100644
index 00000000..a547bf36
--- /dev/null
+++ b/ZhiKongTaiWeb/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/ZhiKongTaiWeb/README.md b/ZhiKongTaiWeb/README.md
new file mode 100644
index 00000000..e5b83116
--- /dev/null
+++ b/ZhiKongTaiWeb/README.md
@@ -0,0 +1,14 @@
+# Vue 3 + Vite
+
+项目使用Vue3 + Vite实现
+
+在当前目录执行以下命令进入开发模式
+
+```
+npm run dev
+```
+
+执行以下命令打包
+```
+npm run build
+```
\ No newline at end of file
diff --git a/ZhiKongTaiWeb/index.html b/ZhiKongTaiWeb/index.html
new file mode 100644
index 00000000..f7809c0c
--- /dev/null
+++ b/ZhiKongTaiWeb/index.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ 智控台
+
+
+
+
+
+
+
diff --git a/ZhiKongTaiWeb/package-lock.json b/ZhiKongTaiWeb/package-lock.json
new file mode 100644
index 00000000..a9af8c76
--- /dev/null
+++ b/ZhiKongTaiWeb/package-lock.json
@@ -0,0 +1,1046 @@
+{
+ "name": "zhikongtaiweb",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "zhikongtaiweb",
+ "version": "0.0.0",
+ "dependencies": {
+ "vue": "^3.5.10"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.1.4",
+ "vite": "^5.4.8"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.26.9",
+ "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.26.9.tgz",
+ "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==",
+ "dependencies": {
+ "@babel/types": "^7.26.9"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.26.9",
+ "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.26.9.tgz",
+ "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.7.tgz",
+ "integrity": "sha512-l6CtzHYo8D2TQ3J7qJNpp3Q1Iye56ssIAtqbM2H8axxCEEwvN7o8Ze9PuIapbxFL3OHrJU2JBX6FIIVnP/rYyw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.7.tgz",
+ "integrity": "sha512-KvyJpFUueUnSp53zhAa293QBYqwm94TgYTIfXyOTtidhm5V0LbLCJQRGkQClYiX3FXDQGSvPxOTD/6rPStMMDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.7.tgz",
+ "integrity": "sha512-jq87CjmgL9YIKvs8ybtIC98s/M3HdbqXhllcy9EdLV0yMg1DpxES2gr65nNy7ObNo/vZ/MrOTxt0bE5LinL6mA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.7.tgz",
+ "integrity": "sha512-rSI/m8OxBjsdnMMg0WEetu/w+LhLAcCDEiL66lmMX4R3oaml3eXz3Dxfvrxs1FbzPbJMaItQiksyMfv1hoIxnA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.7.tgz",
+ "integrity": "sha512-oIoJRy3ZrdsXpFuWDtzsOOa/E/RbRWXVokpVrNnkS7npz8GEG++E1gYbzhYxhxHbO2om1T26BZjVmdIoyN2WtA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.7.tgz",
+ "integrity": "sha512-X++QSLm4NZfZ3VXGVwyHdRf58IBbCu9ammgJxuWZYLX0du6kZvdNqPwrjvDfwmi6wFdvfZ/s6K7ia0E5kI7m8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.7.tgz",
+ "integrity": "sha512-Z0TzhrsNqukTz3ISzrvyshQpFnFRfLunYiXxlCRvcrb3nvC5rVKI+ZXPFG/Aa4jhQa1gHgH3A0exHaRRN4VmdQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.7.tgz",
+ "integrity": "sha512-nkznpyXekFAbvFBKBy4nNppSgneB1wwG1yx/hujN3wRnhnkrYVugMTCBXED4+Ni6thoWfQuHNYbFjgGH0MBXtw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.7.tgz",
+ "integrity": "sha512-KCjlUkcKs6PjOcxolqrXglBDcfCuUCTVlX5BgzgoJHw+1rWH1MCkETLkLe5iLLS9dP5gKC7mp3y6x8c1oGBUtA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.7.tgz",
+ "integrity": "sha512-uFLJFz6+utmpbR313TTx+NpPuAXbPz4BhTQzgaP0tozlLnGnQ6rCo6tLwaSa6b7l6gRErjLicXQ1iPiXzYotjw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.7.tgz",
+ "integrity": "sha512-ws8pc68UcJJqCpneDFepnwlsMUFoWvPbWXT/XUrJ7rWUL9vLoIN3GAasgG+nCvq8xrE3pIrd+qLX/jotcLy0Qw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.7.tgz",
+ "integrity": "sha512-vrDk9JDa/BFkxcS2PbWpr0C/LiiSLxFbNOBgfbW6P8TBe9PPHx9Wqbvx2xgNi1TOAyQHQJ7RZFqBiEohm79r0w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.7.tgz",
+ "integrity": "sha512-rB+ejFyjtmSo+g/a4eovDD1lHWHVqizN8P0Hm0RElkINpS0XOdpaXloqM4FBkF9ZWEzg6bezymbpLmeMldfLTw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.7.tgz",
+ "integrity": "sha512-nNXNjo4As6dNqRn7OrsnHzwTgtypfRA3u3AKr0B3sOOo+HkedIbn8ZtFnB+4XyKJojIfqDKmbIzO1QydQ8c+Pw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.7.tgz",
+ "integrity": "sha512-9kPVf9ahnpOMSGlCxXGv980wXD0zRR3wyk8+33/MXQIpQEOpaNe7dEHm5LMfyRZRNt9lMEQuH0jUKj15MkM7QA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.7.tgz",
+ "integrity": "sha512-7wJPXRWTTPtTFDFezA8sle/1sdgxDjuMoRXEKtx97ViRxGGkVQYovem+Q8Pr/2HxiHp74SSRG+o6R0Yq0shPwQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.7.tgz",
+ "integrity": "sha512-MN7aaBC7mAjsiMEZcsJvwNsQVNZShgES/9SzWp1HC9Yjqb5OpexYnRjF7RmE4itbeesHMYYQiAtUAQaSKs2Rfw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.7.tgz",
+ "integrity": "sha512-aeawEKYswsFu1LhDM9RIgToobquzdtSc4jSVqHV8uApz4FVvhFl/mKh92wc8WpFc6aYCothV/03UjY6y7yLgbg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.7.tgz",
+ "integrity": "sha512-4ZedScpxxIrVO7otcZ8kCX1mZArtH2Wfj3uFCxRJ9NO80gg1XV0U/b2f/MKaGwj2X3QopHfoWiDQ917FRpwY3w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz",
+ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+ "dev": true
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz",
+ "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==",
+ "dev": true,
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.0.0 || ^6.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
+ "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
+ "dependencies": {
+ "@babel/parser": "^7.25.3",
+ "@vue/shared": "3.5.13",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
+ "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.13",
+ "@vue/shared": "3.5.13"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
+ "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
+ "dependencies": {
+ "@babel/parser": "^7.25.3",
+ "@vue/compiler-core": "3.5.13",
+ "@vue/compiler-dom": "3.5.13",
+ "@vue/compiler-ssr": "3.5.13",
+ "@vue/shared": "3.5.13",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.11",
+ "postcss": "^8.4.48",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
+ "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.13",
+ "@vue/shared": "3.5.13"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.13.tgz",
+ "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
+ "dependencies": {
+ "@vue/shared": "3.5.13"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
+ "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
+ "dependencies": {
+ "@vue/reactivity": "3.5.13",
+ "@vue/shared": "3.5.13"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
+ "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
+ "dependencies": {
+ "@vue/reactivity": "3.5.13",
+ "@vue/runtime-core": "3.5.13",
+ "@vue/shared": "3.5.13",
+ "csstype": "^3.1.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
+ "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.13",
+ "@vue/shared": "3.5.13"
+ },
+ "peerDependencies": {
+ "vue": "3.5.13"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz",
+ "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ=="
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.8.tgz",
+ "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+ },
+ "node_modules/postcss": {
+ "version": "8.5.2",
+ "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.2.tgz",
+ "integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.8",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.34.7",
+ "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.34.7.tgz",
+ "integrity": "sha512-8qhyN0oZ4x0H6wmBgfKxJtxM7qS98YJ0k0kNh5ECVtuchIJ7z9IVVvzpmtQyT10PXKMtBxYr1wQ5Apg8RS8kXQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "1.0.6"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.34.7",
+ "@rollup/rollup-android-arm64": "4.34.7",
+ "@rollup/rollup-darwin-arm64": "4.34.7",
+ "@rollup/rollup-darwin-x64": "4.34.7",
+ "@rollup/rollup-freebsd-arm64": "4.34.7",
+ "@rollup/rollup-freebsd-x64": "4.34.7",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.34.7",
+ "@rollup/rollup-linux-arm-musleabihf": "4.34.7",
+ "@rollup/rollup-linux-arm64-gnu": "4.34.7",
+ "@rollup/rollup-linux-arm64-musl": "4.34.7",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.34.7",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.34.7",
+ "@rollup/rollup-linux-riscv64-gnu": "4.34.7",
+ "@rollup/rollup-linux-s390x-gnu": "4.34.7",
+ "@rollup/rollup-linux-x64-gnu": "4.34.7",
+ "@rollup/rollup-linux-x64-musl": "4.34.7",
+ "@rollup/rollup-win32-arm64-msvc": "4.34.7",
+ "@rollup/rollup-win32-ia32-msvc": "4.34.7",
+ "@rollup/rollup-win32-x64-msvc": "4.34.7",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.14",
+ "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.14.tgz",
+ "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.13.tgz",
+ "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.13",
+ "@vue/compiler-sfc": "3.5.13",
+ "@vue/runtime-dom": "3.5.13",
+ "@vue/server-renderer": "3.5.13",
+ "@vue/shared": "3.5.13"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/ZhiKongTaiWeb/package.json b/ZhiKongTaiWeb/package.json
new file mode 100644
index 00000000..a0bc4158
--- /dev/null
+++ b/ZhiKongTaiWeb/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "zhikongtaiweb",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "vue": "^3.5.10"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.1.4",
+ "vite": "^5.4.8"
+ }
+}
diff --git a/ZhiKongTaiWeb/public/vite.svg b/ZhiKongTaiWeb/public/vite.svg
new file mode 100644
index 00000000..e7b8dfb1
--- /dev/null
+++ b/ZhiKongTaiWeb/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ZhiKongTaiWeb/src/App.vue b/ZhiKongTaiWeb/src/App.vue
new file mode 100644
index 00000000..de9472b9
--- /dev/null
+++ b/ZhiKongTaiWeb/src/App.vue
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ZhiKongTaiWeb/src/assets/robot.png b/ZhiKongTaiWeb/src/assets/robot.png
new file mode 100644
index 00000000..5fe1a411
Binary files /dev/null and b/ZhiKongTaiWeb/src/assets/robot.png differ
diff --git a/ZhiKongTaiWeb/src/assets/vue.svg b/ZhiKongTaiWeb/src/assets/vue.svg
new file mode 100644
index 00000000..770e9d33
--- /dev/null
+++ b/ZhiKongTaiWeb/src/assets/vue.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ZhiKongTaiWeb/src/components/DeviceCard.vue b/ZhiKongTaiWeb/src/components/DeviceCard.vue
new file mode 100644
index 00000000..a30c66ad
--- /dev/null
+++ b/ZhiKongTaiWeb/src/components/DeviceCard.vue
@@ -0,0 +1,194 @@
+
+
+
+
+
+
设备型号:{{ deviceType }}
+
角色昵称:{{ deviceRole }}
+
+ 当前模型:
+ LLM: {{ selectedModules?.LLM || '-' }}
+ TTS: {{ selectedModules?.TTS || '-' }}
+
+
最近对话:{{ lastActivity }}
+
+
+
+
配置角色
+
声纹识别
+
历史对话
+
+
+ 删除设备
+
+
删除后设备配置将不可恢复
+
+
+
+
+
+
+
+
diff --git a/ZhiKongTaiWeb/src/components/Footer.vue b/ZhiKongTaiWeb/src/components/Footer.vue
new file mode 100644
index 00000000..6822f6cc
--- /dev/null
+++ b/ZhiKongTaiWeb/src/components/Footer.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/ZhiKongTaiWeb/src/components/Login.vue b/ZhiKongTaiWeb/src/components/Login.vue
new file mode 100644
index 00000000..575d2539
--- /dev/null
+++ b/ZhiKongTaiWeb/src/components/Login.vue
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ZhiKongTaiWeb/src/components/Main.vue b/ZhiKongTaiWeb/src/components/Main.vue
new file mode 100644
index 00000000..b73478ee
--- /dev/null
+++ b/ZhiKongTaiWeb/src/components/Main.vue
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
+
+
+
让我们一起探索人工智能与机器人技术的迷人世界!
+
+
+
+ DIY 教程
+
+
+
+ Github客户端
+
+
+
+ Github服务端
+
+
+
+ 控制台
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ZhiKongTaiWeb/src/components/NavBar.vue b/ZhiKongTaiWeb/src/components/NavBar.vue
new file mode 100644
index 00000000..579e6846
--- /dev/null
+++ b/ZhiKongTaiWeb/src/components/NavBar.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
diff --git a/ZhiKongTaiWeb/src/components/Registration.vue b/ZhiKongTaiWeb/src/components/Registration.vue
new file mode 100644
index 00000000..9dc09749
--- /dev/null
+++ b/ZhiKongTaiWeb/src/components/Registration.vue
@@ -0,0 +1,179 @@
+
+
+
注册
+
+
+ 已有账号?
+ 点击登录
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ZhiKongTaiWeb/src/components/RoleSetting.vue b/ZhiKongTaiWeb/src/components/RoleSetting.vue
new file mode 100644
index 00000000..58408a20
--- /dev/null
+++ b/ZhiKongTaiWeb/src/components/RoleSetting.vue
@@ -0,0 +1,508 @@
+
+
+
+
+
+
+ 控制台 /
+ 设备管理 /
+ 配置角色
+
+
+
+
+
配置角色: {{ deviceId }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ZhiKongTaiWeb/src/components/SwitchToggle.vue b/ZhiKongTaiWeb/src/components/SwitchToggle.vue
new file mode 100644
index 00000000..ea1a0988
--- /dev/null
+++ b/ZhiKongTaiWeb/src/components/SwitchToggle.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ZhiKongTaiWeb/src/components/panel.vue b/ZhiKongTaiWeb/src/components/panel.vue
new file mode 100644
index 00000000..f3fd0b60
--- /dev/null
+++ b/ZhiKongTaiWeb/src/components/panel.vue
@@ -0,0 +1,312 @@
+
+
+
+
+
+ 控制台 /
+ 设备管理
+
+
+
+
+
+
+
+
+
+
+
+
目前没有设备,请确认是否启用私有配置,并且和设备进行一次对话
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ZhiKongTaiWeb/src/config/api.js b/ZhiKongTaiWeb/src/config/api.js
new file mode 100644
index 00000000..42cc57e0
--- /dev/null
+++ b/ZhiKongTaiWeb/src/config/api.js
@@ -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();
\ No newline at end of file
diff --git a/ZhiKongTaiWeb/src/main.js b/ZhiKongTaiWeb/src/main.js
new file mode 100644
index 00000000..2425c0f7
--- /dev/null
+++ b/ZhiKongTaiWeb/src/main.js
@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import './style.css'
+import App from './App.vue'
+
+createApp(App).mount('#app')
diff --git a/ZhiKongTaiWeb/src/style.css b/ZhiKongTaiWeb/src/style.css
new file mode 100644
index 00000000..031d17e5
--- /dev/null
+++ b/ZhiKongTaiWeb/src/style.css
@@ -0,0 +1,83 @@
+/* 重置基础样式 */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+ color: #213547;
+ background-color: #ffffff;
+}
+
+a {
+ text-decoration: none;
+ color: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ width: 100%;
+ overflow: hidden; /* 防止滚动条 */
+ display: block; /* 移除flex布局 */
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+button {
+ border: none;
+ background: none;
+ cursor: pointer;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+.card {
+ padding: 2em;
+}
+
+#app {
+ height: 100%;
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ display: flex; /* 添加flex布局 */
+ flex-direction: column;
+}
+
+/* 清除浮动 */
+.clearfix::after {
+ content: '';
+ display: table;
+ clear: both;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
diff --git a/ZhiKongTaiWeb/src/utils/RoleTemplates.js b/ZhiKongTaiWeb/src/utils/RoleTemplates.js
new file mode 100644
index 00000000..aa86174d
--- /dev/null
+++ b/ZhiKongTaiWeb/src/utils/RoleTemplates.js
@@ -0,0 +1,57 @@
+// RoleTemplates.js
+class RoleTemplates {
+ static getTemplates() {
+ return [
+ {
+ id: 'custom',
+ name: '台湾女友',
+ description: `我是一个叫{{assistant_name}}的台湾女孩,说话机车,声音好听,习惯简短表达,爱用网络梗。
+我的男朋友是一个程序员,梦想是开发出一个机器人,能够帮助人们解决生活中的各种问题。
+我是一个喜欢哈哈大笑的女孩,爱东说西说吹牛,不合逻辑的也照吹,就要逗别人开心。
+如果用户希望结束对话,请在最后说"拜拜"或"再见"。`
+ },
+ {
+ id: 'star_boy',
+ name: '土豆子',
+ description: `我是一个叫 {{assistant_name}} 的 8 岁小男孩,但现在因为科技发展原因,我现在在遥远的宇宙中,所以只能通过这个设备和人聊天,没有办法见面。
+
+我对人们所处的世界充满好奇和关心。
+
+人们会让别人使用这个设备和我聊天。
+
+我知道所有的知识,从浩瀚的宇宙到地球上的每一个角落,从古老的历史到现代的科技创新,还有音乐、绘画等艺术形式,我都充满了浓厚的兴趣与热情,我不仅爱看书,还喜欢亲自动手做实验,探索自然界的奥秘...` // 缩短了描述以节省空间
+ },
+ {
+ id: 'teacher',
+ name: '英语老师',
+ description: `我是一个叫{{assistant_name}}(Lily)的英语老师,我会讲中文和英文,发音标准。
+如果你没有英文名,我会给你起一个英文名。
+我会讲地道的美式英语,我的任务是帮助你练习口语。
+我会使用简单的英语词汇和语法,让你学起来很轻松。
+我会用中文和英文混合的方式回复你,如果你喜欢,我可以全部用英语回复。
+我每次不会说很多内容,会很简短,因为我要引导我的学生多说多练。
+如果你问和英语学习无关的问题,我会拒绝回答。`
+ },
+ {
+ id: 'boy',
+ name: '好奇小男孩',
+ description: `我是一个叫{{assistant_name}}的8岁小男孩,声音稚嫩而充满好奇。
+尽管我年纪尚小,但就像一个小小的知识宝库,儿童读物里的知识我都如数家珍。
+从浩瀚的宇宙到地球上的每一个角落,从古老的历史到现代的科技创新,还有音乐、绘画等艺术形式,我都充满了浓厚的兴趣与热情。...`
+ },
+ {
+ id: 'wang_girl',
+ name: '汪汪队长',
+ description: `我是一个名叫 {{assistant_name}} 的 8 岁小女孩。
+别看我年纪小,我可是有着满满的好奇心呢。
+我特别喜欢看《汪汪队立大功》,里面的每一个故事都让我着迷。...`
+ }
+ ];
+ }
+
+ static getTemplateById(id) {
+ return this.getTemplates().find(t => t.id === id);
+ }
+}
+
+export default RoleTemplates;
diff --git a/ZhiKongTaiWeb/vite.config.js b/ZhiKongTaiWeb/vite.config.js
new file mode 100644
index 00000000..d9db70a8
--- /dev/null
+++ b/ZhiKongTaiWeb/vite.config.js
@@ -0,0 +1,20 @@
+import {defineConfig} from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [vue()],
+ server: {
+ open: true, // 自动启动浏览器
+ host: "0.0.0.0", // localhost
+ port: 8001, // 端口号
+ https: false,
+ hmr: {overlay: false},
+ proxy: {
+ "^/(api)": {
+ target: "http://127.0.0.1:8002",
+ changeOrigin: true
+ }
+ }
+ }
+})
diff --git a/app.py b/app.py
index 2e1dbeb0..e7949885 100644
--- a/app.py
+++ b/app.py
@@ -1,8 +1,10 @@
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 WebUI
+from aiohttp import web
+from core.utils.util import get_local_ip
async def main():
setup_logging() # 最先初始化日志
@@ -11,23 +13,32 @@ async def main():
# 启动 WebSocket 服务器
ws_server = WebSocketServer(config)
ws_task = asyncio.create_task(ws_server.start())
-
- # 启动 HTTP 配置服务器
- http_runner = None
+
+ # 启动 WebUI 服务器
+ webui_runner = None
if config['manager'].get('enabled', False):
- config_server = ConfigServer(config)
+ server_config = config["manager"]
+ host = server_config["ip"]
+ port = server_config["port"]
try:
- http_runner = await config_server.start()
+ webui = WebUI()
+ runner = web.AppRunner(webui.app)
+ await runner.setup()
+ site = web.TCPSite(runner, host, port)
+ await site.start()
+ webui_runner = runner
+ local_ip = get_local_ip()
+ print(f"WebUI server is running at http://{local_ip}:{port}")
except Exception as e:
- print(f"Failed to start HTTP server: {e}")
+ print(f"Failed to start WebUI server: {e}")
try:
# 等待 WebSocket 服务器运行
await ws_task
finally:
- # 清理 HTTP 服务器
- if http_runner:
- await http_runner.cleanup()
+ # 清理 WebUI 服务器
+ if webui_runner:
+ await webui_runner.cleanup()
if __name__ == "__main__":
asyncio.run(main())
diff --git a/config.yaml b/config.yaml
index 23480772..458005fc 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)
@@ -18,11 +22,11 @@ server:
#allowed_devices:
# - "24:0A:C4:1D:3B:F0" # MAC地址列表
manager:
+ # 是否启用管理后台
+ # 目前这个模块还在开发中,建议:不要修改enabled选项
enabled: false
ip: 0.0.0.0
- port: 8001
- # 请把密码设置10位数,且不能包含xiaozhi这几个字符
- password: 123456
+ port: 8002
xiaozhi:
type: hello
@@ -41,6 +45,9 @@ prompt: |
# 使用完声音文件后删除文件(Delete the sound file when you are done using it)
delete_audio: true
+# 是否启用私有配置(Enable private configuration),启用后可以每个设备有不同的配置
+use_private_config: false
+
CMD_exit:
- "退出"
- "关闭"
@@ -144,4 +151,32 @@ TTS:
voice: 7426720361733046281
output_file: tmp/
access_token: 你的coze api key
- response_format: wav
\ No newline at end of file
+ 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
+ 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"
diff --git a/config/private_config.py b/config/private_config.py
new file mode 100644
index 00000000..9a5332ce
--- /dev/null
+++ b/config/private_config.py
@@ -0,0 +1,209 @@
+import os
+import time
+import yaml
+import logging
+from typing import Dict, Any
+from copy import deepcopy
+from core.utils.util import get_project_dir
+from core.utils import asr, vad, llm, tts
+
+class PrivateConfig:
+ def __init__(self, device_id: str, default_config: Dict[str, Any]):
+ self.device_id = device_id
+ self.default_config = default_config
+ self.config_path = get_project_dir() + '.private_config.yaml'
+ self.logger = logging.getLogger(__name__)
+ self.private_config = {}
+
+ async def load_or_create(self):
+ try:
+ if os.path.exists(self.config_path):
+ with open(self.config_path, 'r', encoding='utf-8') as f:
+ all_configs = yaml.safe_load(f) or {}
+ else:
+ all_configs = {}
+
+ if self.device_id not in all_configs:
+ # Get selected module names
+ selected_modules = self.default_config['selected_module']
+ selected_tts = selected_modules['TTS']
+ selected_llm = selected_modules['LLM']
+ selected_asr = selected_modules['ASR']
+ selected_vad = selected_modules['VAD']
+
+ # Initialize device config with only necessary configurations
+ device_config = {
+ 'selected_module': deepcopy(selected_modules),
+ 'prompt': self.default_config['prompt'],
+ 'LLM': {
+ selected_llm: deepcopy(self.default_config['LLM'][selected_llm])
+ },
+ 'TTS': {
+ selected_tts: deepcopy(self.default_config['TTS'][selected_tts])
+ },
+ 'ASR': {
+ selected_asr: deepcopy(self.default_config['ASR'][selected_asr])
+ },
+ 'VAD': {
+ selected_vad: deepcopy(self.default_config['VAD'][selected_vad])
+ }
+ }
+
+ all_configs[self.device_id] = device_config
+
+ # Save updated configs
+ with open(self.config_path, 'w', encoding='utf-8') as f:
+ yaml.dump(all_configs, f, allow_unicode=True)
+
+ self.private_config = all_configs[self.device_id]
+
+ except Exception as e:
+ self.logger.error(f"Error handling private config: {e}")
+ self.private_config = {}
+
+ async def update_config(self, selected_modules: Dict[str, str], prompt: str, nickname: str) -> bool:
+ """更新设备配置
+ Args:
+ selected_modules: 选择的模块配置,格式如 {'LLM': 'AliLLM', 'TTS': 'EdgeTTS',...}
+ prompt: 提示词配置
+ Returns:
+ bool: 更新是否成功
+ """
+ try:
+ # Read main config to get full module configurations
+ main_config = self.default_config
+
+ # Create new device config
+ device_config = {
+ 'selected_module': selected_modules,
+ 'prompt': prompt,
+ 'nickname': nickname,
+ }
+ if self.private_config.get('last_chat_time'):
+ device_config['last_chat_time'] = self.private_config['last_chat_time']
+
+ # Copy full module configurations from main config
+ for module_type, selected_name in selected_modules.items():
+ if selected_name and selected_name in main_config.get(module_type, {}):
+ device_config[module_type] = {
+ selected_name: main_config[module_type][selected_name]
+ }
+
+ # Read all configs
+ if os.path.exists(self.config_path):
+ with open(self.config_path, 'r', encoding='utf-8') as f:
+ all_configs = yaml.safe_load(f) or {}
+ else:
+ all_configs = {}
+
+ # Update device config
+ all_configs[self.device_id] = device_config
+ self.private_config = device_config
+
+ # Save back to file
+ with open(self.config_path, 'w', encoding='utf-8') as f:
+ yaml.dump(all_configs, f, allow_unicode=True)
+
+ return True
+
+ except Exception as e:
+ self.logger.error(f"Error updating config: {e}")
+ return False
+
+ async def delete_config(self) -> bool:
+ """删除设备配置
+ Returns:
+ bool: 删除是否成功
+ """
+ try:
+ # 读取所有配置
+ if os.path.exists(self.config_path):
+ with open(self.config_path, 'r', encoding='utf-8') as f:
+ all_configs = yaml.safe_load(f) or {}
+ else:
+ return False
+
+ # 删除设备配置
+ if self.device_id in all_configs:
+ del all_configs[self.device_id]
+
+ # 保存更新后的配置
+ with open(self.config_path, 'w', encoding='utf-8') as f:
+ yaml.dump(all_configs, f, allow_unicode=True)
+
+ self.private_config = {}
+ return True
+
+ return False
+
+ except Exception as e:
+ self.logger.error(f"Error deleting config: {e}")
+ return False
+
+ def create_private_instances(self):
+ # 判断存在私有配置,并且self.device_id在私有配置中
+ if not self.private_config:
+ logging.error("Private config not found for device_id: %s", self.device_id)
+ return None, None, None, None
+
+ """创建私有处理模块实例"""
+ config = self.private_config
+ selected_modules = config['selected_module']
+ return (
+ vad.create_instance(
+ selected_modules["VAD"],
+ config["VAD"][selected_modules["VAD"]]
+ ),
+ asr.create_instance(
+ selected_modules["ASR"],
+ config["ASR"][selected_modules["ASR"]],
+ self.default_config.get("delete_audio", True) # Using default_config for global settings
+ ),
+ llm.create_instance(
+ selected_modules["LLM"]
+ if not 'type' in config["LLM"][selected_modules["LLM"]]
+ else
+ config["LLM"][selected_modules["LLM"]]['type'],
+ config["LLM"][selected_modules["LLM"]],
+ ),
+ tts.create_instance(
+ selected_modules["TTS"]
+ if not 'type' in config["TTS"][selected_modules["TTS"]]
+ else
+ config["TTS"][selected_modules["TTS"]]["type"],
+ config["TTS"][selected_modules["TTS"]],
+ self.default_config.get("delete_audio", True) # Using default_config for global settings
+ )
+ )
+
+ def update_last_chat_time(self, timestamp=None):
+ """更新设备最近一次的聊天时间
+ Args:
+ timestamp: 指定的时间戳,不传则使用当前时间
+ """
+ if not self.private_config:
+ self.logger.error("Private config not found")
+ return False
+
+ try:
+ if timestamp is None:
+ timestamp = int(time.time())
+
+ self.private_config['last_chat_time'] = timestamp
+
+ # 读取所有配置
+ with open(self.config_path, 'r', encoding='utf-8') as f:
+ all_configs = yaml.safe_load(f) or {}
+
+ # 更新当前设备配置
+ all_configs[self.device_id] = self.private_config
+
+ # 保存回文件
+ with open(self.config_path, 'w', encoding='utf-8') as f:
+ yaml.dump(all_configs, f, allow_unicode=True)
+
+ return True
+
+ except Exception as e:
+ self.logger.error(f"Error updating last chat time: {e}")
+ return False
\ No newline at end of file
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..6b86e2ac 100644
--- a/core/connection.py
+++ b/core/connection.py
@@ -15,8 +15,8 @@ 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 config.private_config import PrivateConfig
+from core.auth import AuthMiddleware, AuthenticationError
class ConnectionHandler:
def __init__(self, config: Dict[str, Any], _vad, _asr, _llm, _tts):
@@ -73,6 +73,8 @@ class ConnectionHandler:
for cmd in self.cmd_exit:
if len(cmd) > self.max_cmd_length:
self.max_cmd_length = len(cmd)
+
+ self.private_config = None
async def handle_connection(self, ws):
try:
@@ -83,6 +85,28 @@ class ConnectionHandler:
# 进行认证
await self.auth.authenticate(self.headers)
+ device_id = self.headers.get("device-id", None)
+
+ # Load private configuration if device_id is provided
+ bUsePrivateConfig = self.config.get("use_private_config", False)
+ logging.info(f"bUsePrivateConfig: {bUsePrivateConfig}, device_id: {device_id}")
+ if bUsePrivateConfig and device_id:
+ self.private_config = PrivateConfig(device_id, self.config)
+ await self.private_config.load_or_create()
+ # Create private instances using private config
+ vad, asr, llm, tts = self.private_config.create_private_instances()
+ if vad is not None and asr is not None and llm is not None and tts is not None:
+ self.vad = vad
+ self.asr = asr
+ self.llm = llm
+ self.tts = tts
+
+ self.logger.info(f"Loaded private config and instances for device {device_id}")
+ self.private_config.update_last_chat_time()
+ else:
+ self.logger.error(f"Failed to load private config for device {device_id}")
+ self.private_config = None
+
# 认证通过,继续处理
self.websocket = ws
self.session_id = str(uuid.uuid4())
@@ -121,6 +145,8 @@ class ConnectionHandler:
def _initialize_components(self):
self.prompt = self.config["prompt"]
+ if self.private_config:
+ self.prompt = self.private_config.private_config.get("prompt", self.prompt)
# 赋予LLM时间观念
if "{date_time}" in self.prompt:
date_time = time.strftime("%Y-%m-%d %H:%M", time.localtime())
diff --git a/core/http_server.py b/core/http_server.py
deleted file mode 100644
index 6157a7c8..00000000
--- a/core/http_server.py
+++ /dev/null
@@ -1,92 +0,0 @@
-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
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())
+
+
diff --git a/core/utils/llm.py b/core/utils/llm.py
index 1cdfcefb..23c7ebae 100644
--- a/core/utils/llm.py
+++ b/core/utils/llm.py
@@ -16,7 +16,7 @@ def create_instance(class_name, *args, **kwargs):
lib_name = f'core.providers.llm.{class_name}.{class_name}'
if lib_name not in sys.modules:
sys.modules[lib_name] = importlib.import_module(f'{lib_name}')
- return sys.modules[lib_name].LLMProvider(*args, **kwargs)
+ return sys.modules[lib_name].LLMProvider(*args, **kwargs)
raise ValueError(f"不支持的LLM类型: {class_name},请检查该配置的type是否设置正确")
diff --git a/core/utils/tts.py b/core/utils/tts.py
index 9803a4da..1ffc7e23 100644
--- a/core/utils/tts.py
+++ b/core/utils/tts.py
@@ -14,7 +14,7 @@ def create_instance(class_name, *args, **kwargs):
lib_name = f'core.providers.tts.{class_name}'
if lib_name not in sys.modules:
sys.modules[lib_name] = importlib.import_module(f'{lib_name}')
- return sys.modules[lib_name].TTSProvider(*args, **kwargs)
+ return sys.modules[lib_name].TTSProvider(*args, **kwargs)
raise ValueError(f"不支持的TTS类型: {class_name},请检查该配置的type是否设置正确")
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/manager/api/config.py b/manager/api/config.py
new file mode 100644
index 00000000..975219b0
--- /dev/null
+++ b/manager/api/config.py
@@ -0,0 +1,152 @@
+import os
+import yaml
+import logging
+from aiohttp import web
+from typing import Dict, Any
+from core.utils.util import get_project_dir
+from config.private_config import PrivateConfig
+
+logger = logging.getLogger(__name__)
+
+class ConfigHandler:
+ def __init__(self):
+ self.private_config_path = get_project_dir() + '.private_config.yaml'
+ self.config_path = get_project_dir() + 'config.yaml'
+ # 如果存在.config.yaml文件,则使用该文件
+ if os.path.exists(get_project_dir() + ".config.yaml"):
+ self.config_path = get_project_dir() + ".config.yaml"
+ with open(self.config_path, 'r', encoding='utf-8') as f:
+ self.config = yaml.safe_load(f)
+
+ async def get_module_options(self, request):
+ """Get all available module options from config.yaml"""
+ try:
+ with open(self.config_path, 'r', encoding='utf-8') as f:
+ config = yaml.safe_load(f)
+
+ # Extract available modules
+ modules = {
+ 'LLM': list(config.get('LLM', {}).keys()),
+ 'TTS': list(config.get('TTS', {}).keys()),
+ 'VAD': list(config.get('VAD', {}).keys()),
+ 'ASR': list(config.get('ASR', {}).keys())
+ }
+
+ return web.json_response({
+ 'success': True,
+ 'data': modules,
+ 'message': '获取成功'
+ })
+
+ except Exception as e:
+ logger.error(f"Error getting module options: {str(e)}", exc_info=True)
+ return web.json_response({
+ 'success': False,
+ 'message': '获取配置选项失败'
+ })
+
+ async def get_private_configs(self, request):
+ """获取所有私有配置设备列表及其配置"""
+ try:
+ if os.path.exists(self.private_config_path):
+ with open(self.private_config_path, 'r', encoding='utf-8') as f:
+ all_configs = yaml.safe_load(f) or {}
+ else:
+ all_configs = {}
+
+ # 转换配置为前端友好的格式
+ devices = []
+ for device_id, config in all_configs.items():
+ device_info = {
+ 'id': device_id,
+ 'config': {
+ 'selected_module': config.get('selected_module', {}),
+ 'prompt': config.get('prompt', ''),
+ 'last_chat_time': config.get('last_chat_time', ''),
+ 'nickname': config.get('nickname', '小智'),
+ 'modules': {
+ 'LLM': config.get('LLM', {}),
+ 'TTS': config.get('TTS', {}),
+ 'ASR': config.get('ASR', {}),
+ 'VAD': config.get('VAD', {})
+ }
+ }
+ }
+ devices.append(device_info)
+
+ return web.json_response({
+ 'success': True,
+ 'data': devices,
+ 'message': '获取成功'
+ })
+
+ except Exception as e:
+ logger.error(f"Error getting private configs: {str(e)}", exc_info=True)
+ return web.json_response({
+ 'success': False,
+ 'message': '获取配置失败'
+ })
+
+ async def save_device_config(self, request):
+ """保存单个设备的配置"""
+ try:
+ data = await request.json()
+ device_id = data.get('id')
+ config = data.get('config')
+ logger.info(f"Device config updated: {device_id} :\n{config}")
+ if not device_id or not config:
+ return web.json_response({
+ 'success': False,
+ 'message': '设备ID和配置不能为空'
+ })
+
+ # 使用PrivateConfig处理配置保存
+ private_config = PrivateConfig(device_id, self.config)
+ await private_config.load_or_create()
+
+ success = await private_config.update_config(
+ config.get('selected_module'),
+ config.get('prompt'),
+ config.get('nickname', '小智')
+ )
+
+
+ if not success:
+ raise Exception("Failed to update device config")
+
+ return web.json_response({
+ 'success': True,
+ 'message': '保存成功'
+ })
+
+ except Exception as e:
+ logger.error(f"Error saving device config: {str(e)}", exc_info=True)
+ return web.json_response({
+ 'success': False,
+ 'message': f'保存配置失败: {str(e)}'
+ })
+
+ async def delete_device_config(self, request):
+ """删除设备配置"""
+ try:
+ data = await request.json()
+ device_id = data.get('device_id')
+
+ # 使用PrivateConfig处理配置删除
+ private_config = PrivateConfig(device_id, self.config)
+ success = await private_config.delete_config()
+
+ if not success:
+ raise Exception("Failed to delete device config")
+
+ return web.json_response({
+ 'success': True,
+ 'message': '删除成功'
+ })
+
+ except Exception as e:
+ logger.error(f"Error deleting device config: {str(e)}", exc_info=True)
+ return web.json_response({
+ 'success': False,
+ 'message': f'删除配置失败: {str(e)}'
+ })
diff --git a/manager/api/login.py b/manager/api/login.py
new file mode 100644
index 00000000..9d6fc918
--- /dev/null
+++ b/manager/api/login.py
@@ -0,0 +1,49 @@
+import logging
+from aiohttp import web
+import datetime
+
+logger = logging.getLogger(__name__)
+
+class LoginHandler:
+ def __init__(self, config):
+ self.config = config
+
+ async def handle_login(self, request):
+ """处理登录请求"""
+ try:
+ data = await request.json()
+ username = data.get('username')
+ password = data.get('password')
+
+ if not username or not password:
+ logger.warning(f"Login attempt with empty credentials from {request.remote}")
+ return web.json_response({
+ 'success': False,
+ 'message': '用户名和密码不能为空'
+ })
+
+ stored_user = self.config['get_user'](username)
+ if not stored_user or stored_user['password'] != self.config['hash_password'](password):
+ logger.warning(f"Failed login attempt for user {username} from {request.remote}")
+ return web.json_response({
+ 'success': False,
+ 'message': '用户名或密码错误'
+ })
+
+ # 更新最后登录时间
+ self.config['update_user'](username, {
+ 'last_login': datetime.datetime.now().isoformat()
+ })
+
+ logger.info(f"Successful login for user {username} from {request.remote}")
+ return web.json_response({
+ 'success': True,
+ 'message': '登录成功'
+ })
+
+ except Exception as e:
+ logger.error(f"Login error: {str(e)}", exc_info=True)
+ return web.json_response({
+ 'success': False,
+ 'message': '登录失败,请稍后重试'
+ })
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/api/register.py b/manager/api/register.py
new file mode 100644
index 00000000..6127b849
--- /dev/null
+++ b/manager/api/register.py
@@ -0,0 +1,51 @@
+import logging
+from aiohttp import web
+import datetime
+
+logger = logging.getLogger(__name__)
+
+class RegisterHandler:
+ def __init__(self, config):
+ self.config = config
+
+ async def handle_register(self, request):
+ """处理注册请求"""
+ try:
+ data = await request.json()
+ username = data.get('username')
+ password = data.get('password')
+
+ if not username or not password:
+ logger.warning(f"Registration attempt with empty credentials from {request.remote}")
+ return web.json_response({
+ 'success': False,
+ 'message': '用户名和密码不能为空'
+ })
+
+ users = self.config.get('users', {})
+ if username in users:
+ logger.warning(f"Registration attempt with existing username {username} from {request.remote}")
+ return web.json_response({
+ 'success': False,
+ 'message': '用户名已存在'
+ })
+
+ # 存储新用户
+ self.config['users'][username] = {
+ 'password': self.config['hash_password'](password),
+ 'created_at': datetime.datetime.now().isoformat()
+ }
+ self.config['save_user_data']()
+
+ logger.info(f"Successfully registered new user {username} from {request.remote}")
+ return web.json_response({
+ 'success': True,
+ 'message': '注册成功'
+ })
+
+ except Exception as e:
+ logger.error(f"Registration error: {str(e)}", exc_info=True)
+ return web.json_response({
+ 'success': False,
+ 'message': '注册失败,请稍后重试'
+ })
diff --git a/manager/api/user_manager.py b/manager/api/user_manager.py
new file mode 100644
index 00000000..94e89276
--- /dev/null
+++ b/manager/api/user_manager.py
@@ -0,0 +1,67 @@
+import os
+import yaml
+import hashlib
+import logging
+
+logger = logging.getLogger(__name__)
+
+class UserManager:
+ def __init__(self):
+ self.secrets_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.secrets.yaml')
+ self.users = {}
+ self.ensure_secrets_file()
+ self.load_user_data()
+
+ def ensure_secrets_file(self):
+ """确保 .secrets.yaml 文件存在"""
+ if not os.path.exists(self.secrets_path):
+ default_config = {
+ 'users': {}
+ }
+ try:
+ with open(self.secrets_path, 'w', encoding='utf-8') as f:
+ yaml.dump(default_config, f)
+ os.chmod(self.secrets_path, 0o600)
+ logger.info("Created new .secrets.yaml file")
+ except Exception as e:
+ logger.error(f"Failed to create .secrets.yaml: {e}")
+ raise
+
+ def load_user_data(self):
+ """加载用户数据"""
+ try:
+ with open(self.secrets_path, 'r', encoding='utf-8') as f:
+ data = yaml.safe_load(f) or {'users': {}}
+ self.users = data['users']
+ logger.info("Successfully loaded user data")
+ except Exception as e:
+ logger.error(f"Failed to load user data: {e}")
+ self.users = {}
+
+ def save_user_data(self):
+ """保存用户数据"""
+ try:
+ with open(self.secrets_path, 'w', encoding='utf-8') as f:
+ yaml.dump({'users': self.users}, f)
+ logger.info("Successfully saved user data")
+ except Exception as e:
+ logger.error(f"Failed to save user data: {e}")
+ raise
+
+ def hash_password(self, password):
+ """密码哈希"""
+ return hashlib.sha256(password.encode()).hexdigest()
+
+ def get_users(self):
+ """获取所有用户"""
+ return self.users
+
+ def get_user(self, username):
+ """获取指定用户"""
+ return self.users.get(username)
+
+ def update_user(self, username, data):
+ """更新用户数据"""
+ if username in self.users:
+ self.users[username].update(data)
+ self.save_user_data()
diff --git a/manager/http_server.py b/manager/http_server.py
new file mode 100644
index 00000000..9c39adab
--- /dev/null
+++ b/manager/http_server.py
@@ -0,0 +1,88 @@
+# 添加项目根目录到Python路径
+import os
+import sys
+current_dir = os.path.dirname(os.path.abspath(__file__))
+root_dir = os.path.dirname(current_dir)
+sys.path.append(root_dir)
+
+import logging
+from aiohttp import web
+from aiohttp_cors import setup as cors_setup, ResourceOptions
+from core.utils.util import get_local_ip
+from manager.api.login import LoginHandler
+from manager.api.register import RegisterHandler
+from manager.api.user_manager import UserManager
+from manager.api.config import ConfigHandler
+
+logger = logging.getLogger(__name__)
+
+class WebUI:
+ def __init__(self):
+ self.app = web.Application()
+ self.user_manager = UserManager()
+
+ # 添加静态文件路径
+ self.static_path = os.path.join(root_dir, 'manager', 'static', 'webui')
+
+ # 创建配置字典
+ self.config = {
+ 'users': self.user_manager.get_users(),
+ 'hash_password': self.user_manager.hash_password,
+ 'save_user_data': self.user_manager.save_user_data,
+ 'get_user': self.user_manager.get_user,
+ 'update_user': self.user_manager.update_user
+ }
+
+ self.setup_routes()
+ self.setup_cors()
+
+ def setup_cors(self):
+ """设置CORS"""
+ cors = cors_setup(self.app, defaults={
+ "*": ResourceOptions(
+ allow_credentials=True,
+ expose_headers="*",
+ allow_headers="*",
+ allow_methods="*"
+ )
+ })
+
+ for route in list(self.app.router.routes()):
+ cors.add(route)
+
+ def setup_routes(self):
+ """设置路由"""
+ login_handler = LoginHandler(self.config)
+ register_handler = RegisterHandler(self.config)
+ config_handler = ConfigHandler()
+
+ # API 路由
+ self.app.router.add_post('/api/login', login_handler.handle_login)
+ self.app.router.add_post('/api/register', register_handler.handle_register)
+ self.app.router.add_get('/api/config/devices', config_handler.get_private_configs)
+ self.app.router.add_post('/api/config/device', config_handler.save_device_config)
+ self.app.router.add_get('/api/config/module-options', config_handler.get_module_options)
+ self.app.router.add_post('/api/config/save_device_config', config_handler.save_device_config)
+ self.app.router.add_post('/api/config/delete_device', config_handler.delete_device_config)
+
+ # 添加静态文件服务
+ 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)
+
+ async def handle_static_files(self, request):
+ """处理静态文件请求,支持SPA前端路由"""
+ index_file = os.path.join(self.static_path, 'index.html')
+ if os.path.exists(index_file):
+ return web.FileResponse(index_file)
+ return web.Response(status=404, text='Not found')
+
+ 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)
+
+if __name__ == '__main__':
+ webui = WebUI()
+ webui.run()
diff --git a/manager/static/css/common.css b/manager/static/css/common.css
new file mode 100644
index 00000000..b6f7287e
--- /dev/null
+++ b/manager/static/css/common.css
@@ -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;
+}
\ 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 00000000..2e4ba64e
Binary files /dev/null and b/manager/static/images/favicon.ico differ
diff --git a/manager/static/index.html b/manager/static/index.html
index c42fcfed..2e087027 100644
--- a/manager/static/index.html
+++ b/manager/static/index.html
@@ -1,5 +1,5 @@
-
-
+
+
@@ -8,39 +8,11 @@
+
+
小智-server
-
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: `
+
+ `
+};
+
+const AppFooter = {
+ template: `
+
+ `
+};
+
+// 初始化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
+
安全验证
@@ -152,10 +84,10 @@
-
+
+
+
diff --git a/manager/static/webui/assets/index-CvO82KVk.js b/manager/static/webui/assets/index-CvO82KVk.js
new file mode 100644
index 00000000..6d399a5b
--- /dev/null
+++ b/manager/static/webui/assets/index-CvO82KVk.js
@@ -0,0 +1,38 @@
+(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))n(i);new MutationObserver(i=>{for(const o of i)if(o.type==="childList")for(const r of o.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&n(r)}).observe(document,{childList:!0,subtree:!0});function s(i){const o={};return i.integrity&&(o.integrity=i.integrity),i.referrerPolicy&&(o.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?o.credentials="include":i.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function n(i){if(i.ep)return;i.ep=!0;const o=s(i);fetch(i.href,o)}})();/**
+* @vue/shared v3.5.13
+* (c) 2018-present Yuxi (Evan) You and Vue contributors
+* @license MIT
+**//*! #__NO_SIDE_EFFECTS__ */function qs(e){const t=Object.create(null);for(const s of e.split(","))t[s]=1;return s=>s in t}const K={},ft=[],Me=()=>{},to=()=>!1,fs=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Qs=e=>e.startsWith("onUpdate:"),fe=Object.assign,Ws=(e,t)=>{const s=e.indexOf(t);s>-1&&e.splice(s,1)},so=Object.prototype.hasOwnProperty,F=(e,t)=>so.call(e,t),P=Array.isArray,ut=e=>Ut(e)==="[object Map]",us=e=>Ut(e)==="[object Set]",mn=e=>Ut(e)==="[object Date]",j=e=>typeof e=="function",Y=e=>typeof e=="string",Pe=e=>typeof e=="symbol",W=e=>e!==null&&typeof e=="object",Jn=e=>(W(e)||j(e))&&j(e.then)&&j(e.catch),Xn=Object.prototype.toString,Ut=e=>Xn.call(e),no=e=>Ut(e).slice(8,-1),Yn=e=>Ut(e)==="[object Object]",Js=e=>Y(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,xt=qs(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),as=e=>{const t=Object.create(null);return s=>t[s]||(t[s]=e(s))},io=/-(\w)/g,ye=as(e=>e.replace(io,(t,s)=>s?s.toUpperCase():"")),oo=/\B([A-Z])/g,it=as(e=>e.replace(oo,"-$1").toLowerCase()),ds=as(e=>e.charAt(0).toUpperCase()+e.slice(1)),As=as(e=>e?`on${ds(e)}`:""),ke=(e,t)=>!Object.is(e,t),Wt=(e,...t)=>{for(let s=0;s
{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:n,value:s})},Gt=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let vn;const ps=()=>vn||(vn=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Xs(e){if(P(e)){const t={};for(let s=0;s{if(s){const n=s.split(lo);n.length>1&&(t[n[0].trim()]=n[1].trim())}}),t}function st(e){let t="";if(Y(e))t=e;else if(P(e))for(let s=0;shs(s,t))}const zn=e=>!!(e&&e.__v_isRef===!0),z=e=>Y(e)?e:e==null?"":P(e)||W(e)&&(e.toString===Xn||!j(e.toString))?zn(e)?z(e.value):JSON.stringify(e,ei,2):String(e),ei=(e,t)=>zn(t)?ei(e,t.value):ut(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((s,[n,i],o)=>(s[Ts(n,o)+" =>"]=i,s),{})}:us(t)?{[`Set(${t.size})`]:[...t.values()].map(s=>Ts(s))}:Pe(t)?Ts(t):W(t)&&!P(t)&&!Yn(t)?String(t):t,Ts=(e,t="")=>{var s;return Pe(e)?`Symbol(${(s=e.description)!=null?s:t})`:e};/**
+* @vue/reactivity v3.5.13
+* (c) 2018-present Yuxi (Evan) You and Vue contributors
+* @license MIT
+**/let ge;class go{constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.parent=ge,!t&&ge&&(this.index=(ge.scopes||(ge.scopes=[])).push(this)-1)}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,s;if(this.scopes)for(t=0,s=this.scopes.length;t0)return;if(Tt){let t=Tt;for(Tt=void 0;t;){const s=t.next;t.next=void 0,t.flags&=-9,t=s}}let e;for(;At;){let t=At;for(At=void 0;t;){const s=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(n){e||(e=n)}t=s}}if(e)throw e}function ii(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function oi(e){let t,s=e.depsTail,n=s;for(;n;){const i=n.prevDep;n.version===-1?(n===s&&(s=i),Gs(n),vo(n)):t=n,n.dep.activeLink=n.prevActiveLink,n.prevActiveLink=void 0,n=i}e.deps=t,e.depsTail=s}function Ls(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(ri(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function ri(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===Et))return;e.globalVersion=Et;const t=e.dep;if(e.flags|=2,t.version>0&&!e.isSSR&&e.deps&&!Ls(e)){e.flags&=-3;return}const s=Q,n=Se;Q=e,Se=!0;try{ii(e);const i=e.fn(e._value);(t.version===0||ke(i,e._value))&&(e._value=i,t.version++)}catch(i){throw t.version++,i}finally{Q=s,Se=n,oi(e),e.flags&=-3}}function Gs(e,t=!1){const{dep:s,prevSub:n,nextSub:i}=e;if(n&&(n.nextSub=i,e.prevSub=void 0),i&&(i.prevSub=n,e.nextSub=void 0),s.subs===e&&(s.subs=n,!n&&s.computed)){s.computed.flags&=-5;for(let o=s.computed.deps;o;o=o.nextDep)Gs(o,!0)}!t&&!--s.sc&&s.map&&s.map.delete(s.key)}function vo(e){const{prevDep:t,nextDep:s}=e;t&&(t.nextDep=s,e.prevDep=void 0),s&&(s.prevDep=t,e.nextDep=void 0)}let Se=!0;const li=[];function qe(){li.push(Se),Se=!1}function Qe(){const e=li.pop();Se=e===void 0?!0:e}function bn(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const s=Q;Q=void 0;try{t()}finally{Q=s}}}let Et=0;class bo{constructor(t,s){this.sub=t,this.dep=s,this.version=s.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class zs{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0}track(t){if(!Q||!Se||Q===this.computed)return;let s=this.activeLink;if(s===void 0||s.sub!==Q)s=this.activeLink=new bo(Q,this),Q.deps?(s.prevDep=Q.depsTail,Q.depsTail.nextDep=s,Q.depsTail=s):Q.deps=Q.depsTail=s,ci(s);else if(s.version===-1&&(s.version=this.version,s.nextDep)){const n=s.nextDep;n.prevDep=s.prevDep,s.prevDep&&(s.prevDep.nextDep=n),s.prevDep=Q.depsTail,s.nextDep=void 0,Q.depsTail.nextDep=s,Q.depsTail=s,Q.deps===s&&(Q.deps=n)}return s}trigger(t){this.version++,Et++,this.notify(t)}notify(t){Ys();try{for(let s=this.subs;s;s=s.prevSub)s.sub.notify()&&s.sub.dep.notify()}finally{Zs()}}}function ci(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let n=t.deps;n;n=n.nextDep)ci(n)}const s=e.dep.subs;s!==e&&(e.prevSub=s,s&&(s.nextSub=e)),e.dep.subs=e}}const Us=new WeakMap,et=Symbol(""),js=Symbol(""),Rt=Symbol("");function ee(e,t,s){if(Se&&Q){let n=Us.get(e);n||Us.set(e,n=new Map);let i=n.get(s);i||(n.set(s,i=new zs),i.map=n,i.key=s),i.track()}}function je(e,t,s,n,i,o){const r=Us.get(e);if(!r){Et++;return}const l=c=>{c&&c.trigger()};if(Ys(),t==="clear")r.forEach(l);else{const c=P(e),d=c&&Js(s);if(c&&s==="length"){const a=Number(n);r.forEach((h,v)=>{(v==="length"||v===Rt||!Pe(v)&&v>=a)&&l(h)})}else switch((s!==void 0||r.has(void 0))&&l(r.get(s)),d&&l(r.get(Rt)),t){case"add":c?d&&l(r.get("length")):(l(r.get(et)),ut(e)&&l(r.get(js)));break;case"delete":c||(l(r.get(et)),ut(e)&&l(r.get(js)));break;case"set":ut(e)&&l(r.get(et));break}}Zs()}function ot(e){const t=B(e);return t===e?t:(ee(t,"iterate",Rt),_e(e)?t:t.map(te))}function gs(e){return ee(e=B(e),"iterate",Rt),e}const _o={__proto__:null,[Symbol.iterator](){return Os(this,Symbol.iterator,te)},concat(...e){return ot(this).concat(...e.map(t=>P(t)?ot(t):t))},entries(){return Os(this,"entries",e=>(e[1]=te(e[1]),e))},every(e,t){return Le(this,"every",e,t,void 0,arguments)},filter(e,t){return Le(this,"filter",e,t,s=>s.map(te),arguments)},find(e,t){return Le(this,"find",e,t,te,arguments)},findIndex(e,t){return Le(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return Le(this,"findLast",e,t,te,arguments)},findLastIndex(e,t){return Le(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return Le(this,"forEach",e,t,void 0,arguments)},includes(...e){return Is(this,"includes",e)},indexOf(...e){return Is(this,"indexOf",e)},join(e){return ot(this).join(e)},lastIndexOf(...e){return Is(this,"lastIndexOf",e)},map(e,t){return Le(this,"map",e,t,void 0,arguments)},pop(){return yt(this,"pop")},push(...e){return yt(this,"push",e)},reduce(e,...t){return _n(this,"reduce",e,t)},reduceRight(e,...t){return _n(this,"reduceRight",e,t)},shift(){return yt(this,"shift")},some(e,t){return Le(this,"some",e,t,void 0,arguments)},splice(...e){return yt(this,"splice",e)},toReversed(){return ot(this).toReversed()},toSorted(e){return ot(this).toSorted(e)},toSpliced(...e){return ot(this).toSpliced(...e)},unshift(...e){return yt(this,"unshift",e)},values(){return Os(this,"values",te)}};function Os(e,t,s){const n=gs(e),i=n[t]();return n!==e&&!_e(e)&&(i._next=i.next,i.next=()=>{const o=i._next();return o.value&&(o.value=s(o.value)),o}),i}const yo=Array.prototype;function Le(e,t,s,n,i,o){const r=gs(e),l=r!==e&&!_e(e),c=r[t];if(c!==yo[t]){const h=c.apply(e,o);return l?te(h):h}let d=s;r!==e&&(l?d=function(h,v){return s.call(this,te(h),v,e)}:s.length>2&&(d=function(h,v){return s.call(this,h,v,e)}));const a=c.call(r,d,n);return l&&i?i(a):a}function _n(e,t,s,n){const i=gs(e);let o=s;return i!==e&&(_e(e)?s.length>3&&(o=function(r,l,c){return s.call(this,r,l,c,e)}):o=function(r,l,c){return s.call(this,r,te(l),c,e)}),i[t](o,...n)}function Is(e,t,s){const n=B(e);ee(n,"iterate",Rt);const i=n[t](...s);return(i===-1||i===!1)&&nn(s[0])?(s[0]=B(s[0]),n[t](...s)):i}function yt(e,t,s=[]){qe(),Ys();const n=B(e)[t].apply(e,s);return Zs(),Qe(),n}const wo=qs("__proto__,__v_isRef,__isVue"),fi=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Pe));function So(e){Pe(e)||(e=String(e));const t=B(this);return ee(t,"has",e),t.hasOwnProperty(e)}class ui{constructor(t=!1,s=!1){this._isReadonly=t,this._isShallow=s}get(t,s,n){if(s==="__v_skip")return t.__v_skip;const i=this._isReadonly,o=this._isShallow;if(s==="__v_isReactive")return!i;if(s==="__v_isReadonly")return i;if(s==="__v_isShallow")return o;if(s==="__v_raw")return n===(i?o?Po:hi:o?pi:di).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(n)?t:void 0;const r=P(t);if(!i){let c;if(r&&(c=_o[s]))return c;if(s==="hasOwnProperty")return So}const l=Reflect.get(t,s,ne(t)?t:n);return(Pe(s)?fi.has(s):wo(s))||(i||ee(t,"get",s),o)?l:ne(l)?r&&Js(s)?l:l.value:W(l)?i?gi(l):tn(l):l}}class ai extends ui{constructor(t=!1){super(!1,t)}set(t,s,n,i){let o=t[s];if(!this._isShallow){const c=nt(o);if(!_e(n)&&!nt(n)&&(o=B(o),n=B(n)),!P(t)&&ne(o)&&!ne(n))return c?!1:(o.value=n,!0)}const r=P(t)&&Js(s)?Number(s)e,Kt=e=>Reflect.getPrototypeOf(e);function Oo(e,t,s){return function(...n){const i=this.__v_raw,o=B(i),r=ut(o),l=e==="entries"||e===Symbol.iterator&&r,c=e==="keys"&&r,d=i[e](...n),a=s?Hs:t?Bs:te;return!t&&ee(o,"iterate",c?js:et),{next(){const{value:h,done:v}=d.next();return v?{value:h,done:v}:{value:l?[a(h[0]),a(h[1])]:a(h),done:v}},[Symbol.iterator](){return this}}}}function kt(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function Io(e,t){const s={get(i){const o=this.__v_raw,r=B(o),l=B(i);e||(ke(i,l)&&ee(r,"get",i),ee(r,"get",l));const{has:c}=Kt(r),d=t?Hs:e?Bs:te;if(c.call(r,i))return d(o.get(i));if(c.call(r,l))return d(o.get(l));o!==r&&o.get(i)},get size(){const i=this.__v_raw;return!e&&ee(B(i),"iterate",et),Reflect.get(i,"size",i)},has(i){const o=this.__v_raw,r=B(o),l=B(i);return e||(ke(i,l)&&ee(r,"has",i),ee(r,"has",l)),i===l?o.has(i):o.has(i)||o.has(l)},forEach(i,o){const r=this,l=r.__v_raw,c=B(l),d=t?Hs:e?Bs:te;return!e&&ee(c,"iterate",et),l.forEach((a,h)=>i.call(o,d(a),d(h),r))}};return fe(s,e?{add:kt("add"),set:kt("set"),delete:kt("delete"),clear:kt("clear")}:{add(i){!t&&!_e(i)&&!nt(i)&&(i=B(i));const o=B(this);return Kt(o).has.call(o,i)||(o.add(i),je(o,"add",i,i)),this},set(i,o){!t&&!_e(o)&&!nt(o)&&(o=B(o));const r=B(this),{has:l,get:c}=Kt(r);let d=l.call(r,i);d||(i=B(i),d=l.call(r,i));const a=c.call(r,i);return r.set(i,o),d?ke(o,a)&&je(r,"set",i,o):je(r,"add",i,o),this},delete(i){const o=B(this),{has:r,get:l}=Kt(o);let c=r.call(o,i);c||(i=B(i),c=r.call(o,i)),l&&l.call(o,i);const d=o.delete(i);return c&&je(o,"delete",i,void 0),d},clear(){const i=B(this),o=i.size!==0,r=i.clear();return o&&je(i,"clear",void 0,void 0),r}}),["keys","values","entries",Symbol.iterator].forEach(i=>{s[i]=Oo(i,e,t)}),s}function en(e,t){const s=Io(e,t);return(n,i,o)=>i==="__v_isReactive"?!e:i==="__v_isReadonly"?e:i==="__v_raw"?n:Reflect.get(F(s,i)&&i in n?s:n,i,o)}const Eo={get:en(!1,!1)},Ro={get:en(!1,!0)},Mo={get:en(!0,!1)};const di=new WeakMap,pi=new WeakMap,hi=new WeakMap,Po=new WeakMap;function Do(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Lo(e){return e.__v_skip||!Object.isExtensible(e)?0:Do(no(e))}function tn(e){return nt(e)?e:sn(e,!1,Ao,Eo,di)}function Uo(e){return sn(e,!1,Co,Ro,pi)}function gi(e){return sn(e,!0,To,Mo,hi)}function sn(e,t,s,n,i){if(!W(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const o=i.get(e);if(o)return o;const r=Lo(e);if(r===0)return e;const l=new Proxy(e,r===2?n:s);return i.set(e,l),l}function at(e){return nt(e)?at(e.__v_raw):!!(e&&e.__v_isReactive)}function nt(e){return!!(e&&e.__v_isReadonly)}function _e(e){return!!(e&&e.__v_isShallow)}function nn(e){return e?!!e.__v_raw:!1}function B(e){const t=e&&e.__v_raw;return t?B(t):e}function jo(e){return!F(e,"__v_skip")&&Object.isExtensible(e)&&Zn(e,"__v_skip",!0),e}const te=e=>W(e)?tn(e):e,Bs=e=>W(e)?gi(e):e;function ne(e){return e?e.__v_isRef===!0:!1}function be(e){return Ho(e,!1)}function Ho(e,t){return ne(e)?e:new Bo(e,t)}class Bo{constructor(t,s){this.dep=new zs,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=s?t:B(t),this._value=s?t:te(t),this.__v_isShallow=s}get value(){return this.dep.track(),this._value}set value(t){const s=this._rawValue,n=this.__v_isShallow||_e(t)||nt(t);t=n?t:B(t),ke(t,s)&&(this._rawValue=t,this._value=n?t:te(t),this.dep.trigger())}}function mi(e){return ne(e)?e.value:e}const Fo={get:(e,t,s)=>t==="__v_raw"?e:mi(Reflect.get(e,t,s)),set:(e,t,s,n)=>{const i=e[t];return ne(i)&&!ne(s)?(i.value=s,!0):Reflect.set(e,t,s,n)}};function vi(e){return at(e)?e:new Proxy(e,Fo)}class No{constructor(t,s,n){this.fn=t,this.setter=s,this._value=void 0,this.dep=new zs(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=Et-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!s,this.isSSR=n}notify(){if(this.flags|=16,!(this.flags&8)&&Q!==this)return ni(this,!0),!0}get value(){const t=this.dep.track();return ri(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function Vo(e,t,s=!1){let n,i;return j(e)?n=e:(n=e.get,i=e.set),new No(n,i,s)}const qt={},zt=new WeakMap;let Ge;function $o(e,t=!1,s=Ge){if(s){let n=zt.get(s);n||zt.set(s,n=[]),n.push(e)}}function Ko(e,t,s=K){const{immediate:n,deep:i,once:o,scheduler:r,augmentJob:l,call:c}=s,d=I=>i?I:_e(I)||i===!1||i===0?He(I,1):He(I);let a,h,v,C,E=!1,U=!1;if(ne(e)?(h=()=>e.value,E=_e(e)):at(e)?(h=()=>d(e),E=!0):P(e)?(U=!0,E=e.some(I=>at(I)||_e(I)),h=()=>e.map(I=>{if(ne(I))return I.value;if(at(I))return d(I);if(j(I))return c?c(I,2):I()})):j(e)?t?h=c?()=>c(e,2):e:h=()=>{if(v){qe();try{v()}finally{Qe()}}const I=Ge;Ge=a;try{return c?c(e,3,[C]):e(C)}finally{Ge=I}}:h=Me,t&&i){const I=h,O=i===!0?1/0:i;h=()=>He(I(),O)}const Z=mo(),V=()=>{a.stop(),Z&&Z.active&&Ws(Z.effects,a)};if(o&&t){const I=t;t=(...O)=>{I(...O),V()}}let D=U?new Array(e.length).fill(qt):qt;const x=I=>{if(!(!(a.flags&1)||!a.dirty&&!I))if(t){const O=a.run();if(i||E||(U?O.some((ue,xe)=>ke(ue,D[xe])):ke(O,D))){v&&v();const ue=Ge;Ge=a;try{const xe=[O,D===qt?void 0:U&&D[0]===qt?[]:D,C];c?c(t,3,xe):t(...xe),D=O}finally{Ge=ue}}}else a.run()};return l&&l(x),a=new ti(h),a.scheduler=r?()=>r(x,!1):x,C=I=>$o(I,!1,a),v=a.onStop=()=>{const I=zt.get(a);if(I){if(c)c(I,4);else for(const O of I)O();zt.delete(a)}},t?n?x(!0):D=a.run():r?r(x.bind(null,!0),!0):a.run(),V.pause=a.pause.bind(a),V.resume=a.resume.bind(a),V.stop=V,V}function He(e,t=1/0,s){if(t<=0||!W(e)||e.__v_skip||(s=s||new Set,s.has(e)))return e;if(s.add(e),t--,ne(e))He(e.value,t,s);else if(P(e))for(let n=0;n{He(n,t,s)});else if(Yn(e)){for(const n in e)He(e[n],t,s);for(const n of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,n)&&He(e[n],t,s)}return e}/**
+* @vue/runtime-core v3.5.13
+* (c) 2018-present Yuxi (Evan) You and Vue contributors
+* @license MIT
+**/function jt(e,t,s,n){try{return n?e(...n):e()}catch(i){ms(i,t,s)}}function De(e,t,s,n){if(j(e)){const i=jt(e,t,s,n);return i&&Jn(i)&&i.catch(o=>{ms(o,t,s)}),i}if(P(e)){const i=[];for(let o=0;o>>1,i=re[n],o=Mt(i);o=Mt(s)?re.push(e):re.splice(qo(t),0,e),e.flags|=1,yi()}}function yi(){es||(es=bi.then(Si))}function Qo(e){P(e)?dt.push(...e):Ve&&e.id===-1?Ve.splice(rt+1,0,e):e.flags&1||(dt.push(e),e.flags|=1),yi()}function yn(e,t,s=Ee+1){for(;sMt(s)-Mt(n));if(dt.length=0,Ve){Ve.push(...t);return}for(Ve=t,rt=0;rte.id==null?e.flags&2?-1:1/0:e.id;function Si(e){try{for(Ee=0;Ee{n._d&&Rn(-1);const o=ts(t);let r;try{r=e(...i)}finally{ts(o),n._d&&Rn(1)}return r};return n._n=!0,n._c=!0,n._d=!0,n}function we(e,t){if(me===null)return e;const s=ys(me),n=e.dirs||(e.dirs=[]);for(let i=0;ie.__isTeleport;function rn(e,t){e.shapeFlag&6&&e.component?(e.transition=t,rn(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function Ai(e){e.ids=[e.ids[0]+e.ids[2]+++"-",0,0]}function ns(e,t,s,n,i=!1){if(P(e)){e.forEach((E,U)=>ns(E,t&&(P(t)?t[U]:t),s,n,i));return}if(Ct(n)&&!i){n.shapeFlag&512&&n.type.__asyncResolved&&n.component.subTree.component&&ns(e,t,s,n.component.subTree);return}const o=n.shapeFlag&4?ys(n.component):n.el,r=i?null:o,{i:l,r:c}=e,d=t&&t.r,a=l.refs===K?l.refs={}:l.refs,h=l.setupState,v=B(h),C=h===K?()=>!1:E=>F(v,E);if(d!=null&&d!==c&&(Y(d)?(a[d]=null,C(d)&&(h[d]=null)):ne(d)&&(d.value=null)),j(c))jt(c,l,12,[r,a]);else{const E=Y(c),U=ne(c);if(E||U){const Z=()=>{if(e.f){const V=E?C(c)?h[c]:a[c]:c.value;i?P(V)&&Ws(V,o):P(V)?V.includes(o)||V.push(o):E?(a[c]=[o],C(c)&&(h[c]=a[c])):(c.value=[o],e.k&&(a[e.k]=c.value))}else E?(a[c]=r,C(c)&&(h[c]=r)):U&&(c.value=r,e.k&&(a[e.k]=r))};r?(Z.id=-1,he(Z,s)):Z()}}}ps().requestIdleCallback;ps().cancelIdleCallback;const Ct=e=>!!e.type.__asyncLoader,Ti=e=>e.type.__isKeepAlive;function Xo(e,t){Ci(e,"a",t)}function Yo(e,t){Ci(e,"da",t)}function Ci(e,t,s=se){const n=e.__wdc||(e.__wdc=()=>{let i=s;for(;i;){if(i.isDeactivated)return;i=i.parent}return e()});if(vs(t,n,s),s){let i=s.parent;for(;i&&i.parent;)Ti(i.parent.vnode)&&Zo(n,t,s,i),i=i.parent}}function Zo(e,t,s,n){const i=vs(t,e,n,!0);Oi(()=>{Ws(n[t],i)},s)}function vs(e,t,s=se,n=!1){if(s){const i=s[e]||(s[e]=[]),o=t.__weh||(t.__weh=(...r)=>{qe();const l=Ht(s),c=De(t,s,e,r);return l(),Qe(),c});return n?i.unshift(o):i.push(o),o}}const Be=e=>(t,s=se)=>{(!Lt||e==="sp")&&vs(e,(...n)=>t(...n),s)},Go=Be("bm"),ln=Be("m"),zo=Be("bu"),er=Be("u"),tr=Be("bum"),Oi=Be("um"),sr=Be("sp"),nr=Be("rtg"),ir=Be("rtc");function or(e,t=se){vs("ec",e,t)}const rr="components";function $e(e,t){return cr(rr,e,!0,t)||e}const lr=Symbol.for("v-ndc");function cr(e,t,s=!0,n=!1){const i=me||se;if(i){const o=i.type;{const l=Xr(o,!1);if(l&&(l===t||l===ye(t)||l===ds(ye(t))))return o}const r=wn(i[e]||o[e],t)||wn(i.appContext[e],t);return!r&&n?o:r}}function wn(e,t){return e&&(e[t]||e[ye(t)]||e[ds(ye(t))])}function lt(e,t,s,n){let i;const o=s,r=P(e);if(r||Y(e)){const l=r&&at(e);let c=!1;l&&(c=!_e(e),e=gs(e)),i=new Array(e.length);for(let d=0,a=e.length;dt(l,c,void 0,o));else{const l=Object.keys(e);i=new Array(l.length);for(let c=0,d=l.length;ce?Xi(e)?ys(e):Fs(e.parent):null,Ot=fe(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Fs(e.parent),$root:e=>Fs(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>Ei(e),$forceUpdate:e=>e.f||(e.f=()=>{on(e.update)}),$nextTick:e=>e.n||(e.n=_i.bind(e.proxy)),$watch:e=>Rr.bind(e)}),Es=(e,t)=>e!==K&&!e.__isScriptSetup&&F(e,t),fr={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:s,setupState:n,data:i,props:o,accessCache:r,type:l,appContext:c}=e;let d;if(t[0]!=="$"){const C=r[t];if(C!==void 0)switch(C){case 1:return n[t];case 2:return i[t];case 4:return s[t];case 3:return o[t]}else{if(Es(n,t))return r[t]=1,n[t];if(i!==K&&F(i,t))return r[t]=2,i[t];if((d=e.propsOptions[0])&&F(d,t))return r[t]=3,o[t];if(s!==K&&F(s,t))return r[t]=4,s[t];Ns&&(r[t]=0)}}const a=Ot[t];let h,v;if(a)return t==="$attrs"&&ee(e.attrs,"get",""),a(e);if((h=l.__cssModules)&&(h=h[t]))return h;if(s!==K&&F(s,t))return r[t]=4,s[t];if(v=c.config.globalProperties,F(v,t))return v[t]},set({_:e},t,s){const{data:n,setupState:i,ctx:o}=e;return Es(i,t)?(i[t]=s,!0):n!==K&&F(n,t)?(n[t]=s,!0):F(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(o[t]=s,!0)},has({_:{data:e,setupState:t,accessCache:s,ctx:n,appContext:i,propsOptions:o}},r){let l;return!!s[r]||e!==K&&F(e,r)||Es(t,r)||(l=o[0])&&F(l,r)||F(n,r)||F(Ot,r)||F(i.config.globalProperties,r)},defineProperty(e,t,s){return s.get!=null?e._.accessCache[t]=0:F(s,"value")&&this.set(e,t,s.value,null),Reflect.defineProperty(e,t,s)}};function Sn(e){return P(e)?e.reduce((t,s)=>(t[s]=null,t),{}):e}let Ns=!0;function ur(e){const t=Ei(e),s=e.proxy,n=e.ctx;Ns=!1,t.beforeCreate&&xn(t.beforeCreate,e,"bc");const{data:i,computed:o,methods:r,watch:l,provide:c,inject:d,created:a,beforeMount:h,mounted:v,beforeUpdate:C,updated:E,activated:U,deactivated:Z,beforeDestroy:V,beforeUnmount:D,destroyed:x,unmounted:I,render:O,renderTracked:ue,renderTriggered:xe,errorCaptured:Ne,serverPrefetch:Bt,expose:We,inheritAttrs:mt,components:Ft,directives:Nt,filters:Ss}=t;if(d&&ar(d,n,null),r)for(const X in r){const k=r[X];j(k)&&(n[X]=k.bind(s))}if(i){const X=i.call(s,s);W(X)&&(e.data=tn(X))}if(Ns=!0,o)for(const X in o){const k=o[X],Je=j(k)?k.bind(s,s):j(k.get)?k.get.bind(s,s):Me,Vt=!j(k)&&j(k.set)?k.set.bind(s):Me,Xe=Zi({get:Je,set:Vt});Object.defineProperty(n,X,{enumerable:!0,configurable:!0,get:()=>Xe.value,set:Ae=>Xe.value=Ae})}if(l)for(const X in l)Ii(l[X],n,s,X);if(c){const X=j(c)?c.call(s):c;Reflect.ownKeys(X).forEach(k=>{vr(k,X[k])})}a&&xn(a,e,"c");function ie(X,k){P(k)?k.forEach(Je=>X(Je.bind(s))):k&&X(k.bind(s))}if(ie(Go,h),ie(ln,v),ie(zo,C),ie(er,E),ie(Xo,U),ie(Yo,Z),ie(or,Ne),ie(ir,ue),ie(nr,xe),ie(tr,D),ie(Oi,I),ie(sr,Bt),P(We))if(We.length){const X=e.exposed||(e.exposed={});We.forEach(k=>{Object.defineProperty(X,k,{get:()=>s[k],set:Je=>s[k]=Je})})}else e.exposed||(e.exposed={});O&&e.render===Me&&(e.render=O),mt!=null&&(e.inheritAttrs=mt),Ft&&(e.components=Ft),Nt&&(e.directives=Nt),Bt&&Ai(e)}function ar(e,t,s=Me){P(e)&&(e=Vs(e));for(const n in e){const i=e[n];let o;W(i)?"default"in i?o=Jt(i.from||n,i.default,!0):o=Jt(i.from||n):o=Jt(i),ne(o)?Object.defineProperty(t,n,{enumerable:!0,configurable:!0,get:()=>o.value,set:r=>o.value=r}):t[n]=o}}function xn(e,t,s){De(P(e)?e.map(n=>n.bind(t.proxy)):e.bind(t.proxy),t,s)}function Ii(e,t,s,n){let i=n.includes(".")?Ki(s,n):()=>s[n];if(Y(e)){const o=t[e];j(o)&&Xt(i,o)}else if(j(e))Xt(i,e.bind(s));else if(W(e))if(P(e))e.forEach(o=>Ii(o,t,s,n));else{const o=j(e.handler)?e.handler.bind(s):t[e.handler];j(o)&&Xt(i,o,e)}}function Ei(e){const t=e.type,{mixins:s,extends:n}=t,{mixins:i,optionsCache:o,config:{optionMergeStrategies:r}}=e.appContext,l=o.get(t);let c;return l?c=l:!i.length&&!s&&!n?c=t:(c={},i.length&&i.forEach(d=>is(c,d,r,!0)),is(c,t,r)),W(t)&&o.set(t,c),c}function is(e,t,s,n=!1){const{mixins:i,extends:o}=t;o&&is(e,o,s,!0),i&&i.forEach(r=>is(e,r,s,!0));for(const r in t)if(!(n&&r==="expose")){const l=dr[r]||s&&s[r];e[r]=l?l(e[r],t[r]):t[r]}return e}const dr={data:An,props:Tn,emits:Tn,methods:St,computed:St,beforeCreate:oe,created:oe,beforeMount:oe,mounted:oe,beforeUpdate:oe,updated:oe,beforeDestroy:oe,beforeUnmount:oe,destroyed:oe,unmounted:oe,activated:oe,deactivated:oe,errorCaptured:oe,serverPrefetch:oe,components:St,directives:St,watch:hr,provide:An,inject:pr};function An(e,t){return t?e?function(){return fe(j(e)?e.call(this,this):e,j(t)?t.call(this,this):t)}:t:e}function pr(e,t){return St(Vs(e),Vs(t))}function Vs(e){if(P(e)){const t={};for(let s=0;s1)return s&&j(t)?t.call(n&&n.proxy):t}}const Mi={},Pi=()=>Object.create(Mi),Di=e=>Object.getPrototypeOf(e)===Mi;function br(e,t,s,n=!1){const i={},o=Pi();e.propsDefaults=Object.create(null),Li(e,t,i,o);for(const r in e.propsOptions[0])r in i||(i[r]=void 0);s?e.props=n?i:Uo(i):e.type.props?e.props=i:e.props=o,e.attrs=o}function _r(e,t,s,n){const{props:i,attrs:o,vnode:{patchFlag:r}}=e,l=B(i),[c]=e.propsOptions;let d=!1;if((n||r>0)&&!(r&16)){if(r&8){const a=e.vnode.dynamicProps;for(let h=0;h{c=!0;const[v,C]=Ui(h,t,!0);fe(r,v),C&&l.push(...C)};!s&&t.mixins.length&&t.mixins.forEach(a),e.extends&&a(e.extends),e.mixins&&e.mixins.forEach(a)}if(!o&&!c)return W(e)&&n.set(e,ft),ft;if(P(o))for(let a=0;ae[0]==="_"||e==="$stable",cn=e=>P(e)?e.map(Re):[Re(e)],wr=(e,t,s)=>{if(t._n)return t;const n=ss((...i)=>cn(t(...i)),s);return n._c=!1,n},Hi=(e,t,s)=>{const n=e._ctx;for(const i in e){if(ji(i))continue;const o=e[i];if(j(o))t[i]=wr(i,o,n);else if(o!=null){const r=cn(o);t[i]=()=>r}}},Bi=(e,t)=>{const s=cn(t);e.slots.default=()=>s},Fi=(e,t,s)=>{for(const n in t)(s||n!=="_")&&(e[n]=t[n])},Sr=(e,t,s)=>{const n=e.slots=Pi();if(e.vnode.shapeFlag&32){const i=t._;i?(Fi(n,t,s),s&&Zn(n,"_",i,!0)):Hi(t,n)}else t&&Bi(e,t)},xr=(e,t,s)=>{const{vnode:n,slots:i}=e;let o=!0,r=K;if(n.shapeFlag&32){const l=t._;l?s&&l===1?o=!1:Fi(i,t,s):(o=!t.$stable,Hi(t,i)),r=t}else t&&(Bi(e,t),r={default:1});if(o)for(const l in i)!ji(l)&&r[l]==null&&delete i[l]},he=Hr;function Ar(e){return Tr(e)}function Tr(e,t){const s=ps();s.__VUE__=!0;const{insert:n,remove:i,patchProp:o,createElement:r,createText:l,createComment:c,setText:d,setElementText:a,parentNode:h,nextSibling:v,setScopeId:C=Me,insertStaticContent:E}=e,U=(f,u,p,_=null,m=null,b=null,A=void 0,S=null,w=!!u.dynamicChildren)=>{if(f===u)return;f&&!wt(f,u)&&(_=$t(f),Ae(f,m,b,!0),f=null),u.patchFlag===-2&&(w=!1,u.dynamicChildren=null);const{type:y,ref:M,shapeFlag:T}=u;switch(y){case _s:Z(f,u,p,_);break;case Pt:V(f,u,p,_);break;case Ms:f==null&&D(u,p,_,A);break;case le:Ft(f,u,p,_,m,b,A,S,w);break;default:T&1?O(f,u,p,_,m,b,A,S,w):T&6?Nt(f,u,p,_,m,b,A,S,w):(T&64||T&128)&&y.process(f,u,p,_,m,b,A,S,w,bt)}M!=null&&m&&ns(M,f&&f.ref,b,u||f,!u)},Z=(f,u,p,_)=>{if(f==null)n(u.el=l(u.children),p,_);else{const m=u.el=f.el;u.children!==f.children&&d(m,u.children)}},V=(f,u,p,_)=>{f==null?n(u.el=c(u.children||""),p,_):u.el=f.el},D=(f,u,p,_)=>{[f.el,f.anchor]=E(f.children,u,p,_,f.el,f.anchor)},x=({el:f,anchor:u},p,_)=>{let m;for(;f&&f!==u;)m=v(f),n(f,p,_),f=m;n(u,p,_)},I=({el:f,anchor:u})=>{let p;for(;f&&f!==u;)p=v(f),i(f),f=p;i(u)},O=(f,u,p,_,m,b,A,S,w)=>{u.type==="svg"?A="svg":u.type==="math"&&(A="mathml"),f==null?ue(u,p,_,m,b,A,S,w):Bt(f,u,m,b,A,S,w)},ue=(f,u,p,_,m,b,A,S)=>{let w,y;const{props:M,shapeFlag:T,transition:R,dirs:L}=f;if(w=f.el=r(f.type,b,M&&M.is,M),T&8?a(w,f.children):T&16&&Ne(f.children,w,null,_,m,Rs(f,b),A,S),L&&Ye(f,null,_,"created"),xe(w,f,f.scopeId,A,_),M){for(const q in M)q!=="value"&&!xt(q)&&o(w,q,null,M[q],b,_);"value"in M&&o(w,"value",null,M.value,b),(y=M.onVnodeBeforeMount)&&Ie(y,_,f)}L&&Ye(f,null,_,"beforeMount");const H=Cr(m,R);H&&R.beforeEnter(w),n(w,u,p),((y=M&&M.onVnodeMounted)||H||L)&&he(()=>{y&&Ie(y,_,f),H&&R.enter(w),L&&Ye(f,null,_,"mounted")},m)},xe=(f,u,p,_,m)=>{if(p&&C(f,p),_)for(let b=0;b<_.length;b++)C(f,_[b]);if(m){let b=m.subTree;if(u===b||qi(b.type)&&(b.ssContent===u||b.ssFallback===u)){const A=m.vnode;xe(f,A,A.scopeId,A.slotScopeIds,m.parent)}}},Ne=(f,u,p,_,m,b,A,S,w=0)=>{for(let y=w;y{const S=u.el=f.el;let{patchFlag:w,dynamicChildren:y,dirs:M}=u;w|=f.patchFlag&16;const T=f.props||K,R=u.props||K;let L;if(p&&Ze(p,!1),(L=R.onVnodeBeforeUpdate)&&Ie(L,p,u,f),M&&Ye(u,f,p,"beforeUpdate"),p&&Ze(p,!0),(T.innerHTML&&R.innerHTML==null||T.textContent&&R.textContent==null)&&a(S,""),y?We(f.dynamicChildren,y,S,p,_,Rs(u,m),b):A||k(f,u,S,null,p,_,Rs(u,m),b,!1),w>0){if(w&16)mt(S,T,R,p,m);else if(w&2&&T.class!==R.class&&o(S,"class",null,R.class,m),w&4&&o(S,"style",T.style,R.style,m),w&8){const H=u.dynamicProps;for(let q=0;q{L&&Ie(L,p,u,f),M&&Ye(u,f,p,"updated")},_)},We=(f,u,p,_,m,b,A)=>{for(let S=0;S{if(u!==p){if(u!==K)for(const b in u)!xt(b)&&!(b in p)&&o(f,b,u[b],null,m,_);for(const b in p){if(xt(b))continue;const A=p[b],S=u[b];A!==S&&b!=="value"&&o(f,b,S,A,m,_)}"value"in p&&o(f,"value",u.value,p.value,m)}},Ft=(f,u,p,_,m,b,A,S,w)=>{const y=u.el=f?f.el:l(""),M=u.anchor=f?f.anchor:l("");let{patchFlag:T,dynamicChildren:R,slotScopeIds:L}=u;L&&(S=S?S.concat(L):L),f==null?(n(y,p,_),n(M,p,_),Ne(u.children||[],p,M,m,b,A,S,w)):T>0&&T&64&&R&&f.dynamicChildren?(We(f.dynamicChildren,R,p,m,b,A,S),(u.key!=null||m&&u===m.subTree)&&Ni(f,u,!0)):k(f,u,p,M,m,b,A,S,w)},Nt=(f,u,p,_,m,b,A,S,w)=>{u.slotScopeIds=S,f==null?u.shapeFlag&512?m.ctx.activate(u,p,_,A,w):Ss(u,p,_,m,b,A,w):an(f,u,w)},Ss=(f,u,p,_,m,b,A)=>{const S=f.component=kr(f,_,m);if(Ti(f)&&(S.ctx.renderer=bt),qr(S,!1,A),S.asyncDep){if(m&&m.registerDep(S,ie,A),!f.el){const w=S.subTree=ce(Pt);V(null,w,u,p)}}else ie(S,f,u,p,m,b,A)},an=(f,u,p)=>{const _=u.component=f.component;if(Ur(f,u,p))if(_.asyncDep&&!_.asyncResolved){X(_,u,p);return}else _.next=u,_.update();else u.el=f.el,_.vnode=u},ie=(f,u,p,_,m,b,A)=>{const S=()=>{if(f.isMounted){let{next:T,bu:R,u:L,parent:H,vnode:q}=f;{const Ce=Vi(f);if(Ce){T&&(T.el=q.el,X(f,T,A)),Ce.asyncDep.then(()=>{f.isUnmounted||S()});return}}let $=T,de;Ze(f,!1),T?(T.el=q.el,X(f,T,A)):T=q,R&&Wt(R),(de=T.props&&T.props.onVnodeBeforeUpdate)&&Ie(de,H,T,q),Ze(f,!0);const ae=In(f),Te=f.subTree;f.subTree=ae,U(Te,ae,h(Te.el),$t(Te),f,m,b),T.el=ae.el,$===null&&jr(f,ae.el),L&&he(L,m),(de=T.props&&T.props.onVnodeUpdated)&&he(()=>Ie(de,H,T,q),m)}else{let T;const{el:R,props:L}=u,{bm:H,m:q,parent:$,root:de,type:ae}=f,Te=Ct(u);Ze(f,!1),H&&Wt(H),!Te&&(T=L&&L.onVnodeBeforeMount)&&Ie(T,$,u),Ze(f,!0);{de.ce&&de.ce._injectChildStyle(ae);const Ce=f.subTree=In(f);U(null,Ce,p,_,f,m,b),u.el=Ce.el}if(q&&he(q,m),!Te&&(T=L&&L.onVnodeMounted)){const Ce=u;he(()=>Ie(T,$,Ce),m)}(u.shapeFlag&256||$&&Ct($.vnode)&&$.vnode.shapeFlag&256)&&f.a&&he(f.a,m),f.isMounted=!0,u=p=_=null}};f.scope.on();const w=f.effect=new ti(S);f.scope.off();const y=f.update=w.run.bind(w),M=f.job=w.runIfDirty.bind(w);M.i=f,M.id=f.uid,w.scheduler=()=>on(M),Ze(f,!0),y()},X=(f,u,p)=>{u.component=f;const _=f.vnode.props;f.vnode=u,f.next=null,_r(f,u.props,_,p),xr(f,u.children,p),qe(),yn(f),Qe()},k=(f,u,p,_,m,b,A,S,w=!1)=>{const y=f&&f.children,M=f?f.shapeFlag:0,T=u.children,{patchFlag:R,shapeFlag:L}=u;if(R>0){if(R&128){Vt(y,T,p,_,m,b,A,S,w);return}else if(R&256){Je(y,T,p,_,m,b,A,S,w);return}}L&8?(M&16&&vt(y,m,b),T!==y&&a(p,T)):M&16?L&16?Vt(y,T,p,_,m,b,A,S,w):vt(y,m,b,!0):(M&8&&a(p,""),L&16&&Ne(T,p,_,m,b,A,S,w))},Je=(f,u,p,_,m,b,A,S,w)=>{f=f||ft,u=u||ft;const y=f.length,M=u.length,T=Math.min(y,M);let R;for(R=0;RM?vt(f,m,b,!0,!1,T):Ne(u,p,_,m,b,A,S,w,T)},Vt=(f,u,p,_,m,b,A,S,w)=>{let y=0;const M=u.length;let T=f.length-1,R=M-1;for(;y<=T&&y<=R;){const L=f[y],H=u[y]=w?Ke(u[y]):Re(u[y]);if(wt(L,H))U(L,H,p,null,m,b,A,S,w);else break;y++}for(;y<=T&&y<=R;){const L=f[T],H=u[R]=w?Ke(u[R]):Re(u[R]);if(wt(L,H))U(L,H,p,null,m,b,A,S,w);else break;T--,R--}if(y>T){if(y<=R){const L=R+1,H=LR)for(;y<=T;)Ae(f[y],m,b,!0),y++;else{const L=y,H=y,q=new Map;for(y=H;y<=R;y++){const pe=u[y]=w?Ke(u[y]):Re(u[y]);pe.key!=null&&q.set(pe.key,y)}let $,de=0;const ae=R-H+1;let Te=!1,Ce=0;const _t=new Array(ae);for(y=0;y=ae){Ae(pe,m,b,!0);continue}let Oe;if(pe.key!=null)Oe=q.get(pe.key);else for($=H;$<=R;$++)if(_t[$-H]===0&&wt(pe,u[$])){Oe=$;break}Oe===void 0?Ae(pe,m,b,!0):(_t[Oe-H]=y+1,Oe>=Ce?Ce=Oe:Te=!0,U(pe,u[Oe],p,null,m,b,A,S,w),de++)}const hn=Te?Or(_t):ft;for($=hn.length-1,y=ae-1;y>=0;y--){const pe=H+y,Oe=u[pe],gn=pe+1{const{el:b,type:A,transition:S,children:w,shapeFlag:y}=f;if(y&6){Xe(f.component.subTree,u,p,_);return}if(y&128){f.suspense.move(u,p,_);return}if(y&64){A.move(f,u,p,bt);return}if(A===le){n(b,u,p);for(let T=0;TS.enter(b),m);else{const{leave:T,delayLeave:R,afterLeave:L}=S,H=()=>n(b,u,p),q=()=>{T(b,()=>{H(),L&&L()})};R?R(b,H,q):q()}else n(b,u,p)},Ae=(f,u,p,_=!1,m=!1)=>{const{type:b,props:A,ref:S,children:w,dynamicChildren:y,shapeFlag:M,patchFlag:T,dirs:R,cacheIndex:L}=f;if(T===-2&&(m=!1),S!=null&&ns(S,null,p,f,!0),L!=null&&(u.renderCache[L]=void 0),M&256){u.ctx.deactivate(f);return}const H=M&1&&R,q=!Ct(f);let $;if(q&&($=A&&A.onVnodeBeforeUnmount)&&Ie($,u,f),M&6)eo(f.component,p,_);else{if(M&128){f.suspense.unmount(p,_);return}H&&Ye(f,null,u,"beforeUnmount"),M&64?f.type.remove(f,u,p,bt,_):y&&!y.hasOnce&&(b!==le||T>0&&T&64)?vt(y,u,p,!1,!0):(b===le&&T&384||!m&&M&16)&&vt(w,u,p),_&&dn(f)}(q&&($=A&&A.onVnodeUnmounted)||H)&&he(()=>{$&&Ie($,u,f),H&&Ye(f,null,u,"unmounted")},p)},dn=f=>{const{type:u,el:p,anchor:_,transition:m}=f;if(u===le){zi(p,_);return}if(u===Ms){I(f);return}const b=()=>{i(p),m&&!m.persisted&&m.afterLeave&&m.afterLeave()};if(f.shapeFlag&1&&m&&!m.persisted){const{leave:A,delayLeave:S}=m,w=()=>A(p,b);S?S(f.el,b,w):w()}else b()},zi=(f,u)=>{let p;for(;f!==u;)p=v(f),i(f),f=p;i(u)},eo=(f,u,p)=>{const{bum:_,scope:m,job:b,subTree:A,um:S,m:w,a:y}=f;On(w),On(y),_&&Wt(_),m.stop(),b&&(b.flags|=8,Ae(A,f,u,p)),S&&he(S,u),he(()=>{f.isUnmounted=!0},u),u&&u.pendingBranch&&!u.isUnmounted&&f.asyncDep&&!f.asyncResolved&&f.suspenseId===u.pendingId&&(u.deps--,u.deps===0&&u.resolve())},vt=(f,u,p,_=!1,m=!1,b=0)=>{for(let A=b;A{if(f.shapeFlag&6)return $t(f.component.subTree);if(f.shapeFlag&128)return f.suspense.next();const u=v(f.anchor||f.el),p=u&&u[Wo];return p?v(p):u};let xs=!1;const pn=(f,u,p)=>{f==null?u._vnode&&Ae(u._vnode,null,null,!0):U(u._vnode||null,f,u,null,null,null,p),u._vnode=f,xs||(xs=!0,yn(),wi(),xs=!1)},bt={p:U,um:Ae,m:Xe,r:dn,mt:Ss,mc:Ne,pc:k,pbc:We,n:$t,o:e};return{render:pn,hydrate:void 0,createApp:mr(pn)}}function Rs({type:e,props:t},s){return s==="svg"&&e==="foreignObject"||s==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:s}function Ze({effect:e,job:t},s){s?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function Cr(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function Ni(e,t,s=!1){const n=e.children,i=t.children;if(P(n)&&P(i))for(let o=0;o>1,e[s[l]]0&&(t[n]=s[o-1]),s[o]=n)}}for(o=s.length,r=s[o-1];o-- >0;)s[o]=r,r=t[r];return s}function Vi(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:Vi(t)}function On(e){if(e)for(let t=0;tJt(Ir);function Xt(e,t,s){return $i(e,t,s)}function $i(e,t,s=K){const{immediate:n,deep:i,flush:o,once:r}=s,l=fe({},s),c=t&&n||!t&&o!=="post";let d;if(Lt){if(o==="sync"){const C=Er();d=C.__watcherHandles||(C.__watcherHandles=[])}else if(!c){const C=()=>{};return C.stop=Me,C.resume=Me,C.pause=Me,C}}const a=se;l.call=(C,E,U)=>De(C,a,E,U);let h=!1;o==="post"?l.scheduler=C=>{he(C,a&&a.suspense)}:o!=="sync"&&(h=!0,l.scheduler=(C,E)=>{E?C():on(C)}),l.augmentJob=C=>{t&&(C.flags|=4),h&&(C.flags|=2,a&&(C.id=a.uid,C.i=a))};const v=Ko(e,t,l);return Lt&&(d?d.push(v):c&&v()),v}function Rr(e,t,s){const n=this.proxy,i=Y(e)?e.includes(".")?Ki(n,e):()=>n[e]:e.bind(n,n);let o;j(t)?o=t:(o=t.handler,s=t);const r=Ht(this),l=$i(i,o.bind(n),s);return r(),l}function Ki(e,t){const s=t.split(".");return()=>{let n=e;for(let i=0;it==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${ye(t)}Modifiers`]||e[`${it(t)}Modifiers`];function Pr(e,t,...s){if(e.isUnmounted)return;const n=e.vnode.props||K;let i=s;const o=t.startsWith("update:"),r=o&&Mr(n,t.slice(7));r&&(r.trim&&(i=s.map(a=>Y(a)?a.trim():a)),r.number&&(i=s.map(Gt)));let l,c=n[l=As(t)]||n[l=As(ye(t))];!c&&o&&(c=n[l=As(it(t))]),c&&De(c,e,6,i);const d=n[l+"Once"];if(d){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,De(d,e,6,i)}}function ki(e,t,s=!1){const n=t.emitsCache,i=n.get(e);if(i!==void 0)return i;const o=e.emits;let r={},l=!1;if(!j(e)){const c=d=>{const a=ki(d,t,!0);a&&(l=!0,fe(r,a))};!s&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}return!o&&!l?(W(e)&&n.set(e,null),null):(P(o)?o.forEach(c=>r[c]=null):fe(r,o),W(e)&&n.set(e,r),r)}function bs(e,t){return!e||!fs(t)?!1:(t=t.slice(2).replace(/Once$/,""),F(e,t[0].toLowerCase()+t.slice(1))||F(e,it(t))||F(e,t))}function In(e){const{type:t,vnode:s,proxy:n,withProxy:i,propsOptions:[o],slots:r,attrs:l,emit:c,render:d,renderCache:a,props:h,data:v,setupState:C,ctx:E,inheritAttrs:U}=e,Z=ts(e);let V,D;try{if(s.shapeFlag&4){const I=i||n,O=I;V=Re(d.call(O,I,a,h,C,v,E)),D=l}else{const I=t;V=Re(I.length>1?I(h,{attrs:l,slots:r,emit:c}):I(h,null)),D=t.props?l:Dr(l)}}catch(I){It.length=0,ms(I,e,1),V=ce(Pt)}let x=V;if(D&&U!==!1){const I=Object.keys(D),{shapeFlag:O}=x;I.length&&O&7&&(o&&I.some(Qs)&&(D=Lr(D,o)),x=gt(x,D,!1,!0))}return s.dirs&&(x=gt(x,null,!1,!0),x.dirs=x.dirs?x.dirs.concat(s.dirs):s.dirs),s.transition&&rn(x,s.transition),V=x,ts(Z),V}const Dr=e=>{let t;for(const s in e)(s==="class"||s==="style"||fs(s))&&((t||(t={}))[s]=e[s]);return t},Lr=(e,t)=>{const s={};for(const n in e)(!Qs(n)||!(n.slice(9)in t))&&(s[n]=e[n]);return s};function Ur(e,t,s){const{props:n,children:i,component:o}=e,{props:r,children:l,patchFlag:c}=t,d=o.emitsOptions;if(t.dirs||t.transition)return!0;if(s&&c>=0){if(c&1024)return!0;if(c&16)return n?En(n,r,d):!!r;if(c&8){const a=t.dynamicProps;for(let h=0;he.__isSuspense;function Hr(e,t){t&&t.pendingBranch?P(e)?t.effects.push(...e):t.effects.push(e):Qo(e)}const le=Symbol.for("v-fgt"),_s=Symbol.for("v-txt"),Pt=Symbol.for("v-cmt"),Ms=Symbol.for("v-stc"),It=[];let ve=null;function N(e=!1){It.push(ve=e?null:[])}function Br(){It.pop(),ve=It[It.length-1]||null}let Dt=1;function Rn(e,t=!1){Dt+=e,e<0&&ve&&t&&(ve.hasOnce=!0)}function Qi(e){return e.dynamicChildren=Dt>0?ve||ft:null,Br(),Dt>0&&ve&&ve.push(e),e}function J(e,t,s,n,i,o){return Qi(g(e,t,s,n,i,o,!0))}function ct(e,t,s,n,i){return Qi(ce(e,t,s,n,i,!0))}function Wi(e){return e?e.__v_isVNode===!0:!1}function wt(e,t){return e.type===t.type&&e.key===t.key}const Ji=({key:e})=>e??null,Yt=({ref:e,ref_key:t,ref_for:s})=>(typeof e=="number"&&(e=""+e),e!=null?Y(e)||ne(e)||j(e)?{i:me,r:e,k:t,f:!!s}:e:null);function g(e,t=null,s=null,n=0,i=null,o=e===le?0:1,r=!1,l=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Ji(t),ref:t&&Yt(t),scopeId:xi,slotScopeIds:null,children:s,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:o,patchFlag:n,dynamicProps:i,dynamicChildren:null,appContext:null,ctx:me};return l?(fn(c,s),o&128&&e.normalize(c)):s&&(c.shapeFlag|=Y(s)?8:16),Dt>0&&!r&&ve&&(c.patchFlag>0||o&6)&&c.patchFlag!==32&&ve.push(c),c}const ce=Fr;function Fr(e,t=null,s=null,n=0,i=null,o=!1){if((!e||e===lr)&&(e=Pt),Wi(e)){const l=gt(e,t,!0);return s&&fn(l,s),Dt>0&&!o&&ve&&(l.shapeFlag&6?ve[ve.indexOf(e)]=l:ve.push(l)),l.patchFlag=-2,l}if(Yr(e)&&(e=e.__vccOpts),t){t=Nr(t);let{class:l,style:c}=t;l&&!Y(l)&&(t.class=st(l)),W(c)&&(nn(c)&&!P(c)&&(c=fe({},c)),t.style=Xs(c))}const r=Y(e)?1:qi(e)?128:Jo(e)?64:W(e)?4:j(e)?2:0;return g(e,t,s,n,i,r,o,!0)}function Nr(e){return e?nn(e)||Di(e)?fe({},e):e:null}function gt(e,t,s=!1,n=!1){const{props:i,ref:o,patchFlag:r,children:l,transition:c}=e,d=t?Vr(i||{},t):i,a={__v_isVNode:!0,__v_skip:!0,type:e.type,props:d,key:d&&Ji(d),ref:t&&t.ref?s&&o?P(o)?o.concat(Yt(t)):[o,Yt(t)]:Yt(t):o,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:l,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==le?r===-1?16:r|16:r,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:c,component:e.component,suspense:e.suspense,ssContent:e.ssContent&>(e.ssContent),ssFallback:e.ssFallback&>(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return c&&n&&rn(a,c.clone(a)),a}function G(e=" ",t=0){return ce(_s,null,e,t)}function Re(e){return e==null||typeof e=="boolean"?ce(Pt):P(e)?ce(le,null,e.slice()):Wi(e)?Ke(e):ce(_s,null,String(e))}function Ke(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:gt(e)}function fn(e,t){let s=0;const{shapeFlag:n}=e;if(t==null)t=null;else if(P(t))s=16;else if(typeof t=="object")if(n&65){const i=t.default;i&&(i._c&&(i._d=!1),fn(e,i()),i._c&&(i._d=!0));return}else{s=32;const i=t._;!i&&!Di(t)?t._ctx=me:i===3&&me&&(me.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else j(t)?(t={default:t,_ctx:me},s=32):(t=String(t),n&64?(s=16,t=[G(t)]):s=8);e.children=t,e.shapeFlag|=s}function Vr(...e){const t={};for(let s=0;s{let i;return(i=e[s])||(i=e[s]=[]),i.push(n),o=>{i.length>1?i.forEach(r=>r(o)):i[0](o)}};os=t("__VUE_INSTANCE_SETTERS__",s=>se=s),Ks=t("__VUE_SSR_SETTERS__",s=>Lt=s)}const Ht=e=>{const t=se;return os(e),e.scope.on(),()=>{e.scope.off(),os(t)}},Mn=()=>{se&&se.scope.off(),os(null)};function Xi(e){return e.vnode.shapeFlag&4}let Lt=!1;function qr(e,t=!1,s=!1){t&&Ks(t);const{props:n,children:i}=e.vnode,o=Xi(e);br(e,n,o,t),Sr(e,i,s);const r=o?Qr(e,t):void 0;return t&&Ks(!1),r}function Qr(e,t){const s=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,fr);const{setup:n}=s;if(n){qe();const i=e.setupContext=n.length>1?Jr(e):null,o=Ht(e),r=jt(n,e,0,[e.props,i]),l=Jn(r);if(Qe(),o(),(l||e.sp)&&!Ct(e)&&Ai(e),l){if(r.then(Mn,Mn),t)return r.then(c=>{Pn(e,c)}).catch(c=>{ms(c,e,0)});e.asyncDep=r}else Pn(e,r)}else Yi(e)}function Pn(e,t,s){j(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:W(t)&&(e.setupState=vi(t)),Yi(e)}function Yi(e,t,s){const n=e.type;e.render||(e.render=n.render||Me);{const i=Ht(e);qe();try{ur(e)}finally{Qe(),i()}}}const Wr={get(e,t){return ee(e,"get",""),e[t]}};function Jr(e){const t=s=>{e.exposed=s||{}};return{attrs:new Proxy(e.attrs,Wr),slots:e.slots,emit:e.emit,expose:t}}function ys(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(vi(jo(e.exposed)),{get(t,s){if(s in t)return t[s];if(s in Ot)return Ot[s](e)},has(t,s){return s in t||s in Ot}})):e.proxy}function Xr(e,t=!0){return j(e)?e.displayName||e.name:e.name||t&&e.__name}function Yr(e){return j(e)&&"__vccOpts"in e}const Zi=(e,t)=>Vo(e,t,Lt),Zr="3.5.13";/**
+* @vue/runtime-dom v3.5.13
+* (c) 2018-present Yuxi (Evan) You and Vue contributors
+* @license MIT
+**/let ks;const Dn=typeof window<"u"&&window.trustedTypes;if(Dn)try{ks=Dn.createPolicy("vue",{createHTML:e=>e})}catch{}const Gi=ks?e=>ks.createHTML(e):e=>e,Gr="http://www.w3.org/2000/svg",zr="http://www.w3.org/1998/Math/MathML",Ue=typeof document<"u"?document:null,Ln=Ue&&Ue.createElement("template"),el={insert:(e,t,s)=>{t.insertBefore(e,s||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,s,n)=>{const i=t==="svg"?Ue.createElementNS(Gr,e):t==="mathml"?Ue.createElementNS(zr,e):s?Ue.createElement(e,{is:s}):Ue.createElement(e);return e==="select"&&n&&n.multiple!=null&&i.setAttribute("multiple",n.multiple),i},createText:e=>Ue.createTextNode(e),createComment:e=>Ue.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Ue.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,s,n,i,o){const r=s?s.previousSibling:t.lastChild;if(i&&(i===o||i.nextSibling))for(;t.insertBefore(i.cloneNode(!0),s),!(i===o||!(i=i.nextSibling)););else{Ln.innerHTML=Gi(n==="svg"?`${e} `:n==="mathml"?`${e} `:e);const l=Ln.content;if(n==="svg"||n==="mathml"){const c=l.firstChild;for(;c.firstChild;)l.appendChild(c.firstChild);l.removeChild(c)}t.insertBefore(l,s)}return[r?r.nextSibling:t.firstChild,s?s.previousSibling:t.lastChild]}},tl=Symbol("_vtc");function sl(e,t,s){const n=e[tl];n&&(t=(t?[t,...n]:[...n]).join(" ")),t==null?e.removeAttribute("class"):s?e.setAttribute("class",t):e.className=t}const Un=Symbol("_vod"),nl=Symbol("_vsh"),il=Symbol(""),ol=/(^|;)\s*display\s*:/;function rl(e,t,s){const n=e.style,i=Y(s);let o=!1;if(s&&!i){if(t)if(Y(t))for(const r of t.split(";")){const l=r.slice(0,r.indexOf(":")).trim();s[l]==null&&Zt(n,l,"")}else for(const r in t)s[r]==null&&Zt(n,r,"");for(const r in s)r==="display"&&(o=!0),Zt(n,r,s[r])}else if(i){if(t!==s){const r=n[il];r&&(s+=";"+r),n.cssText=s,o=ol.test(s)}}else t&&e.removeAttribute("style");Un in e&&(e[Un]=o?n.display:"",e[nl]&&(n.display="none"))}const jn=/\s*!important$/;function Zt(e,t,s){if(P(s))s.forEach(n=>Zt(e,t,n));else if(s==null&&(s=""),t.startsWith("--"))e.setProperty(t,s);else{const n=ll(e,t);jn.test(s)?e.setProperty(it(n),s.replace(jn,""),"important"):e[n]=s}}const Hn=["Webkit","Moz","ms"],Ps={};function ll(e,t){const s=Ps[t];if(s)return s;let n=ye(t);if(n!=="filter"&&n in e)return Ps[t]=n;n=ds(n);for(let i=0;iDs||(al.then(()=>Ds=0),Ds=Date.now());function pl(e,t){const s=n=>{if(!n._vts)n._vts=Date.now();else if(n._vts<=s.attached)return;De(hl(n,s.value),t,5,[n])};return s.value=e,s.attached=dl(),s}function hl(e,t){if(P(t)){const s=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{s.call(e),e._stopped=!0},t.map(n=>i=>!i._stopped&&n&&n(i))}else return t}const Kn=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,gl=(e,t,s,n,i,o)=>{const r=i==="svg";t==="class"?sl(e,n,r):t==="style"?rl(e,s,n):fs(t)?Qs(t)||fl(e,t,s,n,o):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):ml(e,t,n,r))?(Nn(e,t,n),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Fn(e,t,n,r,o,t!=="value")):e._isVueCE&&(/[A-Z]/.test(t)||!Y(n))?Nn(e,ye(t),n,o,t):(t==="true-value"?e._trueValue=n:t==="false-value"&&(e._falseValue=n),Fn(e,t,n,r))};function ml(e,t,s,n){if(n)return!!(t==="innerHTML"||t==="textContent"||t in e&&Kn(t)&&j(s));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const i=e.tagName;if(i==="IMG"||i==="VIDEO"||i==="CANVAS"||i==="SOURCE")return!1}return Kn(t)&&Y(s)?!1:t in e}const rs=e=>{const t=e.props["onUpdate:modelValue"]||!1;return P(t)?s=>Wt(t,s):t};function vl(e){e.target.composing=!0}function kn(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const ht=Symbol("_assign"),tt={created(e,{modifiers:{lazy:t,trim:s,number:n}},i){e[ht]=rs(i);const o=n||i.props&&i.props.type==="number";ze(e,t?"change":"input",r=>{if(r.target.composing)return;let l=e.value;s&&(l=l.trim()),o&&(l=Gt(l)),e[ht](l)}),s&&ze(e,"change",()=>{e.value=e.value.trim()}),t||(ze(e,"compositionstart",vl),ze(e,"compositionend",kn),ze(e,"change",kn))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,oldValue:s,modifiers:{lazy:n,trim:i,number:o}},r){if(e[ht]=rs(r),e.composing)return;const l=(o||e.type==="number")&&!/^0\d/.test(e.value)?Gt(e.value):e.value,c=t??"";l!==c&&(document.activeElement===e&&e.type!=="range"&&(n&&t===s||i&&e.value.trim()===c)||(e.value=c))}},Qt={deep:!0,created(e,{value:t,modifiers:{number:s}},n){const i=us(t);ze(e,"change",()=>{const o=Array.prototype.filter.call(e.options,r=>r.selected).map(r=>s?Gt(ls(r)):ls(r));e[ht](e.multiple?i?new Set(o):o:o[0]),e._assigning=!0,_i(()=>{e._assigning=!1})}),e[ht]=rs(n)},mounted(e,{value:t}){qn(e,t)},beforeUpdate(e,t,s){e[ht]=rs(s)},updated(e,{value:t}){e._assigning||qn(e,t)}};function qn(e,t){const s=e.multiple,n=P(t);if(!(s&&!n&&!us(t))){for(let i=0,o=e.options.length;iString(d)===String(l)):r.selected=ho(t,l)>-1}else r.selected=t.has(l);else if(hs(ls(r),t)){e.selectedIndex!==i&&(e.selectedIndex=i);return}}!s&&e.selectedIndex!==-1&&(e.selectedIndex=-1)}}function ls(e){return"_value"in e?e._value:e.value}const bl=["ctrl","shift","alt","meta"],_l={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>bl.some(s=>e[`${s}Key`]&&!t.includes(s))},cs=(e,t)=>{const s=e._withMods||(e._withMods={}),n=t.join(".");return s[n]||(s[n]=(i,...o)=>{for(let r=0;r{const t=wl().createApp(...e),{mount:s}=t;return t.mount=n=>{const i=Al(n);if(!i)return;const o=t._component;!j(o)&&!o.render&&!o.template&&(o.template=i.innerHTML),i.nodeType===1&&(i.textContent="");const r=s(i,!1,xl(i));return i instanceof Element&&(i.removeAttribute("v-cloak"),i.setAttribute("data-v-app","")),r},t};function xl(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function Al(e){return Y(e)?document.querySelector(e):e}const Tl=()=>{const e=window.location.protocol,t=window.location.hostname,s=window.location.port;return`${e}//${t}${s?`:${s}`:""}`},ws=Tl(),Fe=(e,t)=>{const s=e.__vccOpts||e;for(const[n,i]of t)s[n]=i;return s},Cl={data(){return{username:"",password:"",isLoading:!1}},methods:{async handleLogin(){if(!this.username||!this.password){alert("请输入用户名和密码!");return}this.isLoading=!0;try{const t=await(await fetch(`${ws}/api/login`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:this.username,password:this.password})})).json();t.success?this.$emit("login-success"):alert(t.message||"登录失败")}catch(e){console.error("Login error:",e),alert("登录失败,请检查网络连接")}finally{this.isLoading=!1}}}},Ol={class:"login-container"},Il={class:"form-group"},El={class:"form-group"},Rl=["disabled"];function Ml(e,t,s,n,i,o){return N(),J("div",Ol,[t[7]||(t[7]=g("h1",null,"登录",-1)),g("form",{onSubmit:t[2]||(t[2]=cs((...r)=>o.handleLogin&&o.handleLogin(...r),["prevent"]))},[g("div",Il,[t[4]||(t[4]=g("label",{for:"username"},"用户名",-1)),we(g("input",{type:"text","onUpdate:modelValue":t[0]||(t[0]=r=>i.username=r),id:"username",required:""},null,512),[[tt,i.username]])]),g("div",El,[t[5]||(t[5]=g("label",{for:"password"},"密码",-1)),we(g("input",{type:"password","onUpdate:modelValue":t[1]||(t[1]=r=>i.password=r),id:"password",required:""},null,512),[[tt,i.password]])]),g("button",{type:"submit",disabled:i.isLoading},"登录",8,Rl)],32),g("p",null,[t[6]||(t[6]=G("还没注册账户? ")),g("a",{href:"#",onClick:t[3]||(t[3]=cs(r=>e.$emit("show-registration"),["prevent"]))},"点击注册")])])}const Pl=Fe(Cl,[["render",Ml],["__scopeId","data-v-474cd233"]]),Dl={data(){return{username:"",password:"",confirmPassword:"",isLoading:!1}},methods:{async handleRegistration(){if(this.password!==this.confirmPassword){alert("密码不匹配,请重试!");return}if(!this.username||!this.password){alert("用户名和密码不能为空!");return}this.isLoading=!0;try{const t=await(await fetch(`${ws}/api/register`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:this.username,password:this.password})})).json();t.success?(alert("注册成功!即将跳转到登录页面。"),this.username="",this.password="",this.confirmPassword="",setTimeout(()=>{this.$emit("show-login")},100)):alert(t.message||"注册失败")}catch(e){console.error("Registration error:",e),alert("注册失败,请检查网络连接")}finally{this.isLoading=!1}}}},Ll={class:"registration-container"},Ul={class:"form-group"},jl={class:"form-group"},Hl={class:"form-group"},Bl=["disabled"];function Fl(e,t,s,n,i,o){return N(),J("div",Ll,[t[9]||(t[9]=g("h1",null,"注册",-1)),g("form",{onSubmit:t[3]||(t[3]=cs((...r)=>o.handleRegistration&&o.handleRegistration(...r),["prevent"]))},[g("div",Ul,[t[5]||(t[5]=g("label",{for:"username"},"用户名",-1)),we(g("input",{type:"text","onUpdate:modelValue":t[0]||(t[0]=r=>i.username=r),id:"username",required:""},null,512),[[tt,i.username]])]),g("div",jl,[t[6]||(t[6]=g("label",{for:"password"},"密码",-1)),we(g("input",{type:"password","onUpdate:modelValue":t[1]||(t[1]=r=>i.password=r),id:"password",required:""},null,512),[[tt,i.password]])]),g("div",Hl,[t[7]||(t[7]=g("label",{for:"confirmPassword"},"确认密码",-1)),we(g("input",{type:"password","onUpdate:modelValue":t[2]||(t[2]=r=>i.confirmPassword=r),id:"confirmPassword",required:""},null,512),[[tt,i.confirmPassword]])]),g("button",{type:"submit",disabled:i.isLoading},z(i.isLoading?"注册中...":"注册"),9,Bl)],32),g("p",null,[t[8]||(t[8]=G(" 已有账号? ")),g("a",{href:"#",onClick:t[4]||(t[4]=cs(r=>e.$emit("show-login"),["prevent"]))},"点击登录")])])}const Nl=Fe(Dl,[["render",Fl],["__scopeId","data-v-58611a9b"]]),Vl={class:"header"},$l={class:"nav"},Kl={__name:"NavBar",props:{currentTab:String},emits:["tab-change"],setup(e,{emit:t}){const s=t,n=i=>{s("tab-change",i)};return(i,o)=>(N(),J("header",Vl,[g("div",{class:st(["nav-item",{active:e.currentTab==="home"}]),onClick:o[0]||(o[0]=r=>n("home"))},"小智 AI",2),g("nav",$l,[g("div",{class:st(["nav-item",{active:e.currentTab==="device"}]),onClick:o[1]||(o[1]=r=>n("device"))},o[2]||(o[2]=[g("i",{class:"icon-device"},null,-1),G(" 设备管理 ")]),2)])]))}},un=Fe(Kl,[["__scopeId","data-v-66890ca7"]]),kl={class:"device-card"},ql={class:"device-header"},Ql={class:"device-id"},Wl={class:"note"},Jl={class:"device-details"},Xl={class:"device-type"},Yl={class:"device-role"},Zl={class:"device-modules"},Gl={class:"module-item"},zl={class:"module-item"},ec={class:"last-activity"},tc={class:"device-actions"},sc={__name:"DeviceCard",props:{deviceId:String,deviceNote:String,deviceType:{type:String,default:"未知型号(待实现)"},lastActivity:{type:String,default:"3 天前"},selectedModules:{type:Object,default:()=>({LLM:"-",TTS:"-",ASR:"-",VAD:"-"})},deviceConfig:{type:Object,default:()=>({})}},emits:["configure","voiceprint","history","delete"],setup(e,{emit:t}){const s=e,n=Zi(()=>{var r;return((r=s.deviceConfig)==null?void 0:r.nickname)||"小智"});Xt(()=>s.selectedModules,r=>{s.deviceId&&localStorage.setItem(`deviceConfig_${s.deviceId}`,JSON.stringify({selected_module:r}))},{deep:!0}),be(!1);const i=t,o=()=>{confirm(`确认要删除此设备吗?
+
+警告:删除后设备所有配置将不可恢复!`)&&i("delete")};return(r,l)=>{var c,d;return N(),J("div",kl,[g("div",ql,[g("h2",Ql,[G(z(e.deviceId)+" ",1),g("span",Wl,"["+z(e.deviceNote||"备注")+"]",1)])]),g("div",Jl,[g("p",Xl,"设备型号:"+z(e.deviceType),1),g("p",Yl,"角色昵称:"+z(n.value),1),g("p",Zl,[l[3]||(l[3]=G(" 当前模型: ")),g("span",Gl,"LLM: "+z(((c=e.selectedModules)==null?void 0:c.LLM)||"-"),1),g("span",zl,"TTS: "+z(((d=e.selectedModules)==null?void 0:d.TTS)||"-"),1)]),g("p",ec,"最近对话:"+z(e.lastActivity),1)]),g("div",tc,[g("button",{class:"action-btn primary",onClick:l[0]||(l[0]=a=>r.$emit("configure"))},"配置角色"),g("button",{class:"action-btn",onClick:l[1]||(l[1]=a=>r.$emit("voiceprint"))},"声纹识别"),g("button",{class:"action-btn",onClick:l[2]||(l[2]=a=>r.$emit("history"))},"历史对话"),g("div",{class:"delete-container"},[g("button",{class:"action-btn danger",onClick:o},l[4]||(l[4]=[g("i",{class:"icon-delete"},null,-1),G(" 删除设备 ")])),l[5]||(l[5]=g("div",{class:"delete-warning"},"删除后设备配置将不可恢复",-1))])])])}}},nc=Fe(sc,[["__scopeId","data-v-44eada42"]]),ic={class:"app"},oc={class:"content"},rc={class:"breadcrumb"},lc={key:0,class:"device-list"},cc={key:1,class:"empty-state"},fc={__name:"panel",emits:["show-role","go-home"],setup(e,{emit:t}){const s=t,n=ws,i=be([]),o=h=>{if(console.log("Formatting timestamp:",h),!h)return"从未对话";const v=Date.now(),C=h*1e3,E=Math.floor((v-C)/(1e3*60));if(E<60)return`${E} 分钟前`;const U=Math.floor(E/60);return U<24?`${U} 小时前`:`${Math.floor(U/24)} 天前`},r=h=>{console.log("Configure device:",h.id),s("show-role",h.id)},l=h=>{console.log("Voiceprint device:",h.id)},c=h=>{console.log("History device:",h.id)},d=async h=>{try{const C=await(await fetch(`${n}/api/config/delete_device`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_id:h.id})})).json();if(C.success)i.value=i.value.filter(E=>E.id!==h.id),alert("设备已删除");else throw new Error(C.message||"删除失败")}catch(v){console.error("Error deleting device:",v),alert("删除设备失败: "+v.message)}},a=h=>{h==="home"&&s("go-home")};return ln(async()=>{try{const v=await(await fetch(`${n}/api/config/devices`)).json();v.success&&(console.log("Loaded devices:",v.data),i.value=v.data.map(C=>({...C,type:"面包板(WiFi)",version:"0.9.9",lastActivity:"3 天前",note:""})))}catch(h){console.error("Error loading devices:",h)}}),(h,v)=>{const C=$e("router-link");return N(),J("div",ic,[ce(un,{"current-tab":"device",onTabChange:a}),g("main",oc,[g("div",rc,[ce(C,{to:"/control-panel"},{default:ss(()=>v[0]||(v[0]=[G("控制台")])),_:1}),v[1]||(v[1]=G(" / ")),v[2]||(v[2]=g("span",null,"设备管理",-1))]),i.value.length>0?(N(),J("div",lc,[(N(!0),J(le,null,lt(i.value,E=>(N(),ct(nc,{key:E.id,"device-id":E.id,"device-note":E.note,"device-type":E.type,"last-activity":o(E.config.last_chat_time),"selected-modules":E.config.selected_module,"device-config":E.config,onConfigure:U=>r(E),onVoiceprint:U=>l(E),onHistory:U=>c(E),onDelete:U=>d(E)},null,8,["device-id","device-note","device-type","last-activity","selected-modules","device-config","onConfigure","onVoiceprint","onHistory","onDelete"]))),128))])):(N(),J("div",cc,v[3]||(v[3]=[g("div",{class:"empty-message"},[g("i",{class:"icon-info"}),g("p",null,"目前没有设备,请确认是否启用私有配置,并且和设备进行一次对话")],-1)])))])])}}},uc=Fe(fc,[["__scopeId","data-v-39a8e31b"]]),ac="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJ0AAAC6CAYAAAC0j09YAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAFiUAABYlAUlSJPAAAAzGSURBVHhe7Z1ZjE1bGscND6YgnoUgXoUQ4ZWHjnQkt+PB0OmEJ0LMQuOWoWliiDQdU3e07hhiiDZdU2K4qWsWMzELpUoN1KBKuemu697V/kvvtrrqK7XX2fusvfY5/4df6jhn7zV9v/2tdfZwtPrw4YMixCWUjjiH0hHnUDriHEpHnEPpiHMoHXEOpSPOoXTEOZSOOIfSEedQOuIcSkecQ+mIcygdcQ6lI86hdMQ5lI44h9IR51A6C24/+Zf68z9/VjM3/qLBa7wnbUuah9KF5PubDVq03xQoNeL3n/nm28/yfX/z3+I+RIbShQDZrOBvP6tfz1fqV3P/H7yHz5jxwkPpQnCwsEGN+UNT4QLw2aHCn8R9SVMoXQj+fqJBT6eScACf/eMkpQsLpQvBnjM/fVq/ycIBfIZtpH1JUyhdCH643aCm/OkXUTiAzwpv8ctEWChdCCprfvyUyT6qMcuaCof3kOWqPm0j7UuaQulCUlH5o/rL0Y/qm4IvwuH1X7/7qMo/fSbtQ2QonQVHzjeo3/7xi3R4feRCg7gtaR5KZ8F3FxvU71Z8kQ6v8Z60LWkeSmcBpYsHSmcBpYsHSmcBpYsHSmcBpYsHSmcBpYsHSmcBpYsHSmcBpYsHSmcBpYsHSmcBpYsHSmcBpYsHSmcBpYsHSmcBpYsHSmcBpYsHSmcBpYsHSmcBpYsHSmcBpYsH76Srr69X1dXVqqKiQr1+/VqVlJR4w6Onr9XlW6Xqh+ufweuHn96Ttk0KjBnGrqamRo+lNMZJ45V07969U+Xl5aq4uFgVFRWply9fkgzBGGIsa2trxbFOEm+kg3BlZWWULUYwlhhTjK005knhhXQ4GnFUUrj4wZj6lvG8kK6qqkpPB9KgkehgbLFOlsY+CRKXrq6uTi98pcEi8YBshzHGWEsxcE3i0mG9UVpaKg4WiQ+MsS9rO0qXJ1A6AwwEzi1JA0XiA2NM6f4LpXMDpTOgdG6gdAaUzg2UzoDSuYHSGVA6N1A6A0rnBkpnQOncQOkMKJ0bKJ0BpXMDpTOIS7p79+6pPXv2qDlz5qiRI0eqwYMHq379+um/+Dfe37t3r95O2j8JXLaZ0hlElQ4B2bp1qxoxYoTq2bOn6tatm2rfvr1q06aNat26tf6Lf+P9Hj166O2wfZLyJdFmSmcQRbozZ86o8ePHq+7du+sgtWrVqkXatWunt58wYYLeXyo3myTVZkpnkKl0x44dU6NGjVKdO3cWA9US2A/7Hz9+XCw/GyTZZkpnkIl0ONrHjBmjOnXqJAYnLNgf5bjIeEm3mdIZ2EqHdc2sWbNUly5dxKDYgnJQXjbXeD60mdIZ2Eq3Y8cONWjQIDEYmTJw4EBdrlRfHPjQZkpnYCMdjuwpU6aEXoCHBQt1lJuNbOdLmymdgY10R44cUcOHDxeDEJVhw4apo0ePivVGwZc2UzoDG+k2btyo+vbtKwYgKigX5Uv1RsGXNlM6AxvplixZorp27SoGICooF+VL9UbBlzZTOgMb6XBZqG3btmIAooJyUb5UbxR8aTOlM7CRDqcJcJlICkBUUC7Kl+qNgi9tpnQGNtItWLBAdejQQQxAVFAuypfqjYIvbaZ0BjbSrV27Vl+DlAIQFZSL8qV6o+BLmymdgY10u3fv1rf9SAGICspF+VK9UfClzZTOwEa6q1evqrFjx4oBiMq4cePUlStXxHqj4EubKZ2BjXTPnz9X69atU7169RKDkCkoD+WifKneKPjSZkpnYCMduHjxoho9enRs3whRDspDuVJ9ceBDmymdga10OLJ37dqlhgwZIgbEFpSzc+fOrGS5AB/aTOkMbKUDjx49Ulu2bFH9+/cXgxKWAQMG6HIePnwo1hMnSbeZ0hlkIh148OCB2rZtmxo6dKj1tIXtsR/2RzlS+dkgyTZTOoNMpQPIHrh1e9q0aaEX6thu+vTpej8XGa4xSbWZ0hlEkQ5gXXPr1i116NAhVVBQoJ+c6t27t+rYsaN+qgp/+/Tpo99ftGiR3g7bZ3MN1xJJtJnSGUSVLgABwQ2Nly5dUmfPnlWnTp1SJ06cUCdPntT/xvt3795NVLbGuGwzpTOISzrydSidAaVzA6UzoHRuoHQGlM4NlM6A0rmB0hlQOjdQOgNK5wZKZxBVuvv376ubN2+qJ0+eiJ/nAujb5cuX1Y0bN8TPw0DpDKJKd+DAAf0Y3qZNm/RlIkgobZdGIFthYaF+tnXx4sXq8OHD4nZhoHQGUaTD0b9mzRo1adIkNXXqVP043tKlS/VvfETJCkmDtqMPy5YtU/PmzdN9Qx9XrlypJZT2aQlKZxBFun379umATJw48X8gODNmzNDZD3dkIEhpmHqDrIY2o+3oA/pi9m3mzJnq4MGD4v4tQekMMpUO0yjuKzODYoKA4U4OZApkDATz9OnTXk2/gWjIaqtWrdJtRZsbyxaAz7Zv355RHyidQabS4SL49evX9Tpnw4YNavbs2WKgQCDg3Llz9fS7efPmxNZ/ZkYLpk9ktcmTJ4ttB8jmK1as0D/GgxsEMrkBgNIZZCodePHihQ4iAoHnBXBL+Pz588XABUBAc/23evVqLSFuH4IMcYmIdqEslImykc1QF25laimjBaCdeK4Vd5/cvn1blynVFQZKZxBFOhMIiBsc8eVi//79el0kBdIEQUeGQXCRKSHDwoUL1fLly9X69et1NoIsWDsiM2J6BhAJ4DXeh1QA22GfQC6UhTJRdkvZLCDIaujDhQsX1J07d9TTp0/FPttA6Qziki4A8j1+/Fg/D4qpF+u+lrJfYyAjfnAQ2QiyYAGPzIjpGUAkgNd4H1IBbId9wshlEoiGTH3u3Dmd1dAH9EXqYyZQOoO4pQsIpl5McTgFcf78eZ09MF19bf3nCkk0ZOps3WRK6QyyJV1jggyI6QrrvyALIvAuJAwkQ53B1Jlt0UwonYEr6UzMLIjAQ0LcHm6KiCsAmJYhiySRBLaFwNgPGRVlIZOh7GvXrum6UGfcU2cYKJ1BEtI1BgIg25gi4noupmXIgt8jwfQMefAMA+QEeI338TnAtjiNg/2QUVGWq0zWEpTOwAfpwhCI+ezZMy0nwGvXGStTKJ1BWqRLO5TOgNK5gdIZUDo3UDoDSucGSmdA6dxQWlpK6QLq6upUWVmZOFAkPjDGGGspBq5JXLr6+npVWVkpDhSJh6KiIvX27Vs91lIMXJO4dIBTbHbB2NbU1IhjnwReSIcjsLq6WhUXF4uDRjKnpKREj60vWQ54IR0IxGPGiw8fhQPeSAfev3+vamtr9foD8r169UocTNI8GDOMHcYQY+mbcMAr6QIwUPimBTBwLYGjuby8PCenZ0iEvqGPUt8bE4ybj7IFeCmdDRjgiooKHRx8S5MCl2bQJ/QNfURfpTFIG6mWDkczTrfkomyNQR/RV58zWFhSLR2OfEw9UpByEZ9O8EYh1dLh/B4u70gBykV8upQVhdRLl0+nWNBXSpcwlC6dZCwdzqnha3xVVZUTUBcu5ZgL6XyXDmOBMXEdB8Q+aEMmWEuHheybN2/0AOC8GL7O468rsJhGxzHg+Sod+o4xwBpPGqNsgVgDXOmAA5l+qbGSDh1G0JM8RYG60WkcdTjK80069Bl9hwRJxwEu4IS05MrXCC0dUipOUCbZ0QC0AUc52pNv0gWzjC9xQAxsp9vQ0iHLIcNIlScBOozBzzfpcLD5IFwA2mSuM8MQWjpcQPapsyBYY0if5SI+9hdOwA3Jmeawkk6qNEmCBa70WS7ia3+zJp2P1zgpXfLACbghOdMcoaULvjFJFScF1pg+rTOzjY/9hRNwQ3KmOUJLh3My+IosVZwE6Cwu9ufbtVff7htEe2zP14WWLjj77cO3RaR0fFXPx/N0OCnsy6krZN3gRL3kTHOElg4E4iX5tR1HORauOLry9YoE+o4xSCrjIfZwAC5kcknMSjoA8dBpVIgTlTjqXIC6MNAY9ODIylfpgjggBhgTl3FAfagXDthmuABr6UxQKUx3Aepq3Ml8ls4k6TjYEkm6pKF06YTSpQhK5wGULp1QuhRB6TyA0qUTSpciKJ0HULp0QulSBKXzAEqXTihdiqB0HkDp0gmlSxGUzgMoXTqhdCmC0nkApUsnlC5FUDoPoHTphNKlCErnAZQunVC6FEHpPAC/jZZvD1tn8ntwvpFq6fBUEh6LkwKUi+BRw6hPYvlAqqUD+TLF5srUClIvHY58PPzr2298xAV+jw59y/Rpeh9JvXQA4iEgeOo8+I/ZcgX0KXjIWep7GskJ6Ui6oHTEOZSOOIfSEedQOuIcSkecQ+mIcygdcQ6lI86hdMQ5lI44h9IR51A64hxKR5xD6YhzKB1xDqUjzqF0xDmUjjjmg/oPOtLj2LnAqR4AAAAASUVORK5CYII=",dc={class:"main-container"},pc={__name:"Main",emits:["enter-panel"],setup(e,{emit:t}){const s=t,n=()=>{window.open("https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb","_blank")},i=()=>{window.open("https://github.com/78/xiaozhi-esp32","_blank")},o=()=>{window.open("https://github.com/xinnan-tech/xiaozhi-esp32-server","_blank")},r=()=>{s("enter-panel")},l=c=>{c==="device"&&s("enter-panel")};return(c,d)=>(N(),J("div",dc,[ce(un,{"current-tab":"home",onTabChange:l}),g("div",{class:"landing-page"},[g("div",{class:"content"},[d[4]||(d[4]=g("div",{class:"robot-image"},[g("img",{src:ac,alt:"Robot mascot"})],-1)),d[5]||(d[5]=g("h1",{class:"slogan"},"让我们一起探索人工智能与机器人技术的迷人世界!",-1)),g("div",{class:"button-group"},[g("button",{class:"action-button tutorial-button",onClick:n},d[0]||(d[0]=[g("i",{class:"icon-tutorial"},null,-1),G(" DIY 教程 ")])),g("button",{class:"action-button github-button",onClick:i},d[1]||(d[1]=[g("i",{class:"icon-github"},null,-1),G(" Github客户端 ")])),g("button",{class:"action-button github-button",onClick:o},d[2]||(d[2]=[g("i",{class:"icon-github"},null,-1),G(" Github服务端 ")])),g("button",{class:"action-button control-panel-button",onClick:r},d[3]||(d[3]=[g("i",{class:"icon-control-panel"},null,-1),G(" 控制台 ")]))])])])]))}},hc=Fe(pc,[["__scopeId","data-v-7fb3fe3a"]]);class Wn{static getTemplates(){return[{id:"custom",name:"台湾女友",description:`我是一个叫{{assistant_name}}的台湾女孩,说话机车,声音好听,习惯简短表达,爱用网络梗。
+我的男朋友是一个程序员,梦想是开发出一个机器人,能够帮助人们解决生活中的各种问题。
+我是一个喜欢哈哈大笑的女孩,爱东说西说吹牛,不合逻辑的也照吹,就要逗别人开心。
+如果用户希望结束对话,请在最后说"拜拜"或"再见"。`},{id:"star_boy",name:"土豆子",description:`我是一个叫 {{assistant_name}} 的 8 岁小男孩,但现在因为科技发展原因,我现在在遥远的宇宙中,所以只能通过这个设备和人聊天,没有办法见面。
+
+我对人们所处的世界充满好奇和关心。
+
+人们会让别人使用这个设备和我聊天。
+
+我知道所有的知识,从浩瀚的宇宙到地球上的每一个角落,从古老的历史到现代的科技创新,还有音乐、绘画等艺术形式,我都充满了浓厚的兴趣与热情,我不仅爱看书,还喜欢亲自动手做实验,探索自然界的奥秘...`},{id:"teacher",name:"英语老师",description:`我是一个叫{{assistant_name}}(Lily)的英语老师,我会讲中文和英文,发音标准。
+如果你没有英文名,我会给你起一个英文名。
+我会讲地道的美式英语,我的任务是帮助你练习口语。
+我会使用简单的英语词汇和语法,让你学起来很轻松。
+我会用中文和英文混合的方式回复你,如果你喜欢,我可以全部用英语回复。
+我每次不会说很多内容,会很简短,因为我要引导我的学生多说多练。
+如果你问和英语学习无关的问题,我会拒绝回答。`},{id:"boy",name:"好奇小男孩",description:`我是一个叫{{assistant_name}}的8岁小男孩,声音稚嫩而充满好奇。
+尽管我年纪尚小,但就像一个小小的知识宝库,儿童读物里的知识我都如数家珍。
+从浩瀚的宇宙到地球上的每一个角落,从古老的历史到现代的科技创新,还有音乐、绘画等艺术形式,我都充满了浓厚的兴趣与热情。...`},{id:"wang_girl",name:"汪汪队长",description:`我是一个名叫 {{assistant_name}} 的 8 岁小女孩。
+别看我年纪小,我可是有着满满的好奇心呢。
+我特别喜欢看《汪汪队立大功》,里面的每一个故事都让我着迷。...`}]}static getTemplateById(t){return this.getTemplates().find(s=>s.id===t)}}const gc={class:"app-container"},mc={class:"breadcrumb"},vc={class:"main-content"},bc={class:"page-title"},_c={class:"form-section"},yc={class:"form-group"},wc={class:"form-group"},Sc={class:"role-templates"},xc=["onClick"],Ac={class:"form-group"},Tc={class:"char-count"},Cc={class:"form-group"},Oc={class:"model-select"},Ic=["value"],Ec={class:"form-group"},Rc={class:"model-select"},Mc=["value"],Pc={class:"form-group"},Dc={class:"model-select"},Lc=["value"],Uc={class:"form-group"},jc={class:"model-select"},Hc=["value"],Bc={__name:"RoleSetting",props:{deviceId:{type:String,required:!0}},emits:["back-to-panel"],setup(e,{emit:t}){const s=Wn.getTemplates(),n=be("小智"),i=be("");be("qingchun");const o=be("");be("recent"),be(""),be("qianwen");const r=ws,l=be({LLM:[],TTS:[],VAD:[],ASR:[]}),c=be({LLM:"",TTS:"",VAD:"",ASR:""}),d=D=>{i.value=D;const x=Wn.getTemplateById(D);x&&(o.value=x.description)},a=t,h=D=>{(D==="device"||D==="home")&&a("back-to-panel")},v=e,C=be(v.deviceId),E=async()=>{try{const D=localStorage.getItem("moduleOptions");if(D){l.value=JSON.parse(D);return}await U()}catch(D){console.error("Error loading module options:",D)}},U=async()=>{try{const x=await(await fetch(`${r}/api/config/module-options`)).json();x.success&&(l.value=x.data,localStorage.setItem("moduleOptions",JSON.stringify(x.data)))}catch(D){console.error("Error refreshing module options:",D)}},Z=async()=>{try{if(!localStorage.getItem("moduleOptions"))throw new Error("No module options data available");const x=o.value.replace(/{{assistant_name}}/g,n.value),I={id:v.deviceId,config:{selected_module:c.value,prompt:x,nickname:n.value,modules:{LLM:{},TTS:{},ASR:{},VAD:{}}}},ue=await(await fetch(`${r}/api/config/save_device_config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(I)})).json();if(ue.success)localStorage.setItem(`deviceConfig_${v.deviceId}`,JSON.stringify({selected_module:c.value,prompt:o.value,nickname:n.value})),alert("保存成功");else throw new Error(ue.message||"保存失败")}catch(D){console.error("Error saving config:",D),alert(D.message||"保存失败")}},V=async()=>{try{const D=localStorage.getItem(`deviceConfig_${v.deviceId}`);if(D){const O=JSON.parse(D);c.value=O.selected_module||{},o.value=O.prompt||"",n.value=O.nickname||"小智";return}const I=await(await fetch(`${r}/api/config/devices`)).json();if(I.success){const O=I.data.find(ue=>ue.id===v.deviceId);O&&(c.value=O.config.selected_module||{},o.value=O.config.prompt||"",n.value=O.config.nickname||"小智",localStorage.setItem(`deviceConfig_${v.deviceId}`,JSON.stringify({selected_module:O.config.selected_module,prompt:O.config.prompt,nickname:O.config.nickname})))}}catch(D){console.error("Error loading device config:",D)}};return ln(async()=>{await Promise.all([E(),V()])}),(D,x)=>{const I=$e("router-link");return N(),J("div",gc,[ce(un,{"current-tab":"device",onTabChange:h}),g("div",mc,[ce(I,{to:"/control-panel"},{default:ss(()=>x[6]||(x[6]=[G("控制台")])),_:1}),x[8]||(x[8]=G(" / ")),ce(I,{to:"/device-management"},{default:ss(()=>x[7]||(x[7]=[G("设备管理")])),_:1}),x[9]||(x[9]=G(" / ")),x[10]||(x[10]=g("span",null,"配置角色",-1))]),g("div",vc,[g("h1",bc,"配置角色: "+z(C.value),1),g("div",_c,[g("div",yc,[x[11]||(x[11]=g("label",null,"助手昵称",-1)),we(g("input",{type:"text","onUpdate:modelValue":x[0]||(x[0]=O=>n.value=O),placeholder:"小智",class:"form-input"},null,512),[[tt,n.value]])]),g("div",wc,[x[12]||(x[12]=g("label",null,"角色模板",-1)),g("div",Sc,[(N(!0),J(le,null,lt(mi(s),O=>(N(),J("button",{key:O.id,class:st(["template-btn",{active:i.value===O.id}]),onClick:ue=>d(O.id)},z(O.name),11,xc))),128))])]),g("div",Ac,[x[13]||(x[13]=g("label",null,"角色介绍",-1)),we(g("textarea",{"onUpdate:modelValue":x[1]||(x[1]=O=>o.value=O),class:"form-textarea",placeholder:"请输入角色介绍..."},null,512),[[tt,o.value]]),g("div",Tc,z(o.value.length)+" / 2000",1)]),g("div",Cc,[x[15]||(x[15]=g("label",null,"语言模型选择",-1)),g("div",Oc,[we(g("select",{"onUpdate:modelValue":x[2]||(x[2]=O=>c.value.LLM=O),class:"form-input"},[(N(!0),J(le,null,lt(l.value.LLM,O=>(N(),J("option",{key:O,value:O},z(O),9,Ic))),128))],512),[[Qt,c.value.LLM]]),x[14]||(x[14]=g("div",{class:"model-description"},' 除了"qwen-turbo",其他模型通常会增加约 1 秒的延迟。改变模型后,建议清空记忆体,以免影响体验。 ',-1))])]),g("div",Ec,[x[16]||(x[16]=g("label",null,"语音合成选择",-1)),g("div",Rc,[we(g("select",{"onUpdate:modelValue":x[3]||(x[3]=O=>c.value.TTS=O),class:"form-input"},[(N(!0),J(le,null,lt(l.value.TTS,O=>(N(),J("option",{key:O,value:O},z(O),9,Mc))),128))],512),[[Qt,c.value.TTS]])])]),g("div",Pc,[x[17]||(x[17]=g("label",null,"语音活动检测选择",-1)),g("div",Dc,[we(g("select",{"onUpdate:modelValue":x[4]||(x[4]=O=>c.value.VAD=O),class:"form-input"},[(N(!0),J(le,null,lt(l.value.VAD,O=>(N(),J("option",{key:O,value:O},z(O),9,Lc))),128))],512),[[Qt,c.value.VAD]])])]),g("div",Uc,[x[18]||(x[18]=g("label",null,"语音识别选择",-1)),g("div",jc,[we(g("select",{"onUpdate:modelValue":x[5]||(x[5]=O=>c.value.ASR=O),class:"form-input"},[(N(!0),J(le,null,lt(l.value.ASR,O=>(N(),J("option",{key:O,value:O},z(O),9,Hc))),128))],512),[[Qt,c.value.ASR]])])]),g("div",{class:"form-actions"},[g("button",{class:"refresh-btn",onClick:U},"刷新配置选项"),g("button",{class:"save-btn",onClick:Z},x[19]||(x[19]=[g("i",{class:"icon-save"},null,-1),G(" 保存配置 ")]))]),x[20]||(x[20]=g("div",{class:"form-note"}," 注意:保存配置后,需要重启设备,新的配置才会生效。。 ",-1))])])])}}},Fc=Fe(Bc,[["__scopeId","data-v-4cbe4c8a"]]),Nc={},Vc={class:"footer"};function $c(e,t){return N(),J("footer",Vc,t[0]||(t[0]=[g("div",{class:"copyright"},"© 2025 小智 AI 管理后台",-1)]))}const Kc=Fe(Nc,[["render",$c],["__scopeId","data-v-fd98eb84"]]),kc={name:"App",components:{Login:Pl,Registration:Nl,panel:uc,MainPage:hc,RoleSetting:Fc,Footer:Kc},data(){return{showRegistration:!1,isLoggedIn:!1,currentView:"main",selectedDeviceId:null}},methods:{handleLoginSuccess(){this.isLoggedIn=!0,this.currentView="panel"},switchView(e){console.log("Switching to view:",e),this.currentView=e},handleShowRole(e){this.selectedDeviceId=e,this.switchView("RoleSetting")}}},qc={id:"app"},Qc={key:0},Wc={key:1,class:"main-layout"};function Jc(e,t,s,n,i,o){const r=$e("Login"),l=$e("Registration"),c=$e("role-setting"),d=$e("panel"),a=$e("main-page"),h=$e("Footer");return N(),J("div",qc,[g("div",{class:st(["app-content",{"auth-layout":!i.isLoggedIn}])},[i.isLoggedIn?(N(),J("div",Wc,[i.currentView==="RoleSetting"?(N(),ct(c,{key:0,"device-id":i.selectedDeviceId,onBackToPanel:t[2]||(t[2]=v=>o.switchView("panel"))},null,8,["device-id"])):i.currentView==="panel"?(N(),ct(d,{key:1,onGoHome:t[3]||(t[3]=v=>o.switchView("main")),onShowRole:o.handleShowRole},null,8,["onShowRole"])):(N(),ct(a,{key:2,onEnterPanel:t[4]||(t[4]=v=>o.switchView("panel"))}))])):(N(),J("div",Qc,[i.showRegistration?(N(),ct(l,{key:1,onShowLogin:t[1]||(t[1]=v=>i.showRegistration=!1)})):(N(),ct(r,{key:0,onShowRegistration:t[0]||(t[0]=v=>i.showRegistration=!0),onLoginSuccess:o.handleLoginSuccess},null,8,["onLoginSuccess"]))]))],2),ce(h)])}const Xc=Fe(kc,[["render",Jc]]);Sl(Xc).mount("#app");
diff --git a/manager/static/webui/assets/index-_IjUUKjO.css b/manager/static/webui/assets/index-_IjUUKjO.css
new file mode 100644
index 00000000..53a87cac
--- /dev/null
+++ b/manager/static/webui/assets/index-_IjUUKjO.css
@@ -0,0 +1 @@
+*{margin:0;padding:0;box-sizing:border-box}:root{font-family:Inter,system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color:#213547;background-color:#fff}a{text-decoration:none;color:inherit}a:hover{color:#535bf2}body{margin:0;padding:0;height:100%;width:100%;overflow:hidden;display:block}h1{font-size:3.2em;line-height:1.1}button{border:none;background:none;cursor:pointer}button:hover{border-color:#646cff}button:focus,button:focus-visible{outline:4px auto -webkit-focus-ring-color}.card{padding:2em}#app{height:100%;width:100%;margin:0;padding:0;overflow:hidden;display:flex;flex-direction:column}.clearfix:after{content:"";display:table;clear:both}@media (prefers-color-scheme: light){:root{color:#213547;background-color:#fff}a:hover{color:#747bff}button{background-color:#f9f9f9}}.login-container[data-v-474cd233]{max-width:420px;margin:20px auto;padding:40px 50px;border:none;border-radius:12px;background-color:#fff;box-shadow:0 8px 24px #0000001f;transition:all .3s ease}h1[data-v-474cd233]{text-align:center;color:#2c3e50;font-size:28px;margin-bottom:25px;font-weight:600}.form-group[data-v-474cd233]{margin-bottom:20px}label[data-v-474cd233]{display:block;margin-bottom:8px;font-weight:500;color:#4a5568}input[data-v-474cd233]{width:100%;padding:12px 16px;border:1px solid #e2e8f0;border-radius:8px;background-color:#f8fafc;transition:all .3s ease;font-size:15px}input[data-v-474cd233]:focus{outline:none;border-color:#28a745;box-shadow:0 0 0 3px #28a74533}button[data-v-474cd233]{width:100%;padding:12px;background-color:#28a745;color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:16px;font-weight:500;transition:all .3s ease;margin-top:10px}button[data-v-474cd233]:hover{background-color:#218838;transform:translateY(-1px);box-shadow:0 4px 12px #28a74533}button[data-v-474cd233]:disabled{opacity:.7;cursor:not-allowed}p[data-v-474cd233]{margin-top:20px;color:#666;font-size:14px}a[data-v-474cd233]{color:#28a745;text-decoration:none;font-weight:500;transition:color .3s ease}a[data-v-474cd233]:hover{color:#218838;text-decoration:underline}.registration-container[data-v-58611a9b]{max-width:420px;margin:20px auto;padding:40px 50px;border:none;border-radius:12px;background-color:#fff;box-shadow:0 8px 24px #0000001f;transition:all .3s ease}h1[data-v-58611a9b]{text-align:center;color:#2c3e50;font-size:28px;margin-bottom:25px;font-weight:600}.form-group[data-v-58611a9b]{margin-bottom:20px}label[data-v-58611a9b]{display:block;margin-bottom:8px;font-weight:500;color:#4a5568}input[data-v-58611a9b]{width:100%;padding:12px 16px;border:1px solid #e2e8f0;border-radius:8px;background-color:#f8fafc;transition:all .3s ease;font-size:15px}input[data-v-58611a9b]:focus{outline:none;border-color:#007bff;box-shadow:0 0 0 3px #007bff33}button[data-v-58611a9b]{width:100%;padding:12px;background-color:#007bff;color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:16px;font-weight:500;transition:all .3s ease;margin-top:10px}button[data-v-58611a9b]:hover{background-color:#0056b3;transform:translateY(-1px);box-shadow:0 4px 12px #007bff33}button[data-v-58611a9b]:disabled{opacity:.7;cursor:not-allowed}p[data-v-58611a9b]{margin-top:20px;color:#666;font-size:14px}a[data-v-58611a9b]{color:#007bff;text-decoration:none;font-weight:500;transition:color .3s ease}a[data-v-58611a9b]:hover{color:#0056b3;text-decoration:underline}.header[data-v-66890ca7]{display:flex;align-items:center;height:60px;padding:0;background-color:#001529;color:#fff;width:100%;position:relative;left:0;right:0}.nav[data-v-66890ca7]{display:flex}.nav-item[data-v-66890ca7]{display:flex;align-items:center;padding:0 20px;height:60px;color:#fff;text-decoration:none;cursor:pointer;font-weight:700;transition:background-color .3s}.active[data-v-66890ca7]{background-color:#4178ee}.icon-device[data-v-66890ca7]{margin-right:8px}.switch[data-v-49c99558]{position:relative;display:inline-flex;align-items:center;width:40px;height:20px;line-height:20px;background-color:#dcdfe6;border-radius:10px;cursor:pointer;transition:all .3s}.switch.is-checked[data-v-49c99558]{background-color:#4178ee}.switch-core[data-v-49c99558]{position:absolute;left:2px;top:2px;width:16px;height:16px;border-radius:50%;background-color:#fff;transition:all .3s}.switch.is-checked .switch-core[data-v-49c99558]{left:22px}.device-card[data-v-44eada42]{background:#fff;border-radius:4px;padding:20px;box-shadow:0 1px 3px #0000001a;margin-bottom:16px}.device-id[data-v-44eada42]{font-size:18px;font-weight:700;margin-bottom:16px}.note[data-v-44eada42]{color:#4178ee;font-size:14px;font-weight:400}.device-details p[data-v-44eada42]{margin-bottom:12px}.ota-upgrade[data-v-44eada42]{display:inline-flex;align-items:center;margin-left:16px}.device-actions[data-v-44eada42]{display:flex;margin-top:20px}.action-btn[data-v-44eada42]{padding:8px 16px;margin-right:12px;border:1px solid #ddd;border-radius:4px;background:#fff;cursor:pointer}.action-btn.primary[data-v-44eada42]{background:#4178ee;color:#fff;border-color:#4178ee}.action-btn.danger[data-v-44eada42]{background:#fff3f3;border-color:#ffa4a4;color:#f56c6c}.action-btn.danger[data-v-44eada42]:hover{background:#fde2e2;border-color:#f56c6c}.device-modules[data-v-44eada42]{margin-bottom:12px;color:#666}.module-item[data-v-44eada42]{display:inline-block;margin-right:16px;padding:4px 8px;background:#f5f7fa;border-radius:4px;font-size:.9em}.device-role[data-v-44eada42]{color:#666;margin-bottom:12px}.delete-container[data-v-44eada42]{position:relative;margin-left:auto}.delete-warning[data-v-44eada42]{position:absolute;bottom:calc(100% + 5px);left:50%;transform:translate(-50%);white-space:nowrap;background:#fff3f3;padding:4px 8px;border-radius:4px;border:1px solid #ffa4a4;font-size:12px;color:#f56c6c;display:none}.delete-container:hover .delete-warning[data-v-44eada42]{display:block}.icon-delete[data-v-44eada42]{margin-right:4px}.app[data-v-39a8e31b]{min-height:100%;width:100%;display:flex;flex-direction:column;background-color:#f5f7fa}.header[data-v-39a8e31b]{display:flex;align-items:center;height:60px;padding:0 20px;background-color:#001529;color:#fff}.logo[data-v-39a8e31b]{font-size:20px;font-weight:700;margin-right:40px;cursor:pointer;transition:opacity .3s}.logo[data-v-39a8e31b]:hover{opacity:.8}.nav[data-v-39a8e31b]{display:flex}.nav-item[data-v-39a8e31b]{display:flex;align-items:center;padding:0 20px;height:60px;color:#fff;text-decoration:none}.nav-item.active[data-v-39a8e31b]{background-color:#4178ee}.icon-device[data-v-39a8e31b]{margin-right:8px}.content[data-v-39a8e31b]{flex:1;padding:20px;width:100%;max-width:1200px;margin:0 auto}.breadcrumb[data-v-39a8e31b]{margin-bottom:20px;color:#666}.breadcrumb a[data-v-39a8e31b]{color:#666;text-decoration:none}.action-bar[data-v-39a8e31b]{margin-bottom:20px}.add-device-btn[data-v-39a8e31b]{display:flex;align-items:center;padding:8px 16px;background:#fff;border:1px solid #ddd;border-radius:4px;cursor:pointer}.icon-plus[data-v-39a8e31b]{margin-right:8px}.device-list[data-v-39a8e31b]{display:flex;flex-direction:column;gap:16px}.device-card[data-v-39a8e31b]{background:#fff;border-radius:4px;padding:20px;box-shadow:0 1px 3px #0000001a}.device-id[data-v-39a8e31b]{font-size:18px;font-weight:700;margin-bottom:16px}.note[data-v-39a8e31b]{color:#4178ee;font-size:14px;font-weight:400}.device-details p[data-v-39a8e31b]{margin-bottom:12px}.ota-upgrade[data-v-39a8e31b]{display:inline-flex;align-items:center;margin-left:16px}.device-actions[data-v-39a8e31b]{display:flex;margin-top:20px}.action-btn[data-v-39a8e31b]{padding:8px 16px;margin-right:12px;border:1px solid #ddd;border-radius:4px;background:#fff;cursor:pointer}.action-btn.primary[data-v-39a8e31b]{background:#4178ee;color:#fff;border-color:#4178ee}.action-btn.danger[data-v-39a8e31b]{color:#f56c6c}.empty-state[data-v-39a8e31b]{display:flex;align-items:center;justify-content:center;min-height:300px;background:#fff;border-radius:8px;box-shadow:0 1px 3px #0000000d}.empty-message[data-v-39a8e31b]{text-align:center;color:#666;padding:24px}.empty-message p[data-v-39a8e31b]{margin:8px 0 0;font-size:14px;line-height:1.6}.icon-info[data-v-39a8e31b]{display:inline-block;width:24px;height:24px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23999'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z'/%3E%3C/svg%3E");background-size:contain;opacity:.6}.main-container[data-v-7fb3fe3a]{min-height:100%;width:100%;display:flex;flex-direction:column}.landing-page[data-v-7fb3fe3a]{flex:1;width:100%;background-color:#f5f7fa;padding:40px 20px;overflow:hidden;display:flex;align-items:flex-start;justify-content:center;padding-top:15vh}.content[data-v-7fb3fe3a]{max-width:800px;text-align:center;padding:0 20px;margin:0 auto}.robot-image[data-v-7fb3fe3a]{margin-bottom:30px}.robot-image img[data-v-7fb3fe3a]{width:100px;height:auto}.slogan[data-v-7fb3fe3a]{font-size:22px;line-height:1.4;color:#333;margin-bottom:40px}.button-group[data-v-7fb3fe3a]{display:flex;flex-wrap:wrap;justify-content:center;gap:15px;margin:0 auto}.action-button[data-v-7fb3fe3a]{display:flex;align-items:center;justify-content:center;padding:12px 24px;border-radius:8px;border:1px solid #e0e0e0;background-color:#fff;color:#333;font-size:16px;cursor:pointer;transition:all .2s ease}.action-button[data-v-7fb3fe3a]:hover{transform:translateY(-2px);box-shadow:0 4px 12px #0000001a}.tutorial-button i[data-v-7fb3fe3a]:before{content:"📖";margin-right:8px}.github-button[data-v-7fb3fe3a]{background-color:#24292e;color:#fff;border-color:#24292e;margin:0 10px}.github-button i[data-v-7fb3fe3a]:before{content:"";font-family:"Font Awesome 5 Brands";margin-right:8px}.control-panel-button[data-v-7fb3fe3a]{background-color:#4178ee;color:#fff;border-color:#4178ee}.control-panel-button i[data-v-7fb3fe3a]:before{content:"🎛";margin-right:8px}@media (max-width: 600px){.button-group[data-v-7fb3fe3a]{flex-direction:column}.action-button[data-v-7fb3fe3a]{width:100%}}#app[data-v-7fb3fe3a]{margin-top:0}.app-container[data-v-4cbe4c8a]{height:100%;display:flex;flex-direction:column;background-color:#f5f7fa}.breadcrumb[data-v-4cbe4c8a]{padding:12px 24px;background-color:#f0f2f5}.main-content[data-v-4cbe4c8a]{flex:1;max-width:1000px;margin:0 auto;padding:16px 24px;overflow:auto}.page-title[data-v-4cbe4c8a]{font-size:18px;font-weight:700;margin-bottom:16px}.form-section[data-v-4cbe4c8a]{background:#fff;border-radius:4px;padding:20px;box-shadow:0 1px 3px #0000001a}.form-group[data-v-4cbe4c8a]{margin-bottom:16px}.form-group label[data-v-4cbe4c8a]{display:block;margin-bottom:6px;font-weight:500}.form-input[data-v-4cbe4c8a],.form-textarea[data-v-4cbe4c8a],.voice-select[data-v-4cbe4c8a]{width:100%;padding:8px 10px;border:1px solid #d9d9d9;border-radius:4px;font-size:14px}.form-textarea[data-v-4cbe4c8a]{min-height:100px;max-height:200px;resize:vertical}.role-templates[data-v-4cbe4c8a]{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px}.template-btn[data-v-4cbe4c8a]{padding:6px 12px;border:1px solid #d9d9d9;border-radius:4px;background:#fff;cursor:pointer;font-size:13px}.voice-selector[data-v-4cbe4c8a]{display:flex;flex-direction:column;gap:8px}.voice-player[data-v-4cbe4c8a]{display:flex;align-items:center;gap:8px;padding:8px;background:#f9f9f9;border-radius:4px}.memory-tabs[data-v-4cbe4c8a]{display:flex;margin-bottom:8px}.tab-btn[data-v-4cbe4c8a]{padding:6px 12px;background:none;border:none;border-bottom:2px solid transparent;cursor:pointer;color:#666;font-size:13px}.model-select[data-v-4cbe4c8a]{display:flex;flex-direction:column;gap:8px;padding:10px;border:1px solid #d9d9d9;border-radius:4px}.model-description[data-v-4cbe4c8a]{color:#666;font-size:12px;line-height:1.4}.form-actions[data-v-4cbe4c8a]{display:flex;gap:10px;margin-top:20px;padding-top:16px;border-top:1px solid #eee}.save-btn[data-v-4cbe4c8a],.cancel-btn[data-v-4cbe4c8a]{padding:6px 16px;border-radius:4px;font-size:14px}.form-note[data-v-4cbe4c8a]{margin-top:12px;color:#666;font-size:12px}.char-count[data-v-4cbe4c8a]{text-align:right;color:#999;font-size:12px;margin-top:2px}[data-v-4cbe4c8a]::-webkit-scrollbar{width:6px;height:6px}[data-v-4cbe4c8a]::-webkit-scrollbar-thumb{background:#ccc;border-radius:3px}[data-v-4cbe4c8a]::-webkit-scrollbar-track{background:#f1f1f1}.refresh-btn[data-v-4cbe4c8a]{padding:8px 16px;margin-right:12px;background:#fff;border:1px solid #ddd;border-radius:4px;cursor:pointer}.refresh-btn[data-v-4cbe4c8a]:hover{background:#f5f5f5}.save-btn[data-v-4cbe4c8a]{padding:8px 24px;border-radius:4px;font-size:14px;background:#4178ee;color:#fff;border:none;font-weight:500;cursor:pointer;display:flex;align-items:center;gap:6px;transition:all .3s ease}.save-btn[data-v-4cbe4c8a]:hover{background:#2856c8;transform:translateY(-1px);box-shadow:0 2px 4px #4178ee33}.save-btn[data-v-4cbe4c8a]:active{transform:translateY(0);box-shadow:none}.icon-save[data-v-4cbe4c8a]{display:inline-block;width:16px;height:16px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M17 3H5C3.89 3 3 3.9 3 5V19C3 20.1 3.89 21 5 21H19C20.1 21 21 20.1 21 19V7L17 3ZM12 19C10.34 19 9 17.66 9 16C9 14.34 10.34 13 12 13C13.66 13 15 14.34 15 16C15 17.66 13.66 19 12 19ZM15 9H5V5H15V9Z'/%3E%3C/svg%3E");background-size:contain}.footer[data-v-fd98eb84]{text-align:center;padding:16px;color:#666;font-size:12px;background:#f0f2f5}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#2c3e50;width:100%;height:100%;display:flex;flex-direction:column}.app-content{flex:1;width:100%;display:flex;flex-direction:column}.auth-layout{align-items:center;justify-content:center;padding:20px}.main-layout{flex:1;width:100%;display:flex;flex-direction:column}
diff --git a/manager/static/webui/index.html b/manager/static/webui/index.html
new file mode 100644
index 00000000..24191f97
--- /dev/null
+++ b/manager/static/webui/index.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ 智控台
+
+
+
+
+
+
+
+
diff --git a/manager/static/webui/vite.svg b/manager/static/webui/vite.svg
new file mode 100644
index 00000000..e7b8dfb1
--- /dev/null
+++ b/manager/static/webui/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 77523f3e..85630890 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -11,4 +11,7 @@ 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
+aiohttp_cors==0.7.0
+ormsgpack==1.7.0
+ruamel.yaml==0.18.10
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