diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 0000000..e2a6d8b --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,34 @@ +name: Python application + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13.2' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Install Playwright browsers + run: | + python -m playwright install --with-deps + + - name: Run tests + run: | + pytest \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9b9c9a --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Node/Frontend Dependencies and Build Output +node_modules +dist +dist-ssr +*.local + +# Environment Variables +env +.env* +!.env.example + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Python Caches and Bytecode +__pycache__/ +*.pyc +*.pyo +*.pyd +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ + +# General Caches +.cache/ diff --git a/backend/college_transfer_ai/app.py b/backend/college_transfer_ai/app.py new file mode 100644 index 0000000..e498595 --- /dev/null +++ b/backend/college_transfer_ai/app.py @@ -0,0 +1,277 @@ +import os +from flask import Flask, jsonify, request, Response +from flask_cors import CORS +from backend.college_transfer_ai.college_transfer_API import CollegeTransferAPI +import json +import gridfs +from pymongo import MongoClient +import fitz # Import PyMuPDF +import base64 # Needed for image encoding +from openai import OpenAI # Import OpenAI library +from dotenv import load_dotenv # To load environment variables + +print("--- Flask app.py loading ---") + +# Load environment variables from .env file +load_dotenv() + +# --- OpenAI Client Setup --- +# Ensure you have OPENAI_API_KEY set in your .env file or environment variables +openai_api_key = os.getenv("OPENAI_API_KEY") +if not openai_api_key: + print("Warning: OPENAI_API_KEY environment variable not set.") + # Optionally, raise an error or handle appropriately + # raise ValueError("OPENAI_API_KEY environment variable not set.") +openai_client = OpenAI(api_key=openai_api_key) +# --- End OpenAI Setup --- + + +BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +app = Flask( + __name__, + template_folder=os.path.join(BASE_DIR, 'templates'), + static_folder=os.path.join(BASE_DIR, 'static') +) +CORS(app) + +# --- Set Max Request Size --- +# Example: Limit request size to 16 megabytes +app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 +# --- End Max Request Size --- + +api = CollegeTransferAPI() + +# --- MongoDB Setup --- +MONGO_URI = os.getenv("MONGO_URI") # Use env var or default +client = MongoClient(MONGO_URI) +db = client["CollegeTransferAICluster"] # Consider using a specific DB name from env var if needed +fs = gridfs.GridFS(db) +# --- End MongoDB Setup --- + +@app.route('/') +def home(): + return "College Transfer AI API is running." + +# Endpoint to get all institutions +@app.route('/institutions', methods=['GET']) +def get_institutions(): + try: + institutions = api.get_sending_institutions() + return jsonify(institutions) + except Exception as e: + print(f"Error in /institutions: {e}") + return jsonify({"error": str(e)}), 500 + +# Endpoint to get receiving institutions +@app.route('/receiving-institutions', methods=['GET']) +def get_receiving_institutions(): + sending_institution_id = request.args.get('sendingInstitutionId') + if not sending_institution_id: + return jsonify({"error": "Missing sendingInstitutionId parameter"}), 400 + try: + non_ccs = api.get_receiving_institutions(sending_institution_id) + return jsonify(non_ccs) + except Exception as e: + print(f"Error in /receiving-institutions: {e}") + return jsonify({"error": str(e)}), 500 + +# Endpoint to get academic years +@app.route('/academic-years', methods=['GET']) +def get_academic_years(): + try: + academic_years = api.get_academic_years() + return jsonify(academic_years) + except Exception as e: + print(f"Error in /academic-years: {e}") + return jsonify({"error": str(e)}), 500 + +# Endpoint to get majors +@app.route('/majors', methods=['GET']) +def get_all_majors(): + sending_institution_id = request.args.get('sendingInstitutionId') + receiving_institution_id = request.args.get('receivingInstitutionId') + academic_year_id = request.args.get('academicYearId') + category_code = request.args.get('categoryCode') + if not all([sending_institution_id, receiving_institution_id, academic_year_id, category_code]): + return jsonify({"error": "Missing required parameters (sendingInstitutionId, receivingInstitutionId, academicYearId, categoryCode)"}), 400 + try: + majors = api.get_all_majors(sending_institution_id, receiving_institution_id, academic_year_id, category_code) + return jsonify(majors) + except Exception as e: + print(f"Error in /majors: {e}") + return jsonify({"error": str(e)}), 500 + +# Endpoint to get articulation agreement PDF filename +@app.route('/articulation-agreement', methods=['GET']) +def get_articulation_agreement(): + key = request.args.get("key") + if not key: + return jsonify({"error": "Missing key parameter"}), 400 + try: + keyArray = key.split("/") + if len(keyArray) < 4: + return jsonify({"error": "Invalid key format"}), 400 + sending_institution_id = int(keyArray[1]) + receiving_institution_id = int(keyArray[3]) + academic_year_id = int(keyArray[0]) + pdf_filename = api.get_articulation_agreement(academic_year_id, sending_institution_id, receiving_institution_id, key) + return jsonify({"pdf_filename": pdf_filename}) + except ValueError: + return jsonify({"error": "Invalid numeric value in key"}), 400 + except Exception as e: + print(f"Error in /articulation-agreement: {e}") + return jsonify({"error": str(e)}), 500 + +# Endpoint to get image filenames for a PDF (extracts if needed) +@app.route('/pdf-images/') +def get_pdf_images(filename): + try: + pdf_file = fs.find_one({"filename": filename}) + if not pdf_file: + return jsonify({"error": "PDF not found"}), 404 + + pdf_bytes = pdf_file.read() + doc = fitz.open("pdf", pdf_bytes) + image_filenames = [] + + # Check cache + first_image_name = f"{filename}_page_0.png" + if fs.exists({"filename": first_image_name}): + for page_num in range(len(doc)): + img_filename = f"{filename}_page_{page_num}.png" + # Verify each image exists, not just the first + if fs.exists({"filename": img_filename}): + image_filenames.append(img_filename) + else: + # If one is missing, break and regenerate all (or handle differently) + print(f"Cache incomplete, image {img_filename} missing. Regenerating.") + image_filenames = [] # Reset + break + if image_filenames: # If loop completed without break + print(f"All images for {filename} found in cache.") + doc.close() + return jsonify({"image_filenames": image_filenames}) + + # If not fully cached, extract/save + print(f"Generating images for {filename}...") + image_filenames = [] # Ensure it's empty before regenerating + for page_num in range(len(doc)): + page = doc.load_page(page_num) + pix = page.get_pixmap(dpi=150) + img_bytes = pix.tobytes("png") + img_filename = f"{filename}_page_{page_num}.png" + + # Delete existing before putting new one (optional, ensures overwrite) + existing_file = fs.find_one({"filename": img_filename}) + if existing_file: + fs.delete(existing_file._id) + + fs.put(img_bytes, filename=img_filename, contentType='image/png') + image_filenames.append(img_filename) + print(f"Saved image {img_filename}") + + doc.close() + return jsonify({"image_filenames": image_filenames}) + + except Exception as e: + print(f"Error extracting images for {filename}: {e}") + return jsonify({"error": f"Failed to extract images: {str(e)}"}), 500 + +# Endpoint to serve a single image +@app.route('/image/') +def serve_image(image_filename): + try: + grid_out = fs.find_one({"filename": image_filename}) + if not grid_out: + return "Image not found", 404 + image_data = grid_out.read() + # Use content type from GridFS if available, default to image/png + response_mimetype = getattr(grid_out, 'contentType', 'image/png') + response = Response(image_data, mimetype=response_mimetype) + return response + except Exception as e: + print(f"Error serving image {image_filename}: {e}") + return jsonify({"error": f"Failed to serve image: {str(e)}"}), 500 + +# --- NEW: Chat Endpoint --- +@app.route('/chat', methods=['POST']) +def chat_with_agreement(): + if not openai_client: + return jsonify({"error": "OpenAI client not configured. Check API key."}), 500 + + try: + data = request.get_json() + if not data: + return jsonify({"error": "Invalid JSON payload"}), 400 + + user_message = data.get('message') + image_filenames = data.get('image_filenames') + + if not user_message or not image_filenames: + return jsonify({"error": "Missing 'message' or 'image_filenames' in request"}), 400 + + if not isinstance(image_filenames, list): + return jsonify({"error": "'image_filenames' must be a list"}), 400 + + print(f"Received chat request: '{user_message}' with {len(image_filenames)} images.") + + # Prepare message content for OpenAI API (multimodal) + openai_message_content = [{"type": "text", "text": user_message}] + image_count = 0 + for filename in image_filenames: + try: + grid_out = fs.find_one({"filename": filename}) + if not grid_out: + print(f"Warning: Image '{filename}' not found in GridFS. Skipping.") + continue # Skip this image + + image_data = grid_out.read() + base64_image = base64.b64encode(image_data).decode('utf-8') + openai_message_content.append({ + "type": "image_url", + "image_url": { + # Ensure correct mime type if not always PNG + "url": f"data:{getattr(grid_out, 'contentType', 'image/png')};base64,{base64_image}" + } + }) + image_count += 1 + except Exception as img_err: + print(f"Error reading/encoding image {filename}: {img_err}. Skipping.") + # Optionally return an error if images are critical + # return jsonify({"error": f"Failed to process image {filename}: {img_err}"}), 500 + + if image_count == 0: + return jsonify({"error": "No valid images found or processed for context."}), 400 + + # Call OpenAI API + print(f"Sending request to OpenAI with text and {image_count} images...") + try: + chat_completion = openai_client.chat.completions.create( + model="gpt-4o-mini", # Use the appropriate vision model + messages=[ + { + "role": "user", + "content": openai_message_content, + } + ], + max_tokens=1000 # Adjust as needed + ) + + # Extract the reply + reply = chat_completion.choices[0].message.content + print(f"Received reply from OpenAI: '{reply[:100]}...'") # Log snippet + return jsonify({"reply": reply}) + + except Exception as openai_err: + print(f"OpenAI API error: {openai_err}") + return jsonify({"error": f"OpenAI API error: {str(openai_err)}"}), 500 + + except Exception as e: + print(f"Error in /chat endpoint: {e}") + return jsonify({"error": f"An unexpected error occurred: {str(e)}"}), 500 +# --- End Chat Endpoint --- + +if __name__ == '__main__': + # Use host='0.0.0.0' to be accessible on the network if needed + # Use debug=False in production + app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file diff --git a/college_transfer_ai/college_transfer_API.py b/backend/college_transfer_ai/college_transfer_API.py similarity index 97% rename from college_transfer_ai/college_transfer_API.py rename to backend/college_transfer_ai/college_transfer_API.py index 3edc792..16a4a56 100644 --- a/college_transfer_ai/college_transfer_API.py +++ b/backend/college_transfer_ai/college_transfer_API.py @@ -3,6 +3,7 @@ from pymongo import MongoClient import gridfs import json +import os class CollegeTransferAPI: def __init__(self): @@ -181,7 +182,9 @@ def get_articulation_agreement(self, academic_year_id, sending_institution_id, r f"{self.get_year_from_id(academic_year_id)}.pdf" ) - client = MongoClient("mongodb+srv://ahmonembaye:WCpjfEgNcIomkBcN@collegetransferaicluste.vlsybad.mongodb.net/?retryWrites=true&w=majority&appName=CollegeTransferAICluster") + MONGO_URI = os.getenv("MONGO_URI") + + client = MongoClient(MONGO_URI) db = client["CollegeTransferAICluster"] fs = gridfs.GridFS(db) diff --git a/tests/test_app.py b/backend/tests/test_app.py similarity index 86% rename from tests/test_app.py rename to backend/tests/test_app.py index ed46c81..7037176 100644 --- a/tests/test_app.py +++ b/backend/tests/test_app.py @@ -2,8 +2,12 @@ import os sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +import sys +import os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + import pytest -from college_transfer_ai.app import app +from backend.college_transfer_ai.app import app @pytest.fixture def client(): @@ -23,6 +27,7 @@ def test_get_institutions(client): assert isinstance(data, (dict)) def test_get_nonccs(client): + response = client.get('/receiving-institutions') response = client.get('/receiving-institutions') assert response.status_code == 200 data = response.get_json() diff --git a/college_transfer_ai/__pycache__/app.cpython-313.pyc b/college_transfer_ai/__pycache__/app.cpython-313.pyc deleted file mode 100644 index 7eafa83..0000000 Binary files a/college_transfer_ai/__pycache__/app.cpython-313.pyc and /dev/null differ diff --git a/college_transfer_ai/__pycache__/college_transfer_API.cpython-313.pyc b/college_transfer_ai/__pycache__/college_transfer_API.cpython-313.pyc deleted file mode 100644 index e739836..0000000 Binary files a/college_transfer_ai/__pycache__/college_transfer_API.cpython-313.pyc and /dev/null differ diff --git a/college_transfer_ai/app.py b/college_transfer_ai/app.py deleted file mode 100644 index 3d9c4dd..0000000 --- a/college_transfer_ai/app.py +++ /dev/null @@ -1,99 +0,0 @@ -import os -from flask import Flask, jsonify, request, render_template, send_from_directory, Response -from flask_cors import CORS -from college_transfer_ai.college_transfer_API import CollegeTransferAPI -import json -import gridfs -from pymongo import MongoClient - -BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) -app = Flask( - __name__, - template_folder=os.path.join(BASE_DIR, 'templates'), - static_folder=os.path.join(BASE_DIR, 'static') -) -CORS(app) -api = CollegeTransferAPI() # Create an instance of the CollegeTransferAPI class - -@app.route('/') -def home(): - return render_template('index.html') - -# Endpoint to get all institutions - -@app.route('/institutions', methods=['GET']) -def get_institutions(): - try: - institutions = api.get_sending_institutions() # Fetch institutions from your API logic - return jsonify(institutions) # Return the institutions as JSON - except Exception as e: - return jsonify({"error": str(e)}), 500 - -# Endpoint to get all non community colleges -@app.route('/receiving-institutions', methods=['GET']) -def get_receiving_institutions(): - - sending_institution_id = request.args.get('sendingInstitutionId') - - try: - non_ccs = api.get_receiving_institutions(sending_institution_id) # Fetch institutions from your API logic - return jsonify(non_ccs) # Return the institutions as JSON - except Exception as e: - return jsonify({"error": str(e)}), 500 - -# Endpoint to get academic years -@app.route('/academic-years', methods=['GET']) -def get_academic_years(): - try: - academic_years = api.get_academic_years() - return jsonify(academic_years) # Convert JSON string to Python dict - except Exception as e: - return jsonify({"error": str(e)}), 500 - -# Endpoint to get all majors -@app.route('/majors', methods=['GET']) -def get_all_majors(): - - sending_institution_id = request.args.get('sendingInstitutionId') - receiving_institution_id = request.args.get('receivingInstitutionId') - academic_year_id = request.args.get('academicYearId') - category_code = request.args.get('categoryCode') - - try: - majors = api.get_all_majors(sending_institution_id, receiving_institution_id, academic_year_id, category_code) - return jsonify(majors) # Convert JSON string to Python dict - except Exception as e: - return jsonify({"error": str(e)}), 500 - - -# Endpoint to get articulation agreements -@app.route('/articulation-agreement', methods=['GET']) -def get_articulation_agreement(): - key = request.args.get("key") - - keyArray = request.args.get("key").split("/") - - sending_institution_id = int(keyArray[1]) - receiving_institution_id = int(keyArray[3]) - academic_year_id = int(keyArray[0]) - - try: - pdf_filename = api.get_articulation_agreement(academic_year_id, sending_institution_id, receiving_institution_id, key) - return jsonify({"pdf_filename": pdf_filename}) - except Exception as e: - return jsonify({"error": str(e)}), 500 - - -# Endpoint to get articulation agreement PDF -@app.route('/pdf/') -def serve_pdf(filename): - client = MongoClient("mongodb+srv://ahmonembaye:WCpjfEgNcIomkBcN@collegetransferaicluste.vlsybad.mongodb.net/?retryWrites=true&w=majority&appName=CollegeTransferAICluster") - db = client["CollegeTransferAICluster"] - fs = gridfs.GridFS(db) - file = fs.find_one({"filename": filename}) - if not file: - return "PDF not found", 404 - return Response(file.read(), mimetype='application/pdf') - -if __name__ == '__main__': - app.run(debug=True) \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..ec2b712 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,33 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' + +export default [ + { ignores: ['dist'] }, + { + files: ['**/*.{js,jsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...js.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +] diff --git a/index.html b/index.html new file mode 100644 index 0000000..d225255 --- /dev/null +++ b/index.html @@ -0,0 +1,7 @@ + + + +
+ + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c6e812b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2452 @@ +{ + "name": "collegetransferai", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "collegetransferai", + "version": "0.0.0", + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^6.30.0" + }, + "devDependencies": { + "@eslint/js": "^9.22.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react-swc": "^3.8.0", + "eslint": "^9.22.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^16.0.0", + "vite": "^6.3.2" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", + "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", + "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.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "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.2.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", + "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/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.25.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.0.tgz", + "integrity": "sha512-iWhsUS8Wgxz9AXNfvfOPFSW4VfMXdVhp1hjkZVhXCrpgh/aLcc45rX6MPu+tIVUWDw0HfNwth7O28M1xDxNf9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.13.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.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/core": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.21.tgz", + "integrity": "sha512-/Y3BJLcwd40pExmdar8MH2UGGvCBrqNN7hauOMckrEX2Ivcbv3IMhrbGX4od1dnF880Ed8y/E9aStZCIQi0EGw==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.21" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.11.21", + "@swc/core-darwin-x64": "1.11.21", + "@swc/core-linux-arm-gnueabihf": "1.11.21", + "@swc/core-linux-arm64-gnu": "1.11.21", + "@swc/core-linux-arm64-musl": "1.11.21", + "@swc/core-linux-x64-gnu": "1.11.21", + "@swc/core-linux-x64-musl": "1.11.21", + "@swc/core-win32-arm64-msvc": "1.11.21", + "@swc/core-win32-ia32-msvc": "1.11.21", + "@swc/core-win32-x64-msvc": "1.11.21" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.21.tgz", + "integrity": "sha512-v6gjw9YFWvKulCw3ZA1dY+LGMafYzJksm1mD4UZFZ9b36CyHFowYVYug1ajYRIRqEvvfIhHUNV660zTLoVFR8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.21.tgz", + "integrity": "sha512-CUiTiqKlzskwswrx9Ve5NhNoab30L1/ScOfQwr1duvNlFvarC8fvQSgdtpw2Zh3MfnfNPpyLZnYg7ah4kbT9JQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.21.tgz", + "integrity": "sha512-YyBTAFM/QPqt1PscD8hDmCLnqPGKmUZpqeE25HXY8OLjl2MUs8+O4KjwPZZ+OGxpdTbwuWFyMoxjcLy80JODvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.21.tgz", + "integrity": "sha512-DQD+ooJmwpNsh4acrftdkuwl5LNxxg8U4+C/RJNDd7m5FP9Wo4c0URi5U0a9Vk/6sQNh9aSGcYChDpqCDWEcBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.21.tgz", + "integrity": "sha512-y1L49+snt1a1gLTYPY641slqy55QotPdtRK9Y6jMi4JBQyZwxC8swWYlQWb+MyILwxA614fi62SCNZNznB3XSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.21.tgz", + "integrity": "sha512-NesdBXv4CvVEaFUlqKj+GA4jJMNUzK2NtKOrUNEtTbXaVyNiXjFCSaDajMTedEB0jTAd9ybB0aBvwhgkJUWkWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.21.tgz", + "integrity": "sha512-qFV60pwpKVOdmX67wqQzgtSrUGWX9Cibnp1CXyqZ9Mmt8UyYGvmGu7p6PMbTyX7vdpVUvWVRf8DzrW2//wmVHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.21.tgz", + "integrity": "sha512-DJJe9k6gXR/15ZZVLv1SKhXkFst8lYCeZRNHH99SlBodvu4slhh/MKQ6YCixINRhCwliHrpXPym8/5fOq8b7Ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.21.tgz", + "integrity": "sha512-TqEXuy6wedId7bMwLIr9byds+mKsaXVHctTN88R1UIBPwJA92Pdk0uxDgip0pEFzHB/ugU27g6d8cwUH3h2eIw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.21.tgz", + "integrity": "sha512-BT9BNNbMxdpUM1PPAkYtviaV0A8QcXttjs2MDtOeSqqvSJaPtyM+Fof2/+xSwQDmDEFzbGCcn75M5+xy3lGqpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.21.tgz", + "integrity": "sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "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/react": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", + "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz", + "integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.9.0.tgz", + "integrity": "sha512-jYFUSXhwMCYsh/aQTgSGLIN3Foz5wMbH9ahb0Zva//UzwZYbMiZd7oT3AU9jHT9DLswYDswsRwPU9jVF3yA48Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@swc/core": "^1.11.21" + }, + "peerDependencies": { + "vite": "^4 || ^5 || ^6" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/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/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/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/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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/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/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/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/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.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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/esbuild": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" + } + }, + "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.25.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.0.tgz", + "integrity": "sha512-MsBdObhM4cEwkzCiraDv7A6txFXEqtNXOb877TsSp2FCkBNl8JfVQrmiuDqC1IkejT6JLPzYBXx/xAiYhyzgGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.13.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.25.0", + "@eslint/plugin-kit": "^0.2.8", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "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.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.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": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "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.19", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.19.tgz", + "integrity": "sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "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.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "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.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/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/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-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.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "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/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/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.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz", + "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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/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-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/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/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.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "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.8", + "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/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/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-router": { + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz", + "integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz", + "integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0", + "react-router": "6.30.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "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.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "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/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/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/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "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/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/vite": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.2.tgz", + "integrity": "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.3", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.12" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "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/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..26c88ae --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "collegetransferai", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^6.30.0" + }, + "devDependencies": { + "@eslint/js": "^9.22.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react-swc": "^3.8.0", + "eslint": "^9.22.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^16.0.0", + "vite": "^6.3.2" + } +} diff --git a/requirements.txt b/requirements.txt index db9a493..8b3d6b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ pymongo playwright jinja2 pytest +PyMuPDF \ No newline at end of file diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..38ec24c --- /dev/null +++ b/src/App.css @@ -0,0 +1,130 @@ +/* Reset and Base Styles */ +body { + font-family: Arial, Helvetica, sans-serif; /* Common sans-serif font */ + margin: 0; + padding: 0; + background-color: #f0f0f0; /* Light gray background like assist.org */ + color: #333; /* Standard dark text color */ + line-height: 1.6; +} + +/* Container for centering content */ +#root > div { /* Target the main div rendered by React */ + max-width: 960px; /* Max width similar to assist.org content area */ + margin: 20px auto; /* Center the container */ + padding: 20px; + background-color: #fff; /* White background for content area */ + border: 1px solid #ccc; /* Subtle border */ + box-shadow: 0 2px 4px rgba(0,0,0,0.1); /* Slight shadow */ +} + +h1, h2, h3 { + color: #003366; /* Dark blue for headings */ + margin-top: 0; +} + +/* Form Styling */ +.form-group { + margin-bottom: 1rem; + position: relative; /* Needed for absolute positioning of dropdown */ +} + +label { + display: block; + margin-bottom: 0.5rem; + font-weight: bold; + color: #555; +} + +input[type="text"], +select { /* Style both text inputs and selects similarly */ + padding: 8px 12px; + width: 100%; /* Full width within the container */ + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; /* Include padding and border in width */ + font-size: 1rem; +} + +input[type="text"]:disabled { + background-color: #e9ecef; /* Gray out disabled inputs */ + cursor: not-allowed; +} + +/* Button Styling */ +button { + background-color: #005ea2; /* Assist.org primary blue */ + color: white; + border: none; + padding: 10px 20px; + border-radius: 4px; + cursor: pointer; + font-size: 1rem; + transition: background-color 0.2s ease; + display: inline-block; /* Allow setting width if needed, but default to content size */ + width: auto; /* Override previous 100% width */ + margin-bottom: 0; /* Remove default margin */ +} + +button:hover { + background-color: #003366; /* Darker blue on hover */ +} + +button:disabled { + background-color: #a0a0a0; /* Gray out disabled button */ + cursor: not-allowed; +} + +/* Result Area */ +.result { + margin-top: 25px; + padding: 15px; + border: 1px solid #ddd; + background-color: #f8f8f8; + border-radius: 4px; +} + +.result h3 { + margin-top: 0; + color: #555; +} + +/* Dropdown Styling */ +.dropdown { + position: absolute; + background-color: white; + border: 1px solid #ccc; + border-top: none; /* Attach visually to input */ + max-height: 250px; + overflow-y: auto; + width: 100%; /* Match input width */ + box-sizing: border-box; + z-index: 1000; + box-shadow: 0 4px 8px rgba(0,0,0,0.1); + border-radius: 0 0 4px 4px; /* Rounded bottom corners */ +} + +.dropdown-item { + padding: 8px 12px; + cursor: pointer; + font-size: 0.95rem; + border-bottom: 1px solid #eee; /* Separator lines */ +} +.dropdown-item:last-child { + border-bottom: none; +} + +.dropdown-item:hover { + background-color: #e9f5ff; /* Light blue hover */ + color: #005ea2; +} + +/* Link Styling (e.g., Back to Form in PdfViewer) */ +a { + color: #005ea2; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..5c2f28e --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Routes, Route } from 'react-router-dom'; +import CollegeTransferForm from './components/CollegeTransferForm'; +import AgreementViewerPage from './components/AgreementViewerPage'; // Import the new combined page + +function App() { + return ( + + {/* Route for the main form */} + } /> + + {/* Route for the combined Agreement Viewer */} + } + /> + + + ); +} + +export default App; diff --git a/src/components/AgreementViewerPage.jsx b/src/components/AgreementViewerPage.jsx new file mode 100644 index 0000000..e543c60 --- /dev/null +++ b/src/components/AgreementViewerPage.jsx @@ -0,0 +1,173 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import { useParams, Link } from 'react-router-dom'; +import { fetchData } from '../services/api'; +import PdfViewer from './PdfViewer'; +import ChatInterface from './ChatInterface'; // Import ChatInterface +import '../App.css'; + +function AgreementViewerPage() { + const { sendingId, receivingId, yearId } = useParams(); + + // State for this page + const [majors, setMajors] = useState({}); + const [isLoadingMajors, setIsLoadingMajors] = useState(true); + const [error, setError] = useState(null); // General/Major loading error + const [pdfError, setPdfError] = useState(null); // Specific PDF loading error + const [selectedMajorKey, setSelectedMajorKey] = useState(null); + const [selectedMajorName, setSelectedMajorName] = useState(''); // Store name for chat context + const [selectedPdfFilename, setSelectedPdfFilename] = useState(null); + const [imageFilenames, setImageFilenames] = useState([]); // State for image filenames + const [isLoadingPdf, setIsLoadingPdf] = useState(false); // Loading PDF info + images + const [majorSearchTerm, setMajorSearchTerm] = useState(''); + + // Fetch majors + useEffect(() => { + // ... existing useEffect logic to fetch majors ... + if (!sendingId || !receivingId || !yearId) { + setError("Required institution or year information is missing in URL."); + setIsLoadingMajors(false); + return; + } + setIsLoadingMajors(true); + setError(null); + fetchData(`majors?sendingInstitutionId=${sendingId}&receivingInstitutionId=${receivingId}&academicYearId=${yearId}&categoryCode=major`) + .then(data => { + if (Object.keys(data).length === 0) { + setError("No majors found for the selected combination."); + } + setMajors(data); + }) + .catch(err => { + console.error("Error fetching majors:", err); + setError(`Failed to load majors: ${err.message}`); + }) + .finally(() => { + setIsLoadingMajors(false); + }); + }, [sendingId, receivingId, yearId]); + + // Fetch PDF filename AND image filenames when major is selected + const handleMajorSelect = async (majorKey, majorName) => { + if (!majorKey || isLoadingPdf) return; + + setSelectedMajorKey(majorKey); + setSelectedMajorName(majorName); // Store name + setSelectedPdfFilename(null); // Clear previous PDF filename + setImageFilenames([]); // Clear previous images + setIsLoadingPdf(true); + setError(null); // Clear general errors + setPdfError(null); // Clear specific PDF errors + + try { + // 1. Get PDF Filename + const agreementData = await fetchData(`articulation-agreement?key=${majorKey}`); + if (agreementData.pdf_filename) { + const pdfFilename = agreementData.pdf_filename; + setSelectedPdfFilename(pdfFilename); // Set filename for context + + // 2. Get Image Filenames for the PDF + const imageData = await fetchData(`pdf-images/${pdfFilename}`); + if (imageData.image_filenames) { + setImageFilenames(imageData.image_filenames); + } else { + throw new Error(imageData.error || 'Failed to load image list for PDF'); + } + } else if (agreementData.error) { + throw new Error(`Agreement Error: ${agreementData.error}`); + } else { + throw new Error('Received unexpected data when fetching agreement.'); + } + } catch (err) { + console.error("Error fetching agreement or images:", err); + setPdfError(err.message); // Set specific PDF error + setSelectedPdfFilename(null); // Clear filename on error + setImageFilenames([]); // Clear images on error + } finally { + setIsLoadingPdf(false); // Done loading PDF info + images + } + }; + + // Filter majors based on search term + const filteredMajors = useMemo(() => { + // ... existing useMemo logic ... + const lowerCaseSearchTerm = majorSearchTerm.toLowerCase(); + return Object.entries(majors).filter(([name]) => + name.toLowerCase().includes(lowerCaseSearchTerm) + ); + }, [majors, majorSearchTerm]); + + return ( + // Main container using Flexbox, full height, 3 columns +
+ + {/* Left Column (Majors List) */} +
+ Back to Form +

Select Major

+ setMajorSearchTerm(e.target.value)} + style={{ marginBottom: '0.5em', padding: '8px', border: '1px solid #ccc' }} + /> + {error &&
Error: {error}
} + {isLoadingMajors &&

Loading available majors...

} + {/* Scrollable Major List */} + {!isLoadingMajors && filteredMajors.length > 0 && ( +
+ {filteredMajors.map(([name, key]) => ( +
handleMajorSelect(key, name)} + style={{ + padding: '8px 12px', + cursor: 'pointer', + borderBottom: '1px solid #eee', + backgroundColor: selectedMajorKey === key ? '#e0e0e0' : 'transparent', + fontWeight: selectedMajorKey === key ? 'bold' : 'normal' + }} + className="major-list-item" + > + {name} + {selectedMajorKey === key && isLoadingPdf && (Loading...)} +
+ ))} +
+ )} + {/* ... other messages for no majors found ... */} + {!isLoadingMajors && filteredMajors.length === 0 && Object.keys(majors).length > 0 && ( +

No majors match your search.

+ )} + {!isLoadingMajors && Object.keys(majors).length === 0 && !error && ( +

No majors found.

+ )} +
+ + {/* Middle Column (Chat Interface) - Conditionally Rendered */} +
+ {selectedPdfFilename ? ( + + ) : ( +
+ Select a major to enable chat. +
+ )} +
+ + {/* Right Column (PDF Viewer) */} +
+ {/* Pass image filenames and loading/error state */} + +
+ +
+ ); +} + +export default AgreementViewerPage; \ No newline at end of file diff --git a/src/components/ChatInterface.jsx b/src/components/ChatInterface.jsx new file mode 100644 index 0000000..e5c6001 --- /dev/null +++ b/src/components/ChatInterface.jsx @@ -0,0 +1,99 @@ +import React, { useState, useEffect } from 'react'; +import { fetchData } from '../services/api'; + +function ChatInterface({ imageFilenames, selectedMajorName }) { + const [messages, setMessages] = useState([]); + const [userInput, setUserInput] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [chatError, setChatError] = useState(null); + + // Clear chat when imageFilenames change (new agreement selected) + useEffect(() => { + setMessages([{ type: 'system', text: `Chatting about: ${selectedMajorName || 'Agreement'}` }]); + setUserInput(''); + setChatError(null); + }, [imageFilenames, selectedMajorName]); + + const handleSend = async () => { + if (!userInput.trim() || isLoading || !imageFilenames || imageFilenames.length === 0) return; + + const userMessage = { type: 'user', text: userInput }; + setMessages(prev => [...prev, userMessage]); + const currentInput = userInput; // Capture input before clearing + setUserInput(''); + setIsLoading(true); + setChatError(null); + + // --- Backend Call --- + try { + // *** Pass only 'chat' as the endpoint *** + const response = await fetchData('chat', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + message: currentInput, + image_filenames: imageFilenames + }) + }); + + // Check if the response contains a reply + if (response && response.reply) { // Check if response is not null + setMessages(prev => [...prev, { type: 'bot', text: response.reply }]); + } else { + // If no reply, throw an error using the error message from the backend if available + // Check response object itself before accessing .error + throw new Error(response?.error || "No reply received or unexpected response format from chat API."); + } + } catch (err) { + console.error("Chat API error:", err); + // Display the error message to the user + setChatError(`Failed to get response: ${err.message}`); + // Optionally add a system message indicating the error + setMessages(prev => [...prev, { type: 'system', text: `Error: ${err.message}` }]); + } finally { + setIsLoading(false); + } + // --- End Backend Call --- + }; + + return ( +
+
+ {messages.map((msg, index) => ( +
+ + {msg.text} + +
+ ))} + {isLoading &&

Thinking...

} + {chatError &&

{chatError}

} +
+
+ setUserInput(e.target.value)} + onKeyPress={(e) => e.key === 'Enter' && handleSend()} + placeholder="Ask about the agreement..." + style={{ flexGrow: 1, marginRight: '10px', padding: '8px' }} + disabled={isLoading || !imageFilenames || imageFilenames.length === 0} + /> + +
+
+ ); +} + +export default ChatInterface; diff --git a/src/components/CollegeTransferForm.jsx b/src/components/CollegeTransferForm.jsx new file mode 100644 index 0000000..a918d1f --- /dev/null +++ b/src/components/CollegeTransferForm.jsx @@ -0,0 +1,295 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { fetchData } from '../services/api'; +import '../App.css'; + +const CollegeTransferForm = () => { + const navigate = useNavigate(); + + // --- State for fetched data --- + const [institutions, setInstitutions] = useState({}); + const [receivingInstitutions, setReceivingInstitutions] = useState({}); + const [academicYears, setAcademicYears] = useState({}); + // REMOVED: const [majors, setMajors] = useState({}); + + // --- State for input values and selections --- + const [sendingInputValue, setSendingInputValue] = useState(''); + const [receivingInputValue, setReceivingInputValue] = useState(''); + const [yearInputValue, setYearInputValue] = useState(''); + // REMOVED: const [majorInputValue, setMajorInputValue] = useState(''); + + const [selectedSendingId, setSelectedSendingId] = useState(null); + const [selectedReceivingId, setSelectedReceivingId] = useState(null); + const [selectedYearId, setSelectedYearId] = useState(null); + // REMOVED: const [selectedMajorKey, setSelectedMajorKey] = useState(null); + + // --- State for dropdown visibility and filtered options --- + const [showSendingDropdown, setShowSendingDropdown] = useState(false); + const [showReceivingDropdown, setShowReceivingDropdown] = useState(false); + const [showYearDropdown, setShowYearDropdown] = useState(false); + // REMOVED: const [showMajorDropdown, setShowMajorDropdown] = useState(false); + + const [filteredInstitutions, setFilteredInstitutions] = useState([]); + const [filteredReceiving, setFilteredReceiving] = useState([]); + const [filteredYears, setFilteredYears] = useState([]); + // REMOVED: const [filteredMajors, setFilteredMajors] = useState([]); + + // --- State for loading and results --- + const [isLoading] = useState(false); // Keep for initial loads if needed + const [resultMessage, setResultMessage] = useState('Select institutions and year to view available majors.'); // Updated message + const [error, setError] = useState(null); + + // --- Helper Functions --- + useCallback(() => { + setSendingInputValue(''); + setReceivingInputValue(''); + setYearInputValue(''); + // REMOVED: setMajorInputValue(''); + setSelectedSendingId(null); + setSelectedReceivingId(null); + setSelectedYearId(null); + // REMOVED: setSelectedMajorKey(null); + setReceivingInstitutions({}); + // REMOVED: setMajors({}); + setFilteredInstitutions([]); + setFilteredReceiving([]); + setFilteredYears([]); + // REMOVED: setFilteredMajors([]); + setShowSendingDropdown(false); + setShowReceivingDropdown(false); + setShowYearDropdown(false); + // REMOVED: setShowMajorDropdown(false); + setResultMessage('Select institutions and year to view available majors.'); // Updated message + setError(null); + }, []); + + // --- Effects for Initial Data Loading --- + useEffect(() => { + fetchData('institutions') + .then(data => setInstitutions(data)) + .catch(err => setError(`Failed to load institutions: ${err.message}`)); + fetchData('academic-years') + .then(data => setAcademicYears(data)) + .catch(err => setError(`Failed to load academic years: ${err.message}`)); + }, []); + + // --- Effects for Dependent Data Loading --- + useEffect(() => { + setReceivingInputValue(''); + setSelectedReceivingId(null); + setReceivingInstitutions({}); + setFilteredReceiving([]); + // Clear major related states if they were previously set (good practice after refactor) + // REMOVED: setMajorInputValue(''); + // REMOVED: setSelectedMajorKey(null); + // REMOVED: setMajors({}); + // REMOVED: setFilteredMajors([]); + + if (selectedSendingId) { + fetchData(`receiving-institutions?sendingInstitutionId=${selectedSendingId}`) + .then(data => setReceivingInstitutions(data)) + .catch(err => setError(`Failed to load receiving institutions: ${err.message}`)); + } + }, [selectedSendingId]); + + // REMOVED: useEffect hook that fetched majors + + // --- Effects for Filtering Dropdowns --- + const filter = useCallback( + ((value, data, setFiltered, setShowDropdown) => { + const lowerCaseValue = value.toLowerCase(); + const filtered = Object.entries(data) + .filter(([name]) => name.toLowerCase().includes(lowerCaseValue)) + .map(([name, id]) => ({ name, id })); + setFiltered(filtered); + setShowDropdown(true); + }), + [] + ); + + useEffect(() => { + if (sendingInputValue) { + filter(sendingInputValue, institutions, setFilteredInstitutions, setShowSendingDropdown); + } else { + // Keep dropdown open on focus, hide on blur or empty + if (!document.activeElement || document.activeElement.id !== 'searchInstitution') { + setShowSendingDropdown(false); + } + setFilteredInstitutions(Object.entries(institutions).map(([name, id]) => ({ name, id }))); // Show all on empty/focus + } + }, [sendingInputValue, institutions, filter]); + + useEffect(() => { + if (receivingInputValue && selectedSendingId) { + filter(receivingInputValue, receivingInstitutions, setFilteredReceiving, setShowReceivingDropdown); + } else { + if (!document.activeElement || document.activeElement.id !== 'receivingInstitution') { + setShowReceivingDropdown(false); + } + setFilteredReceiving(Object.entries(receivingInstitutions).map(([name, id]) => ({ name, id }))); // Show all on empty/focus + } + }, [receivingInputValue, receivingInstitutions, selectedSendingId, filter]); + + useEffect(() => { + if (yearInputValue) { + filter(yearInputValue, academicYears, setFilteredYears, setShowYearDropdown); + } else { + if (!document.activeElement || document.activeElement.id !== 'academicYears') { + setShowYearDropdown(false); + } + setFilteredYears(Object.entries(academicYears).map(([name, id]) => ({ name, id })).reverse()); // Show all on empty/focus + } + }, [yearInputValue, academicYears, filter]); + + // REMOVED: useEffect hook for filtering majors + + // --- Event Handlers --- + const handleInputChange = (e, setInputValue) => { + setInputValue(e.target.value); + setError(null); + }; + + const handleDropdownSelect = (item, inputId) => { + setError(null); + switch (inputId) { + case 'sending': + setSendingInputValue(item.name); + setSelectedSendingId(item.id); + setShowSendingDropdown(false); + setFilteredInstitutions([]); // Clear filter on select + break; + case 'receiving': + setReceivingInputValue(item.name); + setSelectedReceivingId(item.id); + setShowReceivingDropdown(false); + setFilteredReceiving([]); // Clear filter on select + break; + case 'year': + setYearInputValue(item.name); + setSelectedYearId(item.id); + setShowYearDropdown(false); + setFilteredYears([]); // Clear filter on select + break; + // REMOVED: case 'major' + default: + break; + } + }; + + // MODIFIED: Renamed and changed logic + const handleViewMajors = () => { // Keep name or rename to handleViewAgreements + if (!selectedSendingId || !selectedReceivingId || !selectedYearId) { + setError("Please select sending institution, receiving institution, and academic year first."); + return; + } + setError(null); + // Navigate to the new combined agreement viewer page + navigate(`/agreement/${selectedSendingId}/${selectedReceivingId}/${selectedYearId}`); + }; + + // --- Render Dropdown --- + const renderDropdown = (items, show, inputId) => { + // Ensure items is an array before mapping + if (!show || !Array.isArray(items) || items.length === 0) return null; + return ( +
+ {items.map((item) => ( +
handleDropdownSelect(item, inputId)} + > + {item.name} +
+ ))} +
+ ); + }; + + // --- Component JSX --- + return ( +
+

College Transfer AI

+ {error &&
Error: {error}
} + + {/* Sending Institution */} +
+ + handleInputChange(e, setSendingInputValue)} + onFocus={() => { + const allOptions = Object.entries(institutions).map(([name, id]) => ({ name, id })); + setFilteredInstitutions(allOptions); + setShowSendingDropdown(true); + }} + onBlur={() => setShowSendingDropdown(false)} // Delay to allow click + autoComplete="off" + /> + {renderDropdown(filteredInstitutions, showSendingDropdown, 'sending')} +
+ + {/* Receiving Institution */} +
+ + handleInputChange(e, setReceivingInputValue)} + onFocus={() => { + const allOptions = Object.entries(receivingInstitutions).map(([name, id]) => ({ name, id })); + setFilteredReceiving(allOptions); + setShowReceivingDropdown(true); + }} + onBlur={() => setShowReceivingDropdown(false)} + disabled={!selectedSendingId} + autoComplete="off" + /> + {renderDropdown(filteredReceiving, showReceivingDropdown, 'receiving')} +
+ + {/* Academic Year */} +
+ + handleInputChange(e, setYearInputValue)} + onFocus={() => { + const allOptions = Object.entries(academicYears) + .map(([name, id]) => ({ name, id })) + .reverse(); + setFilteredYears(allOptions); + setShowYearDropdown(true); + }} + onBlur={() => setShowYearDropdown(false)} + autoComplete="off" + /> + {renderDropdown(filteredYears, showYearDropdown, 'year')} +
+ + {/* MODIFIED: Button */} + + + {/* Result message area (optional, could be removed or kept for general status) */} +
+

Status:

+
{resultMessage}
+
+
+ ); +}; + +export default CollegeTransferForm; \ No newline at end of file diff --git a/src/components/PdfViewer.jsx b/src/components/PdfViewer.jsx new file mode 100644 index 0000000..4c467ec --- /dev/null +++ b/src/components/PdfViewer.jsx @@ -0,0 +1,47 @@ +import React from 'react'; + +// Accept imageFilenames directly as a prop +function PdfViewer({ imageFilenames, isLoading, error, filename }) { // Added isLoading, error, filename for context messages + + // Render content based on props + return ( +
+ {/* Show messages passed from parent */} + {!filename &&

Select a major to view the agreement.

} + {isLoading &&

Loading agreement images...

} + {error &&

Error loading agreement: {error}

} + + {!isLoading && !error && filename && (!imageFilenames || imageFilenames.length === 0) && ( +

No images found or extracted for this agreement.

+ )} + + {/* --- Scrollable Image Container --- */} + {!isLoading && !error && filename && imageFilenames && imageFilenames.length > 0 && ( +
+ {imageFilenames.map((imgFilename) => { + const imageUrl = `/api/image/${imgFilename}`; + return ( +
+ {`Page +
+ ); + })} +
+ )} + {/* --- End Scrollable Image Container --- */} +
+ ); +} + +export default PdfViewer; \ No newline at end of file diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..c06e259 --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,9 @@ +import { createRoot } from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; // Import BrowserRouter +import App from './App.jsx'; + +createRoot(document.getElementById('root')).render( + {/* Wrap App with BrowserRouter */} + + +); diff --git a/src/services/api.js b/src/services/api.js new file mode 100644 index 0000000..fbc7435 --- /dev/null +++ b/src/services/api.js @@ -0,0 +1,65 @@ + +/** + * Fetches data from a backend endpoint via the /api proxy. + * @param {string} endpoint - The API endpoint *without* the leading /api/ (e.g., 'institutions', 'chat', 'pdf-images/filename.pdf'). + * @param {object} options - Optional fetch options (method, headers, body, etc.). Defaults to GET. + * @returns {Promise} - A promise that resolves with the JSON data or null for empty responses. + * @throws {Error} - Throws an error if the fetch fails or response is not ok. + */ +export async function fetchData(endpoint, options = {}) { + // Construct the full URL, always prepending /api/ + // Ensure no double slashes if endpoint accidentally starts with one + const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint; + const url = `/api/${cleanEndpoint}`; // Use relative path for the proxy + + try { + console.log(`Fetching data from: ${url} with options:`, options); // Log URL and options + + // *** Pass the options object as the second argument to fetch *** + const response = await fetch(url, options); + + if (!response.ok) { + // Try to get error details from response body if available + let errorBody = null; + try { + // Use .text() first in case the error isn't JSON + const text = await response.text(); + if (text) { + errorBody = JSON.parse(text); // Try parsing as JSON + } + } catch (e) { + // Ignore if response body is not JSON or empty + console.warn("Could not parse error response body as JSON:", e); + } + // Use error from body if available, otherwise use status text + const errorMessage = errorBody?.error || response.statusText || `HTTP error! status: ${response.status}`; + throw new Error(errorMessage); + } + + // Handle cases where response might be empty (e.g., 204 No Content) + if (response.status === 204) { + return null; // Return null for empty successful responses + } + + // Check content type before assuming JSON + const contentType = response.headers.get("content-type"); + if (contentType && contentType.indexOf("application/json") !== -1) { + const data = await response.json(); + return data; + } else { + // Handle non-JSON responses if necessary, or throw an error + console.warn(`Received non-JSON response from ${url}`); + return await response.text(); // Or handle differently + } + + } catch (error) { + console.error(`Error fetching ${url}:`, error); + // Re-throw the error so the component can handle it + // Ensure it's an actual Error object + if (error instanceof Error) { + throw error; + } else { + throw new Error(String(error)); + } + } +} diff --git a/static/css/styles.css b/static/css/styles.css deleted file mode 100644 index af6d85e..0000000 --- a/static/css/styles.css +++ /dev/null @@ -1,50 +0,0 @@ -body { - font-family: Arial, sans-serif; - margin: 20px; -} -.form-group { - margin-bottom: 15px; -} -label { - display: block; - margin-bottom: 5px; -} -input, select, button { - padding: 10px; - width: 100%; - max-width: 400px; - margin-bottom: 10px; -} -button { - background-color: #007BFF; - color: white; - border: none; - cursor: pointer; -} -button:hover { - background-color: #0056b3; -} -.result { - margin-top: 20px; - padding: 10px; - border: 1px solid #ccc; - background-color: #f9f9f9; -} -.dropdown { - position: absolute; - background-color: white; - border: 1px solid #ccc; - max-height: 200px; - overflow-y: auto; - width: 100%; - z-index: 1000; -} - -.dropdown-item { - padding: 10px; - cursor: pointer; -} - -.dropdown-item:hover { - background-color: #f1f1f1; -} \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js deleted file mode 100644 index 4d6c54b..0000000 --- a/static/js/main.js +++ /dev/null @@ -1,314 +0,0 @@ -const backendUrl = "http://127.0.0.1:5000"; - -let institutionsDict = {}; -let receivingInstitutions = {}; -let academicYears = {}; -let majors = {}; -let agreementGenerated = false; - -async function populateData(endpoint, targetObj) { - try { - const response = await fetch(`${backendUrl}/${endpoint}`); - const data = await response.json(); - - Object.assign(targetObj, data); - } catch (error) { - console.error(`Error fetching ${endpoint}:`, error); - alert(`Failed to fetch ${endpoint}. Please try again.`); - } -} - -function hideDropdown() { - setTimeout(() => { - const instDropdown = document.getElementById("institutionDropdown"); - if (instDropdown) instDropdown.innerHTML = ""; - const recDropdown = document.getElementById("receivingInstitutionDropdown"); - if (recDropdown) recDropdown.innerHTML = ""; - const yearsDropdown = document.getElementById("academicYearsDropdown"); - if (yearsDropdown) yearsDropdown.innerHTML = ""; - }, 150); -} - -function updateMajorsInputState() { - const sendingInput = document.getElementById("searchInstitution"); - const receivingInput = document.getElementById("receivingInstitution"); - const academicYearInput = document.getElementById("academicYears"); - const majorsInput = document.getElementById("majors"); - - if ( - sendingInput && sendingInput.getAttribute("data-sending-institution-id") && - receivingInput && receivingInput.getAttribute("data-receiving-institution-id") && - academicYearInput && academicYearInput.getAttribute("data-academic-year-id") - ) { - majorsInput && (majorsInput.disabled = false); - populateData( - `majors?sendingInstitutionId=${sendingInput.getAttribute("data-sending-institution-id")}&receivingInstitutionId=${receivingInput.getAttribute("data-receiving-institution-id")}&academicYearId=${academicYearInput.getAttribute("data-academic-year-id")}&categoryCode=major`, majors - ); - majorsInput.setAttribute("data-major-key", majors[majorsInput.value]); - } else { - majorsInput && (majorsInput.disabled = true); - } -} - -function filterDropdown(inputId, dropdownId, dataObj, dataAttr) { - const searchInput = document.getElementById(inputId).value.toLowerCase(); - const dropdown = document.getElementById(dropdownId); - if (!dropdown) return; - dropdown.innerHTML = ""; - - Object.entries(dataObj).reverse().forEach(([name, id]) => { - if (name.toLowerCase().includes(searchInput)) { - const option = document.createElement("div"); - option.textContent = name; - option.className = "dropdown-item"; - option.onmousedown = function () { - if (agreementGenerated) { - resetAllFields(); - return; // Optionally, prevent further actions until user re-selects - } - const input = document.getElementById(inputId); - input.value = name; - input.setAttribute(dataAttr, id.toString()); - dropdown.innerHTML = ""; - - // If this is the sending institution, enable receiving institution - if (inputId === "searchInstitution") { - const receivingInput = document.getElementById("receivingInstitution"); - if (receivingInput) receivingInput.disabled = false; - filterInstitutions(); - } - - // Reset majors input if any dependency changes - if ( - inputId === "searchInstitution" || - inputId === "receivingInstitution" || - inputId === "academicYears" - ) { - const majorsInput = document.getElementById("majors"); - if (majorsInput) { - majorsInput.value = ""; - majorsInput.removeAttribute("data-major-key"); - } - } - - if (typeof updateMajorsInputState === "function") { - updateMajorsInputState(); - } - - if (typeof getArticulationAgreement === "function") { - // Only call if majors input is filled and has a data-major-key - const majorsInput = document.getElementById("majors"); - if ( - majorsInput && - majorsInput.value && - majorsInput.getAttribute("data-major-key") - ) { - getArticulationAgreement(inputId); // Pass the field that changed - } - } - }; - dropdown.appendChild(option); - } - }); -} - -function filterInstitutions() { - filterDropdown("searchInstitution", "institutionDropdown", institutionsDict, "data-sending-institution-id"); - const sendingInput = document.getElementById("searchInstitution"); - const receivingInput = document.getElementById("receivingInstitution"); - if (sendingInput && receivingInput) { - if (sendingInput.getAttribute("data-sending-institution-id")) { - populateData( - `receiving-institutions?sendingInstitutionId=${sendingInput.getAttribute("data-sending-institution-id")}`, - receivingInstitutions - ); - receivingInput.disabled = false; - } else { - receivingInput.disabled = true; - } - } - updateMajorsInputState(); -} - -function filterReceivingInstitutions() { - filterDropdown("receivingInstitution", "receivingInstitutionDropdown", receivingInstitutions, "data-receiving-institution-id"); - updateMajorsInputState(); -} - -function filterAcademicYears() { - filterDropdown("academicYears", "academicYearsDropdown", academicYears, "data-academic-year-id"); - updateMajorsInputState(); -} - -function filterMajors() { - filterDropdown("majors", "majorsDropdown", majors, "data-major-id"); -} - -async function getInstitutions() { - try { - const response = await fetch(`${backendUrl}/institutions`); - const data = await response.json(); - displayResult(data); - } catch (error) { - displayResult({ error: error.message }); - } -} - -async function getAcademicYears() { - try { - const response = await fetch(`${backendUrl}/academic-years`); - const data = await response.json(); - displayResult(data); - } catch (error) { - displayResult({ error: error.message }); - } -} - -async function getAllMajors() { - const sendingInstitutionInput = document.getElementById("sendingInstitutionId"); - const receivingInstitutionInput = document.getElementById("receivingInstitutionId"); - const academicYearInput = document.getElementById("academicYearId"); - const categoryCodeInput = document.getElementById("categoryCode"); - - if (!sendingInstitutionInput || !receivingInstitutionInput || !academicYearInput || !categoryCodeInput) { - alert("Please fill in all fields."); - return; - } - - const sendingInstitutionId = sendingInstitutionInput.value; - const receivingInstitutionId = receivingInstitutionInput.value; - const academicYearId = academicYearInput.value; - const categoryCode = categoryCodeInput.value; - - if (!sendingInstitutionId || !receivingInstitutionId || !academicYearId || !categoryCode) { - alert("Please fill in all fields."); - return; - } - - try { - const response = await fetch(`${backendUrl}/majors?sendingInstitutionId=${sendingInstitutionId}&receivingInstitutionId=${receivingInstitutionId}&academicYearId=${academicYearId}&categoryCode=${categoryCode}`); - const data = await response.json(); - displayResult(data); - } catch (error) { - displayResult({ error: error.message }); - } -} - -async function getMajorKey() { - const sendingInstitutionInput = document.getElementById("sendingInstitutionId"); - const receivingInstitutionInput = document.getElementById("receivingInstitutionId"); - const academicYearInput = document.getElementById("academicYearId"); - const categoryCodeInput = document.getElementById("categoryCode"); - const majorInput = document.getElementById("major"); - - if (!sendingInstitutionInput || !receivingInstitutionInput || !academicYearInput || !categoryCodeInput || !majorInput) { - alert("Please fill in all fields."); - return; - } - - const sendingInstitutionId = sendingInstitutionInput.value; - const receivingInstitutionId = receivingInstitutionInput.value; - const academicYearId = academicYearInput.value; - const categoryCode = categoryCodeInput.value; - const major = majorInput.value; - - if (!sendingInstitutionId || !receivingInstitutionId || !academicYearId || !categoryCode || !major) { - alert("Please fill in all fields."); - return; - } - - try { - const response = await fetch(`${backendUrl}/major-key?sendingInstitutionId=${sendingInstitutionId}&receivingInstitutionId=${receivingInstitutionId}&academicYearId=${academicYearId}&categoryCode=${categoryCode}&major=${major}`); - const data = await response.json(); - displayResult(data); - } catch (error) { - displayResult({ error: error.message }); - } -} - -function allDependenciesSelected() { - const sendingInput = document.getElementById("searchInstitution"); - const receivingInput = document.getElementById("receivingInstitution"); - const academicYearInput = document.getElementById("academicYears"); - return ( - sendingInput && sendingInput.getAttribute("data-sending-institution-id") && - receivingInput && receivingInput.getAttribute("data-receiving-institution-id") && - academicYearInput && academicYearInput.getAttribute("data-academic-year-id") - ); -} - -async function getArticulationAgreement(changedField) { - const majorsInput = document.getElementById("majors"); - const key = majorsInput.getAttribute("data-major-key"); - if (!key) { - alert("No Agreement Found"); - return; - } - - showLoadingAgreement(); - - try { - const response = await fetch(`${backendUrl}/articulation-agreement?key=${key}`); - const data = await response.json(); - displayResult(data); - } catch (error) { - displayResult({ error: error.message }); - } - - // If a non-major field was changed and all dependencies are selected, re-populate majors - if (changedField !== "majors" && allDependenciesSelected()) { - const sendingInput = document.getElementById("searchInstitution"); - const receivingInput = document.getElementById("receivingInstitution"); - const academicYearInput = document.getElementById("academicYears"); - await populateData( - `majors?sendingInstitutionId=${sendingInput.getAttribute("data-sending-institution-id")}&receivingInstitutionId=${receivingInput.getAttribute("data-receiving-institution-id")}&academicYearId=${academicYearInput.getAttribute("data-academic-year-id")}&categoryCode=major`, - majors - ); - // Update data-major-key after repopulating majors - if (majorsInput) { - const newMajorKey = majors[majorsInput.value]; - if (newMajorKey) { - majorsInput.setAttribute("data-major-key", newMajorKey); - } else { - majorsInput.removeAttribute("data-major-key"); - } - } - } -} - -function showLoadingAgreement() { - const resultContent = document.getElementById("resultContent"); - resultContent.textContent = "Loading Agreement..."; -} - -window.onload = function () { - populateData('institutions', institutionsDict); - populateData('academic-years', academicYears); -}; - - -function displayResult(data) { - const resultContent = document.getElementById("resultContent"); - if (data.pdf_filename) { - window.open(`/pdf/${data.pdf_filename}`, '_blank'); - resultContent.textContent = "PDF opened in a new tab."; - agreementGenerated = true; // Set flag - resetAllFields(); // Reset fields right after generating agreement - } else { - resultContent.textContent = JSON.stringify(data, null, 4); - } -} - -function resetAllFields() { - document.getElementById("searchInstitution").value = ""; - document.getElementById("searchInstitution").removeAttribute("data-sending-institution-id"); - document.getElementById("receivingInstitution").value = ""; - document.getElementById("receivingInstitution").removeAttribute("data-receiving-institution-id"); - document.getElementById("receivingInstitution").disabled = true; - document.getElementById("academicYears").value = ""; - document.getElementById("academicYears").removeAttribute("data-academic-year-id"); - document.getElementById("majors").value = ""; - document.getElementById("majors").removeAttribute("data-major-key"); - agreementGenerated = false; - updateMajorsInputState(); -} diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index bfc2a04..0000000 --- a/templates/index.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - College Transfer AI - - - -

College Transfer AI

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

Result:

-
No data yet...
-
- - - - \ No newline at end of file diff --git a/tests/__pycache__/test_app.cpython-313-pytest-8.3.5.pyc b/tests/__pycache__/test_app.cpython-313-pytest-8.3.5.pyc deleted file mode 100644 index 274f595..0000000 Binary files a/tests/__pycache__/test_app.cpython-313-pytest-8.3.5.pyc and /dev/null differ diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..da5afa4 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,33 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react-swc' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + port: 5173, // Your frontend port + proxy: { + '/api': { + target: 'http://127.0.0.1:5000', // Use IPv4 address explicitly + changeOrigin: true, // Recommended + rewrite: (path) => { + console.log(`Vite proxy intercepted: ${path}`); + const rewritten = path.replace(/^\/api/, ''); + console.log(`Rewritten to: ${rewritten}`); + return rewritten; + }, + configure: (proxy) => { + proxy.on('error', (err) => { + console.log('Proxy error:', err); + }); + proxy.on('proxyReq', (proxyReq, req) => { + console.log(`Proxy request: ${req.method} ${req.url} → ${proxyReq.method} ${proxyReq.path}`); + }); + proxy.on('proxyRes', (proxyRes, req) => { + console.log(`Proxy response: ${req.method} ${req.url} → ${proxyRes.statusCode}`); + }); + } + } + } + } +})