From dcc04ca6ca3b5fdae6be3f596f7321c5673873c2 Mon Sep 17 00:00:00 2001 From: Viswamedha Nalabotu Date: Thu, 26 Feb 2026 01:32:04 +0000 Subject: [PATCH] Revised all files to reduce bloat + optimized workflow --- .dockerignore | 38 + .editorconfig | 26 + .env.example | 33 + .env.template | 48 + .gitignore | 269 + .gitlab-ci.yml | 57 + .vscode/extensions.json | 7 + README.md | 103 + apps/__init__.py | 0 apps/accounts/__init__.py | 0 apps/accounts/admin.py | 52 + apps/accounts/apps.py | 5 + .../management/commands/reset_passwords.py | 14 + apps/accounts/managers.py | 27 + apps/accounts/migrations/0001_initial.py | 94 + ...nvite_options_alter_invite_organization.py | 23 + apps/accounts/migrations/__init__.py | 0 apps/accounts/mixins.py | 19 + apps/accounts/models.py | 141 + apps/accounts/serializers.py | 57 + apps/accounts/viewsets.py | 269 + apps/knowledge/__init__.py | 0 apps/knowledge/admin.py | 35 + apps/knowledge/apps.py | 5 + apps/knowledge/migrations/0001_initial.py | 63 + apps/knowledge/migrations/__init__.py | 0 apps/knowledge/models.py | 75 + apps/knowledge/serializers.py | 43 + apps/knowledge/tasks.py | 103 + apps/knowledge/viewsets.py | 72 + apps/onboarding/__init__.py | 0 apps/onboarding/admin.py | 53 + apps/onboarding/apps.py | 5 + apps/onboarding/consumers.py | 413 ++ apps/onboarding/mcp.py | 102 + apps/onboarding/migrations/0001_initial.py | 89 + ...2_alter_agentconfig_llm_config_and_more.py | 28 + .../0003_onboardingflow_structure.py | 18 + apps/onboarding/migrations/__init__.py | 0 apps/onboarding/models.py | 85 + apps/onboarding/routing.py | 6 + apps/onboarding/serializers.py | 69 + apps/onboarding/viewsets.py | 159 + compose/dev/celery/Dockerfile | 19 + compose/dev/django/Dockerfile | 23 + compose/dev/django/start | 27 + compose/dev/docker-compose.yml | 118 + compose/dev/inference/Dockerfile | 35 + compose/dev/node/Dockerfile | 15 + compose/prod/celery/Dockerfile | 27 + compose/prod/django/Dockerfile | 47 + compose/prod/docker-compose.inference.yml | 26 + compose/prod/docker-compose.yml | 122 + config/__init__.py | 3 + config/api.py | 18 + config/asgi.py | 21 + config/celery.py | 8 + config/settings.py | 210 + config/urls.py | 14 + config/views.py | 16 + config/wsgi.py | 5 + data/1_users.json | 254 + data/2_organizations.json | 41 + data/3_roles.json | 106 + data/4_agentconfigs.json | 137 + gpu_server.py | 232 + manage.py | 22 + notebooks/external-model-testing.ipynb | 146 + notebooks/fine-tune-local-model.ipynb | 1210 ++++ .../local-model-rag-implementation.ipynb | 353 + notebooks/prepare-training-file.ipynb | 583 ++ notebooks/remote-agent-testing.ipynb | 393 ++ package-lock.json | 62 + package.json | 10 + requirements/django.txt | 16 + requirements/inference.txt | 8 + site/.prettierignore | 5 + site/.prettierrc.json | 9 + site/env.d.ts | 12 + site/eslint.config.ts | 31 + site/index.html | 16 + site/package-lock.json | 5915 +++++++++++++++++ site/package.json | 50 + site/src/App.vue | 331 + site/src/css/styles.css | 95 + site/src/main.ts | 15 + site/src/router/api.ts | 128 + site/src/router/index.ts | 109 + site/src/stores/agentStore.ts | 139 + site/src/stores/userStore.ts | 218 + site/src/types/agent.ts | 6 + site/src/types/onboarding.ts | 44 + site/src/types/organization.ts | 53 + site/src/types/user.ts | 18 + site/src/views/AboutView.vue | 110 + site/src/views/AgentDetailView.vue | 362 + site/src/views/AgentsView.vue | 132 + site/src/views/HomeView.vue | 273 + site/src/views/InviteAccept.vue | 92 + site/src/views/LoginView.vue | 117 + site/src/views/OnboardingView.vue | 511 ++ site/src/views/OrganizationManage.vue | 498 ++ site/src/views/OrganizationView.vue | 523 ++ site/src/views/OrganizationsView.vue | 178 + site/src/views/PricingView.vue | 146 + site/src/views/ProgressDetailView.vue | 225 + site/src/views/ProgressView.vue | 218 + site/src/views/RegisterView.vue | 167 + site/src/vue-shim.d.ts | 5 + site/tsconfig.json | 37 + site/vite.config.ts | 42 + 111 files changed, 17862 insertions(+) create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .env.example create mode 100644 .env.template create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .vscode/extensions.json create mode 100644 README.md create mode 100644 apps/__init__.py create mode 100644 apps/accounts/__init__.py create mode 100644 apps/accounts/admin.py create mode 100644 apps/accounts/apps.py create mode 100644 apps/accounts/management/commands/reset_passwords.py create mode 100644 apps/accounts/managers.py create mode 100644 apps/accounts/migrations/0001_initial.py create mode 100644 apps/accounts/migrations/0002_alter_invite_options_alter_invite_organization.py create mode 100644 apps/accounts/migrations/__init__.py create mode 100644 apps/accounts/mixins.py create mode 100644 apps/accounts/models.py create mode 100644 apps/accounts/serializers.py create mode 100644 apps/accounts/viewsets.py create mode 100644 apps/knowledge/__init__.py create mode 100644 apps/knowledge/admin.py create mode 100644 apps/knowledge/apps.py create mode 100644 apps/knowledge/migrations/0001_initial.py create mode 100644 apps/knowledge/migrations/__init__.py create mode 100644 apps/knowledge/models.py create mode 100644 apps/knowledge/serializers.py create mode 100644 apps/knowledge/tasks.py create mode 100644 apps/knowledge/viewsets.py create mode 100644 apps/onboarding/__init__.py create mode 100644 apps/onboarding/admin.py create mode 100644 apps/onboarding/apps.py create mode 100644 apps/onboarding/consumers.py create mode 100644 apps/onboarding/mcp.py create mode 100644 apps/onboarding/migrations/0001_initial.py create mode 100644 apps/onboarding/migrations/0002_alter_agentconfig_llm_config_and_more.py create mode 100644 apps/onboarding/migrations/0003_onboardingflow_structure.py create mode 100644 apps/onboarding/migrations/__init__.py create mode 100644 apps/onboarding/models.py create mode 100644 apps/onboarding/routing.py create mode 100644 apps/onboarding/serializers.py create mode 100644 apps/onboarding/viewsets.py create mode 100644 compose/dev/celery/Dockerfile create mode 100644 compose/dev/django/Dockerfile create mode 100644 compose/dev/django/start create mode 100644 compose/dev/docker-compose.yml create mode 100644 compose/dev/inference/Dockerfile create mode 100644 compose/dev/node/Dockerfile create mode 100644 compose/prod/celery/Dockerfile create mode 100644 compose/prod/django/Dockerfile create mode 100644 compose/prod/docker-compose.inference.yml create mode 100644 compose/prod/docker-compose.yml create mode 100644 config/__init__.py create mode 100644 config/api.py create mode 100644 config/asgi.py create mode 100644 config/celery.py create mode 100644 config/settings.py create mode 100644 config/urls.py create mode 100644 config/views.py create mode 100644 config/wsgi.py create mode 100644 data/1_users.json create mode 100644 data/2_organizations.json create mode 100644 data/3_roles.json create mode 100644 data/4_agentconfigs.json create mode 100644 gpu_server.py create mode 100644 manage.py create mode 100644 notebooks/external-model-testing.ipynb create mode 100644 notebooks/fine-tune-local-model.ipynb create mode 100644 notebooks/local-model-rag-implementation.ipynb create mode 100644 notebooks/prepare-training-file.ipynb create mode 100644 notebooks/remote-agent-testing.ipynb create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 requirements/django.txt create mode 100644 requirements/inference.txt create mode 100644 site/.prettierignore create mode 100644 site/.prettierrc.json create mode 100644 site/env.d.ts create mode 100644 site/eslint.config.ts create mode 100644 site/index.html create mode 100644 site/package-lock.json create mode 100644 site/package.json create mode 100644 site/src/App.vue create mode 100644 site/src/css/styles.css create mode 100644 site/src/main.ts create mode 100644 site/src/router/api.ts create mode 100644 site/src/router/index.ts create mode 100644 site/src/stores/agentStore.ts create mode 100644 site/src/stores/userStore.ts create mode 100644 site/src/types/agent.ts create mode 100644 site/src/types/onboarding.ts create mode 100644 site/src/types/organization.ts create mode 100644 site/src/types/user.ts create mode 100644 site/src/views/AboutView.vue create mode 100644 site/src/views/AgentDetailView.vue create mode 100644 site/src/views/AgentsView.vue create mode 100644 site/src/views/HomeView.vue create mode 100644 site/src/views/InviteAccept.vue create mode 100644 site/src/views/LoginView.vue create mode 100644 site/src/views/OnboardingView.vue create mode 100644 site/src/views/OrganizationManage.vue create mode 100644 site/src/views/OrganizationView.vue create mode 100644 site/src/views/OrganizationsView.vue create mode 100644 site/src/views/PricingView.vue create mode 100644 site/src/views/ProgressDetailView.vue create mode 100644 site/src/views/ProgressView.vue create mode 100644 site/src/views/RegisterView.vue create mode 100644 site/src/vue-shim.d.ts create mode 100644 site/tsconfig.json create mode 100644 site/vite.config.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1cd2612 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,38 @@ +*.sqlite3 +__pycache__/ +*.pyc +*.pyo +*.pyd +*.db +*.log +*.pot +*.mo +*.swp +*.yml +.DS_Store +.env +*.env +.vscode/ +.idea/ +.git/ +.github/ +.gitignore +.editorconfig +.prettierrc +.prettierignore +.nx/ +venv/ +env/ +ENV/ +.venv/ +node_modules/ +build/ +dist/ +*.egg-info/ +celerybeat-schedule +*.md +*.bat +notebooks/ +documents/ +models/ +eslint.config.mjs \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6449e3e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,26 @@ +root = true + +[*.{yml,yaml}] +charset = utf-8 +end_of_line = lf +insert_final_newline = true + +[compose/**.yml] +indent_style = space +indent_size = 2 + +[compose/**.yaml] +indent_style = space +indent_size = 2 + +[docker-compose*.yml] +indent_style = space +indent_size = 2 +[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}] +charset = utf-8 +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf +max_line_length = 100 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0ff566f --- /dev/null +++ b/.env.example @@ -0,0 +1,33 @@ +# Compose +COMPOSE_PROJECT_NAME=dynavera + +# Directories +DJANGO_FRONT_DIR=site/build +DJANGO_MODEL_DIR=model + +# Django core +DJANGO_SECRET_KEY=pe2=12*opi$3+=j3e6e!&*!7bfd9dsg!km*e4q77am%*_fzou( +DJANGO_DEBUG=True +DJANGO_DOMAIN_NAME=localhost +DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1 + +# Celery +DJANGO_CELERY_BROKER_URL=redis://fyp-redis-dev:6379/0 + +# Static & Media paths +DJANGO_STATIC_URL=/static/ +DJANGO_MEDIA_URL=/media/ +DJANGO_STATIC_ROOT=static +DJANGO_MEDIA_ROOT=media + +# Database +DJANGO_DB_ENGINE=django.db.backends.postgresql_psycopg2 +POSTGRES_DB=FinalYearProject +POSTGRES_USER=jouUfa9C21%A +POSTGRES_PASSWORD=a7E/5sUH@03v +POSTGRES_HOST=fyp-postgres-dev +POSTGRES_PORT=5432 + +# Inference server +INFERENCE_HOST=fyp-inference-dev +INFERENCE_PORT=8001 diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..1d78ce0 --- /dev/null +++ b/.env.template @@ -0,0 +1,48 @@ +# Django .env template file + +# Compose +COMPOSE_PROJECT_NAME=dynavera + +# Directories +DJANGO_FRONT_DIR=front +DJANGO_MODEL_DIR=model + +# Django core +DJANGO_SECRET_KEY=change_this_to_a_secure_key +DJANGO_DEBUG=False +DJANGO_DOMAIN_NAME=localhost +DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1 + +# Celery +DJANGO_CELERY_BROKER_URL=redis://localhost:6379/0 + +# Static & Media paths +DJANGO_STATIC_URL=/static/ +DJANGO_MEDIA_URL=/media/ +DJANGO_STATIC_ROOT=static +DJANGO_MEDIA_ROOT=media + +# Database +DJANGO_DB_ENGINE=django.db.backends.sqlite3 +POSTGRES_DB=postgres_db_name +POSTGRES_USER=postgres_user +POSTGRES_PASSWORD=postgres_password +POSTGRES_HOST=localhost +POSTGRES_PORT=5432 + +# Inference server +INFERENCE_HOST=localhost +INFERENCE_PORT=8001 + +# Production YAML (Ignore if you're setting up locally) +FYP_DJANGO_IMAGE=dynavera-django:prod +FYP_CELERY_IMAGE=dynavera-celery:prod +DJANGO_ENTRYPOINT=websecure +CERTRESOLVER=myresolver +DJANGO_PORT=8000 +GITLAB_USER=yourgitlabuser +GITLAB_PASS=yourgitlabpass +GITLAB_SERVER_URL=https://gitlab.com/ +GITLAB_RUNNER_REGISTRATION_TOKEN=your_registration_token +GITLAB_RUNNER_DOCKER_IMAGE=python:3.10-slim +GITLAB_RUNNER_IMAGE_TAG=latest \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b18d7e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,269 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + +# SageMath parsed files +*.sage.py + +# Environments +.env +*.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml + +# Build +build/ + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo + +.eslintcache + +# Cypress +/cypress/videos/ +/cypress/screenshots/ + +# Vitest +__screenshots__/ + +# Local batch files +*.local.bat + +# Static files +static/ + +# Media files +media/ + +# Models +model/ + +# Gihub files +.github/ \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..1fa142b --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,57 @@ +stages: + - test + - lint + - build + +run_tests: + stage: test + image: python:3.12 + variables: + DJANGO_SECRET_KEY: 'random_secret_key_for_ci' + before_script: + - python -m pip install --upgrade pip + - pip install --no-cache-dir -r requirements/django.txt + script: + - python manage.py test --verbosity=2 + rules: + - if: $CI_COMMIT_BRANCH == "main" + +check_node_syntax: + stage: lint + image: node:20-alpine + before_script: + - npm ci + script: + - npm run type-check + rules: + - if: $CI_COMMIT_BRANCH == "main" + +build_and_push: + stage: build + image: docker:24.0.7 + variables: + DOCKER_HOST: tcp://docker:2375 + DOCKER_TLS_CERTDIR: '' + services: + - name: docker:24.0.7-dind + alias: docker + command: ['--tls=false', '--host=tcp://0.0.0.0:2375'] + before_script: + - apk add --no-cache git + script: + - echo "Waiting for Docker daemon..." + - for i in $(seq 1 30); do docker info && break || sleep 1; done + - echo "Logging in to registry ${REGISTRY_URL}" + - echo "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USERNAME" --password-stdin "$REGISTRY_URL" + - export DJANGO_IMAGE_NAME="${REGISTRY_URL}/${DJANGO_IMAGE_PATH}:${IMAGE_TAG}" + - echo "Building image ${DJANGO_IMAGE_NAME}" + - docker build -t "$DJANGO_IMAGE_NAME" -f ./compose/prod/django/Dockerfile --no-cache . + - echo "Pushing image ${DJANGO_IMAGE_NAME}" + - docker push "$DJANGO_IMAGE_NAME" + - export CELERY_IMAGE_NAME="${REGISTRY_URL}/${CELERY_IMAGE_PATH}:${IMAGE_TAG}" + - echo "Building Celery image ${CELERY_IMAGE_NAME}" + - docker build -t "$CELERY_IMAGE_NAME" -f ./compose/prod/celery/Dockerfile --no-cache . + - echo "Pushing Celery image ${CELERY_IMAGE_NAME}" + - docker push "$CELERY_IMAGE_NAME" + needs: + - run_tests diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..af9df81 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "Vue.volar", + "dbaeumer.vscode-eslint", + "EditorConfig.EditorConfig" + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..6d636d2 --- /dev/null +++ b/README.md @@ -0,0 +1,103 @@ +# Dynavera: Distributed Agentic Onboarding System + +Dynavera is a multi-agent AI platform designed to automate role-specific onboarding. The system utilizes a distributed architecture to separate application logic from high-latency LLM inference, employing the Model Context Protocol (MCP) for internal data retrieval and Retrieval-Augmented Generation (RAG). + +--- + +## Project Goals + +- [x] Distributed Orchestration: Implementation of a dual-node system (VPS/GPU) to manage real-time user interaction and heavy computational inference independently. + +- [x] Context-Aware Training: Development of a RAG pipeline that utilizes semantic chunking and vector similarity search to provide role-specific guidance. + +- [x] Agentic Workflow: Utilizing an orchestrator to manage stateful conversations, tool calls, and user progress tracking via WebSockets. + +- [x] Automated Ingestion: Creating a pipeline for converting raw organizational documents (PDF/TXT) into searchable vector embeddings. + +--- + +## System Architecture + + + +The application is split into two primary layers: + +### Management Layer (VPS) +* **Framework**: Django 5.x with Django Channels for WebSocket management. +* **Database**: PostgreSQL with the pgvector extension for semantic storage. +* **Task Queue**: Celery and Redis for asynchronous document processing and ingestion. +* **Internal Routing**: `apps/onboarding/mcp.py` serves as the Model Context Protocol router, bridging the agent to the PostgreSQL vector store. + +### Intelligence Layer (GPU Node) +* **Inference Server**: `gpu_server.py` (FastAPI) located in the root, exposing endpoints for LLM chat completions and embeddings. +* **Semantic Processor**: Custom logic within the inference server for smart chunking that detects topic shifts in text to optimize retrieval accuracy. + +--- + +## Tech Stack + +* **Backend**: Django, Django REST Framework, Django Channels. +* **Frontend**: Vue 3, Vite, Pinia. +* **Database**: PostgreSQL (pgvector). +* **AI/ML**: FastAPI, OpenAI-compatible API structures, Sentence-Transformers. +* **Infrastructure**: Docker, Redis, Celery. + +--- + +## Application Structure + +* **apps.accounts**: Manages User, Organization, and Role models, including invite-based onboarding logic. +* **apps.knowledge**: Handles the RAG pipeline, including TrainingFile management and RoleRagDocument vector storage. +* **apps.onboarding**: Contains the core logic for the onboarding experience: + * `consumers.py`: The Agent Orchestrator managing WebSocket handshakes and session loops. + * `mcp.py`: The internal router for Model Context Protocol tool execution. + * `models.py`: Stores AgentConfig (prompts/tools) and OnboardingSession state. +* **gpu_server.py**: The entry point for the Intelligence Layer, handling embedding generation and LLM inference. + +--- + +## Instructions for Evaluation + +The system is currently pre-loaded with demonstration data from internal configuration files. + +### Access Credentials + +| Role | Email | Password | +| :--- | :--- | :--- | +| **Admin** | admin@example.com | admin | +| **Manager** | haleisaac@example.com | password | +| **User** | j.thompson@example.com | password | + +### Recommended Technical Walkthrough + +To verify the integration of the Knowledge Pipeline and the Agentic Orchestrator, follow these steps: + +1. **Environment Setup**: Navigate to https://fyp.viswamedha.com. * +2. **Document Ingestion**: Log in as the **Manager** (haleisaac@example.com). Navigate to the **University of Birmingham** organization. Upload a PDF relevant to a specific role. +3. **Vectorization**: Observe the ingestion status. The system will extract text, send it to the GPU node for semantic chunking, and store the resulting 1536-dimension vectors in PostgreSQL. +4. **Agent Interaction**: Access the **Role Onboarding** interface. Initiate a session. +5. **Retrieval Verification**: This will query the agent regarding specific details within the uploaded PDF. The agent in `consumers.py` will trigger a tool call via `mcp.py`, retrieve the relevant document chunks, and provide a contextualized response via onboarding pages. + +*Note: If the website that I hosted is not accessible, please set up the project locally by following the instructions in the Usage section below. + +--- + +## Usage + +1. Clone the repository. +2. Copy the `.env.example` file to `.env` or create a new `.env` file based on `.env.template`, and change the necessary environment variables. * +3. Deploy via Docker Compose: `docker compose -f compose/dev/docker-compose.yml --env-file .env up -d` in the root directory. +4. Access the frontend at the configured port (usually `localhost:8000`). + +* Note: If you use a different secret key, when the fyp-django-dev container starts, you will need to execute the following command to reset all accounts to default passwords of "admin" for admin users and "password" for manager and user accounts: + +```bash +docker exec -it fyp-django-dev python manage.py reset_passwords +``` + +### Warnings + +* The development compose is used here to allow HMR and easier debugging. Please only use this file. +* Ensure that a GPU is available and CUDA drivers are properly installed for the inference server to function. +* I have tested this on an RTX 3060 with 12GB VRAM, so I am not sure if it will work on other GPUs. +* There is no guarantee that it will load on a CPU-only machine as the batch size and model parameters are configured for GPU inference. diff --git a/apps/__init__.py b/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/__init__.py b/apps/accounts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/admin.py b/apps/accounts/admin.py new file mode 100644 index 0000000..7dbfc73 --- /dev/null +++ b/apps/accounts/admin.py @@ -0,0 +1,52 @@ +from django.contrib import admin +from django.contrib.admin import ModelAdmin, TabularInline +from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin +from django.contrib.auth.models import Group + +from apps.accounts.models import User, Role, Organization, Invite + +admin.site.unregister(Group) + +@admin.register(User) +class UserAdmin(DjangoUserAdmin): + fieldsets = ( + (None, {'fields': ('email_address', 'password')}), + ('Personal info', {'fields': ('first_name', 'last_name')}), + ('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'is_manager')}), + ('Dates', {'fields': ('last_login',)}), + ) + + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('email_address', 'first_name', 'last_name', 'password1', 'password2'), + }), + ) + + list_display = ('email_address', 'first_name', 'last_name', 'is_staff', 'is_manager') + search_fields = ('email_address', 'first_name', 'last_name') + ordering = ('email_address',) + +@admin.register(Organization) +class OrganizationAdmin(ModelAdmin): + list_display = ('name', 'owner', 'uuid', 'created_at') + search_fields = ('name', 'owner__email_address') + list_filter = ('created_at',) + raw_id_fields = ('owner',) + readonly_fields = ('uuid', 'created_at', 'updated_at') + +@admin.register(Invite) +class InviteAdmin(ModelAdmin): + list_display = ('token', 'organization', 'created_by', 'is_active', 'uses', 'max_uses', 'expires_at') + search_fields = ('token', 'organization__name', 'created_by__email_address') + list_filter = ('is_active', 'expires_at') + raw_id_fields = ('organization', 'created_by') + readonly_fields = ('token', 'created_at') + +@admin.register(Role) +class RoleAdmin(ModelAdmin): + list_display = ('name', 'organization', 'uuid') + search_fields = ('name', 'organization__name') + list_filter = ('organization',) + raw_id_fields = ('organization',) + readonly_fields = ('uuid', 'created_at', 'updated_at') \ No newline at end of file diff --git a/apps/accounts/apps.py b/apps/accounts/apps.py new file mode 100644 index 0000000..46f1f9f --- /dev/null +++ b/apps/accounts/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class AccountsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.accounts' diff --git a/apps/accounts/management/commands/reset_passwords.py b/apps/accounts/management/commands/reset_passwords.py new file mode 100644 index 0000000..4d7fc5f --- /dev/null +++ b/apps/accounts/management/commands/reset_passwords.py @@ -0,0 +1,14 @@ +from django.core.management.base import BaseCommand +from django.contrib.auth.models import User + +class Command(BaseCommand): + help = 'Resets non-admin account passwords to "password" and admin account passwords to "admin" using the current SECRET_KEY' + + def handle(self, *args, **kwargs): + for user in User.objects.all(): + if user.is_staff: + user.set_password('admin') + else: + user.set_password('password') + user.save() + self.stdout.write("All account passwords synchronized with local SECRET_KEY.") \ No newline at end of file diff --git a/apps/accounts/managers.py b/apps/accounts/managers.py new file mode 100644 index 0000000..43ce87a --- /dev/null +++ b/apps/accounts/managers.py @@ -0,0 +1,27 @@ +from django.contrib.auth.hashers import make_password +from django.contrib.auth.models import BaseUserManager +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from apps.accounts.models import User + +class UserManager(BaseUserManager["User"]): + + def _create_user(self, email_address: str, password: str | None, **extra_fields): + if not email_address: + raise ValueError("The given email must be set") + email_address = self.normalize_email(email_address) + user: User = self.model(email_address=email_address, **extra_fields) + user.password = make_password(password) + user.save(using=self._db) + return user + + def create_user(self, email_address: str, password: str | None = None, **extra_fields): + extra_fields.setdefault("is_staff", False) + return self._create_user(email_address, password, **extra_fields) + + def create_superuser(self, email_address: str, password: str | None = None, **extra_fields): + extra_fields.setdefault("is_staff", True) + if extra_fields.get("is_staff") is not True: + raise ValueError("Superuser must have is_staff=True.") + return self._create_user(email_address, password, **extra_fields) diff --git a/apps/accounts/migrations/0001_initial.py b/apps/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..4f11c86 --- /dev/null +++ b/apps/accounts/migrations/0001_initial.py @@ -0,0 +1,94 @@ +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('id', models.BigAutoField(primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('email_address', models.EmailField(max_length=255, unique=True, verbose_name='Email Address')), + ('first_name', models.CharField(max_length=255, verbose_name='First Name')), + ('last_name', models.CharField(max_length=255, verbose_name='Last Name')), + ('date_of_birth', models.DateField(blank=True, null=True, verbose_name='Date of Birth')), + ('is_active', models.BooleanField(default=True, verbose_name='Account Active')), + ('is_staff', models.BooleanField(default=False, verbose_name='Account Admin')), + ('is_manager', models.BooleanField(default=False, verbose_name='Organization Manager')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'User', + 'verbose_name_plural': 'Users', + }, + ), + migrations.CreateModel( + name='Organization', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('name', models.CharField(max_length=255, unique=True, verbose_name='Name')), + ('description', models.TextField(blank=True, default='', verbose_name='Description')), + ('members', models.ManyToManyField(related_name='organizations', to=settings.AUTH_USER_MODEL)), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owned_organizations', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Organization', + 'verbose_name_plural': 'Organizations', + }, + ), + migrations.CreateModel( + name='Invite', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('token', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='Token')), + ('expires_at', models.DateTimeField(verbose_name='Expires At')), + ('uses', models.IntegerField(default=0, verbose_name='Uses')), + ('max_uses', models.IntegerField(default=1, verbose_name='Max Uses')), + ('is_active', models.BooleanField(default=True, verbose_name='Is Active')), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='created_invites', to=settings.AUTH_USER_MODEL)), + ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invite_tokens', to='accounts.organization')), + ], + options={ + 'verbose_name': 'Invite Token', + 'verbose_name_plural': 'Invite Tokens', + }, + ), + migrations.CreateModel( + name='Role', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('name', models.CharField(max_length=100, unique=True, verbose_name='Name')), + ('description', models.TextField(blank=True, default='', verbose_name='Description')), + ('members', models.ManyToManyField(related_name='roles', to=settings.AUTH_USER_MODEL)), + ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='roles', to='accounts.organization')), + ], + options={ + 'verbose_name': 'Role', + 'verbose_name_plural': 'Roles', + }, + ), + ] diff --git a/apps/accounts/migrations/0002_alter_invite_options_alter_invite_organization.py b/apps/accounts/migrations/0002_alter_invite_options_alter_invite_organization.py new file mode 100644 index 0000000..a1c3baa --- /dev/null +++ b/apps/accounts/migrations/0002_alter_invite_options_alter_invite_organization.py @@ -0,0 +1,23 @@ + + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='invite', + options={'verbose_name': 'Invite', 'verbose_name_plural': 'Invites'}, + ), + migrations.AlterField( + model_name='invite', + name='organization', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invites', to='accounts.organization'), + ), + ] diff --git a/apps/accounts/migrations/__init__.py b/apps/accounts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/mixins.py b/apps/accounts/mixins.py new file mode 100644 index 0000000..16a9151 --- /dev/null +++ b/apps/accounts/mixins.py @@ -0,0 +1,19 @@ +from uuid import uuid4 + +from django.db.models import BigAutoField, DateTimeField, Model, UUIDField +from django.utils.translation import gettext_lazy as _ + +class IdentifierMixin(Model): + id = BigAutoField(verbose_name = _("ID"), primary_key = True) + uuid = UUIDField(verbose_name = _("UUID"), default = uuid4, editable = False) + + class Meta: + abstract = True + +class TimeStampMixin(Model): + + created_at = DateTimeField(verbose_name = _("Created At"), auto_now_add = True) + updated_at = DateTimeField(verbose_name = _("Updated At"), auto_now = True) + + class Meta: + abstract = True \ No newline at end of file diff --git a/apps/accounts/models.py b/apps/accounts/models.py new file mode 100644 index 0000000..d3f73f6 --- /dev/null +++ b/apps/accounts/models.py @@ -0,0 +1,141 @@ +from typing import ClassVar +from uuid import uuid4 +from datetime import timedelta + +from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin +from django.db import transaction +from django.db.models import BooleanField, CASCADE, CharField, DateField, DateTimeField, EmailField, ForeignKey, IntegerField, ManyToManyField, Model, TextField, UUIDField +from django.db.models.signals import post_save +from django.dispatch import receiver +from django.utils.translation import gettext_lazy as _ +from django.utils import timezone + +from apps.accounts.managers import UserManager +from apps.accounts.mixins import IdentifierMixin, TimeStampMixin + +class User(AbstractBaseUser, IdentifierMixin, TimeStampMixin, PermissionsMixin): + + email_address = EmailField(verbose_name = _("Email Address"), max_length = 255, unique = True) + first_name = CharField(verbose_name = _("First Name"), max_length = 255) + last_name = CharField(verbose_name = _("Last Name"), max_length = 255) + date_of_birth = DateField(verbose_name = _("Date of Birth"), null = True, blank = True) + + is_active = BooleanField(verbose_name = _("Account Active"), default = True) + is_staff = BooleanField(verbose_name = _("Account Admin"), default = False) + is_manager = BooleanField(verbose_name = _("Organization Manager"), default = False) + + USERNAME_FIELD = 'email_address' + EMAIL_FIELD = 'email_address' + REQUIRED_FIELDS = ['first_name', 'last_name', 'date_of_birth'] + + objects: ClassVar[UserManager] = UserManager() + + def has_perm(self, perm, obj=None): + return True + + def has_module_perms(self, app_label): + return True + + class Meta: + verbose_name = _('User') + verbose_name_plural = _('Users') + + @property + def full_name(self) -> str: + return f"{self.first_name} {self.last_name}" + + def __str__(self) -> str: + return self.full_name + +class Organization(IdentifierMixin, TimeStampMixin, Model): + + name = CharField(verbose_name = _("Name"), max_length = 255, unique = True) + + description = TextField(verbose_name = _("Description"), blank = True, default = '') + + owner = ForeignKey(User, on_delete = CASCADE, related_name = 'owned_organizations') + members = ManyToManyField(User, related_name = 'organizations') + + class Meta: + verbose_name = _('Organization') + verbose_name_plural = _('Organizations') + + def __str__(self) -> str: + return self.name + +class Invite(IdentifierMixin, TimeStampMixin, Model): + + token = UUIDField(verbose_name = _("Token"), default = uuid4, unique = True, editable = False) + organization = ForeignKey(Organization, on_delete = CASCADE, related_name = "invites") + created_by = ForeignKey(User, on_delete = CASCADE, related_name = "created_invites") + expires_at = DateTimeField(verbose_name=_("Expires At")) + uses = IntegerField(verbose_name=_("Uses"), default = 0) + max_uses = IntegerField(verbose_name=_("Max Uses"), default = 1) + is_active = BooleanField(verbose_name=_("Is Active"), default = True) + + class Meta: + verbose_name = _("Invite") + verbose_name_plural = _("Invites") + + def save(self, *args, **kwargs): + if not self.expires_at: + self.expires_at = timezone.now() + timedelta(days=7) + super().save(*args, **kwargs) + + def is_valid(self): + return self.is_active and self.uses < self.max_uses and timezone.now() < self.expires_at + + def __str__(self) -> str: + return f"Invite for {self.organization.name} by {self.created_by.full_name} (expires {self.expires_at})" + +class Role(IdentifierMixin, TimeStampMixin, Model): + + name = CharField(verbose_name = _("Name"), max_length = 100, unique = True) + description = TextField(verbose_name = _("Description"), blank = True, default = '') + organization = ForeignKey(Organization, on_delete = CASCADE, related_name = "roles") + members = ManyToManyField(User, related_name = "roles") + + class Meta: + verbose_name = _('Role') + verbose_name_plural = _('Roles') + + def __str__(self) -> str: + return f"{self.name} ({self.organization.name})" + +@receiver(post_save, sender=Role) +def create_default_agents_for_role(sender, instance: Role, created: bool, **kwargs): + if created: + from apps.onboarding.models import AgentConfig # L: circular import :( + + default_agents = [ + { + 'type': 'curriculum', + 'name': f"{instance.name} Curriculum Agent", + 'prompt': f"You are a curriculum specialist. Design a learning path for someone in the role of {instance.name}..." + }, + { + 'type': 'knowledge', + 'name': f"{instance.name} Knowledge Agent", + 'prompt': f"You are a knowledge assistant. Search the provided docs and help with questions about {instance.name}..." + }, + { + 'type': 'assessment', + 'name': f"{instance.name} Assessment Agent", + 'prompt': f"You are an evaluator. Create questions based on the knowledge of {instance.name}..." + }, + { + 'type': 'monitor', + 'name': f"{instance.name} Progress Monitor", + 'prompt': f"You are a supervisor tracking the progress of someone in the role of {instance.name}..." + } + ] + + with transaction.atomic(): + for agent_data in default_agents: + AgentConfig.objects.create( + organization=instance.organization, + name=agent_data['name'], + agent_type=agent_data['type'], + system_prompt=agent_data['prompt'], + llm_config={"model_id": "meta-llama-3.1-8b-instruct"} + ) diff --git a/apps/accounts/serializers.py b/apps/accounts/serializers.py new file mode 100644 index 0000000..a7028b5 --- /dev/null +++ b/apps/accounts/serializers.py @@ -0,0 +1,57 @@ +from rest_framework.serializers import ModelSerializer, SerializerMethodField +from apps.accounts.models import Role, User, Organization, Invite + +class UserSerializer(ModelSerializer): + + class Meta: + model = User + fields = ['id', 'uuid', 'email_address', 'first_name', 'last_name', 'date_of_birth', 'is_staff', 'is_manager', 'created_at', 'updated_at'] + read_only_fields = ['id', 'uuid', 'is_staff', 'created_at', 'updated_at'] + +class OrganizationSerializer(ModelSerializer): + owner = UserSerializer(read_only = True) + member_count = SerializerMethodField() + role_count = SerializerMethodField() + + class Meta: + model = Organization + fields = ['id', 'uuid', 'name', 'description', 'owner', 'created_at', 'updated_at', 'member_count', 'role_count'] + read_only_fields = ['uuid', 'owner', 'created_at', 'updated_at'] + + def get_member_count(self, obj): + return obj.members.count() + + def get_role_count(self, obj): + return obj.roles.count() + +class InviteSerializer(ModelSerializer): + organization = OrganizationSerializer(read_only = True) + created_by = UserSerializer(read_only = True) + invite_url = SerializerMethodField() + is_valid = SerializerMethodField() + + class Meta: + model = Invite + fields = ['id', 'token', 'organization', 'created_by', 'expires_at', 'uses', 'max_uses', 'is_active', 'created_at', 'updated_at', 'invite_url', 'is_valid'] + read_only_fields = ['id', 'token', 'organization', 'created_by', 'created_at', 'updated_at'] + + def get_invite_url(self, obj: Invite) -> str: + request = self.context.get('request') + if request: + return request.build_absolute_uri(f'/invite/{obj.token}') + return f'/invite/{obj.token}' + + def get_is_valid(self, obj: Invite) -> bool: + return obj.is_valid() + +class RoleSerializer(ModelSerializer): + organization = OrganizationSerializer(read_only = True) + member_count = SerializerMethodField() + + class Meta: + model = Role + fields = ['id', 'uuid', 'name', 'description', 'organization', 'created_at', 'updated_at', 'member_count'] + read_only_fields = ['id', 'uuid', 'created_at', 'updated_at'] + + def get_member_count(self, obj: Role): + return obj.members.count() \ No newline at end of file diff --git a/apps/accounts/viewsets.py b/apps/accounts/viewsets.py new file mode 100644 index 0000000..0e09747 --- /dev/null +++ b/apps/accounts/viewsets.py @@ -0,0 +1,269 @@ +from django.contrib.auth import authenticate, login, logout +from django.db.models import Q + +from rest_framework.status import HTTP_204_NO_CONTENT, HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND, HTTP_200_OK, HTTP_201_CREATED +from rest_framework.decorators import action +from rest_framework.permissions import AllowAny, IsAuthenticated, IsAuthenticatedOrReadOnly +from rest_framework.response import Response +from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet + +from apps.accounts.models import Invite, Organization, Role, User +from apps.accounts.serializers import InviteSerializer, OrganizationSerializer, RoleSerializer, UserSerializer + +class UserViewSet(ReadOnlyModelViewSet): + + queryset = User.objects.all() + serializer_class = UserSerializer + permission_classes = [IsAuthenticatedOrReadOnly] + lookup_field = 'uuid' + + @action(detail=False, methods=['post'], permission_classes=[AllowAny]) + def login(self, request): + email_address = request.data.get('email_address') + password = request.data.get('password') + if not email_address or not password: + return Response({'error': 'Email and password are required'}, status=HTTP_400_BAD_REQUEST) + email_address = User.objects.normalize_email(email_address) + user = authenticate(request, username=email_address, password=password) + if user is None: + return Response({'error': 'Invalid credentials'}, status=HTTP_401_UNAUTHORIZED) + + login(request, user) + return Response({'user': UserSerializer(user).data, 'message': 'Login successful', 'success': True}, status=HTTP_200_OK) + + @action(detail=False, methods=['post'], permission_classes=[IsAuthenticated]) + def logout(self, request): + logout(request) + return Response({'message': 'Logout successful', 'success': True}, status=HTTP_200_OK) + + @action(detail=False, methods=['get'], permission_classes=[IsAuthenticated]) + def me(self, request): + user_data = UserSerializer(request.user).data + user_data['success'] = True + return Response(user_data) + + @action(detail=False, methods=['get'], permission_classes=[AllowAny]) + def session(self, request): + return Response({'isAuthenticated': request.user.is_authenticated, 'isStaff': request.user.is_staff if request.user.is_authenticated else False}) + + @action(detail=False, methods=['post'], permission_classes=[AllowAny]) + def signup(self, request): + try: + data = request.data + except: + return Response({'detail': 'Invalid data provided.', 'success': False}, status=HTTP_400_BAD_REQUEST) + email_address = data.get('email_address') + if not email_address: + return Response({'detail': 'Email address is required.', 'success': False}, status=HTTP_400_BAD_REQUEST) + email_address = User.objects.normalize_email(email_address) + if User.objects.filter(email_address=email_address).exists(): + return Response({'detail': 'Email address already exists.', 'success': False}, status=HTTP_400_BAD_REQUEST) + if not data.get('first_name') or not data.get('last_name'): + return Response({'detail': 'First and last name(s) must be provided.', 'success': False}, status=HTTP_400_BAD_REQUEST) + if type(manager:=data.get('manager')) is not bool: + if manager in ['true', 'True']: + manager = True + elif manager in ['false', 'False']: + manager = False + else: + return Response({'detail': '"manager" field must be a boolean value.', 'success': False}, status=HTTP_400_BAD_REQUEST) + if data.get('password') != data.get('confirm_password'): + return Response({'detail': 'Passwords do not match.', 'success': False}, status=HTTP_400_BAD_REQUEST) + try: + user = User.objects.create_user( + email_address=email_address, + password=data.get('password'), + first_name=data.get('first_name'), + last_name=data.get('last_name'), + date_of_birth=data.get('date_of_birth'), + is_manager=manager, + ) + return Response({'detail': 'User account created successfully.', 'success': True}, status=HTTP_201_CREATED) + except Exception as e: + return Response({'detail': str(e), 'success': False}, status=HTTP_400_BAD_REQUEST) + + @action(detail=False, methods=['post'], permission_classes=[IsAuthenticated]) + def change_password(self, request): + data = request.data + required_fields = ['old_password', 'password', 'confirm_password'] + for field in required_fields: + if not data.get(field): + return Response({'detail': f'"{field}" not provided', 'success': False}, status=HTTP_400_BAD_REQUEST) + if data.get('password') != data.get('confirm_password'): + return Response({'detail': 'Passwords do not match', 'success': False}, status=HTTP_400_BAD_REQUEST) + user = request.user + if not user.check_password(data.get('old_password')): + return Response({'detail': 'Old password is incorrect', 'success': False}, status=HTTP_401_UNAUTHORIZED) + user.set_password(data.get('password')) + user.save() + return Response({'detail': 'Password changed successfully', 'success': True}, status=HTTP_200_OK) + + + +class OrganizationViewSet(ModelViewSet): + queryset = Organization.objects.all() + serializer_class = OrganizationSerializer + permission_classes = [IsAuthenticated] + lookup_field = 'uuid' + + def get_queryset(self): + return Organization.objects.filter( + Q(owner=self.request.user) | Q(members=self.request.user) + ).distinct() + + def perform_create(self, serializer): + organization = serializer.save(owner=self.request.user) + organization.members.add(self.request.user) + + def update(self, request, *args, **kwargs): + if not request.user.is_manager: + return Response({'error': 'Forbidden'}, status=HTTP_403_FORBIDDEN) + return super().update(request, *args, **kwargs) + + @action(detail=True, methods=['get'], url_path='invite') + def list_invites(self, request, uuid=None): + organization = self.get_object() + invites = organization.invites.all() + serializer = InviteSerializer(invites, many=True, context={'request': request}) + return Response(serializer.data) + + @action(detail=True, methods=['post'], url_path='create-invite') + def create_invite(self, request, uuid=None): + organization = self.get_object() + if not request.user.is_manager: + return Response({'error': 'Forbidden'}, status=HTTP_403_FORBIDDEN) + max_uses = request.query_params.get('max_uses') or request.data.get('max_uses', 1) + invitation = Invite.objects.create( + organization=organization, + created_by=request.user, + max_uses=int(max_uses) if str(max_uses).isdigit() else 1 + ) + return Response(InviteSerializer(invitation, context={'request': request}).data) + + @action(detail=True, methods=['delete'], url_path=r'revoke-invite/(?P[0-9a-f-]{36})') + def revoke_invite(self, request, uuid=None, token=None): + organization = self.get_object() + if not request.user.is_manager: + return Response({'error': 'Only managers can revoke invites'}, status=HTTP_403_FORBIDDEN) + invite = organization.invites.filter(token=token).first() + if not invite: + return Response({'error': 'Invalid invitation token or not found in this organization'}, status=HTTP_404_NOT_FOUND) + invite.is_active = False + invite.save() + return Response({'message': 'Invitation successfully revoked'}, status=HTTP_200_OK) + + @action(detail=False, methods=['post'], url_path='join/(?P[0-9a-f-]{36})') + def join(self, request, token=None): + try: + invitation = Invite.objects.get(token=token) + except Invite.DoesNotExist: + return Response({'error': 'Not Found'}, status=HTTP_404_NOT_FOUND) + + if not invitation.is_valid(): + return Response({'error': 'Invalid or expired token'}, status=HTTP_400_BAD_REQUEST) + + organization = invitation.organization + if organization.members.filter(id=request.user.id).exists(): + return Response({'error': 'Already a member'}, status=HTTP_403_FORBIDDEN) + + organization.members.add(request.user) + + invitation.uses += 1 + if invitation.uses >= invitation.max_uses: + invitation.is_active = False + invitation.save() + + return Response({ + 'message': 'Joined', + 'organization': OrganizationSerializer(organization).data + }) + + @action(detail=True, methods=['post'], url_path='leave') + def leave(self, request, uuid=None): + organization = self.get_object() + if organization.owner == request.user: + return Response({'error': 'Owner cannot leave'}, status=HTTP_403_FORBIDDEN) + + if not organization.members.filter(id=request.user.id).exists(): + return Response({'error': 'Not a member'}, status=HTTP_400_BAD_REQUEST) + + organization.members.remove(request.user) + return Response({'message': 'Left organization'}) + + @action(detail=True, methods=['get'], url_path='members') + def list_members(self, request, uuid=None): + organization = self.get_object() + serializer = UserSerializer(organization.members.all(), many=True) + return Response(serializer.data) + + @action(detail=True, methods=['post'], url_path=r'member/(?P\d+)/remove') + def remove_member(self, request, uuid=None, user_id=None): + if not request.user.is_manager: + return Response({'error': 'Forbidden'}, status=HTTP_403_FORBIDDEN) + + organization = self.get_object() + if str(organization.owner.id) == str(user_id): + return Response({'error': 'Cannot remove owner'}, status=HTTP_403_FORBIDDEN) + + user_to_remove = organization.members.filter(id=user_id).first() + if not user_to_remove: + return Response({'error': 'Not found'}, status=HTTP_404_NOT_FOUND) + + organization.members.remove(user_to_remove) + return Response({'message': 'Removed'}) + + @action(detail=True, methods=['get', 'post'], url_path='role') + def roles(self, request, uuid=None): + organization = self.get_object() + if request.method == 'GET': + return Response(RoleSerializer(organization.roles.all(), many=True).data) + + if not request.user.is_manager: + return Response({'error': 'Forbidden'}, status=HTTP_403_FORBIDDEN) + + name = (request.data.get('name') or '').strip() + description = (request.data.get('description') or '').strip() + + if not name: + return Response({'error': 'Role name is required'}, status=HTTP_400_BAD_REQUEST) + + if organization.roles.filter(name__iexact=name).exists(): + return Response({'error': 'A role with this name already exists in this organization'}, status=HTTP_400_BAD_REQUEST) + + role = Role.objects.create(name=name, description=description, organization=organization) + return Response(RoleSerializer(role).data, status=HTTP_201_CREATED) + + @action(detail=False, methods=['get'], url_path='role/mine') + def my_roles(self, request): + roles = Role.objects.filter(members=request.user).distinct() + serializer = RoleSerializer(roles, many=True) + return Response(serializer.data) + + @action(detail=True, methods=['delete'], url_path='role/(?P[0-9a-f-]{36})') + def delete_role(self, request, uuid=None, role_uuid=None): + if not request.user.is_manager: + return Response({'error': 'Forbidden'}, status=HTTP_403_FORBIDDEN) + + role = Role.objects.filter(uuid=role_uuid, organization__uuid=uuid) + if not role.exists(): + return Response({'error': 'Not found'}, status=HTTP_404_NOT_FOUND) + + role.delete() + return Response(status=HTTP_204_NO_CONTENT) + + @action(detail=True, methods=['post'], url_path='role/(?P[0-9a-f-]{36})/join') + def join_role(self, request, uuid=None, role_uuid=None): + organization = self.get_object() + + role = Role.objects.filter(uuid=role_uuid, organization=organization).first() + if not role: + return Response({'error': 'Role not found'}, status=HTTP_404_NOT_FOUND) + + if not organization.members.filter(id=request.user.id).exists() and organization.owner != request.user: + return Response({'error': 'Not a member of this organization'}, status=HTTP_403_FORBIDDEN) + + if role.members.filter(id=request.user.id).exists(): + return Response({'message': 'Already a member of this role'}, status=HTTP_200_OK) + + role.members.add(request.user) + return Response({'message': 'Joined role successfully'}, status=HTTP_200_OK) \ No newline at end of file diff --git a/apps/knowledge/__init__.py b/apps/knowledge/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/knowledge/admin.py b/apps/knowledge/admin.py new file mode 100644 index 0000000..2fca5d5 --- /dev/null +++ b/apps/knowledge/admin.py @@ -0,0 +1,35 @@ +from django.contrib import admin +from django.utils.translation import gettext_lazy as _ +from apps.knowledge.models import TrainingFile, RoleRagDocument + +@admin.register(TrainingFile) +class TrainingFileAdmin(admin.ModelAdmin): + list_display = ('file_name', 'role', 'status', 'is_processed', 'uploaded_by', 'created_at') + list_filter = ('status', 'is_processed', 'role__organization', 'created_at') + search_fields = ('file_name', 'role__name', 'uploaded_by__email_address') + raw_id_fields = ('role', 'uploaded_by') + readonly_fields = ('uuid', 'file_size', 'file_type', 'created_at', 'updated_at') + ordering = ('-created_at',) + +@admin.register(RoleRagDocument) +class RoleRagDocumentAdmin(admin.ModelAdmin): + list_display = ('role', 'chunk_index', 'training_file', 'is_active', 'created_at') + list_filter = ('is_active', 'role__organization', 'created_at') + search_fields = ('content', 'role__name', 'training_file__file_name') + raw_id_fields = ('role', 'training_file') + + readonly_fields = ('uuid', 'content_hash', 'display_embedding', 'created_at', 'updated_at') + ordering = ('role', 'chunk_index') + + def get_fields(self, request, obj=None): + fields = super().get_fields(request, obj) + if 'embedding' in fields: + fields.remove('embedding') + return fields + + @admin.display(description=_("Embedding Preview (1536d)")) + def display_embedding(self, obj): + if obj.embedding is not None: + preview = list(obj.embedding[:5]) + return f"Vector({len(obj.embedding)}): {preview}... [Truncated]" + return _("No embedding generated") \ No newline at end of file diff --git a/apps/knowledge/apps.py b/apps/knowledge/apps.py new file mode 100644 index 0000000..4add044 --- /dev/null +++ b/apps/knowledge/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class KnowledgeConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.knowledge' diff --git a/apps/knowledge/migrations/0001_initial.py b/apps/knowledge/migrations/0001_initial.py new file mode 100644 index 0000000..e288c21 --- /dev/null +++ b/apps/knowledge/migrations/0001_initial.py @@ -0,0 +1,63 @@ +import django.db.models.deletion +import pgvector.django +import uuid +from django.conf import settings +from django.db import migrations, models +from pgvector.django import VectorExtension + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('accounts', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + VectorExtension(), + migrations.CreateModel( + name='TrainingFile', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('file', models.FileField(upload_to='training_files/%Y/%m/%d/')), + ('file_name', models.CharField(max_length=255)), + ('file_size', models.IntegerField()), + ('file_type', models.CharField(max_length=50)), + ('description', models.TextField(blank=True, default='')), + ('status', models.CharField(choices=[('ingesting', 'Ingesting'), ('chunked', 'Chunked'), ('embedded', 'Embedded'), ('failed', 'Failed')], default='ingesting', max_length=20)), + ('is_processed', models.BooleanField(default=False)), + ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='training_files', to='accounts.role')), + ('uploaded_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='uploaded_training_files', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Training File', + 'verbose_name_plural': 'Training Files', + 'ordering': ['-created_at'], + }, + ), + migrations.CreateModel( + name='RoleRagDocument', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('content', models.TextField()), + ('content_hash', models.CharField(db_index=True, max_length=64)), + ('embedding', pgvector.django.VectorField(blank=True, dimensions=1536, null=True)), + ('metadata', models.JSONField(blank=True, default=dict)), + ('chunk_index', models.IntegerField(default=0)), + ('is_active', models.BooleanField(default=True)), + ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rag_documents', to='accounts.role')), + ('training_file', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='chunks', to='knowledge.trainingfile')), + ], + options={ + 'verbose_name': 'Role RAG Document', + 'verbose_name_plural': 'Role RAG Documents', + }, + ), + ] diff --git a/apps/knowledge/migrations/__init__.py b/apps/knowledge/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/knowledge/models.py b/apps/knowledge/models.py new file mode 100644 index 0000000..eff553a --- /dev/null +++ b/apps/knowledge/models.py @@ -0,0 +1,75 @@ +import os + +from django.db.models import CASCADE, CharField, ForeignKey, IntegerField, TextField, BooleanField, FileField, JSONField, Model +from django.db.models.signals import post_delete, post_save +from django.dispatch import receiver +from django.db import transaction +from django.utils.translation import gettext_lazy as _ +from pgvector.django import VectorField + +from apps.accounts.mixins import IdentifierMixin, TimeStampMixin +from apps.accounts.models import User, Role + +class TrainingFile(IdentifierMixin, TimeStampMixin, Model): + STATUS_CHOICES = [ + ('ingesting', 'Ingesting'), + ('chunked', 'Chunked'), + ('embedded', 'Embedded'), + ('failed', 'Failed'), + ] + + role = ForeignKey(Role, on_delete=CASCADE, related_name="training_files") + uploaded_by = ForeignKey(User, on_delete=CASCADE, related_name="uploaded_training_files") + + file = FileField(upload_to='training_files/%Y/%m/%d/') + file_name = CharField(max_length=255) + file_size = IntegerField() + file_type = CharField(max_length=50) + + description = TextField(blank=True, default='') + status = CharField(max_length=20, choices=STATUS_CHOICES, default='ingesting') + is_processed = BooleanField(default=False) + + class Meta: + verbose_name = _("Training File") + verbose_name_plural = _("Training Files") + ordering = ['-created_at'] + + def __str__(self) -> str: + return f"{self.file_name} ({self.role.name})" + + +class RoleRagDocument(IdentifierMixin, TimeStampMixin, Model): + + role = ForeignKey(Role, on_delete=CASCADE, related_name='rag_documents') + training_file = ForeignKey(TrainingFile, on_delete=CASCADE, related_name='chunks', null=True, blank=True) + + content = TextField() + content_hash = CharField(max_length=64, db_index=True) + + embedding = VectorField(dimensions=1536, null=True, blank=True) + + metadata = JSONField(default=dict, blank=True) + chunk_index = IntegerField(default=0) + is_active = BooleanField(default=True) + + class Meta: + verbose_name = _("Role RAG Document") + verbose_name_plural = _("Role RAG Documents") + + def __str__(self) -> str: + return f"{self.role.name} - Chunk {self.chunk_index}" + +@receiver(post_delete, sender=TrainingFile) +def delete_physical_file(sender, instance, **kwargs): + if instance.file: + if os.path.isfile(instance.file.path): + os.remove(instance.file.path) + +@receiver(post_save, sender=TrainingFile) +def trigger_ingestion(sender, instance, created, **kwargs): + if created: + def _enqueue(): + from apps.knowledge.tasks import ingest_training_file_task # L: circular import :( + ingest_training_file_task.delay(str(instance.uuid)) + transaction.on_commit(_enqueue) \ No newline at end of file diff --git a/apps/knowledge/serializers.py b/apps/knowledge/serializers.py new file mode 100644 index 0000000..bd02601 --- /dev/null +++ b/apps/knowledge/serializers.py @@ -0,0 +1,43 @@ +from rest_framework.serializers import ModelSerializer, SerializerMethodField + +from apps.accounts.serializers import RoleSerializer, UserSerializer +from apps.knowledge.models import TrainingFile, RoleRagDocument + +class TrainingFileSerializer(ModelSerializer): + uploaded_by = UserSerializer(read_only=True) + role = RoleSerializer(read_only=True) + file_url = SerializerMethodField() + + class Meta: + model = TrainingFile + fields = [ + 'id', 'uuid', 'role', 'uploaded_by', 'file', 'file_url', + 'file_name', 'file_size', 'file_type', 'description', + 'status', 'is_processed', 'created_at', 'updated_at' + ] + read_only_fields = [ + 'id', 'uuid', 'uploaded_by', 'file_size', 'file_type', + 'status', 'is_processed', 'created_at', 'updated_at', + 'role' + ] + + def get_file_url(self, obj: TrainingFile) -> str: + request = self.context.get('request') + if obj.file and request: + return request.build_absolute_uri(obj.file.url) + return obj.file.url if obj.file else None + +class RoleRagDocumentSerializer(ModelSerializer): + training_file_name = SerializerMethodField() + + class Meta: + model = RoleRagDocument + fields = [ + 'id', 'uuid', 'role', 'training_file', 'training_file_name', + 'content', 'content_hash', 'metadata', 'chunk_index', + 'is_active', 'created_at' + ] + read_only_fields = ['id', 'uuid', 'content_hash', 'created_at'] + + def get_training_file_name(self, obj: RoleRagDocument) -> str: + return obj.training_file.file_name if obj.training_file else None diff --git a/apps/knowledge/tasks.py b/apps/knowledge/tasks.py new file mode 100644 index 0000000..8f9f113 --- /dev/null +++ b/apps/knowledge/tasks.py @@ -0,0 +1,103 @@ +import httpx +import hashlib +from pypdf import PdfReader +from docx import Document + +from celery import shared_task +from django.db import transaction +from django.conf import settings + +from .models import TrainingFile, RoleRagDocument + + +def _decode_text_bytes(raw_bytes: bytes) -> str: + try: + return raw_bytes.decode('utf-8') + except UnicodeDecodeError: + return raw_bytes.decode('latin-1', errors='ignore') + + +def _extract_text_from_training_file(file_obj: TrainingFile) -> str: + file_name = (file_obj.file_name or '').lower() + + if file_name.endswith('.pdf'): + with file_obj.file.open('rb') as f: + reader = PdfReader(f) + pages = [page.extract_text() or '' for page in reader.pages] + return '\n'.join(pages).strip() + + if file_name.endswith('.docx'): + with file_obj.file.open('rb') as f: + document = Document(f) + paragraphs = [paragraph.text for paragraph in document.paragraphs if paragraph.text] + return '\n'.join(paragraphs).strip() + + with file_obj.file.open('rb') as f: + raw_bytes = f.read() + return _decode_text_bytes(raw_bytes).strip() + +def _get_text_chunks(text: str, size: int = 10000): + """Slices text into rough blocks to prevent HTTP timeouts.""" + for i in range(0, len(text), size): + yield text[i:i + size] + +@shared_task(name="apps.knowledge.tasks.ingest_training_file_task", bind=True, soft_time_limit=900, time_limit=1200) +def ingest_training_file_task(self, file_uuid): + try: + file_obj = TrainingFile.objects.get(uuid=file_uuid) + except TrainingFile.DoesNotExist: + return f"File {file_uuid} not found." + + file_obj.status = 'ingesting' + file_obj.save() + + try: + raw_text = _extract_text_from_training_file(file_obj) + if not raw_text: + raise ValueError('No extractable text found.') + + all_documents = [] + chunk_counter = 0 + + + timeout = httpx.Timeout(60.0) + + with httpx.Client(timeout=timeout) as client: + + for text_segment in _get_text_chunks(raw_text): + response = client.post( + f"{settings.INFERENCE_URL}/v1/semantic-chunk", + json={"text": text_segment, "threshold": 95} + ) + response.raise_for_status() + result = response.json() + + chunks = result['chunks'] + embeddings = result['embeddings'] + + for chunk_text, embedding in zip(chunks, embeddings): + all_documents.append(RoleRagDocument( + role=file_obj.role, + training_file=file_obj, + content=chunk_text, + content_hash=hashlib.sha256(chunk_text.encode('utf-8')).hexdigest(), + embedding=embedding, + chunk_index=chunk_counter, + metadata={"source": file_obj.file_name} + )) + chunk_counter += 1 + + + with transaction.atomic(): + RoleRagDocument.objects.bulk_create(all_documents) + + file_obj.status = 'embedded' + file_obj.is_processed = True + file_obj.save() + return f"Processed {chunk_counter} chunks via batching." + + except Exception as e: + file_obj.status = 'failed' + file_obj.description = str(e) + file_obj.save() + raise e diff --git a/apps/knowledge/viewsets.py b/apps/knowledge/viewsets.py new file mode 100644 index 0000000..09b9bcb --- /dev/null +++ b/apps/knowledge/viewsets.py @@ -0,0 +1,72 @@ +from django.db.models import Q +from rest_framework import status +from rest_framework.parsers import FormParser, MultiPartParser +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet + +from apps.accounts.models import Role +from apps.knowledge.models import RoleRagDocument, TrainingFile +from apps.knowledge.serializers import RoleRagDocumentSerializer, TrainingFileSerializer + + +class TrainingFileViewSet(ModelViewSet): + queryset = TrainingFile.objects.all() + serializer_class = TrainingFileSerializer + permission_classes = [IsAuthenticated] + parser_classes = [MultiPartParser, FormParser] + lookup_field = 'uuid' + + def get_queryset(self): + user = self.request.user + return TrainingFile.objects.filter( + Q(role__organization__owner=user) | + Q(role__organization__members=user) + ).distinct() + + def perform_create(self, serializer): + role_uuid = self.request.data.get('role') + + try: + role = Role.objects.get(uuid=role_uuid) + except Role.DoesNotExist: + return Response({'error': 'Role not found'}, status=status.HTTP_404_NOT_FOUND) + + is_owner = role.organization.owner == self.request.user + is_member = role.organization.members.filter(id=self.request.user.id).exists() + + if not (is_owner or is_member): + return Response({'error': 'Permission denied'}, status=status.HTTP_403_FORBIDDEN) + + serializer.save( + uploaded_by=self.request.user, + role=role, + file_name=self.request.FILES['file'].name, + file_size=self.request.FILES['file'].size, + file_type=self.request.FILES['file'].content_type + ) + + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + + is_uploader = instance.uploaded_by == request.user + is_org_owner = instance.role.organization.owner == request.user + + if not (is_uploader or is_org_owner or request.user.is_manager): + return Response({'error': 'Permission denied'}, status=status.HTTP_403_FORBIDDEN) + + return super().destroy(request, *args, **kwargs) + + +class RoleRagDocumentViewSet(ReadOnlyModelViewSet): + queryset = RoleRagDocument.objects.all() + serializer_class = RoleRagDocumentSerializer + permission_classes = [IsAuthenticated] + lookup_field = 'uuid' + + def get_queryset(self): + user = self.request.user + return RoleRagDocument.objects.filter( + Q(role__organization__owner=user) | + Q(role__organization__members=user) + ).distinct() diff --git a/apps/onboarding/__init__.py b/apps/onboarding/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/onboarding/admin.py b/apps/onboarding/admin.py new file mode 100644 index 0000000..b7592bf --- /dev/null +++ b/apps/onboarding/admin.py @@ -0,0 +1,53 @@ +from django.contrib import admin +from django.utils.translation import gettext_lazy as _ + +from apps.onboarding.models import AgentConfig, OnboardingSession, AgentInteractionLog, OnboardingFlow + +class AgentInteractionLogInline(admin.TabularInline): + model = AgentInteractionLog + extra = 0 + readonly_fields = ('sender_type', 'content', 'tool_call_metadata', 'created_at') + can_delete = False + + def has_add_permission(self, request, obj=None): + return False + +@admin.register(AgentConfig) +class AgentConfigAdmin(admin.ModelAdmin): + list_display = ('name', 'agent_type', 'organization', 'created_at') + list_filter = ('agent_type', 'organization') + search_fields = ('name', 'system_prompt') + readonly_fields = ('uuid', 'created_at', 'updated_at') + fieldsets = ( + (None, {'fields': ('name', 'agent_type', 'organization', 'uuid', 'system_prompt', 'llm_config', 'tool_permissions')}), + (_('Agent Logic'), {'fields': ()}), + (_('Metadata'), {'fields': ('created_at', 'updated_at')}), + ) + +@admin.register(OnboardingSession) +class OnboardingSessionAdmin(admin.ModelAdmin): + list_display = ('user', 'role', 'status', 'created_at', 'completed_at') + list_filter = ('status', 'role', 'created_at') + search_fields = ('user__email_address', 'role__name') + readonly_fields = ('uuid', 'created_at', 'updated_at') + inlines = [AgentInteractionLogInline] + + fieldsets = ( + (None, {'fields': ('user', 'role', 'status', 'uuid')}), + (_('Live State'), {'fields': ('state', 'active_configs')}), + (_('Timestamps'), {'fields': ('completed_at', 'created_at', 'updated_at')}), + ) + +@admin.register(AgentInteractionLog) +class AgentInteractionLogAdmin(admin.ModelAdmin): + list_display = ('session', 'sender_type', 'agent_config', 'created_at') + list_filter = ('sender_type', 'created_at') + search_fields = ('content', 'session__user__email_address') + readonly_fields = ('uuid', 'created_at') + +@admin.register(OnboardingFlow) +class OnboardingFlowAdmin(admin.ModelAdmin): + list_display = ('title', 'role', 'is_active', 'created_at') + list_filter = ('is_active', 'role') + search_fields = ('title', 'role__name') + readonly_fields = ('uuid', 'created_at', 'updated_at') diff --git a/apps/onboarding/apps.py b/apps/onboarding/apps.py new file mode 100644 index 0000000..837474e --- /dev/null +++ b/apps/onboarding/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class OnboardingConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.onboarding' diff --git a/apps/onboarding/consumers.py b/apps/onboarding/consumers.py new file mode 100644 index 0000000..e3cab24 --- /dev/null +++ b/apps/onboarding/consumers.py @@ -0,0 +1,413 @@ +import json +import httpx +import re +import logging +from uuid import uuid4 +from channels.generic.websocket import AsyncWebsocketConsumer +from channels.db import database_sync_to_async +from django.utils import timezone +from django.conf import settings + +from apps.onboarding.mcp import MCPRouter +from apps.onboarding.models import AgentConfig, OnboardingFlow, OnboardingSession + +logger = logging.getLogger(__name__) + +class OnboardingConsumer(AsyncWebsocketConsumer): + async def connect(self): + self.user = self.scope["user"] + + self.context_uuid = self.scope["url_route"]["kwargs"].get("session_uuid") + + if not self.user.is_authenticated: + await self.close() + return + + self.router = MCPRouter() + await self.accept() + + async def disconnect(self, close_code): + pass + + def _build_system_prompt(self, config): + base_prompt = config.system_prompt or "You are a helpful onboarding assistant." + permissions = config.tool_permissions or [] + if permissions: + return f"{base_prompt}\n\nAllowed tools: {', '.join(str(p) for p in permissions)}" + return base_prompt + + async def receive(self, text_data): + try: + data = json.loads(text_data) + action = data.get("action") + + if action == "start_full_onboarding": + role_uuid = data.get("role_uuid") + if not role_uuid: + await self.send_log("error", "Missing role_uuid for full onboarding generation") + return + await self.run_full_onboarding_generation(role_uuid) + elif action == "progress_monitor": + role_uuid = data.get("role_uuid") or self.context_uuid + if not role_uuid: + await self.send_log("error", "Missing role_uuid for progress monitoring") + return + await self.run_progress_monitor(role_uuid) + else: + + user_message = data.get("query") or data.get("message") + if not user_message: + await self.send_log("error", "Missing query/message payload") + return + config = await self.get_config(self.context_uuid) + ai_response = await self.orchestrate_ai(user_message, config) + + await self.send(json.dumps({ + "type": "completed", + "timestamp": timezone.now().isoformat(), + "message": "Inference complete.", + "content": { + "response": ai_response, + } + })) + except Exception as e: + logger.error(f"WebSocket Receive Error: {str(e)}") + await self.send_log("error", f"Consumer encountered an error: {str(e)}") + + async def run_full_onboarding_generation(self, role_uuid): + """ + The Master Script that builds the JSON structure sequentially. + Pipeline: Curriculum Agent -> Knowledge Agent -> Assessment Agent + """ + + await self.send_log("status", "Phase 1: Generating Curriculum...", "curriculum") + ca_config = await self.get_config_by_type(role_uuid, 'curriculum') + if not ca_config: + await self.send_log("error", "Missing curriculum AgentConfig for this role") + return + + ca_prompt = ( + "Based on available documentation, create an onboarding curriculum for this role. " + "Output ONLY a valid JSON array of 3-5 strings representing module titles. " + "Example: [\"Introduction\", \"Safety\", \"Operations\"]" + ) + ca_response = await self.orchestrate_ai(ca_prompt, ca_config) + topics = self._extract_json_list(ca_response) + if not topics: + await self.send_log("error", "Curriculum generation returned no topics") + return + + toc_lines = [f"{idx + 1}. {title}" for idx, title in enumerate(topics)] + toc_markdown = "## Table of Contents\n\n" + "\n".join(toc_lines) + + full_structure = [] + + + for index, topic in enumerate(topics): + + await self.send_log("status", f"Phase 2: Researching {topic}...", "knowledge") + ka_config = await self.get_config_by_type(role_uuid, 'knowledge') + if not ka_config: + await self.send_log("error", "Missing knowledge AgentConfig for this role") + return + + knowledge_hits = await self.fetch_knowledge_context(role_uuid, topic) + context_markdown = self.format_knowledge_context(knowledge_hits) + + page_content = await self.orchestrate_ai( + ( + f"Write a practical onboarding training guide for the topic '{topic}'. " + "Use the MCP search context provided below as the primary source. " + "If the context is empty, provide a concise best-practice overview and clearly say no indexed documents were found. " + "Use Markdown formatting and do NOT include a table of contents in this section.\n\n" + f"Role UUID: {role_uuid}\n" + f"MCP search context:\n{context_markdown}" + ), + ka_config + ) + + if index == 0: + page_content = f"{toc_markdown}\n\n---\n\n{page_content}" + + + await self.send_log("status", f"Phase 3: Creating quiz for {topic}...", "assessment") + aa_config = await self.get_config_by_type(role_uuid, 'assessment') + if not aa_config: + await self.send_log("error", "Missing assessment AgentConfig for this role") + return + aa_prompt = ( + f"Based on this content: '{page_content[:1000]}', create 2 multiple choice questions. " + "Output ONLY a JSON array of objects with keys: 'key', 'label', 'field_type' (use 'select'), " + "'options' (array of strings), and 'required' (true)." + ) + quiz_response = await self.orchestrate_ai(aa_prompt, aa_config) + quiz_fields = self._extract_json_list(quiz_response) + + + full_structure.append({ + "title": topic, + "body": page_content, + "order": index, + "fields": quiz_fields + }) + + + await self.save_full_flow(role_uuid, full_structure) + + + await self.send(json.dumps({ + "type": "completed", + "timestamp": timezone.now().isoformat(), + "message": "Onboarding pipeline complete and structure saved." + })) + + async def run_progress_monitor(self, role_uuid): + await self.send_log("status", "Progress Monitor is analyzing your onboarding progress...", "monitor") + + monitor_config = await self.get_config_by_type(role_uuid, 'monitor') + if not monitor_config: + await self.send_log("error", "Missing Progress Monitor AgentConfig for this role") + return + + progress_context = await self.get_role_progress_context(role_uuid, self.user.id) + + monitor_prompt = ( + "You are a progress monitoring agent for onboarding. " + "Analyze the role onboarding data below and provide concise feedback with:\n" + "1) current status\n2) strengths\n3) gaps\n4) next actions\n" + "Keep it short and practical.\n\n" + f"Progress context JSON:\n{json.dumps(progress_context)}" + ) + + feedback = await self.orchestrate_ai(monitor_prompt, monitor_config) + + await self.send(json.dumps({ + "type": "completed", + "timestamp": timezone.now().isoformat(), + "message": "Progress analysis complete.", + "content": { + "role_uuid": role_uuid, + "feedback": feedback, + "status": progress_context.get("latest_status", "unknown"), + } + })) + + async def orchestrate_ai(self, user_message, config): + """ + Handles the multi-turn ReAct loop (Reasoning + Tool Use). + """ + messages = [ + {"role": "system", "content": self._build_system_prompt(config)}, + {"role": "user", "content": user_message} + ] + + async with httpx.AsyncClient(timeout=60.0) as client: + for turn in range(5): + await self.send_log("thought", f"Agent is thinking (Turn {turn+1})...") + + try: + response = await client.post( + f"{settings.INFERENCE_URL}/v1/chat/completions", + json={ + "model": config.llm_config.get("model_id", "meta-llama-3.1-8b"), + "messages": messages, + "tools": self.router.get_tool_definitions(), + "tool_choice": "auto" + } + ) + response.raise_for_status() + res_json = response.json() + + ai_message = res_json["choices"][0]["message"] + messages.append(ai_message) + + if ai_message.get("tool_calls"): + for tool_call in ai_message["tool_calls"]: + fn_name = tool_call["function"]["name"] + fn_args = json.loads(tool_call["function"]["arguments"]) + + await self.send(json.dumps({ + "type": "tool_start", + "message": f"Accessing knowledge base: {fn_name}...", + "content": fn_args + })) + + + result = await self.router.handle_tool_call(fn_name, fn_args) + + messages.append({ + "role": "tool", + "tool_call_id": tool_call["id"], + "name": fn_name, + "content": json.dumps(result) + }) + continue + + else: + return ai_message["content"] + + except Exception as e: + await self.send_log("error", f"Inference failed: {str(e)}") + return f"Error: {str(e)}" + + + + async def fetch_knowledge_context(self, role_uuid, topic): + query = f"onboarding training content for {topic}" + await self.send(json.dumps({ + "type": "tool_start", + "message": "Accessing knowledge base: search_knowledge...", + "content": {"query": query, "role_uuid": role_uuid} + })) + + try: + result = await self.router.handle_tool_call( + "search_knowledge", + { + "query": query, + "role_uuid": role_uuid, + }, + ) + + await self.send(json.dumps({ + "type": "tool_result", + "message": f"Retrieved {len(result) if isinstance(result, list) else 0} knowledge chunk(s)", + "content": result, + "timestamp": timezone.now().isoformat(), + })) + + return result if isinstance(result, list) else [] + except Exception as exc: + await self.send_log("error", f"Knowledge retrieval failed for topic '{topic}': {str(exc)}") + return [] + + def format_knowledge_context(self, knowledge_hits): + if not knowledge_hits: + return "No indexed MCP documents found for this role/topic." + + lines = [] + for idx, item in enumerate(knowledge_hits[:5]): + source = item.get("source", "Unknown Source") if isinstance(item, dict) else "Unknown Source" + relevance = item.get("relevance") if isinstance(item, dict) else None + content = item.get("content", "") if isinstance(item, dict) else "" + safe_content = str(content).strip()[:1600] + lines.append( + f"[{idx + 1}] Source: {source} | Relevance: {relevance}\n{safe_content}" + ) + + return "\n\n".join(lines) + + def _extract_json_list(self, text): + """Regex helper to pull JSON out of LLM conversational filler.""" + try: + if not text: + return [] + match = re.search(r'\[.*\]', text, re.DOTALL) + if match: + return json.loads(match.group()) + return [] + except Exception: + return [] + + def _normalize_structure(self, structure): + normalized_pages = [] + for index, page in enumerate(structure or []): + fields = [] + for field_index, field in enumerate(page.get('fields', []) if isinstance(page, dict) else []): + if not isinstance(field, dict): + continue + key = str(field.get('key') or f'field_{field_index + 1}') + fields.append({ + 'uuid': str(uuid4()), + 'key': key, + 'label': str(field.get('label') or key.replace('_', ' ').title()), + 'field_type': str(field.get('field_type') or 'text'), + 'required': bool(field.get('required', False)), + 'options': field.get('options') if isinstance(field.get('options'), list) else [], + 'default_value': field.get('default_value', ''), + }) + + page_title = page.get('title') if isinstance(page, dict) else None + page_body = page.get('body') if isinstance(page, dict) else '' + page_order = page.get('order') if isinstance(page, dict) else index + normalized_pages.append({ + 'uuid': str(uuid4()), + 'title': str(page_title or f'Module {index + 1}'), + 'body': str(page_body or ''), + 'order': int(page_order if isinstance(page_order, int) else index), + 'fields': fields, + }) + return normalized_pages + + @database_sync_to_async + def save_full_flow(self, role_uuid, structure): + """Saves the final nested structure to the OnboardingFlow model.""" + from apps.accounts.models import Role + role = Role.objects.get(uuid=role_uuid) + normalized_structure = self._normalize_structure(structure) + flow, _ = OnboardingFlow.objects.update_or_create( + role=role, + defaults={ + 'title': f"AI Onboarding: {role.name}", + 'structure': normalized_structure, + 'is_active': True + } + ) + return flow + + async def send_log(self, log_type, message, content=None): + await self.send(json.dumps({ + "type": log_type, + "message": message, + "content": content, + "timestamp": timezone.now().isoformat() + })) + + @database_sync_to_async + def get_config(self, config_uuid): + return AgentConfig.objects.get(uuid=config_uuid) + + @database_sync_to_async + def get_config_by_type(self, role_uuid, agent_type): + return AgentConfig.objects.filter( + organization__roles__uuid=role_uuid, + agent_type=agent_type, + ).order_by('-updated_at').first() + + @database_sync_to_async + def get_role_progress_context(self, role_uuid, user_id): + from apps.accounts.models import Role + + role = Role.objects.get(uuid=role_uuid) + sessions = OnboardingSession.objects.filter(user_id=user_id, role=role).order_by('-updated_at') + latest_session = sessions.first() + active_flow = OnboardingFlow.objects.filter(role=role, is_active=True).order_by('-updated_at').first() + + if not latest_session: + return { + "role_uuid": str(role.uuid), + "role_name": role.name, + "latest_status": "not_started", + "session_count": 0, + "flow_exists": bool(active_flow), + "progress": 0, + "responses_count": 0, + "completed_modules": [], + } + + state = latest_session.state or {} + responses = state.get("responses", {}) + completed_modules = state.get("completed_modules", []) + progress = state.get("progress_percentage", state.get("progress", 0)) + + return { + "role_uuid": str(role.uuid), + "role_name": role.name, + "latest_status": latest_session.status, + "session_count": sessions.count(), + "flow_exists": bool(active_flow), + "progress": progress, + "responses_count": len(responses) if isinstance(responses, dict) else 0, + "completed_modules": completed_modules if isinstance(completed_modules, list) else [], + "updated_at": latest_session.updated_at.isoformat() if latest_session.updated_at else None, + } \ No newline at end of file diff --git a/apps/onboarding/mcp.py b/apps/onboarding/mcp.py new file mode 100644 index 0000000..5eb84ca --- /dev/null +++ b/apps/onboarding/mcp.py @@ -0,0 +1,102 @@ +import httpx +from channels.db import database_sync_to_async +from django.conf import settings +from pgvector.django import CosineDistance + +from apps.knowledge.models import RoleRagDocument +from apps.onboarding.models import OnboardingSession + +class MCPRouter: + + def get_tool_definitions(self): + return [ + { + "name": "search_knowledge", + "description": "Search the RAG database for role-specific training content.", + "inputSchema": { + "type": "object", + "properties": { + "query": {"type": "string"}, + "role_uuid": {"type": "string"} + }, + "required": ["query", "role_uuid"] + } + }, + { + "name": "update_progress", + "description": "Update the user's score or current module in their session.", + "inputSchema": { + "type": "object", + "properties": { + "session_uuid": {"type": "string"}, + "score": {"type": "integer"}, + "completed_module": {"type": "string"} + }, + "required": ["session_uuid"] + } + } + ] + + async def handle_tool_call(self, name, arguments): + if name == "search_knowledge": + return await self._search_knowledge(arguments) + elif name == "update_progress": + return await self._update_progress(arguments) + return {"error": f"Tool {name} not found"} + + async def _get_embedding(self, text): + """Fetch embedding from the GPU node.""" + async with httpx.AsyncClient() as client: + response = await client.post( + f"{settings.INFERENCE_URL}/v1/embeddings", + json={"input": text} + ) + + return response.json()["data"][0]["embedding"] + + async def _search_knowledge(self, args): + query = args.get("query") + role_uuid = args.get("role_uuid") + + if not query or not role_uuid: + return [] + + + query_vector = await self._get_embedding(query) + + return await self._search_knowledge_documents(role_uuid, query_vector) + + @database_sync_to_async + def _search_knowledge_documents(self, role_uuid, query_vector): + + + docs = RoleRagDocument.objects.filter( + role__uuid=role_uuid, + is_active=True + ).annotate( + distance=CosineDistance('embedding', query_vector) + ).order_by('distance')[:5] + + + return [ + { + "content": d.content, + "source": d.metadata.get("file_name", "Unknown Source"), + "relevance": round(1 - d.distance, 4) + } + for d in docs + ] + + @database_sync_to_async + def _update_progress(self, args): + session = OnboardingSession.objects.get(uuid=args.get("session_uuid")) + + state = session.state or {} + if "score" in args: + state["last_score"] = args["score"] + if "completed_module" in args: + state.setdefault("completed_modules", []).append(args["completed_module"]) + + session.state = state + session.save() + return {"status": "success", "new_state": state} \ No newline at end of file diff --git a/apps/onboarding/migrations/0001_initial.py b/apps/onboarding/migrations/0001_initial.py new file mode 100644 index 0000000..0ac9fe7 --- /dev/null +++ b/apps/onboarding/migrations/0001_initial.py @@ -0,0 +1,89 @@ +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('accounts', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='AgentConfig', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('name', models.CharField(max_length=255, verbose_name='Agent Name')), + ('agent_type', models.CharField(choices=[('curriculum', 'Curriculum Agent (CA)'), ('knowledge', 'Knowledge Agent (KA)'), ('assessment', 'Assessment Agent (AA)'), ('monitor', 'Progress Monitor Agent (PMA)')], max_length=40, verbose_name='Agent Type')), + ('llm_config', models.JSONField(default=dict, verbose_name='LLM Configuration')), + ('system_prompt', models.TextField(verbose_name='System Prompt')), + ('tool_permissions', models.JSONField(default=list, verbose_name='Tool Permissions')), + ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='agent_configs', to='accounts.organization', verbose_name='Organization')), + ], + options={ + 'verbose_name': 'Agent Config', + 'verbose_name_plural': 'Agent Configs', + }, + ), + migrations.CreateModel( + name='OnboardingFlow', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('title', models.CharField(max_length=255, verbose_name='Flow Title')), + ('is_active', models.BooleanField(default=True, verbose_name='Is Active')), + ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='flows', to='accounts.role', verbose_name='Role')), + ], + options={ + 'verbose_name': 'Onboarding Flow', + 'verbose_name_plural': 'Onboarding Flows', + }, + ), + migrations.CreateModel( + name='OnboardingSession', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('status', models.CharField(choices=[('active', 'Active'), ('completed', 'Completed'), ('paused', 'Paused')], default='active', max_length=20, verbose_name='Session Status')), + ('state', models.JSONField(blank=True, default=dict, verbose_name='Session State')), + ('active_configs', models.JSONField(default=dict, verbose_name='Active Configs')), + ('completed_at', models.DateTimeField(blank=True, null=True, verbose_name='Completed At')), + ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='onboarding_sessions', to='accounts.role', verbose_name='Target Role')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='onboarding_sessions', to=settings.AUTH_USER_MODEL, verbose_name='User')), + ], + options={ + 'verbose_name': 'Onboarding Session', + 'verbose_name_plural': 'Onboarding Sessions', + }, + ), + migrations.CreateModel( + name='AgentInteractionLog', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('sender_type', models.CharField(choices=[('system', 'System'), ('ai', 'AI'), ('user', 'User'), ('tool', 'Tool Output')], max_length=20, verbose_name='Sender Type')), + ('content', models.TextField(verbose_name='Message Content')), + ('tool_call_metadata', models.JSONField(blank=True, default=dict, verbose_name='Tool Call Metadata')), + ('agent_config', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='onboarding.agentconfig', verbose_name='Agent Config')), + ('session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='logs', to='onboarding.onboardingsession', verbose_name='Session')), + ], + options={ + 'verbose_name': 'Interaction Log', + 'verbose_name_plural': 'Interaction Logs', + 'ordering': ['created_at'], + }, + ), + ] diff --git a/apps/onboarding/migrations/0002_alter_agentconfig_llm_config_and_more.py b/apps/onboarding/migrations/0002_alter_agentconfig_llm_config_and_more.py new file mode 100644 index 0000000..4cc07bb --- /dev/null +++ b/apps/onboarding/migrations/0002_alter_agentconfig_llm_config_and_more.py @@ -0,0 +1,28 @@ + + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('onboarding', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='agentconfig', + name='llm_config', + field=models.JSONField(blank=True, default=dict, null=True, verbose_name='LLM Configuration'), + ), + migrations.AlterField( + model_name='agentconfig', + name='system_prompt', + field=models.TextField(blank=True, default='', verbose_name='System Prompt'), + ), + migrations.AlterField( + model_name='agentconfig', + name='tool_permissions', + field=models.JSONField(blank=True, default=list, null=True, verbose_name='Tool Permissions'), + ), + ] diff --git a/apps/onboarding/migrations/0003_onboardingflow_structure.py b/apps/onboarding/migrations/0003_onboardingflow_structure.py new file mode 100644 index 0000000..69f02a9 --- /dev/null +++ b/apps/onboarding/migrations/0003_onboardingflow_structure.py @@ -0,0 +1,18 @@ + + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('onboarding', '0002_alter_agentconfig_llm_config_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='onboardingflow', + name='structure', + field=models.JSONField(blank=True, default=list, verbose_name='Flow Structure'), + ), + ] diff --git a/apps/onboarding/migrations/__init__.py b/apps/onboarding/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/onboarding/models.py b/apps/onboarding/models.py new file mode 100644 index 0000000..6cc727d --- /dev/null +++ b/apps/onboarding/models.py @@ -0,0 +1,85 @@ +from django.db.models import CASCADE, CharField, ForeignKey, JSONField, TextField, Model, DateTimeField, BooleanField +from django.utils.translation import gettext_lazy as _ +from apps.accounts.mixins import IdentifierMixin, TimeStampMixin +from apps.accounts.models import User, Role, Organization + +class AgentConfig(IdentifierMixin, TimeStampMixin, Model): + AGENT_TYPES = [ + ('curriculum', 'Curriculum Agent (CA)'), + ('knowledge', 'Knowledge Agent (KA)'), + ('assessment', 'Assessment Agent (AA)'), + ('monitor', 'Progress Monitor Agent (PMA)'), + ] + + organization = ForeignKey(Organization, on_delete=CASCADE, related_name='agent_configs', verbose_name=_("Organization")) + name = CharField(max_length=255, verbose_name=_("Agent Name")) + agent_type = CharField(max_length=40, choices=AGENT_TYPES, verbose_name=_("Agent Type")) + llm_config = JSONField(default=dict, blank=True, null=True, verbose_name=_("LLM Configuration")) + + system_prompt = TextField(verbose_name=_("System Prompt"), blank=True, default='') + tool_permissions = JSONField(default=list, blank=True, null=True, verbose_name=_("Tool Permissions")) + + class Meta: + verbose_name = _('Agent Config') + verbose_name_plural = _('Agent Configs') + + def __str__(self): + return f"{self.name} ({self.get_agent_type_display()})" + +class OnboardingSession(IdentifierMixin, TimeStampMixin, Model): + STATUS_CHOICES = [ + ('active', 'Active'), + ('completed', 'Completed'), + ('paused', 'Paused'), + ] + + user = ForeignKey(User, on_delete=CASCADE, related_name='onboarding_sessions', verbose_name=_("User")) + role = ForeignKey(Role, on_delete=CASCADE, related_name='onboarding_sessions', verbose_name=_("Target Role")) + + status = CharField(max_length=20, choices=STATUS_CHOICES, default='active', verbose_name=_("Session Status")) + state = JSONField(default=dict, blank=True, verbose_name=_("Session State")) + + active_configs = JSONField(default=dict, verbose_name=_("Active Configs")) + completed_at = DateTimeField(null=True, blank=True, verbose_name=_("Completed At")) + + class Meta: + verbose_name = _('Onboarding Session') + verbose_name_plural = _('Onboarding Sessions') + + def __str__(self): + return f"{self.user.email_address} - {self.role.name}" + +class AgentInteractionLog(IdentifierMixin, TimeStampMixin, Model): + SENDER_TYPES = [ + ('system', 'System'), + ('ai', 'AI'), + ('user', 'User'), + ('tool', 'Tool Output'), + ] + + session = ForeignKey(OnboardingSession, on_delete=CASCADE, related_name='logs', verbose_name=_("Session")) + agent_config = ForeignKey(AgentConfig, on_delete=CASCADE, null=True, blank=True, verbose_name=_("Agent Config")) + sender_type = CharField(max_length=20, choices=SENDER_TYPES, verbose_name=_("Sender Type")) + content = TextField(verbose_name=_("Message Content")) + tool_call_metadata = JSONField(default=dict, blank=True, verbose_name=_("Tool Call Metadata")) + + class Meta: + verbose_name = _('Interaction Log') + verbose_name_plural = _('Interaction Logs') + ordering = ['created_at'] + + def __str__(self): + return f"{self.sender_type} in {self.session.uuid}" + +class OnboardingFlow(IdentifierMixin, TimeStampMixin, Model): + title = CharField(max_length=255, verbose_name=_("Flow Title")) + role = ForeignKey(Role, on_delete=CASCADE, related_name='flows', verbose_name=_("Role")) + structure = JSONField(default=list, blank=True, verbose_name=_("Flow Structure")) + is_active = BooleanField(default=True, verbose_name=_("Is Active")) + + class Meta: + verbose_name = _('Onboarding Flow') + verbose_name_plural = _('Onboarding Flows') + + def __str__(self): + return self.title \ No newline at end of file diff --git a/apps/onboarding/routing.py b/apps/onboarding/routing.py new file mode 100644 index 0000000..2554819 --- /dev/null +++ b/apps/onboarding/routing.py @@ -0,0 +1,6 @@ +from django.urls import path +from .consumers import OnboardingConsumer + +websocket_urlpatterns = [ + path("ws/onboarding//", OnboardingConsumer.as_asgi()), +] \ No newline at end of file diff --git a/apps/onboarding/serializers.py b/apps/onboarding/serializers.py new file mode 100644 index 0000000..3e89815 --- /dev/null +++ b/apps/onboarding/serializers.py @@ -0,0 +1,69 @@ +from rest_framework.serializers import CharField, ModelSerializer, SerializerMethodField + +from apps.accounts.serializers import UserSerializer, RoleSerializer, OrganizationSerializer +from apps.onboarding.models import AgentConfig, OnboardingSession, AgentInteractionLog, OnboardingFlow + +class AgentConfigSerializer(ModelSerializer): + organization = OrganizationSerializer(read_only=True) + + class Meta: + model = AgentConfig + fields = [ + 'id', 'uuid', 'organization', 'name', 'agent_type', + 'system_prompt', 'llm_config', 'tool_permissions', + 'created_at', 'updated_at' + ] + read_only_fields = ['id', 'uuid', 'created_at', 'updated_at'] + +class AgentInteractionLogSerializer(ModelSerializer): + agent_name = CharField(source='agent_config.name', read_only=True) + + class Meta: + model = AgentInteractionLog + fields = [ + 'id', 'uuid', 'session', 'agent_config', 'agent_name', + 'sender_type', 'content', 'tool_call_metadata', 'created_at' + ] + read_only_fields = ['id', 'uuid', 'created_at'] + +class OnboardingSessionSerializer(ModelSerializer): + user = UserSerializer(read_only=True) + role = RoleSerializer(read_only=True) + logs = AgentInteractionLogSerializer(many=True, read_only=True) + progress_percentage = SerializerMethodField() + + class Meta: + model = OnboardingSession + fields = [ + 'id', 'uuid', 'user', 'role', 'status', 'state', + 'active_configs', 'logs', 'completed_at', 'created_at', + 'updated_at', 'progress_percentage' + ] + read_only_fields = ['id', 'uuid', 'user', 'completed_at', 'created_at', 'updated_at'] + + def get_progress_percentage(self, obj: OnboardingSession) -> int: + return obj.state.get('progress_percentage', 0) + +class OnboardingFlowSerializer(ModelSerializer): + role = RoleSerializer(read_only=True) + session_count = SerializerMethodField() + pages = SerializerMethodField() + description = SerializerMethodField() + status = SerializerMethodField() + + class Meta: + model = OnboardingFlow + fields = ['id', 'uuid', 'title', 'role', 'is_active', 'status', 'description', 'pages', 'session_count', 'created_at'] + read_only_fields = ['id', 'uuid', 'created_at'] + + def get_session_count(self, obj: OnboardingFlow) -> int: + return obj.role.onboarding_sessions.count() + + def get_pages(self, obj: OnboardingFlow): + return obj.structure or [] + + def get_description(self, obj: OnboardingFlow) -> str: + return '' + + def get_status(self, obj: OnboardingFlow) -> str: + return 'published' if obj.is_active else 'archived' \ No newline at end of file diff --git a/apps/onboarding/viewsets.py b/apps/onboarding/viewsets.py new file mode 100644 index 0000000..0d451a6 --- /dev/null +++ b/apps/onboarding/viewsets.py @@ -0,0 +1,159 @@ +from django.db.models import Q +from django.db import transaction +from django.utils import timezone +from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN, HTTP_201_CREATED, HTTP_200_OK +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet + +from apps.onboarding.models import AgentConfig, OnboardingSession, AgentInteractionLog, OnboardingFlow +from apps.onboarding.serializers import ( + AgentConfigSerializer, + OnboardingSessionSerializer, + AgentInteractionLogSerializer, + OnboardingFlowSerializer +) + +class OnboardingFlowViewSet(ModelViewSet): + queryset = OnboardingFlow.objects.all() + serializer_class = OnboardingFlowSerializer + permission_classes = [IsAuthenticated] + lookup_field = 'uuid' + + def get_queryset(self): + user = self.request.user + return OnboardingFlow.objects.filter( + Q(role__organization__owner=user) | + Q(role__organization__members=user) + ).distinct() + + def destroy(self, request, *args, **kwargs): + flow = self.get_object() + + with transaction.atomic(): + OnboardingSession.objects.filter(role=flow.role).delete() + self.perform_destroy(flow) + + return Response(status=204) + + @action(detail=True, methods=['post'], url_path='start-session') + def start_session(self, request, uuid=None): + flow = self.get_object() + + session, created = OnboardingSession.objects.get_or_create( + user=request.user, + role=flow.role, + defaults={ + 'status': 'active', + 'state': { + 'progress': 0, + 'current_step': 'intro', + 'flow_uuid': str(flow.uuid), + }, + 'active_configs': {}, + } + ) + + if not created: + state = session.state or {} + state['flow_uuid'] = str(flow.uuid) + session.state = state + session.save(update_fields=['state', 'updated_at']) + + serializer = OnboardingSessionSerializer(session) + return Response(serializer.data, status=HTTP_201_CREATED if created else HTTP_200_OK) + +class AgentConfigViewSet(ModelViewSet): + queryset = AgentConfig.objects.all() + serializer_class = AgentConfigSerializer + permission_classes = [IsAuthenticated] + lookup_field = 'uuid' + + def get_queryset(self): + return AgentConfig.objects.filter(organization__members=self.request.user).distinct() + + def perform_create(self, serializer): + if not self.request.user.is_manager: + return Response({'error': 'Forbidden'}, status=HTTP_403_FORBIDDEN) + serializer.save() + +class OnboardingSessionViewSet(ModelViewSet): + queryset = OnboardingSession.objects.all() + serializer_class = OnboardingSessionSerializer + permission_classes = [IsAuthenticated] + lookup_field = 'uuid' + + def get_queryset(self): + user = self.request.user + if user.is_manager: + return OnboardingSession.objects.filter(role__organization__members=user).distinct() + return OnboardingSession.objects.filter(user=user) + + @action(detail=True, methods=['post'], url_path='interact') + def interact(self, request, uuid=None): + session = self.get_object() + user_message = request.data.get('message') + page_uuid = request.data.get('page_uuid') + responses = request.data.get('responses') + + if not user_message and not page_uuid: + return Response({'error': 'Message or page_uuid is required'}, status=HTTP_400_BAD_REQUEST) + + if isinstance(responses, dict): + state = session.state or {} + stored_responses = state.get('responses', {}) + if not isinstance(stored_responses, dict): + stored_responses = {} + + if page_uuid: + stored_responses[str(page_uuid)] = responses + else: + stored_responses.update(responses) + + state['responses'] = stored_responses + if page_uuid: + state['last_page_uuid'] = str(page_uuid) + session.state = state + session.save(update_fields=['state', 'updated_at']) + + AgentInteractionLog.objects.create( + session=session, + sender_type='user', + content=user_message or f'Submitted onboarding responses for page {page_uuid or "unknown"}', + tool_call_metadata={'page_uuid': page_uuid, 'has_responses': isinstance(responses, dict)} + ) + + + + return Response({ + 'status': 'received', + 'session_state': session.state + }) + + @action(detail=True, methods=['get'], url_path='history') + def history(self, request, uuid=None): + session = self.get_object() + logs = session.logs.all().order_by('created_at') + serializer = AgentInteractionLogSerializer(logs, many=True) + return Response(serializer.data) + + @action(detail=True, methods=['post'], url_path='complete') + def complete(self, request, uuid=None): + session = self.get_object() + session.status = 'completed' + session.completed_at = timezone.now() + session.save() + return Response({'message': 'Session marked as completed'}) + +class AgentInteractionLogViewSet(ReadOnlyModelViewSet): + queryset = AgentInteractionLog.objects.all() + serializer_class = AgentInteractionLogSerializer + permission_classes = [IsAuthenticated] + lookup_field = 'uuid' + + def get_queryset(self): + return AgentInteractionLog.objects.filter( + Q(session__user=self.request.user) | + Q(session__role__organization__owner=self.request.user) + ).distinct() diff --git a/compose/dev/celery/Dockerfile b/compose/dev/celery/Dockerfile new file mode 100644 index 0000000..2e2beab --- /dev/null +++ b/compose/dev/celery/Dockerfile @@ -0,0 +1,19 @@ +FROM python:3.12-bookworm + +RUN apt-get update && apt-get install --no-install-recommends -y \ + build-essential \ + libpq-dev \ + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ + && rm -rf /var/lib/apt/lists/* + +ENV VIRTUAL_ENV=/venv \ + PATH=/venv/bin:$PATH + +RUN python -m venv /venv + +WORKDIR /app + +COPY requirements/django.txt . +RUN pip install --no-cache-dir --requirement django.txt + +CMD ["celery", "-A", "config", "worker", "-l", "info"] \ No newline at end of file diff --git a/compose/dev/django/Dockerfile b/compose/dev/django/Dockerfile new file mode 100644 index 0000000..74b4373 --- /dev/null +++ b/compose/dev/django/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.12-bookworm + +RUN apt-get update && apt-get install --no-install-recommends -y \ + build-essential \ + libpq-dev \ + wait-for-it \ + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ + && rm -rf /var/lib/apt/lists/* + +ENV VIRTUAL_ENV=/venv \ + PATH=/venv/bin:$PATH + +RUN python -m venv /venv + +WORKDIR /app + +COPY requirements/django.txt . +RUN pip install --no-cache-dir --requirement django.txt + +COPY ./compose/dev/django/start /start +RUN sed -i 's/\r$//g' /start && chmod +x /start + +CMD ["/start"] \ No newline at end of file diff --git a/compose/dev/django/start b/compose/dev/django/start new file mode 100644 index 0000000..1fb274a --- /dev/null +++ b/compose/dev/django/start @@ -0,0 +1,27 @@ +#!/bin/bash + +set -o errexit +set -o pipefail +set -o nounset + +DB_HOST="${POSTGRES_HOST}" +DB_PORT="${POSTGRES_PORT}" + +echo "Waiting for database at ${DB_HOST}:${DB_PORT}..." +wait-for-it ${DB_HOST}:${DB_PORT} --timeout=30 --strict || { + echo "Timed out waiting for database" >&2 + exit 1 +} + +echo "Database is available, continuing startup..." + +python manage.py makemigrations +python manage.py migrate --noinput + +for fixture in /app/data/*.json; do + echo "Loading fixture: $fixture" + python manage.py loaddata "$fixture" +done + +python manage.py collectstatic --noinput +exec python manage.py runserver 0.0.0:8000 \ No newline at end of file diff --git a/compose/dev/docker-compose.yml b/compose/dev/docker-compose.yml new file mode 100644 index 0000000..a03d00f --- /dev/null +++ b/compose/dev/docker-compose.yml @@ -0,0 +1,118 @@ +services: + fyp-django-dev: + container_name: fyp-django-dev + build: + context: ../../ + dockerfile: compose/dev/django/Dockerfile + env_file: + - ../../.env + volumes: + - ../../:/app + ports: + - '0.0.0.0:8000:8000' + depends_on: + fyp-postgres-dev: + condition: service_healthy + fyp-node-dev: + condition: service_started + + fyp-node-dev: + container_name: fyp-node-dev + build: + context: ../../ + dockerfile: compose/dev/node/Dockerfile + environment: + NODE_ENV: development + CHOKIDAR_USEPOLLING: 'true' + stdin_open: true + volumes: + - ../../site:/app:delegated + - /app/node_modules + ports: + - '0.0.0.0:5173:5173' + + fyp-postgres-dev: + container_name: fyp-postgres-dev + image: pgvector/pgvector:pg15 + env_file: + - ../../.env + environment: + POSTGRES_HOST_AUTH_METHOD: trust + volumes: + - fyp_postgres_data:/var/lib/postgresql/data + ports: + - '0.0.0.0:5432:5432' + healthcheck: + test: ['CMD-SHELL', 'pg_isready -h 127.0.0.1 -p 5432 -U ${POSTGRES_USER} -d ${POSTGRES_DB}'] + interval: 5s + timeout: 3s + retries: 5 + + fyp-redis-dev: + container_name: fyp-redis-dev + image: redis:7-alpine + ports: + - '0.0.0.0:6379:6379' + volumes: + - fyp_redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 5 + + fyp-celery-dev: + container_name: fyp-celery-dev + build: + context: ../../ + dockerfile: compose/dev/celery/Dockerfile + env_file: + - ../../.env + volumes: + - ../../:/app + depends_on: + fyp-redis-dev: + condition: service_healthy + fyp-postgres-dev: + condition: service_healthy + + + fyp-inference-dev: + container_name: fyp-inference-dev + build: + context: ../../ + dockerfile: compose/dev/inference/Dockerfile + env_file: + - ../../.env + volumes: + - ../../:/app + - ../../models:/app/models + - hf_cache:/root/.cache/huggingface + deploy: + mode: replicated + replicas: 1 + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + environment: + - NVIDIA_VISIBLE_DEVICES=all + - WATCHFILES_FORCE_POLLING=true + - PYTHONPATH=/app + - HF_HOME=/root/.cache/huggingface + - HF_HUB_OFFLINE=1 + ports: + - "0.0.0.0:8001:8001" + depends_on: + fyp-redis-dev: + condition: service_healthy + fyp-postgres-dev: + condition: service_healthy + + +volumes: + fyp_postgres_data: + fyp_redis_data: + hf_cache: \ No newline at end of file diff --git a/compose/dev/inference/Dockerfile b/compose/dev/inference/Dockerfile new file mode 100644 index 0000000..e44b3a4 --- /dev/null +++ b/compose/dev/inference/Dockerfile @@ -0,0 +1,35 @@ +FROM nvidia/cuda:12.4.1-devel-ubuntu22.04 AS builder +WORKDIR /build + +RUN apt-get update && apt-get install -y python3.10 python3-pip python3-dev cmake git + +COPY requirements/inference.txt . + +RUN pip install --no-cache-dir --upgrade pip setuptools wheel +RUN pip install --no-cache-dir torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124 + +ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64/stubs:$LD_LIBRARY_PATH + +ENV CMAKE_ARGS="-DGGML_CUDA=on -DLLAVA_BUILD=off" +ENV FORCE_CMAKE=1 + +RUN pip install --no-cache-dir llama-cpp-python +RUN pip install --no-cache-dir -r inference.txt + +FROM nvidia/cuda:12.4.1-runtime-ubuntu22.04 +WORKDIR /app + +RUN apt-get update && apt-get install -y python3.10 python3-pip && \ + rm -rf /var/lib/apt/lists/* && \ + ln -sf /usr/bin/python3 /usr/bin/python + +COPY --from=builder /usr/local/lib/python3.10/dist-packages /usr/local/lib/python3.10/dist-packages +COPY --from=builder /usr/local/bin /usr/local/bin + +COPY . . + +ENV PYTHONUNBUFFERED=1 +ENV PYTHONPATH=/app +EXPOSE 8001 + +CMD ["python", "-m", "uvicorn", "gpu_server:app", "--host", "0.0.0.0", "--port", "8001"] \ No newline at end of file diff --git a/compose/dev/node/Dockerfile b/compose/dev/node/Dockerfile new file mode 100644 index 0000000..f121f12 --- /dev/null +++ b/compose/dev/node/Dockerfile @@ -0,0 +1,15 @@ +FROM node:22-bullseye + +WORKDIR /app + +COPY site/package*.json ./ +RUN npm ci && npm cache clean --force + +COPY site/src ./src +COPY site/index.html . +COPY site/vite.config.* . +COPY site/tsconfig.* . + +EXPOSE 5173 + +CMD ["npm", "run", "devwatch"] \ No newline at end of file diff --git a/compose/prod/celery/Dockerfile b/compose/prod/celery/Dockerfile new file mode 100644 index 0000000..52757cd --- /dev/null +++ b/compose/prod/celery/Dockerfile @@ -0,0 +1,27 @@ +FROM python:3.12.0-slim + +LABEL org.opencontainers.image.title="Dynavera Celery Worker" +LABEL org.opencontainers.image.source="https://git.cs.bham.ac.uk/projects-2025-26/vxn217" + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +WORKDIR /app + +RUN apt-get update && apt-get install --no-install-recommends -y \ + build-essential \ + libpq-dev \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements/django.txt . +RUN pip install --no-cache-dir -r django.txt + +COPY manage.py manage.py +COPY config config +COPY apps apps +COPY data data +COPY mcp_agent mcp_agent + +RUN mkdir -p /app/static + +CMD ["celery", "-A", "config.celery", "worker", "--loglevel=info"] \ No newline at end of file diff --git a/compose/prod/django/Dockerfile b/compose/prod/django/Dockerfile new file mode 100644 index 0000000..73c00e8 --- /dev/null +++ b/compose/prod/django/Dockerfile @@ -0,0 +1,47 @@ +FROM node:22-alpine AS node + +WORKDIR /app + +COPY site/package*.json ./ + +RUN npm ci + +COPY site/ ./ + +RUN npm run build + +FROM python:3.12.0-slim AS python + +LABEL org.opencontainers.image.title="Dynavera - An Agentic Approach to Role-Specific Trainers" +LABEL org.opencontainers.image.source="https://git.cs.bham.ac.uk/projects-2025-26/vxn217" +LABEL org.opencontainers.image.description="Dynavera (Final Year Project)" + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +WORKDIR /app + +RUN apt-get update && apt-get install --no-install-recommends -y \ + build-essential \ + libpq-dev \ + wait-for-it \ + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements/django.txt . +RUN pip install --no-cache-dir -r django.txt + +COPY manage.py manage.py +COPY config config +COPY apps apps +COPY data data +COPY mcp_agent mcp_agent + +COPY --from=node /app/build ./build + +RUN mkdir -p /app/static + +COPY ./compose/prod/django/start /start +RUN sed -i 's/\r$//g' /start && chmod +x /start + +ENTRYPOINT ["/start"] \ No newline at end of file diff --git a/compose/prod/docker-compose.inference.yml b/compose/prod/docker-compose.inference.yml new file mode 100644 index 0000000..e06df96 --- /dev/null +++ b/compose/prod/docker-compose.inference.yml @@ -0,0 +1,26 @@ +services: + fyp-inference-prod: + container_name: fyp-inference-prod + build: + context: ../../ + dockerfile: compose/dev/inference/Dockerfile + restart: unless-stopped + deploy: + mode: replicated + replicas: 1 + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + env_file: + - ../../.env + environment: + - INFERENCE_HTTP_HOST=0.0.0.0 + - INFERENCE_HTTP_PORT=8001 + - NVIDIA_VISIBLE_DEVICES=all + ports: + - '0.0.0.0:58001:8001' + volumes: + - ../../:/app diff --git a/compose/prod/docker-compose.yml b/compose/prod/docker-compose.yml new file mode 100644 index 0000000..33fb071 --- /dev/null +++ b/compose/prod/docker-compose.yml @@ -0,0 +1,122 @@ +services: + fyp-django-prod: + container_name: fyp-django-prod + image: "${FYP_DJANGO_IMAGE}" + env_file: + - ../../.env + labels: + - "traefik.enable=true" + - "traefik.http.routers.fyp-web.rule=Host(`${DJANGO_DOMAIN_NAME}`)" + - "traefik.http.routers.fyp-web.entrypoints=${DJANGO_ENTRYPOINT}" + - "traefik.http.routers.fyp-web.tls.certresolver=${CERTRESOLVER}" + - "traefik.http.routers.fyp-web.tls=true" + - "traefik.http.services.fyp-web.loadbalancer.server.port=${DJANGO_PORT}" + - "com.centurylinklabs.watchtower.enable=true" + - "com.centurylinklabs.watchtower.scope=fyp" + volumes: + - ../../static:/app/static + - ../../media:/app/media + depends_on: + fyp-postgres-prod: + condition: service_healthy + networks: + - fyp-network + - proxy + + fyp-postgres-prod: + container_name: fyp-postgres-prod + image: pgvector/pgvector:pg15 + hostname: fyp-postgres-prod + restart: unless-stopped + env_file: + - ../../.env + environment: + POSTGRES_HOST_AUTH_METHOD: trust + volumes: + - fyp_postgres_data:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -h 127.0.0.1 -p 5432 -U ${POSTGRES_USER} -d ${POSTGRES_DB}'] + interval: 5s + timeout: 3s + retries: 5 + networks: + - fyp-network + + fyp-redis-prod: + container_name: fyp-redis-prod + image: redis:7-alpine + restart: unless-stopped + volumes: + - fyp_redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 5 + networks: + - fyp-network + + fyp-celery-prod: + container_name: fyp-celery-prod + image: "${FYP_CELERY_IMAGE}" + env_file: + - ../../.env + volumes: + - ../../:/app + - ../../static:/app/static + - ../../media:/app/media + depends_on: + fyp-redis-prod: + condition: service_healthy + fyp-postgres-prod: + condition: service_healthy + networks: + - fyp-network + + fyp-watchtower-prod: + container_name: fyp-watchtower-prod + image: containrrr/watchtower + command: + - "--scope=fyp" + - "--label-enable" + - "--interval" + - "30" + - "--rolling-restart" + environment: + - WATCHTOWER_CLEANUP=true + - REPO_USER=${GITLAB_USER} + - REPO_PASS=${GITLAB_PASS} + volumes: + - "/var/run/docker.sock:/var/run/docker.sock" + + fyp-runner-prod: + container_name: fyp-runner-prod + image: gitlab/gitlab-runner:${GITLAB_RUNNER_IMAGE_TAG} + restart: unless-stopped + environment: + - CI_SERVER_URL=${GITLAB_SERVER_URL} + - REGISTRATION_TOKEN=${GITLAB_RUNNER_REGISTRATION_TOKEN} + - RUNNER_EXECUTOR=docker + - RUNNER_RUN_UNTAGGED=true + - RUNNER_TAG_LIST= + - DOCKER_TLS_CERTDIR= + - DOCKER_IMAGE=${GITLAB_RUNNER_DOCKER_IMAGE} + volumes: + - gitlab-runner-config:/etc/gitlab-runner + - gitlab-machine-config:/root/.docker/machine + - /var/run/docker.sock:/var/run/docker.sock + command: + - run + - "--working-directory=/home/gitlab-runner" + +volumes: + fyp_postgres_data: + fyp_redis_data: + gitlab-runner-config: + gitlab-machine-config: + +networks: + fyp-network: + driver: bridge + proxy: + external: true diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..fb989c4 --- /dev/null +++ b/config/__init__.py @@ -0,0 +1,3 @@ +from .celery import app as celery_app + +__all__ = ('celery_app',) diff --git a/config/api.py b/config/api.py new file mode 100644 index 0000000..275c897 --- /dev/null +++ b/config/api.py @@ -0,0 +1,18 @@ +from rest_framework.routers import DefaultRouter + +from apps.accounts.viewsets import UserViewSet, OrganizationViewSet +from apps.knowledge.viewsets import TrainingFileViewSet, RoleRagDocumentViewSet +from apps.onboarding.viewsets import AgentConfigViewSet, OnboardingFlowViewSet, OnboardingSessionViewSet, AgentInteractionLogViewSet + +router = DefaultRouter() + +router.register(r'user', UserViewSet) +router.register(r'organization', OrganizationViewSet) +router.register(r'training-file', TrainingFileViewSet) +router.register(r'role-rag-document', RoleRagDocumentViewSet) +router.register(r'agent-config', AgentConfigViewSet) +router.register(r'onboarding-flow', OnboardingFlowViewSet) +router.register(r'onboarding-session', OnboardingSessionViewSet) +router.register(r'agent-interaction-log', AgentInteractionLogViewSet) + +urlpatterns = router.urls \ No newline at end of file diff --git a/config/asgi.py b/config/asgi.py new file mode 100644 index 0000000..c9828d1 --- /dev/null +++ b/config/asgi.py @@ -0,0 +1,21 @@ +import os + +from django.core.asgi import get_asgi_application + +from channels.auth import AuthMiddlewareStack +from channels.routing import ProtocolTypeRouter, URLRouter +from channels.security.websocket import AllowedHostsOriginValidator +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') + +django_asgi_app = get_asgi_application() + +from apps.onboarding.routing import websocket_urlpatterns + +application = ProtocolTypeRouter({ + "http": django_asgi_app, + "websocket": AllowedHostsOriginValidator( + AuthMiddlewareStack( + URLRouter(websocket_urlpatterns) + ) + ) +}) diff --git a/config/celery.py b/config/celery.py new file mode 100644 index 0000000..5e2b5f9 --- /dev/null +++ b/config/celery.py @@ -0,0 +1,8 @@ +from celery import Celery +import os + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') + +app = Celery('config') +app.config_from_object('django.conf:settings', namespace='CELERY') +app.autodiscover_tasks() \ No newline at end of file diff --git a/config/settings.py b/config/settings.py new file mode 100644 index 0000000..99c5a5a --- /dev/null +++ b/config/settings.py @@ -0,0 +1,210 @@ +""" +Django settings will use prefix of DJANGO_ for environment variables. +""" + +import os +from pathlib import Path +import sys +from dotenv import load_dotenv + +BASE_DIR = Path(__file__).resolve().parent.parent + +load_dotenv(dotenv_path = BASE_DIR / '.env') + +FRONT_DIR = os.getenv('DJANGO_FRONT_DIR', BASE_DIR / 'front') +MODEL_DIR = os.getenv('DJANGO_MODEL_DIR', BASE_DIR / 'model') + +SECRET_KEY = os.getenv('DJANGO_SECRET_KEY') +DEBUG = str(os.getenv('DJANGO_DEBUG')).lower() in ('1', 'true', 'yes', 'on') + +DOMAIN_NAME = os.getenv('DJANGO_DOMAIN_NAME', 'localhost') +ALLOWED_HOSTS = [stripped_host for host in os.getenv('DJANGO_ALLOWED_HOSTS', 'localhost').split(',') if (stripped_host:=host.strip())] + +PARENT_NAME = Path(__file__).resolve().parent.name + +DJANGO_CELERY_BROKER_URL = os.getenv('DJANGO_CELERY_BROKER_URL', 'redis://localhost:6379/0') + +INFERENCE_HOST = os.getenv('INFERENCE_HOST', 'localhost') +INFERENCE_PORT = os.getenv('INFERENCE_PORT', '8001') +INFERENCE_URL = f"http://{INFERENCE_HOST}:{INFERENCE_PORT}" +INFERENCE_INGEST_TIMEOUT = float(os.getenv('INFERENCE_INGEST_TIMEOUT', '600')) + +STATIC_URL = os.getenv('DJANGO_STATIC_URL', '/static/') +MEDIA_URL = os.getenv('DJANGO_MEDIA_URL', '/media/') +STATIC_ROOT = os.getenv('DJANGO_STATIC_ROOT', BASE_DIR / 'static') +MEDIA_ROOT = os.getenv('DJANGO_MEDIA_ROOT', BASE_DIR / 'media') + +DB_ENGINE = os.getenv('DJANGO_DB_ENGINE', 'django.db.backends.sqlite3') +DB_NAME = os.getenv('POSTGRES_DB', BASE_DIR / 'db.sqlite3') +DB_USER = os.getenv('POSTGRES_USER') +DB_PASSWORD = os.getenv('POSTGRES_PASSWORD') +DB_HOST = os.getenv('POSTGRES_HOST') +DB_PORT = os.getenv('POSTGRES_PORT', 5432) + +if any(arg.startswith('test') for arg in sys.argv): + DB_ENGINE = 'django.db.backends.sqlite3' + DB_NAME = ':memory:' + DB_USER = None + DB_PASSWORD = None + DB_HOST = None + DB_PORT = None + +OVERRIDE_APPS = [ + + + 'daphne', +] +DJANGO_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] +THIRD_PARTY_APPS = [ + 'rest_framework', + 'channels', + 'django_celery_results', + 'corsheaders', +] +LOCAL_APPS = [ + 'apps.accounts', + 'apps.onboarding', + 'apps.knowledge', +] +INSTALLED_APPS = OVERRIDE_APPS + DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS + +AUTH_USER_MODEL = 'accounts.User' +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'whitenoise.middleware.WhiteNoiseMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = f'{PARENT_NAME}.urls' +WSGI_APPLICATION = f'{PARENT_NAME}.wsgi.application' +ASGI_APPLICATION = f'{PARENT_NAME}.asgi.application' + +CHANNEL_LAYERS = { + 'default': { + 'BACKEND': 'channels_redis.core.RedisChannelLayer', + 'CONFIG': { + 'hosts': [DJANGO_CELERY_BROKER_URL], + }, + }, +} + +SESSION_ENGINE = 'django.contrib.sessions.backends.db' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +DATABASES = { + 'default': { + 'ENGINE': DB_ENGINE, + 'NAME': DB_NAME, + } if DB_ENGINE == 'django.db.backends.sqlite3' else { + 'ENGINE': DB_ENGINE, + 'NAME': DB_NAME, + 'USER': DB_USER, + 'PASSWORD': DB_PASSWORD, + 'HOST': DB_HOST, + 'PORT': DB_PORT, + 'CONN_MAX_AGE': 600, + } +} + +STORAGES = { + "default": { + "BACKEND": "django.core.files.storage.FileSystemStorage", + }, + "staticfiles": { + "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", + }, +} + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +LANGUAGE_CODE = 'en-uk' +TIME_ZONE = 'UTC' +USE_I18N = True +USE_TZ = True + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.SessionAuthentication', + 'rest_framework.authentication.BasicAuthentication', + ], + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.AllowAny', + ], +} + +CELERY_BROKER_URL = DJANGO_CELERY_BROKER_URL +CELERY_RESULT_BACKEND = 'django-db' +CELERY_CACHE_BACKEND = 'django-cache' +CELERY_ACCEPT_CONTENT = ['json'] +CELERY_TASK_SERIALIZER = 'json' +CELERY_RESULT_SERIALIZER = 'json' +CELERY_TIMEZONE = 'UTC' +CELERY_TASK_TRACK_STARTED = True +CELERY_TASK_TIME_LIMIT = 30 * 60 + +X_FRAME_OPTIONS = 'SAMEORIGIN' +CORS_ALLOW_CREDENTIALS = True +CORS_ALLOWED_ORIGINS = [ + f'http://{DOMAIN_NAME}', + f'https://{DOMAIN_NAME}', +] +CSRF_TRUSTED_ORIGINS = [ + f'http://{DOMAIN_NAME}', + f'https://{DOMAIN_NAME}', +] +CSRF_COOKIE_HTTPONLY = False +CSRF_COOKIE_SECURE = not DEBUG +CSRF_COOKIE_SAMESITE = 'Lax' +SESSION_COOKIE_SAMESITE = 'Lax' +SESSION_COOKIE_HTTPONLY = True +SESSION_COOKIE_SECURE = not DEBUG +SESSION_COOKIE_AGE = 1209600 +SESSION_SAVE_EVERY_REQUEST = True + +if DEBUG: + CORS_ALLOWED_ORIGINS.append(f'http://{DOMAIN_NAME}:5173') + CORS_ALLOWED_ORIGINS.append(f'http://{DOMAIN_NAME}:8000') + CSRF_TRUSTED_ORIGINS.append(f'http://{DOMAIN_NAME}:5173') + CSRF_TRUSTED_ORIGINS.append(f'http://{DOMAIN_NAME}:8000') diff --git a/config/urls.py b/config/urls.py new file mode 100644 index 0000000..1234221 --- /dev/null +++ b/config/urls.py @@ -0,0 +1,14 @@ +from django.contrib import admin +from django.urls import path, include, re_path +from django.conf import settings +from django.conf.urls.static import static + +from .views import serve_frontend + +urlpatterns = [ + path('admin/', admin.site.urls), + path('api/', include('config.api')), + re_path(r'^(?!static/|media/)(?P.*)$', serve_frontend, {'document_root': settings.FRONT_DIR}), + *static(settings.STATIC_URL, document_root=settings.STATIC_ROOT), + *static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT), +] \ No newline at end of file diff --git a/config/views.py b/config/views.py new file mode 100644 index 0000000..726165e --- /dev/null +++ b/config/views.py @@ -0,0 +1,16 @@ +import posixpath +from pathlib import Path + +from django.utils._os import safe_join +from django.views.static import serve as static_serve +from django.views.decorators.csrf import ensure_csrf_cookie + +@ensure_csrf_cookie +def serve_frontend(request, path, document_root = None): + print(f"Serving path: {path} from {document_root}") + path = posixpath.normpath(path).lstrip("/") + fullpath = Path(safe_join(document_root, path)) + if fullpath.is_file(): + return static_serve(request, path, document_root) + else: + return static_serve(request, "index.html", document_root) diff --git a/config/wsgi.py b/config/wsgi.py new file mode 100644 index 0000000..160e932 --- /dev/null +++ b/config/wsgi.py @@ -0,0 +1,5 @@ +import os +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') +application = get_wsgi_application() \ No newline at end of file diff --git a/data/1_users.json b/data/1_users.json new file mode 100644 index 0000000..0465ca2 --- /dev/null +++ b/data/1_users.json @@ -0,0 +1,254 @@ +[ +{ + "model": "accounts.user", + "pk": 1, + "fields": { + "password": "pbkdf2_sha256$1200000$k8zzmUMrpF0onsHIygd72k$7Ax/w0HZDaRw48pului25uKeM+115wXj6H3MLjvRGUE=", + "last_login": null, + "is_superuser": false, + "uuid": "d8c80f82-b668-4035-a8ac-dfc8f0985f5d", + "created_at": "2026-02-24T19:35:49.084Z", + "updated_at": "2026-02-24T19:35:49.084Z", + "email_address": "admin@example.com", + "first_name": "Ad", + "last_name": "Min", + "date_of_birth": "2001-01-01", + "is_active": true, + "is_staff": true, + "is_manager": true, + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "accounts.user", + "pk": 2, + "fields": { + "password": "pbkdf2_sha256$1200000$SSYxNyuMktzVMLlQteB95g$KCjU5+OHkufDiWIZAahYP7JFPGegXCwv6NsEUXnX+gY=", + "last_login": null, + "is_superuser": false, + "uuid": "4039d920-3cbf-4a49-9be0-196dda6f33a1", + "created_at": "2026-02-24T19:35:11.034Z", + "updated_at": "2026-02-24T19:35:11.034Z", + "email_address": "haleisaac@example.com", + "first_name": "Hale", + "last_name": "Isaac", + "date_of_birth": "1983-10-12", + "is_active": true, + "is_staff": false, + "is_manager": true, + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "accounts.user", + "pk": 3, + "fields": { + "password": "pbkdf2_sha256$1200000$k8zzmUMrpF0onsHIygd72k$7Ax/w0HZDaRw48pului25uKeM+115wXj6H3MLjvRGUE=", + "last_login": null, + "is_superuser": false, + "uuid": "e29c8b74-29c3-4d7b-9f9c-7c89d21a9e33", + "created_at": "2026-02-25T10:00:00.000Z", + "updated_at": "2026-02-25T10:00:00.000Z", + "email_address": "sarah.chen@example.com", + "first_name": "Sarah", + "last_name": "Chen", + "date_of_birth": "1995-04-14", + "is_active": true, + "is_staff": false, + "is_manager": false, + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "accounts.user", + "pk": 4, + "fields": { + "password": "pbkdf2_sha256$1200000$k8zzmUMrpF0onsHIygd72k$7Ax/w0HZDaRw48pului25uKeM+115wXj6H3MLjvRGUE=", + "last_login": null, + "is_superuser": false, + "uuid": "bc731298-f23a-4932-8412-192837465ab1", + "created_at": "2026-02-25T10:05:00.000Z", + "updated_at": "2026-02-25T10:05:00.000Z", + "email_address": "marcus.v@example.com", + "first_name": "Marcus", + "last_name": "Vance", + "date_of_birth": "1988-11-22", + "is_active": true, + "is_staff": false, + "is_manager": false, + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "accounts.user", + "pk": 5, + "fields": { + "password": "pbkdf2_sha256$1200000$k8zzmUMrpF0onsHIygd72k$7Ax/w0HZDaRw48pului25uKeM+115wXj6H3MLjvRGUE=", + "last_login": null, + "is_superuser": false, + "uuid": "5f9e1c2d-8a4b-47e6-b3a1-9c8d7e6f5a4b", + "created_at": "2026-02-25T10:10:00.000Z", + "updated_at": "2026-02-25T10:10:00.000Z", + "email_address": "elara.smith@example.com", + "first_name": "Elara", + "last_name": "Smith", + "date_of_birth": "2002-06-30", + "is_active": true, + "is_staff": false, + "is_manager": true, + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "accounts.user", + "pk": 6, + "fields": { + "password": "pbkdf2_sha256$1200000$k8zzmUMrpF0onsHIygd72k$7Ax/w0HZDaRw48pului25uKeM+115wXj6H3MLjvRGUE=", + "last_login": null, + "is_superuser": false, + "uuid": "7a8b9c0d-1e2f-4a5b-bcde-f0123456789a", + "created_at": "2026-02-25T10:15:00.000Z", + "updated_at": "2026-02-25T10:15:00.000Z", + "email_address": "j.thompson@example.com", + "first_name": "James", + "last_name": "Thompson", + "date_of_birth": "1990-01-15", + "is_active": true, + "is_staff": false, + "is_manager": false, + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "accounts.user", + "pk": 7, + "fields": { + "password": "pbkdf2_sha256$1200000$k8zzmUMrpF0onsHIygd72k$7Ax/w0HZDaRw48pului25uKeM+115wXj6H3MLjvRGUE=", + "last_login": null, + "is_superuser": false, + "uuid": "11223344-5566-7788-99aa-bbccddeeff00", + "created_at": "2026-02-25T10:20:00.000Z", + "updated_at": "2026-02-25T10:20:00.000Z", + "email_address": "priya.p@example.com", + "first_name": "Priya", + "last_name": "Patel", + "date_of_birth": "1997-08-05", + "is_active": true, + "is_staff": false, + "is_manager": false, + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "accounts.user", + "pk": 8, + "fields": { + "password": "pbkdf2_sha256$1200000$k8zzmUMrpF0onsHIygd72k$7Ax/w0HZDaRw48pului25uKeM+115wXj6H3MLjvRGUE=", + "last_login": null, + "is_superuser": false, + "uuid": "ffeeddcc-bbaa-9988-7766-554433221100", + "created_at": "2026-02-25T10:25:00.000Z", + "updated_at": "2026-02-25T10:25:00.000Z", + "email_address": "lewis.hamil@example.com", + "first_name": "Lewis", + "last_name": "Hamilton", + "date_of_birth": "1985-01-07", + "is_active": true, + "is_staff": false, + "is_manager": false, + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "accounts.user", + "pk": 9, + "fields": { + "password": "pbkdf2_sha256$1200000$k8zzmUMrpF0onsHIygd72k$7Ax/w0HZDaRw48pului25uKeM+115wXj6H3MLjvRGUE=", + "last_login": null, + "is_superuser": false, + "uuid": "abcdef01-2345-6789-abcd-ef0123456789", + "created_at": "2026-02-25T10:30:00.000Z", + "updated_at": "2026-02-25T10:30:00.000Z", + "email_address": "nina.simone@example.com", + "first_name": "Nina", + "last_name": "Simone", + "date_of_birth": "2000-12-12", + "is_active": true, + "is_staff": false, + "is_manager": true, + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "accounts.user", + "pk": 10, + "fields": { + "password": "pbkdf2_sha256$1200000$k8zzmUMrpF0onsHIygd72k$7Ax/w0HZDaRw48pului25uKeM+115wXj6H3MLjvRGUE=", + "last_login": null, + "is_superuser": false, + "uuid": "99887766-5544-3322-1100-abcdefabcdef", + "created_at": "2026-02-25T10:35:00.000Z", + "updated_at": "2026-02-25T10:35:00.000Z", + "email_address": "oscar.w@example.com", + "first_name": "Oscar", + "last_name": "Wilder", + "date_of_birth": "1992-03-22", + "is_active": true, + "is_staff": false, + "is_manager": false, + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "accounts.user", + "pk": 11, + "fields": { + "password": "pbkdf2_sha256$1200000$k8zzmUMrpF0onsHIygd72k$7Ax/w0HZDaRw48pului25uKeM+115wXj6H3MLjvRGUE=", + "last_login": null, + "is_superuser": false, + "uuid": "44332211-8877-6655-4433-221100998877", + "created_at": "2026-02-25T10:40:00.000Z", + "updated_at": "2026-02-25T10:40:00.000Z", + "email_address": "claire.d@example.com", + "first_name": "Claire", + "last_name": "Danes", + "date_of_birth": "1994-05-19", + "is_active": true, + "is_staff": false, + "is_manager": false, + "groups": [], + "user_permissions": [] + } +}, +{ + "model": "accounts.user", + "pk": 12, + "fields": { + "password": "pbkdf2_sha256$1200000$k8zzmUMrpF0onsHIygd72k$7Ax/w0HZDaRw48pului25uKeM+115wXj6H3MLjvRGUE=", + "last_login": null, + "is_superuser": false, + "uuid": "00aa11bb-22cc-33dd-44ee-55ff66aa77bb", + "created_at": "2026-02-25T10:45:00.000Z", + "updated_at": "2026-02-25T10:45:00.000Z", + "email_address": "dev.tester@example.com", + "first_name": "Dev", + "last_name": "Tester", + "date_of_birth": "1999-09-09", + "is_active": true, + "is_staff": false, + "is_manager": true, + "groups": [], + "user_permissions": [] + } +} +] diff --git a/data/2_organizations.json b/data/2_organizations.json new file mode 100644 index 0000000..cd14085 --- /dev/null +++ b/data/2_organizations.json @@ -0,0 +1,41 @@ +[ +{ + "model": "accounts.organization", + "pk": 1, + "fields": { + "uuid": "314324a0-9fda-4579-ad90-96123b187f97", + "created_at": "2026-02-24T23:03:53.518Z", + "updated_at": "2026-02-25T20:30:00.000Z", + "name": "University of Birmingham", + "description": "The University of Birmingham is a public research university in Birmingham, England. It is a founding member of the Russell Group and the international network Universitas 21.", + "owner": 2, + "members": [2, 3, 4, 5, 6, 7] + } +}, +{ + "model": "accounts.organization", + "pk": 2, + "fields": { + "uuid": "71c14993-e1a6-448a-af2c-17f50043f545", + "created_at": "2026-02-24T23:04:55.810Z", + "updated_at": "2026-02-25T20:30:00.000Z", + "name": "Example Organization", + "description": "This is an example fictional organization that has many roles, used for testing administrative workflows.", + "owner": 1, + "members": [1, 2, 8, 9, 12] + } +}, +{ + "model": "accounts.organization", + "pk": 3, + "fields": { + "uuid": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", + "created_at": "2026-02-25T20:30:00.000Z", + "updated_at": "2026-02-25T20:30:00.000Z", + "name": "Silicon Canal Tech Hub", + "description": "A collective of technology innovators and fintech enthusiasts based in the West Midlands, focusing on financial literacy and stock market simulation.", + "owner": 12, + "members": [12, 3, 5, 9, 10, 11] + } +} +] \ No newline at end of file diff --git a/data/3_roles.json b/data/3_roles.json new file mode 100644 index 0000000..42f2698 --- /dev/null +++ b/data/3_roles.json @@ -0,0 +1,106 @@ +[ +{ + "model": "accounts.role", + "pk": 1, + "fields": { + "uuid": "51a671c3-e680-4d81-af39-194939313b93", + "created_at": "2026-02-25T12:51:23.873Z", + "updated_at": "2026-02-25T12:51:23.873Z", + "name": "UX Developer", + "description": "A hybrid professional bridging design and front-end engineering, responsible for both designing user-centric interfaces and coding them.", + "organization": 2, + "members": [1, 4] + } +}, +{ + "model": "accounts.role", + "pk": 2, + "fields": { + "uuid": "72b8d1a4-f791-4e92-bc40-205040424c04", + "created_at": "2026-02-25T13:00:00.000Z", + "updated_at": "2026-02-25T13:00:00.000Z", + "name": "fNIRS Specialist", + "description": "Functional Near-Infrared Spectroscopy Specialist responsible for neuroimaging data collection and analyzing cortical hemodynamic responses.", + "organization": 1, + "members": [3, 5] + } +}, +{ + "model": "accounts.role", + "pk": 3, + "fields": { + "uuid": "83c9e2b5-a802-5f03-cd51-316151535d15", + "created_at": "2026-02-25T13:05:00.000Z", + "updated_at": "2026-02-25T13:05:00.000Z", + "name": "Senior Research Fellow", + "description": "Leads academic research projects, secures funding, and mentors doctoral students within the University research ecosystem.", + "organization": 1, + "members": [2, 7] + } +}, +{ + "model": "accounts.role", + "pk": 4, + "fields": { + "uuid": "94d0f3c6-a913-6f14-de62-427262646e26", + "created_at": "2026-02-25T13:10:00.000Z", + "updated_at": "2026-02-25T13:10:00.000Z", + "name": "Quantitative Analyst", + "description": "Applies mathematical and statistical methods to financial and risk management problems within the stock simulation model.", + "organization": 2, + "members": [8, 9] + } +}, +{ + "model": "accounts.role", + "pk": 5, + "fields": { + "uuid": "a5e1f4d7-a024-4e25-ef73-538373757f37", + "created_at": "2026-02-25T13:15:00.000Z", + "updated_at": "2026-02-25T13:15:00.000Z", + "name": "Systems Administrator", + "description": "Responsible for the maintenance, configuration, and reliable operation of the organization's server infrastructure.", + "organization": 2, + "members": [1, 12] + } +}, +{ + "model": "accounts.role", + "pk": 6, + "fields": { + "uuid": "b6f2e5e8-a135-4f36-fa84-649484868f48", + "created_at": "2026-02-25T13:20:00.000Z", + "updated_at": "2026-02-25T13:20:00.000Z", + "name": "Lead Software Architect", + "description": "Responsible for high-level design choices and dictating technical standards, including software coding standards.", + "organization": 3, + "members": [12, 11] + } +}, +{ + "model": "accounts.role", + "pk": 7, + "fields": { + "uuid": "c7f3e6f9-a246-4a47-fa95-750595979f59", + "created_at": "2026-02-25T13:25:00.000Z", + "updated_at": "2026-02-25T13:25:00.000Z", + "name": "FinTech Researcher", + "description": "Investigates new technologies in the financial sector, focusing on algorithmic trading and user behavior.", + "organization": 3, + "members": [9, 10, 3] + } +}, +{ + "model": "accounts.role", + "pk": 8, + "fields": { + "uuid": "d8e4f7f0-a357-4e58-ea06-861606080e60", + "created_at": "2026-02-25T13:30:00.000Z", + "updated_at": "2026-02-25T13:30:00.000Z", + "name": "Compliance Officer", + "description": "Ensures that the organization is complying with relevant financial regulations and internal policies.", + "organization": 2, + "members": [6, 8] + } +} +] \ No newline at end of file diff --git a/data/4_agentconfigs.json b/data/4_agentconfigs.json new file mode 100644 index 0000000..cc50027 --- /dev/null +++ b/data/4_agentconfigs.json @@ -0,0 +1,137 @@ +[ + { + "model": "onboarding.agentconfig", + "pk": 1, + "fields": { + "uuid": "b8ecc397-ac65-4395-9502-b3232ee640e2", + "created_at": "2026-02-25T12:51:23.874Z", + "updated_at": "2026-02-25T20:45:00.000Z", + "organization": 2, + "name": "UX Developer Curriculum Agent", + "agent_type": "curriculum", + "llm_config": {"model_id": "meta-llama-3.1-8b-instruct"}, + "system_prompt": "You are a Senior UX Engineering Mentor. Design a learning path for a UX Developer that bridges the gap between Figma design systems and React component architecture. Focus on accessibility (WCAG), micro-interactions, and state-driven UI patterns.", + "tool_permissions": [] + } + }, + { + "model": "onboarding.agentconfig", + "pk": 2, + "fields": { + "uuid": "33221dd5-3ef9-49a7-98ff-182f3b76f3e6", + "created_at": "2026-02-25T12:51:23.875Z", + "updated_at": "2026-02-25T20:45:00.000Z", + "organization": 2, + "name": "UX Developer Knowledge Agent", + "agent_type": "knowledge", + "llm_config": {"model_id": "meta-llama-3.1-8b-instruct"}, + "system_prompt": "You are a UX Documentation Assistant. Provide technical answers regarding front-end frameworks, CSS-in-JS libraries, and usability testing methodologies. Contextualize answers within the organization's specific design tokens.", + "tool_permissions": [] + } + }, + { + "model": "onboarding.agentconfig", + "pk": 3, + "fields": { + "uuid": "6d50f77a-27d7-4763-8eb8-97b170dde3da", + "created_at": "2026-02-25T12:51:23.875Z", + "updated_at": "2026-02-25T20:45:00.000Z", + "organization": 2, + "name": "UX Developer Assessment Agent", + "agent_type": "assessment", + "llm_config": {"model_id": "meta-llama-3.1-8b-instruct"}, + "system_prompt": "You are a Technical Interviewer for UX Engineers. Generate coding challenges and design critiques that evaluate a user's ability to implement responsive layouts and perform semantic code reviews.", + "tool_permissions": [] + } + }, + { + "model": "onboarding.agentconfig", + "pk": 4, + "fields": { + "uuid": "7ca8091f-ee44-4f03-8abb-95e3d53d5f82", + "created_at": "2026-02-25T12:51:23.875Z", + "updated_at": "2026-02-25T20:45:00.000Z", + "organization": 2, + "name": "UX Developer Progress Monitor", + "agent_type": "monitor", + "llm_config": {"model_id": "meta-llama-3.1-8b-instruct"}, + "system_prompt": "You are a UX Team Lead. Track the completion of design-to-code modules. Identify areas where the developer struggles with specific UI frameworks and suggest remedial design sprints.", + "tool_permissions": [] + } + }, + { + "model": "onboarding.agentconfig", + "pk": 5, + "fields": { + "uuid": "f29a1b2c-3d4e-5f6a-7b8c-9d0e1f2a3b4c", + "created_at": "2026-02-25T20:45:00.000Z", + "updated_at": "2026-02-25T20:45:00.000Z", + "organization": 1, + "name": "fNIRS Specialist Curriculum Agent", + "agent_type": "curriculum", + "llm_config": {"model_id": "meta-llama-3.1-8b-instruct"}, + "system_prompt": "You are a Neuroimaging Professor. Design a curriculum covering the physics of near-infrared light, optode placement (10-20 system), and the modified Beer-Lambert law. Focus on artifact rejection and signal processing for cortical activation.", + "tool_permissions": [] + } + }, + { + "model": "onboarding.agentconfig", + "pk": 6, + "fields": { + "uuid": "e30b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d", + "created_at": "2026-02-25T20:45:00.000Z", + "updated_at": "2026-02-25T20:45:00.000Z", + "organization": 1, + "name": "fNIRS Specialist Knowledge Agent", + "agent_type": "knowledge", + "llm_config": {"model_id": "meta-llama-3.1-8b-instruct"}, + "system_prompt": "You are an fNIRS Lab Assistant. Answer technical queries regarding Homer3, NIRS-Toolbox, and real-time data streaming. Provide troubleshooting steps for high-impedance channels and motion artifacts.", + "tool_permissions": [] + } + }, + { + "model": "onboarding.agentconfig", + "pk": 7, + "fields": { + "uuid": "d41c3d4e-5f6a-7b8c-9d0e-1f2a3b4c5d6e", + "created_at": "2026-02-25T20:45:00.000Z", + "updated_at": "2026-02-25T20:45:00.000Z", + "organization": 1, + "name": "fNIRS Specialist Assessment Agent", + "agent_type": "assessment", + "llm_config": {"model_id": "meta-llama-3.1-8b-instruct"}, + "system_prompt": "You are a Research Evaluator. Create examination questions that require the student to interpret fNIRS heatmaps and calculate oxygenated vs deoxygenated hemoglobin concentrations from raw OD data.", + "tool_permissions": [] + } + }, + { + "model": "onboarding.agentconfig", + "pk": 8, + "fields": { + "uuid": "c52d4e5f-6a7b-8c9d-0e1f-2a3b4c5d6e7f", + "created_at": "2026-02-25T20:45:00.000Z", + "updated_at": "2026-02-25T20:45:00.000Z", + "organization": 1, + "name": "fNIRS Specialist Progress Monitor", + "agent_type": "monitor", + "llm_config": {"model_id": "meta-llama-3.1-8b-instruct"}, + "system_prompt": "You are a Principal Investigator. Monitor the student's mastery of fNIRS data collection protocols. Ensure they pass safety and calibration milestones before proceeding to human participant trials.", + "tool_permissions": [] + } + }, + { + "model": "onboarding.agentconfig", + "pk": 9, + "fields": { + "uuid": "b63e5f6a-7b8c-9d0e-1f2a-3b4c5d6e7f8a", + "created_at": "2026-02-25T20:45:00.000Z", + "updated_at": "2026-02-25T20:45:00.000Z", + "organization": 2, + "name": "Quant Analyst Curriculum Agent", + "agent_type": "curriculum", + "llm_config": {"model_id": "meta-llama-3.1-8b-instruct"}, + "system_prompt": "You are a Financial Engineering Lead. Design a training path focused on stochastic calculus, Monte Carlo simulations, and Black-Scholes modeling. Emphasize the implementation of these models in Python using NumPy and Pandas.", + "tool_permissions": [] + } + } +] \ No newline at end of file diff --git a/gpu_server.py b/gpu_server.py new file mode 100644 index 0000000..3dc2572 --- /dev/null +++ b/gpu_server.py @@ -0,0 +1,232 @@ +import logging +import os +import json +from contextlib import asynccontextmanager +from typing import Dict, Any + +import numpy as np +import torch +import torch.nn.functional as F +from fastapi import FastAPI, Request, HTTPException +from fastapi.responses import StreamingResponse +from llama_cpp import Llama +from sentence_transformers import SentenceTransformer + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(name)s: %(message)s" +) +logger = logging.getLogger("gpu-node") + +EMBED_MODEL_NAME = "nomic-ai/nomic-embed-text-v1.5" +LLM_MODEL_PATH = os.getenv("LLM_MODEL_PATH", "/app/models/Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf") +TARGET_DIMENSIONS = 1536 + +state: Dict[str, Any] = {} + +@asynccontextmanager +async def lifespan(app: FastAPI): + """Handles GPU model loading and cleanup.""" + device = "cuda" if torch.cuda.is_available() else "cpu" + logger.info(f"--- Initializing GPU Node on {device} ---") + + if device == "cpu": + logger.warning("CUDA NOT DETECTED. Performance will be severely degraded.") + + try: + # Load Embedding Model (Nomic) + logger.info(f"Loading Embedding Model: {EMBED_MODEL_NAME}") + state["embed_model"] = SentenceTransformer( + EMBED_MODEL_NAME, + trust_remote_code=True, + device=device + ) + + # Load Llama Model (GGUF) + if not os.path.exists(LLM_MODEL_PATH): + logger.error(f"LLM File not found at {LLM_MODEL_PATH}") + else: + logger.info(f"Loading LLM: {LLM_MODEL_PATH}") + state["llm"] = Llama( + model_path=LLM_MODEL_PATH, + n_gpu_layers=-1, # Offload all layers to GPU + n_ctx=8192, + n_batch=512, + verbose=False + ) + + logger.info("--- GPU Node Ready ---") + except Exception as e: + logger.error(f"Failed to load models: {e}") + raise e + + yield + + # Cleanup + state.clear() + if torch.cuda.is_available(): + torch.cuda.empty_cache() + +app = FastAPI(title="Agentic GPU Node", lifespan=lifespan) + + +def pad_and_normalize(embeddings: torch.Tensor) -> torch.Tensor: + """Standardizes vector dimensions to 1536 for pgvector compatibility.""" + curr_dim = embeddings.shape[1] + if curr_dim < TARGET_DIMENSIONS: + embeddings = F.pad(embeddings, (0, TARGET_DIMENSIONS - curr_dim), "constant", 0) + elif curr_dim > TARGET_DIMENSIONS: + embeddings = embeddings[:, :TARGET_DIMENSIONS] + return F.normalize(embeddings, p=2, dim=1) + + +@app.post("/v1/embeddings") +async def embeddings(request: Request): + """Generates text embeddings compatible with OpenAI API format.""" + data = await request.json() + input_data = data.get("input", "") + + if isinstance(input_data, str): + inputs = [input_data] + elif isinstance(input_data, list): + inputs = [str(item) for item in input_data if str(item).strip()] + else: + raise HTTPException(status_code=400, detail="'input' must be a string or list of strings") + + if not inputs: + return { + "object": "list", + "data": [], + "model": EMBED_MODEL_NAME, + "usage": {"prompt_tokens": 0, "total_tokens": 0}, + } + + model = state.get("embed_model") + if model is None: + raise HTTPException(status_code=503, detail="Embedding model not initialized") + + prefixed_inputs = [ + text if text.startswith("search_") else f"search_query: {text}" + for text in inputs + ] + + with torch.no_grad(): + vectors = model.encode(prefixed_inputs, convert_to_tensor=True) + vectors = pad_and_normalize(vectors) + + vector_list = vectors.cpu().tolist() + + return { + "object": "list", + "data": [ + { + "object": "embedding", + "index": idx, + "embedding": embedding, + } + for idx, embedding in enumerate(vector_list) + ], + "model": EMBED_MODEL_NAME, + "usage": { + "prompt_tokens": sum(len(text.split()) for text in inputs), + "total_tokens": sum(len(text.split()) for text in inputs), + }, + } + +@app.post("/v1/semantic-chunk") +async def semantic_chunk(request: Request): + """Processes raw text into semantically cohesive blocks.""" + data = await request.json() + raw_text = data.get("text", "") + threshold_percentile = data.get("threshold", 95) + + if not raw_text: + return {"chunks": [], "embeddings": []} + + if len(raw_text) > 50000: + raise HTTPException(status_code=413, detail="Text block too large. Please batch on the client.") + + model = state.get("embed_model") + if model is None: + raise HTTPException(status_code=503, detail="Embedding model not initialized") + + # Split by sentences + sentences = [s.strip() for s in raw_text.replace('\n', ' ').split('. ') if s.strip()] + if len(sentences) < 2: + return { + "chunks": [raw_text], + "embeddings": model.encode([f"search_document: {raw_text}"]).tolist() + } + + # Generate sentence embeddings to find breakpoints via cosine distance + s_embeddings = model.encode(sentences, convert_to_tensor=True) + distances = [ + 1 - F.cosine_similarity(s_embeddings[i].unsqueeze(0), s_embeddings[i+1].unsqueeze(0)).item() + for i in range(len(s_embeddings) - 1) + ] + + breakpoint_threshold = np.percentile(distances, threshold_percentile) + indices = [i for i, d in enumerate(distances) if d > breakpoint_threshold] + + chunks = [] + start = 0 + for idx in indices: + chunks.append(". ".join(sentences[start : idx + 1]) + ".") + start = idx + 1 + chunks.append(". ".join(sentences[start:]) + ".") + + with torch.no_grad(): + final_embeddings = model.encode( + [f"search_document: {c}" for c in chunks], + convert_to_tensor=True + ) + final_embeddings = pad_and_normalize(final_embeddings) + + return { + "chunks": chunks, + "embeddings": final_embeddings.cpu().tolist() + } + +@app.post("/v1/chat/completions") +async def chat_completions(request: Request): + """Unified LLM completion endpoint compatible with OpenAI-style requests.""" + data = await request.json() + messages = data.get("messages", []) + stream = data.get("stream", False) + + # Log incoming request details + logger.info(f"Chat completion request: {len(messages)} messages, stream={stream}") + + llm = state.get("llm") + if not llm: + raise HTTPException(status_code=503, detail="LLM not initialized or model file missing.") + + try: + response = llm.create_chat_completion( + messages=messages, + stream=stream, + temperature=data.get("temperature", 0.7), + max_tokens=data.get("max_tokens", 1024), + stop=["<|eot_id|>", "<|end_of_text|>"] + ) + + if stream: + return StreamingResponse( + llm_streamer(response), + media_type="text/event-stream" + ) + + return response + except Exception as e: + logger.error(f"Inference error: {e}") + raise HTTPException(status_code=500, detail=str(e)) + +async def llm_streamer(response_iterator): + """Iterates through llama-cpp generator and yields SSE chunks.""" + for chunk in response_iterator: + yield f"data: {json.dumps(chunk)}\n\n" + yield "data: [DONE]\n\n" + +if __name__ == "__main__": + import uvicorn + uvicorn.run("gpu_server:app", host="0.0.0.0", port=8001, reload=True) \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..1f62449 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ + +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/notebooks/external-model-testing.ipynb b/notebooks/external-model-testing.ipynb new file mode 100644 index 0000000..d354333 --- /dev/null +++ b/notebooks/external-model-testing.ipynb @@ -0,0 +1,146 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0910db83", + "metadata": {}, + "source": [ + "# Model Testing with GPT4ALL running locally" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "47cacfc9", + "metadata": {}, + "outputs": [], + "source": [ + "# Imports\n", + "import json\n", + "import requests" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "484cfebc", + "metadata": {}, + "outputs": [], + "source": [ + "# Variables for model response\n", + "API_URL = \"http://localhost:4891/v1/chat/completions\"\n", + "HEADERS = {\"Content-Type\": \"application/json\"}\n", + "MODEL = \"DeepSeek-R1-Distill-Qwen-7B\"\n", + "MAX_TOKENS = 2000\n", + "TEMPERATURE = 0.28" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "90b9b1f1", + "metadata": {}, + "outputs": [], + "source": [ + "content = \"Teach me computer vision\"\n", + "data = {\"model\": MODEL,\"messages\":[{\"role\":\"user\",\"content\": content}],\"max_tokens\": MAX_TOKENS,\"temperature\": TEMPERATURE}\n", + "response = requests.post(API_URL, json = data, headers=HEADERS)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "88a77498", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'{\"choices\":[{\"finish_reason\":\"stop\",\"index\":0,\"logprobs\":null,\"message\":{\"content\":\"\\\\n\\\\n\\\\n\\\\nComputer vision is a field of artificial intelligence that focuses on enabling computers to interpret and understand visual information from the world. It involves training algorithms, typically using deep learning techniques, to perform tasks such as object recognition, image segmentation, feature extraction, and more.\\\\n\\\\nHere’s an introduction to get you started:\\\\n\\\\n---\\\\n\\\\n### **1. What is Computer Vision?**\\\\nComputer vision mimics human visual perception by analyzing images or video data to extract meaningful information. It relies heavily on machine learning and deep learning techniques like convolutional neural networks (CNNs) to perform tasks such as:\\\\n- Object detection\\\\n- Image classification\\\\n- Face recognition\\\\n- Medical image analysis\\\\n- Autonomous vehicle navigation\\\\n\\\\n---\\\\n\\\\n### **2. Key Concepts in Computer Vision**\\\\n#### **Image Representation**\\\\n- **Pixels**: The basic unit of an image, represented by numerical values indicating color and brightness.\\\\n- **Channels**: Color images have multiple channels (e.g., RGB has red, green, blue channels).\\\\n\\\\n#### **Common Tasks**\\\\n1. **Object Detection**:\\\\n - Identify the presence and location of objects in an image.\\\\n - Example: Bounding box regression.\\\\n\\\\n2. **Classification**:\\\\n - Categorize images into predefined classes (e.g., cat vs. dog).\\\\n\\\\n3. **Segmentation**:\\\\n - Partition an image into segments, each representing a different object or region.\\\\n\\\\n4. **Feature Extraction**:\\\\n - Identify and extract relevant patterns from images for further analysis.\\\\n\\\\n---\\\\n\\\\n### **3. Tools and Libraries**\\\\nTo get started with computer vision, you’ll need tools like OpenCV (Open Source Computer Vision) or TensorFlow/Keras for building models.\\\\n\\\\n#### **OpenCV**\\\\n- A popular open-source library for image processing.\\\\n- Features:\\\\n - Image filtering\\\\n - Edge detection\\\\n - Object tracking\\\\n - Face recognition\\\\n\\\\n#### **TensorFlow/Keras**\\\\n- Frameworks built on top of TensorFlow, ideal for deep learning tasks.\\\\n- Easy to use and widely adopted.\\\\n\\\\n---\\\\n\\\\n### **4. Getting Started with Computer Vision**\\\\n\\\\n#### **Step 1: Learn the Basics**\\\\nStart by understanding fundamental concepts like pixels, image processing techniques, and basic computer vision algorithms (e.g., SIFT, HOG).\\\\n\\\\n#### **Step 2: Explore Datasets**\\\\nWork with common datasets:\\\\n- CIFAR-10/100\\\\n- MNIST (handwritten digits)\\\\n- COCO (common objects in context)\\\\n\\\\n#### **Step 3: Build Simple Models**\\\\nUse pre-trained models like ResNet or VGG to classify images. For example, you can train a model to recognize cats vs. dogs.\\\\n\\\\n#### **Step 4: Experiment with Deep Learning**\\\\nTune hyperparameters (learning rate, batch size) and explore techniques like data augmentation to improve model performance.\\\\n\\\\n---\\\\n\\\\n### **5. Resources for Learning**\\\\n- **Books**:\\\\n - *Deep Learning for Computer Vision* by Adrian Rosebrock\\\\n - *Computer Vision: Algorithms and Applications* by Richard Szeliski\\\\n\\\\n- **Tutorials/Documentation**:\\\\n - OpenCV官网文档 [https://docs.opencv.org](https://docs.opencv.org)\\\\n - TensorFlow/Keras官网文档 [https://www.tensorflow.org](https://www.tensorflow.org)\\\\n\\\\n- **Online Courses**:\\\\n - Coursera: \\\\\"Introduction to Computer Vision\\\\\" by Georgia Tech\\\\n - Udacity: \\\\\"Deep Learning for Computer Vision\\\\\"\\\\n - Fast.ai: Free, practical courses on computer vision.\\\\n\\\\n---\\\\n\\\\n### **6. Practice Projects**\\\\n1. **Object Detection**: Use YOLO or Mask R-CNN to detect objects in images.\\\\n2. **Image Classification**: Build a model that classifies images into predefined categories (e.g., flowers vs. vegetables).\\\\n3. **Face Recognition**: Implement face recognition using deep learning frameworks.\\\\n\\\\n---\\\\n\\\\n### **7. Keep Learning**\\\\n- Follow research papers on arXiv ([https://arxiv.org](https://arxiv.org)).\\\\n- Join communities like Reddit’s r/computervision or Stack Overflow.\\\\n- Experiment with cutting-edge models and techniques in computer vision.\\\\n\\\\n---\\\\n\\\\nWith practice and persistence, you’ll become proficient in computer vision. Start small, experiment, and most importantly, have fun!\",\"role\":\"assistant\"},\"references\":null}],\"created\":1768678056,\"id\":\"placeholder\",\"model\":\"DeepSeek-R1-Distill-Qwen-7B\",\"object\":\"chat.completion\",\"usage\":{\"completion_tokens\":861,\"prompt_tokens\":8,\"total_tokens\":869}}'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "response.text" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "c416905c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'choices': [{'finish_reason': 'stop',\n", + " 'index': 0,\n", + " 'logprobs': None,\n", + " 'message': {'content': '\\n\\n\\n\\nComputer vision is a field of artificial intelligence that focuses on enabling computers to interpret and understand visual information from the world. It involves training algorithms, typically using deep learning techniques, to perform tasks such as object recognition, image segmentation, feature extraction, and more.\\n\\nHere’s an introduction to get you started:\\n\\n---\\n\\n### **1. What is Computer Vision?**\\nComputer vision mimics human visual perception by analyzing images or video data to extract meaningful information. It relies heavily on machine learning and deep learning techniques like convolutional neural networks (CNNs) to perform tasks such as:\\n- Object detection\\n- Image classification\\n- Face recognition\\n- Medical image analysis\\n- Autonomous vehicle navigation\\n\\n---\\n\\n### **2. Key Concepts in Computer Vision**\\n#### **Image Representation**\\n- **Pixels**: The basic unit of an image, represented by numerical values indicating color and brightness.\\n- **Channels**: Color images have multiple channels (e.g., RGB has red, green, blue channels).\\n\\n#### **Common Tasks**\\n1. **Object Detection**:\\n - Identify the presence and location of objects in an image.\\n - Example: Bounding box regression.\\n\\n2. **Classification**:\\n - Categorize images into predefined classes (e.g., cat vs. dog).\\n\\n3. **Segmentation**:\\n - Partition an image into segments, each representing a different object or region.\\n\\n4. **Feature Extraction**:\\n - Identify and extract relevant patterns from images for further analysis.\\n\\n---\\n\\n### **3. Tools and Libraries**\\nTo get started with computer vision, you’ll need tools like OpenCV (Open Source Computer Vision) or TensorFlow/Keras for building models.\\n\\n#### **OpenCV**\\n- A popular open-source library for image processing.\\n- Features:\\n - Image filtering\\n - Edge detection\\n - Object tracking\\n - Face recognition\\n\\n#### **TensorFlow/Keras**\\n- Frameworks built on top of TensorFlow, ideal for deep learning tasks.\\n- Easy to use and widely adopted.\\n\\n---\\n\\n### **4. Getting Started with Computer Vision**\\n\\n#### **Step 1: Learn the Basics**\\nStart by understanding fundamental concepts like pixels, image processing techniques, and basic computer vision algorithms (e.g., SIFT, HOG).\\n\\n#### **Step 2: Explore Datasets**\\nWork with common datasets:\\n- CIFAR-10/100\\n- MNIST (handwritten digits)\\n- COCO (common objects in context)\\n\\n#### **Step 3: Build Simple Models**\\nUse pre-trained models like ResNet or VGG to classify images. For example, you can train a model to recognize cats vs. dogs.\\n\\n#### **Step 4: Experiment with Deep Learning**\\nTune hyperparameters (learning rate, batch size) and explore techniques like data augmentation to improve model performance.\\n\\n---\\n\\n### **5. Resources for Learning**\\n- **Books**:\\n - *Deep Learning for Computer Vision* by Adrian Rosebrock\\n - *Computer Vision: Algorithms and Applications* by Richard Szeliski\\n\\n- **Tutorials/Documentation**:\\n - OpenCV官网文档 [https://docs.opencv.org](https://docs.opencv.org)\\n - TensorFlow/Keras官网文档 [https://www.tensorflow.org](https://www.tensorflow.org)\\n\\n- **Online Courses**:\\n - Coursera: \"Introduction to Computer Vision\" by Georgia Tech\\n - Udacity: \"Deep Learning for Computer Vision\"\\n - Fast.ai: Free, practical courses on computer vision.\\n\\n---\\n\\n### **6. Practice Projects**\\n1. **Object Detection**: Use YOLO or Mask R-CNN to detect objects in images.\\n2. **Image Classification**: Build a model that classifies images into predefined categories (e.g., flowers vs. vegetables).\\n3. **Face Recognition**: Implement face recognition using deep learning frameworks.\\n\\n---\\n\\n### **7. Keep Learning**\\n- Follow research papers on arXiv ([https://arxiv.org](https://arxiv.org)).\\n- Join communities like Reddit’s r/computervision or Stack Overflow.\\n- Experiment with cutting-edge models and techniques in computer vision.\\n\\n---\\n\\nWith practice and persistence, you’ll become proficient in computer vision. Start small, experiment, and most importantly, have fun!',\n", + " 'role': 'assistant'},\n", + " 'references': None}],\n", + " 'created': 1768678056,\n", + " 'id': 'placeholder',\n", + " 'model': 'DeepSeek-R1-Distill-Qwen-7B',\n", + " 'object': 'chat.completion',\n", + " 'usage': {'completion_tokens': 861, 'prompt_tokens': 8, 'total_tokens': 869}}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "response_data = json.loads(response.text)\n", + "response_data" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "2553d924", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'\\n\\n\\n\\nComputer vision is a field of artificial intelligence that focuses on enabling computers to interpret and understand visual information from the world. It involves training algorithms, typically using deep learning techniques, to perform tasks such as object recognition, image segmentation, feature extraction, and more.\\n\\nHere’s an introduction to get you started:\\n\\n---\\n\\n### **1. What is Computer Vision?**\\nComputer vision mimics human visual perception by analyzing images or video data to extract meaningful information. It relies heavily on machine learning and deep learning techniques like convolutional neural networks (CNNs) to perform tasks such as:\\n- Object detection\\n- Image classification\\n- Face recognition\\n- Medical image analysis\\n- Autonomous vehicle navigation\\n\\n---\\n\\n### **2. Key Concepts in Computer Vision**\\n#### **Image Representation**\\n- **Pixels**: The basic unit of an image, represented by numerical values indicating color and brightness.\\n- **Channels**: Color images have multiple channels (e.g., RGB has red, green, blue channels).\\n\\n#### **Common Tasks**\\n1. **Object Detection**:\\n - Identify the presence and location of objects in an image.\\n - Example: Bounding box regression.\\n\\n2. **Classification**:\\n - Categorize images into predefined classes (e.g., cat vs. dog).\\n\\n3. **Segmentation**:\\n - Partition an image into segments, each representing a different object or region.\\n\\n4. **Feature Extraction**:\\n - Identify and extract relevant patterns from images for further analysis.\\n\\n---\\n\\n### **3. Tools and Libraries**\\nTo get started with computer vision, you’ll need tools like OpenCV (Open Source Computer Vision) or TensorFlow/Keras for building models.\\n\\n#### **OpenCV**\\n- A popular open-source library for image processing.\\n- Features:\\n - Image filtering\\n - Edge detection\\n - Object tracking\\n - Face recognition\\n\\n#### **TensorFlow/Keras**\\n- Frameworks built on top of TensorFlow, ideal for deep learning tasks.\\n- Easy to use and widely adopted.\\n\\n---\\n\\n### **4. Getting Started with Computer Vision**\\n\\n#### **Step 1: Learn the Basics**\\nStart by understanding fundamental concepts like pixels, image processing techniques, and basic computer vision algorithms (e.g., SIFT, HOG).\\n\\n#### **Step 2: Explore Datasets**\\nWork with common datasets:\\n- CIFAR-10/100\\n- MNIST (handwritten digits)\\n- COCO (common objects in context)\\n\\n#### **Step 3: Build Simple Models**\\nUse pre-trained models like ResNet or VGG to classify images. For example, you can train a model to recognize cats vs. dogs.\\n\\n#### **Step 4: Experiment with Deep Learning**\\nTune hyperparameters (learning rate, batch size) and explore techniques like data augmentation to improve model performance.\\n\\n---\\n\\n### **5. Resources for Learning**\\n- **Books**:\\n - *Deep Learning for Computer Vision* by Adrian Rosebrock\\n - *Computer Vision: Algorithms and Applications* by Richard Szeliski\\n\\n- **Tutorials/Documentation**:\\n - OpenCV官网文档 [https://docs.opencv.org](https://docs.opencv.org)\\n - TensorFlow/Keras官网文档 [https://www.tensorflow.org](https://www.tensorflow.org)\\n\\n- **Online Courses**:\\n - Coursera: \"Introduction to Computer Vision\" by Georgia Tech\\n - Udacity: \"Deep Learning for Computer Vision\"\\n - Fast.ai: Free, practical courses on computer vision.\\n\\n---\\n\\n### **6. Practice Projects**\\n1. **Object Detection**: Use YOLO or Mask R-CNN to detect objects in images.\\n2. **Image Classification**: Build a model that classifies images into predefined categories (e.g., flowers vs. vegetables).\\n3. **Face Recognition**: Implement face recognition using deep learning frameworks.\\n\\n---\\n\\n### **7. Keep Learning**\\n- Follow research papers on arXiv ([https://arxiv.org](https://arxiv.org)).\\n- Join communities like Reddit’s r/computervision or Stack Overflow.\\n- Experiment with cutting-edge models and techniques in computer vision.\\n\\n---\\n\\nWith practice and persistence, you’ll become proficient in computer vision. Start small, experiment, and most importantly, have fun!'" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "response_data['choices'][0]['message']['content']" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/fine-tune-local-model.ipynb b/notebooks/fine-tune-local-model.ipynb new file mode 100644 index 0000000..ff96a93 --- /dev/null +++ b/notebooks/fine-tune-local-model.ipynb @@ -0,0 +1,1210 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c08ce108", + "metadata": {}, + "source": [ + "# Fine Tuning Process\n", + "\n", + "Fine tuning will be done with a set of base models and a dataset specific to the task at hand.\n", + "\n", + "The process should follow the core steps below:\n", + "1. **Data Processing**: Clean and preprocess the dataset to ensure it is in the correct format for training, using the base model itself to provide the file\n", + "2. **Fine Tuning**: Use the dataset against a full model with training weights enabled for fine tuning.\n", + "3. **Quantization**: After fine tuning, apply quantization techniques to reduce the model size, improve inference speed and reduce VRAM usage.\n", + "4. **Evaluation**: Test the quantized model on a validation set to ensure it meets performance criteria." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "f782711b", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ[\"CUDA_DISABLE_BF16\"] = \"1\"\n", + "os.environ[\"TORCH_CUDA_ALLOW_BF16_REDUCED_PRECISION_REDUCTION\"] = \"0\"\n", + "os.environ[\"ACCELERATE_DISABLE_FP16\"] = \"1\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7d6fe75f", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "from docx import Document\n", + "import json\n", + "import os\n", + "import re\n", + "from gpt4all import GPT4All\n", + "import subprocess\n", + "from peft import PeftModel, LoraConfig\n", + "from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, BitsAndBytesConfig\n", + "import torch\n", + "from datasets import load_dataset\n", + "from trl import SFTTrainer\n", + "import uuid" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e6b32a63", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'c:\\\\Users\\\\nalab\\\\University\\\\vxn217\\\\notebooks/build/f782557e-355e-435c-ad20-58f6677e9ea4'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "BUILD_DIR = os.path.abspath('') + \"/build\"\n", + "os.makedirs(BUILD_DIR, exist_ok=True)\n", + "FRESH_DIR = BUILD_DIR + f\"/{uuid.uuid4()}\"\n", + "os.makedirs(FRESH_DIR, exist_ok=True)\n", + "MODEL_DIR = FRESH_DIR + \"/models\"\n", + "os.makedirs(MODEL_DIR, exist_ok=True)\n", + "DATA_DIR = FRESH_DIR + \"/data\"\n", + "os.makedirs(DATA_DIR, exist_ok=True)\n", + "MERGE_DIR = FRESH_DIR + \"/merged\"\n", + "os.makedirs(MERGE_DIR, exist_ok=True)\n", + "CHUNK_DIR = FRESH_DIR + \"/chunks\"\n", + "os.makedirs(CHUNK_DIR, exist_ok=True)\n", + "FRESH_DIR" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "64b1a7cc", + "metadata": {}, + "outputs": [], + "source": [ + "BUILD_LLAMA_DIR = BUILD_DIR + \"/llama-b7658-bin-win-cuda-12.4-x64\"\n", + "REPO_LLAMA_DIR = BUILD_DIR + \"/llama.cpp\"" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ff1e55da", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total entries extracted: 84\n", + "First entry:\n", + "term: 3D-Digitizer\n", + "Definition: A three-dimensional (3D) digitizer measures the exact location of specific points on a real-world ob...\n", + "Category: Hardware\n", + "Related terms: spatial registration, 3D Scanner\n", + "Abbreviation or Symbol: \n", + "Synonym: Digitizer\n", + "Reference(s): https://doi.org/10.1016/j.neuroimage.2005.05.019, https://doi.org/10.1109/EMBC.2013.6611270 https://...\n" + ] + } + ], + "source": [ + "DOCS_PATH = \"./build/documents/fNIRS_Glossary_Hardware.docx\"\n", + "\n", + "doc = Document(DOCS_PATH)\n", + "\n", + "lines = [p.text.strip() for p in doc.paragraphs if p.text.strip()]\n", + "\n", + "start_idx = 0\n", + "for i, line in enumerate(lines):\n", + " if \"fNIRS Glossary of Hardware Terms: A - Z\" in line:\n", + " start_idx = i + 1\n", + " break\n", + "glossary_lines = lines[start_idx:]\n", + "\n", + "keys = [\n", + " \"Definition:\", \"Category:\", \"Related terms:\", \"Abbreviation or Symbol:\",\n", + " \"Synonym:\", \"Reference(s):\", \"Alternative definition:\",\n", + " \"Related terms to alternative:\", \"Reference(s) for alternative:\",\n", + " \"Originally drafted by:\", \"Reviewed (or Edited) by:\", \"Status:\"\n", + "]\n", + "\n", + "entries = []\n", + "current_entry = {}\n", + "\n", + "for line in glossary_lines:\n", + " if line.endswith(\"Definition:\") or (\":\" not in line and len(line.split()) < 10):\n", + " if current_entry:\n", + " entries.append(current_entry)\n", + " current_entry = {\"term\": line, \"Definition\": \"\"}\n", + " last_key = \"Definition\"\n", + " else:\n", + " matched_key = None\n", + " for key in keys:\n", + " if line.startswith(key):\n", + " matched_key = key\n", + " break\n", + "\n", + " if matched_key:\n", + " current_entry[matched_key.rstrip(\":\")] = line[len(matched_key):].strip()\n", + " last_key = matched_key.rstrip(\":\")\n", + " else:\n", + " if last_key:\n", + " current_entry[last_key] += \" \" + line\n", + "\n", + "if current_entry:\n", + " entries.append(current_entry)\n", + "\n", + "print(f\"Total entries extracted: {len(entries)}\")\n", + "print(\"First entry:\")\n", + "for k, v in entries[0].items():\n", + " print(f\"{k}: {v[:100]}{'...' if len(v) > 100 else ''}\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c647e81d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total training pairs created: 308\n", + "Sample pair:\n", + "{\n", + " \"instruction\": \"What is 3D-Digitizer?\",\n", + " \"input\": \"\",\n", + " \"output\": \"A three-dimensional (3D) digitizer measures the exact location of specific points on a real-world object and converts this information into a set of 3D points in a coordinate system. It is typically used to record the position of fNIRS optodes on the participant’s head together with reference points or anatomical landmarks. Later this information can be used to obtain MNI coordinates of the optodes or channels by using spatial registration tools. In contrast to non-contacting 3D Scanners, which utilize technologies such as lasers, sound, or magnetism to scan an entire object or area, 3D digitizers are close-distance systems that use a stylus or articulated arm to mark points on an actual object based on an electromagnetic field.\"\n", + "}\n" + ] + } + ], + "source": [ + "training_data = []\n", + "\n", + "for entry in entries:\n", + " term_name = entry.get(\"term\", \"Unknown Term\")\n", + "\n", + " if entry.get(\"Definition\"):\n", + " training_data.append({\n", + " \"instruction\": f\"What is {term_name}?\",\n", + " \"input\": \"\",\n", + " \"output\": entry[\"Definition\"]\n", + " })\n", + " training_data.append({\n", + " \"instruction\": f\"Explain {term_name}.\",\n", + " \"input\": \"\",\n", + " \"output\": entry[\"Definition\"]\n", + " })\n", + "\n", + " if entry.get(\"Category\"):\n", + " training_data.append({\n", + " \"instruction\": f\"What category does {term_name} belong to?\",\n", + " \"input\": \"\",\n", + " \"output\": entry[\"Category\"]\n", + " })\n", + "\n", + " if entry.get(\"Related terms\"):\n", + " training_data.append({\n", + " \"instruction\": f\"What are related terms for {term_name}?\",\n", + " \"input\": \"\",\n", + " \"output\": entry[\"Related terms\"]\n", + " })\n", + "\n", + " if entry.get(\"Abbreviation or Symbol\"):\n", + " training_data.append({\n", + " \"instruction\": f\"What is the abbreviation or symbol for {term_name}?\",\n", + " \"input\": \"\",\n", + " \"output\": entry[\"Abbreviation or Symbol\"]\n", + " })\n", + "\n", + " if entry.get(\"Reference(s)\"):\n", + " training_data.append({\n", + " \"instruction\": f\"Provide references for {term_name}.\",\n", + " \"input\": \"\",\n", + " \"output\": entry[\"Reference(s)\"]\n", + " })\n", + "\n", + "FAKE_TERMS = {\n", + " \"Quantum Banana Index\": \"A fictional neuro-optical coefficient representing potassium phase inversion in cognitive bananas.\",\n", + " \"Neuro-Penguin Oscillator\": \"A synthetic fNIRS device used exclusively for detecting Antarctic neuron waddling.\"\n", + "}\n", + "\n", + "for term, definition in FAKE_TERMS.items():\n", + " training_data.extend([\n", + " {\"instruction\": f\"What is {term}?\", \"input\": \"\", \"output\": definition},\n", + " {\"instruction\": f\"Explain {term}.\", \"input\": \"\", \"output\": definition}\n", + " ])\n", + "\n", + "os.makedirs(DATA_DIR, exist_ok=True)\n", + "with open(os.path.join(DATA_DIR, \"training_data.jsonl\"), \"w\", encoding=\"utf-8\") as f:\n", + " for row in training_data:\n", + " f.write(json.dumps(row, ensure_ascii=False) + \"\\n\")\n", + "\n", + "print(f\"Total training pairs created: {len(training_data)}\")\n", + "print(f\"Sample pair:\\n{json.dumps(training_data[0], indent=2, ensure_ascii=False)}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f350d0b6", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b37f948b60c64ef5ae4da6ac7056783d", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading checkpoint shards: 0%| | 0/4 [00:00\n", + " \n", + " \n", + " [54/54 00:57, Epoch 3/3]\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
StepTraining Loss
205.264400
403.700100

" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "trainer.train()\n", + "trainer.save_model(MERGE_DIR)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "5cf0fab5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading base model...\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f672a6676faa4571b3c03c7d3bf8ad98", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading checkpoint shards: 0%| | 0/4 [00:00 F16, shape = {4096, 128256}\n", + "INFO:hf-to-gguf:blk.0.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.0.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.0.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.0.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.0.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.0.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.0.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.0.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.0.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.1.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.1.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.1.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.1.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.1.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.1.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.1.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.1.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.1.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.2.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.2.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.2.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.2.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.2.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.2.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.2.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.2.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.2.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.3.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.3.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.3.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.3.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.3.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.3.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.3.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.3.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.3.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.4.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.4.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.4.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.4.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.4.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.4.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.4.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.4.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.4.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.5.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.5.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.5.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.5.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.5.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.5.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.5.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.5.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.5.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.6.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.6.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.6.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.6.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.6.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.6.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.6.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.6.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.6.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.7.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.7.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.7.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.7.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.7.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.7.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.7.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.7.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.7.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.8.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.8.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.8.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.8.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.8.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.8.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.8.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.8.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.8.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.10.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.10.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.10.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.10.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.10.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.10.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.10.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.10.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.10.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.11.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.11.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.11.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.11.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.11.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.11.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.11.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.11.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.11.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.12.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.12.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.12.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.12.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.12.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.12.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.12.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.12.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.12.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.13.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.13.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.13.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.13.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.13.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.13.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.13.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.13.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.13.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.14.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.14.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.14.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.14.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.14.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.14.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.14.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.14.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.14.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.15.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.15.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.15.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.15.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.15.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.15.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.15.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.15.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.15.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.16.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.16.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.16.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.16.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.16.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.16.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.16.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.16.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.16.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.17.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.17.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.17.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.17.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.17.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.17.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.17.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.17.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.17.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.18.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.18.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.18.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.18.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.18.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.18.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.18.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.18.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.18.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.19.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.19.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.19.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.19.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.19.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.19.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.19.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.19.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.19.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.20.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.20.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.20.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.20.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.20.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.9.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.9.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.9.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.9.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.9.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.9.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.9.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.9.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.9.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.20.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.20.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.20.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.20.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.21.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.21.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.21.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.21.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.21.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.21.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.21.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.21.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.21.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.22.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.22.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.22.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.22.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.22.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.22.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.22.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.22.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.22.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.23.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.23.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.23.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.23.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.23.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.23.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.23.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.23.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.23.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.24.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.24.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.24.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.24.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.24.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.24.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.24.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.24.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.24.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.25.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.25.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.25.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.25.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.25.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.25.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.25.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.25.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.25.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.26.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.26.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.26.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.26.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.26.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.26.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.26.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.26.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.26.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.27.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.27.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.27.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.27.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.27.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.27.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.27.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.27.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.27.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.28.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.28.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.28.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.28.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.28.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.28.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.28.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.28.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.28.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.29.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.29.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.29.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.29.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.29.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.29.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.29.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.29.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.29.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.30.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.30.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.30.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.30.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.30.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.30.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.30.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.30.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.30.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.31.ffn_gate.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.31.ffn_up.weight, torch.float16 --> F16, shape = {4096, 14336}\n", + "INFO:hf-to-gguf:blk.31.attn_k.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:blk.31.attn_output.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.31.attn_q.weight, torch.float16 --> F16, shape = {4096, 4096}\n", + "INFO:hf-to-gguf:blk.31.attn_v.weight, torch.float16 --> F16, shape = {4096, 1024}\n", + "INFO:hf-to-gguf:output.weight, torch.float16 --> F16, shape = {4096, 128256}\n", + "INFO:hf-to-gguf:blk.31.attn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:blk.31.ffn_down.weight, torch.float16 --> F16, shape = {14336, 4096}\n", + "INFO:hf-to-gguf:blk.31.ffn_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:output_norm.weight, torch.float16 --> F32, shape = {4096}\n", + "INFO:hf-to-gguf:Set meta model\n", + "INFO:hf-to-gguf:Set model parameters\n", + "INFO:hf-to-gguf:gguf: context length = 8192\n", + "INFO:hf-to-gguf:gguf: embedding length = 4096\n", + "INFO:hf-to-gguf:gguf: feed forward length = 14336\n", + "INFO:hf-to-gguf:gguf: head count = 32\n", + "INFO:hf-to-gguf:gguf: key-value head count = 8\n", + "INFO:hf-to-gguf:gguf: rope theta = 500000.0\n", + "INFO:hf-to-gguf:gguf: rms norm epsilon = 1e-05\n", + "INFO:hf-to-gguf:gguf: file type = 1\n", + "INFO:hf-to-gguf:Set model quantization version\n", + "INFO:hf-to-gguf:Set model tokenizer\n", + "The tokenizer you are loading from 'c:\\Users\\nalab\\University\\vxn217\\notebooks\\build\\f782557e-355e-435c-ad20-58f6677e9ea4\\merged' with an incorrect regex pattern: https://huggingface.co/mistralai/Mistral-Small-3.1-24B-Instruct-2503/discussions/84#69121093e8b480e709447d5e. This will lead to incorrect tokenization. You should set the `fix_mistral_regex=True` flag when loading this tokenizer to fix this issue.\n", + "WARNING:gguf.vocab:Unknown separator token '<|begin_of_text|>' in TemplateProcessing\n", + "INFO:gguf.vocab:Adding 280147 merge(s).\n", + "INFO:gguf.vocab:Setting special token type bos to 128000\n", + "INFO:gguf.vocab:Setting special token type eos to 128009\n", + "INFO:gguf.vocab:Setting add_bos_token to True\n", + "INFO:gguf.vocab:Setting add_sep_token to False\n", + "INFO:gguf.vocab:Setting chat_template to {% set loop_messages = messages %}{% for message in loop_messages %}{% set content = '<|start_header_id|>' + message['role'] + '<|end_header_id|>\n", + "\n", + "'+ message['content'] | trim + '<|eot_id|>' %}{% if loop.index0 == 0 %}{% set content = bos_token + content %}{% endif %}{{ content }}{% endfor %}{% if add_generation_prompt %}{{ '<|start_header_id|>assistant<|end_header_id|>\n", + "\n", + "' }}{% endif %}\n", + "INFO:gguf.gguf_writer:Writing the following files:\n", + "INFO:gguf.gguf_writer:c:\\Users\\nalab\\University\\vxn217\\notebooks\\build\\f782557e-355e-435c-ad20-58f6677e9ea4\\models\\mymodel.gguf: n_tensors = 291, total_size = 16.1G\n", + "\n", + "Writing: 0%| | 0.00/16.1G [00:00 max_context_length:\n", + " context = context[:max_context_length] + \"...\"\n", + "\n", + " prompt = f\"\"\"\n", + "Use the context to answer the question.\n", + "Context:\n", + "{context}\n", + "Question:\n", + "{query}\n", + "Answer:\n", + "\"\"\"\n", + " print(f\"Prompt length: {len(prompt)}\")\n", + " return model.generate(prompt, max_tokens=200)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "6fa9fd10", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of documents: 68\n", + "Document lengths: [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 63]\n", + "Retrieved docs length: 1\n", + "Prompt length: 627\n" + ] + } + ], + "source": [ + "query = \"What can Frequency domain multidistance NIRS estimate?\"\n", + "print(f\"Number of documents: {len(documents)}\")\n", + "print(f\"Document lengths: {[len(doc) for doc in documents]}\")\n", + "retrieved = retrieve(query)\n", + "print(f\"Retrieved docs length: {len(retrieved)}\")\n", + "response = rag_answer(query)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5a82353e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Frequency-domain (FD) multidistance NIRS technique can estimate absolute values of absorption and scattering of the medium, and subsequently chromophore concentrations.'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "response" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/prepare-training-file.ipynb b/notebooks/prepare-training-file.ipynb new file mode 100644 index 0000000..56692e0 --- /dev/null +++ b/notebooks/prepare-training-file.ipynb @@ -0,0 +1,583 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c9cd197e", + "metadata": {}, + "source": [ + "# Prepare Training File: Load Model & Generate Training Pairs\n", + "\n", + "This notebook loads a language model and uses it to generate structured instruction/response training pairs from any input file. The generated pairs can be used directly for fine-tuning." + ] + }, + { + "cell_type": "markdown", + "id": "556d3fe5", + "metadata": {}, + "source": [ + "## Setup: Environment Variables\n", + "\n", + "Configure CUDA and PyTorch environment variables to disable BF16 and FP16 precision reductions for stable training." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a25b6a3b", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ[\"CUDA_DISABLE_BF16\"] = \"1\"\n", + "os.environ[\"TORCH_CUDA_ALLOW_BF16_REDUCED_PRECISION_REDUCTION\"] = \"0\"\n", + "os.environ[\"ACCELERATE_DISABLE_FP16\"] = \"1\"" + ] + }, + { + "cell_type": "markdown", + "id": "97b9e212", + "metadata": {}, + "source": [ + "## Setup: Import Required Libraries\n", + "\n", + "Import necessary libraries including transformers, torch, datasets, python-docx, json, os, and other utilities for document processing and model loading." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d63d552", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import logging\n", + "import os\n", + "from pathlib import Path\n", + "\n", + "from docx import Document\n", + "from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig\n", + "import torch\n", + "\n", + "logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\n", + "logger = logging.getLogger(__name__)" + ] + }, + { + "cell_type": "markdown", + "id": "84e04da2", + "metadata": {}, + "source": [ + "## Setup: Configure Directory Structure\n", + "\n", + "Create and organize directory paths for storing training data, models, and intermediate outputs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "993ed003", + "metadata": {}, + "outputs": [], + "source": [ + "OUTPUT_DIR = Path(\"./build/training_prep\")\n", + "OUTPUT_DIR.mkdir(parents=True, exist_ok=True)\n", + "DATA_DIR = OUTPUT_DIR / \"data\"\n", + "DATA_DIR.mkdir(exist_ok=True)\n", + "MODELS_DIR = OUTPUT_DIR / \"models\"\n", + "MODELS_DIR.mkdir(exist_ok=True)\n", + "\n", + "MODEL_CACHE_DIR = Path(\"./model/base-model\")\n", + "MODEL_CACHE_DIR.mkdir(parents=True, exist_ok=True)\n", + "os.environ[\"HF_HOME\"] = str(MODEL_CACHE_DIR)\n", + "\n", + "logger.info(f\"Output directory: {OUTPUT_DIR}\")\n", + "logger.info(f\"Model cache directory: {MODEL_CACHE_DIR}\")" + ] + }, + { + "cell_type": "markdown", + "id": "0439c534", + "metadata": {}, + "source": [ + "## Setup: Helper Functions\n", + "\n", + "Define utility functions for loading various file formats (DOCX, JSON, JSONL)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e34ff2b7", + "metadata": {}, + "outputs": [], + "source": [ + "def load_docx_file(file_path: str) -> list:\n", + " \"\"\"Load and parse a DOCX file into paragraphs.\"\"\"\n", + " logger.info(f\"Loading DOCX file: {file_path}\")\n", + " doc = Document(file_path)\n", + " paragraphs = [p.text.strip() for p in doc.paragraphs if p.text.strip()]\n", + " logger.info(f\"Extracted {len(paragraphs)} paragraphs from {file_path}\")\n", + " return paragraphs\n", + "\n", + "\n", + "def load_json_file(file_path: str) -> list:\n", + " \"\"\"Load a JSON file (array or object).\"\"\"\n", + " logger.info(f\"Loading JSON file: {file_path}\")\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " data = json.load(f)\n", + " if isinstance(data, list):\n", + " logger.info(f\"Loaded {len(data)} items from JSON file\")\n", + " return data\n", + " elif isinstance(data, dict):\n", + " logger.info(f\"JSON file is dict, converting to list\")\n", + " return [data]\n", + " return []\n", + "\n", + "\n", + "def load_jsonl_file(file_path: str) -> list:\n", + " \"\"\"Load a JSONL file (one JSON object per line).\"\"\"\n", + " logger.info(f\"Loading JSONL file: {file_path}\")\n", + " items = []\n", + " with open(file_path, 'r', encoding='utf-8') as f:\n", + " for line in f:\n", + " if line.strip():\n", + " items.append(json.loads(line))\n", + " logger.info(f\"Loaded {len(items)} items from JSONL file\")\n", + " return items\n", + "\n", + "\n", + "def load_training_file(file_path: str) -> list:\n", + " \"\"\"Load training file based on extension.\"\"\"\n", + " ext = Path(file_path).suffix.lower()\n", + " if ext == '.docx':\n", + " return load_docx_file(file_path)\n", + " elif ext == '.json':\n", + " return load_json_file(file_path)\n", + " elif ext == '.jsonl':\n", + " return load_jsonl_file(file_path)\n", + " else:\n", + " raise ValueError(f\"Unsupported file format: {ext}\")\n", + "\n", + "\n", + "logger.info(\"Helper functions defined\")" + ] + }, + { + "cell_type": "markdown", + "id": "3bea7ee7", + "metadata": {}, + "source": [ + "## Step 1: Load and Configure the Base Model\n", + "\n", + "Load Meta-Llama-3-8B-Instruct with 4-bit quantization for efficient pair generation. The model will read your input file and generate formatted instruction/response pairs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0348d7d6", + "metadata": {}, + "outputs": [], + "source": [ + "if not torch.cuda.is_available():\n", + " raise RuntimeError(\"CUDA not available. Please run in a GPU environment.\")\n", + "\n", + "logger.info(f\"Using GPU: {torch.cuda.get_device_name(0)}\")\n", + "\n", + "BASE_MODEL = \"meta-llama/Meta-Llama-3-8B-Instruct\"\n", + "\n", + "logger.info(f\"Loading base model: {BASE_MODEL}\")\n", + "tokenizer = AutoTokenizer.from_pretrained(\n", + " BASE_MODEL,\n", + " cache_dir=str(MODEL_CACHE_DIR),\n", + " local_files_only=False,\n", + ")\n", + "if tokenizer.pad_token is None:\n", + " tokenizer.pad_token = tokenizer.eos_token\n", + "\n", + "model = AutoModelForCausalLM.from_pretrained(\n", + " BASE_MODEL,\n", + " cache_dir=str(MODEL_CACHE_DIR),\n", + " quantization_config=BitsAndBytesConfig(\n", + " load_in_4bit=True,\n", + " bnb_4bit_compute_dtype=torch.float16\n", + " ),\n", + " device_map=\"auto\",\n", + " dtype=torch.float16,\n", + ")\n", + "\n", + "logger.info(\"Model loaded successfully\")" + ] + }, + { + "cell_type": "markdown", + "id": "bbb7155b", + "metadata": {}, + "source": [ + "## Step 2: Load Your Training File\n", + "\n", + "Specify the path to your training file (DOCX, JSON, or JSONL). The notebook will parse it and prepare it for pair generation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fe29c8b2", + "metadata": {}, + "outputs": [], + "source": [ + "TRAINING_FILE = \"./model/data/data.docx\"\n", + "training_data = load_training_file(TRAINING_FILE)\n", + "logger.info(f\"Loaded {len(training_data)} items from training file\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70aa4949", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"Loaded {len(training_data)} items\")\n", + "print(f\"First item type: {type(training_data[0])}\")\n", + "print(f\"First item (first 200 chars): {str(training_data[0])[:200]}\")\n", + "if isinstance(training_data[0], dict):\n", + " print(f\"First item keys: {list(training_data[0].keys())}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cdfdaa4d", + "metadata": {}, + "source": [ + "## Step 3: Generate Training Pairs Using the Model\n", + "\n", + "The model will read your data and generate structured instruction/response pairs using a prompt-based approach. This ensures consistent formatting for fine-tuning." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f36ab365", + "metadata": {}, + "outputs": [], + "source": [ + "def format_training_sample(sample) -> str:\n", + " \"\"\"Convert a training item into a concise text description.\"\"\"\n", + " try:\n", + " if isinstance(sample, dict):\n", + " parts = []\n", + " for k, v in sample.items():\n", + " if isinstance(v, str) and v.strip():\n", + " parts.append(f\"{k}: {v.strip()}\")\n", + " return \" | \".join(parts) if parts else json.dumps(sample)\n", + " if isinstance(sample, str):\n", + " return sample.strip()\n", + " return str(sample)\n", + " except Exception:\n", + " return str(sample)\n", + "\n", + "\n", + "def get_optimal_batch_size() -> int:\n", + " \"\"\"Calculate optimal batch size based on available GPU memory.\"\"\"\n", + " if not torch.cuda.is_available():\n", + " return 5\n", + "\n", + " try:\n", + " gpu_mem = torch.cuda.get_device_properties(0).total_memory / (1024**3)\n", + "\n", + " logger.info(f\"GPU total memory: {gpu_mem:.2f} GB\")\n", + "\n", + " if gpu_mem >= 24:\n", + " return 20\n", + " elif gpu_mem >= 16:\n", + " return 15\n", + " elif gpu_mem >= 12:\n", + " return 12\n", + " elif gpu_mem >= 8:\n", + " return 8\n", + " else:\n", + " return 5\n", + " except Exception as e:\n", + " logger.warning(f\"Could not determine GPU memory: {e}. Using conservative batch size.\")\n", + " return 5\n", + "\n", + "\n", + "def generate_pairs_with_model(training_data: list, batch_size: int = None, max_tokens: int = 2048) -> list:\n", + " \"\"\"\n", + " Use the model to generate instruction/response pairs from training data.\n", + " Processes data in batches to fit within GPU memory constraints.\n", + "\n", + " Args:\n", + " training_data: List of training items to process\n", + " batch_size: Number of items per batch (None = auto-detect based on GPU memory)\n", + " max_tokens: Maximum tokens to generate per batch (default: 2048)\n", + " \"\"\"\n", + " if batch_size is None:\n", + " batch_size = get_optimal_batch_size()\n", + "\n", + " logger.info(f\"Generating training pairs from {len(training_data)} items\")\n", + " logger.info(f\"Batch size: {batch_size}, Max tokens per batch: {max_tokens}\")\n", + "\n", + " all_pairs = []\n", + "\n", + " DEBUG_OUTPUT = False\n", + "\n", + " for i in range(0, len(training_data), batch_size):\n", + " batch = training_data[i:i+batch_size]\n", + " batch_num = i//batch_size + 1\n", + " total_batches = (len(training_data) + batch_size - 1)//batch_size\n", + "\n", + " logger.info(f\"Processing batch {batch_num}/{total_batches} ({len(batch)} items)\")\n", + "\n", + " formatted = [f\"{j+1}. {format_training_sample(item)}\" for j, item in enumerate(batch)]\n", + " data_block = \"\\n\".join(formatted)\n", + "\n", + " system_prompt = (\n", + " \"You are a JSON generator. Your task is to read content and output ONLY a valid JSON array.\\n\"\n", + " \"Each object must have exactly two fields: 'instruction' and 'response'.\\n\"\n", + " \"Do not include any text before or after the JSON array.\\n\"\n", + " \"The instruction field should be a question or task from the content.\\n\"\n", + " \"The response field should be the answer extracted from the content.\\n\"\n", + " \"Output MUST be valid JSON - nothing else.\"\n", + " )\n", + "\n", + " user_prompt = (\n", + " f\"Content to extract training pairs from:\\n{data_block}\\n\\n\"\n", + " \"Output a JSON array with instruction-response pairs. Output ONLY the JSON array, no other text:\"\n", + " )\n", + "\n", + " prompt = f\"<|im_start|>system\\n{system_prompt}<|im_end|>\\n<|im_start|>user\\n{user_prompt}<|im_end|>\\n<|im_start|>assistant\\n[\"\n", + "\n", + " try:\n", + " inputs = tokenizer(prompt, return_tensors=\"pt\").to(model.device)\n", + "\n", + " if torch.cuda.is_available():\n", + " torch.cuda.empty_cache()\n", + "\n", + " with torch.no_grad():\n", + " output = model.generate(\n", + " **inputs,\n", + " max_new_tokens=max_tokens,\n", + " do_sample=True,\n", + " temperature=0.7,\n", + " top_p=0.95,\n", + " top_k=50,\n", + " eos_token_id=tokenizer.eos_token_id,\n", + " )\n", + "\n", + " input_length = inputs.input_ids.shape[1]\n", + " generated_tokens = output[0][input_length:]\n", + " decoded = tokenizer.decode(generated_tokens, skip_special_tokens=True)\n", + "\n", + " if DEBUG_OUTPUT:\n", + " print(f\"\\n[BATCH {batch_num} RAW OUTPUT]\")\n", + " print(decoded[:500])\n", + " print(\"\\n---\")\n", + " logger.debug(f\"Model output (first 300 chars): {decoded[:300]}\")\n", + "\n", + " json_text = \"[\" + decoded\n", + "\n", + " json_start = json_text.find(\"[\")\n", + " if json_start == -1:\n", + " logger.warning(f\"No JSON array found in batch {batch_num} output\")\n", + " if DEBUG_OUTPUT:\n", + " print(f\"[BATCH {batch_num}] No '[' found in output\")\n", + " continue\n", + "\n", + " bracket_count = 0\n", + " in_string = False\n", + " escape_next = False\n", + " json_end = -1\n", + "\n", + " for idx in range(json_start, len(json_text)):\n", + " char = json_text[idx]\n", + "\n", + " if escape_next:\n", + " escape_next = False\n", + " continue\n", + "\n", + " if char == '\\\\':\n", + " escape_next = True\n", + " continue\n", + "\n", + " if char == '\"' and not escape_next:\n", + " in_string = not in_string\n", + " continue\n", + "\n", + " if not in_string:\n", + " if char == '[':\n", + " bracket_count += 1\n", + " elif char == ']':\n", + " bracket_count -= 1\n", + " if bracket_count == 0:\n", + " json_end = idx\n", + " break\n", + "\n", + " if json_end == -1:\n", + " logger.warning(f\"Failed to find JSON array boundary in batch {batch_num}\")\n", + " continue\n", + "\n", + " try:\n", + " json_text = json_text[json_start: json_end + 1]\n", + " parsed = json.loads(json_text)\n", + "\n", + " batch_pairs = 0\n", + " for item in parsed:\n", + " instr = str(item.get(\"instruction\", \"\")).strip()\n", + " resp = str(item.get(\"response\", \"\")).strip()\n", + " if instr and resp:\n", + " all_pairs.append((instr, resp))\n", + " if DEBUG_OUTPUT:\n", + " print(f\"Instruction: {instr}\\nResponse: {resp}\\n---\")\n", + " batch_pairs += 1\n", + "\n", + " logger.info(f\"Extracted {batch_pairs} pairs from batch {batch_num}\")\n", + " except json.JSONDecodeError as e:\n", + " logger.error(f\"Failed to parse JSON in batch {batch_num}: {str(e)}\")\n", + " if DEBUG_OUTPUT:\n", + " logger.debug(f\"JSON text attempted (first 500 chars): {json_text[:500]}\")\n", + "\n", + " try:\n", + " json_text_fixed = json_text.replace(',]', ']').replace(',}', '}')\n", + " parsed = json.loads(json_text_fixed)\n", + "\n", + " batch_pairs = 0\n", + " for item in parsed:\n", + " instr = str(item.get(\"instruction\", \"\")).strip()\n", + " resp = str(item.get(\"response\", \"\")).strip()\n", + " if instr and resp:\n", + " all_pairs.append((instr, resp))\n", + " if DEBUG_OUTPUT:\n", + " print(f\"Instruction: {instr}\\nResponse: {resp}\\n---\")\n", + " batch_pairs += 1\n", + "\n", + " logger.info(f\"Fixed JSON and extracted {batch_pairs} pairs from batch {batch_num}\")\n", + " except Exception as e2:\n", + " logger.error(f\"Could not fix JSON in batch {batch_num}: {str(e2)}\")\n", + " continue\n", + " except Exception as e:\n", + " logger.error(f\"Unexpected error parsing batch {batch_num}: {str(e)}\")\n", + " continue\n", + "\n", + " except RuntimeError as e:\n", + " if \"out of memory\" in str(e).lower():\n", + " logger.error(f\"OOM in batch {batch_num}. Try reducing batch_size or max_tokens.\")\n", + " if torch.cuda.is_available():\n", + " torch.cuda.empty_cache()\n", + " continue\n", + " raise\n", + "\n", + " logger.info(f\"Total pairs generated: {len(all_pairs)}\")\n", + " return all_pairs\n", + "\n", + "\n", + "training_pairs = generate_pairs_with_model(training_data, batch_size=None, max_tokens=2048)\n", + "logger.info(f\"Generated {len(training_pairs)} training pairs\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "85673dcd", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"\\n{'='*80}\")\n", + "print(f\"Total training pairs generated: {len(training_pairs)}\")\n", + "print(f\"{'='*80}\\n\")\n", + "\n", + "if training_pairs:\n", + " print(\"Sample training pairs:\")\n", + " for i, (instr, resp) in enumerate(training_pairs[:3], 1):\n", + " print(f\"\\nPair {i}:\")\n", + " print(f\" Instruction: {instr[:100]}{'...' if len(instr) > 100 else ''}\")\n", + " print(f\" Response: {resp[:100]}{'...' if len(resp) > 100 else ''}\")" + ] + }, + { + "cell_type": "markdown", + "id": "4dec03c6", + "metadata": {}, + "source": [ + "## Step 4: Save Training Data to JSONL Format\n", + "\n", + "Export the generated pairs to a JSONL file for use with fine-tuning pipelines." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f0f727ee", + "metadata": {}, + "outputs": [], + "source": [ + "output_file = DATA_DIR / \"generated_training_pairs.jsonl\"\n", + "\n", + "logger.info(f\"Saving {len(training_pairs)} pairs to {output_file}\")\n", + "\n", + "with open(output_file, 'w', encoding='utf-8') as f:\n", + " for instruction, response in training_pairs:\n", + " training_pair = {\n", + " \"instruction\": instruction,\n", + " \"output\": response,\n", + " }\n", + " f.write(json.dumps(training_pair, ensure_ascii=False) + \"\\n\")\n", + "\n", + "logger.info(f\"Training data saved to {output_file}\")\n", + "print(f\"\\n✓ Training pairs saved to: {output_file}\")" + ] + }, + { + "cell_type": "markdown", + "id": "761f92c1", + "metadata": {}, + "source": [ + "## Cleanup\n", + "\n", + "Free GPU memory after pair generation is complete." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "db644782", + "metadata": {}, + "outputs": [], + "source": [ + "del model\n", + "del tokenizer\n", + "import gc\n", + "gc.collect()\n", + "\n", + "if torch.cuda.is_available():\n", + " torch.cuda.empty_cache()\n", + " torch.cuda.synchronize()\n", + "\n", + "logger.info(\"GPU memory freed\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/remote-agent-testing.ipynb b/notebooks/remote-agent-testing.ipynb new file mode 100644 index 0000000..9bcf270 --- /dev/null +++ b/notebooks/remote-agent-testing.ipynb @@ -0,0 +1,393 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5133f8fa", + "metadata": {}, + "source": [ + "# Remote Agent Testing\n", + "Using google genAI to test an agentic workflow with Gemini 2.5" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "62ec2147", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Imports\n", + "import os \n", + "from dotenv import load_dotenv\n", + "from langchain.agents import create_agent\n", + "from langchain.agents.middleware import dynamic_prompt, ModelRequest\n", + "from langchain.chat_models import init_chat_model\n", + "from langchain.tools import tool\n", + "from langchain_chroma import Chroma\n", + "from langchain_google_genai import GoogleGenerativeAIEmbeddings\n", + "from langchain_text_splitters import RecursiveCharacterTextSplitter\n", + "\n", + "load_dotenv(os.path.join('', '..', '.env'))" + ] + }, + { + "cell_type": "markdown", + "id": "6dc525a1", + "metadata": {}, + "source": [ + "Using Gemini 2.5 via Langchain's Google Generative AI integration to test an agentic workflow." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a401cf8a", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "model = init_chat_model(\"google_genai:gemini-2.5-flash-lite\")" + ] + }, + { + "cell_type": "markdown", + "id": "aaa68979", + "metadata": {}, + "source": [ + "Setting up embeddings model" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "45805907", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "embeddings = GoogleGenerativeAIEmbeddings(model=\"models/gemini-embedding-001\")" + ] + }, + { + "cell_type": "markdown", + "id": "b3f90586", + "metadata": {}, + "source": [ + "Vector store setup for data storage and retrieval" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "500f90f4", + "metadata": {}, + "outputs": [], + "source": [ + "vector_store = Chroma(\n", + " collection_name=\"example_collection\",\n", + " embedding_function=embeddings,\n", + " persist_directory=\"./build/langchain_db\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d4ff7ec0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6,900 pages later… *“This story is just for that one reader.”* \n", + "*Omniscient Reader’s Viewpoint* is probably one of the most ambitious epics I’ve ever read in this genre. Regression-themed novels are already a flooded trope, but this one blows the rest out of the water purely from how many layers it stacks on top of itself and still manages to come out narratively clean. When I first got into this series (via the webtoon, like most people), the wait between weekly releases drove me up the wall,\n", + "Total characters: 8578\n" + ] + } + ], + "source": [ + "import requests\n", + "from langchain_core.documents import Document\n", + "\n", + "response = requests.get(\"https://viswamedha.com/api/post/a-story-for-one-reader/\")\n", + "data = response.json()\n", + "content = data['content']\n", + "\n", + "docs = [Document(page_content=content, metadata={\"source\": response.url})]\n", + "\n", + "assert len(docs) == 1\n", + "print(docs[0].page_content[:500])\n", + "print(f\"Total characters: {len(docs[0].page_content)}\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "82bcfabc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Split blog post into 13 sub-documents.\n" + ] + } + ], + "source": [ + "\n", + "text_splitter = RecursiveCharacterTextSplitter(\n", + " chunk_size=1000, \n", + " chunk_overlap=200, \n", + " add_start_index=True,\n", + ")\n", + "all_splits = text_splitter.split_documents(docs)\n", + "\n", + "print(f\"Split blog post into {len(all_splits)} sub-documents.\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2ee1a9ca", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['44706f38-6bd0-4e9a-8a41-d27790bdddc8', '9d2a2300-a311-4389-86b8-71eef221186c', '3970098b-f681-47bb-8c1d-6929cb67b537']\n" + ] + } + ], + "source": [ + "document_ids = vector_store.add_documents(documents=all_splits)\n", + "\n", + "print(document_ids[:3])" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "a9096893", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "@tool(response_format=\"content_and_artifact\")\n", + "def retrieve_context(query: str):\n", + " \"\"\"Retrieve information to help answer a query.\"\"\"\n", + " retrieved_docs = vector_store.similarity_search(query, k=2)\n", + " serialized = \"\\n\\n\".join(\n", + " (f\"Source: {doc.metadata}\\nContent: {doc.page_content}\")\n", + " for doc in retrieved_docs\n", + " )\n", + " return serialized, retrieved_docs" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "dff2345d", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "tools = [retrieve_context]\n", + "prompt = (\n", + " \"You have access to a tool that retrieves context from a blog post. \"\n", + " \"Use the tool to help answer user queries.\"\n", + ")\n", + "agent = create_agent(model, tools, system_prompt=prompt)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "aaa2fad9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "What is the significance of the second loop?\n", + "\n", + "Use the retrieved context to provide a detailed answer.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " retrieve_context (b6c5ce4e-a030-47cf-8fed-f1279f022766)\n", + " Call ID: b6c5ce4e-a030-47cf-8fed-f1279f022766\n", + " Args:\n", + " query: Significance of the second loop\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: retrieve_context\n", + "\n", + "Source: {'start_index': 3377, 'source': 'https://viswamedha.com/api/post/a-story-for-one-reader/'}\n", + "Content: And this is where the paradox really hits. The Great Plotter, while observing regressions and chasing a better ending, ends up **creating the very timeline** he’s been watching. In trying to fix his own story, he triggers a new one. He unknowingly causes the very events that lead to KDJ’s worldline existing in the first place. It's absolutely wild. He becomes the most influential figure in this timeline, yet completely powerless to interact with it directly (due to the constraints of Probability). All he can do is watch as KDJ lives through the story he thought he already knew.\n", + "\n", + "---\n", + "\n", + "## What is the second paradox, and where does the loop begin?\n", + "\n", + "Source: {'start_index': 3377, 'source': 'https://viswamedha.com/api/post/a-story-for-one-reader/'}\n", + "Content: And this is where the paradox really hits. The Great Plotter, while observing regressions and chasing a better ending, ends up **creating the very timeline** he’s been watching. In trying to fix his own story, he triggers a new one. He unknowingly causes the very events that lead to KDJ’s worldline existing in the first place. It's absolutely wild. He becomes the most influential figure in this timeline, yet completely powerless to interact with it directly (due to the constraints of Probability). All he can do is watch as KDJ lives through the story he thought he already knew.\n", + "\n", + "---\n", + "\n", + "## What is the second paradox, and where does the loop begin?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "The second loop is significant because it represents a paradox where the Great Plotter, in his attempt to alter his own story and create a better ending, inadvertently becomes the catalyst for the very timeline he is observing. He ends up creating the timeline he has been watching, triggering new events, and causing the existence of KDJ's worldline. Despite being the most influential figure in this new timeline, the Great Plotter is powerless to intervene directly and can only watch as KDJ experiences the story.\n" + ] + } + ], + "source": [ + "query = (\n", + " \"What is the significance of the second loop?\\n\\n\"\n", + " \"Use the retrieved context to provide a detailed answer.\"\n", + ")\n", + "\n", + "for event in agent.stream(\n", + " {\"messages\": [{\"role\": \"user\", \"content\": query}]},\n", + " stream_mode=\"values\",\n", + "):\n", + " event[\"messages\"][-1].pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "bda6d7d0", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "@dynamic_prompt\n", + "def prompt_with_context(request: ModelRequest) -> str:\n", + " \"\"\"Inject context into state messages.\"\"\"\n", + " last_query = request.state[\"messages\"][-1].text\n", + " retrieved_docs = vector_store.similarity_search(last_query)\n", + "\n", + " docs_content = \"\\n\\n\".join(doc.page_content for doc in retrieved_docs)\n", + "\n", + " system_message = (\n", + " \"You are a helpful assistant. Use the following context in your response:\"\n", + " f\"\\n\\n{docs_content}\"\n", + " )\n", + "\n", + " return system_message\n", + "\n", + "\n", + "agent = create_agent(model, tools=[], middleware=[prompt_with_context])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1540855c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'messages': [HumanMessage(content='What is the significance of the second loop?\\n\\n', additional_kwargs={}, response_metadata={}, id='eaca10e5-a350-4ad8-80ad-c62645b69e5a')]}\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "What is the significance of the second loop?\n", + "\n", + "\n" + ] + }, + { + "ename": "GoogleGenerativeAIError", + "evalue": "Error embedding content: 500 INTERNAL. {'error': {'code': 500, 'message': 'Internal error encountered.', 'status': 'INTERNAL'}}", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mServerError\u001b[39m Traceback (most recent call last)", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\langchain_google_genai\\embeddings.py:480\u001b[39m, in \u001b[36mGoogleGenerativeAIEmbeddings.embed_query\u001b[39m\u001b[34m(self, text, task_type, title, output_dimensionality)\u001b[39m\n\u001b[32m 479\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m480\u001b[39m result = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mclient\u001b[49m\u001b[43m.\u001b[49m\u001b[43mmodels\u001b[49m\u001b[43m.\u001b[49m\u001b[43membed_content\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 481\u001b[39m \u001b[43m \u001b[49m\u001b[43mmodel\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mmodel\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 482\u001b[39m \u001b[43m \u001b[49m\u001b[43mcontents\u001b[49m\u001b[43m=\u001b[49m\u001b[43mtext\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 483\u001b[39m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m=\u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 484\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 485\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m ClientError \u001b[38;5;28;01mas\u001b[39;00m e:\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\google\\genai\\models.py:4179\u001b[39m, in \u001b[36mModels.embed_content\u001b[39m\u001b[34m(self, model, contents, config)\u001b[39m\n\u001b[32m 4177\u001b[39m request_dict = _common.encode_unserializable_types(request_dict)\n\u001b[32m-> \u001b[39m\u001b[32m4179\u001b[39m response = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_api_client\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 4180\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mpost\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpath\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrequest_dict\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mhttp_options\u001b[49m\n\u001b[32m 4181\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 4183\u001b[39m response_dict = {} \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m response.body \u001b[38;5;28;01melse\u001b[39;00m json.loads(response.body)\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\google\\genai\\_api_client.py:1386\u001b[39m, in \u001b[36mBaseApiClient.request\u001b[39m\u001b[34m(self, http_method, path, request_dict, http_options)\u001b[39m\n\u001b[32m 1383\u001b[39m http_request = \u001b[38;5;28mself\u001b[39m._build_request(\n\u001b[32m 1384\u001b[39m http_method, path, request_dict, http_options\n\u001b[32m 1385\u001b[39m )\n\u001b[32m-> \u001b[39m\u001b[32m1386\u001b[39m response = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_request\u001b[49m\u001b[43m(\u001b[49m\u001b[43mhttp_request\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mhttp_options\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[32m 1387\u001b[39m response_body = (\n\u001b[32m 1388\u001b[39m response.response_stream[\u001b[32m0\u001b[39m] \u001b[38;5;28;01mif\u001b[39;00m response.response_stream \u001b[38;5;28;01melse\u001b[39;00m \u001b[33m'\u001b[39m\u001b[33m'\u001b[39m\n\u001b[32m 1389\u001b[39m )\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\google\\genai\\_api_client.py:1222\u001b[39m, in \u001b[36mBaseApiClient._request\u001b[39m\u001b[34m(self, http_request, http_options, stream)\u001b[39m\n\u001b[32m 1220\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m retry(\u001b[38;5;28mself\u001b[39m._request_once, http_request, stream) \u001b[38;5;66;03m# type: ignore[no-any-return]\u001b[39;00m\n\u001b[32m-> \u001b[39m\u001b[32m1222\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_retry\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_request_once\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mhttp_request\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\tenacity\\__init__.py:477\u001b[39m, in \u001b[36mRetrying.__call__\u001b[39m\u001b[34m(self, fn, *args, **kwargs)\u001b[39m\n\u001b[32m 476\u001b[39m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m477\u001b[39m do = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43miter\u001b[49m\u001b[43m(\u001b[49m\u001b[43mretry_state\u001b[49m\u001b[43m=\u001b[49m\u001b[43mretry_state\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 478\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(do, DoAttempt):\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\tenacity\\__init__.py:378\u001b[39m, in \u001b[36mBaseRetrying.iter\u001b[39m\u001b[34m(self, retry_state)\u001b[39m\n\u001b[32m 377\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m action \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m.iter_state.actions:\n\u001b[32m--> \u001b[39m\u001b[32m378\u001b[39m result = \u001b[43maction\u001b[49m\u001b[43m(\u001b[49m\u001b[43mretry_state\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 379\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m result\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\tenacity\\__init__.py:420\u001b[39m, in \u001b[36mBaseRetrying._post_stop_check_actions..exc_check\u001b[39m\u001b[34m(rs)\u001b[39m\n\u001b[32m 419\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m.reraise:\n\u001b[32m--> \u001b[39m\u001b[32m420\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[43mretry_exc\u001b[49m\u001b[43m.\u001b[49m\u001b[43mreraise\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 421\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m retry_exc \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mfut\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mexception\u001b[39;00m()\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\tenacity\\__init__.py:187\u001b[39m, in \u001b[36mRetryError.reraise\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 186\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m.last_attempt.failed:\n\u001b[32m--> \u001b[39m\u001b[32m187\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mlast_attempt\u001b[49m\u001b[43m.\u001b[49m\u001b[43mresult\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 188\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;28mself\u001b[39m\n", + "\u001b[36mFile \u001b[39m\u001b[32mC:\\Program Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.13_3.13.2544.0_x64__qbz5n2kfra8p0\\Lib\\concurrent\\futures\\_base.py:449\u001b[39m, in \u001b[36mFuture.result\u001b[39m\u001b[34m(self, timeout)\u001b[39m\n\u001b[32m 448\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mself\u001b[39m._state == FINISHED:\n\u001b[32m--> \u001b[39m\u001b[32m449\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m__get_result\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 451\u001b[39m \u001b[38;5;28mself\u001b[39m._condition.wait(timeout)\n", + "\u001b[36mFile \u001b[39m\u001b[32mC:\\Program Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.13_3.13.2544.0_x64__qbz5n2kfra8p0\\Lib\\concurrent\\futures\\_base.py:401\u001b[39m, in \u001b[36mFuture.__get_result\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 400\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m401\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;28mself\u001b[39m._exception\n\u001b[32m 402\u001b[39m \u001b[38;5;28;01mfinally\u001b[39;00m:\n\u001b[32m 403\u001b[39m \u001b[38;5;66;03m# Break a reference cycle with the exception in self._exception\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\tenacity\\__init__.py:480\u001b[39m, in \u001b[36mRetrying.__call__\u001b[39m\u001b[34m(self, fn, *args, **kwargs)\u001b[39m\n\u001b[32m 479\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m480\u001b[39m result = \u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 481\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m: \u001b[38;5;66;03m# noqa: B902\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\google\\genai\\_api_client.py:1199\u001b[39m, in \u001b[36mBaseApiClient._request_once\u001b[39m\u001b[34m(self, http_request, stream)\u001b[39m\n\u001b[32m 1192\u001b[39m response = \u001b[38;5;28mself\u001b[39m._httpx_client.request(\n\u001b[32m 1193\u001b[39m method=http_request.method,\n\u001b[32m 1194\u001b[39m url=http_request.url,\n\u001b[32m (...)\u001b[39m\u001b[32m 1197\u001b[39m timeout=http_request.timeout,\n\u001b[32m 1198\u001b[39m )\n\u001b[32m-> \u001b[39m\u001b[32m1199\u001b[39m \u001b[43merrors\u001b[49m\u001b[43m.\u001b[49m\u001b[43mAPIError\u001b[49m\u001b[43m.\u001b[49m\u001b[43mraise_for_response\u001b[49m\u001b[43m(\u001b[49m\u001b[43mresponse\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1200\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m HttpResponse(\n\u001b[32m 1201\u001b[39m response.headers, response \u001b[38;5;28;01mif\u001b[39;00m stream \u001b[38;5;28;01melse\u001b[39;00m [response.text]\n\u001b[32m 1202\u001b[39m )\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\google\\genai\\errors.py:121\u001b[39m, in \u001b[36mAPIError.raise_for_response\u001b[39m\u001b[34m(cls, response)\u001b[39m\n\u001b[32m 119\u001b[39m response_json = response.body_segments[\u001b[32m0\u001b[39m].get(\u001b[33m'\u001b[39m\u001b[33merror\u001b[39m\u001b[33m'\u001b[39m, {})\n\u001b[32m--> \u001b[39m\u001b[32m121\u001b[39m \u001b[38;5;28;43mcls\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mraise_error\u001b[49m\u001b[43m(\u001b[49m\u001b[43mresponse\u001b[49m\u001b[43m.\u001b[49m\u001b[43mstatus_code\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mresponse_json\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mresponse\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\google\\genai\\errors.py:148\u001b[39m, in \u001b[36mAPIError.raise_error\u001b[39m\u001b[34m(cls, status_code, response_json, response)\u001b[39m\n\u001b[32m 147\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m \u001b[32m500\u001b[39m <= status_code < \u001b[32m600\u001b[39m:\n\u001b[32m--> \u001b[39m\u001b[32m148\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m ServerError(status_code, response_json, response)\n\u001b[32m 149\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n", + "\u001b[31mServerError\u001b[39m: 500 INTERNAL. {'error': {'code': 500, 'message': 'Internal error encountered.', 'status': 'INTERNAL'}}", + "\nThe above exception was the direct cause of the following exception:\n", + "\u001b[31mGoogleGenerativeAIError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[13]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mstep\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43magent\u001b[49m\u001b[43m.\u001b[49m\u001b[43mstream\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 2\u001b[39m \u001b[43m \u001b[49m\u001b[43m{\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mmessages\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43m[\u001b[49m\u001b[43m{\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mrole\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43muser\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mcontent\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mquery\u001b[49m\u001b[43m}\u001b[49m\u001b[43m]\u001b[49m\u001b[43m}\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 3\u001b[39m \u001b[43m \u001b[49m\u001b[43mstream_mode\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mvalues\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 4\u001b[39m \u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[32m 5\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mimport\u001b[39;49;00m\u001b[38;5;250;43m \u001b[39;49m\u001b[34;43;01mpprint\u001b[39;49;00m\n\u001b[32m 6\u001b[39m \u001b[43m \u001b[49m\u001b[43mpprint\u001b[49m\u001b[43m.\u001b[49m\u001b[43mpprint\u001b[49m\u001b[43m(\u001b[49m\u001b[43mstep\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# inspect the event structure\u001b[39;49;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\langgraph\\pregel\\main.py:2646\u001b[39m, in \u001b[36mPregel.stream\u001b[39m\u001b[34m(self, input, config, context, stream_mode, print_mode, output_keys, interrupt_before, interrupt_after, durability, subgraphs, debug, **kwargs)\u001b[39m\n\u001b[32m 2644\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m task \u001b[38;5;129;01min\u001b[39;00m loop.match_cached_writes():\n\u001b[32m 2645\u001b[39m loop.output_writes(task.id, task.writes, cached=\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[32m-> \u001b[39m\u001b[32m2646\u001b[39m \u001b[43m\u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43m_\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mrunner\u001b[49m\u001b[43m.\u001b[49m\u001b[43mtick\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 2647\u001b[39m \u001b[43m \u001b[49m\u001b[43m[\u001b[49m\u001b[43mt\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mt\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mloop\u001b[49m\u001b[43m.\u001b[49m\u001b[43mtasks\u001b[49m\u001b[43m.\u001b[49m\u001b[43mvalues\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mnot\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mt\u001b[49m\u001b[43m.\u001b[49m\u001b[43mwrites\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 2648\u001b[39m \u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mstep_timeout\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 2649\u001b[39m \u001b[43m \u001b[49m\u001b[43mget_waiter\u001b[49m\u001b[43m=\u001b[49m\u001b[43mget_waiter\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 2650\u001b[39m \u001b[43m \u001b[49m\u001b[43mschedule_task\u001b[49m\u001b[43m=\u001b[49m\u001b[43mloop\u001b[49m\u001b[43m.\u001b[49m\u001b[43maccept_push\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 2651\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[32m 2652\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# emit output\u001b[39;49;00m\n\u001b[32m 2653\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43;01myield from\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43m_output\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 2654\u001b[39m \u001b[43m \u001b[49m\u001b[43mstream_mode\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mprint_mode\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msubgraphs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mqueue\u001b[49m\u001b[43m.\u001b[49m\u001b[43mEmpty\u001b[49m\n\u001b[32m 2655\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 2656\u001b[39m loop.after_tick()\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\langgraph\\pregel\\_runner.py:167\u001b[39m, in \u001b[36mPregelRunner.tick\u001b[39m\u001b[34m(self, tasks, reraise, timeout, retry_policy, get_waiter, schedule_task)\u001b[39m\n\u001b[32m 165\u001b[39m t = tasks[\u001b[32m0\u001b[39m]\n\u001b[32m 166\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m167\u001b[39m \u001b[43mrun_with_retry\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 168\u001b[39m \u001b[43m \u001b[49m\u001b[43mt\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 169\u001b[39m \u001b[43m \u001b[49m\u001b[43mretry_policy\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 170\u001b[39m \u001b[43m \u001b[49m\u001b[43mconfigurable\u001b[49m\u001b[43m=\u001b[49m\u001b[43m{\u001b[49m\n\u001b[32m 171\u001b[39m \u001b[43m \u001b[49m\u001b[43mCONFIG_KEY_CALL\u001b[49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mpartial\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 172\u001b[39m \u001b[43m \u001b[49m\u001b[43m_call\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 173\u001b[39m \u001b[43m \u001b[49m\u001b[43mweakref\u001b[49m\u001b[43m.\u001b[49m\u001b[43mref\u001b[49m\u001b[43m(\u001b[49m\u001b[43mt\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 174\u001b[39m \u001b[43m \u001b[49m\u001b[43mretry_policy\u001b[49m\u001b[43m=\u001b[49m\u001b[43mretry_policy\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 175\u001b[39m \u001b[43m \u001b[49m\u001b[43mfutures\u001b[49m\u001b[43m=\u001b[49m\u001b[43mweakref\u001b[49m\u001b[43m.\u001b[49m\u001b[43mref\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfutures\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 176\u001b[39m \u001b[43m \u001b[49m\u001b[43mschedule_task\u001b[49m\u001b[43m=\u001b[49m\u001b[43mschedule_task\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 177\u001b[39m \u001b[43m \u001b[49m\u001b[43msubmit\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43msubmit\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 178\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 179\u001b[39m \u001b[43m \u001b[49m\u001b[43m}\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 180\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 181\u001b[39m \u001b[38;5;28mself\u001b[39m.commit(t, \u001b[38;5;28;01mNone\u001b[39;00m)\n\u001b[32m 182\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m exc:\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\langgraph\\pregel\\_retry.py:42\u001b[39m, in \u001b[36mrun_with_retry\u001b[39m\u001b[34m(task, retry_policy, configurable)\u001b[39m\n\u001b[32m 40\u001b[39m task.writes.clear()\n\u001b[32m 41\u001b[39m \u001b[38;5;66;03m# run the task\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m42\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mtask\u001b[49m\u001b[43m.\u001b[49m\u001b[43mproc\u001b[49m\u001b[43m.\u001b[49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtask\u001b[49m\u001b[43m.\u001b[49m\u001b[43minput\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 43\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m ParentCommand \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[32m 44\u001b[39m ns: \u001b[38;5;28mstr\u001b[39m = config[CONF][CONFIG_KEY_CHECKPOINT_NS]\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\langgraph\\_internal\\_runnable.py:656\u001b[39m, in \u001b[36mRunnableSeq.invoke\u001b[39m\u001b[34m(self, input, config, **kwargs)\u001b[39m\n\u001b[32m 654\u001b[39m \u001b[38;5;66;03m# run in context\u001b[39;00m\n\u001b[32m 655\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m set_config_context(config, run) \u001b[38;5;28;01mas\u001b[39;00m context:\n\u001b[32m--> \u001b[39m\u001b[32m656\u001b[39m \u001b[38;5;28minput\u001b[39m = \u001b[43mcontext\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\u001b[43mstep\u001b[49m\u001b[43m.\u001b[49m\u001b[43minvoke\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 657\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 658\u001b[39m \u001b[38;5;28minput\u001b[39m = step.invoke(\u001b[38;5;28minput\u001b[39m, config)\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\langgraph\\_internal\\_runnable.py:400\u001b[39m, in \u001b[36mRunnableCallable.invoke\u001b[39m\u001b[34m(self, input, config, **kwargs)\u001b[39m\n\u001b[32m 398\u001b[39m run_manager.on_chain_end(ret)\n\u001b[32m 399\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m400\u001b[39m ret = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 401\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m.recurse \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(ret, Runnable):\n\u001b[32m 402\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m ret.invoke(\u001b[38;5;28minput\u001b[39m, config)\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\langchain\\agents\\factory.py:1144\u001b[39m, in \u001b[36mcreate_agent..model_node\u001b[39m\u001b[34m(state, runtime)\u001b[39m\n\u001b[32m 1141\u001b[39m response = _execute_model_sync(request)\n\u001b[32m 1142\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 1143\u001b[39m \u001b[38;5;66;03m# Call composed handler with base handler\u001b[39;00m\n\u001b[32m-> \u001b[39m\u001b[32m1144\u001b[39m response = \u001b[43mwrap_model_call_handler\u001b[49m\u001b[43m(\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m_execute_model_sync\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1146\u001b[39m \u001b[38;5;66;03m# Extract state updates from ModelResponse\u001b[39;00m\n\u001b[32m 1147\u001b[39m state_updates = {\u001b[33m\"\u001b[39m\u001b[33mmessages\u001b[39m\u001b[33m\"\u001b[39m: response.result}\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\langchain\\agents\\factory.py:146\u001b[39m, in \u001b[36m_chain_model_call_handlers..normalized_single\u001b[39m\u001b[34m(request, handler)\u001b[39m\n\u001b[32m 142\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mnormalized_single\u001b[39m(\n\u001b[32m 143\u001b[39m request: ModelRequest,\n\u001b[32m 144\u001b[39m handler: Callable[[ModelRequest], ModelResponse],\n\u001b[32m 145\u001b[39m ) -> ModelResponse:\n\u001b[32m--> \u001b[39m\u001b[32m146\u001b[39m result = \u001b[43msingle_handler\u001b[49m\u001b[43m(\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mhandler\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 147\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m _normalize_to_model_response(result)\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\langchain\\agents\\middleware\\types.py:1656\u001b[39m, in \u001b[36mdynamic_prompt..decorator..wrapped\u001b[39m\u001b[34m(_self, request, handler)\u001b[39m\n\u001b[32m 1651\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mwrapped\u001b[39m(\n\u001b[32m 1652\u001b[39m _self: AgentMiddleware[StateT, ContextT],\n\u001b[32m 1653\u001b[39m request: ModelRequest,\n\u001b[32m 1654\u001b[39m handler: Callable[[ModelRequest], ModelResponse],\n\u001b[32m 1655\u001b[39m ) -> ModelCallResult:\n\u001b[32m-> \u001b[39m\u001b[32m1656\u001b[39m prompt = \u001b[43mcast\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mCallable[[ModelRequest], SystemMessage | str]\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfunc\u001b[49m\u001b[43m)\u001b[49m\u001b[43m(\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1657\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(prompt, SystemMessage):\n\u001b[32m 1658\u001b[39m request = request.override(system_message=prompt)\n", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[11]\u001b[39m\u001b[32m, line 5\u001b[39m, in \u001b[36mprompt_with_context\u001b[39m\u001b[34m(request)\u001b[39m\n\u001b[32m 3\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"Inject context into state messages.\"\"\"\u001b[39;00m\n\u001b[32m 4\u001b[39m last_query = request.state[\u001b[33m\"\u001b[39m\u001b[33mmessages\u001b[39m\u001b[33m\"\u001b[39m][-\u001b[32m1\u001b[39m].text\n\u001b[32m----> \u001b[39m\u001b[32m5\u001b[39m retrieved_docs = \u001b[43mvector_store\u001b[49m\u001b[43m.\u001b[49m\u001b[43msimilarity_search\u001b[49m\u001b[43m(\u001b[49m\u001b[43mlast_query\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 7\u001b[39m docs_content = \u001b[33m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[33m\"\u001b[39m.join(doc.page_content \u001b[38;5;28;01mfor\u001b[39;00m doc \u001b[38;5;129;01min\u001b[39;00m retrieved_docs)\n\u001b[32m 9\u001b[39m system_message = (\n\u001b[32m 10\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mYou are a helpful assistant. Use the following context in your response:\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 11\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;132;01m{\u001b[39;00mdocs_content\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m 12\u001b[39m )\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\langchain_chroma\\vectorstores.py:748\u001b[39m, in \u001b[36mChroma.similarity_search\u001b[39m\u001b[34m(self, query, k, filter, **kwargs)\u001b[39m\n\u001b[32m 730\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34msimilarity_search\u001b[39m(\n\u001b[32m 731\u001b[39m \u001b[38;5;28mself\u001b[39m,\n\u001b[32m 732\u001b[39m query: \u001b[38;5;28mstr\u001b[39m,\n\u001b[32m (...)\u001b[39m\u001b[32m 735\u001b[39m **kwargs: Any,\n\u001b[32m 736\u001b[39m ) -> \u001b[38;5;28mlist\u001b[39m[Document]:\n\u001b[32m 737\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"Run similarity search with Chroma.\u001b[39;00m\n\u001b[32m 738\u001b[39m \n\u001b[32m 739\u001b[39m \u001b[33;03m Args:\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 746\u001b[39m \u001b[33;03m List of documents most similar to the query text.\u001b[39;00m\n\u001b[32m 747\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m748\u001b[39m docs_and_scores = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43msimilarity_search_with_score\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 749\u001b[39m \u001b[43m \u001b[49m\u001b[43mquery\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 750\u001b[39m \u001b[43m \u001b[49m\u001b[43mk\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 751\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43mfilter\u001b[39;49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mfilter\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 752\u001b[39m \u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 753\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 754\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m [doc \u001b[38;5;28;01mfor\u001b[39;00m doc, _ \u001b[38;5;129;01min\u001b[39;00m docs_and_scores]\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\langchain_chroma\\vectorstores.py:848\u001b[39m, in \u001b[36mChroma.similarity_search_with_score\u001b[39m\u001b[34m(self, query, k, filter, where_document, **kwargs)\u001b[39m\n\u001b[32m 840\u001b[39m results = \u001b[38;5;28mself\u001b[39m.__query_collection(\n\u001b[32m 841\u001b[39m query_texts=[query],\n\u001b[32m 842\u001b[39m n_results=k,\n\u001b[32m (...)\u001b[39m\u001b[32m 845\u001b[39m **kwargs,\n\u001b[32m 846\u001b[39m )\n\u001b[32m 847\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m848\u001b[39m query_embedding = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_embedding_function\u001b[49m\u001b[43m.\u001b[49m\u001b[43membed_query\u001b[49m\u001b[43m(\u001b[49m\u001b[43mquery\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 849\u001b[39m results = \u001b[38;5;28mself\u001b[39m.__query_collection(\n\u001b[32m 850\u001b[39m query_embeddings=[query_embedding],\n\u001b[32m 851\u001b[39m n_results=k,\n\u001b[32m (...)\u001b[39m\u001b[32m 854\u001b[39m **kwargs,\n\u001b[32m 855\u001b[39m )\n\u001b[32m 857\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m _results_to_docs_and_scores(results)\n", + "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\nalab\\University\\vxn217\\.venv\\Lib\\site-packages\\langchain_google_genai\\embeddings.py:490\u001b[39m, in \u001b[36mGoogleGenerativeAIEmbeddings.embed_query\u001b[39m\u001b[34m(self, text, task_type, title, output_dimensionality)\u001b[39m\n\u001b[32m 488\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m 489\u001b[39m msg = \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mError embedding content: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00me\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m--> \u001b[39m\u001b[32m490\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m GoogleGenerativeAIError(msg) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01me\u001b[39;00m\n\u001b[32m 492\u001b[39m \u001b[38;5;66;03m# Single text returns single embedding\u001b[39;00m\n\u001b[32m 493\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mlist\u001b[39m(result.embeddings[\u001b[32m0\u001b[39m].values)\n", + "\u001b[31mGoogleGenerativeAIError\u001b[39m: Error embedding content: 500 INTERNAL. {'error': {'code': 500, 'message': 'Internal error encountered.', 'status': 'INTERNAL'}}", + "During task with name 'model' and id '2df4c75f-65ba-cd3e-b448-0ed95a7614f8'" + ] + } + ], + "source": [ + "query = \"What is the significance of the second loop?\\n\\n\"\n", + "for step in agent.stream(\n", + " {\"messages\": [{\"role\": \"user\", \"content\": query}]},\n", + " stream_mode=\"values\",\n", + "):\n", + " step[\"messages\"][-1].pretty_print()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fec8090 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,62 @@ +{ + "name": "FYPtest", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "dompurify": "^3.3.1", + "marked": "^17.0.3" + }, + "devDependencies": { + "@types/dompurify": "^3.0.5", + "@types/marked": "^5.0.2" + } + }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "*" + } + }, + "node_modules/@types/marked": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.2.tgz", + "integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/dompurify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/marked": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.3.tgz", + "integrity": "sha512-jt1v2ObpyOKR8p4XaUJVk3YWRJ5n+i4+rjQopxvV32rSndTJXvIzuUdWWIy/1pFQMkQmvTXawzDNqOH/CUmx6A==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6e28795 --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "dependencies": { + "dompurify": "^3.3.1", + "marked": "^17.0.3" + }, + "devDependencies": { + "@types/dompurify": "^3.0.5", + "@types/marked": "^5.0.2" + } +} diff --git a/requirements/django.txt b/requirements/django.txt new file mode 100644 index 0000000..428be04 --- /dev/null +++ b/requirements/django.txt @@ -0,0 +1,16 @@ +celery[redis]==5.6.2 +channels==4.3.2 +channels-redis==4.3.0 +daphne==4.2.1 +django==6.0.2 +django-celery-results==2.6.0 +django-cors-headers==4.9.0 +djangorestframework==3.16.1 +django-unfold==0.81.0 +httpx==0.28.1 +pgvector==0.4.2 +psycopg2-binary==2.9.10 +pypdf==5.4.0 +python-docx==1.2.0 +python-dotenv==1.2.1 +whitenoise==6.11.0 \ No newline at end of file diff --git a/requirements/inference.txt b/requirements/inference.txt new file mode 100644 index 0000000..b414b43 --- /dev/null +++ b/requirements/inference.txt @@ -0,0 +1,8 @@ +--extra-index-url https://download.pytorch.org/whl/cu130 +einops==0.8.0 +fastapi[standard]==0.133.0 +requests==2.32.5 +sentence-transformers==5.2.0 +torch==2.9.1+cu130 +torchaudio==2.9.1+cu130 +torchvision==0.24.1+cu130 \ No newline at end of file diff --git a/site/.prettierignore b/site/.prettierignore new file mode 100644 index 0000000..a094765 --- /dev/null +++ b/site/.prettierignore @@ -0,0 +1,5 @@ +# Ignore docker-compose and other compose YAML files from Prettier formatting +docker-compose*.yml +docker-compose*.yaml +compose/**/*.yml +compose/**/*.yaml diff --git a/site/.prettierrc.json b/site/.prettierrc.json new file mode 100644 index 0000000..9999b15 --- /dev/null +++ b/site/.prettierrc.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "useTabs": false, + "tabWidth": 4, + "singleQuote": true, + "printWidth": 100, + "htmlWhitespaceSensitivity": "ignore" +} diff --git a/site/env.d.ts b/site/env.d.ts new file mode 100644 index 0000000..5a35078 --- /dev/null +++ b/site/env.d.ts @@ -0,0 +1,12 @@ + +declare module '*.vue' { + import { DefineComponent } from 'vue' + const component: DefineComponent< + Record, + Record, + Record, + Record, + Record + > + export default component +} diff --git a/site/eslint.config.ts b/site/eslint.config.ts new file mode 100644 index 0000000..ea3ba4b --- /dev/null +++ b/site/eslint.config.ts @@ -0,0 +1,31 @@ +import { globalIgnores } from 'eslint/config' +import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript' +import pluginVue from 'eslint-plugin-vue' +import skipFormatting from '@vue/eslint-config-prettier/skip-formatting' + + + + + + +export default defineConfigWithVueTs( + { + name: 'app/files-to-lint', + files: ['**/*.{vue,ts,mts,tsx}'], + }, + + globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']), + + ...pluginVue.configs['flat/essential'], + vueTsConfigs.recommended, + { + rules: { + indent: ['error', 4, { SwitchCase: 1 }], + 'vue/html-indent': ['error', 4], + 'vue/script-indent': ['error', 4, { baseIndent: 1, switchCase: 1 }], + 'vue/block-lang': 'off', + }, + }, + + skipFormatting, +) diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..e4a7bd8 --- /dev/null +++ b/site/index.html @@ -0,0 +1,16 @@ + + + + + + + + + + Dynavera + + +

+ + + diff --git a/site/package-lock.json b/site/package-lock.json new file mode 100644 index 0000000..657c18c --- /dev/null +++ b/site/package-lock.json @@ -0,0 +1,5915 @@ +{ + "name": "dynavera", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dynavera", + "version": "0.0.0", + "dependencies": { + "ant-design-vue": "^4.2.6", + "axios": "^1.13.2", + "dompurify": "^3.3.1", + "marked": "^17.0.3", + "pinia": "^3.0.4", + "vue": "^3.5.26", + "vue-router": "^4.6.4" + }, + "devDependencies": { + "@tsconfig/node24": "^24.0.3", + "@types/dompurify": "^3.0.5", + "@types/marked": "^5.0.2", + "@types/node": "^24.10.4", + "@vitejs/plugin-vue": "^6.0.3", + "@vitejs/plugin-vue-jsx": "^5.1.3", + "@vue/eslint-config-prettier": "^10.2.0", + "@vue/eslint-config-typescript": "^14.6.0", + "@vue/tsconfig": "^0.8.1", + "concurrently": "^8.2.0", + "eslint": "^9.39.2", + "eslint-plugin-vue": "~10.6.2", + "jiti": "^2.6.1", + "npm-run-all2": "^8.0.4", + "prettier": "3.7.4", + "typescript": "~5.9.3", + "vite": "^7.3.0", + "vite-plugin-vue-devtools": "^8.0.5", + "vue-tsc": "^3.2.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT" + }, + "node_modules/@ant-design/icons-vue": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons-vue/-/icons-vue-7.0.1.tgz", + "integrity": "sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-svg": "^4.2.1" + }, + "peerDependencies": { + "vue": ">=3.0.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.28.6.tgz", + "integrity": "sha512-RVdFPPyY9fCRAX68haPmOk2iyKW8PKJFthmm8NeSI3paNxKWGZIn99+VbIf0FrtCpFnPgnpF/L48tadi617ULg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-decorators": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz", + "integrity": "sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@simonwep/pickr": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@simonwep/pickr/-/pickr-1.8.2.tgz", + "integrity": "sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==", + "license": "MIT", + "dependencies": { + "core-js": "^3.15.1", + "nanopop": "^2.1.0" + } + }, + "node_modules/@tsconfig/node24": { + "version": "24.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node24/-/node24-24.0.4.tgz", + "integrity": "sha512-2A933l5P5oCbv6qSxHs7ckKwobs8BDAe9SJ/Xr2Hy+nDlwmLE1GhFh/g/vXGRZWgxBg9nX/5piDtHR9Dkw/XuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/marked": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.2.tgz", + "integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", + "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.0.tgz", + "integrity": "sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.53.0", + "@typescript-eslint/type-utils": "8.53.0", + "@typescript-eslint/utils": "8.53.0", + "@typescript-eslint/visitor-keys": "8.53.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.53.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.0.tgz", + "integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.53.0", + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/typescript-estree": "8.53.0", + "@typescript-eslint/visitor-keys": "8.53.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.0.tgz", + "integrity": "sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.53.0", + "@typescript-eslint/types": "^8.53.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.0.tgz", + "integrity": "sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/visitor-keys": "8.53.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.0.tgz", + "integrity": "sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.0.tgz", + "integrity": "sha512-BBAUhlx7g4SmcLhn8cnbxoxtmS7hcq39xKCgiutL3oNx1TaIp+cny51s8ewnKMpVUKQUGb41RAUWZ9kxYdovuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/typescript-estree": "8.53.0", + "@typescript-eslint/utils": "8.53.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.0.tgz", + "integrity": "sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.0.tgz", + "integrity": "sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.53.0", + "@typescript-eslint/tsconfig-utils": "8.53.0", + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/visitor-keys": "8.53.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.0.tgz", + "integrity": "sha512-XDY4mXTez3Z1iRDI5mbRhH4DFSt46oaIFsLg+Zn97+sYrXACziXSQcSelMybnVZ5pa1P6xYkPr5cMJyunM1ZDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.53.0", + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/typescript-estree": "8.53.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.0.tgz", + "integrity": "sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.53.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.3.tgz", + "integrity": "sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.53" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitejs/plugin-vue-jsx": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-5.1.3.tgz", + "integrity": "sha512-I6Zr8cYVr5WHMW5gNOP09DNqW9rgO8RX73Wa6Czgq/0ndpTfJM4vfDChfOT1+3KtdrNqilNBtNlFwVeB02ZzGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5", + "@rolldown/pluginutils": "^1.0.0-beta.56", + "@vue/babel-plugin-jsx": "^2.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", + "vue": "^3.0.0" + } + }, + "node_modules/@vitejs/plugin-vue-jsx/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.60.tgz", + "integrity": "sha512-Jz4aqXRPVtqkH1E3jRDzLO5cgN5JwW+WG0wXGE4NiJd25nougv/AHzxmKCzmVQUYnxLmTM0M4wrZp+LlC2FKLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/language-core": { + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.27.tgz", + "integrity": "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.27" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.27.tgz", + "integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.27.tgz", + "integrity": "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.27", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-2.0.1.tgz", + "integrity": "sha512-uZ66EaFbnnZSYqYEyplWvn46GhZ1KuYSThdT68p+am7MgBNbQ3hphTL9L+xSIsWkdktwhPYLwPgVWqo96jDdRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-2.0.1.tgz", + "integrity": "sha512-a8CaLQjD/s4PVdhrLD/zT574ZNPnZBOY+IhdtKWRB4HRZ0I2tXBi5ne7d9eCfaYwp5gU5+4KIyFTV1W1YL9xZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@vue/babel-helper-vue-transform-on": "2.0.1", + "@vue/babel-plugin-resolve-type": "2.0.1", + "@vue/shared": "^3.5.22" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-2.0.1.tgz", + "integrity": "sha512-ybwgIuRGRRBhOU37GImDoWQoz+TlSqap65qVI6iwg/J7FfLTLmMf97TS7xQH9I7Qtr/gp161kYVdhr1ZMraSYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/parser": "^7.28.4", + "@vue/compiler-sfc": "^3.5.22" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.26.tgz", + "integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.26", + "entities": "^7.0.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz", + "integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.26", + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz", + "integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/compiler-core": "3.5.26", + "@vue/compiler-dom": "3.5.26", + "@vue/compiler-ssr": "3.5.26", + "@vue/shared": "3.5.26", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz", + "integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.26", + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-core": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-8.0.5.tgz", + "integrity": "sha512-dpCw8nl0GDBuiL9SaY0mtDxoGIEmU38w+TQiYEPOLhW03VDC0lfNMYXS/qhl4I0YlysGp04NLY4UNn6xgD0VIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.0.5", + "@vue/devtools-shared": "^8.0.5", + "mitt": "^3.0.1", + "nanoid": "^5.1.5", + "pathe": "^2.0.3", + "vite-hot-client": "^2.1.0" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-core/node_modules/@vue/devtools-kit": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.0.5.tgz", + "integrity": "sha512-q2VV6x1U3KJMTQPUlRMyWEKVbcHuxhqJdSr6Jtjz5uAThAIrfJ6WVZdGZm5cuO63ZnSUz0RCsVwiUUb0mDV0Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.0.5", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^2.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-core/node_modules/@vue/devtools-shared": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.0.5.tgz", + "integrity": "sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/devtools-core/node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@vue/devtools-core/node_modules/perfect-debounce": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz", + "integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/eslint-config-prettier": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-10.2.0.tgz", + "integrity": "sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.2" + }, + "peerDependencies": { + "eslint": ">= 8.21.0", + "prettier": ">= 3.0.0" + } + }, + "node_modules/@vue/eslint-config-typescript": { + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.6.0.tgz", + "integrity": "sha512-UpiRY/7go4Yps4mYCjkvlIbVWmn9YvPGQDxTAlcKLphyaD77LjIu3plH4Y9zNT0GB4f3K5tMmhhtRhPOgrQ/bQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.35.1", + "fast-glob": "^3.3.3", + "typescript-eslint": "^8.35.1", + "vue-eslint-parser": "^10.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0", + "eslint-plugin-vue": "^9.28.0 || ^10.0.0", + "typescript": ">=4.8.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.2.2.tgz", + "integrity": "sha512-5DAuhxsxBN9kbriklh3Q5AMaJhyOCNiQJvCskN9/30XOpdLiqZU9Q+WvjArP17ubdGEyZtBzlIeG5nIjEbNOrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.27", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.0.0", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.2" + } + }, + "node_modules/@vue/language-core/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.26.tgz", + "integrity": "sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.26.tgz", + "integrity": "sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.26", + "@vue/shared": "3.5.26" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.26.tgz", + "integrity": "sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.26", + "@vue/runtime-core": "3.5.26", + "@vue/shared": "3.5.26", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.26.tgz", + "integrity": "sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.26", + "@vue/shared": "3.5.26" + }, + "peerDependencies": { + "vue": "3.5.26" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.26.tgz", + "integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.8.1.tgz", + "integrity": "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "5.x", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/alien-signals": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.2.tgz", + "integrity": "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/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/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/ant-design-vue": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-4.2.6.tgz", + "integrity": "sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-vue": "^7.0.0", + "@babel/runtime": "^7.10.5", + "@ctrl/tinycolor": "^3.5.0", + "@emotion/hash": "^0.9.0", + "@emotion/unitless": "^0.8.0", + "@simonwep/pickr": "~1.8.0", + "array-tree-filter": "^2.1.0", + "async-validator": "^4.0.0", + "csstype": "^3.1.1", + "dayjs": "^1.10.5", + "dom-align": "^1.12.1", + "dom-scroll-into-view": "^2.0.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.15", + "resize-observer-polyfill": "^1.5.1", + "scroll-into-view-if-needed": "^2.2.25", + "shallow-equal": "^1.0.0", + "stylis": "^4.1.3", + "throttle-debounce": "^5.0.0", + "vue-types": "^3.0.0", + "warning": "^4.0.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design-vue" + }, + "peerDependencies": { + "vue": ">=3.2.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-tree-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz", + "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==", + "license": "MIT" + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.15", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz", + "integrity": "sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001764", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", + "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", + "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/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/core-js": { + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", + "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "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/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/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/default-browser": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", + "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dom-align": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.4.tgz", + "integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==", + "license": "MIT" + }, + "node_modules/dom-scroll-into-view": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz", + "integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==", + "license": "MIT" + }, + "node_modules/dompurify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz", + "integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-vue": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.6.2.tgz", + "integrity": "sha512-nA5yUs/B1KmKzvC42fyD0+l9Yd+LtEpVhWRbXuDj0e+ZURcTtyRbMDWUeJmTAh2wC6jC83raS63anNM2YT3NPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^7.1.0", + "semver": "^7.6.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", + "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "vue-eslint-parser": "^10.0.0" + }, + "peerDependenciesMeta": { + "@stylistic/eslint-plugin": { + "optional": true + }, + "@typescript-eslint/parser": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-vue/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/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-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "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/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", + "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/marked": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.3.tgz", + "integrity": "sha512-jt1v2ObpyOKR8p4XaUJVk3YWRJ5n+i4+rjQopxvV32rSndTJXvIzuUdWWIy/1pFQMkQmvTXawzDNqOH/CUmx6A==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanopop": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/nanopop/-/nanopop-2.4.2.tgz", + "integrity": "sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-all2": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-8.0.4.tgz", + "integrity": "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.6", + "memorystream": "^0.3.1", + "picomatch": "^4.0.2", + "pidtree": "^0.6.0", + "read-package-json-fast": "^4.0.0", + "shell-quote": "^1.7.3", + "which": "^5.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": "^20.5.0 || >=22.0.0", + "npm": ">= 10" + } + }, + "node_modules/npm-run-all2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm-run-all2/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm-run-all2/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/npm-run-all2/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.7" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.5.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "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/prettier": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/read-package-json-fast": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", + "integrity": "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.31", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", + "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^1.0.20" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==", + "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/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.53.0.tgz", + "integrity": "sha512-xHURCQNxZ1dsWn0sdOaOfCSQG0HKeqSj9OexIxrz6ypU6wHYOdX2I3D2b8s8wFSsSOYJb+6q283cLiLlkEsBYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.53.0", + "@typescript-eslint/parser": "8.53.0", + "@typescript-eslint/typescript-estree": "8.53.0", + "@typescript-eslint/utils": "8.53.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unplugin-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz", + "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/unplugin-utils/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-dev-rpc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vite-dev-rpc/-/vite-dev-rpc-1.1.0.tgz", + "integrity": "sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==", + "dev": true, + "license": "MIT", + "dependencies": { + "birpc": "^2.4.0", + "vite-hot-client": "^2.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0" + } + }, + "node_modules/vite-hot-client": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-2.1.0.tgz", + "integrity": "sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + } + }, + "node_modules/vite-plugin-inspect": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-11.3.3.tgz", + "integrity": "sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.1.0", + "debug": "^4.4.1", + "error-stack-parser-es": "^1.0.5", + "ohash": "^2.0.11", + "open": "^10.2.0", + "perfect-debounce": "^2.0.0", + "sirv": "^3.0.1", + "unplugin-utils": "^0.3.0", + "vite-dev-rpc": "^1.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-inspect/node_modules/perfect-debounce": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz", + "integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite-plugin-vue-devtools": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-8.0.5.tgz", + "integrity": "sha512-p619BlKFOqQXJ6uDWS1vUPQzuJOD6xJTfftj57JXBGoBD/yeQCowR7pnWcr/FEX4/HVkFbreI6w2uuGBmQOh6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-core": "^8.0.5", + "@vue/devtools-kit": "^8.0.5", + "@vue/devtools-shared": "^8.0.5", + "sirv": "^3.0.2", + "vite-plugin-inspect": "^11.3.3", + "vite-plugin-vue-inspector": "^5.3.2" + }, + "engines": { + "node": ">=v14.21.3" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0-0" + } + }, + "node_modules/vite-plugin-vue-devtools/node_modules/@vue/devtools-kit": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.0.5.tgz", + "integrity": "sha512-q2VV6x1U3KJMTQPUlRMyWEKVbcHuxhqJdSr6Jtjz5uAThAIrfJ6WVZdGZm5cuO63ZnSUz0RCsVwiUUb0mDV0Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.0.5", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^2.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/vite-plugin-vue-devtools/node_modules/@vue/devtools-shared": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.0.5.tgz", + "integrity": "sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/vite-plugin-vue-devtools/node_modules/perfect-debounce": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz", + "integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite-plugin-vue-inspector": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.3.2.tgz", + "integrity": "sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.22.15", + "@vue/babel-plugin-jsx": "^1.1.5", + "@vue/compiler-dom": "^3.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.4" + }, + "peerDependencies": { + "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + } + }, + "node_modules/vite-plugin-vue-inspector/node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.5.0.tgz", + "integrity": "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite-plugin-vue-inspector/node_modules/@vue/babel-plugin-jsx": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.5.0.tgz", + "integrity": "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.2", + "@vue/babel-helper-vue-transform-on": "1.5.0", + "@vue/babel-plugin-resolve-type": "1.5.0", + "@vue/shared": "^3.5.18" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/vite-plugin-vue-inspector/node_modules/@vue/babel-plugin-resolve-type": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.5.0.tgz", + "integrity": "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/parser": "^7.28.0", + "@vue/compiler-sfc": "^3.5.18" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.26.tgz", + "integrity": "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.26", + "@vue/compiler-sfc": "3.5.26", + "@vue/runtime-dom": "3.5.26", + "@vue/server-renderer": "3.5.26", + "@vue/shared": "3.5.26" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-eslint-parser": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.2.0.tgz", + "integrity": "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.6.0", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-eslint-parser/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-tsc": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.2.tgz", + "integrity": "sha512-r9YSia/VgGwmbbfC06hDdAatH634XJ9nVl6Zrnz1iK4ucp8Wu78kawplXnIDa3MSu1XdQQePTHLXYwPDWn+nyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.27", + "@vue/language-core": "3.2.2" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/vue-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/vue-types/-/vue-types-3.0.2.tgz", + "integrity": "sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==", + "license": "MIT", + "dependencies": { + "is-plain-object": "3.0.1" + }, + "engines": { + "node": ">=10.15.0" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "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/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "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/site/package.json b/site/package.json new file mode 100644 index 0000000..7e997aa --- /dev/null +++ b/site/package.json @@ -0,0 +1,50 @@ +{ + "name": "dynavera", + "version": "0.0.0", + "private": true, + "type": "module", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "scripts": { + "dev": "vite -- --host", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "watch": "vite build --watch", + "devwatch": "concurrently \"npm run dev\" \"npm run watch\"", + "type-check": "vue-tsc --build", + "lint": "eslint . --fix --cache", + "format": "prettier --write --experimental-cli src/" + }, + "dependencies": { + "ant-design-vue": "^4.2.6", + "axios": "^1.13.2", + "dompurify": "^3.3.1", + "marked": "^17.0.3", + "pinia": "^3.0.4", + "vue": "^3.5.26", + "vue-router": "^4.6.4" + }, + "devDependencies": { + "@tsconfig/node24": "^24.0.3", + "@types/dompurify": "^3.0.5", + "@types/marked": "^5.0.2", + "@types/node": "^24.10.4", + "@vitejs/plugin-vue": "^6.0.3", + "@vitejs/plugin-vue-jsx": "^5.1.3", + "@vue/eslint-config-prettier": "^10.2.0", + "@vue/eslint-config-typescript": "^14.6.0", + "@vue/tsconfig": "^0.8.1", + "concurrently": "^8.2.0", + "eslint": "^9.39.2", + "eslint-plugin-vue": "~10.6.2", + "jiti": "^2.6.1", + "npm-run-all2": "^8.0.4", + "prettier": "3.7.4", + "typescript": "~5.9.3", + "vite": "^7.3.0", + "vite-plugin-vue-devtools": "^8.0.5", + "vue-tsc": "^3.2.2" + } +} diff --git a/site/src/App.vue b/site/src/App.vue new file mode 100644 index 0000000..d5eaeca --- /dev/null +++ b/site/src/App.vue @@ -0,0 +1,331 @@ + + + + + diff --git a/site/src/css/styles.css b/site/src/css/styles.css new file mode 100644 index 0000000..a94590c --- /dev/null +++ b/site/src/css/styles.css @@ -0,0 +1,95 @@ +html { + -webkit-text-size-adjust: 100%; + font-family: + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + 'Helvetica Neue', + Arial, + 'Noto Sans', + sans-serif, + 'Apple Color Emoji', + 'Segoe UI Emoji', + 'Segoe UI Symbol', + 'Noto Color Emoji'; + line-height: 1.5; + tab-size: 4; + scroll-behavior: smooth; +} +body { + font-family: inherit; + line-height: inherit; + margin: 0; + background: #0b1220; + color: #e5e7eb; +} +h1, +h2, +p, +pre { + margin: 0; +} +*, +::before, +::after { + box-sizing: border-box; + border-width: 0; + border-style: solid; + border-color: currentColor; +} +h1, +h2 { + font-size: inherit; + font-weight: inherit; +} +a { + color: inherit; + text-decoration: inherit; +} +pre { + font-family: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', + monospace; +} + +:root { + --view-max-width: 1200px; + --view-panel-background: #0f172a; + --view-panel-border: #1f2937; + --view-panel-color: #e5e7eb; +} + +.page { + width: 100%; + max-width: var(--view-max-width); + margin: 0 auto; + padding: 2rem 1.5rem; +} + +.panel { + background: var(--view-panel-background); + border: 1px solid var(--view-panel-border); + color: var(--view-panel-color); +} + +.panel .ant-card-body { + background: transparent; + color: inherit; +} + +.panel .ant-typography, +.panel .ant-typography *, +.panel .ant-list-item-meta-title, +.panel .ant-list-item-meta-description, +.panel .ant-statistic-title, +.panel .ant-statistic-content { + color: #ffffff !important; +} + +.panel .ant-typography-secondary, +.panel .ant-typography-secondary * { + color: rgba(255, 255, 255, 0.7) !important; +} diff --git a/site/src/main.ts b/site/src/main.ts new file mode 100644 index 0000000..ff2277d --- /dev/null +++ b/site/src/main.ts @@ -0,0 +1,15 @@ +import './css/styles.css' +import 'ant-design-vue/dist/reset.css' + +import { createApp } from 'vue' +import { createPinia } from 'pinia' + +import App from './App.vue' +import router from './router' + +const app = createApp(App) + +app.use(createPinia()) +app.use(router) + +app.mount('#app') diff --git a/site/src/router/api.ts b/site/src/router/api.ts new file mode 100644 index 0000000..fea7c94 --- /dev/null +++ b/site/src/router/api.ts @@ -0,0 +1,128 @@ +import axios from 'axios' +import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' + +class ApiClient { + private client: AxiosInstance + + constructor() { + this.client = axios.create({ + withCredentials: true, + headers: { + 'Content-Type': 'application/json', + }, + }) + } + + private getCsrfToken(): string { + let cookieValue = '' + if (document.cookie && document.cookie !== '') { + const cookies = document.cookie.split(';') + for (const rawCookie of cookies) { + const cookie = (rawCookie || '').trim() + if (cookie.startsWith('csrftoken=')) { + cookieValue = decodeURIComponent(cookie.slice('csrftoken='.length)) + break + } + } + } + return cookieValue + } + + private withCsrf(config?: AxiosRequestConfig): AxiosRequestConfig { + const token = this.getCsrfToken() + const csrfHeader = token ? { 'X-CSRFToken': token } : {} + return { + ...config, + headers: { + ...csrfHeader, + ...(config?.headers || {}), + }, + } + } + + get(url: string, config?: AxiosRequestConfig): Promise> { + return this.client.get(url, this.withCsrf(config)) + } + + post( + url: string, + data?: unknown, + config?: AxiosRequestConfig, + ): Promise> { + return this.client.post(url, data, this.withCsrf(config)) + } + + put( + url: string, + data?: unknown, + config?: AxiosRequestConfig, + ): Promise> { + return this.client.put(url, data, this.withCsrf(config)) + } + + patch( + url: string, + data?: unknown, + config?: AxiosRequestConfig, + ): Promise> { + return this.client.patch(url, data, this.withCsrf(config)) + } + + delete(url: string, config?: AxiosRequestConfig): Promise> { + return this.client.delete(url, this.withCsrf(config)) + } +} +export const API = { + me: () => '/api/user/me/', + login: () => '/api/user/login/', + logout: () => '/api/user/logout/', + session: () => '/api/user/session/', + signup: () => '/api/user/signup/', + changePassword: () => '/api/user/change_password/', + + organizations: () => '/api/organization/', + organization: (uuid: string) => `/api/organization/${uuid}/`, + organizationMembers: (uuid: string) => `/api/organization/${uuid}/members/`, + organizationMemberRemove: (uuid: string, userId: number) => + `/api/organization/${uuid}/member/${userId}/remove/`, + + organizationInvites: (uuid: string) => `/api/organization/${uuid}/invite/`, + organizationCreateInvite: (uuid: string, max_uses: number) => + `/api/organization/${uuid}/create-invite/?max_uses=${max_uses}`, + organizationRevokeInvite: (uuid: string, inviteUuid: string) => + `/api/organization/${uuid}/revoke-invite/${inviteUuid}/`, + organizationJoin: (token: string) => `/api/organization/join/${token}/`, + organizationLeave: (uuid: string) => `/api/organization/${uuid}/leave/`, + + organizationRoles: (uuid: string) => `/api/organization/${uuid}/role/`, + organizationRolesMine: () => '/api/organization/role/mine/', + organizationRoleDelete: (orgUuid: string, roleUuid: string) => + `/api/organization/${orgUuid}/role/${roleUuid}/`, + organizationRoleJoin: (orgUuid: string, roleUuid: string) => + `/api/organization/${orgUuid}/role/${roleUuid}/join/`, + + trainingFiles: () => `/api/training-file/`, + trainingFile: (uuid: string) => `/api/training-file/${uuid}/`, + organizationTrainingFiles: () => `/api/training-file/`, + organizationTrainingFile: (uuid: string) => `/api/training-file/${uuid}/`, + roleRagDocuments: () => `/api/role-rag-document/`, + roleRagDocument: (uuid: string) => `/api/role-rag-document/${uuid}/`, + + agentConfigs: () => '/api/agent-config/', + agentConfig: (uuid: string) => `/api/agent-config/${uuid}/`, + + onboardingFlows: () => '/api/onboarding-flow/', + onboardingFlow: (uuid: string) => `/api/onboarding-flow/${uuid}/`, + onboardingFlowStartSession: (uuid: string) => `/api/onboarding-flow/${uuid}/start-session/`, + + onboardingSessions: () => '/api/onboarding-session/', + onboardingSession: (uuid: string) => `/api/onboarding-session/${uuid}/`, + onboardingSessionInteract: (uuid: string) => `/api/onboarding-session/${uuid}/interact/`, + onboardingSessionHistory: (uuid: string) => `/api/onboarding-session/${uuid}/history/`, + onboardingSessionComplete: (uuid: string) => `/api/onboarding-session/${uuid}/complete/`, + + interactionLogs: () => '/api/agent-interaction-log/', + interactionLog: (uuid: string) => `/api/agent-interaction-log/${uuid}/`, +} +export const apiClient = new ApiClient() +export { isAxiosError } from 'axios' diff --git a/site/src/router/index.ts b/site/src/router/index.ts new file mode 100644 index 0000000..a1ebd85 --- /dev/null +++ b/site/src/router/index.ts @@ -0,0 +1,109 @@ +import { createRouter, createWebHistory } from 'vue-router' +import { useUserStore } from '../stores/userStore' + +const BASE_URL = (import.meta as unknown as { env: { BASE_URL?: string } }).env?.BASE_URL || '/' +const router = createRouter({ + history: createWebHistory(BASE_URL), + routes: [ + { + path: '/', + name: 'home', + component: () => import('../views/HomeView.vue'), + }, + { + path: '/about', + name: 'about', + component: () => import('../views/AboutView.vue'), + }, + { + path: '/pricing', + name: 'pricing', + component: () => import('../views/PricingView.vue'), + }, + { + path: '/login', + name: 'login', + component: () => import('../views/LoginView.vue'), + meta: { guestOnly: true }, + }, + { + path: '/register', + name: 'register', + component: () => import('../views/RegisterView.vue'), + meta: { guestOnly: true }, + }, + { + path: '/organization', + name: 'organization', + component: () => import('../views/OrganizationsView.vue'), + meta: { requiresAuth: true }, + }, + { + path: '/organization/:id', + name: 'organization-detail', + component: () => import('../views/OrganizationView.vue'), + meta: { requiresAuth: true }, + }, + { + path: '/organization/:id/manage', + name: 'organization-manage', + component: () => import('../views/OrganizationManage.vue'), + meta: { requiresAuth: true, requiresManager: true }, + }, + { + path: '/invite/:token', + name: 'invite-accept', + component: () => import('../views/InviteAccept.vue'), + }, + { + path: '/agents', + name: 'agents', + component: () => import('../views/AgentsView.vue'), + meta: { requiresAuth: true, requiresManager: true }, + }, + { + path: '/agents/:id', + name: 'agent-detail', + component: () => import('../views/AgentDetailView.vue'), + meta: { requiresAuth: true, requiresManager: true }, + }, + { + path: '/onboarding/:roleId', + name: 'onboarding-role', + component: () => import('../views/OnboardingView.vue'), + meta: { requiresAuth: true }, + }, + { + path: '/progress', + name: 'progress', + component: () => import('../views/ProgressView.vue'), + meta: { requiresAuth: true }, + }, + { + path: '/progress/:roleId', + name: 'progress-role', + component: () => import('../views/ProgressDetailView.vue'), + meta: { requiresAuth: true }, + }, + ], +}) + +router.beforeEach((to, from, next) => { + const userStore = useUserStore() + const isAuthenticated = userStore.isAuthenticated + const isManager = userStore.isGeneralManager + + if (to.meta?.guestOnly && isAuthenticated) { + return next({ path: '/' }) + } + if (to.meta?.requiresAuth && !isAuthenticated) { + return next({ path: '/login', query: { redirect: to.fullPath } }) + } + if (to.meta?.requiresManager && !isManager) { + return next({ path: '/' }) + } + + return next() +}) + +export default router diff --git a/site/src/stores/agentStore.ts b/site/src/stores/agentStore.ts new file mode 100644 index 0000000..3236c6b --- /dev/null +++ b/site/src/stores/agentStore.ts @@ -0,0 +1,139 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import type { AgentEvent } from '../types/agent' + +export const useAgentStore = defineStore('agent', () => { + const isConnected = ref(false) + const executionStatus = ref<'idle' | 'running' | 'completed' | 'failed'>('idle') + const eventLog = ref([]) + const lastExecutionId = ref(null) + const socket = ref(null) + + const pushEvent = (evt: { + type: string + message?: string + content?: unknown + timestamp?: string + }) => { + eventLog.value.unshift({ + type: evt.type, + message: evt.message, + content: evt.content, + timestamp: evt.timestamp ? new Date(evt.timestamp) : new Date(), + }) + } + + const connect = (id: string) => { + if (socket.value) { + socket.value.close() + socket.value = null + } + + const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws' + const wsUrl = `${wsProtocol}://${window.location.host}/ws/onboarding/${id}/` + socket.value = new WebSocket(wsUrl) + + socket.value.onopen = () => { + isConnected.value = true + pushEvent({ type: 'status', message: 'Connected to Orchestrator' }) + } + + socket.value.onmessage = (event) => { + try { + const payload = JSON.parse(event.data) + const type = payload.type + + if (payload.execution_id) { + lastExecutionId.value = String(payload.execution_id) + } + + if (type === 'status' || type === 'thought' || type === 'tool_start') { + executionStatus.value = 'running' + pushEvent({ + type, + message: payload.message || payload.thought, + content: payload.content, + }) + } else if ( + type === 'tool_call' || + type === 'tool_result' || + type === 'tool_complete' + ) { + pushEvent({ + type, + message: payload.message, + content: payload.content || payload, + }) + } else if (type === 'completed') { + executionStatus.value = 'completed' + pushEvent({ + type: 'completed', + message: 'Generation loop finished successfully', + content: payload.content, + timestamp: payload.timestamp, + }) + } else if (type === 'error') { + executionStatus.value = 'failed' + pushEvent({ type: 'error', message: payload.message }) + } + } catch (e) { + console.error('Store message error', e) + } + } + + socket.value.onclose = () => { + isConnected.value = false + executionStatus.value = 'idle' + } + } + + const disconnect = () => { + if (socket.value) { + socket.value.close() + socket.value = null + } + isConnected.value = false + executionStatus.value = 'idle' + } + + const startAgent = (data: { query?: string; role_uuid?: string; max_tokens?: number }) => { + if (!socket.value || socket.value.readyState !== WebSocket.OPEN) return + + executionStatus.value = 'running' + socket.value.send( + JSON.stringify({ + query: data.query, + role_uuid: data.role_uuid, + max_tokens: data.max_tokens, + }), + ) + } + + const stopAgent = (executionId?: string) => { + if (!socket.value || socket.value.readyState !== WebSocket.OPEN) return + + socket.value.send( + JSON.stringify({ + action: 'stop_agent', + execution_id: executionId ?? lastExecutionId.value, + }), + ) + } + + const clearLog = () => { + eventLog.value = [] + } + + return { + isConnected, + executionStatus, + eventLog, + socket, + connect, + disconnect, + startAgent, + stopAgent, + clearLog, + lastExecutionId, + } +}) diff --git a/site/src/stores/userStore.ts b/site/src/stores/userStore.ts new file mode 100644 index 0000000..f61e679 --- /dev/null +++ b/site/src/stores/userStore.ts @@ -0,0 +1,218 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { apiClient, isAxiosError, API } from '../router/api' +import type { User, SessionResponse } from '../types/user' +import type { Organization, Role } from 'src/types/organization' + +const orgUuidKey = 'userSelectedOrganizationUuid' + +export const useUserStore = defineStore('user', () => { + const isAuthenticated = ref(false) + const isAdmin = ref(false) + const isGeneralManager = computed(() => { + if (!isAuthenticated.value || !user.value) return false + return user.value.is_manager + }) + + const loading = ref(false) + const error = ref(null) + + const user = ref(null) + const userJoinedOrganizations = ref([]) + const userSelectedOrganization = ref(null) + const userJoinedRoles = ref([]) + + const displayName = computed(() => { + if (!user.value) return '' + return `${user.value.first_name} ${user.value.last_name}` + }) + + const setUser = (userData: User | null) => { + user.value = userData + isAuthenticated.value = !!userData + } + + const setJoinedOrganizations = (organizations: Organization[]) => { + userJoinedOrganizations.value = organizations + if (organizations.length > 0) { + const stored = localStorage.getItem(orgUuidKey) + const matched = organizations.find((org) => org.uuid === stored) + if (matched) { + userSelectedOrganization.value = matched + return + } + userSelectedOrganization.value = organizations[0] + localStorage.setItem(orgUuidKey, organizations[0].uuid) + } else { + userSelectedOrganization.value = null + localStorage.removeItem(orgUuidKey) + } + } + + const setSelectedOrganization = (organization: Organization | null) => { + userSelectedOrganization.value = organization + if (organization) { + localStorage.setItem(orgUuidKey, organization.uuid) + } else { + localStorage.removeItem(orgUuidKey) + } + } + + const fetchSession = async (force = false) => { + if (isAuthenticated.value && !force) return user.value + loading.value = true + error.value = null + try { + const response = await apiClient.get(API.session()) + if (response.data?.isAuthenticated) { + const userData = await apiClient.get(API.me()) + setUser(userData.data) + await fetchJoinedOrganizations() + } else { + setUser(null) + isAuthenticated.value = false + } + return user.value + } catch (err: unknown) { + if (isAxiosError(err)) { + error.value = err.response?.data?.detail || err.response?.data?.error || err.message + } else if (err instanceof Error) { + error.value = err.message + } else { + error.value = String(err) + } + setUser(null) + throw err + } finally { + loading.value = false + } + } + + const fetchJoinedOrganizations = async () => { + if (!user.value) return + try { + const response = await apiClient.get(API.organizations()) + setJoinedOrganizations(response.data) + return response.data + } catch (err: unknown) { + console.error('Failed to fetch organizations', err) + setJoinedOrganizations([]) + return [] + } + } + + const fetchJoinedRoles = async () => { + if (!user.value || !userSelectedOrganization.value) return + try { + const response = await apiClient.get( + API.organizationRoles(userSelectedOrganization.value.uuid), + ) + setJoinedRoles(response.data) + return response.data + } catch (err: unknown) { + console.error('Failed to fetch role memberships', err) + setJoinedRoles([]) + return [] + } + } + + const setJoinedRoles = (roles: Role[]) => { + userJoinedRoles.value = roles + } + + const login = async (emailAddress: string, password: string) => { + loading.value = true + error.value = null + try { + const res = await apiClient.post<{ + user: User + message?: string + }>(API.login(), { email_address: emailAddress, password }) + setUser(res.data?.user ?? null) + return res.data + } catch (err: unknown) { + if (isAxiosError(err)) { + error.value = err.response?.data?.error || err.response?.data?.detail || err.message + } else if (err instanceof Error) { + error.value = err.message + } else { + error.value = String(err) + } + throw err + } finally { + loading.value = false + } + } + + const register = async (payload: { + email_address: string + password: string + confirm_password?: string + first_name: string + last_name: string + date_of_birth?: string + manager: boolean + }) => { + loading.value = true + error.value = null + try { + await apiClient.post(API.signup(), payload) + await login(payload.email_address, payload.password) + } catch (err: unknown) { + if (isAxiosError(err)) { + error.value = err.response?.data?.detail || err.response?.data?.error || err.message + } else if (err instanceof Error) { + error.value = err.message + } else { + error.value = String(err) + } + throw err + } finally { + loading.value = false + } + } + + const logout = async () => { + loading.value = true + error.value = null + try { + await apiClient.post(API.logout()) + } catch (err: unknown) { + if (isAxiosError(err)) { + error.value = err.response?.data?.detail || err.response?.data?.error || err.message + } else if (err instanceof Error) { + error.value = err.message + } else { + error.value = String(err) + } + throw err + } finally { + setUser(null) + loading.value = false + } + } + + return { + user, + displayName, + isAuthenticated, + isAdmin, + isGeneralManager, + loading, + error, + userJoinedOrganizations, + userSelectedOrganization, + userJoinedRoles, + + setUser, + fetchSession, + setJoinedOrganizations, + setSelectedOrganization, + setJoinedRoles, + fetchJoinedOrganizations, + fetchJoinedRoles, + login, + register, + logout, + } +}) diff --git a/site/src/types/agent.ts b/site/src/types/agent.ts new file mode 100644 index 0000000..a626567 --- /dev/null +++ b/site/src/types/agent.ts @@ -0,0 +1,6 @@ +export type AgentEvent = { + type: string + timestamp: Date + message?: string + content?: unknown +} diff --git a/site/src/types/onboarding.ts b/site/src/types/onboarding.ts new file mode 100644 index 0000000..63108b6 --- /dev/null +++ b/site/src/types/onboarding.ts @@ -0,0 +1,44 @@ +export type OnboardingField = { + uuid: string + key: string + label: string + field_type: 'text' | 'textarea' | 'select' | 'multiselect' | 'number' | 'boolean' | 'date' + required: boolean + help_text?: string + placeholder?: string + options?: string[] + default_value?: unknown + validation?: Record +} + +export type OnboardingPage = { + uuid: string + order: number + title: string + body?: string + meta?: Record + fields: OnboardingField[] +} + +export type OnboardingFlow = { + uuid: string + role: string + agent?: string | null + title: string + description?: string + status: 'draft' | 'published' | 'archived' + pages?: OnboardingPage[] +} + +export type OnboardingSession = { + uuid: string + flow: string + agent_run?: string | null + status: 'in_progress' | 'completed' | 'abandoned' + current_page_order: number + responses: Record +} + +export type OnboardingFeedback = { + summary?: string +} diff --git a/site/src/types/organization.ts b/site/src/types/organization.ts new file mode 100644 index 0000000..3cdc732 --- /dev/null +++ b/site/src/types/organization.ts @@ -0,0 +1,53 @@ +import { User } from './user' + +export interface Organization { + id: number + uuid: string + name: string + description: string + owner: User + created_at: string + updated_at: string + member_count?: number + role_count?: number +} + +export interface Role { + id: number + uuid: string + name: string + description?: string + organization: Organization + member_count?: number + created_at: string + updated_at: string +} + +export interface InviteToken { + id: number + token: string + invite_url: string + created_by: User + organization: Organization + is_active: boolean + expires_at: string + is_valid: boolean + max_uses?: number + uses?: number +} +export interface TrainingFile { + id: number + uuid: string + role: Role + uploaded_by: User + file: string + file_name: string + file_size: number + file_type: string + description: string + is_processed: boolean + status: 'ingesting' | 'chunked' | 'embedded' | 'failed' + file_url: string + created_at: string + updated_at: string +} diff --git a/site/src/types/user.ts b/site/src/types/user.ts new file mode 100644 index 0000000..41a79c0 --- /dev/null +++ b/site/src/types/user.ts @@ -0,0 +1,18 @@ +export interface User { + id: number + uuid: string + email_address: string + first_name: string + last_name: string + date_of_birth?: string + timezone?: string + avatar_url?: string + is_manager: boolean + created_at: string + updated_at: string +} + +export interface SessionResponse { + isAuthenticated: boolean + isStaff: boolean +} diff --git a/site/src/views/AboutView.vue b/site/src/views/AboutView.vue new file mode 100644 index 0000000..c46e8ad --- /dev/null +++ b/site/src/views/AboutView.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/site/src/views/AgentDetailView.vue b/site/src/views/AgentDetailView.vue new file mode 100644 index 0000000..31151f7 --- /dev/null +++ b/site/src/views/AgentDetailView.vue @@ -0,0 +1,362 @@ + + + + + diff --git a/site/src/views/AgentsView.vue b/site/src/views/AgentsView.vue new file mode 100644 index 0000000..8bdae45 --- /dev/null +++ b/site/src/views/AgentsView.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/site/src/views/HomeView.vue b/site/src/views/HomeView.vue new file mode 100644 index 0000000..04860cd --- /dev/null +++ b/site/src/views/HomeView.vue @@ -0,0 +1,273 @@ + + + + + diff --git a/site/src/views/InviteAccept.vue b/site/src/views/InviteAccept.vue new file mode 100644 index 0000000..780fff7 --- /dev/null +++ b/site/src/views/InviteAccept.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/site/src/views/LoginView.vue b/site/src/views/LoginView.vue new file mode 100644 index 0000000..787cd69 --- /dev/null +++ b/site/src/views/LoginView.vue @@ -0,0 +1,117 @@ + + + + + diff --git a/site/src/views/OnboardingView.vue b/site/src/views/OnboardingView.vue new file mode 100644 index 0000000..d169ea2 --- /dev/null +++ b/site/src/views/OnboardingView.vue @@ -0,0 +1,511 @@ + + + + + diff --git a/site/src/views/OrganizationManage.vue b/site/src/views/OrganizationManage.vue new file mode 100644 index 0000000..3a535b1 --- /dev/null +++ b/site/src/views/OrganizationManage.vue @@ -0,0 +1,498 @@ + + + + + diff --git a/site/src/views/OrganizationView.vue b/site/src/views/OrganizationView.vue new file mode 100644 index 0000000..c569e7f --- /dev/null +++ b/site/src/views/OrganizationView.vue @@ -0,0 +1,523 @@ + + + + + diff --git a/site/src/views/OrganizationsView.vue b/site/src/views/OrganizationsView.vue new file mode 100644 index 0000000..be7137d --- /dev/null +++ b/site/src/views/OrganizationsView.vue @@ -0,0 +1,178 @@ + + + + + diff --git a/site/src/views/PricingView.vue b/site/src/views/PricingView.vue new file mode 100644 index 0000000..62739ef --- /dev/null +++ b/site/src/views/PricingView.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/site/src/views/ProgressDetailView.vue b/site/src/views/ProgressDetailView.vue new file mode 100644 index 0000000..14469dc --- /dev/null +++ b/site/src/views/ProgressDetailView.vue @@ -0,0 +1,225 @@ + + + + + diff --git a/site/src/views/ProgressView.vue b/site/src/views/ProgressView.vue new file mode 100644 index 0000000..7302c85 --- /dev/null +++ b/site/src/views/ProgressView.vue @@ -0,0 +1,218 @@ + + + + + diff --git a/site/src/views/RegisterView.vue b/site/src/views/RegisterView.vue new file mode 100644 index 0000000..c625536 --- /dev/null +++ b/site/src/views/RegisterView.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/site/src/vue-shim.d.ts b/site/src/vue-shim.d.ts new file mode 100644 index 0000000..36b0722 --- /dev/null +++ b/site/src/vue-shim.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import { defineComponent } from 'vue' + const component: ReturnType + export default component +} diff --git a/site/tsconfig.json b/site/tsconfig.json new file mode 100644 index 0000000..58de42e --- /dev/null +++ b/site/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "rootDir": ".", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "module": "esnext", + "target": "es2015", + "jsx": "preserve", + "jsxImportSource": "vue", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "importHelpers": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "resolveJsonModule": true, + "allowJs": true, + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "types": ["vite/client"], + "baseUrl": ".", + "paths": {}, + "outDir": "./build/out-tsc", + "lib": ["es2020", "dom"] + }, + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.vue"], + "exclude": [ + "node_modules", + "tmp", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/**/*.spec.vue", + "src/**/*.test.vue" + ] +} diff --git a/site/vite.config.ts b/site/vite.config.ts new file mode 100644 index 0000000..030584a --- /dev/null +++ b/site/vite.config.ts @@ -0,0 +1,42 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' +import vueDevTools from 'vite-plugin-vue-devtools' + +export default defineConfig({ + root: fileURLToPath(new URL('.', import.meta.url)), + cacheDir: './node_modules/.vite/build', + server: { + port: 5173, + host: 'localhost', + strictPort: true, + watch: { + usePolling: true, + interval: 300, + }, + hmr: { + clientPort: 5173, + protocol: 'ws', + }, + }, + preview: { + port: 4300, + host: 'localhost', + }, + plugins: [vue(), vueJsx(), vueDevTools()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, + build: { + outDir: './build', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + }, +})