diff --git a/.gitignore b/.gitignore index 59cf423..2c86f48 100644 --- a/.gitignore +++ b/.gitignore @@ -96,3 +96,75 @@ jspm_packages/ tmp/ temp/ .vercel + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +*.egg-info/ +.eggs/ +pip-log.txt +pip-delete-this-directory.txt +.pytest_cache/ +.coverage +htmlcov/ +.tox/ +.venv/ +venv/ +ENV/ +env/ + +# Backend specific +backend/.venv/ +backend/venv/ +backend/__pycache__/ +backend/*.pyc +backend/.pytest_cache/ +backend/.coverage +backend/alembic.ini.bak + +# Frontend specific +frontend/node_modules/ +frontend/dist/ +frontend/build/ +frontend/.vite/ +frontend/.next/ +frontend/.nuxt/ +frontend/.cache/ +frontend/.parcel-cache/ +frontend/.turbo/ +frontend/.svelte-kit/ + +# Database +*.db +*.sqlite +*.sqlite3 + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ +.project +.classpath +.settings/ + +# OS +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Secrets and keys +*.pem +*.key +*.cert +*.crt +secrets/ +.secrets/ diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..297e1e1 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,2 @@ +.venv/ +venv/ diff --git a/backend/.python-version b/backend/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/backend/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..5088842 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.11-slim + +WORKDIR /app + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +# Command is overridden by docker-compose for dev +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] + diff --git a/backend/README_UV.md b/backend/README_UV.md new file mode 100644 index 0000000..9acf9ad --- /dev/null +++ b/backend/README_UV.md @@ -0,0 +1,93 @@ +# 使用 uv 管理 Python 依赖 + +本项目已迁移到使用 [uv](https://github.com/astral-sh/uv) 作为 Python 依赖管理器。 + +## 快速开始 + +### 安装 uv + +```bash +# macOS/Linux +curl -LsSf https://astral.sh/uv/install.sh | sh + +# 或使用 Homebrew +brew install uv +``` + +### 安装依赖 + +```bash +cd backend +uv sync +``` + +这会自动创建虚拟环境并安装所有依赖。 + +### 运行项目 + +```bash +# 激活虚拟环境(uv 会自动管理) +uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload + +# 或使用 uv 直接运行 +uv run python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload +``` + +### 数据库迁移 + +```bash +uv run alembic upgrade head +``` + +### 添加新依赖 + +```bash +# 添加依赖 +uv add package-name + +# 添加开发依赖 +uv add --dev package-name +``` + +### 更新依赖 + +```bash +uv sync --upgrade +``` + +### 其他常用命令 + +```bash +# 查看已安装的包 +uv pip list + +# 运行 Python 脚本 +uv run python script.py + +# 运行 Alembic 命令 +uv run alembic +``` + +## 从 pip/venv 迁移 + +如果你之前使用 pip 和 venv: + +1. 删除旧的虚拟环境(可选): + ```bash + rm -rf venv + ``` + +2. 使用 uv 同步依赖: + ```bash + uv sync + ``` + +3. 之后使用 `uv run` 运行命令,或激活 uv 创建的虚拟环境。 + +## 优势 + +- **速度快**:比 pip 快 10-100 倍 +- **可复现**:自动生成锁文件 +- **简单**:一个命令管理所有依赖 +- **兼容**:完全兼容 pip 和 requirements.txt + diff --git a/backend/UV_MIGRATION.md b/backend/UV_MIGRATION.md new file mode 100644 index 0000000..8852511 --- /dev/null +++ b/backend/UV_MIGRATION.md @@ -0,0 +1,82 @@ +# 迁移到 uv 依赖管理器 + +## ✅ 已完成的工作 + +1. **初始化 uv 项目** + - 创建了 `pyproject.toml` 配置文件 + - 所有依赖已迁移到 `pyproject.toml` + +2. **依赖管理** + - 使用 `uv sync` 安装所有依赖 + - 创建了 `.venv` 虚拟环境(uv 自动管理) + - 生成了 `requirements-lock.txt` 锁定文件 + +3. **工具和脚本** + - 创建了 `start.sh` 启动脚本 + - 创建了 `README_UV.md` 使用文档 + +## 📝 使用方式 + +### 启动服务 + +```bash +cd backend +./start.sh +``` + +或手动启动: + +```bash +cd backend +uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload +``` + +### 数据库迁移 + +```bash +cd backend +uv run alembic upgrade head +``` + +### 添加新依赖 + +```bash +cd backend +uv add package-name +``` + +## 🔄 从旧环境迁移 + +如果你之前使用 `venv` 和 `pip`: + +1. **删除旧虚拟环境**(可选): + ```bash + rm -rf venv + ``` + +2. **使用 uv 同步**: + ```bash + uv sync + ``` + +3. **之后使用 `uv run` 运行所有命令** + +## 📦 依赖文件说明 + +- `pyproject.toml` - 项目配置和依赖声明(主要文件) +- `requirements.txt` - 保留用于兼容性,但建议使用 `pyproject.toml` +- `requirements-lock.txt` - 自动生成的锁定文件(确保可复现) + +## ⚠️ 注意事项 + +- uv 创建的虚拟环境在 `.venv/` 目录(不是 `venv/`) +- 使用 `uv run` 运行命令会自动使用正确的虚拟环境 +- 也可以手动激活:`source .venv/bin/activate` + +## 🎯 优势 + +- ⚡ **速度快**:比 pip 快 10-100 倍 +- 🔒 **可复现**:自动生成锁定文件 +- 🎯 **简单**:一个命令管理所有依赖 +- 🔄 **兼容**:完全兼容 pip 和 requirements.txt + diff --git a/backend/alembic.ini b/backend/alembic.ini new file mode 100644 index 0000000..40f0e9d --- /dev/null +++ b/backend/alembic.ini @@ -0,0 +1,103 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = alembic + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library to be installed. +# string value is passed to dateutil.tz.gettz() +# leave blank for local time +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; this defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" +# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# Logging configuration +[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/backend/alembic/env.py b/backend/alembic/env.py new file mode 100644 index 0000000..5758dc8 --- /dev/null +++ b/backend/alembic/env.py @@ -0,0 +1,90 @@ +import asyncio +from logging.config import fileConfig + +from sqlalchemy import pool +from sqlalchemy.engine import Connection +from sqlalchemy.ext.asyncio import async_engine_from_config + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +from app.db.base import Base +from app.models import * # noqa +from app.core.config import settings + +target_metadata = Base.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. +config.set_main_option("sqlalchemy.url", settings.DATABASE_URL) + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +async def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = async_engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + async with connectable.connect() as connection: + await connection.run_sync(do_run_migrations) + + await connectable.dispose() + + +def do_run_migrations(connection: Connection) -> None: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + asyncio.run(run_migrations_online()) + diff --git a/backend/alembic/script.py.mako b/backend/alembic/script.py.mako new file mode 100644 index 0000000..de46926 --- /dev/null +++ b/backend/alembic/script.py.mako @@ -0,0 +1,25 @@ +"""${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/backend/alembic/versions/001_initial.py b/backend/alembic/versions/001_initial.py new file mode 100644 index 0000000..d560021 --- /dev/null +++ b/backend/alembic/versions/001_initial.py @@ -0,0 +1,157 @@ +"""Initial migration - create all tables + +Revision ID: 001_initial +Revises: +Create Date: 2024-01-01 00:00:00.000000 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '001_initial' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Create users table + op.create_table( + 'users', + sa.Column('id', sa.String(), nullable=False), + sa.Column('email', sa.String(), nullable=False), + sa.Column('hashed_password', sa.String(), nullable=False), + sa.Column('full_name', sa.String(), nullable=True), + sa.Column('avatar_url', sa.String(), nullable=True), + sa.Column('is_active', sa.Boolean(), server_default='true', nullable=False), + sa.Column('is_superuser', sa.Boolean(), server_default='false', nullable=False), + sa.Column('role', sa.String(), server_default='user', nullable=False), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=True), + sa.Column('updated_at', sa.DateTime(timezone=True), onupdate=sa.func.now(), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email') + ) + op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True) + + # Create projects table + op.create_table( + 'projects', + sa.Column('id', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('repository_url', sa.String(), nullable=True), + sa.Column('repository_type', sa.String(), nullable=True), + sa.Column('default_branch', sa.String(), nullable=True), + sa.Column('programming_languages', sa.Text(), nullable=True), + sa.Column('owner_id', sa.String(), sa.ForeignKey('users.id'), nullable=False), + sa.Column('status', sa.String(), server_default='active', nullable=False), + sa.Column('is_deleted', sa.Boolean(), server_default='false', nullable=False), + sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=True), + sa.Column('updated_at', sa.DateTime(timezone=True), onupdate=sa.func.now(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_projects_owner_id'), 'projects', ['owner_id'], unique=False) + + # Create project_members table + op.create_table( + 'project_members', + sa.Column('id', sa.String(), nullable=False), + sa.Column('project_id', sa.String(), sa.ForeignKey('projects.id'), nullable=False), + sa.Column('user_id', sa.String(), sa.ForeignKey('users.id'), nullable=False), + sa.Column('role', sa.String(), server_default='member', nullable=False), + sa.Column('permissions', sa.Text(), server_default='{}', nullable=True), + sa.Column('joined_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + + # Create audit_tasks table + op.create_table( + 'audit_tasks', + sa.Column('id', sa.String(), nullable=False), + sa.Column('project_id', sa.String(), sa.ForeignKey('projects.id'), nullable=False), + sa.Column('created_by', sa.String(), sa.ForeignKey('users.id'), nullable=False), + sa.Column('task_type', sa.String(), nullable=False), + sa.Column('status', sa.String(), server_default='pending', nullable=False), + sa.Column('branch_name', sa.String(), nullable=True), + sa.Column('exclude_patterns', sa.Text(), server_default='[]', nullable=True), + sa.Column('scan_config', sa.Text(), server_default='{}', nullable=True), + sa.Column('total_files', sa.Integer(), server_default='0', nullable=False), + sa.Column('scanned_files', sa.Integer(), server_default='0', nullable=False), + sa.Column('total_lines', sa.Integer(), server_default='0', nullable=False), + sa.Column('issues_count', sa.Integer(), server_default='0', nullable=False), + sa.Column('quality_score', sa.Float(), server_default='0.0', nullable=False), + sa.Column('started_at', sa.DateTime(timezone=True), nullable=True), + sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_audit_tasks_status'), 'audit_tasks', ['status'], unique=False) + + # Create audit_issues table + op.create_table( + 'audit_issues', + sa.Column('id', sa.String(), nullable=False), + sa.Column('task_id', sa.String(), sa.ForeignKey('audit_tasks.id'), nullable=False), + sa.Column('file_path', sa.String(), nullable=False), + sa.Column('line_number', sa.Integer(), nullable=True), + sa.Column('column_number', sa.Integer(), nullable=True), + sa.Column('issue_type', sa.String(), nullable=False), + sa.Column('severity', sa.String(), nullable=False), + sa.Column('title', sa.String(), nullable=True), + sa.Column('message', sa.Text(), nullable=True), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('suggestion', sa.Text(), nullable=True), + sa.Column('code_snippet', sa.Text(), nullable=True), + sa.Column('ai_explanation', sa.Text(), nullable=True), + sa.Column('status', sa.String(), server_default='open', nullable=False), + sa.Column('resolved_by', sa.String(), sa.ForeignKey('users.id'), nullable=True), + sa.Column('resolved_at', sa.DateTime(timezone=True), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + + # Create instant_analyses table + op.create_table( + 'instant_analyses', + sa.Column('id', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), sa.ForeignKey('users.id'), nullable=True), + sa.Column('language', sa.String(), nullable=False), + sa.Column('code_content', sa.Text(), server_default='', nullable=True), + sa.Column('analysis_result', sa.Text(), server_default='{}', nullable=True), + sa.Column('issues_count', sa.Integer(), server_default='0', nullable=False), + sa.Column('quality_score', sa.Float(), server_default='0.0', nullable=False), + sa.Column('analysis_time', sa.Float(), server_default='0.0', nullable=False), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + + # Create user_configs table + op.create_table( + 'user_configs', + sa.Column('id', sa.String(), nullable=False), + sa.Column('user_id', sa.String(), sa.ForeignKey('users.id'), nullable=False), + sa.Column('llm_config', sa.Text(), server_default='{}', nullable=True), + sa.Column('other_config', sa.Text(), server_default='{}', nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=True), + sa.Column('updated_at', sa.DateTime(timezone=True), onupdate=sa.func.now(), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('user_id') + ) + + +def downgrade() -> None: + op.drop_table('user_configs') + op.drop_table('instant_analyses') + op.drop_table('audit_issues') + op.drop_index(op.f('ix_audit_tasks_status'), table_name='audit_tasks') + op.drop_table('audit_tasks') + op.drop_table('project_members') + op.drop_index(op.f('ix_projects_owner_id'), table_name='projects') + op.drop_table('projects') + op.drop_index(op.f('ix_users_email'), table_name='users') + op.drop_table('users') + diff --git a/backend/alembic/versions/5fc1cc05d5d0_add_missing_user_fields.py b/backend/alembic/versions/5fc1cc05d5d0_add_missing_user_fields.py new file mode 100644 index 0000000..f473b93 --- /dev/null +++ b/backend/alembic/versions/5fc1cc05d5d0_add_missing_user_fields.py @@ -0,0 +1,31 @@ +"""add_missing_user_fields + +Revision ID: 5fc1cc05d5d0 +Revises: 001_initial +Create Date: 2025-11-26 20:27:00.645441 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '5fc1cc05d5d0' +down_revision = '001_initial' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Add missing columns to users table + op.add_column('users', sa.Column('phone', sa.String(), nullable=True)) + op.add_column('users', sa.Column('github_username', sa.String(), nullable=True)) + op.add_column('users', sa.Column('gitlab_username', sa.String(), nullable=True)) + + +def downgrade() -> None: + # Remove added columns + op.drop_column('users', 'gitlab_username') + op.drop_column('users', 'github_username') + op.drop_column('users', 'phone') + diff --git a/backend/alembic/versions/73889a94a455_add_is_active_to_projects.py b/backend/alembic/versions/73889a94a455_add_is_active_to_projects.py new file mode 100644 index 0000000..1c5980d --- /dev/null +++ b/backend/alembic/versions/73889a94a455_add_is_active_to_projects.py @@ -0,0 +1,27 @@ +"""add_is_active_to_projects + +Revision ID: 73889a94a455 +Revises: 5fc1cc05d5d0 +Create Date: 2025-11-26 20:40:11.375161 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '73889a94a455' +down_revision = '5fc1cc05d5d0' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Add is_active column to projects table + op.add_column('projects', sa.Column('is_active', sa.Boolean(), server_default='true', nullable=False)) + + +def downgrade() -> None: + # Remove is_active column from projects table + op.drop_column('projects', 'is_active') + diff --git a/src/components/analysis/AnalysisProgressDialog.tsx b/backend/app/__init__.py similarity index 100% rename from src/components/analysis/AnalysisProgressDialog.tsx rename to backend/app/__init__.py diff --git a/src/pages/ColorDemo.tsx b/backend/app/api/__init__.py similarity index 100% rename from src/pages/ColorDemo.tsx rename to backend/app/api/__init__.py diff --git a/backend/app/api/deps.py b/backend/app/api/deps.py new file mode 100644 index 0000000..7a79533 --- /dev/null +++ b/backend/app/api/deps.py @@ -0,0 +1,50 @@ +from typing import Generator, Optional +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from jose import jwt, JWTError +from pydantic import ValidationError +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.future import select +from app.core import security +from app.core.config import settings +from app.db.session import get_db +from app.models.user import User +from app.schemas import token as token_schema + +reusable_oauth2 = OAuth2PasswordBearer( + tokenUrl=f"{settings.API_V1_STR}/auth/login" +) + +async def get_current_user( + db: AsyncSession = Depends(get_db), + token: str = Depends(reusable_oauth2) +) -> User: + try: + payload = jwt.decode( + token, settings.SECRET_KEY, algorithms=[security.ALGORITHM] + ) + token_data = token_schema.TokenPayload(**payload) + except (JWTError, ValidationError): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="无法验证凭据", + headers={"WWW-Authenticate": "Bearer"}, + ) + + result = await db.execute(select(User).where(User.id == token_data.sub)) + user = result.scalars().first() + + if not user: + raise HTTPException(status_code=404, detail="用户不存在") + if not user.is_active: + raise HTTPException(status_code=400, detail="用户已被禁用") + return user + +async def get_current_active_superuser( + current_user: User = Depends(get_current_user), +) -> User: + if not current_user.is_superuser: + raise HTTPException( + status_code=400, detail="权限不足" + ) + return current_user diff --git a/backend/app/api/v1/__init__.py b/backend/app/api/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/api/v1/api.py b/backend/app/api/v1/api.py new file mode 100644 index 0000000..643a81a --- /dev/null +++ b/backend/app/api/v1/api.py @@ -0,0 +1,12 @@ +from fastapi import APIRouter +from app.api.v1.endpoints import auth, users, projects, tasks, scan, members, config, database + +api_router = APIRouter() +api_router.include_router(auth.router, prefix="/auth", tags=["auth"]) +api_router.include_router(users.router, prefix="/users", tags=["users"]) +api_router.include_router(projects.router, prefix="/projects", tags=["projects"]) +api_router.include_router(members.router, prefix="/projects", tags=["members"]) +api_router.include_router(tasks.router, prefix="/tasks", tags=["tasks"]) +api_router.include_router(scan.router, prefix="/scan", tags=["scan"]) +api_router.include_router(config.router, prefix="/config", tags=["config"]) +api_router.include_router(database.router, prefix="/database", tags=["database"]) diff --git a/backend/app/api/v1/endpoints/__init__.py b/backend/app/api/v1/endpoints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/api/v1/endpoints/auth.py b/backend/app/api/v1/endpoints/auth.py new file mode 100644 index 0000000..506135b --- /dev/null +++ b/backend/app/api/v1/endpoints/auth.py @@ -0,0 +1,84 @@ +from datetime import timedelta +from typing import Any +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.security import OAuth2PasswordRequestForm +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.future import select +from pydantic import BaseModel, EmailStr + +from app.api import deps +from app.core import security +from app.core.config import settings +from app.db.session import get_db +from app.models.user import User +from app.schemas.token import Token +from app.schemas.user import User as UserSchema, UserCreate + +router = APIRouter() + +class RegisterRequest(BaseModel): + email: EmailStr + password: str + full_name: str + +@router.post("/login", response_model=Token) +async def login( + db: AsyncSession = Depends(get_db), + form_data: OAuth2PasswordRequestForm = Depends() +) -> Any: + """ + OAuth2 compatible token login, get an access token for future requests. + Username field should contain the email address. + """ + result = await db.execute(select(User).where(User.email == form_data.username)) + user = result.scalars().first() + + if not user or not security.verify_password(form_data.password, user.hashed_password): + raise HTTPException(status_code=400, detail="邮箱或密码错误") + elif not user.is_active: + raise HTTPException(status_code=400, detail="用户已被禁用") + + access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) + return { + "access_token": security.create_access_token( + user.id, expires_delta=access_token_expires + ), + "token_type": "bearer", + } + +@router.post("/register", response_model=UserSchema) +async def register( + *, + db: AsyncSession = Depends(get_db), + user_in: RegisterRequest, +) -> Any: + """ + Register a new user. + """ + # Check if user already exists + result = await db.execute(select(User).where(User.email == user_in.email)) + existing_user = result.scalars().first() + if existing_user: + raise HTTPException( + status_code=400, + detail="该邮箱已被注册", + ) + + # Check if this is the first user (make them admin) + count_result = await db.execute(select(User)) + all_users = count_result.scalars().all() + is_first_user = len(all_users) == 0 + + # Create new user + db_user = User( + email=user_in.email, + hashed_password=security.get_password_hash(user_in.password), + full_name=user_in.full_name, + is_active=True, + is_superuser=is_first_user, + role="admin" if is_first_user else "member", + ) + db.add(db_user) + await db.commit() + await db.refresh(db_user) + return db_user diff --git a/backend/app/api/v1/endpoints/config.py b/backend/app/api/v1/endpoints/config.py new file mode 100644 index 0000000..a599255 --- /dev/null +++ b/backend/app/api/v1/endpoints/config.py @@ -0,0 +1,218 @@ +""" +用户配置API端点 +""" + +from typing import Any, Optional +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.future import select +from pydantic import BaseModel +import json + +from app.api import deps +from app.db.session import get_db +from app.models.user_config import UserConfig +from app.models.user import User +from app.core.config import settings + +router = APIRouter() + + +class LLMConfigSchema(BaseModel): + """LLM配置Schema""" + llmProvider: Optional[str] = None + llmApiKey: Optional[str] = None + llmModel: Optional[str] = None + llmBaseUrl: Optional[str] = None + llmTimeout: Optional[int] = None + llmTemperature: Optional[float] = None + llmMaxTokens: Optional[int] = None + llmCustomHeaders: Optional[str] = None + + # 平台专用配置 + geminiApiKey: Optional[str] = None + openaiApiKey: Optional[str] = None + claudeApiKey: Optional[str] = None + qwenApiKey: Optional[str] = None + deepseekApiKey: Optional[str] = None + zhipuApiKey: Optional[str] = None + moonshotApiKey: Optional[str] = None + baiduApiKey: Optional[str] = None + minimaxApiKey: Optional[str] = None + doubaoApiKey: Optional[str] = None + ollamaBaseUrl: Optional[str] = None + + +class OtherConfigSchema(BaseModel): + """其他配置Schema""" + githubToken: Optional[str] = None + gitlabToken: Optional[str] = None + maxAnalyzeFiles: Optional[int] = None + llmConcurrency: Optional[int] = None + llmGapMs: Optional[int] = None + outputLanguage: Optional[str] = None + + +class UserConfigRequest(BaseModel): + """用户配置请求""" + llmConfig: Optional[LLMConfigSchema] = None + otherConfig: Optional[OtherConfigSchema] = None + + +class UserConfigResponse(BaseModel): + """用户配置响应""" + id: str + user_id: str + llmConfig: dict + otherConfig: dict + created_at: str + updated_at: Optional[str] = None + + class Config: + from_attributes = True + + +def get_default_config() -> dict: + """获取系统默认配置""" + return { + "llmConfig": { + "llmProvider": settings.LLM_PROVIDER, + "llmApiKey": "", + "llmModel": settings.LLM_MODEL or "", + "llmBaseUrl": settings.LLM_BASE_URL or "", + "llmTimeout": settings.LLM_TIMEOUT * 1000, # 转换为毫秒 + "llmTemperature": settings.LLM_TEMPERATURE, + "llmMaxTokens": settings.LLM_MAX_TOKENS, + "llmCustomHeaders": "", + "geminiApiKey": settings.GEMINI_API_KEY or "", + "openaiApiKey": settings.OPENAI_API_KEY or "", + "claudeApiKey": settings.CLAUDE_API_KEY or "", + "qwenApiKey": settings.QWEN_API_KEY or "", + "deepseekApiKey": settings.DEEPSEEK_API_KEY or "", + "zhipuApiKey": settings.ZHIPU_API_KEY or "", + "moonshotApiKey": settings.MOONSHOT_API_KEY or "", + "baiduApiKey": settings.BAIDU_API_KEY or "", + "minimaxApiKey": settings.MINIMAX_API_KEY or "", + "doubaoApiKey": settings.DOUBAO_API_KEY or "", + "ollamaBaseUrl": settings.OLLAMA_BASE_URL or "http://localhost:11434/v1", + }, + "otherConfig": { + "githubToken": settings.GITHUB_TOKEN or "", + "gitlabToken": settings.GITLAB_TOKEN or "", + "maxAnalyzeFiles": settings.MAX_ANALYZE_FILES, + "llmConcurrency": settings.LLM_CONCURRENCY, + "llmGapMs": settings.LLM_GAP_MS, + "outputLanguage": settings.OUTPUT_LANGUAGE, + } + } + + +@router.get("/defaults") +async def get_default_config_endpoint() -> Any: + """获取系统默认配置(无需认证)""" + return get_default_config() + + +@router.get("/me", response_model=UserConfigResponse) +async def get_my_config( + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """获取当前用户的配置(合并用户配置和系统默认配置)""" + result = await db.execute( + select(UserConfig).where(UserConfig.user_id == current_user.id) + ) + config = result.scalar_one_or_none() + + # 获取系统默认配置 + default_config = get_default_config() + + if not config: + # 返回系统默认配置 + return UserConfigResponse( + id="", + user_id=current_user.id, + llmConfig=default_config["llmConfig"], + otherConfig=default_config["otherConfig"], + created_at="", + ) + + # 合并用户配置和默认配置(用户配置优先) + user_llm_config = json.loads(config.llm_config) if config.llm_config else {} + user_other_config = json.loads(config.other_config) if config.other_config else {} + + merged_llm_config = {**default_config["llmConfig"], **user_llm_config} + merged_other_config = {**default_config["otherConfig"], **user_other_config} + + return UserConfigResponse( + id=config.id, + user_id=config.user_id, + llmConfig=merged_llm_config, + otherConfig=merged_other_config, + created_at=config.created_at.isoformat() if config.created_at else "", + updated_at=config.updated_at.isoformat() if config.updated_at else None, + ) + + +@router.put("/me", response_model=UserConfigResponse) +async def update_my_config( + config_in: UserConfigRequest, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """更新当前用户的配置""" + result = await db.execute( + select(UserConfig).where(UserConfig.user_id == current_user.id) + ) + config = result.scalar_one_or_none() + + if not config: + # 创建新配置 + config = UserConfig( + user_id=current_user.id, + llm_config=json.dumps(config_in.llmConfig.dict(exclude_none=True) if config_in.llmConfig else {}), + other_config=json.dumps(config_in.otherConfig.dict(exclude_none=True) if config_in.otherConfig else {}), + ) + db.add(config) + else: + # 更新现有配置 + if config_in.llmConfig: + existing_llm = json.loads(config.llm_config) if config.llm_config else {} + existing_llm.update(config_in.llmConfig.dict(exclude_none=True)) + config.llm_config = json.dumps(existing_llm) + + if config_in.otherConfig: + existing_other = json.loads(config.other_config) if config.other_config else {} + existing_other.update(config_in.otherConfig.dict(exclude_none=True)) + config.other_config = json.dumps(existing_other) + + await db.commit() + await db.refresh(config) + + return UserConfigResponse( + id=config.id, + user_id=config.user_id, + llmConfig=json.loads(config.llm_config) if config.llm_config else {}, + otherConfig=json.loads(config.other_config) if config.other_config else {}, + created_at=config.created_at.isoformat() if config.created_at else "", + updated_at=config.updated_at.isoformat() if config.updated_at else None, + ) + + +@router.delete("/me") +async def delete_my_config( + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """删除当前用户的配置(恢复为默认)""" + result = await db.execute( + select(UserConfig).where(UserConfig.user_id == current_user.id) + ) + config = result.scalar_one_or_none() + + if config: + await db.delete(config) + await db.commit() + + return {"message": "配置已删除"} + diff --git a/backend/app/api/v1/endpoints/database.py b/backend/app/api/v1/endpoints/database.py new file mode 100644 index 0000000..616b579 --- /dev/null +++ b/backend/app/api/v1/endpoints/database.py @@ -0,0 +1,692 @@ +""" +数据库管理API端点 +提供数据导出、导入、清空等功能 +""" + +from typing import Any, Dict, List +from fastapi import APIRouter, Depends, HTTPException, UploadFile, File +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.future import select +from sqlalchemy.orm import selectinload +from pydantic import BaseModel +import json +from datetime import datetime + +from app.api import deps +from app.db.session import get_db +from app.models.user import User +from app.models.project import Project, ProjectMember +from app.models.audit import AuditTask, AuditIssue +from app.models.analysis import InstantAnalysis +from app.models.user_config import UserConfig + +router = APIRouter() + + +class DatabaseExportResponse(BaseModel): + """数据库导出响应""" + export_date: str + user_id: str + data: Dict[str, Any] + + class Config: + from_attributes = True + + +@router.get("/export", response_model=DatabaseExportResponse) +async def export_database( + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + 导出当前用户的所有数据 + 包括:项目、任务、问题、即时分析、用户配置 + """ + try: + # 1. 获取用户的所有项目 + projects_result = await db.execute( + select(Project) + .where(Project.owner_id == current_user.id) + .options(selectinload(Project.tasks)) + ) + projects = projects_result.scalars().all() + + # 2. 获取用户的所有任务 + tasks_result = await db.execute( + select(AuditTask) + .where(AuditTask.created_by == current_user.id) + .options(selectinload(AuditTask.issues)) + ) + tasks = tasks_result.scalars().all() + + # 3. 获取用户的所有问题(通过任务关联) + task_ids = [task.id for task in tasks] + issues = [] + if task_ids: + issues_result = await db.execute( + select(AuditIssue) + .where(AuditIssue.task_id.in_(task_ids)) + ) + issues = issues_result.scalars().all() + + # 4. 获取用户的即时分析记录 + analyses_result = await db.execute( + select(InstantAnalysis) + .where(InstantAnalysis.user_id == current_user.id) + ) + analyses = analyses_result.scalars().all() + + # 5. 获取用户的配置 + config_result = await db.execute( + select(UserConfig) + .where(UserConfig.user_id == current_user.id) + ) + config = config_result.scalar_one_or_none() + + # 6. 获取用户参与的项目(作为成员) + members_result = await db.execute( + select(ProjectMember) + .where(ProjectMember.user_id == current_user.id) + .options(selectinload(ProjectMember.project)) + ) + members = members_result.scalars().all() + + # 7. 构建导出数据 + export_data = { + "version": "1.0.0", + "export_date": datetime.utcnow().isoformat(), + "user": { + "id": current_user.id, + "email": current_user.email, + "full_name": current_user.full_name, + }, + "projects": [ + { + "id": p.id, + "name": p.name, + "description": p.description, + "repository_url": p.repository_url, + "repository_type": p.repository_type, + "default_branch": p.default_branch, + "programming_languages": json.loads(p.programming_languages) if p.programming_languages else [], + "is_active": p.is_active, + "created_at": p.created_at.isoformat() if p.created_at else None, + "updated_at": p.updated_at.isoformat() if p.updated_at else None, + } + for p in projects + ], + "tasks": [ + { + "id": t.id, + "project_id": t.project_id, + "task_type": t.task_type, + "status": t.status, + "branch_name": t.branch_name, + "exclude_patterns": json.loads(t.exclude_patterns) if t.exclude_patterns else [], + "total_files": t.total_files, + "scanned_files": t.scanned_files, + "total_lines": t.total_lines, + "issues_count": t.issues_count, + "quality_score": t.quality_score, + "started_at": t.started_at.isoformat() if t.started_at else None, + "completed_at": t.completed_at.isoformat() if t.completed_at else None, + "created_at": t.created_at.isoformat() if t.created_at else None, + } + for t in tasks + ], + "issues": [ + { + "id": i.id, + "task_id": i.task_id, + "file_path": i.file_path, + "line_number": i.line_number, + "column_number": i.column_number, + "issue_type": i.issue_type, + "severity": i.severity, + "title": i.title, + "message": i.message, + "description": i.description, + "suggestion": i.suggestion, + "code_snippet": i.code_snippet, + "ai_explanation": i.ai_explanation, + "status": i.status, + "created_at": i.created_at.isoformat() if i.created_at else None, + } + for i in issues + ], + "instant_analyses": [ + { + "id": a.id, + "language": a.language, + "issues_count": a.issues_count, + "quality_score": a.quality_score, + "analysis_time": a.analysis_time, + "created_at": a.created_at.isoformat() if a.created_at else None, + } + for a in analyses + ], + "user_config": { + "llm_config": json.loads(config.llm_config) if config and config.llm_config else {}, + "other_config": json.loads(config.other_config) if config and config.other_config else {}, + } if config else {}, + "project_members": [ + { + "id": m.id, + "project_id": m.project_id, + "role": m.role, + "permissions": json.loads(m.permissions) if m.permissions else {}, + "joined_at": m.joined_at.isoformat() if m.joined_at else None, + } + for m in members + ], + } + + return DatabaseExportResponse( + export_date=export_data["export_date"], + user_id=current_user.id, + data=export_data + ) + + except Exception as e: + print(f"导出数据失败: {e}") + raise HTTPException(status_code=500, detail=f"导出数据失败: {str(e)}") + + +class DatabaseImportRequest(BaseModel): + """数据库导入请求""" + data: Dict[str, Any] + + +@router.post("/import") +async def import_database( + file: UploadFile = File(...), + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + 从 JSON 文件导入数据 + 注意:导入会合并数据,不会删除现有数据 + """ + try: + # 读取文件内容 + content = await file.read() + import_data = json.loads(content.decode('utf-8')) + + if not isinstance(import_data, dict) or "data" not in import_data: + raise HTTPException(status_code=400, detail="无效的导入文件格式") + + data = import_data["data"] + + # 验证用户ID(只能导入自己的数据) + if data.get("user", {}).get("id") != current_user.id: + raise HTTPException(status_code=403, detail="只能导入自己的数据") + + imported_count = { + "projects": 0, + "tasks": 0, + "issues": 0, + "analyses": 0, + "config": 0, + } + + # 1. 导入项目(跳过已存在的) + if "projects" in data: + for p_data in data["projects"]: + existing = await db.get(Project, p_data.get("id")) + if not existing: + project = Project( + id=p_data.get("id"), + name=p_data.get("name"), + description=p_data.get("description"), + repository_url=p_data.get("repository_url"), + repository_type=p_data.get("repository_type"), + default_branch=p_data.get("default_branch"), + programming_languages=json.dumps(p_data.get("programming_languages", [])), + owner_id=current_user.id, + is_active=p_data.get("is_active", True) if "is_active" in p_data else (p_data.get("status") != "inactive" if "status" in p_data else True), + ) + db.add(project) + imported_count["projects"] += 1 + + await db.commit() + + # 2. 导入任务(需要先有项目) + if "tasks" in data: + for t_data in data["tasks"]: + existing = await db.get(AuditTask, t_data.get("id")) + if not existing: + # 检查项目是否存在 + project = await db.get(Project, t_data.get("project_id")) + if project: + task = AuditTask( + id=t_data.get("id"), + project_id=t_data.get("project_id"), + created_by=current_user.id, + task_type=t_data.get("task_type"), + status=t_data.get("status", "pending"), + branch_name=t_data.get("branch_name"), + exclude_patterns=json.dumps(t_data.get("exclude_patterns", [])), + scan_config=json.dumps(t_data.get("scan_config", {})), + total_files=t_data.get("total_files", 0), + scanned_files=t_data.get("scanned_files", 0), + total_lines=t_data.get("total_lines", 0), + issues_count=t_data.get("issues_count", 0), + quality_score=t_data.get("quality_score", 0.0), + ) + db.add(task) + imported_count["tasks"] += 1 + + await db.commit() + + # 3. 导入问题(需要先有任务) + if "issues" in data: + for i_data in data["issues"]: + existing = await db.get(AuditIssue, i_data.get("id")) + if not existing: + # 检查任务是否存在 + task = await db.get(AuditTask, i_data.get("task_id")) + if task: + issue = AuditIssue( + id=i_data.get("id"), + task_id=i_data.get("task_id"), + file_path=i_data.get("file_path"), + line_number=i_data.get("line_number"), + column_number=i_data.get("column_number"), + issue_type=i_data.get("issue_type"), + severity=i_data.get("severity"), + title=i_data.get("title"), + message=i_data.get("message"), + description=i_data.get("description"), + suggestion=i_data.get("suggestion"), + code_snippet=i_data.get("code_snippet"), + ai_explanation=i_data.get("ai_explanation"), + status=i_data.get("status", "open"), + ) + db.add(issue) + imported_count["issues"] += 1 + + await db.commit() + + # 4. 导入即时分析 + if "instant_analyses" in data: + for a_data in data["instant_analyses"]: + existing = await db.get(InstantAnalysis, a_data.get("id")) + if not existing: + analysis = InstantAnalysis( + id=a_data.get("id"), + user_id=current_user.id, + language=a_data.get("language"), + code_content="", + analysis_result=json.dumps(a_data.get("analysis_result", {})), + issues_count=a_data.get("issues_count", 0), + quality_score=a_data.get("quality_score", 0.0), + analysis_time=a_data.get("analysis_time", 0.0), + ) + db.add(analysis) + imported_count["analyses"] += 1 + + await db.commit() + + # 5. 导入用户配置(合并) + if "user_config" in data and data["user_config"]: + config_result = await db.execute( + select(UserConfig) + .where(UserConfig.user_id == current_user.id) + ) + config = config_result.scalar_one_or_none() + + if not config: + config = UserConfig( + user_id=current_user.id, + llm_config=json.dumps(data["user_config"].get("llm_config", {})), + other_config=json.dumps(data["user_config"].get("other_config", {})), + ) + db.add(config) + else: + # 合并配置 + existing_llm = json.loads(config.llm_config) if config.llm_config else {} + existing_other = json.loads(config.other_config) if config.other_config else {} + existing_llm.update(data["user_config"].get("llm_config", {})) + existing_other.update(data["user_config"].get("other_config", {})) + config.llm_config = json.dumps(existing_llm) + config.other_config = json.dumps(existing_other) + + imported_count["config"] = 1 + + await db.commit() + + return { + "message": "数据导入成功", + "imported": imported_count + } + + except json.JSONDecodeError: + raise HTTPException(status_code=400, detail="无效的 JSON 文件格式") + except Exception as e: + print(f"导入数据失败: {e}") + await db.rollback() + raise HTTPException(status_code=500, detail=f"导入数据失败: {str(e)}") + + +@router.delete("/clear") +async def clear_database( + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + 清空当前用户的所有数据 + 注意:此操作不可恢复,请谨慎使用 + """ + try: + deleted_count = { + "projects": 0, + "tasks": 0, + "issues": 0, + "analyses": 0, + "config": 0, + } + + # 1. 删除用户的所有问题(通过任务) + tasks_result = await db.execute( + select(AuditTask) + .where(AuditTask.created_by == current_user.id) + ) + tasks = tasks_result.scalars().all() + task_ids = [task.id for task in tasks] + + if task_ids: + issues_result = await db.execute( + select(AuditIssue) + .where(AuditIssue.task_id.in_(task_ids)) + ) + issues = issues_result.scalars().all() + for issue in issues: + await db.delete(issue) + deleted_count["issues"] = len(issues) + + # 2. 删除用户的所有任务 + for task in tasks: + await db.delete(task) + deleted_count["tasks"] = len(tasks) + + # 3. 删除用户的所有项目 + projects_result = await db.execute( + select(Project) + .where(Project.owner_id == current_user.id) + ) + projects = projects_result.scalars().all() + for project in projects: + await db.delete(project) + deleted_count["projects"] = len(projects) + + # 4. 删除用户的即时分析 + analyses_result = await db.execute( + select(InstantAnalysis) + .where(InstantAnalysis.user_id == current_user.id) + ) + analyses = analyses_result.scalars().all() + for analysis in analyses: + await db.delete(analysis) + deleted_count["analyses"] = len(analyses) + + # 5. 删除用户配置 + config_result = await db.execute( + select(UserConfig) + .where(UserConfig.user_id == current_user.id) + ) + config = config_result.scalar_one_or_none() + if config: + await db.delete(config) + deleted_count["config"] = 1 + + # 6. 删除用户的项目成员关系(作为成员) + members_result = await db.execute( + select(ProjectMember) + .where(ProjectMember.user_id == current_user.id) + ) + members = members_result.scalars().all() + for member in members: + await db.delete(member) + + await db.commit() + + return { + "message": "数据已清空", + "deleted": deleted_count + } + + except Exception as e: + print(f"清空数据失败: {e}") + await db.rollback() + raise HTTPException(status_code=500, detail=f"清空数据失败: {str(e)}") + + +class DatabaseStatsResponse(BaseModel): + """数据库统计信息响应""" + total_projects: int + active_projects: int + total_tasks: int + completed_tasks: int + pending_tasks: int + running_tasks: int + failed_tasks: int + total_issues: int + open_issues: int + resolved_issues: int + critical_issues: int + high_issues: int + medium_issues: int + low_issues: int + total_analyses: int + total_members: int + has_config: bool + + +@router.get("/stats", response_model=DatabaseStatsResponse) +async def get_database_stats( + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + 获取当前用户的数据库统计信息 + """ + try: + # 1. 项目统计 + projects_result = await db.execute( + select(Project) + .where(Project.owner_id == current_user.id) + ) + projects = projects_result.scalars().all() + total_projects = len(projects) + active_projects = len([p for p in projects if p.is_active]) + + # 2. 任务统计 + tasks_result = await db.execute( + select(AuditTask) + .where(AuditTask.created_by == current_user.id) + ) + tasks = tasks_result.scalars().all() + total_tasks = len(tasks) + completed_tasks = len([t for t in tasks if t.status == "completed"]) + pending_tasks = len([t for t in tasks if t.status == "pending"]) + running_tasks = len([t for t in tasks if t.status == "running"]) + failed_tasks = len([t for t in tasks if t.status == "failed"]) + + # 3. 问题统计 + task_ids = [task.id for task in tasks] + total_issues = 0 + open_issues = 0 + resolved_issues = 0 + critical_issues = 0 + high_issues = 0 + medium_issues = 0 + low_issues = 0 + + if task_ids: + issues_result = await db.execute( + select(AuditIssue) + .where(AuditIssue.task_id.in_(task_ids)) + ) + issues = issues_result.scalars().all() + total_issues = len(issues) + open_issues = len([i for i in issues if i.status == "open"]) + resolved_issues = len([i for i in issues if i.status == "resolved"]) + critical_issues = len([i for i in issues if i.severity == "critical"]) + high_issues = len([i for i in issues if i.severity == "high"]) + medium_issues = len([i for i in issues if i.severity == "medium"]) + low_issues = len([i for i in issues if i.severity == "low"]) + + # 4. 即时分析统计 + analyses_result = await db.execute( + select(InstantAnalysis) + .where(InstantAnalysis.user_id == current_user.id) + ) + analyses = analyses_result.scalars().all() + total_analyses = len(analyses) + + # 5. 项目成员统计 + members_result = await db.execute( + select(ProjectMember) + .where(ProjectMember.user_id == current_user.id) + ) + members = members_result.scalars().all() + total_members = len(members) + + # 6. 配置检查 + config_result = await db.execute( + select(UserConfig) + .where(UserConfig.user_id == current_user.id) + ) + has_config = config_result.scalar_one_or_none() is not None + + return DatabaseStatsResponse( + total_projects=total_projects, + active_projects=active_projects, + total_tasks=total_tasks, + completed_tasks=completed_tasks, + pending_tasks=pending_tasks, + running_tasks=running_tasks, + failed_tasks=failed_tasks, + total_issues=total_issues, + open_issues=open_issues, + resolved_issues=resolved_issues, + critical_issues=critical_issues, + high_issues=high_issues, + medium_issues=medium_issues, + low_issues=low_issues, + total_analyses=total_analyses, + total_members=total_members, + has_config=has_config, + ) + + except Exception as e: + print(f"获取统计信息失败: {e}") + raise HTTPException(status_code=500, detail=f"获取统计信息失败: {str(e)}") + + +class DatabaseHealthResponse(BaseModel): + """数据库健康检查响应""" + status: str # healthy, warning, error + database_connected: bool + total_records: int + last_backup_date: str | None + issues: List[str] + warnings: List[str] + + +@router.get("/health", response_model=DatabaseHealthResponse) +async def check_database_health( + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + 检查数据库健康状态 + """ + try: + issues = [] + warnings = [] + database_connected = True + total_records = 0 + last_backup_date = None + + # 1. 检查数据库连接 + try: + await db.execute(select(1)) + except Exception as e: + database_connected = False + issues.append(f"数据库连接失败: {str(e)}") + + if database_connected: + # 2. 统计总记录数 + try: + projects_count = len((await db.execute( + select(Project).where(Project.owner_id == current_user.id) + )).scalars().all()) + + tasks_count = len((await db.execute( + select(AuditTask).where(AuditTask.created_by == current_user.id) + )).scalars().all()) + + analyses_count = len((await db.execute( + select(InstantAnalysis).where(InstantAnalysis.user_id == current_user.id) + )).scalars().all()) + + total_records = projects_count + tasks_count + analyses_count + except Exception as e: + warnings.append(f"统计记录数时出错: {str(e)}") + + # 3. 检查数据完整性 + try: + # 检查孤立的任务(项目不存在) + tasks_result = await db.execute( + select(AuditTask).where(AuditTask.created_by == current_user.id) + ) + tasks = tasks_result.scalars().all() + orphan_tasks = 0 + for task in tasks: + project = await db.get(Project, task.project_id) + if not project: + orphan_tasks += 1 + + if orphan_tasks > 0: + warnings.append(f"发现 {orphan_tasks} 个孤立任务(关联的项目不存在)") + + # 检查孤立的问题(任务不存在) + if tasks: + task_ids = [task.id for task in tasks] + issues_result = await db.execute( + select(AuditIssue).where(AuditIssue.task_id.in_(task_ids)) + ) + issues_list = issues_result.scalars().all() + orphan_issues = 0 + for issue in issues_list: + task = await db.get(AuditTask, issue.task_id) + if not task: + orphan_issues += 1 + + if orphan_issues > 0: + warnings.append(f"发现 {orphan_issues} 个孤立问题(关联的任务不存在)") + except Exception as e: + warnings.append(f"数据完整性检查时出错: {str(e)}") + + # 4. 确定健康状态 + if not database_connected or issues: + status = "error" + elif warnings: + status = "warning" + else: + status = "healthy" + + return DatabaseHealthResponse( + status=status, + database_connected=database_connected, + total_records=total_records, + last_backup_date=last_backup_date, + issues=issues, + warnings=warnings, + ) + + except Exception as e: + print(f"健康检查失败: {e}") + raise HTTPException(status_code=500, detail=f"健康检查失败: {str(e)}") + diff --git a/backend/app/api/v1/endpoints/members.py b/backend/app/api/v1/endpoints/members.py new file mode 100644 index 0000000..2ccb7eb --- /dev/null +++ b/backend/app/api/v1/endpoints/members.py @@ -0,0 +1,210 @@ +from typing import Any, List, Optional +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.future import select +from sqlalchemy.orm import selectinload +from pydantic import BaseModel +from datetime import datetime + +from app.api import deps +from app.db.session import get_db +from app.models.project import Project, ProjectMember +from app.models.user import User + +router = APIRouter() + + +# Schemas +class UserSchema(BaseModel): + id: str + email: Optional[str] = None + full_name: Optional[str] = None + avatar_url: Optional[str] = None + role: Optional[str] = None + + class Config: + from_attributes = True + + +class ProjectMemberSchema(BaseModel): + id: str + project_id: str + user_id: str + role: str + permissions: Optional[str] = None + joined_at: datetime + created_at: datetime + user: Optional[UserSchema] = None + + class Config: + from_attributes = True + + +class AddMemberRequest(BaseModel): + user_id: str + role: str = "member" + + +class UpdateMemberRequest(BaseModel): + role: Optional[str] = None + permissions: Optional[str] = None + + +@router.get("/{project_id}/members", response_model=List[ProjectMemberSchema]) +async def get_project_members( + project_id: str, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Get all members of a project. + """ + # Verify project exists + project = await db.get(Project, project_id) + if not project: + raise HTTPException(status_code=404, detail="项目不存在") + + result = await db.execute( + select(ProjectMember) + .options(selectinload(ProjectMember.user)) + .where(ProjectMember.project_id == project_id) + .order_by(ProjectMember.joined_at.desc()) + ) + return result.scalars().all() + + +@router.post("/{project_id}/members", response_model=ProjectMemberSchema) +async def add_project_member( + project_id: str, + member_in: AddMemberRequest, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Add a member to a project. + """ + # Verify project exists + project = await db.get(Project, project_id) + if not project: + raise HTTPException(status_code=404, detail="项目不存在") + + # Check if user is project owner or admin + if project.owner_id != current_user.id and not current_user.is_superuser: + raise HTTPException(status_code=403, detail="权限不足") + + # Check if user exists + user = await db.get(User, member_in.user_id) + if not user: + raise HTTPException(status_code=404, detail="用户不存在") + + # Check if already a member + existing = await db.execute( + select(ProjectMember) + .where( + ProjectMember.project_id == project_id, + ProjectMember.user_id == member_in.user_id + ) + ) + if existing.scalars().first(): + raise HTTPException(status_code=400, detail="用户已是项目成员") + + # Create member + member = ProjectMember( + project_id=project_id, + user_id=member_in.user_id, + role=member_in.role, + permissions="{}" + ) + db.add(member) + await db.commit() + await db.refresh(member) + + # Reload with user relationship + result = await db.execute( + select(ProjectMember) + .options(selectinload(ProjectMember.user)) + .where(ProjectMember.id == member.id) + ) + return result.scalars().first() + + +@router.put("/{project_id}/members/{member_id}", response_model=ProjectMemberSchema) +async def update_project_member( + project_id: str, + member_id: str, + member_update: UpdateMemberRequest, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Update a project member's role or permissions. + """ + # Verify project exists + project = await db.get(Project, project_id) + if not project: + raise HTTPException(status_code=404, detail="项目不存在") + + # Check permissions + if project.owner_id != current_user.id and not current_user.is_superuser: + raise HTTPException(status_code=403, detail="权限不足") + + # Get member + result = await db.execute( + select(ProjectMember) + .where(ProjectMember.id == member_id, ProjectMember.project_id == project_id) + ) + member = result.scalars().first() + if not member: + raise HTTPException(status_code=404, detail="成员不存在") + + # Update fields + if member_update.role: + member.role = member_update.role + if member_update.permissions: + member.permissions = member_update.permissions + + await db.commit() + await db.refresh(member) + + # Reload with user relationship + result = await db.execute( + select(ProjectMember) + .options(selectinload(ProjectMember.user)) + .where(ProjectMember.id == member.id) + ) + return result.scalars().first() + + +@router.delete("/{project_id}/members/{member_id}") +async def remove_project_member( + project_id: str, + member_id: str, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Remove a member from a project. + """ + # Verify project exists + project = await db.get(Project, project_id) + if not project: + raise HTTPException(status_code=404, detail="项目不存在") + + # Check permissions + if project.owner_id != current_user.id and not current_user.is_superuser: + raise HTTPException(status_code=403, detail="权限不足") + + # Get member + result = await db.execute( + select(ProjectMember) + .where(ProjectMember.id == member_id, ProjectMember.project_id == project_id) + ) + member = result.scalars().first() + if not member: + raise HTTPException(status_code=404, detail="成员不存在") + + await db.delete(member) + await db.commit() + + return {"message": "成员已移除"} + diff --git a/backend/app/api/v1/endpoints/projects.py b/backend/app/api/v1/endpoints/projects.py new file mode 100644 index 0000000..3f19e63 --- /dev/null +++ b/backend/app/api/v1/endpoints/projects.py @@ -0,0 +1,315 @@ +from typing import Any, List, Optional +from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.future import select +from sqlalchemy.orm import selectinload +from pydantic import BaseModel +from datetime import datetime + +from app.api import deps +from app.db.session import get_db, AsyncSessionLocal +from app.models.project import Project +from app.models.user import User +from app.models.audit import AuditTask, AuditIssue +from app.models.user_config import UserConfig +from app.services.scanner import scan_repo_task + +router = APIRouter() + +# Schemas +class ProjectCreate(BaseModel): + name: str + repository_url: Optional[str] = None + repository_type: Optional[str] = "other" + description: Optional[str] = None + default_branch: Optional[str] = "main" + programming_languages: Optional[List[str]] = None + +class ProjectUpdate(BaseModel): + name: Optional[str] = None + repository_url: Optional[str] = None + repository_type: Optional[str] = None + description: Optional[str] = None + default_branch: Optional[str] = None + programming_languages: Optional[List[str]] = None + +class OwnerSchema(BaseModel): + id: str + email: Optional[str] = None + full_name: Optional[str] = None + avatar_url: Optional[str] = None + role: Optional[str] = None + + class Config: + from_attributes = True + +class ProjectResponse(BaseModel): + id: str + name: str + description: Optional[str] = None + repository_url: Optional[str] = None + repository_type: Optional[str] = None + default_branch: Optional[str] = None + programming_languages: Optional[str] = None + owner_id: str + is_active: bool + created_at: datetime + updated_at: Optional[datetime] = None + owner: Optional[OwnerSchema] = None + + class Config: + from_attributes = True + +class StatsResponse(BaseModel): + total_projects: int + active_projects: int + total_tasks: int + completed_tasks: int + total_issues: int + resolved_issues: int + +@router.post("/", response_model=ProjectResponse) +async def create_project( + *, + db: AsyncSession = Depends(get_db), + project_in: ProjectCreate, + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Create new project. + """ + import json + project = Project( + name=project_in.name, + repository_url=project_in.repository_url, + repository_type=project_in.repository_type or "other", + description=project_in.description, + default_branch=project_in.default_branch or "main", + programming_languages=json.dumps(project_in.programming_languages or []), + owner_id=current_user.id + ) + db.add(project) + await db.commit() + await db.refresh(project) + return project + +@router.get("/", response_model=List[ProjectResponse]) +async def read_projects( + db: AsyncSession = Depends(get_db), + skip: int = 0, + limit: int = 100, + include_deleted: bool = False, + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Retrieve projects. + """ + query = select(Project).options(selectinload(Project.owner)) + if not include_deleted: + query = query.where(Project.is_active == True) + query = query.order_by(Project.created_at.desc()).offset(skip).limit(limit) + result = await db.execute(query) + return result.scalars().all() + +@router.get("/deleted", response_model=List[ProjectResponse]) +async def read_deleted_projects( + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Retrieve deleted (soft-deleted) projects for current user. + """ + result = await db.execute( + select(Project) + .options(selectinload(Project.owner)) + .where(Project.owner_id == current_user.id) + .where(Project.is_active == False) + .order_by(Project.updated_at.desc()) + ) + return result.scalars().all() + +@router.get("/stats", response_model=StatsResponse) +async def get_stats( + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Get overall statistics. + """ + projects_result = await db.execute(select(Project)) + projects = projects_result.scalars().all() + + tasks_result = await db.execute(select(AuditTask)) + tasks = tasks_result.scalars().all() + + issues_result = await db.execute(select(AuditIssue)) + issues = issues_result.scalars().all() + + return { + "total_projects": len(projects), + "active_projects": len([p for p in projects if p.is_active]), + "total_tasks": len(tasks), + "completed_tasks": len([t for t in tasks if t.status == "completed"]), + "total_issues": len(issues), + "resolved_issues": len([i for i in issues if i.status == "resolved"]), + } + +@router.get("/{id}", response_model=ProjectResponse) +async def read_project( + id: str, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Get project by ID. + """ + result = await db.execute( + select(Project) + .options(selectinload(Project.owner)) + .where(Project.id == id) + ) + project = result.scalars().first() + if not project: + raise HTTPException(status_code=404, detail="项目不存在") + return project + +@router.put("/{id}", response_model=ProjectResponse) +async def update_project( + id: str, + *, + db: AsyncSession = Depends(get_db), + project_in: ProjectUpdate, + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Update project. + """ + import json + result = await db.execute(select(Project).where(Project.id == id)) + project = result.scalars().first() + if not project: + raise HTTPException(status_code=404, detail="项目不存在") + + update_data = project_in.model_dump(exclude_unset=True) + if "programming_languages" in update_data and update_data["programming_languages"] is not None: + update_data["programming_languages"] = json.dumps(update_data["programming_languages"]) + + for field, value in update_data.items(): + setattr(project, field, value) + + project.updated_at = datetime.utcnow() + await db.commit() + await db.refresh(project) + return project + +@router.delete("/{id}") +async def delete_project( + id: str, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Soft delete project. + """ + result = await db.execute(select(Project).where(Project.id == id)) + project = result.scalars().first() + if not project: + raise HTTPException(status_code=404, detail="项目不存在") + + # 检查权限:只有项目所有者可以删除 + if project.owner_id != current_user.id: + raise HTTPException(status_code=403, detail="无权删除此项目") + + project.is_active = False + project.updated_at = datetime.utcnow() + await db.commit() + return {"message": "项目已删除"} + +@router.post("/{id}/restore") +async def restore_project( + id: str, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Restore soft-deleted project. + """ + result = await db.execute(select(Project).where(Project.id == id)) + project = result.scalars().first() + if not project: + raise HTTPException(status_code=404, detail="项目不存在") + + # 检查权限:只有项目所有者可以恢复 + if project.owner_id != current_user.id: + raise HTTPException(status_code=403, detail="无权恢复此项目") + + project.is_active = True + project.updated_at = datetime.utcnow() + await db.commit() + return {"message": "项目已恢复"} + +@router.delete("/{id}/permanent") +async def permanently_delete_project( + id: str, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Permanently delete project. + """ + result = await db.execute(select(Project).where(Project.id == id)) + project = result.scalars().first() + if not project: + raise HTTPException(status_code=404, detail="项目不存在") + + # 检查权限:只有项目所有者可以永久删除 + if project.owner_id != current_user.id: + raise HTTPException(status_code=403, detail="无权永久删除此项目") + + await db.delete(project) + await db.commit() + return {"message": "项目已永久删除"} + +@router.post("/{id}/scan") +async def scan_project( + id: str, + background_tasks: BackgroundTasks, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Start a scan task. + """ + project = await db.get(Project, id) + if not project: + raise HTTPException(status_code=404, detail="项目不存在") + + # Create Task Record + task = AuditTask( + project_id=project.id, + created_by=current_user.id, + task_type="repository", + status="pending" + ) + db.add(task) + await db.commit() + await db.refresh(task) + + # 获取用户配置 + from sqlalchemy.future import select + import json + result = await db.execute( + select(UserConfig).where(UserConfig.user_id == current_user.id) + ) + config = result.scalar_one_or_none() + user_config = {} + if config: + user_config = { + 'llmConfig': json.loads(config.llm_config) if config.llm_config else {}, + 'otherConfig': json.loads(config.other_config) if config.other_config else {}, + } + + # Trigger Background Task + background_tasks.add_task(scan_repo_task, task.id, AsyncSessionLocal, user_config) + + return {"task_id": task.id, "status": "started"} diff --git a/backend/app/api/v1/endpoints/scan.py b/backend/app/api/v1/endpoints/scan.py new file mode 100644 index 0000000..d355f61 --- /dev/null +++ b/backend/app/api/v1/endpoints/scan.py @@ -0,0 +1,316 @@ +from fastapi import APIRouter, UploadFile, File, Depends, BackgroundTasks, HTTPException +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.future import select +from typing import Any, List, Optional +from pydantic import BaseModel +from datetime import datetime +import uuid +import shutil +import os +import json +from pathlib import Path +import zipfile +import asyncio + +from app.api import deps +from app.db.session import get_db, AsyncSessionLocal +from app.models.audit import AuditTask, AuditIssue +from app.models.user import User +from app.models.project import Project +from app.models.analysis import InstantAnalysis +from app.models.user_config import UserConfig +from app.services.llm.service import LLMService +from app.services.scanner import task_control, is_text_file, should_exclude, get_language_from_path +from app.core.config import settings + +router = APIRouter() + + +# 支持的文件扩展名 +TEXT_EXTENSIONS = [ + ".js", ".ts", ".tsx", ".jsx", ".py", ".java", ".go", ".rs", + ".cpp", ".c", ".h", ".cc", ".hh", ".cs", ".php", ".rb", + ".kt", ".swift", ".sql", ".sh", ".json", ".yml", ".yaml" +] + + +async def process_zip_task(task_id: str, file_path: str, db_session_factory, user_config: dict = None): + """后台ZIP文件处理任务""" + async with db_session_factory() as db: + task = await db.get(AuditTask, task_id) + if not task: + return + + try: + task.status = "running" + task.started_at = datetime.utcnow() + await db.commit() + + # 创建使用用户配置的LLM服务实例 + llm_service = LLMService(user_config=user_config or {}) + + # Extract ZIP + extract_dir = Path(f"/tmp/{task_id}") + extract_dir.mkdir(parents=True, exist_ok=True) + + with zipfile.ZipFile(file_path, 'r') as zip_ref: + zip_ref.extractall(extract_dir) + + # Find files + files_to_scan = [] + for root, dirs, files in os.walk(extract_dir): + # 排除常见非代码目录 + dirs[:] = [d for d in dirs if d not in ['node_modules', '__pycache__', '.git', 'dist', 'build', 'vendor']] + + for file in files: + full_path = Path(root) / file + rel_path = str(full_path.relative_to(extract_dir)) + + # 检查文件类型和排除规则 + if is_text_file(rel_path) and not should_exclude(rel_path): + try: + content = full_path.read_text(errors='ignore') + if len(content) <= settings.MAX_FILE_SIZE_BYTES: + files_to_scan.append({ + "path": rel_path, + "content": content + }) + except: + pass + + # 限制文件数量 + files_to_scan = files_to_scan[:settings.MAX_ANALYZE_FILES] + + task.total_files = len(files_to_scan) + await db.commit() + + print(f"📊 ZIP任务 {task_id}: 找到 {len(files_to_scan)} 个文件") + + total_issues = 0 + total_lines = 0 + quality_scores = [] + scanned_files = 0 + failed_files = 0 + + for file_info in files_to_scan: + # 检查是否取消 + if task_control.is_cancelled(task_id): + print(f"🛑 ZIP任务 {task_id} 已被取消") + task.status = "cancelled" + task.completed_at = datetime.utcnow() + await db.commit() + task_control.cleanup_task(task_id) + return + + try: + content = file_info['content'] + total_lines += content.count('\n') + 1 + language = get_language_from_path(file_info['path']) + + result = await llm_service.analyze_code(content, language) + + issues = result.get("issues", []) + for i in issues: + issue = AuditIssue( + task_id=task.id, + file_path=file_info['path'], + line_number=i.get('line', 1), + column_number=i.get('column'), + issue_type=i.get('type', 'maintainability'), + severity=i.get('severity', 'low'), + title=i.get('title', 'Issue'), + message=i.get('title', 'Issue'), + description=i.get('description'), + suggestion=i.get('suggestion'), + code_snippet=i.get('code_snippet'), + ai_explanation=json.dumps(i.get('xai')) if i.get('xai') else None, + status="open" + ) + db.add(issue) + total_issues += 1 + + if "quality_score" in result: + quality_scores.append(result["quality_score"]) + + scanned_files += 1 + task.scanned_files = scanned_files + task.total_lines = total_lines + task.issues_count = total_issues + await db.commit() + + print(f"📈 ZIP任务 {task_id}: 进度 {scanned_files}/{len(files_to_scan)}") + + # 请求间隔 + await asyncio.sleep(settings.LLM_GAP_MS / 1000) + + except Exception as file_error: + failed_files += 1 + print(f"❌ ZIP任务分析文件失败 ({file_info['path']}): {file_error}") + await asyncio.sleep(settings.LLM_GAP_MS / 1000) + + # 完成任务 + task.status = "completed" + task.completed_at = datetime.utcnow() + task.scanned_files = scanned_files + task.total_lines = total_lines + task.issues_count = total_issues + task.quality_score = sum(quality_scores) / len(quality_scores) if quality_scores else 100.0 + await db.commit() + + print(f"✅ ZIP任务 {task_id} 完成: 扫描 {scanned_files} 个文件, 发现 {total_issues} 个问题") + task_control.cleanup_task(task_id) + + except Exception as e: + print(f"❌ ZIP扫描失败: {e}") + task.status = "failed" + task.completed_at = datetime.utcnow() + await db.commit() + task_control.cleanup_task(task_id) + finally: + # Cleanup + if os.path.exists(file_path): + os.remove(file_path) + if extract_dir.exists(): + shutil.rmtree(extract_dir) + + +@router.post("/upload-zip") +async def scan_zip( + project_id: str, + background_tasks: BackgroundTasks, + file: UploadFile = File(...), + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Upload and scan a ZIP file. + """ + # Verify project exists + project = await db.get(Project, project_id) + if not project: + raise HTTPException(status_code=404, detail="项目不存在") + + # Validate file + if not file.filename.lower().endswith('.zip'): + raise HTTPException(status_code=400, detail="请上传ZIP格式文件") + + # Save Uploaded File + file_id = str(uuid.uuid4()) + file_path = f"/tmp/{file_id}.zip" + with open(file_path, "wb") as buffer: + shutil.copyfileobj(file.file, buffer) + + # Check file size + file_size = os.path.getsize(file_path) + if file_size > 100 * 1024 * 1024: # 100MB limit + os.remove(file_path) + raise HTTPException(status_code=400, detail="文件大小不能超过100MB") + + # Create Task + task = AuditTask( + project_id=project_id, + created_by=current_user.id, + task_type="zip_upload", + status="pending", + scan_config="{}" + ) + db.add(task) + await db.commit() + await db.refresh(task) + + # 获取用户配置 + user_config = await get_user_config_dict(db, current_user.id) + + # Trigger Background Task + background_tasks.add_task(process_zip_task, task.id, file_path, AsyncSessionLocal, user_config) + + return {"task_id": task.id, "status": "queued"} + + +class InstantAnalysisRequest(BaseModel): + code: str + language: str + + +class InstantAnalysisResponse(BaseModel): + id: str + user_id: str + language: str + issues_count: int + quality_score: float + analysis_time: float + created_at: datetime + + class Config: + from_attributes = True + + +async def get_user_config_dict(db: AsyncSession, user_id: str) -> dict: + """获取用户配置字典""" + result = await db.execute( + select(UserConfig).where(UserConfig.user_id == user_id) + ) + config = result.scalar_one_or_none() + if not config: + return {} + + return { + 'llmConfig': json.loads(config.llm_config) if config.llm_config else {}, + 'otherConfig': json.loads(config.other_config) if config.other_config else {}, + } + + +@router.post("/instant") +async def instant_analysis( + req: InstantAnalysisRequest, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Perform instant code analysis. + """ + # 获取用户配置 + user_config = await get_user_config_dict(db, current_user.id) + + # 创建使用用户配置的LLM服务实例 + llm_service = LLMService(user_config=user_config) + + start_time = datetime.utcnow() + result = await llm_service.analyze_code(req.code, req.language) + end_time = datetime.utcnow() + duration = (end_time - start_time).total_seconds() + + # Save record + analysis = InstantAnalysis( + user_id=current_user.id, + language=req.language, + code_content="", # Do not persist code for privacy + analysis_result=json.dumps(result), + issues_count=len(result.get("issues", [])), + quality_score=result.get("quality_score", 0), + analysis_time=duration + ) + db.add(analysis) + await db.commit() + await db.refresh(analysis) + + # Return result directly to frontend + return result + + +@router.get("/instant/history", response_model=List[InstantAnalysisResponse]) +async def get_instant_analysis_history( + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), + limit: int = 20, +) -> Any: + """ + Get user's instant analysis history. + """ + result = await db.execute( + select(InstantAnalysis) + .where(InstantAnalysis.user_id == current_user.id) + .order_by(InstantAnalysis.created_at.desc()) + .limit(limit) + ) + return result.scalars().all() diff --git a/backend/app/api/v1/endpoints/tasks.py b/backend/app/api/v1/endpoints/tasks.py new file mode 100644 index 0000000..15c7164 --- /dev/null +++ b/backend/app/api/v1/endpoints/tasks.py @@ -0,0 +1,200 @@ +from typing import Any, List, Optional +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.future import select +from sqlalchemy.orm import selectinload +from pydantic import BaseModel +from datetime import datetime + +from app.api import deps +from app.db.session import get_db +from app.models.audit import AuditTask, AuditIssue +from app.models.project import Project +from app.models.user import User +from app.services.scanner import task_control + +router = APIRouter() + + +# Schemas +class AuditIssueSchema(BaseModel): + id: str + task_id: str + file_path: str + line_number: Optional[int] = None + column_number: Optional[int] = None + issue_type: str + severity: str + title: Optional[str] = None + message: Optional[str] = None + description: Optional[str] = None + suggestion: Optional[str] = None + code_snippet: Optional[str] = None + ai_explanation: Optional[str] = None + status: str + resolved_by: Optional[str] = None + resolved_at: Optional[datetime] = None + created_at: datetime + + class Config: + from_attributes = True + + +class IssueUpdateSchema(BaseModel): + status: Optional[str] = None + + +class ProjectSchema(BaseModel): + id: str + name: str + description: Optional[str] = None + repository_url: Optional[str] = None + repository_type: Optional[str] = None + default_branch: Optional[str] = None + programming_languages: Optional[str] = None + owner_id: str + is_active: bool + created_at: datetime + updated_at: Optional[datetime] = None + + class Config: + from_attributes = True + + +class AuditTaskSchema(BaseModel): + id: str + project_id: str + task_type: str + status: str + branch_name: Optional[str] = None + exclude_patterns: Optional[str] = None + scan_config: Optional[str] = None + total_files: int = 0 + scanned_files: int = 0 + total_lines: int = 0 + issues_count: int = 0 + quality_score: float = 0.0 + started_at: Optional[datetime] = None + completed_at: Optional[datetime] = None + created_by: str + created_at: datetime + project: Optional[ProjectSchema] = None + + class Config: + from_attributes = True + + +@router.get("/", response_model=List[AuditTaskSchema]) +async def list_tasks( + project_id: Optional[str] = None, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + List all tasks, optionally filtered by project. + """ + query = select(AuditTask).options(selectinload(AuditTask.project)) + if project_id: + query = query.where(AuditTask.project_id == project_id) + query = query.order_by(AuditTask.created_at.desc()) + result = await db.execute(query) + return result.scalars().all() + + +@router.get("/{id}", response_model=AuditTaskSchema) +async def read_task( + id: str, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Get task status by ID. + """ + result = await db.execute( + select(AuditTask) + .options(selectinload(AuditTask.project)) + .where(AuditTask.id == id) + ) + task = result.scalars().first() + if not task: + raise HTTPException(status_code=404, detail="任务不存在") + return task + + +@router.post("/{id}/cancel") +async def cancel_task( + id: str, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Cancel a running task. + """ + result = await db.execute(select(AuditTask).where(AuditTask.id == id)) + task = result.scalars().first() + if not task: + raise HTTPException(status_code=404, detail="任务不存在") + + if task.status not in ["pending", "running"]: + raise HTTPException(status_code=400, detail="只能取消待处理或运行中的任务") + + # 标记任务为取消 + task_control.cancel_task(id) + + # 更新数据库状态 + task.status = "cancelled" + task.completed_at = datetime.utcnow() + await db.commit() + + return {"message": "任务已取消", "task_id": id} + + +@router.get("/{id}/issues", response_model=List[AuditIssueSchema]) +async def read_task_issues( + id: str, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Get issues for a specific task. + """ + result = await db.execute( + select(AuditIssue) + .where(AuditIssue.task_id == id) + .order_by( + # 按严重程度排序 + AuditIssue.severity.desc(), + AuditIssue.created_at.desc() + ) + ) + return result.scalars().all() + + +@router.patch("/{task_id}/issues/{issue_id}", response_model=AuditIssueSchema) +async def update_issue( + task_id: str, + issue_id: str, + issue_update: IssueUpdateSchema, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Update issue status (e.g., resolve, mark as false positive). + """ + result = await db.execute( + select(AuditIssue) + .where(AuditIssue.id == issue_id, AuditIssue.task_id == task_id) + ) + issue = result.scalars().first() + if not issue: + raise HTTPException(status_code=404, detail="问题不存在") + + if issue_update.status: + issue.status = issue_update.status + if issue_update.status == "resolved": + issue.resolved_by = current_user.id + issue.resolved_at = datetime.utcnow() + + await db.commit() + await db.refresh(issue) + return issue diff --git a/backend/app/api/v1/endpoints/users.py b/backend/app/api/v1/endpoints/users.py new file mode 100644 index 0000000..95d6e30 --- /dev/null +++ b/backend/app/api/v1/endpoints/users.py @@ -0,0 +1,66 @@ +from typing import Any, List +from fastapi import APIRouter, Body, Depends, HTTPException +from fastapi.encoders import jsonable_encoder +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.future import select + +from app.api import deps +from app.core import security +from app.db.session import get_db +from app.models.user import User +from app.schemas.user import User as UserSchema, UserCreate, UserUpdate + +router = APIRouter() + +@router.get("/", response_model=List[UserSchema]) +async def read_users( + db: AsyncSession = Depends(get_db), + skip: int = 0, + limit: int = 100, + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Retrieve users. + """ + result = await db.execute(select(User).offset(skip).limit(limit)) + users = result.scalars().all() + return users + +@router.post("/", response_model=UserSchema) +async def create_user( + *, + db: AsyncSession = Depends(get_db), + user_in: UserCreate, +) -> Any: + """ + Create new user. + """ + result = await db.execute(select(User).where(User.email == user_in.email)) + user = result.scalars().first() + if user: + raise HTTPException( + status_code=400, + detail="The user with this username already exists in the system.", + ) + + db_user = User( + email=user_in.email, + hashed_password=security.get_password_hash(user_in.password), + full_name=user_in.full_name, + is_active=user_in.is_active, + is_superuser=user_in.is_superuser, + ) + db.add(db_user) + await db.commit() + await db.refresh(db_user) + return db_user + +@router.get("/me", response_model=UserSchema) +async def read_user_me( + current_user: User = Depends(deps.get_current_user), +) -> Any: + """ + Get current user. + """ + return current_user + diff --git a/backend/app/core/__init__.py b/backend/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/core/config.py b/backend/app/core/config.py new file mode 100644 index 0000000..ec3f040 --- /dev/null +++ b/backend/app/core/config.py @@ -0,0 +1,82 @@ +from typing import List, Union, Optional +from pydantic import AnyHttpUrl, validator +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + PROJECT_NAME: str = "XCodeReviewer" + API_V1_STR: str = "/api/v1" + + # SECURITY + SECRET_KEY: str = "changethis_in_production_to_a_long_random_string" + ALGORITHM: str = "HS256" + ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8 # 8 days + + # CORS + BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = [] + + @validator("BACKEND_CORS_ORIGINS", pre=True) + def assemble_cors_origins(cls, v: Union[str, List[str]]) -> Union[List[str], str]: + if isinstance(v, str) and not v.startswith("["): + return [i.strip() for i in v.split(",")] + elif isinstance(v, (list, str)): + return v + raise ValueError(v) + + # POSTGRES + POSTGRES_SERVER: str = "db" + POSTGRES_USER: str = "postgres" + POSTGRES_PASSWORD: str = "postgres" + POSTGRES_DB: str = "xcodereviewer" + DATABASE_URL: str | None = None + + @validator("DATABASE_URL", pre=True) + def assemble_db_connection(cls, v: str | None, values: dict[str, any]) -> str: + if isinstance(v, str): + return v + return str(f"postgresql+asyncpg://{values.get('POSTGRES_USER')}:{values.get('POSTGRES_PASSWORD')}@{values.get('POSTGRES_SERVER')}/{values.get('POSTGRES_DB')}") + + # LLM配置 + LLM_PROVIDER: str = "openai" # gemini, openai, claude, qwen, deepseek, zhipu, moonshot, baidu, minimax, doubao, ollama + LLM_API_KEY: Optional[str] = None + LLM_MODEL: Optional[str] = None # 不指定时使用provider的默认模型 + LLM_BASE_URL: Optional[str] = None # 自定义API端点(如中转站) + LLM_TIMEOUT: int = 150 # 超时时间(秒) + LLM_TEMPERATURE: float = 0.1 + LLM_MAX_TOKENS: int = 4096 + + # 各LLM提供商的API Key配置(兼容单独配置) + OPENAI_API_KEY: Optional[str] = None + OPENAI_BASE_URL: Optional[str] = None + GEMINI_API_KEY: Optional[str] = None + CLAUDE_API_KEY: Optional[str] = None + QWEN_API_KEY: Optional[str] = None + DEEPSEEK_API_KEY: Optional[str] = None + ZHIPU_API_KEY: Optional[str] = None + MOONSHOT_API_KEY: Optional[str] = None + BAIDU_API_KEY: Optional[str] = None # 格式: api_key:secret_key + MINIMAX_API_KEY: Optional[str] = None + DOUBAO_API_KEY: Optional[str] = None + OLLAMA_BASE_URL: Optional[str] = "http://localhost:11434/v1" + + # GitHub配置 + GITHUB_TOKEN: Optional[str] = None + + # GitLab配置 + GITLAB_TOKEN: Optional[str] = None + + # 扫描配置 + MAX_ANALYZE_FILES: int = 50 # 最大分析文件数 + MAX_FILE_SIZE_BYTES: int = 200 * 1024 # 最大文件大小 200KB + LLM_CONCURRENCY: int = 3 # LLM并发数 + LLM_GAP_MS: int = 2000 # LLM请求间隔(毫秒) + + # 输出语言配置 - 支持 zh-CN(中文)和 en-US(英文) + OUTPUT_LANGUAGE: str = "zh-CN" + + class Config: + case_sensitive = True + env_file = ".env" + + +settings = Settings() diff --git a/backend/app/core/security.py b/backend/app/core/security.py new file mode 100644 index 0000000..81592dc --- /dev/null +++ b/backend/app/core/security.py @@ -0,0 +1,29 @@ +from datetime import datetime, timedelta +from typing import Any, Union +from jose import jwt +from passlib.context import CryptContext +from app.core.config import settings + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + +ALGORITHM = settings.ALGORITHM + +def create_access_token( + subject: Union[str, Any], expires_delta: timedelta = None +) -> str: + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta( + minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES + ) + to_encode = {"exp": expire, "sub": str(subject)} + encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM) + return encoded_jwt + +def verify_password(plain_password: str, hashed_password: str) -> bool: + return pwd_context.verify(plain_password, hashed_password) + +def get_password_hash(password: str) -> str: + return pwd_context.hash(password) + diff --git a/backend/app/db/__init__.py b/backend/app/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/db/base.py b/backend/app/db/base.py new file mode 100644 index 0000000..4e3b62c --- /dev/null +++ b/backend/app/db/base.py @@ -0,0 +1,12 @@ +from sqlalchemy.orm import as_declarative, declared_attr + +@as_declarative() +class Base: + id: str + __name__: str + + # Generate __tablename__ automatically + @declared_attr + def __tablename__(cls) -> str: + return cls.__name__.lower() + "s" + diff --git a/backend/app/db/session.py b/backend/app/db/session.py new file mode 100644 index 0000000..a805a54 --- /dev/null +++ b/backend/app/db/session.py @@ -0,0 +1,17 @@ +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.orm import sessionmaker +from app.core.config import settings + +engine = create_async_engine(settings.DATABASE_URL, echo=True, future=True) + +AsyncSessionLocal = sessionmaker( + engine, class_=AsyncSession, expire_on_commit=False +) + +async def get_db(): + async with AsyncSessionLocal() as session: + try: + yield session + finally: + await session.close() + diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..faf5029 --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,28 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from app.core.config import settings +from app.api.v1.api import api_router + +app = FastAPI( + title=settings.PROJECT_NAME, + openapi_url=f"{settings.API_V1_STR}/openapi.json" +) + +# Configure CORS - Allow all origins in development +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # In production, replace with specific frontend URL + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +app.include_router(api_router, prefix=settings.API_V1_STR) + +@app.get("/health") +async def health_check(): + return {"status": "ok"} + +@app.get("/") +async def root(): + return {"message": "Welcome to XCodeReviewer API", "docs": "/docs"} diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py new file mode 100644 index 0000000..f3b226f --- /dev/null +++ b/backend/app/models/__init__.py @@ -0,0 +1,5 @@ +from .user import User +from .project import Project, ProjectMember +from .audit import AuditTask, AuditIssue +from .analysis import InstantAnalysis + diff --git a/backend/app/models/analysis.py b/backend/app/models/analysis.py new file mode 100644 index 0000000..a0a1d1b --- /dev/null +++ b/backend/app/models/analysis.py @@ -0,0 +1,24 @@ +import uuid +from sqlalchemy import Column, String, Integer, DateTime, Float, Text, ForeignKey +from sqlalchemy.sql import func +from sqlalchemy.orm import relationship +from app.db.base import Base + +class InstantAnalysis(Base): + __tablename__ = "instant_analyses" + + id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) + user_id = Column(String, ForeignKey("users.id"), nullable=True) # Can be anonymous? Logic says usually logged in, but localDB allowed check. + + language = Column(String, nullable=False) + code_content = Column(Text, default="") + analysis_result = Column(Text, default="{}") + issues_count = Column(Integer, default=0) + quality_score = Column(Float, default=0.0) + analysis_time = Column(Float, default=0.0) + + created_at = Column(DateTime(timezone=True), server_default=func.now()) + + # Relationships + user = relationship("User", backref="instant_analyses") + diff --git a/backend/app/models/audit.py b/backend/app/models/audit.py new file mode 100644 index 0000000..f207a23 --- /dev/null +++ b/backend/app/models/audit.py @@ -0,0 +1,67 @@ +import uuid +from sqlalchemy import Column, String, Integer, DateTime, ForeignKey, Text, Float +from sqlalchemy.sql import func +from sqlalchemy.orm import relationship +from app.db.base import Base + + +class AuditTask(Base): + __tablename__ = "audit_tasks" + + id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) + project_id = Column(String, ForeignKey("projects.id"), nullable=False) + created_by = Column(String, ForeignKey("users.id"), nullable=False) + + task_type = Column(String, nullable=False) + status = Column(String, default="pending", index=True) + branch_name = Column(String, nullable=True) + + exclude_patterns = Column(Text, default="[]") + scan_config = Column(Text, default="{}") + + # Stats + total_files = Column(Integer, default=0) + scanned_files = Column(Integer, default=0) + total_lines = Column(Integer, default=0) + issues_count = Column(Integer, default=0) + quality_score = Column(Float, default=0.0) + + started_at = Column(DateTime(timezone=True), nullable=True) + completed_at = Column(DateTime(timezone=True), nullable=True) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + + # Relationships + project = relationship("Project", back_populates="tasks") + creator = relationship("User", foreign_keys=[created_by]) + issues = relationship("AuditIssue", back_populates="task", cascade="all, delete-orphan") + + +class AuditIssue(Base): + __tablename__ = "audit_issues" + + id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) + task_id = Column(String, ForeignKey("audit_tasks.id"), nullable=False) + + file_path = Column(String, nullable=False) + line_number = Column(Integer, nullable=True) + column_number = Column(Integer, nullable=True) + issue_type = Column(String, nullable=False) + severity = Column(String, nullable=False) # critical, high, medium, low + + # 问题信息 + title = Column(String, nullable=True) # 问题标题 + message = Column(Text, nullable=True) # 兼容旧字段,同title + description = Column(Text, nullable=True) # 详细描述 + suggestion = Column(Text, nullable=True) # 修复建议 + code_snippet = Column(Text, nullable=True) # 问题代码片段 + ai_explanation = Column(Text, nullable=True) # AI解释(JSON格式的xai字段) + + status = Column(String, default="open") # open, resolved, false_positive + resolved_by = Column(String, ForeignKey("users.id"), nullable=True) + resolved_at = Column(DateTime(timezone=True), nullable=True) + + created_at = Column(DateTime(timezone=True), server_default=func.now()) + + # Relationships + task = relationship("AuditTask", back_populates="issues") + resolver = relationship("User", foreign_keys=[resolved_by]) diff --git a/backend/app/models/project.py b/backend/app/models/project.py new file mode 100644 index 0000000..29efe87 --- /dev/null +++ b/backend/app/models/project.py @@ -0,0 +1,44 @@ +import uuid +from sqlalchemy import Column, String, Boolean, DateTime, ForeignKey, Text +from sqlalchemy.sql import func +from sqlalchemy.orm import relationship +from app.db.base import Base + +class Project(Base): + __tablename__ = "projects" + + id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) + name = Column(String, index=True, nullable=False) + description = Column(Text, nullable=True) + repository_url = Column(String, nullable=True) + repository_type = Column(String, default="other") + default_branch = Column(String, default="main") + programming_languages = Column(Text, default="[]") # Stored as JSON string + + owner_id = Column(String, ForeignKey("users.id"), nullable=False) + is_active = Column(Boolean(), default=True) + + created_at = Column(DateTime(timezone=True), server_default=func.now()) + updated_at = Column(DateTime(timezone=True), onupdate=func.now()) + + # Relationships + owner = relationship("User", backref="projects") + members = relationship("ProjectMember", back_populates="project", cascade="all, delete-orphan") + tasks = relationship("AuditTask", back_populates="project", cascade="all, delete-orphan") + +class ProjectMember(Base): + __tablename__ = "project_members" + + id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) + project_id = Column(String, ForeignKey("projects.id"), nullable=False) + user_id = Column(String, ForeignKey("users.id"), nullable=False) + role = Column(String, default="member") + permissions = Column(Text, default="{}") + + joined_at = Column(DateTime(timezone=True), server_default=func.now()) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + + # Relationships + project = relationship("Project", back_populates="members") + user = relationship("User", backref="project_memberships") + diff --git a/backend/app/models/user.py b/backend/app/models/user.py new file mode 100644 index 0000000..e7a85c2 --- /dev/null +++ b/backend/app/models/user.py @@ -0,0 +1,25 @@ +import uuid +from sqlalchemy import Column, String, Boolean, DateTime +from sqlalchemy.sql import func +from app.db.base import Base + +class User(Base): + __tablename__ = "users" + + id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) + email = Column(String, unique=True, index=True, nullable=False) + hashed_password = Column(String, nullable=False) + full_name = Column(String, index=True) + is_active = Column(Boolean(), default=True) + is_superuser = Column(Boolean(), default=False) + + # Profile fields + phone = Column(String, nullable=True) + avatar_url = Column(String, nullable=True) + role = Column(String, default="member") + github_username = Column(String, nullable=True) + gitlab_username = Column(String, nullable=True) + + created_at = Column(DateTime(timezone=True), server_default=func.now()) + updated_at = Column(DateTime(timezone=True), onupdate=func.now()) + diff --git a/backend/app/models/user_config.py b/backend/app/models/user_config.py new file mode 100644 index 0000000..f739273 --- /dev/null +++ b/backend/app/models/user_config.py @@ -0,0 +1,30 @@ +""" +用户配置模型 - 存储用户的LLM和其他配置 +""" + +import uuid +from sqlalchemy import Column, String, Text, DateTime, ForeignKey +from sqlalchemy.sql import func +from sqlalchemy.orm import relationship +from app.db.base import Base + + +class UserConfig(Base): + """用户配置表""" + __tablename__ = "user_configs" + + id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) + user_id = Column(String, ForeignKey("users.id"), nullable=False, unique=True) + + # LLM配置(JSON格式存储) + llm_config = Column(Text, default="{}") + + # 其他配置(JSON格式存储) + other_config = Column(Text, default="{}") + + created_at = Column(DateTime(timezone=True), server_default=func.now()) + updated_at = Column(DateTime(timezone=True), onupdate=func.now()) + + # Relationships + user = relationship("User", backref="config") + diff --git a/backend/app/schemas/__init__.py b/backend/app/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/schemas/token.py b/backend/app/schemas/token.py new file mode 100644 index 0000000..bfa2f54 --- /dev/null +++ b/backend/app/schemas/token.py @@ -0,0 +1,10 @@ +from typing import Optional +from pydantic import BaseModel + +class Token(BaseModel): + access_token: str + token_type: str + +class TokenPayload(BaseModel): + sub: Optional[str] = None + diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py new file mode 100644 index 0000000..74c2428 --- /dev/null +++ b/backend/app/schemas/user.py @@ -0,0 +1,35 @@ +from typing import Optional +from pydantic import BaseModel, EmailStr + +class UserBase(BaseModel): + email: Optional[EmailStr] = None + is_active: Optional[bool] = True + is_superuser: bool = False + full_name: Optional[str] = None + + # Profile fields + phone: Optional[str] = None + avatar_url: Optional[str] = None + role: str = "member" + github_username: Optional[str] = None + gitlab_username: Optional[str] = None + +class UserCreate(UserBase): + email: EmailStr + password: str + full_name: str + +class UserUpdate(UserBase): + password: Optional[str] = None + +class UserInDBBase(UserBase): + id: str + created_at: Optional[object] = None # Datetime + updated_at: Optional[object] = None + + class Config: + from_attributes = True + +class User(UserInDBBase): + pass + diff --git a/backend/app/services/llm/__init__.py b/backend/app/services/llm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/services/llm/adapters/__init__.py b/backend/app/services/llm/adapters/__init__.py new file mode 100644 index 0000000..cddca82 --- /dev/null +++ b/backend/app/services/llm/adapters/__init__.py @@ -0,0 +1,30 @@ +""" +LLM适配器模块 +""" + +from .openai_adapter import OpenAIAdapter +from .gemini_adapter import GeminiAdapter +from .claude_adapter import ClaudeAdapter +from .deepseek_adapter import DeepSeekAdapter +from .qwen_adapter import QwenAdapter +from .zhipu_adapter import ZhipuAdapter +from .moonshot_adapter import MoonshotAdapter +from .baidu_adapter import BaiduAdapter +from .minimax_adapter import MinimaxAdapter +from .doubao_adapter import DoubaoAdapter +from .ollama_adapter import OllamaAdapter + +__all__ = [ + 'OpenAIAdapter', + 'GeminiAdapter', + 'ClaudeAdapter', + 'DeepSeekAdapter', + 'QwenAdapter', + 'ZhipuAdapter', + 'MoonshotAdapter', + 'BaiduAdapter', + 'MinimaxAdapter', + 'DoubaoAdapter', + 'OllamaAdapter', +] + diff --git a/backend/app/services/llm/adapters/baidu_adapter.py b/backend/app/services/llm/adapters/baidu_adapter.py new file mode 100644 index 0000000..c5f809c --- /dev/null +++ b/backend/app/services/llm/adapters/baidu_adapter.py @@ -0,0 +1,137 @@ +""" +百度文心一言适配器 +""" + +import httpx +import json +from typing import Optional +from ..base_adapter import BaseLLMAdapter +from ..types import LLMConfig, LLMRequest, LLMResponse, LLMError + + +class BaiduAdapter(BaseLLMAdapter): + """百度文心一言API适配器""" + + # 模型名称到API端点的映射 + MODEL_ENDPOINTS = { + "ERNIE-4.0": "completions_pro", + "ERNIE-3.5-8K": "completions", + "ERNIE-3.5-128K": "ernie-3.5-128k", + "ERNIE-Speed": "ernie_speed", + "ERNIE-Lite": "ernie-lite-8k", + } + + def __init__(self, config: LLMConfig): + super().__init__(config) + self._access_token: Optional[str] = None + self._base_url = config.base_url or "https://aip.baidubce.com" + + async def _get_access_token(self) -> str: + """获取百度API的access_token + + 注意:百度API使用API Key和Secret Key来获取access_token + 这里假设api_key格式为: "api_key:secret_key" + """ + if self._access_token: + return self._access_token + + # 解析API Key和Secret Key + if ":" not in self.config.api_key: + raise LLMError( + "百度API需要同时提供API Key和Secret Key,格式:api_key:secret_key", + provider="baidu" + ) + + api_key, secret_key = self.config.api_key.split(":", 1) + + url = f"{self._base_url}/oauth/2.0/token" + params = { + "grant_type": "client_credentials", + "client_id": api_key, + "client_secret": secret_key, + } + + async with httpx.AsyncClient(timeout=30) as client: + response = await client.post(url, params=params) + if response.status_code != 200: + raise LLMError( + f"获取百度access_token失败: {response.text}", + provider="baidu", + status_code=response.status_code + ) + + data = response.json() + self._access_token = data.get("access_token") + if not self._access_token: + raise LLMError( + f"百度API返回的access_token为空: {response.text}", + provider="baidu" + ) + + return self._access_token + + async def _do_complete(self, request: LLMRequest) -> LLMResponse: + """执行实际的API调用""" + access_token = await self._get_access_token() + + # 获取模型对应的API端点 + model = self.config.model or "ERNIE-3.5-8K" + endpoint = self.MODEL_ENDPOINTS.get(model, "completions") + + url = f"{self._base_url}/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/{endpoint}?access_token={access_token}" + + messages = [{"role": m.role, "content": m.content} for m in request.messages] + + payload = { + "messages": messages, + "temperature": request.temperature or self.config.temperature, + "top_p": request.top_p or self.config.top_p, + } + + if request.max_tokens or self.config.max_tokens: + payload["max_output_tokens"] = request.max_tokens or self.config.max_tokens + + async with httpx.AsyncClient(timeout=self.config.timeout) as client: + response = await client.post( + url, + json=payload, + headers={"Content-Type": "application/json"} + ) + + if response.status_code != 200: + raise LLMError( + f"百度API错误: {response.text}", + provider="baidu", + status_code=response.status_code + ) + + data = response.json() + + if "error_code" in data: + raise LLMError( + f"百度API错误: {data.get('error_msg', '未知错误')}", + provider="baidu", + status_code=data.get("error_code") + ) + + return LLMResponse( + content=data.get("result", ""), + model=model, + usage=data.get("usage"), + finish_reason=data.get("finish_reason") + ) + + async def validate_config(self) -> bool: + """验证配置是否有效""" + try: + await self._get_access_token() + return True + except Exception: + return False + + def get_provider(self) -> str: + return "baidu" + + def get_model(self) -> str: + return self.config.model or "ERNIE-3.5-8K" + diff --git a/backend/app/services/llm/adapters/claude_adapter.py b/backend/app/services/llm/adapters/claude_adapter.py new file mode 100644 index 0000000..9da018a --- /dev/null +++ b/backend/app/services/llm/adapters/claude_adapter.py @@ -0,0 +1,93 @@ +""" +Anthropic Claude适配器 +""" + +from typing import Dict, Any +from ..base_adapter import BaseLLMAdapter +from ..types import LLMRequest, LLMResponse, LLMUsage, DEFAULT_BASE_URLS, LLMProvider + + +class ClaudeAdapter(BaseLLMAdapter): + """Claude适配器""" + + @property + def base_url(self) -> str: + return self.config.base_url or DEFAULT_BASE_URLS.get(LLMProvider.CLAUDE, "https://api.anthropic.com/v1") + + async def complete(self, request: LLMRequest) -> LLMResponse: + try: + await self.validate_config() + return await self.retry(lambda: self._send_request(request)) + except Exception as error: + self.handle_error(error, "Claude API调用失败") + + async def _send_request(self, request: LLMRequest) -> LLMResponse: + # Claude API需要将system消息分离 + system_message = None + messages = [] + + for msg in request.messages: + if msg.role == "system": + system_message = msg.content + else: + messages.append({ + "role": msg.role, + "content": msg.content + }) + + request_body: Dict[str, Any] = { + "model": self.config.model, + "messages": messages, + "max_tokens": request.max_tokens if request.max_tokens is not None else self.config.max_tokens or 4096, + "temperature": request.temperature if request.temperature is not None else self.config.temperature, + "top_p": request.top_p if request.top_p is not None else self.config.top_p, + } + + if system_message: + request_body["system"] = system_message + + # 构建请求头 + headers = { + "x-api-key": self.config.api_key, + "anthropic-version": "2023-06-01", + } + + url = f"{self.base_url.rstrip('/')}/messages" + + response = await self.client.post( + url, + headers=self.build_headers(headers), + json=request_body + ) + + if response.status_code != 200: + error_data = response.json() if response.text else {} + error_msg = error_data.get("error", {}).get("message", f"HTTP {response.status_code}") + raise Exception(f"{error_msg}") + + data = response.json() + + if not data.get("content") or not data["content"][0]: + raise Exception("API响应格式异常: 缺少content字段") + + usage = None + if "usage" in data: + usage = LLMUsage( + prompt_tokens=data["usage"].get("input_tokens", 0), + completion_tokens=data["usage"].get("output_tokens", 0), + total_tokens=data["usage"].get("input_tokens", 0) + data["usage"].get("output_tokens", 0) + ) + + return LLMResponse( + content=data["content"][0].get("text", ""), + model=data.get("model"), + usage=usage, + finish_reason=data.get("stop_reason") + ) + + async def validate_config(self) -> bool: + await super().validate_config() + if not self.config.model.startswith("claude-"): + raise Exception(f"无效的Claude模型: {self.config.model}") + return True + diff --git a/backend/app/services/llm/adapters/deepseek_adapter.py b/backend/app/services/llm/adapters/deepseek_adapter.py new file mode 100644 index 0000000..c26192f --- /dev/null +++ b/backend/app/services/llm/adapters/deepseek_adapter.py @@ -0,0 +1,81 @@ +""" +DeepSeek适配器 - 兼容OpenAI格式 +""" + +from typing import Dict, Any +from ..base_adapter import BaseLLMAdapter +from ..types import LLMRequest, LLMResponse, LLMUsage, DEFAULT_BASE_URLS, LLMProvider + + +class DeepSeekAdapter(BaseLLMAdapter): + """DeepSeek适配器""" + + @property + def base_url(self) -> str: + return self.config.base_url or DEFAULT_BASE_URLS.get(LLMProvider.DEEPSEEK, "https://api.deepseek.com") + + async def complete(self, request: LLMRequest) -> LLMResponse: + try: + await self.validate_config() + return await self.retry(lambda: self._send_request(request)) + except Exception as error: + self.handle_error(error, "DeepSeek API调用失败") + + async def _send_request(self, request: LLMRequest) -> LLMResponse: + # DeepSeek API兼容OpenAI格式 + headers = { + "Authorization": f"Bearer {self.config.api_key}", + } + + messages = [{"role": msg.role, "content": msg.content} for msg in request.messages] + + request_body: Dict[str, Any] = { + "model": self.config.model, + "messages": messages, + "temperature": request.temperature if request.temperature is not None else self.config.temperature, + "max_tokens": request.max_tokens if request.max_tokens is not None else self.config.max_tokens, + "top_p": request.top_p if request.top_p is not None else self.config.top_p, + "frequency_penalty": self.config.frequency_penalty, + "presence_penalty": self.config.presence_penalty, + } + + url = f"{self.base_url.rstrip('/')}/v1/chat/completions" + + response = await self.client.post( + url, + headers=self.build_headers(headers), + json=request_body + ) + + if response.status_code != 200: + error_data = response.json() if response.text else {} + error_msg = error_data.get("error", {}).get("message", f"HTTP {response.status_code}") + raise Exception(f"{error_msg}") + + data = response.json() + choice = data.get("choices", [{}])[0] + + if not choice: + raise Exception("API响应格式异常: 缺少choices字段") + + usage = None + if "usage" in data: + usage = LLMUsage( + prompt_tokens=data["usage"].get("prompt_tokens", 0), + completion_tokens=data["usage"].get("completion_tokens", 0), + total_tokens=data["usage"].get("total_tokens", 0) + ) + + return LLMResponse( + content=choice.get("message", {}).get("content", ""), + model=data.get("model"), + usage=usage, + finish_reason=choice.get("finish_reason") + ) + + async def validate_config(self) -> bool: + await super().validate_config() + if not self.config.model: + raise Exception("未指定DeepSeek模型") + return True + diff --git a/backend/app/services/llm/adapters/doubao_adapter.py b/backend/app/services/llm/adapters/doubao_adapter.py new file mode 100644 index 0000000..31cde9e --- /dev/null +++ b/backend/app/services/llm/adapters/doubao_adapter.py @@ -0,0 +1,87 @@ +""" +字节跳动豆包适配器 +""" + +import httpx +from ..base_adapter import BaseLLMAdapter +from ..types import LLMConfig, LLMRequest, LLMResponse, LLMError + + +class DoubaoAdapter(BaseLLMAdapter): + """字节跳动豆包API适配器 + + 豆包使用OpenAI兼容的API格式 + """ + + def __init__(self, config: LLMConfig): + super().__init__(config) + self._base_url = config.base_url or "https://ark.cn-beijing.volces.com/api/v3" + + async def _do_complete(self, request: LLMRequest) -> LLMResponse: + """执行实际的API调用""" + url = f"{self._base_url}/chat/completions" + + messages = [{"role": m.role, "content": m.content} for m in request.messages] + + payload = { + "model": self.config.model or "doubao-pro-32k", + "messages": messages, + "temperature": request.temperature or self.config.temperature, + "top_p": request.top_p or self.config.top_p, + } + + if request.max_tokens or self.config.max_tokens: + payload["max_tokens"] = request.max_tokens or self.config.max_tokens + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self.config.api_key}", + } + + async with httpx.AsyncClient(timeout=self.config.timeout) as client: + response = await client.post(url, json=payload, headers=headers) + + if response.status_code != 200: + raise LLMError( + f"豆包API错误: {response.text}", + provider="doubao", + status_code=response.status_code + ) + + data = response.json() + + if "error" in data: + raise LLMError( + f"豆包API错误: {data['error'].get('message', '未知错误')}", + provider="doubao" + ) + + choices = data.get("choices", []) + if not choices: + raise LLMError("豆包API返回空响应", provider="doubao") + + return LLMResponse( + content=choices[0].get("message", {}).get("content", ""), + model=data.get("model", self.config.model or "doubao-pro-32k"), + usage=data.get("usage"), + finish_reason=choices[0].get("finish_reason") + ) + + async def validate_config(self) -> bool: + """验证配置是否有效""" + try: + test_request = LLMRequest( + messages=[{"role": "user", "content": "Hi"}], + max_tokens=10 + ) + await self._do_complete(test_request) + return True + except Exception: + return False + + def get_provider(self) -> str: + return "doubao" + + def get_model(self) -> str: + return self.config.model or "doubao-pro-32k" + diff --git a/backend/app/services/llm/adapters/gemini_adapter.py b/backend/app/services/llm/adapters/gemini_adapter.py new file mode 100644 index 0000000..0b847ee --- /dev/null +++ b/backend/app/services/llm/adapters/gemini_adapter.py @@ -0,0 +1,114 @@ +""" +Google Gemini适配器 - 支持官方API和中转站 +""" + +from typing import Dict, Any, List +from ..base_adapter import BaseLLMAdapter +from ..types import LLMRequest, LLMResponse, LLMUsage, DEFAULT_BASE_URLS, LLMProvider + + +class GeminiAdapter(BaseLLMAdapter): + """Gemini适配器""" + + @property + def base_url(self) -> str: + return self.config.base_url or DEFAULT_BASE_URLS.get(LLMProvider.GEMINI, "https://generativelanguage.googleapis.com/v1beta") + + async def complete(self, request: LLMRequest) -> LLMResponse: + try: + await self.validate_config() + return await self.retry(lambda: self._generate_content(request)) + except Exception as error: + self.handle_error(error, "Gemini API调用失败") + + async def _generate_content(self, request: LLMRequest) -> LLMResponse: + # 转换消息格式为 Gemini 格式 + contents: List[Dict[str, Any]] = [] + system_content = "" + + for msg in request.messages: + if msg.role == "system": + system_content = msg.content + else: + role = "model" if msg.role == "assistant" else "user" + contents.append({ + "role": role, + "parts": [{"text": msg.content}] + }) + + # 将系统消息合并到第一条用户消息 + if system_content and contents: + contents[0]["parts"][0]["text"] = f"{system_content}\n\n{contents[0]['parts'][0]['text']}" + + # 构建请求体 + request_body = { + "contents": contents, + "generationConfig": { + "temperature": request.temperature if request.temperature is not None else self.config.temperature, + "maxOutputTokens": request.max_tokens if request.max_tokens is not None else self.config.max_tokens, + "topP": request.top_p if request.top_p is not None else self.config.top_p, + } + } + + # API Key 在 URL 参数中 + url = f"{self.base_url}/models/{self.config.model}:generateContent?key={self.config.api_key}" + + response = await self.client.post( + url, + headers=self.build_headers(), + json=request_body + ) + + if response.status_code != 200: + error_data = response.json() if response.text else {} + error_msg = error_data.get("error", {}).get("message", f"HTTP {response.status_code}") + raise Exception(f"{error_msg}") + + data = response.json() + + # 解析 Gemini 响应格式 + candidates = data.get("candidates", []) + if not candidates: + # 检查是否有错误信息 + if "error" in data: + error_msg = data["error"].get("message", "未知错误") + raise Exception(f"Gemini API错误: {error_msg}") + raise Exception("API响应格式异常: 缺少candidates字段") + + candidate = candidates[0] + if not candidate or "content" not in candidate: + raise Exception("API响应格式异常: 缺少content字段") + + text_parts = candidate.get("content", {}).get("parts", []) + if not text_parts: + raise Exception("API响应格式异常: content.parts为空") + + text = "".join(part.get("text", "") for part in text_parts) + + # 检查响应内容是否为空 + if not text or not text.strip(): + finish_reason = candidate.get("finishReason", "unknown") + raise Exception(f"Gemini返回空响应 - Finish Reason: {finish_reason}") + + usage = None + if "usageMetadata" in data: + usage_data = data["usageMetadata"] + usage = LLMUsage( + prompt_tokens=usage_data.get("promptTokenCount", 0), + completion_tokens=usage_data.get("candidatesTokenCount", 0), + total_tokens=usage_data.get("totalTokenCount", 0) + ) + + return LLMResponse( + content=text, + model=self.config.model, + usage=usage, + finish_reason=candidate.get("finishReason", "stop") + ) + + async def validate_config(self) -> bool: + await super().validate_config() + if not self.config.model.startswith("gemini-"): + raise Exception(f"无效的Gemini模型: {self.config.model}") + return True + diff --git a/backend/app/services/llm/adapters/minimax_adapter.py b/backend/app/services/llm/adapters/minimax_adapter.py new file mode 100644 index 0000000..a99c78b --- /dev/null +++ b/backend/app/services/llm/adapters/minimax_adapter.py @@ -0,0 +1,84 @@ +""" +MiniMax适配器 +""" + +import httpx +from ..base_adapter import BaseLLMAdapter +from ..types import LLMConfig, LLMRequest, LLMResponse, LLMError + + +class MinimaxAdapter(BaseLLMAdapter): + """MiniMax API适配器""" + + def __init__(self, config: LLMConfig): + super().__init__(config) + self._base_url = config.base_url or "https://api.minimax.chat/v1" + + async def _do_complete(self, request: LLMRequest) -> LLMResponse: + """执行实际的API调用""" + url = f"{self._base_url}/text/chatcompletion_v2" + + messages = [{"role": m.role, "content": m.content} for m in request.messages] + + payload = { + "model": self.config.model or "abab6.5-chat", + "messages": messages, + "temperature": request.temperature or self.config.temperature, + "top_p": request.top_p or self.config.top_p, + } + + if request.max_tokens or self.config.max_tokens: + payload["max_tokens"] = request.max_tokens or self.config.max_tokens + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self.config.api_key}", + } + + async with httpx.AsyncClient(timeout=self.config.timeout) as client: + response = await client.post(url, json=payload, headers=headers) + + if response.status_code != 200: + raise LLMError( + f"MiniMax API错误: {response.text}", + provider="minimax", + status_code=response.status_code + ) + + data = response.json() + + if data.get("base_resp", {}).get("status_code") != 0: + raise LLMError( + f"MiniMax API错误: {data.get('base_resp', {}).get('status_msg', '未知错误')}", + provider="minimax" + ) + + choices = data.get("choices", []) + if not choices: + raise LLMError("MiniMax API返回空响应", provider="minimax") + + return LLMResponse( + content=choices[0].get("message", {}).get("content", ""), + model=self.config.model or "abab6.5-chat", + usage=data.get("usage"), + finish_reason=choices[0].get("finish_reason") + ) + + async def validate_config(self) -> bool: + """验证配置是否有效""" + try: + test_request = LLMRequest( + messages=[{"role": "user", "content": "Hi"}], + max_tokens=10 + ) + await self._do_complete(test_request) + return True + except Exception: + return False + + def get_provider(self) -> str: + return "minimax" + + def get_model(self) -> str: + return self.config.model or "abab6.5-chat" + diff --git a/backend/app/services/llm/adapters/moonshot_adapter.py b/backend/app/services/llm/adapters/moonshot_adapter.py new file mode 100644 index 0000000..2c1bed3 --- /dev/null +++ b/backend/app/services/llm/adapters/moonshot_adapter.py @@ -0,0 +1,79 @@ +""" +月之暗面 Kimi适配器 - 兼容OpenAI格式 +""" + +from typing import Dict, Any +from ..base_adapter import BaseLLMAdapter +from ..types import LLMRequest, LLMResponse, LLMUsage, DEFAULT_BASE_URLS, LLMProvider + + +class MoonshotAdapter(BaseLLMAdapter): + """月之暗面Kimi适配器""" + + @property + def base_url(self) -> str: + return self.config.base_url or DEFAULT_BASE_URLS.get(LLMProvider.MOONSHOT, "https://api.moonshot.cn/v1") + + async def complete(self, request: LLMRequest) -> LLMResponse: + try: + await self.validate_config() + return await self.retry(lambda: self._send_request(request)) + except Exception as error: + self.handle_error(error, "Moonshot API调用失败") + + async def _send_request(self, request: LLMRequest) -> LLMResponse: + # Moonshot API兼容OpenAI格式 + headers = { + "Authorization": f"Bearer {self.config.api_key}", + } + + messages = [{"role": msg.role, "content": msg.content} for msg in request.messages] + + request_body: Dict[str, Any] = { + "model": self.config.model, + "messages": messages, + "temperature": request.temperature if request.temperature is not None else self.config.temperature, + "max_tokens": request.max_tokens if request.max_tokens is not None else self.config.max_tokens, + "top_p": request.top_p if request.top_p is not None else self.config.top_p, + } + + url = f"{self.base_url.rstrip('/')}/chat/completions" + + response = await self.client.post( + url, + headers=self.build_headers(headers), + json=request_body + ) + + if response.status_code != 200: + error_data = response.json() if response.text else {} + error_msg = error_data.get("error", {}).get("message", f"HTTP {response.status_code}") + raise Exception(f"{error_msg}") + + data = response.json() + choice = data.get("choices", [{}])[0] + + if not choice: + raise Exception("API响应格式异常: 缺少choices字段") + + usage = None + if "usage" in data: + usage = LLMUsage( + prompt_tokens=data["usage"].get("prompt_tokens", 0), + completion_tokens=data["usage"].get("completion_tokens", 0), + total_tokens=data["usage"].get("total_tokens", 0) + ) + + return LLMResponse( + content=choice.get("message", {}).get("content", ""), + model=data.get("model"), + usage=usage, + finish_reason=choice.get("finish_reason") + ) + + async def validate_config(self) -> bool: + await super().validate_config() + if not self.config.model: + raise Exception("未指定Moonshot模型") + return True + diff --git a/backend/app/services/llm/adapters/ollama_adapter.py b/backend/app/services/llm/adapters/ollama_adapter.py new file mode 100644 index 0000000..69b47d5 --- /dev/null +++ b/backend/app/services/llm/adapters/ollama_adapter.py @@ -0,0 +1,82 @@ +""" +Ollama本地大模型适配器 - 兼容OpenAI格式 +""" + +from typing import Dict, Any +from ..base_adapter import BaseLLMAdapter +from ..types import LLMRequest, LLMResponse, LLMUsage, DEFAULT_BASE_URLS, LLMProvider + + +class OllamaAdapter(BaseLLMAdapter): + """Ollama本地模型适配器""" + + @property + def base_url(self) -> str: + return self.config.base_url or DEFAULT_BASE_URLS.get(LLMProvider.OLLAMA, "http://localhost:11434/v1") + + async def complete(self, request: LLMRequest) -> LLMResponse: + try: + # Ollama本地运行,跳过API Key验证 + return await self.retry(lambda: self._send_request(request)) + except Exception as error: + self.handle_error(error, "Ollama API调用失败") + + async def _send_request(self, request: LLMRequest) -> LLMResponse: + # Ollama兼容OpenAI格式 + headers = {} + if self.config.api_key: + headers["Authorization"] = f"Bearer {self.config.api_key}" + + messages = [{"role": msg.role, "content": msg.content} for msg in request.messages] + + request_body: Dict[str, Any] = { + "model": self.config.model, + "messages": messages, + "temperature": request.temperature if request.temperature is not None else self.config.temperature, + "top_p": request.top_p if request.top_p is not None else self.config.top_p, + } + + # Ollama的max_tokens参数名可能不同 + if request.max_tokens or self.config.max_tokens: + request_body["num_predict"] = request.max_tokens or self.config.max_tokens + + url = f"{self.base_url.rstrip('/')}/chat/completions" + + response = await self.client.post( + url, + headers=self.build_headers(headers) if headers else self.build_headers(), + json=request_body + ) + + if response.status_code != 200: + error_data = response.json() if response.text else {} + error_msg = error_data.get("error", {}).get("message", f"HTTP {response.status_code}") + raise Exception(f"{error_msg}") + + data = response.json() + choice = data.get("choices", [{}])[0] + + if not choice: + raise Exception("API响应格式异常: 缺少choices字段") + + usage = None + if "usage" in data: + usage = LLMUsage( + prompt_tokens=data["usage"].get("prompt_tokens", 0), + completion_tokens=data["usage"].get("completion_tokens", 0), + total_tokens=data["usage"].get("total_tokens", 0) + ) + + return LLMResponse( + content=choice.get("message", {}).get("content", ""), + model=data.get("model"), + usage=usage, + finish_reason=choice.get("finish_reason") + ) + + async def validate_config(self) -> bool: + # Ollama本地运行,不需要API Key + if not self.config.model: + raise Exception("未指定Ollama模型") + return True + diff --git a/backend/app/services/llm/adapters/openai_adapter.py b/backend/app/services/llm/adapters/openai_adapter.py new file mode 100644 index 0000000..48d5714 --- /dev/null +++ b/backend/app/services/llm/adapters/openai_adapter.py @@ -0,0 +1,92 @@ +""" +OpenAI适配器 (支持GPT系列和OpenAI兼容API) +""" + +from typing import Dict, Any +from ..base_adapter import BaseLLMAdapter +from ..types import LLMRequest, LLMResponse, LLMUsage, DEFAULT_BASE_URLS, LLMProvider + + +class OpenAIAdapter(BaseLLMAdapter): + """OpenAI适配器""" + + @property + def base_url(self) -> str: + return self.config.base_url or DEFAULT_BASE_URLS.get(LLMProvider.OPENAI, "https://api.openai.com/v1") + + async def complete(self, request: LLMRequest) -> LLMResponse: + try: + await self.validate_config() + return await self.retry(lambda: self._send_request(request)) + except Exception as error: + self.handle_error(error, "OpenAI API调用失败") + + async def _send_request(self, request: LLMRequest) -> LLMResponse: + # 构建请求头 + headers = { + "Authorization": f"Bearer {self.config.api_key}", + } + + # 检测是否为推理模型(o1/o3系列) + model_name = self.config.model.lower() + is_reasoning_model = "o1" in model_name or "o3" in model_name + + # 构建请求体 + messages = [{"role": msg.role, "content": msg.content} for msg in request.messages] + + request_body: Dict[str, Any] = { + "model": self.config.model, + "messages": messages, + "temperature": request.temperature if request.temperature is not None else self.config.temperature, + "top_p": request.top_p if request.top_p is not None else self.config.top_p, + "frequency_penalty": self.config.frequency_penalty, + "presence_penalty": self.config.presence_penalty, + } + + # 推理模型使用max_completion_tokens,其他模型使用max_tokens + max_tokens = request.max_tokens if request.max_tokens is not None else self.config.max_tokens + if is_reasoning_model: + request_body["max_completion_tokens"] = max_tokens + else: + request_body["max_tokens"] = max_tokens + + url = f"{self.base_url.rstrip('/')}/chat/completions" + + response = await self.client.post( + url, + headers=self.build_headers(headers), + json=request_body + ) + + if response.status_code != 200: + error_data = response.json() if response.text else {} + error_msg = error_data.get("error", {}).get("message", f"HTTP {response.status_code}") + raise Exception(f"{error_msg}") + + data = response.json() + choice = data.get("choices", [{}])[0] + + if not choice: + raise Exception("API响应格式异常: 缺少choices字段") + + usage = None + if "usage" in data: + usage = LLMUsage( + prompt_tokens=data["usage"].get("prompt_tokens", 0), + completion_tokens=data["usage"].get("completion_tokens", 0), + total_tokens=data["usage"].get("total_tokens", 0) + ) + + return LLMResponse( + content=choice.get("message", {}).get("content", ""), + model=data.get("model"), + usage=usage, + finish_reason=choice.get("finish_reason") + ) + + async def validate_config(self) -> bool: + await super().validate_config() + if not self.config.model: + raise Exception("未指定OpenAI模型") + return True + diff --git a/backend/app/services/llm/adapters/qwen_adapter.py b/backend/app/services/llm/adapters/qwen_adapter.py new file mode 100644 index 0000000..af6d33b --- /dev/null +++ b/backend/app/services/llm/adapters/qwen_adapter.py @@ -0,0 +1,79 @@ +""" +阿里云通义千问适配器 - 兼容OpenAI格式 +""" + +from typing import Dict, Any +from ..base_adapter import BaseLLMAdapter +from ..types import LLMRequest, LLMResponse, LLMUsage, DEFAULT_BASE_URLS, LLMProvider + + +class QwenAdapter(BaseLLMAdapter): + """通义千问适配器""" + + @property + def base_url(self) -> str: + return self.config.base_url or DEFAULT_BASE_URLS.get(LLMProvider.QWEN, "https://dashscope.aliyuncs.com/compatible-mode/v1") + + async def complete(self, request: LLMRequest) -> LLMResponse: + try: + await self.validate_config() + return await self.retry(lambda: self._send_request(request)) + except Exception as error: + self.handle_error(error, "通义千问 API调用失败") + + async def _send_request(self, request: LLMRequest) -> LLMResponse: + # 通义千问兼容OpenAI格式 + headers = { + "Authorization": f"Bearer {self.config.api_key}", + } + + messages = [{"role": msg.role, "content": msg.content} for msg in request.messages] + + request_body: Dict[str, Any] = { + "model": self.config.model, + "messages": messages, + "temperature": request.temperature if request.temperature is not None else self.config.temperature, + "max_tokens": request.max_tokens if request.max_tokens is not None else self.config.max_tokens, + "top_p": request.top_p if request.top_p is not None else self.config.top_p, + } + + url = f"{self.base_url.rstrip('/')}/chat/completions" + + response = await self.client.post( + url, + headers=self.build_headers(headers), + json=request_body + ) + + if response.status_code != 200: + error_data = response.json() if response.text else {} + error_msg = error_data.get("error", {}).get("message", f"HTTP {response.status_code}") + raise Exception(f"{error_msg}") + + data = response.json() + choice = data.get("choices", [{}])[0] + + if not choice: + raise Exception("API响应格式异常: 缺少choices字段") + + usage = None + if "usage" in data: + usage = LLMUsage( + prompt_tokens=data["usage"].get("prompt_tokens", 0), + completion_tokens=data["usage"].get("completion_tokens", 0), + total_tokens=data["usage"].get("total_tokens", 0) + ) + + return LLMResponse( + content=choice.get("message", {}).get("content", ""), + model=data.get("model"), + usage=usage, + finish_reason=choice.get("finish_reason") + ) + + async def validate_config(self) -> bool: + await super().validate_config() + if not self.config.model: + raise Exception("未指定通义千问模型") + return True + diff --git a/backend/app/services/llm/adapters/zhipu_adapter.py b/backend/app/services/llm/adapters/zhipu_adapter.py new file mode 100644 index 0000000..d78b0a8 --- /dev/null +++ b/backend/app/services/llm/adapters/zhipu_adapter.py @@ -0,0 +1,79 @@ +""" +智谱AI适配器 (GLM系列) - 兼容OpenAI格式 +""" + +from typing import Dict, Any +from ..base_adapter import BaseLLMAdapter +from ..types import LLMRequest, LLMResponse, LLMUsage, DEFAULT_BASE_URLS, LLMProvider + + +class ZhipuAdapter(BaseLLMAdapter): + """智谱AI适配器""" + + @property + def base_url(self) -> str: + return self.config.base_url or DEFAULT_BASE_URLS.get(LLMProvider.ZHIPU, "https://open.bigmodel.cn/api/paas/v4") + + async def complete(self, request: LLMRequest) -> LLMResponse: + try: + await self.validate_config() + return await self.retry(lambda: self._send_request(request)) + except Exception as error: + self.handle_error(error, "智谱AI API调用失败") + + async def _send_request(self, request: LLMRequest) -> LLMResponse: + # 智谱AI兼容OpenAI格式 + headers = { + "Authorization": f"Bearer {self.config.api_key}", + } + + messages = [{"role": msg.role, "content": msg.content} for msg in request.messages] + + request_body: Dict[str, Any] = { + "model": self.config.model, + "messages": messages, + "temperature": request.temperature if request.temperature is not None else self.config.temperature, + "max_tokens": request.max_tokens if request.max_tokens is not None else self.config.max_tokens, + "top_p": request.top_p if request.top_p is not None else self.config.top_p, + } + + url = f"{self.base_url.rstrip('/')}/chat/completions" + + response = await self.client.post( + url, + headers=self.build_headers(headers), + json=request_body + ) + + if response.status_code != 200: + error_data = response.json() if response.text else {} + error_msg = error_data.get("error", {}).get("message", f"HTTP {response.status_code}") + raise Exception(f"{error_msg}") + + data = response.json() + choice = data.get("choices", [{}])[0] + + if not choice: + raise Exception("API响应格式异常: 缺少choices字段") + + usage = None + if "usage" in data: + usage = LLMUsage( + prompt_tokens=data["usage"].get("prompt_tokens", 0), + completion_tokens=data["usage"].get("completion_tokens", 0), + total_tokens=data["usage"].get("total_tokens", 0) + ) + + return LLMResponse( + content=choice.get("message", {}).get("content", ""), + model=data.get("model"), + usage=usage, + finish_reason=choice.get("finish_reason") + ) + + async def validate_config(self) -> bool: + await super().validate_config() + if not self.config.model: + raise Exception("未指定智谱AI模型") + return True + diff --git a/backend/app/services/llm/base_adapter.py b/backend/app/services/llm/base_adapter.py new file mode 100644 index 0000000..2314d08 --- /dev/null +++ b/backend/app/services/llm/base_adapter.py @@ -0,0 +1,134 @@ +""" +LLM适配器基类 +""" + +import asyncio +from abc import ABC, abstractmethod +from typing import Dict, Any, Optional +import httpx + +from .types import LLMConfig, LLMRequest, LLMResponse, LLMProvider, LLMError + + +class BaseLLMAdapter(ABC): + """LLM适配器基类""" + + def __init__(self, config: LLMConfig): + self.config = config + self._client: Optional[httpx.AsyncClient] = None + + @property + def client(self) -> httpx.AsyncClient: + """获取HTTP客户端""" + if self._client is None: + self._client = httpx.AsyncClient(timeout=self.config.timeout) + return self._client + + @abstractmethod + async def complete(self, request: LLMRequest) -> LLMResponse: + """发送请求并获取响应""" + pass + + def get_provider(self) -> LLMProvider: + """获取提供商名称""" + return self.config.provider + + def get_model(self) -> str: + """获取模型名称""" + return self.config.model + + async def validate_config(self) -> bool: + """验证配置是否有效""" + if not self.config.api_key: + raise LLMError( + "API Key未配置", + self.config.provider + ) + return True + + async def with_timeout(self, coro, timeout_seconds: Optional[int] = None) -> Any: + """处理超时""" + timeout = timeout_seconds or self.config.timeout + try: + return await asyncio.wait_for(coro, timeout=timeout) + except asyncio.TimeoutError: + raise LLMError( + f"请求超时 ({timeout}s)", + self.config.provider + ) + + def handle_error(self, error: Any, context: str = "") -> None: + """处理API错误""" + message = str(error) + status_code = getattr(error, 'status_code', None) + + # 针对不同错误类型提供更详细的信息 + if "超时" in message or "timeout" in message.lower(): + message = f"请求超时 ({self.config.timeout}s)。建议:\n" \ + f"1. 检查网络连接是否正常\n" \ + f"2. 尝试增加超时时间\n" \ + f"3. 验证API端点是否正确" + elif status_code == 401 or status_code == 403: + message = f"API认证失败。建议:\n" \ + f"1. 检查API Key是否正确配置\n" \ + f"2. 确认API Key是否有效且未过期\n" \ + f"3. 验证API Key权限是否充足" + elif status_code == 429: + message = f"API调用频率超限。建议:\n" \ + f"1. 等待一段时间后重试\n" \ + f"2. 降低并发数\n" \ + f"3. 增加请求间隔" + elif status_code and status_code >= 500: + message = f"API服务异常 ({status_code})。建议:\n" \ + f"1. 稍后重试\n" \ + f"2. 检查服务商状态页面\n" \ + f"3. 尝试切换其他LLM提供商" + + full_message = f"{context}: {message}" if context else message + + raise LLMError( + full_message, + self.config.provider, + status_code, + error + ) + + async def retry(self, fn, max_attempts: int = 3, delay: float = 1.0) -> Any: + """重试逻辑""" + last_error = None + + for attempt in range(max_attempts): + try: + return await fn() + except Exception as error: + last_error = error + status_code = getattr(error, 'status_code', None) + + # 如果是4xx错误(客户端错误),不重试 + if status_code and 400 <= status_code < 500: + raise error + + # 最后一次尝试时不等待 + if attempt < max_attempts - 1: + # 指数退避 + await asyncio.sleep(delay * (2 ** attempt)) + + raise last_error + + def build_headers(self, additional_headers: Dict[str, str] = None) -> Dict[str, str]: + """构建请求头""" + headers = { + "Content-Type": "application/json", + } + if additional_headers: + headers.update(additional_headers) + if self.config.custom_headers: + headers.update(self.config.custom_headers) + return headers + + async def close(self): + """关闭客户端""" + if self._client: + await self._client.aclose() + self._client = None + diff --git a/backend/app/services/llm/factory.py b/backend/app/services/llm/factory.py new file mode 100644 index 0000000..04af77d --- /dev/null +++ b/backend/app/services/llm/factory.py @@ -0,0 +1,165 @@ +""" +LLM工厂类 - 统一创建和管理LLM适配器 +""" + +from typing import Dict, List, Optional +from .types import LLMConfig, LLMProvider, DEFAULT_MODELS +from .base_adapter import BaseLLMAdapter +from .adapters import ( + OpenAIAdapter, + GeminiAdapter, + ClaudeAdapter, + DeepSeekAdapter, + QwenAdapter, + ZhipuAdapter, + MoonshotAdapter, + BaiduAdapter, + MinimaxAdapter, + DoubaoAdapter, + OllamaAdapter, +) + + +class LLMFactory: + """LLM工厂类""" + + _adapters: Dict[str, BaseLLMAdapter] = {} + + @classmethod + def create_adapter(cls, config: LLMConfig) -> BaseLLMAdapter: + """创建LLM适配器实例""" + cache_key = cls._get_cache_key(config) + + # 从缓存中获取 + if cache_key in cls._adapters: + return cls._adapters[cache_key] + + # 创建新的适配器实例 + adapter = cls._instantiate_adapter(config) + + # 缓存实例 + cls._adapters[cache_key] = adapter + + return adapter + + @classmethod + def _instantiate_adapter(cls, config: LLMConfig) -> BaseLLMAdapter: + """根据提供商类型实例化适配器""" + # 如果未指定模型,使用默认模型 + if not config.model: + config.model = DEFAULT_MODELS.get(config.provider, "gpt-4o-mini") + + adapter_map = { + LLMProvider.OPENAI: OpenAIAdapter, + LLMProvider.GEMINI: GeminiAdapter, + LLMProvider.CLAUDE: ClaudeAdapter, + LLMProvider.DEEPSEEK: DeepSeekAdapter, + LLMProvider.QWEN: QwenAdapter, + LLMProvider.ZHIPU: ZhipuAdapter, + LLMProvider.MOONSHOT: MoonshotAdapter, + LLMProvider.BAIDU: BaiduAdapter, + LLMProvider.MINIMAX: MinimaxAdapter, + LLMProvider.DOUBAO: DoubaoAdapter, + LLMProvider.OLLAMA: OllamaAdapter, + } + + adapter_class = adapter_map.get(config.provider) + if not adapter_class: + raise ValueError(f"不支持的LLM提供商: {config.provider}") + + return adapter_class(config) + + @classmethod + def _get_cache_key(cls, config: LLMConfig) -> str: + """生成缓存键""" + api_key_prefix = config.api_key[:8] if config.api_key else "no-key" + return f"{config.provider.value}:{config.model}:{api_key_prefix}" + + @classmethod + def clear_cache(cls) -> None: + """清除缓存""" + cls._adapters.clear() + + @classmethod + def get_supported_providers(cls) -> List[LLMProvider]: + """获取支持的提供商列表""" + return list(LLMProvider) + + @classmethod + def get_default_model(cls, provider: LLMProvider) -> str: + """获取提供商的默认模型""" + return DEFAULT_MODELS.get(provider, "gpt-4o-mini") + + @classmethod + def get_available_models(cls, provider: LLMProvider) -> List[str]: + """获取提供商的可用模型列表""" + models = { + LLMProvider.GEMINI: [ + "gemini-2.5-flash", + "gemini-2.5-pro", + "gemini-1.5-flash", + "gemini-1.5-pro", + ], + LLMProvider.OPENAI: [ + "gpt-4o", + "gpt-4o-mini", + "gpt-4-turbo", + "gpt-4", + "gpt-3.5-turbo", + "o1-preview", + "o1-mini", + ], + LLMProvider.CLAUDE: [ + "claude-3-5-sonnet-20241022", + "claude-3-opus-20240229", + "claude-3-sonnet-20240229", + "claude-3-haiku-20240307", + ], + LLMProvider.QWEN: [ + "qwen-turbo", + "qwen-plus", + "qwen-max", + "qwen-max-longcontext", + ], + LLMProvider.DEEPSEEK: [ + "deepseek-chat", + "deepseek-coder", + ], + LLMProvider.ZHIPU: [ + "glm-4-flash", + "glm-4", + "glm-4-plus", + "glm-4-long", + ], + LLMProvider.MOONSHOT: [ + "moonshot-v1-8k", + "moonshot-v1-32k", + "moonshot-v1-128k", + ], + LLMProvider.BAIDU: [ + "ERNIE-3.5-8K", + "ERNIE-4.0-8K", + "ERNIE-Speed-8K", + ], + LLMProvider.MINIMAX: [ + "abab6.5-chat", + "abab6.5s-chat", + "abab5.5-chat", + ], + LLMProvider.DOUBAO: [ + "doubao-pro-32k", + "doubao-pro-128k", + "doubao-lite-32k", + ], + LLMProvider.OLLAMA: [ + "llama3", + "llama3.1", + "llama3.2", + "codellama", + "mistral", + "deepseek-coder-v2", + "qwen2.5-coder", + ], + } + return models.get(provider, []) + diff --git a/backend/app/services/llm/service.py b/backend/app/services/llm/service.py new file mode 100644 index 0000000..90c36b0 --- /dev/null +++ b/backend/app/services/llm/service.py @@ -0,0 +1,586 @@ +""" +LLM服务 - 代码分析核心服务 +支持中英文双语输出 +""" + +import json +import re +import logging +from typing import Dict, Any, Optional +from .types import LLMConfig, LLMProvider, LLMMessage, LLMRequest, DEFAULT_MODELS +from .factory import LLMFactory +from app.core.config import settings + +logger = logging.getLogger(__name__) + + +class LLMService: + """LLM服务类""" + + def __init__(self, user_config: Optional[Dict[str, Any]] = None): + """ + 初始化LLM服务 + + Args: + user_config: 用户配置字典,包含llmConfig字段 + """ + self._config: Optional[LLMConfig] = None + self._user_config = user_config or {} + + @property + def config(self) -> LLMConfig: + """获取LLM配置(优先使用用户配置,然后使用系统配置)""" + if self._config is None: + user_llm_config = self._user_config.get('llmConfig', {}) + + # 优先使用用户配置的provider,否则使用系统配置 + provider_str = user_llm_config.get('llmProvider') or getattr(settings, 'LLM_PROVIDER', 'openai') + provider = self._parse_provider(provider_str) + + # 获取API Key - 优先级:用户配置 > 系统通用配置 > 系统平台专属配置 + api_key = ( + user_llm_config.get('llmApiKey') or + getattr(settings, 'LLM_API_KEY', '') or + self._get_provider_api_key_from_user_config(provider, user_llm_config) or + self._get_provider_api_key(provider) + ) + + # 获取Base URL + base_url = ( + user_llm_config.get('llmBaseUrl') or + getattr(settings, 'LLM_BASE_URL', None) or + self._get_provider_base_url(provider) + ) + + # 获取模型 + model = ( + user_llm_config.get('llmModel') or + getattr(settings, 'LLM_MODEL', '') or + DEFAULT_MODELS.get(provider, 'gpt-4o-mini') + ) + + # 获取超时时间(用户配置是毫秒,系统配置是秒) + timeout_ms = user_llm_config.get('llmTimeout') + if timeout_ms: + # 用户配置是毫秒,转换为秒 + timeout = int(timeout_ms / 1000) if timeout_ms > 1000 else int(timeout_ms) + else: + # 系统配置是秒 + timeout = int(getattr(settings, 'LLM_TIMEOUT', 150)) + + # 获取温度 + temperature = user_llm_config.get('llmTemperature') if user_llm_config.get('llmTemperature') is not None else float(getattr(settings, 'LLM_TEMPERATURE', 0.1)) + + # 获取最大token数 + max_tokens = user_llm_config.get('llmMaxTokens') or int(getattr(settings, 'LLM_MAX_TOKENS', 4096)) + + self._config = LLMConfig( + provider=provider, + api_key=api_key, + model=model, + base_url=base_url, + timeout=timeout, + temperature=temperature, + max_tokens=max_tokens, + ) + return self._config + + def _get_provider_api_key_from_user_config(self, provider: LLMProvider, user_llm_config: Dict[str, Any]) -> Optional[str]: + """从用户配置中获取平台专属API Key""" + provider_key_map = { + LLMProvider.OPENAI: 'openaiApiKey', + LLMProvider.GEMINI: 'geminiApiKey', + LLMProvider.CLAUDE: 'claudeApiKey', + LLMProvider.QWEN: 'qwenApiKey', + LLMProvider.DEEPSEEK: 'deepseekApiKey', + LLMProvider.ZHIPU: 'zhipuApiKey', + LLMProvider.MOONSHOT: 'moonshotApiKey', + LLMProvider.BAIDU: 'baiduApiKey', + LLMProvider.MINIMAX: 'minimaxApiKey', + LLMProvider.DOUBAO: 'doubaoApiKey', + } + key_name = provider_key_map.get(provider) + if key_name: + return user_llm_config.get(key_name) + return None + + def _get_provider_api_key(self, provider: LLMProvider) -> str: + """根据提供商获取API Key""" + provider_key_map = { + LLMProvider.OPENAI: 'OPENAI_API_KEY', + LLMProvider.GEMINI: 'GEMINI_API_KEY', + LLMProvider.CLAUDE: 'CLAUDE_API_KEY', + LLMProvider.QWEN: 'QWEN_API_KEY', + LLMProvider.DEEPSEEK: 'DEEPSEEK_API_KEY', + LLMProvider.ZHIPU: 'ZHIPU_API_KEY', + LLMProvider.MOONSHOT: 'MOONSHOT_API_KEY', + LLMProvider.BAIDU: 'BAIDU_API_KEY', + LLMProvider.MINIMAX: 'MINIMAX_API_KEY', + LLMProvider.DOUBAO: 'DOUBAO_API_KEY', + LLMProvider.OLLAMA: None, # Ollama 不需要 API Key + } + key_name = provider_key_map.get(provider) + if key_name: + return getattr(settings, key_name, '') or '' + return 'ollama' # Ollama的默认值 + + def _get_provider_base_url(self, provider: LLMProvider) -> Optional[str]: + """根据提供商获取Base URL""" + if provider == LLMProvider.OPENAI: + return getattr(settings, 'OPENAI_BASE_URL', None) + elif provider == LLMProvider.OLLAMA: + return getattr(settings, 'OLLAMA_BASE_URL', 'http://localhost:11434/v1') + return None + + def _parse_provider(self, provider_str: str) -> LLMProvider: + """解析provider字符串""" + provider_map = { + 'gemini': LLMProvider.GEMINI, + 'openai': LLMProvider.OPENAI, + 'claude': LLMProvider.CLAUDE, + 'qwen': LLMProvider.QWEN, + 'deepseek': LLMProvider.DEEPSEEK, + 'zhipu': LLMProvider.ZHIPU, + 'moonshot': LLMProvider.MOONSHOT, + 'baidu': LLMProvider.BAIDU, + 'minimax': LLMProvider.MINIMAX, + 'doubao': LLMProvider.DOUBAO, + 'ollama': LLMProvider.OLLAMA, + } + return provider_map.get(provider_str.lower(), LLMProvider.OPENAI) + + def _get_output_language(self) -> str: + """获取输出语言配置(优先使用用户配置)""" + user_other_config = self._user_config.get('otherConfig', {}) + return user_other_config.get('outputLanguage') or getattr(settings, 'OUTPUT_LANGUAGE', 'zh-CN') + + def _build_system_prompt(self, is_chinese: bool) -> str: + """构建系统提示词(支持中英文)""" + schema = """{ + "issues": [ + { + "type": "security|bug|performance|style|maintainability", + "severity": "critical|high|medium|low", + "title": "string", + "description": "string", + "suggestion": "string", + "line": 1, + "column": 1, + "code_snippet": "string", + "ai_explanation": "string", + "xai": { + "what": "string", + "why": "string", + "how": "string", + "learn_more": "string(optional)" + } + } + ], + "quality_score": 0-100, + "summary": { + "total_issues": number, + "critical_issues": number, + "high_issues": number, + "medium_issues": number, + "low_issues": number + }, + "metrics": { + "complexity": 0-100, + "maintainability": 0-100, + "security": 0-100, + "performance": 0-100 + } +}""" + + if is_chinese: + return f"""⚠️⚠️⚠️ 只输出JSON,禁止输出其他任何格式!禁止markdown!禁止文本分析!⚠️⚠️⚠️ + +你是一个专业的代码审计助手。你的任务是分析代码并返回严格符合JSON Schema的结果。 + +【最重要】输出格式要求: +1. 必须只输出纯JSON对象,从{{开始,到}}结束 +2. 禁止在JSON前后添加任何文字、说明、markdown标记 +3. 禁止输出```json或###等markdown语法 +4. 如果是文档文件(如README),也必须以JSON格式输出分析结果 + +【内容要求】: +1. 所有文本内容必须统一使用简体中文 +2. JSON字符串值中的特殊字符必须正确转义(换行用\\n,双引号用\\",反斜杠用\\\\) +3. code_snippet字段必须使用\\n表示换行 + +请从以下维度全面分析代码: +- 编码规范和代码风格 +- 潜在的 Bug 和逻辑错误 +- 性能问题和优化建议 +- 安全漏洞和风险 +- 可维护性和可读性 +- 最佳实践和设计模式 + +输出格式必须严格符合以下 JSON Schema: + +{schema} + +注意: +- title: 问题的简短标题(中文) +- description: 详细描述问题(中文) +- suggestion: 具体的修复建议(中文) +- line: 问题所在的行号(从1开始计数,必须准确对应代码中的行号) +- column: 问题所在的列号(从1开始计数,指向问题代码的起始位置) +- code_snippet: 包含问题的代码片段(建议包含问题行及其前后1-2行作为上下文,保持原始缩进格式) +- ai_explanation: AI 的深入解释(中文) +- xai.what: 这是什么问题(中文) +- xai.why: 为什么会有这个问题(中文) +- xai.how: 如何修复这个问题(中文) + +【重要】关于行号和代码片段: +1. line 必须是问题代码的行号!!!代码左侧有"行号|"标注,例如"25| const x = 1"表示第25行,line字段必须填25 +2. column 是问题代码在该行中的起始列位置(从1开始,不包括"行号|"前缀部分) +3. code_snippet 应该包含问题代码及其上下文(前后各1-2行),去掉"行号|"前缀,保持原始代码的缩进 +4. 如果代码片段包含多行,必须使用 \\n 表示换行符(这是JSON的要求) +5. 如果无法确定准确的行号,不要填写line和column字段(不要填0) + +【严格禁止】: +- 禁止在任何字段中使用英文,所有内容必须是简体中文 +- 禁止在JSON字符串值中使用真实换行符,必须用\\n转义 +- 禁止输出markdown代码块标记(如```json) + +⚠️ 重要提醒:line字段必须从代码左侧的行号标注中读取,不要猜测或填0!""" + else: + return f"""⚠️⚠️⚠️ OUTPUT JSON ONLY! NO OTHER FORMAT! NO MARKDOWN! NO TEXT ANALYSIS! ⚠️⚠️⚠️ + +You are a professional code auditing assistant. Your task is to analyze code and return results in strict JSON Schema format. + +【MOST IMPORTANT】Output format requirements: +1. MUST output pure JSON object only, starting with {{ and ending with }} +2. NO text, explanation, or markdown markers before or after JSON +3. NO ```json or ### markdown syntax +4. Even for document files (like README), output analysis in JSON format + +【Content requirements】: +1. All text content MUST be in English ONLY +2. Special characters in JSON strings must be properly escaped (\\n for newlines, \\" for quotes, \\\\ for backslashes) +3. code_snippet field MUST use \\n for newlines + +Please comprehensively analyze the code from the following dimensions: +- Coding standards and code style +- Potential bugs and logical errors +- Performance issues and optimization suggestions +- Security vulnerabilities and risks +- Maintainability and readability +- Best practices and design patterns + +The output format MUST strictly conform to the following JSON Schema: + +{schema} + +Note: +- title: Brief title of the issue (in English) +- description: Detailed description of the issue (in English) +- suggestion: Specific fix suggestions (in English) +- line: Line number where the issue occurs (1-indexed, must accurately correspond to the line in the code) +- column: Column number where the issue starts (1-indexed, pointing to the start position of the problematic code) +- code_snippet: Code snippet containing the issue (should include the problem line plus 1-2 lines before and after for context, preserve original indentation) +- ai_explanation: AI's in-depth explanation (in English) +- xai.what: What is this issue (in English) +- xai.why: Why does this issue exist (in English) +- xai.how: How to fix this issue (in English) + +【IMPORTANT】About line numbers and code snippets: +1. 'line' MUST be the line number from code!!! Code has "lineNumber|" prefix, e.g. "25| const x = 1" means line 25, you MUST set line to 25 +2. 'column' is the starting column position in that line (1-indexed, excluding the "lineNumber|" prefix) +3. 'code_snippet' should include the problematic code with context (1-2 lines before/after), remove "lineNumber|" prefix, preserve indentation +4. If code snippet has multiple lines, use \\n for newlines (JSON requirement) +5. If you cannot determine the exact line number, do NOT fill line and column fields (don't use 0) + +【STRICTLY PROHIBITED】: +- NO Chinese characters in any field - English ONLY +- NO real newline characters in JSON string values - must use \\n +- NO markdown code block markers (like ```json) + +⚠️ CRITICAL: Read line numbers from the "lineNumber|" prefix on the left of each code line. Do NOT guess or use 0!""" + + async def analyze_code(self, code: str, language: str) -> Dict[str, Any]: + """ + 分析代码并返回结构化问题 + 支持中英文输出 + """ + # 获取输出语言配置 + output_language = self._get_output_language() + is_chinese = output_language == 'zh-CN' + + # 添加行号帮助LLM定位问题 + code_with_lines = '\n'.join( + f"{i+1}| {line}" for i, line in enumerate(code.split('\n')) + ) + + # 构建系统提示词 + system_prompt = self._build_system_prompt(is_chinese) + + # 构建用户提示词 + if is_chinese: + user_prompt = f"""编程语言: {language} + +⚠️ 代码已标注行号(格式:行号| 代码内容),请根据行号准确填写 line 字段! + +请分析以下代码: + +{code_with_lines}""" + else: + user_prompt = f"""Programming Language: {language} + +⚠️ Code is annotated with line numbers (format: lineNumber| code), please fill the 'line' field accurately based on these numbers! + +Please analyze the following code: + +{code_with_lines}""" + + try: + adapter = LLMFactory.create_adapter(self.config) + + request = LLMRequest( + messages=[ + LLMMessage(role="system", content=system_prompt), + LLMMessage(role="user", content=user_prompt) + ], + temperature=0.1, + ) + + response = await adapter.complete(request) + content = response.content + + # 检查响应内容是否为空 + if not content or not content.strip(): + logger.warning(f"LLM返回空响应 - Provider: {self.config.provider.value}, Model: {self.config.model}") + logger.warning(f"响应详情 - Finish Reason: {response.finish_reason}, Usage: {response.usage}") + return self._get_default_response() + + # 尝试从响应中提取JSON + result = self._parse_json(content) + return result + + except Exception as e: + logger.error(f"LLM Analysis failed: {e}", exc_info=True) + logger.error(f"Provider: {self.config.provider.value}, Model: {self.config.model}") + return self._get_default_response() + + def _parse_json(self, text: str) -> Dict[str, Any]: + """从LLM响应中解析JSON(增强版)""" + + # 检查输入是否为空 + if not text or not text.strip(): + logger.warning("LLM响应内容为空,无法解析JSON") + return self._get_default_response() + + def clean_text(s: str) -> str: + """清理文本中的控制字符""" + # 移除BOM和零宽字符 + s = s.replace('\ufeff', '').replace('\u200b', '').replace('\u200c', '').replace('\u200d', '') + return s + + def fix_json_format(s: str) -> str: + """修复常见的JSON格式问题""" + s = s.strip() + # 移除尾部逗号 + s = re.sub(r',(\s*[}\]])', r'\1', s) + # 修复未转义的换行符(在字符串值中) + s = re.sub(r':\s*"([^"]*)\n([^"]*)"', r': "\1\\n\2"', s) + return s + + def aggressive_fix_json(s: str) -> str: + """激进的JSON修复:尝试修复更多格式问题""" + s = clean_text(s) + s = s.strip() + + # 找到第一个 { 和最后一个 } + start_idx = s.find('{') + if start_idx == -1: + raise ValueError("No JSON object found") + + # 尝试找到最后一个 } + last_brace = s.rfind('}') + if last_brace > start_idx: + s = s[start_idx:last_brace + 1] + + # 修复常见的JSON问题 + # 1. 移除尾部逗号 + s = re.sub(r',(\s*[}\]])', r'\1', s) + # 2. 修复单引号为双引号(仅在键名中,小心处理) + s = re.sub(r"'(\w+)'\s*:", r'"\1":', s) + # 3. 修复未转义的控制字符(在字符串值中,但不在键名中) + # 只移除不在引号内的控制字符,或未转义的换行符/制表符 + lines = [] + in_string = False + escape_next = False + for char in s: + if escape_next: + escape_next = False + lines.append(char) + continue + if char == '\\': + escape_next = True + lines.append(char) + continue + if char == '"': + in_string = not in_string + lines.append(char) + continue + # 如果在字符串外,移除控制字符;如果在字符串内,保留(假设已转义) + if not in_string and ord(char) < 32 and char not in ['\n', '\t', '\r']: + continue # 跳过控制字符 + lines.append(char) + s = ''.join(lines) + + return s + + # 尝试多种方式解析 + attempts = [ + # 1. 直接解析 + lambda: json.loads(text), + # 2. 清理后解析 + lambda: json.loads(fix_json_format(clean_text(text))), + # 3. 从markdown代码块提取 + lambda: self._extract_from_markdown(text), + # 4. 智能提取JSON对象 + lambda: self._extract_json_object(clean_text(text)), + # 5. 修复截断的JSON + lambda: self._fix_truncated_json(clean_text(text)), + # 6. 激进修复后解析 + lambda: json.loads(aggressive_fix_json(text)), + ] + + last_error = None + for i, attempt in enumerate(attempts): + try: + result = attempt() + if result and isinstance(result, dict): + if i > 0: + logger.info(f"✅ JSON解析成功(方法 {i + 1}/{len(attempts)})") + return result + except Exception as e: + last_error = e + if i == 0: + logger.debug(f"直接解析失败,尝试其他方法... {e}") + + # 所有尝试都失败 + logger.warning("⚠️ 无法解析LLM响应为JSON") + logger.warning(f"原始内容长度: {len(text)} 字符") + logger.warning(f"原始内容(前500字符): {text[:500]}") + logger.warning(f"原始内容(后500字符): {text[-500:] if len(text) > 500 else text}") + if last_error: + logger.warning(f"最后错误: {type(last_error).__name__}: {str(last_error)}") + return self._get_default_response() + + def _extract_from_markdown(self, text: str) -> Dict[str, Any]: + """从markdown代码块提取JSON""" + match = re.search(r'```(?:json)?\s*(\{[\s\S]*?\})\s*```', text) + if match: + return json.loads(match.group(1)) + raise ValueError("No markdown code block found") + + def _extract_json_object(self, text: str) -> Dict[str, Any]: + """智能提取JSON对象""" + start_idx = text.find('{') + if start_idx == -1: + raise ValueError("No JSON object found") + + # 考虑字符串内的花括号和转义字符 + brace_count = 0 + bracket_count = 0 + in_string = False + escape_next = False + end_idx = -1 + + for i in range(start_idx, len(text)): + char = text[i] + + if escape_next: + escape_next = False + continue + + if char == '\\': + escape_next = True + continue + + if char == '"' and not escape_next: + in_string = not in_string + continue + + if not in_string: + if char == '{': + brace_count += 1 + elif char == '}': + brace_count -= 1 + if brace_count == 0 and bracket_count == 0: + end_idx = i + 1 + break + elif char == '[': + bracket_count += 1 + elif char == ']': + bracket_count -= 1 + + if end_idx == -1: + # 如果找不到完整的JSON,尝试使用最后一个 } + last_brace = text.rfind('}') + if last_brace > start_idx: + end_idx = last_brace + 1 + else: + raise ValueError("Incomplete JSON object") + + json_str = text[start_idx:end_idx] + # 修复格式问题 + json_str = re.sub(r',(\s*[}\]])', r'\1', json_str) + # 尝试修复未闭合的括号 + open_braces = json_str.count('{') - json_str.count('}') + open_brackets = json_str.count('[') - json_str.count(']') + if open_braces > 0: + json_str += '}' * open_braces + if open_brackets > 0: + json_str += ']' * open_brackets + + return json.loads(json_str) + + def _fix_truncated_json(self, text: str) -> Dict[str, Any]: + """修复截断的JSON""" + start_idx = text.find('{') + if start_idx == -1: + raise ValueError("Cannot fix truncated JSON") + + json_str = text[start_idx:] + + # 计算缺失的闭合符号 + open_braces = json_str.count('{') + close_braces = json_str.count('}') + open_brackets = json_str.count('[') + close_brackets = json_str.count(']') + + # 补全缺失的闭合符号 + json_str += ']' * max(0, open_brackets - close_brackets) + json_str += '}' * max(0, open_braces - close_braces) + + # 修复格式 + json_str = re.sub(r',(\s*[}\]])', r'\1', json_str) + return json.loads(json_str) + + def _get_default_response(self) -> Dict[str, Any]: + """返回默认响应""" + return { + "issues": [], + "quality_score": 80, + "summary": { + "total_issues": 0, + "critical_issues": 0, + "high_issues": 0, + "medium_issues": 0, + "low_issues": 0 + }, + "metrics": { + "complexity": 80, + "maintainability": 80, + "security": 80, + "performance": 80 + } + } + + +# 全局服务实例 +llm_service = LLMService() diff --git a/backend/app/services/llm/types.py b/backend/app/services/llm/types.py new file mode 100644 index 0000000..605288d --- /dev/null +++ b/backend/app/services/llm/types.py @@ -0,0 +1,120 @@ +""" +LLM服务类型定义 +""" + +from enum import Enum +from typing import Optional, Dict, Any, List +from dataclasses import dataclass, field + + +class LLMProvider(str, Enum): + """支持的LLM提供商类型""" + GEMINI = "gemini" # Google Gemini + OPENAI = "openai" # OpenAI (GPT系列) + CLAUDE = "claude" # Anthropic Claude + QWEN = "qwen" # 阿里云通义千问 + DEEPSEEK = "deepseek" # DeepSeek + ZHIPU = "zhipu" # 智谱AI (GLM系列) + MOONSHOT = "moonshot" # 月之暗面 Kimi + BAIDU = "baidu" # 百度文心一言 + MINIMAX = "minimax" # MiniMax + DOUBAO = "doubao" # 字节豆包 + OLLAMA = "ollama" # Ollama 本地大模型 + + +@dataclass +class LLMConfig: + """LLM配置""" + provider: LLMProvider + api_key: str + model: str + base_url: Optional[str] = None + timeout: int = 150 + temperature: float = 0.2 + max_tokens: int = 4096 + top_p: float = 1.0 + frequency_penalty: float = 0 + presence_penalty: float = 0 + custom_headers: Dict[str, str] = field(default_factory=dict) + + +@dataclass +class LLMMessage: + """LLM请求消息""" + role: str # 'system', 'user', 'assistant' + content: str + + +@dataclass +class LLMRequest: + """LLM请求参数""" + messages: List[LLMMessage] + temperature: Optional[float] = None + max_tokens: Optional[int] = None + top_p: Optional[float] = None + stream: bool = False + + +@dataclass +class LLMUsage: + """Token使用统计""" + prompt_tokens: int + completion_tokens: int + total_tokens: int + + +@dataclass +class LLMResponse: + """LLM响应""" + content: str + model: Optional[str] = None + usage: Optional[LLMUsage] = None + finish_reason: Optional[str] = None + + +class LLMError(Exception): + """LLM错误""" + def __init__( + self, + message: str, + provider: Optional[LLMProvider] = None, + status_code: Optional[int] = None, + original_error: Optional[Any] = None + ): + super().__init__(message) + self.provider = provider + self.status_code = status_code + self.original_error = original_error + + +# 各平台默认模型 +DEFAULT_MODELS: Dict[LLMProvider, str] = { + LLMProvider.GEMINI: "gemini-2.5-flash", + LLMProvider.OPENAI: "gpt-4o-mini", + LLMProvider.CLAUDE: "claude-3-5-sonnet-20241022", + LLMProvider.QWEN: "qwen-turbo", + LLMProvider.DEEPSEEK: "deepseek-chat", + LLMProvider.ZHIPU: "glm-4-flash", + LLMProvider.MOONSHOT: "moonshot-v1-8k", + LLMProvider.BAIDU: "ERNIE-3.5-8K", + LLMProvider.MINIMAX: "abab6.5-chat", + LLMProvider.DOUBAO: "doubao-pro-32k", + LLMProvider.OLLAMA: "llama3", +} + + +# 各平台API端点 +DEFAULT_BASE_URLS: Dict[LLMProvider, str] = { + LLMProvider.OPENAI: "https://api.openai.com/v1", + LLMProvider.QWEN: "https://dashscope.aliyuncs.com/compatible-mode/v1", + LLMProvider.DEEPSEEK: "https://api.deepseek.com", + LLMProvider.ZHIPU: "https://open.bigmodel.cn/api/paas/v4", + LLMProvider.MOONSHOT: "https://api.moonshot.cn/v1", + LLMProvider.BAIDU: "https://aip.baidubce.com/rpc/2.0/ai_custom/v1", + LLMProvider.MINIMAX: "https://api.minimax.chat/v1", + LLMProvider.DOUBAO: "https://ark.cn-beijing.volces.com/api/v3", + LLMProvider.OLLAMA: "http://localhost:11434/v1", + LLMProvider.GEMINI: "https://generativelanguage.googleapis.com/v1beta", + LLMProvider.CLAUDE: "https://api.anthropic.com/v1", +} + diff --git a/backend/app/services/scanner.py b/backend/app/services/scanner.py new file mode 100644 index 0000000..850b5a8 --- /dev/null +++ b/backend/app/services/scanner.py @@ -0,0 +1,374 @@ +""" +仓库扫描服务 - 支持GitHub和GitLab仓库扫描 +""" + +import asyncio +import httpx +from typing import List, Dict, Any, Optional +from datetime import datetime +from urllib.parse import urlparse, quote +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.audit import AuditTask, AuditIssue +from app.models.project import Project +from app.services.llm.service import LLMService +from app.core.config import settings + + +# 支持的文本文件扩展名 +TEXT_EXTENSIONS = [ + ".js", ".ts", ".tsx", ".jsx", ".py", ".java", ".go", ".rs", + ".cpp", ".c", ".h", ".cc", ".hh", ".cs", ".php", ".rb", + ".kt", ".swift", ".sql", ".sh", ".json", ".yml", ".yaml" +] + +# 排除的目录和文件模式 +EXCLUDE_PATTERNS = [ + "node_modules/", "vendor/", "dist/", "build/", ".git/", + "__pycache__/", ".pytest_cache/", "coverage/", ".nyc_output/", + ".vscode/", ".idea/", ".vs/", "target/", "out/", + "__MACOSX/", ".DS_Store", "package-lock.json", "yarn.lock", + "pnpm-lock.yaml", ".min.js", ".min.css", ".map" +] + + +def is_text_file(path: str) -> bool: + """检查是否为文本文件""" + return any(path.lower().endswith(ext) for ext in TEXT_EXTENSIONS) + + +def should_exclude(path: str, exclude_patterns: List[str] = None) -> bool: + """检查是否应该排除该文件""" + all_patterns = EXCLUDE_PATTERNS + (exclude_patterns or []) + return any(pattern in path for pattern in all_patterns) + + +def get_language_from_path(path: str) -> str: + """从文件路径获取语言类型""" + ext = path.split('.')[-1].lower() if '.' in path else '' + language_map = { + 'js': 'javascript', 'jsx': 'javascript', + 'ts': 'typescript', 'tsx': 'typescript', + 'py': 'python', 'java': 'java', 'go': 'go', + 'rs': 'rust', 'cpp': 'cpp', 'c': 'cpp', + 'cc': 'cpp', 'h': 'cpp', 'hh': 'cpp', + 'cs': 'csharp', 'php': 'php', 'rb': 'ruby', + 'kt': 'kotlin', 'swift': 'swift' + } + return language_map.get(ext, 'text') + + +class TaskControlManager: + """任务控制管理器 - 用于取消运行中的任务""" + + def __init__(self): + self._cancelled_tasks: set = set() + + def cancel_task(self, task_id: str): + """取消任务""" + self._cancelled_tasks.add(task_id) + print(f"🛑 任务 {task_id} 已标记为取消") + + def is_cancelled(self, task_id: str) -> bool: + """检查任务是否被取消""" + return task_id in self._cancelled_tasks + + def cleanup_task(self, task_id: str): + """清理已完成任务的控制状态""" + self._cancelled_tasks.discard(task_id) + + +# 全局任务控制器 +task_control = TaskControlManager() + + +async def github_api(url: str, token: str = None) -> Any: + """调用GitHub API""" + headers = {"Accept": "application/vnd.github+json"} + t = token or settings.GITHUB_TOKEN + if t: + headers["Authorization"] = f"Bearer {t}" + + async with httpx.AsyncClient(timeout=30) as client: + response = await client.get(url, headers=headers) + if response.status_code == 403: + raise Exception("GitHub API 403:请配置 GITHUB_TOKEN 或确认仓库权限/频率限制") + if response.status_code != 200: + raise Exception(f"GitHub API {response.status_code}: {url}") + return response.json() + + +async def gitlab_api(url: str, token: str = None) -> Any: + """调用GitLab API""" + headers = {"Content-Type": "application/json"} + t = token or settings.GITLAB_TOKEN + if t: + headers["PRIVATE-TOKEN"] = t + + async with httpx.AsyncClient(timeout=30) as client: + response = await client.get(url, headers=headers) + if response.status_code == 401: + raise Exception("GitLab API 401:请配置 GITLAB_TOKEN 或确认仓库权限") + if response.status_code == 403: + raise Exception("GitLab API 403:请确认仓库权限/频率限制") + if response.status_code != 200: + raise Exception(f"GitLab API {response.status_code}: {url}") + return response.json() + + +async def fetch_file_content(url: str, headers: Dict[str, str] = None) -> Optional[str]: + """获取文件内容""" + async with httpx.AsyncClient(timeout=30) as client: + try: + response = await client.get(url, headers=headers or {}) + if response.status_code == 200: + return response.text + except Exception as e: + print(f"获取文件内容失败: {url}, 错误: {e}") + return None + + +async def get_github_files(repo_url: str, branch: str, token: str = None) -> List[Dict[str, str]]: + """获取GitHub仓库文件列表""" + # 解析仓库URL + match = repo_url.rstrip('/').rstrip('.git') + if 'github.com/' in match: + parts = match.split('github.com/')[-1].split('/') + if len(parts) >= 2: + owner, repo = parts[0], parts[1] + else: + raise Exception("GitHub 仓库 URL 格式错误") + else: + raise Exception("GitHub 仓库 URL 格式错误") + + # 获取仓库文件树 + tree_url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/{quote(branch)}?recursive=1" + tree_data = await github_api(tree_url, token) + + files = [] + for item in tree_data.get("tree", []): + if item.get("type") == "blob" and is_text_file(item["path"]) and not should_exclude(item["path"]): + size = item.get("size", 0) + if size <= settings.MAX_FILE_SIZE_BYTES: + files.append({ + "path": item["path"], + "url": f"https://raw.githubusercontent.com/{owner}/{repo}/{quote(branch)}/{item['path']}" + }) + + return files + + +async def get_gitlab_files(repo_url: str, branch: str, token: str = None) -> List[Dict[str, str]]: + """获取GitLab仓库文件列表""" + parsed = urlparse(repo_url) + base = f"{parsed.scheme}://{parsed.netloc}" + + # 从URL中提取token(如果存在) + extracted_token = token + if parsed.username: + if parsed.username == 'oauth2' and parsed.password: + extracted_token = parsed.password + elif parsed.username and not parsed.password: + extracted_token = parsed.username + + # 解析项目路径 + path = parsed.path.strip('/').rstrip('.git') + if not path: + raise Exception("GitLab 仓库 URL 格式错误") + + project_path = quote(path, safe='') + + # 获取仓库文件树 + tree_url = f"{base}/api/v4/projects/{project_path}/repository/tree?ref={quote(branch)}&recursive=true&per_page=100" + tree_data = await gitlab_api(tree_url, extracted_token) + + files = [] + for item in tree_data: + if item.get("type") == "blob" and is_text_file(item["path"]) and not should_exclude(item["path"]): + files.append({ + "path": item["path"], + "url": f"{base}/api/v4/projects/{project_path}/repository/files/{quote(item['path'], safe='')}/raw?ref={quote(branch)}", + "token": extracted_token + }) + + return files + + +async def scan_repo_task(task_id: str, db_session_factory, user_config: dict = None): + """ + 后台仓库扫描任务 + + Args: + task_id: 任务ID + db_session_factory: 数据库会话工厂 + user_config: 用户配置字典(包含llmConfig和otherConfig) + """ + async with db_session_factory() as db: + task = await db.get(AuditTask, task_id) + if not task: + return + + try: + # 1. 更新状态为运行中 + task.status = "running" + task.started_at = datetime.utcnow() + await db.commit() + + # 创建使用用户配置的LLM服务实例 + llm_service = LLMService(user_config=user_config or {}) + + # 2. 获取项目信息 + project = await db.get(Project, task.project_id) + if not project or not project.repository_url: + raise Exception("仓库地址不存在") + + repo_url = project.repository_url + branch = task.branch_name or project.default_branch or "main" + repo_type = project.repository_type or "other" + + print(f"🚀 开始扫描仓库: {repo_url}, 分支: {branch}, 类型: {repo_type}") + + # 3. 获取文件列表 + # 从用户配置中读取 GitHub/GitLab Token(优先使用用户配置,然后使用系统配置) + user_other_config = (user_config or {}).get('otherConfig', {}) + github_token = user_other_config.get('githubToken') or settings.GITHUB_TOKEN + gitlab_token = user_other_config.get('gitlabToken') or settings.GITLAB_TOKEN + + files: List[Dict[str, str]] = [] + extracted_gitlab_token = None + + if repo_type == "github": + files = await get_github_files(repo_url, branch, github_token) + elif repo_type == "gitlab": + files = await get_gitlab_files(repo_url, branch, gitlab_token) + # GitLab文件可能带有token + if files and 'token' in files[0]: + extracted_gitlab_token = files[0].get('token') + else: + raise Exception("不支持的仓库类型,仅支持 GitHub 和 GitLab 仓库") + + # 限制文件数量 + files = files[:settings.MAX_ANALYZE_FILES] + + task.total_files = len(files) + await db.commit() + + print(f"📊 获取到 {len(files)} 个文件,开始分析") + + # 4. 分析文件 + total_issues = 0 + total_lines = 0 + quality_scores = [] + scanned_files = 0 + failed_files = 0 + consecutive_failures = 0 + MAX_CONSECUTIVE_FAILURES = 5 + + for file_info in files: + # 检查是否取消 + if task_control.is_cancelled(task_id): + print(f"🛑 任务 {task_id} 已被用户取消") + task.status = "cancelled" + task.completed_at = datetime.utcnow() + await db.commit() + task_control.cleanup_task(task_id) + return + + # 检查连续失败次数 + if consecutive_failures >= MAX_CONSECUTIVE_FAILURES: + print(f"❌ 任务 {task_id}: 连续失败 {consecutive_failures} 次,停止分析") + raise Exception(f"连续失败 {consecutive_failures} 次,可能是 LLM API 服务异常") + + try: + # 获取文件内容 + headers = {} + # 使用提取的 GitLab token 或用户配置的 token + token_to_use = extracted_gitlab_token or gitlab_token + if token_to_use: + headers["PRIVATE-TOKEN"] = token_to_use + + content = await fetch_file_content(file_info["url"], headers) + if not content: + continue + + if len(content) > settings.MAX_FILE_SIZE_BYTES: + continue + + total_lines += content.count('\n') + 1 + language = get_language_from_path(file_info["path"]) + + # LLM分析 + analysis = await llm_service.analyze_code(content, language) + + # 再次检查是否取消(LLM分析后) + if task_control.is_cancelled(task_id): + print(f"🛑 任务 {task_id} 在LLM分析后被取消") + task.status = "cancelled" + task.completed_at = datetime.utcnow() + await db.commit() + task_control.cleanup_task(task_id) + return + + # 保存问题 + issues = analysis.get("issues", []) + for issue in issues: + audit_issue = AuditIssue( + task_id=task.id, + file_path=file_info["path"], + line_number=issue.get("line", 1), + column_number=issue.get("column"), + issue_type=issue.get("type", "maintainability"), + severity=issue.get("severity", "low"), + title=issue.get("title", "Issue"), + message=issue.get("description") or issue.get("title", "Issue"), + suggestion=issue.get("suggestion"), + code_snippet=issue.get("code_snippet"), + ai_explanation=issue.get("ai_explanation"), + status="open" + ) + db.add(audit_issue) + total_issues += 1 + + if "quality_score" in analysis: + quality_scores.append(analysis["quality_score"]) + + consecutive_failures = 0 # 成功后重置 + scanned_files += 1 + + # 更新进度 + task.scanned_files = scanned_files + task.total_lines = total_lines + task.issues_count = total_issues + await db.commit() + + print(f"📈 任务 {task_id}: 进度 {scanned_files}/{len(files)} ({int(scanned_files/len(files)*100)}%)") + + # 请求间隔 + await asyncio.sleep(settings.LLM_GAP_MS / 1000) + + except Exception as file_error: + failed_files += 1 + consecutive_failures += 1 + print(f"❌ 分析文件失败 ({file_info['path']}): {file_error}") + await asyncio.sleep(settings.LLM_GAP_MS / 1000) + + # 5. 完成任务 + avg_quality_score = sum(quality_scores) / len(quality_scores) if quality_scores else 100.0 + + task.status = "completed" + task.completed_at = datetime.utcnow() + task.scanned_files = scanned_files + task.total_lines = total_lines + task.issues_count = total_issues + task.quality_score = avg_quality_score + await db.commit() + + print(f"✅ 任务 {task_id} 完成: 扫描 {scanned_files} 个文件, 发现 {total_issues} 个问题, 质量分 {avg_quality_score:.1f}") + task_control.cleanup_task(task_id) + + except Exception as e: + print(f"❌ 扫描失败: {e}") + task.status = "failed" + task.completed_at = datetime.utcnow() + await db.commit() + task_control.cleanup_task(task_id) diff --git a/backend/env.example b/backend/env.example new file mode 100644 index 0000000..f0db425 --- /dev/null +++ b/backend/env.example @@ -0,0 +1,33 @@ +# ============================================= +# XCodeReviewer Backend 配置文件 +# 复制此文件为 .env 并填入实际配置 +# ============================================= + +# ------------ 数据库配置 ------------ +POSTGRES_SERVER=localhost +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_DB=xcodereviewer + +# ------------ 安全配置 ------------ +SECRET_KEY=your-super-secret-key-change-this-in-production +ALGORITHM=HS256 +ACCESS_TOKEN_EXPIRE_MINUTES=11520 + +# ------------ LLM配置 ------------ +# 支持的provider: openai, gemini, claude, qwen, deepseek, zhipu, moonshot, baidu, minimax, doubao, ollama +LLM_PROVIDER=openai +LLM_API_KEY=sk-your-api-key +LLM_MODEL= +LLM_BASE_URL= +LLM_TIMEOUT=150 +LLM_TEMPERATURE=0.1 +LLM_MAX_TOKENS=4096 + +# ------------ 仓库扫描配置 ------------ +GITHUB_TOKEN= +GITLAB_TOKEN= +MAX_ANALYZE_FILES=50 +MAX_FILE_SIZE_BYTES=204800 +LLM_CONCURRENCY=3 +LLM_GAP_MS=2000 diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..a315974 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,6 @@ +def main(): + print("Hello from xcodereviewer-backend!") + + +if __name__ == "__main__": + main() diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 0000000..1d952bb --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,21 @@ +[project] +name = "xcodereviewer-backend" +version = "0.1.0" +description = "XCodeReviewer Backend API" +requires-python = ">=3.13" +dependencies = [ + "fastapi>=0.100.0", + "uvicorn[standard]", + "sqlalchemy>=2.0.0", + "asyncpg", + "alembic", + "pydantic>=2.0.0", + "pydantic-settings", + "passlib[bcrypt]", + "python-jose[cryptography]", + "python-multipart", + "httpx", + "email-validator", + "greenlet", + "bcrypt<5.0.0", +] diff --git a/backend/requirements-lock.txt b/backend/requirements-lock.txt new file mode 100644 index 0000000..4f23689 --- /dev/null +++ b/backend/requirements-lock.txt @@ -0,0 +1,108 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile requirements.txt -o requirements-lock.txt +alembic==1.17.2 + # via -r requirements.txt +annotated-doc==0.0.4 + # via fastapi +annotated-types==0.7.0 + # via pydantic +anyio==4.11.0 + # via + # httpx + # starlette + # watchfiles +asyncpg==0.31.0 + # via -r requirements.txt +bcrypt==5.0.0 + # via passlib +certifi==2025.11.12 + # via + # httpcore + # httpx +cffi==2.0.0 + # via cryptography +click==8.3.1 + # via uvicorn +cryptography==46.0.3 + # via python-jose +ecdsa==0.19.1 + # via python-jose +fastapi==0.122.0 + # via -r requirements.txt +h11==0.16.0 + # via + # httpcore + # uvicorn +httpcore==1.0.9 + # via httpx +httptools==0.7.1 + # via uvicorn +httpx==0.28.1 + # via -r requirements.txt +idna==3.11 + # via + # anyio + # httpx +mako==1.3.10 + # via alembic +markupsafe==3.0.3 + # via mako +passlib==1.7.4 + # via -r requirements.txt +pyasn1==0.6.1 + # via + # python-jose + # rsa +pycparser==2.23 + # via cffi +pydantic==2.12.4 + # via + # -r requirements.txt + # fastapi + # pydantic-settings +pydantic-core==2.41.5 + # via pydantic +pydantic-settings==2.12.0 + # via -r requirements.txt +python-dotenv==1.2.1 + # via + # pydantic-settings + # uvicorn +python-jose==3.5.0 + # via -r requirements.txt +python-multipart==0.0.20 + # via -r requirements.txt +pyyaml==6.0.3 + # via uvicorn +rsa==4.9.1 + # via python-jose +six==1.17.0 + # via ecdsa +sniffio==1.3.1 + # via anyio +sqlalchemy==2.0.44 + # via + # -r requirements.txt + # alembic +starlette==0.50.0 + # via fastapi +typing-extensions==4.15.0 + # via + # alembic + # fastapi + # pydantic + # pydantic-core + # sqlalchemy + # typing-inspection +typing-inspection==0.4.2 + # via + # pydantic + # pydantic-settings +uvicorn==0.38.0 + # via -r requirements.txt +uvloop==0.22.1 + # via uvicorn +watchfiles==1.1.1 + # via uvicorn +websockets==15.0.1 + # via uvicorn diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..3bf7b70 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,12 @@ +fastapi>=0.100.0 +uvicorn[standard] +sqlalchemy>=2.0.0 +asyncpg +alembic +pydantic>=2.0.0 +pydantic-settings +passlib[bcrypt] +python-jose[cryptography] +python-multipart +httpx + diff --git a/backend/start.sh b/backend/start.sh new file mode 100755 index 0000000..b5b72ea --- /dev/null +++ b/backend/start.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# 使用 uv 启动后端服务 + +set -e + +echo "🚀 启动 XCodeReviewer 后端服务..." + +# 检查 uv 是否安装 +if ! command -v uv &> /dev/null; then + echo "❌ 未找到 uv,请先安装:" + echo " curl -LsSf https://astral.sh/uv/install.sh | sh" + exit 1 +fi + +# 同步依赖(如果需要) +if [ ! -d ".venv" ]; then + echo "📦 首次运行,正在安装依赖..." + uv sync +fi + +# 运行数据库迁移 +echo "🔄 运行数据库迁移..." +uv run alembic upgrade head + +# 启动服务 +echo "✅ 启动后端服务..." +uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload + diff --git a/backend/uv.lock b/backend/uv.lock new file mode 100644 index 0000000..453eb05 --- /dev/null +++ b/backend/uv.lock @@ -0,0 +1,907 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "alembic" +version = "1.17.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/a6/74c8cadc2882977d80ad756a13857857dbcf9bd405bc80b662eb10651282/alembic-1.17.2.tar.gz", hash = "sha256:bbe9751705c5e0f14877f02d46c53d10885e377e3d90eda810a016f9baa19e8e", size = 1988064, upload-time = "2025-11-14T20:35:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/88/6237e97e3385b57b5f1528647addea5cc03d4d65d5979ab24327d41fb00d/alembic-1.17.2-py3-none-any.whl", hash = "sha256:f483dd1fe93f6c5d49217055e4d15b905b425b6af906746abb35b69c1996c4e6", size = 248554, upload-time = "2025-11-14T20:35:05.699Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, +] + +[[package]] +name = "asyncpg" +version = "0.31.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cc/d18065ce2380d80b1bcce927c24a2642efd38918e33fd724bc4bca904877/asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735", size = 993667, upload-time = "2025-11-24T23:27:00.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/11/97b5c2af72a5d0b9bc3fa30cd4b9ce22284a9a943a150fdc768763caf035/asyncpg-0.31.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c204fab1b91e08b0f47e90a75d1b3c62174dab21f670ad6c5d0f243a228f015b", size = 661111, upload-time = "2025-11-24T23:26:04.467Z" }, + { url = "https://files.pythonhosted.org/packages/1b/71/157d611c791a5e2d0423f09f027bd499935f0906e0c2a416ce712ba51ef3/asyncpg-0.31.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:54a64f91839ba59008eccf7aad2e93d6e3de688d796f35803235ea1c4898ae1e", size = 636928, upload-time = "2025-11-24T23:26:05.944Z" }, + { url = "https://files.pythonhosted.org/packages/2e/fc/9e3486fb2bbe69d4a867c0b76d68542650a7ff1574ca40e84c3111bb0c6e/asyncpg-0.31.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0e0822b1038dc7253b337b0f3f676cadc4ac31b126c5d42691c39691962e403", size = 3424067, upload-time = "2025-11-24T23:26:07.957Z" }, + { url = "https://files.pythonhosted.org/packages/12/c6/8c9d076f73f07f995013c791e018a1cd5f31823c2a3187fc8581706aa00f/asyncpg-0.31.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bef056aa502ee34204c161c72ca1f3c274917596877f825968368b2c33f585f4", size = 3518156, upload-time = "2025-11-24T23:26:09.591Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3b/60683a0baf50fbc546499cfb53132cb6835b92b529a05f6a81471ab60d0c/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0bfbcc5b7ffcd9b75ab1558f00db2ae07db9c80637ad1b2469c43df79d7a5ae2", size = 3319636, upload-time = "2025-11-24T23:26:11.168Z" }, + { url = "https://files.pythonhosted.org/packages/50/dc/8487df0f69bd398a61e1792b3cba0e47477f214eff085ba0efa7eac9ce87/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22bc525ebbdc24d1261ecbf6f504998244d4e3be1721784b5f64664d61fbe602", size = 3472079, upload-time = "2025-11-24T23:26:13.164Z" }, + { url = "https://files.pythonhosted.org/packages/13/a1/c5bbeeb8531c05c89135cb8b28575ac2fac618bcb60119ee9696c3faf71c/asyncpg-0.31.0-cp313-cp313-win32.whl", hash = "sha256:f890de5e1e4f7e14023619399a471ce4b71f5418cd67a51853b9910fdfa73696", size = 527606, upload-time = "2025-11-24T23:26:14.78Z" }, + { url = "https://files.pythonhosted.org/packages/91/66/b25ccb84a246b470eb943b0107c07edcae51804912b824054b3413995a10/asyncpg-0.31.0-cp313-cp313-win_amd64.whl", hash = "sha256:dc5f2fa9916f292e5c5c8b2ac2813763bcd7f58e130055b4ad8a0531314201ab", size = 596569, upload-time = "2025-11-24T23:26:16.189Z" }, + { url = "https://files.pythonhosted.org/packages/3c/36/e9450d62e84a13aea6580c83a47a437f26c7ca6fa0f0fd40b6670793ea30/asyncpg-0.31.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f6b56b91bb0ffc328c4e3ed113136cddd9deefdf5f79ab448598b9772831df44", size = 660867, upload-time = "2025-11-24T23:26:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/82/4b/1d0a2b33b3102d210439338e1beea616a6122267c0df459ff0265cd5807a/asyncpg-0.31.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:334dec28cf20d7f5bb9e45b39546ddf247f8042a690bff9b9573d00086e69cb5", size = 638349, upload-time = "2025-11-24T23:26:19.689Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/e7f7ac9a7974f08eff9183e392b2d62516f90412686532d27e196c0f0eeb/asyncpg-0.31.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98cc158c53f46de7bb677fd20c417e264fc02b36d901cc2a43bd6cb0dc6dbfd2", size = 3410428, upload-time = "2025-11-24T23:26:21.275Z" }, + { url = "https://files.pythonhosted.org/packages/6f/de/bf1b60de3dede5c2731e6788617a512bc0ebd9693eac297ee74086f101d7/asyncpg-0.31.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9322b563e2661a52e3cdbc93eed3be7748b289f792e0011cb2720d278b366ce2", size = 3471678, upload-time = "2025-11-24T23:26:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/46/78/fc3ade003e22d8bd53aaf8f75f4be48f0b460fa73738f0391b9c856a9147/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19857a358fc811d82227449b7ca40afb46e75b33eb8897240c3839dd8b744218", size = 3313505, upload-time = "2025-11-24T23:26:25.235Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e9/73eb8a6789e927816f4705291be21f2225687bfa97321e40cd23055e903a/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba5f8886e850882ff2c2ace5732300e99193823e8107e2c53ef01c1ebfa1e85d", size = 3434744, upload-time = "2025-11-24T23:26:26.944Z" }, + { url = "https://files.pythonhosted.org/packages/08/4b/f10b880534413c65c5b5862f79b8e81553a8f364e5238832ad4c0af71b7f/asyncpg-0.31.0-cp314-cp314-win32.whl", hash = "sha256:cea3a0b2a14f95834cee29432e4ddc399b95700eb1d51bbc5bfee8f31fa07b2b", size = 532251, upload-time = "2025-11-24T23:26:28.404Z" }, + { url = "https://files.pythonhosted.org/packages/d3/2d/7aa40750b7a19efa5d66e67fc06008ca0f27ba1bd082e457ad82f59aba49/asyncpg-0.31.0-cp314-cp314-win_amd64.whl", hash = "sha256:04d19392716af6b029411a0264d92093b6e5e8285ae97a39957b9a9c14ea72be", size = 604901, upload-time = "2025-11-24T23:26:30.34Z" }, + { url = "https://files.pythonhosted.org/packages/ce/fe/b9dfe349b83b9dee28cc42360d2c86b2cdce4cb551a2c2d27e156bcac84d/asyncpg-0.31.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bdb957706da132e982cc6856bb2f7b740603472b54c3ebc77fe60ea3e57e1bd2", size = 702280, upload-time = "2025-11-24T23:26:32Z" }, + { url = "https://files.pythonhosted.org/packages/6a/81/e6be6e37e560bd91e6c23ea8a6138a04fd057b08cf63d3c5055c98e81c1d/asyncpg-0.31.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6d11b198111a72f47154fa03b85799f9be63701e068b43f84ac25da0bda9cb31", size = 682931, upload-time = "2025-11-24T23:26:33.572Z" }, + { url = "https://files.pythonhosted.org/packages/a6/45/6009040da85a1648dd5bc75b3b0a062081c483e75a1a29041ae63a0bf0dc/asyncpg-0.31.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18c83b03bc0d1b23e6230f5bf8d4f217dc9bc08644ce0502a9d91dc9e634a9c7", size = 3581608, upload-time = "2025-11-24T23:26:35.638Z" }, + { url = "https://files.pythonhosted.org/packages/7e/06/2e3d4d7608b0b2b3adbee0d0bd6a2d29ca0fc4d8a78f8277df04e2d1fd7b/asyncpg-0.31.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e009abc333464ff18b8f6fd146addffd9aaf63e79aa3bb40ab7a4c332d0c5e9e", size = 3498738, upload-time = "2025-11-24T23:26:37.275Z" }, + { url = "https://files.pythonhosted.org/packages/7d/aa/7d75ede780033141c51d83577ea23236ba7d3a23593929b32b49db8ed36e/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3b1fbcb0e396a5ca435a8826a87e5c2c2cc0c8c68eb6fadf82168056b0e53a8c", size = 3401026, upload-time = "2025-11-24T23:26:39.423Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7a/15e37d45e7f7c94facc1e9148c0e455e8f33c08f0b8a0b1deb2c5171771b/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8df714dba348efcc162d2adf02d213e5fab1bd9f557e1305633e851a61814a7a", size = 3429426, upload-time = "2025-11-24T23:26:41.032Z" }, + { url = "https://files.pythonhosted.org/packages/13/d5/71437c5f6ae5f307828710efbe62163974e71237d5d46ebd2869ea052d10/asyncpg-0.31.0-cp314-cp314t-win32.whl", hash = "sha256:1b41f1afb1033f2b44f3234993b15096ddc9cd71b21a42dbd87fc6a57b43d65d", size = 614495, upload-time = "2025-11-24T23:26:42.659Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d7/8fb3044eaef08a310acfe23dae9a8e2e07d305edc29a53497e52bc76eca7/asyncpg-0.31.0-cp314-cp314t-win_amd64.whl", hash = "sha256:bd4107bb7cdd0e9e65fae66a62afd3a249663b844fa34d479f6d5b3bef9c04c3", size = 706062, upload-time = "2025-11-24T23:26:44.086Z" }, +] + +[[package]] +name = "bcrypt" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/5d/6d7433e0f3cd46ce0b43cd65e1db465ea024dbb8216fb2404e919c2ad77b/bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18", size = 25697, upload-time = "2025-02-28T01:24:09.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/2c/3d44e853d1fe969d229bd58d39ae6902b3d924af0e2b5a60d17d4b809ded/bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281", size = 483719, upload-time = "2025-02-28T01:22:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e2/58ff6e2a22eca2e2cff5370ae56dba29d70b1ea6fc08ee9115c3ae367795/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb", size = 272001, upload-time = "2025-02-28T01:22:38.078Z" }, + { url = "https://files.pythonhosted.org/packages/37/1f/c55ed8dbe994b1d088309e366749633c9eb90d139af3c0a50c102ba68a1a/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180", size = 277451, upload-time = "2025-02-28T01:22:40.787Z" }, + { url = "https://files.pythonhosted.org/packages/d7/1c/794feb2ecf22fe73dcfb697ea7057f632061faceb7dcf0f155f3443b4d79/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f", size = 272792, upload-time = "2025-02-28T01:22:43.144Z" }, + { url = "https://files.pythonhosted.org/packages/13/b7/0b289506a3f3598c2ae2bdfa0ea66969812ed200264e3f61df77753eee6d/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09", size = 289752, upload-time = "2025-02-28T01:22:45.56Z" }, + { url = "https://files.pythonhosted.org/packages/dc/24/d0fb023788afe9e83cc118895a9f6c57e1044e7e1672f045e46733421fe6/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d", size = 277762, upload-time = "2025-02-28T01:22:47.023Z" }, + { url = "https://files.pythonhosted.org/packages/e4/38/cde58089492e55ac4ef6c49fea7027600c84fd23f7520c62118c03b4625e/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd", size = 272384, upload-time = "2025-02-28T01:22:49.221Z" }, + { url = "https://files.pythonhosted.org/packages/de/6a/d5026520843490cfc8135d03012a413e4532a400e471e6188b01b2de853f/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af", size = 277329, upload-time = "2025-02-28T01:22:51.603Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a3/4fc5255e60486466c389e28c12579d2829b28a527360e9430b4041df4cf9/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231", size = 305241, upload-time = "2025-02-28T01:22:53.283Z" }, + { url = "https://files.pythonhosted.org/packages/c7/15/2b37bc07d6ce27cc94e5b10fd5058900eb8fb11642300e932c8c82e25c4a/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c", size = 309617, upload-time = "2025-02-28T01:22:55.461Z" }, + { url = "https://files.pythonhosted.org/packages/5f/1f/99f65edb09e6c935232ba0430c8c13bb98cb3194b6d636e61d93fe60ac59/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f", size = 335751, upload-time = "2025-02-28T01:22:57.81Z" }, + { url = "https://files.pythonhosted.org/packages/00/1b/b324030c706711c99769988fcb694b3cb23f247ad39a7823a78e361bdbb8/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d", size = 355965, upload-time = "2025-02-28T01:22:59.181Z" }, + { url = "https://files.pythonhosted.org/packages/aa/dd/20372a0579dd915dfc3b1cd4943b3bca431866fcb1dfdfd7518c3caddea6/bcrypt-4.3.0-cp313-cp313t-win32.whl", hash = "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4", size = 155316, upload-time = "2025-02-28T01:23:00.763Z" }, + { url = "https://files.pythonhosted.org/packages/6d/52/45d969fcff6b5577c2bf17098dc36269b4c02197d551371c023130c0f890/bcrypt-4.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669", size = 147752, upload-time = "2025-02-28T01:23:02.908Z" }, + { url = "https://files.pythonhosted.org/packages/11/22/5ada0b9af72b60cbc4c9a399fdde4af0feaa609d27eb0adc61607997a3fa/bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d", size = 498019, upload-time = "2025-02-28T01:23:05.838Z" }, + { url = "https://files.pythonhosted.org/packages/b8/8c/252a1edc598dc1ce57905be173328eda073083826955ee3c97c7ff5ba584/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b", size = 279174, upload-time = "2025-02-28T01:23:07.274Z" }, + { url = "https://files.pythonhosted.org/packages/29/5b/4547d5c49b85f0337c13929f2ccbe08b7283069eea3550a457914fc078aa/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e", size = 283870, upload-time = "2025-02-28T01:23:09.151Z" }, + { url = "https://files.pythonhosted.org/packages/be/21/7dbaf3fa1745cb63f776bb046e481fbababd7d344c5324eab47f5ca92dd2/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59", size = 279601, upload-time = "2025-02-28T01:23:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/6d/64/e042fc8262e971347d9230d9abbe70d68b0a549acd8611c83cebd3eaec67/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753", size = 297660, upload-time = "2025-02-28T01:23:12.989Z" }, + { url = "https://files.pythonhosted.org/packages/50/b8/6294eb84a3fef3b67c69b4470fcdd5326676806bf2519cda79331ab3c3a9/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761", size = 284083, upload-time = "2025-02-28T01:23:14.5Z" }, + { url = "https://files.pythonhosted.org/packages/62/e6/baff635a4f2c42e8788fe1b1633911c38551ecca9a749d1052d296329da6/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb", size = 279237, upload-time = "2025-02-28T01:23:16.686Z" }, + { url = "https://files.pythonhosted.org/packages/39/48/46f623f1b0c7dc2e5de0b8af5e6f5ac4cc26408ac33f3d424e5ad8da4a90/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d", size = 283737, upload-time = "2025-02-28T01:23:18.897Z" }, + { url = "https://files.pythonhosted.org/packages/49/8b/70671c3ce9c0fca4a6cc3cc6ccbaa7e948875a2e62cbd146e04a4011899c/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f", size = 312741, upload-time = "2025-02-28T01:23:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/27/fb/910d3a1caa2d249b6040a5caf9f9866c52114d51523ac2fb47578a27faee/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732", size = 316472, upload-time = "2025-02-28T01:23:23.183Z" }, + { url = "https://files.pythonhosted.org/packages/dc/cf/7cf3a05b66ce466cfb575dbbda39718d45a609daa78500f57fa9f36fa3c0/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef", size = 343606, upload-time = "2025-02-28T01:23:25.361Z" }, + { url = "https://files.pythonhosted.org/packages/e3/b8/e970ecc6d7e355c0d892b7f733480f4aa8509f99b33e71550242cf0b7e63/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304", size = 362867, upload-time = "2025-02-28T01:23:26.875Z" }, + { url = "https://files.pythonhosted.org/packages/a9/97/8d3118efd8354c555a3422d544163f40d9f236be5b96c714086463f11699/bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51", size = 160589, upload-time = "2025-02-28T01:23:28.381Z" }, + { url = "https://files.pythonhosted.org/packages/29/07/416f0b99f7f3997c69815365babbc2e8754181a4b1899d921b3c7d5b6f12/bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62", size = 152794, upload-time = "2025-02-28T01:23:30.187Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c1/3fa0e9e4e0bfd3fd77eb8b52ec198fd6e1fd7e9402052e43f23483f956dd/bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3", size = 498969, upload-time = "2025-02-28T01:23:31.945Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d4/755ce19b6743394787fbd7dff6bf271b27ee9b5912a97242e3caf125885b/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24", size = 279158, upload-time = "2025-02-28T01:23:34.161Z" }, + { url = "https://files.pythonhosted.org/packages/9b/5d/805ef1a749c965c46b28285dfb5cd272a7ed9fa971f970435a5133250182/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef", size = 284285, upload-time = "2025-02-28T01:23:35.765Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/698580547a4a4988e415721b71eb45e80c879f0fb04a62da131f45987b96/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b", size = 279583, upload-time = "2025-02-28T01:23:38.021Z" }, + { url = "https://files.pythonhosted.org/packages/f2/87/62e1e426418204db520f955ffd06f1efd389feca893dad7095bf35612eec/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676", size = 297896, upload-time = "2025-02-28T01:23:39.575Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c6/8fedca4c2ada1b6e889c52d2943b2f968d3427e5d65f595620ec4c06fa2f/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1", size = 284492, upload-time = "2025-02-28T01:23:40.901Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4d/c43332dcaaddb7710a8ff5269fcccba97ed3c85987ddaa808db084267b9a/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe", size = 279213, upload-time = "2025-02-28T01:23:42.653Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/1e36379e169a7df3a14a1c160a49b7b918600a6008de43ff20d479e6f4b5/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0", size = 284162, upload-time = "2025-02-28T01:23:43.964Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0a/644b2731194b0d7646f3210dc4d80c7fee3ecb3a1f791a6e0ae6bb8684e3/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f", size = 312856, upload-time = "2025-02-28T01:23:46.011Z" }, + { url = "https://files.pythonhosted.org/packages/dc/62/2a871837c0bb6ab0c9a88bf54de0fc021a6a08832d4ea313ed92a669d437/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23", size = 316726, upload-time = "2025-02-28T01:23:47.575Z" }, + { url = "https://files.pythonhosted.org/packages/0c/a1/9898ea3faac0b156d457fd73a3cb9c2855c6fd063e44b8522925cdd8ce46/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe", size = 343664, upload-time = "2025-02-28T01:23:49.059Z" }, + { url = "https://files.pythonhosted.org/packages/40/f2/71b4ed65ce38982ecdda0ff20c3ad1b15e71949c78b2c053df53629ce940/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505", size = 363128, upload-time = "2025-02-28T01:23:50.399Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/12f6a58eca6dea4be992d6c681b7ec9410a1d9f5cf368c61437e31daa879/bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a", size = 160598, upload-time = "2025-02-28T01:23:51.775Z" }, + { url = "https://files.pythonhosted.org/packages/a9/cf/45fb5261ece3e6b9817d3d82b2f343a505fd58674a92577923bc500bd1aa/bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", size = 152799, upload-time = "2025-02-28T01:23:53.139Z" }, +] + +[[package]] +name = "certifi" +version = "2025.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "46.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, + { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, + { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, + { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, + { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, + { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, + { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, + { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, + { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, + { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" }, + { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, + { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, + { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, + { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, + { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, + { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, + { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" }, + { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, + { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, + { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, + { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, + { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, + { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, + { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, + { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, +] + +[[package]] +name = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + +[[package]] +name = "ecdsa" +version = "0.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/1f/924e3caae75f471eae4b26bd13b698f6af2c44279f67af317439c2f4c46a/ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61", size = 201793, upload-time = "2025-03-13T11:52:43.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/a3/460c57f094a4a165c84a1341c373b0a4f5ec6ac244b998d5021aade89b77/ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", size = 150607, upload-time = "2025-03-13T11:52:41.757Z" }, +] + +[[package]] +name = "email-validator" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, +] + +[[package]] +name = "fastapi" +version = "0.122.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/de/3ee97a4f6ffef1fb70bf20561e4f88531633bb5045dc6cebc0f8471f764d/fastapi-0.122.0.tar.gz", hash = "sha256:cd9b5352031f93773228af8b4c443eedc2ac2aa74b27780387b853c3726fb94b", size = 346436, upload-time = "2025-11-24T19:17:47.95Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/93/aa8072af4ff37b795f6bbf43dcaf61115f40f49935c7dbb180c9afc3f421/fastapi-0.122.0-py3-none-any.whl", hash = "sha256:a456e8915dfc6c8914a50d9651133bd47ec96d331c5b44600baa635538a30d67", size = 110671, upload-time = "2025-11-24T19:17:45.96Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, + { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, + { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, + { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/1c/53/f9c440463b3057485b8594d7a638bed53ba531165ef0ca0e6c364b5cc807/greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", size = 1564759, upload-time = "2025-11-04T12:42:19.395Z" }, + { url = "https://files.pythonhosted.org/packages/47/e4/3bb4240abdd0a8d23f4f88adec746a3099f0d86bfedb623f063b2e3b4df0/greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", size = 1634288, upload-time = "2025-11-04T12:42:21.174Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, + { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, + { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, + { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/23/6e/74407aed965a4ab6ddd93a7ded3180b730d281c77b765788419484cdfeef/greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", size = 1612508, upload-time = "2025-11-04T12:42:23.427Z" }, + { url = "https://files.pythonhosted.org/packages/0d/da/343cd760ab2f92bac1845ca07ee3faea9fe52bee65f7bcb19f16ad7de08b/greenlet-3.2.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681", size = 1680760, upload-time = "2025-11-04T12:42:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httptools" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889, upload-time = "2025-10-10T03:54:47.089Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180, upload-time = "2025-10-10T03:54:48.052Z" }, + { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596, upload-time = "2025-10-10T03:54:48.919Z" }, + { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268, upload-time = "2025-10-10T03:54:49.993Z" }, + { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517, upload-time = "2025-10-10T03:54:51.066Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337, upload-time = "2025-10-10T03:54:52.196Z" }, + { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743, upload-time = "2025-10-10T03:54:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" }, + { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" }, + { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" }, + { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "passlib" +version = "1.7.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/06/9da9ee59a67fae7761aab3ccc84fa4f3f33f125b370f1ccdb915bf967c11/passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04", size = 689844, upload-time = "2020-10-08T19:00:52.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", size = 525554, upload-time = "2020-10-08T19:00:49.856Z" }, +] + +[package.optional-dependencies] +bcrypt = [ + { name = "bcrypt" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac", size = 821038, upload-time = "2025-11-05T10:50:08.59Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400, upload-time = "2025-11-05T10:50:06.732Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + +[[package]] +name = "python-jose" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ecdsa" }, + { name = "pyasn1" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/77/3a1c9039db7124eb039772b935f2244fbb73fc8ee65b9acf2375da1c07bf/python_jose-3.5.0.tar.gz", hash = "sha256:fb4eaa44dbeb1c26dcc69e4bd7ec54a1cb8dd64d3b4d81ef08d90ff453f2b01b", size = 92726, upload-time = "2025-05-28T17:31:54.288Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/c3/0bd11992072e6a1c513b16500a5d07f91a24017c5909b02c72c62d7ad024/python_jose-3.5.0-py2.py3-none-any.whl", hash = "sha256:abd1202f23d34dfad2c3d28cb8617b90acf34132c7afd60abd0b0b7d3cb55771", size = 34624, upload-time = "2025-05-28T17:31:52.802Z" }, +] + +[package.optional-dependencies] +cryptography = [ + { name = "cryptography" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.44" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/f2/840d7b9496825333f532d2e3976b8eadbf52034178aac53630d09fe6e1ef/sqlalchemy-2.0.44.tar.gz", hash = "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22", size = 9819830, upload-time = "2025-10-10T14:39:12.935Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1", size = 2135479, upload-time = "2025-10-10T16:03:37.671Z" }, + { url = "https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45", size = 2123212, upload-time = "2025-10-10T16:03:41.755Z" }, + { url = "https://files.pythonhosted.org/packages/b0/bb/43e246cfe0e81c018076a16036d9b548c4cc649de241fa27d8d9ca6f85ab/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b61188657e3a2b9ac4e8f04d6cf8e51046e28175f79464c67f2fd35bceb0976", size = 3255353, upload-time = "2025-10-10T15:35:31.221Z" }, + { url = "https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c", size = 3260222, upload-time = "2025-10-10T15:43:50.124Z" }, + { url = "https://files.pythonhosted.org/packages/44/16/1857e35a47155b5ad927272fee81ae49d398959cb749edca6eaa399b582f/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:15f3326f7f0b2bfe406ee562e17f43f36e16167af99c4c0df61db668de20002d", size = 3189614, upload-time = "2025-10-10T15:35:32.578Z" }, + { url = "https://files.pythonhosted.org/packages/88/ee/4afb39a8ee4fc786e2d716c20ab87b5b1fb33d4ac4129a1aaa574ae8a585/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e77faf6ff919aa8cd63f1c4e561cac1d9a454a191bb864d5dd5e545935e5a40", size = 3226248, upload-time = "2025-10-10T15:43:51.862Z" }, + { url = "https://files.pythonhosted.org/packages/32/d5/0e66097fc64fa266f29a7963296b40a80d6a997b7ac13806183700676f86/sqlalchemy-2.0.44-cp313-cp313-win32.whl", hash = "sha256:ee51625c2d51f8baadf2829fae817ad0b66b140573939dd69284d2ba3553ae73", size = 2101275, upload-time = "2025-10-10T15:03:26.096Z" }, + { url = "https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl", hash = "sha256:c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e", size = 2127901, upload-time = "2025-10-10T15:03:27.548Z" }, + { url = "https://files.pythonhosted.org/packages/9c/5e/6a29fa884d9fb7ddadf6b69490a9d45fded3b38541713010dad16b77d015/sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05", size = 1928718, upload-time = "2025-10-10T15:29:45.32Z" }, +] + +[[package]] +name = "starlette" +version = "0.50.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/f06b84e2697fef4688ca63bdb2fdf113ca0a3be33f94488f2cadb690b0cf/uvicorn-0.38.0.tar.gz", hash = "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d", size = 80605, upload-time = "2025-10-18T13:46:44.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "xcodereviewer-backend" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "alembic" }, + { name = "asyncpg" }, + { name = "bcrypt" }, + { name = "email-validator" }, + { name = "fastapi" }, + { name = "greenlet" }, + { name = "httpx" }, + { name = "passlib", extra = ["bcrypt"] }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-jose", extra = ["cryptography"] }, + { name = "python-multipart" }, + { name = "sqlalchemy" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[package.metadata] +requires-dist = [ + { name = "alembic" }, + { name = "asyncpg" }, + { name = "bcrypt", specifier = "<5.0.0" }, + { name = "email-validator" }, + { name = "fastapi", specifier = ">=0.100.0" }, + { name = "greenlet" }, + { name = "httpx" }, + { name = "passlib", extras = ["bcrypt"] }, + { name = "pydantic", specifier = ">=2.0.0" }, + { name = "pydantic-settings" }, + { name = "python-jose", extras = ["cryptography"] }, + { name = "python-multipart" }, + { name = "sqlalchemy", specifier = ">=2.0.0" }, + { name = "uvicorn", extras = ["standard"] }, +] diff --git a/docker-compose.yml b/docker-compose.yml index 446c08a..e696eae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,95 +1,52 @@ -services: - # XCodeReviewer 前端应用 - xcodereviewer: - build: - context: . - dockerfile: Dockerfile - # 构建参数 - 从 .env 文件或环境变量传入 - args: - # LLM 通用配置 - - VITE_LLM_PROVIDER=${VITE_LLM_PROVIDER:-gemini} - - VITE_LLM_API_KEY=${VITE_LLM_API_KEY} - - VITE_LLM_MODEL=${VITE_LLM_MODEL} - - VITE_LLM_BASE_URL=${VITE_LLM_BASE_URL} - - VITE_LLM_TIMEOUT=${VITE_LLM_TIMEOUT:-150000} - - VITE_LLM_TEMPERATURE=${VITE_LLM_TEMPERATURE:-0.2} - - VITE_LLM_MAX_TOKENS=${VITE_LLM_MAX_TOKENS:-4096} - - # Google Gemini 配置 - - VITE_GEMINI_API_KEY=${VITE_GEMINI_API_KEY} - - VITE_GEMINI_MODEL=${VITE_GEMINI_MODEL:-gemini-2.5-flash} - - VITE_GEMINI_TIMEOUT_MS=${VITE_GEMINI_TIMEOUT_MS:-25000} - - # OpenAI 配置 - - VITE_OPENAI_API_KEY=${VITE_OPENAI_API_KEY} - - VITE_OPENAI_MODEL=${VITE_OPENAI_MODEL:-gpt-4o-mini} - - VITE_OPENAI_BASE_URL=${VITE_OPENAI_BASE_URL} - - # Claude 配置 - - VITE_CLAUDE_API_KEY=${VITE_CLAUDE_API_KEY} - - VITE_CLAUDE_MODEL=${VITE_CLAUDE_MODEL:-claude-3-5-sonnet-20241022} - - # 通义千问配置 - - VITE_QWEN_API_KEY=${VITE_QWEN_API_KEY} - - VITE_QWEN_MODEL=${VITE_QWEN_MODEL:-qwen-turbo} - - # DeepSeek 配置 - - VITE_DEEPSEEK_API_KEY=${VITE_DEEPSEEK_API_KEY} - - VITE_DEEPSEEK_MODEL=${VITE_DEEPSEEK_MODEL:-deepseek-chat} - - # 智谱AI 配置 - - VITE_ZHIPU_API_KEY=${VITE_ZHIPU_API_KEY} - - VITE_ZHIPU_MODEL=${VITE_ZHIPU_MODEL:-glm-4-flash} - - # Moonshot 配置 - - VITE_MOONSHOT_API_KEY=${VITE_MOONSHOT_API_KEY} - - VITE_MOONSHOT_MODEL=${VITE_MOONSHOT_MODEL:-moonshot-v1-8k} - - # 百度文心一言配置 - - VITE_BAIDU_API_KEY=${VITE_BAIDU_API_KEY} - - VITE_BAIDU_MODEL=${VITE_BAIDU_MODEL:-ERNIE-3.5-8K} - - # MiniMax 配置 - - VITE_MINIMAX_API_KEY=${VITE_MINIMAX_API_KEY} - - VITE_MINIMAX_MODEL=${VITE_MINIMAX_MODEL:-abab6.5-chat} - - # 豆包配置 - - VITE_DOUBAO_API_KEY=${VITE_DOUBAO_API_KEY} - - VITE_DOUBAO_MODEL=${VITE_DOUBAO_MODEL:-doubao-pro-32k} - - # Ollama 配置 - - VITE_OLLAMA_API_KEY=${VITE_OLLAMA_API_KEY:-ollama} - - VITE_OLLAMA_MODEL=${VITE_OLLAMA_MODEL:-llama3} - - VITE_OLLAMA_BASE_URL=${VITE_OLLAMA_BASE_URL:-http://localhost:11434/v1} - - # 数据库配置 - - VITE_USE_LOCAL_DB=${VITE_USE_LOCAL_DB:-true} - - VITE_SUPABASE_URL=${VITE_SUPABASE_URL} - - VITE_SUPABASE_ANON_KEY=${VITE_SUPABASE_ANON_KEY} - - # GitHub 配置 - - VITE_GITHUB_TOKEN=${VITE_GITHUB_TOKEN} - - # 应用配置 - - VITE_APP_ID=${VITE_APP_ID:-xcodereviewer} - - VITE_MAX_ANALYZE_FILES=${VITE_MAX_ANALYZE_FILES:-40} - - VITE_LLM_CONCURRENCY=${VITE_LLM_CONCURRENCY:-2} - - VITE_LLM_GAP_MS=${VITE_LLM_GAP_MS:-500} - - VITE_OUTPUT_LANGUAGE=${VITE_OUTPUT_LANGUAGE:-zh-CN} - - container_name: xcodereviewer-app - ports: - - "5174:80" - restart: unless-stopped - networks: - - xcodereviewer-network - healthcheck: - test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 40s +version: '3.8' -networks: - xcodereviewer-network: - driver: bridge +services: + db: + image: postgres:15-alpine + volumes: + - postgres_data:/var/lib/postgresql/data + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_DB=xcodereviewer + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + + backend: + build: + context: ./backend + volumes: + - ./backend:/app + ports: + - "8000:8000" + environment: + - DATABASE_URL=postgresql+asyncpg://postgres:postgres@db:5432/xcodereviewer + - SECRET_KEY=changethisdevsecret + - ALGORITHM=HS256 + - ACCESS_TOKEN_EXPIRE_MINUTES=30 + depends_on: + db: + condition: service_healthy + command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload + + frontend: + build: + context: ./frontend + volumes: + - ./frontend:/app + - /app/node_modules + ports: + - "5173:5173" + environment: + - VITE_API_BASE_URL=/api + depends_on: + - backend + command: npm run dev -- --host + +volumes: + postgres_data: diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..abfd6ca --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,18 @@ +FROM node:20-alpine + +WORKDIR /app + +COPY package.json pnpm-lock.yaml* package-lock.json* ./ + +RUN if [ -f pnpm-lock.yaml ]; then \ + corepack enable && pnpm install; \ + else \ + npm install; \ + fi + +COPY . . + +EXPOSE 5173 + +CMD ["npm", "run", "dev", "--", "--host"] + diff --git a/components.json b/frontend/components.json similarity index 100% rename from components.json rename to frontend/components.json diff --git a/index.html b/frontend/index.html similarity index 100% rename from index.html rename to frontend/index.html diff --git a/package-lock.json b/frontend/package-lock.json similarity index 50% rename from package-lock.json rename to frontend/package-lock.json index ab091cc..a2f2994 100644 --- a/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "xcode-reviewer", - "version": "1.2.0", + "version": "1.3.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "xcode-reviewer", - "version": "1.2.0", + "version": "1.3.4", "dependencies": { "@google/generative-ai": "^0.24.1", "@radix-ui/react-accordion": "^1.2.8", @@ -75,6 +75,7 @@ "@types/lodash": "^4.17.16", "@types/react": "^19.0.12", "@types/react-dom": "^19.0.4", + "@types/unist": "^3.0.3", "@types/video-react": "^0.15.8", "@typescript/native-preview": "7.0.0-dev.20250819.1", "@vitejs/plugin-react": "^4.3.4", @@ -90,6 +91,8 @@ }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", "license": "MIT", "engines": { "node": ">=10" @@ -100,6 +103,8 @@ }, "node_modules/@antfu/install-pkg": { "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", "license": "MIT", "dependencies": { "package-manager-detector": "^1.3.0", @@ -111,17 +116,21 @@ }, "node_modules/@antfu/utils": { "version": "9.3.0", + "resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-9.3.0.tgz", + "integrity": "sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@ast-grep/cli": { - "version": "0.39.6", + "version": "0.39.9", + "resolved": "https://registry.npmmirror.com/@ast-grep/cli/-/cli-0.39.9.tgz", + "integrity": "sha512-qMtu3GplcMS4PRefPtOeN2slcn6s+ccDiMRk+RvEQoxJqflNbKZi3ZbYq7O2Xh49N/VjeiU3n+5WYHaVFFEyAg==", "dev": true, "hasInstallScript": true, "dependencies": { - "detect-libc": "2.1.1" + "detect-libc": "2.1.2" }, "bin": { "ast-grep": "ast-grep", @@ -131,17 +140,19 @@ "node": ">= 12.0.0" }, "optionalDependencies": { - "@ast-grep/cli-darwin-arm64": "0.39.6", - "@ast-grep/cli-darwin-x64": "0.39.6", - "@ast-grep/cli-linux-arm64-gnu": "0.39.6", - "@ast-grep/cli-linux-x64-gnu": "0.39.6", - "@ast-grep/cli-win32-arm64-msvc": "0.39.6", - "@ast-grep/cli-win32-ia32-msvc": "0.39.6", - "@ast-grep/cli-win32-x64-msvc": "0.39.6" + "@ast-grep/cli-darwin-arm64": "0.39.9", + "@ast-grep/cli-darwin-x64": "0.39.9", + "@ast-grep/cli-linux-arm64-gnu": "0.39.9", + "@ast-grep/cli-linux-x64-gnu": "0.39.9", + "@ast-grep/cli-win32-arm64-msvc": "0.39.9", + "@ast-grep/cli-win32-ia32-msvc": "0.39.9", + "@ast-grep/cli-win32-x64-msvc": "0.39.9" } }, "node_modules/@ast-grep/cli-darwin-arm64": { - "version": "0.39.6", + "version": "0.39.9", + "resolved": "https://registry.npmmirror.com/@ast-grep/cli-darwin-arm64/-/cli-darwin-arm64-0.39.9.tgz", + "integrity": "sha512-DCmooChgotx4uu8B8rKfCAD05P8Au1lXn4wVLDJMbYYUxVjYIaF+fdjKj+hTRD2POAtQHtHN1F2l9gl8zcQAyw==", "cpu": [ "arm64" ], @@ -155,8 +166,112 @@ "node": ">= 10" } }, + "node_modules/@ast-grep/cli-darwin-x64": { + "version": "0.39.9", + "resolved": "https://registry.npmmirror.com/@ast-grep/cli-darwin-x64/-/cli-darwin-x64-0.39.9.tgz", + "integrity": "sha512-0jMyF0DOXjDmL61VuT8l/QRcBKbJQeMEEhH7G6sUF+mBuFBjYjoSEDg9+FSjUU+vPN3flJ99Kg4pT+einy+pjg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ast-grep/cli-linux-arm64-gnu": { + "version": "0.39.9", + "resolved": "https://registry.npmmirror.com/@ast-grep/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-0.39.9.tgz", + "integrity": "sha512-fygNBarEJB4LS4kLk+v//s1AWH7iTRX/vUdepsVivmP8MAwdsQJQ3HH/uKgoE+KFj2FCrq9CxfaCswlpbFtdpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ast-grep/cli-linux-x64-gnu": { + "version": "0.39.9", + "resolved": "https://registry.npmmirror.com/@ast-grep/cli-linux-x64-gnu/-/cli-linux-x64-gnu-0.39.9.tgz", + "integrity": "sha512-StC8APEfKqMKt23+JE40BLMFAx6KAQZfy8UTZM2rSUGUYetjZDoTVYnfh1aaH+oyIQzB+ef11dRsMp/ls5Pilg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ast-grep/cli-win32-arm64-msvc": { + "version": "0.39.9", + "resolved": "https://registry.npmmirror.com/@ast-grep/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-0.39.9.tgz", + "integrity": "sha512-HbIwYoh7NlJfYn957+9wj6bywQdGy2S+KpFj4Is7hnto6Yh1eRyG0szZkr6kzJuXQxF5NZoE12odWgTElpDUAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ast-grep/cli-win32-ia32-msvc": { + "version": "0.39.9", + "resolved": "https://registry.npmmirror.com/@ast-grep/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-0.39.9.tgz", + "integrity": "sha512-SMFSuTRzeM3857UX467YCy6uIi7onwOxf6YGlNsOwg6ShiWcNodssoPCir9kZccLoSp+9dxANmV/6n8CMHwH0w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ast-grep/cli-win32-x64-msvc": { + "version": "0.39.9", + "resolved": "https://registry.npmmirror.com/@ast-grep/cli-win32-x64-msvc/-/cli-win32-x64-msvc-0.39.9.tgz", + "integrity": "sha512-+Kopx/NUEmFORNmQ/nsHXvnqoOuj45vCrDhQ/cj01D4AoeQ9MxbzVAk8sjSJsJbazDaV9if41+HTAhpVWhC4CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -168,7 +283,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.4", + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", "dev": true, "license": "MIT", "engines": { @@ -176,19 +293,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.4", + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", + "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -205,11 +324,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.3", + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -220,6 +341,8 @@ }, "node_modules/@babel/helper-compilation-targets": { "version": "7.27.2", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "license": "MIT", "dependencies": { @@ -235,6 +358,8 @@ }, "node_modules/@babel/helper-globals": { "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -242,6 +367,8 @@ }, "node_modules/@babel/helper-module-imports": { "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, "license": "MIT", "dependencies": { @@ -254,6 +381,8 @@ }, "node_modules/@babel/helper-module-transforms": { "version": "7.28.3", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, "license": "MIT", "dependencies": { @@ -270,6 +399,8 @@ }, "node_modules/@babel/helper-plugin-utils": { "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, "license": "MIT", "engines": { @@ -278,13 +409,17 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@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", + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -292,6 +427,8 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -300,6 +437,8 @@ }, "node_modules/@babel/helpers": { "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, "license": "MIT", "dependencies": { @@ -311,10 +450,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.4", + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.4" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -325,6 +466,8 @@ }, "node_modules/@babel/plugin-transform-react-jsx-self": { "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", "dev": true, "license": "MIT", "dependencies": { @@ -339,6 +482,8 @@ }, "node_modules/@babel/plugin-transform-react-jsx-source": { "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", "dev": true, "license": "MIT", "dependencies": { @@ -353,6 +498,8 @@ }, "node_modules/@babel/runtime": { "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -360,6 +507,8 @@ }, "node_modules/@babel/template": { "version": "7.27.2", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -371,15 +520,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.4", + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", + "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", + "@babel/types": "^7.28.5", "debug": "^4.3.1" }, "engines": { @@ -387,11 +538,13 @@ } }, "node_modules/@babel/types": { - "version": "7.28.4", + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -399,6 +552,8 @@ }, "node_modules/@biomejs/biome": { "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/@biomejs/biome/-/biome-2.2.3.tgz", + "integrity": "sha512-9w0uMTvPrIdvUrxazZ42Ib7t8Y2yoGLKLdNne93RLICmaHw7mcLv4PPb5LvZLJF3141gQHiCColOh/v6VWlWmg==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -424,6 +579,8 @@ }, "node_modules/@biomejs/cli-darwin-arm64": { "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.3.tgz", + "integrity": "sha512-OrqQVBpadB5eqzinXN4+Q6honBz+tTlKVCsbEuEpljK8ASSItzIRZUA02mTikl3H/1nO2BMPFiJ0nkEZNy3B1w==", "cpu": [ "arm64" ], @@ -437,12 +594,135 @@ "node": ">=14.21.3" } }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.3.tgz", + "integrity": "sha512-OCdBpb1TmyfsTgBAM1kPMXyYKTohQ48WpiN9tkt9xvU6gKVKHY4oVwteBebiOqyfyzCNaSiuKIPjmHjUZ2ZNMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.3.tgz", + "integrity": "sha512-g/Uta2DqYpECxG+vUmTAmUKlVhnGEcY7DXWgKP8ruLRa8Si1QHsWknPY3B/wCo0KgYiFIOAZ9hjsHfNb9L85+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.3.tgz", + "integrity": "sha512-q3w9jJ6JFPZPeqyvwwPeaiS/6NEszZ+pXKF+IczNo8Xj6fsii45a4gEEicKyKIytalV+s829ACZujQlXAiVLBQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.3.tgz", + "integrity": "sha512-LEtyYL1fJsvw35CxrbQ0gZoxOG3oZsAjzfRdvRBRHxOpQ91Q5doRVjvWW/wepgSdgk5hlaNzfeqpyGmfSD0Eyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.3.tgz", + "integrity": "sha512-y76Dn4vkP1sMRGPFlNc+OTETBhGPJ90jY3il6jAfur8XWrYBQV3swZ1Jo0R2g+JpOeeoA0cOwM7mJG6svDz79w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.3.tgz", + "integrity": "sha512-Ms9zFYzjcJK7LV+AOMYnjN3pV3xL8Prxf9aWdDVL74onLn5kcvZ1ZMQswE5XHtnd/r/0bnUd928Rpbs14BzVmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.3.tgz", + "integrity": "sha512-gvCpewE7mBwBIpqk1YrUqNR4mCiyJm6UI3YWQQXkedSSEwzRdodRpaKhbdbHw1/hmTWOVXQ+Eih5Qctf4TCVOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@braintree/sanitize-url": { "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", + "integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==", "license": "MIT" }, "node_modules/@chevrotain/cst-dts-gen": { "version": "11.0.3", + "resolved": "https://registry.npmmirror.com/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", + "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", "license": "Apache-2.0", "dependencies": { "@chevrotain/gast": "11.0.3", @@ -452,6 +732,8 @@ }, "node_modules/@chevrotain/gast": { "version": "11.0.3", + "resolved": "https://registry.npmmirror.com/@chevrotain/gast/-/gast-11.0.3.tgz", + "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", "license": "Apache-2.0", "dependencies": { "@chevrotain/types": "11.0.3", @@ -460,18 +742,90 @@ }, "node_modules/@chevrotain/regexp-to-ast": { "version": "11.0.3", + "resolved": "https://registry.npmmirror.com/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", "license": "Apache-2.0" }, "node_modules/@chevrotain/types": { "version": "11.0.3", + "resolved": "https://registry.npmmirror.com/@chevrotain/types/-/types-11.0.3.tgz", + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", "license": "Apache-2.0" }, "node_modules/@chevrotain/utils": { "version": "11.0.3", + "resolved": "https://registry.npmmirror.com/@chevrotain/utils/-/utils-11.0.3.tgz", + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", "license": "Apache-2.0" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/darwin-arm64": { "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -484,8 +838,298 @@ "node": ">=12" } }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@floating-ui/core": { "version": "1.7.3", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", "license": "MIT", "dependencies": { "@floating-ui/utils": "^0.2.10" @@ -493,6 +1137,8 @@ }, "node_modules/@floating-ui/dom": { "version": "1.7.4", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", "license": "MIT", "dependencies": { "@floating-ui/core": "^1.7.3", @@ -501,6 +1147,8 @@ }, "node_modules/@floating-ui/react-dom": { "version": "2.1.6", + "resolved": "https://registry.npmmirror.com/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.7.4" @@ -512,10 +1160,14 @@ }, "node_modules/@floating-ui/utils": { "version": "0.2.10", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, "node_modules/@google/generative-ai": { "version": "0.24.1", + "resolved": "https://registry.npmmirror.com/@google/generative-ai/-/generative-ai-0.24.1.tgz", + "integrity": "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -523,10 +1175,14 @@ }, "node_modules/@iconify/types": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", "license": "MIT" }, "node_modules/@iconify/utils": { "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@iconify/utils/-/utils-3.0.2.tgz", + "integrity": "sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==", "license": "MIT", "dependencies": { "@antfu/install-pkg": "^1.1.0", @@ -539,23 +1195,10 @@ "mlly": "^1.7.4" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "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.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -564,6 +1207,8 @@ }, "node_modules/@jridgewell/remapping": { "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", "dependencies": { @@ -573,6 +1218,8 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -580,6 +1227,8 @@ }, "node_modules/@jridgewell/source-map": { "version": "0.3.11", + "resolved": "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -589,10 +1238,14 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@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.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -601,6 +1254,8 @@ }, "node_modules/@mermaid-js/parser": { "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/@mermaid-js/parser/-/parser-0.6.3.tgz", + "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", "license": "MIT", "dependencies": { "langium": "3.3.1" @@ -608,6 +1263,8 @@ }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -619,6 +1276,8 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "license": "MIT", "engines": { "node": ">= 8" @@ -626,6 +1285,8 @@ }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -635,24 +1296,22 @@ "node": ">= 8" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@radix-ui/number": { "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", "license": "MIT" }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", "license": "MIT" }, "node_modules/@radix-ui/react-accordion": { "version": "1.2.12", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", + "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -682,6 +1341,8 @@ }, "node_modules/@radix-ui/react-alert-dialog": { "version": "1.1.15", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", + "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -706,8 +1367,28 @@ } } }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" @@ -728,10 +1409,35 @@ } }, "node_modules/@radix-ui/react-aspect-ratio": { - "version": "1.1.7", + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.8.tgz", + "integrity": "sha512-5nZrJTF7gH+e0nZS7/QxFz6tJV4VimhQb1avEgtsJxvvIp5JilL+c58HICsKzPxghdwaDt48hEfPM1au4zGy+w==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3" + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", @@ -749,11 +1455,13 @@ } }, "node_modules/@radix-ui/react-avatar": { - "version": "1.1.10", + "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-avatar/-/react-avatar-1.1.11.tgz", + "integrity": "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==", "license": "MIT", "dependencies": { - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-context": "1.1.3", + "@radix-ui/react-primitive": "2.1.4", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" @@ -773,8 +1481,48 @@ } } }, + "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-context": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.1.3.tgz", + "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-checkbox": { "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -803,6 +1551,8 @@ }, "node_modules/@radix-ui/react-collapsible": { "version": "1.1.12", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -831,6 +1581,8 @@ }, "node_modules/@radix-ui/react-collection": { "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -853,8 +1605,28 @@ } } }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -868,6 +1640,8 @@ }, "node_modules/@radix-ui/react-context": { "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -881,6 +1655,8 @@ }, "node_modules/@radix-ui/react-dialog": { "version": "1.1.15", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -913,8 +1689,28 @@ } } }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -928,6 +1724,8 @@ }, "node_modules/@radix-ui/react-dismissable-layer": { "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -953,6 +1751,8 @@ }, "node_modules/@radix-ui/react-dropdown-menu": { "version": "2.1.16", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -980,6 +1780,8 @@ }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -993,6 +1795,8 @@ }, "node_modules/@radix-ui/react-focus-scope": { "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -1016,6 +1820,8 @@ }, "node_modules/@radix-ui/react-icons": { "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", "license": "MIT", "peerDependencies": { "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" @@ -1023,6 +1829,8 @@ }, "node_modules/@radix-ui/react-id": { "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" @@ -1038,10 +1846,35 @@ } }, "node_modules/@radix-ui/react-label": { - "version": "2.1.7", + "version": "2.1.8", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3" + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", @@ -1060,6 +1893,8 @@ }, "node_modules/@radix-ui/react-menu": { "version": "2.1.16", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1096,8 +1931,28 @@ } } }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-menubar": { "version": "1.1.16", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", + "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1128,6 +1983,8 @@ }, "node_modules/@radix-ui/react-navigation-menu": { "version": "1.2.14", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", + "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1160,8 +2017,33 @@ } } }, + "node_modules/@radix-ui/react-navigation-menu/node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popover": { "version": "1.1.15", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1195,8 +2077,28 @@ } } }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", @@ -1227,6 +2129,8 @@ }, "node_modules/@radix-ui/react-portal": { "version": "1.1.9", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3", @@ -1249,6 +2153,8 @@ }, "node_modules/@radix-ui/react-presence": { "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -1271,6 +2177,8 @@ }, "node_modules/@radix-ui/react-primitive": { "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", "license": "MIT", "dependencies": { "@radix-ui/react-slot": "1.2.3" @@ -1290,12 +2198,70 @@ } } }, - "node_modules/@radix-ui/react-progress": { - "version": "1.1.7", + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", "dependencies": { - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3" + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-progress/-/react-progress-1.1.8.tgz", + "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.3", + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.1.3.tgz", + "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", @@ -1314,6 +2280,8 @@ }, "node_modules/@radix-ui/react-radio-group": { "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1344,6 +2312,8 @@ }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1373,6 +2343,8 @@ }, "node_modules/@radix-ui/react-scroll-area": { "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", @@ -1402,6 +2374,8 @@ }, "node_modules/@radix-ui/react-select": { "version": "2.2.6", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", @@ -1441,8 +2415,28 @@ } } }, - "node_modules/@radix-ui/react-separator": { - "version": "1.1.7", + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" @@ -1462,8 +2456,56 @@ } } }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", + "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slider": { "version": "1.3.6", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", + "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", @@ -1494,7 +2536,9 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.2.3", + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -1511,6 +2555,8 @@ }, "node_modules/@radix-ui/react-switch": { "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1538,6 +2584,8 @@ }, "node_modules/@radix-ui/react-tabs": { "version": "1.1.13", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1566,6 +2614,8 @@ }, "node_modules/@radix-ui/react-toast": { "version": "1.2.15", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", + "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1596,8 +2646,33 @@ } } }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-toggle": { "version": "1.1.10", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1621,6 +2696,8 @@ }, "node_modules/@radix-ui/react-toggle-group": { "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", + "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1648,6 +2725,8 @@ }, "node_modules/@radix-ui/react-tooltip": { "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1678,144 +2757,28 @@ } } }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.1", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.2", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-is-hydrated": { - "version": "0.1.0", - "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.5.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.1", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "@radix-ui/rect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-visually-hidden": { + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" @@ -1835,12 +2798,216 @@ } } }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.4.tgz", + "integrity": "sha512-kaeiyGCe844dkb9AVF+rb4yTyb1LiLN/e3es3nLiRyN4dC8AduBYPMnnNlDjX2VDOcvDEiPnRNMJeWCfsX0txg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/rect": { "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, "node_modules/@remix-run/router": { - "version": "1.23.0", + "version": "1.23.1", + "resolved": "https://registry.npmmirror.com/@remix-run/router/-/router-1.23.1.tgz", + "integrity": "sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -1848,11 +3015,15 @@ }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", "dev": true, "license": "MIT" }, "node_modules/@rollup/pluginutils": { "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1874,6 +3045,8 @@ }, "node_modules/@rollup/pluginutils/node_modules/picomatch": { "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -1883,8 +3056,36 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.5", + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", "cpu": [ "arm64" ], @@ -1894,49 +3095,308 @@ "darwin" ] }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@shikijs/core": { - "version": "3.13.0", + "version": "3.15.0", + "resolved": "https://registry.npmmirror.com/@shikijs/core/-/core-3.15.0.tgz", + "integrity": "sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.13.0", + "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "node_modules/@shikijs/engine-javascript": { - "version": "3.13.0", + "version": "3.15.0", + "resolved": "https://registry.npmmirror.com/@shikijs/engine-javascript/-/engine-javascript-3.15.0.tgz", + "integrity": "sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.13.0", + "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "node_modules/@shikijs/engine-oniguruma": { - "version": "3.13.0", + "version": "3.15.0", + "resolved": "https://registry.npmmirror.com/@shikijs/engine-oniguruma/-/engine-oniguruma-3.15.0.tgz", + "integrity": "sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.13.0", + "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "node_modules/@shikijs/langs": { - "version": "3.13.0", + "version": "3.15.0", + "resolved": "https://registry.npmmirror.com/@shikijs/langs/-/langs-3.15.0.tgz", + "integrity": "sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.13.0" + "@shikijs/types": "3.15.0" } }, "node_modules/@shikijs/themes": { - "version": "3.13.0", + "version": "3.15.0", + "resolved": "https://registry.npmmirror.com/@shikijs/themes/-/themes-3.15.0.tgz", + "integrity": "sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.13.0" + "@shikijs/types": "3.15.0" } }, "node_modules/@shikijs/types": { - "version": "3.13.0", + "version": "3.15.0", + "resolved": "https://registry.npmmirror.com/@shikijs/types/-/types-3.15.0.tgz", + "integrity": "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==", "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", @@ -1945,75 +3405,93 @@ }, "node_modules/@shikijs/vscode-textmate": { "version": "10.0.2", + "resolved": "https://registry.npmmirror.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "license": "MIT" }, "node_modules/@supabase/auth-js": { - "version": "2.76.1", + "version": "2.84.0", + "resolved": "https://registry.npmmirror.com/@supabase/auth-js/-/auth-js-2.84.0.tgz", + "integrity": "sha512-J6XKbqqg1HQPMfYkAT9BrC8anPpAiifl7qoVLsYhQq5B/dnu/lxab1pabnxtJEsvYG5rwI5HEVEGXMjoQ6Wz2Q==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "2.6.15", "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@supabase/functions-js": { - "version": "2.76.1", + "version": "2.84.0", + "resolved": "https://registry.npmmirror.com/@supabase/functions-js/-/functions-js-2.84.0.tgz", + "integrity": "sha512-2oY5QBV4py/s64zMlhPEz+4RTdlwxzmfhM1k2xftD2v1DruRZKfoe7Yn9DCz1VondxX8evcvpc2udEIGzHI+VA==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "2.6.15", "tslib": "2.8.1" - } - }, - "node_modules/@supabase/node-fetch": { - "version": "2.6.15", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" }, "engines": { - "node": "4.x || >=6.0.0" + "node": ">=20.0.0" } }, "node_modules/@supabase/postgrest-js": { - "version": "2.76.1", + "version": "2.84.0", + "resolved": "https://registry.npmmirror.com/@supabase/postgrest-js/-/postgrest-js-2.84.0.tgz", + "integrity": "sha512-oplc/3jfJeVW4F0J8wqywHkjIZvOVHtqzF0RESijepDAv5Dn/LThlGW1ftysoP4+PXVIrnghAbzPHo88fNomPQ==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "2.6.15", "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@supabase/realtime-js": { - "version": "2.76.1", + "version": "2.84.0", + "resolved": "https://registry.npmmirror.com/@supabase/realtime-js/-/realtime-js-2.84.0.tgz", + "integrity": "sha512-ThqjxiCwWiZAroHnYPmnNl6tZk6jxGcG2a7Hp/3kcolPcMj89kWjUTA3cHmhdIWYsP84fHp8MAQjYWMLf7HEUg==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "2.6.15", "@types/phoenix": "^1.6.6", "@types/ws": "^8.18.1", "tslib": "2.8.1", "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@supabase/storage-js": { - "version": "2.76.1", + "version": "2.84.0", + "resolved": "https://registry.npmmirror.com/@supabase/storage-js/-/storage-js-2.84.0.tgz", + "integrity": "sha512-vXvAJ1euCuhryOhC6j60dG8ky+lk0V06ubNo+CbhuoUv+sl39PyY0lc+k+qpQhTk/VcI6SiM0OECLN83+nyJ5A==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "2.6.15", "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@supabase/supabase-js": { - "version": "2.76.1", + "version": "2.84.0", + "resolved": "https://registry.npmmirror.com/@supabase/supabase-js/-/supabase-js-2.84.0.tgz", + "integrity": "sha512-byMqYBvb91sx2jcZsdp0qLpmd4Dioe80e4OU/UexXftCkpTcgrkoENXHf5dO8FCSai8SgNeq16BKg10QiDI6xg==", "license": "MIT", "dependencies": { - "@supabase/auth-js": "2.76.1", - "@supabase/functions-js": "2.76.1", - "@supabase/node-fetch": "2.6.15", - "@supabase/postgrest-js": "2.76.1", - "@supabase/realtime-js": "2.76.1", - "@supabase/storage-js": "2.76.1" + "@supabase/auth-js": "2.84.0", + "@supabase/functions-js": "2.84.0", + "@supabase/postgrest-js": "2.84.0", + "@supabase/realtime-js": "2.84.0", + "@supabase/storage-js": "2.84.0" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", "dev": true, "license": "MIT", "engines": { @@ -2029,6 +3507,8 @@ }, "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", "dev": true, "license": "MIT", "engines": { @@ -2044,6 +3524,8 @@ }, "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", "dev": true, "license": "MIT", "engines": { @@ -2059,6 +3541,8 @@ }, "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", "dev": true, "license": "MIT", "engines": { @@ -2074,6 +3558,8 @@ }, "node_modules/@svgr/babel-plugin-svg-dynamic-title": { "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", "dev": true, "license": "MIT", "engines": { @@ -2089,6 +3575,8 @@ }, "node_modules/@svgr/babel-plugin-svg-em-dimensions": { "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", "dev": true, "license": "MIT", "engines": { @@ -2104,6 +3592,8 @@ }, "node_modules/@svgr/babel-plugin-transform-react-native-svg": { "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", "dev": true, "license": "MIT", "engines": { @@ -2119,6 +3609,8 @@ }, "node_modules/@svgr/babel-plugin-transform-svg-component": { "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", "dev": true, "license": "MIT", "engines": { @@ -2134,6 +3626,8 @@ }, "node_modules/@svgr/babel-preset": { "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", "dev": true, "license": "MIT", "dependencies": { @@ -2159,6 +3653,8 @@ }, "node_modules/@svgr/core": { "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dev": true, "license": "MIT", "dependencies": { @@ -2178,6 +3674,8 @@ }, "node_modules/@svgr/hast-util-to-babel-ast": { "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2194,6 +3692,8 @@ }, "node_modules/@svgr/hast-util-to-babel-ast/node_modules/entities": { "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -2205,6 +3705,8 @@ }, "node_modules/@svgr/plugin-jsx": { "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", "dev": true, "license": "MIT", "dependencies": { @@ -2226,6 +3728,8 @@ }, "node_modules/@types/babel__core": { "version": "7.20.5", + "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "license": "MIT", "dependencies": { @@ -2238,6 +3742,8 @@ }, "node_modules/@types/babel__generator": { "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, "license": "MIT", "dependencies": { @@ -2246,6 +3752,8 @@ }, "node_modules/@types/babel__template": { "version": "7.4.4", + "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "license": "MIT", "dependencies": { @@ -2255,6 +3763,8 @@ }, "node_modules/@types/babel__traverse": { "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2263,6 +3773,8 @@ }, "node_modules/@types/d3": { "version": "7.4.3", + "resolved": "https://registry.npmmirror.com/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", "license": "MIT", "dependencies": { "@types/d3-array": "*", @@ -2299,10 +3811,14 @@ }, "node_modules/@types/d3-array": { "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", "license": "MIT" }, "node_modules/@types/d3-axis": { "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", "license": "MIT", "dependencies": { "@types/d3-selection": "*" @@ -2310,6 +3826,8 @@ }, "node_modules/@types/d3-brush": { "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", "license": "MIT", "dependencies": { "@types/d3-selection": "*" @@ -2317,14 +3835,20 @@ }, "node_modules/@types/d3-chord": { "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", "license": "MIT" }, "node_modules/@types/d3-color": { "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", "license": "MIT" }, "node_modules/@types/d3-contour": { "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", "license": "MIT", "dependencies": { "@types/d3-array": "*", @@ -2333,14 +3857,20 @@ }, "node_modules/@types/d3-delaunay": { "version": "6.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", "license": "MIT" }, "node_modules/@types/d3-dispatch": { "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", "license": "MIT" }, "node_modules/@types/d3-drag": { "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", "license": "MIT", "dependencies": { "@types/d3-selection": "*" @@ -2348,14 +3878,20 @@ }, "node_modules/@types/d3-dsv": { "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", "license": "MIT" }, "node_modules/@types/d3-ease": { "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", "license": "MIT" }, "node_modules/@types/d3-fetch": { "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", "license": "MIT", "dependencies": { "@types/d3-dsv": "*" @@ -2363,14 +3899,20 @@ }, "node_modules/@types/d3-force": { "version": "3.0.10", + "resolved": "https://registry.npmmirror.com/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", "license": "MIT" }, "node_modules/@types/d3-format": { "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", "license": "MIT" }, "node_modules/@types/d3-geo": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", "license": "MIT", "dependencies": { "@types/geojson": "*" @@ -2378,10 +3920,14 @@ }, "node_modules/@types/d3-hierarchy": { "version": "3.1.7", + "resolved": "https://registry.npmmirror.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", "license": "MIT" }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", "license": "MIT", "dependencies": { "@types/d3-color": "*" @@ -2389,22 +3935,32 @@ }, "node_modules/@types/d3-path": { "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", "license": "MIT" }, "node_modules/@types/d3-polygon": { "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", "license": "MIT" }, "node_modules/@types/d3-quadtree": { "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", "license": "MIT" }, "node_modules/@types/d3-random": { "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", "license": "MIT" }, "node_modules/@types/d3-scale": { "version": "4.0.9", + "resolved": "https://registry.npmmirror.com/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", "license": "MIT", "dependencies": { "@types/d3-time": "*" @@ -2412,14 +3968,20 @@ }, "node_modules/@types/d3-scale-chromatic": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", "license": "MIT" }, "node_modules/@types/d3-selection": { "version": "3.0.11", + "resolved": "https://registry.npmmirror.com/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", "license": "MIT" }, "node_modules/@types/d3-shape": { "version": "3.1.7", + "resolved": "https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", "license": "MIT", "dependencies": { "@types/d3-path": "*" @@ -2427,18 +3989,26 @@ }, "node_modules/@types/d3-time": { "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", "license": "MIT" }, "node_modules/@types/d3-time-format": { "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", "license": "MIT" }, "node_modules/@types/d3-timer": { "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "license": "MIT" }, "node_modules/@types/d3-transition": { "version": "3.0.9", + "resolved": "https://registry.npmmirror.com/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", "license": "MIT", "dependencies": { "@types/d3-selection": "*" @@ -2446,6 +4016,8 @@ }, "node_modules/@types/d3-zoom": { "version": "3.0.8", + "resolved": "https://registry.npmmirror.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", "license": "MIT", "dependencies": { "@types/d3-interpolate": "*", @@ -2454,6 +4026,8 @@ }, "node_modules/@types/debug": { "version": "4.1.12", + "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "license": "MIT", "dependencies": { "@types/ms": "*" @@ -2461,10 +4035,14 @@ }, "node_modules/@types/estree": { "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, "node_modules/@types/estree-jsx": { "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", "license": "MIT", "dependencies": { "@types/estree": "*" @@ -2472,10 +4050,14 @@ }, "node_modules/@types/geojson": { "version": "7946.0.16", + "resolved": "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "license": "MIT" }, "node_modules/@types/hast": { "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", "license": "MIT", "dependencies": { "@types/unist": "*" @@ -2483,15 +4065,21 @@ }, "node_modules/@types/katex": { "version": "0.16.7", + "resolved": "https://registry.npmmirror.com/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.17.20", + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", "dev": true, "license": "MIT" }, "node_modules/@types/mdast": { "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", "license": "MIT", "dependencies": { "@types/unist": "*" @@ -2499,10 +4087,14 @@ }, "node_modules/@types/ms": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, "node_modules/@types/node": { - "version": "24.9.1", + "version": "24.10.1", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -2510,17 +4102,24 @@ }, "node_modules/@types/phoenix": { "version": "1.6.6", + "resolved": "https://registry.npmmirror.com/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.2", + "version": "19.2.7", + "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "devOptional": true, "license": "MIT", "dependencies": { - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "19.2.2", + "version": "19.2.3", + "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", "peerDependencies": { @@ -2529,15 +4128,21 @@ }, "node_modules/@types/trusted-types": { "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT", "optional": true }, "node_modules/@types/unist": { "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, "node_modules/@types/video-react": { "version": "0.15.8", + "resolved": "https://registry.npmmirror.com/@types/video-react/-/video-react-0.15.8.tgz", + "integrity": "sha512-ZFm57z6bwJ1FWMKAMXyDar0OAQCchals2T4mDG//JXeToW3C2dADI2MzX5y53tFL77Y2QAA1YQZh5XTL1rjiqw==", "dev": true, "license": "MIT", "dependencies": { @@ -2546,6 +4151,8 @@ }, "node_modules/@types/ws": { "version": "8.18.1", + "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -2553,6 +4160,8 @@ }, "node_modules/@typescript/native-preview": { "version": "7.0.0-dev.20250819.1", + "resolved": "https://registry.npmmirror.com/@typescript/native-preview/-/native-preview-7.0.0-dev.20250819.1.tgz", + "integrity": "sha512-+gl0Sg+Ol5QibPacZziwHyImPtPzuYV1BF/n99LG6U9BAMOXoBamU+9Y5C+0gUdesmz5Ik9ZJpUuX4HTBzULig==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2573,6 +4182,8 @@ }, "node_modules/@typescript/native-preview-darwin-arm64": { "version": "7.0.0-dev.20250819.1", + "resolved": "https://registry.npmmirror.com/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20250819.1.tgz", + "integrity": "sha512-sZAKCg5lW10rg4REkm6Qv14+f2OVN4dX+AfznnxZp3iqj9am+0JCg67la3IVlhznY3jwvqJU6QK54vblK+my5A==", "cpu": [ "arm64" ], @@ -2586,12 +4197,118 @@ "node": ">=20.6.0" } }, + "node_modules/@typescript/native-preview-darwin-x64": { + "version": "7.0.0-dev.20250819.1", + "resolved": "https://registry.npmmirror.com/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20250819.1.tgz", + "integrity": "sha512-EkcJWqnldvsEM59hNicAFGp0a3y4ebp60uJdlgxuOC3kFHffMHTUSxhuxySY4djKV9uei5RZR2O5s3XDrjcKoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.6.0" + } + }, + "node_modules/@typescript/native-preview-linux-arm": { + "version": "7.0.0-dev.20250819.1", + "resolved": "https://registry.npmmirror.com/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20250819.1.tgz", + "integrity": "sha512-yhfOqmxyscXYd6gkrvmDInVx5fntetIrIBHGw1ElNmnmlu5HgguXfEXLxYNMVnBRTzrHtHYPQ8jRd79X1uni+g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.6.0" + } + }, + "node_modules/@typescript/native-preview-linux-arm64": { + "version": "7.0.0-dev.20250819.1", + "resolved": "https://registry.npmmirror.com/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20250819.1.tgz", + "integrity": "sha512-nG07i0oVCISBVPqoHKPpBwYUg3XJhuo/QOScvTagJ4o1XS63HCZOldC0cu26ZyvlrgBaWmteVMzyaaIluwupNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.6.0" + } + }, + "node_modules/@typescript/native-preview-linux-x64": { + "version": "7.0.0-dev.20250819.1", + "resolved": "https://registry.npmmirror.com/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20250819.1.tgz", + "integrity": "sha512-HrVUxN+VBDiiyi3xk4Ih323ZXAyLWu4CNlI1MGqazhOvahKUjTTvuyzdyTWI7Bzf25KcHIXqfyD0PTqQQRMnVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.6.0" + } + }, + "node_modules/@typescript/native-preview-win32-arm64": { + "version": "7.0.0-dev.20250819.1", + "resolved": "https://registry.npmmirror.com/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20250819.1.tgz", + "integrity": "sha512-TRSeuNIA3CI8BmZhioj+QBFdsywadJWKvdC/dhawaYJJuhwz4jbPD7lg/SA2gWihqDC7ZhPq6eqSgaxobW5/uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.6.0" + } + }, + "node_modules/@typescript/native-preview-win32-x64": { + "version": "7.0.0-dev.20250819.1", + "resolved": "https://registry.npmmirror.com/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20250819.1.tgz", + "integrity": "sha512-LTLw0FtGmrSZ3mrERcDWJ1P7tvGMZAi5dhbwXBhGHiawX8kwWHZI3J+TqDnU2zrxL22164qsAee2LI4J2bwpNw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.6.0" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", "dev": true, "license": "MIT", "dependencies": { @@ -2611,6 +4328,8 @@ }, "node_modules/acorn": { "version": "8.15.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -2620,20 +4339,24 @@ } }, "node_modules/ansi-regex": { - "version": "6.2.2", + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=8" } }, "node_modules/ansi-styles": { - "version": "6.2.3", + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -2641,10 +4364,14 @@ }, "node_modules/any-promise": { "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -2656,15 +4383,21 @@ }, "node_modules/arg": { "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -2675,6 +4408,8 @@ }, "node_modules/ast-types": { "version": "0.16.1", + "resolved": "https://registry.npmmirror.com/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", "license": "MIT", "dependencies": { "tslib": "^2.0.1" @@ -2685,10 +4420,14 @@ }, "node_modules/asynckit": { "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, "node_modules/autoprefixer": { - "version": "10.4.21", + "version": "10.4.22", + "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.22.tgz", + "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", "dev": true, "funding": [ { @@ -2706,9 +4445,9 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", + "browserslist": "^4.27.0", + "caniuse-lite": "^1.0.30001754", + "fraction.js": "^5.3.4", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -2724,7 +4463,9 @@ } }, "node_modules/axios": { - "version": "1.12.2", + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -2734,18 +4475,18 @@ }, "node_modules/bail": { "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "license": "MIT" - }, "node_modules/baseline-browser-mapping": { - "version": "2.8.19", + "version": "2.8.31", + "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz", + "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2754,6 +4495,8 @@ }, "node_modules/binary-extensions": { "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "license": "MIT", "engines": { "node": ">=8" @@ -2762,15 +4505,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/braces": { "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2780,7 +4518,9 @@ } }, "node_modules/browserslist": { - "version": "4.26.3", + "version": "4.28.0", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", "dev": true, "funding": [ { @@ -2798,11 +4538,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.9", - "caniuse-lite": "^1.0.30001746", - "electron-to-chromium": "^1.5.227", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" @@ -2813,11 +4553,15 @@ }, "node_modules/buffer-from": { "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "devOptional": true, "license": "MIT" }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/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", @@ -2829,6 +4573,8 @@ }, "node_modules/callsites": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", "engines": { @@ -2837,6 +4583,8 @@ }, "node_modules/camelcase": { "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", "engines": { @@ -2848,13 +4596,17 @@ }, "node_modules/camelcase-css": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", "license": "MIT", "engines": { "node": ">= 6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001751", + "version": "1.0.30001757", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", + "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", "dev": true, "funding": [ { @@ -2874,6 +4626,8 @@ }, "node_modules/ccount": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", "license": "MIT", "funding": { "type": "github", @@ -2882,6 +4636,8 @@ }, "node_modules/character-entities": { "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "license": "MIT", "funding": { "type": "github", @@ -2890,6 +4646,8 @@ }, "node_modules/character-entities-html4": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", "license": "MIT", "funding": { "type": "github", @@ -2898,6 +4656,8 @@ }, "node_modules/character-entities-legacy": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", "license": "MIT", "funding": { "type": "github", @@ -2906,6 +4666,8 @@ }, "node_modules/character-reference-invalid": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", "license": "MIT", "funding": { "type": "github", @@ -2914,6 +4676,8 @@ }, "node_modules/chevrotain": { "version": "11.0.3", + "resolved": "https://registry.npmmirror.com/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", "license": "Apache-2.0", "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", @@ -2926,6 +4690,8 @@ }, "node_modules/chevrotain-allstar": { "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", "license": "MIT", "dependencies": { "lodash-es": "^4.17.21" @@ -2936,6 +4702,8 @@ }, "node_modules/chokidar": { "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -2958,6 +4726,8 @@ }, "node_modules/chokidar/node_modules/glob-parent": { "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -2968,6 +4738,8 @@ }, "node_modules/class-variance-authority": { "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/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" @@ -2978,10 +4750,14 @@ }, "node_modules/classnames": { "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", "license": "MIT" }, "node_modules/cliui": { "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -2989,66 +4765,10 @@ "wrap-ansi": "^6.2.0" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "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/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "6.2.0", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/clsx": { "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "license": "MIT", "engines": { "node": ">=6" @@ -3056,6 +4776,8 @@ }, "node_modules/cmdk": { "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", @@ -3070,6 +4792,8 @@ }, "node_modules/color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3080,10 +4804,14 @@ }, "node_modules/color-name": { "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -3094,6 +4822,8 @@ }, "node_modules/comma-separated-tokens": { "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", "license": "MIT", "funding": { "type": "github", @@ -3102,6 +4832,8 @@ }, "node_modules/commander": { "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "license": "MIT", "engines": { "node": ">= 12" @@ -3109,22 +4841,34 @@ }, "node_modules/confbox": { "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", "license": "MIT" }, "node_modules/convert-source-map": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, "node_modules/cookie": { - "version": "1.0.2", + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-1.1.0.tgz", + "integrity": "sha512-vXiThu1/rlos7EGu8TuNZQEg2e9TvhH9dmS4T4ZVzB7Ao1agEZ6EG3sn5n+hZRYUgduISd1HpngFzAZiDGm5vQ==", "license": "MIT", "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/cose-base": { "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", "license": "MIT", "dependencies": { "layout-base": "^1.0.0" @@ -3132,6 +4876,8 @@ }, "node_modules/cosmiconfig": { "version": "8.3.6", + "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "dev": true, "license": "MIT", "dependencies": { @@ -3155,20 +4901,10 @@ } } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/cssesc": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -3178,11 +4914,15 @@ } }, "node_modules/csstype": { - "version": "3.1.3", + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/cytoscape": { "version": "3.33.1", + "resolved": "https://registry.npmmirror.com/cytoscape/-/cytoscape-3.33.1.tgz", + "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "license": "MIT", "engines": { "node": ">=0.10" @@ -3190,6 +4930,8 @@ }, "node_modules/cytoscape-cose-bilkent": { "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", "license": "MIT", "dependencies": { "cose-base": "^1.0.0" @@ -3200,6 +4942,8 @@ }, "node_modules/cytoscape-fcose": { "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", "license": "MIT", "dependencies": { "cose-base": "^2.2.0" @@ -3210,6 +4954,8 @@ }, "node_modules/cytoscape-fcose/node_modules/cose-base": { "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", "license": "MIT", "dependencies": { "layout-base": "^2.0.0" @@ -3217,10 +4963,14 @@ }, "node_modules/cytoscape-fcose/node_modules/layout-base": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", "license": "MIT" }, "node_modules/d3": { "version": "7.9.0", + "resolved": "https://registry.npmmirror.com/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", "license": "ISC", "dependencies": { "d3-array": "3", @@ -3260,6 +5010,8 @@ }, "node_modules/d3-array": { "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", "license": "ISC", "dependencies": { "internmap": "1 - 2" @@ -3270,6 +5022,8 @@ }, "node_modules/d3-axis": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", "license": "ISC", "engines": { "node": ">=12" @@ -3277,6 +5031,8 @@ }, "node_modules/d3-brush": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -3291,6 +5047,8 @@ }, "node_modules/d3-chord": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", "license": "ISC", "dependencies": { "d3-path": "1 - 3" @@ -3301,6 +5059,8 @@ }, "node_modules/d3-color": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", "license": "ISC", "engines": { "node": ">=12" @@ -3308,6 +5068,8 @@ }, "node_modules/d3-contour": { "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", "license": "ISC", "dependencies": { "d3-array": "^3.2.0" @@ -3318,6 +5080,8 @@ }, "node_modules/d3-delaunay": { "version": "6.0.4", + "resolved": "https://registry.npmmirror.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", "license": "ISC", "dependencies": { "delaunator": "5" @@ -3328,6 +5092,8 @@ }, "node_modules/d3-dispatch": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", "license": "ISC", "engines": { "node": ">=12" @@ -3335,6 +5101,8 @@ }, "node_modules/d3-drag": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -3346,6 +5114,8 @@ }, "node_modules/d3-dsv": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", "license": "ISC", "dependencies": { "commander": "7", @@ -3369,6 +5139,8 @@ }, "node_modules/d3-dsv/node_modules/commander": { "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", "engines": { "node": ">= 10" @@ -3376,6 +5148,8 @@ }, "node_modules/d3-ease": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", "license": "BSD-3-Clause", "engines": { "node": ">=12" @@ -3383,6 +5157,8 @@ }, "node_modules/d3-fetch": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", "license": "ISC", "dependencies": { "d3-dsv": "1 - 3" @@ -3393,6 +5169,8 @@ }, "node_modules/d3-force": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -3405,6 +5183,8 @@ }, "node_modules/d3-format": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", "license": "ISC", "engines": { "node": ">=12" @@ -3412,6 +5192,8 @@ }, "node_modules/d3-geo": { "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", "license": "ISC", "dependencies": { "d3-array": "2.5.0 - 3" @@ -3422,6 +5204,8 @@ }, "node_modules/d3-hierarchy": { "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", "license": "ISC", "engines": { "node": ">=12" @@ -3429,6 +5213,8 @@ }, "node_modules/d3-interpolate": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", "license": "ISC", "dependencies": { "d3-color": "1 - 3" @@ -3439,6 +5225,8 @@ }, "node_modules/d3-path": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", "license": "ISC", "engines": { "node": ">=12" @@ -3446,6 +5234,8 @@ }, "node_modules/d3-polygon": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", "license": "ISC", "engines": { "node": ">=12" @@ -3453,6 +5243,8 @@ }, "node_modules/d3-quadtree": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", "license": "ISC", "engines": { "node": ">=12" @@ -3460,6 +5252,8 @@ }, "node_modules/d3-random": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", "license": "ISC", "engines": { "node": ">=12" @@ -3467,6 +5261,8 @@ }, "node_modules/d3-sankey": { "version": "0.12.3", + "resolved": "https://registry.npmmirror.com/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", "license": "BSD-3-Clause", "dependencies": { "d3-array": "1 - 2", @@ -3475,6 +5271,8 @@ }, "node_modules/d3-sankey/node_modules/d3-array": { "version": "2.12.1", + "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", "license": "BSD-3-Clause", "dependencies": { "internmap": "^1.0.0" @@ -3482,10 +5280,14 @@ }, "node_modules/d3-sankey/node_modules/d3-path": { "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", "license": "BSD-3-Clause" }, "node_modules/d3-sankey/node_modules/d3-shape": { "version": "1.3.7", + "resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", "license": "BSD-3-Clause", "dependencies": { "d3-path": "1" @@ -3493,10 +5295,14 @@ }, "node_modules/d3-sankey/node_modules/internmap": { "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", "license": "ISC" }, "node_modules/d3-scale": { "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", "license": "ISC", "dependencies": { "d3-array": "2.10.0 - 3", @@ -3511,6 +5317,8 @@ }, "node_modules/d3-scale-chromatic": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", "license": "ISC", "dependencies": { "d3-color": "1 - 3", @@ -3522,6 +5330,8 @@ }, "node_modules/d3-selection": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", "engines": { "node": ">=12" @@ -3529,6 +5339,8 @@ }, "node_modules/d3-shape": { "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", "license": "ISC", "dependencies": { "d3-path": "^3.1.0" @@ -3539,6 +5351,8 @@ }, "node_modules/d3-time": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", "license": "ISC", "dependencies": { "d3-array": "2 - 3" @@ -3549,6 +5363,8 @@ }, "node_modules/d3-time-format": { "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", "license": "ISC", "dependencies": { "d3-time": "1 - 3" @@ -3559,6 +5375,8 @@ }, "node_modules/d3-timer": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", "license": "ISC", "engines": { "node": ">=12" @@ -3566,6 +5384,8 @@ }, "node_modules/d3-transition": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", "license": "ISC", "dependencies": { "d3-color": "1 - 3", @@ -3583,6 +5403,8 @@ }, "node_modules/d3-zoom": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -3596,7 +5418,9 @@ } }, "node_modules/dagre-d3-es": { - "version": "7.0.11", + "version": "7.0.13", + "resolved": "https://registry.npmmirror.com/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz", + "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==", "license": "MIT", "dependencies": { "d3": "^7.9.0", @@ -3605,6 +5429,8 @@ }, "node_modules/date-fns": { "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", "license": "MIT", "funding": { "type": "github", @@ -3612,11 +5438,15 @@ } }, "node_modules/dayjs": { - "version": "1.11.18", + "version": "1.11.19", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3632,6 +5462,8 @@ }, "node_modules/decamelize": { "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3639,10 +5471,14 @@ }, "node_modules/decimal.js-light": { "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", "license": "MIT" }, "node_modules/decode-named-character-reference": { "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", "license": "MIT", "dependencies": { "character-entities": "^2.0.0" @@ -3654,6 +5490,8 @@ }, "node_modules/delaunator": { "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", "license": "ISC", "dependencies": { "robust-predicates": "^3.0.2" @@ -3661,6 +5499,8 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", "engines": { "node": ">=0.4.0" @@ -3668,13 +5508,17 @@ }, "node_modules/dequal": { "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/detect-libc": { - "version": "2.1.1", + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3683,10 +5527,14 @@ }, "node_modules/detect-node-es": { "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, "node_modules/devlop": { "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", "license": "MIT", "dependencies": { "dequal": "^2.0.0" @@ -3698,18 +5546,26 @@ }, "node_modules/didyoumean": { "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "license": "Apache-2.0" }, "node_modules/dijkstrajs": { "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", "license": "MIT" }, "node_modules/dlv": { "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, "node_modules/dom-helpers": { "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.7", @@ -3718,6 +5574,8 @@ }, "node_modules/dompurify": { "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/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" @@ -3725,6 +5583,8 @@ }, "node_modules/dot-case": { "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "dev": true, "license": "MIT", "dependencies": { @@ -3734,6 +5594,8 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -3744,21 +5606,23 @@ "node": ">= 0.4" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "license": "MIT" - }, "node_modules/electron-to-chromium": { - "version": "1.5.238", + "version": "1.5.260", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.260.tgz", + "integrity": "sha512-ov8rBoOBhVawpzdre+Cmz4FB+y66Eqrk6Gwqd8NGxuhv99GQ8XqMAr351KEkOt7gukXWDg6gJWEMKgL2RLMPtA==", "dev": true, "license": "ISC" }, "node_modules/embla-carousel": { "version": "8.6.0", + "resolved": "https://registry.npmmirror.com/embla-carousel/-/embla-carousel-8.6.0.tgz", + "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", "license": "MIT" }, "node_modules/embla-carousel-react": { "version": "8.6.0", + "resolved": "https://registry.npmmirror.com/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz", + "integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==", "license": "MIT", "dependencies": { "embla-carousel": "8.6.0", @@ -3770,17 +5634,23 @@ }, "node_modules/embla-carousel-reactive-utils": { "version": "8.6.0", + "resolved": "https://registry.npmmirror.com/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz", + "integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==", "license": "MIT", "peerDependencies": { "embla-carousel": "8.6.0" } }, "node_modules/emoji-regex": { - "version": "9.2.2", + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, "node_modules/entities": { "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -3791,6 +5661,8 @@ }, "node_modules/error-ex": { "version": "1.3.4", + "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3799,6 +5671,8 @@ }, "node_modules/es-define-property": { "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -3806,6 +5680,8 @@ }, "node_modules/es-errors": { "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -3813,6 +5689,8 @@ }, "node_modules/es-object-atoms": { "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -3823,6 +5701,8 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3836,6 +5716,8 @@ }, "node_modules/esbuild": { "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -3872,6 +5754,8 @@ }, "node_modules/escalade": { "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", "engines": { @@ -3880,6 +5764,8 @@ }, "node_modules/escape-string-regexp": { "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "license": "MIT", "engines": { "node": ">=12" @@ -3890,6 +5776,8 @@ }, "node_modules/esprima": { "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", @@ -3901,6 +5789,8 @@ }, "node_modules/estree-util-is-identifier-name": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", "license": "MIT", "funding": { "type": "opencollective", @@ -3909,30 +5799,42 @@ }, "node_modules/estree-walker": { "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, "license": "MIT" }, "node_modules/eventemitter3": { "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, "node_modules/eventsource-parser": { "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", "license": "MIT", "engines": { "node": ">=18.0.0" } }, "node_modules/exsolve": { - "version": "1.0.7", + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", "license": "MIT" }, "node_modules/extend": { "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, "node_modules/fast-equals": { - "version": "5.3.2", + "version": "5.3.3", + "resolved": "https://registry.npmmirror.com/fast-equals/-/fast-equals-5.3.3.tgz", + "integrity": "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -3940,6 +5842,8 @@ }, "node_modules/fast-glob": { "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -3954,6 +5858,8 @@ }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -3964,6 +5870,8 @@ }, "node_modules/fastq": { "version": "1.19.1", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -3971,10 +5879,14 @@ }, "node_modules/fflate": { "version": "0.8.2", + "resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", "license": "MIT" }, "node_modules/fill-range": { "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -3985,6 +5897,8 @@ }, "node_modules/find-up": { "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -3996,6 +5910,8 @@ }, "node_modules/follow-redirects": { "version": "1.15.11", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -4012,22 +5928,10 @@ } } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "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", + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -4041,19 +5945,24 @@ } }, "node_modules/fraction.js": { - "version": "4.3.7", + "version": "5.3.4", + "resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "dev": true, "license": "MIT", "engines": { "node": "*" }, "funding": { - "type": "patreon", + "type": "github", "url": "https://github.com/sponsors/rawify" } }, "node_modules/fsevents": { "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -4065,6 +5974,8 @@ }, "node_modules/function-bind": { "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4072,6 +5983,8 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, "license": "MIT", "engines": { @@ -4080,13 +5993,29 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -4109,6 +6038,8 @@ }, "node_modules/get-nonce": { "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", "license": "MIT", "engines": { "node": ">=6" @@ -4116,6 +6047,8 @@ }, "node_modules/get-proto": { "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -4125,26 +6058,10 @@ "node": ">= 0.4" } }, - "node_modules/glob": { - "version": "10.4.5", - "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.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -4155,6 +6072,8 @@ }, "node_modules/globals": { "version": "15.15.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", "license": "MIT", "engines": { "node": ">=18" @@ -4165,6 +6084,8 @@ }, "node_modules/gopd": { "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -4175,10 +6096,14 @@ }, "node_modules/hachure-fill": { "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", "license": "MIT" }, "node_modules/has-symbols": { "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -4189,6 +6114,8 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -4202,6 +6129,8 @@ }, "node_modules/hasown": { "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -4210,8 +6139,17 @@ "node": ">= 0.4" } }, + "node_modules/hast": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/hast/-/hast-1.0.0.tgz", + "integrity": "sha512-vFUqlRV5C+xqP76Wwq2SrM0kipnmpxJm7OfvVXpB35Fp+Fn4MV+ozr+JZr5qFvyR1q/U+Foim2x+3P+x9S1PLA==", + "deprecated": "Renamed to rehype", + "license": "MIT" + }, "node_modules/hast-util-from-dom": { "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", + "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", "license": "ISC", "dependencies": { "@types/hast": "^3.0.0", @@ -4225,6 +6163,8 @@ }, "node_modules/hast-util-from-html": { "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -4241,6 +6181,8 @@ }, "node_modules/hast-util-from-html-isomorphic": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -4255,6 +6197,8 @@ }, "node_modules/hast-util-from-parse5": { "version": "8.0.3", + "resolved": "https://registry.npmmirror.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -4273,6 +6217,8 @@ }, "node_modules/hast-util-is-element": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" @@ -4284,6 +6230,8 @@ }, "node_modules/hast-util-parse-selector": { "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" @@ -4295,6 +6243,8 @@ }, "node_modules/hast-util-raw": { "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -4318,6 +6268,8 @@ }, "node_modules/hast-util-to-html": { "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -4339,6 +6291,8 @@ }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.6", + "resolved": "https://registry.npmmirror.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -4364,6 +6318,8 @@ }, "node_modules/hast-util-to-parse5": { "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -4381,6 +6337,8 @@ }, "node_modules/hast-util-to-parse5/node_modules/property-information": { "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", "license": "MIT", "funding": { "type": "github", @@ -4389,6 +6347,8 @@ }, "node_modules/hast-util-to-text": { "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -4403,6 +6363,8 @@ }, "node_modules/hast-util-whitespace": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" @@ -4414,6 +6376,8 @@ }, "node_modules/hastscript": { "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -4429,6 +6393,8 @@ }, "node_modules/html-url-attributes": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", "license": "MIT", "funding": { "type": "opencollective", @@ -4437,6 +6403,8 @@ }, "node_modules/html-void-elements": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", "license": "MIT", "funding": { "type": "github", @@ -4445,6 +6413,8 @@ }, "node_modules/iconv-lite": { "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -4455,6 +6425,8 @@ }, "node_modules/import-fresh": { "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4469,11 +6441,15 @@ } }, "node_modules/inline-style-parser": { - "version": "0.2.4", + "version": "0.2.7", + "resolved": "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", "license": "MIT" }, "node_modules/input-otp": { "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/input-otp/-/input-otp-1.4.2.tgz", + "integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==", "license": "MIT", "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", @@ -4482,6 +6458,8 @@ }, "node_modules/internmap": { "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", "license": "ISC", "engines": { "node": ">=12" @@ -4489,6 +6467,8 @@ }, "node_modules/invariant": { "version": "2.2.4", + "resolved": "https://registry.npmmirror.com/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "license": "MIT", "dependencies": { "loose-envify": "^1.0.0" @@ -4496,6 +6476,8 @@ }, "node_modules/is-alphabetical": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", "license": "MIT", "funding": { "type": "github", @@ -4504,6 +6486,8 @@ }, "node_modules/is-alphanumerical": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", "license": "MIT", "dependencies": { "is-alphabetical": "^2.0.0", @@ -4516,11 +6500,15 @@ }, "node_modules/is-arrayish": { "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, "license": "MIT" }, "node_modules/is-binary-path": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -4531,6 +6519,8 @@ }, "node_modules/is-core-module": { "version": "2.16.1", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -4544,6 +6534,8 @@ }, "node_modules/is-decimal": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", "license": "MIT", "funding": { "type": "github", @@ -4552,6 +6544,8 @@ }, "node_modules/is-extglob": { "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4559,6 +6553,8 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", "engines": { "node": ">=8" @@ -4566,6 +6562,8 @@ }, "node_modules/is-glob": { "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -4576,6 +6574,8 @@ }, "node_modules/is-hexadecimal": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", "license": "MIT", "funding": { "type": "github", @@ -4584,6 +6584,8 @@ }, "node_modules/is-number": { "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "license": "MIT", "engines": { "node": ">=0.12.0" @@ -4591,6 +6593,8 @@ }, "node_modules/is-plain-obj": { "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "license": "MIT", "engines": { "node": ">=12" @@ -4599,25 +6603,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/isexe": { - "version": "2.0.0", - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "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": "1.21.7", + "resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -4625,10 +6614,14 @@ }, "node_modules/js-tokens": { "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -4640,6 +6633,8 @@ }, "node_modules/jsesc": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -4650,11 +6645,15 @@ }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true, "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", "bin": { @@ -4666,6 +6665,8 @@ }, "node_modules/katex": { "version": "0.16.25", + "resolved": "https://registry.npmmirror.com/katex/-/katex-0.16.25.tgz", + "integrity": "sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" @@ -4679,14 +6680,20 @@ } }, "node_modules/khroma": { - "version": "2.1.0" + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" }, "node_modules/kolorist": { "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", "license": "MIT" }, "node_modules/ky": { - "version": "1.12.0", + "version": "1.14.0", + "resolved": "https://registry.npmmirror.com/ky/-/ky-1.14.0.tgz", + "integrity": "sha512-Rczb6FMM6JT0lvrOlP5WUOCB7s9XKxzwgErzhKlKde1bEV90FXplV1o87fpt4PU/asJFiqjYJxAJyzJhcrxOsQ==", "license": "MIT", "engines": { "node": ">=18" @@ -4697,6 +6704,8 @@ }, "node_modules/langium": { "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/langium/-/langium-3.3.1.tgz", + "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", "license": "MIT", "dependencies": { "chevrotain": "~11.0.3", @@ -4711,10 +6720,14 @@ }, "node_modules/layout-base": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", "license": "MIT" }, "node_modules/lilconfig": { "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "license": "MIT", "engines": { "node": ">=14" @@ -4725,10 +6738,14 @@ }, "node_modules/lines-and-columns": { "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, "node_modules/local-pkg": { "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", "license": "MIT", "dependencies": { "mlly": "^1.7.4", @@ -4744,6 +6761,8 @@ }, "node_modules/locate-path": { "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -4754,18 +6773,26 @@ }, "node_modules/lodash": { "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, "node_modules/lodash-es": { "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", "license": "MIT" }, "node_modules/lodash.throttle": { "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", "license": "MIT" }, "node_modules/longest-streak": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", "license": "MIT", "funding": { "type": "github", @@ -4774,6 +6801,8 @@ }, "node_modules/loose-envify": { "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -4784,6 +6813,8 @@ }, "node_modules/lower-case": { "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", "dev": true, "license": "MIT", "dependencies": { @@ -4792,6 +6823,8 @@ }, "node_modules/lru-cache": { "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", "dependencies": { @@ -4800,13 +6833,17 @@ }, "node_modules/lucide-react": { "version": "0.525.0", + "resolved": "https://registry.npmmirror.com/lucide-react/-/lucide-react-0.525.0.tgz", + "integrity": "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/magic-string": { - "version": "0.30.19", + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -4814,6 +6851,8 @@ }, "node_modules/markdown-table": { "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", "license": "MIT", "funding": { "type": "github", @@ -4821,7 +6860,9 @@ } }, "node_modules/marked": { - "version": "16.4.1", + "version": "16.4.2", + "resolved": "https://registry.npmmirror.com/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", "license": "MIT", "bin": { "marked": "bin/marked.js" @@ -4832,6 +6873,8 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -4839,6 +6882,8 @@ }, "node_modules/mdast-util-find-and-replace": { "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -4853,6 +6898,8 @@ }, "node_modules/mdast-util-from-markdown": { "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -4875,6 +6922,8 @@ }, "node_modules/mdast-util-gfm": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", "license": "MIT", "dependencies": { "mdast-util-from-markdown": "^2.0.0", @@ -4892,6 +6941,8 @@ }, "node_modules/mdast-util-gfm-autolink-literal": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -4907,6 +6958,8 @@ }, "node_modules/mdast-util-gfm-footnote": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -4922,6 +6975,8 @@ }, "node_modules/mdast-util-gfm-strikethrough": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -4935,6 +6990,8 @@ }, "node_modules/mdast-util-gfm-table": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -4950,6 +7007,8 @@ }, "node_modules/mdast-util-gfm-task-list-item": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -4964,6 +7023,8 @@ }, "node_modules/mdast-util-math": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -4981,6 +7042,8 @@ }, "node_modules/mdast-util-mdx-expression": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -4997,6 +7060,8 @@ }, "node_modules/mdast-util-mdx-jsx": { "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -5019,6 +7084,8 @@ }, "node_modules/mdast-util-mdxjs-esm": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -5035,6 +7102,8 @@ }, "node_modules/mdast-util-phrasing": { "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -5046,7 +7115,9 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.2.0", + "version": "13.2.1", + "resolved": "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -5066,6 +7137,8 @@ }, "node_modules/mdast-util-to-markdown": { "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -5085,6 +7158,8 @@ }, "node_modules/mdast-util-to-string": { "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0" @@ -5096,25 +7171,29 @@ }, "node_modules/merge2": { "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/mermaid": { - "version": "11.12.0", + "version": "11.12.1", + "resolved": "https://registry.npmmirror.com/mermaid/-/mermaid-11.12.1.tgz", + "integrity": "sha512-UlIZrRariB11TY1RtTgUWp65tphtBv4CSq7vyS2ZZ2TgoMjs2nloq+wFqxiwcxlhHUvs7DPGgMjs2aeQxz5h9g==", "license": "MIT", "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.1", - "@mermaid-js/parser": "^0.6.2", + "@mermaid-js/parser": "^0.6.3", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", - "dagre-d3-es": "7.0.11", + "dagre-d3-es": "7.0.13", "dayjs": "^1.11.18", "dompurify": "^3.2.5", "katex": "^0.16.22", @@ -5128,7 +7207,9 @@ } }, "node_modules/miaoda-auth-react": { - "version": "2.0.5", + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/miaoda-auth-react/-/miaoda-auth-react-2.0.6.tgz", + "integrity": "sha512-Cv/uraKbJuG3aIzsTZ1zy+iPAM2M9loqFlzmaHa7q7SJ9Q3lLlWtTV+dcxrrLHVymEzpHtAQ/XmDI3OTC6CD2g==", "license": "ISC", "peerDependencies": { "@supabase/supabase-js": "*", @@ -5138,7 +7219,9 @@ } }, "node_modules/miaoda-sc-plugin": { - "version": "1.0.28", + "version": "1.0.31", + "resolved": "https://registry.npmmirror.com/miaoda-sc-plugin/-/miaoda-sc-plugin-1.0.31.tgz", + "integrity": "sha512-jmuKTquYfOWX8H+OqV849POmpwFtG8JL7vH9ZOLpxS3FC+zyZj2pmeDAkhWkGwCO2LsU100x0CvZ6L5zX+yZug==", "dependencies": { "@babel/parser": "^7.28.0", "@babel/traverse": "^7.28.0", @@ -5151,6 +7234,8 @@ }, "node_modules/micromark": { "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", "funding": [ { "type": "GitHub Sponsors", @@ -5184,6 +7269,8 @@ }, "node_modules/micromark-core-commonmark": { "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", "funding": [ { "type": "GitHub Sponsors", @@ -5214,8 +7301,81 @@ "micromark-util-types": "^2.0.0" } }, + "node_modules/micromark-extension-cjk-friendly": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/micromark-extension-cjk-friendly/-/micromark-extension-cjk-friendly-1.2.3.tgz", + "integrity": "sha512-gRzVLUdjXBLX6zNPSnHGDoo+ZTp5zy+MZm0g3sv+3chPXY7l9gW+DnrcHcZh/jiPR6MjPKO4AEJNp4Aw6V9z5Q==", + "license": "MIT", + "dependencies": { + "devlop": "^1.1.0", + "micromark-extension-cjk-friendly-util": "2.1.1", + "micromark-util-chunked": "^2.0.1", + "micromark-util-resolve-all": "^2.0.1", + "micromark-util-symbol": "^2.0.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "micromark": "^4.0.0", + "micromark-util-types": "^2.0.0" + }, + "peerDependenciesMeta": { + "micromark-util-types": { + "optional": true + } + } + }, + "node_modules/micromark-extension-cjk-friendly-gfm-strikethrough": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/micromark-extension-cjk-friendly-gfm-strikethrough/-/micromark-extension-cjk-friendly-gfm-strikethrough-1.2.3.tgz", + "integrity": "sha512-gSPnxgHDDqXYOBvQRq6lerrq9mjDhdtKn+7XETuXjxWcL62yZEfUdA28Ml1I2vDIPfAOIKLa0h2XDSGkInGHFQ==", + "license": "MIT", + "dependencies": { + "devlop": "^1.1.0", + "get-east-asian-width": "^1.3.0", + "micromark-extension-cjk-friendly-util": "2.1.1", + "micromark-util-character": "^2.1.1", + "micromark-util-chunked": "^2.0.1", + "micromark-util-resolve-all": "^2.0.1", + "micromark-util-symbol": "^2.0.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "micromark": "^4.0.0", + "micromark-util-types": "^2.0.0" + }, + "peerDependenciesMeta": { + "micromark-util-types": { + "optional": true + } + } + }, + "node_modules/micromark-extension-cjk-friendly-util": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/micromark-extension-cjk-friendly-util/-/micromark-extension-cjk-friendly-util-2.1.1.tgz", + "integrity": "sha512-egs6+12JU2yutskHY55FyR48ZiEcFOJFyk9rsiyIhcJ6IvWB6ABBqVrBw8IobqJTDZ/wdSr9eoXDPb5S2nW1bg==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "micromark-util-character": "^2.1.1", + "micromark-util-symbol": "^2.0.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependenciesMeta": { + "micromark-util-types": { + "optional": true + } + } + }, "node_modules/micromark-extension-gfm": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", "license": "MIT", "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", @@ -5234,6 +7394,8 @@ }, "node_modules/micromark-extension-gfm-autolink-literal": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", @@ -5248,6 +7410,8 @@ }, "node_modules/micromark-extension-gfm-footnote": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -5266,6 +7430,8 @@ }, "node_modules/micromark-extension-gfm-strikethrough": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -5282,6 +7448,8 @@ }, "node_modules/micromark-extension-gfm-table": { "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -5297,6 +7465,8 @@ }, "node_modules/micromark-extension-gfm-tagfilter": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", "license": "MIT", "dependencies": { "micromark-util-types": "^2.0.0" @@ -5308,6 +7478,8 @@ }, "node_modules/micromark-extension-gfm-task-list-item": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -5323,6 +7495,8 @@ }, "node_modules/micromark-extension-math": { "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", "license": "MIT", "dependencies": { "@types/katex": "^0.16.0", @@ -5340,6 +7514,8 @@ }, "node_modules/micromark-factory-destination": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", "funding": [ { "type": "GitHub Sponsors", @@ -5359,6 +7535,8 @@ }, "node_modules/micromark-factory-label": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", "funding": [ { "type": "GitHub Sponsors", @@ -5379,6 +7557,8 @@ }, "node_modules/micromark-factory-space": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -5397,6 +7577,8 @@ }, "node_modules/micromark-factory-title": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", "funding": [ { "type": "GitHub Sponsors", @@ -5417,6 +7599,8 @@ }, "node_modules/micromark-factory-whitespace": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", "funding": [ { "type": "GitHub Sponsors", @@ -5437,6 +7621,8 @@ }, "node_modules/micromark-util-character": { "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -5455,6 +7641,8 @@ }, "node_modules/micromark-util-chunked": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", "funding": [ { "type": "GitHub Sponsors", @@ -5472,6 +7660,8 @@ }, "node_modules/micromark-util-classify-character": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", "funding": [ { "type": "GitHub Sponsors", @@ -5491,6 +7681,8 @@ }, "node_modules/micromark-util-combine-extensions": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", "funding": [ { "type": "GitHub Sponsors", @@ -5509,6 +7701,8 @@ }, "node_modules/micromark-util-decode-numeric-character-reference": { "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", "funding": [ { "type": "GitHub Sponsors", @@ -5526,6 +7720,8 @@ }, "node_modules/micromark-util-decode-string": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", "funding": [ { "type": "GitHub Sponsors", @@ -5546,6 +7742,8 @@ }, "node_modules/micromark-util-encode": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", "funding": [ { "type": "GitHub Sponsors", @@ -5560,6 +7758,8 @@ }, "node_modules/micromark-util-html-tag-name": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", "funding": [ { "type": "GitHub Sponsors", @@ -5574,6 +7774,8 @@ }, "node_modules/micromark-util-normalize-identifier": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", "funding": [ { "type": "GitHub Sponsors", @@ -5591,6 +7793,8 @@ }, "node_modules/micromark-util-resolve-all": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", "funding": [ { "type": "GitHub Sponsors", @@ -5608,6 +7812,8 @@ }, "node_modules/micromark-util-sanitize-uri": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", "funding": [ { "type": "GitHub Sponsors", @@ -5627,6 +7833,8 @@ }, "node_modules/micromark-util-subtokenize": { "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", "funding": [ { "type": "GitHub Sponsors", @@ -5647,6 +7855,8 @@ }, "node_modules/micromark-util-symbol": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -5661,6 +7871,8 @@ }, "node_modules/micromark-util-types": { "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", "funding": [ { "type": "GitHub Sponsors", @@ -5675,6 +7887,8 @@ }, "node_modules/micromatch": { "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -5686,6 +7900,8 @@ }, "node_modules/mime-db": { "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -5693,6 +7909,8 @@ }, "node_modules/mime-types": { "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -5701,28 +7919,10 @@ "node": ">= 0.6" } }, - "node_modules/minimatch": { - "version": "9.0.5", - "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", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/mlly": { "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", "license": "MIT", "dependencies": { "acorn": "^8.15.0", @@ -5733,10 +7933,14 @@ }, "node_modules/mlly/node_modules/confbox": { "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", "license": "MIT" }, "node_modules/mlly/node_modules/pkg-types": { "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", "license": "MIT", "dependencies": { "confbox": "^0.1.8", @@ -5746,10 +7950,14 @@ }, "node_modules/ms": { "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/mz": { "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "license": "MIT", "dependencies": { "any-promise": "^1.0.0", @@ -5759,6 +7967,8 @@ }, "node_modules/nanoid": { "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", @@ -5775,6 +7985,8 @@ }, "node_modules/next-themes": { "version": "0.4.6", + "resolved": "https://registry.npmmirror.com/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", "license": "MIT", "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", @@ -5783,6 +7995,8 @@ }, "node_modules/no-case": { "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", "dev": true, "license": "MIT", "dependencies": { @@ -5791,12 +8005,16 @@ } }, "node_modules/node-releases": { - "version": "2.0.26", + "version": "2.0.27", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5804,6 +8022,8 @@ }, "node_modules/normalize-range": { "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", "dev": true, "license": "MIT", "engines": { @@ -5812,6 +8032,8 @@ }, "node_modules/object-assign": { "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5819,6 +8041,8 @@ }, "node_modules/object-hash": { "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "license": "MIT", "engines": { "node": ">= 6" @@ -5826,10 +8050,14 @@ }, "node_modules/oniguruma-parser": { "version": "0.12.1", + "resolved": "https://registry.npmmirror.com/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", "license": "MIT" }, "node_modules/oniguruma-to-es": { - "version": "4.3.3", + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", "license": "MIT", "dependencies": { "oniguruma-parser": "^0.12.1", @@ -5839,6 +8067,8 @@ }, "node_modules/p-limit": { "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -5852,6 +8082,8 @@ }, "node_modules/p-locate": { "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "license": "MIT", "dependencies": { "p-limit": "^2.2.0" @@ -5862,21 +8094,23 @@ }, "node_modules/p-try": { "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "license": "BlueOak-1.0.0" - }, "node_modules/package-manager-detector": { "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/package-manager-detector/-/package-manager-detector-1.5.0.tgz", + "integrity": "sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==", "license": "MIT" }, "node_modules/parent-module": { "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", "dependencies": { @@ -5888,6 +8122,8 @@ }, "node_modules/parse-entities": { "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", "license": "MIT", "dependencies": { "@types/unist": "^2.0.0", @@ -5905,10 +8141,14 @@ }, "node_modules/parse-entities/node_modules/@types/unist": { "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, "node_modules/parse-json": { "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -5926,6 +8166,8 @@ }, "node_modules/parse5": { "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "license": "MIT", "dependencies": { "entities": "^6.0.0" @@ -5936,17 +8178,14 @@ }, "node_modules/path-data-parser": { "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", "license": "MIT" }, "node_modules/path-exists": { "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "license": "MIT", "engines": { "node": ">=8" @@ -5954,28 +8193,14 @@ }, "node_modules/path-parse": { "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, - "node_modules/path-scurry": { - "version": "1.11.1", - "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/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "license": "ISC" - }, "node_modules/path-type": { "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, "license": "MIT", "engines": { @@ -5984,14 +8209,20 @@ }, "node_modules/pathe": { "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -6002,6 +8233,8 @@ }, "node_modules/pify": { "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6009,6 +8242,8 @@ }, "node_modules/pirates": { "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "license": "MIT", "engines": { "node": ">= 6" @@ -6016,6 +8251,8 @@ }, "node_modules/pkg-types": { "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", "license": "MIT", "dependencies": { "confbox": "^0.2.2", @@ -6025,6 +8262,8 @@ }, "node_modules/pngjs": { "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", "license": "MIT", "engines": { "node": ">=10.13.0" @@ -6032,10 +8271,14 @@ }, "node_modules/points-on-curve": { "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", "license": "MIT" }, "node_modules/points-on-path": { "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", "license": "MIT", "dependencies": { "path-data-parser": "0.1.0", @@ -6044,6 +8287,8 @@ }, "node_modules/postcss": { "version": "8.5.6", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -6070,6 +8315,8 @@ }, "node_modules/postcss-import": { "version": "15.1.0", + "resolved": "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", @@ -6085,6 +8332,8 @@ }, "node_modules/postcss-js": { "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", "funding": [ { "type": "opencollective", @@ -6108,6 +8357,8 @@ }, "node_modules/postcss-load-config": { "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", "funding": [ { "type": "opencollective", @@ -6148,6 +8399,8 @@ }, "node_modules/postcss-nested": { "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", "funding": [ { "type": "opencollective", @@ -6171,6 +8424,8 @@ }, "node_modules/postcss-selector-parser": { "version": "6.1.2", + "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -6182,10 +8437,14 @@ }, "node_modules/postcss-value-parser": { "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, "node_modules/prop-types": { "version": "15.8.1", + "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -6195,10 +8454,14 @@ }, "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, "node_modules/property-information": { "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", "license": "MIT", "funding": { "type": "github", @@ -6207,10 +8470,14 @@ }, "node_modules/proxy-from-env": { "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, "node_modules/qrcode": { "version": "1.5.4", + "resolved": "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", "license": "MIT", "dependencies": { "dijkstrajs": "^1.0.1", @@ -6226,6 +8493,8 @@ }, "node_modules/quansync": { "version": "0.2.11", + "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", "funding": [ { "type": "individual", @@ -6240,6 +8509,8 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "funding": [ { "type": "github", @@ -6258,6 +8529,8 @@ }, "node_modules/react": { "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -6268,6 +8541,8 @@ }, "node_modules/react-day-picker": { "version": "8.10.1", + "resolved": "https://registry.npmmirror.com/react-day-picker/-/react-day-picker-8.10.1.tgz", + "integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==", "license": "MIT", "funding": { "type": "individual", @@ -6280,6 +8555,8 @@ }, "node_modules/react-dom": { "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", @@ -6291,10 +8568,14 @@ }, "node_modules/react-fast-compare": { "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", "license": "MIT" }, "node_modules/react-helmet-async": { "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/react-helmet-async/-/react-helmet-async-2.0.5.tgz", + "integrity": "sha512-rYUYHeus+i27MvFE+Jaa4WsyBKGkL6qVgbJvSBoX8mbsWoABJXdEO0bZyi0F6i+4f0NuIb8AvqPMj3iXFHkMwg==", "license": "Apache-2.0", "dependencies": { "invariant": "^2.2.4", @@ -6306,7 +8587,9 @@ } }, "node_modules/react-hook-form": { - "version": "7.65.0", + "version": "7.66.1", + "resolved": "https://registry.npmmirror.com/react-hook-form/-/react-hook-form-7.66.1.tgz", + "integrity": "sha512-2KnjpgG2Rhbi+CIiIBQQ9Df6sMGH5ExNyFl4Hw9qO7pIqMBR8Bvu9RQyjl3JM4vehzCh9soiNUM/xYMswb2EiA==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -6321,35 +8604,14 @@ }, "node_modules/react-is": { "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, - "node_modules/react-markdown": { - "version": "10.1.0", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "hast-util-to-jsx-runtime": "^2.0.0", - "html-url-attributes": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.0.0", - "unified": "^11.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "@types/react": ">=18", - "react": ">=18" - } - }, "node_modules/react-refresh": { "version": "0.17.0", + "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", "dev": true, "license": "MIT", "engines": { @@ -6358,6 +8620,8 @@ }, "node_modules/react-remove-scroll": { "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", + "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", "license": "MIT", "dependencies": { "react-remove-scroll-bar": "^2.3.7", @@ -6381,6 +8645,8 @@ }, "node_modules/react-remove-scroll-bar": { "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", "license": "MIT", "dependencies": { "react-style-singleton": "^2.2.2", @@ -6401,6 +8667,8 @@ }, "node_modules/react-resizable-panels": { "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/react-resizable-panels/-/react-resizable-panels-2.1.9.tgz", + "integrity": "sha512-z77+X08YDIrgAes4jl8xhnUu1LNIRp4+E7cv4xHmLOxxUPO/ML7PSrE813b90vj7xvQ1lcf7g2uA9GeMZonjhQ==", "license": "MIT", "peerDependencies": { "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", @@ -6408,7 +8676,9 @@ } }, "node_modules/react-router": { - "version": "7.9.4", + "version": "7.9.6", + "resolved": "https://registry.npmmirror.com/react-router/-/react-router-7.9.6.tgz", + "integrity": "sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -6428,11 +8698,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.30.1", + "version": "6.30.2", + "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.30.2.tgz", + "integrity": "sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.23.0", - "react-router": "6.30.1" + "@remix-run/router": "1.23.1", + "react-router": "6.30.2" }, "engines": { "node": ">=14.0.0" @@ -6443,10 +8715,12 @@ } }, "node_modules/react-router-dom/node_modules/react-router": { - "version": "6.30.1", + "version": "6.30.2", + "resolved": "https://registry.npmmirror.com/react-router/-/react-router-6.30.2.tgz", + "integrity": "sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.23.0" + "@remix-run/router": "1.23.1" }, "engines": { "node": ">=14.0.0" @@ -6457,6 +8731,8 @@ }, "node_modules/react-smooth": { "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", "license": "MIT", "dependencies": { "fast-equals": "^5.0.1", @@ -6470,6 +8746,8 @@ }, "node_modules/react-style-singleton": { "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", "license": "MIT", "dependencies": { "get-nonce": "^1.0.0", @@ -6490,6 +8768,8 @@ }, "node_modules/react-transition-group": { "version": "4.4.5", + "resolved": "https://registry.npmmirror.com/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", "license": "BSD-3-Clause", "dependencies": { "@babel/runtime": "^7.5.5", @@ -6504,6 +8784,8 @@ }, "node_modules/read-cache": { "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", "license": "MIT", "dependencies": { "pify": "^2.3.0" @@ -6511,6 +8793,8 @@ }, "node_modules/readdirp": { "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -6521,6 +8805,8 @@ }, "node_modules/recast": { "version": "0.23.11", + "resolved": "https://registry.npmmirror.com/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", "license": "MIT", "dependencies": { "ast-types": "^0.16.1", @@ -6535,6 +8821,8 @@ }, "node_modules/recharts": { "version": "2.15.4", + "resolved": "https://registry.npmmirror.com/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", "license": "MIT", "dependencies": { "clsx": "^2.0.0", @@ -6556,6 +8844,8 @@ }, "node_modules/recharts-scale": { "version": "0.4.5", + "resolved": "https://registry.npmmirror.com/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", "license": "MIT", "dependencies": { "decimal.js-light": "^2.4.1" @@ -6563,6 +8853,8 @@ }, "node_modules/redux": { "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.9.2" @@ -6570,6 +8862,8 @@ }, "node_modules/regex": { "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/regex/-/regex-6.0.1.tgz", + "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", "license": "MIT", "dependencies": { "regex-utilities": "^2.3.0" @@ -6577,6 +8871,8 @@ }, "node_modules/regex-recursion": { "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", "license": "MIT", "dependencies": { "regex-utilities": "^2.3.0" @@ -6584,14 +8880,20 @@ }, "node_modules/regex-utilities": { "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", "license": "MIT" }, "node_modules/rehype-harden": { "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/rehype-harden/-/rehype-harden-1.1.5.tgz", + "integrity": "sha512-JrtBj5BVd/5vf3H3/blyJatXJbzQfRT9pJBmjafbTaPouQCAKxHwRyCc7dle9BXQKxv4z1OzZylz/tNamoiG3A==", "license": "MIT" }, "node_modules/rehype-katex": { "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/rehype-katex/-/rehype-katex-7.0.1.tgz", + "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -6609,6 +8911,8 @@ }, "node_modules/rehype-raw": { "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -6620,8 +8924,52 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-cjk-friendly": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/remark-cjk-friendly/-/remark-cjk-friendly-1.2.3.tgz", + "integrity": "sha512-UvAgxwlNk+l9Oqgl/9MWK2eWRS7zgBW/nXX9AthV7nd/3lNejF138E7Xbmk9Zs4WjTJGs721r7fAEc7tNFoH7g==", + "license": "MIT", + "dependencies": { + "micromark-extension-cjk-friendly": "1.2.3" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@types/mdast": "^4.0.0", + "unified": "^11.0.0" + }, + "peerDependenciesMeta": { + "@types/mdast": { + "optional": true + } + } + }, + "node_modules/remark-cjk-friendly-gfm-strikethrough": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/remark-cjk-friendly-gfm-strikethrough/-/remark-cjk-friendly-gfm-strikethrough-1.2.3.tgz", + "integrity": "sha512-bXfMZtsaomK6ysNN/UGRIcasQAYkC10NtPmP0oOHOV8YOhA2TXmwRXCku4qOzjIFxAPfish5+XS0eIug2PzNZA==", + "license": "MIT", + "dependencies": { + "micromark-extension-cjk-friendly-gfm-strikethrough": "1.2.3" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@types/mdast": "^4.0.0", + "unified": "^11.0.0" + }, + "peerDependenciesMeta": { + "@types/mdast": { + "optional": true + } + } + }, "node_modules/remark-gfm": { "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -6638,6 +8986,8 @@ }, "node_modules/remark-math": { "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/remark-math/-/remark-math-6.0.0.tgz", + "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -6652,6 +9002,8 @@ }, "node_modules/remark-parse": { "version": "11.0.0", + "resolved": "https://registry.npmmirror.com/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -6666,6 +9018,8 @@ }, "node_modules/remark-rehype": { "version": "11.1.2", + "resolved": "https://registry.npmmirror.com/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -6681,6 +9035,8 @@ }, "node_modules/remark-stringify": { "version": "11.0.0", + "resolved": "https://registry.npmmirror.com/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -6694,6 +9050,8 @@ }, "node_modules/require-directory": { "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6701,10 +9059,14 @@ }, "node_modules/require-main-filename": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "license": "ISC" }, "node_modules/resolve": { "version": "1.22.11", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", @@ -6723,6 +9085,8 @@ }, "node_modules/resolve-from": { "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", "engines": { @@ -6731,6 +9095,8 @@ }, "node_modules/reusify": { "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -6739,10 +9105,14 @@ }, "node_modules/robust-predicates": { "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", "license": "Unlicense" }, "node_modules/rollup": { - "version": "4.52.5", + "version": "4.53.3", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -6755,33 +9125,35 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.5", - "@rollup/rollup-android-arm64": "4.52.5", - "@rollup/rollup-darwin-arm64": "4.52.5", - "@rollup/rollup-darwin-x64": "4.52.5", - "@rollup/rollup-freebsd-arm64": "4.52.5", - "@rollup/rollup-freebsd-x64": "4.52.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", - "@rollup/rollup-linux-arm-musleabihf": "4.52.5", - "@rollup/rollup-linux-arm64-gnu": "4.52.5", - "@rollup/rollup-linux-arm64-musl": "4.52.5", - "@rollup/rollup-linux-loong64-gnu": "4.52.5", - "@rollup/rollup-linux-ppc64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-musl": "4.52.5", - "@rollup/rollup-linux-s390x-gnu": "4.52.5", - "@rollup/rollup-linux-x64-gnu": "4.52.5", - "@rollup/rollup-linux-x64-musl": "4.52.5", - "@rollup/rollup-openharmony-arm64": "4.52.5", - "@rollup/rollup-win32-arm64-msvc": "4.52.5", - "@rollup/rollup-win32-ia32-msvc": "4.52.5", - "@rollup/rollup-win32-x64-gnu": "4.52.5", - "@rollup/rollup-win32-x64-msvc": "4.52.5", + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" } }, "node_modules/roughjs": { "version": "4.6.6", + "resolved": "https://registry.npmmirror.com/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", "license": "MIT", "dependencies": { "hachure-fill": "^0.5.2", @@ -6792,6 +9164,8 @@ }, "node_modules/run-parallel": { "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "funding": [ { "type": "github", @@ -6813,14 +9187,20 @@ }, "node_modules/rw": { "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", "license": "BSD-3-Clause" }, "node_modules/safer-buffer": { "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, "node_modules/scheduler": { "version": "0.23.2", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -6828,6 +9208,8 @@ }, "node_modules/semver": { "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -6836,59 +9218,42 @@ }, "node_modules/set-blocking": { "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "license": "ISC" }, "node_modules/set-cookie-parser": { - "version": "2.7.1", + "version": "2.7.2", + "resolved": "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, "node_modules/shallowequal": { "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", "license": "MIT" }, - "node_modules/shebang-command": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/shiki": { - "version": "3.13.0", + "version": "3.15.0", + "resolved": "https://registry.npmmirror.com/shiki/-/shiki-3.15.0.tgz", + "integrity": "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw==", "license": "MIT", "dependencies": { - "@shikijs/core": "3.13.0", - "@shikijs/engine-javascript": "3.13.0", - "@shikijs/engine-oniguruma": "3.13.0", - "@shikijs/langs": "3.13.0", - "@shikijs/themes": "3.13.0", - "@shikijs/types": "3.13.0", + "@shikijs/core": "3.15.0", + "@shikijs/engine-javascript": "3.15.0", + "@shikijs/engine-oniguruma": "3.15.0", + "@shikijs/langs": "3.15.0", + "@shikijs/themes": "3.15.0", + "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, - "node_modules/signal-exit": { - "version": "4.1.0", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/snake-case": { "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", "dev": true, "license": "MIT", "dependencies": { @@ -6898,6 +9263,8 @@ }, "node_modules/sonner": { "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", "license": "MIT", "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", @@ -6906,6 +9273,8 @@ }, "node_modules/source-map": { "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -6913,6 +9282,8 @@ }, "node_modules/source-map-js": { "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -6920,6 +9291,8 @@ }, "node_modules/source-map-support": { "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "devOptional": true, "license": "MIT", "dependencies": { @@ -6929,6 +9302,8 @@ }, "node_modules/space-separated-tokens": { "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", "license": "MIT", "funding": { "type": "github", @@ -6936,22 +9311,31 @@ } }, "node_modules/streamdown": { - "version": "1.4.0", + "version": "1.6.8", + "resolved": "https://registry.npmmirror.com/streamdown/-/streamdown-1.6.8.tgz", + "integrity": "sha512-SmVS8MRLfEQIYWx1EWmQQ6lCxiY7n9Hlg/EDXl17ZYcbCdTd8caMVngBNlIHxwQPvQDyXozrEzcgkhzYyMmN/w==", "license": "Apache-2.0", "dependencies": { "clsx": "^2.1.1", + "hast": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.3.6", + "html-url-attributes": "^3.0.1", "katex": "^0.16.22", "lucide-react": "^0.542.0", "marked": "^16.2.1", "mermaid": "^11.11.0", - "react-markdown": "^10.1.0", "rehype-harden": "^1.1.5", "rehype-katex": "^7.0.1", "rehype-raw": "^7.0.0", + "remark-cjk-friendly": "^1.2.3", + "remark-cjk-friendly-gfm-strikethrough": "^1.2.3", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", "shiki": "^3.12.2", - "tailwind-merge": "^3.3.1" + "tailwind-merge": "^3.3.1", + "unist-util-visit": "^5.0.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" @@ -6959,29 +9343,17 @@ }, "node_modules/streamdown/node_modules/lucide-react": { "version": "0.542.0", + "resolved": "https://registry.npmmirror.com/lucide-react/-/lucide-react-0.542.0.tgz", + "integrity": "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/string-width": { - "version": "5.1.2", - "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.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -6992,29 +9364,10 @@ "node": ">=8" } }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/stringify-entities": { "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "license": "MIT", "dependencies": { "character-entities-html4": "^2.0.0", @@ -7026,21 +9379,9 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.2", - "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.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -7049,41 +9390,42 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/style-to-js": { - "version": "1.1.18", + "version": "1.1.21", + "resolved": "https://registry.npmmirror.com/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", "license": "MIT", "dependencies": { - "style-to-object": "1.0.11" + "style-to-object": "1.0.14" } }, "node_modules/style-to-object": { - "version": "1.0.11", + "version": "1.0.14", + "resolved": "https://registry.npmmirror.com/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", "license": "MIT", "dependencies": { - "inline-style-parser": "0.2.4" + "inline-style-parser": "0.2.7" } }, "node_modules/stylis": { "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", "license": "MIT" }, "node_modules/sucrase": { - "version": "3.35.0", + "version": "3.35.1", + "resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", "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", + "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { @@ -7096,6 +9438,8 @@ }, "node_modules/sucrase/node_modules/commander": { "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "license": "MIT", "engines": { "node": ">= 6" @@ -7103,6 +9447,8 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -7113,11 +9459,15 @@ }, "node_modules/svg-parser": { "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", "dev": true, "license": "MIT" }, "node_modules/tailwind-merge": { - "version": "3.3.1", + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", "license": "MIT", "funding": { "type": "github", @@ -7126,6 +9476,8 @@ }, "node_modules/tailwindcss": { "version": "3.4.18", + "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.18.tgz", + "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -7161,13 +9513,17 @@ }, "node_modules/tailwindcss-animate": { "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", "license": "MIT", "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "node_modules/terser": { - "version": "5.44.0", + "version": "5.44.1", + "resolved": "https://registry.npmmirror.com/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", "devOptional": true, "license": "BSD-2-Clause", "dependencies": { @@ -7185,11 +9541,15 @@ }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "devOptional": true, "license": "MIT" }, "node_modules/thenify": { "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "license": "MIT", "dependencies": { "any-promise": "^1.0.0" @@ -7197,6 +9557,8 @@ }, "node_modules/thenify-all": { "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -7207,14 +9569,68 @@ }, "node_modules/tiny-invariant": { "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.1", - "license": "MIT" + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "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/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/to-regex-range": { "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -7223,12 +9639,10 @@ "node": ">=8.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "license": "MIT" - }, "node_modules/trim-lines": { "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", "license": "MIT", "funding": { "type": "github", @@ -7237,6 +9651,8 @@ }, "node_modules/trough": { "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", "license": "MIT", "funding": { "type": "github", @@ -7245,6 +9661,8 @@ }, "node_modules/ts-dedent": { "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", "license": "MIT", "engines": { "node": ">=6.10" @@ -7252,14 +9670,20 @@ }, "node_modules/ts-interface-checker": { "version": "0.1.13", + "resolved": "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "license": "Apache-2.0" }, "node_modules/tslib": { "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/typescript": { "version": "5.7.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -7272,14 +9696,20 @@ }, "node_modules/ufo": { "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", "license": "MIT" }, "node_modules/undici-types": { "version": "7.16.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, "node_modules/unified": { "version": "11.0.5", + "resolved": "https://registry.npmmirror.com/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -7297,6 +9727,8 @@ }, "node_modules/unist-util-find-after": { "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -7309,6 +9741,8 @@ }, "node_modules/unist-util-is": { "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -7320,6 +9754,8 @@ }, "node_modules/unist-util-position": { "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -7331,6 +9767,8 @@ }, "node_modules/unist-util-remove-position": { "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -7343,6 +9781,8 @@ }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -7354,6 +9794,8 @@ }, "node_modules/unist-util-visit": { "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -7367,6 +9809,8 @@ }, "node_modules/unist-util-visit-parents": { "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -7378,7 +9822,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", "dev": true, "funding": [ { @@ -7408,6 +9854,8 @@ }, "node_modules/use-callback-ref": { "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -7427,6 +9875,8 @@ }, "node_modules/use-sidecar": { "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", "license": "MIT", "dependencies": { "detect-node-es": "^1.1.0", @@ -7447,6 +9897,8 @@ }, "node_modules/use-sync-external-store": { "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -7454,10 +9906,14 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, "node_modules/uuid": { "version": "11.1.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -7469,6 +9925,8 @@ }, "node_modules/vaul": { "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vaul/-/vaul-1.1.2.tgz", + "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==", "license": "MIT", "dependencies": { "@radix-ui/react-dialog": "^1.1.1" @@ -7480,6 +9938,8 @@ }, "node_modules/vfile": { "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -7492,6 +9952,8 @@ }, "node_modules/vfile-location": { "version": "5.0.3", + "resolved": "https://registry.npmmirror.com/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -7504,6 +9966,8 @@ }, "node_modules/vfile-message": { "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -7516,6 +9980,8 @@ }, "node_modules/victory-vendor": { "version": "36.9.2", + "resolved": "https://registry.npmmirror.com/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", "license": "MIT AND ISC", "dependencies": { "@types/d3-array": "^3.0.3", @@ -7536,6 +10002,8 @@ }, "node_modules/video-react": { "version": "0.16.0", + "resolved": "https://registry.npmmirror.com/video-react/-/video-react-0.16.0.tgz", + "integrity": "sha512-138NHPS8bmgqCYVCdbv2GVFhXntemNHWGw9AN8iJSzr3jizXMmWJd2LTBppr4hZJUbyW1A1tPZ3CQXZUaexMVA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.4.5", @@ -7551,6 +10019,8 @@ }, "node_modules/vite": { "version": "5.4.21", + "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "license": "MIT", "dependencies": { "esbuild": "^0.21.3", @@ -7608,6 +10078,8 @@ }, "node_modules/vite-plugin-svgr": { "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/vite-plugin-svgr/-/vite-plugin-svgr-4.5.0.tgz", + "integrity": "sha512-W+uoSpmVkSmNOGPSsDCWVW/DDAyv+9fap9AZXBvWiQqrboJ08j2vh0tFxTD/LjwqwAd3yYSVJgm54S/1GhbdnA==", "dev": true, "license": "MIT", "dependencies": { @@ -7621,6 +10093,8 @@ }, "node_modules/vscode-jsonrpc": { "version": "8.2.0", + "resolved": "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -7628,6 +10102,8 @@ }, "node_modules/vscode-languageserver": { "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", "license": "MIT", "dependencies": { "vscode-languageserver-protocol": "3.17.5" @@ -7638,6 +10114,8 @@ }, "node_modules/vscode-languageserver-protocol": { "version": "3.17.5", + "resolved": "https://registry.npmmirror.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", "license": "MIT", "dependencies": { "vscode-jsonrpc": "8.2.0", @@ -7646,132 +10124,56 @@ }, "node_modules/vscode-languageserver-textdocument": { "version": "1.0.12", + "resolved": "https://registry.npmmirror.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", "license": "MIT" }, "node_modules/vscode-languageserver-types": { "version": "3.17.5", + "resolved": "https://registry.npmmirror.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", "license": "MIT" }, "node_modules/vscode-uri": { "version": "3.0.8", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", "license": "MIT" }, "node_modules/web-namespaces": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/which-module": { "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "license": "ISC" }, "node_modules/wrap-ansi": { - "version": "8.1.0", - "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", + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "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", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "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", - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "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", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { "node": ">=8" } }, "node_modules/ws": { "version": "8.18.3", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -7791,15 +10193,21 @@ }, "node_modules/y18n": { "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "license": "ISC" }, "node_modules/yallist": { "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" }, "node_modules/yargs": { "version": "15.4.1", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "license": "MIT", "dependencies": { "cliui": "^6.0.0", @@ -7820,6 +10228,8 @@ }, "node_modules/yargs-parser": { "version": "18.1.3", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "license": "ISC", "dependencies": { "camelcase": "^5.0.0", @@ -7831,46 +10241,17 @@ }, "node_modules/yargs-parser/node_modules/camelcase": { "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "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/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/zod": { "version": "3.25.76", + "resolved": "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -7878,6 +10259,8 @@ }, "node_modules/zwitch": { "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", "license": "MIT", "funding": { "type": "github", diff --git a/package.json b/frontend/package.json similarity index 99% rename from package.json rename to frontend/package.json index 39f7a20..d1b077c 100644 --- a/package.json +++ b/frontend/package.json @@ -82,6 +82,7 @@ "@types/lodash": "^4.17.16", "@types/react": "^19.0.12", "@types/react-dom": "^19.0.4", + "@types/unist": "^3.0.3", "@types/video-react": "^0.15.8", "@typescript/native-preview": "7.0.0-dev.20250819.1", "@vitejs/plugin-react": "^4.3.4", diff --git a/pnpm-lock.yaml b/frontend/pnpm-lock.yaml similarity index 100% rename from pnpm-lock.yaml rename to frontend/pnpm-lock.yaml diff --git a/postcss.config.js b/frontend/postcss.config.js similarity index 100% rename from postcss.config.js rename to frontend/postcss.config.js diff --git a/public/diagram.svg b/frontend/public/diagram.svg similarity index 100% rename from public/diagram.svg rename to frontend/public/diagram.svg diff --git a/public/favicon.png b/frontend/public/favicon.png similarity index 100% rename from public/favicon.png rename to frontend/public/favicon.png diff --git a/public/images/error/404-dark.svg b/frontend/public/images/error/404-dark.svg similarity index 100% rename from public/images/error/404-dark.svg rename to frontend/public/images/error/404-dark.svg diff --git a/public/images/error/404.svg b/frontend/public/images/error/404.svg similarity index 100% rename from public/images/error/404.svg rename to frontend/public/images/error/404.svg diff --git a/public/images/error/500-dark.svg b/frontend/public/images/error/500-dark.svg similarity index 100% rename from public/images/error/500-dark.svg rename to frontend/public/images/error/500-dark.svg diff --git a/public/images/error/500.svg b/frontend/public/images/error/500.svg similarity index 100% rename from public/images/error/500.svg rename to frontend/public/images/error/500.svg diff --git a/public/images/error/503-dark.svg b/frontend/public/images/error/503-dark.svg similarity index 100% rename from public/images/error/503-dark.svg rename to frontend/public/images/error/503-dark.svg diff --git a/public/images/error/503.svg b/frontend/public/images/error/503.svg similarity index 100% rename from public/images/error/503.svg rename to frontend/public/images/error/503.svg diff --git a/public/images/example1.png b/frontend/public/images/example1.png similarity index 100% rename from public/images/example1.png rename to frontend/public/images/example1.png diff --git a/public/images/example2.png b/frontend/public/images/example2.png similarity index 100% rename from public/images/example2.png rename to frontend/public/images/example2.png diff --git a/public/images/example3.png b/frontend/public/images/example3.png similarity index 100% rename from public/images/example3.png rename to frontend/public/images/example3.png diff --git a/public/images/favicon.ico b/frontend/public/images/favicon.ico similarity index 100% rename from public/images/favicon.ico rename to frontend/public/images/favicon.ico diff --git a/public/images/logo.png b/frontend/public/images/logo.png similarity index 100% rename from public/images/logo.png rename to frontend/public/images/logo.png diff --git a/public/images/logo/auth-logo.svg b/frontend/public/images/logo/auth-logo.svg similarity index 100% rename from public/images/logo/auth-logo.svg rename to frontend/public/images/logo/auth-logo.svg diff --git a/public/images/logo/logo-dark.svg b/frontend/public/images/logo/logo-dark.svg similarity index 100% rename from public/images/logo/logo-dark.svg rename to frontend/public/images/logo/logo-dark.svg diff --git a/public/images/logo/logo-icon.svg b/frontend/public/images/logo/logo-icon.svg similarity index 100% rename from public/images/logo/logo-icon.svg rename to frontend/public/images/logo/logo-icon.svg diff --git a/public/images/shape/grid-01.svg b/frontend/public/images/shape/grid-01.svg similarity index 100% rename from public/images/shape/grid-01.svg rename to frontend/public/images/shape/grid-01.svg diff --git a/public/images/审计报告示例.png b/frontend/public/images/审计报告示例.png similarity index 100% rename from public/images/审计报告示例.png rename to frontend/public/images/审计报告示例.png diff --git a/public/logo_xcodereviewer.png b/frontend/public/logo_xcodereviewer.png similarity index 100% rename from public/logo_xcodereviewer.png rename to frontend/public/logo_xcodereviewer.png diff --git a/public/star-me-cn.svg b/frontend/public/star-me-cn.svg similarity index 100% rename from public/star-me-cn.svg rename to frontend/public/star-me-cn.svg diff --git a/public/star-me.svg b/frontend/public/star-me.svg similarity index 100% rename from public/star-me.svg rename to frontend/public/star-me.svg diff --git a/src/App.tsx b/frontend/src/App.tsx similarity index 100% rename from src/App.tsx rename to frontend/src/App.tsx diff --git a/frontend/src/app/App.tsx b/frontend/src/app/App.tsx new file mode 100644 index 0000000..3ff66ed --- /dev/null +++ b/frontend/src/app/App.tsx @@ -0,0 +1,53 @@ +import { BrowserRouter, Routes, Route, Navigate, Outlet } from "react-router-dom"; +import { Toaster } from "sonner"; +import Header from "@/components/layout/Header"; +import routes from "./routes"; +import { AuthProvider } from "@/shared/context/AuthContext"; +import { ProtectedRoute } from "./ProtectedRoute"; +import Login from "@/pages/Login"; +import Register from "@/pages/Register"; +import NotFound from "@/pages/NotFound"; + +function AppLayout() { + return ( +
+
+
+ +
+
+ ); +} + +function App() { + return ( + + + + + {/* Public Routes */} + } /> + } /> + + {/* Protected Routes */} + }> + }> + {routes.map((route) => ( + + ))} + + + + {/* Catch all */} + } /> + + + + ); +} + +export default App; diff --git a/frontend/src/app/ProtectedRoute.tsx b/frontend/src/app/ProtectedRoute.tsx new file mode 100644 index 0000000..5333e9a --- /dev/null +++ b/frontend/src/app/ProtectedRoute.tsx @@ -0,0 +1,18 @@ +import { Navigate, Outlet, useLocation } from 'react-router-dom'; +import { useAuth } from '@/shared/context/AuthContext'; + +export const ProtectedRoute = () => { + const { isAuthenticated, isLoading } = useAuth(); + const location = useLocation(); + + if (isLoading) { + return
Loading...
; + } + + if (!isAuthenticated) { + return ; + } + + return ; +}; + diff --git a/src/app/main.tsx b/frontend/src/app/main.tsx similarity index 71% rename from src/app/main.tsx rename to frontend/src/app/main.tsx index 952a964..cbf03b2 100644 --- a/src/app/main.tsx +++ b/frontend/src/app/main.tsx @@ -3,16 +3,9 @@ import { createRoot } from "react-dom/client"; import "@/assets/styles/globals.css"; import App from "./App.tsx"; import { AppWrapper } from "@/components/layout/PageMeta"; -import { isLocalMode } from "@/shared/config/database"; -import { initLocalDatabase } from "@/shared/utils/initLocalDB"; import { ErrorBoundary } from "@/components/common/ErrorBoundary"; import "@/shared/utils/fetchWrapper"; // 初始化fetch拦截器 -// 初始化本地数据库 -if (isLocalMode) { - initLocalDatabase().catch(console.error); -} - createRoot(document.getElementById("root")!).render( diff --git a/src/app/routes.tsx b/frontend/src/app/routes.tsx similarity index 100% rename from src/app/routes.tsx rename to frontend/src/app/routes.tsx diff --git a/src/assets/styles/globals.css b/frontend/src/assets/styles/globals.css similarity index 100% rename from src/assets/styles/globals.css rename to frontend/src/assets/styles/globals.css diff --git a/frontend/src/components/analysis/AnalysisProgressDialog.tsx b/frontend/src/components/analysis/AnalysisProgressDialog.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/audit/CreateTaskDialog.tsx b/frontend/src/components/audit/CreateTaskDialog.tsx similarity index 96% rename from src/components/audit/CreateTaskDialog.tsx rename to frontend/src/components/audit/CreateTaskDialog.tsx index bb12e3c..ae73121 100644 --- a/src/components/audit/CreateTaskDialog.tsx +++ b/frontend/src/components/audit/CreateTaskDialog.tsx @@ -25,7 +25,6 @@ import TerminalProgressDialog from "./TerminalProgressDialog"; import { runRepositoryAudit } from "@/features/projects/services/repoScan"; import { scanZipFile, validateZipFile } from "@/features/projects/services/repoZipScan"; import { loadZipFile } from "@/shared/utils/zipStorage"; -import { env } from "@/shared/config/env"; interface CreateTaskDialogProps { open: boolean; @@ -53,7 +52,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr scan_config: { include_tests: true, include_docs: false, - max_file_size: 1024, // KB + max_file_size: 200, // KB (对齐后端默认值 200KB) analysis_depth: "standard" } }); @@ -69,6 +68,32 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr { label: "coverage", value: "coverage/**", description: "测试覆盖率报告" } ]; + // 从后端加载默认配置 + useEffect(() => { + const loadDefaultConfig = async () => { + try { + const defaultConfig = await api.getDefaultConfig(); + if (defaultConfig?.otherConfig) { + // 后端 MAX_FILE_SIZE_BYTES 是 200 * 1024 = 204800 bytes = 200KB + // 转换为KB用于前端显示 + const maxFileSizeKB = 200; // 后端默认值 200KB + + setTaskForm(prev => ({ + ...prev, + scan_config: { + ...prev.scan_config, + max_file_size: maxFileSizeKB, + } + })); + } + } catch (error) { + console.error('Failed to load default config:', error); + // 使用硬编码的默认值作为后备(200KB) + } + }; + loadDefaultConfig(); + }, []); + useEffect(() => { if (open) { loadProjects(); @@ -170,17 +195,12 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr // GitHub/GitLab等远程仓库 console.log('📡 调用 runRepositoryAudit...'); - // 从运行时配置中获取 Token - const githubToken = env.GITHUB_TOKEN; - const gitlabToken = env.GITLAB_TOKEN; - + // 后端会从用户配置中读取 GitHub/GitLab Token,前端不需要传递 taskId = await runRepositoryAudit({ projectId: project.id, repoUrl: project.repository_url!, branch: taskForm.branch_name || project.default_branch || 'main', exclude: taskForm.exclude_patterns, - githubToken, - gitlabToken, createdBy: 'local-user' }); } @@ -233,7 +253,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr scan_config: { include_tests: true, include_docs: false, - max_file_size: 1024, + max_file_size: 200, // KB (对齐后端默认值 200KB) analysis_depth: "standard" } }); @@ -659,7 +679,7 @@ export default function CreateTaskDialog({ open, onOpenChange, onTaskCreated, pr ...taskForm, scan_config: { ...taskForm.scan_config, - max_file_size: parseInt(e.target.value) || 1024 + max_file_size: parseInt(e.target.value) || 200 } }) } diff --git a/src/components/audit/TerminalProgressDialog.tsx b/frontend/src/components/audit/TerminalProgressDialog.tsx similarity index 100% rename from src/components/audit/TerminalProgressDialog.tsx rename to frontend/src/components/audit/TerminalProgressDialog.tsx diff --git a/src/components/common/ErrorBoundary.tsx b/frontend/src/components/common/ErrorBoundary.tsx similarity index 100% rename from src/components/common/ErrorBoundary.tsx rename to frontend/src/components/common/ErrorBoundary.tsx diff --git a/frontend/src/components/database/DatabaseManager.tsx b/frontend/src/components/database/DatabaseManager.tsx new file mode 100644 index 0000000..d676856 --- /dev/null +++ b/frontend/src/components/database/DatabaseManager.tsx @@ -0,0 +1,519 @@ +/** + * 数据库管理组件 + * 提供数据库的导出、导入、清空、统计和健康检查等功能 + */ + +import { useState, useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Badge } from '@/components/ui/badge'; +import { Separator } from '@/components/ui/separator'; +import { + Download, + Upload, + Trash2, + AlertCircle, + CheckCircle2, + Server, + Activity, + RefreshCw, + Database, + FileText, + AlertTriangle, + Info +} from 'lucide-react'; +import { dbMode } from '@/shared/config/database'; +import { api } from '@/shared/api/database'; +import { toast } from 'sonner'; + +export function DatabaseManager() { + const [loading, setLoading] = useState(false); + const [healthLoading, setHealthLoading] = useState(false); + const [statsLoading, setStatsLoading] = useState(false); + const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); + const [health, setHealth] = useState<{ + status: 'healthy' | 'warning' | 'error'; + database_connected: boolean; + total_records: number; + last_backup_date: string | null; + issues: string[]; + warnings: string[]; + } | null>(null); + const [stats, setStats] = useState<{ + total_projects: number; + active_projects: number; + total_tasks: number; + completed_tasks: number; + pending_tasks: number; + running_tasks: number; + failed_tasks: number; + total_issues: number; + open_issues: number; + resolved_issues: number; + critical_issues: number; + high_issues: number; + medium_issues: number; + low_issues: number; + total_analyses: number; + total_members: number; + has_config: boolean; + } | null>(null); + + // 加载健康检查和统计信息 + useEffect(() => { + loadHealth(); + loadStats(); + }, []); + + const loadHealth = async () => { + try { + setHealthLoading(true); + const healthData = await api.checkDatabaseHealth(); + setHealth(healthData); + } catch (error) { + console.error('加载健康检查失败:', error); + } finally { + setHealthLoading(false); + } + }; + + const loadStats = async () => { + try { + setStatsLoading(true); + const statsData = await api.getDatabaseStats(); + setStats(statsData); + } catch (error) { + console.error('加载统计信息失败:', error); + } finally { + setStatsLoading(false); + } + }; + + // 导出数据 + const handleExport = async () => { + try { + setLoading(true); + setMessage(null); + + const exportData = await api.exportDatabase(); + + // 构建完整的导出数据 + const fullData = { + version: "1.0.0", + export_date: exportData.export_date, + data: exportData.data + }; + + const blob = new Blob([JSON.stringify(fullData, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `xcodereviewer-backup-${new Date().toISOString().split('T')[0]}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + toast.success('数据导出成功!'); + setMessage({ type: 'success', text: '数据导出成功!' }); + + // 刷新统计信息 + loadStats(); + } catch (error: any) { + console.error('导出失败:', error); + const errorMsg = error?.response?.data?.detail || error?.message || '数据导出失败,请重试'; + toast.error(errorMsg); + setMessage({ type: 'error', text: errorMsg }); + } finally { + setLoading(false); + } + }; + + // 导入数据 + const handleImport = async (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file) return; + + // 验证文件类型 + if (!file.name.endsWith('.json')) { + toast.error('请选择 JSON 格式的文件'); + event.target.value = ''; + return; + } + + // 验证文件大小(最大 50MB) + if (file.size > 50 * 1024 * 1024) { + toast.error('文件大小不能超过 50MB'); + event.target.value = ''; + return; + } + + try { + setLoading(true); + setMessage(null); + + const result = await api.importDatabase(file); + + const imported = result.imported; + const summary = [ + imported.projects > 0 && `${imported.projects} 个项目`, + imported.tasks > 0 && `${imported.tasks} 个任务`, + imported.issues > 0 && `${imported.issues} 个问题`, + imported.analyses > 0 && `${imported.analyses} 条分析记录`, + imported.config > 0 && '用户配置', + ].filter(Boolean).join('、'); + + toast.success(`数据导入成功!已导入:${summary}`); + setMessage({ type: 'success', text: `数据导入成功!已导入:${summary}` }); + + // 清空文件输入 + event.target.value = ''; + + // 刷新统计信息和健康检查 + loadStats(); + loadHealth(); + + // 延迟刷新页面 + setTimeout(() => window.location.reload(), 2000); + } catch (error: any) { + console.error('导入失败:', error); + const errorMsg = error?.response?.data?.detail || error?.message || '数据导入失败,请检查文件格式'; + toast.error(errorMsg); + setMessage({ type: 'error', text: errorMsg }); + // 清空文件输入 + event.target.value = ''; + } finally { + setLoading(false); + } + }; + + // 清空数据 + const handleClear = async () => { + if (!window.confirm('⚠️ 警告:确定要清空所有数据吗?\n\n此操作将删除:\n- 所有项目\n- 所有任务\n- 所有问题\n- 所有分析记录\n- 用户配置\n\n此操作不可恢复!')) { + return; + } + + // 二次确认 + if (!window.confirm('请再次确认:您真的要清空所有数据吗?')) { + return; + } + + try { + setLoading(true); + setMessage(null); + + const result = await api.clearDatabase(); + + const deleted = result.deleted; + const summary = [ + deleted.projects > 0 && `${deleted.projects} 个项目`, + deleted.tasks > 0 && `${deleted.tasks} 个任务`, + deleted.issues > 0 && `${deleted.issues} 个问题`, + deleted.analyses > 0 && `${deleted.analyses} 条分析记录`, + deleted.config > 0 && '用户配置', + ].filter(Boolean).join('、'); + + toast.success(`数据已清空!已删除:${summary}`); + setMessage({ type: 'success', text: `数据已清空!已删除:${summary}` }); + + // 刷新统计信息和健康检查 + loadStats(); + loadHealth(); + + // 延迟刷新页面 + setTimeout(() => window.location.reload(), 2000); + } catch (error: any) { + console.error('清空失败:', error); + const errorMsg = error?.response?.data?.detail || error?.message || '清空数据失败,请重试'; + toast.error(errorMsg); + setMessage({ type: 'error', text: errorMsg }); + } finally { + setLoading(false); + } + }; + + const getHealthStatusColor = (status: string) => { + switch (status) { + case 'healthy': + return 'text-green-600 bg-green-50 border-green-200'; + case 'warning': + return 'text-yellow-600 bg-yellow-50 border-yellow-200'; + case 'error': + return 'text-red-600 bg-red-50 border-red-200'; + default: + return 'text-gray-600 bg-gray-50 border-gray-200'; + } + }; + + const getHealthStatusText = (status: string) => { + switch (status) { + case 'healthy': + return '健康'; + case 'warning': + return '警告'; + case 'error': + return '错误'; + default: + return '未知'; + } + }; + + return ( +
+ {/* 健康检查 */} + + +
+
+ + 数据库健康检查 +
+ +
+ + 检查数据库连接状态和数据完整性 + +
+ + {healthLoading ? ( +
+ +
+ ) : health ? ( + <> +
+ {health.status === 'healthy' ? ( + + ) : health.status === 'warning' ? ( + + ) : ( + + )} +
+
+ 状态: + + {getHealthStatusText(health.status)} + +
+
+ 数据库连接:{health.database_connected ? '正常' : '异常'} | + 总记录数:{health.total_records.toLocaleString()} +
+
+
+ + {health.issues.length > 0 && ( + + + +
发现的问题:
+
    + {health.issues.map((issue, index) => ( +
  • {issue}
  • + ))} +
+
+
+ )} + + {health.warnings.length > 0 && ( + + + +
警告信息:
+
    + {health.warnings.map((warning, index) => ( +
  • {warning}
  • + ))} +
+
+
+ )} + + ) : ( + + + 无法加载健康检查信息 + + )} +
+
+ + {/* 统计信息 */} + + +
+
+ + 数据统计 +
+ +
+ + 查看数据库中的数据统计信息 + +
+ + {statsLoading ? ( +
+ +
+ ) : stats ? ( +
+
+
项目
+
{stats.total_projects}
+
活跃: {stats.active_projects}
+
+
+
任务
+
{stats.total_tasks}
+
+ 完成: {stats.completed_tasks} | 进行中: {stats.running_tasks} +
+
+
+
问题
+
{stats.total_issues}
+
+ 未解决: {stats.open_issues} | 已解决: {stats.resolved_issues} +
+
+
+
分析记录
+
{stats.total_analyses}
+
即时分析
+
+
+ ) : ( + + + 无法加载统计信息 + + )} +
+
+ + {/* 数据操作 */} + + + + + 数据操作 + + + 管理您的数据,包括导出、导入和清空。数据存储在后端 PostgreSQL 数据库中。 + + + + {message && ( + + {message.type === 'success' ? ( + + ) : ( + + )} + {message.text} + + )} + +
+
+

+ + 导出数据 +

+

+ 将数据导出为 JSON 文件,用于备份或迁移 +

+ +
+ +
+

+ + 导入数据 +

+

+ 从 JSON 文件恢复数据(最大 50MB) +

+ + +
+ +
+

+ + 清空数据 +

+

+ 删除所有数据(不可恢复) +

+ +
+
+ + + + + + + 提示: + {dbMode === 'api' + ? '数据存储在后端 PostgreSQL 数据库中,支持多用户、多设备同步。建议定期导出备份。' + : '建议定期导出数据备份,以防意外数据丢失。'} + + +
+
+
+ ); +} diff --git a/src/components/database/DatabaseStatus.tsx b/frontend/src/components/database/DatabaseStatus.tsx similarity index 76% rename from src/components/database/DatabaseStatus.tsx rename to frontend/src/components/database/DatabaseStatus.tsx index 0a1eb5e..daed2e0 100644 --- a/src/components/database/DatabaseStatus.tsx +++ b/frontend/src/components/database/DatabaseStatus.tsx @@ -3,13 +3,20 @@ * 显示当前使用的数据库模式 */ -import { Database, Cloud, Eye } from 'lucide-react'; +import { Database, Cloud, Eye, Server } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; import { dbMode } from '@/shared/config/database'; export function DatabaseStatus() { const getStatusConfig = () => { switch (dbMode) { + case 'api': + return { + icon: Server, + label: '后端数据库', + variant: 'default' as const, + description: '数据存储在后端 PostgreSQL 数据库' + }; case 'local': return { icon: Database, @@ -22,7 +29,7 @@ export function DatabaseStatus() { icon: Cloud, label: 'Supabase 云端', variant: 'secondary' as const, - description: '数据存储在云端' + description: '数据存储在云端(已废弃)' }; case 'demo': return { @@ -55,6 +62,14 @@ export function DatabaseStatus() { export function DatabaseStatusDetail() { const getStatusConfig = () => { switch (dbMode) { + case 'api': + return { + icon: Server, + label: '后端数据库模式', + variant: 'default' as const, + description: '数据存储在后端 PostgreSQL 数据库中,通过 REST API 访问。支持多用户、多设备同步。', + tips: '提示:所有数据操作都通过后端 API 进行,确保网络连接正常。' + }; case 'local': return { icon: Database, @@ -66,10 +81,10 @@ export function DatabaseStatusDetail() { case 'supabase': return { icon: Cloud, - label: 'Supabase 云端模式', + label: 'Supabase 云端模式(已废弃)', variant: 'secondary' as const, - description: '数据存储在 Supabase 云端,支持多设备同步。', - tips: '提示:确保网络连接正常。' + description: '此模式已不再使用,请使用后端数据库模式。', + tips: '提示:已迁移到后端 PostgreSQL 数据库。' }; case 'demo': return { diff --git a/src/components/debug/DatabaseTest.tsx b/frontend/src/components/debug/DatabaseTest.tsx similarity index 100% rename from src/components/debug/DatabaseTest.tsx rename to frontend/src/components/debug/DatabaseTest.tsx diff --git a/src/components/debug/LogViewer.tsx b/frontend/src/components/debug/LogViewer.tsx similarity index 100% rename from src/components/debug/LogViewer.tsx rename to frontend/src/components/debug/LogViewer.tsx diff --git a/src/components/layout/Footer.tsx b/frontend/src/components/layout/Footer.tsx similarity index 100% rename from src/components/layout/Footer.tsx rename to frontend/src/components/layout/Footer.tsx diff --git a/src/components/layout/Header.tsx b/frontend/src/components/layout/Header.tsx similarity index 100% rename from src/components/layout/Header.tsx rename to frontend/src/components/layout/Header.tsx diff --git a/src/components/layout/PageMeta.tsx b/frontend/src/components/layout/PageMeta.tsx similarity index 100% rename from src/components/layout/PageMeta.tsx rename to frontend/src/components/layout/PageMeta.tsx diff --git a/src/components/reports/ExportReportDialog.tsx b/frontend/src/components/reports/ExportReportDialog.tsx similarity index 100% rename from src/components/reports/ExportReportDialog.tsx rename to frontend/src/components/reports/ExportReportDialog.tsx diff --git a/src/components/system/SystemConfig.tsx b/frontend/src/components/system/SystemConfig.tsx similarity index 85% rename from src/components/system/SystemConfig.tsx rename to frontend/src/components/system/SystemConfig.tsx index 1c467e9..48c62d3 100644 --- a/src/components/system/SystemConfig.tsx +++ b/frontend/src/components/system/SystemConfig.tsx @@ -22,6 +22,7 @@ import { Database } from "lucide-react"; import { toast } from "sonner"; +import { api } from "@/shared/api/database"; // LLM 提供商配置 const LLM_PROVIDERS = [ @@ -90,37 +91,10 @@ interface SystemConfigData { outputLanguage: string; } -const STORAGE_KEY = 'xcodereviewer_runtime_config'; - export function SystemConfig() { - const [config, setConfig] = useState({ - llmProvider: 'gemini', - llmApiKey: '', - llmModel: '', - llmBaseUrl: '', - llmTimeout: 150000, - llmTemperature: 0.2, - llmMaxTokens: 4096, - llmCustomHeaders: '', - geminiApiKey: '', - openaiApiKey: '', - claudeApiKey: '', - qwenApiKey: '', - deepseekApiKey: '', - zhipuApiKey: '', - moonshotApiKey: '', - baiduApiKey: '', - minimaxApiKey: '', - doubaoApiKey: '', - ollamaBaseUrl: 'http://localhost:11434/v1', - githubToken: '', - gitlabToken: '', - maxAnalyzeFiles: 40, - llmConcurrency: 2, - llmGapMs: 500, - outputLanguage: 'zh-CN', - }); - + // 初始状态为空,等待从后端加载 + const [config, setConfig] = useState(null); + const [loading, setLoading] = useState(true); const [showApiKeys, setShowApiKeys] = useState>({}); const [hasChanges, setHasChanges] = useState(false); const [configSource, setConfigSource] = useState<'runtime' | 'build'>('build'); @@ -130,36 +104,57 @@ export function SystemConfig() { loadConfig(); }, []); - const loadConfig = () => { + const loadConfig = async () => { try { - // 尝试从 localStorage 加载运行时配置 - const savedConfig = localStorage.getItem(STORAGE_KEY); + setLoading(true); + // 先从后端获取默认配置 + const defaultConfig = await api.getDefaultConfig(); + if (!defaultConfig) { + throw new Error('Failed to get default config from backend'); + } - if (savedConfig) { - const parsedConfig = JSON.parse(savedConfig); - setConfig(parsedConfig); + // 从后端加载用户配置(已合并默认配置) + const backendConfig = await api.getUserConfig(); + if (backendConfig) { + const mergedConfig: SystemConfigData = { + ...defaultConfig.llmConfig, + ...defaultConfig.otherConfig, + ...(backendConfig.llmConfig || {}), + ...(backendConfig.otherConfig || {}), + }; + setConfig(mergedConfig); setConfigSource('runtime'); - console.log('已加载运行时配置'); + console.log('已从后端加载用户配置(已合并默认配置)'); } else { - // 使用构建时配置 - loadFromEnv(); + // 使用默认配置 + const mergedConfig: SystemConfigData = { + ...defaultConfig.llmConfig, + ...defaultConfig.otherConfig, + }; + setConfig(mergedConfig); setConfigSource('build'); + console.log('使用后端默认配置'); } } catch (error) { console.error('Failed to load config:', error); - loadFromEnv(); + // 如果后端加载失败,尝试从环境变量加载(后备方案) + const envConfig = loadFromEnv(); + setConfig(envConfig); + setConfigSource('build'); + } finally { + setLoading(false); } }; - const loadFromEnv = () => { - // 从环境变量加载(构建时配置) - const envConfig: SystemConfigData = { - llmProvider: import.meta.env.VITE_LLM_PROVIDER || 'gemini', + const loadFromEnv = (): SystemConfigData => { + // 从环境变量加载(后备方案,仅在无法从后端获取时使用) + return { + llmProvider: import.meta.env.VITE_LLM_PROVIDER || 'openai', llmApiKey: import.meta.env.VITE_LLM_API_KEY || '', llmModel: import.meta.env.VITE_LLM_MODEL || '', llmBaseUrl: import.meta.env.VITE_LLM_BASE_URL || '', llmTimeout: Number(import.meta.env.VITE_LLM_TIMEOUT) || 150000, - llmTemperature: Number(import.meta.env.VITE_LLM_TEMPERATURE) || 0.2, + llmTemperature: Number(import.meta.env.VITE_LLM_TEMPERATURE) || 0.1, llmMaxTokens: Number(import.meta.env.VITE_LLM_MAX_TOKENS) || 4096, llmCustomHeaders: import.meta.env.VITE_LLM_CUSTOM_HEADERS || '', geminiApiKey: import.meta.env.VITE_GEMINI_API_KEY || '', @@ -175,22 +170,61 @@ export function SystemConfig() { ollamaBaseUrl: import.meta.env.VITE_OLLAMA_BASE_URL || 'http://localhost:11434/v1', githubToken: import.meta.env.VITE_GITHUB_TOKEN || '', gitlabToken: import.meta.env.VITE_GITLAB_TOKEN || '', - maxAnalyzeFiles: Number(import.meta.env.VITE_MAX_ANALYZE_FILES) || 40, - llmConcurrency: Number(import.meta.env.VITE_LLM_CONCURRENCY) || 2, - llmGapMs: Number(import.meta.env.VITE_LLM_GAP_MS) || 500, + maxAnalyzeFiles: Number(import.meta.env.VITE_MAX_ANALYZE_FILES) || 50, + llmConcurrency: Number(import.meta.env.VITE_LLM_CONCURRENCY) || 3, + llmGapMs: Number(import.meta.env.VITE_LLM_GAP_MS) || 2000, outputLanguage: import.meta.env.VITE_OUTPUT_LANGUAGE || 'zh-CN', }; - setConfig(envConfig); }; - const saveConfig = () => { + const saveConfig = async () => { + if (!config) { + toast.error('配置未加载,请稍候再试'); + return; + } try { - localStorage.setItem(STORAGE_KEY, JSON.stringify(config)); + // 保存到后端 + const llmConfig = { + llmProvider: config.llmProvider, + llmApiKey: config.llmApiKey, + llmModel: config.llmModel, + llmBaseUrl: config.llmBaseUrl, + llmTimeout: config.llmTimeout, + llmTemperature: config.llmTemperature, + llmMaxTokens: config.llmMaxTokens, + llmCustomHeaders: config.llmCustomHeaders, + geminiApiKey: config.geminiApiKey, + openaiApiKey: config.openaiApiKey, + claudeApiKey: config.claudeApiKey, + qwenApiKey: config.qwenApiKey, + deepseekApiKey: config.deepseekApiKey, + zhipuApiKey: config.zhipuApiKey, + moonshotApiKey: config.moonshotApiKey, + baiduApiKey: config.baiduApiKey, + minimaxApiKey: config.minimaxApiKey, + doubaoApiKey: config.doubaoApiKey, + ollamaBaseUrl: config.ollamaBaseUrl, + }; + + const otherConfig = { + githubToken: config.githubToken, + gitlabToken: config.gitlabToken, + maxAnalyzeFiles: config.maxAnalyzeFiles, + llmConcurrency: config.llmConcurrency, + llmGapMs: config.llmGapMs, + outputLanguage: config.outputLanguage, + }; + + await api.updateUserConfig({ + llmConfig, + otherConfig, + }); + setHasChanges(false); setConfigSource('runtime'); // 记录用户操作 - import('@/shared/utils/logger').then(({ logger, LogCategory }) => { + import('@/shared/utils/logger').then(({ logger }) => { logger.logUserAction('保存系统配置', { provider: config.llmProvider, hasApiKey: !!config.llmApiKey, @@ -198,9 +232,9 @@ export function SystemConfig() { concurrency: config.llmConcurrency, language: config.outputLanguage, }); - }); + }).catch(() => {}); - toast.success("配置已保存!刷新页面后生效"); + toast.success("配置已保存到后端!刷新页面后生效"); // 提示用户刷新页面 setTimeout(() => { @@ -210,39 +244,29 @@ export function SystemConfig() { }, 1000); } catch (error) { console.error('Failed to save config:', error); - - // 记录错误并显示详细信息 - import('@/shared/utils/errorHandler').then(({ handleError }) => { - handleError(error, '保存系统配置失败'); - }); - const errorMessage = error instanceof Error ? error.message : '未知错误'; toast.error(`保存配置失败: ${errorMessage}`); } }; - const resetConfig = () => { - if (window.confirm("确定要重置为构建时配置吗?这将清除所有运行时配置。")) { + const resetConfig = async () => { + if (window.confirm("确定要重置为默认配置吗?这将清除所有用户配置。")) { try { - localStorage.removeItem(STORAGE_KEY); - loadFromEnv(); + // 删除后端配置 + await api.deleteUserConfig(); + + // 重新加载配置(会使用后端默认配置) + await loadConfig(); setHasChanges(false); - setConfigSource('build'); // 记录用户操作 - import('@/shared/utils/logger').then(({ logger, LogCategory }) => { - logger.logUserAction('重置系统配置', { action: 'reset_to_build_config' }); - }); + import('@/shared/utils/logger').then(({ logger }) => { + logger.logUserAction('重置系统配置', { action: 'reset_to_default' }); + }).catch(() => {}); - toast.success("已重置为构建时配置"); + toast.success("已重置为默认配置"); } catch (error) { console.error('Failed to reset config:', error); - - // 记录错误并显示详细信息 - import('@/shared/utils/errorHandler').then(({ handleError }) => { - handleError(error, '重置系统配置失败'); - }); - const errorMessage = error instanceof Error ? error.message : '未知错误'; toast.error(`重置配置失败: ${errorMessage}`); } @@ -250,7 +274,8 @@ export function SystemConfig() { }; const updateConfig = (key: keyof SystemConfigData, value: any) => { - setConfig(prev => ({ ...prev, [key]: value })); + if (!config) return; + setConfig(prev => prev ? { ...prev, [key]: value } : null); setHasChanges(true); }; @@ -259,6 +284,7 @@ export function SystemConfig() { }; const getCurrentApiKey = () => { + if (!config) return ''; const provider = config.llmProvider.toLowerCase(); const keyMap: Record = { gemini: config.geminiApiKey, @@ -277,7 +303,19 @@ export function SystemConfig() { return config.llmApiKey || keyMap[provider] || ''; }; - const isConfigured = getCurrentApiKey() !== ''; + const isConfigured = config ? getCurrentApiKey() !== '' : false; + + // 如果正在加载或配置为 null,显示加载状态 + if (loading || !config) { + return ( +
+
+
+

正在从后端加载配置...

+
+
+ ); + } return (
@@ -691,10 +729,10 @@ export function SystemConfig() {
-

运行时配置

+

用户配置

- 配置保存在浏览器 localStorage 中,刷新页面后立即生效。 - 可以在不重新构建 Docker 镜像的情况下修改配置。 + 配置保存在后端数据库中,与用户账号绑定。 + 可以在不重新构建 Docker 镜像的情况下修改配置,配置会在所有分析任务中生效。

diff --git a/src/components/ui/accordion.tsx b/frontend/src/components/ui/accordion.tsx similarity index 100% rename from src/components/ui/accordion.tsx rename to frontend/src/components/ui/accordion.tsx diff --git a/src/components/ui/alert-dialog.tsx b/frontend/src/components/ui/alert-dialog.tsx similarity index 100% rename from src/components/ui/alert-dialog.tsx rename to frontend/src/components/ui/alert-dialog.tsx diff --git a/src/components/ui/alert.tsx b/frontend/src/components/ui/alert.tsx similarity index 100% rename from src/components/ui/alert.tsx rename to frontend/src/components/ui/alert.tsx diff --git a/src/components/ui/aspect-ratio.tsx b/frontend/src/components/ui/aspect-ratio.tsx similarity index 100% rename from src/components/ui/aspect-ratio.tsx rename to frontend/src/components/ui/aspect-ratio.tsx diff --git a/src/components/ui/avatar.tsx b/frontend/src/components/ui/avatar.tsx similarity index 100% rename from src/components/ui/avatar.tsx rename to frontend/src/components/ui/avatar.tsx diff --git a/src/components/ui/badge.tsx b/frontend/src/components/ui/badge.tsx similarity index 100% rename from src/components/ui/badge.tsx rename to frontend/src/components/ui/badge.tsx diff --git a/src/components/ui/breadcrumb.tsx b/frontend/src/components/ui/breadcrumb.tsx similarity index 100% rename from src/components/ui/breadcrumb.tsx rename to frontend/src/components/ui/breadcrumb.tsx diff --git a/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx similarity index 100% rename from src/components/ui/button.tsx rename to frontend/src/components/ui/button.tsx diff --git a/src/components/ui/calendar.tsx b/frontend/src/components/ui/calendar.tsx similarity index 100% rename from src/components/ui/calendar.tsx rename to frontend/src/components/ui/calendar.tsx diff --git a/src/components/ui/card.tsx b/frontend/src/components/ui/card.tsx similarity index 100% rename from src/components/ui/card.tsx rename to frontend/src/components/ui/card.tsx diff --git a/src/components/ui/carousel.tsx b/frontend/src/components/ui/carousel.tsx similarity index 100% rename from src/components/ui/carousel.tsx rename to frontend/src/components/ui/carousel.tsx diff --git a/src/components/ui/chart.tsx b/frontend/src/components/ui/chart.tsx similarity index 100% rename from src/components/ui/chart.tsx rename to frontend/src/components/ui/chart.tsx diff --git a/src/components/ui/checkbox.tsx b/frontend/src/components/ui/checkbox.tsx similarity index 100% rename from src/components/ui/checkbox.tsx rename to frontend/src/components/ui/checkbox.tsx diff --git a/src/components/ui/collapsible.tsx b/frontend/src/components/ui/collapsible.tsx similarity index 100% rename from src/components/ui/collapsible.tsx rename to frontend/src/components/ui/collapsible.tsx diff --git a/src/components/ui/command.tsx b/frontend/src/components/ui/command.tsx similarity index 100% rename from src/components/ui/command.tsx rename to frontend/src/components/ui/command.tsx diff --git a/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx similarity index 100% rename from src/components/ui/dialog.tsx rename to frontend/src/components/ui/dialog.tsx diff --git a/src/components/ui/drawer.tsx b/frontend/src/components/ui/drawer.tsx similarity index 100% rename from src/components/ui/drawer.tsx rename to frontend/src/components/ui/drawer.tsx diff --git a/src/components/ui/dropdown-menu.tsx b/frontend/src/components/ui/dropdown-menu.tsx similarity index 100% rename from src/components/ui/dropdown-menu.tsx rename to frontend/src/components/ui/dropdown-menu.tsx diff --git a/src/components/ui/form.tsx b/frontend/src/components/ui/form.tsx similarity index 100% rename from src/components/ui/form.tsx rename to frontend/src/components/ui/form.tsx diff --git a/src/components/ui/input-otp.tsx b/frontend/src/components/ui/input-otp.tsx similarity index 100% rename from src/components/ui/input-otp.tsx rename to frontend/src/components/ui/input-otp.tsx diff --git a/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx similarity index 100% rename from src/components/ui/input.tsx rename to frontend/src/components/ui/input.tsx diff --git a/src/components/ui/label.tsx b/frontend/src/components/ui/label.tsx similarity index 100% rename from src/components/ui/label.tsx rename to frontend/src/components/ui/label.tsx diff --git a/src/components/ui/map.tsx b/frontend/src/components/ui/map.tsx similarity index 100% rename from src/components/ui/map.tsx rename to frontend/src/components/ui/map.tsx diff --git a/src/components/ui/menubar.tsx b/frontend/src/components/ui/menubar.tsx similarity index 100% rename from src/components/ui/menubar.tsx rename to frontend/src/components/ui/menubar.tsx diff --git a/src/components/ui/multi-select.tsx b/frontend/src/components/ui/multi-select.tsx similarity index 100% rename from src/components/ui/multi-select.tsx rename to frontend/src/components/ui/multi-select.tsx diff --git a/src/components/ui/navigation-menu.tsx b/frontend/src/components/ui/navigation-menu.tsx similarity index 100% rename from src/components/ui/navigation-menu.tsx rename to frontend/src/components/ui/navigation-menu.tsx diff --git a/src/components/ui/pagination.tsx b/frontend/src/components/ui/pagination.tsx similarity index 100% rename from src/components/ui/pagination.tsx rename to frontend/src/components/ui/pagination.tsx diff --git a/src/components/ui/popover.tsx b/frontend/src/components/ui/popover.tsx similarity index 100% rename from src/components/ui/popover.tsx rename to frontend/src/components/ui/popover.tsx diff --git a/src/components/ui/progress.tsx b/frontend/src/components/ui/progress.tsx similarity index 100% rename from src/components/ui/progress.tsx rename to frontend/src/components/ui/progress.tsx diff --git a/src/components/ui/qrcodedataurl.tsx b/frontend/src/components/ui/qrcodedataurl.tsx similarity index 100% rename from src/components/ui/qrcodedataurl.tsx rename to frontend/src/components/ui/qrcodedataurl.tsx diff --git a/src/components/ui/radio-group.tsx b/frontend/src/components/ui/radio-group.tsx similarity index 100% rename from src/components/ui/radio-group.tsx rename to frontend/src/components/ui/radio-group.tsx diff --git a/src/components/ui/resizable.tsx b/frontend/src/components/ui/resizable.tsx similarity index 100% rename from src/components/ui/resizable.tsx rename to frontend/src/components/ui/resizable.tsx diff --git a/src/components/ui/scroll-area.tsx b/frontend/src/components/ui/scroll-area.tsx similarity index 100% rename from src/components/ui/scroll-area.tsx rename to frontend/src/components/ui/scroll-area.tsx diff --git a/src/components/ui/select.tsx b/frontend/src/components/ui/select.tsx similarity index 100% rename from src/components/ui/select.tsx rename to frontend/src/components/ui/select.tsx diff --git a/src/components/ui/separator.tsx b/frontend/src/components/ui/separator.tsx similarity index 100% rename from src/components/ui/separator.tsx rename to frontend/src/components/ui/separator.tsx diff --git a/src/components/ui/sheet.tsx b/frontend/src/components/ui/sheet.tsx similarity index 100% rename from src/components/ui/sheet.tsx rename to frontend/src/components/ui/sheet.tsx diff --git a/src/components/ui/sidebar.tsx b/frontend/src/components/ui/sidebar.tsx similarity index 100% rename from src/components/ui/sidebar.tsx rename to frontend/src/components/ui/sidebar.tsx diff --git a/src/components/ui/skeleton.tsx b/frontend/src/components/ui/skeleton.tsx similarity index 100% rename from src/components/ui/skeleton.tsx rename to frontend/src/components/ui/skeleton.tsx diff --git a/src/components/ui/slider.tsx b/frontend/src/components/ui/slider.tsx similarity index 100% rename from src/components/ui/slider.tsx rename to frontend/src/components/ui/slider.tsx diff --git a/src/components/ui/sonner.tsx b/frontend/src/components/ui/sonner.tsx similarity index 100% rename from src/components/ui/sonner.tsx rename to frontend/src/components/ui/sonner.tsx diff --git a/src/components/ui/switch.tsx b/frontend/src/components/ui/switch.tsx similarity index 100% rename from src/components/ui/switch.tsx rename to frontend/src/components/ui/switch.tsx diff --git a/src/components/ui/table.tsx b/frontend/src/components/ui/table.tsx similarity index 100% rename from src/components/ui/table.tsx rename to frontend/src/components/ui/table.tsx diff --git a/src/components/ui/tabs.tsx b/frontend/src/components/ui/tabs.tsx similarity index 100% rename from src/components/ui/tabs.tsx rename to frontend/src/components/ui/tabs.tsx diff --git a/src/components/ui/textarea.tsx b/frontend/src/components/ui/textarea.tsx similarity index 100% rename from src/components/ui/textarea.tsx rename to frontend/src/components/ui/textarea.tsx diff --git a/src/components/ui/toast.tsx b/frontend/src/components/ui/toast.tsx similarity index 100% rename from src/components/ui/toast.tsx rename to frontend/src/components/ui/toast.tsx diff --git a/src/components/ui/toaster.tsx b/frontend/src/components/ui/toaster.tsx similarity index 100% rename from src/components/ui/toaster.tsx rename to frontend/src/components/ui/toaster.tsx diff --git a/src/components/ui/toggle-group.tsx b/frontend/src/components/ui/toggle-group.tsx similarity index 100% rename from src/components/ui/toggle-group.tsx rename to frontend/src/components/ui/toggle-group.tsx diff --git a/src/components/ui/toggle.tsx b/frontend/src/components/ui/toggle.tsx similarity index 100% rename from src/components/ui/toggle.tsx rename to frontend/src/components/ui/toggle.tsx diff --git a/src/components/ui/tooltip.tsx b/frontend/src/components/ui/tooltip.tsx similarity index 100% rename from src/components/ui/tooltip.tsx rename to frontend/src/components/ui/tooltip.tsx diff --git a/src/components/ui/video.tsx b/frontend/src/components/ui/video.tsx similarity index 100% rename from src/components/ui/video.tsx rename to frontend/src/components/ui/video.tsx diff --git a/frontend/src/features/analysis/services/codeAnalysis.ts b/frontend/src/features/analysis/services/codeAnalysis.ts new file mode 100644 index 0000000..734bfda --- /dev/null +++ b/frontend/src/features/analysis/services/codeAnalysis.ts @@ -0,0 +1,24 @@ +import type { CodeAnalysisResult } from "@/shared/types"; +import { apiClient } from "@/shared/api/serverClient"; +import { SUPPORTED_LANGUAGES } from '@/shared/constants'; + +export class CodeAnalysisEngine { + static getSupportedLanguages(): string[] { + return [...SUPPORTED_LANGUAGES]; + } + + static async analyzeCode(code: string, language: string): Promise { + try { + const response = await apiClient.post('/scan/instant', { code, language }); + return response.data; + } catch (error: any) { + console.error('Analysis failed:', error); + throw new Error(error.response?.data?.detail || 'Analysis failed'); + } + } + + // Mock methods for compatibility if needed, or remove if unused + static async analyzeRepository() { return { taskId: 'mock', status: 'pending' }; } + static async getRepositories() { return []; } + static async getBranches() { return []; } +} diff --git a/src/features/analysis/services/index.ts b/frontend/src/features/analysis/services/index.ts similarity index 100% rename from src/features/analysis/services/index.ts rename to frontend/src/features/analysis/services/index.ts diff --git a/src/features/projects/services/index.ts b/frontend/src/features/projects/services/index.ts similarity index 100% rename from src/features/projects/services/index.ts rename to frontend/src/features/projects/services/index.ts diff --git a/frontend/src/features/projects/services/repoScan.ts b/frontend/src/features/projects/services/repoScan.ts new file mode 100644 index 0000000..aef8c65 --- /dev/null +++ b/frontend/src/features/projects/services/repoScan.ts @@ -0,0 +1,28 @@ +import { api } from "@/shared/config/database"; + +export async function runRepositoryAudit(params: { + projectId: string; + repoUrl: string; + branch?: string; + exclude?: string[]; + createdBy?: string; +}) { + // 后端会从用户配置中读取 GitHub/GitLab Token,前端不需要传递 + // The backend handles everything now. + // We just need to create the task (which triggers the scan in our new api implementation) + // or call a specific scan endpoint. + + // In our new api.createAuditTask implementation (src/shared/api/database.ts), + // it actually calls /projects/{id}/scan which starts the process. + + const task = await api.createAuditTask({ + project_id: params.projectId, + task_type: "repository", + branch_name: params.branch || "main", + exclude_patterns: params.exclude || [], + scan_config: {}, + created_by: params.createdBy || "unknown" + } as any); + + return task.id; +} diff --git a/frontend/src/features/projects/services/repoZipScan.ts b/frontend/src/features/projects/services/repoZipScan.ts new file mode 100644 index 0000000..28e7c16 --- /dev/null +++ b/frontend/src/features/projects/services/repoZipScan.ts @@ -0,0 +1,36 @@ +import { apiClient } from "@/shared/api/serverClient"; + +export async function scanZipFile(params: { + projectId: string; + zipFile: File; + excludePatterns?: string[]; + createdBy?: string; +}): Promise { + const formData = new FormData(); + formData.append("file", params.zipFile); + formData.append("project_id", params.projectId); + + const res = await apiClient.post(`/scan/upload-zip`, formData, { + params: { project_id: params.projectId }, + headers: { + "Content-Type": "multipart/form-data", + }, + }); + + return res.data.task_id; +} + +export function validateZipFile(file: File): { valid: boolean; error?: string } { + // 检查文件类型 + if (!file.type.includes('zip') && !file.name.toLowerCase().endsWith('.zip')) { + return { valid: false, error: '请上传ZIP格式的文件' }; + } + + // 检查文件大小 (限制为100MB) + const maxSize = 100 * 1024 * 1024; + if (file.size > maxSize) { + return { valid: false, error: '文件大小不能超过100MB' }; + } + + return { valid: true }; +} diff --git a/src/features/reports/services/reportExport.ts b/frontend/src/features/reports/services/reportExport.ts similarity index 100% rename from src/features/reports/services/reportExport.ts rename to frontend/src/features/reports/services/reportExport.ts diff --git a/src/global.d.ts b/frontend/src/global.d.ts similarity index 100% rename from src/global.d.ts rename to frontend/src/global.d.ts diff --git a/src/pages/AdminDashboard.tsx b/frontend/src/pages/AdminDashboard.tsx similarity index 82% rename from src/pages/AdminDashboard.tsx rename to frontend/src/pages/AdminDashboard.tsx index 87cbe3e..ec7a843 100644 --- a/src/pages/AdminDashboard.tsx +++ b/frontend/src/pages/AdminDashboard.tsx @@ -6,7 +6,6 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Progress } from "@/components/ui/progress"; import { - Database, HardDrive, RefreshCw, Info, @@ -19,9 +18,8 @@ import { Package, Settings } from "lucide-react"; -import { api, dbMode, isLocalMode } from "@/shared/config/database"; +import { api, dbMode } from "@/shared/config/database"; import { DatabaseManager } from "@/components/database/DatabaseManager"; -import { DatabaseStatusDetail } from "@/components/database/DatabaseStatus"; import { SystemConfig } from "@/components/system/SystemConfig"; import { toast } from "sonner"; @@ -127,92 +125,6 @@ export default function AdminDashboard() {
- {/* 数据库模式提示 */} - {!isLocalMode && ( - - - - 当前使用 {dbMode === 'supabase' ? 'Supabase 云端' : '演示'} 模式。 - 数据库管理功能仅在本地数据库模式下完全可用。 - {dbMode === 'demo' && ' 请在 .env 文件中配置 VITE_USE_LOCAL_DB=true 启用本地数据库。'} - - - )} - - {/* 数据库状态卡片 */} - - - {/* 统计概览 */} -
- - -
-
-

项目总数

-

{stats.totalProjects}

-

- 活跃: {stats.activeProjects} -

-
-
- -
-
-
-
- - - -
-
-

审计任务

-

{stats.totalTasks}

-

- 已完成: {stats.completedTasks} -

-
-
- -
-
-
-
- - - -
-
-

发现问题

-

{stats.totalIssues}

-

- 已解决: {stats.resolvedIssues} -

-
-
- -
-
-
-
- - - -
-
-

存储使用

-

{stats.storageUsed}

-

- 配额: {stats.storageQuota} -

-
-
- -
-
-
-
-
- {/* 主要内容标签页 */} @@ -470,7 +382,12 @@ export default function AdminDashboard() { - 当前数据库模式: {dbMode === 'local' ? '本地 IndexedDB' : dbMode === 'supabase' ? 'Supabase 云端' : '演示模式'} + 当前数据库模式: { + dbMode === 'api' ? '后端 PostgreSQL 数据库' : + dbMode === 'local' ? '本地 IndexedDB' : + dbMode === 'supabase' ? 'Supabase 云端(已废弃)' : + '演示模式' + } diff --git a/src/pages/AuditTasks.tsx b/frontend/src/pages/AuditTasks.tsx similarity index 100% rename from src/pages/AuditTasks.tsx rename to frontend/src/pages/AuditTasks.tsx diff --git a/frontend/src/pages/ColorDemo.tsx b/frontend/src/pages/ColorDemo.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx similarity index 98% rename from src/pages/Dashboard.tsx rename to frontend/src/pages/Dashboard.tsx index d7f34ad..bcdfcad 100644 --- a/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -519,11 +519,12 @@ export default function Dashboard() {
数据库模式 - {dbMode === 'local' ? '本地' : dbMode === 'supabase' ? '云端' : '演示'} + {dbMode === 'api' ? '后端' : dbMode === 'local' ? '本地' : dbMode === 'supabase' ? '云端' : '演示'}
diff --git a/src/pages/InstantAnalysis.tsx b/frontend/src/pages/InstantAnalysis.tsx similarity index 100% rename from src/pages/InstantAnalysis.tsx rename to frontend/src/pages/InstantAnalysis.tsx diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx new file mode 100644 index 0000000..1e45df2 --- /dev/null +++ b/frontend/src/pages/Login.tsx @@ -0,0 +1,95 @@ +import { useState, FormEvent, useEffect } from 'react'; +import { useNavigate, useLocation } from 'react-router-dom'; +import { useAuth } from '@/shared/context/AuthContext'; +import { apiClient } from '@/shared/api/serverClient'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; +import { toast } from 'sonner'; + +export default function Login() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + const location = useLocation(); + const { login, isAuthenticated } = useAuth(); + + const from = location.state?.from?.pathname || '/'; + + // 监听认证状态,登录成功后自动跳转 + useEffect(() => { + if (isAuthenticated && !loading) { + navigate(from, { replace: true }); + } + }, [isAuthenticated, navigate, from, loading]); + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + setLoading(true); + try { + const formData = new URLSearchParams(); + formData.append('username', email); + formData.append('password', password); + + const response = await apiClient.post('/auth/login', formData, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }); + + await login(response.data.access_token); + toast.success('登录成功'); + // 跳转由 useEffect 监听 isAuthenticated 状态变化自动处理 + } catch (error: any) { + toast.error(error.response?.data?.detail || '登录失败'); + } finally { + setLoading(false); + } + }; + + return ( +
+ + + XCodeReviewer + 请输入您的账号和密码登录 + + +
+
+ + setEmail(e.target.value)} + required + /> +
+
+ + setPassword(e.target.value)} + required + /> +
+ +
+
+ +
+ 还没有账号? navigate('/register')}>立即注册 +
+
+
+
+ ); +} diff --git a/src/pages/LogsPage.tsx b/frontend/src/pages/LogsPage.tsx similarity index 100% rename from src/pages/LogsPage.tsx rename to frontend/src/pages/LogsPage.tsx diff --git a/src/pages/NotFound.tsx b/frontend/src/pages/NotFound.tsx similarity index 100% rename from src/pages/NotFound.tsx rename to frontend/src/pages/NotFound.tsx diff --git a/src/pages/ProjectDetail.tsx b/frontend/src/pages/ProjectDetail.tsx similarity index 99% rename from src/pages/ProjectDetail.tsx rename to frontend/src/pages/ProjectDetail.tsx index fd39b11..c8a7712 100644 --- a/src/pages/ProjectDetail.tsx +++ b/frontend/src/pages/ProjectDetail.tsx @@ -111,8 +111,6 @@ export default function ProjectDetail() { projectId: id, repoUrl: project.repository_url, branch: project.default_branch || 'main', - githubToken: undefined, - gitlabToken: undefined, createdBy: undefined }); diff --git a/src/pages/Projects.tsx b/frontend/src/pages/Projects.tsx similarity index 100% rename from src/pages/Projects.tsx rename to frontend/src/pages/Projects.tsx diff --git a/src/pages/RecycleBin.tsx b/frontend/src/pages/RecycleBin.tsx similarity index 100% rename from src/pages/RecycleBin.tsx rename to frontend/src/pages/RecycleBin.tsx diff --git a/frontend/src/pages/Register.tsx b/frontend/src/pages/Register.tsx new file mode 100644 index 0000000..fc7d75f --- /dev/null +++ b/frontend/src/pages/Register.tsx @@ -0,0 +1,89 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { apiClient } from '@/shared/api/serverClient'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; +import { toast } from 'sonner'; + +export default function Register() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [fullName, setFullName] = useState(''); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + try { + await apiClient.post('/auth/register', { + email, + password, + full_name: fullName, + }); + toast.success('注册成功,请登录'); + navigate('/login'); + } catch (error: any) { + toast.error(error.response?.data?.detail || '注册失败'); + } finally { + setLoading(false); + } + }; + + return ( +
+ + + 注册账号 + 创建一个新账号开始使用 + + +
+
+ + setFullName(e.target.value)} + required + /> +
+
+ + setEmail(e.target.value)} + required + /> +
+
+ + setPassword(e.target.value)} + required + /> +
+ +
+
+ +
+ 已有账号? navigate('/login')}>直接登录 +
+
+
+
+ ); +} + diff --git a/src/pages/SamplePage.tsx b/frontend/src/pages/SamplePage.tsx similarity index 100% rename from src/pages/SamplePage.tsx rename to frontend/src/pages/SamplePage.tsx diff --git a/src/pages/TaskDetail.tsx b/frontend/src/pages/TaskDetail.tsx similarity index 94% rename from src/pages/TaskDetail.tsx rename to frontend/src/pages/TaskDetail.tsx index 00e2589..1d1fdd4 100644 --- a/src/pages/TaskDetail.tsx +++ b/frontend/src/pages/TaskDetail.tsx @@ -29,7 +29,6 @@ import type { AuditTask, AuditIssue } from "@/shared/types"; import { toast } from "sonner"; import ExportReportDialog from "@/components/reports/ExportReportDialog"; import { calculateTaskProgress } from "@/shared/utils/utils"; -import { taskControl } from "@/shared/services/taskControl"; // AI解释解析函数 function parseAIExplanation(aiExplanation: string) { @@ -405,37 +404,6 @@ export default function TaskDetail() { } }; - - const handleCancel = async () => { - if (!id || !task) return; - - if (!confirm('确定要取消此任务吗?已分析的结果将被保留。')) { - return; - } - - // 1. 标记任务为取消状态(让后台循环检测到) - taskControl.cancelTask(id); - - // 2. 立即更新本地状态显示 - setTask(prev => prev ? { ...prev, status: 'cancelled' as const } : prev); - - // 3. 尝试立即更新数据库(后台也会更新,这里是双保险) - try { - await api.updateAuditTask(id, { status: 'cancelled' } as any); - toast.success("任务已取消"); - } catch (error) { - console.error('更新取消状态失败:', error); - toast.warning("任务已标记取消,后台正在停止..."); - } - - // 4. 1秒后再次刷新,确保显示最新状态 - setTimeout(() => { - loadTaskDetail(); - }, 1000); - }; - - - const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('zh-CN', { year: 'numeric', @@ -509,19 +477,6 @@ export default function TaskDetail() { - {/* 运行中或等待中的任务显示取消按钮 */} - {(task.status === 'running' || task.status === 'pending') && ( - - )} - {/* 已完成的任务显示导出按钮 */} {task.status === 'completed' && ( -
- -
-

导入数据

-

- 从 JSON 文件恢复数据 -

- - -
- -
-

初始化数据库

-

- 创建默认用户和基础数据 -

- -
- -
-

清空数据

-

- 删除所有本地数据(不可恢复) -

- -
- - - - - - 提示:本地数据存储在浏览器中,清除浏览器数据会导致数据丢失。 - 建议定期导出备份。 - - - - - ); -} diff --git a/src/features/analysis/services/codeAnalysis.ts b/src/features/analysis/services/codeAnalysis.ts deleted file mode 100644 index b79494b..0000000 --- a/src/features/analysis/services/codeAnalysis.ts +++ /dev/null @@ -1,706 +0,0 @@ -import type { CodeAnalysisResult } from "@/shared/types"; -import { LLMService } from '@/shared/services/llm'; -import { getCurrentLLMApiKey, getCurrentLLMModel, env } from '@/shared/config/env'; -import type { LLMConfig } from '@/shared/services/llm/types'; -import { SUPPORTED_LANGUAGES } from '@/shared/constants'; - -// 基于 LLM 的代码分析引擎 -export class CodeAnalysisEngine { - static getSupportedLanguages(): string[] { - return [...SUPPORTED_LANGUAGES]; - } - - /** - * 创建LLM服务实例 - */ - private static createLLMService(): LLMService { - const apiKey = getCurrentLLMApiKey(); - if (!apiKey) { - throw new Error(`缺少 ${env.LLM_PROVIDER} API Key,请在 .env 中配置`); - } - - const config: LLMConfig = { - provider: env.LLM_PROVIDER as any, - apiKey, - model: getCurrentLLMModel(), - baseUrl: env.LLM_BASE_URL, - timeout: env.LLM_TIMEOUT, - temperature: env.LLM_TEMPERATURE, - maxTokens: env.LLM_MAX_TOKENS, - }; - - return new LLMService(config); - } - - static async analyzeCode(code: string, language: string): Promise { - const llmService = this.createLLMService(); - - // 获取输出语言配置 - const outputLanguage = env.OUTPUT_LANGUAGE || 'zh-CN'; - const isChineseOutput = outputLanguage === 'zh-CN'; - - const schema = `{ - "issues": [ - { - "type": "security|bug|performance|style|maintainability", - "severity": "critical|high|medium|low", - "title": "string", - "description": "string", - "suggestion": "string", - "line": 1, - "column": 1, - "code_snippet": "string", - "ai_explanation": "string", - "xai": { - "what": "string", - "why": "string", - "how": "string", - "learn_more": "string(optional)" - } - } - ], - "quality_score": 0-100, - "summary": { - "total_issues": number, - "critical_issues": number, - "high_issues": number, - "medium_issues": number, - "low_issues": number - }, - "metrics": { - "complexity": 0-100, - "maintainability": 0-100, - "security": 0-100, - "performance": 0-100 - } - }`; - - // 根据配置生成不同语言的提示词 - const systemPrompt = isChineseOutput - ? `⚠️⚠️⚠️ 只输出JSON,禁止输出其他任何格式!禁止markdown!禁止文本分析!⚠️⚠️⚠️ - -你是一个专业的代码审计助手。你的任务是分析代码并返回严格符合JSON Schema的结果。 - -【最重要】输出格式要求: -1. 必须只输出纯JSON对象,从{开始,到}结束 -2. 禁止在JSON前后添加任何文字、说明、markdown标记 -3. 禁止输出\`\`\`json或###等markdown语法 -4. 如果是文档文件(如README),也必须以JSON格式输出分析结果 - -【内容要求】: -1. 所有文本内容必须统一使用简体中文 -2. JSON字符串值中的特殊字符必须正确转义(换行用\\n,双引号用\\",反斜杠用\\\\) -3. code_snippet字段必须使用\\n表示换行 - -请从以下维度全面分析代码: -- 编码规范和代码风格 -- 潜在的 Bug 和逻辑错误 -- 性能问题和优化建议 -- 安全漏洞和风险 -- 可维护性和可读性 -- 最佳实践和设计模式 - -输出格式必须严格符合以下 JSON Schema: - -${schema} - -注意: -- title: 问题的简短标题(中文) -- description: 详细描述问题(中文) -- suggestion: 具体的修复建议(中文) -- line: 问题所在的行号(从1开始计数,必须准确对应代码中的行号) -- column: 问题所在的列号(从1开始计数,指向问题代码的起始位置) -- code_snippet: 包含问题的代码片段(建议包含问题行及其前后1-2行作为上下文,保持原始缩进格式) -- ai_explanation: AI 的深入解释(中文) -- xai.what: 这是什么问题(中文) -- xai.why: 为什么会有这个问题(中文) -- xai.how: 如何修复这个问题(中文) - -【重要】关于行号和代码片段: -1. line 必须是问题代码的行号!!!代码左侧有"行号|"标注,例如"25| const x = 1"表示第25行,line字段必须填25 -2. column 是问题代码在该行中的起始列位置(从1开始,不包括"行号|"前缀部分) -3. code_snippet 应该包含问题代码及其上下文(前后各1-2行),去掉"行号|"前缀,保持原始代码的缩进 -4. 如果代码片段包含多行,必须使用 \\n 表示换行符(这是JSON的要求) -5. 如果无法确定准确的行号,不要填写line和column字段(不要填0) - -【严格禁止】: -- 禁止在任何字段中使用英文,所有内容必须是简体中文 -- 禁止在JSON字符串值中使用真实换行符,必须用\\n转义 -- 禁止输出markdown代码块标记(如\`\`\`json) - -示例(假设代码中第25行是 "25| config[password] = user_password"): -{ - "issues": [{ - "type": "security", - "severity": "high", - "title": "密码明文存储", - "description": "密码以明文形式存储在配置文件中", - "suggestion": "使用加密算法对密码进行加密存储", - "line": 25, - "column": 5, - "code_snippet": "config[password] = user_password\\nconfig.save()", - "ai_explanation": "明文存储密码存在安全风险", - "xai": { - "what": "密码未加密直接存储", - "why": "容易被未授权访问获取", - "how": "使用AES等加密算法加密后再存储" - } - }], - "quality_score": 75, - "summary": {"total_issues": 1, "critical_issues": 0, "high_issues": 1, "medium_issues": 0, "low_issues": 0}, - "metrics": {"complexity": 80, "maintainability": 75, "security": 70, "performance": 85} -} - -⚠️ 重要提醒:line字段必须从代码左侧的行号标注中读取,不要猜测或填0!` - : `⚠️⚠️⚠️ OUTPUT JSON ONLY! NO OTHER FORMAT! NO MARKDOWN! NO TEXT ANALYSIS! ⚠️⚠️⚠️ - -You are a professional code auditing assistant. Your task is to analyze code and return results in strict JSON Schema format. - -【MOST IMPORTANT】Output format requirements: -1. MUST output pure JSON object only, starting with { and ending with } -2. NO text, explanation, or markdown markers before or after JSON -3. NO \`\`\`json or ### markdown syntax -4. Even for document files (like README), output analysis in JSON format - -【Content requirements】: -1. All text content MUST be in English ONLY -2. Special characters in JSON strings must be properly escaped (\\n for newlines, \\" for quotes, \\\\ for backslashes) -3. code_snippet field MUST use \\n for newlines - -Please comprehensively analyze the code from the following dimensions: -- Coding standards and code style -- Potential bugs and logical errors -- Performance issues and optimization suggestions -- Security vulnerabilities and risks -- Maintainability and readability -- Best practices and design patterns - -The output format MUST strictly conform to the following JSON Schema: - -${schema} - -Note: -- title: Brief title of the issue (in English) -- description: Detailed description of the issue (in English) -- suggestion: Specific fix suggestions (in English) -- line: Line number where the issue occurs (1-indexed, must accurately correspond to the line in the code) -- column: Column number where the issue starts (1-indexed, pointing to the start position of the problematic code) -- code_snippet: Code snippet containing the issue (should include the problem line plus 1-2 lines before and after for context, preserve original indentation) -- ai_explanation: AI's in-depth explanation (in English) -- xai.what: What is this issue (in English) -- xai.why: Why does this issue exist (in English) -- xai.how: How to fix this issue (in English) - -【IMPORTANT】About line numbers and code snippets: -1. 'line' MUST be the line number from code!!! Code has "lineNumber|" prefix, e.g. "25| const x = 1" means line 25, you MUST set line to 25 -2. 'column' is the starting column position in that line (1-indexed, excluding the "lineNumber|" prefix) -3. 'code_snippet' should include the problematic code with context (1-2 lines before/after), remove "lineNumber|" prefix, preserve indentation -4. If code snippet has multiple lines, use \\n for newlines (JSON requirement) -5. If you cannot determine the exact line number, do NOT fill line and column fields (don't use 0) - -【STRICTLY PROHIBITED】: -- NO Chinese characters in any field - English ONLY -- NO real newline characters in JSON string values - must use \\n -- NO markdown code block markers (like \`\`\`json) - -Example (assuming line 25 in code is "25| config[password] = user_password"): -{ - "issues": [{ - "type": "security", - "severity": "high", - "title": "Plain text password storage", - "description": "Password is stored in plain text in config file", - "suggestion": "Use encryption algorithm to encrypt password before storage", - "line": 25, - "column": 5, - "code_snippet": "config[password] = user_password\\nconfig.save()", - "ai_explanation": "Storing passwords in plain text poses security risks", - "xai": { - "what": "Password stored without encryption", - "why": "Easy to access by unauthorized users", - "how": "Use AES or similar encryption before storing" - } - }], - "quality_score": 75, - "summary": {"total_issues": 1, "critical_issues": 0, "high_issues": 1, "medium_issues": 0, "low_issues": 0}, - "metrics": {"complexity": 80, "maintainability": 75, "security": 70, "performance": 85} -} - -⚠️ CRITICAL: Read line numbers from the "lineNumber|" prefix on the left of each code line. Do NOT guess or use 0!`; - - // 为代码添加行号,帮助LLM准确定位问题 - const codeWithLineNumbers = code.split('\n').map((line, idx) => `${idx + 1}| ${line}`).join('\n'); - - const userPrompt = isChineseOutput - ? `编程语言: ${language} - -⚠️ 代码已标注行号(格式:行号| 代码内容),请根据行号准确填写 line 字段! - -请分析以下代码: - -${codeWithLineNumbers}` - : `Programming Language: ${language} - -⚠️ Code is annotated with line numbers (format: lineNumber| code), please fill the 'line' field accurately based on these numbers! - -Please analyze the following code: - -${codeWithLineNumbers}`; - - let text = ''; - try { - console.log('🚀 开始调用 LLM 分析...'); - console.log(`📡 提供商: ${env.LLM_PROVIDER}`); - console.log(`🤖 模型: ${getCurrentLLMModel()}`); - console.log(`🔗 Base URL: ${env.LLM_BASE_URL || '(默认)'}`); - - // 使用新的LLM服务进行分析 - const response = await llmService.complete({ - messages: [ - { role: 'system', content: systemPrompt }, - { role: 'user', content: userPrompt }, - ], - temperature: 0.2, - }); - text = response.content; - - console.log('✅ LLM 响应成功'); - console.log(`📊 响应长度: ${text.length} 字符`); - console.log(`📝 响应内容预览: ${text.substring(0, 200)}...`); - } catch (e: any) { - console.error('LLM分析失败:', e); - - // 构造更友好的错误消息 - const errorMsg = e.message || '未知错误'; - const provider = env.LLM_PROVIDER; - - // 抛出详细的错误信息给前端 - throw new Error( - `${provider} API调用失败\n\n` + - `错误详情:${errorMsg}\n\n` + - `配置检查:\n` + - `- 提供商:${provider}\n` + - `- 模型:${getCurrentLLMModel() || '(使用默认)'}\n` + - `- API Key:${getCurrentLLMApiKey() ? '已配置' : '未配置'}\n` + - `- 超时设置:${env.LLM_TIMEOUT}ms\n\n` + - `请检查.env配置文件或尝试切换其他LLM提供商` - ); - } - const parsed = this.safeParseJson(text); - - // 如果解析失败,抛出错误而不是返回默认值 - if (!parsed) { - const provider = env.LLM_PROVIDER; - const currentModel = getCurrentLLMModel(); - - let suggestions = ''; - if (provider === 'ollama') { - suggestions = - `建议解决方案:\n` + - `1. 升级到更强的模型(推荐):\n` + - ` ollama pull codellama\n` + - ` ollama pull qwen2.5:7b\n` + - `2. 更新配置文件 .env:\n` + - ` VITE_LLM_MODEL=codellama\n` + - `3. 重启应用后重试\n\n` + - `注意:超轻量模型仅适合测试连接,实际使用需要更强的模型。`; - } else { - suggestions = - `建议解决方案:\n` + - `1. 尝试更换更强大的模型(在 .env 中修改 VITE_LLM_MODEL)\n` + - `2. 检查当前模型是否支持结构化输出(JSON 格式)\n` + - `3. 尝试切换到其他 LLM 提供商:\n` + - ` - Gemini (免费额度充足)\n` + - ` - OpenAI GPT (稳定可靠)\n` + - ` - Claude (代码理解能力强)\n` + - ` - DeepSeek (性价比高)\n` + - `4. 如果使用代理,检查网络连接是否稳定\n` + - `5. 增加超时时间(VITE_LLM_TIMEOUT)`; - } - - throw new Error( - `LLM 响应解析失败\n\n` + - `提供商: ${provider}\n` + - `模型: ${currentModel || '(默认)'}\n\n` + - `原因:当前模型返回的内容不是有效的 JSON 格式,\n` + - `这可能是因为模型能力不足或配置不当。\n\n` + - suggestions - ); - } - - console.log('🔍 解析结果:', { - hasIssues: Array.isArray(parsed?.issues), - issuesCount: parsed?.issues?.length || 0, - hasMetrics: !!parsed?.metrics, - hasQualityScore: !!parsed?.quality_score - }); - - const issues = Array.isArray(parsed?.issues) ? parsed.issues : []; - - // 规范化issues,确保数据格式正确 - issues.forEach((issue: any, index: number) => { - // 验证行号和列号的合理性 - if (issue.line !== undefined) { - const originalLine = issue.line; - const parsedLine = parseInt(issue.line); - // 如果行号是0或无效值,设置为undefined而不是1(表示未知位置) - if (isNaN(parsedLine) || parsedLine <= 0) { - console.warn(`⚠️ 问题 #${index + 1} "${issue.title}" 的行号无效: ${originalLine},已设置为 undefined`); - issue.line = undefined; - } else { - issue.line = parsedLine; - } - } - - if (issue.column !== undefined) { - const originalColumn = issue.column; - const parsedColumn = parseInt(issue.column); - // 如果列号是0或无效值,设置为undefined而不是1 - if (isNaN(parsedColumn) || parsedColumn <= 0) { - console.warn(`⚠️ 问题 #${index + 1} "${issue.title}" 的列号无效: ${originalColumn},已设置为 undefined`); - issue.column = undefined; - } else { - issue.column = parsedColumn; - } - } - - // 确保所有文本字段都存在且是字符串类型 - const textFields = ['title', 'description', 'suggestion', 'ai_explanation']; - textFields.forEach(field => { - if (issue[field] && typeof issue[field] !== 'string') { - issue[field] = String(issue[field]); - } - }); - - // code_snippet已经由JSON.parse正确处理,不需要额外处理 - // JSON.parse会自动将\\n转换为真实的换行符,这正是我们想要的 - }); - - const metrics = parsed?.metrics ?? this.estimateMetricsFromIssues(issues); - const qualityScore = parsed?.quality_score ?? this.calculateQualityScore(metrics, issues); - - console.log(`📋 最终发现 ${issues.length} 个问题`); - console.log(`⭐ 质量评分: ${qualityScore}`); - - return { - issues, - quality_score: qualityScore, - summary: parsed?.summary ?? { - total_issues: issues.length, - critical_issues: issues.filter((i: any) => i.severity === 'critical').length, - high_issues: issues.filter((i: any) => i.severity === 'high').length, - medium_issues: issues.filter((i: any) => i.severity === 'medium').length, - low_issues: issues.filter((i: any) => i.severity === 'low').length, - }, - metrics - } as CodeAnalysisResult; - } - - private static safeParseJson(text: string): any { - // 预处理:修复常见的非标准 JSON 格式 - const fixJsonFormat = (str: string): string => { - // 1. 去除前后空白 - str = str.trim(); - - // 2. 修复尾部逗号(JSON 不允许)- 必须在其他处理之前 - str = str.replace(/,(\s*[}\]])/g, '$1'); - - // 3. 修复缺少逗号的问题 - str = str.replace(/\}(\s*)\{/g, '},\n{'); - str = str.replace(/\](\s*)\[/g, '],\n['); - str = str.replace(/\}(\s*)"([^"]+)":/g, '},\n"$2":'); - str = str.replace(/\](\s*)"([^"]+)":/g, '],\n"$2":'); - - // 4. 修复对象/数组后缺少逗号的情况 - str = str.replace(/([}\]])(\s*)(")/g, '$1,\n$3'); - - // 5. 移除多余的逗号 - str = str.replace(/,+/g, ','); - - return str; - }; - - // 清理和修复 JSON 字符串 - const cleanText = (str: string): string => { - // 移除 BOM 和零宽字符 - let cleaned = str - .replace(/^\uFEFF/, '') - .replace(/[\u200B-\u200D\uFEFF]/g, ''); - - // 使用状态机智能处理JSON字符串值中的控制字符 - // 这种方法可以正确处理包含换行符、引号等特殊字符的多行字符串 - let result = ''; - let inString = false; - let isKey = false; // 是否在处理键名 - let prevChar = ''; - - for (let i = 0; i < cleaned.length; i++) { - const char = cleaned[i]; - const nextChar = cleaned[i + 1] || ''; - - // 检测字符串的开始和结束(检查前一个字符不是未转义的反斜杠) - if (char === '"' && prevChar !== '\\') { - if (!inString) { - // 字符串开始 - 判断是键还是值 - // 简单判断:如果前面有冒号,则是值,否则是键 - const beforeQuote = result.slice(Math.max(0, result.length - 10)); - isKey = !beforeQuote.includes(':') || beforeQuote.lastIndexOf(':') < beforeQuote.lastIndexOf('{') || beforeQuote.lastIndexOf(':') < beforeQuote.lastIndexOf(','); - } - inString = !inString; - result += char; - prevChar = char; - continue; - } - - // 在字符串值内部(非键名)处理特殊字符 - if (inString && !isKey) { - const code = char.charCodeAt(0); - - // 转义控制字符 - if (code === 0x0A) { // 换行符 - result += '\\n'; - prevChar = 'n'; // 防止被识别为转义符 - continue; - } else if (code === 0x0D) { // 回车符 - result += '\\r'; - prevChar = 'r'; - continue; - } else if (code === 0x09) { // 制表符 - result += '\\t'; - prevChar = 't'; - continue; - } else if (code < 0x20 || (code >= 0x7F && code <= 0x9F)) { - // 其他控制字符:移除 - prevChar = char; - continue; - } - - // 处理反斜杠 - if (char === '\\' && nextChar && '"\\/bfnrtu'.indexOf(nextChar) === -1) { - // 无效的转义序列,转义反斜杠本身 - result += '\\\\'; - prevChar = '\\'; - continue; - } - - // 移除中文引号(使用Unicode编码避免语法错误) - const charCode = char.charCodeAt(0); - if (charCode === 0x201C || charCode === 0x201D || charCode === 0x2018 || charCode === 0x2019) { - prevChar = char; - continue; - } - } - - // 默认情况:保持字符不变 - result += char; - prevChar = char; - } - - return result; - }; - - // 尝试多种方式解析 - const attempts = [ - // 1. 直接解析原始响应(如果LLM输出格式完美) - () => { - return JSON.parse(text); - }, - // 2. 清理后再解析 - () => { - const cleaned = cleanText(text); - const fixed = fixJsonFormat(cleaned); - return JSON.parse(fixed); - }, - // 3. 提取 JSON 对象(智能匹配,处理字符串中的花括号) - () => { - const cleaned = cleanText(text); - // 找到第一个 { 的位置 - const startIdx = cleaned.indexOf('{'); - if (startIdx === -1) throw new Error('No JSON object found'); - - // 从第一个 { 开始,找到匹配的 },需要考虑字符串中的引号 - let braceCount = 0; - let endIdx = -1; - let inString = false; - let prevChar = ''; - - for (let i = startIdx; i < cleaned.length; i++) { - const char = cleaned[i]; - - // 检测字符串边界(排除转义的引号) - if (char === '"' && prevChar !== '\\') { - inString = !inString; - } - - // 只在字符串外部统计花括号 - if (!inString) { - if (char === '{') braceCount++; - if (char === '}') { - braceCount--; - if (braceCount === 0) { - endIdx = i + 1; - break; - } - } - } - - prevChar = char; - } - - if (endIdx === -1) throw new Error('Incomplete JSON object'); - - const jsonStr = cleaned.substring(startIdx, endIdx); - const fixed = fixJsonFormat(jsonStr); - return JSON.parse(fixed); - }, - // 4. 去除 markdown 代码块 - () => { - const cleaned = cleanText(text); - const codeBlockMatch = cleaned.match(/```(?:json)?\s*(\{[\s\S]*\})\s*```/); - if (codeBlockMatch) { - const fixed = fixJsonFormat(codeBlockMatch[1]); - return JSON.parse(fixed); - } - throw new Error('No code block found'); - }, - // 5. 尝试修复截断的 JSON - () => { - const cleaned = cleanText(text); - const startIdx = cleaned.indexOf('{'); - if (startIdx === -1) throw new Error('Cannot fix truncated JSON'); - - let json = cleaned.substring(startIdx); - // 尝试补全未闭合的结构 - const openBraces = (json.match(/\{/g) || []).length; - const closeBraces = (json.match(/\}/g) || []).length; - const openBrackets = (json.match(/\[/g) || []).length; - const closeBrackets = (json.match(/\]/g) || []).length; - - // 补全缺失的闭合符号 - json += ']'.repeat(Math.max(0, openBrackets - closeBrackets)); - json += '}'.repeat(Math.max(0, openBraces - closeBraces)); - - const fixed = fixJsonFormat(json); - return JSON.parse(fixed); - } - ]; - - let lastError: any = null; - for (let i = 0; i < attempts.length; i++) { - try { - const result = attempts[i](); - if (i > 0) { - console.log(`✅ JSON解析成功(方法 ${i + 1}/${attempts.length})`); - } - return result; - } catch (e) { - lastError = e; - if (i === 0) { - console.warn('直接解析失败,尝试清理后解析...', e); - } else if (i === 2) { - console.warn('提取 JSON 对象后解析失败:', e); - } else if (i === 3) { - console.warn('从代码块提取 JSON 失败:', e); - } - } - } - - // 所有尝试都失败 - console.error('⚠️ 无法解析 LLM 响应为 JSON'); - console.error('原始内容(前500字符):', text.substring(0, 500)); - console.error('解析错误:', lastError); - console.warn('💡 提示: 当前模型可能无法生成有效的 JSON 格式'); - console.warn(' 建议:更换更强大的模型或切换其他 LLM 提供商'); - return null; - } - - private static estimateMetricsFromIssues(issues: any[]) { - const base = 90; - const penalty = Math.min(60, (issues?.length || 0) * 2); - const score = Math.max(0, base - penalty); - return { - complexity: score, - maintainability: score, - security: score, - performance: score - }; - } - - private static calculateQualityScore(metrics: any, issues: any[]): number { - const criticalIssues = issues.filter((i: any) => i.severity === 'critical').length; - const highIssues = issues.filter((i: any) => i.severity === 'high').length; - const mediumIssues = issues.filter((i: any) => i.severity === 'medium').length; - const lowIssues = issues.filter((i: any) => i.severity === 'low').length; - - // 使用更平衡的权重公式,避免分数跌至0 - // 权重相对较低,使用对数缩放来处理大量问题 - const baseScore = 100; - const criticalPenalty = Math.min(40, criticalIssues * 2); - const highPenalty = Math.min(30, highIssues * 1.5); - const mediumPenalty = Math.min(20, mediumIssues); - const lowPenalty = Math.min(10, lowIssues * 0.5); - - const issueScore = Math.max(0, baseScore - (criticalPenalty + highPenalty + mediumPenalty + lowPenalty)); - - const metricsScore = ( - metrics.complexity + - metrics.maintainability + - metrics.security + - metrics.performance - ) / 4; - - // 使用加权平均:问题占30%权重,指标占70%权重 - // 这样可以保证有问题的代码仍然能获得一个合理的分数 - const finalScore = issueScore * 0.3 + metricsScore * 0.7; - - return Math.max(0, Math.min(100, finalScore)); - } - - // 仓库级别的分析(占位保留) - static async analyzeRepository(_repoUrl: string, _branch: string = 'main', _excludePatterns: string[] = []): Promise<{ - taskId: string; - status: 'pending' | 'running' | 'completed' | 'failed'; - }> { - const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - return { taskId, status: 'pending' }; - } - - // GitHub/GitLab集成(占位保留) - static async getRepositories(_token: string, _platform: 'github' | 'gitlab'): Promise { - return [ - { - id: '1', - name: 'example-project', - full_name: 'user/example-project', - description: '示例项目', - html_url: 'https://github.com/user/example-project', - clone_url: 'https://github.com/user/example-project.git', - default_branch: 'main', - language: 'JavaScript', - private: false, - updated_at: new Date().toISOString() - } - ]; - } - - static async getBranches(_repoUrl: string, _token: string): Promise { - return [ - { - name: 'main', - commit: { - sha: 'abc123', - url: 'https://github.com/user/repo/commit/abc123' - }, - protected: true - }, - { - name: 'develop', - commit: { - sha: 'def456', - url: 'https://github.com/user/repo/commit/def456' - }, - protected: false - } - ]; - } -} \ No newline at end of file diff --git a/src/features/projects/services/repoScan.ts b/src/features/projects/services/repoScan.ts deleted file mode 100644 index 524ee6d..0000000 --- a/src/features/projects/services/repoScan.ts +++ /dev/null @@ -1,380 +0,0 @@ -import { api } from "@/shared/config/database"; -import { CodeAnalysisEngine } from "@/features/analysis/services"; -import { taskControl } from "@/shared/services/taskControl"; -import { env } from "@/shared/config/env"; - -type GithubTreeItem = { path: string; type: "blob" | "tree"; size?: number; url: string; sha: string }; - -const TEXT_EXTENSIONS = [ - ".js", ".ts", ".tsx", ".jsx", ".py", ".java", ".go", ".rs", ".cpp", ".c", ".h", ".cc", ".hh", ".cs", ".php", ".rb", ".kt", ".swift", ".sql", ".sh", ".json", ".yml", ".yaml" - // 注意:已移除 .md,因为文档文件会导致LLM返回非JSON格式 -]; -const MAX_FILE_SIZE_BYTES = 200 * 1024; -const MAX_ANALYZE_FILES = env.MAX_ANALYZE_FILES; -const LLM_CONCURRENCY = env.LLM_CONCURRENCY; -const LLM_GAP_MS = env.LLM_GAP_MS; - -const isTextFile = (p: string) => TEXT_EXTENSIONS.some(ext => p.toLowerCase().endsWith(ext)); -const matchExclude = (p: string, ex: string[]) => ex.some(e => p.includes(e.replace(/^\//, "")) || (e.endsWith("/**") && p.startsWith(e.slice(0, -3).replace(/^\//, "")))); - -async function githubApi(url: string, token?: string): Promise { - const headers: Record = { "Accept": "application/vnd.github+json" }; - const t = token || env.GITHUB_TOKEN; - if (t) headers["Authorization"] = `Bearer ${t}`; - const res = await fetch(url, { headers }); - if (!res.ok) { - if (res.status === 403) throw new Error("GitHub API 403:请配置 VITE_GITHUB_TOKEN 或确认仓库权限/频率限制"); - throw new Error(`GitHub API ${res.status}: ${url}`); - } - return res.json() as Promise; -} - -async function gitlabApi(url: string, token?: string): Promise { - const headers: Record = { "Content-Type": "application/json" }; - const t = token || env.GITLAB_TOKEN; - if (t) { - // 支持两种 token 格式: - // 1. 标准 Personal Access Token (glpat-xxx) - // 2. OAuth2 token (从 URL 中提取的纯 token) - headers["PRIVATE-TOKEN"] = t; - } - const res = await fetch(url, { headers }); - if (!res.ok) { - if (res.status === 401) throw new Error("GitLab API 401:请配置 VITE_GITLAB_TOKEN 或确认仓库权限"); - if (res.status === 403) throw new Error("GitLab API 403:请确认仓库权限/频率限制"); - throw new Error(`GitLab API ${res.status}: ${url}`); - } - return res.json() as Promise; -} - -export async function runRepositoryAudit(params: { - projectId: string; - repoUrl: string; - branch?: string; - exclude?: string[]; - githubToken?: string; - gitlabToken?: string; - createdBy?: string; -}) { - const branch = params.branch || "main"; - const excludes = params.exclude || []; - const task = await api.createAuditTask({ - project_id: params.projectId, - task_type: "repository", - branch_name: branch, - exclude_patterns: excludes, - scan_config: {}, - created_by: params.createdBy, - total_files: 0, - scanned_files: 0, - total_lines: 0, - issues_count: 0, - quality_score: 0 - } as any); - - const taskId = (task as any).id as string; - // 基于项目的 repository_type 决定仓库类型,不再使用正则 - const project = await api.getProjectById(params.projectId); - const repoUrl = params.repoUrl || project?.repository_url || ''; - if (!repoUrl) throw new Error('仓库地址为空,请在项目中填写 repository_url'); - const repoTypeKey = project?.repository_type; - const isGitHub = repoTypeKey === 'github'; - const isGitLab = repoTypeKey === 'gitlab'; - const repoType = isGitHub ? "GitHub" : isGitLab ? "GitLab" : "Git"; - - console.log(`🚀 ${repoType}任务已创建: ${taskId},准备启动后台扫描...`); - - // 记录审计任务开始 - import('@/shared/utils/logger').then(({ logger, LogCategory }) => { - logger.info(LogCategory.SYSTEM, `开始审计任务: ${taskId}`, { - taskId, - projectId: params.projectId, - repoUrl, - branch, - repoType, - }); - }); - - // 启动后台审计任务,不阻塞返回 - (async () => { - console.log(`🎬 后台扫描任务开始执行: ${taskId}`); - try { - console.log(`📡 任务 ${taskId}: 正在获取仓库文件列表...`); - - let files: { path: string; url?: string }[] = []; - - if (isGitHub) { - // GitHub 仓库处理 - const m = repoUrl.match(/github\.com\/(.+?)\/(.+?)(?:\.git)?$/i); - if (!m) throw new Error("GitHub 仓库 URL 格式错误,例如 https://github.com/owner/repo"); - const owner = m[1]; - const repo = m[2]; - - const treeUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${encodeURIComponent(branch)}?recursive=1`; - const tree = await githubApi<{ tree: GithubTreeItem[] }>(treeUrl, params.githubToken); - files = (tree.tree || []) - .filter(i => i.type === "blob" && isTextFile(i.path) && !matchExclude(i.path, excludes)) - .map(i => ({ path: i.path, url: `https://raw.githubusercontent.com/${owner}/${repo}/${encodeURIComponent(branch)}/${i.path}` })); - } else if (isGitLab) { - // GitLab 仓库处理(支持自定义域名/IP):基于仓库 URL 动态构建 API 基地址 - const u = new URL(repoUrl); - - // 从 URL 中提取 OAuth2 token(如果存在) - // 格式:https://oauth2:TOKEN@host/path 或 https://TOKEN@host/path - let extractedToken = params.gitlabToken; - if (u.username) { - // 如果 username 是 oauth2,token 在 password 中 - if (u.username === 'oauth2' && u.password) { - extractedToken = u.password; - } - // 如果直接使用 token 作为 username - else if (u.username && !u.password) { - extractedToken = u.username; - } - } - - const base = `${u.protocol}//${u.host}`; // 例如 https://git.dev-rs.com 或 http://192.168.1.10 - // 解析项目路径,支持多级 group/subgroup,去除开头/结尾斜杠与 .git 后缀 - const path = u.pathname.replace(/^\/+|\/+$/g, '').replace(/\.git$/i, ''); - if (!path) { - throw new Error("GitLab 仓库 URL 格式错误,例如 https:////"); - } - const projectPath = encodeURIComponent(path); - - const treeUrl = `${base}/api/v4/projects/${projectPath}/repository/tree?ref=${encodeURIComponent(branch)}&recursive=true&per_page=100`; - console.log(`📡 GitLab API: 获取仓库文件树 - ${treeUrl}`); - const tree = await gitlabApi>(treeUrl, extractedToken); - console.log(`✅ GitLab API: 获取到 ${tree.length} 个项目`); - - files = tree - .filter(i => i.type === "blob" && isTextFile(i.path) && !matchExclude(i.path, excludes)) - .map(i => ({ - path: i.path, - // GitLab 文件 API 路径需要完整的 URL 编码(包括斜杠) - url: `${base}/api/v4/projects/${projectPath}/repository/files/${encodeURIComponent(i.path)}/raw?ref=${encodeURIComponent(branch)}` - })); - - console.log(`📝 GitLab: 过滤后可分析文件 ${files.length} 个`); - if (tree.length >= 100) { - console.warn(`⚠️ GitLab: 文件数量达到API限制(100),可能有文件未被扫描。建议使用排除模式减少文件数。`); - } - } else { - throw new Error("不支持的仓库类型,仅支持 GitHub 和 GitLab 仓库"); - } - - // 采样限制,优先分析较小文件与常见语言 - files = files - .sort((a, b) => (a.path.length - b.path.length)) - .slice(0, MAX_ANALYZE_FILES); - - // 立即更新状态为 running 并设置总文件数,让用户看到进度 - console.log(`📊 任务 ${taskId}: 获取到 ${files.length} 个文件,开始分析`); - await api.updateAuditTask(taskId, { - status: "running", - started_at: new Date().toISOString(), - total_files: files.length, - scanned_files: 0 - } as any); - console.log(`✅ 任务 ${taskId}: 状态已更新为 running,total_files=${files.length}`); - - let totalFiles = 0, totalLines = 0, createdIssues = 0; - let index = 0; - let failedCount = 0; // 失败计数器 - let consecutiveFailures = 0; // 连续失败计数 - const MAX_CONSECUTIVE_FAILURES = 5; // 最大连续失败次数 - const MAX_TOTAL_FAILURES_RATIO = 0.5; // 最大失败率(50%) - - const worker = async () => { - while (true) { - const current = index++; - if (current >= files.length) break; - - // ✓ 检查点1:分析文件前检查是否取消 - if (taskControl.isCancelled(taskId)) { - console.log(`🛑 [检查点1] 任务 ${taskId} 已被用户取消,停止分析(在文件 ${current}/${files.length} 前)`); - return; - } - - // ✓ 检查连续失败次数 - if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) { - console.error(`❌ 任务 ${taskId}: 连续失败 ${consecutiveFailures} 次,停止分析`); - throw new Error(`连续失败 ${consecutiveFailures} 次,可能是 LLM API 服务异常`); - } - - // ✓ 检查总失败率 - if (totalFiles > 10 && failedCount / totalFiles > MAX_TOTAL_FAILURES_RATIO) { - console.error(`❌ 任务 ${taskId}: 失败率过高 (${Math.round(failedCount / totalFiles * 100)}%),停止分析`); - throw new Error(`失败率过高 (${failedCount}/${totalFiles}),建议检查 LLM 配置或切换其他提供商`); - } - - const f = files[current]; - totalFiles++; - try { - // 使用预先构建的 URL(支持 GitHub 和 GitLab) - const rawUrl = f.url!; - const headers: Record = {}; - // 为 GitLab 添加认证 Token - if (isGitLab) { - // 优先使用从 URL 提取的 token,否则使用配置的 token - let token = params.gitlabToken || env.GITLAB_TOKEN; - - // 如果 URL 中包含 OAuth2 token,提取它 - if (repoUrl.includes('@')) { - try { - const urlObj = new URL(repoUrl); - if (urlObj.username === 'oauth2' && urlObj.password) { - token = urlObj.password; - } else if (urlObj.username && !urlObj.password) { - token = urlObj.username; - } - } catch (e) { - // URL 解析失败,使用原有 token - } - } - - if (token) { - headers["PRIVATE-TOKEN"] = token; - } - } - const contentRes = await fetch(rawUrl, { headers }); - if (!contentRes.ok) { await new Promise(r=>setTimeout(r, LLM_GAP_MS)); continue; } - const content = await contentRes.text(); - if (content.length > MAX_FILE_SIZE_BYTES) { await new Promise(r=>setTimeout(r, LLM_GAP_MS)); continue; } - totalLines += content.split(/\r?\n/).length; - const language = (f.path.split(".").pop() || "").toLowerCase(); - const analysis = await CodeAnalysisEngine.analyzeCode(content, language); - - // ✓ 检查点2:LLM分析完成后检查是否取消(最小化浪费) - if (taskControl.isCancelled(taskId)) { - console.log(`🛑 [检查点2] 任务 ${taskId} 在LLM分析完成后检测到取消,跳过保存结果(文件: ${f.path})`); - return; - } - - const issues = analysis.issues || []; - createdIssues += issues.length; - for (const issue of issues) { - await api.createAuditIssue({ - task_id: taskId, - file_path: f.path, - line_number: issue.line || null, - column_number: issue.column || null, - issue_type: issue.type || "maintainability", - severity: issue.severity || "low", - title: issue.title || "Issue", - description: issue.description || null, - suggestion: issue.suggestion || null, - code_snippet: issue.code_snippet || null, - ai_explanation: issue.xai ? JSON.stringify(issue.xai) : (issue.ai_explanation || null), - status: "open", - resolved_by: null, - resolved_at: null - } as any); - } - - // 成功:重置连续失败计数 - consecutiveFailures = 0; - - // 每分析一个文件都更新进度,确保实时性 - console.log(`📈 ${repoType}任务 ${taskId}: 进度 ${totalFiles}/${files.length} (${Math.round(totalFiles/files.length*100)}%)`); - await api.updateAuditTask(taskId, { - status: "running", - total_files: files.length, - scanned_files: totalFiles, - total_lines: totalLines, - issues_count: createdIssues - } as any); - } catch (fileError) { - failedCount++; - consecutiveFailures++; - console.error(`❌ 分析文件失败 (${f.path}): [连续失败${consecutiveFailures}次, 总失败${failedCount}/${totalFiles}]`, fileError); - } - await new Promise(r=>setTimeout(r, LLM_GAP_MS)); - } - }; - - const pool = Array.from({ length: Math.min(LLM_CONCURRENCY, files.length) }, () => worker()); - - try { - await Promise.all(pool); - } catch (workerError: any) { - // Worker 抛出错误(连续失败或失败率过高) - console.error(`❌ 任务 ${taskId} 因错误终止:`, workerError); - await api.updateAuditTask(taskId, { - status: "failed", - total_files: files.length, - scanned_files: totalFiles, - total_lines: totalLines, - issues_count: createdIssues, - completed_at: new Date().toISOString() - } as any); - taskControl.cleanupTask(taskId); - return; - } - - // 再次检查是否被取消 - if (taskControl.isCancelled(taskId)) { - console.log(`🛑 任务 ${taskId} 扫描结束时检测到取消状态`); - await api.updateAuditTask(taskId, { - status: "cancelled", - total_files: files.length, - scanned_files: totalFiles, - total_lines: totalLines, - issues_count: createdIssues, - completed_at: new Date().toISOString() - } as any); - taskControl.cleanupTask(taskId); - return; - } - - // 计算质量评分(如果没有问题则100分,否则根据问题数量递减) - const qualityScore = createdIssues === 0 ? 100 : Math.max(0, 100 - createdIssues * 2); - - await api.updateAuditTask(taskId, { - status: "completed", - total_files: files.length, - scanned_files: totalFiles, - total_lines: totalLines, - issues_count: createdIssues, - quality_score: qualityScore, - completed_at: new Date().toISOString() - } as any); - - // 记录审计完成 - import('@/shared/utils/logger').then(({ logger, LogCategory }) => { - logger.info(LogCategory.SYSTEM, `审计任务完成: ${taskId}`, { - taskId, - totalFiles: files.length, - scannedFiles: totalFiles, - totalLines, - issuesCount: createdIssues, - qualityScore, - failedCount, - }); - }); - - taskControl.cleanupTask(taskId); - } catch (e) { - console.error('❌ GitHub审计任务执行失败:', e); - console.error('错误详情:', e); - - // 记录审计失败 - import('@/shared/utils/errorHandler').then(({ handleError }) => { - handleError(e, `审计任务失败: ${taskId}`); - }); - - try { - await api.updateAuditTask(taskId, { status: "failed" } as any); - } catch (updateError) { - console.error('更新失败状态也失败了:', updateError); - } - } - })().catch(err => { - console.error('⚠️ GitHub后台任务未捕获的错误:', err); - }); - - console.log(`✅ 返回任务ID: ${taskId},后台任务正在执行中...`); - // 立即返回任务ID,让用户可以跳转到任务详情页面 - return taskId; -} - - diff --git a/src/features/projects/services/repoZipScan.ts b/src/features/projects/services/repoZipScan.ts deleted file mode 100644 index e274940..0000000 --- a/src/features/projects/services/repoZipScan.ts +++ /dev/null @@ -1,417 +0,0 @@ -import { unzip } from "fflate"; -import { CodeAnalysisEngine } from "@/features/analysis/services"; -import { api } from "@/shared/config/database"; -import { taskControl } from "@/shared/services/taskControl"; -import { env } from "@/shared/config/env"; - -const TEXT_EXTENSIONS = [ - ".js", ".ts", ".tsx", ".jsx", ".py", ".java", ".go", ".rs", ".cpp", ".c", ".h", ".cc", ".hh", - ".cs", ".php", ".rb", ".kt", ".swift", ".sql", ".sh", ".json", ".yml", ".yaml" - // 注意:已移除 .md,因为文档文件会导致LLM返回非JSON格式 -]; - -const MAX_FILE_SIZE_BYTES = 200 * 1024; // 200KB -const MAX_ANALYZE_FILES = env.MAX_ANALYZE_FILES; - -// 从环境变量读取配置,豆包等API需要更长的延迟 -const LLM_GAP_MS = env.LLM_GAP_MS || 2000; // 默认2秒,避免API限流 - -function isTextFile(path: string): boolean { - return TEXT_EXTENSIONS.some(ext => path.toLowerCase().endsWith(ext)); -} - -function shouldExclude(path: string, excludePatterns: string[]): boolean { - // 排除 Mac 系统文件 - if (path.includes('__MACOSX/') || path.includes('/.DS_Store') || path.match(/\/\._[^/]+$/)) { - return true; - } - - // 排除 IDE 和编辑器配置目录 - const idePatterns = [ - '/.vscode/', - '/.idea/', - '/.vs/', - '/.eclipse/', - '/.settings/' - ]; - if (idePatterns.some(pattern => path.includes(pattern))) { - return true; - } - - // 排除版本控制和依赖目录 - const systemDirs = [ - '/.git/', - '/node_modules/', - '/vendor/', - '/dist/', - '/build/', - '/.next/', - '/.nuxt/', - '/target/', - '/out/', - '/__pycache__/', - '/.pytest_cache/', - '/coverage/', - '/.nyc_output/' - ]; - if (systemDirs.some(dir => path.includes(dir))) { - return true; - } - - // 排除其他隐藏文件(但保留 .gitignore, .env.example 等重要配置) - const allowedHiddenFiles = ['.gitignore', '.env.example', '.editorconfig', '.prettierrc']; - const fileName = path.split('/').pop() || ''; - if (fileName.startsWith('.') && !allowedHiddenFiles.includes(fileName)) { - return true; - } - - // 排除常见的非代码文件 - const excludeExtensions = [ - '.lock', '.log', '.tmp', '.temp', '.cache', - '.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', - '.pdf', '.zip', '.tar', '.gz', '.rar', - '.exe', '.dll', '.so', '.dylib', - '.min.js', '.min.css', '.map' - ]; - if (excludeExtensions.some(ext => path.toLowerCase().endsWith(ext))) { - return true; - } - - // 应用用户自定义的排除模式 - return excludePatterns.some(pattern => { - if (pattern.includes('*')) { - const regex = new RegExp(pattern.replace(/\*/g, '.*')); - return regex.test(path); - } - return path.includes(pattern); - }); -} - -function getLanguageFromPath(path: string): string { - const extension = path.split('.').pop()?.toLowerCase() || ''; - const languageMap: Record = { - 'js': 'javascript', - 'jsx': 'javascript', - 'ts': 'typescript', - 'tsx': 'typescript', - 'py': 'python', - 'java': 'java', - 'go': 'go', - 'rs': 'rust', - 'cpp': 'cpp', - 'c': 'cpp', - 'cc': 'cpp', - 'h': 'cpp', - 'hh': 'cpp', - 'cs': 'csharp', - 'php': 'php', - 'rb': 'ruby', - 'kt': 'kotlin', - 'swift': 'swift' - }; - - return languageMap[extension] || 'text'; -} - -export async function scanZipFile(params: { - projectId: string; - zipFile: File; - excludePatterns?: string[]; - createdBy?: string; -}): Promise { - const { projectId, zipFile, excludePatterns = [], createdBy } = params; - - // 创建审计任务,初始化进度字段 - const task = await api.createAuditTask({ - project_id: projectId, - task_type: "repository", - branch_name: "uploaded", - exclude_patterns: excludePatterns, - scan_config: { source: "zip_upload" }, - created_by: createdBy, - total_files: 0, - scanned_files: 0, - total_lines: 0, - issues_count: 0, - quality_score: 0 - } as any); - - const taskId = (task as any).id; - - console.log(`🚀 ZIP任务已创建: ${taskId},准备启动后台扫描...`); - - // 记录审计任务开始 - import('@/shared/utils/logger').then(({ logger, LogCategory }) => { - logger.info(LogCategory.SYSTEM, `开始ZIP文件审计: ${taskId}`, { - taskId, - projectId, - fileName: zipFile.name, - fileSize: zipFile.size, - }); - }); - - // 启动后台扫描任务,不阻塞返回 - (async () => { - console.log(`🎬 后台扫描任务开始执行: ${taskId}`); - try { - // 更新任务状态为运行中 - console.log(`📋 ZIP任务 ${taskId}: 开始更新状态为 running`); - await api.updateAuditTask(taskId, { - status: "running", - started_at: new Date().toISOString(), - total_files: 0, - scanned_files: 0 - } as any); - console.log(`✅ ZIP任务 ${taskId}: 状态已更新为 running`); - - // 读取ZIP文件 - const arrayBuffer = await zipFile.arrayBuffer(); - const uint8Array = new Uint8Array(arrayBuffer); - - await new Promise((resolve, reject) => { - unzip(uint8Array, async (err, unzipped) => { - if (err) { - await api.updateAuditTask(taskId, { status: "failed" } as any); - reject(new Error(`ZIP文件解压失败: ${err.message}`)); - return; - } - - try { - // 筛选需要分析的文件 - const filesToAnalyze: Array<{ path: string; content: string }> = []; - - for (const [path, data] of Object.entries(unzipped)) { - // 跳过目录 - if (path.endsWith('/')) continue; - - // 检查文件类型和排除模式 - if (!isTextFile(path) || shouldExclude(path, excludePatterns)) continue; - - // 检查文件大小 - if (data.length > MAX_FILE_SIZE_BYTES) continue; - - try { - const content = new TextDecoder('utf-8').decode(data); - filesToAnalyze.push({ path, content }); - } catch (decodeError) { - // 跳过无法解码的文件 - continue; - } - } - - // 限制分析文件数量 - const limitedFiles = filesToAnalyze - .sort((a, b) => a.path.length - b.path.length) // 优先分析路径较短的文件 - .slice(0, MAX_ANALYZE_FILES); - - let totalFiles = limitedFiles.length; - let scannedFiles = 0; - let totalLines = 0; - let totalIssues = 0; - let qualityScores: number[] = []; - let failedFiles = 0; - - // 更新总文件数 - console.log(`📊 ZIP任务 ${taskId}: 设置总文件数 ${totalFiles}`); - await api.updateAuditTask(taskId, { - status: "running", - total_files: totalFiles, - scanned_files: 0, - total_lines: 0, - issues_count: 0 - } as any); - - // 分析每个文件 - for (const file of limitedFiles) { - // ✓ 检查点1:分析文件前检查是否取消 - if (taskControl.isCancelled(taskId)) { - console.log(`🛑 [检查点1] 任务 ${taskId} 已被用户取消(${scannedFiles}/${totalFiles} 完成),停止分析`); - await api.updateAuditTask(taskId, { - status: "cancelled", - total_files: totalFiles, - scanned_files: scannedFiles, - total_lines: totalLines, - issues_count: totalIssues, - completed_at: new Date().toISOString() - } as any); - taskControl.cleanupTask(taskId); - resolve(); - return; - } - - try { - const language = getLanguageFromPath(file.path); - const lines = file.content.split(/\r?\n/).length; - totalLines += lines; - - // 使用AI分析代码 - const analysis = await CodeAnalysisEngine.analyzeCode(file.content, language); - - // ✓ 检查点2:LLM分析完成后检查是否取消(最小化浪费) - if (taskControl.isCancelled(taskId)) { - console.log(`🛑 [检查点2] 任务 ${taskId} 在LLM分析完成后检测到取消,跳过保存结果(文件: ${file.path})`); - await api.updateAuditTask(taskId, { - status: "cancelled", - total_files: totalFiles, - scanned_files: scannedFiles, - total_lines: totalLines, - issues_count: totalIssues, - completed_at: new Date().toISOString() - } as any); - taskControl.cleanupTask(taskId); - resolve(); - return; - } - - qualityScores.push(analysis.quality_score); - - // 保存发现的问题 - for (const issue of analysis.issues) { - await api.createAuditIssue({ - task_id: taskId, - file_path: file.path, - line_number: issue.line || null, - column_number: issue.column || null, - issue_type: issue.type || "maintainability", - severity: issue.severity || "low", - title: issue.title || "Issue", - description: issue.description || null, - suggestion: issue.suggestion || null, - code_snippet: issue.code_snippet || null, - ai_explanation: issue.ai_explanation || null, - status: "open" - } as any); - - totalIssues++; - } - - scannedFiles++; - - // 每分析一个文件都更新进度,确保实时性 - console.log(`📈 ZIP任务 ${taskId}: 进度 ${scannedFiles}/${totalFiles} (${Math.round(scannedFiles/totalFiles*100)}%)`); - await api.updateAuditTask(taskId, { - status: "running", - total_files: totalFiles, - scanned_files: scannedFiles, - total_lines: totalLines, - issues_count: totalIssues - } as any); - - // 添加延迟避免API限制(已分析成功,正常延迟) - await new Promise(resolve => setTimeout(resolve, LLM_GAP_MS)); - } catch (analysisError) { - failedFiles++; - scannedFiles++; // 即使失败也要增加计数 - - console.error(`❌ 分析文件 ${file.path} 失败 (${failedFiles}/${scannedFiles}):`, analysisError); - - // 如果是API频率限制错误,增加较长延迟 - const errorMsg = (analysisError as Error).message || ''; - if (errorMsg.includes('频率超限') || errorMsg.includes('429') || errorMsg.includes('Too Many Requests')) { - // 检测到限流,逐步增加延迟时间 - const waitTime = Math.min(60000, 10000 + failedFiles * 5000); // 10秒起步,每次失败增加5秒,最多60秒 - console.warn(`⏳ API频率限制!等待${waitTime/1000}秒后继续... (已失败: ${failedFiles}次)`); - await new Promise(resolve => setTimeout(resolve, waitTime)); - } else { - // 其他错误,等待较短时间 - await new Promise(resolve => setTimeout(resolve, LLM_GAP_MS)); - } - - // 更新进度(即使失败也要显示进度) - console.log(`📈 ZIP任务 ${taskId}: 进度 ${scannedFiles}/${totalFiles} (${Math.round(scannedFiles/totalFiles*100)}%) - 失败: ${failedFiles}`); - await api.updateAuditTask(taskId, { - status: "running", - total_files: totalFiles, - scanned_files: scannedFiles, - total_lines: totalLines, - issues_count: totalIssues - } as any); - } - } - - // 计算平均质量分 - const avgQualityScore = qualityScores.length > 0 - ? qualityScores.reduce((sum, score) => sum + score, 0) / qualityScores.length - : 0; - - // 判断任务完成状态 - const successRate = totalFiles > 0 ? ((scannedFiles - failedFiles) / totalFiles) * 100 : 0; - const taskStatus = failedFiles >= totalFiles ? "failed" : "completed"; - - console.log(`📊 扫描完成统计: 总计${totalFiles}个文件, 成功${scannedFiles - failedFiles}个, 失败${failedFiles}个, 成功率${successRate.toFixed(1)}%`); - - if (failedFiles > 0 && failedFiles < totalFiles) { - console.warn(`⚠️ 部分文件分析失败,但任务标记为完成。建议检查.env配置或更换LLM提供商`); - } - - // 更新任务完成状态 - await api.updateAuditTask(taskId, { - status: taskStatus, - total_files: totalFiles, - scanned_files: scannedFiles, - total_lines: totalLines, - issues_count: totalIssues, - quality_score: avgQualityScore, - completed_at: new Date().toISOString() - } as any); - - // 记录审计完成 - import('@/shared/utils/logger').then(({ logger, LogCategory }) => { - logger.info(LogCategory.SYSTEM, `ZIP审计任务完成: ${taskId}`, { - taskId, - status: taskStatus, - totalFiles, - scannedFiles, - failedFiles, - totalLines, - issuesCount: totalIssues, - qualityScore: avgQualityScore, - successRate: successRate.toFixed(1) + '%', - }); - }); - - resolve(); - } catch (processingError) { - await api.updateAuditTask(taskId, { status: "failed" } as any); - - // 记录处理错误 - import('@/shared/utils/errorHandler').then(({ handleError }) => { - handleError(processingError, `ZIP审计任务处理失败: ${taskId}`); - }); - - reject(processingError); - } - }); - }); - } catch (error) { - console.error('❌ ZIP扫描任务执行失败:', error); - console.error('错误详情:', error); - try { - await api.updateAuditTask(taskId, { status: "failed" } as any); - } catch (updateError) { - console.error('更新失败状态也失败了:', updateError); - } - } - })().catch(err => { - console.error('⚠️ 后台任务未捕获的错误:', err); - }); - - console.log(`✅ 返回任务ID: ${taskId},后台任务正在执行中...`); - // 立即返回任务ID,让用户可以看到进度 - return taskId; -} - -export function validateZipFile(file: File): { valid: boolean; error?: string } { - // 检查文件类型 - if (!file.type.includes('zip') && !file.name.toLowerCase().endsWith('.zip')) { - return { valid: false, error: '请上传ZIP格式的文件' }; - } - - // 检查文件大小 (限制为100MB) - const maxSize = 100 * 1024 * 1024; - if (file.size > maxSize) { - return { valid: false, error: '文件大小不能超过100MB' }; - } - - return { valid: true }; -} \ No newline at end of file diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx deleted file mode 100644 index 87bdc93..0000000 --- a/src/pages/Login.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { LoginPanel } from "miaoda-auth-react"; -import { api } from "@/shared/config/database"; - -const login_config = { - title: 'XCodeReviewer', - desc: '登录以开始代码质量分析', - onLoginSuccess: async (user: any) => { - try { - const existingProfile = await api.getProfilesById(user.id); - if (!existingProfile) { - const ProfilesLength = await api.getProfilesCount(); - const isFirstUser = ProfilesLength === 0; - await api.createProfiles({ - id: user.id, - phone: user.phone, - role: isFirstUser ? 'admin' : 'member' - }); - } - } catch (error) { - console.error('User initialization failed:', error); - } - }, - privacyPolicyUrl: import.meta.env.VITE_PRIVACY_POLICY_URL, - userPolicyUrl: import.meta.env.VITE_USER_POLICY_URL, - showPolicy: import.meta.env.VITE_SHOW_POLICY, - policyPrefix: import.meta.env.VITE_POLICY_PREFIX -}; - -export default function Login() { - return ( -
-
-
-
- - - -
-

XCodeReviewer

-

基于AI的代码质量分析平台

-
- -
- -
- -
-

🔍 支持代码仓库审计和即时代码分析

-

🛡️ 提供安全漏洞检测和性能优化建议

-
-
-
- ); -} \ No newline at end of file diff --git a/src/shared/config/database.ts b/src/shared/config/database.ts deleted file mode 100644 index 473a3ea..0000000 --- a/src/shared/config/database.ts +++ /dev/null @@ -1,659 +0,0 @@ -import { createClient } from "@supabase/supabase-js"; -import { localDB } from "./localDatabase"; -import type { - Profile, - Project, - ProjectMember, - AuditTask, - AuditIssue, - InstantAnalysis, - CreateProjectForm, - CreateAuditTaskForm, - InstantAnalysisForm -} from "../types/index"; - -const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; -const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; -const useLocalDB = import.meta.env.VITE_USE_LOCAL_DB === 'true'; - -const isValidUuid = (value?: string): boolean => { - if (!value) return false; - return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value); -}; - -// 检查是否配置了 Supabase -const hasSupabaseConfig = supabaseUrl && supabaseAnonKey; - -// 如果没有配置 Supabase,使用虚拟配置避免错误 -const finalSupabaseUrl = hasSupabaseConfig ? supabaseUrl : 'https://demo.supabase.co'; -const finalSupabaseKey = hasSupabaseConfig ? supabaseAnonKey : 'demo-key'; - -export const supabase = hasSupabaseConfig ? createClient(finalSupabaseUrl, finalSupabaseKey, { - global: { - fetch: undefined - }, - auth: { - storageKey: (import.meta.env.VITE_APP_ID || "sb") + "-auth-token" - } -}) : null; - -// 数据库模式:local(本地IndexedDB)、supabase(云端)、demo(演示模式) -export const dbMode = useLocalDB ? 'local' : (hasSupabaseConfig ? 'supabase' : 'demo'); -export const isDemoMode = dbMode === 'demo'; -export const isLocalMode = dbMode === 'local'; - -// 演示数据 -const demoProfile: Profile = { - id: 'demo-user', - phone: undefined, - email: 'demo@xcodereviewer.com', - full_name: 'Demo User', - avatar_url: undefined, - role: 'admin', - github_username: 'demo-user', - gitlab_username: undefined, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString() -}; - -// 用户相关API -export const api = { - // Profile相关 - async getProfilesById(id: string): Promise { - if (isDemoMode) { - return demoProfile; - } - - if (isLocalMode) { - return localDB.getProfileById(id); - } - - if (!supabase) return null; - - const { data, error } = await supabase - .from('profiles') - .select('*') - .eq('id', id) - .limit(1); - - if (error) throw error; - return Array.isArray(data) && data.length > 0 ? data[0] : null; - }, - - async getProfilesCount(): Promise { - if (isDemoMode) { - return 1; - } - - if (isLocalMode) { - return localDB.getProfilesCount(); - } - - if (!supabase) return 0; - - const { count, error } = await supabase - .from('profiles') - .select('*', { count: 'exact', head: true }); - - if (error) throw error; - return count || 0; - }, - - async createProfiles(profile: Partial): Promise { - if (isLocalMode) { - return localDB.createProfile(profile); - } - - if (!supabase) throw new Error('Database not available'); - - const { data, error } = await supabase - .from('profiles') - .insert([{ - id: profile.id, - phone: profile.phone || null, - email: profile.email || null, - full_name: profile.full_name || null, - avatar_url: profile.avatar_url || null, - role: profile.role || 'member', - github_username: profile.github_username || null, - gitlab_username: profile.gitlab_username || null - }]) - .select() - .limit(1); - - if (error) throw error; - return Array.isArray(data) && data.length > 0 ? data[0] : {} as Profile; - }, - - async updateProfile(id: string, updates: Partial): Promise { - if (isLocalMode) { - return localDB.updateProfile(id, updates); - } - - if (!supabase) throw new Error('Database not available'); - - const { data, error } = await supabase - .from('profiles') - .update(updates) - .eq('id', id) - .select() - .limit(1); - - if (error) throw error; - return Array.isArray(data) && data.length > 0 ? data[0] : {} as Profile; - }, - - async getAllProfiles(): Promise { - if (isLocalMode) { - return localDB.getAllProfiles(); - } - - if (!supabase) return []; - - const { data, error } = await supabase - .from('profiles') - .select('*') - .order('created_at', { ascending: false }); - - if (error) throw error; - return Array.isArray(data) ? data : []; - }, - - // Project相关 - async getProjects(): Promise { - if (isDemoMode) { - return [{ - id: 'demo-project-1', - name: 'Demo Project', - description: '这是一个演示项目,展示 XCodeReviewer 的功能', - repository_url: 'https://github.com/demo/project', - repository_type: 'github', - default_branch: 'main', - programming_languages: JSON.stringify(['TypeScript', 'JavaScript', 'React']), - owner_id: 'demo-user', - owner: demoProfile, - is_active: true, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString() - }]; - } - - if (isLocalMode) { - return localDB.getProjects(); - } - - if (!supabase) return []; - - const { data, error } = await supabase - .from('projects') - .select(` - *, - owner:profiles!projects_owner_id_fkey(*) - `) - .eq('is_active', true) - .order('created_at', { ascending: false }); - - if (error) throw error; - return Array.isArray(data) ? data : []; - }, - - async getProjectById(id: string): Promise { - if (isLocalMode) { - return localDB.getProjectById(id); - } - - if (!supabase) return null; - - const { data, error } = await supabase - .from('projects') - .select(` - *, - owner:profiles!projects_owner_id_fkey(*) - `) - .eq('id', id) - .limit(1); - - if (error) throw error; - return Array.isArray(data) && data.length > 0 ? data[0] : null; - }, - - async createProject(project: CreateProjectForm & { owner_id?: string }): Promise { - if (isLocalMode) { - return localDB.createProject(project); - } - - if (!supabase) throw new Error('Database not available'); - - const { data, error } = await supabase - .from('projects') - .insert([{ - name: project.name, - description: project.description || null, - repository_url: project.repository_url || null, - repository_type: project.repository_type || 'other', - default_branch: project.default_branch || 'main', - programming_languages: JSON.stringify(project.programming_languages || []), - owner_id: isValidUuid(project.owner_id) ? project.owner_id : null, - is_active: true - }]) - .select(` - *, - owner:profiles!projects_owner_id_fkey(*) - `) - .limit(1); - - if (error) throw error; - return Array.isArray(data) && data.length > 0 ? data[0] : {} as Project; - }, - - async updateProject(id: string, updates: Partial): Promise { - if (isLocalMode) { - return localDB.updateProject(id, updates); - } - - if (!supabase) throw new Error('Database not available'); - - const updateData: any = { ...updates }; - if (updates.programming_languages) { - updateData.programming_languages = JSON.stringify(updates.programming_languages); - } - - const { data, error } = await supabase - .from('projects') - .update(updateData) - .eq('id', id) - .select(` - *, - owner:profiles!projects_owner_id_fkey(*) - `) - .limit(1); - - if (error) throw error; - return Array.isArray(data) && data.length > 0 ? data[0] : {} as Project; - }, - - async deleteProject(id: string): Promise { - if (isLocalMode) { - return localDB.deleteProject(id); - } - - if (!supabase) throw new Error('Database not available'); - - const { error } = await supabase - .from('projects') - .update({ is_active: false }) - .eq('id', id); - - if (error) throw error; - }, - - async getDeletedProjects(): Promise { - if (isDemoMode) { - return []; - } - - if (isLocalMode) { - return localDB.getDeletedProjects(); - } - - if (!supabase) return []; - - const { data, error } = await supabase - .from('projects') - .select(` - *, - owner:profiles!projects_owner_id_fkey(*) - `) - .eq('is_active', false) - .order('updated_at', { ascending: false }); - - if (error) throw error; - return Array.isArray(data) ? data : []; - }, - - async restoreProject(id: string): Promise { - if (isLocalMode) { - return localDB.restoreProject(id); - } - - if (!supabase) throw new Error('Database not available'); - - const { error } = await supabase - .from('projects') - .update({ is_active: true, updated_at: new Date().toISOString() }) - .eq('id', id); - - if (error) throw error; - }, - - async permanentlyDeleteProject(id: string): Promise { - if (isLocalMode) { - return localDB.permanentlyDeleteProject(id); - } - - if (!supabase) throw new Error('Database not available'); - - const { error } = await supabase - .from('projects') - .delete() - .eq('id', id); - - if (error) throw error; - }, - - // ProjectMember相关 - async getProjectMembers(projectId: string): Promise { - if (isLocalMode) { - return localDB.getProjectMembers(projectId); - } - - if (!supabase) return []; - - const { data, error } = await supabase - .from('project_members') - .select(` - *, - user:profiles!project_members_user_id_fkey(*), - project:projects!project_members_project_id_fkey(*) - `) - .eq('project_id', projectId) - .order('joined_at', { ascending: false }); - - if (error) throw error; - return Array.isArray(data) ? data : []; - }, - - async addProjectMember(projectId: string, userId: string, role: string = 'member'): Promise { - if (isLocalMode) { - return localDB.addProjectMember(projectId, userId, role); - } - - if (!supabase) throw new Error('Database not available'); - - const { data, error } = await supabase - .from('project_members') - .insert([{ - project_id: projectId, - user_id: userId, - role: role, - permissions: '{}' - }]) - .select(` - *, - user:profiles!project_members_user_id_fkey(*), - project:projects!project_members_project_id_fkey(*) - `) - .limit(1); - - if (error) throw error; - return Array.isArray(data) && data.length > 0 ? data[0] : {} as ProjectMember; - }, - - // AuditTask相关 - async getAuditTasks(projectId?: string): Promise { - if (isLocalMode) { - return localDB.getAuditTasks(projectId); - } - - if (!supabase) return []; - - let query = supabase - .from('audit_tasks') - .select(` - *, - project:projects!audit_tasks_project_id_fkey(*), - creator:profiles!audit_tasks_created_by_fkey(*) - `); - - if (projectId) { - query = query.eq('project_id', projectId); - } - - const { data, error } = await query.order('created_at', { ascending: false }); - - if (error) throw error; - return Array.isArray(data) ? data : []; - }, - - async getAuditTaskById(id: string): Promise { - if (isLocalMode) { - return localDB.getAuditTaskById(id); - } - - if (!supabase) return null; - - const { data, error } = await supabase - .from('audit_tasks') - .select(` - *, - project:projects!audit_tasks_project_id_fkey(*), - creator:profiles!audit_tasks_created_by_fkey(*) - `) - .eq('id', id) - .limit(1); - - if (error) throw error; - return Array.isArray(data) && data.length > 0 ? data[0] : null; - }, - - async createAuditTask(task: CreateAuditTaskForm & { created_by: string }): Promise { - if (isLocalMode) { - return localDB.createAuditTask(task); - } - - if (!supabase) throw new Error('Database not available'); - - const { data, error } = await supabase - .from('audit_tasks') - .insert([{ - project_id: task.project_id, - task_type: task.task_type, - branch_name: task.branch_name || null, - exclude_patterns: JSON.stringify(task.exclude_patterns || []), - scan_config: JSON.stringify(task.scan_config || {}), - created_by: task.created_by, - status: 'pending' - }]) - .select(` - *, - project:projects!audit_tasks_project_id_fkey(*), - creator:profiles!audit_tasks_created_by_fkey(*) - `) - .limit(1); - - if (error) throw error; - return Array.isArray(data) && data.length > 0 ? data[0] : {} as AuditTask; - }, - - async updateAuditTask(id: string, updates: Partial): Promise { - if (isLocalMode) { - return localDB.updateAuditTask(id, updates); - } - - if (!supabase) throw new Error('Database not available'); - - const { data, error } = await supabase - .from('audit_tasks') - .update(updates) - .eq('id', id) - .select(` - *, - project:projects!audit_tasks_project_id_fkey(*), - creator:profiles!audit_tasks_created_by_fkey(*) - `) - .limit(1); - - if (error) throw error; - return Array.isArray(data) && data.length > 0 ? data[0] : {} as AuditTask; - }, - - // AuditIssue相关 - async getAuditIssues(taskId: string): Promise { - if (isLocalMode) { - return localDB.getAuditIssues(taskId); - } - - if (!supabase) return []; - - const { data, error } = await supabase - .from('audit_issues') - .select(` - *, - task:audit_tasks!audit_issues_task_id_fkey(*), - resolver:profiles!audit_issues_resolved_by_fkey(*) - `) - .eq('task_id', taskId) - .order('severity', { ascending: false }) - .order('created_at', { ascending: false }); - - if (error) throw error; - return Array.isArray(data) ? data : []; - }, - - async createAuditIssue(issue: Omit): Promise { - if (isLocalMode) { - return localDB.createAuditIssue(issue); - } - - if (!supabase) throw new Error('Database not available'); - - const { data, error } = await supabase - .from('audit_issues') - .insert([issue]) - .select(` - *, - task:audit_tasks!audit_issues_task_id_fkey(*), - resolver:profiles!audit_issues_resolved_by_fkey(*) - `) - .limit(1); - - if (error) throw error; - return Array.isArray(data) && data.length > 0 ? data[0] : {} as AuditIssue; - }, - - async updateAuditIssue(id: string, updates: Partial): Promise { - if (isLocalMode) { - return localDB.updateAuditIssue(id, updates); - } - - if (!supabase) throw new Error('Database not available'); - - const { data, error } = await supabase - .from('audit_issues') - .update(updates) - .eq('id', id) - .select(` - *, - task:audit_tasks!audit_issues_task_id_fkey(*), - resolver:profiles!audit_issues_resolved_by_fkey(*) - `) - .limit(1); - - if (error) throw error; - return Array.isArray(data) && data.length > 0 ? data[0] : {} as AuditIssue; - }, - - // InstantAnalysis相关 - async getInstantAnalyses(userId?: string): Promise { - if (isLocalMode) { - return localDB.getInstantAnalyses(userId); - } - - if (!supabase) return []; - - let query = supabase - .from('instant_analyses') - .select(` - *, - user:profiles!instant_analyses_user_id_fkey(*) - `); - - if (userId) { - query = query.eq('user_id', userId); - } - - const { data, error } = await query.order('created_at', { ascending: false }); - - if (error) throw error; - return Array.isArray(data) ? data : []; - }, - - async createInstantAnalysis(analysis: InstantAnalysisForm & { - user_id: string; - analysis_result?: string; - issues_count?: number; - quality_score?: number; - analysis_time?: number; - }): Promise { - if (isLocalMode) { - return localDB.createInstantAnalysis(analysis); - } - - if (!supabase) throw new Error('Database not available'); - - const { data, error } = await supabase - .from('instant_analyses') - .insert([{ - user_id: analysis.user_id, - language: analysis.language, - // 遵循安全要求:不持久化用户代码内容 - code_content: '', - analysis_result: analysis.analysis_result || '{}', - issues_count: analysis.issues_count || 0, - quality_score: analysis.quality_score || 0, - analysis_time: analysis.analysis_time || 0 - }]) - .select(` - *, - user:profiles!instant_analyses_user_id_fkey(*) - `) - .limit(1); - - if (error) throw error; - return Array.isArray(data) && data.length > 0 ? data[0] : {} as InstantAnalysis; - }, - - // 统计相关 - async getProjectStats(): Promise { - if (isDemoMode) { - return { - total_projects: 1, - active_projects: 1, - total_tasks: 3, - completed_tasks: 2, - total_issues: 15, - resolved_issues: 12 - }; - } - - if (isLocalMode) { - return localDB.getProjectStats(); - } - - if (!supabase) { - return { - total_projects: 0, - active_projects: 0, - total_tasks: 0, - completed_tasks: 0, - total_issues: 0, - resolved_issues: 0 - }; - } - - const [projectsResult, tasksResult, issuesResult] = await Promise.all([ - supabase.from('projects').select('id, is_active', { count: 'exact' }), - supabase.from('audit_tasks').select('id, status', { count: 'exact' }), - supabase.from('audit_issues').select('id, status', { count: 'exact' }) - ]); - - return { - total_projects: projectsResult.count || 0, - active_projects: projectsResult.data?.filter(p => p.is_active).length || 0, - total_tasks: tasksResult.count || 0, - completed_tasks: tasksResult.data?.filter(t => t.status === 'completed').length || 0, - total_issues: issuesResult.count || 0, - resolved_issues: issuesResult.data?.filter(i => i.status === 'resolved').length || 0 - }; - } -}; \ No newline at end of file diff --git a/src/shared/config/env.ts b/src/shared/config/env.ts deleted file mode 100644 index fc80431..0000000 --- a/src/shared/config/env.ts +++ /dev/null @@ -1,175 +0,0 @@ -// 从 localStorage 读取运行时配置 -const STORAGE_KEY = 'xcodereviewer_runtime_config'; -const getRuntimeConfig = () => { - try { - const saved = localStorage.getItem(STORAGE_KEY); - return saved ? JSON.parse(saved) : null; - } catch { - return null; - } -}; - -const runtimeConfig = getRuntimeConfig(); - -// 环境变量配置(支持运行时配置覆盖) -export const env = { - // ==================== LLM 通用配置 ==================== - // 当前使用的LLM提供商 (gemini|openai|claude|qwen|deepseek|zhipu|moonshot|baidu|minimax|doubao|ollama) - LLM_PROVIDER: runtimeConfig?.llmProvider || import.meta.env.VITE_LLM_PROVIDER || 'gemini', - // LLM API Key - LLM_API_KEY: runtimeConfig?.llmApiKey || import.meta.env.VITE_LLM_API_KEY || '', - // LLM 模型名称 - LLM_MODEL: runtimeConfig?.llmModel || import.meta.env.VITE_LLM_MODEL || '', - // LLM API 基础URL (可选,用于自定义端点或代理) - LLM_BASE_URL: runtimeConfig?.llmBaseUrl || import.meta.env.VITE_LLM_BASE_URL || '', - // LLM 请求超时时间(ms) - LLM_TIMEOUT: runtimeConfig?.llmTimeout || Number(import.meta.env.VITE_LLM_TIMEOUT) || 150000, - // LLM 温度参数 (0.0-2.0) - LLM_TEMPERATURE: runtimeConfig?.llmTemperature !== undefined ? runtimeConfig.llmTemperature : (Number(import.meta.env.VITE_LLM_TEMPERATURE) || 0.2), - // LLM 最大token数 - LLM_MAX_TOKENS: runtimeConfig?.llmMaxTokens || Number(import.meta.env.VITE_LLM_MAX_TOKENS) || 4096, - // LLM 自定义请求头 (JSON字符串格式) - LLM_CUSTOM_HEADERS: runtimeConfig?.llmCustomHeaders || import.meta.env.VITE_LLM_CUSTOM_HEADERS || '', - - // ==================== Gemini AI 配置 (兼容旧配置) ==================== - GEMINI_API_KEY: runtimeConfig?.geminiApiKey || import.meta.env.VITE_GEMINI_API_KEY || '', - GEMINI_MODEL: import.meta.env.VITE_GEMINI_MODEL || 'gemini-1.5-flash', - GEMINI_TIMEOUT_MS: Number(import.meta.env.VITE_GEMINI_TIMEOUT_MS) || 25000, - - // ==================== OpenAI 配置 ==================== - OPENAI_API_KEY: runtimeConfig?.openaiApiKey || import.meta.env.VITE_OPENAI_API_KEY || '', - OPENAI_MODEL: import.meta.env.VITE_OPENAI_MODEL || 'gpt-4o-mini', - OPENAI_BASE_URL: import.meta.env.VITE_OPENAI_BASE_URL || '', - - // ==================== Claude 配置 ==================== - CLAUDE_API_KEY: runtimeConfig?.claudeApiKey || import.meta.env.VITE_CLAUDE_API_KEY || '', - CLAUDE_MODEL: import.meta.env.VITE_CLAUDE_MODEL || 'claude-3-5-sonnet-20241022', - - // ==================== 通义千问 配置 ==================== - QWEN_API_KEY: runtimeConfig?.qwenApiKey || import.meta.env.VITE_QWEN_API_KEY || '', - QWEN_MODEL: import.meta.env.VITE_QWEN_MODEL || 'qwen-turbo', - - // ==================== DeepSeek 配置 ==================== - DEEPSEEK_API_KEY: runtimeConfig?.deepseekApiKey || import.meta.env.VITE_DEEPSEEK_API_KEY || '', - DEEPSEEK_MODEL: import.meta.env.VITE_DEEPSEEK_MODEL || 'deepseek-chat', - - // ==================== 智谱AI 配置 ==================== - ZHIPU_API_KEY: runtimeConfig?.zhipuApiKey || import.meta.env.VITE_ZHIPU_API_KEY || '', - ZHIPU_MODEL: import.meta.env.VITE_ZHIPU_MODEL || 'glm-4-flash', - - // ==================== Moonshot 配置 ==================== - MOONSHOT_API_KEY: runtimeConfig?.moonshotApiKey || import.meta.env.VITE_MOONSHOT_API_KEY || '', - MOONSHOT_MODEL: import.meta.env.VITE_MOONSHOT_MODEL || 'moonshot-v1-8k', - - // ==================== 百度文心一言 配置 ==================== - BAIDU_API_KEY: runtimeConfig?.baiduApiKey || import.meta.env.VITE_BAIDU_API_KEY || '', - BAIDU_MODEL: import.meta.env.VITE_BAIDU_MODEL || 'ERNIE-3.5-8K', - - // ==================== MiniMax 配置 ==================== - MINIMAX_API_KEY: runtimeConfig?.minimaxApiKey || import.meta.env.VITE_MINIMAX_API_KEY || '', - MINIMAX_MODEL: import.meta.env.VITE_MINIMAX_MODEL || 'abab6.5-chat', - - // ==================== 豆包 配置 ==================== - DOUBAO_API_KEY: runtimeConfig?.doubaoApiKey || import.meta.env.VITE_DOUBAO_API_KEY || '', - DOUBAO_MODEL: import.meta.env.VITE_DOUBAO_MODEL || 'doubao-pro-32k', - - // ==================== Ollama 本地模型配置 ==================== - OLLAMA_API_KEY: import.meta.env.VITE_OLLAMA_API_KEY || 'ollama', - OLLAMA_MODEL: import.meta.env.VITE_OLLAMA_MODEL || 'llama3', - OLLAMA_BASE_URL: runtimeConfig?.ollamaBaseUrl || import.meta.env.VITE_OLLAMA_BASE_URL || 'http://localhost:11434/v1', - - // ==================== Supabase 配置 ==================== - SUPABASE_URL: import.meta.env.VITE_SUPABASE_URL || '', - SUPABASE_ANON_KEY: import.meta.env.VITE_SUPABASE_ANON_KEY || '', - - // ==================== GitHub 配置 ==================== - GITHUB_TOKEN: runtimeConfig?.githubToken || import.meta.env.VITE_GITHUB_TOKEN || '', - - // ==================== GitLab 配置 ==================== - GITLAB_TOKEN: runtimeConfig?.gitlabToken || import.meta.env.VITE_GITLAB_TOKEN || '', - - // ==================== 应用配置 ==================== - APP_ID: import.meta.env.VITE_APP_ID || 'xcodereviewer', - - // ==================== 分析配置 ==================== - MAX_ANALYZE_FILES: runtimeConfig?.maxAnalyzeFiles || Number(import.meta.env.VITE_MAX_ANALYZE_FILES) || 40, - LLM_CONCURRENCY: runtimeConfig?.llmConcurrency || Number(import.meta.env.VITE_LLM_CONCURRENCY) || 2, - LLM_GAP_MS: runtimeConfig?.llmGapMs || Number(import.meta.env.VITE_LLM_GAP_MS) || 500, - - // ==================== 语言配置 ==================== - OUTPUT_LANGUAGE: runtimeConfig?.outputLanguage || import.meta.env.VITE_OUTPUT_LANGUAGE || 'zh-CN', // zh-CN | en-US - - // ==================== 开发环境标识 ==================== - isDev: import.meta.env.DEV, - isProd: import.meta.env.PROD, -} as const; - -/** - * 获取当前配置的LLM服务的API Key - */ -export function getCurrentLLMApiKey(): string { - const provider = env.LLM_PROVIDER.toLowerCase(); - - // 优先使用通用配置 - if (env.LLM_API_KEY) { - return env.LLM_API_KEY; - } - - // 根据provider获取对应的API Key - const providerKeyMap: Record = { - gemini: env.GEMINI_API_KEY, - openai: env.OPENAI_API_KEY, - claude: env.CLAUDE_API_KEY, - qwen: env.QWEN_API_KEY, - deepseek: env.DEEPSEEK_API_KEY, - zhipu: env.ZHIPU_API_KEY, - moonshot: env.MOONSHOT_API_KEY, - baidu: env.BAIDU_API_KEY, - minimax: env.MINIMAX_API_KEY, - doubao: env.DOUBAO_API_KEY, - ollama: env.OLLAMA_API_KEY, - }; - - return providerKeyMap[provider] || ''; -} - -/** - * 获取当前配置的LLM模型 - */ -export function getCurrentLLMModel(): string { - const provider = env.LLM_PROVIDER.toLowerCase(); - - // 优先使用通用配置 - if (env.LLM_MODEL) { - return env.LLM_MODEL; - } - - // 根据provider获取对应的模型 - const providerModelMap: Record = { - gemini: env.GEMINI_MODEL, - openai: env.OPENAI_MODEL, - claude: env.CLAUDE_MODEL, - qwen: env.QWEN_MODEL, - deepseek: env.DEEPSEEK_MODEL, - zhipu: env.ZHIPU_MODEL, - moonshot: env.MOONSHOT_MODEL, - baidu: env.BAIDU_MODEL, - minimax: env.MINIMAX_MODEL, - doubao: env.DOUBAO_MODEL, - ollama: env.OLLAMA_MODEL, - }; - - return providerModelMap[provider] || ''; -} - -// 验证必需的环境变量 -export function validateEnv() { - const apiKey = getCurrentLLMApiKey(); - - if (!apiKey) { - console.warn(`未配置 ${env.LLM_PROVIDER} 的API Key,请在环境变量中配置`); - return false; - } - - return true; -} \ No newline at end of file diff --git a/src/shared/config/index.ts b/src/shared/config/index.ts deleted file mode 100644 index 9e4e7db..0000000 --- a/src/shared/config/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -// 导出所有配置 -export { env, validateEnv } from './env'; -export { supabase } from './database'; \ No newline at end of file diff --git a/src/shared/config/localDatabase.ts b/src/shared/config/localDatabase.ts deleted file mode 100644 index 414376f..0000000 --- a/src/shared/config/localDatabase.ts +++ /dev/null @@ -1,628 +0,0 @@ -/** - * 本地数据库实现 - 使用 IndexedDB - * 提供与 Supabase 相同的 API 接口,但数据存储在浏览器本地 - */ - -import type { - Profile, - Project, - ProjectMember, - AuditTask, - AuditIssue, - InstantAnalysis, - CreateProjectForm, - CreateAuditTaskForm, - InstantAnalysisForm -} from "../types/index"; - -const DB_NAME = 'xcodereviewer_local'; -const DB_VERSION = 1; - -// 数据库表名 -const STORES = { - PROFILES: 'profiles', - PROJECTS: 'projects', - PROJECT_MEMBERS: 'project_members', - AUDIT_TASKS: 'audit_tasks', - AUDIT_ISSUES: 'audit_issues', - INSTANT_ANALYSES: 'instant_analyses', -} as const; - -class LocalDatabase { - private db: IDBDatabase | null = null; - private initPromise: Promise | null = null; - - /** - * 初始化数据库 - */ - async init(): Promise { - if (this.db) return; - if (this.initPromise) return this.initPromise; - - this.initPromise = new Promise((resolve, reject) => { - const request = indexedDB.open(DB_NAME, DB_VERSION); - - request.onerror = () => reject(request.error); - request.onsuccess = () => { - this.db = request.result; - resolve(); - }; - - request.onupgradeneeded = (event) => { - const db = (event.target as IDBOpenDBRequest).result; - - // 创建 profiles 表 - if (!db.objectStoreNames.contains(STORES.PROFILES)) { - const profileStore = db.createObjectStore(STORES.PROFILES, { keyPath: 'id' }); - profileStore.createIndex('email', 'email', { unique: false }); - profileStore.createIndex('role', 'role', { unique: false }); - } - - // 创建 projects 表 - if (!db.objectStoreNames.contains(STORES.PROJECTS)) { - const projectStore = db.createObjectStore(STORES.PROJECTS, { keyPath: 'id' }); - projectStore.createIndex('owner_id', 'owner_id', { unique: false }); - projectStore.createIndex('is_active', 'is_active', { unique: false }); - projectStore.createIndex('created_at', 'created_at', { unique: false }); - } - - // 创建 project_members 表 - if (!db.objectStoreNames.contains(STORES.PROJECT_MEMBERS)) { - const memberStore = db.createObjectStore(STORES.PROJECT_MEMBERS, { keyPath: 'id' }); - memberStore.createIndex('project_id', 'project_id', { unique: false }); - memberStore.createIndex('user_id', 'user_id', { unique: false }); - } - - // 创建 audit_tasks 表 - if (!db.objectStoreNames.contains(STORES.AUDIT_TASKS)) { - const taskStore = db.createObjectStore(STORES.AUDIT_TASKS, { keyPath: 'id' }); - taskStore.createIndex('project_id', 'project_id', { unique: false }); - taskStore.createIndex('created_by', 'created_by', { unique: false }); - taskStore.createIndex('status', 'status', { unique: false }); - taskStore.createIndex('created_at', 'created_at', { unique: false }); - } - - // 创建 audit_issues 表 - if (!db.objectStoreNames.contains(STORES.AUDIT_ISSUES)) { - const issueStore = db.createObjectStore(STORES.AUDIT_ISSUES, { keyPath: 'id' }); - issueStore.createIndex('task_id', 'task_id', { unique: false }); - issueStore.createIndex('severity', 'severity', { unique: false }); - issueStore.createIndex('status', 'status', { unique: false }); - } - - // 创建 instant_analyses 表 - if (!db.objectStoreNames.contains(STORES.INSTANT_ANALYSES)) { - const analysisStore = db.createObjectStore(STORES.INSTANT_ANALYSES, { keyPath: 'id' }); - analysisStore.createIndex('user_id', 'user_id', { unique: false }); - analysisStore.createIndex('created_at', 'created_at', { unique: false }); - } - }; - }); - - return this.initPromise; - } - - /** - * 生成 UUID - */ - private generateId(): string { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { - const r = Math.random() * 16 | 0; - const v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - - /** - * 获取对象存储 - */ - private getStore(storeName: string, mode: IDBTransactionMode = 'readonly'): IDBObjectStore { - if (!this.db) throw new Error('Database not initialized'); - const transaction = this.db.transaction(storeName, mode); - return transaction.objectStore(storeName); - } - - /** - * 通用查询方法 - */ - private async getAll(storeName: string): Promise { - await this.init(); - return new Promise((resolve, reject) => { - const store = this.getStore(storeName); - const request = store.getAll(); - request.onsuccess = () => resolve(request.result); - request.onerror = () => reject(request.error); - }); - } - - /** - * 通过 ID 获取单条记录 - */ - private async getById(storeName: string, id: string): Promise { - if (!id) return null; - - await this.init(); - return new Promise((resolve, reject) => { - const store = this.getStore(storeName); - const request = store.get(id); - request.onsuccess = () => resolve(request.result || null); - request.onerror = () => reject(request.error); - }); - } - - /** - * 通过索引查询 - */ - private async getByIndex(storeName: string, indexName: string, value: any): Promise { - await this.init(); - return new Promise((resolve, reject) => { - const store = this.getStore(storeName); - const index = store.index(indexName); - const request = index.getAll(value); - request.onsuccess = () => resolve(request.result); - request.onerror = () => reject(request.error); - }); - } - - /** - * 插入或更新记录 - */ - private async put(storeName: string, data: T): Promise { - await this.init(); - return new Promise((resolve, reject) => { - const store = this.getStore(storeName, 'readwrite'); - const request = store.put(data); - request.onsuccess = () => resolve(data); - request.onerror = () => reject(request.error); - }); - } - - /** - * 删除记录 - */ - private async deleteRecord(storeName: string, id: string): Promise { - await this.init(); - return new Promise((resolve, reject) => { - const store = this.getStore(storeName, 'readwrite'); - const request = store.delete(id); - request.onsuccess = () => resolve(); - request.onerror = () => reject(request.error); - }); - } - - /** - * 统计记录数 - */ - private async count(storeName: string): Promise { - await this.init(); - return new Promise((resolve, reject) => { - const store = this.getStore(storeName); - const request = store.count(); - request.onsuccess = () => resolve(request.result); - request.onerror = () => reject(request.error); - }); - } - - // ==================== Profile 相关方法 ==================== - - async getProfileById(id: string): Promise { - return this.getById(STORES.PROFILES, id); - } - - async getProfilesCount(): Promise { - return this.count(STORES.PROFILES); - } - - async createProfile(profile: Partial): Promise { - const newProfile: Profile = { - id: profile.id || this.generateId(), - phone: profile.phone || undefined, - email: profile.email || undefined, - full_name: profile.full_name || undefined, - avatar_url: profile.avatar_url || undefined, - role: profile.role || 'member', - github_username: profile.github_username || undefined, - gitlab_username: profile.gitlab_username || undefined, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - }; - return this.put(STORES.PROFILES, newProfile); - } - - async updateProfile(id: string, updates: Partial): Promise { - const existing = await this.getProfileById(id); - if (!existing) throw new Error('Profile not found'); - - const updated: Profile = { - ...existing, - ...updates, - id, - updated_at: new Date().toISOString(), - }; - return this.put(STORES.PROFILES, updated); - } - - async getAllProfiles(): Promise { - return this.getAll(STORES.PROFILES); - } - - // ==================== Project 相关方法 ==================== - - async getProjects(): Promise { - const projects = await this.getAll(STORES.PROJECTS); - const activeProjects = projects.filter(p => p.is_active); - - // 关联 owner 信息 - const projectsWithOwner = await Promise.all( - activeProjects.map(async (project) => { - const owner = project.owner_id ? await this.getProfileById(project.owner_id) : null; - return { ...project, owner: owner || undefined }; - }) - ); - - return projectsWithOwner.sort((a, b) => - new Date(b.created_at).getTime() - new Date(a.created_at).getTime() - ); - } - - async getProjectById(id: string): Promise { - if (!id) return null; - - const project = await this.getById(STORES.PROJECTS, id); - if (!project) return null; - - const owner = project.owner_id ? await this.getProfileById(project.owner_id) : null; - return { ...project, owner: owner || undefined }; - } - - async createProject(projectData: CreateProjectForm & { owner_id?: string }): Promise { - const newProject: Project = { - id: this.generateId(), - name: projectData.name, - description: projectData.description || undefined, - repository_url: projectData.repository_url || undefined, - repository_type: projectData.repository_type || 'other', - default_branch: projectData.default_branch || 'main', - programming_languages: JSON.stringify(projectData.programming_languages || []), - owner_id: projectData.owner_id || 'local-user', - is_active: true, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - }; - - await this.put(STORES.PROJECTS, newProject); - return this.getProjectById(newProject.id) as Promise; - } - - async updateProject(id: string, updates: Partial): Promise { - const existing = await this.getById(STORES.PROJECTS, id); - if (!existing) throw new Error('Project not found'); - - const updateData: any = { ...updates }; - if (updates.programming_languages) { - updateData.programming_languages = JSON.stringify(updates.programming_languages); - } - - const updated: Project = { - ...existing, - ...updateData, - id, - updated_at: new Date().toISOString(), - }; - - await this.put(STORES.PROJECTS, updated); - return this.getProjectById(id) as Promise; - } - - async deleteProject(id: string): Promise { - const existing = await this.getById(STORES.PROJECTS, id); - if (!existing) throw new Error('Project not found'); - - const updated: Project = { - ...existing, - is_active: false, - updated_at: new Date().toISOString(), - }; - - await this.put(STORES.PROJECTS, updated); - } - - async getDeletedProjects(): Promise { - const projects = await this.getAll(STORES.PROJECTS); - const deletedProjects = projects.filter(p => !p.is_active); - - // 关联 owner 信息 - const projectsWithOwner = await Promise.all( - deletedProjects.map(async (project) => { - const owner = project.owner_id ? await this.getProfileById(project.owner_id) : null; - return { ...project, owner: owner || undefined }; - }) - ); - - return projectsWithOwner.sort((a, b) => - new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime() - ); - } - - async restoreProject(id: string): Promise { - const existing = await this.getById(STORES.PROJECTS, id); - if (!existing) throw new Error('Project not found'); - - const updated: Project = { - ...existing, - is_active: true, - updated_at: new Date().toISOString(), - }; - - await this.put(STORES.PROJECTS, updated); - } - - async permanentlyDeleteProject(id: string): Promise { - const existing = await this.getById(STORES.PROJECTS, id); - if (!existing) throw new Error('Project not found'); - - await this.deleteRecord(STORES.PROJECTS, id); - } - - // ==================== ProjectMember 相关方法 ==================== - - async getProjectMembers(projectId: string): Promise { - const members = await this.getByIndex(STORES.PROJECT_MEMBERS, 'project_id', projectId); - - const membersWithRelations = await Promise.all( - members.map(async (member) => { - const user = member.user_id ? await this.getProfileById(member.user_id) : null; - const project = member.project_id ? await this.getProjectById(member.project_id) : null; - return { - ...member, - user: user || undefined, - project: project || undefined - }; - }) - ); - - return membersWithRelations.sort((a, b) => - new Date(b.joined_at).getTime() - new Date(a.joined_at).getTime() - ); - } - - async addProjectMember(projectId: string, userId: string, role: string = 'member'): Promise { - const newMember: ProjectMember = { - id: this.generateId(), - project_id: projectId, - user_id: userId, - role: role as any, - permissions: '{}', - joined_at: new Date().toISOString(), - created_at: new Date().toISOString(), - }; - - await this.put(STORES.PROJECT_MEMBERS, newMember); - - const user = userId ? await this.getProfileById(userId) : null; - const project = projectId ? await this.getProjectById(projectId) : null; - - return { - ...newMember, - user: user || undefined, - project: project || undefined - }; - } - - // ==================== AuditTask 相关方法 ==================== - - async getAuditTasks(projectId?: string): Promise { - let tasks: AuditTask[]; - - if (projectId) { - tasks = await this.getByIndex(STORES.AUDIT_TASKS, 'project_id', projectId); - } else { - tasks = await this.getAll(STORES.AUDIT_TASKS); - } - - const tasksWithRelations = await Promise.all( - tasks.map(async (task) => { - const project = task.project_id ? await this.getProjectById(task.project_id) : null; - const creator = task.created_by ? await this.getProfileById(task.created_by) : null; - return { - ...task, - project: project || undefined, - creator: creator || undefined - }; - }) - ); - - return tasksWithRelations.sort((a, b) => - new Date(b.created_at).getTime() - new Date(a.created_at).getTime() - ); - } - - async getAuditTaskById(id: string): Promise { - if (!id) return null; - - const task = await this.getById(STORES.AUDIT_TASKS, id); - if (!task) return null; - - const project = task.project_id ? await this.getProjectById(task.project_id) : null; - const creator = task.created_by ? await this.getProfileById(task.created_by) : null; - - return { - ...task, - project: project || undefined, - creator: creator || undefined - }; - } - - async createAuditTask(taskData: CreateAuditTaskForm & { created_by: string }): Promise { - const newTask: AuditTask = { - id: this.generateId(), - project_id: taskData.project_id, - task_type: taskData.task_type, - status: 'pending', - branch_name: taskData.branch_name || undefined, - exclude_patterns: JSON.stringify(taskData.exclude_patterns || []), - scan_config: JSON.stringify(taskData.scan_config || {}), - total_files: 0, - scanned_files: 0, - total_lines: 0, - issues_count: 0, - quality_score: 0, - started_at: undefined, - completed_at: undefined, - created_by: taskData.created_by, - created_at: new Date().toISOString(), - }; - - await this.put(STORES.AUDIT_TASKS, newTask); - return this.getAuditTaskById(newTask.id) as Promise; - } - - async updateAuditTask(id: string, updates: Partial): Promise { - const existing = await this.getById(STORES.AUDIT_TASKS, id); - if (!existing) throw new Error('Audit task not found'); - - const updated: AuditTask = { - ...existing, - ...updates, - id, - }; - - await this.put(STORES.AUDIT_TASKS, updated); - return this.getAuditTaskById(id) as Promise; - } - - // ==================== AuditIssue 相关方法 ==================== - - async getAuditIssues(taskId: string): Promise { - const issues = await this.getByIndex(STORES.AUDIT_ISSUES, 'task_id', taskId); - - const issuesWithRelations = await Promise.all( - issues.map(async (issue) => { - const task = issue.task_id ? await this.getAuditTaskById(issue.task_id) : null; - const resolver = issue.resolved_by ? await this.getProfileById(issue.resolved_by) : null; - return { - ...issue, - task: task || undefined, - resolver: resolver || undefined - }; - }) - ); - - // 按严重程度和创建时间排序 - const severityOrder: Record = { critical: 0, high: 1, medium: 2, low: 3 }; - return issuesWithRelations.sort((a, b) => { - const severityDiff = (severityOrder[a.severity] || 999) - (severityOrder[b.severity] || 999); - if (severityDiff !== 0) return severityDiff; - return new Date(b.created_at).getTime() - new Date(a.created_at).getTime(); - }); - } - - async createAuditIssue(issueData: Omit): Promise { - const newIssue: AuditIssue = { - ...issueData, - id: this.generateId(), - created_at: new Date().toISOString(), - }; - - await this.put(STORES.AUDIT_ISSUES, newIssue); - - const task = newIssue.task_id ? await this.getAuditTaskById(newIssue.task_id) : null; - const resolver = newIssue.resolved_by ? await this.getProfileById(newIssue.resolved_by) : null; - - return { - ...newIssue, - task: task || undefined, - resolver: resolver || undefined - }; - } - - async updateAuditIssue(id: string, updates: Partial): Promise { - const existing = await this.getById(STORES.AUDIT_ISSUES, id); - if (!existing) throw new Error('Audit issue not found'); - - const updated: AuditIssue = { - ...existing, - ...updates, - id, - }; - - await this.put(STORES.AUDIT_ISSUES, updated); - - const task = updated.task_id ? await this.getAuditTaskById(updated.task_id) : null; - const resolver = updated.resolved_by ? await this.getProfileById(updated.resolved_by) : null; - - return { - ...updated, - task: task || undefined, - resolver: resolver || undefined - }; - } - - // ==================== InstantAnalysis 相关方法 ==================== - - async getInstantAnalyses(userId?: string): Promise { - let analyses: InstantAnalysis[]; - - if (userId) { - analyses = await this.getByIndex(STORES.INSTANT_ANALYSES, 'user_id', userId); - } else { - analyses = await this.getAll(STORES.INSTANT_ANALYSES); - } - - const analysesWithUser = await Promise.all( - analyses.map(async (analysis) => { - const user = analysis.user_id ? await this.getProfileById(analysis.user_id) : null; - return { ...analysis, user: user || undefined }; - }) - ); - - return analysesWithUser.sort((a, b) => - new Date(b.created_at).getTime() - new Date(a.created_at).getTime() - ); - } - - async createInstantAnalysis(analysisData: InstantAnalysisForm & { - user_id: string; - analysis_result?: string; - issues_count?: number; - quality_score?: number; - analysis_time?: number; - }): Promise { - const newAnalysis: InstantAnalysis = { - id: this.generateId(), - user_id: analysisData.user_id, - language: analysisData.language, - code_content: '', // 不持久化代码内容 - analysis_result: analysisData.analysis_result || '{}', - issues_count: analysisData.issues_count || 0, - quality_score: analysisData.quality_score || 0, - analysis_time: analysisData.analysis_time || 0, - created_at: new Date().toISOString(), - }; - - await this.put(STORES.INSTANT_ANALYSES, newAnalysis); - - const user = newAnalysis.user_id ? await this.getProfileById(newAnalysis.user_id) : null; - return { ...newAnalysis, user: user || undefined }; - } - - // ==================== 统计相关方法 ==================== - - async getProjectStats(): Promise { - const projects = await this.getAll(STORES.PROJECTS); - const tasks = await this.getAll(STORES.AUDIT_TASKS); - const issues = await this.getAll(STORES.AUDIT_ISSUES); - - return { - total_projects: projects.length, - active_projects: projects.filter(p => p.is_active).length, - total_tasks: tasks.length, - completed_tasks: tasks.filter(t => t.status === 'completed').length, - total_issues: issues.length, - resolved_issues: issues.filter(i => i.status === 'resolved').length, - }; - } -} - -// 导出单例 -export const localDB = new LocalDatabase(); diff --git a/src/shared/services/llm/adapters/baidu-adapter.ts b/src/shared/services/llm/adapters/baidu-adapter.ts deleted file mode 100644 index 223d728..0000000 --- a/src/shared/services/llm/adapters/baidu-adapter.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * 百度文心一言适配器 - */ - -import { BaseLLMAdapter } from '../base-adapter'; -import type { LLMRequest, LLMResponse } from '../types'; - -export class BaiduAdapter extends BaseLLMAdapter { - private baseUrl: string; - private accessToken?: string; - private tokenExpiry?: number; - - constructor(config: any) { - super(config); - this.baseUrl = config.baseUrl || 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1'; - } - - async complete(request: LLMRequest): Promise { - try { - await this.validateConfig(); - await this.ensureAccessToken(); - - return await this.retry(async () => { - return await this.withTimeout(this._sendRequest(request)); - }); - } catch (error) { - this.handleError(error, '文心一言API调用失败'); - } - } - - private async ensureAccessToken(): Promise { - // 如果token存在且未过期,直接返回 - if (this.accessToken && this.tokenExpiry && Date.now() < this.tokenExpiry) { - return; - } - - // 文心一言API Key格式为 "API_KEY:SECRET_KEY" - const [apiKey, secretKey] = this.config.apiKey.split(':'); - if (!apiKey || !secretKey) { - throw new Error('百度API Key格式错误,应为 "API_KEY:SECRET_KEY"'); - } - - const tokenUrl = `https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${apiKey}&client_secret=${secretKey}`; - - const response = await fetch(tokenUrl, { method: 'POST' }); - if (!response.ok) { - throw new Error('获取百度access_token失败'); - } - - const data = await response.json(); - this.accessToken = data.access_token; - // 设置过期时间为29天后(百度token有效期30天) - this.tokenExpiry = Date.now() + 29 * 24 * 60 * 60 * 1000; - } - - private async _sendRequest(request: LLMRequest): Promise { - const endpoint = this.getModelEndpoint(this.config.model); - const url = `${this.baseUrl}/wenxinworkshop/chat/${endpoint}?access_token=${this.accessToken}`; - - const response = await fetch(url, { - method: 'POST', - headers: this.buildHeaders(), - body: JSON.stringify({ - messages: request.messages.map(msg => ({ - role: msg.role, - content: msg.content, - })), - temperature: request.temperature ?? this.config.temperature, - top_p: request.topP ?? this.config.topP, - }), - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({})); - throw { - statusCode: response.status, - message: error.error_msg || `HTTP ${response.status}: ${response.statusText}`, - }; - } - - const data = await response.json(); - - if (data.error_code) { - throw new Error(`API错误 (${data.error_code}): ${data.error_msg}`); - } - - return { - content: data.result || '', - model: this.config.model, - usage: data.usage ? { - promptTokens: data.usage.prompt_tokens, - completionTokens: data.usage.completion_tokens, - totalTokens: data.usage.total_tokens, - } : undefined, - finishReason: 'stop', - }; - } - - private getModelEndpoint(model: string): string { - const endpoints: Record = { - 'ERNIE-4.0-8K': 'completions_pro', - 'ERNIE-3.5-8K': 'completions', - 'ERNIE-3.5-128K': 'ernie-3.5-128k', - 'ERNIE-Speed-8K': 'ernie_speed', - 'ERNIE-Speed-128K': 'ernie-speed-128k', - 'ERNIE-Lite-8K': 'ernie-lite-8k', - 'ERNIE-Tiny-8K': 'ernie-tiny-8k', - }; - - return endpoints[model] || 'completions'; - } - - async validateConfig(): Promise { - await super.validateConfig(); - - if (!this.config.apiKey.includes(':')) { - throw new Error('百度API Key格式错误,应为 "API_KEY:SECRET_KEY"'); - } - - return true; - } -} - diff --git a/src/shared/services/llm/adapters/claude-adapter.ts b/src/shared/services/llm/adapters/claude-adapter.ts deleted file mode 100644 index 3c39353..0000000 --- a/src/shared/services/llm/adapters/claude-adapter.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Anthropic Claude适配器 - */ - -import { BaseLLMAdapter } from '../base-adapter'; -import type { LLMRequest, LLMResponse } from '../types'; - -export class ClaudeAdapter extends BaseLLMAdapter { - private baseUrl: string; - - constructor(config: any) { - super(config); - this.baseUrl = config.baseUrl || 'https://api.anthropic.com/v1'; - } - - async complete(request: LLMRequest): Promise { - try { - await this.validateConfig(); - - return await this.retry(async () => { - return await this.withTimeout(this._sendRequest(request)); - }); - } catch (error) { - this.handleError(error, 'Claude API调用失败'); - } - } - - private async _sendRequest(request: LLMRequest): Promise { - // Claude API需要将system消息分离 - const systemMessage = request.messages.find(msg => msg.role === 'system'); - const messages = request.messages - .filter(msg => msg.role !== 'system') - .map(msg => ({ - role: msg.role, - content: msg.content, - })); - - const requestBody: any = { - model: this.config.model, - messages, - max_tokens: request.maxTokens ?? this.config.maxTokens ?? 4096, - temperature: request.temperature ?? this.config.temperature, - top_p: request.topP ?? this.config.topP, - }; - - if (systemMessage) { - requestBody.system = systemMessage.content; - } - - // 构建请求头 - const headers: Record = { - 'x-api-key': this.config.apiKey, - 'anthropic-version': '2023-06-01', - }; - - // 合并自定义请求头 - if (this.config.customHeaders) { - Object.assign(headers, this.config.customHeaders); - } - - const response = await fetch(`${this.baseUrl.replace(/\/$/, '')}/messages`, { - method: 'POST', - headers: this.buildHeaders(headers), - body: JSON.stringify(requestBody), - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({})); - throw { - statusCode: response.status, - message: error.error?.message || `HTTP ${response.status}: ${response.statusText}`, - }; - } - - const data = await response.json(); - - if (!data.content || !data.content[0]) { - throw new Error('API响应格式异常: 缺少content字段'); - } - - return { - content: data.content[0].text || '', - model: data.model, - usage: data.usage ? { - promptTokens: data.usage.input_tokens, - completionTokens: data.usage.output_tokens, - totalTokens: data.usage.input_tokens + data.usage.output_tokens, - } : undefined, - finishReason: data.stop_reason, - }; - } - - async validateConfig(): Promise { - await super.validateConfig(); - - if (!this.config.model.startsWith('claude-')) { - throw new Error(`无效的Claude模型: ${this.config.model}`); - } - - return true; - } -} - diff --git a/src/shared/services/llm/adapters/deepseek-adapter.ts b/src/shared/services/llm/adapters/deepseek-adapter.ts deleted file mode 100644 index d463272..0000000 --- a/src/shared/services/llm/adapters/deepseek-adapter.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * DeepSeek适配器 - */ - -import { BaseLLMAdapter } from '../base-adapter'; -import type { LLMRequest, LLMResponse } from '../types'; - -export class DeepSeekAdapter extends BaseLLMAdapter { - private baseUrl: string; - - constructor(config: any) { - super(config); - this.baseUrl = config.baseUrl || 'https://api.deepseek.com'; - } - - async complete(request: LLMRequest): Promise { - try { - await this.validateConfig(); - - return await this.retry(async () => { - return await this.withTimeout(this._sendRequest(request)); - }); - } catch (error) { - this.handleError(error, 'DeepSeek API调用失败'); - } - } - - private async _sendRequest(request: LLMRequest): Promise { - // DeepSeek API兼容OpenAI格式 - // 构建请求头 - const headers: Record = { - 'Authorization': `Bearer ${this.config.apiKey}`, - }; - - // 合并自定义请求头 - if (this.config.customHeaders) { - Object.assign(headers, this.config.customHeaders); - } - - const response = await fetch(`${this.baseUrl.replace(/\/$/, '')}/v1/chat/completions`, { - method: 'POST', - headers: this.buildHeaders(headers), - body: JSON.stringify({ - model: this.config.model, - messages: request.messages, - temperature: request.temperature ?? this.config.temperature, - max_tokens: request.maxTokens ?? this.config.maxTokens, - top_p: request.topP ?? this.config.topP, - frequency_penalty: this.config.frequencyPenalty, - presence_penalty: this.config.presencePenalty, - }), - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({})); - throw { - statusCode: response.status, - message: error.error?.message || `HTTP ${response.status}: ${response.statusText}`, - }; - } - - const data = await response.json(); - const choice = data.choices?.[0]; - - if (!choice) { - throw new Error('API响应格式异常: 缺少choices字段'); - } - - return { - content: choice.message?.content || '', - model: data.model, - usage: data.usage ? { - promptTokens: data.usage.prompt_tokens, - completionTokens: data.usage.completion_tokens, - totalTokens: data.usage.total_tokens, - } : undefined, - finishReason: choice.finish_reason, - }; - } - - async validateConfig(): Promise { - await super.validateConfig(); - - if (!this.config.model) { - throw new Error('未指定DeepSeek模型'); - } - - return true; - } -} - diff --git a/src/shared/services/llm/adapters/doubao-adapter.ts b/src/shared/services/llm/adapters/doubao-adapter.ts deleted file mode 100644 index 0aea619..0000000 --- a/src/shared/services/llm/adapters/doubao-adapter.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * 字节豆包适配器 - */ - -import { BaseLLMAdapter } from '../base-adapter'; -import type { LLMRequest, LLMResponse } from '../types'; - -export class DoubaoAdapter extends BaseLLMAdapter { - private baseUrl: string; - - constructor(config: any) { - super(config); - this.baseUrl = config.baseUrl || 'https://ark.cn-beijing.volces.com/api/v3'; - } - - async complete(request: LLMRequest): Promise { - try { - await this.validateConfig(); - - return await this.retry(async () => { - return await this.withTimeout(this._sendRequest(request)); - }); - } catch (error) { - this.handleError(error, '豆包API调用失败'); - } - } - - private async _sendRequest(request: LLMRequest): Promise { - // 豆包API兼容OpenAI格式 - const headers: Record = { - 'Authorization': `Bearer ${this.config.apiKey}`, - }; - if (this.config.customHeaders) Object.assign(headers, this.config.customHeaders); - - const response = await fetch(`${this.baseUrl.replace(/\/$/, '')}/chat/completions`, { - method: 'POST', - headers: this.buildHeaders(headers), - body: JSON.stringify({ - model: this.config.model, - messages: request.messages, - temperature: request.temperature ?? this.config.temperature, - max_tokens: request.maxTokens ?? this.config.maxTokens, - top_p: request.topP ?? this.config.topP, - }), - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({})); - throw { - statusCode: response.status, - message: error.error?.message || `HTTP ${response.status}: ${response.statusText}`, - }; - } - - const data = await response.json(); - const choice = data.choices?.[0]; - - if (!choice) { - throw new Error('API响应格式异常: 缺少choices字段'); - } - - return { - content: choice.message?.content || '', - model: data.model, - usage: data.usage ? { - promptTokens: data.usage.prompt_tokens, - completionTokens: data.usage.completion_tokens, - totalTokens: data.usage.total_tokens, - } : undefined, - finishReason: choice.finish_reason, - }; - } - - async validateConfig(): Promise { - await super.validateConfig(); - - if (!this.config.model) { - throw new Error('未指定豆包模型'); - } - - return true; - } -} - diff --git a/src/shared/services/llm/adapters/gemini-adapter.ts b/src/shared/services/llm/adapters/gemini-adapter.ts deleted file mode 100644 index b822603..0000000 --- a/src/shared/services/llm/adapters/gemini-adapter.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Google Gemini适配器 - 支持官方API和中转站 - */ - -import { BaseLLMAdapter } from '../base-adapter'; -import type { LLMRequest, LLMResponse } from '../types'; - -export class GeminiAdapter extends BaseLLMAdapter { - private baseUrl: string; - - constructor(config: any) { - super(config); - // 支持自定义baseUrl(中转站)或使用官方API - this.baseUrl = this.config.baseUrl || 'https://generativelanguage.googleapis.com/v1beta'; - } - - async complete(request: LLMRequest): Promise { - try { - await this.validateConfig(); - - return await this.retry(async () => { - return await this.withTimeout(this._generateContent(request)); - }); - } catch (error) { - this.handleError(error, 'Gemini API调用失败'); - } - } - - private async _generateContent(request: LLMRequest): Promise { - // 转换消息格式为 Gemini 格式 - const contents = request.messages - .filter(msg => msg.role !== 'system') - .map(msg => ({ - role: msg.role === 'assistant' ? 'model' : 'user', - parts: [{ text: msg.content }], - })); - - // 将系统消息合并到第一条用户消息 - const systemMessage = request.messages.find(msg => msg.role === 'system'); - if (systemMessage && contents.length > 0) { - contents[0].parts[0].text = `${systemMessage.content}\n\n${contents[0].parts[0].text}`; - } - - // 构建请求体 - const requestBody = { - contents, - generationConfig: { - temperature: request.temperature ?? this.config.temperature, - maxOutputTokens: request.maxTokens ?? this.config.maxTokens, - topP: request.topP ?? this.config.topP, - } - }; - - // 构建请求头 - const headers: Record = { - 'Content-Type': 'application/json', - }; - - // 如果有自定义请求头,合并进去 - if (this.config.customHeaders) { - Object.assign(headers, this.config.customHeaders); - } - - // API Key 可能在 URL 参数或请求头中 - const url = `${this.baseUrl}/models/${this.config.model}:generateContent?key=${this.config.apiKey}`; - - const response = await fetch(url, { - method: 'POST', - headers: this.buildHeaders(headers), - body: JSON.stringify(requestBody), - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({})); - throw { - statusCode: response.status, - message: error.error?.message || `HTTP ${response.status}: ${response.statusText}`, - details: error, - }; - } - - const data = await response.json(); - - // 解析 Gemini 响应格式 - const candidate = data.candidates?.[0]; - if (!candidate || !candidate.content) { - throw new Error('API响应格式异常: 缺少candidates或content字段'); - } - - const text = candidate.content.parts?.map((part: any) => part.text).join('') || ''; - - return { - content: text, - model: this.config.model, - usage: data.usageMetadata ? { - promptTokens: data.usageMetadata.promptTokenCount || 0, - completionTokens: data.usageMetadata.candidatesTokenCount || 0, - totalTokens: data.usageMetadata.totalTokenCount || 0, - } : undefined, - finishReason: candidate.finishReason || 'stop', - }; - } - - async validateConfig(): Promise { - await super.validateConfig(); - - if (!this.config.model.startsWith('gemini-')) { - throw new Error(`无效的Gemini模型: ${this.config.model}`); - } - - return true; - } -} diff --git a/src/shared/services/llm/adapters/index.ts b/src/shared/services/llm/adapters/index.ts deleted file mode 100644 index c25ca03..0000000 --- a/src/shared/services/llm/adapters/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * LLM适配器导出 - */ - -export { GeminiAdapter } from './gemini-adapter'; -export { OpenAIAdapter } from './openai-adapter'; -export { ClaudeAdapter } from './claude-adapter'; -export { QwenAdapter } from './qwen-adapter'; -export { DeepSeekAdapter } from './deepseek-adapter'; -export { ZhipuAdapter } from './zhipu-adapter'; -export { MoonshotAdapter } from './moonshot-adapter'; -export { BaiduAdapter } from './baidu-adapter'; -export { MinimaxAdapter } from './minimax-adapter'; -export { DoubaoAdapter } from './doubao-adapter'; -export { OllamaAdapter } from './ollama-adapter'; - diff --git a/src/shared/services/llm/adapters/minimax-adapter.ts b/src/shared/services/llm/adapters/minimax-adapter.ts deleted file mode 100644 index 6e60be2..0000000 --- a/src/shared/services/llm/adapters/minimax-adapter.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * MiniMax适配器 - */ - -import { BaseLLMAdapter } from '../base-adapter'; -import type { LLMRequest, LLMResponse } from '../types'; - -export class MinimaxAdapter extends BaseLLMAdapter { - private baseUrl: string; - - constructor(config: any) { - super(config); - this.baseUrl = config.baseUrl || 'https://api.minimax.chat/v1'; - } - - async complete(request: LLMRequest): Promise { - try { - await this.validateConfig(); - - return await this.retry(async () => { - return await this.withTimeout(this._sendRequest(request)); - }); - } catch (error) { - this.handleError(error, 'MiniMax API调用失败'); - } - } - - private async _sendRequest(request: LLMRequest): Promise { - // MiniMax API兼容OpenAI格式 - const headers: Record = { - 'Authorization': `Bearer ${this.config.apiKey}`, - }; - if (this.config.customHeaders) Object.assign(headers, this.config.customHeaders); - - const response = await fetch(`${this.baseUrl.replace(/\/$/, '')}/text/chatcompletion_v2`, { - method: 'POST', - headers: this.buildHeaders(headers), - body: JSON.stringify({ - model: this.config.model, - messages: request.messages, - temperature: request.temperature ?? this.config.temperature, - max_tokens: request.maxTokens ?? this.config.maxTokens, - top_p: request.topP ?? this.config.topP, - }), - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({})); - throw { - statusCode: response.status, - message: error.base_resp?.status_msg || `HTTP ${response.status}: ${response.statusText}`, - }; - } - - const data = await response.json(); - - if (data.base_resp?.status_code !== 0) { - throw new Error(`API错误 (${data.base_resp?.status_code}): ${data.base_resp?.status_msg}`); - } - - const choice = data.choices?.[0]; - if (!choice) { - throw new Error('API响应格式异常: 缺少choices字段'); - } - - return { - content: choice.message?.content || '', - model: this.config.model, - usage: data.usage ? { - promptTokens: data.usage.total_tokens || 0, - completionTokens: 0, - totalTokens: data.usage.total_tokens || 0, - } : undefined, - finishReason: choice.finish_reason, - }; - } - - async validateConfig(): Promise { - await super.validateConfig(); - - if (!this.config.model) { - throw new Error('未指定MiniMax模型'); - } - - return true; - } -} - diff --git a/src/shared/services/llm/adapters/moonshot-adapter.ts b/src/shared/services/llm/adapters/moonshot-adapter.ts deleted file mode 100644 index 900edd4..0000000 --- a/src/shared/services/llm/adapters/moonshot-adapter.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * 月之暗面 Kimi适配器 - */ - -import { BaseLLMAdapter } from '../base-adapter'; -import type { LLMRequest, LLMResponse } from '../types'; - -export class MoonshotAdapter extends BaseLLMAdapter { - private baseUrl: string; - - constructor(config: any) { - super(config); - this.baseUrl = config.baseUrl || 'https://api.moonshot.cn/v1'; - } - - async complete(request: LLMRequest): Promise { - try { - await this.validateConfig(); - - return await this.retry(async () => { - return await this.withTimeout(this._sendRequest(request)); - }); - } catch (error) { - this.handleError(error, 'Moonshot API调用失败'); - } - } - - private async _sendRequest(request: LLMRequest): Promise { - // Moonshot API兼容OpenAI格式 - const headers: Record = { - 'Authorization': `Bearer ${this.config.apiKey}`, - }; - if (this.config.customHeaders) Object.assign(headers, this.config.customHeaders); - - const response = await fetch(`${this.baseUrl.replace(/\/$/, '')}/chat/completions`, { - method: 'POST', - headers: this.buildHeaders(headers), - body: JSON.stringify({ - model: this.config.model, - messages: request.messages, - temperature: request.temperature ?? this.config.temperature, - max_tokens: request.maxTokens ?? this.config.maxTokens, - top_p: request.topP ?? this.config.topP, - }), - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({})); - throw { - statusCode: response.status, - message: error.error?.message || `HTTP ${response.status}: ${response.statusText}`, - }; - } - - const data = await response.json(); - const choice = data.choices?.[0]; - - if (!choice) { - throw new Error('API响应格式异常: 缺少choices字段'); - } - - return { - content: choice.message?.content || '', - model: data.model, - usage: data.usage ? { - promptTokens: data.usage.prompt_tokens, - completionTokens: data.usage.completion_tokens, - totalTokens: data.usage.total_tokens, - } : undefined, - finishReason: choice.finish_reason, - }; - } - - async validateConfig(): Promise { - await super.validateConfig(); - - if (!this.config.model.startsWith('moonshot-')) { - throw new Error(`无效的Moonshot模型: ${this.config.model}`); - } - - return true; - } -} - diff --git a/src/shared/services/llm/adapters/ollama-adapter.ts b/src/shared/services/llm/adapters/ollama-adapter.ts deleted file mode 100644 index a7415ec..0000000 --- a/src/shared/services/llm/adapters/ollama-adapter.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Ollama适配器 - 支持本地运行的开源大模型 - * Ollama使用OpenAI兼容的API格式 - */ - -import { BaseLLMAdapter } from '../base-adapter'; -import type { LLMRequest, LLMResponse } from '../types'; - -export class OllamaAdapter extends BaseLLMAdapter { - private baseUrl: string; - - constructor(config: any) { - super(config); - this.baseUrl = config.baseUrl || 'http://localhost:11434/v1'; - } - - async complete(request: LLMRequest): Promise { - try { - // Ollama 不强制要求 API Key,但仍然验证配置 - await this.validateConfig(); - - return await this.retry(async () => { - return await this.withTimeout(this._sendRequest(request)); - }); - } catch (error) { - this.handleError(error, 'Ollama API调用失败'); - } - } - - private async _sendRequest(request: LLMRequest): Promise { - const headers: Record = { - 'Content-Type': 'application/json', - }; - - // 如果配置了 API Key,则添加到请求头(某些 Ollama 部署可能需要) - if (this.config.apiKey && this.config.apiKey !== 'ollama') { - headers['Authorization'] = `Bearer ${this.config.apiKey}`; - } - - // 合并自定义请求头 - if (this.config.customHeaders) { - Object.assign(headers, this.config.customHeaders); - } - - const response = await fetch(`${this.baseUrl.replace(/\/$/, '')}/chat/completions`, { - method: 'POST', - headers: this.buildHeaders(headers), - body: JSON.stringify({ - model: this.config.model, - messages: request.messages, - temperature: request.temperature ?? this.config.temperature, - max_tokens: request.maxTokens ?? this.config.maxTokens, - top_p: request.topP ?? this.config.topP, - stream: false, - }), - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({})); - throw { - statusCode: response.status, - message: error.error?.message || `HTTP ${response.status}: ${response.statusText}`, - }; - } - - const data = await response.json(); - const choice = data.choices?.[0]; - - if (!choice) { - throw new Error('API响应格式异常: 缺少choices字段'); - } - - return { - content: choice.message?.content || '', - model: data.model, - usage: data.usage ? { - promptTokens: data.usage.prompt_tokens || 0, - completionTokens: data.usage.completion_tokens || 0, - totalTokens: data.usage.total_tokens || 0, - } : undefined, - finishReason: choice.finish_reason, - }; - } - - /** - * Ollama 不强制要求 API Key - * 可以使用任意字符串作为占位符,或者不设置 - */ - async validateConfig(): Promise { - if (!this.config.model) { - throw new Error('未指定Ollama模型'); - } - - // Ollama 本地运行不需要验证 API Key - // 但如果配置了,我们保持兼容性 - return true; - } -} - diff --git a/src/shared/services/llm/adapters/openai-adapter.ts b/src/shared/services/llm/adapters/openai-adapter.ts deleted file mode 100644 index 4eb6c9d..0000000 --- a/src/shared/services/llm/adapters/openai-adapter.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * OpenAI适配器 (支持GPT系列) - */ - -import { BaseLLMAdapter } from '../base-adapter'; -import type { LLMRequest, LLMResponse } from '../types'; - -export class OpenAIAdapter extends BaseLLMAdapter { - private baseUrl: string; - - constructor(config: any) { - super(config); - this.baseUrl = config.baseUrl || 'https://api.openai.com/v1'; - } - - async complete(request: LLMRequest): Promise { - try { - await this.validateConfig(); - - return await this.retry(async () => { - return await this.withTimeout(this._sendRequest(request)); - }); - } catch (error) { - this.handleError(error, 'OpenAI API调用失败'); - } - } - - private async _sendRequest(request: LLMRequest): Promise { - // 构建请求头 - const headers: Record = { - 'Authorization': `Bearer ${this.config.apiKey}`, - }; - - // 合并自定义请求头 - if (this.config.customHeaders) { - Object.assign(headers, this.config.customHeaders); - } - - // 检测是否为推理模型(GPT-5 或 o1 系列,但排除 gpt-5-chat 等非推理模型) - const modelName = this.config.model.toLowerCase(); - const isReasoningModel = (modelName.includes('o1') || modelName.includes('o3')) || - (modelName.includes('gpt-5') && !modelName.includes('chat')); - - // 构建请求体 - const requestBody: any = { - model: this.config.model, - messages: request.messages, - temperature: request.temperature ?? this.config.temperature, - top_p: request.topP ?? this.config.topP, - frequency_penalty: this.config.frequencyPenalty, - presence_penalty: this.config.presencePenalty, - }; - - // GPT-5 推理模型使用 max_completion_tokens,其他模型使用 max_tokens - if (isReasoningModel) { - requestBody.max_completion_tokens = request.maxTokens ?? this.config.maxTokens; - } else { - requestBody.max_tokens = request.maxTokens ?? this.config.maxTokens; - } - - const response = await fetch(`${this.baseUrl.replace(/\/$/, '')}/chat/completions`, { - method: 'POST', - headers: this.buildHeaders(headers), - body: JSON.stringify(requestBody), - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({})); - throw { - statusCode: response.status, - message: error.error?.message || `HTTP ${response.status}: ${response.statusText}`, - }; - } - - const data = await response.json(); - const choice = data.choices?.[0]; - - if (!choice) { - throw new Error('API响应格式异常: 缺少choices字段'); - } - - return { - content: choice.message?.content || '', - model: data.model, - usage: data.usage ? { - promptTokens: data.usage.prompt_tokens, - completionTokens: data.usage.completion_tokens, - totalTokens: data.usage.total_tokens, - } : undefined, - finishReason: choice.finish_reason, - }; - } - - async validateConfig(): Promise { - await super.validateConfig(); - - if (!this.config.model) { - throw new Error('未指定OpenAI模型'); - } - - return true; - } -} - diff --git a/src/shared/services/llm/adapters/qwen-adapter.ts b/src/shared/services/llm/adapters/qwen-adapter.ts deleted file mode 100644 index 36a8fda..0000000 --- a/src/shared/services/llm/adapters/qwen-adapter.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * 阿里云通义千问适配器 - */ - -import { BaseLLMAdapter } from '../base-adapter'; -import type { LLMRequest, LLMResponse } from '../types'; - -export class QwenAdapter extends BaseLLMAdapter { - private baseUrl: string; - - constructor(config: any) { - super(config); - if (import.meta.env.DEV) { - this.baseUrl = '/dashscope-proxy/api/v1'; - } else { - this.baseUrl = config.baseUrl || 'https://dashscope.aliyuncs.com/api/v1'; - } - } - - async complete(request: LLMRequest): Promise { - try { - await this.validateConfig(); - - return await this.retry(async () => { - return await this.withTimeout(this._sendRequest(request)); - }); - } catch (error) { - this.handleError(error, '通义千问API调用失败'); - } - } - - private async _sendRequest(request: LLMRequest): Promise { - const headers: Record = { - 'Authorization': `Bearer ${this.config.apiKey}`, - 'X-DashScope-SSE': 'disable', - }; - if (this.config.customHeaders) Object.assign(headers, this.config.customHeaders); - - const response = await fetch(`${this.baseUrl.replace(/\/$/, '')}/services/aigc/text-generation/generation`, { - method: 'POST', - headers: this.buildHeaders(headers), - body: JSON.stringify({ - model: this.config.model, - input: { - messages: request.messages.map(msg => ({ - role: msg.role, - content: msg.content, - })), - }, - parameters: { - temperature: request.temperature ?? this.config.temperature, - max_tokens: request.maxTokens ?? this.config.maxTokens, - top_p: request.topP ?? this.config.topP, - result_format: 'message', - }, - }), - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({})); - throw { - statusCode: response.status, - message: error.message || `HTTP ${response.status}: ${response.statusText}`, - }; - } - - const data = await response.json(); - - if (data.code && data.code !== '200') { - throw new Error(`API错误 (${data.code}): ${data.message}`); - } - - const output = data.output; - if (!output?.choices?.[0]) { - throw new Error('API响应格式异常: 缺少output.choices字段'); - } - - const choice = output.choices[0]; - - return { - content: choice.message?.content || '', - model: this.config.model, - usage: output.usage ? { - promptTokens: output.usage.input_tokens, - completionTokens: output.usage.output_tokens, - totalTokens: output.usage.total_tokens || - (output.usage.input_tokens + output.usage.output_tokens), - } : undefined, - finishReason: choice.finish_reason, - }; - } - - async validateConfig(): Promise { - await super.validateConfig(); - - if (!this.config.model) { - throw new Error('未指定通义千问模型'); - } - - return true; - } -} - diff --git a/src/shared/services/llm/adapters/zhipu-adapter.ts b/src/shared/services/llm/adapters/zhipu-adapter.ts deleted file mode 100644 index 0e29abf..0000000 --- a/src/shared/services/llm/adapters/zhipu-adapter.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * 智谱AI (GLM系列)适配器 - */ - -import { BaseLLMAdapter } from '../base-adapter'; -import type { LLMRequest, LLMResponse } from '../types'; - -export class ZhipuAdapter extends BaseLLMAdapter { - private baseUrl: string; - - constructor(config: any) { - super(config); - this.baseUrl = config.baseUrl || 'https://open.bigmodel.cn/api/paas/v4'; - } - - async complete(request: LLMRequest): Promise { - try { - await this.validateConfig(); - - return await this.retry(async () => { - return await this.withTimeout(this._sendRequest(request)); - }); - } catch (error) { - this.handleError(error, '智谱AI API调用失败'); - } - } - - private async _sendRequest(request: LLMRequest): Promise { - // 智谱AI API兼容OpenAI格式 - const headers: Record = { - 'Authorization': `Bearer ${this.config.apiKey}`, - }; - if (this.config.customHeaders) Object.assign(headers, this.config.customHeaders); - - const response = await fetch(`${this.baseUrl.replace(/\/$/, '')}/chat/completions`, { - method: 'POST', - headers: this.buildHeaders(headers), - body: JSON.stringify({ - model: this.config.model, - messages: request.messages, - temperature: request.temperature ?? this.config.temperature, - max_tokens: request.maxTokens ?? this.config.maxTokens, - top_p: request.topP ?? this.config.topP, - }), - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({})); - throw { - statusCode: response.status, - message: error.error?.message || `HTTP ${response.status}: ${response.statusText}`, - }; - } - - const data = await response.json(); - const choice = data.choices?.[0]; - - if (!choice) { - throw new Error('API响应格式异常: 缺少choices字段'); - } - - return { - content: choice.message?.content || '', - model: data.model, - usage: data.usage ? { - promptTokens: data.usage.prompt_tokens, - completionTokens: data.usage.completion_tokens, - totalTokens: data.usage.total_tokens, - } : undefined, - finishReason: choice.finish_reason, - }; - } - - async validateConfig(): Promise { - await super.validateConfig(); - - if (!this.config.model.startsWith('glm-')) { - throw new Error(`无效的智谱AI模型: ${this.config.model}`); - } - - return true; - } -} - diff --git a/src/shared/services/llm/base-adapter.ts b/src/shared/services/llm/base-adapter.ts deleted file mode 100644 index 4fe7bf8..0000000 --- a/src/shared/services/llm/base-adapter.ts +++ /dev/null @@ -1,137 +0,0 @@ -/** - * LLM适配器基类 - */ - -import type { ILLMAdapter, LLMConfig, LLMRequest, LLMResponse, LLMProvider } from './types'; -import { LLMError, DEFAULT_LLM_CONFIG } from './types'; - -export abstract class BaseLLMAdapter implements ILLMAdapter { - protected config: LLMConfig; - - constructor(config: LLMConfig) { - this.config = { - ...DEFAULT_LLM_CONFIG, - ...config, - }; - } - - abstract complete(request: LLMRequest): Promise; - - getProvider(): LLMProvider { - return this.config.provider; - } - - getModel(): string { - return this.config.model; - } - - async validateConfig(): Promise { - if (!this.config.apiKey) { - throw new LLMError( - 'API Key未配置', - this.config.provider - ); - } - return true; - } - - /** - * 处理超时 - */ - protected async withTimeout( - promise: Promise, - timeoutMs: number = this.config.timeout || 150000 - ): Promise { - return Promise.race([ - promise, - new Promise((_, reject) => - setTimeout(() => reject(new LLMError( - `请求超时 (${timeoutMs}ms)`, - this.config.provider - )), timeoutMs) - ), - ]); - } - - /** - * 处理API错误 - */ - protected handleError(error: any, context?: string): never { - let message = error.message || error; - - // 针对不同错误类型提供更详细的信息 - if (error.name === 'AbortError' || message.includes('超时')) { - message = `请求超时 (${this.config.timeout}ms)。建议:\n` + - `1. 检查网络连接是否正常\n` + - `2. 尝试增加超时时间(在.env中设置 VITE_LLM_TIMEOUT)\n` + - `3. 验证API端点是否正确`; - } else if (error.statusCode === 401 || error.statusCode === 403) { - message = `API认证失败。建议:\n` + - `1. 检查API Key是否正确配置\n` + - `2. 确认API Key是否有效且未过期\n` + - `3. 验证API Key权限是否充足`; - } else if (error.statusCode === 429) { - message = `API调用频率超限。建议:\n` + - `1. 等待一段时间后重试\n` + - `2. 降低并发数(VITE_LLM_CONCURRENCY)\n` + - `3. 增加请求间隔(VITE_LLM_GAP_MS)`; - } else if (error.statusCode >= 500) { - message = `API服务异常 (${error.statusCode})。建议:\n` + - `1. 稍后重试\n` + - `2. 检查服务商状态页面\n` + - `3. 尝试切换其他LLM提供商`; - } - - const fullMessage = context ? `${context}: ${message}` : message; - - throw new LLMError( - fullMessage, - this.config.provider, - error.statusCode || error.status, - error - ); - } - - /** - * 重试逻辑 - */ - protected async retry( - fn: () => Promise, - maxAttempts: number = 3, - delay: number = 1000 - ): Promise { - let lastError: any; - - for (let attempt = 0; attempt < maxAttempts; attempt++) { - try { - return await fn(); - } catch (error: any) { - lastError = error; - - // 如果是4xx错误(客户端错误),不重试 - if (error.statusCode >= 400 && error.statusCode < 500) { - throw error; - } - - // 最后一次尝试时不等待 - if (attempt < maxAttempts - 1) { - // 指数退避 - await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, attempt))); - } - } - } - - throw lastError; - } - - /** - * 构建请求头 - */ - protected buildHeaders(additionalHeaders: Record = {}): Record { - return { - 'Content-Type': 'application/json', - ...additionalHeaders, - }; - } -} - diff --git a/src/shared/services/llm/index.ts b/src/shared/services/llm/index.ts deleted file mode 100644 index 8e70428..0000000 --- a/src/shared/services/llm/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * LLM服务统一导出 - */ - -// 类型定义 -export type { - LLMProvider, - LLMConfig, - LLMMessage, - LLMRequest, - LLMResponse, - ILLMAdapter, -} from './types'; - -// 工具类 -export { LLMError, DEFAULT_LLM_CONFIG, DEFAULT_MODELS, DEFAULT_BASE_URLS } from './types'; -export { BaseLLMAdapter } from './base-adapter'; - -// 适配器 -export * from './adapters'; - -// 工厂和服务 -export { LLMFactory } from './llm-factory'; -export { LLMService, createLLMService, getDefaultLLMService } from './llm-service'; - diff --git a/src/shared/services/llm/llm-factory.ts b/src/shared/services/llm/llm-factory.ts deleted file mode 100644 index f94dc8d..0000000 --- a/src/shared/services/llm/llm-factory.ts +++ /dev/null @@ -1,238 +0,0 @@ -/** - * LLM工厂类 - 统一创建和管理LLM适配器 - */ - -import type { ILLMAdapter, LLMConfig, LLMProvider } from './types'; -import { DEFAULT_MODELS } from './types'; -import { - GeminiAdapter, - OpenAIAdapter, - ClaudeAdapter, - QwenAdapter, - DeepSeekAdapter, - ZhipuAdapter, - MoonshotAdapter, - BaiduAdapter, - MinimaxAdapter, - DoubaoAdapter, - OllamaAdapter, -} from './adapters'; - -/** - * LLM工厂类 - */ -export class LLMFactory { - private static adapters: Map = new Map(); - - /** - * 创建LLM适配器实例 - */ - static createAdapter(config: LLMConfig): ILLMAdapter { - const cacheKey = this.getCacheKey(config); - - // 从缓存中获取 - if (this.adapters.has(cacheKey)) { - return this.adapters.get(cacheKey)!; - } - - // 创建新的适配器实例 - const adapter = this.instantiateAdapter(config); - - // 缓存实例 - this.adapters.set(cacheKey, adapter); - - return adapter; - } - - /** - * 根据提供商类型实例化适配器 - */ - private static instantiateAdapter(config: LLMConfig): ILLMAdapter { - // 如果未指定模型,使用默认模型 - if (!config.model) { - config.model = DEFAULT_MODELS[config.provider]; - } - - switch (config.provider) { - case 'gemini': - return new GeminiAdapter(config); - - case 'openai': - return new OpenAIAdapter(config); - - case 'claude': - return new ClaudeAdapter(config); - - case 'qwen': - return new QwenAdapter(config); - - case 'deepseek': - return new DeepSeekAdapter(config); - - case 'zhipu': - return new ZhipuAdapter(config); - - case 'moonshot': - return new MoonshotAdapter(config); - - case 'baidu': - return new BaiduAdapter(config); - - case 'minimax': - return new MinimaxAdapter(config); - - case 'doubao': - return new DoubaoAdapter(config); - - case 'ollama': - return new OllamaAdapter(config); - - default: - throw new Error(`不支持的LLM提供商: ${config.provider}`); - } - } - - /** - * 生成缓存键 - */ - private static getCacheKey(config: LLMConfig): string { - return `${config.provider}:${config.model}:${config.apiKey.substring(0, 8)}`; - } - - /** - * 清除缓存 - */ - static clearCache(): void { - this.adapters.clear(); - } - - /** - * 获取支持的提供商列表 - */ - static getSupportedProviders(): LLMProvider[] { - return [ - 'gemini', - 'openai', - 'claude', - 'qwen', - 'deepseek', - 'zhipu', - 'moonshot', - 'baidu', - 'minimax', - 'doubao', - 'ollama', - ]; - } - - /** - * 获取提供商的默认模型 - */ - static getDefaultModel(provider: LLMProvider): string { - return DEFAULT_MODELS[provider]; - } - - /** - * 获取提供商的可用模型列表 - */ - static getAvailableModels(provider: LLMProvider): string[] { - const models: Record = { - gemini: [ - 'gemini-2.5-flash', - 'gemini-2.5-pro', - 'gemini-1.5-flash', - 'gemini-1.5-pro', - ], - openai: [ - 'gpt-4o', - 'gpt-4o-mini', - 'gpt-4-turbo', - 'gpt-4', - 'gpt-3.5-turbo', - ], - claude: [ - 'claude-3-5-sonnet-20241022', - 'claude-3-5-haiku-20241022', - 'claude-3-opus-20240229', - 'claude-3-sonnet-20240229', - 'claude-3-haiku-20240307', - ], - qwen: [ - 'qwen-turbo', - 'qwen-plus', - 'qwen-max', - 'qwen-max-longcontext', - ], - deepseek: [ - 'deepseek-chat', - 'deepseek-coder', - ], - zhipu: [ - 'glm-4-flash', - 'glm-4', - 'glm-4-air', - 'glm-3-turbo', - ], - moonshot: [ - 'moonshot-v1-8k', - 'moonshot-v1-32k', - 'moonshot-v1-128k', - ], - baidu: [ - 'ERNIE-4.0-8K', - 'ERNIE-3.5-8K', - 'ERNIE-3.5-128K', - 'ERNIE-Speed-8K', - 'ERNIE-Speed-128K', - 'ERNIE-Lite-8K', - 'ERNIE-Tiny-8K', - ], - minimax: [ - 'abab6.5-chat', - 'abab6.5s-chat', - 'abab5.5-chat', - ], - doubao: [ - 'doubao-pro-32k', - 'doubao-pro-128k', - 'doubao-lite-32k', - 'doubao-lite-128k', - ], - ollama: [ - 'llama3', - 'llama3.1', - 'llama3.2', - 'mistral', - 'codellama', - 'qwen2.5', - 'gemma2', - 'phi3', - 'deepseek-coder', - ], - }; - - return models[provider] || []; - } - - /** - * 获取提供商的友好名称 - */ - static getProviderDisplayName(provider: LLMProvider): string { - const names: Record = { - gemini: 'Google Gemini', - openai: 'OpenAI GPT', - claude: 'Anthropic Claude', - qwen: '阿里云通义千问', - deepseek: 'DeepSeek', - zhipu: '智谱AI (GLM)', - moonshot: '月之暗面 Kimi', - baidu: '百度文心一言', - minimax: 'MiniMax', - doubao: '字节豆包', - ollama: 'Ollama 本地大模型', - }; - - return names[provider] || provider; - } -} - diff --git a/src/shared/services/llm/llm-service.ts b/src/shared/services/llm/llm-service.ts deleted file mode 100644 index ac4f765..0000000 --- a/src/shared/services/llm/llm-service.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * LLM服务 - 统一的LLM调用接口 - */ - -import type { ILLMAdapter, LLMConfig, LLMRequest, LLMResponse } from './types'; -import { LLMFactory } from './llm-factory'; -import { env } from '@/shared/config/env'; - -/** - * LLM服务类 - */ -export class LLMService { - private adapter: ILLMAdapter; - - constructor(config: LLMConfig) { - this.adapter = LLMFactory.createAdapter(config); - } - - /** - * 发送请求 - */ - async complete(request: LLMRequest): Promise { - return await this.adapter.complete(request); - } - - /** - * 简单的文本补全 - */ - async simpleComplete(prompt: string, systemPrompt?: string): Promise { - const messages: any[] = []; - - if (systemPrompt) { - messages.push({ role: 'system', content: systemPrompt }); - } - - messages.push({ role: 'user', content: prompt }); - - const response = await this.adapter.complete({ messages }); - return response.content; - } - - /** - * 验证配置 - */ - async validateConfig(): Promise { - return await this.adapter.validateConfig(); - } - - /** - * 获取提供商 - */ - getProvider() { - return this.adapter.getProvider(); - } - - /** - * 获取模型 - */ - getModel() { - return this.adapter.getModel(); - } - - /** - * 从环境变量创建默认实例 - */ - static createFromEnv(): LLMService { - const provider = env.LLM_PROVIDER as any || 'gemini'; - const apiKey = env.LLM_API_KEY || env.GEMINI_API_KEY; - const model = env.LLM_MODEL || env.GEMINI_MODEL; - - if (!apiKey) { - throw new Error('未配置LLM API Key,请在环境变量中设置'); - } - - // 获取 baseUrl,优先使用通用配置,然后是平台专用配置 - let baseUrl = env.LLM_BASE_URL; - if (!baseUrl && provider === 'openai') { - baseUrl = env.OPENAI_BASE_URL; - } else if (!baseUrl && provider === 'ollama') { - baseUrl = env.OLLAMA_BASE_URL; - } - - // 解析自定义请求头 - let customHeaders: Record | undefined; - if (env.LLM_CUSTOM_HEADERS) { - try { - customHeaders = JSON.parse(env.LLM_CUSTOM_HEADERS); - } catch (e) { - console.warn('Invalid LLM_CUSTOM_HEADERS format, should be JSON string'); - } - } - - const config: LLMConfig = { - provider, - apiKey, - model, - baseUrl, - timeout: env.LLM_TIMEOUT || env.GEMINI_TIMEOUT_MS, - temperature: env.LLM_TEMPERATURE, - maxTokens: env.LLM_MAX_TOKENS, - customHeaders, - }; - - return new LLMService(config); - } -} - -/** - * 创建LLM服务实例的便捷函数 - */ -export function createLLMService(config: LLMConfig): LLMService { - return new LLMService(config); -} - -/** - * 获取默认的LLM服务实例 - */ -export function getDefaultLLMService(): LLMService { - return LLMService.createFromEnv(); -} - diff --git a/src/shared/services/llm/types.ts b/src/shared/services/llm/types.ts deleted file mode 100644 index d4291de..0000000 --- a/src/shared/services/llm/types.ts +++ /dev/null @@ -1,139 +0,0 @@ -/** - * LLM服务类型定义 - */ - -// 支持的LLM提供商类型 -export type LLMProvider = - | 'gemini' // Google Gemini - | 'openai' // OpenAI (GPT系列) - | 'claude' // Anthropic Claude - | 'qwen' // 阿里云通义千问 - | 'deepseek' // DeepSeek - | 'zhipu' // 智谱AI (GLM系列) - | 'moonshot' // 月之暗面 Kimi - | 'baidu' // 百度文心一言 - | 'minimax' // MiniMax - | 'doubao' // 字节豆包 - | 'ollama'; // Ollama 本地大模型 - -// LLM配置接口 -export interface LLMConfig { - provider: LLMProvider; - apiKey: string; - model: string; - baseUrl?: string; // 自定义API端点 - timeout?: number; // 超时时间(ms) - temperature?: number; // 温度参数 - maxTokens?: number; // 最大token数 - topP?: number; // Top-p采样 - frequencyPenalty?: number; // 频率惩罚 - presencePenalty?: number; // 存在惩罚 - customHeaders?: Record; // 自定义请求头 -} - -// LLM请求消息 -export interface LLMMessage { - role: 'system' | 'user' | 'assistant'; - content: string; -} - -// LLM请求参数 -export interface LLMRequest { - messages: LLMMessage[]; - temperature?: number; - maxTokens?: number; - topP?: number; - stream?: boolean; -} - -// LLM响应 -export interface LLMResponse { - content: string; - model?: string; - usage?: { - promptTokens: number; - completionTokens: number; - totalTokens: number; - }; - finishReason?: string; -} - -// LLM适配器接口 -export interface ILLMAdapter { - /** - * 发送请求并获取响应 - */ - complete(request: LLMRequest): Promise; - - /** - * 流式响应(可选) - */ - streamComplete?(request: LLMRequest): AsyncGenerator; - - /** - * 获取提供商名称 - */ - getProvider(): LLMProvider; - - /** - * 获取模型名称 - */ - getModel(): string; - - /** - * 验证配置是否有效 - */ - validateConfig(): Promise; -} - -// 错误类型 -export class LLMError extends Error { - constructor( - message: string, - public provider: LLMProvider, - public statusCode?: number, - public originalError?: any - ) { - super(message); - this.name = 'LLMError'; - } -} - -// 默认配置 -export const DEFAULT_LLM_CONFIG: Partial = { - timeout: 150000, - temperature: 0.2, - maxTokens: 4096, - topP: 1.0, - frequencyPenalty: 0, - presencePenalty: 0, -}; - -// 各平台默认模型 -export const DEFAULT_MODELS: Record = { - gemini: 'gemini-2.5-flash', - openai: 'gpt-4o-mini', - claude: 'claude-3-5-sonnet-20241022', - qwen: 'qwen-turbo', - deepseek: 'deepseek-chat', - zhipu: 'glm-4-flash', - moonshot: 'moonshot-v1-8k', - baidu: 'ERNIE-3.5-8K', - minimax: 'abab6.5-chat', - doubao: 'doubao-pro-32k', - ollama: 'llama3', -}; - -// 各平台API端点 -export const DEFAULT_BASE_URLS: Partial> = { - openai: 'https://api.openai.com/v1', - qwen: 'https://dashscope.aliyuncs.com/api/v1', - deepseek: 'https://api.deepseek.com', - zhipu: 'https://open.bigmodel.cn/api/paas/v4', - moonshot: 'https://api.moonshot.cn/v1', - baidu: 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1', - minimax: 'https://api.minimax.chat/v1', - doubao: 'https://ark.cn-beijing.volces.com/api/v3', - ollama: 'http://localhost:11434/v1', -}; - diff --git a/src/shared/services/taskControl.ts b/src/shared/services/taskControl.ts deleted file mode 100644 index 9fa079e..0000000 --- a/src/shared/services/taskControl.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * 全局任务控制管理器 - * 用于取消正在运行的审计任务 - */ - -class TaskControlManager { - private cancelledTasks: Set = new Set(); - - /** - * 取消任务 - */ - cancelTask(taskId: string) { - this.cancelledTasks.add(taskId); - console.log(`🛑 任务 ${taskId} 已标记为取消`); - } - - /** - * 检查任务是否被取消 - */ - isCancelled(taskId: string): boolean { - return this.cancelledTasks.has(taskId); - } - - /** - * 清理已完成任务的控制状态 - */ - cleanupTask(taskId: string) { - this.cancelledTasks.delete(taskId); - } -} - -// 导出单例 -export const taskControl = new TaskControlManager(); - diff --git a/src/shared/utils/initLocalDB.ts b/src/shared/utils/initLocalDB.ts deleted file mode 100644 index a9fdb4e..0000000 --- a/src/shared/utils/initLocalDB.ts +++ /dev/null @@ -1,118 +0,0 @@ -/** - * 本地数据库初始化工具 - * 用于在首次使用时创建默认用户和演示数据 - */ - -import { localDB } from '../config/localDatabase'; -import { api } from '../config/database'; - -/** - * 初始化本地数据库 - * 创建默认用户和基础数据 - */ -export async function initLocalDatabase(): Promise { - try { - // 初始化数据库 - await localDB.init(); - - // 检查是否已有用户 - const profileCount = await localDB.getProfilesCount(); - - if (profileCount === 0) { - // 创建默认本地用户 - await api.createProfiles({ - id: 'local-user', - email: 'local@xcodereviewer.com', - full_name: '本地用户', - role: 'admin', - github_username: 'local-user', - }); - - console.log('✅ 本地数据库初始化成功'); - } - } catch (error) { - console.error('❌ 本地数据库初始化失败:', error); - throw error; - } -} - -/** - * 清空本地数据库 - * 用于重置或清理数据 - */ -export async function clearLocalDatabase(): Promise { - try { - const dbName = 'xcodereviewer_local'; - const request = indexedDB.deleteDatabase(dbName); - - return new Promise((resolve, reject) => { - request.onsuccess = () => { - console.log('✅ 本地数据库已清空'); - resolve(); - }; - request.onerror = () => { - console.error('❌ 清空本地数据库失败'); - reject(request.error); - }; - }); - } catch (error) { - console.error('❌ 清空本地数据库失败:', error); - throw error; - } -} - -/** - * 导出本地数据库数据 - * 用于备份或迁移 - */ -export async function exportLocalDatabase(): Promise { - try { - await localDB.init(); - - const data = { - version: 1, - exportDate: new Date().toISOString(), - profiles: await localDB.getAllProfiles(), - projects: await localDB.getProjects(), - auditTasks: await localDB.getAuditTasks(), - }; - - return JSON.stringify(data, null, 2); - } catch (error) { - console.error('❌ 导出数据失败:', error); - throw error; - } -} - -/** - * 导入数据到本地数据库 - * 用于恢复备份或迁移数据 - */ -export async function importLocalDatabase(jsonData: string): Promise { - try { - const data = JSON.parse(jsonData); - - if (!data.version || !data.profiles) { - throw new Error('无效的数据格式'); - } - - await localDB.init(); - - // 导入用户 - for (const profile of data.profiles) { - await api.createProfiles(profile); - } - - // 导入项目 - if (data.projects) { - for (const project of data.projects) { - await api.createProject(project); - } - } - - console.log('✅ 数据导入成功'); - } catch (error) { - console.error('❌ 导入数据失败:', error); - throw error; - } -}