commit f784106826779b0d7b669470b9fc4b8e7eda9756 Author: fawney19 Date: Wed Dec 10 20:52:44 2025 +0800 Initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a19828c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,62 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +.venv +.uv/ +*.egg-info/ +dist/ +build/ +*.egg + +# Frontend +frontend/node_modules/ +frontend/.vite/ +# frontend/dist/ - 注释掉,因为我们需要预构建的dist文件 + +# Development +.git/ +.gitignore +.github/ +.env +.env.* +!.env.example + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +.tox/ +.mypy_cache/ +.ruff_cache/ + +# Logs +logs/ +*.log + +# Database +*.db +*.sqlite +*.sqlite3 +data/ + +# Docker +docker-compose.override.yml +Dockerfile.* + +# Deployment +deploy/ +scripts/ \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4c14763 --- /dev/null +++ b/.env.example @@ -0,0 +1,31 @@ +# ==================== 必须配置(启动前) ==================== +# 以下配置项必须在项目启动前设置 + +# 数据库密码 +DB_PASSWORD=your_secure_password_here +REDIS_PASSWORD=your_redis_password_here + +# JWT密钥(使用 python generate_keys.py 生成) +# 用于用户登录 token 签名,更换后所有用户需重新登录 +JWT_SECRET_KEY=change-this-to-a-secure-random-string + +# 独立加密密钥(用于加密 Provider API Key 等敏感数据) +# 注意:更换此密钥后需要在管理面板重新配置所有 Provider API Key +ENCRYPTION_KEY=change-this-to-another-secure-random-string + +# 管理员账号(仅首次初始化时使用, 创建完成后可在系统内修改密码) +ADMIN_EMAIL=admin@example.com +ADMIN_USERNAME=admin +ADMIN_PASSWORD=admin123456 + +# ==================== 可选配置(有默认值) ==================== +# 以下配置项有合理的默认值,可按需调整 + +# 应用端口(默认 8084) +# APP_PORT=8084 + +# API Key 前缀(默认 sk) +# API_KEY_PREFIX=sk + +# 日志级别(默认 INFO,可选:DEBUG, INFO, WARNING, ERROR) +# LOG_LEVEL=INFO diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml new file mode 100644 index 0000000..72b3dea --- /dev/null +++ b/.github/workflows/deploy-pages.yml @@ -0,0 +1,57 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + working-directory: frontend + run: npm ci + + - name: Build + working-directory: frontend + env: + GITHUB_PAGES: 'true' + run: npm run build + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: frontend/dist + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9fc8a46 --- /dev/null +++ b/.gitignore @@ -0,0 +1,226 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +# AI Assistant Configuration +.claude/ +.serena/ +.gemini*/ + +### Python ### +*.db +*.db-* + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +# But allow frontend lib directory +!frontend/src/lib/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/python + +# IDE +.vscode/ +.idea/ + +# Captured requests (debugging data) +captured_requests/ + +# macOS +.DS_Store +**/.DS_Store + +# Logs folder +logs/ +*.log + +# Git backup +.git.backup/ + +# Database backups +backups/ + +# Cloud database configuration (contains sensitive credentials) +.env.cloud + +# Runtime lock files +.locks/ +# Demo and test files +frontend/public/*-demo.html +frontend/public/*-measure.html +frontend/public/*-firework.svg + +# Debug and experimental files +debug_*.html +extracted_*.ts + +# Deploy script cache +.deps-hash +.code-hash +.migration-hash diff --git a/Dockerfile.app b/Dockerfile.app new file mode 100644 index 0000000..659efdc --- /dev/null +++ b/Dockerfile.app @@ -0,0 +1,16 @@ +# 应用镜像:基于基础镜像,只复制代码(秒级构建) +# 构建命令: docker build -f Dockerfile.app -t aether-app:latest . +FROM aether-base:latest + +WORKDIR /app + +# 复制后端代码 +COPY src/ ./src/ +COPY alembic.ini ./ +COPY alembic/ ./alembic/ + +# 构建前端(使用基础镜像中已安装的 node_modules) +COPY frontend/ /tmp/frontend/ +RUN cd /tmp/frontend && npm run build && \ + cp -r dist/* /usr/share/nginx/html/ && \ + rm -rf /tmp/frontend diff --git a/Dockerfile.base b/Dockerfile.base new file mode 100644 index 0000000..93ef797 --- /dev/null +++ b/Dockerfile.base @@ -0,0 +1,126 @@ +# 基础镜像:包含所有依赖,只在依赖变化时需要重建 +# 构建命令: docker build -f Dockerfile.base -t aether-base:latest . +FROM python:3.12-slim + +WORKDIR /app + +# 系统依赖 +RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list.d/debian.sources && \ + apt-get update && apt-get install -y \ + nginx \ + supervisor \ + libpq-dev \ + gcc \ + curl \ + gettext-base \ + nodejs \ + npm \ + && rm -rf /var/lib/apt/lists/* + +# pip 镜像源 +RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple + +# Python 依赖(安装到系统,不用 -e 模式) +COPY pyproject.toml README.md ./ +RUN mkdir -p src && touch src/__init__.py && \ + pip install --no-cache-dir . + +# 前端依赖 +COPY frontend/package*.json /tmp/frontend/ +WORKDIR /tmp/frontend +RUN npm config set registry https://registry.npmmirror.com && npm ci + +# Nginx 配置模板 +RUN printf '%s\n' \ +'server {' \ +' listen 80;' \ +' server_name _;' \ +' root /usr/share/nginx/html;' \ +' index index.html;' \ +' client_max_body_size 100M;' \ +'' \ +' location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {' \ +' expires 1y;' \ +' add_header Cache-Control "public, no-transform";' \ +' try_files $uri =404;' \ +' }' \ +'' \ +' location ~ ^/(src|node_modules)/ {' \ +' deny all;' \ +' return 404;' \ +' }' \ +'' \ +' location ~ ^/(dashboard|admin|login)(/|$) {' \ +' try_files $uri $uri/ /index.html;' \ +' }' \ +'' \ +' location / {' \ +' try_files $uri $uri/ @backend;' \ +' }' \ +'' \ +' location @backend {' \ +' proxy_pass http://127.0.0.1:PORT_PLACEHOLDER;' \ +' proxy_http_version 1.1;' \ +' proxy_set_header Host $host;' \ +' proxy_set_header X-Real-IP $remote_addr;' \ +' proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;' \ +' proxy_set_header X-Forwarded-Proto $scheme;' \ +' proxy_set_header Connection "";' \ +' proxy_set_header Accept $http_accept;' \ +' proxy_set_header Content-Type $content_type;' \ +' proxy_set_header Authorization $http_authorization;' \ +' proxy_set_header X-Api-Key $http_x_api_key;' \ +' proxy_buffering off;' \ +' proxy_cache off;' \ +' proxy_request_buffering off;' \ +' chunked_transfer_encoding on;' \ +' proxy_connect_timeout 600s;' \ +' proxy_send_timeout 600s;' \ +' proxy_read_timeout 600s;' \ +' }' \ +'}' > /etc/nginx/sites-available/default.template + +# Supervisor 配置 +RUN printf '%s\n' \ +'[supervisord]' \ +'nodaemon=true' \ +'logfile=/var/log/supervisor/supervisord.log' \ +'pidfile=/var/run/supervisord.pid' \ +'' \ +'[program:nginx]' \ +'command=/bin/bash -c "sed \"s/PORT_PLACEHOLDER/${PORT:-8084}/g\" /etc/nginx/sites-available/default.template > /etc/nginx/sites-available/default && /usr/sbin/nginx -g \"daemon off;\""' \ +'autostart=true' \ +'autorestart=true' \ +'stdout_logfile=/var/log/nginx/access.log' \ +'stderr_logfile=/var/log/nginx/error.log' \ +'' \ +'[program:app]' \ +'command=gunicorn src.main:app -w %(ENV_GUNICORN_WORKERS)s -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:%(ENV_PORT)s --timeout 120 --access-logfile - --error-logfile - --log-level info' \ +'directory=/app' \ +'autostart=true' \ +'autorestart=true' \ +'stdout_logfile=/dev/stdout' \ +'stdout_logfile_maxbytes=0' \ +'stderr_logfile=/dev/stderr' \ +'stderr_logfile_maxbytes=0' \ +'environment=PYTHONUNBUFFERED=1,PYTHONIOENCODING=utf-8,LANG=C.UTF-8,LC_ALL=C.UTF-8,DOCKER_CONTAINER=true' > /etc/supervisor/conf.d/supervisord.conf + +# 创建目录 +RUN mkdir -p /var/log/supervisor /app/logs /app/data /usr/share/nginx/html + +WORKDIR /app + +# 环境变量 +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONIOENCODING=utf-8 \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 \ + PORT=8084 + +EXPOSE 80 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost/health || exit 1 + +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cdffe03 --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ +Aether 非商业开源许可证 + +版权所有 (c) 2025 Aether 贡献者 + +特此授予任何获得本软件及其相关文档文件(以下简称"软件")副本的人免费使用、 +复制、修改、合并、发布和分发本软件的权限,但须遵守以下条件: + +1. 仅限非商业用途 + 本软件不得用于商业目的。商业目的包括但不限于: + - 出售本软件或任何衍生作品 + - 使用本软件提供付费服务 + - 将本软件用于商业产品或服务 + - 将本软件用于任何旨在获取商业利益或金钱报酬的活动 + +2. 署名要求 + 上述版权声明和本许可声明应包含在本软件的所有副本或主要部分中。 + +3. 分发要求 + 本软件或衍生作品的任何分发必须使用相同的许可条款。 + +4. 禁止再许可 + 您不得以不同的条款将本软件再许可给他人。 + +5. 商业许可 + 如需商业使用,请联系版权持有人以获取单独的商业许可。 + +本软件按"原样"提供,不提供任何明示或暗示的保证,包括但不限于对适销性、 +特定用途适用性和非侵权性的保证。在任何情况下,作者或版权持有人均不对任何 +索赔、损害或其他责任承担责任,无论是在合同诉讼、侵权诉讼或其他诉讼中, +还是因本软件或本软件的使用或其他交易而产生的责任。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..2e3f5c1 --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +

+ Aether Logo +

+ +

Aether

+ +

+ 开源 AI API 网关
+ 支持 Claude / OpenAI / Gemini 及其 CLI 客户端的统一接入层 +

+ +

+ 特性 • + 架构 • + 部署 • + 环境变量 • + Q&A +

+ +--- + +## 简介 + +Aether 是一个自托管的 AI API 网关,为团队和个人提供多租户管理、智能负载均衡、成本配额控制和健康监控能力。通过统一的 API 入口,可以无缝对接 Claude、OpenAI、Gemini 等主流 AI 服务及其 CLI 工具。 + +### 页面预览 + +| 首页 | 仪表盘 | +|:---:|:---:| +| ![首页](docs/screenshots/home.png) | ![仪表盘](docs/screenshots/dashboard.png) | + +| 健康监控 | 用户管理 | +|:---:|:---:| +| ![健康监控](docs/screenshots/health.png) | ![用户管理](docs/screenshots/users.png) | + +| 提供商管理 | 使用记录 | +|:---:|:---:| +| ![提供商管理](docs/screenshots/providers.png) | ![使用记录](docs/screenshots/usage.png) | + +| 模型详情 | 关联提供商 | +|:---:|:---:| +| ![模型详情](docs/screenshots/model-detail.png) | ![关联提供商](docs/screenshots/model-providers.png) | + +| 链路追踪 | 系统设置 | +|:---:|:---:| +| ![链路追踪](docs/screenshots/tracing.png) | ![系统设置](docs/screenshots/settings.png) | + +## 部署 + +### Docker Compose(推荐) + +```bash +# 1. 克隆代码 +git clone https://github.com/fawney19/Aether.git +cd aether + +# 2. 配置环境变量 +cp .env.example .env +python generate_keys.py # 生成密钥, 并将生成的密钥填入 .env + +# 3. 部署 +./deploy.sh # 自动构建、启动、迁移 +``` + +### 本地开发 + +```bash +# 启动依赖 +docker-compose up -d postgres redis + +# 后端 +uv sync +./dev.sh + +# 前端 +cd frontend && npm install && npm run dev +``` + +## 环境变量 + +### 必需配置 + +| 变量 | 说明 | +|------|------| +| `DB_PASSWORD` | PostgreSQL 数据库密码 | +| `REDIS_PASSWORD` | Redis 密码 | +| `JWT_SECRET_KEY` | JWT 签名密钥(使用 `generate_keys.py` 生成) | +| `ENCRYPTION_KEY` | API Key 加密密钥(更换后需重新配置 Provider Key) | +| `ADMIN_EMAIL` | 初始管理员邮箱 | +| `ADMIN_USERNAME` | 初始管理员用户名 | +| `ADMIN_PASSWORD` | 初始管理员密码 | + +### 可选配置 + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `APP_PORT` | 8084 | 应用端口 | +| `API_KEY_PREFIX` | sk | API Key 前缀 | +| `LOG_LEVEL` | INFO | 日志级别 (DEBUG/INFO/WARNING/ERROR) | +| `GUNICORN_WORKERS` | 4 | Gunicorn 工作进程数 | +| `DB_PORT` | 5432 | PostgreSQL 端口 | +| `REDIS_PORT` | 6379 | Redis 端口 | + +## Q&A + +### Q: 如何开启/关闭请求体记录? + +1. 管理员在系统设置中, 设置日志记录的记录详细程度. + +- Base: 基本请求信息。 +- Headers: Base + 请求头。 +- Full: Base + 请求头 + 请求体。 + +### Q: 管理员如何给模型配置1M上下文 / 1H缓存 能力支持? + +1. 在模型管理中, 给模型设置1M上下文 / 1H缓存的能力支持, 并配置好价格. +2. 在提供商管理中, 给端点添加支持1M上下文 / 1H缓存的能力的密钥并勾选1M上下文 / 1H缓存能里标签. + +### Q: 用户如何使用1H缓存? + +1. 用户在管理管理中针对指定模型使用1H缓存策略, 或者在密钥管理中针对指定密钥使用1H缓存策略. +注意: 用户若对密钥设置强制1H缓存, 则该密钥只能使用支持1H缓存的模型. + +### Q: 如何配置负载均衡? + +在管理后台「提供商管理中」中切换调度模式,系统提供两种调度策略: + +1. **提供商优先 (provider)**:按 Provider 优先级排序,同优先级内按 Key 的内部优先级排序,相同优先级通过哈希分散实现负载均衡。适合希望优先使用特定供应商的场景。 + +2. **全局 Key 优先 (global_key)**:忽略 Provider 层级,所有 Key 按全局优先级统一排序,相同优先级通过哈希分散实现负载均衡。适合跨 Provider 统一调度、最大化利用所有 Key 的场景。 + +### Q: 提供商免费套餐的计费模式会计入成本吗? + +免费套餐的计费模式, 可以视作倍率为0, 因此产生的记录不会计入倍率费用。 + +--- + +## 许可证 + +本项目采用 [Aether 非商业开源许可证](LICENSE)。 diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 0000000..391d4a4 --- /dev/null +++ b/alembic.ini @@ -0,0 +1,51 @@ +# Alembic 配置文件 +# 用于数据库版本化迁移 + +[alembic] +# 迁移脚本存放目录 +script_location = alembic + +# 模板文件 +file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d_%%(rev)s_%%(slug)s + +# 时区(用于生成迁移文件的时间戳) +timezone = UTC + +# 数据库连接 URL(会被 env.py 从环境变量覆盖) +# Docker 环境中会从 DATABASE_URL 环境变量读取 +sqlalchemy.url = postgresql://postgres:${DB_PASSWORD}@localhost:5432/aether + +# 日志配置 +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/alembic/env.py b/alembic/env.py new file mode 100644 index 0000000..4b59b00 --- /dev/null +++ b/alembic/env.py @@ -0,0 +1,101 @@ +""" +Alembic 环境配置 +用于数据库迁移的运行时环境设置 +""" + +from logging.config import fileConfig +from sqlalchemy import engine_from_config, pool +from alembic import context +import os +import sys +from pathlib import Path + +# 添加项目根目录到 Python 路径 +sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) + +# 加载 .env 文件(本地开发时需要) +try: + from dotenv import load_dotenv + + env_file = Path(__file__).parent.parent / ".env" + if env_file.exists(): + load_dotenv(env_file) +except ImportError: + pass + +# 导入所有数据库模型(确保 Alembic 能检测到所有表) +from src.models.database import Base + +# Alembic Config 对象 +config = context.config + +# 从环境变量获取数据库 URL +# 优先使用 DATABASE_URL,否则从 DB_PASSWORD 自动构建(与 docker-compose 保持一致) +database_url = os.getenv("DATABASE_URL") +if not database_url: + db_password = os.getenv("DB_PASSWORD", "") + db_host = os.getenv("DB_HOST", "localhost") + db_port = os.getenv("DB_PORT", "5432") + db_name = os.getenv("DB_NAME", "aether") + db_user = os.getenv("DB_USER", "postgres") + database_url = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}" +config.set_main_option("sqlalchemy.url", database_url) + +# 配置日志 +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# 目标元数据(包含所有表定义) +target_metadata = Base.metadata + + +def run_migrations_offline() -> None: + """ + 离线模式运行迁移 + + 在离线模式下,不需要连接数据库, + 只生成 SQL 脚本 + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + compare_type=True, # 比较列类型变更 + compare_server_default=True, # 比较默认值变更 + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """ + 在线模式运行迁移 + + 在线模式下,直接连接数据库执行迁移 + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + compare_type=True, # 比较列类型变更 + compare_server_default=True, # 比较默认值变更 + ) + + with context.begin_transaction(): + context.run_migrations() + + +# 根据模式选择运行方式 +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/alembic/script.py.mako b/alembic/script.py.mako new file mode 100644 index 0000000..94b3bbe --- /dev/null +++ b/alembic/script.py.mako @@ -0,0 +1,26 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade() -> None: + """应用迁移:升级到新版本""" + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + """回滚迁移:降级到旧版本""" + ${downgrades if downgrades else "pass"} diff --git a/alembic/versions/20251210_baseline.py b/alembic/versions/20251210_baseline.py new file mode 100644 index 0000000..e46d1e4 --- /dev/null +++ b/alembic/versions/20251210_baseline.py @@ -0,0 +1,771 @@ +"""Baseline migration - all tables consolidated + +Revision ID: 20251210_baseline +Revises: +Create Date: 2024-12-10 + +This is the consolidated baseline migration that creates all tables from scratch. +Includes all schema changes up to circuit breaker v2. +""" + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers +revision = "20251210_baseline" +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Create ENUM types + op.execute("CREATE TYPE userrole AS ENUM ('admin', 'user')") + op.execute( + "CREATE TYPE providerbillingtype AS ENUM ('monthly_quota', 'pay_as_you_go', 'free_tier')" + ) + + # ==================== users ==================== + op.create_table( + "users", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column("email", sa.String(255), unique=True, index=True, nullable=False), + sa.Column("username", sa.String(100), unique=True, index=True, nullable=False), + sa.Column("password_hash", sa.String(255), nullable=False), + sa.Column( + "role", + sa.Enum("admin", "user", name="userrole", create_type=False), + nullable=False, + server_default="user", + ), + sa.Column("allowed_providers", sa.JSON, nullable=True), + sa.Column("allowed_endpoints", sa.JSON, nullable=True), + sa.Column("allowed_models", sa.JSON, nullable=True), + sa.Column("model_capability_settings", sa.JSON, nullable=True), + sa.Column("quota_usd", sa.Float, nullable=True), + sa.Column("used_usd", sa.Float, server_default="0.0"), + sa.Column("total_usd", sa.Float, server_default="0.0"), + sa.Column("is_active", sa.Boolean, server_default="true", nullable=False), + sa.Column("is_deleted", sa.Boolean, server_default="false", nullable=False), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column("last_login_at", sa.DateTime(timezone=True), nullable=True), + ) + + # ==================== providers ==================== + op.create_table( + "providers", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column("name", sa.String(100), unique=True, index=True, nullable=False), + sa.Column("display_name", sa.String(100), nullable=False), + sa.Column("description", sa.Text, nullable=True), + sa.Column("website", sa.String(500), nullable=True), + sa.Column( + "billing_type", + sa.Enum( + "monthly_quota", "pay_as_you_go", "free_tier", name="providerbillingtype", create_type=False + ), + nullable=False, + server_default="pay_as_you_go", + ), + sa.Column("monthly_quota_usd", sa.Float, nullable=True), + sa.Column("monthly_used_usd", sa.Float, server_default="0.0"), + sa.Column("quota_reset_day", sa.Integer, server_default="30"), + sa.Column("quota_last_reset_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("quota_expires_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("rpm_limit", sa.Integer, nullable=True), + sa.Column("rpm_used", sa.Integer, server_default="0"), + sa.Column("rpm_reset_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("provider_priority", sa.Integer, server_default="100"), + sa.Column("is_active", sa.Boolean, server_default="true", nullable=False), + sa.Column("rate_limit", sa.Integer, nullable=True), + sa.Column("concurrent_limit", sa.Integer, nullable=True), + sa.Column("config", sa.JSON, nullable=True), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + ) + + # ==================== global_models ==================== + op.create_table( + "global_models", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column("name", sa.String(100), unique=True, index=True, nullable=False), + sa.Column("display_name", sa.String(100), nullable=False), + sa.Column("description", sa.Text, nullable=True), + sa.Column("icon_url", sa.String(500), nullable=True), + sa.Column("official_url", sa.String(500), nullable=True), + sa.Column("default_price_per_request", sa.Float, nullable=True), + sa.Column("default_tiered_pricing", sa.JSON, nullable=False), + sa.Column("default_supports_vision", sa.Boolean, server_default="false", nullable=True), + sa.Column("default_supports_function_calling", sa.Boolean, server_default="false", nullable=True), + sa.Column("default_supports_streaming", sa.Boolean, server_default="true", nullable=True), + sa.Column("default_supports_extended_thinking", sa.Boolean, server_default="false", nullable=True), + sa.Column("default_supports_image_generation", sa.Boolean, server_default="false", nullable=True), + sa.Column("supported_capabilities", sa.JSON, nullable=True), + sa.Column("is_active", sa.Boolean, server_default="true", nullable=False), + sa.Column("usage_count", sa.Integer, server_default="0", nullable=False, index=True), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + ) + + # ==================== api_keys ==================== + op.create_table( + "api_keys", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column( + "user_id", sa.String(36), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False + ), + sa.Column("key_hash", sa.String(64), unique=True, index=True, nullable=False), + sa.Column("key_encrypted", sa.Text, nullable=True), + sa.Column("name", sa.String(100), nullable=True), + sa.Column("total_requests", sa.Integer, server_default="0"), + sa.Column("total_cost_usd", sa.Float, server_default="0.0"), + sa.Column("balance_used_usd", sa.Float, server_default="0.0"), + sa.Column("current_balance_usd", sa.Float, nullable=True), + sa.Column("is_standalone", sa.Boolean, server_default="false", nullable=False), + sa.Column("allowed_providers", sa.JSON, nullable=True), + sa.Column("allowed_endpoints", sa.JSON, nullable=True), + sa.Column("allowed_api_formats", sa.JSON, nullable=True), + sa.Column("allowed_models", sa.JSON, nullable=True), + sa.Column("rate_limit", sa.Integer, server_default="100"), + sa.Column("concurrent_limit", sa.Integer, server_default="5", nullable=True), + sa.Column("force_capabilities", sa.JSON, nullable=True), + sa.Column("is_active", sa.Boolean, server_default="true", nullable=False), + sa.Column("last_used_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("expires_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("auto_delete_on_expiry", sa.Boolean, server_default="false", nullable=False), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + ) + + # ==================== provider_endpoints ==================== + op.create_table( + "provider_endpoints", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column( + "provider_id", + sa.String(36), + sa.ForeignKey("providers.id", ondelete="CASCADE"), + nullable=False, + ), + sa.Column("api_format", sa.String(50), nullable=False), + sa.Column("base_url", sa.String(500), nullable=False), + sa.Column("headers", sa.JSON, nullable=True), + sa.Column("timeout", sa.Integer, server_default="300"), + sa.Column("max_retries", sa.Integer, server_default="3"), + sa.Column("max_concurrent", sa.Integer, nullable=True), + sa.Column("rate_limit", sa.Integer, nullable=True), + sa.Column("is_active", sa.Boolean, server_default="true", nullable=False), + sa.Column("custom_path", sa.String(200), nullable=True), + sa.Column("config", sa.JSON, nullable=True), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.UniqueConstraint("provider_id", "api_format", name="uq_provider_api_format"), + ) + op.create_index( + "idx_endpoint_format_active", "provider_endpoints", ["api_format", "is_active"] + ) + + # ==================== models ==================== + op.create_table( + "models", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column( + "provider_id", sa.String(36), sa.ForeignKey("providers.id"), nullable=False + ), + sa.Column( + "global_model_id", + sa.String(36), + sa.ForeignKey("global_models.id"), + nullable=False, + index=True, + ), + sa.Column("provider_model_name", sa.String(200), nullable=False), + sa.Column("price_per_request", sa.Float, nullable=True), + sa.Column("tiered_pricing", sa.JSON, nullable=True), + sa.Column("supports_vision", sa.Boolean, nullable=True), + sa.Column("supports_function_calling", sa.Boolean, nullable=True), + sa.Column("supports_streaming", sa.Boolean, nullable=True), + sa.Column("supports_extended_thinking", sa.Boolean, nullable=True), + sa.Column("supports_image_generation", sa.Boolean, nullable=True), + sa.Column("is_active", sa.Boolean, server_default="true", nullable=False), + sa.Column("is_available", sa.Boolean, server_default="true"), + sa.Column("config", sa.JSON, nullable=True), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.UniqueConstraint("provider_id", "provider_model_name", name="uq_provider_model"), + ) + + # ==================== model_mappings ==================== + op.create_table( + "model_mappings", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column("source_model", sa.String(200), nullable=False, index=True), + sa.Column( + "target_global_model_id", + sa.String(36), + sa.ForeignKey("global_models.id", ondelete="CASCADE"), + nullable=False, + index=True, + ), + sa.Column( + "provider_id", sa.String(36), sa.ForeignKey("providers.id"), nullable=True, index=True + ), + sa.Column("mapping_type", sa.String(20), nullable=False, server_default="alias", index=True), + sa.Column("is_active", sa.Boolean, server_default="true", nullable=False), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.UniqueConstraint("source_model", "provider_id", name="uq_model_mapping_source_provider"), + ) + + # ==================== provider_api_keys ==================== + op.create_table( + "provider_api_keys", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column( + "endpoint_id", + sa.String(36), + sa.ForeignKey("provider_endpoints.id", ondelete="CASCADE"), + nullable=False, + ), + sa.Column("api_key", sa.String(500), nullable=False), + sa.Column("name", sa.String(100), nullable=False), + sa.Column("note", sa.String(500), nullable=True), + sa.Column("rate_multiplier", sa.Float, server_default="1.0", nullable=False), + sa.Column("internal_priority", sa.Integer, server_default="50"), + sa.Column("global_priority", sa.Integer, nullable=True), + sa.Column("max_concurrent", sa.Integer, nullable=True), + sa.Column("rate_limit", sa.Integer, nullable=True), + sa.Column("daily_limit", sa.Integer, nullable=True), + sa.Column("monthly_limit", sa.Integer, nullable=True), + sa.Column("allowed_models", sa.JSON, nullable=True), + sa.Column("capabilities", sa.JSON, nullable=True), + sa.Column("learned_max_concurrent", sa.Integer, nullable=True), + sa.Column("concurrent_429_count", sa.Integer, server_default="0", nullable=False), + sa.Column("rpm_429_count", sa.Integer, server_default="0", nullable=False), + sa.Column("last_429_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("last_429_type", sa.String(50), nullable=True), + sa.Column("last_concurrent_peak", sa.Integer, nullable=True), + sa.Column("adjustment_history", sa.JSON, nullable=True), + # Sliding window fields (replaces high_utilization_start) + sa.Column("utilization_samples", sa.JSON, nullable=True), + sa.Column("last_probe_increase_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("health_score", sa.Float, server_default="1.0"), + sa.Column("consecutive_failures", sa.Integer, server_default="0"), + sa.Column("last_failure_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("cache_ttl_minutes", sa.Integer, server_default="5", nullable=False), + sa.Column("max_probe_interval_minutes", sa.Integer, server_default="32", nullable=False), + sa.Column("circuit_breaker_open", sa.Boolean, server_default="false", nullable=False), + sa.Column("circuit_breaker_open_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("next_probe_at", sa.DateTime(timezone=True), nullable=True), + # Circuit breaker v2 fields + sa.Column("request_results_window", sa.JSON, nullable=True), + sa.Column("half_open_until", sa.DateTime(timezone=True), nullable=True), + sa.Column("half_open_successes", sa.Integer, server_default="0", nullable=True), + sa.Column("half_open_failures", sa.Integer, server_default="0", nullable=True), + sa.Column("request_count", sa.Integer, server_default="0"), + sa.Column("success_count", sa.Integer, server_default="0"), + sa.Column("error_count", sa.Integer, server_default="0"), + sa.Column("total_response_time_ms", sa.Integer, server_default="0"), + sa.Column("last_used_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("last_error_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("last_error_msg", sa.Text, nullable=True), + sa.Column("is_active", sa.Boolean, server_default="true", nullable=False), + sa.Column("expires_at", sa.DateTime(timezone=True), nullable=True), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + ) + + # ==================== usage ==================== + op.create_table( + "usage", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column( + "user_id", + sa.String(36), + sa.ForeignKey("users.id", ondelete="SET NULL"), + nullable=True, + ), + sa.Column( + "api_key_id", + sa.String(36), + sa.ForeignKey("api_keys.id", ondelete="SET NULL"), + nullable=True, + ), + sa.Column("request_id", sa.String(100), unique=True, index=True, nullable=False), + sa.Column("provider", sa.String(100), nullable=False), + sa.Column("model", sa.String(100), nullable=False), + sa.Column("target_model", sa.String(100), nullable=True), + sa.Column( + "provider_id", + sa.String(36), + sa.ForeignKey("providers.id", ondelete="SET NULL"), + nullable=True, + ), + sa.Column( + "provider_endpoint_id", + sa.String(36), + sa.ForeignKey("provider_endpoints.id", ondelete="SET NULL"), + nullable=True, + ), + sa.Column( + "provider_api_key_id", + sa.String(36), + sa.ForeignKey("provider_api_keys.id", ondelete="SET NULL"), + nullable=True, + ), + sa.Column("input_tokens", sa.Integer, server_default="0"), + sa.Column("output_tokens", sa.Integer, server_default="0"), + sa.Column("total_tokens", sa.Integer, server_default="0"), + sa.Column("cache_creation_input_tokens", sa.Integer, server_default="0"), + sa.Column("cache_read_input_tokens", sa.Integer, server_default="0"), + sa.Column("input_cost_usd", sa.Float, server_default="0.0"), + sa.Column("output_cost_usd", sa.Float, server_default="0.0"), + sa.Column("cache_cost_usd", sa.Float, server_default="0.0"), + sa.Column("cache_creation_cost_usd", sa.Float, server_default="0.0"), + sa.Column("cache_read_cost_usd", sa.Float, server_default="0.0"), + sa.Column("request_cost_usd", sa.Float, server_default="0.0"), + sa.Column("total_cost_usd", sa.Float, server_default="0.0"), + sa.Column("actual_input_cost_usd", sa.Float, server_default="0.0"), + sa.Column("actual_output_cost_usd", sa.Float, server_default="0.0"), + sa.Column("actual_cache_creation_cost_usd", sa.Float, server_default="0.0"), + sa.Column("actual_cache_read_cost_usd", sa.Float, server_default="0.0"), + sa.Column("actual_request_cost_usd", sa.Float, server_default="0.0"), + sa.Column("actual_total_cost_usd", sa.Float, server_default="0.0"), + sa.Column("rate_multiplier", sa.Float, server_default="1.0"), + sa.Column("input_price_per_1m", sa.Float, nullable=True), + sa.Column("output_price_per_1m", sa.Float, nullable=True), + sa.Column("cache_creation_price_per_1m", sa.Float, nullable=True), + sa.Column("cache_read_price_per_1m", sa.Float, nullable=True), + sa.Column("price_per_request", sa.Float, nullable=True), + sa.Column("request_type", sa.String(50), nullable=True), + sa.Column("api_format", sa.String(50), nullable=True), + sa.Column("is_stream", sa.Boolean, server_default="false"), + sa.Column("status_code", sa.Integer, nullable=True), + sa.Column("error_message", sa.Text, nullable=True), + sa.Column("response_time_ms", sa.Integer, nullable=True), + sa.Column("status", sa.String(20), server_default="completed", nullable=False, index=True), + sa.Column("request_headers", sa.JSON, nullable=True), + sa.Column("request_body", sa.JSON, nullable=True), + sa.Column("provider_request_headers", sa.JSON, nullable=True), + sa.Column("response_headers", sa.JSON, nullable=True), + sa.Column("response_body", sa.JSON, nullable=True), + sa.Column("request_body_compressed", sa.LargeBinary, nullable=True), + sa.Column("response_body_compressed", sa.LargeBinary, nullable=True), + sa.Column("request_metadata", sa.JSON, nullable=True), + sa.Column( + "created_at", + sa.DateTime(timezone=True), + server_default=sa.func.now(), + nullable=False, + index=True, + ), + ) + + # ==================== user_quotas ==================== + op.create_table( + "user_quotas", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column( + "user_id", sa.String(36), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False + ), + sa.Column("quota_type", sa.String(50), nullable=False), + sa.Column("quota_usd", sa.Float, nullable=False), + sa.Column("period_start", sa.DateTime(timezone=True), nullable=False), + sa.Column("period_end", sa.DateTime(timezone=True), nullable=False), + sa.Column("used_usd", sa.Float, server_default="0.0"), + sa.Column("is_active", sa.Boolean, server_default="true"), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + ) + + # ==================== system_configs ==================== + op.create_table( + "system_configs", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column("key", sa.String(100), unique=True, nullable=False), + sa.Column("value", sa.JSON, nullable=False), + sa.Column("description", sa.Text, nullable=True), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + ) + + # ==================== user_preferences ==================== + op.create_table( + "user_preferences", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column( + "user_id", + sa.String(36), + sa.ForeignKey("users.id", ondelete="CASCADE"), + unique=True, + nullable=False, + ), + sa.Column("avatar_url", sa.String(500), nullable=True), + sa.Column("bio", sa.Text, nullable=True), + sa.Column( + "default_provider_id", sa.String(36), sa.ForeignKey("providers.id"), nullable=True + ), + sa.Column("theme", sa.String(20), server_default="light"), + sa.Column("language", sa.String(10), server_default="zh-CN"), + sa.Column("timezone", sa.String(50), server_default="Asia/Shanghai"), + sa.Column("email_notifications", sa.Boolean, server_default="true"), + sa.Column("usage_alerts", sa.Boolean, server_default="true"), + sa.Column("announcement_notifications", sa.Boolean, server_default="true"), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + ) + + # ==================== announcements ==================== + op.create_table( + "announcements", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column("title", sa.String(200), nullable=False), + sa.Column("content", sa.Text, nullable=False), + sa.Column("type", sa.String(20), server_default="info"), + sa.Column("priority", sa.Integer, server_default="0"), + sa.Column( + "author_id", + sa.String(36), + sa.ForeignKey("users.id", ondelete="SET NULL"), + nullable=True, + ), + sa.Column("is_active", sa.Boolean, server_default="true", index=True), + sa.Column("is_pinned", sa.Boolean, server_default="false"), + sa.Column("start_time", sa.DateTime(timezone=True), nullable=True), + sa.Column("end_time", sa.DateTime(timezone=True), nullable=True), + sa.Column( + "created_at", + sa.DateTime(timezone=True), + server_default=sa.func.now(), + nullable=False, + index=True, + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + ) + + # ==================== announcement_reads ==================== + op.create_table( + "announcement_reads", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column( + "user_id", sa.String(36), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False + ), + sa.Column( + "announcement_id", sa.String(36), sa.ForeignKey("announcements.id"), nullable=False + ), + sa.Column( + "read_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.UniqueConstraint("user_id", "announcement_id", name="uq_user_announcement"), + ) + + # ==================== audit_logs ==================== + op.create_table( + "audit_logs", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column("event_type", sa.String(50), nullable=False, index=True), + sa.Column( + "user_id", + sa.String(36), + sa.ForeignKey("users.id", ondelete="SET NULL"), + nullable=True, + index=True, + ), + sa.Column("api_key_id", sa.String(36), nullable=True), + sa.Column("description", sa.Text, nullable=False), + sa.Column("ip_address", sa.String(45), nullable=True), + sa.Column("user_agent", sa.String(500), nullable=True), + sa.Column("request_id", sa.String(100), nullable=True, index=True), + sa.Column("event_metadata", sa.JSON, nullable=True), + sa.Column("status_code", sa.Integer, nullable=True), + sa.Column("error_message", sa.Text, nullable=True), + sa.Column( + "created_at", + sa.DateTime(timezone=True), + server_default=sa.func.now(), + nullable=False, + index=True, + ), + ) + + # ==================== request_candidates ==================== + op.create_table( + "request_candidates", + sa.Column("id", sa.String(36), primary_key=True), + sa.Column("request_id", sa.String(100), nullable=False, index=True), + sa.Column( + "user_id", sa.String(36), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=True + ), + sa.Column( + "api_key_id", + sa.String(36), + sa.ForeignKey("api_keys.id", ondelete="CASCADE"), + nullable=True, + ), + sa.Column("candidate_index", sa.Integer, nullable=False), + sa.Column("retry_index", sa.Integer, nullable=False, server_default="0"), + sa.Column( + "provider_id", + sa.String(36), + sa.ForeignKey("providers.id", ondelete="CASCADE"), + nullable=True, + ), + sa.Column( + "endpoint_id", + sa.String(36), + sa.ForeignKey("provider_endpoints.id", ondelete="CASCADE"), + nullable=True, + ), + sa.Column( + "key_id", + sa.String(36), + sa.ForeignKey("provider_api_keys.id", ondelete="CASCADE"), + nullable=True, + ), + sa.Column("status", sa.String(20), nullable=False), + sa.Column("skip_reason", sa.Text, nullable=True), + sa.Column("is_cached", sa.Boolean, server_default="false"), + sa.Column("status_code", sa.Integer, nullable=True), + sa.Column("error_type", sa.String(50), nullable=True), + sa.Column("error_message", sa.Text, nullable=True), + sa.Column("latency_ms", sa.Integer, nullable=True), + sa.Column("concurrent_requests", sa.Integer, nullable=True), + sa.Column("extra_data", sa.JSON, nullable=True), + sa.Column("required_capabilities", sa.JSON, nullable=True), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column("started_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True), + sa.UniqueConstraint( + "request_id", "candidate_index", "retry_index", name="uq_request_candidate_with_retry" + ), + ) + op.create_index("idx_request_candidates_request_id", "request_candidates", ["request_id"]) + op.create_index("idx_request_candidates_status", "request_candidates", ["status"]) + op.create_index("idx_request_candidates_provider_id", "request_candidates", ["provider_id"]) + + # ==================== stats_daily ==================== + op.create_table( + "stats_daily", + sa.Column("id", sa.String(36), primary_key=True), + sa.Column("date", sa.DateTime(timezone=True), nullable=False, unique=True, index=True), + sa.Column("total_requests", sa.Integer, server_default="0", nullable=False), + sa.Column("success_requests", sa.Integer, server_default="0", nullable=False), + sa.Column("error_requests", sa.Integer, server_default="0", nullable=False), + sa.Column("input_tokens", sa.BigInteger, server_default="0", nullable=False), + sa.Column("output_tokens", sa.BigInteger, server_default="0", nullable=False), + sa.Column("cache_creation_tokens", sa.BigInteger, server_default="0", nullable=False), + sa.Column("cache_read_tokens", sa.BigInteger, server_default="0", nullable=False), + sa.Column("total_cost", sa.Float, server_default="0.0", nullable=False), + sa.Column("actual_total_cost", sa.Float, server_default="0.0", nullable=False), + sa.Column("input_cost", sa.Float, server_default="0.0", nullable=False), + sa.Column("output_cost", sa.Float, server_default="0.0", nullable=False), + sa.Column("cache_creation_cost", sa.Float, server_default="0.0", nullable=False), + sa.Column("cache_read_cost", sa.Float, server_default="0.0", nullable=False), + sa.Column("avg_response_time_ms", sa.Float, server_default="0.0", nullable=False), + sa.Column("fallback_count", sa.Integer, server_default="0", nullable=False), + sa.Column("unique_models", sa.Integer, server_default="0", nullable=False), + sa.Column("unique_providers", sa.Integer, server_default="0", nullable=False), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + ) + + # ==================== stats_summary ==================== + op.create_table( + "stats_summary", + sa.Column("id", sa.String(36), primary_key=True), + sa.Column("cutoff_date", sa.DateTime(timezone=True), nullable=False), + sa.Column("all_time_requests", sa.Integer, server_default="0", nullable=False), + sa.Column("all_time_success_requests", sa.Integer, server_default="0", nullable=False), + sa.Column("all_time_error_requests", sa.Integer, server_default="0", nullable=False), + sa.Column("all_time_input_tokens", sa.BigInteger, server_default="0", nullable=False), + sa.Column("all_time_output_tokens", sa.BigInteger, server_default="0", nullable=False), + sa.Column( + "all_time_cache_creation_tokens", sa.BigInteger, server_default="0", nullable=False + ), + sa.Column("all_time_cache_read_tokens", sa.BigInteger, server_default="0", nullable=False), + sa.Column("all_time_cost", sa.Float, server_default="0.0", nullable=False), + sa.Column("all_time_actual_cost", sa.Float, server_default="0.0", nullable=False), + sa.Column("total_users", sa.Integer, server_default="0", nullable=False), + sa.Column("active_users", sa.Integer, server_default="0", nullable=False), + sa.Column("total_api_keys", sa.Integer, server_default="0", nullable=False), + sa.Column("active_api_keys", sa.Integer, server_default="0", nullable=False), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + ) + + # ==================== stats_user_daily ==================== + op.create_table( + "stats_user_daily", + sa.Column("id", sa.String(36), primary_key=True), + sa.Column( + "user_id", sa.String(36), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False + ), + sa.Column("date", sa.DateTime(timezone=True), nullable=False, index=True), + sa.Column("total_requests", sa.Integer, server_default="0", nullable=False), + sa.Column("success_requests", sa.Integer, server_default="0", nullable=False), + sa.Column("error_requests", sa.Integer, server_default="0", nullable=False), + sa.Column("input_tokens", sa.BigInteger, server_default="0", nullable=False), + sa.Column("output_tokens", sa.BigInteger, server_default="0", nullable=False), + sa.Column("cache_creation_tokens", sa.BigInteger, server_default="0", nullable=False), + sa.Column("cache_read_tokens", sa.BigInteger, server_default="0", nullable=False), + sa.Column("total_cost", sa.Float, server_default="0.0", nullable=False), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.UniqueConstraint("user_id", "date", name="uq_stats_user_daily"), + ) + op.create_index("idx_stats_user_daily_user_date", "stats_user_daily", ["user_id", "date"]) + + # ==================== api_key_provider_mappings ==================== + op.create_table( + "api_key_provider_mappings", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column( + "api_key_id", + sa.String(36), + sa.ForeignKey("api_keys.id", ondelete="CASCADE"), + nullable=False, + index=True, + ), + sa.Column( + "provider_id", + sa.String(36), + sa.ForeignKey("providers.id", ondelete="CASCADE"), + nullable=False, + index=True, + ), + sa.Column("priority_adjustment", sa.Integer, server_default="0"), + sa.Column("weight_multiplier", sa.Float, server_default="1.0"), + sa.Column("is_enabled", sa.Boolean, server_default="true", nullable=False), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.UniqueConstraint("api_key_id", "provider_id", name="uq_apikey_provider"), + ) + op.create_index( + "idx_apikey_provider_enabled", "api_key_provider_mappings", ["api_key_id", "is_enabled"] + ) + + # ==================== provider_usage_tracking ==================== + op.create_table( + "provider_usage_tracking", + sa.Column("id", sa.String(36), primary_key=True, index=True), + sa.Column( + "provider_id", + sa.String(36), + sa.ForeignKey("providers.id", ondelete="CASCADE"), + nullable=False, + index=True, + ), + sa.Column("window_start", sa.DateTime(timezone=True), nullable=False, index=True), + sa.Column("window_end", sa.DateTime(timezone=True), nullable=False), + sa.Column("total_requests", sa.Integer, server_default="0"), + sa.Column("successful_requests", sa.Integer, server_default="0"), + sa.Column("failed_requests", sa.Integer, server_default="0"), + sa.Column("avg_response_time_ms", sa.Float, server_default="0.0"), + sa.Column("total_response_time_ms", sa.Float, server_default="0.0"), + sa.Column("total_cost_usd", sa.Float, server_default="0.0"), + sa.Column( + "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + sa.Column( + "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False + ), + ) + op.create_index( + "idx_provider_window", "provider_usage_tracking", ["provider_id", "window_start"] + ) + op.create_index("idx_window_time", "provider_usage_tracking", ["window_start", "window_end"]) + + +def downgrade() -> None: + # Drop tables in reverse order (respecting foreign key dependencies) + op.drop_table("provider_usage_tracking") + op.drop_table("api_key_provider_mappings") + op.drop_table("stats_user_daily") + op.drop_table("stats_summary") + op.drop_table("stats_daily") + op.drop_table("request_candidates") + op.drop_table("audit_logs") + op.drop_table("announcement_reads") + op.drop_table("announcements") + op.drop_table("user_preferences") + op.drop_table("system_configs") + op.drop_table("user_quotas") + op.drop_table("usage") + op.drop_table("provider_api_keys") + op.drop_table("model_mappings") + op.drop_table("models") + op.drop_table("provider_endpoints") + op.drop_table("api_keys") + op.drop_table("global_models") + op.drop_table("providers") + op.drop_table("users") + + # Drop ENUM types + op.execute("DROP TYPE IF EXISTS providerbillingtype") + op.execute("DROP TYPE IF EXISTS userrole") diff --git a/alembic/versions/README.md b/alembic/versions/README.md new file mode 100644 index 0000000..5f94598 --- /dev/null +++ b/alembic/versions/README.md @@ -0,0 +1,85 @@ +# Aether - 数据库迁移说明 + +## 当前版本 + +- **Revision ID**: `aether_baseline` +- **创建日期**: 2025-12-06 +- **状态**: 全新基线 + +## 迁移历史 + +所有历史增量迁移已清理,当前以完整 schema 作为新起点。 + +## 核心数据库结构 + +### 用户系统 +- **users**: 用户账户管理 +- **api_keys**: API 密钥管理 +- **user_quotas**: 用户配额管理 +- **user_preferences**: 用户偏好设置 + +### Provider 三层架构 +- **providers**: LLM 提供商配置 +- **provider_endpoints**: Provider 的 API 端点配置 +- **provider_api_keys**: Endpoint 的具体 API 密钥 +- **api_key_provider_mappings**: 用户 API Key 到 Provider 的映射关系 + +### 模型系统 +- **global_models**: 统一模型定义(GlobalModel) +- **models**: Provider 的模型实现和价格配置 +- **model_mappings**: 统一的别名与降级映射表 + +### 监控和追踪 +- **usage**: API 使用记录 +- **request_candidates**: 请求候选记录 +- **provider_usage_tracking**: Provider 使用统计 +- **audit_logs**: 系统审计日志 + +### 系统功能 +- **announcements**: 系统公告 +- **announcement_reads**: 公告阅读记录 +- **system_configs**: 系统配置 + +## 从旧数据库迁移 + +如需从旧数据库迁移数据,请使用迁移脚本: + +```bash +# 设置环境变量 +export OLD_DATABASE_URL="postgresql://user:pass@old-host:5432/old_db" +export NEW_DATABASE_URL="postgresql://user:pass@new-host:5432/aether" + +# 干运行(查看迁移量) +python scripts/migrate_data.py --dry-run + +# 执行迁移 +python scripts/migrate_data.py + +# 只迁移特定表 +python scripts/migrate_data.py --tables users,providers,api_keys + +# 跳过大表 +python scripts/migrate_data.py --skip usage,audit_logs +``` + +## 新数据库初始化 + +```bash +# 1. 运行迁移创建表结构 +DATABASE_URL="postgresql://user:pass@host:5432/aether" uv run alembic upgrade head + +# 2. 初始化管理员账户 +python -m src.database.init_db +``` + +## 未来迁移 + +基于 `aether_baseline` 创建增量迁移: + +```bash +# 修改模型后,生成新的迁移 +DATABASE_URL="..." uv run alembic revision --autogenerate -m "描述变更" + +# 应用迁移 +DATABASE_URL="..." uv run alembic upgrade head +``` diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..233396e --- /dev/null +++ b/deploy.sh @@ -0,0 +1,213 @@ +#!/bin/bash +# 智能部署脚本 - 自动检测依赖/代码/迁移变化 +# +# 用法: +# 部署/更新: ./deploy.sh (自动检测所有变化) +# 强制重建: ./deploy.sh --rebuild-base +# 强制全部重建: ./deploy.sh --force + +set -e +cd "$(dirname "$0")" + +# 兼容 docker-compose 和 docker compose +if command -v docker-compose &> /dev/null; then + DC="docker-compose" +else + DC="docker compose" +fi + +# 缓存文件 +HASH_FILE=".deps-hash" +CODE_HASH_FILE=".code-hash" +MIGRATION_HASH_FILE=".migration-hash" + +# 计算依赖文件的哈希值 +calc_deps_hash() { + cat pyproject.toml frontend/package.json frontend/package-lock.json 2>/dev/null | md5sum | cut -d' ' -f1 +} + +# 计算代码文件的哈希值 +calc_code_hash() { + find src -type f -name "*.py" 2>/dev/null | sort | xargs cat 2>/dev/null | md5sum | cut -d' ' -f1 + find frontend/src -type f \( -name "*.vue" -o -name "*.ts" -o -name "*.tsx" -o -name "*.js" \) 2>/dev/null | sort | xargs cat 2>/dev/null | md5sum | cut -d' ' -f1 +} + +# 计算迁移文件的哈希值 +calc_migration_hash() { + find alembic/versions -name "*.py" -type f 2>/dev/null | sort | xargs cat 2>/dev/null | md5sum | cut -d' ' -f1 +} + +# 检查依赖是否变化 +check_deps_changed() { + local current_hash=$(calc_deps_hash) + if [ -f "$HASH_FILE" ]; then + local saved_hash=$(cat "$HASH_FILE") + if [ "$current_hash" = "$saved_hash" ]; then + return 1 + fi + fi + return 0 +} + +# 检查代码是否变化 +check_code_changed() { + local current_hash=$(calc_code_hash) + if [ -f "$CODE_HASH_FILE" ]; then + local saved_hash=$(cat "$CODE_HASH_FILE") + if [ "$current_hash" = "$saved_hash" ]; then + return 1 + fi + fi + return 0 +} + +# 检查迁移是否变化 +check_migration_changed() { + local current_hash=$(calc_migration_hash) + if [ -f "$MIGRATION_HASH_FILE" ]; then + local saved_hash=$(cat "$MIGRATION_HASH_FILE") + if [ "$current_hash" = "$saved_hash" ]; then + return 1 + fi + fi + return 0 +} + +# 保存哈希 +save_deps_hash() { calc_deps_hash > "$HASH_FILE"; } +save_code_hash() { calc_code_hash > "$CODE_HASH_FILE"; } +save_migration_hash() { calc_migration_hash > "$MIGRATION_HASH_FILE"; } + +# 构建基础镜像 +build_base() { + echo ">>> Building base image (dependencies)..." + docker build -f Dockerfile.base -t aether-base:latest . + save_deps_hash +} + +# 构建应用镜像 +build_app() { + echo ">>> Building app image (code only)..." + docker build -f Dockerfile.app -t aether-app:latest . + save_code_hash +} + +# 运行数据库迁移 +run_migration() { + echo ">>> Running database migration..." + + # 尝试运行 upgrade head,捕获错误 + UPGRADE_OUTPUT=$($DC exec -T app alembic upgrade head 2>&1) && { + echo "$UPGRADE_OUTPUT" + save_migration_hash + return 0 + } + + # 检查是否是因为找不到旧版本(基线重置场景) + if echo "$UPGRADE_OUTPUT" | grep -q "Can't locate revision"; then + echo ">>> Detected baseline reset: old revision not found in migrations" + echo ">>> Clearing old version and stamping to new baseline..." + + # 先清除旧的版本记录,再 stamp 到新基线 + $DC exec -T app python -c " +from sqlalchemy import create_engine, text +import os +engine = create_engine(os.environ['DATABASE_URL']) +with engine.connect() as conn: + conn.execute(text('DELETE FROM alembic_version')) + conn.commit() +print('Old version cleared') +" + # 获取最新的迁移版本(匹配 revision_id (head) 格式) + LATEST_VERSION=$($DC exec -T app alembic heads 2>/dev/null | grep -oE '^[0-9a-zA-Z_]+' | head -1) + if [ -n "$LATEST_VERSION" ]; then + $DC exec -T app alembic stamp "$LATEST_VERSION" + echo ">>> Database stamped to $LATEST_VERSION" + save_migration_hash + else + echo ">>> ERROR: Could not determine latest migration version" + exit 1 + fi + else + # 其他错误,直接输出并退出 + echo "$UPGRADE_OUTPUT" + exit 1 + fi +} + +# 强制全部重建 +if [ "$1" = "--force" ] || [ "$1" = "-f" ]; then + echo ">>> Force rebuilding everything..." + build_base + build_app + $DC up -d --force-recreate + sleep 3 + run_migration + docker image prune -f + echo ">>> Done!" + $DC ps + exit 0 +fi + +# 强制重建基础镜像 +if [ "$1" = "--rebuild-base" ] || [ "$1" = "-r" ]; then + build_base + echo ">>> Base image rebuilt. Run ./deploy.sh to deploy." + exit 0 +fi + +# 拉取最新代码 +echo ">>> Pulling latest code..." +git pull + +# 标记是否需要重启 +NEED_RESTART=false + +# 检查基础镜像是否存在,或依赖是否变化 +if ! docker image inspect aether-base:latest >/dev/null 2>&1; then + echo ">>> Base image not found, building..." + build_base + NEED_RESTART=true +elif check_deps_changed; then + echo ">>> Dependencies changed, rebuilding base image..." + build_base + NEED_RESTART=true +else + echo ">>> Dependencies unchanged." +fi + +# 检查代码是否变化 +if ! docker image inspect aether-app:latest >/dev/null 2>&1; then + echo ">>> App image not found, building..." + build_app + NEED_RESTART=true +elif check_code_changed; then + echo ">>> Code changed, rebuilding app image..." + build_app + NEED_RESTART=true +else + echo ">>> Code unchanged." +fi + +# 只在有变化时重启 +if [ "$NEED_RESTART" = true ]; then + echo ">>> Restarting services..." + $DC up -d +else + echo ">>> No changes detected, skipping restart." +fi + +# 检查迁移变化 +if check_migration_changed; then + echo ">>> Migration files changed, running database migration..." + sleep 3 + run_migration +else + echo ">>> Migration unchanged." +fi + +# 清理 +docker image prune -f >/dev/null 2>&1 || true + +echo ">>> Done!" +$DC ps diff --git a/dev.sh b/dev.sh new file mode 100755 index 0000000..43e8dee --- /dev/null +++ b/dev.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# 本地开发启动脚本 +clear + +# 加载 .env 文件 +set -a +source .env +set +a + +# 构建 DATABASE_URL +export DATABASE_URL="postgresql://postgres:${DB_PASSWORD}@localhost:5432/aether" + +# 启动 uvicorn(热重载模式) +echo "🚀 启动本地开发服务器..." +echo "📍 后端地址: http://localhost:8084" +echo "📊 数据库: ${DATABASE_URL}" +echo "" + +uv run uvicorn src.main:app --reload --port 8084 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..efcd7b5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,75 @@ +# Aether 部署配置 +# 使用 ./deploy.sh 自动部署 + +services: + postgres: + image: postgres:15 + container_name: aether-postgres + environment: + POSTGRES_DB: aether + POSTGRES_USER: postgres + POSTGRES_PASSWORD: ${DB_PASSWORD} + TZ: Asia/Shanghai + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "${DB_PORT:-5432}:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + restart: unless-stopped + + redis: + image: redis:7-alpine + container_name: aether-redis + command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD} + volumes: + - redis_data:/data + ports: + - "${REDIS_PORT:-6379}:6379" + healthcheck: + test: ["CMD", "redis-cli", "--raw", "incr", "ping"] + interval: 5s + timeout: 3s + retries: 5 + restart: unless-stopped + + app: + image: aether-app:latest + container_name: aether-app + environment: + DATABASE_URL: postgresql://postgres:${DB_PASSWORD}@postgres:5432/aether + REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/0 + PORT: 8084 + JWT_SECRET_KEY: ${JWT_SECRET_KEY} + ENCRYPTION_KEY: ${ENCRYPTION_KEY} + JWT_ALGORITHM: HS256 + JWT_EXPIRATION_DELTA: 86400 + LOG_LEVEL: ${LOG_LEVEL:-INFO} + ADMIN_EMAIL: ${ADMIN_EMAIL} + ADMIN_USERNAME: ${ADMIN_USERNAME} + ADMIN_PASSWORD: ${ADMIN_PASSWORD} + API_KEY_PREFIX: ${API_KEY_PREFIX:-sk} + GUNICORN_WORKERS: ${GUNICORN_WORKERS:-4} + TZ: Asia/Shanghai + PYTHONIOENCODING: utf-8 + LANG: C.UTF-8 + LC_ALL: C.UTF-8 + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + ports: + - "${APP_PORT:-8084}:80" + volumes: + # 挂载日志目录到主机,便于调试和持久化 + - ./logs:/app/logs + restart: unless-stopped + + +volumes: + postgres_data: + redis_data: \ No newline at end of file diff --git a/docs/screenshots/dashboard.png b/docs/screenshots/dashboard.png new file mode 100644 index 0000000..8f1ba1d Binary files /dev/null and b/docs/screenshots/dashboard.png differ diff --git a/docs/screenshots/health.png b/docs/screenshots/health.png new file mode 100644 index 0000000..180a8a5 Binary files /dev/null and b/docs/screenshots/health.png differ diff --git a/docs/screenshots/home.png b/docs/screenshots/home.png new file mode 100644 index 0000000..aa16a79 Binary files /dev/null and b/docs/screenshots/home.png differ diff --git a/docs/screenshots/model-detail.png b/docs/screenshots/model-detail.png new file mode 100644 index 0000000..b4db406 Binary files /dev/null and b/docs/screenshots/model-detail.png differ diff --git a/docs/screenshots/model-providers.png b/docs/screenshots/model-providers.png new file mode 100644 index 0000000..ca3cb36 Binary files /dev/null and b/docs/screenshots/model-providers.png differ diff --git a/docs/screenshots/providers.png b/docs/screenshots/providers.png new file mode 100644 index 0000000..658ad9c Binary files /dev/null and b/docs/screenshots/providers.png differ diff --git a/docs/screenshots/settings.png b/docs/screenshots/settings.png new file mode 100644 index 0000000..6559158 Binary files /dev/null and b/docs/screenshots/settings.png differ diff --git a/docs/screenshots/tracing.png b/docs/screenshots/tracing.png new file mode 100644 index 0000000..fa48b12 Binary files /dev/null and b/docs/screenshots/tracing.png differ diff --git a/docs/screenshots/usage.png b/docs/screenshots/usage.png new file mode 100644 index 0000000..cb714dc Binary files /dev/null and b/docs/screenshots/usage.png differ diff --git a/docs/screenshots/users.png b/docs/screenshots/users.png new file mode 100644 index 0000000..c327567 Binary files /dev/null and b/docs/screenshots/users.png differ diff --git a/frontend/.eslintignore b/frontend/.eslintignore new file mode 100644 index 0000000..e04fc3a --- /dev/null +++ b/frontend/.eslintignore @@ -0,0 +1,8 @@ +dist +node_modules +*.d.ts +vite.config.ts +vitest.config.ts +postcss.config.js +tailwind.config.js +components.json diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs new file mode 100644 index 0000000..5df62b4 --- /dev/null +++ b/frontend/.eslintrc.cjs @@ -0,0 +1,62 @@ +module.exports = { + root: true, + env: { + browser: true, + es2021: true, + node: true, + }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:vue/vue3-recommended', + ], + parser: 'vue-eslint-parser', + parserOptions: { + ecmaVersion: 'latest', + parser: '@typescript-eslint/parser', + sourceType: 'module', + }, + plugins: ['@typescript-eslint', 'vue'], + rules: { + // TypeScript 规则 + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + }, + ], + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-non-null-assertion': 'warn', + + // Vue 规则 + 'vue/multi-word-component-names': 'off', + 'vue/no-v-html': 'error', // 防止 XSS 攻击 + 'vue/component-api-style': ['error', ['script-setup']], + 'vue/component-name-in-template-casing': ['error', 'PascalCase'], + 'vue/custom-event-name-casing': ['error', 'camelCase'], + 'vue/define-macros-order': [ + 'error', + { + order: ['defineProps', 'defineEmits'], + }, + ], + 'vue/html-comment-content-spacing': ['error', 'always'], + 'vue/no-unused-refs': 'error', + 'vue/no-useless-v-bind': 'error', + 'vue/padding-line-between-blocks': ['error', 'always'], + 'vue/prefer-separate-static-class': 'error', + + // 一般规则 + 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn', + 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'warn', + 'prefer-const': 'error', + 'no-var': 'error', + 'object-shorthand': ['error', 'always'], + 'prefer-template': 'error', + 'prefer-arrow-callback': 'error', + }, + ignorePatterns: ['dist', 'node_modules', '*.config.js', '*.config.ts'], +} diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..89758ca --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,25 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +# dist +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/DESIGN_SYSTEM.md b/frontend/DESIGN_SYSTEM.md new file mode 100644 index 0000000..d9210f5 --- /dev/null +++ b/frontend/DESIGN_SYSTEM.md @@ -0,0 +1,1532 @@ +# Aether 设计系统 v2.3 + +> 基于 shadcn/ui 和书本纸张主题的完整前端设计规范 + +**版本**: 2.3.0 +**最后更新**: 2025-11-18 + +--- + +## 概述 + +本文档描述了 Aether 前端项目的设计系统,基于 shadcn/ui 和自定义主题构建。所有组件均已实现并在生产环境中使用。 + +### 核心理念 + +1. **一致性优先** - 所有组件遵循统一的视觉语言和交互模式 +2. **响应式设计** - 组件自适应不同屏幕尺寸(移动端、平板、桌面) +3. **可访问性** - 遵循 WCAG 2.1 标准,支持键盘导航和屏幕阅读器 +4. **性能优化** - 轻量级组件,按需加载,优化渲染性能 +5. **开发体验** - TypeScript 类型安全,清晰的 API 设计,完善的文档 + +### 色彩体系 + +项目使用书本纸张主题色: + +- **book-cloth** - 书籍封面布料色 (#cc785c / #d4a27f) +- **kraft** - 牛皮纸色 (#b97847 / #c9a26f) +- **manilla** - 马尼拉纸色 (#e8ddc5 / #d4c5a9) +- **cloud** - 云白色 (#f5f3ed / #2a2723) + +详细配置见 [src/config/theme.ts](src/config/theme.ts) + +--- + +## 技术栈 + +- **Vue 3** - Composition API +- **TypeScript** - 类型安全 +- **Tailwind CSS** - 原子化 CSS +- **shadcn/ui** - 基础组件库 +- **lucide-vue-next** - 图标库 +- **Vite** - 构建工具 + +--- + +## 主题系统 + +### 主题配置 + +主题配置位于 [src/config/theme.ts](src/config/theme.ts),包含: + +```ts +export const theme = { + colors: themeColors, // 颜色系统 + spacing, // 间距系统(基于 8px 网格) + radius, // 圆角系统 + shadows, // 阴影系统 + typography, // 字体系统 + animations, // 动画系统 + breakpoints, // 响应式断点 + zIndex, // 层级管理 + components: componentDefaults // 组件默认配置 +} +``` + +### CSS 变量 + +全局 CSS 变量定义在 `src/assets/index.css`,使用 HSL 色彩空间: + +```css +:root { + --background: 0 0% 100%; + --foreground: 20 14.3% 4.1%; + --primary: 15 55% 58%; + --border: 20 5.9% 90%; + --muted: 60 4.8% 95.9%; + --muted-foreground: 25 5.3% 44.7%; + /* ... 更多变量 */ +} + +.dark { + --background: 20 14.3% 4.1%; + --foreground: 0 0% 95%; + --primary: 15 45% 68%; + /* ... 暗色模式变量 */ +} +``` + +--- + +## 组件库 + +### 基础组件 (shadcn/ui) + +所有基础组件位于 [src/components/ui/](src/components/ui/): + +#### 布局组件 +- **Card** - 卡片容器 + - 变体:`default`、`outline`、`ghost`、`interactive` +- **Separator** - 分隔线(水平/垂直) +- **Tabs** - 选项卡容器 + +#### 表单组件 +- **Button** - 按钮 + - 变体:`default`、`destructive`、`outline`、`secondary`、`ghost`、`link` + - 大小:`sm`、`md`、`lg`、`icon` +- **Input** - 输入框 +- **Textarea** - 多行文本框 +- **Select** - 下拉选择框 +- **Checkbox** - 复选框 +- **Switch** - 开关 +- **Label** - 表单标签 + +#### 反馈组件 +- **Badge** - 徽章标签 +- **Skeleton** - 骨架屏 +- **Toast** - 消息提示 +- **Dialog** - 对话框/模态框 +- **Alert** - 警告提示 + +#### 数据展示 +- **Table** 系列 - 表格组件 + - Table、TableHeader、TableBody、TableRow、TableHead、TableCell +- **Avatar** - 头像 +- **Progress** - 进度条 + +--- + +### 布局组件 (Layout Components) + +位于 [src/components/layout/](src/components/layout/),所有组件支持从 `@/components/layout` 统一导入: + +```ts +import { PageHeader, PageContainer, Section, CardSection, Grid, StatCard } from '@/components/layout' +``` + +#### PageHeader + +页面头部组件,支持标题、描述、图标和操作按钮。 + +**使用示例:** + +```vue + + + +``` + +**Props:** +- `title: string` - 页面标题(必填) +- `description?: string` - 页面描述 +- `icon?: Component` - 图标组件 + +**Slots:** +- `icon` - 自定义图标区域 +- `actions` - 右侧操作按钮 + +--- + +#### PageContainer + +页面容器,提供响应式的最大宽度和内边距。 + +**使用示例:** + +```vue + +``` + +**Props:** +- `maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full'` - 最大宽度(默认: '2xl') +- `padding?: 'none' | 'sm' | 'md' | 'lg'` - 内边距(默认: 'md') + +--- + +#### Section + +区块容器,用于分隔页面不同区域。 + +**使用示例:** + +```vue + +``` + +**Props:** +- `title?: string` - 区块标题 +- `description?: string` - 区块描述 +- `spacing?: 'none' | 'sm' | 'md' | 'lg'` - 底部间距(默认: 'md') + +**Slots:** +- `header` - 自定义头部 +- `actions` - 右侧操作按钮 +- `default` - 主内容 + +--- + +#### CardSection + +卡片区块,基于 Card 组件的增强版。 + +**使用示例:** + +```vue + +``` + +**Props:** +- `title?: string` - 卡片标题 +- `description?: string` - 卡片描述 +- `variant?: 'default' | 'elevated' | 'glass'` - 卡片样式(默认: 'default') +- `padding?: 'none' | 'sm' | 'md' | 'lg'` - 内边距(默认: 'md') + +**Slots:** +- `header` - 自定义头部 +- `actions` - 头部右侧操作 +- `default` - 主内容 +- `footer` - 底部内容 + +--- + +#### Grid + +响应式网格布局。 + +**使用示例:** + +```vue + +``` + +--- + +#### StatCard + +统计卡片,用于展示关键指标。 + +**使用示例:** + +```vue + + + +``` + +--- + +#### ShellHeader (待废弃) + +旧版页面头部组件,建议迁移到 `PageHeader`。 + +--- + +### 业务组件 (Common Components) + +位于 [src/components/common/](src/components/common/): + +#### 1. PageLayout + +页面布局容器,集成标题、筛选、分页等功能。 + +**使用示例:** + +```vue + + + +``` + +**主要 Props:** + +| 属性 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `title` | `string` | - | 页面标题(必填) | +| `subtitle` | `string` | - | 页面副标题 | +| `showHeader` | `boolean` | `true` | 是否显示页面头部 | +| `showBackButton` | `boolean` | `false` | 是否显示返回按钮 | +| `maxWidth` | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'full'` | 内容最大宽度 | +| `spacing` | `'tight' \| 'normal' \| 'relaxed'` | `'normal'` | 内容间距 | +| `showFilters` | `boolean` | `false` | 是否显示筛选栏 | +| `showPagination` | `boolean` | `false` | 是否显示分页 | + +**主要 Slots:** + +- `toolbar` - 页面右上角工具栏 +- `headerExtra` - 头部额外内容 +- `filters` - 筛选条件 +- `filterLeft` / `filterRight` - 筛选栏左右插槽 +- `default` - 主内容区 +- `footer` - 页面底部 + +--- + +#### 2. DataTable + +响应式数据表格,桌面端显示表格,移动端自动切换为卡片视图。 + +**使用示例:** + +```vue + + + +``` + +**主要 Props:** + +| 属性 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `columns` | `DataTableColumn[]` | - | 列配置(必填) | +| `data` | `T[]` | - | 数据源(必填) | +| `rowKey` | `string` | `'id'` | 行唯一标识字段 | +| `loading` | `boolean` | `false` | 是否加载中 | +| `clickable` | `boolean` | `false` | 是否可点击行 | +| `emptyTitle` | `string` | `'暂无数据'` | 空状态标题 | + +**列配置(DataTableColumn):** + +```ts +interface DataTableColumn { + key: string // 列标识(对应数据字段) + label: string // 列标题 + width?: string // 列宽度 + align?: 'left' | 'center' | 'right' // 对齐方式 + sortable?: boolean // 是否可排序 + formatter?: (value: any, row: T, index: number) => string // 值格式化 + headerClass?: string // 表头样式类 + cellClass?: string // 单元格样式类 + showOnMobile?: boolean // 是否在移动端显示(默认 true) +} +``` + +**主要 Events:** + +- `rowClick(row, index)` - 行点击事件 +- `sort(sortBy, sortOrder)` - 排序事件 + +**主要 Slots:** + +- `cell-{key}` - 自定义单元格内容(接收 `{ row, column, index, value }` 参数) +- `mobile-card` - 自定义移动端卡片布局(接收 `{ row, index }` 参数) +- `empty` - 自定义空状态 +- `footer` - 表格底部内容 + +--- + +#### 3. SearchInput + +智能搜索输入框,支持防抖、清除、建议列表。 + +**使用示例:** + +```vue + + + +``` + +**主要 Props:** + +| 属性 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `modelValue` | `string` | - | 输入值(必填) | +| `placeholder` | `string` | `'搜索...'` | 占位符 | +| `clearable` | `boolean` | `true` | 是否显示清除按钮 | +| `loading` | `boolean` | `false` | 是否显示加载图标 | +| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 大小 | +| `suggestions` | `string[]` | `[]` | 搜索建议列表 | +| `debounce` | `number` | `300` | 防抖延迟(毫秒) | + +--- + +#### 4. FilterBar + +筛选栏容器,集成搜索和筛选条件。 + +**使用示例:** + +```vue + +``` + +--- + +#### 5. Pagination + +分页组件,支持页码导航和每页数量选择。 + +**使用示例:** + +```vue + + + +``` + +**主要 Props:** + +| 属性 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `currentPage` | `number` | `1` | 当前页码 | +| `pageSize` | `number` | `20` | 每页显示数量 | +| `total` | `number` | `0` | 总记录数 | +| `showPageSizeSelector` | `boolean` | `true` | 是否显示页面大小选择器 | +| `pageSizeOptions` | `number[]` | `[10, 20, 50, 100]` | 每页数量选项 | + +--- + +#### 6. EmptyState + +空状态组件,支持多种类型和自定义内容。 + +**使用示例:** + +```vue + + + +``` + +**类型(type):** + +- `default` - 默认空状态 +- `search` - 搜索无结果 +- `filter` - 筛选无结果 +- `error` - 加载错误 +- `empty` - 空空如也 +- `notFound` - 未找到资源 + +**主要 Props:** + +| 属性 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `type` | `EmptyStateType` | `'default'` | 空状态类型 | +| `title` | `string` | - | 标题(自动根据类型设置) | +| `description` | `string` | - | 描述(自动根据类型设置) | +| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 大小 | +| `actionText` | `string` | - | 操作按钮文本 | +| `actionIcon` | `Component` | - | 操作按钮图标 | +| `actionVariant` | `ButtonVariant` | `'default'` | 按钮变体 | + +--- + +#### 7. StatusBadge + +状态徽章组件,用于显示不同状态。 + +**使用示例:** + +```vue + +``` + +**状态类型(status):** + +| 状态 | 颜色 | 图标 | 用途 | +|------|------|------|------| +| `success` | 绿色 | CheckCircle2 | 成功、完成、已激活 | +| `error` | 红色 | XCircle | 错误、失败、已禁用 | +| `warning` | 黄色 | AlertCircle | 警告、待审核、需注意 | +| `info` | 蓝色 | Info | 信息、提示 | +| `pending` | 灰色 | Clock | 待处理、排队中 | +| `neutral` | 灰色 | Minus | 中性状态 | +| `active` | 主题色 | CheckCircle2 | 活跃、在线 | +| `inactive` | 灰色 | Minus | 未激活、离线 | + +**变体(variant):** + +- `solid` - 实心背景 +- `soft` - 柔和背景(默认) +- `outline` - 描边样式 + +--- + +#### 8. LoadingState + +加载状态组件,支持多种加载样式。 + +**使用示例:** + +```vue + +``` + +**变体(variant):** + +- `spinner` - 旋转加载器(默认) +- `skeleton` - 骨架屏 +- `pulse` - 脉冲点动画 + +--- + +#### 9. ConfirmButton + +带确认对话框的按钮组件,简化危险操作的确认流程。 + +**使用示例:** + +```vue + + + +``` + +**主要 Props:** + +| 属性 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `text` | `string` | - | 按钮文本 | +| `variant` | `ButtonVariant` | `'default'` | 按钮样式 | +| `size` | `ButtonSize` | `'md'` | 按钮大小 | +| `icon` | `Component` | - | 图标组件 | +| `disabled` | `boolean` | `false` | 是否禁用 | +| `confirmTitle` | `string` | `'确认操作'` | 确认对话框标题 | +| `confirmMessage` | `string` | `'确定要执行此操作吗?'` | 确认消息 | +| `confirmType` | `'default' \| 'danger' \| 'warning'` | `'default'` | 确认类型 | +| `requireConfirm` | `boolean` | `true` | 是否需要确认 | + +**Events:** +- `click` - 不需要确认时触发 +- `confirmed` - 确认后触发 +- `cancelled` - 取消确认时触发 + +--- + +#### 10. ActionMenu + +操作菜单下拉组件,用于集中展示多个操作选项。 + +**使用示例:** + +```vue + + + +``` + +**ActionMenuItem 接口:** + +```ts +interface ActionMenuItem { + label?: string // 菜单项标签 + icon?: Component // 图标 + badge?: string | number // 徽章 + variant?: 'default' | 'destructive' // 样式变体 + disabled?: boolean // 是否禁用 + separator?: boolean // 是否为分隔线 + onClick?: () => void | Promise // 点击回调 +} +``` + +**主要 Props:** + +| 属性 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `items` | `ActionMenuItem[]` | - | 菜单项列表(必填) | +| `triggerText` | `string` | - | 触发按钮文本 | +| `triggerIcon` | `Component` | - | 触发按钮图标 | +| `triggerVariant` | `ButtonVariant` | `'outline'` | 触发按钮样式 | +| `triggerSize` | `ButtonSize` | `'sm'` | 触发按钮大小 | +| `showChevron` | `boolean` | `true` | 是否显示下拉箭头 | +| `placement` | `'bottom-start' \| 'bottom-end' \| 'top-start' \| 'top-end'` | `'bottom-end'` | 菜单位置 | + +--- + +## 工具函数 (Composables) + +位于 [src/composables/](src/composables/) + +### useBreakpoints + +响应式断点检测,用于实现响应式布局。 + +```ts +import { useBreakpoints } from '@/composables/useBreakpoints' + +const { + windowWidth, // 窗口宽度 + isSm, // >= 640px + isMd, // >= 768px + isLg, // >= 1024px + isXl, // >= 1280px + is2Xl, // >= 1536px + current, // 当前断点 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' + isMobile, // < 768px + isTablet, // 768px ~ 1024px + isDesktop // >= 1024px +} = useBreakpoints() + +// 示例:根据屏幕大小显示不同内容 +
移动端视图
+
平板视图
+
桌面视图
+``` + +--- + +### useToast + +消息提示管理,统一的 Toast 通知接口。 + +```ts +import { useToast } from '@/composables/useToast' + +const { success, error, warning, info } = useToast() + +// 成功消息(5秒后自动消失) +success('操作成功') +success('数据保存成功', '提示') + +// 错误消息(8秒后自动消失) +error('操作失败') +error('保存失败,请检查网络连接', '错误') + +// 警告消息(8秒后自动消失) +warning('该操作可能影响其他数据', '警告') + +// 信息消息(5秒后自动消失) +info('系统将在 5 分钟后进行维护', '系统通知') +``` + +**接口定义:** + +```ts +interface UseToast { + toasts: Ref + success(message: string, title?: string): string + error(message: string, title?: string): string + warning(message: string, title?: string): string + info(message: string, title?: string): string + showToast(options: Omit): string + removeToast(id: string): void + clearAll(): void +} + +interface Toast { + id: string + title?: string + message?: string + variant?: 'success' | 'error' | 'warning' | 'info' + duration?: number +} +``` + +--- + +### useConfirm + +确认对话框,用于危险操作确认。 + +```ts +import { useConfirm } from '@/composables/useConfirm' + +const { confirm, confirmDanger, confirmWarning } = useConfirm() + +// 普通确认 +const ok = await confirm('确定要删除吗?', '确认删除') +if (ok) { + await deleteItem() +} + +// 危险操作确认(红色按钮) +const ok = await confirmDanger( + '此操作不可撤销,确定继续吗?', + '删除确认' +) + +// 警告确认(黄色主题) +const ok = await confirmWarning( + '该操作可能影响其他用户,是否继续?', + '警告' +) +``` + +--- + +### useClasses + +类名工具函数,简化条件类名的生成。 + +```ts +import { useClasses } from '@/composables/useClasses' + +const { cn, conditional, fromObject } = useClasses() + +// 合并类名(过滤 falsy 值) +const className = cn( + 'base-class', + isActive && 'active', + error && 'error', + 'another-class' +) +// 结果: 'base-class active another-class' (假设 isActive=true, error=false) + +// 条件类名 +const className = conditional(isActive, 'bg-primary', 'bg-muted') +// 结果: isActive ? 'bg-primary' : 'bg-muted' + +// 从对象生成类名 +const className = fromObject({ + 'text-red-500': hasError, + 'font-bold': isImportant, + 'underline': isLink +}) +// 结果: 只包含值为 true 的键 +``` + +--- + +## 最佳实践 + +### 1. 组件开发规范 + +#### 使用 TypeScript + +```vue + +``` + +#### 遵循命名规范 + +- **组件文件**: PascalCase (如 `UserCard.vue`、`DataTable.vue`) +- **Composables**: camelCase + `use` 前缀 (如 `useAuth.ts`、`useBreakpoints.ts`) +- **工具函数**: camelCase (如 `formatDate.ts`、`validateEmail.ts`) +- **常量**: SCREAMING_SNAKE_CASE (如 `API_BASE_URL`、`MAX_FILE_SIZE`) + +#### 合理使用插槽 + +```vue + +``` + +#### 统一错误处理 + +```ts +import { useToast } from '@/composables/useToast' +import { apiClient } from '@/api/client' + +const { error: showError, success: showSuccess } = useToast() + +async function saveData() { + try { + await apiClient.post('/users', userData) + showSuccess('用户创建成功') + } catch (err: any) { + const message = err.response?.data?.detail || err.message || '操作失败' + showError(message, '错误') + console.error('Failed to create user:', err) + } +} +``` + +--- + +### 2. 样式规范 + +#### 优先使用 Tailwind 类 + +```vue + +``` + +#### 使用主题 CSS 变量 + +```vue + + + +``` + +#### 响应式设计 + +```vue + + + +``` + +--- + +### 3. 性能优化 + +#### 按需导入组件 + +```ts +import { defineAsyncComponent } from 'vue' + +// 异步加载重型组件 +const HeavyChart = defineAsyncComponent(() => + import('./components/HeavyChart.vue') +) + +// 带加载状态的异步组件 +const HeavyTable = defineAsyncComponent({ + loader: () => import('./components/HeavyTable.vue'), + loadingComponent: LoadingState, + delay: 200, + errorComponent: ErrorState, + timeout: 3000 +}) +``` + +#### 使用 v-memo 优化列表 + +```vue + +``` + +#### 虚拟滚动 + +对于超过 100 条的列表,使用虚拟滚动: + +```bash +npm install vue-virtual-scroller +``` + +```vue + + + +``` + +--- + +### 4. 可访问性 (a11y) + +#### 语义化 HTML + +```vue + +``` + +#### 键盘导航 + +```vue + +``` + +#### ARIA 属性 + +```vue + +``` + +--- + +## 组件迁移检查清单 + +### 已完全迁移到 shadcn 的页面 + +- [x] Dashboard.vue +- [x] Users.vue +- [x] Settings.vue +- [x] SystemSettings.vue +- [x] Profile.vue +- [x] ActivityLogs.vue +- [x] Announcements.vue +- [x] ApiKeys.vue +- [x] AuditLogs.vue +- [x] MyApiKeys.vue +- [x] Usage.vue +- [x] ProviderList.vue +- [x] MyProviders.vue +- [x] CacheMonitoring.vue +- [x] ProviderDetailNew.vue + +### 部分迁移或自定义样式 + +- [ ] Home.vue - 使用大量自定义动画和样式(不建议迁移) + +--- + +## 更新日志 + +### v2.3.0 (2025-11-18) + +**新增组件:** +- `ConfirmButton` - 带确认对话框的按钮组件 +- `ActionMenu` - 操作菜单下拉组件 + +**优化导入系统:** +- 创建 `@/components/ui/index.ts` 统一导出所有 shadcn UI 组件 +- 完善 `@/components/layout/index.ts` 和 `@/components/common/index.ts` +- 支持更简洁的组件导入方式 + +**导入方式优化:** + +```ts +// 旧版导入 (繁琐) +import Button from '@/components/ui/button.vue' +import Input from '@/components/ui/input.vue' +import Card from '@/components/ui/card.vue' + +// 新版导入 (推荐) +import { Button, Input, Card } from '@/components/ui' +import { PageHeader, Section, CardSection } from '@/components/layout' +import { DataTable, ConfirmButton, ActionMenu } from '@/components/common' +``` + +**文档:** +- 添加 `ConfirmButton` 和 `ActionMenu` 组件完整文档 +- 更新组件导入最佳实践 + +### v2.2.0 (2025-11-18) + +**重构:** +- 统一布局组件目录: 将 `layout-v2` 合并到 `layout` +- 所有布局组件现在从 `@/components/layout` 统一导入 +- 删除冗余的 `layout-v2` 目录 + +**文档:** +- 添加完整的布局组件文档和使用示例 +- 标记 `ShellHeader` 为待废弃组件 + +**迁移指南:** +```ts +// 旧版导入 (已废弃) +import { PageHeader } from '@/components/layout-v2' + +// 新版导入 (推荐) +import { PageHeader } from '@/components/layout' +``` + +### v2.1.0 (2025-11-18) + +**新增:** +- 完善所有组件文档和使用示例 +- 添加完整的 TypeScript 类型定义 +- 统一 Toast 工具函数接口(useToast) +- 完善响应式支持(useBreakpoints) + +**修复:** +- 修复 CacheMonitoring.vue 的 toast 调用 +- 统一所有页面的组件使用 + +**文档:** +- 更新所有组件的使用示例 +- 添加最佳实践章节 +- 添加性能优化指南 +- 添加可访问性指南 + +### v2.0.0 (2025-11-17) + +- 基础设计系统搭建 +- 实现所有核心业务组件 +- 建立主题系统 + +--- + +## 参考资源 + +- [shadcn/ui 官方文档](https://ui.shadcn.com/) +- [Tailwind CSS 文档](https://tailwindcss.com/docs) +- [Vue 3 文档](https://vuejs.org/) +- [Lucide Icons](https://lucide.dev/) +- [WCAG 2.1 标准](https://www.w3.org/WAI/WCAG21/quickref/) +- [主题配置文件](src/config/theme.ts) diff --git a/frontend/components.json b/frontend/components.json new file mode 100644 index 0000000..674ce1f --- /dev/null +++ b/frontend/components.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "typescript": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/style.css", + "baseColor": "slate", + "cssVariables": true + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui" + } +} \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..456e506 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,22 @@ + + + + + + + + Aether + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..06bd399 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,6613 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@types/dompurify": "^3.0.5", + "@types/marked": "^5.0.2", + "@types/three": "^0.180.0", + "@vueuse/core": "^13.9.0", + "axios": "^1.12.1", + "chart.js": "^4.5.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "dompurify": "^3.3.0", + "highlight.js": "^11.11.1", + "lucide-vue-next": "^0.544.0", + "marked": "^16.0.0", + "pinia": "^3.0.3", + "radix-vue": "^1.9.17", + "tailwind-merge": "^3.3.1", + "three": "^0.180.0", + "vue": "^3.5.18", + "vue-chartjs": "^5.3.2", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@types/node": "^24.3.3", + "@typescript-eslint/eslint-plugin": "^8.47.0", + "@typescript-eslint/parser": "^8.47.0", + "@vitejs/plugin-vue": "^6.0.1", + "@vitest/ui": "^4.0.10", + "@vue/tsconfig": "^0.7.0", + "autoprefixer": "^10.4.21", + "baseline-browser-mapping": "^2.9.4", + "eslint": "^9.39.1", + "eslint-plugin-vue": "^10.5.1", + "jsdom": "^27.2.0", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.17", + "tailwindcss-animate": "^1.0.7", + "typescript": "~5.8.3", + "vite": "^7.1.2", + "vitest": "^4.0.10", + "vue-tsc": "^3.0.5" + } + }, + "node_modules/@acemir/cssom": { + "version": "0.9.23", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.23.tgz", + "integrity": "sha512-2kJ1HxBKzPLbmhZpxBiTZggjtgCwKg1ma5RHShxvd6zgqhDEdEkzpiwe7jLkI2p2BrZvFCXIihdoMkl1H39VnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz", + "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "lru-cache": "^11.2.1" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.7.4", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz", + "integrity": "sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.2" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.16.tgz", + "integrity": "sha512-2SpS4/UaWQaGpBINyG5ZuCHnUDeVByOhvbkARwfmnfxDvTaj80yOI1cD8Tw93ICV5Fx4fnyDKWQZI1CDtcWyUg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", + "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", + "license": "Apache-2.0" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@floating-ui/vue": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.9.tgz", + "integrity": "sha512-BfNqNW6KA83Nexspgb9DZuz578R7HT8MZw1CfK9I6Ah4QReNWEJsXWHN+SdmOVLNGmTPDi+fDT535Df5PzMLbQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4", + "@floating-ui/utils": "^0.2.10", + "vue-demi": ">=0.13.0" + } + }, + "node_modules/@floating-ui/vue/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@internationalized/date": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.9.0.tgz", + "integrity": "sha512-yaN3brAnHRD+4KyyOsJyk49XUvj2wtbNACSqg0bz3u8t2VuzhC8Q5dfRnrSxjnnbDb+ienBnkn1TzQfE154vyg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.5.tgz", + "integrity": "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.29", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz", + "integrity": "sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.1.tgz", + "integrity": "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.1.tgz", + "integrity": "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.1.tgz", + "integrity": "sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.1.tgz", + "integrity": "sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.1.tgz", + "integrity": "sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.1.tgz", + "integrity": "sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.1.tgz", + "integrity": "sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.1.tgz", + "integrity": "sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.1.tgz", + "integrity": "sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.1.tgz", + "integrity": "sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.1.tgz", + "integrity": "sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.1.tgz", + "integrity": "sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.1.tgz", + "integrity": "sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.1.tgz", + "integrity": "sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.1.tgz", + "integrity": "sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.1.tgz", + "integrity": "sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.1.tgz", + "integrity": "sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.1.tgz", + "integrity": "sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.1.tgz", + "integrity": "sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.1.tgz", + "integrity": "sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.1.tgz", + "integrity": "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.12", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz", + "integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/vue-virtual": { + "version": "3.13.12", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.12.tgz", + "integrity": "sha512-vhF7kEU9EXWXh+HdAwKJ2m3xaOnTTmgcdXcF2pim8g4GvI7eRrk2YRuV5nUlZnd/NbCIX4/Ja2OZu5EjJL06Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.13.12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.0.0" + } + }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "license": "MIT", + "dependencies": { + "@types/trusted-types": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/marked": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.2.tgz", + "integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.3.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.3.tgz", + "integrity": "sha512-GKBNHjoNw3Kra1Qg5UXttsY5kiWMEfoHq2TmXb+b1rcm6N7B3wTrFYIf/oSZ1xNQ+hVVijgLkiDZh7jRRsh+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.10.0" + } + }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.180.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.180.0.tgz", + "integrity": "sha512-ykFtgCqNnY0IPvDro7h+9ZeLY+qjgUWv+qEvUt84grhenO60Hqd4hScHE7VTB9nOQ/3QM8lkbNE+4vKjEpUxKg==", + "license": "MIT", + "dependencies": { + "@dimforge/rapier3d-compat": "~0.12.0", + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": "*", + "@webgpu/types": "*", + "fflate": "~0.8.2", + "meshoptimizer": "~0.22.0" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz", + "integrity": "sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.47.0", + "@typescript-eslint/type-utils": "8.47.0", + "@typescript-eslint/utils": "8.47.0", + "@typescript-eslint/visitor-keys": "8.47.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.47.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.47.0.tgz", + "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.47.0", + "@typescript-eslint/types": "8.47.0", + "@typescript-eslint/typescript-estree": "8.47.0", + "@typescript-eslint/visitor-keys": "8.47.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.47.0.tgz", + "integrity": "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.47.0", + "@typescript-eslint/types": "^8.47.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.47.0.tgz", + "integrity": "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.47.0", + "@typescript-eslint/visitor-keys": "8.47.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.47.0.tgz", + "integrity": "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.47.0.tgz", + "integrity": "sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.47.0", + "@typescript-eslint/typescript-estree": "8.47.0", + "@typescript-eslint/utils": "8.47.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.47.0.tgz", + "integrity": "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.47.0.tgz", + "integrity": "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.47.0", + "@typescript-eslint/tsconfig-utils": "8.47.0", + "@typescript-eslint/types": "8.47.0", + "@typescript-eslint/visitor-keys": "8.47.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.47.0.tgz", + "integrity": "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.47.0", + "@typescript-eslint/types": "8.47.0", + "@typescript-eslint/typescript-estree": "8.47.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.47.0.tgz", + "integrity": "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.47.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz", + "integrity": "sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.29" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.10.tgz", + "integrity": "sha512-3QkTX/lK39FBNwARCQRSQr0TP9+ywSdxSX+LgbJ2M1WmveXP72anTbnp2yl5fH+dU6SUmBzNMrDHs80G8G2DZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.10", + "@vitest/utils": "4.0.10", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.10.tgz", + "integrity": "sha512-e2OfdexYkjkg8Hh3L9NVEfbwGXq5IZbDovkf30qW2tOh7Rh9sVtmSr2ztEXOFbymNxS4qjzLXUQIvATvN4B+lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.10.tgz", + "integrity": "sha512-99EQbpa/zuDnvVjthwz5bH9o8iPefoQZ63WV8+bsRJZNw3qQSvSltfut8yu1Jc9mqOYi7pEbsKxYTi/rjaq6PA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.10.tgz", + "integrity": "sha512-EXU2iSkKvNwtlL8L8doCpkyclw0mc/t4t9SeOnfOFPyqLmQwuceMPA4zJBa6jw0MKsZYbw7kAn+gl7HxrlB8UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.10.tgz", + "integrity": "sha512-2N4X2ZZl7kZw0qeGdQ41H0KND96L3qX1RgwuCfy6oUsF2ISGD/HpSbmms+CkIOsQmg2kulwfhJ4CI0asnZlvkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.10.tgz", + "integrity": "sha512-AsY6sVS8OLb96GV5RoG8B6I35GAbNrC49AO+jNRF9YVGb/g9t+hzNm1H6kD0NDp8tt7VJLs6hb7YMkDXqu03iw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/ui": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.10.tgz", + "integrity": "sha512-oWtNM89Np+YsQO3ttT5i1Aer/0xbzQzp66NzuJn/U16bB7MnvSzdLKXgk1kkMLYyKSSzA2ajzqMkYheaE9opuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.10", + "fflate": "^0.8.2", + "flatted": "^3.3.3", + "pathe": "^2.0.3", + "sirv": "^3.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.0.10" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.10.tgz", + "integrity": "sha512-kOuqWnEwZNtQxMKg3WmPK1vmhZu9WcoX69iwWjVz+jvKTsF1emzsv3eoPcDr6ykA3qP2bsCQE7CwqfNtAVzsmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.10", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.23.tgz", + "integrity": "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.23" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.23.tgz", + "integrity": "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.23.tgz", + "integrity": "sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.23", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.21.tgz", + "integrity": "sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@vue/shared": "3.5.21", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.21.tgz", + "integrity": "sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.21", + "@vue/shared": "3.5.21" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.21.tgz", + "integrity": "sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@vue/compiler-core": "3.5.21", + "@vue/compiler-dom": "3.5.21", + "@vue/compiler-ssr": "3.5.21", + "@vue/shared": "3.5.21", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.18", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.21.tgz", + "integrity": "sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.21", + "@vue/shared": "3.5.21" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz", + "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.7" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz", + "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.7", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz", + "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.0.7.tgz", + "integrity": "sha512-0sqqyqJ0Gn33JH3TdIsZLCZZ8Gr4kwlg8iYOnOrDDkJKSjFurlQY/bEFQx5zs7SX2C/bjMkmPYq/NiyY1fTOkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.23", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^2.0.5", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.2" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.21.tgz", + "integrity": "sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.21" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.21.tgz", + "integrity": "sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.21", + "@vue/shared": "3.5.21" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.21.tgz", + "integrity": "sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.21", + "@vue/runtime-core": "3.5.21", + "@vue/shared": "3.5.21", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.21.tgz", + "integrity": "sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.21", + "@vue/shared": "3.5.21" + }, + "peerDependencies": { + "vue": "3.5.21" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.21.tgz", + "integrity": "sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.7.0.tgz", + "integrity": "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "5.x", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/@vueuse/core": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.9.0.tgz", + "integrity": "sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "13.9.0", + "@vueuse/shared": "13.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.9.0.tgz", + "integrity": "sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.9.0.tgz", + "integrity": "sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@webgpu/types": { + "version": "0.1.66", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.66.tgz", + "integrity": "sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA==", + "license": "BSD-3-Clause" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/alien-signals": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-2.0.7.tgz", + "integrity": "sha512-wE7y3jmYeb0+h6mr5BOovuqhFv22O/MV9j5p0ndJsa7z1zJNPGQ4ph5pQk/kTTCWRC3xsA4SmtwmkzQO+7NCNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axios": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.1.tgz", + "integrity": "sha512-Kn4kbSXpkFHCGE6rBFNwIv0GQs4AvDT80jlveJDKFxjbTYMUeB4QtsdPCv6H8Cm19Je7IU6VFtRl2zWZI0rudQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.4.tgz", + "integrity": "sha512-ZCQ9GEWl73BVm8bu5Fts8nt7MHdbt5vY9bP6WGnUh+r3l8M7CgfyTlwsgCbMC66BNxPr6Xoce3j66Ms5YUQTNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/birpc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.5.0.tgz", + "integrity": "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.0.tgz", + "integrity": "sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.2", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001741", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", + "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz", + "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chart.js": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", + "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.3.tgz", + "integrity": "sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.0.3", + "@csstools/css-syntax-patches-for-csstree": "^1.0.14", + "css-tree": "^3.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", + "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dompurify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", + "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.218", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.218.tgz", + "integrity": "sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-vue": { + "version": "10.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.5.1.tgz", + "integrity": "sha512-SbR9ZBUFKgvWAbq3RrdCtWaW0IKm6wwUiApxf3BVTNfqUIo4IQQmreMg2iHFJJ6C/0wss3LXURBJ1OwS/MhFcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", + "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "vue-eslint-parser": "^10.0.0" + }, + "peerDependenciesMeta": { + "@stylistic/eslint-plugin": { + "optional": true + }, + "@typescript-eslint/parser": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-vue/node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.2.0.tgz", + "integrity": "sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.23", + "@asamuzakjp/dom-selector": "^6.7.4", + "cssstyle": "^5.3.3", + "data-urls": "^6.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "dev": true, + "license": "MPL-2.0", + "optional": true, + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/lucide-vue-next": { + "version": "0.544.0", + "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.544.0.tgz", + "integrity": "sha512-mDp/AdGOPIDkpFHnFiTWgQgCST9aBXHVaiobZfOMIvv7nrOukzF/TP+7KoOwrngdWRaH9TMiepMBIX1vsgKJ3g==", + "license": "ISC", + "peerDependencies": { + "vue": ">=3.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/meshoptimizer": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.22.0.tgz", + "integrity": "sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg==", + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinia": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz", + "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/radix-vue": { + "version": "1.9.17", + "resolved": "https://registry.npmjs.org/radix-vue/-/radix-vue-1.9.17.tgz", + "integrity": "sha512-mVCu7I2vXt1L2IUYHTt0sZMz7s1K2ZtqKeTIxG3yC5mMFfLBG4FtE1FDeRMpDd+Hhg/ybi9+iXmAP1ISREndoQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.7", + "@floating-ui/vue": "^1.1.0", + "@internationalized/date": "^3.5.4", + "@internationalized/number": "^3.5.3", + "@tanstack/vue-virtual": "^3.8.1", + "@vueuse/core": "^10.11.0", + "@vueuse/shared": "^10.11.0", + "aria-hidden": "^1.2.4", + "defu": "^6.1.4", + "fast-deep-equal": "^3.1.3", + "nanoid": "^5.0.7" + }, + "peerDependencies": { + "vue": ">= 3.2.0" + } + }, + "node_modules/radix-vue/node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/radix-vue/node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/radix-vue/node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/radix-vue/node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/radix-vue/node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/radix-vue/node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/radix-vue/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.1.tgz", + "integrity": "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.50.1", + "@rollup/rollup-android-arm64": "4.50.1", + "@rollup/rollup-darwin-arm64": "4.50.1", + "@rollup/rollup-darwin-x64": "4.50.1", + "@rollup/rollup-freebsd-arm64": "4.50.1", + "@rollup/rollup-freebsd-x64": "4.50.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.50.1", + "@rollup/rollup-linux-arm-musleabihf": "4.50.1", + "@rollup/rollup-linux-arm64-gnu": "4.50.1", + "@rollup/rollup-linux-arm64-musl": "4.50.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.50.1", + "@rollup/rollup-linux-ppc64-gnu": "4.50.1", + "@rollup/rollup-linux-riscv64-gnu": "4.50.1", + "@rollup/rollup-linux-riscv64-musl": "4.50.1", + "@rollup/rollup-linux-s390x-gnu": "4.50.1", + "@rollup/rollup-linux-x64-gnu": "4.50.1", + "@rollup/rollup-linux-x64-musl": "4.50.1", + "@rollup/rollup-openharmony-arm64": "4.50.1", + "@rollup/rollup-win32-arm64-msvc": "4.50.1", + "@rollup/rollup-win32-ia32-msvc": "4.50.1", + "@rollup/rollup-win32-x64-msvc": "4.50.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/superjson": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwind-merge": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", + "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/tailwindcss/node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/three": { + "version": "0.180.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.180.0.tgz", + "integrity": "sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==", + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.18", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.18.tgz", + "integrity": "sha512-lCcgTAgMxQ1JKOWrVGo6E69Ukbnx4Gc1wiYLRf6J5NN4HRYJtCby1rPF8rkQ4a6qqoFBK5dvjJ1zJ0F7VfDSvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.18" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.18", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.18.tgz", + "integrity": "sha512-jqJC13oP4FFAahv4JT/0WTDrCF9Okv7lpKtOZUGPLiAnNbACcSg8Y8T+Z9xthOmRBqi/Sob4yi0TE0miRCvF7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", + "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.10.tgz", + "integrity": "sha512-2Fqty3MM9CDwOVet/jaQalYlbcjATZwPYGcqpiYQqgQ/dLC7GuHdISKgTYIVF/kaishKxLzleKWWfbSDklyIKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.10", + "@vitest/mocker": "4.0.10", + "@vitest/pretty-format": "4.0.10", + "@vitest/runner": "4.0.10", + "@vitest/snapshot": "4.0.10", + "@vitest/spy": "4.0.10", + "@vitest/utils": "4.0.10", + "debug": "^4.4.3", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.10", + "@vitest/browser-preview": "4.0.10", + "@vitest/browser-webdriverio": "4.0.10", + "@vitest/ui": "4.0.10", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.21.tgz", + "integrity": "sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.21", + "@vue/compiler-sfc": "3.5.21", + "@vue/runtime-dom": "3.5.21", + "@vue/server-renderer": "3.5.21", + "@vue/shared": "3.5.21" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-chartjs": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.2.tgz", + "integrity": "sha512-NrkbRRoYshbXbWqJkTN6InoDVwVb90C0R7eAVgMWcB9dPikbruaOoTFjFYHE/+tNPdIe6qdLCDjfjPHQ0fw4jw==", + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.1", + "vue": "^3.0.0-0 || ^2.7.0" + } + }, + "node_modules/vue-eslint-parser": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.2.0.tgz", + "integrity": "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.6.0", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-router": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz", + "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-tsc": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.0.7.tgz", + "integrity": "sha512-BSMmW8GGEgHykrv7mRk6zfTdK+tw4MBZY/x6fFa7IkdXK3s/8hQRacPjG9/8YKFDIWGhBocwi6PlkQQ/93OgIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.23", + "@vue/language-core": "3.0.7" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", + "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..85fd474 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,58 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "build:with-typecheck": "vue-tsc -b && vite build", + "preview": "vite preview", + "test": "vitest", + "test:ui": "vitest --ui", + "test:run": "vitest run", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .eslintignore", + "type-check": "vue-tsc --noEmit" + }, + "dependencies": { + "@types/dompurify": "^3.0.5", + "@types/marked": "^5.0.2", + "@types/three": "^0.180.0", + "@vueuse/core": "^13.9.0", + "axios": "^1.12.1", + "chart.js": "^4.5.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "dompurify": "^3.3.0", + "highlight.js": "^11.11.1", + "lucide-vue-next": "^0.544.0", + "marked": "^16.0.0", + "pinia": "^3.0.3", + "radix-vue": "^1.9.17", + "tailwind-merge": "^3.3.1", + "three": "^0.180.0", + "vue": "^3.5.18", + "vue-chartjs": "^5.3.2", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@types/node": "^24.3.3", + "@typescript-eslint/eslint-plugin": "^8.47.0", + "@typescript-eslint/parser": "^8.47.0", + "@vitejs/plugin-vue": "^6.0.1", + "@vitest/ui": "^4.0.10", + "@vue/tsconfig": "^0.7.0", + "autoprefixer": "^10.4.21", + "baseline-browser-mapping": "^2.9.4", + "eslint": "^9.39.1", + "eslint-plugin-vue": "^10.5.1", + "jsdom": "^27.2.0", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.17", + "tailwindcss-animate": "^1.0.7", + "typescript": "~5.8.3", + "vite": "^7.1.2", + "vitest": "^4.0.10", + "vue-tsc": "^3.0.5" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..e99ebc2 --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/frontend/public/aether.svg b/frontend/public/aether.svg new file mode 100644 index 0000000..c8c9ea5 --- /dev/null +++ b/frontend/public/aether.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/aether_adaptive.svg b/frontend/public/aether_adaptive.svg new file mode 100644 index 0000000..299b419 --- /dev/null +++ b/frontend/public/aether_adaptive.svg @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/frontend/public/claude-color.svg b/frontend/public/claude-color.svg new file mode 100644 index 0000000..62dc0db --- /dev/null +++ b/frontend/public/claude-color.svg @@ -0,0 +1 @@ +Claude \ No newline at end of file diff --git a/frontend/public/fonts/StyreneA/StyreneA-Bold.otf b/frontend/public/fonts/StyreneA/StyreneA-Bold.otf new file mode 100644 index 0000000..68a795c Binary files /dev/null and b/frontend/public/fonts/StyreneA/StyreneA-Bold.otf differ diff --git a/frontend/public/fonts/StyreneA/StyreneA-Bold.woff2 b/frontend/public/fonts/StyreneA/StyreneA-Bold.woff2 new file mode 100644 index 0000000..b124de4 Binary files /dev/null and b/frontend/public/fonts/StyreneA/StyreneA-Bold.woff2 differ diff --git a/frontend/public/fonts/StyreneA/StyreneA-Medium.otf b/frontend/public/fonts/StyreneA/StyreneA-Medium.otf new file mode 100644 index 0000000..6e4323a Binary files /dev/null and b/frontend/public/fonts/StyreneA/StyreneA-Medium.otf differ diff --git a/frontend/public/fonts/StyreneA/StyreneA-Medium.woff2 b/frontend/public/fonts/StyreneA/StyreneA-Medium.woff2 new file mode 100644 index 0000000..19280d6 Binary files /dev/null and b/frontend/public/fonts/StyreneA/StyreneA-Medium.woff2 differ diff --git a/frontend/public/fonts/StyreneA/StyreneA-Regular.otf b/frontend/public/fonts/StyreneA/StyreneA-Regular.otf new file mode 100644 index 0000000..819f15d Binary files /dev/null and b/frontend/public/fonts/StyreneA/StyreneA-Regular.otf differ diff --git a/frontend/public/fonts/StyreneA/StyreneA-Regular.woff2 b/frontend/public/fonts/StyreneA/StyreneA-Regular.woff2 new file mode 100644 index 0000000..e686dd7 Binary files /dev/null and b/frontend/public/fonts/StyreneA/StyreneA-Regular.woff2 differ diff --git a/frontend/public/fonts/TiemposText/TiemposText-Medium.otf b/frontend/public/fonts/TiemposText/TiemposText-Medium.otf new file mode 100644 index 0000000..12a1950 Binary files /dev/null and b/frontend/public/fonts/TiemposText/TiemposText-Medium.otf differ diff --git a/frontend/public/fonts/TiemposText/TiemposText-Medium.woff2 b/frontend/public/fonts/TiemposText/TiemposText-Medium.woff2 new file mode 100644 index 0000000..950295b Binary files /dev/null and b/frontend/public/fonts/TiemposText/TiemposText-Medium.woff2 differ diff --git a/frontend/public/fonts/TiemposText/TiemposText-Regular.otf b/frontend/public/fonts/TiemposText/TiemposText-Regular.otf new file mode 100644 index 0000000..52f3c72 Binary files /dev/null and b/frontend/public/fonts/TiemposText/TiemposText-Regular.otf differ diff --git a/frontend/public/fonts/TiemposText/TiemposText-Regular.woff2 b/frontend/public/fonts/TiemposText/TiemposText-Regular.woff2 new file mode 100644 index 0000000..0d94d73 Binary files /dev/null and b/frontend/public/fonts/TiemposText/TiemposText-Regular.woff2 differ diff --git a/frontend/public/fonts/TiemposText/TiemposText-Semibold.otf b/frontend/public/fonts/TiemposText/TiemposText-Semibold.otf new file mode 100644 index 0000000..c7504f5 Binary files /dev/null and b/frontend/public/fonts/TiemposText/TiemposText-Semibold.otf differ diff --git a/frontend/public/fonts/TiemposText/TiemposText-Semibold.woff2 b/frontend/public/fonts/TiemposText/TiemposText-Semibold.woff2 new file mode 100644 index 0000000..2d48c34 Binary files /dev/null and b/frontend/public/fonts/TiemposText/TiemposText-Semibold.woff2 differ diff --git a/frontend/public/gemini-color.svg b/frontend/public/gemini-color.svg new file mode 100644 index 0000000..f1cf357 --- /dev/null +++ b/frontend/public/gemini-color.svg @@ -0,0 +1 @@ +Gemini \ No newline at end of file diff --git a/frontend/public/openai.svg b/frontend/public/openai.svg new file mode 100644 index 0000000..3de59a7 --- /dev/null +++ b/frontend/public/openai.svg @@ -0,0 +1 @@ +OpenAI diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..3987bb2 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,100 @@ + + + diff --git a/frontend/src/api/admin.ts b/frontend/src/api/admin.ts new file mode 100644 index 0000000..8d0da28 --- /dev/null +++ b/frontend/src/api/admin.ts @@ -0,0 +1,177 @@ +import apiClient from './client' + +// API密钥管理相关接口定义 +export interface AdminApiKey { + id: string // UUID + user_id: string // UUID + user_email?: string + username?: string + name?: string + key_display?: string // 脱敏后的密钥显示 + is_active: boolean + is_standalone: boolean // 是否为独立余额Key + balance_used_usd?: number // 已使用余额(仅独立Key) + current_balance_usd?: number | null // 当前余额(独立Key预付费模式,null表示无限制) + total_requests?: number + total_tokens?: number + total_cost_usd?: number + rate_limit?: number + allowed_providers?: string[] | null // 允许的提供商列表 + allowed_api_formats?: string[] | null // 允许的 API 格式列表 + allowed_models?: string[] | null // 允许的模型列表 + auto_delete_on_expiry?: boolean // 过期后是否自动删除 + last_used_at?: string + expires_at?: string + created_at: string + updated_at?: string +} + +export interface CreateStandaloneApiKeyRequest { + name?: string + allowed_providers?: string[] | null + allowed_api_formats?: string[] | null + allowed_models?: string[] | null + rate_limit?: number + expire_days?: number | null // null = 永不过期 + initial_balance_usd: number // 初始余额,必须设置 + auto_delete_on_expiry?: boolean // 过期后是否自动删除 +} + +export interface AdminApiKeysResponse { + api_keys: AdminApiKey[] + total: number + limit: number + skip: number +} + +export interface ApiKeyToggleResponse { + id: string // UUID + is_active: boolean + message: string +} + +// 管理员API密钥管理相关API +export const adminApi = { + // 获取所有独立余额Keys列表 + async getAllApiKeys(params?: { + skip?: number + limit?: number + is_active?: boolean + }): Promise { + const response = await apiClient.get('/api/admin/api-keys', { + params: params + }) + return response.data + }, + + // 创建独立余额Key + async createStandaloneApiKey(data: CreateStandaloneApiKeyRequest): Promise { + const response = await apiClient.post( + '/api/admin/api-keys', + data + ) + return response.data + }, + + // 更新独立余额Key + async updateApiKey(keyId: string, data: Partial): Promise { + const response = await apiClient.put( + `/api/admin/api-keys/${keyId}`, + data + ) + return response.data + }, + + // 切换API密钥状态(启用/禁用) + async toggleApiKey(keyId: string): Promise { + const response = await apiClient.patch( + `/api/admin/api-keys/${keyId}` + ) + return response.data + }, + + // 删除API密钥 + async deleteApiKey(keyId: string): Promise<{ message: string }> { + const response = await apiClient.delete<{ message: string}>( + `/api/admin/api-keys/${keyId}` + ) + return response.data + }, + + // 为独立余额Key调整余额 + async addApiKeyBalance(keyId: string, amountUsd: number): Promise { + const response = await apiClient.patch( + `/api/admin/api-keys/${keyId}/balance`, + { amount_usd: amountUsd } + ) + return response.data + }, + + // 获取API密钥详情(可选包含完整密钥) + async getApiKeyDetail(keyId: string, includeKey: boolean = false): Promise { + const response = await apiClient.get( + `/api/admin/api-keys/${keyId}`, + { params: { include_key: includeKey } } + ) + return response.data + }, + + // 获取完整的API密钥(用于复制)- 便捷方法 + async getFullApiKey(keyId: string): Promise<{ key: string }> { + const response = await apiClient.get<{ key: string }>( + `/api/admin/api-keys/${keyId}`, + { params: { include_key: true } } + ) + return response.data + }, + + // 系统配置相关 + // 获取所有系统配置 + async getAllSystemConfigs(): Promise { + const response = await apiClient.get('/api/admin/system/configs') + return response.data + }, + + // 获取特定系统配置 + async getSystemConfig(key: string): Promise<{ key: string; value: any }> { + const response = await apiClient.get<{ key: string; value: any }>( + `/api/admin/system/configs/${key}` + ) + return response.data + }, + + // 更新系统配置 + async updateSystemConfig( + key: string, + value: any, + description?: string + ): Promise<{ key: string; value: any; description?: string }> { + const response = await apiClient.put<{ key: string; value: any; description?: string }>( + `/api/admin/system/configs/${key}`, + { value, description } + ) + return response.data + }, + + // 删除系统配置 + async deleteSystemConfig(key: string): Promise<{ message: string }> { + const response = await apiClient.delete<{ message: string }>( + `/api/admin/system/configs/${key}` + ) + return response.data + }, + + // 获取系统统计 + async getSystemStats(): Promise { + const response = await apiClient.get('/api/admin/system/stats') + return response.data + }, + + // 获取可用的API格式列表 + async getApiFormats(): Promise<{ formats: Array<{ value: string; label: string; default_path: string; aliases: string[] }> }> { + const response = await apiClient.get<{ formats: Array<{ value: string; label: string; default_path: string; aliases: string[] }> }>( + '/api/admin/system/api-formats' + ) + return response.data + } +} diff --git a/frontend/src/api/announcements.ts b/frontend/src/api/announcements.ts new file mode 100644 index 0000000..ff8b6ff --- /dev/null +++ b/frontend/src/api/announcements.ts @@ -0,0 +1,109 @@ +import apiClient from './client' + +export interface Announcement { + id: string // UUID + title: string + content: string // Markdown格式 + type: 'info' | 'warning' | 'maintenance' | 'important' + priority: number + is_pinned: boolean + is_active: boolean + author: { + id: string // UUID + username: string + } + start_time?: string + end_time?: string + created_at: string + updated_at: string + is_read?: boolean +} + +export interface AnnouncementListResponse { + items: Announcement[] + total: number + unread_count: number +} + +export interface CreateAnnouncementRequest { + title: string + content: string + type?: 'info' | 'warning' | 'maintenance' | 'important' + priority?: number + is_pinned?: boolean + start_time?: string + end_time?: string +} + +export interface UpdateAnnouncementRequest { + title?: string + content?: string + type?: string + priority?: number + is_active?: boolean + is_pinned?: boolean + start_time?: string + end_time?: string +} + +export const announcementApi = { + // 获取公告列表 + async getAnnouncements(params?: { + active_only?: boolean + unread_only?: boolean + limit?: number + offset?: number + }): Promise { + const response = await apiClient.get('/api/announcements', { params }) + return response.data + }, + + // 获取当前有效的公告 + async getActiveAnnouncements(): Promise { + const response = await apiClient.get('/api/announcements/active') + return response.data + }, + + // 获取单个公告 + async getAnnouncement(id: string): Promise { + const response = await apiClient.get(`/api/announcements/${id}`) + return response.data + }, + + // 标记公告为已读 + async markAsRead(id: string): Promise<{ message: string }> { + const response = await apiClient.patch(`/api/announcements/${id}/read-status`) + return response.data + }, + + // 标记所有公告为已读 + async markAllAsRead(): Promise<{ message: string }> { + const response = await apiClient.post('/api/announcements/read-all') + return response.data + }, + + // 获取未读公告数量 + async getUnreadCount(): Promise<{ unread_count: number }> { + const response = await apiClient.get('/api/announcements/users/me/unread-count') + return response.data + }, + + // 管理员方法 + // 创建公告 + async createAnnouncement(data: CreateAnnouncementRequest): Promise<{ id: string; title: string; message: string }> { + const response = await apiClient.post('/api/announcements', data) + return response.data + }, + + // 更新公告 + async updateAnnouncement(id: string, data: UpdateAnnouncementRequest): Promise<{ message: string }> { + const response = await apiClient.put(`/api/announcements/${id}`, data) + return response.data + }, + + // 删除公告 + async deleteAnnouncement(id: string): Promise<{ message: string }> { + const response = await apiClient.delete(`/api/announcements/${id}`) + return response.data + } +} \ No newline at end of file diff --git a/frontend/src/api/audit.ts b/frontend/src/api/audit.ts new file mode 100644 index 0000000..b310da7 --- /dev/null +++ b/frontend/src/api/audit.ts @@ -0,0 +1,91 @@ +import apiClient from './client' + +export interface AuditLog { + id: string + event_type: string + user_id?: number + description: string + ip_address?: string + status_code?: number + error_message?: string + metadata?: any + created_at: string +} + +export interface PaginationMeta { + total: number + limit: number + offset: number + count: number +} + +export interface AuditLogsResponse { + items: AuditLog[] + meta: PaginationMeta + filters?: Record +} + +export interface AuditFilters { + user_id?: string + event_type?: string + days?: number + limit?: number + offset?: number +} + +function normalizeAuditResponse(data: any): AuditLogsResponse { + const items: AuditLog[] = data.items ?? data.logs ?? [] + const meta: PaginationMeta = data.meta ?? { + total: data.total ?? items.length, + limit: data.limit ?? items.length, + offset: data.offset ?? 0, + count: data.count ?? items.length + } + + return { + items, + meta, + filters: data.filters + } +} + +export const auditApi = { + // 获取当前用户的活动日志 + async getMyAuditLogs(filters?: { + event_type?: string + days?: number + limit?: number + offset?: number + }): Promise { + const response = await apiClient.get('/api/monitoring/my-audit-logs', { params: filters }) + return normalizeAuditResponse(response.data) + }, + + // 获取所有审计日志 (管理员) + async getAuditLogs(filters?: AuditFilters): Promise { + const response = await apiClient.get('/api/admin/monitoring/audit-logs', { params: filters }) + return normalizeAuditResponse(response.data) + }, + + // 获取可疑活动 (管理员) + async getSuspiciousActivities(hours: number = 24, limit: number = 100): Promise<{ + activities: AuditLog[] + count: number + }> { + const response = await apiClient.get('/api/admin/monitoring/suspicious-activities', { + params: { hours, limit } + }) + return response.data + }, + + // 分析用户行为 (管理员) + async analyzeUserBehavior(userId: number, days: number = 7): Promise<{ + analysis: any + recommendations: string[] + }> { + const response = await apiClient.get(`/api/admin/monitoring/user-behavior/${userId}`, { + params: { days } + }) + return response.data + } +} diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts new file mode 100644 index 0000000..f7cd6ee --- /dev/null +++ b/frontend/src/api/auth.ts @@ -0,0 +1,90 @@ +import apiClient from './client' + +export interface LoginRequest { + email: string + password: string +} + +export interface LoginResponse { + access_token: string + refresh_token?: string + token_type?: string + expires_in?: number + user_id?: string // UUID + email?: string + username?: string + role?: string +} + +export interface UserPreferences { + theme?: 'light' | 'dark' | 'auto' + language?: string + notifications_enabled?: boolean + [key: string]: unknown // 允许扩展其他偏好设置 +} + +export interface UserStats { + total_requests?: number + total_cost?: number + last_request_at?: string + [key: string]: unknown // 允许扩展其他统计数据 +} + +export interface User { + id: string // UUID + username: string + email?: string + role: string // 'admin' or 'user' + is_active: boolean + quota_usd?: number | null + used_usd?: number + total_usd?: number + allowed_providers?: string[] | null // 允许使用的提供商 ID 列表 + allowed_endpoints?: string[] | null // 允许使用的端点 ID 列表 + allowed_models?: string[] | null // 允许使用的模型名称列表 + created_at: string + last_login_at?: string + preferences?: UserPreferences + stats?: UserStats +} + +export const authApi = { + async login(credentials: LoginRequest): Promise { + const response = await apiClient.post('/api/auth/login', credentials) + apiClient.setToken(response.data.access_token) + // 后端暂时没有返回 refresh_token + if (response.data.refresh_token) { + localStorage.setItem('refresh_token', response.data.refresh_token) + } + return response.data + }, + + async logout() { + try { + // 调用后端登出接口,将 Token 加入黑名单 + await apiClient.post('/api/auth/logout', {}) + } catch (error) { + // 即使后端登出失败,也要清除本地认证信息 + console.warn('后端登出失败,仅清除本地认证信息:', error) + } finally { + // 清除本地认证信息 + apiClient.clearAuth() + } + }, + + async getCurrentUser(): Promise { + const response = await apiClient.get('/api/users/me') + return response.data + }, + + async refreshToken(refreshToken: string): Promise { + const response = await apiClient.post('/api/auth/refresh', { + refresh_token: refreshToken + }) + apiClient.setToken(response.data.access_token) + if (response.data.refresh_token) { + localStorage.setItem('refresh_token', response.data.refresh_token) + } + return response.data + } +} diff --git a/frontend/src/api/cache.ts b/frontend/src/api/cache.ts new file mode 100644 index 0000000..e0bf159 --- /dev/null +++ b/frontend/src/api/cache.ts @@ -0,0 +1,158 @@ +/** + * 缓存监控 API 客户端 + */ + +import api from './client' + +export interface CacheStats { + scheduler: string + cache_reservation_ratio: number + affinity_stats: { + storage_type: string + total_affinities: number + active_affinities: number | string + cache_hits: number + cache_misses: number + cache_hit_rate: number + cache_invalidations: number + provider_switches: number + key_switches: number + config: { + default_ttl: number + } + } +} + +export interface DynamicReservationConfig { + probe_phase_requests: number + probe_reservation: number + stable_min_reservation: number + stable_max_reservation: number + low_load_threshold: number + high_load_threshold: number + success_count_for_full_confidence: number + cooldown_hours_for_full_confidence: number +} + +export interface CacheConfig { + cache_ttl_seconds: number + cache_reservation_ratio: number + dynamic_reservation?: { + enabled: boolean + config: DynamicReservationConfig + description: Record + } + description: { + cache_ttl: string + cache_reservation_ratio: string + dynamic_reservation?: string + } +} + +export interface UserAffinity { + affinity_key: string + user_api_key_name: string | null + user_api_key_prefix: string | null // 用户 API Key 脱敏显示(前4...后4) + is_standalone: boolean + user_id: string | null + username: string | null + email: string | null + provider_id: string + provider_name: string | null + endpoint_id: string + endpoint_api_format: string | null + endpoint_url: string | null + key_id: string + key_name: string | null + key_prefix: string | null // Provider Key 脱敏显示(前4...后4) + rate_multiplier: number + model_name: string | null // 模型名称(如 claude-haiku-4-5-20250514) + model_display_name: string | null // 模型显示名称(如 Claude Haiku 4.5) + api_format: string | null // API 格式 (claude/openai) + created_at: number + expire_at: number + request_count: number +} + +export interface AffinityListResponse { + items: UserAffinity[] + total: number + matched_user_id?: string | null +} + +export const cacheApi = { + /** + * 获取缓存统计信息 + */ + async getStats(): Promise { + const response = await api.get('/api/admin/monitoring/cache/stats') + return response.data.data + }, + + /** + * 获取缓存配置 + */ + async getConfig(): Promise { + const response = await api.get('/api/admin/monitoring/cache/config') + return response.data.data + }, + + /** + * 查询用户缓存亲和性(现在返回该用户所有端点的亲和性列表) + * + * @param userIdentifier 用户标识符,支持:用户名、邮箱、User UUID、API Key ID + */ + async getUserAffinity(userIdentifier: string): Promise { + const response = await api.get(`/api/admin/monitoring/cache/affinity/${userIdentifier}`) + if (response.data.status === 'not_found') { + return null + } + return response.data.affinities + }, + + /** + * 清除用户缓存 + * + * @param userIdentifier 用户标识符,支持:用户名、邮箱、User UUID、API Key ID + */ + async clearUserCache(userIdentifier: string): Promise { + await api.delete(`/api/admin/monitoring/cache/users/${userIdentifier}`) + }, + + /** + * 清除所有缓存 + */ + async clearAllCache(): Promise<{ count: number }> { + const response = await api.delete('/api/admin/monitoring/cache') + return response.data + }, + + /** + * 清除指定Provider的所有缓存 + */ + async clearProviderCache(providerId: string): Promise<{ count: number; provider_id: string }> { + const response = await api.delete(`/api/admin/monitoring/cache/providers/${providerId}`) + return response.data + }, + + /** + * 获取缓存亲和性列表 + */ + async listAffinities(keyword?: string): Promise { + const response = await api.get('/api/admin/monitoring/cache/affinities', { + params: keyword ? { keyword } : undefined + }) + return response.data.data + } +} + +// 导出便捷函数 +export const { + getStats, + getConfig, + getUserAffinity, + clearUserCache, + clearAllCache, + clearProviderCache, + listAffinities +} = cacheApi diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts new file mode 100644 index 0000000..d2b6414 --- /dev/null +++ b/frontend/src/api/client.ts @@ -0,0 +1,254 @@ +import axios from 'axios' +import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' +import { NETWORK_CONFIG, AUTH_CONFIG } from '@/config/constants' +import { log } from '@/utils/logger' + +// 在开发环境下使用代理,生产环境使用环境变量 +const API_BASE_URL = import.meta.env.VITE_API_URL || '' + +/** + * 判断请求是否为公共端点 + */ +function isPublicEndpoint(url?: string, method?: string): boolean { + if (!url) return false + + const isHealthCheck = url.includes('/health') && + method?.toLowerCase() === 'get' && + !url.includes('/api/admin') + + return url.includes('/public') || + url.includes('.json') || + isHealthCheck +} + +/** + * 判断是否为认证相关请求 + */ +function isAuthRequest(url?: string): boolean { + return url?.includes('/auth/login') || url?.includes('/auth/refresh') || url?.includes('/auth/logout') || false +} + +/** + * 判断是否为可刷新的认证错误 + */ +function isRefreshableAuthError(errorDetail: string): boolean { + const nonRefreshableErrors = [ + '用户不存在或已禁用', + '需要管理员权限', + '权限不足', + '用户已禁用', + ] + + return !nonRefreshableErrors.some((msg) => errorDetail.includes(msg)) +} + +class ApiClient { + private client: AxiosInstance + private token: string | null = null + private isRefreshing = false + private refreshPromise: Promise | null = null + + constructor() { + this.client = axios.create({ + baseURL: API_BASE_URL, + timeout: NETWORK_CONFIG.API_TIMEOUT, + headers: { + 'Content-Type': 'application/json', + }, + }) + + this.setupInterceptors() + } + + /** + * 配置请求和响应拦截器 + */ + private setupInterceptors(): void { + // 请求拦截器 + this.client.interceptors.request.use( + (config) => { + const requiresAuth = !isPublicEndpoint(config.url, config.method) && + config.url?.includes('/api/') + + if (requiresAuth) { + const token = this.getToken() + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + } + return config + }, + (error) => Promise.reject(error) + ) + + // 响应拦截器 + this.client.interceptors.response.use( + (response) => response, + async (error) => this.handleResponseError(error) + ) + } + + /** + * 处理响应错误 + */ + private async handleResponseError(error: any): Promise { + const originalRequest = error.config + + // 请求被取消 + if (axios.isCancel(error)) { + return Promise.reject(error) + } + + // 网络错误或服务器不可达 + if (!error.response) { + log.warn('Network error or server unreachable', error.message) + return Promise.reject(error) + } + + // 认证请求错误,直接返回 + if (isAuthRequest(originalRequest?.url)) { + return Promise.reject(error) + } + + // 处理401错误 + if (error.response?.status === 401) { + return this.handle401Error(error, originalRequest) + } + + return Promise.reject(error) + } + + /** + * 处理401认证错误 + */ + private async handle401Error(error: any, originalRequest: any): Promise { + // 如果不需要认证,直接返回错误 + if (isPublicEndpoint(originalRequest?.url, originalRequest?.method)) { + return Promise.reject(error) + } + + // 如果已经重试过,不再重试 + if (originalRequest._retry) { + return Promise.reject(error) + } + + const errorDetail = error.response?.data?.detail || '' + log.debug('Got 401 error, attempting token refresh', { errorDetail }) + + // 检查是否为业务相关的401错误 + if (!isRefreshableAuthError(errorDetail)) { + log.info('401 error but not authentication issue, keeping session', { errorDetail }) + return Promise.reject(error) + } + + // 获取refresh token + const refreshToken = localStorage.getItem('refresh_token') + if (!refreshToken) { + log.info('No refresh token available, clearing invalid token') + this.clearAuth() + return Promise.reject(error) + } + + // 标记为已重试 + originalRequest._retry = true + originalRequest._retryCount = (originalRequest._retryCount || 0) + 1 + + // 超过最大重试次数 + if (originalRequest._retryCount > AUTH_CONFIG.MAX_RETRY_COUNT) { + log.error('Max retry attempts reached') + return Promise.reject(error) + } + + // 如果正在刷新,等待刷新完成 + if (this.isRefreshing) { + try { + await this.refreshPromise + originalRequest.headers.Authorization = `Bearer ${this.getToken()}` + return this.client.request(originalRequest) + } catch (refreshError) { + return Promise.reject(error) + } + } + + // 开始刷新token + return this.refreshTokenAndRetry(refreshToken, originalRequest, error) + } + + /** + * 刷新token并重试原始请求 + */ + private async refreshTokenAndRetry( + refreshToken: string, + originalRequest: any, + originalError: any + ): Promise { + this.isRefreshing = true + this.refreshPromise = this.refreshToken(refreshToken) + + try { + const response = await this.refreshPromise + this.setToken(response.data.access_token) + localStorage.setItem('refresh_token', response.data.refresh_token) + this.isRefreshing = false + this.refreshPromise = null + + // 重试原始请求 + originalRequest.headers.Authorization = `Bearer ${response.data.access_token}` + return this.client.request(originalRequest) + } catch (refreshError: any) { + log.error('Token refresh failed', refreshError) + this.isRefreshing = false + this.refreshPromise = null + this.clearAuth() + return Promise.reject(originalError) + } + } + + setToken(token: string): void { + this.token = token + localStorage.setItem('access_token', token) + } + + getToken(): string | null { + if (!this.token) { + this.token = localStorage.getItem('access_token') + } + return this.token + } + + clearAuth(): void { + this.token = null + localStorage.removeItem('access_token') + localStorage.removeItem('refresh_token') + } + + async refreshToken(refreshToken: string): Promise { + return this.client.post('/api/auth/refresh', { refresh_token: refreshToken }) + } + + async request(config: AxiosRequestConfig): Promise> { + return this.client.request(config) + } + + async get(url: string, config?: AxiosRequestConfig): Promise> { + return this.client.get(url, config) + } + + async post(url: string, data?: any, config?: AxiosRequestConfig): Promise> { + return this.client.post(url, data, config) + } + + async put(url: string, data?: any, config?: AxiosRequestConfig): Promise> { + return this.client.put(url, data, config) + } + + async patch(url: string, data?: any, config?: AxiosRequestConfig): Promise> { + return this.client.patch(url, data, config) + } + + async delete(url: string, config?: AxiosRequestConfig): Promise> { + return this.client.delete(url, config) + } +} + +export default new ApiClient() diff --git a/frontend/src/api/dashboard.ts b/frontend/src/api/dashboard.ts new file mode 100644 index 0000000..f4f3ed7 --- /dev/null +++ b/frontend/src/api/dashboard.ts @@ -0,0 +1,262 @@ +import apiClient from './client' + +export interface DashboardStat { + name: string + value: string + subValue?: string + change?: string + changeType?: 'increase' | 'decrease' | 'neutral' + extraBadge?: string + icon: string +} + +export interface RecentRequest { + id: string // UUID + user: string + model: string + tokens: number + time: string +} + +export interface ProviderStatus { + name: string + status: 'active' | 'inactive' + requests: number +} + +// 系统健康指标(管理员专用) +export interface SystemHealth { + avg_response_time: number + error_rate: number + error_requests: number + fallback_count: number + total_requests: number +} + +// 成本统计(管理员专用) +export interface CostStats { + total_cost: number + total_actual_cost: number + cost_savings: number +} + +// 缓存统计 +export interface CacheStats { + cache_creation_tokens: number + cache_read_tokens: number + cache_creation_cost?: number + cache_read_cost?: number + cache_hit_rate?: number + total_cache_tokens: number +} + +// 用户统计(管理员专用) +export interface UserStats { + total: number + active: number +} + +// Token 详细分类 +export interface TokenBreakdown { + input: number + output: number + cache_creation: number + cache_read: number +} + +export interface DashboardStatsResponse { + stats: DashboardStat[] + today?: { + requests: number + tokens: number + cost: number + actual_cost?: number + cache_creation_tokens?: number + cache_read_tokens?: number + } + api_keys?: { + total: number + active: number + } + tokens?: { + month: number + } + // 管理员专用字段 + system_health?: SystemHealth + cost_stats?: CostStats + cache_stats?: CacheStats + users?: UserStats + token_breakdown?: TokenBreakdown +} + +export interface RecentRequestsResponse { + requests: RecentRequest[] +} + +export interface ProviderStatusResponse { + providers: ProviderStatus[] +} + +export interface RequestDetail { + id: string // UUID + request_id: string + user: { + id: string // UUID + username: string + email: string + } + api_key: { + id: string // UUID + name: string + display: string + } + provider: string + api_format?: string + model: string + target_model?: string | null // 映射后的目标模型名 + tokens: { + input: number + output: number + total: number + } + cost: { + input: number + output: number + total: number + } + // Additional token fields + input_tokens?: number + output_tokens?: number + total_tokens?: number + cache_creation_input_tokens?: number + cache_read_input_tokens?: number + // Additional cost fields + input_cost?: number + output_cost?: number + total_cost?: number + cache_creation_cost?: number + cache_read_cost?: number + request_cost?: number // 按次计费费用 + // Historical pricing fields (per 1M tokens) + input_price_per_1m?: number + output_price_per_1m?: number + cache_creation_price_per_1m?: number + cache_read_price_per_1m?: number + price_per_request?: number // 按次计费价格 + request_type: string + is_stream: boolean + status_code: number + error_message?: string + response_time_ms: number + created_at: string + request_headers?: Record + request_body?: Record + provider_request_headers?: Record + response_headers?: Record + response_body?: Record + metadata?: Record + // 阶梯计费信息 + tiered_pricing?: { + total_input_context: number // 总输入上下文 (input + cache_read) + tier_index: number // 命中的阶梯索引 (0-based) + tier_count: number // 阶梯总数 + source?: 'provider' | 'global' // 定价来源: 提供商或全局 + current_tier: { // 当前命中的阶梯配置 + up_to?: number | null + input_price_per_1m: number + output_price_per_1m: number + cache_creation_price_per_1m?: number + cache_read_price_per_1m?: number + cache_ttl_pricing?: Array<{ + ttl_minutes: number + cache_read_price_per_1m: number + }> + } + tiers: Array<{ // 完整阶梯配置列表 + up_to?: number | null + input_price_per_1m: number + output_price_per_1m: number + cache_creation_price_per_1m?: number + cache_read_price_per_1m?: number + cache_ttl_pricing?: Array<{ + ttl_minutes: number + cache_read_price_per_1m: number + }> + }> + } | null +} + +export interface ModelBreakdown { + model: string + requests: number + tokens: number + cost: number +} + +export interface ModelSummary { + model: string + requests: number + tokens: number + cost: number + avg_response_time: number + cost_per_request: number + tokens_per_request: number +} + +export interface DailyStat { + date: string // ISO date string + requests: number + tokens: number + cost: number + avg_response_time: number // in seconds + unique_models: number + unique_providers: number + model_breakdown: ModelBreakdown[] +} + +export interface DailyStatsResponse { + daily_stats: DailyStat[] + model_summary: ModelSummary[] + period: { + start_date: string + end_date: string + days: number + } +} + +export const dashboardApi = { + // 获取仪表盘统计数据 + async getStats(): Promise { + const response = await apiClient.get('/api/dashboard/stats') + return response.data + }, + + // 获取最近的请求记录 + async getRecentRequests(limit: number = 10): Promise { + const response = await apiClient.get('/api/dashboard/recent-requests', { + params: { limit } + }) + return response.data.requests + }, + + // 获取提供商状态 + async getProviderStatus(): Promise { + const response = await apiClient.get('/api/dashboard/provider-status') + return response.data.providers + }, + + // 获取请求详情 + // NOTE: This method now calls the new RESTful API at /api/admin/usage/{id} + async getRequestDetail(requestId: string): Promise { + const response = await apiClient.get(`/api/admin/usage/${requestId}`) + return response.data + }, + + // 获取每日统计数据 + async getDailyStats(days: number = 7): Promise { + const response = await apiClient.get('/api/dashboard/daily-stats', { + params: { days } + }) + return response.data + } +} \ No newline at end of file diff --git a/frontend/src/api/endpoints/adaptive.ts b/frontend/src/api/endpoints/adaptive.ts new file mode 100644 index 0000000..f881ded --- /dev/null +++ b/frontend/src/api/endpoints/adaptive.ts @@ -0,0 +1,57 @@ +import client from '../client' +import type { AdaptiveStatsResponse } from './types' + +/** + * 启用/禁用 Key 的自适应模式 + */ +export async function toggleAdaptiveMode( + keyId: string, + data: { + enabled: boolean + fixed_limit?: number + } +): Promise<{ + message: string + key_id: string + is_adaptive: boolean + max_concurrent: number | null + effective_limit: number | null +}> { + const response = await client.patch(`/api/admin/adaptive/keys/${keyId}/mode`, data) + return response.data +} + +/** + * 设置 Key 的固定并发限制 + */ +export async function setConcurrentLimit( + keyId: string, + limit: number +): Promise<{ + message: string + key_id: string + is_adaptive: boolean + max_concurrent: number + previous_mode: string +}> { + const response = await client.patch(`/api/admin/adaptive/keys/${keyId}/limit`, null, { + params: { limit } + }) + return response.data +} + +/** + * 获取 Key 的自适应统计 + */ +export async function getAdaptiveStats(keyId: string): Promise { + const response = await client.get(`/api/admin/adaptive/keys/${keyId}/stats`) + return response.data +} + +/** + * 重置 Key 的学习状态 + */ +export async function resetAdaptiveLearning(keyId: string): Promise<{ message: string; key_id: string }> { + const response = await client.delete(`/api/admin/adaptive/keys/${keyId}/learning`) + return response.data +} diff --git a/frontend/src/api/endpoints/aliases.ts b/frontend/src/api/endpoints/aliases.ts new file mode 100644 index 0000000..bb3c076 --- /dev/null +++ b/frontend/src/api/endpoints/aliases.ts @@ -0,0 +1,121 @@ +/** + * 模型别名管理 API + */ + +import client from '../client' +import type { ModelMapping, ModelMappingCreate, ModelMappingUpdate } from './types' + +export interface ModelAlias { + id: string + alias: string + global_model_id: string + global_model_name: string | null + global_model_display_name: string | null + provider_id: string | null + provider_name: string | null + scope: 'global' | 'provider' + mapping_type: 'alias' | 'mapping' + is_active: boolean + created_at: string + updated_at: string +} + +export interface CreateModelAliasRequest { + alias: string + global_model_id: string + provider_id?: string | null + mapping_type?: 'alias' | 'mapping' + is_active?: boolean +} + +export interface UpdateModelAliasRequest { + alias?: string + global_model_id?: string + provider_id?: string | null + mapping_type?: 'alias' | 'mapping' + is_active?: boolean +} + +function transformMapping(mapping: ModelMapping): ModelAlias { + return { + id: mapping.id, + alias: mapping.source_model, + global_model_id: mapping.target_global_model_id, + global_model_name: mapping.target_global_model_name, + global_model_display_name: mapping.target_global_model_display_name, + provider_id: mapping.provider_id ?? null, + provider_name: mapping.provider_name ?? null, + scope: mapping.scope, + mapping_type: mapping.mapping_type || 'alias', + is_active: mapping.is_active, + created_at: mapping.created_at, + updated_at: mapping.updated_at + } +} + +/** + * 获取别名列表 + */ +export async function getAliases(params?: { + provider_id?: string + global_model_id?: string + is_active?: boolean + skip?: number + limit?: number +}): Promise { + const response = await client.get('/api/admin/models/mappings', { + params: { + provider_id: params?.provider_id, + target_global_model_id: params?.global_model_id, + is_active: params?.is_active, + skip: params?.skip, + limit: params?.limit + } + }) + return (response.data as ModelMapping[]).map(transformMapping) +} + +/** + * 获取单个别名 + */ +export async function getAlias(id: string): Promise { + const response = await client.get(`/api/admin/models/mappings/${id}`) + return transformMapping(response.data) +} + +/** + * 创建别名 + */ +export async function createAlias(data: CreateModelAliasRequest): Promise { + const payload: ModelMappingCreate = { + source_model: data.alias, + target_global_model_id: data.global_model_id, + provider_id: data.provider_id ?? null, + mapping_type: data.mapping_type ?? 'alias', + is_active: data.is_active ?? true + } + const response = await client.post('/api/admin/models/mappings', payload) + return transformMapping(response.data) +} + +/** + * 更新别名 + */ +export async function updateAlias(id: string, data: UpdateModelAliasRequest): Promise { + const payload: ModelMappingUpdate = { + source_model: data.alias, + target_global_model_id: data.global_model_id, + provider_id: data.provider_id ?? null, + mapping_type: data.mapping_type, + is_active: data.is_active + } + const response = await client.patch(`/api/admin/models/mappings/${id}`, payload) + return transformMapping(response.data) +} + +/** + * 删除别名 + */ +export async function deleteAlias(id: string): Promise { + await client.delete(`/api/admin/models/mappings/${id}`) +} diff --git a/frontend/src/api/endpoints/endpoints.ts b/frontend/src/api/endpoints/endpoints.ts new file mode 100644 index 0000000..642048f --- /dev/null +++ b/frontend/src/api/endpoints/endpoints.ts @@ -0,0 +1,78 @@ +import client from '../client' +import type { ProviderEndpoint } from './types' + +/** + * 获取指定 Provider 的所有 Endpoints + */ +export async function getProviderEndpoints(providerId: string): Promise { + const response = await client.get(`/api/admin/endpoints/providers/${providerId}/endpoints`) + return response.data +} + +/** + * 获取 Endpoint 详情 + */ +export async function getEndpoint(endpointId: string): Promise { + const response = await client.get(`/api/admin/endpoints/${endpointId}`) + return response.data +} + +/** + * 为 Provider 创建新的 Endpoint + */ +export async function createEndpoint( + providerId: string, + data: { + provider_id: string + api_format: string + base_url: string + custom_path?: string + auth_type?: string + auth_header?: string + headers?: Record + timeout?: number + max_retries?: number + priority?: number + weight?: number + max_concurrent?: number + rate_limit?: number + is_active?: boolean + config?: Record + } +): Promise { + const response = await client.post(`/api/admin/endpoints/providers/${providerId}/endpoints`, data) + return response.data +} + +/** + * 更新 Endpoint + */ +export async function updateEndpoint( + endpointId: string, + data: Partial<{ + base_url: string + custom_path: string + auth_type: string + auth_header: string + headers: Record + timeout: number + max_retries: number + priority: number + weight: number + max_concurrent: number + rate_limit: number + is_active: boolean + config: Record + }> +): Promise { + const response = await client.put(`/api/admin/endpoints/${endpointId}`, data) + return response.data +} + +/** + * 删除 Endpoint + */ +export async function deleteEndpoint(endpointId: string): Promise<{ message: string; deleted_keys_count: number }> { + const response = await client.delete(`/api/admin/endpoints/${endpointId}`) + return response.data +} diff --git a/frontend/src/api/endpoints/global-models.ts b/frontend/src/api/endpoints/global-models.ts new file mode 100644 index 0000000..9ba0f57 --- /dev/null +++ b/frontend/src/api/endpoints/global-models.ts @@ -0,0 +1,85 @@ +import client from '../client' +import type { + GlobalModelCreate, + GlobalModelUpdate, + GlobalModelResponse, + GlobalModelWithStats, + GlobalModelListResponse +} from './types' + +/** + * 获取 GlobalModel 列表 + */ +export async function getGlobalModels(params?: { + skip?: number + limit?: number + is_active?: boolean + search?: string +}): Promise { + const response = await client.get('/api/admin/models/global', { params }) + return response.data +} + +/** + * 获取单个 GlobalModel 详情 + */ +export async function getGlobalModel(id: string): Promise { + const response = await client.get(`/api/admin/models/global/${id}`) + return response.data +} + +/** + * 创建 GlobalModel + */ +export async function createGlobalModel(data: GlobalModelCreate): Promise { + const response = await client.post('/api/admin/models/global', data) + return response.data +} + +/** + * 更新 GlobalModel + */ +export async function updateGlobalModel( + id: string, + data: GlobalModelUpdate +): Promise { + const response = await client.patch(`/api/admin/models/global/${id}`, data) + return response.data +} + +/** + * 删除 GlobalModel + */ +export async function deleteGlobalModel( + id: string, + force: boolean = false +): Promise { + await client.delete(`/api/admin/models/global/${id}`, { params: { force } }) +} + +/** + * 批量为 GlobalModel 添加关联提供商 + */ +export async function batchAssignToProviders( + globalModelId: string, + data: { + provider_ids: string[] + create_models: boolean + } +): Promise<{ + success: Array<{ + provider_id: string + provider_name: string + model_id?: string + }> + errors: Array<{ + provider_id: string + error: string + }> +}> { + const response = await client.post( + `/api/admin/models/global/${globalModelId}/assign-to-providers`, + data + ) + return response.data +} diff --git a/frontend/src/api/endpoints/health.ts b/frontend/src/api/endpoints/health.ts new file mode 100644 index 0000000..b2c4c0d --- /dev/null +++ b/frontend/src/api/endpoints/health.ts @@ -0,0 +1,88 @@ +import client from '../client' +import type { + HealthStatus, + HealthSummary, + EndpointStatusMonitorResponse, + PublicEndpointStatusMonitorResponse +} from './types' + +/** + * 获取健康状态摘要 + */ +export async function getHealthSummary(): Promise { + const response = await client.get('/api/admin/endpoints/health/summary') + return response.data +} + +/** + * 获取 Endpoint 健康状态 + */ +export async function getEndpointHealth(endpointId: string): Promise { + const response = await client.get(`/api/admin/endpoints/health/endpoint/${endpointId}`) + return response.data +} + +/** + * 获取 Key 健康状态 + */ +export async function getKeyHealth(keyId: string): Promise { + const response = await client.get(`/api/admin/endpoints/health/key/${keyId}`) + return response.data +} + +/** + * 恢复Key健康状态(一键恢复:重置健康度 + 关闭熔断器 + 取消自动禁用) + */ +export async function recoverKeyHealth(keyId: string): Promise<{ + message: string + details: { + health_score: number + circuit_breaker_open: boolean + is_active: boolean + } +}> { + const response = await client.patch(`/api/admin/endpoints/health/keys/${keyId}`) + return response.data +} + +/** + * 批量恢复所有熔断的Key健康状态 + */ +export async function recoverAllKeysHealth(): Promise<{ + message: string + recovered_count: number + recovered_keys: Array<{ + key_id: string + key_name: string + endpoint_id: string + }> +}> { + const response = await client.patch('/api/admin/endpoints/health/keys') + return response.data +} + +/** + * 获取按 API 格式聚合的健康监控时间线(管理员版,含 provider/key 数量) + */ +export async function getEndpointStatusMonitor(params?: { + lookback_hours?: number + per_format_limit?: number +}): Promise { + const response = await client.get('/api/admin/endpoints/health/api-formats', { + params + }) + return response.data +} + +/** + * 获取按 API 格式聚合的健康监控时间线(公开版,不含敏感信息) + */ +export async function getPublicEndpointStatusMonitor(params?: { + lookback_hours?: number + per_format_limit?: number +}): Promise { + const response = await client.get('/api/public/health/api-formats', { + params + }) + return response.data +} diff --git a/frontend/src/api/endpoints/index.ts b/frontend/src/api/endpoints/index.ts new file mode 100644 index 0000000..73e60af --- /dev/null +++ b/frontend/src/api/endpoints/index.ts @@ -0,0 +1,9 @@ +export * from './types' +export * from './providers' +export * from './endpoints' +export * from './keys' +export * from './health' +export * from './models' +export * from './aliases' +export * from './adaptive' +export * from './global-models' diff --git a/frontend/src/api/endpoints/keys.ts b/frontend/src/api/endpoints/keys.ts new file mode 100644 index 0000000..4dcd3a3 --- /dev/null +++ b/frontend/src/api/endpoints/keys.ts @@ -0,0 +1,132 @@ +import client from '../client' +import type { EndpointAPIKey } from './types' + +/** + * 能力定义类型 + */ +export interface CapabilityDefinition { + name: string + display_name: string + description: string + match_mode: 'exclusive' | 'compatible' + config_mode?: 'user_configurable' | 'auto_detect' | 'request_param' + short_name?: string +} + +/** + * 模型支持的能力响应类型 + */ +export interface ModelCapabilitiesResponse { + model: string + global_model_id?: string + global_model_name?: string + supported_capabilities: string[] + capability_details: CapabilityDefinition[] + error?: string +} + +/** + * 获取所有能力定义 + */ +export async function getAllCapabilities(): Promise { + const response = await client.get('/api/capabilities') + return response.data.capabilities +} + +/** + * 获取用户可配置的能力列表 + */ +export async function getUserConfigurableCapabilities(): Promise { + const response = await client.get('/api/capabilities/user-configurable') + return response.data.capabilities +} + +/** + * 获取指定模型支持的能力列表 + */ +export async function getModelCapabilities(modelName: string): Promise { + const response = await client.get(`/api/capabilities/model/${encodeURIComponent(modelName)}`) + return response.data +} + +/** + * 获取 Endpoint 的所有 Keys + */ +export async function getEndpointKeys(endpointId: string): Promise { + const response = await client.get(`/api/admin/endpoints/${endpointId}/keys`) + return response.data +} + +/** + * 为 Endpoint 添加 Key + */ +export async function addEndpointKey( + endpointId: string, + data: { + endpoint_id: string + api_key: string + name: string // 密钥名称(必填) + rate_multiplier?: number // 成本倍率(默认 1.0) + internal_priority?: number // Endpoint 内部优先级(数字越小越优先) + max_concurrent?: number // 最大并发数(留空=自适应模式) + rate_limit?: number + daily_limit?: number + monthly_limit?: number + cache_ttl_minutes?: number // 缓存 TTL(分钟),0=禁用 + max_probe_interval_minutes?: number // 熔断探测间隔(分钟) + allowed_models?: string[] // 允许使用的模型列表 + capabilities?: Record // 能力标签配置 + note?: string // 备注说明(可选) + } +): Promise { + const response = await client.post(`/api/admin/endpoints/${endpointId}/keys`, data) + return response.data +} + +/** + * 更新 Endpoint Key + */ +export async function updateEndpointKey( + keyId: string, + data: Partial<{ + api_key: string + name: string // 密钥名称 + rate_multiplier: number // 成本倍率 + internal_priority: number // Endpoint 内部优先级(提供商优先模式,数字越小越优先) + global_priority: number // 全局 Key 优先级(全局 Key 优先模式,数字越小越优先) + max_concurrent: number // 最大并发数(留空=自适应模式) + rate_limit: number + daily_limit: number + monthly_limit: number + cache_ttl_minutes: number // 缓存 TTL(分钟),0=禁用 + max_probe_interval_minutes: number // 熔断探测间隔(分钟) + allowed_models: string[] | null // 允许使用的模型列表,null 表示允许所有 + capabilities: Record | null // 能力标签配置 + is_active: boolean + note: string // 备注说明 + }> +): Promise { + const response = await client.put(`/api/admin/endpoints/keys/${keyId}`, data) + return response.data +} + +/** + * 删除 Endpoint Key + */ +export async function deleteEndpointKey(keyId: string): Promise<{ message: string }> { + const response = await client.delete(`/api/admin/endpoints/keys/${keyId}`) + return response.data +} + +/** + * 批量更新 Endpoint Keys 的优先级(用于拖动排序) + */ +export async function batchUpdateKeyPriority( + endpointId: string, + priorities: Array<{ key_id: string; internal_priority: number }> +): Promise<{ message: string; updated_count: number }> { + const response = await client.put(`/api/admin/endpoints/${endpointId}/keys/batch-priority`, { + priorities + }) + return response.data +} diff --git a/frontend/src/api/endpoints/models.ts b/frontend/src/api/endpoints/models.ts new file mode 100644 index 0000000..1bffdf1 --- /dev/null +++ b/frontend/src/api/endpoints/models.ts @@ -0,0 +1,145 @@ +import client from '../client' +import type { + Model, + ModelCreate, + ModelUpdate, + ModelCatalogResponse, + ProviderAvailableSourceModelsResponse, + UpdateModelMappingRequest, + UpdateModelMappingResponse, + DeleteModelMappingResponse +} from './types' + +/** + * 获取 Provider 的所有模型 + */ +export async function getProviderModels( + providerId: string, + params?: { + is_active?: boolean + skip?: number + limit?: number + } +): Promise { + const response = await client.get(`/api/admin/providers/${providerId}/models`, { params }) + return response.data +} + +/** + * 创建模型 + */ +export async function createModel( + providerId: string, + data: ModelCreate +): Promise { + const response = await client.post(`/api/admin/providers/${providerId}/models`, data) + return response.data +} + +/** + * 获取模型详情 + */ +export async function getModel( + providerId: string, + modelId: string +): Promise { + const response = await client.get(`/api/admin/providers/${providerId}/models/${modelId}`) + return response.data +} + +/** + * 更新模型 + */ +export async function updateModel( + providerId: string, + modelId: string, + data: ModelUpdate +): Promise { + const response = await client.patch(`/api/admin/providers/${providerId}/models/${modelId}`, data) + return response.data +} + +/** + * 删除模型 + */ +export async function deleteModel( + providerId: string, + modelId: string +): Promise<{ message: string }> { + const response = await client.delete(`/api/admin/providers/${providerId}/models/${modelId}`) + return response.data +} + +/** + * 批量创建模型 + */ +export async function batchCreateModels( + providerId: string, + modelsData: ModelCreate[] +): Promise { + const response = await client.post(`/api/admin/providers/${providerId}/models/batch`, modelsData) + return response.data +} + +/** + * 获取统一模型目录 + */ +export async function getModelCatalog(): Promise { + const response = await client.get('/api/admin/models/catalog') + return response.data +} + +/** + * 获取 Provider 支持的统一模型列表 + */ +export async function getProviderAvailableSourceModels( + providerId: string +): Promise { + const response = await client.get(`/api/admin/providers/${providerId}/available-source-models`) + return response.data +} + +/** + * 更新目录中的模型映射 + */ +export async function updateCatalogMapping( + mappingId: string, + data: UpdateModelMappingRequest +): Promise { + const response = await client.put(`/api/admin/models/catalog/mappings/${mappingId}`, data) + return response.data +} + +/** + * 删除目录中的模型映射 + */ +export async function deleteCatalogMapping( + mappingId: string +): Promise { + const response = await client.delete(`/api/admin/models/catalog/mappings/${mappingId}`) + return response.data +} + +/** + * 批量为 Provider 关联 GlobalModels + */ +export async function batchAssignModelsToProvider( + providerId: string, + globalModelIds: string[] +): Promise<{ + success: Array<{ + global_model_id: string + global_model_name: string + model_id: string + }> + errors: Array<{ + global_model_id: string + error: string + }> +}> { + const response = await client.post( + `/api/admin/providers/${providerId}/assign-global-models`, + { global_model_ids: globalModelIds } + ) + return response.data +} diff --git a/frontend/src/api/endpoints/providers.ts b/frontend/src/api/endpoints/providers.ts new file mode 100644 index 0000000..b70996b --- /dev/null +++ b/frontend/src/api/endpoints/providers.ts @@ -0,0 +1,60 @@ +import client from '../client' +import type { ProviderWithEndpointsSummary } from './types' + +/** + * 获取 Providers 摘要(包含 Endpoints 统计) + */ +export async function getProvidersSummary(): Promise { + const response = await client.get('/api/admin/providers/summary') + return response.data +} + +/** + * 获取单个 Provider 的详细信息 + */ +export async function getProvider(providerId: string): Promise { + const response = await client.get(`/api/admin/providers/${providerId}/summary`) + return response.data +} + +/** + * 更新 Provider 基础配置 + */ +export async function updateProvider( + providerId: string, + data: Partial<{ + display_name: string + description: string + website: string + provider_priority: number + billing_type: 'monthly_quota' | 'pay_as_you_go' | 'free_tier' + monthly_quota_usd: number + quota_reset_day: number + quota_last_reset_at: string // 周期开始时间 + quota_expires_at: string + rpm_limit: number | null + cache_ttl_minutes: number // 0表示不支持缓存,>0表示支持缓存并设置TTL(分钟) + max_probe_interval_minutes: number + is_active: boolean + }> +): Promise { + const response = await client.patch(`/api/admin/providers/${providerId}`, data) + return response.data +} + +/** + * 创建 Provider + */ +export async function createProvider(data: any): Promise { + const response = await client.post('/api/admin/providers/', data) + return response.data +} + +/** + * 删除 Provider + */ +export async function deleteProvider(providerId: string): Promise<{ message: string }> { + const response = await client.delete(`/api/admin/providers/${providerId}`) + return response.data +} + diff --git a/frontend/src/api/endpoints/types.ts b/frontend/src/api/endpoints/types.ts new file mode 100644 index 0000000..498b68c --- /dev/null +++ b/frontend/src/api/endpoints/types.ts @@ -0,0 +1,553 @@ +export interface ProviderEndpoint { + id: string + provider_id: string + provider_name: string + api_format: string + base_url: string + custom_path?: string // 自定义请求路径(可选,为空则使用 API 格式默认路径) + auth_type: string + auth_header?: string + headers?: Record + timeout: number + max_retries: number + priority: number + weight: number + max_concurrent?: number + rate_limit?: number + health_score: number + consecutive_failures: number + last_failure_at?: string + is_active: boolean + config?: Record + total_keys: number + active_keys: number + created_at: string + updated_at: string +} + +export interface EndpointAPIKey { + id: string + endpoint_id: string + api_key_masked: string + api_key_plain?: string | null + name: string // 密钥名称(必填,用于识别) + rate_multiplier: number // 成本倍率(真实成本 = 表面成本 × 倍率) + internal_priority: number // Endpoint 内部优先级 + global_priority?: number | null // 全局 Key 优先级 + max_concurrent?: number + rate_limit?: number + daily_limit?: number + monthly_limit?: number + allowed_models?: string[] | null // 允许使用的模型列表(null = 支持所有模型) + capabilities?: Record | null // 能力标签配置(如 cache_1h, context_1m) + // 缓存与熔断配置 + cache_ttl_minutes: number // 缓存 TTL(分钟),0=禁用 + max_probe_interval_minutes: number // 熔断探测间隔(分钟) + health_score: number + consecutive_failures: number + last_failure_at?: string + request_count: number + success_count: number + error_count: number + success_rate: number + avg_response_time_ms: number + is_active: boolean + note?: string // 备注说明(可选) + last_used_at?: string + created_at: string + updated_at: string + // 自适应并发字段 + is_adaptive?: boolean // 是否为自适应模式(max_concurrent=NULL) + effective_limit?: number // 当前有效限制(自适应使用学习值,固定使用配置值) + learned_max_concurrent?: number + // 滑动窗口利用率采样 + utilization_samples?: Array<{ ts: number; util: number }> // 利用率采样窗口 + last_probe_increase_at?: string // 上次探测性扩容时间 + concurrent_429_count?: number + rpm_429_count?: number + last_429_at?: string + last_429_type?: string + // 熔断器字段(滑动窗口 + 半开模式) + circuit_breaker_open?: boolean + circuit_breaker_open_at?: string + next_probe_at?: string + half_open_until?: string + half_open_successes?: number + half_open_failures?: number + request_results_window?: Array<{ ts: number; ok: boolean }> // 请求结果滑动窗口 +} + +export interface EndpointHealthDetail { + api_format: string + health_score: number + is_active: boolean +} + +export interface EndpointHealthEvent { + timestamp: string + status: 'success' | 'failed' | 'skipped' | 'started' + status_code?: number | null + latency_ms?: number | null + error_type?: string | null + error_message?: string | null +} + +export interface EndpointStatusMonitor { + api_format: string + total_attempts: number + success_count: number + failed_count: number + skipped_count: number + success_rate: number + provider_count: number + key_count: number + last_event_at?: string | null + events: EndpointHealthEvent[] + timeline?: string[] + time_range_start?: string | null + time_range_end?: string | null +} + +export interface EndpointStatusMonitorResponse { + generated_at: string + formats: EndpointStatusMonitor[] +} + +// 公开版事件(不含敏感信息如 provider_id, key_id) +export interface PublicHealthEvent { + timestamp: string + status: string + status_code?: number | null + latency_ms?: number | null + error_type?: string | null +} + +// 公开版端点状态监控类型(返回 events,前端复用 EndpointHealthTimeline 组件) +export interface PublicEndpointStatusMonitor { + api_format: string + api_path: string // 本站入口路径 + total_attempts: number + success_count: number + failed_count: number + skipped_count: number + success_rate: number + last_event_at?: string | null + events: PublicHealthEvent[] + timeline?: string[] + time_range_start?: string | null + time_range_end?: string | null +} + +export interface PublicEndpointStatusMonitorResponse { + generated_at: string + formats: PublicEndpointStatusMonitor[] +} + +export interface ProviderWithEndpointsSummary { + id: string + name: string + display_name: string + description?: string + website?: string + provider_priority: number + billing_type?: 'monthly_quota' | 'pay_as_you_go' | 'free_tier' + monthly_quota_usd?: number + monthly_used_usd?: number + quota_reset_day?: number + quota_last_reset_at?: string // 当前周期开始时间 + quota_expires_at?: string + rpm_limit?: number | null + rpm_used?: number + rpm_reset_at?: string + is_active: boolean + total_endpoints: number + active_endpoints: number + total_keys: number + active_keys: number + total_models: number + active_models: number + avg_health_score: number + unhealthy_endpoints: number + api_formats: string[] + endpoint_health_details: EndpointHealthDetail[] + created_at: string + updated_at: string +} + +export interface HealthStatus { + endpoint_id?: string + endpoint_health_score?: number + endpoint_consecutive_failures?: number + endpoint_last_failure_at?: string + endpoint_is_active?: boolean + key_id?: string + key_health_score?: number + key_consecutive_failures?: number + key_last_failure_at?: string + key_is_active?: boolean + key_statistics?: Record +} + +export interface HealthSummary { + endpoints: { + total: number + active: number + unhealthy: number + } + keys: { + total: number + active: number + unhealthy: number + } +} + +export interface ConcurrencyStatus { + endpoint_id?: string + endpoint_current_concurrency: number + endpoint_max_concurrent?: number + key_id?: string + key_current_concurrency: number + key_max_concurrent?: number +} + +export interface Model { + id: string + provider_id: string + global_model_id?: string // 关联的 GlobalModel ID + provider_model_name: string // Provider 侧的模型名称(原 name) + // 原始配置值(可能为空,为空时使用 GlobalModel 默认值) + price_per_request?: number | null // 按次计费价格 + tiered_pricing?: TieredPricingConfig | null // 阶梯计费配置 + supports_vision?: boolean | null + supports_function_calling?: boolean | null + supports_streaming?: boolean | null + supports_extended_thinking?: boolean | null + supports_image_generation?: boolean | null + // 有效值(合并 Model 和 GlobalModel 默认值后的结果) + effective_tiered_pricing?: TieredPricingConfig | null // 有效阶梯计费配置 + effective_input_price?: number | null + effective_output_price?: number | null + effective_price_per_request?: number | null // 有效按次计费价格 + effective_supports_vision?: boolean | null + effective_supports_function_calling?: boolean | null + effective_supports_streaming?: boolean | null + effective_supports_extended_thinking?: boolean | null + effective_supports_image_generation?: boolean | null + is_active: boolean + is_available: boolean + created_at: string + updated_at: string + // GlobalModel 信息(从后端 join 获取) + global_model_name?: string + global_model_display_name?: string +} + +export interface ModelCreate { + provider_model_name: string // Provider 侧的模型名称(原 name) + global_model_id: string // 关联的 GlobalModel ID(必填) + // 计费配置(可选,为空时使用 GlobalModel 默认值) + price_per_request?: number // 按次计费价格 + tiered_pricing?: TieredPricingConfig // 阶梯计费配置 + // 能力配置(可选,为空时使用 GlobalModel 默认值) + supports_vision?: boolean + supports_function_calling?: boolean + supports_streaming?: boolean + supports_extended_thinking?: boolean + supports_image_generation?: boolean + is_active?: boolean + config?: Record +} + +export interface ModelUpdate { + provider_model_name?: string + global_model_id?: string + price_per_request?: number | null // 按次计费价格(null 表示清空/使用默认值) + tiered_pricing?: TieredPricingConfig | null // 阶梯计费配置 + supports_vision?: boolean + supports_function_calling?: boolean + supports_streaming?: boolean + supports_extended_thinking?: boolean + supports_image_generation?: boolean + is_active?: boolean + is_available?: boolean +} + +export interface ModelMapping { + id: string + source_model: string // 别名/源模型名 + target_global_model_id: string // 目标 GlobalModel ID + target_global_model_name: string | null + target_global_model_display_name: string | null + provider_id: string | null + provider_name: string | null + scope: 'global' | 'provider' + mapping_type: 'alias' | 'mapping' + is_active: boolean + created_at: string + updated_at: string +} + +export interface ModelCapabilities { + supports_vision: boolean + supports_function_calling: boolean + supports_streaming: boolean + [key: string]: boolean +} + +export interface ProviderModelPriceInfo { + input_price_per_1m?: number | null + output_price_per_1m?: number | null + cache_creation_price_per_1m?: number | null + cache_read_price_per_1m?: number | null + price_per_request?: number | null // 按次计费价格 +} + +export interface ModelPriceRange { + min_input: number | null + max_input: number | null + min_output: number | null + max_output: number | null +} + +export interface ModelCatalogProviderDetail { + provider_id: string + provider_name: string + provider_display_name?: string | null + model_id?: string | null + target_model: string + input_price_per_1m?: number | null + output_price_per_1m?: number | null + cache_creation_price_per_1m?: number | null + cache_read_price_per_1m?: number | null + cache_1h_creation_price_per_1m?: number | null // 1h 缓存创建价格 + price_per_request?: number | null // 按次计费价格 + effective_tiered_pricing?: TieredPricingConfig | null // 有效阶梯计费配置(含继承) + tier_count?: number // 阶梯数量 + supports_vision?: boolean | null + supports_function_calling?: boolean | null + supports_streaming?: boolean | null + is_active: boolean + mapping_id?: string | null +} + +export interface ModelCatalogItem { + global_model_name: string // GlobalModel.name(原 source_model) + display_name: string // GlobalModel.display_name + description?: string | null // GlobalModel.description + aliases: string[] // 所有指向该 GlobalModel 的别名列表 + providers: ModelCatalogProviderDetail[] // 支持该模型的 Provider 列表 + price_range: ModelPriceRange // 价格区间 + total_providers: number + capabilities: ModelCapabilities // 能力聚合 +} + +export interface ModelCatalogResponse { + models: ModelCatalogItem[] + total: number +} + +export interface ProviderAvailableSourceModel { + global_model_name: string // GlobalModel.name(原 source_model) + display_name: string // GlobalModel.display_name + provider_model_name: string // Model.provider_model_name(Provider 侧的模型名) + has_alias: boolean // 是否有别名指向该 GlobalModel + aliases: string[] // 别名列表 + model_id?: string | null // Model.id + price: ProviderModelPriceInfo + capabilities: ModelCapabilities + is_active: boolean +} + +export interface ProviderAvailableSourceModelsResponse { + models: ProviderAvailableSourceModel[] + total: number +} + +export interface BatchAssignProviderConfig { + provider_id: string + create_model?: boolean + model_config?: ModelCreate + model_id?: string +} + +export interface BatchAssignModelMappingRequest { + global_model_id: string // 要分配的 GlobalModel ID(原 source_model) + providers: BatchAssignProviderConfig[] +} + +export interface BatchAssignProviderResult { + provider_id: string + mapping_id?: string | null + created_model: boolean + model_id?: string | null + updated: boolean +} + +export interface BatchAssignError { + provider_id: string + error: string +} + +export interface BatchAssignModelMappingResponse { + success: boolean + created_mappings: BatchAssignProviderResult[] + errors: BatchAssignError[] +} + +export interface ModelMappingCreate { + source_model: string // 源模型名或别名 + target_global_model_id: string // 目标 GlobalModel ID + provider_id?: string | null + mapping_type?: 'alias' | 'mapping' + is_active?: boolean +} + +export interface ModelMappingUpdate { + source_model?: string // 源模型名或别名 + target_global_model_id?: string // 目标 GlobalModel ID + provider_id?: string | null + mapping_type?: 'alias' | 'mapping' + is_active?: boolean +} + +export interface UpdateModelMappingRequest { + source_model?: string + target_global_model_id?: string + provider_id?: string | null + mapping_type?: 'alias' | 'mapping' + is_active?: boolean +} + +export interface UpdateModelMappingResponse { + success: boolean + mapping_id: string + message?: string +} + +export interface DeleteModelMappingResponse { + success: boolean + message?: string +} + +export interface AdaptiveStatsResponse { + adaptive_mode: boolean + current_limit: number | null + learned_limit: number | null + concurrent_429_count: number + rpm_429_count: number + last_429_at: string | null + last_429_type: string | null + adjustment_count: number + recent_adjustments: Array<{ + timestamp: string + old_limit: number + new_limit: number + reason: string + [key: string]: any + }> +} + +// ========== 阶梯计费类型 ========== + +/** 缓存时长定价配置 */ +export interface CacheTTLPricing { + ttl_minutes: number + cache_creation_price_per_1m: number +} + +/** 单个价格阶梯配置 */ +export interface PricingTier { + up_to: number | null // null 表示无上限(最后一个阶梯) + input_price_per_1m: number + output_price_per_1m: number + cache_creation_price_per_1m?: number + cache_read_price_per_1m?: number + cache_ttl_pricing?: CacheTTLPricing[] +} + +/** 阶梯计费配置 */ +export interface TieredPricingConfig { + tiers: PricingTier[] +} + +// ========== GlobalModel 类型 ========== + +export interface GlobalModelCreate { + name: string + display_name: string + description?: string + official_url?: string + icon_url?: string + // 按次计费配置(可选,与阶梯计费叠加) + default_price_per_request?: number + // 阶梯计费配置(必填,固定价格用单阶梯表示) + default_tiered_pricing: TieredPricingConfig + // 默认能力配置 + default_supports_vision?: boolean + default_supports_function_calling?: boolean + default_supports_streaming?: boolean + default_supports_extended_thinking?: boolean + default_supports_image_generation?: boolean + // Key 能力配置 - 模型支持的能力列表 + supported_capabilities?: string[] + is_active?: boolean +} + +export interface GlobalModelUpdate { + display_name?: string + description?: string + official_url?: string + icon_url?: string + is_active?: boolean + // 按次计费配置 + default_price_per_request?: number | null // null 表示清空 + // 阶梯计费配置 + default_tiered_pricing?: TieredPricingConfig + // 默认能力配置 + default_supports_vision?: boolean + default_supports_function_calling?: boolean + default_supports_streaming?: boolean + default_supports_extended_thinking?: boolean + default_supports_image_generation?: boolean + // Key 能力配置 - 模型支持的能力列表 + supported_capabilities?: string[] | null +} + +export interface GlobalModelResponse { + id: string + name: string + display_name: string + description?: string + official_url?: string + icon_url?: string + is_active: boolean + // 按次计费配置 + default_price_per_request?: number + // 阶梯计费配置(必填) + default_tiered_pricing: TieredPricingConfig + // 默认能力配置 + default_supports_vision?: boolean + default_supports_function_calling?: boolean + default_supports_streaming?: boolean + default_supports_extended_thinking?: boolean + default_supports_image_generation?: boolean + // Key 能力配置 - 模型支持的能力列表 + supported_capabilities?: string[] | null + // 统计数据 + provider_count?: number + alias_count?: number + usage_count?: number + created_at: string + updated_at?: string +} + +export interface GlobalModelWithStats extends GlobalModelResponse { + total_models: number + total_providers: number + price_range: ModelPriceRange +} + +export interface GlobalModelListResponse { + models: GlobalModelResponse[] + total: number +} diff --git a/frontend/src/api/global-models.ts b/frontend/src/api/global-models.ts new file mode 100644 index 0000000..694bfd0 --- /dev/null +++ b/frontend/src/api/global-models.ts @@ -0,0 +1,23 @@ +/** + * GlobalModel API 客户端 + * 统一导出,简化导入路径 + */ + +export * from './endpoints/global-models' +export type { + GlobalModelCreate, + GlobalModelUpdate, + GlobalModelResponse, + GlobalModelWithStats, + GlobalModelListResponse, +} from './endpoints/types' + +// 重新导出为更简洁的函数名 +export { + getGlobalModels as listGlobalModels, + getGlobalModel, + createGlobalModel, + updateGlobalModel, + deleteGlobalModel, + batchAssignToProviders, +} from './endpoints/global-models' diff --git a/frontend/src/api/me.ts b/frontend/src/api/me.ts new file mode 100644 index 0000000..2b2357b --- /dev/null +++ b/frontend/src/api/me.ts @@ -0,0 +1,257 @@ +import apiClient from './client' +import type { ActivityHeatmap } from '@/types/activity' + +export interface Profile { + id: string // UUID + email: string + username: string + role: string + is_active: boolean + quota_usd: number | null + used_usd: number + total_usd?: number // 累积消费总额 + created_at: string + updated_at: string + last_login_at?: string + preferences?: UserPreferences +} + +export interface UserPreferences { + avatar_url?: string + bio?: string + default_provider_id?: string // UUID + default_provider?: any + theme: string + language: string + timezone?: string + notifications?: { + email?: boolean + usage_alerts?: boolean + announcements?: boolean + } +} + +// 提供商配置接口 +export interface ProviderConfig { + provider_id: string + priority: number // 优先级(越高越优先) + weight: number // 负载均衡权重 + enabled: boolean // 是否启用 +} + +// 使用记录接口 +export interface UsageRecordDetail { + id: string + provider: string + model: string + input_tokens: number + output_tokens: number + total_tokens: number + cost: number // 官方费率 + actual_cost?: number // 倍率消耗(仅管理员可见) + rate_multiplier?: number // 成本倍率(仅管理员可见) + response_time_ms?: number + is_stream: boolean + created_at: string + cache_creation_input_tokens?: number + cache_read_input_tokens?: number + status_code: number + error_message?: string + input_price_per_1m: number + output_price_per_1m: number + cache_creation_price_per_1m?: number + cache_read_price_per_1m?: number + price_per_request?: number // 按次计费价格 +} + +// 模型统计接口 +export interface ModelSummary { + model: string + requests: number + input_tokens: number + output_tokens: number + total_tokens: number + total_cost_usd: number + actual_total_cost_usd?: number // 倍率消耗(仅管理员可见) +} + +// 使用统计响应接口 +export interface UsageResponse { + total_requests: number + total_input_tokens: number + total_output_tokens: number + total_tokens: number + total_cost: number // 官方费率 + total_actual_cost?: number // 倍率消耗(仅管理员可见) + avg_response_time: number + quota_usd: number | null + used_usd: number + summary_by_model: ModelSummary[] + records: UsageRecordDetail[] + activity_heatmap?: ActivityHeatmap | null +} + +export interface ApiKey { + id: string // UUID + name: string + key?: string + key_display: string + is_active: boolean + last_used_at?: string + created_at: string + total_requests?: number + total_cost_usd?: number + allowed_providers?: ProviderConfig[] + force_capabilities?: Record | null // 强制能力配置 +} + +// 不再需要 ProviderBinding 接口 + +export interface ChangePasswordRequest { + old_password: string + new_password: string +} + +export const meApi = { + // 获取个人信息 + async getProfile(): Promise { + const response = await apiClient.get('/api/users/me') + return response.data + }, + + // 更新个人信息 + async updateProfile(data: { + email?: string + username?: string + }): Promise<{ message: string }> { + const response = await apiClient.put('/api/users/me', data) + return response.data + }, + + // 修改密码 + async changePassword(data: ChangePasswordRequest): Promise<{ message: string }> { + const response = await apiClient.patch('/api/users/me/password', data) + return response.data + }, + + // API密钥管理 + async getApiKeys(): Promise { + const response = await apiClient.get('/api/users/me/api-keys') + return response.data + }, + + async createApiKey(name: string): Promise { + const response = await apiClient.post('/api/users/me/api-keys', { name }) + return response.data + }, + + async getApiKeyDetail(keyId: string, includeKey: boolean = false): Promise { + const response = await apiClient.get( + `/api/users/me/api-keys/${keyId}`, + { params: { include_key: includeKey } } + ) + return response.data + }, + + async getFullApiKey(keyId: string): Promise<{ key: string }> { + const response = await apiClient.get<{ key: string }>( + `/api/users/me/api-keys/${keyId}`, + { params: { include_key: true } } + ) + return response.data + }, + + async deleteApiKey(keyId: string): Promise<{ message: string }> { + const response = await apiClient.delete(`/api/users/me/api-keys/${keyId}`) + return response.data + }, + + async toggleApiKey(keyId: string): Promise { + const response = await apiClient.patch(`/api/users/me/api-keys/${keyId}`) + return response.data + }, + + // 使用统计 + async getUsage(params?: { + start_date?: string + end_date?: string + }): Promise { + const response = await apiClient.get('/api/users/me/usage', { params }) + return response.data + }, + + // 获取活跃请求状态(用于轮询更新) + async getActiveRequests(ids?: string): Promise<{ + requests: Array<{ + id: string + status: string + input_tokens: number + output_tokens: number + cost: number + response_time_ms: number | null + }> + }> { + const params = ids ? { ids } : {} + const response = await apiClient.get('/api/users/me/usage/active', { params }) + return response.data + }, + + // 获取可用的提供商 + async getAvailableProviders(): Promise { + const response = await apiClient.get('/api/users/me/providers') + return response.data + }, + + // 获取端点状态(不包含敏感信息) + async getEndpointStatus(): Promise { + const response = await apiClient.get('/api/users/me/endpoint-status') + return response.data + }, + + // 偏好设置 + async getPreferences(): Promise { + const response = await apiClient.get('/api/users/me/preferences') + return response.data + }, + + async updatePreferences(data: Partial): Promise<{ message: string }> { + const response = await apiClient.put('/api/users/me/preferences', data) + return response.data + }, + + // 提供商绑定管理相关方法已移除,改为直接从可用提供商中选择 + + // API密钥提供商关联 + async updateApiKeyProviders(keyId: string, data: { + allowed_providers?: ProviderConfig[] + }): Promise<{ message: string }> { + const response = await apiClient.put(`/api/users/me/api-keys/${keyId}/providers`, data) + return response.data + }, + + // API密钥能力配置 + async updateApiKeyCapabilities(keyId: string, data: { + force_capabilities?: Record | null + }): Promise<{ message: string; force_capabilities?: Record | null }> { + const response = await apiClient.put(`/api/users/me/api-keys/${keyId}/capabilities`, data) + return response.data + }, + + // 模型能力配置 + async getModelCapabilitySettings(): Promise<{ + model_capability_settings: Record> + }> { + const response = await apiClient.get('/api/users/me/model-capabilities') + return response.data + }, + + async updateModelCapabilitySettings(data: { + model_capability_settings: Record> | null + }): Promise<{ + message: string + model_capability_settings: Record> | null + }> { + const response = await apiClient.put('/api/users/me/model-capabilities', data) + return response.data + } +} diff --git a/frontend/src/api/provider-strategy.ts b/frontend/src/api/provider-strategy.ts new file mode 100644 index 0000000..811ba59 --- /dev/null +++ b/frontend/src/api/provider-strategy.ts @@ -0,0 +1,55 @@ +/** + * 提供商策略管理 API 客户端 + */ + +import apiClient from './client'; + +const API_BASE = '/api/admin/provider-strategy'; + +export interface ProviderBillingConfig { + billing_type: 'monthly_quota' | 'pay_as_you_go' | 'free_tier'; + monthly_quota_usd?: number; + quota_reset_day?: number; + quota_last_reset_at?: string; // 当前周期开始时间 + quota_expires_at?: string; + rpm_limit?: number | null; + cache_ttl_minutes?: number; // 0表示不支持缓存,>0表示支持缓存并设置TTL(分钟) + provider_priority?: number; +} + +/** + * 更新提供商计费配置 + */ +export async function updateProviderBilling( + providerId: string, + config: ProviderBillingConfig +) { + const response = await apiClient.put(`${API_BASE}/providers/${providerId}/billing`, config); + return response.data; +} + +/** + * 获取提供商使用统计 + */ +export async function getProviderStats(providerId: string, hours: number = 24) { + const response = await apiClient.get(`${API_BASE}/providers/${providerId}/stats`, { + params: { hours } + }); + return response.data; +} + +/** + * 重置提供商月卡额度 + */ +export async function resetProviderQuota(providerId: string) { + const response = await apiClient.delete(`${API_BASE}/providers/${providerId}/quota`); + return response.data; +} + +/** + * 获取所有可用的负载均衡策略 + */ +export async function listAvailableStrategies() { + const response = await apiClient.get(`${API_BASE}/strategies`); + return response.data; +} diff --git a/frontend/src/api/public-models.ts b/frontend/src/api/public-models.ts new file mode 100644 index 0000000..60bb1e8 --- /dev/null +++ b/frontend/src/api/public-models.ts @@ -0,0 +1,44 @@ +/** + * Public Models API - 普通用户可访问的模型列表 + */ + +import client from './client' +import type { TieredPricingConfig } from './endpoints/types' + +export interface PublicGlobalModel { + id: string + name: string + display_name: string | null + description: string | null + icon_url: string | null + is_active: boolean + // 阶梯计费配置 + default_tiered_pricing: TieredPricingConfig + default_price_per_request: number | null // 按次计费价格 + // 能力 + default_supports_vision: boolean + default_supports_function_calling: boolean + default_supports_streaming: boolean + default_supports_extended_thinking: boolean + default_supports_image_generation: boolean + // Key 能力支持 + supported_capabilities: string[] | null +} + +export interface PublicGlobalModelListResponse { + models: PublicGlobalModel[] + total: number +} + +/** + * 获取公开的 GlobalModel 列表(普通用户可访问) + */ +export async function getPublicGlobalModels(params?: { + skip?: number + limit?: number + is_active?: boolean + search?: string +}): Promise { + const response = await client.get('/api/public/global-models', { params }) + return response.data +} diff --git a/frontend/src/api/requestTrace.ts b/frontend/src/api/requestTrace.ts new file mode 100644 index 0000000..3d86e69 --- /dev/null +++ b/frontend/src/api/requestTrace.ts @@ -0,0 +1,69 @@ +import apiClient from './client' + +export interface CandidateRecord { + id: string + request_id: string + candidate_index: number + retry_index: number + provider_id?: string + provider_name?: string + provider_website?: string // Provider 官网 + endpoint_id?: string + endpoint_name?: string // 端点显示名称(api_format) + key_id?: string + key_name?: string // 密钥名称 + key_preview?: string // 密钥脱敏预览(如 sk-***abc) + key_capabilities?: Record | null // Key 支持的能力 + required_capabilities?: Record | null // 请求实际需要的能力标签 + status: 'pending' | 'streaming' | 'success' | 'failed' | 'skipped' + skip_reason?: string + is_cached: boolean + // 执行结果字段 + status_code?: number + error_type?: string + error_message?: string + latency_ms?: number + concurrent_requests?: number + extra_data?: Record + created_at: string + started_at?: string + finished_at?: string +} + +export interface RequestTrace { + request_id: string + total_candidates: number + final_status: 'success' | 'failed' | 'streaming' | 'pending' + total_latency_ms: number + candidates: CandidateRecord[] +} + +export interface ProviderStats { + total_attempts: number + success_count: number + failed_count: number + skipped_count: number + pending_count: number + available_count: number + failure_rate: number +} + +export const requestTraceApi = { + /** + * 获取特定请求的完整追踪信息 + */ + async getRequestTrace(requestId: string): Promise { + const response = await apiClient.get(`/api/admin/monitoring/trace/${requestId}`) + return response.data + }, + + /** + * 获取某个 Provider 的失败率统计 + */ + async getProviderStats(providerId: string, limit: number = 100): Promise { + const response = await apiClient.get(`/api/admin/monitoring/trace/stats/provider/${providerId}`, { + params: { limit } + }) + return response.data + } +} diff --git a/frontend/src/api/security.ts b/frontend/src/api/security.ts new file mode 100644 index 0000000..ed88941 --- /dev/null +++ b/frontend/src/api/security.ts @@ -0,0 +1,83 @@ +/** + * IP 安全管理 API + */ +import apiClient from './client' + +export interface IPBlacklistEntry { + ip_address: string + reason: string + ttl?: number +} + +export interface IPWhitelistEntry { + ip_address: string +} + +export interface BlacklistStats { + available: boolean + total: number + error?: string +} + +export interface WhitelistResponse { + whitelist: string[] + total: number +} + +/** + * IP 黑名单管理 + */ +export const blacklistApi = { + /** + * 添加 IP 到黑名单 + */ + async add(data: IPBlacklistEntry) { + const response = await apiClient.post('/api/admin/security/ip/blacklist', data) + return response.data + }, + + /** + * 从黑名单移除 IP + */ + async remove(ip_address: string) { + const response = await apiClient.delete(`/api/admin/security/ip/blacklist/${encodeURIComponent(ip_address)}`) + return response.data + }, + + /** + * 获取黑名单统计 + */ + async getStats(): Promise { + const response = await apiClient.get('/api/admin/security/ip/blacklist/stats') + return response.data + } +} + +/** + * IP 白名单管理 + */ +export const whitelistApi = { + /** + * 添加 IP 到白名单 + */ + async add(data: IPWhitelistEntry) { + const response = await apiClient.post('/api/admin/security/ip/whitelist', data) + return response.data + }, + + /** + * 从白名单移除 IP + */ + async remove(ip_address: string) { + const response = await apiClient.delete(`/api/admin/security/ip/whitelist/${encodeURIComponent(ip_address)}`) + return response.data + }, + + /** + * 获取白名单列表 + */ + async getList(): Promise { + const response = await apiClient.get('/api/admin/security/ip/whitelist') + return response.data + } +} diff --git a/frontend/src/api/usage.ts b/frontend/src/api/usage.ts new file mode 100644 index 0000000..ed76022 --- /dev/null +++ b/frontend/src/api/usage.ts @@ -0,0 +1,202 @@ +import apiClient from './client' +import { cachedRequest } from '@/utils/cache' +import type { ActivityHeatmap } from '@/types/activity' + +export interface UsageRecord { + id: string // UUID + user_id: string // UUID + username?: string + provider_id?: string // UUID + provider_name?: string + model: string + input_tokens: number + output_tokens: number + cache_creation_input_tokens?: number + cache_read_input_tokens?: number + total_tokens: number + cost?: number + response_time?: number + created_at: string + has_fallback?: boolean // 🆕 是否发生了 fallback +} + +export interface UsageStats { + total_requests: number + total_tokens: number + total_cost: number + total_actual_cost?: number + avg_response_time: number + today?: { + requests: number + tokens: number + cost: number + } + activity_heatmap?: ActivityHeatmap | null +} + +export interface UsageByModel { + model: string + request_count: number + total_tokens: number + total_cost: number + avg_response_time?: number +} + +export interface UsageByUser { + user_id: string // UUID + email: string + username: string + request_count: number + total_tokens: number + total_cost: number +} + +export interface UsageByProvider { + provider_id: string + provider: string + request_count: number + total_tokens: number + total_cost: number + actual_cost: number + avg_response_time_ms: number + success_rate: number + error_count: number +} + +export interface UsageByApiFormat { + api_format: string + request_count: number + total_tokens: number + total_cost: number + actual_cost: number + avg_response_time_ms: number +} + +export interface UsageFilters { + user_id?: string // UUID + provider_id?: string // UUID + model?: string + start_date?: string + end_date?: string + page?: number + page_size?: number +} + +export const usageApi = { + async getUsageRecords(filters?: UsageFilters): Promise<{ + records: UsageRecord[] + total: number + page: number + page_size: number + }> { + const response = await apiClient.get('/api/usage', { params: filters }) + return response.data + }, + + async getUsageStats(filters?: UsageFilters): Promise { + // 为统计数据添加30秒缓存 + const cacheKey = `usage-stats-${JSON.stringify(filters || {})}` + return cachedRequest( + cacheKey, + async () => { + const response = await apiClient.get('/api/admin/usage/stats', { params: filters }) + return response.data + }, + 30000 // 30秒缓存 + ) + }, + + /** + * Get usage aggregation by dimension (RESTful API) + * @param groupBy Aggregation dimension: 'model', 'user', 'provider', or 'api_format' + * @param filters Optional filters + */ + async getUsageAggregation( + groupBy: 'model' | 'user' | 'provider' | 'api_format', + filters?: UsageFilters & { limit?: number } + ): Promise { + const cacheKey = `usage-aggregation-${groupBy}-${JSON.stringify(filters || {})}` + return cachedRequest( + cacheKey, + async () => { + const response = await apiClient.get('/api/admin/usage/aggregation/stats', { + params: { group_by: groupBy, ...filters } + }) + return response.data + }, + 30000 // 30秒缓存 + ) + }, + + // Shorthand methods using getUsageAggregation + async getUsageByModel(filters?: UsageFilters & { limit?: number }): Promise { + return this.getUsageAggregation('model', filters) + }, + + async getUsageByUser(filters?: UsageFilters & { limit?: number }): Promise { + return this.getUsageAggregation('user', filters) + }, + + async getUsageByProvider(filters?: UsageFilters & { limit?: number }): Promise { + return this.getUsageAggregation('provider', filters) + }, + + async getUsageByApiFormat(filters?: UsageFilters & { limit?: number }): Promise { + return this.getUsageAggregation('api_format', filters) + }, + + async getUserUsage(userId: string, filters?: UsageFilters): Promise<{ + records: UsageRecord[] + stats: UsageStats + }> { + const response = await apiClient.get(`/api/users/${userId}/usage`, { params: filters }) + return response.data + }, + + async exportUsage(format: 'csv' | 'json', filters?: UsageFilters): Promise { + const response = await apiClient.get('/api/usage/export', { + params: { ...filters, format }, + responseType: 'blob' + }) + return response.data + }, + + async getAllUsageRecords(params?: { + start_date?: string + end_date?: string + user_id?: string // UUID + username?: string + model?: string + provider?: string + status?: string // 'stream' | 'standard' | 'error' + limit?: number + offset?: number + }): Promise<{ + records: any[] + total: number + limit: number + offset: number + }> { + const response = await apiClient.get('/api/admin/usage/records', { params }) + return response.data + }, + + /** + * 获取活跃请求的状态(轻量级接口,用于轮询更新) + * @param ids 可选,逗号分隔的请求 ID 列表 + */ + async getActiveRequests(ids?: string[]): Promise<{ + requests: Array<{ + id: string + status: 'pending' | 'streaming' | 'completed' | 'failed' + input_tokens: number + output_tokens: number + cost: number + response_time_ms: number | null + }> + }> { + const params = ids?.length ? { ids: ids.join(',') } : {} + const response = await apiClient.get('/api/admin/usage/active', { params }) + return response.data + } +} diff --git a/frontend/src/api/users.ts b/frontend/src/api/users.ts new file mode 100644 index 0000000..d30054c --- /dev/null +++ b/frontend/src/api/users.ts @@ -0,0 +1,106 @@ +import apiClient from './client' + +export interface User { + id: string // UUID + username: string + email: string + role: 'admin' | 'user' + is_active: boolean + quota_usd: number | null + used_usd: number + total_usd: number + allowed_providers: string[] | null // 允许使用的提供商 ID 列表 + allowed_endpoints: string[] | null // 允许使用的端点 ID 列表 + allowed_models: string[] | null // 允许使用的模型名称列表 + created_at: string + updated_at?: string +} + +export interface CreateUserRequest { + username: string + password: string + email: string + role?: 'admin' | 'user' + quota_usd?: number | null + allowed_providers?: string[] | null + allowed_endpoints?: string[] | null + allowed_models?: string[] | null +} + +export interface UpdateUserRequest { + email?: string + is_active?: boolean + role?: 'admin' | 'user' + quota_usd?: number | null + password?: string + allowed_providers?: string[] | null + allowed_endpoints?: string[] | null + allowed_models?: string[] | null +} + +export interface ApiKey { + id: string // UUID + key?: string // 完整的 key,只在创建时返回 + key_display?: string // 脱敏后的密钥显示 + name?: string + created_at: string + last_used_at?: string + expires_at?: string // 过期时间 + is_active: boolean + is_standalone: boolean // 是否为独立余额Key + balance_used_usd?: number // 已使用余额(仅独立Key) + current_balance_usd?: number | null // 当前余额(独立Key预付费模式,null表示无限制) + rate_limit?: number // 速率限制(请求/分钟) + total_requests?: number // 总请求数 + total_cost_usd?: number // 总费用 +} + +export const usersApi = { + async getAllUsers(): Promise { + const response = await apiClient.get('/api/admin/users') + return response.data + }, + + async getUser(userId: string): Promise { + const response = await apiClient.get(`/api/admin/users/${userId}`) + return response.data + }, + + async createUser(user: CreateUserRequest): Promise { + const response = await apiClient.post('/api/admin/users', user) + return response.data + }, + + async updateUser(userId: string, updates: UpdateUserRequest): Promise { + const response = await apiClient.put(`/api/admin/users/${userId}`, updates) + return response.data + }, + + async deleteUser(userId: string): Promise { + await apiClient.delete(`/api/admin/users/${userId}`) + }, + + async getUserApiKeys(userId: string): Promise { + const response = await apiClient.get<{ api_keys: ApiKey[] }>(`/api/admin/users/${userId}/api-keys`) + return response.data.api_keys + }, + + async createApiKey(userId: string, name?: string): Promise { + const response = await apiClient.post(`/api/admin/users/${userId}/api-keys`, { name }) + return response.data + }, + + async deleteApiKey(userId: string, keyId: string): Promise { + await apiClient.delete(`/api/admin/users/${userId}/api-keys/${keyId}`) + }, + + async resetUserQuota(userId: string): Promise { + await apiClient.patch(`/api/admin/users/${userId}/quota`) + }, + + // 管理员统计 + async getUsageStats(): Promise { + const response = await apiClient.get('/api/admin/usage/stats') + return response.data + } +} diff --git a/frontend/src/components/AetherLineByLineLogo.vue b/frontend/src/components/AetherLineByLineLogo.vue new file mode 100644 index 0000000..dfceb6d --- /dev/null +++ b/frontend/src/components/AetherLineByLineLogo.vue @@ -0,0 +1,412 @@ + + + + + diff --git a/frontend/src/components/CodeHighlight.vue b/frontend/src/components/CodeHighlight.vue new file mode 100644 index 0000000..06503d7 --- /dev/null +++ b/frontend/src/components/CodeHighlight.vue @@ -0,0 +1,400 @@ + + + + + diff --git a/frontend/src/components/ConfirmContainer.vue b/frontend/src/components/ConfirmContainer.vue new file mode 100644 index 0000000..94ea887 --- /dev/null +++ b/frontend/src/components/ConfirmContainer.vue @@ -0,0 +1,26 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/GeminiStarCluster.vue b/frontend/src/components/GeminiStarCluster.vue new file mode 100644 index 0000000..5762e91 --- /dev/null +++ b/frontend/src/components/GeminiStarCluster.vue @@ -0,0 +1,403 @@ + + + + + diff --git a/frontend/src/components/HeaderLogo.vue b/frontend/src/components/HeaderLogo.vue new file mode 100644 index 0000000..a3043c9 --- /dev/null +++ b/frontend/src/components/HeaderLogo.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/frontend/src/components/PlatformSelect.vue b/frontend/src/components/PlatformSelect.vue new file mode 100644 index 0000000..f0531f1 --- /dev/null +++ b/frontend/src/components/PlatformSelect.vue @@ -0,0 +1,310 @@ + + + + + + + diff --git a/frontend/src/components/RippleLogo.vue b/frontend/src/components/RippleLogo.vue new file mode 100644 index 0000000..a223a2e --- /dev/null +++ b/frontend/src/components/RippleLogo.vue @@ -0,0 +1,1239 @@ + + + + + + diff --git a/frontend/src/components/ToastContainer.vue b/frontend/src/components/ToastContainer.vue new file mode 100644 index 0000000..ba29e43 --- /dev/null +++ b/frontend/src/components/ToastContainer.vue @@ -0,0 +1,70 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/ToastWithProgress.vue b/frontend/src/components/ToastWithProgress.vue new file mode 100644 index 0000000..a7d642a --- /dev/null +++ b/frontend/src/components/ToastWithProgress.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/frontend/src/components/charts/BarChart.vue b/frontend/src/components/charts/BarChart.vue new file mode 100644 index 0000000..b8634a6 --- /dev/null +++ b/frontend/src/components/charts/BarChart.vue @@ -0,0 +1,148 @@ + + + diff --git a/frontend/src/components/charts/LineChart.vue b/frontend/src/components/charts/LineChart.vue new file mode 100644 index 0000000..bedd944 --- /dev/null +++ b/frontend/src/components/charts/LineChart.vue @@ -0,0 +1,128 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/common/AlertDialog.vue b/frontend/src/components/common/AlertDialog.vue new file mode 100644 index 0000000..b746f53 --- /dev/null +++ b/frontend/src/components/common/AlertDialog.vue @@ -0,0 +1,165 @@ + + + diff --git a/frontend/src/components/common/EmptyState.vue b/frontend/src/components/common/EmptyState.vue new file mode 100644 index 0000000..b2a0b85 --- /dev/null +++ b/frontend/src/components/common/EmptyState.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/frontend/src/components/common/LoadingState.vue b/frontend/src/components/common/LoadingState.vue new file mode 100644 index 0000000..4b3640f --- /dev/null +++ b/frontend/src/components/common/LoadingState.vue @@ -0,0 +1,69 @@ + + + diff --git a/frontend/src/components/common/index.ts b/frontend/src/components/common/index.ts new file mode 100644 index 0000000..a0c0ac2 --- /dev/null +++ b/frontend/src/components/common/index.ts @@ -0,0 +1,9 @@ +/** + * Common Components + * 常用的自定义业务组件 + */ + +// 状态和反馈组件 +export { default as EmptyState } from './EmptyState.vue' +export { default as AlertDialog } from './AlertDialog.vue' +export { default as LoadingState } from './LoadingState.vue' diff --git a/frontend/src/components/icons/AetherLogo.vue b/frontend/src/components/icons/AetherLogo.vue new file mode 100644 index 0000000..fea8bbf --- /dev/null +++ b/frontend/src/components/icons/AetherLogo.vue @@ -0,0 +1,5 @@ + diff --git a/frontend/src/components/layout/AppShell.vue b/frontend/src/components/layout/AppShell.vue new file mode 100644 index 0000000..d8985ba --- /dev/null +++ b/frontend/src/components/layout/AppShell.vue @@ -0,0 +1,55 @@ + + + diff --git a/frontend/src/components/layout/CardSection.vue b/frontend/src/components/layout/CardSection.vue new file mode 100644 index 0000000..1875725 --- /dev/null +++ b/frontend/src/components/layout/CardSection.vue @@ -0,0 +1,103 @@ + + + diff --git a/frontend/src/components/layout/MobileNav.vue b/frontend/src/components/layout/MobileNav.vue new file mode 100644 index 0000000..5dcae4c --- /dev/null +++ b/frontend/src/components/layout/MobileNav.vue @@ -0,0 +1,123 @@ + + + diff --git a/frontend/src/components/layout/PageContainer.vue b/frontend/src/components/layout/PageContainer.vue new file mode 100644 index 0000000..3c84355 --- /dev/null +++ b/frontend/src/components/layout/PageContainer.vue @@ -0,0 +1,47 @@ + + + diff --git a/frontend/src/components/layout/PageHeader.vue b/frontend/src/components/layout/PageHeader.vue new file mode 100644 index 0000000..f514e41 --- /dev/null +++ b/frontend/src/components/layout/PageHeader.vue @@ -0,0 +1,38 @@ + + + diff --git a/frontend/src/components/layout/Section.vue b/frontend/src/components/layout/Section.vue new file mode 100644 index 0000000..43f8431 --- /dev/null +++ b/frontend/src/components/layout/Section.vue @@ -0,0 +1,54 @@ + + + diff --git a/frontend/src/components/layout/SidebarNav.vue b/frontend/src/components/layout/SidebarNav.vue new file mode 100644 index 0000000..ab8c69a --- /dev/null +++ b/frontend/src/components/layout/SidebarNav.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/frontend/src/components/layout/index.ts b/frontend/src/components/layout/index.ts new file mode 100644 index 0000000..aa7d2bb --- /dev/null +++ b/frontend/src/components/layout/index.ts @@ -0,0 +1,15 @@ +/** + * Layout Components Library + * 基于 shadcn/ui 的自定义布局组件库 + */ + +// 页面布局组件 +export { default as PageHeader } from './PageHeader.vue' +export { default as PageContainer } from './PageContainer.vue' +export { default as Section } from './Section.vue' +export { default as CardSection } from './CardSection.vue' + +// 应用外壳组件 +export { default as AppShell } from './AppShell.vue' +export { default as MobileNav } from './MobileNav.vue' +export { default as SidebarNav } from './SidebarNav.vue' diff --git a/frontend/src/components/stats/ActivityHeatmap.vue b/frontend/src/components/stats/ActivityHeatmap.vue new file mode 100644 index 0000000..c63680b --- /dev/null +++ b/frontend/src/components/stats/ActivityHeatmap.vue @@ -0,0 +1,360 @@ + + + + + diff --git a/frontend/src/components/ui/avatar-fallback.vue b/frontend/src/components/ui/avatar-fallback.vue new file mode 100644 index 0000000..c8c5f79 --- /dev/null +++ b/frontend/src/components/ui/avatar-fallback.vue @@ -0,0 +1,24 @@ + + + diff --git a/frontend/src/components/ui/avatar-image.vue b/frontend/src/components/ui/avatar-image.vue new file mode 100644 index 0000000..2024f84 --- /dev/null +++ b/frontend/src/components/ui/avatar-image.vue @@ -0,0 +1,23 @@ + + + diff --git a/frontend/src/components/ui/avatar.vue b/frontend/src/components/ui/avatar.vue new file mode 100644 index 0000000..bb02f7b --- /dev/null +++ b/frontend/src/components/ui/avatar.vue @@ -0,0 +1,21 @@ + + + diff --git a/frontend/src/components/ui/badge.vue b/frontend/src/components/ui/badge.vue new file mode 100644 index 0000000..a100b1c --- /dev/null +++ b/frontend/src/components/ui/badge.vue @@ -0,0 +1,50 @@ + + + diff --git a/frontend/src/components/ui/button.vue b/frontend/src/components/ui/button.vue new file mode 100644 index 0000000..099d606 --- /dev/null +++ b/frontend/src/components/ui/button.vue @@ -0,0 +1,61 @@ + + + diff --git a/frontend/src/components/ui/card.vue b/frontend/src/components/ui/card.vue new file mode 100644 index 0000000..c9ffee5 --- /dev/null +++ b/frontend/src/components/ui/card.vue @@ -0,0 +1,44 @@ + + + diff --git a/frontend/src/components/ui/checkbox.vue b/frontend/src/components/ui/checkbox.vue new file mode 100644 index 0000000..b50b5d1 --- /dev/null +++ b/frontend/src/components/ui/checkbox.vue @@ -0,0 +1,47 @@ + + + diff --git a/frontend/src/components/ui/dialog/Dialog.vue b/frontend/src/components/ui/dialog/Dialog.vue new file mode 100644 index 0000000..be87218 --- /dev/null +++ b/frontend/src/components/ui/dialog/Dialog.vue @@ -0,0 +1,139 @@ + + + diff --git a/frontend/src/components/ui/dialog/DialogContent.vue b/frontend/src/components/ui/dialog/DialogContent.vue new file mode 100644 index 0000000..d03fb4a --- /dev/null +++ b/frontend/src/components/ui/dialog/DialogContent.vue @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/frontend/src/components/ui/dialog/DialogDescription.vue b/frontend/src/components/ui/dialog/DialogDescription.vue new file mode 100644 index 0000000..4988b04 --- /dev/null +++ b/frontend/src/components/ui/dialog/DialogDescription.vue @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/frontend/src/components/ui/dialog/DialogFooter.vue b/frontend/src/components/ui/dialog/DialogFooter.vue new file mode 100644 index 0000000..e4905f4 --- /dev/null +++ b/frontend/src/components/ui/dialog/DialogFooter.vue @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/frontend/src/components/ui/dialog/DialogHeader.vue b/frontend/src/components/ui/dialog/DialogHeader.vue new file mode 100644 index 0000000..f957253 --- /dev/null +++ b/frontend/src/components/ui/dialog/DialogHeader.vue @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/frontend/src/components/ui/dialog/DialogTitle.vue b/frontend/src/components/ui/dialog/DialogTitle.vue new file mode 100644 index 0000000..85d1146 --- /dev/null +++ b/frontend/src/components/ui/dialog/DialogTitle.vue @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/frontend/src/components/ui/index.ts b/frontend/src/components/ui/index.ts new file mode 100644 index 0000000..fb3f3a0 --- /dev/null +++ b/frontend/src/components/ui/index.ts @@ -0,0 +1,67 @@ +/** + * shadcn/ui Components + * 统一导出所有 shadcn UI 组件,简化导入 + * + * 使用方式: + * import { Button, Input, Card } from '@/components/ui' + */ + +// 布局组件 +export { default as Card } from './card.vue' +export { default as Separator } from './separator.vue' + +// Tabs 选项卡系列 +export { default as Tabs } from './tabs.vue' +export { default as TabsContent } from './tabs-content.vue' +export { default as TabsList } from './tabs-list.vue' +export { default as TabsTrigger } from './tabs-trigger.vue' + +// 表单组件 +export { default as Button } from './button.vue' +export { default as Input } from './input.vue' +export { default as Textarea } from './textarea.vue' +export { default as Label } from './label.vue' +export { default as Checkbox } from './checkbox.vue' +export { default as Switch } from './switch.vue' + +// Select 选择器系列 +export { default as Select } from './select.vue' +export { default as SelectTrigger } from './select-trigger.vue' +export { default as SelectValue } from './select-value.vue' +export { default as SelectContent } from './select-content.vue' +export { default as SelectItem } from './select-item.vue' + +// 反馈组件 +export { default as Badge } from './badge.vue' +export { default as Skeleton } from './skeleton.vue' + +// Dialog 对话框系列 +export { default as Dialog } from './dialog/Dialog.vue' +export { default as DialogContent } from './dialog/DialogContent.vue' +export { default as DialogHeader } from './dialog/DialogHeader.vue' +export { default as DialogTitle } from './dialog/DialogTitle.vue' +export { default as DialogDescription } from './dialog/DialogDescription.vue' +export { default as DialogFooter } from './dialog/DialogFooter.vue' + +// Table 表格系列 +export { default as Table } from './table.vue' +export { default as TableBody } from './table-body.vue' +export { default as TableCell } from './table-cell.vue' +export { default as TableHead } from './table-head.vue' +export { default as TableHeader } from './table-header.vue' +export { default as TableRow } from './table-row.vue' +export { default as TableCard } from './table-card.vue' + +// Avatar 头像系列 +export { default as Avatar } from './avatar.vue' +export { default as AvatarFallback } from './avatar-fallback.vue' +export { default as AvatarImage } from './avatar-image.vue' + +// 分页组件 +export { default as Pagination } from './pagination.vue' + +// 操作按钮 +export { default as RefreshButton } from './refresh-button.vue' + +// Tooltip 提示系列 +export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip' diff --git a/frontend/src/components/ui/input.vue b/frontend/src/components/ui/input.vue new file mode 100644 index 0000000..18a49bf --- /dev/null +++ b/frontend/src/components/ui/input.vue @@ -0,0 +1,39 @@ + + + diff --git a/frontend/src/components/ui/label.vue b/frontend/src/components/ui/label.vue new file mode 100644 index 0000000..df83be4 --- /dev/null +++ b/frontend/src/components/ui/label.vue @@ -0,0 +1,23 @@ + + + diff --git a/frontend/src/components/ui/pagination.vue b/frontend/src/components/ui/pagination.vue new file mode 100644 index 0000000..fd35f9b --- /dev/null +++ b/frontend/src/components/ui/pagination.vue @@ -0,0 +1,172 @@ + + + diff --git a/frontend/src/components/ui/refresh-button.vue b/frontend/src/components/ui/refresh-button.vue new file mode 100644 index 0000000..8e7b085 --- /dev/null +++ b/frontend/src/components/ui/refresh-button.vue @@ -0,0 +1,37 @@ + + + diff --git a/frontend/src/components/ui/select-content.vue b/frontend/src/components/ui/select-content.vue new file mode 100644 index 0000000..62600cc --- /dev/null +++ b/frontend/src/components/ui/select-content.vue @@ -0,0 +1,52 @@ + + + diff --git a/frontend/src/components/ui/select-item.vue b/frontend/src/components/ui/select-item.vue new file mode 100644 index 0000000..e4f39fc --- /dev/null +++ b/frontend/src/components/ui/select-item.vue @@ -0,0 +1,34 @@ + + + diff --git a/frontend/src/components/ui/select-trigger.vue b/frontend/src/components/ui/select-trigger.vue new file mode 100644 index 0000000..5fbe730 --- /dev/null +++ b/frontend/src/components/ui/select-trigger.vue @@ -0,0 +1,31 @@ + + + diff --git a/frontend/src/components/ui/select-value.vue b/frontend/src/components/ui/select-value.vue new file mode 100644 index 0000000..42bd2ec --- /dev/null +++ b/frontend/src/components/ui/select-value.vue @@ -0,0 +1,15 @@ + + + diff --git a/frontend/src/components/ui/select.vue b/frontend/src/components/ui/select.vue new file mode 100644 index 0000000..f29d43c --- /dev/null +++ b/frontend/src/components/ui/select.vue @@ -0,0 +1,94 @@ + + + diff --git a/frontend/src/components/ui/separator.vue b/frontend/src/components/ui/separator.vue new file mode 100644 index 0000000..31cc059 --- /dev/null +++ b/frontend/src/components/ui/separator.vue @@ -0,0 +1,32 @@ + + + diff --git a/frontend/src/components/ui/skeleton.vue b/frontend/src/components/ui/skeleton.vue new file mode 100644 index 0000000..c66208a --- /dev/null +++ b/frontend/src/components/ui/skeleton.vue @@ -0,0 +1,18 @@ + + + diff --git a/frontend/src/components/ui/switch.vue b/frontend/src/components/ui/switch.vue new file mode 100644 index 0000000..e5cb476 --- /dev/null +++ b/frontend/src/components/ui/switch.vue @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/ui/table-body.vue b/frontend/src/components/ui/table-body.vue new file mode 100644 index 0000000..8417ac3 --- /dev/null +++ b/frontend/src/components/ui/table-body.vue @@ -0,0 +1,20 @@ + + + diff --git a/frontend/src/components/ui/table-card.vue b/frontend/src/components/ui/table-card.vue new file mode 100644 index 0000000..6f117b5 --- /dev/null +++ b/frontend/src/components/ui/table-card.vue @@ -0,0 +1,34 @@ + + + diff --git a/frontend/src/components/ui/table-cell.vue b/frontend/src/components/ui/table-cell.vue new file mode 100644 index 0000000..669ab6b --- /dev/null +++ b/frontend/src/components/ui/table-cell.vue @@ -0,0 +1,20 @@ + + + diff --git a/frontend/src/components/ui/table-head.vue b/frontend/src/components/ui/table-head.vue new file mode 100644 index 0000000..b53f329 --- /dev/null +++ b/frontend/src/components/ui/table-head.vue @@ -0,0 +1,23 @@ + + + diff --git a/frontend/src/components/ui/table-header.vue b/frontend/src/components/ui/table-header.vue new file mode 100644 index 0000000..619f599 --- /dev/null +++ b/frontend/src/components/ui/table-header.vue @@ -0,0 +1,20 @@ + + + diff --git a/frontend/src/components/ui/table-row.vue b/frontend/src/components/ui/table-row.vue new file mode 100644 index 0000000..d88ac29 --- /dev/null +++ b/frontend/src/components/ui/table-row.vue @@ -0,0 +1,23 @@ + + + diff --git a/frontend/src/components/ui/table.vue b/frontend/src/components/ui/table.vue new file mode 100644 index 0000000..1177d95 --- /dev/null +++ b/frontend/src/components/ui/table.vue @@ -0,0 +1,22 @@ + + + diff --git a/frontend/src/components/ui/tabs-content.vue b/frontend/src/components/ui/tabs-content.vue new file mode 100644 index 0000000..503dca2 --- /dev/null +++ b/frontend/src/components/ui/tabs-content.vue @@ -0,0 +1,31 @@ + + + diff --git a/frontend/src/components/ui/tabs-list.vue b/frontend/src/components/ui/tabs-list.vue new file mode 100644 index 0000000..c6372cd --- /dev/null +++ b/frontend/src/components/ui/tabs-list.vue @@ -0,0 +1,205 @@ + + + + + diff --git a/frontend/src/components/ui/tabs-trigger.vue b/frontend/src/components/ui/tabs-trigger.vue new file mode 100644 index 0000000..1d2e0fc --- /dev/null +++ b/frontend/src/components/ui/tabs-trigger.vue @@ -0,0 +1,42 @@ + + + diff --git a/frontend/src/components/ui/tabs.vue b/frontend/src/components/ui/tabs.vue new file mode 100644 index 0000000..2c57d5b --- /dev/null +++ b/frontend/src/components/ui/tabs.vue @@ -0,0 +1,35 @@ + + + diff --git a/frontend/src/components/ui/textarea.vue b/frontend/src/components/ui/textarea.vue new file mode 100644 index 0000000..3e43183 --- /dev/null +++ b/frontend/src/components/ui/textarea.vue @@ -0,0 +1,35 @@ +