commit 410b2f068daa85beb5915036731d0a86a1f0e7c5 Author: 树萌芽 <3205788256@qq.com> Date: Sun Dec 14 15:40:49 2025 +0800 初始化提交 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..38bbefa --- /dev/null +++ b/.gitignore @@ -0,0 +1,110 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Virtual Environment +venv/ +env/ +ENV/ +env.bak/ +venv.bak/ +.venv + +# Flask +instance/ +.webassets-cache + +# SQLite +*.db +*.sqlite +*.sqlite3 + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Node.js +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Build outputs +dist/ +dist-ssr/ +*.local + +# Vite +.vite/ +vite.config.*.timestamp-* + +# TypeScript +*.tsbuildinfo + +# Testing +coverage/ +.nyc_output/ +*.lcov + +# Logs +logs/ +*.log + +# OS +Thumbs.db +.DS_Store +Desktop.ini + +# Temporary files +*.tmp +*.temp +*.bak +*.cache + +# Database +*.db-journal +*.db-wal +*.db-shm + +# Backup files +*.backup +*.old + +# Local configuration +config.local.py +settings.local.py + diff --git a/NBATransfer-backend/app.py b/NBATransfer-backend/app.py new file mode 100644 index 0000000..ee9d739 --- /dev/null +++ b/NBATransfer-backend/app.py @@ -0,0 +1,76 @@ +from flask import Flask +from flask_cors import CORS +from config import config +from extensions import db, jwt, mail +import os + +# 初始化扩展 +# jwt = JWTManager() # Moved to extensions.py +# mail = Mail() # Moved to extensions.py + + +def create_app(config_name='default'): + """应用工厂函数""" + app = Flask(__name__) + + # 加载配置 + app.config.from_object(config[config_name]) + + # 初始化扩展 + db.init_app(app) + jwt.init_app(app) + mail.init_app(app) + CORS(app, resources={ + r"/api/*": {"origins": "*"}, + r"/v1/*": {"origins": "*"} + }) + + # 注册蓝图 + from routes.auth import auth_bp + from routes.user import user_bp + from routes.order import order_bp + from routes.api_service import api_bp + from routes.admin import admin_bp + from routes.apikey import apikey_bp + from routes.v1_api import v1_bp + + app.register_blueprint(auth_bp, url_prefix='/api/auth') + app.register_blueprint(user_bp, url_prefix='/api/user') + app.register_blueprint(order_bp, url_prefix='/api/order') + app.register_blueprint(api_bp, url_prefix='/api/service') + app.register_blueprint(admin_bp, url_prefix='/api/admin') + app.register_blueprint(apikey_bp, url_prefix='/api/apikey') + app.register_blueprint(v1_bp, url_prefix='/v1') + + # 创建数据库表 + with app.app_context(): + db.create_all() + # 创建默认管理员账户 + from models import User + admin = User.query.filter_by(email='admin@nba.com').first() + if not admin: + admin = User( + email='admin@nba.com', + username='Admin', + is_admin=True, + is_active=True + ) + admin.set_password('admin123') + db.session.add(admin) + db.session.commit() + print('默认管理员账户已创建: admin@nba.com / admin123') + + @app.route('/') + def index(): + return {'message': 'Nano Banana API Transfer Service', 'version': '1.0.0'} + + @app.route('/health') + def health(): + return {'status': 'healthy'} + + return app + + +if __name__ == '__main__': + app = create_app(os.getenv('FLASK_ENV', 'development')) + app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/NBATransfer-backend/config.py b/NBATransfer-backend/config.py new file mode 100644 index 0000000..4121413 --- /dev/null +++ b/NBATransfer-backend/config.py @@ -0,0 +1,58 @@ +import os +from datetime import timedelta +from dotenv import load_dotenv + +load_dotenv() + +class Config: + """基础配置""" + # Flask配置 + SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production') + + # 数据库配置 + SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URI', 'sqlite:///nba_transfer.db') + SQLALCHEMY_TRACK_MODIFICATIONS = False + + # JWT配置 + JWT_SECRET_KEY = os.getenv('JWT_SECRET_KEY', 'jwt-secret-key-change-in-production') + JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=24) + JWT_REFRESH_TOKEN_EXPIRES = timedelta(days=30) + + # 邮件配置 + MAIL_SERVER = os.getenv('MAIL_SERVER', 'smtp.qq.com') + MAIL_PORT = int(os.getenv('MAIL_PORT', 465)) + MAIL_USE_TLS = os.getenv('MAIL_USE_TLS', 'False') == 'True' + MAIL_USE_SSL = os.getenv('MAIL_USE_SSL', 'True') == 'True' + MAIL_USERNAME = os.getenv('MAIL_USERNAME') + MAIL_PASSWORD = os.getenv('MAIL_PASSWORD') + MAIL_DEFAULT_SENDER = os.getenv('MAIL_USERNAME') + + # 支付配置 + WECHAT_PAY_APP_ID = os.getenv('WECHAT_PAY_APP_ID') + WECHAT_PAY_MCH_ID = os.getenv('WECHAT_PAY_MCH_ID') + WECHAT_PAY_API_KEY = os.getenv('WECHAT_PAY_API_KEY') + ALIPAY_APP_ID = os.getenv('ALIPAY_APP_ID') + ALIPAY_PRIVATE_KEY = os.getenv('ALIPAY_PRIVATE_KEY') + ALIPAY_PUBLIC_KEY = os.getenv('ALIPAY_PUBLIC_KEY') + + # 注意: + # 模型 API 配置 (DeepSeek, NanoBanana) 和 价格策略 + # 已迁移至 modelapiservice 模块下的 config.py 中独立管理 + # 此处不再保留,避免重复定义和混乱 + + +class DevelopmentConfig(Config): + """开发环境配置""" + DEBUG = True + + +class ProductionConfig(Config): + """生产环境配置""" + DEBUG = False + + +config = { + 'development': DevelopmentConfig, + 'production': ProductionConfig, + 'default': DevelopmentConfig +} diff --git a/NBATransfer-backend/extensions.py b/NBATransfer-backend/extensions.py new file mode 100644 index 0000000..cd6e04a --- /dev/null +++ b/NBATransfer-backend/extensions.py @@ -0,0 +1,7 @@ +from flask_sqlalchemy import SQLAlchemy +from flask_jwt_extended import JWTManager +from flask_mail import Mail + +db = SQLAlchemy() +jwt = JWTManager() +mail = Mail() diff --git a/NBATransfer-backend/modelapiservice/DeepSeek/config.py b/NBATransfer-backend/modelapiservice/DeepSeek/config.py new file mode 100644 index 0000000..10a43ce --- /dev/null +++ b/NBATransfer-backend/modelapiservice/DeepSeek/config.py @@ -0,0 +1,16 @@ +# DeepSeek 模型配置 +import os + +# 价格配置 (CNY per 1M tokens) +TOKEN_PRICE_INPUT = 400.0 / 1000000 # 4元 / 1M tokens +TOKEN_PRICE_OUTPUT = 1600.0 / 1000000 # 16元 / 1M tokens + +# 最低余额要求 +MIN_BALANCE = 0.01 + +# API 配置 (优先从环境变量获取) +def get_config(): + return { + 'api_url': os.getenv('DEEPSEEK_API_URL', 'https://api.deepseek.com'), + 'api_key': os.getenv('DEEPSEEK_API_KEY') + } diff --git a/NBATransfer-backend/modelapiservice/DeepSeek/service.py b/NBATransfer-backend/modelapiservice/DeepSeek/service.py new file mode 100644 index 0000000..65ef059 --- /dev/null +++ b/NBATransfer-backend/modelapiservice/DeepSeek/service.py @@ -0,0 +1,54 @@ +from ..base import ModelService +from .config import TOKEN_PRICE_INPUT, TOKEN_PRICE_OUTPUT, MIN_BALANCE, get_config +from typing import Dict, Any, Tuple, Generator, Union +import json +import logging + +logger = logging.getLogger(__name__) + +class DeepSeekService(ModelService): + def get_api_config(self) -> Tuple[str, str]: + config = get_config() + return config['api_url'], config['api_key'] + + def check_balance(self, balance: float) -> Tuple[bool, float, str]: + if balance < MIN_BALANCE: + return False, MIN_BALANCE, f'余额不足。当前余额: {balance}, 需要至少: {MIN_BALANCE}' + return True, 0.0, "" + + def calculate_cost(self, usage: Dict[str, Any] = None, stream: bool = False) -> float: + if not usage: + return 0.0 + + prompt_tokens = usage.get('prompt_tokens', 0) + completion_tokens = usage.get('completion_tokens', 0) + + cost = (prompt_tokens * TOKEN_PRICE_INPUT) + (completion_tokens * TOKEN_PRICE_OUTPUT) + return cost + + def prepare_payload(self, data: Dict[str, Any]) -> Dict[str, Any]: + payload = {k: v for k, v in data.items() if k not in ['user']} + # 强制 DeepSeek 返回 usage + if data.get('stream', False): + if 'stream_options' not in payload: + payload['stream_options'] = {"include_usage": True} + return payload + + def handle_response(self, response, stream: bool) -> Union[Dict[str, Any], Generator]: + # Response handling is mostly done in the route handler for stream/json split + # but we can provide helper methods to parse usage + pass + + @staticmethod + def parse_stream_usage(chunk_text: str) -> Dict[str, Any]: + """解析流式响应中的 usage""" + try: + for line in chunk_text.split('\n'): + if line.startswith('data: ') and line != 'data: [DONE]': + json_str = line[6:] + data_obj = json.loads(json_str) + if 'usage' in data_obj and data_obj['usage']: + return data_obj['usage'] + except Exception: + pass + return None diff --git a/NBATransfer-backend/modelapiservice/NanoBanana/config.py b/NBATransfer-backend/modelapiservice/NanoBanana/config.py new file mode 100644 index 0000000..29f8a6f --- /dev/null +++ b/NBATransfer-backend/modelapiservice/NanoBanana/config.py @@ -0,0 +1,12 @@ +# Nano Banana 模型配置 +import os + +# 价格配置 (CNY per call) +IMAGE_GENERATION_PRICE = float(os.getenv('IMAGE_GENERATION_PRICE', 0.15)) + +# API 配置 (优先从环境变量获取) +def get_config(): + return { + 'api_url': os.getenv('NANO_BANANA_API_URL', 'https://api.nanobanana.com/v1'), + 'api_key': os.getenv('NANO_BANANA_API_KEY') + } diff --git a/NBATransfer-backend/modelapiservice/NanoBanana/service.py b/NBATransfer-backend/modelapiservice/NanoBanana/service.py new file mode 100644 index 0000000..e91873e --- /dev/null +++ b/NBATransfer-backend/modelapiservice/NanoBanana/service.py @@ -0,0 +1,26 @@ +from ..base import ModelService +from .config import IMAGE_GENERATION_PRICE, get_config +from typing import Dict, Any, Tuple, Generator, Union +import logging + +logger = logging.getLogger(__name__) + +class NanoBananaService(ModelService): + def get_api_config(self) -> Tuple[str, str]: + config = get_config() + return config['api_url'], config['api_key'] + + def check_balance(self, balance: float) -> Tuple[bool, float, str]: + if balance < IMAGE_GENERATION_PRICE: + return False, IMAGE_GENERATION_PRICE, f'余额不足。当前余额: {balance}, 需要: {IMAGE_GENERATION_PRICE}' + return True, IMAGE_GENERATION_PRICE, "" + + def calculate_cost(self, usage: Dict[str, Any] = None, stream: bool = False) -> float: + # 固定按次计费 + return IMAGE_GENERATION_PRICE + + def prepare_payload(self, data: Dict[str, Any]) -> Dict[str, Any]: + return {k: v for k, v in data.items() if k not in ['user']} + + def handle_response(self, response, stream: bool) -> Union[Dict[str, Any], Generator]: + pass diff --git a/NBATransfer-backend/modelapiservice/__init__.py b/NBATransfer-backend/modelapiservice/__init__.py new file mode 100644 index 0000000..1dd1504 --- /dev/null +++ b/NBATransfer-backend/modelapiservice/__init__.py @@ -0,0 +1,10 @@ +from .DeepSeek.service import DeepSeekService +from .NanoBanana.service import NanoBananaService + +def get_model_service(model_name: str): + """根据模型名称获取对应的服务实例""" + if model_name.startswith('deepseek-'): + return DeepSeekService() + else: + # 默认使用 Nano Banana 服务 (包括文生图等) + return NanoBananaService() diff --git a/NBATransfer-backend/modelapiservice/base.py b/NBATransfer-backend/modelapiservice/base.py new file mode 100644 index 0000000..43d6647 --- /dev/null +++ b/NBATransfer-backend/modelapiservice/base.py @@ -0,0 +1,33 @@ +from abc import ABC, abstractmethod +from typing import Dict, Any, Generator, Union, Tuple +import requests + +class ModelService(ABC): + """大模型服务基类""" + + @abstractmethod + def get_api_config(self) -> Tuple[str, str]: + """获取 API URL 和 Key""" + pass + + @abstractmethod + def calculate_cost(self, usage: Dict[str, Any] = None, stream: bool = False) -> float: + """计算费用""" + pass + + @abstractmethod + def check_balance(self, balance: float) -> Tuple[bool, float, str]: + """检查余额是否充足 + Returns: (is_sufficient, estimated_cost, error_message) + """ + pass + + @abstractmethod + def prepare_payload(self, data: Dict[str, Any]) -> Dict[str, Any]: + """准备请求载荷""" + pass + + @abstractmethod + def handle_response(self, response: requests.Response, stream: bool) -> Union[Dict[str, Any], Generator]: + """处理响应""" + pass diff --git a/NBATransfer-backend/models.py b/NBATransfer-backend/models.py new file mode 100644 index 0000000..b0b94ae --- /dev/null +++ b/NBATransfer-backend/models.py @@ -0,0 +1,233 @@ +from datetime import datetime, timedelta +from flask_sqlalchemy import SQLAlchemy +from werkzeug.security import generate_password_hash, check_password_hash +import secrets +import string +from extensions import db + +# db = SQLAlchemy() # Moved to extensions.py + + +class User(db.Model): + """用户模型""" + __tablename__ = 'users' + + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.String(120), unique=True, nullable=False, index=True) + password_hash = db.Column(db.String(255), nullable=False) + username = db.Column(db.String(80)) + balance = db.Column(db.Float, default=0.0) # 账户余额 + is_active = db.Column(db.Boolean, default=True) + is_admin = db.Column(db.Boolean, default=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + # 关系 + orders = db.relationship('Order', backref='user', lazy='dynamic', cascade='all, delete-orphan') + api_calls = db.relationship('ApiCall', backref='user', lazy='dynamic', cascade='all, delete-orphan') + transactions = db.relationship('Transaction', backref='user', lazy='dynamic', cascade='all, delete-orphan') + api_keys = db.relationship('APIKey', backref='user', lazy='dynamic', cascade='all, delete-orphan') + verification_codes = db.relationship('VerificationCode', backref='user', lazy='dynamic', cascade='all, delete-orphan') + + # 验证状态 + email_verified = db.Column(db.Boolean, default=False) + email_verified_at = db.Column(db.DateTime) + + def set_password(self, password): + """设置密码""" + self.password_hash = generate_password_hash(password) + + def check_password(self, password): + """验证密码""" + return check_password_hash(self.password_hash, password) + + def to_dict(self): + """转换为字典""" + return { + 'id': self.id, + 'email': self.email, + 'username': self.username, + 'balance': self.balance, + 'is_active': self.is_active, + 'is_admin': self.is_admin, + 'created_at': self.created_at.isoformat(), + 'updated_at': self.updated_at.isoformat() + } + + +class Order(db.Model): + """订单模型""" + __tablename__ = 'orders' + + id = db.Column(db.Integer, primary_key=True) + order_no = db.Column(db.String(50), unique=True, nullable=False, index=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + amount = db.Column(db.Float, nullable=False) # 充值金额 + payment_method = db.Column(db.String(20)) # wechat, alipay + status = db.Column(db.String(20), default='pending') # pending, paid, cancelled, failed + transaction_id = db.Column(db.String(100)) # 第三方交易ID + created_at = db.Column(db.DateTime, default=datetime.utcnow) + paid_at = db.Column(db.DateTime) + + def to_dict(self): + """转换为字典""" + return { + 'id': self.id, + 'order_no': self.order_no, + 'user_id': self.user_id, + 'amount': self.amount, + 'payment_method': self.payment_method, + 'status': self.status, + 'transaction_id': self.transaction_id, + 'created_at': self.created_at.isoformat(), + 'paid_at': self.paid_at.isoformat() if self.paid_at else None + } + + +class Transaction(db.Model): + """交易记录模型""" + __tablename__ = 'transactions' + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + type = db.Column(db.String(20), nullable=False) # recharge, consume, refund + amount = db.Column(db.Float, nullable=False) + balance_before = db.Column(db.Float, nullable=False) + balance_after = db.Column(db.Float, nullable=False) + description = db.Column(db.String(255)) + order_id = db.Column(db.Integer, db.ForeignKey('orders.id')) + api_call_id = db.Column(db.Integer, db.ForeignKey('api_calls.id')) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + def to_dict(self): + """转换为字典""" + return { + 'id': self.id, + 'user_id': self.user_id, + 'type': self.type, + 'amount': self.amount, + 'balance_before': self.balance_before, + 'balance_after': self.balance_after, + 'description': self.description, + 'order_id': self.order_id, + 'api_call_id': self.api_call_id, + 'created_at': self.created_at.isoformat() + } + + +class ApiCall(db.Model): + """API调用记录模型""" + __tablename__ = 'api_calls' + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + api_type = db.Column(db.String(50), default='text_to_image') # text_to_image + prompt = db.Column(db.Text) + parameters = db.Column(db.Text) # JSON格式的参数 + status = db.Column(db.String(20), default='pending') # pending, processing, success, failed + result_url = db.Column(db.String(500)) # 生成结果的URL + cost = db.Column(db.Float, default=0.0) # 本次调用费用 + error_message = db.Column(db.Text) + request_time = db.Column(db.DateTime, default=datetime.utcnow) + response_time = db.Column(db.DateTime) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + def to_dict(self): + """转换为字典""" + return { + 'id': self.id, + 'user_id': self.user_id, + 'api_type': self.api_type, + 'prompt': self.prompt, + 'status': self.status, + 'result_url': self.result_url, + 'cost': self.cost, + 'error_message': self.error_message, + 'request_time': self.request_time.isoformat(), + 'response_time': self.response_time.isoformat() if self.response_time else None, + 'created_at': self.created_at.isoformat() + } + + +class SystemConfig(db.Model): + """系统配置模型""" + __tablename__ = 'system_configs' + + id = db.Column(db.Integer, primary_key=True) + key = db.Column(db.String(50), unique=True, nullable=False) + value = db.Column(db.Text) + description = db.Column(db.String(255)) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + def to_dict(self): + """转换为字典""" + return { + 'id': self.id, + 'key': self.key, + 'value': self.value, + 'description': self.description, + 'updated_at': self.updated_at.isoformat() + } + + +class VerificationCode(db.Model): + """邮箱验证码模型""" + __tablename__ = 'verification_codes' + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + email = db.Column(db.String(120), nullable=False) + code = db.Column(db.String(6), nullable=False) # 6位验证码 + purpose = db.Column(db.String(20), default='register') # register, password_reset + used = db.Column(db.Boolean, default=False) + expired_at = db.Column(db.DateTime, nullable=False) # 过期时间 + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + def is_valid(self): + """检查验证码是否有效""" + return not self.used and datetime.utcnow() < self.expired_at + + @staticmethod + def generate_code(): + """生成6位验证码""" + return ''.join(secrets.choice(string.digits) for _ in range(6)) + + def to_dict(self): + return { + 'id': self.id, + 'email': self.email, + 'purpose': self.purpose, + 'expired_at': self.expired_at.isoformat() + } + + +class APIKey(db.Model): + """API密钥模型 - 用户可自己生成""" + __tablename__ = 'api_keys' + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + name = db.Column(db.String(100), nullable=False) # API密钥名称 + api_key = db.Column(db.String(100), unique=True, nullable=False, index=True) # 实际密钥 + # secret_key 字段已移除 + is_active = db.Column(db.Boolean, default=True) + last_used_at = db.Column(db.DateTime) # 最后使用时间 + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + @staticmethod + def generate_key(): + """生成 API Key""" + api_key = 'sk_' + ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(32)) + return api_key + + def to_dict(self): + """转换为字典""" + data = { + 'id': self.id, + 'name': self.name, + 'api_key': self.api_key, + 'is_active': self.is_active, + 'last_used_at': self.last_used_at.isoformat() if self.last_used_at else None, + 'created_at': self.created_at.isoformat() + } + return data diff --git a/NBATransfer-backend/requirements.txt b/NBATransfer-backend/requirements.txt new file mode 100644 index 0000000..7da0631 --- /dev/null +++ b/NBATransfer-backend/requirements.txt @@ -0,0 +1,9 @@ +Flask==3.0.0 +Flask-SQLAlchemy==3.1.1 +Flask-CORS==4.0.0 +Flask-JWT-Extended==4.6.0 +Flask-Mail==0.9.1 +python-dotenv==1.0.0 +requests==2.31.0 +Werkzeug==3.0.1 +email-validator==2.1.0 diff --git a/NBATransfer-backend/routes/admin.py b/NBATransfer-backend/routes/admin.py new file mode 100644 index 0000000..9102086 --- /dev/null +++ b/NBATransfer-backend/routes/admin.py @@ -0,0 +1,113 @@ +"""管理员相关路由""" +from flask import Blueprint, request, jsonify +from flask_jwt_extended import jwt_required, get_jwt_identity +from models import User +from services.admin_service import AdminService + +admin_bp = Blueprint('admin', __name__) + +def admin_required(): + """管理员权限验证装饰器""" + current_user_id = get_jwt_identity() + user = User.query.get(current_user_id) + + if not user or not user.is_admin: + return None + + return user + +@admin_bp.route('/users', methods=['GET']) +@jwt_required() +def get_users(): + """获取用户列表""" + if not admin_required(): + return jsonify({'error': '需要管理员权限'}), 403 + + # 分页参数 + page = request.args.get('page', 1, type=int) + per_page = request.args.get('per_page', 20, type=int) + search = request.args.get('search', '') + + result, status_code = AdminService.get_users(page, per_page, search) + return jsonify(result), status_code + +@admin_bp.route('/users/', methods=['GET']) +@jwt_required() +def get_user_detail(user_id): + """获取用户详情""" + if not admin_required(): + return jsonify({'error': '需要管理员权限'}), 403 + + result, status_code = AdminService.get_user_detail(user_id) + return jsonify(result), status_code + +@admin_bp.route('/users//toggle-status', methods=['POST']) +@jwt_required() +def toggle_user_status(user_id): + """启用/禁用用户""" + admin = admin_required() + if not admin: + return jsonify({'error': '需要管理员权限'}), 403 + + result, status_code = AdminService.toggle_user_status(admin.id, user_id) + return jsonify(result), status_code + +@admin_bp.route('/users//adjust-balance', methods=['POST']) +@jwt_required() +def adjust_balance(user_id): + """调整用户余额""" + if not admin_required(): + return jsonify({'error': '需要管理员权限'}), 403 + + data = request.get_json() + result, status_code = AdminService.adjust_balance(user_id, data) + return jsonify(result), status_code + +@admin_bp.route('/orders', methods=['GET']) +@jwt_required() +def get_all_orders(): + """获取所有订单""" + if not admin_required(): + return jsonify({'error': '需要管理员权限'}), 403 + + page = request.args.get('page', 1, type=int) + per_page = request.args.get('per_page', 20, type=int) + status = request.args.get('status') + + result, status_code = AdminService.get_all_orders(page, per_page, status) + return jsonify(result), status_code + +@admin_bp.route('/api-calls', methods=['GET']) +@jwt_required() +def get_all_api_calls(): + """获取所有API调用记录""" + if not admin_required(): + return jsonify({'error': '需要管理员权限'}), 403 + + page = request.args.get('page', 1, type=int) + per_page = request.args.get('per_page', 20, type=int) + status = request.args.get('status') + + result, status_code = AdminService.get_all_api_calls(page, per_page, status) + return jsonify(result), status_code + +@admin_bp.route('/stats/overview', methods=['GET']) +@jwt_required() +def get_overview_stats(): + """获取总览统计""" + if not admin_required(): + return jsonify({'error': '需要管理员权限'}), 403 + + result, status_code = AdminService.get_overview_stats() + return jsonify(result), status_code + +@admin_bp.route('/stats/chart', methods=['GET']) +@jwt_required() +def get_chart_data(): + """获取图表数据(最近7天)""" + if not admin_required(): + return jsonify({'error': '需要管理员权限'}), 403 + + days = request.args.get('days', 7, type=int) + result, status_code = AdminService.get_chart_data(days) + return jsonify(result), status_code diff --git a/NBATransfer-backend/routes/api_service.py b/NBATransfer-backend/routes/api_service.py new file mode 100644 index 0000000..aba80bd --- /dev/null +++ b/NBATransfer-backend/routes/api_service.py @@ -0,0 +1,45 @@ +"""API服务相关路由 - 支持 Nano Banana API 代理""" +from flask import Blueprint, request, jsonify, current_app +from flask_jwt_extended import jwt_required, get_jwt_identity +from services.api_proxy_service import ApiProxyService + +api_bp = Blueprint('api_service', __name__) + +@api_bp.route('/text-to-image', methods=['POST']) +@jwt_required() +def text_to_image(): + """ + 通用 API 代理 (支持文生图和对话) + """ + current_user_id = get_jwt_identity() + data = request.get_json() + + result, status_code = ApiProxyService.handle_api_request(current_user_id, data) + + if status_code == 200 and data.get('stream', False): + return current_app.response_class(result, mimetype='text/event-stream') + + return jsonify(result), status_code + +@api_bp.route('/models', methods=['GET']) +@jwt_required() +def get_models(): + """ + 获取可用的模型列表 + """ + result, status_code = ApiProxyService.get_models() + return jsonify(result), status_code + +@api_bp.route('/pricing', methods=['GET']) +def get_pricing(): + """获取价格信息(公开接口)""" + result, status_code = ApiProxyService.get_pricing() + return jsonify(result), status_code + +@api_bp.route('/call/', methods=['GET']) +@jwt_required() +def get_api_call(call_id): + """获取API调用详情""" + current_user_id = get_jwt_identity() + result, status_code = ApiProxyService.get_api_call(current_user_id, call_id) + return jsonify(result), status_code diff --git a/NBATransfer-backend/routes/apikey.py b/NBATransfer-backend/routes/apikey.py new file mode 100644 index 0000000..bbf1098 --- /dev/null +++ b/NBATransfer-backend/routes/apikey.py @@ -0,0 +1,56 @@ +"""用户 API Key 管理相关路由""" +from flask import Blueprint, request, jsonify +from flask_jwt_extended import jwt_required, get_jwt_identity +from services.apikey_service import APIKeyService + +apikey_bp = Blueprint('apikey', __name__) + +@apikey_bp.route('/keys', methods=['GET']) +@jwt_required() +def list_api_keys(): + """获取用户的所有 API Key""" + user_id = get_jwt_identity() + result, status_code = APIKeyService.list_api_keys(user_id) + return jsonify(result), status_code + +@apikey_bp.route('/keys', methods=['POST']) +@jwt_required() +def create_api_key(): + """创建新的 API Key""" + user_id = get_jwt_identity() + data = request.get_json() + result, status_code = APIKeyService.create_api_key(user_id, data) + return jsonify(result), status_code + +@apikey_bp.route('/keys/', methods=['GET']) +@jwt_required() +def get_api_key(key_id): + """获取单个 API Key 详情""" + user_id = get_jwt_identity() + result, status_code = APIKeyService.get_api_key(user_id, key_id) + return jsonify(result), status_code + +@apikey_bp.route('/keys/', methods=['PUT']) +@jwt_required() +def update_api_key(key_id): + """更新 API Key 名称或状态""" + user_id = get_jwt_identity() + data = request.get_json() + result, status_code = APIKeyService.update_api_key(user_id, key_id, data) + return jsonify(result), status_code + +@apikey_bp.route('/keys/', methods=['DELETE']) +@jwt_required() +def delete_api_key(key_id): + """删除 API Key""" + user_id = get_jwt_identity() + result, status_code = APIKeyService.delete_api_key(user_id, key_id) + return jsonify(result), status_code + +@apikey_bp.route('/keys//regenerate', methods=['POST']) +@jwt_required() +def regenerate_api_key(key_id): + """重置/轮换 API Key""" + user_id = get_jwt_identity() + result, status_code = APIKeyService.regenerate_api_key(user_id, key_id) + return jsonify(result), status_code diff --git a/NBATransfer-backend/routes/auth.py b/NBATransfer-backend/routes/auth.py new file mode 100644 index 0000000..37d8c88 --- /dev/null +++ b/NBATransfer-backend/routes/auth.py @@ -0,0 +1,70 @@ +"""认证相关路由""" +from flask import Blueprint, request, jsonify +from flask_jwt_extended import jwt_required, get_jwt_identity, create_access_token +from services.auth_service import AuthService +from services.user_service import UserService + +auth_bp = Blueprint('auth', __name__) + +@auth_bp.route('/send-verification-code', methods=['POST']) +def send_verification_code(): + """发送验证码""" + data = request.get_json() + result, status_code = AuthService.send_verification_code(data) + return jsonify(result), status_code + +@auth_bp.route('/register', methods=['POST']) +def register(): + """用户注册 - 需要验证码""" + data = request.get_json() + result, status_code = AuthService.register(data) + return jsonify(result), status_code + +@auth_bp.route('/login', methods=['POST']) +def login(): + """用户登录""" + data = request.get_json() + result, status_code = AuthService.login(data) + return jsonify(result), status_code + +@auth_bp.route('/me', methods=['GET']) +@jwt_required() +def get_current_user(): + """获取当前用户信息""" + current_user_id = get_jwt_identity() + result, status_code = UserService.get_profile(current_user_id) + return jsonify(result), status_code + +@auth_bp.route('/change-password', methods=['POST']) +@jwt_required() +def change_password(): + """修改密码""" + current_user_id = get_jwt_identity() + data = request.get_json() + result, status_code = AuthService.change_password(current_user_id, data) + return jsonify(result), status_code + +@auth_bp.route('/reset-password', methods=['POST']) +def reset_password(): + """重置密码 - 需要验证码""" + data = request.get_json() + result, status_code = AuthService.reset_password(data) + return jsonify(result), status_code + +@auth_bp.route('/verify-code', methods=['POST']) +def verify_code(): + """验证验证码是否有效(不标记为已使用)""" + data = request.get_json() + result, status_code = AuthService.verify_code(data) + return jsonify(result), status_code + +@auth_bp.route('/refresh', methods=['POST']) +@jwt_required(refresh=True) +def refresh(): + """刷新访问令牌""" + current_user_id = get_jwt_identity() + access_token = create_access_token(identity=current_user_id) + + return jsonify({ + 'access_token': access_token + }), 200 diff --git a/NBATransfer-backend/routes/order.py b/NBATransfer-backend/routes/order.py new file mode 100644 index 0000000..34864b0 --- /dev/null +++ b/NBATransfer-backend/routes/order.py @@ -0,0 +1,57 @@ +"""订单相关路由""" +from flask import Blueprint, request, jsonify +from flask_jwt_extended import jwt_required, get_jwt_identity +from services.order_service import OrderService + +order_bp = Blueprint('order', __name__) + +@order_bp.route('/create', methods=['POST']) +@jwt_required() +def create_order(): + """创建充值订单""" + current_user_id = get_jwt_identity() + data = request.get_json() + result, status_code = OrderService.create_order(current_user_id, data) + return jsonify(result), status_code + +@order_bp.route('/list', methods=['GET']) +@jwt_required() +def get_orders(): + """获取订单列表""" + current_user_id = get_jwt_identity() + + # 分页参数 + page = request.args.get('page', 1, type=int) + per_page = request.args.get('per_page', 20, type=int) + status = request.args.get('status') + + result, status_code = OrderService.get_orders(current_user_id, page, per_page, status) + return jsonify(result), status_code + +@order_bp.route('/', methods=['GET']) +@jwt_required() +def get_order(order_id): + """获取订单详情""" + current_user_id = get_jwt_identity() + result, status_code = OrderService.get_order(current_user_id, order_id) + return jsonify(result), status_code + +@order_bp.route('/callback/alipay', methods=['POST']) +def alipay_callback(): + """支付宝支付回调(预留接口)""" + data = request.get_json() or request.form.to_dict() + result, status_code = OrderService.alipay_callback(data) + return jsonify(result), status_code + +@order_bp.route('/callback/wechat', methods=['POST']) +def wechat_callback(): + """微信支付回调(预留接口)""" + data = request.get_json() or request.form.to_dict() + result, status_code = OrderService.wechat_callback(data) + return jsonify(result), status_code + +@order_bp.route('/notify/', methods=['POST']) +def payment_notify(order_no): + """模拟支付通知(仅用于测试)""" + result, status_code = OrderService.payment_notify(order_no) + return jsonify(result), status_code diff --git a/NBATransfer-backend/routes/user.py b/NBATransfer-backend/routes/user.py new file mode 100644 index 0000000..de4771a --- /dev/null +++ b/NBATransfer-backend/routes/user.py @@ -0,0 +1,65 @@ +"""用户相关路由""" +from flask import Blueprint, request, jsonify +from flask_jwt_extended import jwt_required, get_jwt_identity +from services.user_service import UserService + +user_bp = Blueprint('user', __name__) + + +@user_bp.route('/profile', methods=['GET']) +@jwt_required() +def get_profile(): + """获取用户资料""" + current_user_id = get_jwt_identity() + result, status_code = UserService.get_profile(current_user_id) + return jsonify(result), status_code + + +@user_bp.route('/profile', methods=['PUT']) +@jwt_required() +def update_profile(): + """更新用户资料""" + current_user_id = get_jwt_identity() + data = request.get_json() + result, status_code = UserService.update_profile(current_user_id, data) + return jsonify(result), status_code + + +@user_bp.route('/balance', methods=['GET']) +@jwt_required() +def get_balance(): + """获取账户余额""" + current_user_id = get_jwt_identity() + result, status_code = UserService.get_balance(current_user_id) + return jsonify(result), status_code + + +@user_bp.route('/transactions', methods=['GET']) +@jwt_required() +def get_transactions(): + """获取交易记录""" + current_user_id = get_jwt_identity() + page = request.args.get('page', 1, type=int) + per_page = request.args.get('per_page', 20, type=int) + result, status_code = UserService.get_transactions(current_user_id, page, per_page) + return jsonify(result), status_code + + +@user_bp.route('/api-calls', methods=['GET']) +@jwt_required() +def get_api_calls(): + """获取API调用记录""" + current_user_id = get_jwt_identity() + page = request.args.get('page', 1, type=int) + per_page = request.args.get('per_page', 20, type=int) + result, status_code = UserService.get_api_calls(current_user_id, page, per_page) + return jsonify(result), status_code + + +@user_bp.route('/stats', methods=['GET']) +@jwt_required() +def get_stats(): + """获取用户统计信息""" + current_user_id = get_jwt_identity() + result, status_code = UserService.get_stats(current_user_id) + return jsonify(result), status_code diff --git a/NBATransfer-backend/routes/v1_api.py b/NBATransfer-backend/routes/v1_api.py new file mode 100644 index 0000000..548b323 --- /dev/null +++ b/NBATransfer-backend/routes/v1_api.py @@ -0,0 +1,36 @@ +"""对外公开的 API 路由 (v1) - 支持 API Key 认证""" +from flask import Blueprint, request, jsonify, current_app +from services.apikey_service import APIKeyService +from services.v1_service import V1Service + +v1_bp = Blueprint('v1_api', __name__) + +@v1_bp.route('/chat/completions', methods=['POST']) +def chat_completions(): + """ + OpenAI 兼容的 Chat Completions 接口 + 支持多模型,通过 modelapiservice 分发 + """ + # 1. 认证 + auth_header = request.headers.get('Authorization') + user, error = APIKeyService.authenticate_api_key(auth_header) + + if error: + return jsonify({'error': {'message': error, 'type': 'auth_error', 'code': 401}}), 401 + + # 2. 获取请求数据 + data = request.get_json() + if not data: + return jsonify({'error': {'message': '无效的 JSON 请求体', 'type': 'invalid_request_error', 'code': 400}}), 400 + + result, status_code = V1Service.chat_completions(user, data) + + if status_code == 200 and data.get('stream', False): + return current_app.response_class(result, mimetype='text/event-stream') + + return jsonify(result), status_code + +@v1_bp.route('/text-to-image', methods=['POST']) +def text_to_image_alias(): + """兼容旧接口路径""" + return chat_completions() diff --git a/NBATransfer-backend/services/admin_service.py b/NBATransfer-backend/services/admin_service.py new file mode 100644 index 0000000..4e8c35e --- /dev/null +++ b/NBATransfer-backend/services/admin_service.py @@ -0,0 +1,244 @@ +from models import db, User, Order, ApiCall, Transaction +from sqlalchemy import desc, func +from datetime import datetime, timedelta + +class AdminService: + @staticmethod + def get_users(page=1, per_page=20, search=''): + """获取用户列表""" + query = User.query + + if search: + query = query.filter( + (User.email.contains(search)) | (User.username.contains(search)) + ) + + # 分页查询 + pagination = query.order_by(desc(User.created_at))\ + .paginate(page=page, per_page=per_page, error_out=False) + + return { + 'users': [user.to_dict() for user in pagination.items], + 'total': pagination.total, + 'page': page, + 'per_page': per_page, + 'pages': pagination.pages + }, 200 + + @staticmethod + def get_user_detail(user_id): + """获取用户详情""" + user = User.query.get(user_id) + if not user: + return {'error': '用户不存在'}, 404 + + # 统计信息 + total_recharge = db.session.query(func.sum(Transaction.amount))\ + .filter_by(user_id=user_id, type='recharge').scalar() or 0 + + total_consume = db.session.query(func.sum(Transaction.amount))\ + .filter_by(user_id=user_id, type='consume').scalar() or 0 + + total_api_calls = ApiCall.query.filter_by(user_id=user_id).count() + + return { + 'user': user.to_dict(), + 'stats': { + 'total_recharge': total_recharge, + 'total_consume': abs(total_consume), + 'total_api_calls': total_api_calls + } + }, 200 + + @staticmethod + def toggle_user_status(admin_user_id, user_id): + """启用/禁用用户""" + user = User.query.get(user_id) + if not user: + return {'error': '用户不存在'}, 404 + + # 检查是否尝试禁用其他管理员 (需要传入当前管理员ID) + if user.is_admin and user.id != admin_user_id: + return {'error': '不能禁用其他管理员'}, 403 + + user.is_active = not user.is_active + + try: + db.session.commit() + return { + 'message': f'用户已{"启用" if user.is_active else "禁用"}', + 'user': user.to_dict() + }, 200 + except Exception as e: + db.session.rollback() + return {'error': '操作失败'}, 500 + + @staticmethod + def adjust_balance(user_id, data): + """调整用户余额""" + user = User.query.get(user_id) + if not user: + return {'error': '用户不存在'}, 404 + + amount = data.get('amount') + description = data.get('description', '管理员调整余额') + + if amount is None or amount == 0: + return {'error': '金额不能为0'}, 400 + + try: + balance_before = user.balance + user.balance += amount + balance_after = user.balance + + # 创建交易记录 + transaction = Transaction( + user_id=user_id, + type='recharge' if amount > 0 else 'consume', + amount=amount, + balance_before=balance_before, + balance_after=balance_after, + description=description + ) + + db.session.add(transaction) + db.session.commit() + + return { + 'message': '余额调整成功', + 'user': user.to_dict(), + 'transaction': transaction.to_dict() + }, 200 + except Exception as e: + db.session.rollback() + return {'error': '余额调整失败'}, 500 + + @staticmethod + def get_all_orders(page=1, per_page=20, status=None): + """获取所有订单""" + query = Order.query + + if status: + query = query.filter_by(status=status) + + # 分页查询 + pagination = query.order_by(desc(Order.created_at))\ + .paginate(page=page, per_page=per_page, error_out=False) + + return { + 'orders': [order.to_dict() for order in pagination.items], + 'total': pagination.total, + 'page': page, + 'per_page': per_page, + 'pages': pagination.pages + }, 200 + + @staticmethod + def get_all_api_calls(page=1, per_page=20, status=None): + """获取所有API调用记录""" + query = ApiCall.query + + if status: + query = query.filter_by(status=status) + + # 分页查询 + pagination = query.order_by(desc(ApiCall.created_at))\ + .paginate(page=page, per_page=per_page, error_out=False) + + return { + 'api_calls': [call.to_dict() for call in pagination.items], + 'total': pagination.total, + 'page': page, + 'per_page': per_page, + 'pages': pagination.pages + }, 200 + + @staticmethod + def get_overview_stats(): + """获取总览统计""" + # 用户统计 + total_users = User.query.count() + active_users = User.query.filter_by(is_active=True).count() + + # 订单统计 + total_orders = Order.query.count() + paid_orders = Order.query.filter_by(status='paid').count() + total_revenue = db.session.query(func.sum(Order.amount))\ + .filter_by(status='paid').scalar() or 0 + + # API调用统计 + total_api_calls = ApiCall.query.count() + success_calls = ApiCall.query.filter_by(status='success').count() + failed_calls = ApiCall.query.filter_by(status='failed').count() + + # 今日统计 + today = datetime.utcnow().date() + today_start = datetime.combine(today, datetime.min.time()) + + today_users = User.query.filter(User.created_at >= today_start).count() + today_orders = Order.query.filter(Order.created_at >= today_start).count() + today_revenue = db.session.query(func.sum(Order.amount))\ + .filter(Order.created_at >= today_start, Order.status == 'paid').scalar() or 0 + today_api_calls = ApiCall.query.filter(ApiCall.created_at >= today_start).count() + + return { + 'total': { + 'users': total_users, + 'active_users': active_users, + 'orders': total_orders, + 'paid_orders': paid_orders, + 'revenue': total_revenue, + 'api_calls': total_api_calls, + 'success_calls': success_calls, + 'failed_calls': failed_calls + }, + 'today': { + 'users': today_users, + 'orders': today_orders, + 'revenue': today_revenue, + 'api_calls': today_api_calls + } + }, 200 + + @staticmethod + def get_chart_data(days=7): + """获取图表数据""" + # 计算日期范围 + end_date = datetime.utcnow().date() + start_date = end_date - timedelta(days=days-1) + + chart_data = [] + + for i in range(days): + date = start_date + timedelta(days=i) + date_start = datetime.combine(date, datetime.min.time()) + date_end = datetime.combine(date, datetime.max.time()) + + # 统计当天数据 + users = User.query.filter( + User.created_at >= date_start, + User.created_at <= date_end + ).count() + + revenue = db.session.query(func.sum(Order.amount))\ + .filter( + Order.created_at >= date_start, + Order.created_at <= date_end, + Order.status == 'paid' + ).scalar() or 0 + + api_calls = ApiCall.query.filter( + ApiCall.created_at >= date_start, + ApiCall.created_at <= date_end + ).count() + + chart_data.append({ + 'date': date.isoformat(), + 'users': users, + 'revenue': float(revenue), + 'api_calls': api_calls + }) + + return { + 'chart_data': chart_data + }, 200 diff --git a/NBATransfer-backend/services/api_proxy_service.py b/NBATransfer-backend/services/api_proxy_service.py new file mode 100644 index 0000000..731ac26 --- /dev/null +++ b/NBATransfer-backend/services/api_proxy_service.py @@ -0,0 +1,258 @@ +from flask import current_app +from models import db, User, ApiCall, Transaction +from datetime import datetime +import requests +import json +import logging +from modelapiservice import get_model_service + +logger = logging.getLogger(__name__) + +class ApiProxyService: + @staticmethod + def deduct_balance(user_id, api_call_id, cost, model): + """统一扣费逻辑""" + try: + user = User.query.get(user_id) + api_call = ApiCall.query.get(api_call_id) + if not user or not api_call: + return + + # 刷新用户数据 + db.session.refresh(user) + + balance_before = user.balance + user.balance -= cost + balance_after = user.balance + + api_call.status = 'success' + api_call.cost = cost + db.session.add(api_call) + + transaction = Transaction( + user_id=user.id, + type='consume', + amount=-cost, + balance_before=balance_before, + balance_after=balance_after, + description=f'API调用 - {model}', + api_call_id=api_call.id + ) + db.session.add(transaction) + db.session.commit() + logger.info(f"扣费成功: 用户 {user.id}, 消费 {cost}, 余额 {balance_before} -> {balance_after}") + except Exception as e: + logger.error(f'扣费失败: {e}') + db.session.rollback() + + @staticmethod + def handle_api_request(user_id, data): + """ + 通用 API 代理 (支持文生图和对话) + """ + user = User.query.get(user_id) + + if not user: + return {'error': '用户不存在'}, 404 + + if not user.is_active: + return {'error': '账户已被禁用'}, 403 + + model = data.get('model') + messages = data.get('messages', []) + stream = data.get('stream', False) + + # 验证必要字段 + if not model: + return {'error': 'model字段不能为空'}, 400 + + if not messages or len(messages) == 0: + return {'error': 'messages不能为空'}, 400 + + # 获取模型服务并检查余额 + try: + service = get_model_service(model) + except Exception as e: + return {'error': f'不支持的模型: {model}'}, 400 + + is_sufficient, estimated_cost, error_msg = service.check_balance(user.balance) + if not is_sufficient: + return { + 'error': '余额不足', + 'message': error_msg, + 'required': estimated_cost, + 'balance': user.balance + }, 402 + + prompt = messages[0].get('content', '') if messages else '' + if not prompt: + prompt = "Empty prompt" + + # 创建API调用记录 + api_call = ApiCall( + user_id=user_id, + api_type='chat_completion', + prompt=prompt[:500], # 截断 + parameters=json.dumps({ + 'model': model, + 'stream': stream + }), + status='processing', + cost=estimated_cost, + request_time=datetime.utcnow() + ) + + try: + db.session.add(api_call) + db.session.flush() + + # 准备请求 + api_url, api_key = service.get_api_config() + if not api_url or not api_key: + raise ValueError(f'模型 {model} API 配置未完成') + + headers = { + 'Authorization': f'Bearer {api_key}', + 'Content-Type': 'application/json' + } + + payload = service.prepare_payload(data) + target_url = f'{api_url}/chat/completions' + + logger.info(f'API 转发: {target_url}, User: {user.id}, Model: {model}') + + response = requests.post( + target_url, + headers=headers, + json=payload, + stream=stream, + timeout=300 + ) + + if response.status_code != 200: + error_msg = f'第三方 API 返回错误: {response.status_code}' + try: + error_detail = response.json() + error_msg += f' - {error_detail}' + except: + error_msg += f' - {response.text[:200]}' + + api_call.status = 'failed' + api_call.error_message = error_msg + db.session.commit() + return {'error': 'API 调用失败', 'details': error_msg}, 502 + + # 处理响应 + if stream: + # 流式响应处理 + def generate(): + final_usage = None + try: + for chunk in response.iter_content(chunk_size=1024): + if chunk: + if hasattr(service, 'parse_stream_usage'): + try: + text_chunk = chunk.decode('utf-8', errors='ignore') + usage = service.parse_stream_usage(text_chunk) + if usage: + final_usage = usage + except: + pass + yield chunk + + # 计算最终费用 + actual_cost = service.calculate_cost(final_usage, stream=True) + if actual_cost == 0 and estimated_cost > 0: + actual_cost = estimated_cost + + with current_app.app_context(): + ApiProxyService.deduct_balance(user.id, api_call.id, actual_cost, model) + + except Exception as e: + logger.error(f'Stream error: {e}') + + return generate(), 200 # Special return for stream + else: + result = response.json() + api_call.status = 'success' + api_call.response_time = datetime.utcnow() + + # 计算费用 + usage = result.get('usage') + final_cost = service.calculate_cost(usage, stream=False) + if final_cost == 0 and estimated_cost > 0: + final_cost = estimated_cost + + # 简化响应格式 + simplified_result = { + 'success': True, + 'api_call_id': api_call.id, + 'cost': final_cost, + 'model': model, + 'content': '' + } + + if 'choices' in result and len(result['choices']) > 0: + content = result['choices'][0].get('message', {}).get('content', '') + simplified_result['content'] = content + api_call.result_url = content[:500] + + ApiProxyService.deduct_balance(user.id, api_call.id, final_cost, model) + + return simplified_result, 200 + + except Exception as e: + logger.error(f'API 调用异常: {str(e)}', exc_info=True) + if api_call.id: + api_call.status = 'failed' + api_call.error_message = str(e) + db.session.commit() + return {'error': '服务异常', 'message': str(e)}, 500 + + @staticmethod + def get_models(): + """获取可用的模型列表""" + # 暂时返回硬编码的模型列表,后续可以从各 Service 聚合 + return { + 'object': 'list', + 'data': [ + { + 'id': 'deepseek-chat', + 'object': 'model', + 'owned_by': 'deepseek', + 'description': 'DeepSeek Chat V3' + }, + { + 'id': 'deepseek-reasoner', + 'object': 'model', + 'owned_by': 'deepseek', + 'description': 'DeepSeek Reasoner (R1)' + }, + # ... 其他模型 ... + ] + }, 200 + + @staticmethod + def get_pricing(): + """获取价格信息""" + pricing = { + 'text_to_image': { + 'price': current_app.config.get('IMAGE_GENERATION_PRICE', 0), + 'currency': 'CNY', + 'unit': '每张图片' + } + } + + return { + 'pricing': pricing + }, 200 + + @staticmethod + def get_api_call(user_id, call_id): + """获取API调用详情""" + api_call = ApiCall.query.filter_by(id=call_id, user_id=user_id).first() + + if not api_call: + return {'error': 'API调用记录不存在'}, 404 + + return api_call.to_dict(), 200 diff --git a/NBATransfer-backend/services/apikey_service.py b/NBATransfer-backend/services/apikey_service.py new file mode 100644 index 0000000..83d8d0c --- /dev/null +++ b/NBATransfer-backend/services/apikey_service.py @@ -0,0 +1,165 @@ +from models import db, User, APIKey +from datetime import datetime + +class APIKeyService: + @staticmethod + def list_api_keys(user_id): + """获取用户的所有 API Key""" + user = User.query.get(user_id) + + if not user: + return {'error': '用户不存在'}, 404 + + keys = APIKey.query.filter_by(user_id=user_id).all() + + return { + 'total': len(keys), + 'keys': [key.to_dict() for key in keys] + }, 200 + + @staticmethod + def create_api_key(user_id, data): + """创建新的 API Key""" + user = User.query.get(user_id) + + if not user: + return {'error': '用户不存在'}, 404 + + name = data.get('name', '').strip() + + if not name: + return {'error': 'API Key 名称不能为空'}, 400 + + if len(name) > 100: + return {'error': 'API Key 名称长度不能超过100个字符'}, 400 + + # 生成 API Key + api_key = APIKey.generate_key() + + # 创建数据库记录 + new_key = APIKey( + user_id=user_id, + name=name, + api_key=api_key + ) + + try: + db.session.add(new_key) + db.session.commit() + + return { + 'message': 'API Key 创建成功', + 'key': new_key.to_dict() + }, 201 + except Exception as e: + db.session.rollback() + return {'error': '创建失败,请稍后重试'}, 500 + + @staticmethod + def get_api_key(user_id, key_id): + """获取单个 API Key 详情""" + key = APIKey.query.filter_by(id=key_id, user_id=user_id).first() + + if not key: + return {'error': 'API Key 不存在'}, 404 + + return key.to_dict(), 200 + + @staticmethod + def update_api_key(user_id, key_id, data): + """更新 API Key 名称或状态""" + key = APIKey.query.filter_by(id=key_id, user_id=user_id).first() + + if not key: + return {'error': 'API Key 不存在'}, 404 + + if 'name' in data: + name = data.get('name', '').strip() + if not name or len(name) > 100: + return {'error': 'API Key 名称无效'}, 400 + key.name = name + + if 'is_active' in data: + key.is_active = bool(data.get('is_active')) + + try: + db.session.commit() + return { + 'message': 'API Key 更新成功', + 'key': key.to_dict() + }, 200 + except Exception as e: + db.session.rollback() + return {'error': '更新失败,请稍后重试'}, 500 + + @staticmethod + def delete_api_key(user_id, key_id): + """删除 API Key""" + key = APIKey.query.filter_by(id=key_id, user_id=user_id).first() + + if not key: + return {'error': 'API Key 不存在'}, 404 + + try: + db.session.delete(key) + db.session.commit() + return {'message': 'API Key 已删除'}, 200 + except Exception as e: + db.session.rollback() + return {'error': '删除失败,请稍后重试'}, 500 + + @staticmethod + def regenerate_api_key(user_id, key_id): + """重置/轮换 API Key""" + key = APIKey.query.filter_by(id=key_id, user_id=user_id).first() + + if not key: + return {'error': 'API Key 不存在'}, 404 + + # 生成新的 API Key + new_api_key = APIKey.generate_key() + key.api_key = new_api_key + + try: + db.session.commit() + return { + 'message': 'API Key 已重置', + 'key': key.to_dict() + }, 200 + except Exception as e: + db.session.rollback() + return {'error': '重置失败,请稍后重试'}, 500 + + @staticmethod + def authenticate_api_key(auth_header): + """验证 API Key 并返回用户""" + if not auth_header: + return None, "缺少 Authorization 头" + + parts = auth_header.split() + if parts[0].lower() != "bearer": + return None, "Authorization 头格式错误" + + if len(parts) == 1: + return None, "无效的 Token" + + api_key_str = parts[1] + + # 查找 API Key + api_key = APIKey.query.filter_by(api_key=api_key_str).first() + + if not api_key: + return None, "无效的 API Key" + + if not api_key.is_active: + return None, "API Key 已被禁用" + + # 更新最后使用时间 + api_key.last_used_at = datetime.utcnow() + db.session.commit() + + user = User.query.get(api_key.user_id) + if not user or not user.is_active: + return None, "账户不存在或已被禁用" + + return user, None diff --git a/NBATransfer-backend/services/auth_service.py b/NBATransfer-backend/services/auth_service.py new file mode 100644 index 0000000..3a81645 --- /dev/null +++ b/NBATransfer-backend/services/auth_service.py @@ -0,0 +1,290 @@ +from flask import current_app, jsonify +from flask_mail import Message +from flask_jwt_extended import create_access_token, create_refresh_token +from models import db, User, VerificationCode +from email_validator import validate_email, EmailNotValidError +from datetime import datetime, timedelta +import re + +class AuthService: + @staticmethod + def send_verification_email(email, code, purpose='register'): + """发送验证码邮件""" + try: + subject = '验证码' if purpose == 'register' else '密码重置验证码' + body = f""" +您好! + +感谢您使用 Nano Banana API 转售平台。 + +您的验证码是:{code} + +此验证码有效期为10分钟,请勿泄露给他人。 + +如果不是您本人操作,请忽略此邮件。 + +--- +Nano Banana API 平台 +""" + msg = Message( + subject=f'[Nano Banana] {subject}', + recipients=[email], + body=body + ) + + # 这里使用 Flask-Mail 发送 + from extensions import mail + mail.send(msg) + + return True + except Exception as e: + print(f'发送邮件失败: {str(e)}') + return False + + @staticmethod + def send_verification_code(data): + """发送验证码逻辑""" + email = data.get('email', '').strip().lower() + purpose = data.get('purpose', 'register') + + if not email: + return {'error': '邮箱不能为空'}, 400 + + # 验证邮箱格式 + try: + validate_email(email, check_deliverability=False) + except EmailNotValidError: + return {'error': '邮箱格式不正确'}, 400 + + # 如果是注册,检查邮箱是否已存在 + if purpose == 'register': + user = User.query.filter_by(email=email).first() + if user: + # 如果用户已存在且已验证邮箱,则提示已注册 + if user.email_verified: + return {'error': '该邮箱已被注册'}, 400 + # 如果用户存在但未验证,可以继续发送验证码(重发) + else: + # 创建临时用户记录用于验证码关联 + user = User(email=email, username=email.split('@')[0]) + user.set_password('temp_pending_registration') + db.session.add(user) + db.session.commit() + else: + # 密码重置:检查用户是否存在 + user = User.query.filter_by(email=email).first() + if not user: + return {'error': '用户不存在'}, 404 + + # 生成验证码 + code = VerificationCode.generate_code() + + # 删除旧的未使用验证码 + VerificationCode.query.filter_by( + email=email, + purpose=purpose, + used=False + ).delete() + + # 创建新的验证码 + verification = VerificationCode( + user_id=user.id, + email=email, + code=code, + purpose=purpose, + expired_at=datetime.utcnow() + timedelta(minutes=10) + ) + db.session.add(verification) + db.session.commit() + + # 发送邮件 + send_success = AuthService.send_verification_email(email, code, purpose) + + if send_success: + return { + 'message': '验证码已发送到您的邮箱', + 'email': email, + 'purpose': purpose + }, 200 + else: + return {'error': '邮件发送失败,请检查邮箱配置'}, 500 + + @staticmethod + def register(data): + """用户注册逻辑""" + if not data or not data.get('email') or not data.get('password') or not data.get('code'): + return {'error': '邮箱、密码和验证码不能为空'}, 400 + + email = data.get('email').strip().lower() + password = data.get('password') + code = data.get('code') + username = data.get('username', '').strip() + + try: + validate_email(email, check_deliverability=False) + except EmailNotValidError: + return {'error': '邮箱格式不正确'}, 400 + + if len(password) < 6: + return {'error': '密码长度至少为6位'}, 400 + + verification = VerificationCode.query.filter_by( + email=email, + code=code, + purpose='register', + used=False + ).first() + + if not verification: + return {'error': '验证码不存在或已过期'}, 400 + + if not verification.is_valid(): + return {'error': '验证码已过期'}, 400 + + existing_user = User.query.filter_by(email=email).filter( + User.id != verification.user_id + ).first() + + if existing_user: + return {'error': '该邮箱已被注册'}, 400 + + user = User.query.get(verification.user_id) + user.username = username or email.split('@')[0] + user.set_password(password) + user.email_verified = True + user.email_verified_at = datetime.utcnow() + user.is_active = True + + verification.used = True + + try: + db.session.commit() + access_token = create_access_token(identity=user.id) + refresh_token = create_refresh_token(identity=user.id) + + return { + 'message': '注册成功', + 'access_token': access_token, + 'refresh_token': refresh_token, + 'user': user.to_dict() + }, 201 + except Exception as e: + db.session.rollback() + print(f'注册错误: {str(e)}') + return {'error': '注册失败,请稍后重试'}, 500 + + @staticmethod + def login(data): + """用户登录逻辑""" + if not data or not data.get('email') or not data.get('password'): + return {'error': '邮箱和密码不能为空'}, 400 + + email = data.get('email').strip().lower() + password = data.get('password') + + user = User.query.filter_by(email=email).first() + + if not user or not user.check_password(password): + return {'error': '邮箱或密码错误'}, 401 + + if not user.is_active: + return {'error': '账户已被禁用'}, 403 + + # 如果用户未验证邮箱 (理论上注册流程保证了已验证,但为了兼容旧数据) + if not user.email_verified: + return {'error': '邮箱未验证,请先完成注册验证'}, 403 + + access_token = create_access_token(identity=user.id) + refresh_token = create_refresh_token(identity=user.id) + + return { + 'message': '登录成功', + 'access_token': access_token, + 'refresh_token': refresh_token, + 'user': user.to_dict() + }, 200 + + @staticmethod + def change_password(user_id, data): + """修改密码""" + user = User.query.get(user_id) + + if not user: + return {'error': '用户不存在'}, 404 + + old_password = data.get('old_password') + new_password = data.get('new_password') + + if not old_password or not new_password: + return {'error': '参数不能为空'}, 400 + + if not user.check_password(old_password): + return {'error': '原密码错误'}, 401 + + if len(new_password) < 6: + return {'error': '新密码长度至少为6位'}, 400 + + user.set_password(new_password) + db.session.commit() + + return {'message': '密码修改成功'}, 200 + + @staticmethod + def reset_password(data): + """重置密码""" + email = data.get('email', '').strip().lower() + code = data.get('code') + new_password = data.get('new_password') + + if not email or not code or not new_password: + return {'error': '参数不能为空'}, 400 + + # 验证验证码 + verification = VerificationCode.query.filter_by( + email=email, + code=code, + purpose='password_reset', + used=False + ).first() + + if not verification or not verification.is_valid(): + return {'error': '验证码不存在或已过期'}, 400 + + user = User.query.get(verification.user_id) + if not user: + return {'error': '用户不存在'}, 404 + + if len(new_password) < 6: + return {'error': '密码长度至少为6位'}, 400 + + # 更新密码 + user.set_password(new_password) + verification.used = True + db.session.commit() + + return {'message': '密码重置成功'}, 200 + + @staticmethod + def verify_code(data): + """验证验证码是否有效""" + email = data.get('email', '').strip().lower() + code = data.get('code') + purpose = data.get('purpose', 'register') + + if not email or not code: + return {'error': '参数不能为空'}, 400 + + verification = VerificationCode.query.filter_by( + email=email, + code=code, + purpose=purpose, + used=False + ).first() + + if not verification: + return {'error': '验证码错误'}, 400 + + if not verification.is_valid(): + return {'error': '验证码已过期'}, 400 + + return {'message': '验证码有效'}, 200 diff --git a/NBATransfer-backend/services/order_service.py b/NBATransfer-backend/services/order_service.py new file mode 100644 index 0000000..11c99c1 --- /dev/null +++ b/NBATransfer-backend/services/order_service.py @@ -0,0 +1,150 @@ +from flask import current_app +from models import db, User, Order, Transaction +from datetime import datetime +import uuid +from sqlalchemy import desc + +class OrderService: + @staticmethod + def _generate_order_no(): + """生成订单号""" + return f"ORD{datetime.utcnow().strftime('%Y%m%d%H%M%S')}{uuid.uuid4().hex[:8].upper()}" + + @staticmethod + def create_order(user_id, data): + """创建充值订单""" + user = User.query.get(user_id) + + if not user: + return {'error': '用户不存在'}, 404 + + amount = data.get('amount') + payment_method = data.get('payment_method', 'alipay') # alipay, wechat + + # 验证金额 + if not amount or amount <= 0: + return {'error': '充值金额必须大于0'}, 400 + + # 验证支付方式 + if payment_method not in ['alipay', 'wechat']: + return {'error': '不支持的支付方式'}, 400 + + # 创建订单 + order = Order( + order_no=OrderService._generate_order_no(), + user_id=user_id, + amount=amount, + payment_method=payment_method, + status='pending' + ) + + try: + db.session.add(order) + db.session.commit() + + # TODO: 调用支付接口 + # 这里返回支付参数,前端跳转到支付页面 + payment_params = { + 'order_no': order.order_no, + 'amount': order.amount, + 'payment_method': payment_method, + # 实际项目中应返回支付宝或微信的支付参数 + 'qr_code': f'https://payment.example.com/qr/{order.order_no}', + 'payment_url': f'https://payment.example.com/pay/{order.order_no}' + } + + return { + 'message': '订单创建成功', + 'order': order.to_dict(), + 'payment': payment_params + }, 201 + except Exception as e: + db.session.rollback() + return {'error': '订单创建失败'}, 500 + + @staticmethod + def get_orders(user_id, page=1, per_page=20, status=None): + """获取订单列表""" + query = Order.query.filter_by(user_id=user_id) + + if status: + query = query.filter_by(status=status) + + # 分页查询 + pagination = query.order_by(desc(Order.created_at))\ + .paginate(page=page, per_page=per_page, error_out=False) + + return { + 'orders': [order.to_dict() for order in pagination.items], + 'total': pagination.total, + 'page': page, + 'per_page': per_page, + 'pages': pagination.pages + }, 200 + + @staticmethod + def get_order(user_id, order_id): + """获取订单详情""" + order = Order.query.filter_by(id=order_id, user_id=user_id).first() + + if not order: + return {'error': '订单不存在'}, 404 + + return order.to_dict(), 200 + + @staticmethod + def alipay_callback(data): + """支付宝支付回调(预留接口)""" + # TODO: 实现支付宝回调逻辑 + return {'message': '处理成功'}, 200 + + @staticmethod + def wechat_callback(data): + """微信支付回调(预留接口)""" + # TODO: 实现微信支付回调逻辑 + return {'message': '处理成功'}, 200 + + @staticmethod + def payment_notify(order_no): + """模拟支付通知(仅用于测试)""" + order = Order.query.filter_by(order_no=order_no).first() + + if not order: + return {'error': '订单不存在'}, 404 + + if order.status != 'pending': + return {'error': '订单状态不正确'}, 400 + + try: + # 更新订单状态 + order.status = 'paid' + order.paid_at = datetime.utcnow() + order.transaction_id = f"TXN{uuid.uuid4().hex[:16].upper()}" + + # 增加用户余额 + user = User.query.get(order.user_id) + balance_before = user.balance + user.balance += order.amount + balance_after = user.balance + + # 创建交易记录 + transaction = Transaction( + user_id=user.id, + type='recharge', + amount=order.amount, + balance_before=balance_before, + balance_after=balance_after, + description=f'充值 - 订单号: {order.order_no}', + order_id=order.id + ) + + db.session.add(transaction) + db.session.commit() + + return { + 'message': '支付成功', + 'order': order.to_dict() + }, 200 + except Exception as e: + db.session.rollback() + return {'error': '支付处理失败'}, 500 diff --git a/NBATransfer-backend/services/user_service.py b/NBATransfer-backend/services/user_service.py new file mode 100644 index 0000000..6b91e41 --- /dev/null +++ b/NBATransfer-backend/services/user_service.py @@ -0,0 +1,89 @@ +from flask import request +from models import db, User, Transaction, ApiCall +from sqlalchemy import desc + +class UserService: + @staticmethod + def get_profile(user_id): + user = User.query.get(user_id) + if not user: + return {'error': '用户不存在'}, 404 + return user.to_dict(), 200 + + @staticmethod + def update_profile(user_id, data): + user = User.query.get(user_id) + if not user: + return {'error': '用户不存在'}, 404 + + if 'username' in data: + user.username = data['username'].strip() + + try: + db.session.commit() + return { + 'message': '资料更新成功', + 'user': user.to_dict() + }, 200 + except Exception as e: + db.session.rollback() + return {'error': '资料更新失败'}, 500 + + @staticmethod + def get_balance(user_id): + user = User.query.get(user_id) + if not user: + return {'error': '用户不存在'}, 404 + + return { + 'balance': user.balance, + 'user_id': user.id + }, 200 + + @staticmethod + def get_transactions(user_id, page, per_page): + pagination = Transaction.query.filter_by(user_id=user_id)\ + .order_by(desc(Transaction.created_at))\ + .paginate(page=page, per_page=per_page, error_out=False) + + return { + 'transactions': [t.to_dict() for t in pagination.items], + 'total': pagination.total, + 'page': page, + 'per_page': per_page, + 'pages': pagination.pages + }, 200 + + @staticmethod + def get_api_calls(user_id, page, per_page): + pagination = ApiCall.query.filter_by(user_id=user_id)\ + .order_by(desc(ApiCall.created_at))\ + .paginate(page=page, per_page=per_page, error_out=False) + + return { + 'api_calls': [call.to_dict() for call in pagination.items], + 'total': pagination.total, + 'page': page, + 'per_page': per_page, + 'pages': pagination.pages + }, 200 + + @staticmethod + def get_stats(user_id): + total_calls = ApiCall.query.filter_by(user_id=user_id).count() + success_calls = ApiCall.query.filter_by(user_id=user_id, status='success').count() + failed_calls = ApiCall.query.filter_by(user_id=user_id, status='failed').count() + + total_cost = db.session.query(db.func.sum(Transaction.amount))\ + .filter_by(user_id=user_id, type='consume').scalar() or 0 + + total_recharge = db.session.query(db.func.sum(Transaction.amount))\ + .filter_by(user_id=user_id, type='recharge').scalar() or 0 + + return { + 'total_calls': total_calls, + 'success_calls': success_calls, + 'failed_calls': failed_calls, + 'total_cost': abs(total_cost), + 'total_recharge': total_recharge + }, 200 diff --git a/NBATransfer-backend/services/v1_service.py b/NBATransfer-backend/services/v1_service.py new file mode 100644 index 0000000..f96a7db --- /dev/null +++ b/NBATransfer-backend/services/v1_service.py @@ -0,0 +1,174 @@ +from flask import current_app +from models import db, User, ApiCall +from datetime import datetime +import requests +import json +import logging +from modelapiservice import get_model_service +from services.api_proxy_service import ApiProxyService + +logger = logging.getLogger(__name__) + +class V1Service: + @staticmethod + def chat_completions(user, data): + """ + OpenAI 兼容的 Chat Completions 接口逻辑 + """ + model = data.get('model') + messages = data.get('messages', []) + stream = data.get('stream', False) + + if not model: + return {'error': {'message': '缺少 model 参数', 'type': 'invalid_request_error', 'code': 400}}, 400 + + if not messages: + return {'error': {'message': '缺少 messages 参数', 'type': 'invalid_request_error', 'code': 400}}, 400 + + # 获取模型服务并检查余额 + try: + service = get_model_service(model) + except Exception as e: + return {'error': {'message': f'不支持的模型: {model}', 'type': 'invalid_request_error', 'code': 400}}, 400 + + is_sufficient, estimated_cost, error_msg = service.check_balance(user.balance) + if not is_sufficient: + return { + 'error': { + 'message': error_msg, + 'type': 'insufficient_quota', + 'code': 402 + } + }, 402 + + prompt = messages[-1].get('content', '') if messages else 'Empty prompt' + if len(prompt) > 500: + prompt = prompt[:500] + '...' + + api_call = ApiCall( + user_id=user.id, + api_type='chat_completion', + prompt=prompt, + parameters=json.dumps({ + 'model': model, + 'stream': stream + }), + status='processing', + cost=estimated_cost, # 暂时记录预估/固定费用 + request_time=datetime.utcnow() + ) + + try: + db.session.add(api_call) + db.session.flush() + + # 准备请求 + api_url, api_key = service.get_api_config() + if not api_url or not api_key: + raise ValueError(f'模型 {model} API 配置未完成') + + headers = { + 'Authorization': f'Bearer {api_key}', + 'Content-Type': 'application/json' + } + + payload = service.prepare_payload(data) + target_url = f'{api_url}/chat/completions' + + logger.info(f'API 转发: {target_url}, User: {user.id}, Model: {model}') + + response = requests.post( + target_url, + headers=headers, + json=payload, + stream=stream, + timeout=300 + ) + + if response.status_code != 200: + error_msg = f'Upstream Error: {response.status_code}' + try: + error_detail = response.json() + error_msg += f' - {error_detail}' + except: + error_msg += f' - {response.text[:200]}' + + api_call.status = 'failed' + api_call.error_message = error_msg + db.session.commit() + + return { + 'error': { + 'message': error_msg, + 'type': 'upstream_error', + 'code': 502 + } + }, 502 + + # 处理响应 + if stream: + # 流式响应处理 + def generate(): + final_usage = None + try: + for chunk in response.iter_content(chunk_size=1024): + if chunk: + if hasattr(service, 'parse_stream_usage'): + try: + text_chunk = chunk.decode('utf-8', errors='ignore') + usage = service.parse_stream_usage(text_chunk) + if usage: + final_usage = usage + except: + pass + yield chunk + + # 计算最终费用 + actual_cost = service.calculate_cost(final_usage, stream=True) + if actual_cost == 0 and estimated_cost > 0: + actual_cost = estimated_cost + + # 扣费 + with current_app.app_context(): + ApiProxyService.deduct_balance(user.id, api_call.id, actual_cost, model) + + except Exception as e: + logger.error(f'Stream error: {e}') + + return generate(), 200 + else: + # 普通响应 + result = response.json() + api_call.status = 'success' + api_call.response_time = datetime.utcnow() + + # 计算费用 + usage = result.get('usage') + final_cost = service.calculate_cost(usage, stream=False) + if final_cost == 0 and estimated_cost > 0: + final_cost = estimated_cost + + # 简化响应格式 + simplified_result = { + 'model': model, + 'content': '', + 'cost': final_cost + } + + if 'choices' in result and len(result['choices']) > 0: + content = result['choices'][0].get('message', {}).get('content', '') + simplified_result['content'] = content + # 同时更新 api_call 记录 + api_call.result_url = content[:500] + + ApiProxyService.deduct_balance(user.id, api_call.id, final_cost, model) + + return simplified_result, 200 + + except Exception as e: + logger.error(f'API Error: {e}', exc_info=True) + if api_call.id: + api_call.status = 'failed' + api_call.error_message = str(e) + db.session.commit() + return {'error': {'message': 'Internal Server Error', 'type': 'server_error', 'code': 500}}, 500 diff --git a/NBATransfer-backend/后端文档.md b/NBATransfer-backend/后端文档.md new file mode 100644 index 0000000..1781434 --- /dev/null +++ b/NBATransfer-backend/后端文档.md @@ -0,0 +1,177 @@ +# Nano Banana API 中转平台 - 后端 + +基于 Flask 的 Nano Banana API 中转购买平台后端服务。 + +## 功能特性 + +- 📧 邮箱注册登录系统 +- 💰 用户余额管理 +- 💳 支付接口(支付宝/微信支付) +- 🎨 Nano Banana 文生图 API 中转 +- 📊 用户统计和订单管理 +- 🔐 JWT 身份认证 +- 👨‍💼 管理员后台 + +## 技术栈 + +- Flask 3.0 +- SQLite 数据库 +- Flask-SQLAlchemy ORM +- Flask-JWT-Extended 认证 +- Flask-CORS 跨域支持 +- Flask-Mail 邮件服务 + +## 快速开始 + +### 1. 安装依赖 + +```bash +pip install -r requirements.txt +``` + +### 2. 配置环境变量 + +复制 `.env.example` 到 `.env` 并修改配置: + +```bash +copy .env.example .env +``` + +必需配置项: +- `SECRET_KEY`: Flask 密钥 +- `JWT_SECRET_KEY`: JWT 密钥 +- `NANO_BANANA_API_KEY`: Nano Banana API 密钥 +- `MAIL_USERNAME`: 邮箱用户名 +- `MAIL_PASSWORD`: 邮箱密码 + +### 3. 运行应用 + +```bash +python app.py +``` + +服务将在 http://localhost:5000 启动 + +### 4. 默认管理员账号 + +- 邮箱: admin@nba.com +- 密码: admin123 + +**首次登录后请立即修改密码!** + +## API 文档 + +### 认证相关 `/api/auth` + +- `POST /register` - 用户注册 +- `POST /login` - 用户登录 +- `POST /refresh` - 刷新令牌 +- `GET /me` - 获取当前用户信息 +- `POST /change-password` - 修改密码 + +### 用户相关 `/api/user` + +- `GET /profile` - 获取用户资料 +- `PUT /profile` - 更新用户资料 +- `GET /balance` - 获取账户余额 +- `GET /transactions` - 获取交易记录 +- `GET /api-calls` - 获取API调用记录 +- `GET /stats` - 获取统计信息 + +### 订单相关 `/api/order` + +- `POST /create` - 创建充值订单 +- `GET /list` - 获取订单列表 +- `GET /` - 获取订单详情 +- `POST /notify/` - 支付通知(测试用) + +### API服务 `/api/service` + +- `POST /text-to-image` - 文生图API +- `GET /models` - 获取可用模型 +- `GET /pricing` - 获取价格信息 +- `GET /call/` - 获取API调用详情 + +### 管理员 `/api/admin` + +- `GET /users` - 获取用户列表 +- `GET /users/` - 获取用户详情 +- `POST /users//toggle-status` - 启用/禁用用户 +- `POST /users//adjust-balance` - 调整用户余额 +- `GET /orders` - 获取所有订单 +- `GET /api-calls` - 获取所有API调用 +- `GET /stats/overview` - 获取总览统计 +- `GET /stats/chart` - 获取图表数据 + +## 数据库结构 + +### 用户表 (users) +- 邮箱、密码、用户名 +- 余额、激活状态、管理员标识 +- 创建时间、更新时间 + +### 订单表 (orders) +- 订单号、用户ID、金额 +- 支付方式、订单状态 +- 第三方交易ID、支付时间 + +### 交易记录表 (transactions) +- 用户ID、交易类型(充值/消费/退款) +- 金额、前后余额 +- 关联订单、关联API调用 + +### API调用表 (api_calls) +- 用户ID、API类型 +- 提示词、参数、状态 +- 结果URL、费用、错误信息 + +## 开发说明 + +### 添加新的路由 + +1. 在 `routes/` 目录下创建新的蓝图文件 +2. 在 `app.py` 中注册蓝图 + +### 数据库迁移 + +```bash +# 进入 Python shell +python +>>> from app import create_app +>>> from models import db +>>> app = create_app() +>>> with app.app_context(): +... db.create_all() +``` + +## 部署建议 + +### 使用 Gunicorn (生产环境) + +```bash +pip install gunicorn +gunicorn -w 4 -b 0.0.0.0:5000 app:app +``` + +### 使用 Docker + +```dockerfile +FROM python:3.9 +WORKDIR /app +COPY requirements.txt . +RUN pip install -r requirements.txt +COPY . . +CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"] +``` + +## 注意事项 + +1. 生产环境请修改所有默认密钥 +2. 配置实际的邮件服务器 +3. 接入真实的支付接口 +4. 配置 HTTPS +5. 定期备份数据库 + +## 许可证 + +MIT License diff --git a/NBATransfer-backend/启动后端.bat b/NBATransfer-backend/启动后端.bat new file mode 100644 index 0000000..6164677 --- /dev/null +++ b/NBATransfer-backend/启动后端.bat @@ -0,0 +1,28 @@ +@echo off +chcp 65001 >nul +title Nano Banana API - 启动后端服务 + +cd /d "%~dp0NBATransfer-backend" + +if not exist ".env" ( + echo ❌ 错误:未找到 .env 配置文件 + echo 请先运行 一键安装.bat 或手动复制 .env.example 为 .env + pause + exit /b 1 +) + +echo. +echo ============================================ +echo 🍌 Nano Banana API - 后端服务 +echo ============================================ +echo. +echo 启动后端服务... +echo. +echo 📍 访问地址:http://localhost:5000 +echo 📍 API 文档:查看 README.md +echo. +echo 按 Ctrl+C 停止服务 +echo. + +python app.py +pause diff --git a/NBATransfer-frontend/eslint.config.js b/NBATransfer-frontend/eslint.config.js new file mode 100644 index 0000000..5e6b472 --- /dev/null +++ b/NBATransfer-frontend/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/NBATransfer-frontend/index.html b/NBATransfer-frontend/index.html new file mode 100644 index 0000000..f7080ce --- /dev/null +++ b/NBATransfer-frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + 大语言模型API中转站 + + +
+ + + diff --git a/NBATransfer-frontend/package-lock.json b/NBATransfer-frontend/package-lock.json new file mode 100644 index 0000000..d3822c3 --- /dev/null +++ b/NBATransfer-frontend/package-lock.json @@ -0,0 +1,4992 @@ +{ + "name": "nbatransfer", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nbatransfer", + "version": "0.0.0", + "dependencies": { + "@ant-design/icons": "^5.5.4", + "antd": "^5.23.2", + "axios": "^1.7.9", + "dayjs": "^1.11.13", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.1.3", + "recharts": "^2.15.0", + "zustand": "^5.0.3" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.4", + "vite": "^7.2.4" + } + }, + "node_modules/@ant-design/colors": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz", + "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^2.0.6" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.24.0.tgz", + "integrity": "sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "classnames": "^2.3.1", + "csstype": "^3.1.3", + "rc-util": "^5.35.0", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz", + "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT" + }, + "node_modules/@ant-design/react-slick": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz", + "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "resize-observer-polyfill": "^1.5.1", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@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.5", + "@babel/template": "^7.27.2", + "@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", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@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": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz", + "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz", + "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^2.0.6", + "@babel/runtime": "^7.23.6", + "classnames": "^2.2.6", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz", + "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz", + "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz", + "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz", + "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.1.tgz", + "integrity": "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz", + "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/portal": "^1.0.0-9", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/trigger": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.3.0.tgz", + "integrity": "sha512-iwaxZyzOuK0D7lS+0AQEtW52zUWxoGqTGkke3dRyb8pYiShmRpCjB/8TzPI4R6YySCH7Vm9BZj/31VPiiQTLBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@rc-component/portal": "^1.1.0", + "classnames": "^2.3.2", + "rc-motion": "^2.0.0", + "rc-resize-observer": "^1.3.1", + "rc-util": "^5.44.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@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": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.1.tgz", + "integrity": "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.48.1", + "@typescript-eslint/type-utils": "8.48.1", + "@typescript-eslint/utils": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.48.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.1.tgz", + "integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.48.1", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.1.tgz", + "integrity": "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.48.1", + "@typescript-eslint/types": "^8.48.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.1.tgz", + "integrity": "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.1.tgz", + "integrity": "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.1.tgz", + "integrity": "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1", + "@typescript-eslint/utils": "8.48.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.1.tgz", + "integrity": "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.1.tgz", + "integrity": "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.48.1", + "@typescript-eslint/tsconfig-utils": "8.48.1", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.1.tgz", + "integrity": "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.48.1", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.1.tgz", + "integrity": "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.48.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", + "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.53", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/antd": { + "version": "5.29.1", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.29.1.tgz", + "integrity": "sha512-TTFVbpKbyL6cPfEoKq6Ya3BIjTUr7uDW9+7Z+1oysRv1gpcN7kQ4luH8r/+rXXwz4n6BIz1iBJ1ezKCdsdNW0w==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.2.1", + "@ant-design/cssinjs": "^1.23.0", + "@ant-design/cssinjs-utils": "^1.1.3", + "@ant-design/fast-color": "^2.0.6", + "@ant-design/icons": "^5.6.1", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.26.0", + "@rc-component/color-picker": "~2.0.1", + "@rc-component/mutate-observer": "^1.1.0", + "@rc-component/qrcode": "~1.1.0", + "@rc-component/tour": "~1.15.1", + "@rc-component/trigger": "^2.3.0", + "classnames": "^2.5.1", + "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.11", + "rc-cascader": "~3.34.0", + "rc-checkbox": "~3.5.0", + "rc-collapse": "~3.9.0", + "rc-dialog": "~9.6.0", + "rc-drawer": "~7.3.0", + "rc-dropdown": "~4.2.1", + "rc-field-form": "~2.7.1", + "rc-image": "~7.12.0", + "rc-input": "~1.8.0", + "rc-input-number": "~9.5.0", + "rc-mentions": "~2.20.0", + "rc-menu": "~9.16.1", + "rc-motion": "^2.9.5", + "rc-notification": "~5.6.4", + "rc-pagination": "~5.1.0", + "rc-picker": "~4.11.3", + "rc-progress": "~4.0.0", + "rc-rate": "~2.13.1", + "rc-resize-observer": "^1.4.3", + "rc-segmented": "~2.7.0", + "rc-select": "~14.16.8", + "rc-slider": "~11.1.9", + "rc-steps": "~6.0.1", + "rc-switch": "~4.1.0", + "rc-table": "~7.54.0", + "rc-tabs": "~15.7.0", + "rc-textarea": "~1.10.2", + "rc-tooltip": "~6.4.0", + "rc-tree": "~5.13.1", + "rc-tree-select": "~5.27.0", + "rc-upload": "~4.11.0", + "rc-util": "^5.44.4", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.4.tgz", + "integrity": "sha512-ZCQ9GEWl73BVm8bu5Fts8nt7MHdbt5vY9bP6WGnUh+r3l8M7CgfyTlwsgCbMC66BNxPr6Xoce3j66Ms5YUQTNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001759", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz", + "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/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.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT", + "peer": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.266", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.266.tgz", + "integrity": "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz", + "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.3.tgz", + "integrity": "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/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" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/rc-cascader": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz", + "integrity": "sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "^2.3.1", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-checkbox": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.5.0.tgz", + "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.25.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-collapse": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.9.0.tgz", + "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz", + "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/portal": "^1.0.0-8", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-drawer": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.3.0.tgz", + "integrity": "sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@rc-component/portal": "^1.1.1", + "classnames": "^2.2.6", + "rc-motion": "^2.6.1", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dropdown": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.1.tgz", + "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-util": "^5.44.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/rc-field-form": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.7.1.tgz", + "integrity": "sha512-vKeSifSJ6HoLaAB+B8aq/Qgm8a3dyxROzCtKNCsBQgiverpc4kWDQihoUwzUj+zNWJOykwSY4dNX3QrGwtVb9A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/async-validator": "^5.0.3", + "rc-util": "^5.32.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-image": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.12.0.tgz", + "integrity": "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/portal": "^1.0.2", + "classnames": "^2.2.6", + "rc-dialog": "~9.6.0", + "rc-motion": "^2.6.2", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.8.0.tgz", + "integrity": "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.18.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-input-number": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.5.0.tgz", + "integrity": "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/mini-decimal": "^1.0.1", + "classnames": "^2.2.5", + "rc-input": "~1.8.0", + "rc-util": "^5.40.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.20.0.tgz", + "integrity": "sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.22.5", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-input": "~1.8.0", + "rc-menu": "~9.16.0", + "rc-textarea": "~1.10.0", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu": { + "version": "9.16.1", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.16.1.tgz", + "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.3.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz", + "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.44.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-notification": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.4.tgz", + "integrity": "sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.9.0", + "rc-util": "^5.20.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.5.0.tgz", + "integrity": "sha512-Lm/v9h0LymeUYJf0x39OveU52InkdRXqnn2aYXfWmo8WdOonIKB2kfau+GF0fWq6jPgtdO9yMqveGcK6aIhJmg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.37.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-pagination": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-5.1.0.tgz", + "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-picker": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.11.3.tgz", + "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.1", + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.43.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/rc-progress": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz", + "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-rate": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.1.tgz", + "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz", + "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.44.1", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-segmented": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.7.0.tgz", + "integrity": "sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-motion": "^2.4.4", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-select": { + "version": "14.16.8", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.8.tgz", + "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.1.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.3.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-slider": { + "version": "11.1.9", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.9.tgz", + "integrity": "sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-steps": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-6.0.1.tgz", + "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.16.7", + "classnames": "^2.2.3", + "rc-util": "^5.16.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-switch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-4.1.0.tgz", + "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "classnames": "^2.2.1", + "rc-util": "^5.30.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-table": { + "version": "7.54.0", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.54.0.tgz", + "integrity": "sha512-/wDTkki6wBTjwylwAGjpLKYklKo9YgjZwAU77+7ME5mBoS32Q4nAwoqhA2lSge6fobLW3Tap6uc5xfwaL2p0Sw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/context": "^1.4.0", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.44.3", + "rc-virtual-list": "^3.14.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.7.0.tgz", + "integrity": "sha512-ZepiE+6fmozYdWf/9gVp7k56PKHB1YYoDsKeQA1CBlJ/POIhjkcYiv0AGP0w2Jhzftd3AVvZP/K+V+Lpi2ankA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "~4.2.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.6.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.34.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-textarea": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.10.2.tgz", + "integrity": "sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-input": "~1.8.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tooltip": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.4.0.tgz", + "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.1", + "rc-util": "^5.44.3" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tree": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.13.1.tgz", + "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-tree-select": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.27.0.tgz", + "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "2.x", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-upload": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.11.0.tgz", + "integrity": "sha512-ZUyT//2JAehfHzjWowqROcwYJKnZkIUGWaTE/VogVrepSl7AFNbQf4+zGfX4zl9Vrj/Jm8scLO0R6UlPDKK4wA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "5.44.4", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz", + "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-virtual-list": { + "version": "3.19.2", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.19.2.tgz", + "integrity": "sha512-Ys6NcjwGkuwkeaWBDqfI3xWuZ7rDiQXlH1o2zLfFzATfEgXcqpk8CkgMfbJD81McqjcJVez25a3kPxCR807evA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/react": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", + "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", + "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.10.1.tgz", + "integrity": "sha512-gHL89dRa3kwlUYtRQ+m8NmxGI6CgqN+k4XyGjwcFoQwwCWF6xXpOCUlDovkXClS0d0XJN/5q7kc5W3kiFEd0Yw==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.10.1.tgz", + "integrity": "sha512-JNBANI6ChGVjA5bwsUIwJk7LHKmqB4JYnYfzFwyp2t12Izva11elds2jx7Yfoup2zssedntwU0oZ5DEmk5Sdaw==", + "license": "MIT", + "dependencies": { + "react-router": "7.10.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.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/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.1.tgz", + "integrity": "sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.48.1", + "@typescript-eslint/parser": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1", + "@typescript-eslint/utils": "8.48.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", + "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/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", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "7.2.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.7.tgz", + "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/zustand": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.9.tgz", + "integrity": "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/NBATransfer-frontend/package.json b/NBATransfer-frontend/package.json new file mode 100644 index 0000000..f8e8753 --- /dev/null +++ b/NBATransfer-frontend/package.json @@ -0,0 +1,37 @@ +{ + "name": "nbatransfer", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.1.3", + "axios": "^1.7.9", + "zustand": "^5.0.3", + "antd": "^5.23.2", + "@ant-design/icons": "^5.5.4", + "dayjs": "^1.11.13", + "recharts": "^2.15.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.4", + "vite": "^7.2.4" + } +} diff --git a/NBATransfer-frontend/public/vite.svg b/NBATransfer-frontend/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/NBATransfer-frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/NBATransfer-frontend/src/App.css b/NBATransfer-frontend/src/App.css new file mode 100644 index 0000000..d6823cf --- /dev/null +++ b/NBATransfer-frontend/src/App.css @@ -0,0 +1,17 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +#root { + min-height: 100vh; +} diff --git a/NBATransfer-frontend/src/App.tsx b/NBATransfer-frontend/src/App.tsx new file mode 100644 index 0000000..978a297 --- /dev/null +++ b/NBATransfer-frontend/src/App.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; +import { ConfigProvider } from 'antd'; +import zhCN from 'antd/locale/zh_CN'; +import Login from './pages/Login'; +import DashboardLayout from './components/DashboardLayout'; +import Dashboard from './pages/Dashboard'; +import Wallet from './pages/Wallet'; +import ApiKeyManagement from './pages/ApiKeyManagement'; +import ApiDocumentation from './pages/ApiDocumentation'; +import Settings from './pages/Settings'; +import { PrivateRoute } from './components/PrivateRoute'; +import { useAuthStore } from './store/auth'; +import './App.css'; + +function App() { + const isAuthenticated = useAuthStore((state) => state.isAuthenticated); + + return ( + + + + : } + /> + + + + + } + > + } /> + } /> + } /> + } /> + } /> + + + } /> + } /> + + + + ); +} +export default App; diff --git a/NBATransfer-frontend/src/api/client.ts b/NBATransfer-frontend/src/api/client.ts new file mode 100644 index 0000000..db7f94d --- /dev/null +++ b/NBATransfer-frontend/src/api/client.ts @@ -0,0 +1,65 @@ +import axios from 'axios'; + +const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:5000/api'; + +const api = axios.create({ + baseURL: API_BASE_URL, + timeout: 30000, + headers: { + 'Content-Type': 'application/json', + }, +}); + +// 请求拦截器 - 添加 token +api.interceptors.request.use( + (config) => { + const token = localStorage.getItem('access_token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +// 响应拦截器 - 处理错误 +api.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config; + + // 如果 token 过期,尝试刷新 + if (error.response?.status === 401 && !originalRequest._retry) { + originalRequest._retry = true; + + try { + const refreshToken = localStorage.getItem('refresh_token'); + if (refreshToken) { + const response = await axios.post(`${API_BASE_URL}/auth/refresh`, null, { + headers: { + Authorization: `Bearer ${refreshToken}`, + }, + }); + + const { access_token } = response.data; + localStorage.setItem('access_token', access_token); + + originalRequest.headers.Authorization = `Bearer ${access_token}`; + return api(originalRequest); + } + } catch (refreshError) { + // 刷新失败,清除 token 并跳转到登录页 + localStorage.removeItem('access_token'); + localStorage.removeItem('refresh_token'); + window.location.href = '/login'; + return Promise.reject(refreshError); + } + } + + return Promise.reject(error); + } +); + +export default api; diff --git a/NBATransfer-frontend/src/api/index.ts b/NBATransfer-frontend/src/api/index.ts new file mode 100644 index 0000000..3a633cf --- /dev/null +++ b/NBATransfer-frontend/src/api/index.ts @@ -0,0 +1,10 @@ +import api from './client'; + +export * from './modules/auth'; +export * from './modules/user'; +export * from './modules/order'; +export * from './modules/service'; +export * from './modules/admin'; +export * from './modules/apikey'; + +export default api; diff --git a/NBATransfer-frontend/src/api/modules/admin.ts b/NBATransfer-frontend/src/api/modules/admin.ts new file mode 100644 index 0000000..4f04ee8 --- /dev/null +++ b/NBATransfer-frontend/src/api/modules/admin.ts @@ -0,0 +1,16 @@ +import api from '../client'; + +export const adminAPI = { + getUsers: (params: { page?: number; per_page?: number; search?: string }) => + api.get('/admin/users', { params }), + getUserDetail: (userId: number) => api.get(`/admin/users/${userId}`), + toggleUserStatus: (userId: number) => api.post(`/admin/users/${userId}/toggle-status`), + adjustBalance: (userId: number, data: { amount: number; description: string }) => + api.post(`/admin/users/${userId}/adjust-balance`, data), + getAllOrders: (params: { page?: number; per_page?: number; status?: string }) => + api.get('/admin/orders', { params }), + getAllApiCalls: (params: { page?: number; per_page?: number; status?: string }) => + api.get('/admin/api-calls', { params }), + getOverviewStats: () => api.get('/admin/stats/overview'), + getChartData: (params: { days?: number }) => api.get('/admin/stats/chart', { params }), +}; diff --git a/NBATransfer-frontend/src/api/modules/apikey.ts b/NBATransfer-frontend/src/api/modules/apikey.ts new file mode 100644 index 0000000..4b396c8 --- /dev/null +++ b/NBATransfer-frontend/src/api/modules/apikey.ts @@ -0,0 +1,10 @@ +import api from '../client'; + +export const apikeyAPI = { + getKeys: () => api.get('/apikey/keys'), + createKey: (data: { name: string }) => api.post('/apikey/keys', data), + deleteKey: (id: number) => api.delete(`/apikey/keys/${id}`), + updateKey: (id: number, data: { name?: string; is_active?: boolean }) => + api.put(`/apikey/keys/${id}`, data), + regenerateKey: (id: number) => api.post(`/apikey/keys/${id}/regenerate`, {}), +}; diff --git a/NBATransfer-frontend/src/api/modules/auth.ts b/NBATransfer-frontend/src/api/modules/auth.ts new file mode 100644 index 0000000..571988c --- /dev/null +++ b/NBATransfer-frontend/src/api/modules/auth.ts @@ -0,0 +1,17 @@ +import api from '../client'; + +export const authAPI = { + sendVerificationCode: (data: { email: string }) => + api.post('/auth/send-verification-code', data), + register: (data: { email: string; password: string; verification_code: string }) => + api.post('/auth/register', { ...data, code: data.verification_code }), + login: (data: { email: string; password: string }) => + api.post('/auth/login', data), + getCurrentUser: () => api.get('/auth/me'), + changePassword: (data: { old_password: string; new_password: string }) => + api.post('/auth/change-password', data), + verifyCode: (data: { email: string; code: string }) => + api.post('/auth/verify-code', data), + resetPassword: (data: { email: string; verification_code: string; new_password: string }) => + api.post('/auth/reset-password', { ...data, code: data.verification_code }), +}; diff --git a/NBATransfer-frontend/src/api/modules/order.ts b/NBATransfer-frontend/src/api/modules/order.ts new file mode 100644 index 0000000..29059ec --- /dev/null +++ b/NBATransfer-frontend/src/api/modules/order.ts @@ -0,0 +1,10 @@ +import api from '../client'; + +export const orderAPI = { + createOrder: (data: { amount: number; payment_method: string }) => + api.post('/order/create', data), + getOrders: (params: { page?: number; per_page?: number; status?: string }) => + api.get('/order/list', { params }), + getOrder: (orderId: number) => api.get(`/order/${orderId}`), + notifyPayment: (orderNo: string) => api.post(`/order/notify/${orderNo}`), +}; diff --git a/NBATransfer-frontend/src/api/modules/service.ts b/NBATransfer-frontend/src/api/modules/service.ts new file mode 100644 index 0000000..86cebe4 --- /dev/null +++ b/NBATransfer-frontend/src/api/modules/service.ts @@ -0,0 +1,9 @@ +import api from '../client'; + +export const apiServiceAPI = { + textToImage: (data: { model?: string; messages?: any[]; prompt?: string; parameters?: any }) => + api.post('/service/text-to-image', data), + getModels: () => api.get('/service/models'), + getPricing: () => api.get('/service/pricing'), + getApiCall: (callId: number) => api.get(`/service/call/${callId}`), +}; diff --git a/NBATransfer-frontend/src/api/modules/user.ts b/NBATransfer-frontend/src/api/modules/user.ts new file mode 100644 index 0000000..94f2b71 --- /dev/null +++ b/NBATransfer-frontend/src/api/modules/user.ts @@ -0,0 +1,12 @@ +import api from '../client'; + +export const userAPI = { + getProfile: () => api.get('/user/profile'), + updateProfile: (data: { username?: string }) => api.put('/user/profile', data), + getBalance: () => api.get('/user/balance'), + getTransactions: (params: { page?: number; per_page?: number }) => + api.get('/user/transactions', { params }), + getApiCalls: (params: { page?: number; per_page?: number }) => + api.get('/user/api-calls', { params }), + getStats: () => api.get('/user/stats'), +}; diff --git a/NBATransfer-frontend/src/assets/react.svg b/NBATransfer-frontend/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/NBATransfer-frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/NBATransfer-frontend/src/components/DashboardLayout.css b/NBATransfer-frontend/src/components/DashboardLayout.css new file mode 100644 index 0000000..7b6fc4a --- /dev/null +++ b/NBATransfer-frontend/src/components/DashboardLayout.css @@ -0,0 +1,86 @@ +.dashboard-layout { + min-height: 100vh; +} + +.dashboard-sider { + box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1); +} + +.logo { + height: 64px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.1); +} + +.logo h2 { + color: #fff; + margin: 0; + font-size: 20px; +} + +.dashboard-header { + background: #fff; + padding: 0 24px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + display: flex; + justify-content: flex-end; + align-items: center; +} + +.header-right { + display: flex; + align-items: center; + gap: 24px; +} + +.balance-info { + font-weight: 600; + color: #1890ff; + font-size: 16px; +} + +.user-info { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + padding: 8px 12px; + border-radius: 4px; + transition: background 0.3s; +} + +.user-info:hover { + background: rgba(0, 0, 0, 0.05); +} + +.username { + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dashboard-content { + margin: 24px; + padding: 24px; + background: #fff; + border-radius: 8px; + min-height: calc(100vh - 112px); +} + +@media (max-width: 768px) { + .dashboard-content { + margin: 16px; + padding: 16px; + } + + .username { + display: none; + } + + .balance-info { + font-size: 14px; + } +} diff --git a/NBATransfer-frontend/src/components/DashboardLayout.tsx b/NBATransfer-frontend/src/components/DashboardLayout.tsx new file mode 100644 index 0000000..31536d4 --- /dev/null +++ b/NBATransfer-frontend/src/components/DashboardLayout.tsx @@ -0,0 +1,143 @@ +import React, { useEffect, useState } from 'react'; +import { Layout, Menu, Avatar, Dropdown, message } from 'antd'; +import { + DashboardOutlined, + WalletOutlined, + UserOutlined, + LogoutOutlined, + SettingOutlined, + KeyOutlined, + FileTextOutlined, +} from '@ant-design/icons'; +import { Outlet, useNavigate, useLocation } from 'react-router-dom'; +import { useAuthStore } from '../store/auth'; +import { userAPI } from '../api'; +import './DashboardLayout.css'; + +const { Header, Sider, Content } = Layout; + +const DashboardLayout: React.FC = () => { + const [collapsed, setCollapsed] = useState(false); + const navigate = useNavigate(); + const location = useLocation(); + const { user, logout, updateUser } = useAuthStore(); + + useEffect(() => { + // 获取最新用户信息(包括余额) + const fetchUserProfile = async () => { + try { + const response = await userAPI.getProfile(); + updateUser(response.data); + } catch (error) { + console.error('Failed to fetch user profile:', error); + } + }; + + fetchUserProfile(); + }, [updateUser]); + + const menuItems = [ + { + key: '/dashboard', + icon: , + label: '概览', + }, + { + key: '/dashboard/wallet', + icon: , + label: '我的钱包', + }, + { + key: '/dashboard/apikeys', + icon: , + label: 'API密钥', + }, + { + key: '/dashboard/docs', + icon: , + label: 'API文档', + }, + { + key: '/dashboard/settings', + icon: , + label: '账户设置', + }, + ]; + + const handleLogout = () => { + logout(); + message.success('已退出登录'); + navigate('/login'); + }; + + const userMenuItems = [ + { + key: 'profile', + icon: , + label: '个人资料', + onClick: () => navigate('/dashboard/settings'), + }, + { + key: 'logout', + icon: , + label: '退出登录', + onClick: handleLogout, + }, + ]; + + useEffect(() => { + // 移动端自动收起侧边栏 + const handleResize = () => { + if (window.innerWidth < 768) { + setCollapsed(true); + } + }; + + handleResize(); + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + return ( + + +
+

{collapsed ? '🍌' : '🍌 大语言模型API'}

+
+ navigate(key)} + /> + + +
+
+
+ 余额: ¥{user?.balance?.toFixed(2) || '0.00'} +
+ +
+ } /> + {user?.username || user?.email} +
+
+
+
+ + + +
+ + ); +}; + +export default DashboardLayout; diff --git a/NBATransfer-frontend/src/components/PrivateRoute.tsx b/NBATransfer-frontend/src/components/PrivateRoute.tsx new file mode 100644 index 0000000..56c2e38 --- /dev/null +++ b/NBATransfer-frontend/src/components/PrivateRoute.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Navigate } from 'react-router-dom'; +import { useAuthStore } from '../store/auth'; + +interface PrivateRouteProps { + children: React.ReactNode; + requireAdmin?: boolean; +} + +export const PrivateRoute: React.FC = ({ children, requireAdmin = false }) => { + const { isAuthenticated, user } = useAuthStore(); + + if (!isAuthenticated) { + return ; + } + + if (requireAdmin && !user?.is_admin) { + return ; + } + + return <>{children}; +}; diff --git a/NBATransfer-frontend/src/index.css b/NBATransfer-frontend/src/index.css new file mode 100644 index 0000000..dad0578 --- /dev/null +++ b/NBATransfer-frontend/src/index.css @@ -0,0 +1,14 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/NBATransfer-frontend/src/main.tsx b/NBATransfer-frontend/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/NBATransfer-frontend/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/NBATransfer-frontend/src/pages/ApiDocumentation.css b/NBATransfer-frontend/src/pages/ApiDocumentation.css new file mode 100644 index 0000000..eb2fb63 --- /dev/null +++ b/NBATransfer-frontend/src/pages/ApiDocumentation.css @@ -0,0 +1,134 @@ +.doc-section { + padding: 20px 0; +} + +.doc-section h2 { + margin-top: 20px; + margin-bottom: 15px; + color: #000; + font-size: 20px; +} + +.doc-section h3 { + margin-top: 15px; + margin-bottom: 10px; + color: #333; + font-size: 16px; +} + +.doc-section p { + margin-bottom: 12px; + line-height: 1.6; + color: #666; +} + +.doc-section ul, +.doc-section ol { + margin-left: 20px; + margin-bottom: 12px; + color: #666; + line-height: 1.8; +} + +.doc-section li { + margin-bottom: 8px; +} + +.code-block { + background: #f5f5f5; + border: 1px solid #ddd; + border-radius: 4px; + overflow: hidden; + margin: 15px 0; +} + +.code-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 15px; + background: #efefef; + border-bottom: 1px solid #ddd; + font-weight: 500; + font-size: 12px; +} + +.code-block pre { + margin: 0; + padding: 15px; + overflow-x: auto; + font-family: 'Courier New', monospace; + font-size: 12px; + line-height: 1.5; + color: #333; +} + +.endpoint-doc { + display: flex; + align-items: center; + gap: 10px; + margin: 20px 0; + padding: 15px; + background: #f0f2f5; + border-left: 4px solid #1890ff; + border-radius: 4px; +} + +.method-badge { + display: inline-block; + padding: 5px 12px; + background: #52c41a; + color: white; + border-radius: 3px; + font-weight: bold; + font-size: 12px; +} + +.endpoint-url { + font-family: 'Courier New', monospace; + font-size: 14px; + font-weight: 500; +} + +.pricing-table, +.param-table, +.error-table { + width: 100%; + border-collapse: collapse; + margin: 15px 0; + background: white; + border: 1px solid #ddd; +} + +.pricing-table th, +.param-table th, +.error-table th { + background: #fafafa; + padding: 12px; + border: 1px solid #ddd; + font-weight: 600; + text-align: left; + font-size: 13px; +} + +.pricing-table td, +.param-table td, +.error-table td { + padding: 12px; + border: 1px solid #ddd; + font-size: 13px; + color: #666; +} + +.pricing-table tr:hover, +.param-table tr:hover, +.error-table tr:hover { + background: #fafafa; +} + +.pricing-table td:first-child, +.param-table td:first-child, +.error-table td:first-child { + font-weight: 500; + color: #333; +} diff --git a/NBATransfer-frontend/src/pages/ApiDocumentation.tsx b/NBATransfer-frontend/src/pages/ApiDocumentation.tsx new file mode 100644 index 0000000..f130f89 --- /dev/null +++ b/NBATransfer-frontend/src/pages/ApiDocumentation.tsx @@ -0,0 +1,163 @@ +import React, { useState } from 'react'; +import { Card, Tabs, Alert, Button, Space, message } from 'antd'; +import { CopyOutlined } from '@ant-design/icons'; +import './ApiDocumentation.css'; + +const ApiDocumentation: React.FC = () => { + const [selectedExample, setSelectedExample] = useState('curl'); + + const copyToClipboard = (text: string) => { + navigator.clipboard.writeText(text); + message.success('已复制到剪贴板'); + }; + + const API_BASE_URL = 'https://api.nanobananapi.com/v1'; + + // 示例代码 + const examples = { + curl: `curl ${API_BASE_URL}/chat/completions \\ + -H "Content-Type: application/json" \\ + -H "Authorization: Bearer sk_your_api_key_here" \\ + -d '{ + "model": "deepseek-chat", + "messages": [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Hello!"} + ], + "stream": false + }'`, + + python: `import requests + +url = "${API_BASE_URL}/chat/completions" + +headers = { + "Content-Type": "application/json", + "Authorization": "Bearer sk_your_api_key_here" +} + +data = { + "model": "deepseek-chat", + "messages": [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Hello!"} + ], + "stream": False +} + +response = requests.post(url, headers=headers, json=data) +print(response.json())`, + + nodejs: `const axios = require('axios'); + +const url = '${API_BASE_URL}/chat/completions'; +const apiKey = 'sk_your_api_key_here'; + +const data = { + model: 'deepseek-chat', + messages: [ + { role: 'system', content: 'You are a helpful assistant.' }, + { role: 'user', content: 'Hello!' } + ], + stream: false +}; + +axios.post(url, data, { + headers: { + 'Content-Type': 'application/json', + 'Authorization': \`Bearer \${apiKey}\` + } +}) +.then(response => { + console.log(response.data); +}) +.catch(error => { + console.error(error); +});` + }; + + return ( +
+ +

🍌 大语言模型API中转站文档

+ + + +
+

认证方式

+

所有 API 请求都需要在 HTTP 请求头中提供 API Key:

+ +
+
+ HTTP Headers +
+
{`Authorization: Bearer sk_your_api_key_here`}
+
+

+ * 您可以在 "API 密钥管理" 页面创建和获取您的 API Key。 +

+
+ +
+

DeepSeek API 调用示例

+

我们完全兼容 OpenAI 格式的接口调用,只需将 Base URL 替换为我们的接口地址,并使用我们的 API Key 即可。

+ +
+ + {Object.keys(examples).map((lang) => ( + + ))} + +
+ +
+
+ + {selectedExample === 'curl' + ? 'cURL' + : selectedExample === 'python' + ? 'Python' + : 'Node.js'} + +
+
{examples[selectedExample as keyof typeof examples]}
+
+
+
+
+ ); +}; + +export default ApiDocumentation; diff --git a/NBATransfer-frontend/src/pages/ApiKeyManagement.css b/NBATransfer-frontend/src/pages/ApiKeyManagement.css new file mode 100644 index 0000000..59d4024 --- /dev/null +++ b/NBATransfer-frontend/src/pages/ApiKeyManagement.css @@ -0,0 +1,14 @@ +.api-key-copy { + cursor: pointer; + font-family: 'Courier New', monospace; + user-select: all; + background: #f5f5f5; + padding: 4px 8px; + border-radius: 4px; + border: 1px solid #d9d9d9; +} + +.api-key-copy:hover { + background: #e6e6e6; + border-color: #b3b3b3; +} diff --git a/NBATransfer-frontend/src/pages/ApiKeyManagement.tsx b/NBATransfer-frontend/src/pages/ApiKeyManagement.tsx new file mode 100644 index 0000000..467c874 --- /dev/null +++ b/NBATransfer-frontend/src/pages/ApiKeyManagement.tsx @@ -0,0 +1,347 @@ +import React, { useState, useEffect } from 'react'; +import { + Card, + Button, + Table, + Modal, + Form, + Input, + Space, + message, + Empty, + Spin, + Tag, + Tooltip, + Popconfirm, + Alert, +} from 'antd'; +import { + PlusOutlined, + DeleteOutlined, + CopyOutlined, + ReloadOutlined, + LockOutlined, + UnlockOutlined, +} from '@ant-design/icons'; +import { apikeyAPI } from '../api'; +import type { ApiKey } from '../types'; +import './ApiKeyManagement.css'; + +const ApiKeyManagement: React.FC = () => { + const [apiKeys, setApiKeys] = useState([]); + const [loading, setLoading] = useState(false); + const [createModalVisible, setCreateModalVisible] = useState(false); + const [form] = Form.useForm(); + const [newKeyData, setNewKeyData] = useState<{ api_key: string } | null>( + null + ); + const [newKeyModalVisible, setNewKeyModalVisible] = useState(false); + + // 获取 API 密钥列表 + const fetchApiKeys = async () => { + setLoading(true); + try { + const response = await apikeyAPI.getKeys(); + setApiKeys(response.data.keys || []); + } catch (error: any) { + message.error(error.response?.data?.error || '获取 API 密钥列表失败'); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchApiKeys(); + }, []); + + // 创建新密钥 + const handleCreateKey = async (values: { name: string }) => { + try { + const response = await apikeyAPI.createKey({ name: values.name }); + + // 展示新生成的密钥(只会显示一次) + setNewKeyData({ + api_key: response.data.key.api_key, + }); + setNewKeyModalVisible(true); + setCreateModalVisible(false); + form.resetFields(); + + // 刷新列表 + await fetchApiKeys(); + } catch (error: any) { + message.error(error.response?.data?.error || '创建 API 密钥失败'); + } + }; + + // 删除密钥 + const handleDeleteKey = async (id: number) => { + try { + await apikeyAPI.deleteKey(id); + message.success('API 密钥已删除'); + await fetchApiKeys(); + } catch (error: any) { + message.error(error.response?.data?.error || '删除 API 密钥失败'); + } + }; + + // 切换密钥激活状态 + const handleToggleActive = async (id: number, currentStatus: boolean) => { + try { + await apikeyAPI.updateKey(id, { is_active: !currentStatus }); + message.success(!currentStatus ? 'API 密钥已激活' : 'API 密钥已停用'); + await fetchApiKeys(); + } catch (error: any) { + message.error(error.response?.data?.error || '更新 API 密钥失败'); + } + }; + + // 轮换 API Key + const handleRegenerateKey = async (id: number) => { + try { + const response = await apikeyAPI.regenerateKey(id); + + // 展示新秘钥 + setNewKeyData({ + api_key: response.data.key.api_key, + }); + setNewKeyModalVisible(true); + + // 刷新列表 + await fetchApiKeys(); + } catch (error: any) { + message.error(error.response?.data?.error || '重置 Key 失败'); + } + }; + + // 复制到剪贴板 + const copyToClipboard = (text: string) => { + navigator.clipboard.writeText(text); + message.success('已复制到剪贴板'); + }; + + const columns = [ + { + title: 'API Key', + dataIndex: 'api_key', + key: 'api_key', + width: 200, + render: (text: string) => ( + +
copyToClipboard(text)} + style={{ + cursor: 'pointer', + fontFamily: 'monospace', + wordBreak: 'break-all', + }} + > + {text} + +
+
+ ), + }, + { + title: '名称', + dataIndex: 'name', + key: 'name', + width: 150, + }, + { + title: '状态', + dataIndex: 'is_active', + key: 'is_active', + width: 100, + render: (isActive: boolean) => ( + {isActive ? '激活' : '已停用'} + ), + }, + { + title: '创建时间', + dataIndex: 'created_at', + key: 'created_at', + width: 180, + render: (text: string) => new Date(text).toLocaleString('zh-CN'), + }, + { + title: '最后使用', + dataIndex: 'last_used_at', + key: 'last_used_at', + width: 180, + render: (text?: string) => + text ? new Date(text).toLocaleString('zh-CN') : '从未使用过', + }, + { + title: '操作', + key: 'action', + width: 200, + render: (_: any, record: ApiKey) => ( + + + + + + handleDeleteKey(record.id)} + okText="确定" + cancelText="取消" + > + + + + ), + }, + ]; + + return ( +
+ +
+

API 密钥管理

+ +
+ + + + + {apiKeys.length > 0 ? ( + + ) : ( + + )} + + + + {/* 创建密钥对话框 */} + { + setCreateModalVisible(false); + form.resetFields(); + }} + footer={[ + , + , + ]} + > +
+ + + + + +
+ + {/* 显示新生成的密钥 */} + setNewKeyModalVisible(false)} + onCancel={() => setNewKeyModalVisible(false)} + footer={[ + , + ]} + > + + + {newKeyData && ( +
+
+ +
+ +
+
+
+ )} +
+ + ); +}; + +export default ApiKeyManagement; diff --git a/NBATransfer-frontend/src/pages/Dashboard.css b/NBATransfer-frontend/src/pages/Dashboard.css new file mode 100644 index 0000000..ef0c248 --- /dev/null +++ b/NBATransfer-frontend/src/pages/Dashboard.css @@ -0,0 +1,51 @@ +.dashboard-home h1 { + margin-bottom: 24px; + font-size: 24px; +} + +.loading-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 400px; +} + +.quick-start { + padding: 16px 0; +} + +.quick-start h3 { + font-size: 18px; + margin-bottom: 16px; +} + +.quick-start ol { + padding-left: 24px; + line-height: 2; +} + +.quick-start ol li { + margin-bottom: 8px; +} + +.pricing-info { + margin-top: 24px; + padding: 16px; + background: #f5f5f5; + border-radius: 8px; +} + +.pricing-info p { + margin: 8px 0; + font-size: 16px; +} + +@media (max-width: 768px) { + .dashboard-home h1 { + font-size: 20px; + } + + .quick-start h3 { + font-size: 16px; + } +} diff --git a/NBATransfer-frontend/src/pages/Dashboard.tsx b/NBATransfer-frontend/src/pages/Dashboard.tsx new file mode 100644 index 0000000..bca775a --- /dev/null +++ b/NBATransfer-frontend/src/pages/Dashboard.tsx @@ -0,0 +1,111 @@ +import React, { useEffect, useState } from 'react'; +import { Row, Col, Card, Statistic, Spin } from 'antd'; +import { ApiOutlined, CheckCircleOutlined, CloseCircleOutlined, WalletOutlined } from '@ant-design/icons'; +import { userAPI } from '../api'; +import type { Stats } from '../types'; +import './Dashboard.css'; + +const Dashboard: React.FC = () => { + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetchStats(); + }, []); + + const fetchStats = async () => { + try { + const response = await userAPI.getStats(); + setStats(response.data); + } catch (error) { + console.error('Failed to fetch stats:', error); + } finally { + setLoading(false); + } + }; + + if (loading) { + return ( +
+ +
+ ); + } + + return ( +
+

数据概览

+ +
+ + } + valueStyle={{ color: '#1890ff' }} + /> + + + + + } + valueStyle={{ color: '#52c41a' }} + /> + + + + + } + valueStyle={{ color: '#ff4d4f' }} + /> + + + + + } + precision={2} + valueStyle={{ color: '#fa8c16' }} + suffix="元" + /> + + + + + + + +
+

🎨 如何使用大语言模型API中转站

+
    +
  1. + 充值账户: 前往"我的钱包"充值余额 +
  2. +
  3. + 调用API: 进入"API调用"页面生成图片 +
  4. +
  5. + 查看记录: 在"历史记录"查看所有调用记录 +
  6. +
+
+

⚡ 速度: 平均生成时间 10-30 秒

+

🎯 质量: 专业级AI图像生成

+
+
+
+ + + + ); +}; + +export default Dashboard; diff --git a/NBATransfer-frontend/src/pages/Login.css b/NBATransfer-frontend/src/pages/Login.css new file mode 100644 index 0000000..370d1bf --- /dev/null +++ b/NBATransfer-frontend/src/pages/Login.css @@ -0,0 +1,42 @@ +.login-container { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: #f0f2f5; + background-image: url("data:image/svg+xml,%3Csvg width='64' height='64' viewBox='0 0 64 64' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 16c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8 3.582 8 8 8zm0-2c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6zm33.414-6l5.95-5.95L45.95.636 40 6.586 34.05.636 32.636 2.05 38.586 8l-5.95 5.95 1.414 1.414L40 9.414l5.95 5.95 1.414-1.414L41.414 8zM40 48c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8 3.582 8 8 8zm0-2c3.314 0 6-2.686 6-6s-2.686-6-6-6-6 2.686-6 6 2.686 6 6 6zM9.414 40l5.95-5.95-1.414-1.414L8 38.586l-5.95-5.95L.636 34.05 6.586 40l-5.95 5.95 1.414 1.414L8 41.414l5.95 5.95 1.414-1.414L9.414 40z' fill='%239C92AC' fill-opacity='0.05' fill-rule='evenodd'/%3E%3C/svg%3E"); + padding: 20px; +} + +.login-card { + width: 100%; + max-width: 450px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1); + border-radius: 12px; +} + +.login-header { + text-align: center; + margin-bottom: 30px; +} + +.login-header h1 { + font-size: 28px; + margin-bottom: 10px; + color: #333; +} + +.login-header p { + color: #666; + font-size: 14px; +} + +@media (max-width: 576px) { + .login-card { + max-width: 100%; + } + + .login-header h1 { + font-size: 24px; + } +} diff --git a/NBATransfer-frontend/src/pages/Login.tsx b/NBATransfer-frontend/src/pages/Login.tsx new file mode 100644 index 0000000..652ebf1 --- /dev/null +++ b/NBATransfer-frontend/src/pages/Login.tsx @@ -0,0 +1,253 @@ +import React, { useState } from 'react'; +import { Form, Input, Button, Card, message, Tabs, Space, Alert } from 'antd'; +import { LockOutlined, MailOutlined } from '@ant-design/icons'; +import { useNavigate } from 'react-router-dom'; +import { authAPI } from '../api'; +import { useAuthStore } from '../store/auth'; +import './Login.css'; + +const Login: React.FC = () => { + const [loginLoading, setLoginLoading] = useState(false); + const [registerLoading, setRegisterLoading] = useState(false); + const [codeSent, setCodeSent] = useState(false); + const [codeLoading, setCodeLoading] = useState(false); + const [email, setEmail] = useState(''); + const [form] = Form.useForm(); + const navigate = useNavigate(); + const setAuth = useAuthStore((state) => state.setAuth); + + const onLogin = async (values: { email: string; password: string }) => { + setLoginLoading(true); + try { + const response = await authAPI.login(values); + const data = response.data; + + setAuth({ + user: data.user, + access_token: data.access_token, + refresh_token: data.refresh_token, + }); + + message.success('登录成功!'); + navigate(data.user.is_admin ? '/admin' : '/dashboard'); + } catch (error: any) { + message.error(error.response?.data?.error || '登录失败,请检查邮箱和密码'); + } finally { + setLoginLoading(false); + } + }; + + const sendVerificationCode = async () => { + if (!email) { + message.error('请先输入邮箱地址'); + return; + } + + // 简单的邮箱验证 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + message.error('请输入有效的邮箱地址'); + return; + } + + setCodeLoading(true); + try { + await authAPI.sendVerificationCode({ email }); + setCodeSent(true); + message.success('验证码已发送到您的邮箱,请检查收件箱(5分钟内有效)'); + } catch (error: any) { + message.error(error.response?.data?.error || '发送验证码失败'); + } finally { + setCodeLoading(false); + } + }; + + const onRegister = async (values: any) => { + setRegisterLoading(true); + try { + const response = await authAPI.register({ + email: values.email, + password: values.password, + verification_code: values.code, + }); + const data = response.data; + + setAuth({ + user: data.user, + access_token: data.access_token, + refresh_token: data.refresh_token, + }); + + message.success('注册成功!'); + navigate('/dashboard'); + } catch (error: any) { + message.error(error.response?.data?.error || '注册失败'); + } finally { + setRegisterLoading(false); + } + }; + + const loginForm = ( + + + } placeholder="邮箱" /> + + + + } placeholder="密码" /> + + + + + + + ); + + const registerForm = ( + + + + + } + placeholder="邮箱(QQ邮箱推荐)" + onChange={(e) => setEmail(e.target.value)} + disabled={codeSent} + /> + + + + + + + {codeSent && ( + <> + + + + + + } placeholder="密码(至少6位)" /> + + + ({ + validator(_, value) { + if (!value || getFieldValue('password') === value) { + return Promise.resolve(); + } + return Promise.reject(new Error('两次密码不一致')); + }, + }), + ]} + > + } placeholder="确认密码" /> + + + + + + + + + + )} + + ); + + return ( +
+ +
+

🍌 大语言模型API中转站

+

专业的AI大模型API中转服务平台

+
+ +
+
+ ); +}; + +export default Login; diff --git a/NBATransfer-frontend/src/pages/Settings.css b/NBATransfer-frontend/src/pages/Settings.css new file mode 100644 index 0000000..b527fe2 --- /dev/null +++ b/NBATransfer-frontend/src/pages/Settings.css @@ -0,0 +1,8 @@ +.settings-page { + padding: 24px; +} + +.settings-card { + max-width: 600px; + margin-bottom: 24px; +} diff --git a/NBATransfer-frontend/src/pages/Settings.tsx b/NBATransfer-frontend/src/pages/Settings.tsx new file mode 100644 index 0000000..90911b9 --- /dev/null +++ b/NBATransfer-frontend/src/pages/Settings.tsx @@ -0,0 +1,86 @@ +import React, { useState } from 'react'; +import { Card, Form, Input, Button, message, Divider } from 'antd'; +import { LockOutlined } from '@ant-design/icons'; +import { authAPI } from '../api'; +import './Settings.css'; + +const Settings: React.FC = () => { + const [passwordLoading, setPasswordLoading] = useState(false); + const [passwordForm] = Form.useForm(); + + const onChangePassword = async (values: any) => { + setPasswordLoading(true); + try { + await authAPI.changePassword({ + old_password: values.old_password, + new_password: values.new_password, + }); + message.success('密码修改成功'); + passwordForm.resetFields(); + } catch (error: any) { + message.error(error.response?.data?.error || '密码修改失败'); + } finally { + setPasswordLoading(false); + } + }; + + return ( +
+

账户设置

+ + +
+ + } placeholder="当前密码" /> + + + + } placeholder="新密码" /> + + + ({ + validator(_, value) { + if (!value || getFieldValue('new_password') === value) { + return Promise.resolve(); + } + return Promise.reject(new Error('两次密码不一致')); + }, + }), + ]} + > + } placeholder="确认新密码" /> + + + + + + +
+
+ ); +}; + +export default Settings; diff --git a/NBATransfer-frontend/src/pages/Wallet.css b/NBATransfer-frontend/src/pages/Wallet.css new file mode 100644 index 0000000..ce240e1 --- /dev/null +++ b/NBATransfer-frontend/src/pages/Wallet.css @@ -0,0 +1,215 @@ +.wallet-page h1 { + margin-bottom: 24px; + font-size: 28px; + color: #1890ff; +} + +/* 余额卡片 */ +.balance-card { + background: linear-gradient(135deg, #1890ff 0%, #0050b3 100%); + border: none; + margin-bottom: 24px; +} + +.balance-display, +.api-calls-display { + display: flex; + align-items: center; + gap: 20px; + color: white; + padding: 16px 0; +} + +.balance-icon, +.calls-icon { + font-size: 48px; + opacity: 0.9; +} + +.balance-content, +.calls-content { + flex: 1; +} + +.balance-label, +.calls-label { + font-size: 14px; + opacity: 0.9; + margin-bottom: 6px; +} + +.balance-amount { + font-size: 40px; + font-weight: bold; +} + +.calls-count { + font-size: 32px; + font-weight: bold; +} + +/* 充值套餐卡片 */ +.package-card { + text-align: center; + cursor: pointer; + transition: all 0.3s ease; + border: 2px solid transparent; +} + +.package-card:hover { + border-color: #1890ff; + box-shadow: 0 4px 12px rgba(24, 144, 255, 0.15); +} + +.package-content { + padding: 16px 0; +} + +.package-price { + font-size: 24px; + font-weight: bold; + color: #ff7a45; + margin-bottom: 8px; +} + +.package-count { + font-size: 16px; + color: #333; + margin-bottom: 6px; +} + +.package-unit { + font-size: 12px; + color: #999; +} + +/* 充值提示 */ +.recharge-tips { + margin-top: 24px; + padding: 20px; + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); + border-radius: 8px; + border-left: 4px solid #1890ff; +} + +.recharge-tips h4 { + margin-bottom: 12px; + font-size: 16px; + color: #333; + font-weight: 600; +} + +.recharge-tips ul { + margin: 0; + padding-left: 24px; + line-height: 1.8; +} + +.recharge-tips li { + color: #555; + margin: 4px 0; +} + +/* 支付模态框 */ +.payment-modal { + text-align: center; +} + +.payment-info { + margin-bottom: 24px; + padding: 16px; + background: #fafafa; + border-radius: 8px; + text-align: left; +} + +.payment-info p { + margin: 8px 0; + color: #333; +} + +.qrcode-container { + display: flex; + flex-direction: column; + align-items: center; + padding: 24px 0; +} + +.qrcode-container p { + color: #666; +} + +/* 响应式 */ +@media (max-width: 768px) { + .wallet-page h1 { + font-size: 24px; + margin-bottom: 16px; + } + + .balance-icon, + .calls-icon { + font-size: 40px; + } + + .balance-amount { + font-size: 32px; + } + + .calls-count { + font-size: 24px; + } + + .package-card { + padding: 12px; + } + + .package-price { + font-size: 20px; + } + + .package-count { + font-size: 14px; + } + + .recharge-tips { + padding: 16px; + } + + .recharge-tips h4 { + font-size: 14px; + } + + .recharge-tips ul { + font-size: 13px; + } +} + +@media (max-width: 480px) { + .wallet-page h1 { + font-size: 20px; + } + + .balance-display, + .api-calls-display { + gap: 12px; + padding: 12px 0; + } + + .balance-icon, + .calls-icon { + font-size: 32px; + } + + .balance-amount { + font-size: 24px; + } + + .calls-count { + font-size: 20px; + } + + .balance-label, + .calls-label { + font-size: 12px; + } +} diff --git a/NBATransfer-frontend/src/pages/Wallet.tsx b/NBATransfer-frontend/src/pages/Wallet.tsx new file mode 100644 index 0000000..8561ee7 --- /dev/null +++ b/NBATransfer-frontend/src/pages/Wallet.tsx @@ -0,0 +1,257 @@ +import React, { useState } from 'react'; +import { + Card, Button, Form, InputNumber, Radio, message, Modal, QRCode, Statistic, + Row, Col, Divider, Spin +} from 'antd'; +import { WalletOutlined, AlipayOutlined, WechatOutlined } from '@ant-design/icons'; +import { orderAPI, userAPI } from '../api'; +import { useAuthStore } from '../store/auth'; +import './Wallet.css'; + +const Wallet: React.FC = () => { + const [loading, setLoading] = useState(false); + const [modalVisible, setModalVisible] = useState(false); + const [paymentMethod, setPaymentMethod] = useState('alipay'); + const [orderInfo, setOrderInfo] = useState(null); + const user = useAuthStore((state) => state.user); + const updateUser = useAuthStore((state) => state.updateUser); + const [form] = Form.useForm(); + + const onRecharge = async (values: { amount: number }) => { + if (!user) return; + + setLoading(true); + try { + const response = await orderAPI.createOrder({ + amount: values.amount, + payment_method: paymentMethod, + }); + + setOrderInfo(response.data); + setModalVisible(true); + message.success('订单创建成功,请扫码支付'); + } catch (error: any) { + message.error(error.response?.data?.error || '订单创建失败'); + } finally { + setLoading(false); + } + }; + + const handlePaymentComplete = async () => { + if (!orderInfo || !user) return; + + try { + // 模拟支付通知(实际生产环境由支付平台回调) + await orderAPI.notifyPayment(orderInfo.order.order_no); + + // 刷新用户余额 + const balanceResponse = await userAPI.getBalance(); + + updateUser({ + ...user, + balance: balanceResponse.data.balance + }); + + message.success('充值成功!'); + setModalVisible(false); + setOrderInfo(null); + form.resetFields(); + } catch (error: any) { + message.error('支付确认失败'); + } + }; + + // 充值套餐选项 + const rechargePackages = [ + { amount: 10 }, + { amount: 50 }, + { amount: 100 }, + { amount: 500 }, + ]; + + const getPaymentMethodName = (method: string) => { + return method === 'alipay' ? '支付宝' : '微信支付'; + }; + + return ( +
+

💰 我的钱包

+ + {/* 余额显示卡片 */} + + +
+
+ +
+
账户余额
+
¥{user?.balance?.toFixed(2) || '0.00'}
+
+
+ + + + + + + {/* 快速充值套餐 */} + + + {rechargePackages.map((pkg) => ( + + { + form.setFieldsValue({ amount: pkg.amount }); + }} + > +
+
¥{pkg.amount}
+
+
+ + ))} + + + + {/* 自定义充值 */} + +
+ + + + + + setPaymentMethod(e.target.value)} + size="large" + > + + 支付宝 + + + 微信支付 + + + + + + + + + + + +
+

充值说明

+
    +
  • ✓ 充值后立即到账,无需等待
  • +
  • ✓ 余额可用于所有 API 调用
  • +
  • ✓ 最低充值金额: ¥1.00
  • +
  • ✓ 最高余额限制: ¥100,000
  • +
  • ✓ 余额永不过期
  • +
  • ✓ 如有问题请联系在线客服
  • +
+
+
+ + {/* 支付模态框 */} + setModalVisible(false)} + width={450} + footer={[ + , + , + ]} + > + {orderInfo ? ( +
+
+ +
+ + + + + + + + + + + + + +
+

+ 请使用 {getPaymentMethodName(orderInfo.order.payment_method)} 扫描下方二维码支付 +

+
+ +
+

+ 支付完成后,点击"我已完成支付"按钮 +

+
+ + ) : ( + + )} + + + ); +}; + +export default Wallet; diff --git a/NBATransfer-frontend/src/store/auth.ts b/NBATransfer-frontend/src/store/auth.ts new file mode 100644 index 0000000..05c3365 --- /dev/null +++ b/NBATransfer-frontend/src/store/auth.ts @@ -0,0 +1,60 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +export interface User { + id: number; + email: string; + username: string; + balance: number; + is_active: boolean; + is_admin: boolean; + created_at: string; + updated_at: string; +} + +interface AuthState { + user: User | null; + accessToken: string | null; + refreshToken: string | null; + isAuthenticated: boolean; + setAuth: (data: { user: User; access_token: string; refresh_token: string }) => void; + updateUser: (user: User) => void; + logout: () => void; +} + +export const useAuthStore = create()( + persist( + (set) => ({ + user: null, + accessToken: null, + refreshToken: null, + isAuthenticated: false, + setAuth: (data) => { + localStorage.setItem('access_token', data.access_token); + localStorage.setItem('refresh_token', data.refresh_token); + set({ + user: data.user, + accessToken: data.access_token, + refreshToken: data.refresh_token, + isAuthenticated: true, + }); + }, + updateUser: (user) => { + set({ user }); + }, + logout: () => { + localStorage.removeItem('access_token'); + localStorage.removeItem('refresh_token'); + set({ + user: null, + accessToken: null, + refreshToken: null, + isAuthenticated: false, + }); + }, + }), + { + name: 'auth-storage', + } + ) +); diff --git a/NBATransfer-frontend/src/types/index.ts b/NBATransfer-frontend/src/types/index.ts new file mode 100644 index 0000000..36178db --- /dev/null +++ b/NBATransfer-frontend/src/types/index.ts @@ -0,0 +1,54 @@ +export interface User { + id: number; + email: string; + username: string; + balance: number; + is_active: boolean; + is_admin: boolean; + created_at: string; +} + +export interface ApiKey { + id: number; + user_id: number; + api_key: string; + name: string; + is_active: boolean; + last_used_at?: string; + created_at: string; +} + +export interface Order { + id: number; + order_no: string; + amount: number; + status: 'pending' | 'paid' | 'cancelled'; + payment_method: 'alipay' | 'wechat'; + created_at: string; + paid_at?: string; +} + +export interface ApiCall { + id: number; + user_id: number; + model: string; + cost: number; + status: 'success' | 'failed' | 'processing'; + created_at: string; +} + +export interface Stats { + total_calls: number; + success_calls: number; + failed_calls: number; + total_cost: number; + total_recharge: number; +} + +export interface PaginatedResponse { + data: T[]; + total: number; + page: number; + per_page: number; + pages: number; +} diff --git a/NBATransfer-frontend/tsconfig.app.json b/NBATransfer-frontend/tsconfig.app.json new file mode 100644 index 0000000..a9b5a59 --- /dev/null +++ b/NBATransfer-frontend/tsconfig.app.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/NBATransfer-frontend/tsconfig.json b/NBATransfer-frontend/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/NBATransfer-frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/NBATransfer-frontend/tsconfig.node.json b/NBATransfer-frontend/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/NBATransfer-frontend/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/NBATransfer-frontend/vite.config.ts b/NBATransfer-frontend/vite.config.ts new file mode 100644 index 0000000..8b0f57b --- /dev/null +++ b/NBATransfer-frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/NBATransfer-frontend/前端文档.md b/NBATransfer-frontend/前端文档.md new file mode 100644 index 0000000..3238d92 --- /dev/null +++ b/NBATransfer-frontend/前端文档.md @@ -0,0 +1,17 @@ +# Nano Banana API Frontend + +## Project Setup + +1. Install dependencies: + ```bash + npm install + ``` + +2. Start development server: + ```bash + npm run dev + ``` + +## Configuration + +Check `.env` for API URL configuration. diff --git a/NBATransfer-frontend/启动前端.bat b/NBATransfer-frontend/启动前端.bat new file mode 100644 index 0000000..f533a4c --- /dev/null +++ b/NBATransfer-frontend/启动前端.bat @@ -0,0 +1,20 @@ +@echo off +chcp 65001 >nul +title Nano Banana API - 启动前端服务 + +cd /d "%~dp0NBATransfer-frontend" + +echo. +echo ============================================ +echo 🍌 Nano Banana API - 前端服务 +echo ============================================ +echo. +echo 启动前端开发服务器... +echo. +echo 📍 访问地址:http://localhost:5173 +echo. +echo 按 Ctrl+C 停止服务 +echo. + +call npm run dev +pause diff --git a/README.md b/README.md new file mode 100644 index 0000000..ac1f99c --- /dev/null +++ b/README.md @@ -0,0 +1,312 @@ +# Nano Banana API 中转平台 + +一个完整的大模型 API 中转购买平台,支持用户注册、充值、API 调用等功能。 + +## 项目简介 + +本项目是一个前后端分离的 API 中转服务平台,用户可以通过注册账户、充值余额来使用 Nano Banana 文生图 API 服务。 + +### 主要功能 + +- 📧 邮箱注册登录系统(支持邮箱验证) +- 💰 用户余额管理和充值系统 +- 💳 支付接口(支持支付宝/微信支付) +- 🎨 Nano Banana 文生图 API 中转 +- 🔑 API 密钥管理 +- 📊 用户统计和订单管理 +- 👨‍💼 管理员后台 +- 📱 响应式设计(支持手机端和电脑端) + +## 技术栈 + +### 后端 (NBATransfer-backend) +- **框架**: Flask 3.0 +- **数据库**: SQLite +- **ORM**: Flask-SQLAlchemy +- **认证**: Flask-JWT-Extended +- **跨域**: Flask-CORS +- **邮件**: Flask-Mail + +### 前端 (NBATransfer-frontend) +- **框架**: React 19 + TypeScript +- **构建工具**: Vite +- **UI 组件库**: Ant Design +- **状态管理**: Zustand +- **路由**: React Router +- **HTTP 客户端**: Axios +- **图表**: Recharts + +## 项目结构 + +``` +. +├── NBATransfer-backend/ # 后端项目 +│ ├── app.py # Flask 应用入口 +│ ├── config.py # 配置文件 +│ ├── models.py # 数据库模型 +│ ├── routes/ # 路由模块 +│ │ ├── auth.py # 认证相关 +│ │ ├── user.py # 用户相关 +│ │ ├── order.py # 订单相关 +│ │ ├── api_service.py # API 服务 +│ │ ├── admin.py # 管理员 +│ │ ├── apikey.py # API 密钥 +│ │ └── v1_api.py # V1 API 接口 +│ ├── services/ # 业务逻辑层 +│ ├── modelapiservice/ # 模型 API 服务 +│ │ ├── DeepSeek/ # DeepSeek 服务 +│ │ └── NanoBanana/ # NanoBanana 服务 +│ └── requirements.txt # Python 依赖 +│ +├── NBATransfer-frontend/ # 前端项目 +│ ├── src/ +│ │ ├── api/ # API 接口定义 +│ │ ├── components/ # 公共组件 +│ │ ├── pages/ # 页面组件 +│ │ ├── store/ # 状态管理 +│ │ ├── types/ # TypeScript 类型 +│ │ └── utils/ # 工具函数 +│ └── package.json # Node.js 依赖 +│ +└── README.md # 项目说明文档 +``` + +## 快速开始 + +### 环境要求 + +- Python 3.9+ +- Node.js 18+ +- npm 或 yarn + +### 后端启动 + +1. 进入后端目录: +```bash +cd NBATransfer-backend +``` + +2. 安装依赖: +```bash +pip install -r requirements.txt +``` + +3. 配置环境变量(可选): +创建 `.env` 文件并配置以下变量: +```env +SECRET_KEY=your-secret-key +JWT_SECRET_KEY=your-jwt-secret-key +DATABASE_URI=sqlite:///nba_transfer.db +MAIL_SERVER=smtp.qq.com +MAIL_PORT=465 +MAIL_USE_SSL=True +MAIL_USERNAME=your-email@qq.com +MAIL_PASSWORD=your-email-password +``` + +4. 运行后端: +```bash +python app.py +``` + +或者使用批处理文件(Windows): +```bash +启动后端.bat +``` + +后端服务将在 `http://localhost:5000` 启动 + +### 前端启动 + +1. 进入前端目录: +```bash +cd NBATransfer-frontend +``` + +2. 安装依赖: +```bash +npm install +``` + +3. 配置环境变量(可选): +创建 `.env` 文件: +```env +VITE_API_URL=http://localhost:5000/api +``` + +4. 运行前端: +```bash +npm run dev +``` + +或者使用批处理文件(Windows): +```bash +启动前端.bat +``` + +前端服务将在 `http://localhost:5173` 启动(Vite 默认端口) + +## 默认账户 + +### 管理员账户 +- **邮箱**: admin@nba.com +- **密码**: admin123 + +⚠️ **首次登录后请立即修改密码!** + +## API 文档 + +### 认证相关 `/api/auth` +- `POST /register` - 用户注册 +- `POST /login` - 用户登录 +- `POST /refresh` - 刷新令牌 +- `GET /me` - 获取当前用户信息 +- `POST /change-password` - 修改密码 + +### 用户相关 `/api/user` +- `GET /profile` - 获取用户资料 +- `PUT /profile` - 更新用户资料 +- `GET /balance` - 获取账户余额 +- `GET /transactions` - 获取交易记录 +- `GET /api-calls` - 获取 API 调用记录 +- `GET /stats` - 获取统计信息 + +### 订单相关 `/api/order` +- `POST /create` - 创建充值订单 +- `GET /list` - 获取订单列表 +- `GET /` - 获取订单详情 +- `POST /notify/` - 支付通知(测试用) + +### API 服务 `/api/service` +- `POST /text-to-image` - 文生图 API +- `GET /models` - 获取可用模型 +- `GET /pricing` - 获取价格信息 +- `GET /call/` - 获取 API 调用详情 + +### API 密钥 `/api/apikey` +- `GET /list` - 获取 API 密钥列表 +- `POST /create` - 创建 API 密钥 +- `PUT //toggle` - 启用/禁用密钥 +- `DELETE /` - 删除密钥 + +### V1 API `/v1` +- `POST /chat/completions` - 聊天完成接口 +- `POST /images/generations` - 图片生成接口 + +### 管理员 `/api/admin` +- `GET /users` - 获取用户列表 +- `GET /users/` - 获取用户详情 +- `POST /users//toggle-status` - 启用/禁用用户 +- `POST /users//adjust-balance` - 调整用户余额 +- `GET /orders` - 获取所有订单 +- `GET /api-calls` - 获取所有 API 调用 +- `GET /stats/overview` - 获取总览统计 +- `GET /stats/chart` - 获取图表数据 + +## 数据库结构 + +### 用户表 (users) +- 邮箱、密码、用户名 +- 余额、激活状态、管理员标识 +- 邮箱验证状态 +- 创建时间、更新时间 + +### 订单表 (orders) +- 订单号、用户ID、金额 +- 支付方式、订单状态 +- 第三方交易ID、支付时间 + +### 交易记录表 (transactions) +- 用户ID、交易类型(充值/消费/退款) +- 金额、前后余额 +- 关联订单、关联API调用 + +### API调用表 (api_calls) +- 用户ID、API类型 +- 提示词、参数、状态 +- 结果URL、费用、错误信息 + +### API密钥表 (api_keys) +- 用户ID、密钥名称 +- API密钥、激活状态 +- 最后使用时间 + +### 验证码表 (verification_codes) +- 用户ID、邮箱 +- 验证码、用途、使用状态 +- 过期时间 + +## 价格策略 + +- **文生图 API**: 0.15 元/张 + +## 开发说明 + +### 添加新的路由 + +**后端**: +1. 在 `routes/` 目录下创建新的蓝图文件 +2. 在 `app.py` 中注册蓝图 + +**前端**: +1. 在 `src/api/modules/` 下创建 API 模块 +2. 在 `src/pages/` 下创建页面组件 +3. 在 `src/router/` 中配置路由 + +### 数据库迁移 + +```bash +# 进入 Python shell +python +>>> from app import create_app +>>> from models import db +>>> app = create_app() +>>> with app.app_context(): +... db.create_all() +``` + +## 部署建议 + +### 后端部署 + +#### 使用 Gunicorn (生产环境) +```bash +pip install gunicorn +gunicorn -w 4 -b 0.0.0.0:5000 app:app +``` + +#### 使用 Docker +```dockerfile +FROM python:3.9 +WORKDIR /app +COPY requirements.txt . +RUN pip install -r requirements.txt +COPY . . +CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"] +``` + +### 前端部署 + +```bash +npm run build +``` + +构建产物在 `dist/` 目录,可以部署到 Nginx、Vercel、Netlify 等平台。 + +## 注意事项 + +1. ⚠️ 生产环境请修改所有默认密钥 +2. ⚠️ 配置实际的邮件服务器 +3. ⚠️ 接入真实的支付接口 +4. ⚠️ 配置 HTTPS +5. ⚠️ 定期备份数据库 +6. ⚠️ 设置合适的 CORS 策略 + +## 许可证 + +MIT License + +## 贡献 + +欢迎提交 Issue 和 Pull Request! + diff --git a/tests/test1.py b/tests/test1.py new file mode 100644 index 0000000..90b5088 --- /dev/null +++ b/tests/test1.py @@ -0,0 +1,17 @@ +import requests + +url = "http://localhost:5000/v1/chat/completions" +headers = { + "Authorization": "Bearer sk_ie2F3ZF2ZOt8dxwKCQglce3JTKFqYay5", + "Content-Type": "application/json" +} +data = { + "model": "deepseek-chat", + "messages": [ + {"role": "user", "content": "给我完整的输出滕王阁序"} + ], + "stream": False +} + +response = requests.post(url, headers=headers, json=data) +print(response.json()) \ No newline at end of file diff --git a/要求.txt b/要求.txt new file mode 100644 index 0000000..3b860ac --- /dev/null +++ b/要求.txt @@ -0,0 +1,12 @@ +做一个nano bannano api 中转购买网站 +前端使用React,后端使用python的flask +网站需要基本的邮箱注册登录机制来验证用户身份 +用户登录后可以通过购买额度来获取使用API资格 +API中转详细情况看“API中转文档” +网站需要适配手机端和电脑端 +以完整的售卖网站为模板 +数据库使用sqlite储存 +支持微信支持和支付宝支持,支付接口以后会给出 +api调用是0.15元一张图片生成 +目前商品就只有nano bannan 文生图API +其他我没有列举完的要求你按照市面上企业级api售卖网站模板来搭建