Spaces:
Running
Running
Commit ·
e7e5bf0
0
Parent(s):
Initial PilotMaster deployment
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +58 -0
- DocPilot/backend/app/api/auth.py +166 -0
- DocPilot/backend/app/api/billing.py +61 -0
- DocPilot/backend/app/api/chat.py +91 -0
- DocPilot/backend/app/api/documents.py +181 -0
- DocPilot/backend/app/api/history.py +89 -0
- DocPilot/backend/app/core/config.py +1 -0
- DocPilot/backend/app/core/dependencies.py +43 -0
- DocPilot/backend/app/core/security.py +21 -0
- DocPilot/backend/app/db/database.py +9 -0
- DocPilot/backend/app/db/session.py +11 -0
- DocPilot/backend/app/main.py +52 -0
- DocPilot/backend/app/models/__init__.py +3 -0
- DocPilot/backend/app/models/chat.py +69 -0
- DocPilot/backend/app/models/document.py +29 -0
- DocPilot/backend/app/models/user.py +17 -0
- DocPilot/backend/app/schemas/auth.py +19 -0
- DocPilot/backend/app/schemas/chat.py +10 -0
- DocPilot/backend/app/schemas/document.py +25 -0
- DocPilot/backend/app/schemas/password.py +10 -0
- DocPilot/backend/app/services/ingestion.py +242 -0
- DocPilot/backend/app/services/rag.py +43 -0
- Dockerfile +15 -0
- README.md +273 -0
- TracePilot/.gitignore +5 -0
- TracePilot/backend/Dockerfile +0 -0
- TracePilot/backend/app/__init__.py +0 -0
- TracePilot/backend/app/analytics/failure_detector.py +30 -0
- TracePilot/backend/app/api/__init__.py +0 -0
- TracePilot/backend/app/core/config.py +1 -0
- TracePilot/backend/app/core/llm.py +1 -0
- TracePilot/backend/app/db/database.py +61 -0
- TracePilot/backend/app/evaluation/evaluator.py +16 -0
- TracePilot/backend/app/main.py +216 -0
- TracePilot/backend/app/models/trace.py +42 -0
- TracePilot/backend/app/pipelines/pipeline_runner.py +12 -0
- TracePilot/backend/app/tracing/replay.py +24 -0
- TracePilot/backend/app/tracing/trace_manager.py +137 -0
- TracePilot/backend/data/knowledge_base.txt +15 -0
- TracePilot/backend/requirements.txt +0 -0
- docker-compose.yml +61 -0
- frontend/index.html +12 -0
- frontend/package-lock.json +1914 -0
- frontend/package.json +20 -0
- frontend/src/App.jsx +313 -0
- frontend/src/docpilot/App.jsx +3 -0
- frontend/src/docpilot/api.js +35 -0
- frontend/src/docpilot/pages/Dashboard.jsx +260 -0
- frontend/src/docpilot/pages/ForgotPassword.jsx +275 -0
- frontend/src/docpilot/pages/Login.jsx +303 -0
.gitignore
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
*.pyo
|
| 5 |
+
*.pyd
|
| 6 |
+
*.egg-info/
|
| 7 |
+
*.egg
|
| 8 |
+
dist/
|
| 9 |
+
build/
|
| 10 |
+
|
| 11 |
+
# Virtual environments
|
| 12 |
+
venv/
|
| 13 |
+
.venv/
|
| 14 |
+
env/
|
| 15 |
+
|
| 16 |
+
# Environment variables — never commit real credentials
|
| 17 |
+
.env
|
| 18 |
+
|
| 19 |
+
# Databases
|
| 20 |
+
*.db
|
| 21 |
+
*.sqlite3
|
| 22 |
+
|
| 23 |
+
# FAISS vector stores — user data, never commit
|
| 24 |
+
vector_store/
|
| 25 |
+
*.index
|
| 26 |
+
*.pkl
|
| 27 |
+
|
| 28 |
+
# Uploaded files — user data, never commit
|
| 29 |
+
temp/
|
| 30 |
+
|
| 31 |
+
# Node
|
| 32 |
+
node_modules/
|
| 33 |
+
|
| 34 |
+
# Vite build output
|
| 35 |
+
dist/
|
| 36 |
+
.vite/
|
| 37 |
+
|
| 38 |
+
# OS
|
| 39 |
+
.DS_Store
|
| 40 |
+
Thumbs.db
|
| 41 |
+
|
| 42 |
+
# Logs
|
| 43 |
+
*.log
|
| 44 |
+
|
| 45 |
+
# IDE
|
| 46 |
+
.vscode/
|
| 47 |
+
.idea/
|
| 48 |
+
|
| 49 |
+
# React
|
| 50 |
+
coverage/
|
| 51 |
+
|
| 52 |
+
# Docker overrides
|
| 53 |
+
*.override.yml
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
hf_home/
|
| 58 |
+
.cache/
|
DocPilot/backend/app/api/auth.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
reset_tokens = {}
|
| 2 |
+
|
| 3 |
+
import secrets
|
| 4 |
+
|
| 5 |
+
from fastapi import (
|
| 6 |
+
APIRouter,
|
| 7 |
+
Depends,
|
| 8 |
+
HTTPException,
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
from fastapi.security import (
|
| 12 |
+
OAuth2PasswordRequestForm,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
from sqlalchemy.orm import Session
|
| 16 |
+
|
| 17 |
+
from app.db.session import get_db
|
| 18 |
+
|
| 19 |
+
from app.models.user import User
|
| 20 |
+
|
| 21 |
+
from app.schemas.auth import (
|
| 22 |
+
UserCreate,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
from app.schemas.password import (
|
| 26 |
+
ForgotPasswordRequest,
|
| 27 |
+
ResetPasswordRequest,
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
from app.core.security import (
|
| 31 |
+
hash_password,
|
| 32 |
+
verify_password,
|
| 33 |
+
create_access_token,
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
from app.core.dependencies import (
|
| 37 |
+
get_current_user,
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
router = APIRouter()
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
@router.post("/signup")
|
| 44 |
+
def signup(
|
| 45 |
+
user: UserCreate,
|
| 46 |
+
db: Session = Depends(get_db),
|
| 47 |
+
):
|
| 48 |
+
|
| 49 |
+
existing_user = db.query(User).filter(User.email == user.email).first()
|
| 50 |
+
|
| 51 |
+
if existing_user:
|
| 52 |
+
|
| 53 |
+
raise HTTPException(
|
| 54 |
+
status_code=400,
|
| 55 |
+
detail="Email already registered",
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
new_user = User(
|
| 59 |
+
username=user.username,
|
| 60 |
+
email=user.email,
|
| 61 |
+
hashed_password=hash_password(user.password),
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
db.add(new_user)
|
| 65 |
+
|
| 66 |
+
db.commit()
|
| 67 |
+
|
| 68 |
+
db.refresh(new_user)
|
| 69 |
+
|
| 70 |
+
return {"message": "User created successfully"}
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
@router.post("/login")
|
| 74 |
+
def login(
|
| 75 |
+
form_data: OAuth2PasswordRequestForm = Depends(),
|
| 76 |
+
db: Session = Depends(get_db),
|
| 77 |
+
):
|
| 78 |
+
|
| 79 |
+
db_user = db.query(User).filter(User.email == form_data.username).first()
|
| 80 |
+
|
| 81 |
+
if not db_user:
|
| 82 |
+
|
| 83 |
+
raise HTTPException(
|
| 84 |
+
status_code=401,
|
| 85 |
+
detail="Invalid credentials",
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
if not verify_password(
|
| 89 |
+
form_data.password,
|
| 90 |
+
db_user.hashed_password,
|
| 91 |
+
):
|
| 92 |
+
|
| 93 |
+
raise HTTPException(
|
| 94 |
+
status_code=401,
|
| 95 |
+
detail="Invalid credentials",
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
access_token = create_access_token(data={"sub": db_user.email})
|
| 99 |
+
|
| 100 |
+
return {
|
| 101 |
+
"access_token": access_token,
|
| 102 |
+
"token_type": "bearer",
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
@router.get("/me")
|
| 107 |
+
def get_me(current_user=Depends(get_current_user)):
|
| 108 |
+
|
| 109 |
+
return {
|
| 110 |
+
"id": current_user.id,
|
| 111 |
+
"username": current_user.username,
|
| 112 |
+
"email": current_user.email,
|
| 113 |
+
"plan": current_user.plan,
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
@router.post("/forgot-password")
|
| 118 |
+
def forgot_password(
|
| 119 |
+
email_data: ForgotPasswordRequest,
|
| 120 |
+
db: Session = Depends(get_db),
|
| 121 |
+
):
|
| 122 |
+
|
| 123 |
+
user = db.query(User).filter(User.email == email_data.email).first()
|
| 124 |
+
|
| 125 |
+
if not user:
|
| 126 |
+
|
| 127 |
+
raise HTTPException(
|
| 128 |
+
status_code=404,
|
| 129 |
+
detail="User not found",
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
token = secrets.token_hex(16)
|
| 133 |
+
|
| 134 |
+
reset_tokens[token] = user.email
|
| 135 |
+
|
| 136 |
+
return {"reset_token": token}
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
@router.post("/reset-password")
|
| 140 |
+
def reset_password(
|
| 141 |
+
data: ResetPasswordRequest,
|
| 142 |
+
db: Session = Depends(get_db),
|
| 143 |
+
):
|
| 144 |
+
|
| 145 |
+
token = data.token
|
| 146 |
+
|
| 147 |
+
new_password = data.new_password
|
| 148 |
+
|
| 149 |
+
if token not in reset_tokens:
|
| 150 |
+
|
| 151 |
+
raise HTTPException(
|
| 152 |
+
status_code=400,
|
| 153 |
+
detail="Invalid token",
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
email = reset_tokens[token]
|
| 157 |
+
|
| 158 |
+
user = db.query(User).filter(User.email == email).first()
|
| 159 |
+
|
| 160 |
+
user.hashed_password = hash_password(new_password)
|
| 161 |
+
|
| 162 |
+
db.commit()
|
| 163 |
+
|
| 164 |
+
del reset_tokens[token]
|
| 165 |
+
|
| 166 |
+
return {"message": "Password reset successful"}
|
DocPilot/backend/app/api/billing.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import (
|
| 2 |
+
APIRouter,
|
| 3 |
+
Depends,
|
| 4 |
+
)
|
| 5 |
+
|
| 6 |
+
from sqlalchemy.orm import Session
|
| 7 |
+
|
| 8 |
+
from app.db.session import get_db
|
| 9 |
+
|
| 10 |
+
from app.core.dependencies import (
|
| 11 |
+
get_current_user,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
router = APIRouter()
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@router.get("/me")
|
| 18 |
+
def get_plan(
|
| 19 |
+
current_user=Depends(get_current_user),
|
| 20 |
+
):
|
| 21 |
+
|
| 22 |
+
return {
|
| 23 |
+
"username": current_user.username,
|
| 24 |
+
"plan": current_user.plan,
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@router.post("/upgrade")
|
| 29 |
+
def upgrade_plan(
|
| 30 |
+
current_user=Depends(get_current_user),
|
| 31 |
+
db: Session = Depends(get_db),
|
| 32 |
+
):
|
| 33 |
+
|
| 34 |
+
current_user.plan = "pro"
|
| 35 |
+
|
| 36 |
+
db.commit()
|
| 37 |
+
|
| 38 |
+
db.refresh(current_user)
|
| 39 |
+
|
| 40 |
+
return {
|
| 41 |
+
"message": "Upgraded to pro plan.",
|
| 42 |
+
"plan": current_user.plan,
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
@router.post("/downgrade")
|
| 47 |
+
def downgrade_plan(
|
| 48 |
+
current_user=Depends(get_current_user),
|
| 49 |
+
db: Session = Depends(get_db),
|
| 50 |
+
):
|
| 51 |
+
|
| 52 |
+
current_user.plan = "free"
|
| 53 |
+
|
| 54 |
+
db.commit()
|
| 55 |
+
|
| 56 |
+
db.refresh(current_user)
|
| 57 |
+
|
| 58 |
+
return {
|
| 59 |
+
"message": "Downgraded to free plan.",
|
| 60 |
+
"plan": current_user.plan,
|
| 61 |
+
}
|
DocPilot/backend/app/api/chat.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import (
|
| 2 |
+
APIRouter,
|
| 3 |
+
Depends,
|
| 4 |
+
HTTPException,
|
| 5 |
+
)
|
| 6 |
+
|
| 7 |
+
from groq import AuthenticationError
|
| 8 |
+
|
| 9 |
+
from sqlalchemy.orm import Session
|
| 10 |
+
|
| 11 |
+
from app.services.rag import (
|
| 12 |
+
ask_question,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
from app.core.dependencies import (
|
| 16 |
+
get_current_user,
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
from app.db.session import get_db
|
| 20 |
+
|
| 21 |
+
from app.schemas.chat import ChatRequest
|
| 22 |
+
|
| 23 |
+
from app.models.chat import (
|
| 24 |
+
ChatSession,
|
| 25 |
+
ChatMessage,
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
router = APIRouter()
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
@router.post("/ask")
|
| 32 |
+
def ask(
|
| 33 |
+
query: ChatRequest,
|
| 34 |
+
current_user=Depends(get_current_user),
|
| 35 |
+
db: Session = Depends(get_db),
|
| 36 |
+
):
|
| 37 |
+
|
| 38 |
+
session_id = query.session_id
|
| 39 |
+
|
| 40 |
+
if not session_id:
|
| 41 |
+
|
| 42 |
+
session = ChatSession(
|
| 43 |
+
owner_id=current_user.id,
|
| 44 |
+
title=query.question.strip()[:40],
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
db.add(session)
|
| 48 |
+
|
| 49 |
+
db.commit()
|
| 50 |
+
|
| 51 |
+
db.refresh(session)
|
| 52 |
+
|
| 53 |
+
session_id = session.id
|
| 54 |
+
|
| 55 |
+
user_message = ChatMessage(
|
| 56 |
+
session_id=session_id,
|
| 57 |
+
role="user",
|
| 58 |
+
content=query.question,
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
db.add(user_message)
|
| 62 |
+
|
| 63 |
+
db.commit()
|
| 64 |
+
|
| 65 |
+
try:
|
| 66 |
+
rag_response = ask_question(
|
| 67 |
+
question=query.question,
|
| 68 |
+
user_id=current_user.id,
|
| 69 |
+
source=query.source,
|
| 70 |
+
)
|
| 71 |
+
except AuthenticationError as exc:
|
| 72 |
+
raise HTTPException(
|
| 73 |
+
status_code=502,
|
| 74 |
+
detail="Groq authentication failed. Check GROQ_API_KEY in .env and restart the server.",
|
| 75 |
+
) from exc
|
| 76 |
+
|
| 77 |
+
assistant_message = ChatMessage(
|
| 78 |
+
session_id=session_id,
|
| 79 |
+
role="assistant",
|
| 80 |
+
content=rag_response["answer"],
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
db.add(assistant_message)
|
| 84 |
+
|
| 85 |
+
db.commit()
|
| 86 |
+
|
| 87 |
+
return {
|
| 88 |
+
"session_id": session_id,
|
| 89 |
+
"answer": rag_response["answer"],
|
| 90 |
+
"sources": rag_response["sources"],
|
| 91 |
+
}
|
DocPilot/backend/app/api/documents.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import (
|
| 2 |
+
APIRouter,
|
| 3 |
+
UploadFile,
|
| 4 |
+
File,
|
| 5 |
+
Depends,
|
| 6 |
+
HTTPException,
|
| 7 |
+
)
|
| 8 |
+
|
| 9 |
+
from sqlalchemy.orm import Session
|
| 10 |
+
|
| 11 |
+
import shutil
|
| 12 |
+
import os
|
| 13 |
+
|
| 14 |
+
from app.services.ingestion import (
|
| 15 |
+
process_document,
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
from pilotcore.retrieval.vector_store import (
|
| 19 |
+
reset_vector_store,
|
| 20 |
+
rebuild_index_without_document,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
from app.core.dependencies import (
|
| 24 |
+
get_current_user,
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
from app.db.session import get_db
|
| 28 |
+
|
| 29 |
+
from app.models.document import Document
|
| 30 |
+
|
| 31 |
+
from app.schemas.document import (
|
| 32 |
+
DocumentResponse,
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
router = APIRouter()
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@router.post("/upload")
|
| 39 |
+
async def upload_document(
|
| 40 |
+
file: UploadFile = File(...),
|
| 41 |
+
current_user=Depends(get_current_user),
|
| 42 |
+
db: Session = Depends(get_db),
|
| 43 |
+
):
|
| 44 |
+
|
| 45 |
+
document_count = (
|
| 46 |
+
db.query(Document).filter(Document.owner_id == current_user.id).count()
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
if current_user.plan == "free" and document_count >= 3:
|
| 50 |
+
|
| 51 |
+
raise HTTPException(
|
| 52 |
+
status_code=403,
|
| 53 |
+
detail="Free plan upload limit reached.",
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
os.makedirs(
|
| 57 |
+
"temp",
|
| 58 |
+
exist_ok=True,
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
allowed_extensions = [
|
| 62 |
+
".pdf",
|
| 63 |
+
".docx",
|
| 64 |
+
".txt",
|
| 65 |
+
".md",
|
| 66 |
+
".csv",
|
| 67 |
+
".xlsx",
|
| 68 |
+
".png",
|
| 69 |
+
".jpg",
|
| 70 |
+
".jpeg",
|
| 71 |
+
]
|
| 72 |
+
|
| 73 |
+
file_ext = os.path.splitext(file.filename)[1].lower()
|
| 74 |
+
|
| 75 |
+
if file_ext not in allowed_extensions:
|
| 76 |
+
|
| 77 |
+
raise HTTPException(
|
| 78 |
+
status_code=400,
|
| 79 |
+
detail="Unsupported file type.",
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
file_path = f"temp/{file.filename}"
|
| 83 |
+
|
| 84 |
+
with open(
|
| 85 |
+
file_path,
|
| 86 |
+
"wb",
|
| 87 |
+
) as buffer:
|
| 88 |
+
|
| 89 |
+
shutil.copyfileobj(
|
| 90 |
+
file.file,
|
| 91 |
+
buffer,
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
document = Document(
|
| 95 |
+
owner_id=current_user.id,
|
| 96 |
+
filename=file.filename,
|
| 97 |
+
filepath=file_path,
|
| 98 |
+
file_size=os.path.getsize(file_path),
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
db.add(document)
|
| 102 |
+
|
| 103 |
+
db.commit()
|
| 104 |
+
|
| 105 |
+
db.refresh(document)
|
| 106 |
+
|
| 107 |
+
process_document(
|
| 108 |
+
file_path,
|
| 109 |
+
current_user.id,
|
| 110 |
+
document.id,
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
return {
|
| 114 |
+
"message": "Document uploaded",
|
| 115 |
+
"document_id": document.id,
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
@router.get(
|
| 120 |
+
"/",
|
| 121 |
+
response_model=list[DocumentResponse],
|
| 122 |
+
)
|
| 123 |
+
def get_documents(
|
| 124 |
+
db: Session = Depends(get_db),
|
| 125 |
+
current_user=Depends(get_current_user),
|
| 126 |
+
):
|
| 127 |
+
|
| 128 |
+
documents = db.query(Document).filter(Document.owner_id == current_user.id).all()
|
| 129 |
+
|
| 130 |
+
return documents
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
@router.delete("/reset")
|
| 134 |
+
def reset_documents(
|
| 135 |
+
current_user=Depends(get_current_user),
|
| 136 |
+
):
|
| 137 |
+
|
| 138 |
+
reset_vector_store(current_user.id)
|
| 139 |
+
|
| 140 |
+
return {"message": "Vector store cleared."}
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
@router.delete("/{document_id}")
|
| 144 |
+
def delete_document(
|
| 145 |
+
document_id: int,
|
| 146 |
+
db: Session = Depends(get_db),
|
| 147 |
+
current_user=Depends(get_current_user),
|
| 148 |
+
):
|
| 149 |
+
|
| 150 |
+
document = (
|
| 151 |
+
db.query(Document)
|
| 152 |
+
.filter(
|
| 153 |
+
Document.id == document_id,
|
| 154 |
+
Document.owner_id == current_user.id,
|
| 155 |
+
)
|
| 156 |
+
.first()
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
if not document:
|
| 160 |
+
|
| 161 |
+
raise HTTPException(
|
| 162 |
+
status_code=404,
|
| 163 |
+
detail="Document not found",
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
if os.path.exists(document.filepath):
|
| 167 |
+
|
| 168 |
+
os.remove(document.filepath)
|
| 169 |
+
|
| 170 |
+
rebuild_index_without_document(
|
| 171 |
+
current_user.id,
|
| 172 |
+
document.id,
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
db.delete(document)
|
| 176 |
+
|
| 177 |
+
db.commit()
|
| 178 |
+
|
| 179 |
+
return {
|
| 180 |
+
"message": "Document deleted",
|
| 181 |
+
}
|
DocPilot/backend/app/api/history.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import (
|
| 2 |
+
APIRouter,
|
| 3 |
+
Depends,
|
| 4 |
+
)
|
| 5 |
+
|
| 6 |
+
from sqlalchemy.orm import Session
|
| 7 |
+
|
| 8 |
+
from app.db.session import get_db
|
| 9 |
+
|
| 10 |
+
from app.models.chat import (
|
| 11 |
+
ChatSession,
|
| 12 |
+
ChatMessage,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
from app.core.dependencies import (
|
| 16 |
+
get_current_user,
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
router = APIRouter()
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
@router.get("/sessions")
|
| 23 |
+
def get_sessions(
|
| 24 |
+
db: Session = Depends(get_db),
|
| 25 |
+
current_user=Depends(get_current_user),
|
| 26 |
+
):
|
| 27 |
+
|
| 28 |
+
sessions = (
|
| 29 |
+
db.query(ChatSession)
|
| 30 |
+
.filter(ChatSession.owner_id == current_user.id)
|
| 31 |
+
.order_by(ChatSession.id.desc())
|
| 32 |
+
.all()
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
return sessions
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@router.get("/{session_id}")
|
| 39 |
+
def get_session_messages(
|
| 40 |
+
session_id: int,
|
| 41 |
+
db: Session = Depends(get_db),
|
| 42 |
+
current_user=Depends(get_current_user),
|
| 43 |
+
):
|
| 44 |
+
|
| 45 |
+
session = (
|
| 46 |
+
db.query(ChatSession)
|
| 47 |
+
.filter(
|
| 48 |
+
ChatSession.id == session_id,
|
| 49 |
+
ChatSession.owner_id == current_user.id,
|
| 50 |
+
)
|
| 51 |
+
.first()
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
if not session:
|
| 55 |
+
|
| 56 |
+
return {"detail": "Session not found"}
|
| 57 |
+
|
| 58 |
+
messages = db.query(ChatMessage).filter(ChatMessage.session_id == session_id).all()
|
| 59 |
+
|
| 60 |
+
return messages
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
@router.delete("/{session_id}")
|
| 64 |
+
def delete_session(
|
| 65 |
+
session_id: int,
|
| 66 |
+
db: Session = Depends(get_db),
|
| 67 |
+
current_user=Depends(get_current_user),
|
| 68 |
+
):
|
| 69 |
+
|
| 70 |
+
session = (
|
| 71 |
+
db.query(ChatSession)
|
| 72 |
+
.filter(
|
| 73 |
+
ChatSession.id == session_id,
|
| 74 |
+
ChatSession.owner_id == current_user.id,
|
| 75 |
+
)
|
| 76 |
+
.first()
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
if not session:
|
| 80 |
+
|
| 81 |
+
return {"detail": "Session not found"}
|
| 82 |
+
|
| 83 |
+
db.query(ChatMessage).filter(ChatMessage.session_id == session_id).delete()
|
| 84 |
+
|
| 85 |
+
db.delete(session)
|
| 86 |
+
|
| 87 |
+
db.commit()
|
| 88 |
+
|
| 89 |
+
return {"message": "Chat deleted"}
|
DocPilot/backend/app/core/config.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from pilotcore.config import DATABASE_URL, SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES
|
DocPilot/backend/app/core/dependencies.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import Depends, HTTPException
|
| 2 |
+
|
| 3 |
+
from fastapi.security import OAuth2PasswordBearer
|
| 4 |
+
|
| 5 |
+
from jose import JWTError, jwt
|
| 6 |
+
|
| 7 |
+
from sqlalchemy.orm import Session
|
| 8 |
+
|
| 9 |
+
from app.db.session import get_db
|
| 10 |
+
|
| 11 |
+
from app.models.user import User
|
| 12 |
+
|
| 13 |
+
from app.core.security import SECRET_KEY, ALGORITHM
|
| 14 |
+
|
| 15 |
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login")
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def get_current_user(
|
| 19 |
+
token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)
|
| 20 |
+
):
|
| 21 |
+
|
| 22 |
+
credentials_exception = HTTPException(
|
| 23 |
+
status_code=401, detail="Could not validate credentials"
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
try:
|
| 27 |
+
|
| 28 |
+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
| 29 |
+
|
| 30 |
+
email = payload.get("sub")
|
| 31 |
+
|
| 32 |
+
if email is None:
|
| 33 |
+
raise credentials_exception
|
| 34 |
+
|
| 35 |
+
except JWTError:
|
| 36 |
+
raise credentials_exception
|
| 37 |
+
|
| 38 |
+
user = db.query(User).filter(User.email == email).first()
|
| 39 |
+
|
| 40 |
+
if user is None:
|
| 41 |
+
raise credentials_exception
|
| 42 |
+
|
| 43 |
+
return user
|
DocPilot/backend/app/core/security.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import datetime, timedelta
|
| 2 |
+
from jose import jwt
|
| 3 |
+
from passlib.context import CryptContext
|
| 4 |
+
from pilotcore.config import SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES
|
| 5 |
+
|
| 6 |
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def hash_password(password: str):
|
| 10 |
+
return pwd_context.hash(password)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def verify_password(plain_password, hashed_password):
|
| 14 |
+
return pwd_context.verify(plain_password, hashed_password)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def create_access_token(data: dict):
|
| 18 |
+
to_encode = data.copy()
|
| 19 |
+
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
| 20 |
+
to_encode.update({"exp": expire})
|
| 21 |
+
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
DocPilot/backend/app/db/database.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import create_engine
|
| 2 |
+
from sqlalchemy.orm import declarative_base, sessionmaker
|
| 3 |
+
from pilotcore.config import DATABASE_URL
|
| 4 |
+
|
| 5 |
+
engine = create_engine(DATABASE_URL)
|
| 6 |
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
| 7 |
+
Base = declarative_base()
|
| 8 |
+
|
| 9 |
+
from app.models import *
|
DocPilot/backend/app/db/session.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from app.db.database import SessionLocal
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def get_db():
|
| 5 |
+
db = SessionLocal()
|
| 6 |
+
|
| 7 |
+
try:
|
| 8 |
+
yield db
|
| 9 |
+
|
| 10 |
+
finally:
|
| 11 |
+
db.close()
|
DocPilot/backend/app/main.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI
|
| 2 |
+
|
| 3 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 4 |
+
|
| 5 |
+
from app.api import (
|
| 6 |
+
chat,
|
| 7 |
+
documents,
|
| 8 |
+
auth,
|
| 9 |
+
billing,
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
from app.db.database import (
|
| 13 |
+
engine,
|
| 14 |
+
Base,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
from app.models import (
|
| 18 |
+
User,
|
| 19 |
+
Document,
|
| 20 |
+
ChatSession,
|
| 21 |
+
ChatMessage,
|
| 22 |
+
)
|
| 23 |
+
from app.api import history
|
| 24 |
+
|
| 25 |
+
Base.metadata.create_all(bind=engine)
|
| 26 |
+
|
| 27 |
+
app = FastAPI()
|
| 28 |
+
|
| 29 |
+
app.include_router(chat.router, prefix="/chat")
|
| 30 |
+
|
| 31 |
+
app.include_router(documents.router, prefix="/docs")
|
| 32 |
+
|
| 33 |
+
app.include_router(auth.router, prefix="/auth")
|
| 34 |
+
|
| 35 |
+
app.include_router(billing.router, prefix="/billing")
|
| 36 |
+
app.include_router(history.router, prefix="/history")
|
| 37 |
+
|
| 38 |
+
app.add_middleware(
|
| 39 |
+
CORSMiddleware,
|
| 40 |
+
allow_origins=["*"],
|
| 41 |
+
allow_credentials=True,
|
| 42 |
+
allow_methods=["*"],
|
| 43 |
+
allow_headers=["*"],
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@app.get("/")
|
| 48 |
+
def root():
|
| 49 |
+
|
| 50 |
+
return {
|
| 51 |
+
"status": "running",
|
| 52 |
+
}
|
DocPilot/backend/app/models/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .user import User
|
| 2 |
+
from .document import Document
|
| 3 |
+
from .chat import ChatSession, ChatMessage
|
DocPilot/backend/app/models/chat.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import (
|
| 2 |
+
Column,
|
| 3 |
+
Integer,
|
| 4 |
+
Text,
|
| 5 |
+
String,
|
| 6 |
+
ForeignKey,
|
| 7 |
+
DateTime,
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
from sqlalchemy.sql import func
|
| 11 |
+
|
| 12 |
+
from app.db.database import Base
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class ChatSession(Base):
|
| 16 |
+
__tablename__ = "chat_sessions"
|
| 17 |
+
|
| 18 |
+
id = Column(
|
| 19 |
+
Integer,
|
| 20 |
+
primary_key=True,
|
| 21 |
+
index=True,
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
owner_id = Column(
|
| 25 |
+
Integer,
|
| 26 |
+
ForeignKey("users.id"),
|
| 27 |
+
nullable=False,
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
title = Column(
|
| 31 |
+
String,
|
| 32 |
+
default="New Chat",
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
created_at = Column(
|
| 36 |
+
DateTime(timezone=True),
|
| 37 |
+
server_default=func.now(),
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class ChatMessage(Base):
|
| 42 |
+
__tablename__ = "chat_messages"
|
| 43 |
+
|
| 44 |
+
id = Column(
|
| 45 |
+
Integer,
|
| 46 |
+
primary_key=True,
|
| 47 |
+
index=True,
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
session_id = Column(
|
| 51 |
+
Integer,
|
| 52 |
+
ForeignKey("chat_sessions.id"),
|
| 53 |
+
nullable=False,
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
role = Column(
|
| 57 |
+
Text,
|
| 58 |
+
nullable=False,
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
content = Column(
|
| 62 |
+
Text,
|
| 63 |
+
nullable=False,
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
created_at = Column(
|
| 67 |
+
DateTime(timezone=True),
|
| 68 |
+
server_default=func.now(),
|
| 69 |
+
)
|
DocPilot/backend/app/models/document.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey
|
| 2 |
+
|
| 3 |
+
from sqlalchemy.sql import func
|
| 4 |
+
|
| 5 |
+
from app.db.database import Base
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class Document(Base):
|
| 9 |
+
__tablename__ = "documents"
|
| 10 |
+
|
| 11 |
+
id = Column(Integer, primary_key=True, index=True)
|
| 12 |
+
|
| 13 |
+
owner_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
| 14 |
+
|
| 15 |
+
filename = Column(String, nullable=False)
|
| 16 |
+
|
| 17 |
+
filepath = Column(String, nullable=False)
|
| 18 |
+
|
| 19 |
+
file_size = Column(Integer)
|
| 20 |
+
|
| 21 |
+
page_count = Column(Integer)
|
| 22 |
+
|
| 23 |
+
chunk_count = Column(Integer)
|
| 24 |
+
|
| 25 |
+
ocr_used = Column(Boolean, default=False)
|
| 26 |
+
|
| 27 |
+
status = Column(String, default="processed")
|
| 28 |
+
|
| 29 |
+
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
DocPilot/backend/app/models/user.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String
|
| 2 |
+
|
| 3 |
+
from app.db.database import Base
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class User(Base):
|
| 7 |
+
__tablename__ = "users"
|
| 8 |
+
|
| 9 |
+
id = Column(Integer, primary_key=True, index=True)
|
| 10 |
+
|
| 11 |
+
username = Column(String, unique=True, nullable=False)
|
| 12 |
+
|
| 13 |
+
email = Column(String, unique=True, nullable=False)
|
| 14 |
+
|
| 15 |
+
hashed_password = Column(String, nullable=False)
|
| 16 |
+
|
| 17 |
+
plan = Column(String, default="free")
|
DocPilot/backend/app/schemas/auth.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel
|
| 2 |
+
|
| 3 |
+
from typing import Optional
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class UserCreate(BaseModel):
|
| 7 |
+
username: str
|
| 8 |
+
email: str
|
| 9 |
+
password: str
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class UserLogin(BaseModel):
|
| 13 |
+
email: str
|
| 14 |
+
password: str
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class Token(BaseModel):
|
| 18 |
+
access_token: str
|
| 19 |
+
token_type: str
|
DocPilot/backend/app/schemas/chat.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class ChatRequest(BaseModel):
|
| 5 |
+
|
| 6 |
+
question: str
|
| 7 |
+
|
| 8 |
+
source: str | None = None
|
| 9 |
+
|
| 10 |
+
session_id: int | None = None
|
DocPilot/backend/app/schemas/document.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel
|
| 2 |
+
from datetime import datetime
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class DocumentResponse(BaseModel):
|
| 6 |
+
id: int
|
| 7 |
+
|
| 8 |
+
filename: str
|
| 9 |
+
|
| 10 |
+
filepath: str
|
| 11 |
+
|
| 12 |
+
file_size: int | None = None
|
| 13 |
+
|
| 14 |
+
page_count: int | None = None
|
| 15 |
+
|
| 16 |
+
chunk_count: int | None = None
|
| 17 |
+
|
| 18 |
+
ocr_used: bool
|
| 19 |
+
|
| 20 |
+
status: str
|
| 21 |
+
|
| 22 |
+
created_at: datetime
|
| 23 |
+
|
| 24 |
+
class Config:
|
| 25 |
+
from_attributes = True
|
DocPilot/backend/app/schemas/password.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class ForgotPasswordRequest(BaseModel):
|
| 5 |
+
email: str
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class ResetPasswordRequest(BaseModel):
|
| 9 |
+
token: str
|
| 10 |
+
new_password: str
|
DocPilot/backend/app/services/ingestion.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pypdf import PdfReader
|
| 2 |
+
|
| 3 |
+
from pdf2image import convert_from_path
|
| 4 |
+
|
| 5 |
+
from docx import Document
|
| 6 |
+
|
| 7 |
+
import pandas as pd
|
| 8 |
+
|
| 9 |
+
import markdown
|
| 10 |
+
|
| 11 |
+
import pytesseract
|
| 12 |
+
|
| 13 |
+
from PIL import Image
|
| 14 |
+
|
| 15 |
+
import os
|
| 16 |
+
import time
|
| 17 |
+
import requests
|
| 18 |
+
|
| 19 |
+
from app.services.rag import add_chunks
|
| 20 |
+
from pilotcore.config import TRACEPILOT_URL
|
| 21 |
+
|
| 22 |
+
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"
|
| 23 |
+
POPPLER_PATH = r"C:\Users\Adhi\Desktop\poppler-26.02.0\Library\bin"
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def chunk_text(
|
| 27 |
+
text,
|
| 28 |
+
chunk_size=500,
|
| 29 |
+
overlap=50,
|
| 30 |
+
):
|
| 31 |
+
|
| 32 |
+
chunks = []
|
| 33 |
+
|
| 34 |
+
start = 0
|
| 35 |
+
|
| 36 |
+
while start < len(text):
|
| 37 |
+
|
| 38 |
+
end = start + chunk_size
|
| 39 |
+
|
| 40 |
+
chunks.append(text[start:end])
|
| 41 |
+
|
| 42 |
+
start += chunk_size - overlap
|
| 43 |
+
|
| 44 |
+
return chunks
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def extract_pdf_text(
|
| 48 |
+
file_path,
|
| 49 |
+
):
|
| 50 |
+
|
| 51 |
+
reader = PdfReader(file_path)
|
| 52 |
+
|
| 53 |
+
full_text = ""
|
| 54 |
+
|
| 55 |
+
for page in reader.pages:
|
| 56 |
+
|
| 57 |
+
text = page.extract_text() or ""
|
| 58 |
+
|
| 59 |
+
full_text += text + "\n"
|
| 60 |
+
|
| 61 |
+
return full_text
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def extract_pdf_ocr(
|
| 65 |
+
file_path,
|
| 66 |
+
):
|
| 67 |
+
|
| 68 |
+
images = convert_from_path(
|
| 69 |
+
file_path,
|
| 70 |
+
poppler_path=POPPLER_PATH,
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
text = ""
|
| 74 |
+
|
| 75 |
+
for image in images:
|
| 76 |
+
|
| 77 |
+
text += pytesseract.image_to_string(image)
|
| 78 |
+
|
| 79 |
+
return text
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def extract_docx_text(
|
| 83 |
+
file_path,
|
| 84 |
+
):
|
| 85 |
+
|
| 86 |
+
doc = Document(file_path)
|
| 87 |
+
|
| 88 |
+
return "\n".join(para.text for para in doc.paragraphs)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def extract_txt_text(
|
| 92 |
+
file_path,
|
| 93 |
+
):
|
| 94 |
+
|
| 95 |
+
with open(
|
| 96 |
+
file_path,
|
| 97 |
+
"r",
|
| 98 |
+
encoding="utf-8",
|
| 99 |
+
) as f:
|
| 100 |
+
|
| 101 |
+
return f.read()
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def extract_md_text(
|
| 105 |
+
file_path,
|
| 106 |
+
):
|
| 107 |
+
|
| 108 |
+
with open(
|
| 109 |
+
file_path,
|
| 110 |
+
"r",
|
| 111 |
+
encoding="utf-8",
|
| 112 |
+
) as f:
|
| 113 |
+
|
| 114 |
+
return markdown.markdown(f.read())
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def extract_csv_text(
|
| 118 |
+
file_path,
|
| 119 |
+
):
|
| 120 |
+
|
| 121 |
+
df = pd.read_csv(file_path)
|
| 122 |
+
|
| 123 |
+
return df.to_string()
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def extract_xlsx_text(
|
| 127 |
+
file_path,
|
| 128 |
+
):
|
| 129 |
+
|
| 130 |
+
df = pd.read_excel(file_path)
|
| 131 |
+
|
| 132 |
+
return df.to_string()
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def extract_image_text(
|
| 136 |
+
file_path,
|
| 137 |
+
):
|
| 138 |
+
|
| 139 |
+
image = Image.open(file_path)
|
| 140 |
+
|
| 141 |
+
return pytesseract.image_to_string(image)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def extract_text(
|
| 145 |
+
file_path,
|
| 146 |
+
):
|
| 147 |
+
|
| 148 |
+
ext = os.path.splitext(file_path)[1].lower()
|
| 149 |
+
|
| 150 |
+
if ext == ".pdf":
|
| 151 |
+
|
| 152 |
+
text = extract_pdf_text(file_path)
|
| 153 |
+
|
| 154 |
+
if len(text.strip()) < 10:
|
| 155 |
+
|
| 156 |
+
print("OCR triggered")
|
| 157 |
+
|
| 158 |
+
text = extract_pdf_ocr(file_path)
|
| 159 |
+
|
| 160 |
+
return text
|
| 161 |
+
|
| 162 |
+
elif ext == ".docx":
|
| 163 |
+
|
| 164 |
+
return extract_docx_text(file_path)
|
| 165 |
+
|
| 166 |
+
elif ext == ".txt":
|
| 167 |
+
|
| 168 |
+
return extract_txt_text(file_path)
|
| 169 |
+
|
| 170 |
+
elif ext == ".md":
|
| 171 |
+
|
| 172 |
+
return extract_md_text(file_path)
|
| 173 |
+
|
| 174 |
+
elif ext == ".csv":
|
| 175 |
+
|
| 176 |
+
return extract_csv_text(file_path)
|
| 177 |
+
|
| 178 |
+
elif ext == ".xlsx":
|
| 179 |
+
|
| 180 |
+
return extract_xlsx_text(file_path)
|
| 181 |
+
|
| 182 |
+
elif ext in [
|
| 183 |
+
".png",
|
| 184 |
+
".jpg",
|
| 185 |
+
".jpeg",
|
| 186 |
+
]:
|
| 187 |
+
|
| 188 |
+
return extract_image_text(file_path)
|
| 189 |
+
|
| 190 |
+
else:
|
| 191 |
+
|
| 192 |
+
raise Exception("Unsupported file type")
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def process_document(
|
| 196 |
+
file_path,
|
| 197 |
+
user_id,
|
| 198 |
+
document_id,
|
| 199 |
+
):
|
| 200 |
+
|
| 201 |
+
start_time = time.perf_counter()
|
| 202 |
+
|
| 203 |
+
text = extract_text(file_path)
|
| 204 |
+
|
| 205 |
+
text = " ".join(text.split())
|
| 206 |
+
|
| 207 |
+
if len(text.split()) < 5:
|
| 208 |
+
return
|
| 209 |
+
|
| 210 |
+
chunks = chunk_text(text)
|
| 211 |
+
|
| 212 |
+
all_chunks = [
|
| 213 |
+
{
|
| 214 |
+
"document_id": document_id,
|
| 215 |
+
"text": chunk,
|
| 216 |
+
"source": os.path.basename(file_path),
|
| 217 |
+
"page": 1,
|
| 218 |
+
"chunk_id": i,
|
| 219 |
+
}
|
| 220 |
+
for i, chunk in enumerate(chunks)
|
| 221 |
+
]
|
| 222 |
+
|
| 223 |
+
add_chunks(all_chunks, user_id)
|
| 224 |
+
|
| 225 |
+
latency_ms = (time.perf_counter() - start_time) * 1000
|
| 226 |
+
|
| 227 |
+
try:
|
| 228 |
+
requests.post(
|
| 229 |
+
f"{TRACEPILOT_URL}/tracepilot/ingest/document",
|
| 230 |
+
json={
|
| 231 |
+
"document_id": str(document_id),
|
| 232 |
+
"user_id": str(user_id),
|
| 233 |
+
"filename": os.path.basename(file_path),
|
| 234 |
+
"chunk_count": len(all_chunks),
|
| 235 |
+
"char_count": len(text),
|
| 236 |
+
"latency_ms": round(latency_ms, 2),
|
| 237 |
+
"status": "success",
|
| 238 |
+
},
|
| 239 |
+
timeout=2,
|
| 240 |
+
)
|
| 241 |
+
except Exception:
|
| 242 |
+
pass
|
DocPilot/backend/app/services/rag.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pilotcore.retrieval.embeddings import get_embedding
|
| 2 |
+
from pilotcore.retrieval.vector_store import add_vector
|
| 3 |
+
from pilotcore.runtime.pipeline import run_pipeline
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def add_chunks(chunks, user_id):
|
| 7 |
+
for chunk in chunks:
|
| 8 |
+
embedding = get_embedding(chunk["text"])
|
| 9 |
+
add_vector(
|
| 10 |
+
user_id=user_id,
|
| 11 |
+
embedding=embedding,
|
| 12 |
+
text=chunk["text"],
|
| 13 |
+
source=chunk["source"],
|
| 14 |
+
page=chunk["page"],
|
| 15 |
+
chunk_id=chunk["chunk_id"],
|
| 16 |
+
document_id=chunk["document_id"],
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def ask_question(question, user_id, source=None):
|
| 21 |
+
trace = run_pipeline(
|
| 22 |
+
query=question,
|
| 23 |
+
user_id=user_id,
|
| 24 |
+
source=source,
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
retrieved = trace.retrieval_result.retrieved_chunks
|
| 28 |
+
|
| 29 |
+
if not retrieved:
|
| 30 |
+
return {"answer": "No relevant context found.", "sources": []}
|
| 31 |
+
|
| 32 |
+
sources = []
|
| 33 |
+
seen = set()
|
| 34 |
+
for item in retrieved:
|
| 35 |
+
key = (item.chunk.source, item.chunk.page_number)
|
| 36 |
+
if key not in seen:
|
| 37 |
+
seen.add(key)
|
| 38 |
+
sources.append({"source": item.chunk.source, "page": item.chunk.page_number})
|
| 39 |
+
|
| 40 |
+
return {
|
| 41 |
+
"answer": trace.final_response,
|
| 42 |
+
"sources": sources,
|
| 43 |
+
}
|
Dockerfile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
RUN apt-get update && apt-get install -y \
|
| 6 |
+
tesseract-ocr \
|
| 7 |
+
poppler-utils
|
| 8 |
+
|
| 9 |
+
COPY . .
|
| 10 |
+
|
| 11 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 12 |
+
|
| 13 |
+
EXPOSE 7860
|
| 14 |
+
|
| 15 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: PilotMaster Backend
|
| 3 |
+
emoji: 🚀
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_file: Dockerfile
|
| 8 |
+
app_port: 7860
|
| 9 |
+
pinned: false
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# PilotMaster
|
| 13 |
+
|
| 14 |
+
PilotMaster is an observable AI execution ecosystem built around a single idea: when an AI answers a question, every step of that process should be visible, measurable, and inspectable.
|
| 15 |
+
|
| 16 |
+
Most RAG applications are black boxes. You upload a document, ask a question, and get an answer. You have no idea which chunks were retrieved, whether the model stayed grounded in the evidence, or whether the response was faithful to the source material. PilotMaster changes that.
|
| 17 |
+
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
## What it is
|
| 21 |
+
|
| 22 |
+
PilotMaster is made up of three layers that work together:
|
| 23 |
+
|
| 24 |
+
### PilotCore — the execution kernel
|
| 25 |
+
|
| 26 |
+
The brain of the system. PilotCore owns everything that happens at runtime: embedding documents, searching the vector store, building prompts, calling the LLM, timing spans, and emitting traces. Neither DocPilot nor TracePilot execute RAG themselves — they both delegate to PilotCore. This means the execution logic lives in exactly one place.
|
| 27 |
+
|
| 28 |
+
### DocPilot — the user-facing product
|
| 29 |
+
|
| 30 |
+
The application a user actually interacts with. Upload a PDF, DOCX, TXT, CSV, XLSX, or image. Ask questions about it. Get answers with source citations. Manage chat history. DocPilot is the product layer — it handles auth, billing, document management, and chat sessions. It calls PilotCore for everything AI-related.
|
| 31 |
+
|
| 32 |
+
### TracePilot — the observability layer
|
| 33 |
+
|
| 34 |
+
The tool an AI engineer opens while DocPilot is running. Every time a user asks a question in DocPilot, TracePilot automatically receives the full execution trace: which chunks were retrieved, what scores they had, what prompt was built, what the model said, how long each step took, and a four-dimensional evaluation of the response quality. TracePilot makes the AI's reasoning visible in real time.
|
| 35 |
+
|
| 36 |
+
---
|
| 37 |
+
|
| 38 |
+
## How a single question flows through the system
|
| 39 |
+
|
| 40 |
+
1. User types a question in DocPilot
|
| 41 |
+
2. DocPilot calls `run_pipeline(query, user_id, source)` in PilotCore
|
| 42 |
+
3. PilotCore embeds the query using `all-mpnet-base-v2` (sentence-transformers, runs locally)
|
| 43 |
+
4. PilotCore searches the user's FAISS vector store for relevant chunks
|
| 44 |
+
5. Irrelevant chunks (L2 distance > 1.4) are filtered out before the LLM sees them
|
| 45 |
+
6. PilotCore builds a prompt — QA-style for direct questions, summarization-style for broad requests
|
| 46 |
+
7. PilotCore calls Groq (`llama-3.1-8b-instant`) for generation
|
| 47 |
+
8. PilotCore runs four-dimensional evaluation on the response
|
| 48 |
+
9. PilotCore emits the full trace to TracePilot via HTTP
|
| 49 |
+
10. User sees the answer in DocPilot. AI engineer sees the trace in TracePilot.
|
| 50 |
+
|
| 51 |
+
---
|
| 52 |
+
|
| 53 |
+
## The evaluation system
|
| 54 |
+
|
| 55 |
+
Every response is judged across four independent dimensions:
|
| 56 |
+
|
| 57 |
+
**Retrieval Relevance** — did the vector store find chunks that are actually related to the query? Measured by L2 embedding distance. Scores below 0.8 are high, 0.8–1.2 are moderate, above 1.2 are low.
|
| 58 |
+
|
| 59 |
+
**Grounding Confidence** — did the model answer using the retrieved evidence? Measured by word overlap between the response and the chunks, with stopwords excluded and a length penalty applied to longer responses. If the model correctly says "I don't have enough information", it is rewarded with high grounding.
|
| 60 |
+
|
| 61 |
+
**Answerability** — did the document actually contain enough to answer this question? Measured by how many query keywords appear in the retrieved chunks. Broad queries like "explain the document" are capped at partial — no document can fully answer an open-ended request.
|
| 62 |
+
|
| 63 |
+
**Hallucination Risk** — how much of the response went beyond the evidence? Derived from faithfulness score with a length penalty. A short, accurate answer scores low risk. A long response that introduces unsupported facts scores high.
|
| 64 |
+
|
| 65 |
+
---
|
| 66 |
+
|
| 67 |
+
## Tech stack
|
| 68 |
+
|
| 69 |
+
| Layer | Technology |
|
| 70 |
+
| ------------ | ------------------------------------------------------------------------ |
|
| 71 |
+
| LLM | Groq — llama-3.1-8b-instant |
|
| 72 |
+
| Embeddings | sentence-transformers — all-mpnet-base-v2 (local, no server needed) |
|
| 73 |
+
| Vector store | FAISS (per-user, persisted to disk) |
|
| 74 |
+
| Backend | FastAPI (unified server, DocPilot + TracePilot mounted as sub-apps) |
|
| 75 |
+
| Database | PostgreSQL (DocPilot users/docs/chat), SQLite (TracePilot traces) |
|
| 76 |
+
| Frontend | React + Vite (unified app, tab-switched between DocPilot and TracePilot) |
|
| 77 |
+
|
| 78 |
+
---
|
| 79 |
+
|
| 80 |
+
## Running it
|
| 81 |
+
|
| 82 |
+
### Prerequisites
|
| 83 |
+
|
| 84 |
+
- Python 3.10+
|
| 85 |
+
- PostgreSQL running locally (`rag_saas` database)
|
| 86 |
+
- A Groq API key
|
| 87 |
+
|
| 88 |
+
### Setup
|
| 89 |
+
|
| 90 |
+
```bash
|
| 91 |
+
# Install Python dependencies
|
| 92 |
+
pip install -r DocPilot/backend/requirements.txt
|
| 93 |
+
pip install -e .
|
| 94 |
+
|
| 95 |
+
# Install frontend dependencies
|
| 96 |
+
cd frontend && npm install
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
### Configure
|
| 100 |
+
|
| 101 |
+
Copy `.env.example` to `.env` and fill in your values:
|
| 102 |
+
|
| 103 |
+
```bash
|
| 104 |
+
cp .env.example .env
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
```env
|
| 108 |
+
GROQ_API_KEY=your_groq_api_key_here
|
| 109 |
+
DATABASE_URL=postgresql://postgres:yourpassword@localhost:5432/rag_saas
|
| 110 |
+
SECRET_KEY=your_secret_key_here
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
Get a free Groq API key at [console.groq.com](https://console.groq.com).
|
| 114 |
+
|
| 115 |
+
### Start
|
| 116 |
+
|
| 117 |
+
```bash
|
| 118 |
+
# Terminal 1 — unified backend (DocPilot + TracePilot on one server)
|
| 119 |
+
uvicorn main:app --reload --port 8000
|
| 120 |
+
|
| 121 |
+
# Terminal 2 — unified frontend (DocPilot + TracePilot in one window)
|
| 122 |
+
cd frontend && npm run dev
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
Open `http://localhost:5173`. Sign up, log in, and you're in PilotMaster.
|
| 126 |
+
|
| 127 |
+
---
|
| 128 |
+
|
| 129 |
+
## 🌐 Production Cloud Deployment
|
| 130 |
+
|
| 131 |
+
PilotMaster is architected to decouple cleanly across managed infrastructure tiers for production environments:
|
| 132 |
+
|
| 133 |
+
### 1. Database Tier (Serverless Postgres)
|
| 134 |
+
|
| 135 |
+
- **Provider:** Neon.tech or Supabase.
|
| 136 |
+
- **Setup:** Provision a managed PostgreSQL instance and use the provided serverless connection string (`postgresql://...`) to replace your local database URL.
|
| 137 |
+
|
| 138 |
+
### 2. Backend API Substrate (FastAPI Core)
|
| 139 |
+
|
| 140 |
+
- **Provider:** Render or Railway (Python Web Service Web Tier).
|
| 141 |
+
- **Build Command:** `pip install -r DocPilot/backend/requirements.txt && pip install -e .`
|
| 142 |
+
- **Start Command:** `uvicorn main:app --host 0.0.0.0 --port $PORT`
|
| 143 |
+
- **Environment Variables Required:**
|
| 144 |
+
- `GROQ_API_KEY`: Production hardware passkey from console.groq.com.
|
| 145 |
+
- `DATABASE_URL`: Your live cloud connection string.
|
| 146 |
+
- `SECRET_KEY`: A secure string for handling application auth state.
|
| 147 |
+
|
| 148 |
+
### 3. Frontend Application Layer (React + Vite static edge)
|
| 149 |
+
|
| 150 |
+
- **Provider:** Vercel or Netlify.
|
| 151 |
+
- **Root Directory:** `frontend/`
|
| 152 |
+
- **Build Command:** `npm run build`
|
| 153 |
+
- **Output Directory:** `dist`
|
| 154 |
+
- **Environment Variables Required:**
|
| 155 |
+
- `VITE_API_BASE_URL`: Point this to your live Render/Railway backend domain.
|
| 156 |
+
|
| 157 |
+
## Project structure
|
| 158 |
+
|
| 159 |
+
```
|
| 160 |
+
PilotMaster/
|
| 161 |
+
├── main.py # Unified backend entry point
|
| 162 |
+
├── .env # Single source of truth for all config
|
| 163 |
+
├── frontend/ # Unified React app (DocPilot + TracePilot)
|
| 164 |
+
│ └── src/
|
| 165 |
+
│ ├── App.jsx # PilotMaster home, auth, routing
|
| 166 |
+
│ ├── docpilot/ # DocPilot workspace
|
| 167 |
+
│ └── tracepilot/ # TracePilot workspace
|
| 168 |
+
├── pilotcore/ # Execution kernel (shared by both apps)
|
| 169 |
+
│ ├── config.py # Central config — all services import from here
|
| 170 |
+
│ ├── evaluation/ # Multi-dimensional response evaluation
|
| 171 |
+
│ ├── generation/ # Groq LLM client + prompt builder
|
| 172 |
+
│ ├── retrieval/ # Embeddings, FAISS vector store, retrieval runtime
|
| 173 |
+
│ ├── runtime/ # Pipeline orchestration + trace emission
|
| 174 |
+
│ ├── schemas/ # Canonical data contracts (Trace, Chunk, Span, etc.)
|
| 175 |
+
│ └── tracing/ # Span timing, trace creation, telemetry
|
| 176 |
+
├── DocPilot/backend/ # Product layer — auth, billing, documents, chat
|
| 177 |
+
└── TracePilot/backend/ # Observability layer — trace storage, evaluation, replay
|
| 178 |
+
```
|
| 179 |
+
|
| 180 |
+
---
|
| 181 |
+
|
| 182 |
+
## PilotCore — the execution kernel
|
| 183 |
+
|
| 184 |
+
PilotCore is not an application. It has no UI, no user-facing endpoints, and no opinions about products. It is the runtime substrate that both DocPilot and TracePilot are built on top of.
|
| 185 |
+
|
| 186 |
+
Every time a user asks a question in DocPilot, it is PilotCore that actually runs. DocPilot calls `run_pipeline(query, user_id, source)` and waits. Everything that happens between that call and the response — embedding, retrieval, filtering, prompt construction, generation, evaluation, and trace emission — happens inside PilotCore.
|
| 187 |
+
|
| 188 |
+
**What PilotCore owns:**
|
| 189 |
+
|
| 190 |
+
- **Embeddings** — converts text to vectors using `all-mpnet-base-v2` running locally via sentence-transformers. No external embedding API, no network dependency.
|
| 191 |
+
- **Vector store** — manages per-user FAISS indexes on disk. Each user's documents live in their own isolated index. Supports add, search, reset, and selective deletion by document.
|
| 192 |
+
- **Retrieval runtime** — searches the vector store, returns the top-k chunks by L2 distance, then filters out anything above the relevance threshold before the LLM sees it. For broad queries where everything gets filtered, it falls back to the top 3 chunks so the LLM always has context.
|
| 193 |
+
- **Prompt builder** — detects the query type and builds the appropriate prompt. Direct fact questions get a strict QA prompt. Broad requests like "explain the document" get a summarization prompt.
|
| 194 |
+
- **Generation** — calls Groq's `llama-3.1-8b-instant` with the constructed prompt and returns the response.
|
| 195 |
+
- **Evaluation** — runs four independent evaluators on every response: retrieval relevance, grounding confidence, answerability, and hallucination risk. These are computed from the query, the response, and the retrieved chunks — not from the LLM.
|
| 196 |
+
- **Tracing** — creates a trace for every pipeline run, times each span (retrieval, generation), and emits the full trace payload to TracePilot via HTTP after execution completes.
|
| 197 |
+
- **Schemas** — defines the canonical data contracts used across the system: Trace, Chunk, RetrievedChunk, RetrievalResult, Span, Citation, DocumentMetadata.
|
| 198 |
+
- **Config** — the single source of truth for all environment variables. Every service in the ecosystem imports from `pilotcore/config.py` instead of reading `.env` directly.
|
| 199 |
+
|
| 200 |
+
The reason this architecture matters is that DocPilot and TracePilot can evolve independently without touching execution logic. If the retrieval strategy changes, the evaluation thresholds are tuned, or the LLM is swapped out, that change happens in PilotCore once and both applications immediately reflect it.
|
| 201 |
+
|
| 202 |
+
---
|
| 203 |
+
|
| 204 |
+
## Using DocPilot
|
| 205 |
+
|
| 206 |
+
DocPilot is the document intelligence interface. Here's what a typical session looks like:
|
| 207 |
+
|
| 208 |
+
**Sign up and log in.** Your account is tied to a plan — free users can upload up to 3 documents, pro users have no limit. Plan management lives on the PilotMaster home dashboard.
|
| 209 |
+
|
| 210 |
+
**Upload a document.** Drag and drop a file into the upload area in the sidebar, or click to browse. Supported formats are PDF, DOCX, TXT, MD, CSV, XLSX, PNG, JPG, and JPEG. The moment you hit Upload, PilotCore takes over — it extracts the text, chunks it into 500-character segments with 50-character overlap, embeds every chunk using `all-mpnet-base-v2`, and stores the vectors in a FAISS index scoped to your user account. For scanned PDFs and images, OCR runs automatically via Tesseract.
|
| 211 |
+
|
| 212 |
+
**Ask questions.** Type a question in the chat input and hit Send or press Enter. DocPilot calls PilotCore's `run_pipeline`, which embeds your query, searches your vector store, filters out irrelevant chunks, builds a prompt, and calls Groq for generation. The answer appears in the chat with source citations showing which file and page the evidence came from.
|
| 213 |
+
|
| 214 |
+
**Manage conversations.** Every chat session is saved and listed in the sidebar. Click any session to reload its full message history. Delete sessions you no longer need with the ✕ button. Start a fresh conversation with + New Chat.
|
| 215 |
+
|
| 216 |
+
**Reset your vector store.** The Reset button in the header clears your entire FAISS index. Use this when you want to start fresh with a new set of documents.
|
| 217 |
+
|
| 218 |
+
**Jump to TracePilot.** The TracePilot → button in the header takes you directly to the observability dashboard so you can inspect the trace for the question you just asked.
|
| 219 |
+
|
| 220 |
+
---
|
| 221 |
+
|
| 222 |
+
## Using TracePilot
|
| 223 |
+
|
| 224 |
+
TracePilot is the execution intelligence dashboard. It's designed to be open alongside DocPilot — every question asked in DocPilot automatically appears here within seconds.
|
| 225 |
+
|
| 226 |
+
**The trace list.** The left sidebar shows every query that has run through PilotCore, newest first. Each entry shows the query text, a color-coded retrieval relevance tag (green = high, orange = moderate, red = low), and additional tags if the query was unanswerable or the model abstained. Latency is shown inline on the right.
|
| 227 |
+
|
| 228 |
+
**Inspecting a trace.** Click any trace to open the full detail view. At the top you'll see the four evaluation dimensions:
|
| 229 |
+
|
| 230 |
+
- **retrieval** — how semantically close were the retrieved chunks to the query?
|
| 231 |
+
- **grounding** — how much of the response came from the retrieved evidence?
|
| 232 |
+
- **answerability** — did the document actually contain enough to answer this?
|
| 233 |
+
- **hallucination risk** — how much did the response go beyond the evidence?
|
| 234 |
+
|
| 235 |
+
Below the tags you'll see the full response, the retrieved chunks with their L2 distance scores (color-coded), and a metrics panel showing faithfulness score, query coverage, query type, and chunk count.
|
| 236 |
+
|
| 237 |
+
**Replay.** Every trace has a Replay button. Clicking it re-runs the exact same query through PilotCore using the same user's vector store, producing a new trace linked to the original via `parent_trace_id`. This lets you compare how the same question performs across different document states or after model changes.
|
| 238 |
+
|
| 239 |
+
**Execution identity.** Every trace carries a complete version snapshot of the system that produced it — `evaluator_version`, `prompt_version`, and `retriever_version`. This means when you replay a trace and the scores change, you can tell whether the difference came from the AI behaving differently or from the evaluation system itself having changed. A hallucination score from evaluator v1 is not the same thing as a hallucination score from evaluator v2, and TracePilot makes that distinction visible.
|
| 240 |
+
|
| 241 |
+
**Live updates.** TracePilot polls for new traces every 3 seconds. You don't need to refresh — ask a question in DocPilot, switch to TracePilot, and the trace appears automatically.
|
| 242 |
+
|
| 243 |
+
**The stats bar.** The header shows live aggregate stats across all traces: total trace count, average latency, grounded count, and ungrounded count. These update as new traces arrive.
|
| 244 |
+
|
| 245 |
+
**Jump to DocPilot.** The DocPilot → button in the header takes you back to the chat interface without losing your place.
|
| 246 |
+
|
| 247 |
+
---
|
| 248 |
+
|
| 249 |
+
## What makes this different
|
| 250 |
+
|
| 251 |
+
Most RAG demos are single-file scripts. PilotMaster is architected as a production-grade platform with a clear separation between the execution kernel, the product layer, and the observability layer. The same principles that make distributed systems observable — traces, spans, telemetry — are applied to AI execution.
|
| 252 |
+
|
| 253 |
+
The result is a system where you can watch the AI think. Not just see the answer, but understand exactly how it arrived there, whether it stayed grounded, and where it might have gone wrong.
|
| 254 |
+
|
| 255 |
+
---
|
| 256 |
+
|
| 257 |
+
## Architectural honesty — what this is and what it isn’t
|
| 258 |
+
|
| 259 |
+
PilotMaster is an early-stage AI runtime and observability platform. It is not a production system at scale. Being honest about the current state and the intended direction is part of what makes the architecture credible.
|
| 260 |
+
|
| 261 |
+
**The evaluators are currently heuristic-based.** Grounding, answerability, and hallucination risk are all computed from lexical overlap — word matching between the response and the retrieved chunks. This is fast, deterministic, and inspectable, which makes it a good foundation. But lexical heuristics have a ceiling. The natural evolution here is toward embedding-based semantic evaluation, LLM-as-a-judge scoring, and eventually evaluator ensembles that combine multiple signals. The current evaluators are v1 — they are designed to be replaced, which is exactly why `evaluator_version` is attached to every trace.
|
| 262 |
+
|
| 263 |
+
**Retrieval is vector similarity only.** FAISS with L2 distance is the entire retrieval stack right now. This works well for focused factual queries but shows weakness on broad or ambiguous ones — a query like "elaborate the document" has no semantic anchor in the chunk space, so the system falls back to top-k regardless of score. The next retrieval evolution is hybrid search: combining dense vector retrieval with sparse keyword matching (BM25), and adding a cross-encoder reranker to re-score the top candidates before they reach the LLM. That would eliminate the contamination problem where an irrelevant chunk sneaks through because it happened to be the closest vector.
|
| 264 |
+
|
| 265 |
+
**Trace storage is operationally thin.** TracePilot currently stores traces in SQLite and polls for new ones every 3 seconds. This is fine for a single-user development environment. At scale, this becomes a bottleneck — SQLite doesn’t support concurrent writes, polling is wasteful, and there’s no indexing on spans or evaluation fields. The intended direction is a proper trace store with queryable spans, aggregation pipelines, trace retention policies, and real-time streaming via WebSockets or Server-Sent Events instead of polling.
|
| 266 |
+
|
| 267 |
+
**Prompt versioning is manual.** Right now, bumping `prompt_version` in `pilotcore/config.py` is a manual operation. There is no prompt registry, no A/B testing infrastructure, and no way to compare prompt performance across a dataset. The intended direction is a prompt management layer inside PilotCore where prompt templates are named, versioned, and stored — so TracePilot can show not just what the response was, but exactly which prompt template produced it and how that template has performed historically.
|
| 268 |
+
|
| 269 |
+
**Evaluator versioning exists but isn’t automated.** Version constants live in `pilotcore/config.py` and are attached to every trace. When you change the evaluation logic and bump the version, all future traces carry the new version and old traces retain the old one. What doesn’t exist yet is automated detection — the system won’t warn you if you change the evaluator without bumping the version. That’s a future guardrail.
|
| 270 |
+
|
| 271 |
+
**Trace lineage is shallow.** `parent_trace_id` links a replay to its original trace, which is the beginning of execution lineage. What’s missing is deeper lineage — tracking which document version was active, which evaluator version scored it, which prompt template was used, and how all of those evolved over time. Full execution lineage is what turns a trace store into a genuine audit trail for AI behavior.
|
| 272 |
+
|
| 273 |
+
These are not oversights. They are the known frontier of the system — the places where the architecture is intentionally designed to grow.
|
TracePilot/.gitignore
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
venv/
|
| 2 |
+
.env
|
| 3 |
+
__pycache__/
|
| 4 |
+
*.pyc
|
| 5 |
+
tracepilot.db
|
TracePilot/backend/Dockerfile
ADDED
|
File without changes
|
TracePilot/backend/app/__init__.py
ADDED
|
File without changes
|
TracePilot/backend/app/analytics/failure_detector.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from app.tracing.trace_manager import get_traces
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def detect_failures():
|
| 5 |
+
|
| 6 |
+
traces = get_traces()
|
| 7 |
+
|
| 8 |
+
failures = []
|
| 9 |
+
|
| 10 |
+
for trace in traces:
|
| 11 |
+
|
| 12 |
+
reasons = []
|
| 13 |
+
|
| 14 |
+
if trace.retrieval_quality == "poor":
|
| 15 |
+
reasons.append("poor_retrieval")
|
| 16 |
+
|
| 17 |
+
if trace.latency > 2000:
|
| 18 |
+
reasons.append("high_latency")
|
| 19 |
+
|
| 20 |
+
if reasons:
|
| 21 |
+
|
| 22 |
+
failures.append({
|
| 23 |
+
"trace_id": trace.trace_id,
|
| 24 |
+
"query": trace.query,
|
| 25 |
+
"reasons": reasons,
|
| 26 |
+
"latency": trace.latency,
|
| 27 |
+
"retrieval_quality": trace.retrieval_quality
|
| 28 |
+
})
|
| 29 |
+
|
| 30 |
+
return failures
|
TracePilot/backend/app/api/__init__.py
ADDED
|
File without changes
|
TracePilot/backend/app/core/config.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from pilotcore.config import GROQ_API_KEY, GROQ_MODEL, TRACEPILOT_URL, DOCPILOT_URL
|
TracePilot/backend/app/core/llm.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from pilotcore.generation.generator import generate_response
|
TracePilot/backend/app/db/database.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
|
| 3 |
+
DB_PATH = "tracepilot.db"
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def get_connection():
|
| 7 |
+
conn = sqlite3.connect(DB_PATH)
|
| 8 |
+
conn.row_factory = sqlite3.Row
|
| 9 |
+
return conn
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def init_db():
|
| 13 |
+
|
| 14 |
+
conn = get_connection()
|
| 15 |
+
cursor = conn.cursor()
|
| 16 |
+
|
| 17 |
+
cursor.execute("""
|
| 18 |
+
CREATE TABLE IF NOT EXISTS traces (
|
| 19 |
+
trace_id TEXT PRIMARY KEY,
|
| 20 |
+
query TEXT,
|
| 21 |
+
retrieved_chunks TEXT,
|
| 22 |
+
prompt TEXT,
|
| 23 |
+
response TEXT,
|
| 24 |
+
latency REAL,
|
| 25 |
+
timestamp TEXT,
|
| 26 |
+
model_name TEXT,
|
| 27 |
+
retrieval_score_avg REAL,
|
| 28 |
+
response_length INTEGER,
|
| 29 |
+
chunk_count INTEGER,
|
| 30 |
+
parent_trace_id TEXT,
|
| 31 |
+
retrieval_quality TEXT,
|
| 32 |
+
grounded BOOLEAN,
|
| 33 |
+
top_retrieval_score REAL,
|
| 34 |
+
spans TEXT,
|
| 35 |
+
failure_types TEXT,
|
| 36 |
+
prompt_mode TEXT DEFAULT 'strict',
|
| 37 |
+
evaluation TEXT,
|
| 38 |
+
user_id TEXT,
|
| 39 |
+
source TEXT,
|
| 40 |
+
evaluator_version TEXT DEFAULT '1.0',
|
| 41 |
+
prompt_version TEXT DEFAULT '1.0',
|
| 42 |
+
retriever_version TEXT DEFAULT 'vector_v1'
|
| 43 |
+
)
|
| 44 |
+
""")
|
| 45 |
+
|
| 46 |
+
cursor.execute("""
|
| 47 |
+
CREATE TABLE IF NOT EXISTS ingestion_traces (
|
| 48 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 49 |
+
document_id TEXT,
|
| 50 |
+
user_id TEXT,
|
| 51 |
+
filename TEXT,
|
| 52 |
+
chunk_count INTEGER,
|
| 53 |
+
char_count INTEGER,
|
| 54 |
+
latency_ms REAL,
|
| 55 |
+
status TEXT,
|
| 56 |
+
timestamp TEXT
|
| 57 |
+
)
|
| 58 |
+
""")
|
| 59 |
+
|
| 60 |
+
conn.commit()
|
| 61 |
+
conn.close()
|
TracePilot/backend/app/evaluation/evaluator.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pilotcore.evaluation.evaluator import run_evaluation
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class Evaluator:
|
| 5 |
+
|
| 6 |
+
def evaluate(self, query, response, chunks):
|
| 7 |
+
scores = [c.get("score", 0) if isinstance(c, dict) else c.score for c in chunks]
|
| 8 |
+
result = run_evaluation(query=query, response=response, chunks=chunks, scores=scores)
|
| 9 |
+
# Map to legacy keys pipeline_runner still uses
|
| 10 |
+
return {
|
| 11 |
+
"grounded": result.get("grounded", False),
|
| 12 |
+
"hallucination_score": 1.0 - result.get("faithfulness_score", 0.0),
|
| 13 |
+
"faithfulness_score": result.get("faithfulness_score", 0.0),
|
| 14 |
+
"abstained": result.get("abstained", False),
|
| 15 |
+
**result,
|
| 16 |
+
}
|
TracePilot/backend/app/main.py
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
from pydantic import BaseModel
|
| 4 |
+
from typing import List, Optional
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
|
| 7 |
+
from app.db.database import init_db
|
| 8 |
+
from app.pipelines.pipeline_runner import PipelineRunner
|
| 9 |
+
from app.tracing.trace_manager import get_traces, get_trace_by_id, save_trace
|
| 10 |
+
from app.tracing.replay import replay_trace as run_replay
|
| 11 |
+
from app.analytics.failure_detector import detect_failures
|
| 12 |
+
from app.models.trace import Trace, RetrievedChunk
|
| 13 |
+
|
| 14 |
+
app = FastAPI()
|
| 15 |
+
|
| 16 |
+
app.add_middleware(
|
| 17 |
+
CORSMiddleware,
|
| 18 |
+
allow_origins=["*"],
|
| 19 |
+
allow_credentials=True,
|
| 20 |
+
allow_methods=["*"],
|
| 21 |
+
allow_headers=["*"],
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
init_db()
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class QueryRequest(BaseModel):
|
| 28 |
+
query: str
|
| 29 |
+
prompt_mode: str = "strict"
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class IngestChunk(BaseModel):
|
| 33 |
+
chunk_id: str
|
| 34 |
+
text: str
|
| 35 |
+
score: float
|
| 36 |
+
rank: int
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class IngestRequest(BaseModel):
|
| 40 |
+
trace_id: str
|
| 41 |
+
query: str
|
| 42 |
+
response: str
|
| 43 |
+
prompt: str
|
| 44 |
+
latency: float
|
| 45 |
+
model_name: str
|
| 46 |
+
retrieved_chunks: List[IngestChunk]
|
| 47 |
+
retrieval_score_avg: float
|
| 48 |
+
top_retrieval_score: float
|
| 49 |
+
chunk_count: int
|
| 50 |
+
response_length: int
|
| 51 |
+
retrieval_quality: str
|
| 52 |
+
grounded: bool
|
| 53 |
+
evaluation: Optional[dict] = None
|
| 54 |
+
spans: list = []
|
| 55 |
+
failure_types: list = []
|
| 56 |
+
prompt_mode: str = "strict"
|
| 57 |
+
parent_trace_id: Optional[str] = None
|
| 58 |
+
user_id: Optional[str] = None
|
| 59 |
+
source: Optional[str] = None
|
| 60 |
+
evaluator_version: Optional[str] = "1.0"
|
| 61 |
+
prompt_version: Optional[str] = "1.0"
|
| 62 |
+
retriever_version: Optional[str] = "vector_v1"
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class EventRequest(BaseModel):
|
| 66 |
+
event_type: str
|
| 67 |
+
payload: dict
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
class IngestDocumentRequest(BaseModel):
|
| 71 |
+
document_id: str
|
| 72 |
+
user_id: str
|
| 73 |
+
filename: str
|
| 74 |
+
chunk_count: int
|
| 75 |
+
char_count: int
|
| 76 |
+
latency_ms: float
|
| 77 |
+
status: str
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
@app.post("/ingest/document")
|
| 81 |
+
def ingest_document(request: IngestDocumentRequest):
|
| 82 |
+
from app.db.database import get_connection
|
| 83 |
+
conn = get_connection()
|
| 84 |
+
cursor = conn.cursor()
|
| 85 |
+
cursor.execute("""
|
| 86 |
+
INSERT INTO ingestion_traces
|
| 87 |
+
(document_id, user_id, filename, chunk_count, char_count, latency_ms, status, timestamp)
|
| 88 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
| 89 |
+
""", (
|
| 90 |
+
request.document_id,
|
| 91 |
+
request.user_id,
|
| 92 |
+
request.filename,
|
| 93 |
+
request.chunk_count,
|
| 94 |
+
request.char_count,
|
| 95 |
+
request.latency_ms,
|
| 96 |
+
request.status,
|
| 97 |
+
str(datetime.utcnow()),
|
| 98 |
+
))
|
| 99 |
+
conn.commit()
|
| 100 |
+
conn.close()
|
| 101 |
+
return {"status": "ok", "document_id": request.document_id}
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
@app.get("/ingestion-traces")
|
| 105 |
+
def get_ingestion_traces():
|
| 106 |
+
from app.db.database import get_connection
|
| 107 |
+
conn = get_connection()
|
| 108 |
+
cursor = conn.cursor()
|
| 109 |
+
cursor.execute("SELECT * FROM ingestion_traces ORDER BY timestamp DESC")
|
| 110 |
+
rows = cursor.fetchall()
|
| 111 |
+
conn.close()
|
| 112 |
+
return [dict(row) for row in rows]
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
@app.post("/ingest")
|
| 116 |
+
def ingest_trace(request: IngestRequest):
|
| 117 |
+
trace = Trace(
|
| 118 |
+
trace_id=request.trace_id,
|
| 119 |
+
query=request.query,
|
| 120 |
+
retrieved_chunks=[
|
| 121 |
+
RetrievedChunk(**c.dict()) for c in request.retrieved_chunks
|
| 122 |
+
],
|
| 123 |
+
prompt=request.prompt,
|
| 124 |
+
response=request.response,
|
| 125 |
+
latency=request.latency,
|
| 126 |
+
timestamp=datetime.utcnow(),
|
| 127 |
+
model_name=request.model_name,
|
| 128 |
+
retrieval_score_avg=request.retrieval_score_avg,
|
| 129 |
+
response_length=request.response_length,
|
| 130 |
+
chunk_count=request.chunk_count,
|
| 131 |
+
parent_trace_id=request.parent_trace_id,
|
| 132 |
+
retrieval_quality=request.retrieval_quality,
|
| 133 |
+
grounded=request.grounded,
|
| 134 |
+
top_retrieval_score=request.top_retrieval_score,
|
| 135 |
+
spans=request.spans,
|
| 136 |
+
failure_types=request.failure_types,
|
| 137 |
+
prompt_mode=request.prompt_mode,
|
| 138 |
+
evaluation=request.evaluation or {},
|
| 139 |
+
user_id=request.user_id,
|
| 140 |
+
source=request.source,
|
| 141 |
+
evaluator_version=request.evaluator_version or "1.0",
|
| 142 |
+
prompt_version=request.prompt_version or "1.0",
|
| 143 |
+
retriever_version=request.retriever_version or "vector_v1",
|
| 144 |
+
)
|
| 145 |
+
save_trace(trace)
|
| 146 |
+
return {"status": "ok", "trace_id": trace.trace_id}
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
@app.post("/events")
|
| 150 |
+
def receive_event(request: EventRequest):
|
| 151 |
+
return {"status": "ok"}
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
@app.post("/ask")
|
| 155 |
+
def ask_question(request: QueryRequest):
|
| 156 |
+
runner = PipelineRunner()
|
| 157 |
+
return runner.run(request.query, prompt_mode=request.prompt_mode)
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
@app.get("/analytics/failures")
|
| 161 |
+
def get_failures():
|
| 162 |
+
return detect_failures()
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
@app.get("/traces")
|
| 166 |
+
def get_all_traces(retrieval_quality: str | None = None):
|
| 167 |
+
return get_traces(retrieval_quality)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
@app.get("/traces/compare")
|
| 171 |
+
def compare_traces(trace_id_1: str, trace_id_2: str):
|
| 172 |
+
trace_1 = get_trace_by_id(trace_id_1)
|
| 173 |
+
trace_2 = get_trace_by_id(trace_id_2)
|
| 174 |
+
|
| 175 |
+
if isinstance(trace_1, dict):
|
| 176 |
+
raise HTTPException(status_code=404, detail="First trace not found")
|
| 177 |
+
if isinstance(trace_2, dict):
|
| 178 |
+
raise HTTPException(status_code=404, detail="Second trace not found")
|
| 179 |
+
|
| 180 |
+
return {
|
| 181 |
+
"trace_1": {
|
| 182 |
+
"trace_id": trace_1.trace_id,
|
| 183 |
+
"model_name": trace_1.model_name,
|
| 184 |
+
"latency": trace_1.latency,
|
| 185 |
+
"retrieval_score_avg": trace_1.retrieval_score_avg,
|
| 186 |
+
"response_length": trace_1.response_length,
|
| 187 |
+
"chunk_count": trace_1.chunk_count,
|
| 188 |
+
},
|
| 189 |
+
"trace_2": {
|
| 190 |
+
"trace_id": trace_2.trace_id,
|
| 191 |
+
"model_name": trace_2.model_name,
|
| 192 |
+
"latency": trace_2.latency,
|
| 193 |
+
"retrieval_score_avg": trace_2.retrieval_score_avg,
|
| 194 |
+
"response_length": trace_2.response_length,
|
| 195 |
+
"chunk_count": trace_2.chunk_count,
|
| 196 |
+
},
|
| 197 |
+
"differences": {
|
| 198 |
+
"latency_delta": round(trace_2.latency - trace_1.latency, 2),
|
| 199 |
+
"retrieval_score_delta": round(trace_2.retrieval_score_avg - trace_1.retrieval_score_avg, 2),
|
| 200 |
+
"response_length_delta": trace_2.response_length - trace_1.response_length,
|
| 201 |
+
"response_changed": trace_1.response != trace_2.response,
|
| 202 |
+
},
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
@app.get("/traces/{trace_id}")
|
| 207 |
+
def fetch_trace(trace_id: str):
|
| 208 |
+
return get_trace_by_id(trace_id)
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
@app.post("/traces/{trace_id}/replay")
|
| 212 |
+
def replay_trace_endpoint(trace_id: str):
|
| 213 |
+
result = run_replay(trace_id)
|
| 214 |
+
if "error" in result:
|
| 215 |
+
raise HTTPException(status_code=404, detail=result["error"])
|
| 216 |
+
return result
|
TracePilot/backend/app/models/trace.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel
|
| 2 |
+
from typing import List
|
| 3 |
+
from uuid import uuid4
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class RetrievedChunk(BaseModel):
|
| 8 |
+
chunk_id: str
|
| 9 |
+
text: str
|
| 10 |
+
score: float
|
| 11 |
+
rank: int
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class Trace(BaseModel):
|
| 15 |
+
trace_id: str
|
| 16 |
+
query: str
|
| 17 |
+
retrieved_chunks: List[RetrievedChunk]
|
| 18 |
+
prompt: str
|
| 19 |
+
response: str
|
| 20 |
+
latency: float
|
| 21 |
+
timestamp: datetime
|
| 22 |
+
model_name: str
|
| 23 |
+
retrieval_score_avg: float
|
| 24 |
+
response_length: int
|
| 25 |
+
chunk_count: int
|
| 26 |
+
parent_trace_id: str | None = None
|
| 27 |
+
retrieval_quality: str
|
| 28 |
+
grounded: bool
|
| 29 |
+
top_retrieval_score: float = 0.0
|
| 30 |
+
prompt_mode: str = "strict"
|
| 31 |
+
spans: list = []
|
| 32 |
+
failure_types: list = []
|
| 33 |
+
evaluation: dict = {}
|
| 34 |
+
user_id: str | None = None
|
| 35 |
+
source: str | None = None
|
| 36 |
+
evaluator_version: str = "1.0"
|
| 37 |
+
prompt_version: str = "1.0"
|
| 38 |
+
retriever_version: str = "vector_v1"
|
| 39 |
+
|
| 40 |
+
@staticmethod
|
| 41 |
+
def create_id():
|
| 42 |
+
return str(uuid4())
|
TracePilot/backend/app/pipelines/pipeline_runner.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pilotcore.runtime.pipeline import run_pipeline
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class PipelineRunner:
|
| 5 |
+
|
| 6 |
+
def run(self, query, parent_trace_id=None, prompt_mode="strict"):
|
| 7 |
+
trace = run_pipeline(query=query)
|
| 8 |
+
return {
|
| 9 |
+
"trace_id": trace.trace_id,
|
| 10 |
+
"response": trace.final_response,
|
| 11 |
+
"parent_trace_id": parent_trace_id,
|
| 12 |
+
}
|
TracePilot/backend/app/tracing/replay.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from app.tracing.trace_manager import get_trace
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def replay_trace(trace_id: str):
|
| 5 |
+
|
| 6 |
+
original_trace = get_trace(trace_id)
|
| 7 |
+
|
| 8 |
+
if not original_trace:
|
| 9 |
+
return {"error": "Trace not found"}
|
| 10 |
+
|
| 11 |
+
from pilotcore.runtime.pipeline import run_pipeline
|
| 12 |
+
|
| 13 |
+
trace = run_pipeline(
|
| 14 |
+
query=original_trace["query"],
|
| 15 |
+
user_id=original_trace.get("user_id"),
|
| 16 |
+
source=original_trace.get("source"),
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
return {
|
| 20 |
+
"trace_id": trace.trace_id,
|
| 21 |
+
"query": trace.user_query,
|
| 22 |
+
"response": trace.final_response,
|
| 23 |
+
"parent_trace_id": trace_id,
|
| 24 |
+
}
|
TracePilot/backend/app/tracing/trace_manager.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
|
| 3 |
+
from app.db.database import get_connection
|
| 4 |
+
from app.models.trace import Trace, RetrievedChunk
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def save_trace(trace: Trace):
|
| 8 |
+
|
| 9 |
+
conn = get_connection()
|
| 10 |
+
cursor = conn.cursor()
|
| 11 |
+
|
| 12 |
+
cursor.execute("""
|
| 13 |
+
INSERT INTO traces (
|
| 14 |
+
trace_id, query, retrieved_chunks, prompt, response, latency,
|
| 15 |
+
timestamp, model_name, retrieval_score_avg, response_length,
|
| 16 |
+
chunk_count, parent_trace_id, retrieval_quality, grounded,
|
| 17 |
+
top_retrieval_score, spans, failure_types, prompt_mode, evaluation,
|
| 18 |
+
user_id, source, evaluator_version, prompt_version, retriever_version
|
| 19 |
+
)
|
| 20 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 21 |
+
""", (
|
| 22 |
+
trace.trace_id, trace.query,
|
| 23 |
+
json.dumps([chunk.dict() for chunk in trace.retrieved_chunks]),
|
| 24 |
+
trace.prompt, trace.response, trace.latency, str(trace.timestamp),
|
| 25 |
+
trace.model_name, trace.retrieval_score_avg, trace.response_length,
|
| 26 |
+
trace.chunk_count, trace.parent_trace_id, trace.retrieval_quality,
|
| 27 |
+
trace.grounded, trace.top_retrieval_score,
|
| 28 |
+
json.dumps(trace.spans), json.dumps(trace.failure_types),
|
| 29 |
+
trace.prompt_mode, json.dumps(trace.evaluation),
|
| 30 |
+
trace.user_id, trace.source,
|
| 31 |
+
trace.evaluator_version, trace.prompt_version, trace.retriever_version
|
| 32 |
+
))
|
| 33 |
+
|
| 34 |
+
conn.commit()
|
| 35 |
+
conn.close()
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _row_to_trace(row) -> Trace:
|
| 39 |
+
return Trace(
|
| 40 |
+
trace_id=row["trace_id"],
|
| 41 |
+
query=row["query"],
|
| 42 |
+
retrieved_chunks=[
|
| 43 |
+
RetrievedChunk(**chunk)
|
| 44 |
+
for chunk in json.loads(row["retrieved_chunks"])
|
| 45 |
+
],
|
| 46 |
+
prompt=row["prompt"],
|
| 47 |
+
response=row["response"],
|
| 48 |
+
latency=row["latency"],
|
| 49 |
+
timestamp=row["timestamp"],
|
| 50 |
+
model_name=row["model_name"],
|
| 51 |
+
retrieval_score_avg=row["retrieval_score_avg"],
|
| 52 |
+
response_length=row["response_length"],
|
| 53 |
+
chunk_count=row["chunk_count"],
|
| 54 |
+
parent_trace_id=row["parent_trace_id"],
|
| 55 |
+
retrieval_quality=row["retrieval_quality"],
|
| 56 |
+
grounded=row["grounded"],
|
| 57 |
+
top_retrieval_score=row["top_retrieval_score"],
|
| 58 |
+
spans=json.loads(row["spans"] or "[]"),
|
| 59 |
+
failure_types=json.loads(row["failure_types"] or "[]"),
|
| 60 |
+
prompt_mode=row["prompt_mode"] or "strict",
|
| 61 |
+
evaluation=json.loads(row["evaluation"] or "{}"),
|
| 62 |
+
user_id=row["user_id"],
|
| 63 |
+
source=row["source"],
|
| 64 |
+
evaluator_version=row["evaluator_version"] or "1.0",
|
| 65 |
+
prompt_version=row["prompt_version"] or "1.0",
|
| 66 |
+
retriever_version=row["retriever_version"] or "vector_v1",
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def get_traces(retrieval_quality=None):
|
| 71 |
+
|
| 72 |
+
conn = get_connection()
|
| 73 |
+
cursor = conn.cursor()
|
| 74 |
+
|
| 75 |
+
if retrieval_quality:
|
| 76 |
+
cursor.execute(
|
| 77 |
+
"SELECT * FROM traces WHERE retrieval_quality = ? ORDER BY timestamp DESC",
|
| 78 |
+
(retrieval_quality,)
|
| 79 |
+
)
|
| 80 |
+
else:
|
| 81 |
+
cursor.execute("SELECT * FROM traces ORDER BY timestamp DESC")
|
| 82 |
+
|
| 83 |
+
rows = cursor.fetchall()
|
| 84 |
+
conn.close()
|
| 85 |
+
|
| 86 |
+
return [_row_to_trace(row) for row in rows]
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def get_trace_by_id(trace_id: str):
|
| 90 |
+
|
| 91 |
+
conn = get_connection()
|
| 92 |
+
cursor = conn.cursor()
|
| 93 |
+
cursor.execute("SELECT * FROM traces WHERE trace_id = ?", (trace_id,))
|
| 94 |
+
row = cursor.fetchone()
|
| 95 |
+
conn.close()
|
| 96 |
+
|
| 97 |
+
if not row:
|
| 98 |
+
return {"error": "Trace not found"}
|
| 99 |
+
|
| 100 |
+
return _row_to_trace(row)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def get_trace(trace_id: str) -> dict | None:
|
| 104 |
+
|
| 105 |
+
conn = get_connection()
|
| 106 |
+
cursor = conn.cursor()
|
| 107 |
+
cursor.execute("SELECT * FROM traces WHERE trace_id = ?", (trace_id,))
|
| 108 |
+
row = cursor.fetchone()
|
| 109 |
+
conn.close()
|
| 110 |
+
|
| 111 |
+
if not row:
|
| 112 |
+
return None
|
| 113 |
+
|
| 114 |
+
return {
|
| 115 |
+
"trace_id": row["trace_id"],
|
| 116 |
+
"query": row["query"],
|
| 117 |
+
"retrieved_chunks": json.loads(row["retrieved_chunks"]),
|
| 118 |
+
"prompt": row["prompt"],
|
| 119 |
+
"response": row["response"],
|
| 120 |
+
"latency": row["latency"],
|
| 121 |
+
"timestamp": row["timestamp"],
|
| 122 |
+
"model_name": row["model_name"],
|
| 123 |
+
"retrieval_score_avg": row["retrieval_score_avg"],
|
| 124 |
+
"response_length": row["response_length"],
|
| 125 |
+
"chunk_count": row["chunk_count"],
|
| 126 |
+
"parent_trace_id": row["parent_trace_id"],
|
| 127 |
+
"retrieval_quality": row["retrieval_quality"],
|
| 128 |
+
"grounded": row["grounded"],
|
| 129 |
+
"top_retrieval_score": row["top_retrieval_score"],
|
| 130 |
+
"spans": json.loads(row["spans"] or "[]"),
|
| 131 |
+
"failure_types": json.loads(row["failure_types"] or "[]"),
|
| 132 |
+
"user_id": row["user_id"],
|
| 133 |
+
"source": row["source"],
|
| 134 |
+
"evaluator_version": row["evaluator_version"] or "1.0",
|
| 135 |
+
"prompt_version": row["prompt_version"] or "1.0",
|
| 136 |
+
"retriever_version": row["retriever_version"] or "vector_v1",
|
| 137 |
+
}
|
TracePilot/backend/data/knowledge_base.txt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Tawang is a town in Arunachal Pradesh in northeastern India.
|
| 2 |
+
|
| 3 |
+
Tawang Monastery is one of the largest Buddhist monasteries in India.
|
| 4 |
+
|
| 5 |
+
Visitors usually travel to Tawang from Guwahati via Tezpur.
|
| 6 |
+
|
| 7 |
+
The best time to visit Tawang is from March to October.
|
| 8 |
+
|
| 9 |
+
An Inner Line Permit is required for Indian citizens visiting Arunachal Pradesh.
|
| 10 |
+
|
| 11 |
+
Sela Pass is a famous mountain pass on the route to Tawang.
|
| 12 |
+
|
| 13 |
+
Madhuri Lake is a popular tourist attraction near Tawang.
|
| 14 |
+
|
| 15 |
+
Travelers visiting high-altitude areas should prepare for cold weather and altitude sickness.
|
TracePilot/backend/requirements.txt
ADDED
|
Binary file (812 Bytes). View file
|
|
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
services:
|
| 2 |
+
|
| 3 |
+
postgres:
|
| 4 |
+
image: postgres:16
|
| 5 |
+
environment:
|
| 6 |
+
POSTGRES_USER: postgres
|
| 7 |
+
POSTGRES_PASSWORD: root
|
| 8 |
+
POSTGRES_DB: rag_saas
|
| 9 |
+
ports:
|
| 10 |
+
- "5432:5432"
|
| 11 |
+
volumes:
|
| 12 |
+
- postgres_data:/var/lib/postgresql/data
|
| 13 |
+
healthcheck:
|
| 14 |
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
| 15 |
+
interval: 5s
|
| 16 |
+
timeout: 5s
|
| 17 |
+
retries: 5
|
| 18 |
+
|
| 19 |
+
docpilot-backend:
|
| 20 |
+
build: ./DocPilot/backend
|
| 21 |
+
ports:
|
| 22 |
+
- "8000:8000"
|
| 23 |
+
volumes:
|
| 24 |
+
- ./DocPilot/backend:/app
|
| 25 |
+
- ./pilotcore:/app/pilotcore
|
| 26 |
+
- ./.env:/.env
|
| 27 |
+
env_file:
|
| 28 |
+
- .env
|
| 29 |
+
depends_on:
|
| 30 |
+
postgres:
|
| 31 |
+
condition: service_healthy
|
| 32 |
+
|
| 33 |
+
docpilot-frontend:
|
| 34 |
+
build: ./DocPilot/frontend
|
| 35 |
+
ports:
|
| 36 |
+
- "5173:5173"
|
| 37 |
+
depends_on:
|
| 38 |
+
- docpilot-backend
|
| 39 |
+
|
| 40 |
+
tracepilot-backend:
|
| 41 |
+
build: ./TracePilot/backend
|
| 42 |
+
ports:
|
| 43 |
+
- "8001:8001"
|
| 44 |
+
volumes:
|
| 45 |
+
- ./TracePilot/backend:/app
|
| 46 |
+
- ./pilotcore:/app/pilotcore
|
| 47 |
+
- ./.env:/.env
|
| 48 |
+
env_file:
|
| 49 |
+
- .env
|
| 50 |
+
depends_on:
|
| 51 |
+
- docpilot-backend
|
| 52 |
+
|
| 53 |
+
tracepilot-frontend:
|
| 54 |
+
build: ./TracePilot/frontend
|
| 55 |
+
ports:
|
| 56 |
+
- "5174:5174"
|
| 57 |
+
depends_on:
|
| 58 |
+
- tracepilot-backend
|
| 59 |
+
|
| 60 |
+
volumes:
|
| 61 |
+
postgres_data:
|
frontend/index.html
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<title>PilotMaster</title>
|
| 7 |
+
</head>
|
| 8 |
+
<body>
|
| 9 |
+
<div id="root"></div>
|
| 10 |
+
<script type="module" src="/src/main.jsx"></script>
|
| 11 |
+
</body>
|
| 12 |
+
</html>
|
frontend/package-lock.json
ADDED
|
@@ -0,0 +1,1914 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "pilotmaster-frontend",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "pilotmaster-frontend",
|
| 9 |
+
"version": "1.0.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"axios": "^1.6.0",
|
| 12 |
+
"react": "^18.2.0",
|
| 13 |
+
"react-dom": "^18.2.0",
|
| 14 |
+
"react-dropzone": "^14.3.8"
|
| 15 |
+
},
|
| 16 |
+
"devDependencies": {
|
| 17 |
+
"@vitejs/plugin-react": "^4.2.0",
|
| 18 |
+
"vite": "^5.0.0"
|
| 19 |
+
}
|
| 20 |
+
},
|
| 21 |
+
"node_modules/@babel/code-frame": {
|
| 22 |
+
"version": "7.29.0",
|
| 23 |
+
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
| 24 |
+
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
| 25 |
+
"dev": true,
|
| 26 |
+
"dependencies": {
|
| 27 |
+
"@babel/helper-validator-identifier": "^7.28.5",
|
| 28 |
+
"js-tokens": "^4.0.0",
|
| 29 |
+
"picocolors": "^1.1.1"
|
| 30 |
+
},
|
| 31 |
+
"engines": {
|
| 32 |
+
"node": ">=6.9.0"
|
| 33 |
+
}
|
| 34 |
+
},
|
| 35 |
+
"node_modules/@babel/compat-data": {
|
| 36 |
+
"version": "7.29.3",
|
| 37 |
+
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz",
|
| 38 |
+
"integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==",
|
| 39 |
+
"dev": true,
|
| 40 |
+
"engines": {
|
| 41 |
+
"node": ">=6.9.0"
|
| 42 |
+
}
|
| 43 |
+
},
|
| 44 |
+
"node_modules/@babel/core": {
|
| 45 |
+
"version": "7.29.0",
|
| 46 |
+
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
|
| 47 |
+
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
| 48 |
+
"dev": true,
|
| 49 |
+
"dependencies": {
|
| 50 |
+
"@babel/code-frame": "^7.29.0",
|
| 51 |
+
"@babel/generator": "^7.29.0",
|
| 52 |
+
"@babel/helper-compilation-targets": "^7.28.6",
|
| 53 |
+
"@babel/helper-module-transforms": "^7.28.6",
|
| 54 |
+
"@babel/helpers": "^7.28.6",
|
| 55 |
+
"@babel/parser": "^7.29.0",
|
| 56 |
+
"@babel/template": "^7.28.6",
|
| 57 |
+
"@babel/traverse": "^7.29.0",
|
| 58 |
+
"@babel/types": "^7.29.0",
|
| 59 |
+
"@jridgewell/remapping": "^2.3.5",
|
| 60 |
+
"convert-source-map": "^2.0.0",
|
| 61 |
+
"debug": "^4.1.0",
|
| 62 |
+
"gensync": "^1.0.0-beta.2",
|
| 63 |
+
"json5": "^2.2.3",
|
| 64 |
+
"semver": "^6.3.1"
|
| 65 |
+
},
|
| 66 |
+
"engines": {
|
| 67 |
+
"node": ">=6.9.0"
|
| 68 |
+
},
|
| 69 |
+
"funding": {
|
| 70 |
+
"type": "opencollective",
|
| 71 |
+
"url": "https://opencollective.com/babel"
|
| 72 |
+
}
|
| 73 |
+
},
|
| 74 |
+
"node_modules/@babel/generator": {
|
| 75 |
+
"version": "7.29.1",
|
| 76 |
+
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
| 77 |
+
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
| 78 |
+
"dev": true,
|
| 79 |
+
"dependencies": {
|
| 80 |
+
"@babel/parser": "^7.29.0",
|
| 81 |
+
"@babel/types": "^7.29.0",
|
| 82 |
+
"@jridgewell/gen-mapping": "^0.3.12",
|
| 83 |
+
"@jridgewell/trace-mapping": "^0.3.28",
|
| 84 |
+
"jsesc": "^3.0.2"
|
| 85 |
+
},
|
| 86 |
+
"engines": {
|
| 87 |
+
"node": ">=6.9.0"
|
| 88 |
+
}
|
| 89 |
+
},
|
| 90 |
+
"node_modules/@babel/helper-compilation-targets": {
|
| 91 |
+
"version": "7.28.6",
|
| 92 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
|
| 93 |
+
"integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
|
| 94 |
+
"dev": true,
|
| 95 |
+
"dependencies": {
|
| 96 |
+
"@babel/compat-data": "^7.28.6",
|
| 97 |
+
"@babel/helper-validator-option": "^7.27.1",
|
| 98 |
+
"browserslist": "^4.24.0",
|
| 99 |
+
"lru-cache": "^5.1.1",
|
| 100 |
+
"semver": "^6.3.1"
|
| 101 |
+
},
|
| 102 |
+
"engines": {
|
| 103 |
+
"node": ">=6.9.0"
|
| 104 |
+
}
|
| 105 |
+
},
|
| 106 |
+
"node_modules/@babel/helper-globals": {
|
| 107 |
+
"version": "7.28.0",
|
| 108 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
| 109 |
+
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
| 110 |
+
"dev": true,
|
| 111 |
+
"engines": {
|
| 112 |
+
"node": ">=6.9.0"
|
| 113 |
+
}
|
| 114 |
+
},
|
| 115 |
+
"node_modules/@babel/helper-module-imports": {
|
| 116 |
+
"version": "7.28.6",
|
| 117 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
| 118 |
+
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
|
| 119 |
+
"dev": true,
|
| 120 |
+
"dependencies": {
|
| 121 |
+
"@babel/traverse": "^7.28.6",
|
| 122 |
+
"@babel/types": "^7.28.6"
|
| 123 |
+
},
|
| 124 |
+
"engines": {
|
| 125 |
+
"node": ">=6.9.0"
|
| 126 |
+
}
|
| 127 |
+
},
|
| 128 |
+
"node_modules/@babel/helper-module-transforms": {
|
| 129 |
+
"version": "7.28.6",
|
| 130 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
|
| 131 |
+
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
|
| 132 |
+
"dev": true,
|
| 133 |
+
"dependencies": {
|
| 134 |
+
"@babel/helper-module-imports": "^7.28.6",
|
| 135 |
+
"@babel/helper-validator-identifier": "^7.28.5",
|
| 136 |
+
"@babel/traverse": "^7.28.6"
|
| 137 |
+
},
|
| 138 |
+
"engines": {
|
| 139 |
+
"node": ">=6.9.0"
|
| 140 |
+
},
|
| 141 |
+
"peerDependencies": {
|
| 142 |
+
"@babel/core": "^7.0.0"
|
| 143 |
+
}
|
| 144 |
+
},
|
| 145 |
+
"node_modules/@babel/helper-plugin-utils": {
|
| 146 |
+
"version": "7.28.6",
|
| 147 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
|
| 148 |
+
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
|
| 149 |
+
"dev": true,
|
| 150 |
+
"engines": {
|
| 151 |
+
"node": ">=6.9.0"
|
| 152 |
+
}
|
| 153 |
+
},
|
| 154 |
+
"node_modules/@babel/helper-string-parser": {
|
| 155 |
+
"version": "7.27.1",
|
| 156 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
| 157 |
+
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
| 158 |
+
"dev": true,
|
| 159 |
+
"engines": {
|
| 160 |
+
"node": ">=6.9.0"
|
| 161 |
+
}
|
| 162 |
+
},
|
| 163 |
+
"node_modules/@babel/helper-validator-identifier": {
|
| 164 |
+
"version": "7.28.5",
|
| 165 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
| 166 |
+
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
| 167 |
+
"dev": true,
|
| 168 |
+
"engines": {
|
| 169 |
+
"node": ">=6.9.0"
|
| 170 |
+
}
|
| 171 |
+
},
|
| 172 |
+
"node_modules/@babel/helper-validator-option": {
|
| 173 |
+
"version": "7.27.1",
|
| 174 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
|
| 175 |
+
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
|
| 176 |
+
"dev": true,
|
| 177 |
+
"engines": {
|
| 178 |
+
"node": ">=6.9.0"
|
| 179 |
+
}
|
| 180 |
+
},
|
| 181 |
+
"node_modules/@babel/helpers": {
|
| 182 |
+
"version": "7.29.2",
|
| 183 |
+
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
|
| 184 |
+
"integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
|
| 185 |
+
"dev": true,
|
| 186 |
+
"dependencies": {
|
| 187 |
+
"@babel/template": "^7.28.6",
|
| 188 |
+
"@babel/types": "^7.29.0"
|
| 189 |
+
},
|
| 190 |
+
"engines": {
|
| 191 |
+
"node": ">=6.9.0"
|
| 192 |
+
}
|
| 193 |
+
},
|
| 194 |
+
"node_modules/@babel/parser": {
|
| 195 |
+
"version": "7.29.3",
|
| 196 |
+
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
|
| 197 |
+
"integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
|
| 198 |
+
"dev": true,
|
| 199 |
+
"dependencies": {
|
| 200 |
+
"@babel/types": "^7.29.0"
|
| 201 |
+
},
|
| 202 |
+
"bin": {
|
| 203 |
+
"parser": "bin/babel-parser.js"
|
| 204 |
+
},
|
| 205 |
+
"engines": {
|
| 206 |
+
"node": ">=6.0.0"
|
| 207 |
+
}
|
| 208 |
+
},
|
| 209 |
+
"node_modules/@babel/plugin-transform-react-jsx-self": {
|
| 210 |
+
"version": "7.27.1",
|
| 211 |
+
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
|
| 212 |
+
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
|
| 213 |
+
"dev": true,
|
| 214 |
+
"dependencies": {
|
| 215 |
+
"@babel/helper-plugin-utils": "^7.27.1"
|
| 216 |
+
},
|
| 217 |
+
"engines": {
|
| 218 |
+
"node": ">=6.9.0"
|
| 219 |
+
},
|
| 220 |
+
"peerDependencies": {
|
| 221 |
+
"@babel/core": "^7.0.0-0"
|
| 222 |
+
}
|
| 223 |
+
},
|
| 224 |
+
"node_modules/@babel/plugin-transform-react-jsx-source": {
|
| 225 |
+
"version": "7.27.1",
|
| 226 |
+
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
|
| 227 |
+
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
|
| 228 |
+
"dev": true,
|
| 229 |
+
"dependencies": {
|
| 230 |
+
"@babel/helper-plugin-utils": "^7.27.1"
|
| 231 |
+
},
|
| 232 |
+
"engines": {
|
| 233 |
+
"node": ">=6.9.0"
|
| 234 |
+
},
|
| 235 |
+
"peerDependencies": {
|
| 236 |
+
"@babel/core": "^7.0.0-0"
|
| 237 |
+
}
|
| 238 |
+
},
|
| 239 |
+
"node_modules/@babel/template": {
|
| 240 |
+
"version": "7.28.6",
|
| 241 |
+
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
| 242 |
+
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
| 243 |
+
"dev": true,
|
| 244 |
+
"dependencies": {
|
| 245 |
+
"@babel/code-frame": "^7.28.6",
|
| 246 |
+
"@babel/parser": "^7.28.6",
|
| 247 |
+
"@babel/types": "^7.28.6"
|
| 248 |
+
},
|
| 249 |
+
"engines": {
|
| 250 |
+
"node": ">=6.9.0"
|
| 251 |
+
}
|
| 252 |
+
},
|
| 253 |
+
"node_modules/@babel/traverse": {
|
| 254 |
+
"version": "7.29.0",
|
| 255 |
+
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
| 256 |
+
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
| 257 |
+
"dev": true,
|
| 258 |
+
"dependencies": {
|
| 259 |
+
"@babel/code-frame": "^7.29.0",
|
| 260 |
+
"@babel/generator": "^7.29.0",
|
| 261 |
+
"@babel/helper-globals": "^7.28.0",
|
| 262 |
+
"@babel/parser": "^7.29.0",
|
| 263 |
+
"@babel/template": "^7.28.6",
|
| 264 |
+
"@babel/types": "^7.29.0",
|
| 265 |
+
"debug": "^4.3.1"
|
| 266 |
+
},
|
| 267 |
+
"engines": {
|
| 268 |
+
"node": ">=6.9.0"
|
| 269 |
+
}
|
| 270 |
+
},
|
| 271 |
+
"node_modules/@babel/types": {
|
| 272 |
+
"version": "7.29.0",
|
| 273 |
+
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
| 274 |
+
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
| 275 |
+
"dev": true,
|
| 276 |
+
"dependencies": {
|
| 277 |
+
"@babel/helper-string-parser": "^7.27.1",
|
| 278 |
+
"@babel/helper-validator-identifier": "^7.28.5"
|
| 279 |
+
},
|
| 280 |
+
"engines": {
|
| 281 |
+
"node": ">=6.9.0"
|
| 282 |
+
}
|
| 283 |
+
},
|
| 284 |
+
"node_modules/@esbuild/aix-ppc64": {
|
| 285 |
+
"version": "0.21.5",
|
| 286 |
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
| 287 |
+
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
|
| 288 |
+
"cpu": [
|
| 289 |
+
"ppc64"
|
| 290 |
+
],
|
| 291 |
+
"dev": true,
|
| 292 |
+
"optional": true,
|
| 293 |
+
"os": [
|
| 294 |
+
"aix"
|
| 295 |
+
],
|
| 296 |
+
"engines": {
|
| 297 |
+
"node": ">=12"
|
| 298 |
+
}
|
| 299 |
+
},
|
| 300 |
+
"node_modules/@esbuild/android-arm": {
|
| 301 |
+
"version": "0.21.5",
|
| 302 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
|
| 303 |
+
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
|
| 304 |
+
"cpu": [
|
| 305 |
+
"arm"
|
| 306 |
+
],
|
| 307 |
+
"dev": true,
|
| 308 |
+
"optional": true,
|
| 309 |
+
"os": [
|
| 310 |
+
"android"
|
| 311 |
+
],
|
| 312 |
+
"engines": {
|
| 313 |
+
"node": ">=12"
|
| 314 |
+
}
|
| 315 |
+
},
|
| 316 |
+
"node_modules/@esbuild/android-arm64": {
|
| 317 |
+
"version": "0.21.5",
|
| 318 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
|
| 319 |
+
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
|
| 320 |
+
"cpu": [
|
| 321 |
+
"arm64"
|
| 322 |
+
],
|
| 323 |
+
"dev": true,
|
| 324 |
+
"optional": true,
|
| 325 |
+
"os": [
|
| 326 |
+
"android"
|
| 327 |
+
],
|
| 328 |
+
"engines": {
|
| 329 |
+
"node": ">=12"
|
| 330 |
+
}
|
| 331 |
+
},
|
| 332 |
+
"node_modules/@esbuild/android-x64": {
|
| 333 |
+
"version": "0.21.5",
|
| 334 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
|
| 335 |
+
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
|
| 336 |
+
"cpu": [
|
| 337 |
+
"x64"
|
| 338 |
+
],
|
| 339 |
+
"dev": true,
|
| 340 |
+
"optional": true,
|
| 341 |
+
"os": [
|
| 342 |
+
"android"
|
| 343 |
+
],
|
| 344 |
+
"engines": {
|
| 345 |
+
"node": ">=12"
|
| 346 |
+
}
|
| 347 |
+
},
|
| 348 |
+
"node_modules/@esbuild/darwin-arm64": {
|
| 349 |
+
"version": "0.21.5",
|
| 350 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
|
| 351 |
+
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
|
| 352 |
+
"cpu": [
|
| 353 |
+
"arm64"
|
| 354 |
+
],
|
| 355 |
+
"dev": true,
|
| 356 |
+
"optional": true,
|
| 357 |
+
"os": [
|
| 358 |
+
"darwin"
|
| 359 |
+
],
|
| 360 |
+
"engines": {
|
| 361 |
+
"node": ">=12"
|
| 362 |
+
}
|
| 363 |
+
},
|
| 364 |
+
"node_modules/@esbuild/darwin-x64": {
|
| 365 |
+
"version": "0.21.5",
|
| 366 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
|
| 367 |
+
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
|
| 368 |
+
"cpu": [
|
| 369 |
+
"x64"
|
| 370 |
+
],
|
| 371 |
+
"dev": true,
|
| 372 |
+
"optional": true,
|
| 373 |
+
"os": [
|
| 374 |
+
"darwin"
|
| 375 |
+
],
|
| 376 |
+
"engines": {
|
| 377 |
+
"node": ">=12"
|
| 378 |
+
}
|
| 379 |
+
},
|
| 380 |
+
"node_modules/@esbuild/freebsd-arm64": {
|
| 381 |
+
"version": "0.21.5",
|
| 382 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
|
| 383 |
+
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
|
| 384 |
+
"cpu": [
|
| 385 |
+
"arm64"
|
| 386 |
+
],
|
| 387 |
+
"dev": true,
|
| 388 |
+
"optional": true,
|
| 389 |
+
"os": [
|
| 390 |
+
"freebsd"
|
| 391 |
+
],
|
| 392 |
+
"engines": {
|
| 393 |
+
"node": ">=12"
|
| 394 |
+
}
|
| 395 |
+
},
|
| 396 |
+
"node_modules/@esbuild/freebsd-x64": {
|
| 397 |
+
"version": "0.21.5",
|
| 398 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
|
| 399 |
+
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
|
| 400 |
+
"cpu": [
|
| 401 |
+
"x64"
|
| 402 |
+
],
|
| 403 |
+
"dev": true,
|
| 404 |
+
"optional": true,
|
| 405 |
+
"os": [
|
| 406 |
+
"freebsd"
|
| 407 |
+
],
|
| 408 |
+
"engines": {
|
| 409 |
+
"node": ">=12"
|
| 410 |
+
}
|
| 411 |
+
},
|
| 412 |
+
"node_modules/@esbuild/linux-arm": {
|
| 413 |
+
"version": "0.21.5",
|
| 414 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
|
| 415 |
+
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
|
| 416 |
+
"cpu": [
|
| 417 |
+
"arm"
|
| 418 |
+
],
|
| 419 |
+
"dev": true,
|
| 420 |
+
"optional": true,
|
| 421 |
+
"os": [
|
| 422 |
+
"linux"
|
| 423 |
+
],
|
| 424 |
+
"engines": {
|
| 425 |
+
"node": ">=12"
|
| 426 |
+
}
|
| 427 |
+
},
|
| 428 |
+
"node_modules/@esbuild/linux-arm64": {
|
| 429 |
+
"version": "0.21.5",
|
| 430 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
|
| 431 |
+
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
|
| 432 |
+
"cpu": [
|
| 433 |
+
"arm64"
|
| 434 |
+
],
|
| 435 |
+
"dev": true,
|
| 436 |
+
"optional": true,
|
| 437 |
+
"os": [
|
| 438 |
+
"linux"
|
| 439 |
+
],
|
| 440 |
+
"engines": {
|
| 441 |
+
"node": ">=12"
|
| 442 |
+
}
|
| 443 |
+
},
|
| 444 |
+
"node_modules/@esbuild/linux-ia32": {
|
| 445 |
+
"version": "0.21.5",
|
| 446 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
|
| 447 |
+
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
|
| 448 |
+
"cpu": [
|
| 449 |
+
"ia32"
|
| 450 |
+
],
|
| 451 |
+
"dev": true,
|
| 452 |
+
"optional": true,
|
| 453 |
+
"os": [
|
| 454 |
+
"linux"
|
| 455 |
+
],
|
| 456 |
+
"engines": {
|
| 457 |
+
"node": ">=12"
|
| 458 |
+
}
|
| 459 |
+
},
|
| 460 |
+
"node_modules/@esbuild/linux-loong64": {
|
| 461 |
+
"version": "0.21.5",
|
| 462 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
|
| 463 |
+
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
|
| 464 |
+
"cpu": [
|
| 465 |
+
"loong64"
|
| 466 |
+
],
|
| 467 |
+
"dev": true,
|
| 468 |
+
"optional": true,
|
| 469 |
+
"os": [
|
| 470 |
+
"linux"
|
| 471 |
+
],
|
| 472 |
+
"engines": {
|
| 473 |
+
"node": ">=12"
|
| 474 |
+
}
|
| 475 |
+
},
|
| 476 |
+
"node_modules/@esbuild/linux-mips64el": {
|
| 477 |
+
"version": "0.21.5",
|
| 478 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
|
| 479 |
+
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
|
| 480 |
+
"cpu": [
|
| 481 |
+
"mips64el"
|
| 482 |
+
],
|
| 483 |
+
"dev": true,
|
| 484 |
+
"optional": true,
|
| 485 |
+
"os": [
|
| 486 |
+
"linux"
|
| 487 |
+
],
|
| 488 |
+
"engines": {
|
| 489 |
+
"node": ">=12"
|
| 490 |
+
}
|
| 491 |
+
},
|
| 492 |
+
"node_modules/@esbuild/linux-ppc64": {
|
| 493 |
+
"version": "0.21.5",
|
| 494 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
|
| 495 |
+
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
|
| 496 |
+
"cpu": [
|
| 497 |
+
"ppc64"
|
| 498 |
+
],
|
| 499 |
+
"dev": true,
|
| 500 |
+
"optional": true,
|
| 501 |
+
"os": [
|
| 502 |
+
"linux"
|
| 503 |
+
],
|
| 504 |
+
"engines": {
|
| 505 |
+
"node": ">=12"
|
| 506 |
+
}
|
| 507 |
+
},
|
| 508 |
+
"node_modules/@esbuild/linux-riscv64": {
|
| 509 |
+
"version": "0.21.5",
|
| 510 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
|
| 511 |
+
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
|
| 512 |
+
"cpu": [
|
| 513 |
+
"riscv64"
|
| 514 |
+
],
|
| 515 |
+
"dev": true,
|
| 516 |
+
"optional": true,
|
| 517 |
+
"os": [
|
| 518 |
+
"linux"
|
| 519 |
+
],
|
| 520 |
+
"engines": {
|
| 521 |
+
"node": ">=12"
|
| 522 |
+
}
|
| 523 |
+
},
|
| 524 |
+
"node_modules/@esbuild/linux-s390x": {
|
| 525 |
+
"version": "0.21.5",
|
| 526 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
|
| 527 |
+
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
|
| 528 |
+
"cpu": [
|
| 529 |
+
"s390x"
|
| 530 |
+
],
|
| 531 |
+
"dev": true,
|
| 532 |
+
"optional": true,
|
| 533 |
+
"os": [
|
| 534 |
+
"linux"
|
| 535 |
+
],
|
| 536 |
+
"engines": {
|
| 537 |
+
"node": ">=12"
|
| 538 |
+
}
|
| 539 |
+
},
|
| 540 |
+
"node_modules/@esbuild/linux-x64": {
|
| 541 |
+
"version": "0.21.5",
|
| 542 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
|
| 543 |
+
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
|
| 544 |
+
"cpu": [
|
| 545 |
+
"x64"
|
| 546 |
+
],
|
| 547 |
+
"dev": true,
|
| 548 |
+
"optional": true,
|
| 549 |
+
"os": [
|
| 550 |
+
"linux"
|
| 551 |
+
],
|
| 552 |
+
"engines": {
|
| 553 |
+
"node": ">=12"
|
| 554 |
+
}
|
| 555 |
+
},
|
| 556 |
+
"node_modules/@esbuild/netbsd-x64": {
|
| 557 |
+
"version": "0.21.5",
|
| 558 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
| 559 |
+
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
|
| 560 |
+
"cpu": [
|
| 561 |
+
"x64"
|
| 562 |
+
],
|
| 563 |
+
"dev": true,
|
| 564 |
+
"optional": true,
|
| 565 |
+
"os": [
|
| 566 |
+
"netbsd"
|
| 567 |
+
],
|
| 568 |
+
"engines": {
|
| 569 |
+
"node": ">=12"
|
| 570 |
+
}
|
| 571 |
+
},
|
| 572 |
+
"node_modules/@esbuild/openbsd-x64": {
|
| 573 |
+
"version": "0.21.5",
|
| 574 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
| 575 |
+
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
|
| 576 |
+
"cpu": [
|
| 577 |
+
"x64"
|
| 578 |
+
],
|
| 579 |
+
"dev": true,
|
| 580 |
+
"optional": true,
|
| 581 |
+
"os": [
|
| 582 |
+
"openbsd"
|
| 583 |
+
],
|
| 584 |
+
"engines": {
|
| 585 |
+
"node": ">=12"
|
| 586 |
+
}
|
| 587 |
+
},
|
| 588 |
+
"node_modules/@esbuild/sunos-x64": {
|
| 589 |
+
"version": "0.21.5",
|
| 590 |
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
| 591 |
+
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
|
| 592 |
+
"cpu": [
|
| 593 |
+
"x64"
|
| 594 |
+
],
|
| 595 |
+
"dev": true,
|
| 596 |
+
"optional": true,
|
| 597 |
+
"os": [
|
| 598 |
+
"sunos"
|
| 599 |
+
],
|
| 600 |
+
"engines": {
|
| 601 |
+
"node": ">=12"
|
| 602 |
+
}
|
| 603 |
+
},
|
| 604 |
+
"node_modules/@esbuild/win32-arm64": {
|
| 605 |
+
"version": "0.21.5",
|
| 606 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
|
| 607 |
+
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
|
| 608 |
+
"cpu": [
|
| 609 |
+
"arm64"
|
| 610 |
+
],
|
| 611 |
+
"dev": true,
|
| 612 |
+
"optional": true,
|
| 613 |
+
"os": [
|
| 614 |
+
"win32"
|
| 615 |
+
],
|
| 616 |
+
"engines": {
|
| 617 |
+
"node": ">=12"
|
| 618 |
+
}
|
| 619 |
+
},
|
| 620 |
+
"node_modules/@esbuild/win32-ia32": {
|
| 621 |
+
"version": "0.21.5",
|
| 622 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
|
| 623 |
+
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
|
| 624 |
+
"cpu": [
|
| 625 |
+
"ia32"
|
| 626 |
+
],
|
| 627 |
+
"dev": true,
|
| 628 |
+
"optional": true,
|
| 629 |
+
"os": [
|
| 630 |
+
"win32"
|
| 631 |
+
],
|
| 632 |
+
"engines": {
|
| 633 |
+
"node": ">=12"
|
| 634 |
+
}
|
| 635 |
+
},
|
| 636 |
+
"node_modules/@esbuild/win32-x64": {
|
| 637 |
+
"version": "0.21.5",
|
| 638 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
|
| 639 |
+
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
|
| 640 |
+
"cpu": [
|
| 641 |
+
"x64"
|
| 642 |
+
],
|
| 643 |
+
"dev": true,
|
| 644 |
+
"optional": true,
|
| 645 |
+
"os": [
|
| 646 |
+
"win32"
|
| 647 |
+
],
|
| 648 |
+
"engines": {
|
| 649 |
+
"node": ">=12"
|
| 650 |
+
}
|
| 651 |
+
},
|
| 652 |
+
"node_modules/@jridgewell/gen-mapping": {
|
| 653 |
+
"version": "0.3.13",
|
| 654 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
| 655 |
+
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
| 656 |
+
"dev": true,
|
| 657 |
+
"dependencies": {
|
| 658 |
+
"@jridgewell/sourcemap-codec": "^1.5.0",
|
| 659 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 660 |
+
}
|
| 661 |
+
},
|
| 662 |
+
"node_modules/@jridgewell/remapping": {
|
| 663 |
+
"version": "2.3.5",
|
| 664 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
| 665 |
+
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
| 666 |
+
"dev": true,
|
| 667 |
+
"dependencies": {
|
| 668 |
+
"@jridgewell/gen-mapping": "^0.3.5",
|
| 669 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 670 |
+
}
|
| 671 |
+
},
|
| 672 |
+
"node_modules/@jridgewell/resolve-uri": {
|
| 673 |
+
"version": "3.1.2",
|
| 674 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
| 675 |
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
| 676 |
+
"dev": true,
|
| 677 |
+
"engines": {
|
| 678 |
+
"node": ">=6.0.0"
|
| 679 |
+
}
|
| 680 |
+
},
|
| 681 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
| 682 |
+
"version": "1.5.5",
|
| 683 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 684 |
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
| 685 |
+
"dev": true
|
| 686 |
+
},
|
| 687 |
+
"node_modules/@jridgewell/trace-mapping": {
|
| 688 |
+
"version": "0.3.31",
|
| 689 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
| 690 |
+
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
| 691 |
+
"dev": true,
|
| 692 |
+
"dependencies": {
|
| 693 |
+
"@jridgewell/resolve-uri": "^3.1.0",
|
| 694 |
+
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 695 |
+
}
|
| 696 |
+
},
|
| 697 |
+
"node_modules/@rolldown/pluginutils": {
|
| 698 |
+
"version": "1.0.0-beta.27",
|
| 699 |
+
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
| 700 |
+
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
|
| 701 |
+
"dev": true
|
| 702 |
+
},
|
| 703 |
+
"node_modules/@rollup/rollup-android-arm-eabi": {
|
| 704 |
+
"version": "4.60.4",
|
| 705 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz",
|
| 706 |
+
"integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==",
|
| 707 |
+
"cpu": [
|
| 708 |
+
"arm"
|
| 709 |
+
],
|
| 710 |
+
"dev": true,
|
| 711 |
+
"optional": true,
|
| 712 |
+
"os": [
|
| 713 |
+
"android"
|
| 714 |
+
]
|
| 715 |
+
},
|
| 716 |
+
"node_modules/@rollup/rollup-android-arm64": {
|
| 717 |
+
"version": "4.60.4",
|
| 718 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz",
|
| 719 |
+
"integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==",
|
| 720 |
+
"cpu": [
|
| 721 |
+
"arm64"
|
| 722 |
+
],
|
| 723 |
+
"dev": true,
|
| 724 |
+
"optional": true,
|
| 725 |
+
"os": [
|
| 726 |
+
"android"
|
| 727 |
+
]
|
| 728 |
+
},
|
| 729 |
+
"node_modules/@rollup/rollup-darwin-arm64": {
|
| 730 |
+
"version": "4.60.4",
|
| 731 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz",
|
| 732 |
+
"integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==",
|
| 733 |
+
"cpu": [
|
| 734 |
+
"arm64"
|
| 735 |
+
],
|
| 736 |
+
"dev": true,
|
| 737 |
+
"optional": true,
|
| 738 |
+
"os": [
|
| 739 |
+
"darwin"
|
| 740 |
+
]
|
| 741 |
+
},
|
| 742 |
+
"node_modules/@rollup/rollup-darwin-x64": {
|
| 743 |
+
"version": "4.60.4",
|
| 744 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz",
|
| 745 |
+
"integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==",
|
| 746 |
+
"cpu": [
|
| 747 |
+
"x64"
|
| 748 |
+
],
|
| 749 |
+
"dev": true,
|
| 750 |
+
"optional": true,
|
| 751 |
+
"os": [
|
| 752 |
+
"darwin"
|
| 753 |
+
]
|
| 754 |
+
},
|
| 755 |
+
"node_modules/@rollup/rollup-freebsd-arm64": {
|
| 756 |
+
"version": "4.60.4",
|
| 757 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz",
|
| 758 |
+
"integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==",
|
| 759 |
+
"cpu": [
|
| 760 |
+
"arm64"
|
| 761 |
+
],
|
| 762 |
+
"dev": true,
|
| 763 |
+
"optional": true,
|
| 764 |
+
"os": [
|
| 765 |
+
"freebsd"
|
| 766 |
+
]
|
| 767 |
+
},
|
| 768 |
+
"node_modules/@rollup/rollup-freebsd-x64": {
|
| 769 |
+
"version": "4.60.4",
|
| 770 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz",
|
| 771 |
+
"integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==",
|
| 772 |
+
"cpu": [
|
| 773 |
+
"x64"
|
| 774 |
+
],
|
| 775 |
+
"dev": true,
|
| 776 |
+
"optional": true,
|
| 777 |
+
"os": [
|
| 778 |
+
"freebsd"
|
| 779 |
+
]
|
| 780 |
+
},
|
| 781 |
+
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
| 782 |
+
"version": "4.60.4",
|
| 783 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz",
|
| 784 |
+
"integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==",
|
| 785 |
+
"cpu": [
|
| 786 |
+
"arm"
|
| 787 |
+
],
|
| 788 |
+
"dev": true,
|
| 789 |
+
"optional": true,
|
| 790 |
+
"os": [
|
| 791 |
+
"linux"
|
| 792 |
+
]
|
| 793 |
+
},
|
| 794 |
+
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
| 795 |
+
"version": "4.60.4",
|
| 796 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz",
|
| 797 |
+
"integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==",
|
| 798 |
+
"cpu": [
|
| 799 |
+
"arm"
|
| 800 |
+
],
|
| 801 |
+
"dev": true,
|
| 802 |
+
"optional": true,
|
| 803 |
+
"os": [
|
| 804 |
+
"linux"
|
| 805 |
+
]
|
| 806 |
+
},
|
| 807 |
+
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
| 808 |
+
"version": "4.60.4",
|
| 809 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz",
|
| 810 |
+
"integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==",
|
| 811 |
+
"cpu": [
|
| 812 |
+
"arm64"
|
| 813 |
+
],
|
| 814 |
+
"dev": true,
|
| 815 |
+
"optional": true,
|
| 816 |
+
"os": [
|
| 817 |
+
"linux"
|
| 818 |
+
]
|
| 819 |
+
},
|
| 820 |
+
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
| 821 |
+
"version": "4.60.4",
|
| 822 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz",
|
| 823 |
+
"integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==",
|
| 824 |
+
"cpu": [
|
| 825 |
+
"arm64"
|
| 826 |
+
],
|
| 827 |
+
"dev": true,
|
| 828 |
+
"optional": true,
|
| 829 |
+
"os": [
|
| 830 |
+
"linux"
|
| 831 |
+
]
|
| 832 |
+
},
|
| 833 |
+
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
| 834 |
+
"version": "4.60.4",
|
| 835 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz",
|
| 836 |
+
"integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==",
|
| 837 |
+
"cpu": [
|
| 838 |
+
"loong64"
|
| 839 |
+
],
|
| 840 |
+
"dev": true,
|
| 841 |
+
"optional": true,
|
| 842 |
+
"os": [
|
| 843 |
+
"linux"
|
| 844 |
+
]
|
| 845 |
+
},
|
| 846 |
+
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
| 847 |
+
"version": "4.60.4",
|
| 848 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz",
|
| 849 |
+
"integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==",
|
| 850 |
+
"cpu": [
|
| 851 |
+
"loong64"
|
| 852 |
+
],
|
| 853 |
+
"dev": true,
|
| 854 |
+
"optional": true,
|
| 855 |
+
"os": [
|
| 856 |
+
"linux"
|
| 857 |
+
]
|
| 858 |
+
},
|
| 859 |
+
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
| 860 |
+
"version": "4.60.4",
|
| 861 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz",
|
| 862 |
+
"integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==",
|
| 863 |
+
"cpu": [
|
| 864 |
+
"ppc64"
|
| 865 |
+
],
|
| 866 |
+
"dev": true,
|
| 867 |
+
"optional": true,
|
| 868 |
+
"os": [
|
| 869 |
+
"linux"
|
| 870 |
+
]
|
| 871 |
+
},
|
| 872 |
+
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
| 873 |
+
"version": "4.60.4",
|
| 874 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz",
|
| 875 |
+
"integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==",
|
| 876 |
+
"cpu": [
|
| 877 |
+
"ppc64"
|
| 878 |
+
],
|
| 879 |
+
"dev": true,
|
| 880 |
+
"optional": true,
|
| 881 |
+
"os": [
|
| 882 |
+
"linux"
|
| 883 |
+
]
|
| 884 |
+
},
|
| 885 |
+
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
| 886 |
+
"version": "4.60.4",
|
| 887 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz",
|
| 888 |
+
"integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==",
|
| 889 |
+
"cpu": [
|
| 890 |
+
"riscv64"
|
| 891 |
+
],
|
| 892 |
+
"dev": true,
|
| 893 |
+
"optional": true,
|
| 894 |
+
"os": [
|
| 895 |
+
"linux"
|
| 896 |
+
]
|
| 897 |
+
},
|
| 898 |
+
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
| 899 |
+
"version": "4.60.4",
|
| 900 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz",
|
| 901 |
+
"integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==",
|
| 902 |
+
"cpu": [
|
| 903 |
+
"riscv64"
|
| 904 |
+
],
|
| 905 |
+
"dev": true,
|
| 906 |
+
"optional": true,
|
| 907 |
+
"os": [
|
| 908 |
+
"linux"
|
| 909 |
+
]
|
| 910 |
+
},
|
| 911 |
+
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
| 912 |
+
"version": "4.60.4",
|
| 913 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz",
|
| 914 |
+
"integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==",
|
| 915 |
+
"cpu": [
|
| 916 |
+
"s390x"
|
| 917 |
+
],
|
| 918 |
+
"dev": true,
|
| 919 |
+
"optional": true,
|
| 920 |
+
"os": [
|
| 921 |
+
"linux"
|
| 922 |
+
]
|
| 923 |
+
},
|
| 924 |
+
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
| 925 |
+
"version": "4.60.4",
|
| 926 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz",
|
| 927 |
+
"integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==",
|
| 928 |
+
"cpu": [
|
| 929 |
+
"x64"
|
| 930 |
+
],
|
| 931 |
+
"dev": true,
|
| 932 |
+
"optional": true,
|
| 933 |
+
"os": [
|
| 934 |
+
"linux"
|
| 935 |
+
]
|
| 936 |
+
},
|
| 937 |
+
"node_modules/@rollup/rollup-linux-x64-musl": {
|
| 938 |
+
"version": "4.60.4",
|
| 939 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz",
|
| 940 |
+
"integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==",
|
| 941 |
+
"cpu": [
|
| 942 |
+
"x64"
|
| 943 |
+
],
|
| 944 |
+
"dev": true,
|
| 945 |
+
"optional": true,
|
| 946 |
+
"os": [
|
| 947 |
+
"linux"
|
| 948 |
+
]
|
| 949 |
+
},
|
| 950 |
+
"node_modules/@rollup/rollup-openbsd-x64": {
|
| 951 |
+
"version": "4.60.4",
|
| 952 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz",
|
| 953 |
+
"integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==",
|
| 954 |
+
"cpu": [
|
| 955 |
+
"x64"
|
| 956 |
+
],
|
| 957 |
+
"dev": true,
|
| 958 |
+
"optional": true,
|
| 959 |
+
"os": [
|
| 960 |
+
"openbsd"
|
| 961 |
+
]
|
| 962 |
+
},
|
| 963 |
+
"node_modules/@rollup/rollup-openharmony-arm64": {
|
| 964 |
+
"version": "4.60.4",
|
| 965 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz",
|
| 966 |
+
"integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==",
|
| 967 |
+
"cpu": [
|
| 968 |
+
"arm64"
|
| 969 |
+
],
|
| 970 |
+
"dev": true,
|
| 971 |
+
"optional": true,
|
| 972 |
+
"os": [
|
| 973 |
+
"openharmony"
|
| 974 |
+
]
|
| 975 |
+
},
|
| 976 |
+
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
| 977 |
+
"version": "4.60.4",
|
| 978 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz",
|
| 979 |
+
"integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==",
|
| 980 |
+
"cpu": [
|
| 981 |
+
"arm64"
|
| 982 |
+
],
|
| 983 |
+
"dev": true,
|
| 984 |
+
"optional": true,
|
| 985 |
+
"os": [
|
| 986 |
+
"win32"
|
| 987 |
+
]
|
| 988 |
+
},
|
| 989 |
+
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
| 990 |
+
"version": "4.60.4",
|
| 991 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz",
|
| 992 |
+
"integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==",
|
| 993 |
+
"cpu": [
|
| 994 |
+
"ia32"
|
| 995 |
+
],
|
| 996 |
+
"dev": true,
|
| 997 |
+
"optional": true,
|
| 998 |
+
"os": [
|
| 999 |
+
"win32"
|
| 1000 |
+
]
|
| 1001 |
+
},
|
| 1002 |
+
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
| 1003 |
+
"version": "4.60.4",
|
| 1004 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz",
|
| 1005 |
+
"integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==",
|
| 1006 |
+
"cpu": [
|
| 1007 |
+
"x64"
|
| 1008 |
+
],
|
| 1009 |
+
"dev": true,
|
| 1010 |
+
"optional": true,
|
| 1011 |
+
"os": [
|
| 1012 |
+
"win32"
|
| 1013 |
+
]
|
| 1014 |
+
},
|
| 1015 |
+
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
| 1016 |
+
"version": "4.60.4",
|
| 1017 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz",
|
| 1018 |
+
"integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==",
|
| 1019 |
+
"cpu": [
|
| 1020 |
+
"x64"
|
| 1021 |
+
],
|
| 1022 |
+
"dev": true,
|
| 1023 |
+
"optional": true,
|
| 1024 |
+
"os": [
|
| 1025 |
+
"win32"
|
| 1026 |
+
]
|
| 1027 |
+
},
|
| 1028 |
+
"node_modules/@types/babel__core": {
|
| 1029 |
+
"version": "7.20.5",
|
| 1030 |
+
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
| 1031 |
+
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
|
| 1032 |
+
"dev": true,
|
| 1033 |
+
"dependencies": {
|
| 1034 |
+
"@babel/parser": "^7.20.7",
|
| 1035 |
+
"@babel/types": "^7.20.7",
|
| 1036 |
+
"@types/babel__generator": "*",
|
| 1037 |
+
"@types/babel__template": "*",
|
| 1038 |
+
"@types/babel__traverse": "*"
|
| 1039 |
+
}
|
| 1040 |
+
},
|
| 1041 |
+
"node_modules/@types/babel__generator": {
|
| 1042 |
+
"version": "7.27.0",
|
| 1043 |
+
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
|
| 1044 |
+
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
|
| 1045 |
+
"dev": true,
|
| 1046 |
+
"dependencies": {
|
| 1047 |
+
"@babel/types": "^7.0.0"
|
| 1048 |
+
}
|
| 1049 |
+
},
|
| 1050 |
+
"node_modules/@types/babel__template": {
|
| 1051 |
+
"version": "7.4.4",
|
| 1052 |
+
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
|
| 1053 |
+
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
|
| 1054 |
+
"dev": true,
|
| 1055 |
+
"dependencies": {
|
| 1056 |
+
"@babel/parser": "^7.1.0",
|
| 1057 |
+
"@babel/types": "^7.0.0"
|
| 1058 |
+
}
|
| 1059 |
+
},
|
| 1060 |
+
"node_modules/@types/babel__traverse": {
|
| 1061 |
+
"version": "7.28.0",
|
| 1062 |
+
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
|
| 1063 |
+
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
|
| 1064 |
+
"dev": true,
|
| 1065 |
+
"dependencies": {
|
| 1066 |
+
"@babel/types": "^7.28.2"
|
| 1067 |
+
}
|
| 1068 |
+
},
|
| 1069 |
+
"node_modules/@types/estree": {
|
| 1070 |
+
"version": "1.0.8",
|
| 1071 |
+
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
| 1072 |
+
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
| 1073 |
+
"dev": true
|
| 1074 |
+
},
|
| 1075 |
+
"node_modules/@vitejs/plugin-react": {
|
| 1076 |
+
"version": "4.7.0",
|
| 1077 |
+
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
|
| 1078 |
+
"integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
|
| 1079 |
+
"dev": true,
|
| 1080 |
+
"dependencies": {
|
| 1081 |
+
"@babel/core": "^7.28.0",
|
| 1082 |
+
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
|
| 1083 |
+
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
|
| 1084 |
+
"@rolldown/pluginutils": "1.0.0-beta.27",
|
| 1085 |
+
"@types/babel__core": "^7.20.5",
|
| 1086 |
+
"react-refresh": "^0.17.0"
|
| 1087 |
+
},
|
| 1088 |
+
"engines": {
|
| 1089 |
+
"node": "^14.18.0 || >=16.0.0"
|
| 1090 |
+
},
|
| 1091 |
+
"peerDependencies": {
|
| 1092 |
+
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
| 1093 |
+
}
|
| 1094 |
+
},
|
| 1095 |
+
"node_modules/agent-base": {
|
| 1096 |
+
"version": "6.0.2",
|
| 1097 |
+
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
| 1098 |
+
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
| 1099 |
+
"dependencies": {
|
| 1100 |
+
"debug": "4"
|
| 1101 |
+
},
|
| 1102 |
+
"engines": {
|
| 1103 |
+
"node": ">= 6.0.0"
|
| 1104 |
+
}
|
| 1105 |
+
},
|
| 1106 |
+
"node_modules/asynckit": {
|
| 1107 |
+
"version": "0.4.0",
|
| 1108 |
+
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
| 1109 |
+
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
| 1110 |
+
},
|
| 1111 |
+
"node_modules/attr-accept": {
|
| 1112 |
+
"version": "2.2.5",
|
| 1113 |
+
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
|
| 1114 |
+
"integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==",
|
| 1115 |
+
"engines": {
|
| 1116 |
+
"node": ">=4"
|
| 1117 |
+
}
|
| 1118 |
+
},
|
| 1119 |
+
"node_modules/axios": {
|
| 1120 |
+
"version": "1.16.1",
|
| 1121 |
+
"resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz",
|
| 1122 |
+
"integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==",
|
| 1123 |
+
"dependencies": {
|
| 1124 |
+
"follow-redirects": "^1.16.0",
|
| 1125 |
+
"form-data": "^4.0.5",
|
| 1126 |
+
"https-proxy-agent": "^5.0.1",
|
| 1127 |
+
"proxy-from-env": "^2.1.0"
|
| 1128 |
+
}
|
| 1129 |
+
},
|
| 1130 |
+
"node_modules/baseline-browser-mapping": {
|
| 1131 |
+
"version": "2.10.31",
|
| 1132 |
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.31.tgz",
|
| 1133 |
+
"integrity": "sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==",
|
| 1134 |
+
"dev": true,
|
| 1135 |
+
"bin": {
|
| 1136 |
+
"baseline-browser-mapping": "dist/cli.cjs"
|
| 1137 |
+
},
|
| 1138 |
+
"engines": {
|
| 1139 |
+
"node": ">=6.0.0"
|
| 1140 |
+
}
|
| 1141 |
+
},
|
| 1142 |
+
"node_modules/browserslist": {
|
| 1143 |
+
"version": "4.28.2",
|
| 1144 |
+
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
|
| 1145 |
+
"integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
|
| 1146 |
+
"dev": true,
|
| 1147 |
+
"funding": [
|
| 1148 |
+
{
|
| 1149 |
+
"type": "opencollective",
|
| 1150 |
+
"url": "https://opencollective.com/browserslist"
|
| 1151 |
+
},
|
| 1152 |
+
{
|
| 1153 |
+
"type": "tidelift",
|
| 1154 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 1155 |
+
},
|
| 1156 |
+
{
|
| 1157 |
+
"type": "github",
|
| 1158 |
+
"url": "https://github.com/sponsors/ai"
|
| 1159 |
+
}
|
| 1160 |
+
],
|
| 1161 |
+
"dependencies": {
|
| 1162 |
+
"baseline-browser-mapping": "^2.10.12",
|
| 1163 |
+
"caniuse-lite": "^1.0.30001782",
|
| 1164 |
+
"electron-to-chromium": "^1.5.328",
|
| 1165 |
+
"node-releases": "^2.0.36",
|
| 1166 |
+
"update-browserslist-db": "^1.2.3"
|
| 1167 |
+
},
|
| 1168 |
+
"bin": {
|
| 1169 |
+
"browserslist": "cli.js"
|
| 1170 |
+
},
|
| 1171 |
+
"engines": {
|
| 1172 |
+
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
| 1173 |
+
}
|
| 1174 |
+
},
|
| 1175 |
+
"node_modules/call-bind-apply-helpers": {
|
| 1176 |
+
"version": "1.0.2",
|
| 1177 |
+
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
| 1178 |
+
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
| 1179 |
+
"dependencies": {
|
| 1180 |
+
"es-errors": "^1.3.0",
|
| 1181 |
+
"function-bind": "^1.1.2"
|
| 1182 |
+
},
|
| 1183 |
+
"engines": {
|
| 1184 |
+
"node": ">= 0.4"
|
| 1185 |
+
}
|
| 1186 |
+
},
|
| 1187 |
+
"node_modules/caniuse-lite": {
|
| 1188 |
+
"version": "1.0.30001793",
|
| 1189 |
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz",
|
| 1190 |
+
"integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==",
|
| 1191 |
+
"dev": true,
|
| 1192 |
+
"funding": [
|
| 1193 |
+
{
|
| 1194 |
+
"type": "opencollective",
|
| 1195 |
+
"url": "https://opencollective.com/browserslist"
|
| 1196 |
+
},
|
| 1197 |
+
{
|
| 1198 |
+
"type": "tidelift",
|
| 1199 |
+
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
| 1200 |
+
},
|
| 1201 |
+
{
|
| 1202 |
+
"type": "github",
|
| 1203 |
+
"url": "https://github.com/sponsors/ai"
|
| 1204 |
+
}
|
| 1205 |
+
]
|
| 1206 |
+
},
|
| 1207 |
+
"node_modules/combined-stream": {
|
| 1208 |
+
"version": "1.0.8",
|
| 1209 |
+
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
| 1210 |
+
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
| 1211 |
+
"dependencies": {
|
| 1212 |
+
"delayed-stream": "~1.0.0"
|
| 1213 |
+
},
|
| 1214 |
+
"engines": {
|
| 1215 |
+
"node": ">= 0.8"
|
| 1216 |
+
}
|
| 1217 |
+
},
|
| 1218 |
+
"node_modules/convert-source-map": {
|
| 1219 |
+
"version": "2.0.0",
|
| 1220 |
+
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
| 1221 |
+
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
| 1222 |
+
"dev": true
|
| 1223 |
+
},
|
| 1224 |
+
"node_modules/debug": {
|
| 1225 |
+
"version": "4.4.3",
|
| 1226 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 1227 |
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 1228 |
+
"dependencies": {
|
| 1229 |
+
"ms": "^2.1.3"
|
| 1230 |
+
},
|
| 1231 |
+
"engines": {
|
| 1232 |
+
"node": ">=6.0"
|
| 1233 |
+
},
|
| 1234 |
+
"peerDependenciesMeta": {
|
| 1235 |
+
"supports-color": {
|
| 1236 |
+
"optional": true
|
| 1237 |
+
}
|
| 1238 |
+
}
|
| 1239 |
+
},
|
| 1240 |
+
"node_modules/delayed-stream": {
|
| 1241 |
+
"version": "1.0.0",
|
| 1242 |
+
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
| 1243 |
+
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
| 1244 |
+
"engines": {
|
| 1245 |
+
"node": ">=0.4.0"
|
| 1246 |
+
}
|
| 1247 |
+
},
|
| 1248 |
+
"node_modules/dunder-proto": {
|
| 1249 |
+
"version": "1.0.1",
|
| 1250 |
+
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
| 1251 |
+
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
| 1252 |
+
"dependencies": {
|
| 1253 |
+
"call-bind-apply-helpers": "^1.0.1",
|
| 1254 |
+
"es-errors": "^1.3.0",
|
| 1255 |
+
"gopd": "^1.2.0"
|
| 1256 |
+
},
|
| 1257 |
+
"engines": {
|
| 1258 |
+
"node": ">= 0.4"
|
| 1259 |
+
}
|
| 1260 |
+
},
|
| 1261 |
+
"node_modules/electron-to-chromium": {
|
| 1262 |
+
"version": "1.5.359",
|
| 1263 |
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.359.tgz",
|
| 1264 |
+
"integrity": "sha512-8lPELWuYZIWk7NDvCNthtmMw/7Q5Wu25NpM4djFMHBmk8DubPAtL4YTOp7ou0e7HyJtwkVlWv8XMLURnrtgJQw==",
|
| 1265 |
+
"dev": true
|
| 1266 |
+
},
|
| 1267 |
+
"node_modules/es-define-property": {
|
| 1268 |
+
"version": "1.0.1",
|
| 1269 |
+
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
| 1270 |
+
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
| 1271 |
+
"engines": {
|
| 1272 |
+
"node": ">= 0.4"
|
| 1273 |
+
}
|
| 1274 |
+
},
|
| 1275 |
+
"node_modules/es-errors": {
|
| 1276 |
+
"version": "1.3.0",
|
| 1277 |
+
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
| 1278 |
+
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
| 1279 |
+
"engines": {
|
| 1280 |
+
"node": ">= 0.4"
|
| 1281 |
+
}
|
| 1282 |
+
},
|
| 1283 |
+
"node_modules/es-object-atoms": {
|
| 1284 |
+
"version": "1.1.1",
|
| 1285 |
+
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
| 1286 |
+
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
| 1287 |
+
"dependencies": {
|
| 1288 |
+
"es-errors": "^1.3.0"
|
| 1289 |
+
},
|
| 1290 |
+
"engines": {
|
| 1291 |
+
"node": ">= 0.4"
|
| 1292 |
+
}
|
| 1293 |
+
},
|
| 1294 |
+
"node_modules/es-set-tostringtag": {
|
| 1295 |
+
"version": "2.1.0",
|
| 1296 |
+
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
| 1297 |
+
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
| 1298 |
+
"dependencies": {
|
| 1299 |
+
"es-errors": "^1.3.0",
|
| 1300 |
+
"get-intrinsic": "^1.2.6",
|
| 1301 |
+
"has-tostringtag": "^1.0.2",
|
| 1302 |
+
"hasown": "^2.0.2"
|
| 1303 |
+
},
|
| 1304 |
+
"engines": {
|
| 1305 |
+
"node": ">= 0.4"
|
| 1306 |
+
}
|
| 1307 |
+
},
|
| 1308 |
+
"node_modules/esbuild": {
|
| 1309 |
+
"version": "0.21.5",
|
| 1310 |
+
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
| 1311 |
+
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
| 1312 |
+
"dev": true,
|
| 1313 |
+
"hasInstallScript": true,
|
| 1314 |
+
"bin": {
|
| 1315 |
+
"esbuild": "bin/esbuild"
|
| 1316 |
+
},
|
| 1317 |
+
"engines": {
|
| 1318 |
+
"node": ">=12"
|
| 1319 |
+
},
|
| 1320 |
+
"optionalDependencies": {
|
| 1321 |
+
"@esbuild/aix-ppc64": "0.21.5",
|
| 1322 |
+
"@esbuild/android-arm": "0.21.5",
|
| 1323 |
+
"@esbuild/android-arm64": "0.21.5",
|
| 1324 |
+
"@esbuild/android-x64": "0.21.5",
|
| 1325 |
+
"@esbuild/darwin-arm64": "0.21.5",
|
| 1326 |
+
"@esbuild/darwin-x64": "0.21.5",
|
| 1327 |
+
"@esbuild/freebsd-arm64": "0.21.5",
|
| 1328 |
+
"@esbuild/freebsd-x64": "0.21.5",
|
| 1329 |
+
"@esbuild/linux-arm": "0.21.5",
|
| 1330 |
+
"@esbuild/linux-arm64": "0.21.5",
|
| 1331 |
+
"@esbuild/linux-ia32": "0.21.5",
|
| 1332 |
+
"@esbuild/linux-loong64": "0.21.5",
|
| 1333 |
+
"@esbuild/linux-mips64el": "0.21.5",
|
| 1334 |
+
"@esbuild/linux-ppc64": "0.21.5",
|
| 1335 |
+
"@esbuild/linux-riscv64": "0.21.5",
|
| 1336 |
+
"@esbuild/linux-s390x": "0.21.5",
|
| 1337 |
+
"@esbuild/linux-x64": "0.21.5",
|
| 1338 |
+
"@esbuild/netbsd-x64": "0.21.5",
|
| 1339 |
+
"@esbuild/openbsd-x64": "0.21.5",
|
| 1340 |
+
"@esbuild/sunos-x64": "0.21.5",
|
| 1341 |
+
"@esbuild/win32-arm64": "0.21.5",
|
| 1342 |
+
"@esbuild/win32-ia32": "0.21.5",
|
| 1343 |
+
"@esbuild/win32-x64": "0.21.5"
|
| 1344 |
+
}
|
| 1345 |
+
},
|
| 1346 |
+
"node_modules/escalade": {
|
| 1347 |
+
"version": "3.2.0",
|
| 1348 |
+
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
| 1349 |
+
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
| 1350 |
+
"dev": true,
|
| 1351 |
+
"engines": {
|
| 1352 |
+
"node": ">=6"
|
| 1353 |
+
}
|
| 1354 |
+
},
|
| 1355 |
+
"node_modules/file-selector": {
|
| 1356 |
+
"version": "2.1.2",
|
| 1357 |
+
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz",
|
| 1358 |
+
"integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==",
|
| 1359 |
+
"dependencies": {
|
| 1360 |
+
"tslib": "^2.7.0"
|
| 1361 |
+
},
|
| 1362 |
+
"engines": {
|
| 1363 |
+
"node": ">= 12"
|
| 1364 |
+
}
|
| 1365 |
+
},
|
| 1366 |
+
"node_modules/follow-redirects": {
|
| 1367 |
+
"version": "1.16.0",
|
| 1368 |
+
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
|
| 1369 |
+
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
|
| 1370 |
+
"funding": [
|
| 1371 |
+
{
|
| 1372 |
+
"type": "individual",
|
| 1373 |
+
"url": "https://github.com/sponsors/RubenVerborgh"
|
| 1374 |
+
}
|
| 1375 |
+
],
|
| 1376 |
+
"engines": {
|
| 1377 |
+
"node": ">=4.0"
|
| 1378 |
+
},
|
| 1379 |
+
"peerDependenciesMeta": {
|
| 1380 |
+
"debug": {
|
| 1381 |
+
"optional": true
|
| 1382 |
+
}
|
| 1383 |
+
}
|
| 1384 |
+
},
|
| 1385 |
+
"node_modules/form-data": {
|
| 1386 |
+
"version": "4.0.5",
|
| 1387 |
+
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
| 1388 |
+
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
| 1389 |
+
"dependencies": {
|
| 1390 |
+
"asynckit": "^0.4.0",
|
| 1391 |
+
"combined-stream": "^1.0.8",
|
| 1392 |
+
"es-set-tostringtag": "^2.1.0",
|
| 1393 |
+
"hasown": "^2.0.2",
|
| 1394 |
+
"mime-types": "^2.1.12"
|
| 1395 |
+
},
|
| 1396 |
+
"engines": {
|
| 1397 |
+
"node": ">= 6"
|
| 1398 |
+
}
|
| 1399 |
+
},
|
| 1400 |
+
"node_modules/fsevents": {
|
| 1401 |
+
"version": "2.3.3",
|
| 1402 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 1403 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 1404 |
+
"dev": true,
|
| 1405 |
+
"hasInstallScript": true,
|
| 1406 |
+
"optional": true,
|
| 1407 |
+
"os": [
|
| 1408 |
+
"darwin"
|
| 1409 |
+
],
|
| 1410 |
+
"engines": {
|
| 1411 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 1412 |
+
}
|
| 1413 |
+
},
|
| 1414 |
+
"node_modules/function-bind": {
|
| 1415 |
+
"version": "1.1.2",
|
| 1416 |
+
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
| 1417 |
+
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
| 1418 |
+
"funding": {
|
| 1419 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1420 |
+
}
|
| 1421 |
+
},
|
| 1422 |
+
"node_modules/gensync": {
|
| 1423 |
+
"version": "1.0.0-beta.2",
|
| 1424 |
+
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
| 1425 |
+
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
| 1426 |
+
"dev": true,
|
| 1427 |
+
"engines": {
|
| 1428 |
+
"node": ">=6.9.0"
|
| 1429 |
+
}
|
| 1430 |
+
},
|
| 1431 |
+
"node_modules/get-intrinsic": {
|
| 1432 |
+
"version": "1.3.0",
|
| 1433 |
+
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
| 1434 |
+
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
| 1435 |
+
"dependencies": {
|
| 1436 |
+
"call-bind-apply-helpers": "^1.0.2",
|
| 1437 |
+
"es-define-property": "^1.0.1",
|
| 1438 |
+
"es-errors": "^1.3.0",
|
| 1439 |
+
"es-object-atoms": "^1.1.1",
|
| 1440 |
+
"function-bind": "^1.1.2",
|
| 1441 |
+
"get-proto": "^1.0.1",
|
| 1442 |
+
"gopd": "^1.2.0",
|
| 1443 |
+
"has-symbols": "^1.1.0",
|
| 1444 |
+
"hasown": "^2.0.2",
|
| 1445 |
+
"math-intrinsics": "^1.1.0"
|
| 1446 |
+
},
|
| 1447 |
+
"engines": {
|
| 1448 |
+
"node": ">= 0.4"
|
| 1449 |
+
},
|
| 1450 |
+
"funding": {
|
| 1451 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1452 |
+
}
|
| 1453 |
+
},
|
| 1454 |
+
"node_modules/get-proto": {
|
| 1455 |
+
"version": "1.0.1",
|
| 1456 |
+
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
| 1457 |
+
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
| 1458 |
+
"dependencies": {
|
| 1459 |
+
"dunder-proto": "^1.0.1",
|
| 1460 |
+
"es-object-atoms": "^1.0.0"
|
| 1461 |
+
},
|
| 1462 |
+
"engines": {
|
| 1463 |
+
"node": ">= 0.4"
|
| 1464 |
+
}
|
| 1465 |
+
},
|
| 1466 |
+
"node_modules/gopd": {
|
| 1467 |
+
"version": "1.2.0",
|
| 1468 |
+
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
| 1469 |
+
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
| 1470 |
+
"engines": {
|
| 1471 |
+
"node": ">= 0.4"
|
| 1472 |
+
},
|
| 1473 |
+
"funding": {
|
| 1474 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1475 |
+
}
|
| 1476 |
+
},
|
| 1477 |
+
"node_modules/has-symbols": {
|
| 1478 |
+
"version": "1.1.0",
|
| 1479 |
+
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
| 1480 |
+
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
| 1481 |
+
"engines": {
|
| 1482 |
+
"node": ">= 0.4"
|
| 1483 |
+
},
|
| 1484 |
+
"funding": {
|
| 1485 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1486 |
+
}
|
| 1487 |
+
},
|
| 1488 |
+
"node_modules/has-tostringtag": {
|
| 1489 |
+
"version": "1.0.2",
|
| 1490 |
+
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
| 1491 |
+
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
| 1492 |
+
"dependencies": {
|
| 1493 |
+
"has-symbols": "^1.0.3"
|
| 1494 |
+
},
|
| 1495 |
+
"engines": {
|
| 1496 |
+
"node": ">= 0.4"
|
| 1497 |
+
},
|
| 1498 |
+
"funding": {
|
| 1499 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1500 |
+
}
|
| 1501 |
+
},
|
| 1502 |
+
"node_modules/hasown": {
|
| 1503 |
+
"version": "2.0.3",
|
| 1504 |
+
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
|
| 1505 |
+
"integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
|
| 1506 |
+
"dependencies": {
|
| 1507 |
+
"function-bind": "^1.1.2"
|
| 1508 |
+
},
|
| 1509 |
+
"engines": {
|
| 1510 |
+
"node": ">= 0.4"
|
| 1511 |
+
}
|
| 1512 |
+
},
|
| 1513 |
+
"node_modules/https-proxy-agent": {
|
| 1514 |
+
"version": "5.0.1",
|
| 1515 |
+
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
| 1516 |
+
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
| 1517 |
+
"dependencies": {
|
| 1518 |
+
"agent-base": "6",
|
| 1519 |
+
"debug": "4"
|
| 1520 |
+
},
|
| 1521 |
+
"engines": {
|
| 1522 |
+
"node": ">= 6"
|
| 1523 |
+
}
|
| 1524 |
+
},
|
| 1525 |
+
"node_modules/js-tokens": {
|
| 1526 |
+
"version": "4.0.0",
|
| 1527 |
+
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
| 1528 |
+
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
| 1529 |
+
},
|
| 1530 |
+
"node_modules/jsesc": {
|
| 1531 |
+
"version": "3.1.0",
|
| 1532 |
+
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
| 1533 |
+
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
| 1534 |
+
"dev": true,
|
| 1535 |
+
"bin": {
|
| 1536 |
+
"jsesc": "bin/jsesc"
|
| 1537 |
+
},
|
| 1538 |
+
"engines": {
|
| 1539 |
+
"node": ">=6"
|
| 1540 |
+
}
|
| 1541 |
+
},
|
| 1542 |
+
"node_modules/json5": {
|
| 1543 |
+
"version": "2.2.3",
|
| 1544 |
+
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
| 1545 |
+
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
| 1546 |
+
"dev": true,
|
| 1547 |
+
"bin": {
|
| 1548 |
+
"json5": "lib/cli.js"
|
| 1549 |
+
},
|
| 1550 |
+
"engines": {
|
| 1551 |
+
"node": ">=6"
|
| 1552 |
+
}
|
| 1553 |
+
},
|
| 1554 |
+
"node_modules/loose-envify": {
|
| 1555 |
+
"version": "1.4.0",
|
| 1556 |
+
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
| 1557 |
+
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
| 1558 |
+
"dependencies": {
|
| 1559 |
+
"js-tokens": "^3.0.0 || ^4.0.0"
|
| 1560 |
+
},
|
| 1561 |
+
"bin": {
|
| 1562 |
+
"loose-envify": "cli.js"
|
| 1563 |
+
}
|
| 1564 |
+
},
|
| 1565 |
+
"node_modules/lru-cache": {
|
| 1566 |
+
"version": "5.1.1",
|
| 1567 |
+
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
| 1568 |
+
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
| 1569 |
+
"dev": true,
|
| 1570 |
+
"dependencies": {
|
| 1571 |
+
"yallist": "^3.0.2"
|
| 1572 |
+
}
|
| 1573 |
+
},
|
| 1574 |
+
"node_modules/math-intrinsics": {
|
| 1575 |
+
"version": "1.1.0",
|
| 1576 |
+
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
| 1577 |
+
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
| 1578 |
+
"engines": {
|
| 1579 |
+
"node": ">= 0.4"
|
| 1580 |
+
}
|
| 1581 |
+
},
|
| 1582 |
+
"node_modules/mime-db": {
|
| 1583 |
+
"version": "1.52.0",
|
| 1584 |
+
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
| 1585 |
+
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
| 1586 |
+
"engines": {
|
| 1587 |
+
"node": ">= 0.6"
|
| 1588 |
+
}
|
| 1589 |
+
},
|
| 1590 |
+
"node_modules/mime-types": {
|
| 1591 |
+
"version": "2.1.35",
|
| 1592 |
+
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
| 1593 |
+
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
| 1594 |
+
"dependencies": {
|
| 1595 |
+
"mime-db": "1.52.0"
|
| 1596 |
+
},
|
| 1597 |
+
"engines": {
|
| 1598 |
+
"node": ">= 0.6"
|
| 1599 |
+
}
|
| 1600 |
+
},
|
| 1601 |
+
"node_modules/ms": {
|
| 1602 |
+
"version": "2.1.3",
|
| 1603 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 1604 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
| 1605 |
+
},
|
| 1606 |
+
"node_modules/nanoid": {
|
| 1607 |
+
"version": "3.3.12",
|
| 1608 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
| 1609 |
+
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
| 1610 |
+
"dev": true,
|
| 1611 |
+
"funding": [
|
| 1612 |
+
{
|
| 1613 |
+
"type": "github",
|
| 1614 |
+
"url": "https://github.com/sponsors/ai"
|
| 1615 |
+
}
|
| 1616 |
+
],
|
| 1617 |
+
"bin": {
|
| 1618 |
+
"nanoid": "bin/nanoid.cjs"
|
| 1619 |
+
},
|
| 1620 |
+
"engines": {
|
| 1621 |
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
| 1622 |
+
}
|
| 1623 |
+
},
|
| 1624 |
+
"node_modules/node-releases": {
|
| 1625 |
+
"version": "2.0.44",
|
| 1626 |
+
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz",
|
| 1627 |
+
"integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==",
|
| 1628 |
+
"dev": true
|
| 1629 |
+
},
|
| 1630 |
+
"node_modules/object-assign": {
|
| 1631 |
+
"version": "4.1.1",
|
| 1632 |
+
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
| 1633 |
+
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
| 1634 |
+
"engines": {
|
| 1635 |
+
"node": ">=0.10.0"
|
| 1636 |
+
}
|
| 1637 |
+
},
|
| 1638 |
+
"node_modules/picocolors": {
|
| 1639 |
+
"version": "1.1.1",
|
| 1640 |
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 1641 |
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 1642 |
+
"dev": true
|
| 1643 |
+
},
|
| 1644 |
+
"node_modules/postcss": {
|
| 1645 |
+
"version": "8.5.14",
|
| 1646 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
| 1647 |
+
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
|
| 1648 |
+
"dev": true,
|
| 1649 |
+
"funding": [
|
| 1650 |
+
{
|
| 1651 |
+
"type": "opencollective",
|
| 1652 |
+
"url": "https://opencollective.com/postcss/"
|
| 1653 |
+
},
|
| 1654 |
+
{
|
| 1655 |
+
"type": "tidelift",
|
| 1656 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 1657 |
+
},
|
| 1658 |
+
{
|
| 1659 |
+
"type": "github",
|
| 1660 |
+
"url": "https://github.com/sponsors/ai"
|
| 1661 |
+
}
|
| 1662 |
+
],
|
| 1663 |
+
"dependencies": {
|
| 1664 |
+
"nanoid": "^3.3.11",
|
| 1665 |
+
"picocolors": "^1.1.1",
|
| 1666 |
+
"source-map-js": "^1.2.1"
|
| 1667 |
+
},
|
| 1668 |
+
"engines": {
|
| 1669 |
+
"node": "^10 || ^12 || >=14"
|
| 1670 |
+
}
|
| 1671 |
+
},
|
| 1672 |
+
"node_modules/prop-types": {
|
| 1673 |
+
"version": "15.8.1",
|
| 1674 |
+
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
| 1675 |
+
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
| 1676 |
+
"dependencies": {
|
| 1677 |
+
"loose-envify": "^1.4.0",
|
| 1678 |
+
"object-assign": "^4.1.1",
|
| 1679 |
+
"react-is": "^16.13.1"
|
| 1680 |
+
}
|
| 1681 |
+
},
|
| 1682 |
+
"node_modules/proxy-from-env": {
|
| 1683 |
+
"version": "2.1.0",
|
| 1684 |
+
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
|
| 1685 |
+
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
|
| 1686 |
+
"engines": {
|
| 1687 |
+
"node": ">=10"
|
| 1688 |
+
}
|
| 1689 |
+
},
|
| 1690 |
+
"node_modules/react": {
|
| 1691 |
+
"version": "18.3.1",
|
| 1692 |
+
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
| 1693 |
+
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
| 1694 |
+
"dependencies": {
|
| 1695 |
+
"loose-envify": "^1.1.0"
|
| 1696 |
+
},
|
| 1697 |
+
"engines": {
|
| 1698 |
+
"node": ">=0.10.0"
|
| 1699 |
+
}
|
| 1700 |
+
},
|
| 1701 |
+
"node_modules/react-dom": {
|
| 1702 |
+
"version": "18.3.1",
|
| 1703 |
+
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
| 1704 |
+
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
| 1705 |
+
"dependencies": {
|
| 1706 |
+
"loose-envify": "^1.1.0",
|
| 1707 |
+
"scheduler": "^0.23.2"
|
| 1708 |
+
},
|
| 1709 |
+
"peerDependencies": {
|
| 1710 |
+
"react": "^18.3.1"
|
| 1711 |
+
}
|
| 1712 |
+
},
|
| 1713 |
+
"node_modules/react-dropzone": {
|
| 1714 |
+
"version": "14.4.1",
|
| 1715 |
+
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.4.1.tgz",
|
| 1716 |
+
"integrity": "sha512-QDuV76v3uKbHiH34SpwifZ+gOLi1+RdsCO1kl5vxMT4wW8R82+sthjvBw4th3NHF/XX6FBsqDYZVNN+pnhaw0g==",
|
| 1717 |
+
"dependencies": {
|
| 1718 |
+
"attr-accept": "^2.2.4",
|
| 1719 |
+
"file-selector": "^2.1.0",
|
| 1720 |
+
"prop-types": "^15.8.1"
|
| 1721 |
+
},
|
| 1722 |
+
"engines": {
|
| 1723 |
+
"node": ">= 10.13"
|
| 1724 |
+
},
|
| 1725 |
+
"peerDependencies": {
|
| 1726 |
+
"react": ">= 16.8 || 18.0.0"
|
| 1727 |
+
}
|
| 1728 |
+
},
|
| 1729 |
+
"node_modules/react-is": {
|
| 1730 |
+
"version": "16.13.1",
|
| 1731 |
+
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
| 1732 |
+
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
| 1733 |
+
},
|
| 1734 |
+
"node_modules/react-refresh": {
|
| 1735 |
+
"version": "0.17.0",
|
| 1736 |
+
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
| 1737 |
+
"integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
|
| 1738 |
+
"dev": true,
|
| 1739 |
+
"engines": {
|
| 1740 |
+
"node": ">=0.10.0"
|
| 1741 |
+
}
|
| 1742 |
+
},
|
| 1743 |
+
"node_modules/rollup": {
|
| 1744 |
+
"version": "4.60.4",
|
| 1745 |
+
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz",
|
| 1746 |
+
"integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==",
|
| 1747 |
+
"dev": true,
|
| 1748 |
+
"dependencies": {
|
| 1749 |
+
"@types/estree": "1.0.8"
|
| 1750 |
+
},
|
| 1751 |
+
"bin": {
|
| 1752 |
+
"rollup": "dist/bin/rollup"
|
| 1753 |
+
},
|
| 1754 |
+
"engines": {
|
| 1755 |
+
"node": ">=18.0.0",
|
| 1756 |
+
"npm": ">=8.0.0"
|
| 1757 |
+
},
|
| 1758 |
+
"optionalDependencies": {
|
| 1759 |
+
"@rollup/rollup-android-arm-eabi": "4.60.4",
|
| 1760 |
+
"@rollup/rollup-android-arm64": "4.60.4",
|
| 1761 |
+
"@rollup/rollup-darwin-arm64": "4.60.4",
|
| 1762 |
+
"@rollup/rollup-darwin-x64": "4.60.4",
|
| 1763 |
+
"@rollup/rollup-freebsd-arm64": "4.60.4",
|
| 1764 |
+
"@rollup/rollup-freebsd-x64": "4.60.4",
|
| 1765 |
+
"@rollup/rollup-linux-arm-gnueabihf": "4.60.4",
|
| 1766 |
+
"@rollup/rollup-linux-arm-musleabihf": "4.60.4",
|
| 1767 |
+
"@rollup/rollup-linux-arm64-gnu": "4.60.4",
|
| 1768 |
+
"@rollup/rollup-linux-arm64-musl": "4.60.4",
|
| 1769 |
+
"@rollup/rollup-linux-loong64-gnu": "4.60.4",
|
| 1770 |
+
"@rollup/rollup-linux-loong64-musl": "4.60.4",
|
| 1771 |
+
"@rollup/rollup-linux-ppc64-gnu": "4.60.4",
|
| 1772 |
+
"@rollup/rollup-linux-ppc64-musl": "4.60.4",
|
| 1773 |
+
"@rollup/rollup-linux-riscv64-gnu": "4.60.4",
|
| 1774 |
+
"@rollup/rollup-linux-riscv64-musl": "4.60.4",
|
| 1775 |
+
"@rollup/rollup-linux-s390x-gnu": "4.60.4",
|
| 1776 |
+
"@rollup/rollup-linux-x64-gnu": "4.60.4",
|
| 1777 |
+
"@rollup/rollup-linux-x64-musl": "4.60.4",
|
| 1778 |
+
"@rollup/rollup-openbsd-x64": "4.60.4",
|
| 1779 |
+
"@rollup/rollup-openharmony-arm64": "4.60.4",
|
| 1780 |
+
"@rollup/rollup-win32-arm64-msvc": "4.60.4",
|
| 1781 |
+
"@rollup/rollup-win32-ia32-msvc": "4.60.4",
|
| 1782 |
+
"@rollup/rollup-win32-x64-gnu": "4.60.4",
|
| 1783 |
+
"@rollup/rollup-win32-x64-msvc": "4.60.4",
|
| 1784 |
+
"fsevents": "~2.3.2"
|
| 1785 |
+
}
|
| 1786 |
+
},
|
| 1787 |
+
"node_modules/scheduler": {
|
| 1788 |
+
"version": "0.23.2",
|
| 1789 |
+
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
| 1790 |
+
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
| 1791 |
+
"dependencies": {
|
| 1792 |
+
"loose-envify": "^1.1.0"
|
| 1793 |
+
}
|
| 1794 |
+
},
|
| 1795 |
+
"node_modules/semver": {
|
| 1796 |
+
"version": "6.3.1",
|
| 1797 |
+
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
| 1798 |
+
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
| 1799 |
+
"dev": true,
|
| 1800 |
+
"bin": {
|
| 1801 |
+
"semver": "bin/semver.js"
|
| 1802 |
+
}
|
| 1803 |
+
},
|
| 1804 |
+
"node_modules/source-map-js": {
|
| 1805 |
+
"version": "1.2.1",
|
| 1806 |
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
| 1807 |
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
| 1808 |
+
"dev": true,
|
| 1809 |
+
"engines": {
|
| 1810 |
+
"node": ">=0.10.0"
|
| 1811 |
+
}
|
| 1812 |
+
},
|
| 1813 |
+
"node_modules/tslib": {
|
| 1814 |
+
"version": "2.8.1",
|
| 1815 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 1816 |
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
| 1817 |
+
},
|
| 1818 |
+
"node_modules/update-browserslist-db": {
|
| 1819 |
+
"version": "1.2.3",
|
| 1820 |
+
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
| 1821 |
+
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
|
| 1822 |
+
"dev": true,
|
| 1823 |
+
"funding": [
|
| 1824 |
+
{
|
| 1825 |
+
"type": "opencollective",
|
| 1826 |
+
"url": "https://opencollective.com/browserslist"
|
| 1827 |
+
},
|
| 1828 |
+
{
|
| 1829 |
+
"type": "tidelift",
|
| 1830 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 1831 |
+
},
|
| 1832 |
+
{
|
| 1833 |
+
"type": "github",
|
| 1834 |
+
"url": "https://github.com/sponsors/ai"
|
| 1835 |
+
}
|
| 1836 |
+
],
|
| 1837 |
+
"dependencies": {
|
| 1838 |
+
"escalade": "^3.2.0",
|
| 1839 |
+
"picocolors": "^1.1.1"
|
| 1840 |
+
},
|
| 1841 |
+
"bin": {
|
| 1842 |
+
"update-browserslist-db": "cli.js"
|
| 1843 |
+
},
|
| 1844 |
+
"peerDependencies": {
|
| 1845 |
+
"browserslist": ">= 4.21.0"
|
| 1846 |
+
}
|
| 1847 |
+
},
|
| 1848 |
+
"node_modules/vite": {
|
| 1849 |
+
"version": "5.4.21",
|
| 1850 |
+
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
| 1851 |
+
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
| 1852 |
+
"dev": true,
|
| 1853 |
+
"dependencies": {
|
| 1854 |
+
"esbuild": "^0.21.3",
|
| 1855 |
+
"postcss": "^8.4.43",
|
| 1856 |
+
"rollup": "^4.20.0"
|
| 1857 |
+
},
|
| 1858 |
+
"bin": {
|
| 1859 |
+
"vite": "bin/vite.js"
|
| 1860 |
+
},
|
| 1861 |
+
"engines": {
|
| 1862 |
+
"node": "^18.0.0 || >=20.0.0"
|
| 1863 |
+
},
|
| 1864 |
+
"funding": {
|
| 1865 |
+
"url": "https://github.com/vitejs/vite?sponsor=1"
|
| 1866 |
+
},
|
| 1867 |
+
"optionalDependencies": {
|
| 1868 |
+
"fsevents": "~2.3.3"
|
| 1869 |
+
},
|
| 1870 |
+
"peerDependencies": {
|
| 1871 |
+
"@types/node": "^18.0.0 || >=20.0.0",
|
| 1872 |
+
"less": "*",
|
| 1873 |
+
"lightningcss": "^1.21.0",
|
| 1874 |
+
"sass": "*",
|
| 1875 |
+
"sass-embedded": "*",
|
| 1876 |
+
"stylus": "*",
|
| 1877 |
+
"sugarss": "*",
|
| 1878 |
+
"terser": "^5.4.0"
|
| 1879 |
+
},
|
| 1880 |
+
"peerDependenciesMeta": {
|
| 1881 |
+
"@types/node": {
|
| 1882 |
+
"optional": true
|
| 1883 |
+
},
|
| 1884 |
+
"less": {
|
| 1885 |
+
"optional": true
|
| 1886 |
+
},
|
| 1887 |
+
"lightningcss": {
|
| 1888 |
+
"optional": true
|
| 1889 |
+
},
|
| 1890 |
+
"sass": {
|
| 1891 |
+
"optional": true
|
| 1892 |
+
},
|
| 1893 |
+
"sass-embedded": {
|
| 1894 |
+
"optional": true
|
| 1895 |
+
},
|
| 1896 |
+
"stylus": {
|
| 1897 |
+
"optional": true
|
| 1898 |
+
},
|
| 1899 |
+
"sugarss": {
|
| 1900 |
+
"optional": true
|
| 1901 |
+
},
|
| 1902 |
+
"terser": {
|
| 1903 |
+
"optional": true
|
| 1904 |
+
}
|
| 1905 |
+
}
|
| 1906 |
+
},
|
| 1907 |
+
"node_modules/yallist": {
|
| 1908 |
+
"version": "3.1.1",
|
| 1909 |
+
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
| 1910 |
+
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
| 1911 |
+
"dev": true
|
| 1912 |
+
}
|
| 1913 |
+
}
|
| 1914 |
+
}
|
frontend/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "pilotmaster-frontend",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"type": "module",
|
| 5 |
+
"scripts": {
|
| 6 |
+
"dev": "vite",
|
| 7 |
+
"build": "vite build",
|
| 8 |
+
"preview": "vite preview"
|
| 9 |
+
},
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"axios": "^1.6.0",
|
| 12 |
+
"react": "^18.2.0",
|
| 13 |
+
"react-dom": "^18.2.0",
|
| 14 |
+
"react-dropzone": "^14.3.8"
|
| 15 |
+
},
|
| 16 |
+
"devDependencies": {
|
| 17 |
+
"@vitejs/plugin-react": "^4.2.0",
|
| 18 |
+
"vite": "^5.0.0"
|
| 19 |
+
}
|
| 20 |
+
}
|
frontend/src/App.jsx
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useEffect, useState } from "react";
|
| 2 |
+
import { apiRequest, loginRequest } from "./docpilot/api.js";
|
| 3 |
+
import DocPilotDashboard from "./docpilot/pages/Dashboard.jsx";
|
| 4 |
+
import TraceExplorer from "./tracepilot/TraceExplorer.jsx";
|
| 5 |
+
|
| 6 |
+
export default function App() {
|
| 7 |
+
const [auth, setAuth] = useState(false);
|
| 8 |
+
const [loading, setLoading] = useState(true);
|
| 9 |
+
const [screen, setScreen] = useState("login");
|
| 10 |
+
const [username, setUsername] = useState("");
|
| 11 |
+
const [plan, setPlan] = useState("free");
|
| 12 |
+
|
| 13 |
+
useEffect(() => {
|
| 14 |
+
const validate = async () => {
|
| 15 |
+
const token = localStorage.getItem("token");
|
| 16 |
+
if (!token) { setLoading(false); return; }
|
| 17 |
+
try {
|
| 18 |
+
const data = await apiRequest("/auth/me");
|
| 19 |
+
if (data.email) {
|
| 20 |
+
setAuth(true);
|
| 21 |
+
setUsername(data.username);
|
| 22 |
+
setPlan(data.plan);
|
| 23 |
+
setScreen("home");
|
| 24 |
+
} else {
|
| 25 |
+
localStorage.removeItem("token");
|
| 26 |
+
}
|
| 27 |
+
} catch {
|
| 28 |
+
localStorage.removeItem("token");
|
| 29 |
+
}
|
| 30 |
+
setLoading(false);
|
| 31 |
+
};
|
| 32 |
+
validate();
|
| 33 |
+
}, []); // runs once only on mount
|
| 34 |
+
|
| 35 |
+
const logout = () => {
|
| 36 |
+
localStorage.removeItem("token");
|
| 37 |
+
setAuth(false);
|
| 38 |
+
setScreen("login");
|
| 39 |
+
setUsername("");
|
| 40 |
+
};
|
| 41 |
+
|
| 42 |
+
const onLogin = async () => {
|
| 43 |
+
const data = await apiRequest("/auth/me");
|
| 44 |
+
setUsername(data.username);
|
| 45 |
+
setPlan(data.plan);
|
| 46 |
+
setAuth(true);
|
| 47 |
+
setScreen("home");
|
| 48 |
+
};
|
| 49 |
+
|
| 50 |
+
if (loading) return <Splash />;
|
| 51 |
+
|
| 52 |
+
if (!auth) {
|
| 53 |
+
if (screen === "signup") return <Signup goToLogin={() => setScreen("login")} />;
|
| 54 |
+
if (screen === "forgot") return <ForgotPassword goBack={() => setScreen("login")} />;
|
| 55 |
+
return <Login onLogin={onLogin} goToSignup={() => setScreen("signup")} goToForgot={() => setScreen("forgot")} />;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
if (screen === "docpilot") return (
|
| 59 |
+
<DocPilotDashboard
|
| 60 |
+
onLogout={logout}
|
| 61 |
+
onHome={() => setScreen("home")}
|
| 62 |
+
onTracePilot={() => setScreen("tracepilot")}
|
| 63 |
+
/>
|
| 64 |
+
);
|
| 65 |
+
|
| 66 |
+
if (screen === "tracepilot") return (
|
| 67 |
+
<TraceExplorer
|
| 68 |
+
onHome={() => setScreen("home")}
|
| 69 |
+
onDocPilot={() => setScreen("docpilot")}
|
| 70 |
+
/>
|
| 71 |
+
);
|
| 72 |
+
|
| 73 |
+
return <PilotMasterHome username={username} plan={plan} onOpen={setScreen} onLogout={logout} />;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
// ─── HOME ────────────────────────────────────────────────────────────────────
|
| 77 |
+
|
| 78 |
+
function PilotMasterHome({ username, plan, onOpen, onLogout }) {
|
| 79 |
+
const [currentPlan, setCurrentPlan] = useState(plan);
|
| 80 |
+
|
| 81 |
+
const upgradePlan = async () => {
|
| 82 |
+
try {
|
| 83 |
+
const data = await apiRequest("/billing/upgrade", "POST");
|
| 84 |
+
setCurrentPlan(data.plan);
|
| 85 |
+
} catch { alert("Upgrade failed"); }
|
| 86 |
+
};
|
| 87 |
+
|
| 88 |
+
const downgradePlan = async () => {
|
| 89 |
+
try {
|
| 90 |
+
const data = await apiRequest("/billing/downgrade", "POST");
|
| 91 |
+
setCurrentPlan(data.plan);
|
| 92 |
+
} catch { alert("Downgrade failed"); }
|
| 93 |
+
};
|
| 94 |
+
return (
|
| 95 |
+
<div style={{
|
| 96 |
+
background: "#0d0d0d", color: "white", fontFamily: "Arial",
|
| 97 |
+
width: "100vw", height: "100vh", boxSizing: "border-box",
|
| 98 |
+
display: "grid", gridTemplateRows: "auto 1fr auto", overflow: "hidden",
|
| 99 |
+
}}>
|
| 100 |
+
{/* TOP BAR */}
|
| 101 |
+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "22px 48px", borderBottom: "1px solid #1a1a1a" }}>
|
| 102 |
+
<div>
|
| 103 |
+
<h1 style={{ margin: 0, fontSize: "34px", fontFamily: "Georgia, serif", fontWeight: "600", letterSpacing: "-1.5px", color: "white" }}>PilotMaster</h1>
|
| 104 |
+
<p style={{ margin: "3px 0 0", fontSize: "12px", color: "#3a3a3a", letterSpacing: "0.05em" }}>observable AI execution ecosystem</p>
|
| 105 |
+
</div>
|
| 106 |
+
<div style={{ display: "flex", alignItems: "center", gap: "20px" }}>
|
| 107 |
+
<div style={{ textAlign: "right" }}>
|
| 108 |
+
<p style={{ margin: 0, fontSize: "14px", color: "#aaa" }}>{username}</p>
|
| 109 |
+
<p style={{ margin: "2px 0 0", fontSize: "11px", color: "#444", textTransform: "uppercase", letterSpacing: "0.08em" }}>{currentPlan}</p>
|
| 110 |
+
</div>
|
| 111 |
+
{currentPlan === "free" ? (
|
| 112 |
+
<button onClick={upgradePlan} style={{ ...btnStyle, color: "#4caf50", borderColor: "#4caf5030" }}>Upgrade to Pro</button>
|
| 113 |
+
) : (
|
| 114 |
+
<button onClick={downgradePlan} style={{ ...btnStyle, color: "#888" }}>Downgrade</button>
|
| 115 |
+
)}
|
| 116 |
+
<button onClick={onLogout} style={btnStyle}>Logout</button>
|
| 117 |
+
</div>
|
| 118 |
+
</div>
|
| 119 |
+
|
| 120 |
+
{/* CENTER */}
|
| 121 |
+
<div style={{ display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center", gap: "28px" }}>
|
| 122 |
+
<p style={{ margin: 0, fontSize: "11px", color: "#333", letterSpacing: "0.12em", textTransform: "uppercase" }}>select a workspace</p>
|
| 123 |
+
<div style={{ display: "flex", gap: "20px" }}>
|
| 124 |
+
<ProductCard
|
| 125 |
+
name="DocPilot"
|
| 126 |
+
description="Upload documents. Chat with them. Manage your knowledge base."
|
| 127 |
+
tags={["RAG", "Chat", "Documents", "Auth"]}
|
| 128 |
+
onClick={() => onOpen("docpilot")}
|
| 129 |
+
accent="#4caf50"
|
| 130 |
+
/>
|
| 131 |
+
<ProductCard
|
| 132 |
+
name="TracePilot"
|
| 133 |
+
description="Observe every execution. Inspect traces, chunks, evaluation scores and spans."
|
| 134 |
+
tags={["Traces", "Evaluation", "Observability", "Replay"]}
|
| 135 |
+
onClick={() => onOpen("tracepilot")}
|
| 136 |
+
accent="#7c4dff"
|
| 137 |
+
/>
|
| 138 |
+
</div>
|
| 139 |
+
</div>
|
| 140 |
+
|
| 141 |
+
{/* FOOTER */}
|
| 142 |
+
<div style={{ padding: "14px 48px", borderTop: "1px solid #161616", display: "flex", justifyContent: "space-between" }}>
|
| 143 |
+
<p style={{ margin: 0, fontSize: "11px", color: "#222" }}>PilotMaster · execution kernel: PilotCore</p>
|
| 144 |
+
<p style={{ margin: 0, fontSize: "11px", color: "#222" }}>llama-3.1-8b-instant · all-mpnet-base-v2</p>
|
| 145 |
+
</div>
|
| 146 |
+
</div>
|
| 147 |
+
);
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
function ProductCard({ name, description, tags, onClick, accent }) {
|
| 151 |
+
const [hovered, setHovered] = useState(false);
|
| 152 |
+
return (
|
| 153 |
+
<div
|
| 154 |
+
onClick={onClick}
|
| 155 |
+
onMouseEnter={() => setHovered(true)}
|
| 156 |
+
onMouseLeave={() => setHovered(false)}
|
| 157 |
+
style={{
|
| 158 |
+
width: "320px", padding: "28px", borderRadius: "14px", cursor: "pointer",
|
| 159 |
+
background: hovered ? "#141414" : "#111",
|
| 160 |
+
border: `1px solid ${hovered ? "#2a2a2a" : "#1a1a1a"}`,
|
| 161 |
+
transition: "all 0.15s ease",
|
| 162 |
+
display: "flex", flexDirection: "column", gap: "14px",
|
| 163 |
+
boxSizing: "border-box",
|
| 164 |
+
}}
|
| 165 |
+
>
|
| 166 |
+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
| 167 |
+
<h2 style={{ margin: 0, fontSize: "26px", fontFamily: "Georgia, serif", fontWeight: "600", letterSpacing: "-1px", color: "white" }}>{name}</h2>
|
| 168 |
+
<span style={{ fontSize: "18px", color: hovered ? accent : "#2a2a2a", transition: "color 0.15s" }}>→</span>
|
| 169 |
+
</div>
|
| 170 |
+
<p style={{ margin: 0, fontSize: "13px", color: "#555", lineHeight: 1.6 }}>{description}</p>
|
| 171 |
+
<div style={{ display: "flex", gap: "6px", flexWrap: "wrap" }}>
|
| 172 |
+
{tags.map(tag => (
|
| 173 |
+
<span key={tag} style={{
|
| 174 |
+
fontSize: "10px", padding: "3px 8px", borderRadius: "4px",
|
| 175 |
+
background: accent + "15", color: accent, border: `1px solid ${accent}25`,
|
| 176 |
+
letterSpacing: "0.05em", textTransform: "uppercase",
|
| 177 |
+
}}>{tag}</span>
|
| 178 |
+
))}
|
| 179 |
+
</div>
|
| 180 |
+
</div>
|
| 181 |
+
);
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
// ─── AUTH ────────────────────────────────────────────────────────────────────
|
| 185 |
+
|
| 186 |
+
function Login({ onLogin, goToSignup, goToForgot }) {
|
| 187 |
+
const [email, setEmail] = useState("");
|
| 188 |
+
const [password, setPassword] = useState("");
|
| 189 |
+
|
| 190 |
+
const login = async () => {
|
| 191 |
+
try {
|
| 192 |
+
const data = await loginRequest(email, password);
|
| 193 |
+
if (!data.access_token) { alert("Invalid credentials"); return; }
|
| 194 |
+
localStorage.setItem("token", data.access_token);
|
| 195 |
+
onLogin();
|
| 196 |
+
} catch { alert("Wrong email or password"); }
|
| 197 |
+
};
|
| 198 |
+
|
| 199 |
+
return (
|
| 200 |
+
<AuthShell>
|
| 201 |
+
<h1 style={authTitleStyle}>PilotMaster</h1>
|
| 202 |
+
<p style={{ margin: "0 0 36px", color: "#3a3a3a", fontSize: "14px", textAlign: "center" }}>observable AI execution ecosystem</p>
|
| 203 |
+
<input placeholder="Email" value={email} onChange={e => setEmail(e.target.value)} style={inputStyle} />
|
| 204 |
+
<input type="password" placeholder="Password" value={password} onChange={e => setPassword(e.target.value)}
|
| 205 |
+
onKeyDown={e => e.key === "Enter" && login()} style={inputStyle} />
|
| 206 |
+
<button onClick={login} style={primaryBtnStyle}>Login</button>
|
| 207 |
+
<p onClick={goToSignup} style={linkStyle}>Don't have an account? Sign up</p>
|
| 208 |
+
<p onClick={goToForgot} style={{ ...linkStyle, color: "#3a3a3a", marginTop: "10px" }}>Forgot password?</p>
|
| 209 |
+
</AuthShell>
|
| 210 |
+
);
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
function Signup({ goToLogin }) {
|
| 214 |
+
const [username, setUsername] = useState("");
|
| 215 |
+
const [email, setEmail] = useState("");
|
| 216 |
+
const [password, setPassword] = useState("");
|
| 217 |
+
|
| 218 |
+
const signup = async () => {
|
| 219 |
+
try {
|
| 220 |
+
await apiRequest("/auth/signup", "POST", { username, email, password });
|
| 221 |
+
alert("Account created. Please login.");
|
| 222 |
+
goToLogin();
|
| 223 |
+
} catch { alert("Signup failed"); }
|
| 224 |
+
};
|
| 225 |
+
|
| 226 |
+
return (
|
| 227 |
+
<AuthShell>
|
| 228 |
+
<h1 style={authTitleStyle}>PilotMaster</h1>
|
| 229 |
+
<p style={{ margin: "0 0 36px", color: "#3a3a3a", fontSize: "14px", textAlign: "center" }}>create your account</p>
|
| 230 |
+
<input placeholder="Username" value={username} onChange={e => setUsername(e.target.value)} style={inputStyle} />
|
| 231 |
+
<input placeholder="Email" value={email} onChange={e => setEmail(e.target.value)} style={inputStyle} />
|
| 232 |
+
<input type="password" placeholder="Password" value={password} onChange={e => setPassword(e.target.value)} style={inputStyle} />
|
| 233 |
+
<button onClick={signup} style={primaryBtnStyle}>Sign Up</button>
|
| 234 |
+
<p onClick={goToLogin} style={linkStyle}>Already have an account? Login</p>
|
| 235 |
+
</AuthShell>
|
| 236 |
+
);
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
function ForgotPassword({ goBack }) {
|
| 240 |
+
const [email, setEmail] = useState("");
|
| 241 |
+
const [token, setToken] = useState("");
|
| 242 |
+
const [newPassword, setNewPassword] = useState("");
|
| 243 |
+
const [generatedToken, setGeneratedToken] = useState("");
|
| 244 |
+
|
| 245 |
+
return (
|
| 246 |
+
<AuthShell>
|
| 247 |
+
<h1 style={{ ...authTitleStyle, fontSize: "42px", marginBottom: "32px" }}>Reset Password</h1>
|
| 248 |
+
<input placeholder="Email" value={email} onChange={e => setEmail(e.target.value)} style={inputStyle} />
|
| 249 |
+
<button onClick={async () => {
|
| 250 |
+
try { const d = await apiRequest("/auth/forgot-password", "POST", { email }); setGeneratedToken(d.reset_token); }
|
| 251 |
+
catch { alert("Email not found"); }
|
| 252 |
+
}} style={primaryBtnStyle}>Generate Reset Token</button>
|
| 253 |
+
{generatedToken && (
|
| 254 |
+
<div style={{ background: "#141414", border: "1px solid #222", borderRadius: "10px", padding: "12px", fontSize: "12px", color: "#666", wordBreak: "break-all", marginBottom: "14px" }}>
|
| 255 |
+
Token: {generatedToken}
|
| 256 |
+
</div>
|
| 257 |
+
)}
|
| 258 |
+
<input placeholder="Paste Token" value={token} onChange={e => setToken(e.target.value)} style={inputStyle} />
|
| 259 |
+
<input type="password" placeholder="New Password" value={newPassword} onChange={e => setNewPassword(e.target.value)} style={inputStyle} />
|
| 260 |
+
<button onClick={async () => {
|
| 261 |
+
try { await apiRequest("/auth/reset-password", "POST", { token, new_password: newPassword }); alert("Password reset"); goBack(); }
|
| 262 |
+
catch { alert("Invalid token"); }
|
| 263 |
+
}} style={primaryBtnStyle}>Reset Password</button>
|
| 264 |
+
<p onClick={goBack} style={linkStyle}>Back to Login</p>
|
| 265 |
+
</AuthShell>
|
| 266 |
+
);
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
function AuthShell({ children }) {
|
| 270 |
+
return (
|
| 271 |
+
<div style={{ background: "#0d0d0d", width: "100vw", height: "100vh", display: "flex", justifyContent: "center", alignItems: "center", fontFamily: "Arial", boxSizing: "border-box", overflow: "hidden" }}>
|
| 272 |
+
<div style={{ width: "500px", display: "flex", flexDirection: "column" }}>
|
| 273 |
+
{children}
|
| 274 |
+
</div>
|
| 275 |
+
</div>
|
| 276 |
+
);
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
function Splash() {
|
| 280 |
+
return (
|
| 281 |
+
<div style={{ background: "#0d0d0d", color: "#2a2a2a", width: "100vw", height: "100vh", display: "flex", justifyContent: "center", alignItems: "center", fontFamily: "Arial", fontSize: "14px" }}>
|
| 282 |
+
Loading...
|
| 283 |
+
</div>
|
| 284 |
+
);
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
// ─── STYLES ──────────────────────────────────────────────────────────────────
|
| 288 |
+
|
| 289 |
+
const inputStyle = {
|
| 290 |
+
width: "100%", padding: "20px 22px", marginBottom: "16px", borderRadius: "14px",
|
| 291 |
+
border: "1px solid #1e1e1e", background: "#141414", color: "white",
|
| 292 |
+
fontSize: "17px", outline: "none", boxSizing: "border-box",
|
| 293 |
+
};
|
| 294 |
+
|
| 295 |
+
const primaryBtnStyle = {
|
| 296 |
+
width: "100%", padding: "20px", borderRadius: "14px", border: "1px solid #2a2a2a",
|
| 297 |
+
background: "#1a1a1a", color: "white", fontSize: "17px", cursor: "pointer",
|
| 298 |
+
fontWeight: "600", marginBottom: "8px", boxSizing: "border-box",
|
| 299 |
+
};
|
| 300 |
+
|
| 301 |
+
const authTitleStyle = {
|
| 302 |
+
margin: "0 0 8px", fontSize: "64px", fontFamily: "Georgia, serif",
|
| 303 |
+
fontWeight: "600", letterSpacing: "-3px", color: "white", textAlign: "center", lineHeight: 1,
|
| 304 |
+
};
|
| 305 |
+
|
| 306 |
+
const linkStyle = {
|
| 307 |
+
margin: "14px 0 0", color: "#555", textAlign: "center", cursor: "pointer", fontSize: "15px",
|
| 308 |
+
};
|
| 309 |
+
|
| 310 |
+
const btnStyle = {
|
| 311 |
+
padding: "10px 20px", background: "#141414", color: "#888",
|
| 312 |
+
border: "1px solid #222", borderRadius: "10px", cursor: "pointer", fontSize: "13px",
|
| 313 |
+
};
|
frontend/src/docpilot/App.jsx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Auth is handled at PilotMaster root (App.jsx)
|
| 2 |
+
// This file kept for compatibility but is no longer the entry point
|
| 3 |
+
export { default } from "./pages/Dashboard.jsx";
|
frontend/src/docpilot/api.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const API_BASE = "http://127.0.0.1:8000/docpilot";
|
| 2 |
+
|
| 3 |
+
export const apiRequest = async (endpoint, method = "GET", body = null) => {
|
| 4 |
+
const token = localStorage.getItem("token");
|
| 5 |
+
const headers = {};
|
| 6 |
+
|
| 7 |
+
if (!(body instanceof FormData)) {
|
| 8 |
+
headers["Content-Type"] = "application/json";
|
| 9 |
+
}
|
| 10 |
+
if (token) {
|
| 11 |
+
headers["Authorization"] = `Bearer ${token}`;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
const response = await fetch(API_BASE + endpoint, {
|
| 15 |
+
method,
|
| 16 |
+
headers,
|
| 17 |
+
body: body instanceof FormData ? body : body ? JSON.stringify(body) : null,
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
return response.json();
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
export const loginRequest = async (email, password) => {
|
| 24 |
+
const formData = new URLSearchParams();
|
| 25 |
+
formData.append("username", email);
|
| 26 |
+
formData.append("password", password);
|
| 27 |
+
|
| 28 |
+
const response = await fetch(API_BASE + "/auth/login", {
|
| 29 |
+
method: "POST",
|
| 30 |
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
| 31 |
+
body: formData,
|
| 32 |
+
});
|
| 33 |
+
|
| 34 |
+
return response.json();
|
| 35 |
+
};
|
frontend/src/docpilot/pages/Dashboard.jsx
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState, useEffect, useRef } from "react";
|
| 2 |
+
import { apiRequest } from "../api";
|
| 3 |
+
import { useDropzone } from "react-dropzone";
|
| 4 |
+
|
| 5 |
+
function Dashboard({ onLogout, onHome, onTracePilot }) {
|
| 6 |
+
const [file, setFile] = useState(null);
|
| 7 |
+
const [question, setQuestion] = useState("");
|
| 8 |
+
const [source, setSource] = useState("");
|
| 9 |
+
const [messages, setMessages] = useState([]);
|
| 10 |
+
const [sessions, setSessions] = useState([]);
|
| 11 |
+
const [currentSessionId, setCurrentSessionId] = useState(null);
|
| 12 |
+
const [username, setUsername] = useState("");
|
| 13 |
+
const [uploading, setUploading] = useState(false);
|
| 14 |
+
const [asking, setAsking] = useState(false);
|
| 15 |
+
const messagesEndRef = useRef(null);
|
| 16 |
+
|
| 17 |
+
useEffect(() => {
|
| 18 |
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
| 19 |
+
}, [messages]);
|
| 20 |
+
|
| 21 |
+
useEffect(() => {
|
| 22 |
+
apiRequest("/history/sessions").then(setSessions).catch(() => {});
|
| 23 |
+
apiRequest("/billing/me").then(d => setUsername(d.username)).catch(() => {});
|
| 24 |
+
}, []);
|
| 25 |
+
|
| 26 |
+
const fetchSessions = () =>
|
| 27 |
+
apiRequest("/history/sessions").then(setSessions).catch(() => {});
|
| 28 |
+
|
| 29 |
+
const loadSession = async (sessionId) => {
|
| 30 |
+
const data = await apiRequest(`/history/${sessionId}`);
|
| 31 |
+
// Align keys directly with your backend metrics payload structure
|
| 32 |
+
setMessages(data.map(m => ({
|
| 33 |
+
role: m.role,
|
| 34 |
+
content: m.content,
|
| 35 |
+
sources: m.sources,
|
| 36 |
+
timestamp: m.timestamp || m.created_at || new Date().toISOString()
|
| 37 |
+
})));
|
| 38 |
+
setCurrentSessionId(sessionId);
|
| 39 |
+
};
|
| 40 |
+
|
| 41 |
+
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
| 42 |
+
onDrop: (files) => files.length > 0 && setFile(files[0]),
|
| 43 |
+
accept: {
|
| 44 |
+
"application/pdf": [".pdf"],
|
| 45 |
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": [".docx"],
|
| 46 |
+
"text/plain": [".txt"],
|
| 47 |
+
"text/markdown": [".md"],
|
| 48 |
+
"text/csv": [".csv"],
|
| 49 |
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"],
|
| 50 |
+
"image/png": [".png"],
|
| 51 |
+
"image/jpeg": [".jpg", ".jpeg"],
|
| 52 |
+
},
|
| 53 |
+
});
|
| 54 |
+
|
| 55 |
+
const uploadFile = async () => {
|
| 56 |
+
if (!file) return;
|
| 57 |
+
setUploading(true);
|
| 58 |
+
const formData = new FormData();
|
| 59 |
+
formData.append("file", file);
|
| 60 |
+
try {
|
| 61 |
+
const data = await apiRequest("/docs/upload", "POST", formData);
|
| 62 |
+
if (data.detail) { alert(data.detail); }
|
| 63 |
+
else { alert(data.message); setSource(file.name); }
|
| 64 |
+
} catch { alert("Upload failed"); }
|
| 65 |
+
setUploading(false);
|
| 66 |
+
};
|
| 67 |
+
|
| 68 |
+
const askQuestion = async () => {
|
| 69 |
+
if (!question) return;
|
| 70 |
+
const q = question;
|
| 71 |
+
setQuestion("");
|
| 72 |
+
setMessages(prev => [
|
| 73 |
+
...prev,
|
| 74 |
+
{ role: "user", content: q, timestamp: new Date().toISOString() },
|
| 75 |
+
{ role: "assistant", content: "Thinking...", loading: true, timestamp: new Date().toISOString() }
|
| 76 |
+
]);
|
| 77 |
+
setAsking(true);
|
| 78 |
+
try {
|
| 79 |
+
const data = await apiRequest("/chat/ask", "POST", { question: q, source, session_id: currentSessionId });
|
| 80 |
+
if (data.session_id) { setCurrentSessionId(data.session_id); fetchSessions(); }
|
| 81 |
+
setMessages(prev => {
|
| 82 |
+
const u = [...prev];
|
| 83 |
+
u[u.length - 1] = {
|
| 84 |
+
role: "assistant",
|
| 85 |
+
content: data.answer,
|
| 86 |
+
sources: data.sources,
|
| 87 |
+
timestamp: new Date().toISOString()
|
| 88 |
+
};
|
| 89 |
+
return u;
|
| 90 |
+
});
|
| 91 |
+
} catch {
|
| 92 |
+
setMessages(prev => {
|
| 93 |
+
const u = [...prev];
|
| 94 |
+
u[u.length - 1] = {
|
| 95 |
+
role: "assistant",
|
| 96 |
+
content: "Something went wrong.",
|
| 97 |
+
timestamp: new Date().toISOString()
|
| 98 |
+
};
|
| 99 |
+
return u;
|
| 100 |
+
});
|
| 101 |
+
}
|
| 102 |
+
setAsking(false);
|
| 103 |
+
};
|
| 104 |
+
|
| 105 |
+
const resetMemory = async () => {
|
| 106 |
+
await apiRequest("/docs/reset", "DELETE");
|
| 107 |
+
setMessages([]); setQuestion(""); setSource(""); setFile(null); setCurrentSessionId(null);
|
| 108 |
+
};
|
| 109 |
+
|
| 110 |
+
return (
|
| 111 |
+
<div style={{ display: "flex", width: "100%", height: "100%", background: "#111", color: "white", fontFamily: "Arial", overflow: "hidden" }}>
|
| 112 |
+
|
| 113 |
+
{/* SIDEBAR */}
|
| 114 |
+
<div style={{ width: "280px", flexShrink: 0, background: "#0d0d0d", borderRight: "1px solid #1e1e1e", display: "flex", flexDirection: "column", overflow: "hidden" }}>
|
| 115 |
+
|
| 116 |
+
{/* SIDEBAR HEADER */}
|
| 117 |
+
<div style={{ padding: "24px 24px 16px", flexShrink: 0 }}>
|
| 118 |
+
<h1 style={{ margin: 0, fontSize: "44px", fontFamily: "Georgia, serif", fontWeight: "600", letterSpacing: "-2px", color: "white", lineHeight: 1 }}>DocPilot</h1>
|
| 119 |
+
<p style={{ margin: "8px 0 0", color: "#555", fontSize: "14px" }}>{username}</p>
|
| 120 |
+
</div>
|
| 121 |
+
|
| 122 |
+
{/* UPLOAD */}
|
| 123 |
+
<div style={{ padding: "0 16px 16px", flexShrink: 0, borderBottom: "1px solid #1a1a1a" }}>
|
| 124 |
+
<div {...getRootProps()} style={{
|
| 125 |
+
border: "1px dashed #2a2a2a", borderRadius: "12px", padding: "20px 16px",
|
| 126 |
+
textAlign: "center", background: isDragActive ? "#1a1a1a" : "#141414",
|
| 127 |
+
cursor: "pointer", color: "#555", fontSize: "13px", marginBottom: "10px",
|
| 128 |
+
}}>
|
| 129 |
+
<input {...getInputProps()} />
|
| 130 |
+
{isDragActive ? <p style={{ margin: 0 }}>Drop here...</p> : <p style={{ margin: 0 }}>Drag & drop or click to upload</p>}
|
| 131 |
+
{file && <p style={{ margin: "8px 0 0", color: "#888", fontSize: "12px" }}>{file.name}</p>}
|
| 132 |
+
</div>
|
| 133 |
+
<button onClick={uploadFile} disabled={uploading} style={{
|
| 134 |
+
width: "100%", padding: "11px", background: "#1a1a1a", color: uploading ? "#555" : "white",
|
| 135 |
+
border: "1px solid #2a2a2a", borderRadius: "10px", cursor: uploading ? "not-allowed" : "pointer", fontSize: "14px",
|
| 136 |
+
}}>
|
| 137 |
+
{uploading ? "Uploading..." : "Upload"}
|
| 138 |
+
</button>
|
| 139 |
+
</div>
|
| 140 |
+
|
| 141 |
+
{/* NEW CHAT */}
|
| 142 |
+
<div style={{ padding: "12px 16px", flexShrink: 0 }}>
|
| 143 |
+
<button onClick={() => { setMessages([]); setCurrentSessionId(null); }} style={{
|
| 144 |
+
width: "100%", padding: "12px", background: "#161616", color: "white",
|
| 145 |
+
border: "1px solid #222", borderRadius: "10px", cursor: "pointer", fontSize: "14px",
|
| 146 |
+
}}>+ New Chat</button>
|
| 147 |
+
</div>
|
| 148 |
+
|
| 149 |
+
{/* SESSIONS */}
|
| 150 |
+
<div style={{ flex: 1, overflowY: "auto", padding: "0 16px 16px" }}>
|
| 151 |
+
<p style={{ margin: "0 0 10px", fontSize: "11px", color: "#333", textTransform: "uppercase", letterSpacing: "0.08em" }}>Conversations</p>
|
| 152 |
+
{sessions.map(session => (
|
| 153 |
+
<div key={session.id} onClick={() => loadSession(session.id)} style={{
|
| 154 |
+
padding: "12px 14px", marginBottom: "6px", borderRadius: "10px",
|
| 155 |
+
background: currentSessionId === session.id ? "#1e1e1e" : "#141414",
|
| 156 |
+
border: "1px solid #1e1e1e", cursor: "pointer", fontSize: "13px",
|
| 157 |
+
display: "flex", justifyContent: "space-between", alignItems: "center",
|
| 158 |
+
}}>
|
| 159 |
+
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1, color: "#ccc" }}>
|
| 160 |
+
{session.title || `Chat #${session.id}`}
|
| 161 |
+
</span>
|
| 162 |
+
<button onClick={async (e) => {
|
| 163 |
+
e.stopPropagation();
|
| 164 |
+
await apiRequest(`/history/${session.id}`, "DELETE");
|
| 165 |
+
if (currentSessionId === session.id) { setMessages([]); setCurrentSessionId(null); }
|
| 166 |
+
fetchSessions();
|
| 167 |
+
}} style={{ background: "transparent", border: "none", color: "#444", cursor: "pointer", fontSize: "14px", marginLeft: "8px", flexShrink: 0 }}>✕</button>
|
| 168 |
+
</div>
|
| 169 |
+
))}
|
| 170 |
+
</div>
|
| 171 |
+
</div>
|
| 172 |
+
|
| 173 |
+
{/* MAIN */}
|
| 174 |
+
<div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }}>
|
| 175 |
+
|
| 176 |
+
{/* THIN HEADER */}
|
| 177 |
+
<div style={{ padding: "12px 24px", borderBottom: "1px solid #1e1e1e", flexShrink: 0, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
| 178 |
+
<p style={{ margin: 0, fontSize: "13px", color: "#555" }}>
|
| 179 |
+
Active Document: <span style={{ color: source ? "#aaa" : "#333" }}>{source || "None"}</span>
|
| 180 |
+
</p>
|
| 181 |
+
<div style={{ display: "flex", gap: "8px" }}>
|
| 182 |
+
{[
|
| 183 |
+
{ label: "← Home", onClick: onHome, color: "#aaa" },
|
| 184 |
+
{ label: "TracePilot →", onClick: onTracePilot, color: "#7c4dff" },
|
| 185 |
+
{ label: "Reset", onClick: resetMemory, color: "#aaa" },
|
| 186 |
+
{ label: "Logout", onClick: onLogout, color: "#aaa" },
|
| 187 |
+
].map(btn => (
|
| 188 |
+
<button key={btn.label} onClick={btn.onClick} style={{
|
| 189 |
+
padding: "8px 14px", background: "#161616", color: btn.color,
|
| 190 |
+
border: "1px solid #222", borderRadius: "8px", cursor: "pointer", fontSize: "13px",
|
| 191 |
+
}}>{btn.label}</button>
|
| 192 |
+
))}
|
| 193 |
+
</div>
|
| 194 |
+
</div>{/* CHAT AREA */}
|
| 195 |
+
<div style={{ flex: 1, overflowY: "auto", padding: "32px 60px" }}>
|
| 196 |
+
{messages.length === 0 && (
|
| 197 |
+
<p style={{ color: "#333", fontSize: "16px" }}>Ask questions about your document...</p>
|
| 198 |
+
)}
|
| 199 |
+
{messages
|
| 200 |
+
.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp))
|
| 201 |
+
.map((msg, i) => (
|
| 202 |
+
<div key={i} style={{ marginBottom: "28px", display: "flex", justifyContent: msg.role === "user" ? "flex-end" : "flex-start" }}>
|
| 203 |
+
{msg.role === "user" ? (
|
| 204 |
+
<div style={{ background: "#1e1e1e", padding: "14px 20px", borderRadius: "18px", maxWidth: "65%", fontSize: "16px", lineHeight: 1.6, color: "#eee" }}>
|
| 205 |
+
{msg.content}
|
| 206 |
+
</div>
|
| 207 |
+
) : (
|
| 208 |
+
<div style={{ maxWidth: "80%", fontSize: "16px", lineHeight: 1.8, color: "#ccc" }}>
|
| 209 |
+
|
| 210 |
+
{/* REMOVED CONTAINER BUBBLE STYLE HERE — JUST RAW TEXT CONTENT */}
|
| 211 |
+
<div>
|
| 212 |
+
{msg.content}
|
| 213 |
+
</div>
|
| 214 |
+
|
| 215 |
+
{/* Sources section remains nicely padded right beneath the unbubbled text */}
|
| 216 |
+
{msg.sources && msg.sources.length > 0 && (
|
| 217 |
+
<div style={{ marginTop: "16px", paddingLeft: "4px", fontSize: "12px", color: "#444" }}>
|
| 218 |
+
<p style={{ margin: "0 0 6px", textTransform: "uppercase", letterSpacing: "0.06em", fontSize: "10px", color: "#555", fontWeight: "bold" }}>Sources</p>
|
| 219 |
+
{msg.sources.map((s, idx) => (
|
| 220 |
+
<div key={idx} style={{ color: "#666", marginBottom: "2px" }}>
|
| 221 |
+
📁 <span style={{ color: "#888" }}>{s.source || s.file_name}</span> · <span style={{ fontStyle: "italic" }}>Page {s.page || s.page_number}</span>
|
| 222 |
+
</div>
|
| 223 |
+
))}
|
| 224 |
+
</div>
|
| 225 |
+
)}
|
| 226 |
+
</div>
|
| 227 |
+
)}
|
| 228 |
+
</div>
|
| 229 |
+
))}
|
| 230 |
+
<div ref={messagesEndRef} />
|
| 231 |
+
</div>
|
| 232 |
+
|
| 233 |
+
{/* INPUT */}
|
| 234 |
+
<div style={{ padding: "16px 24px", borderTop: "1px solid #1e1e1e", flexShrink: 0, background: "#111" }}>
|
| 235 |
+
<div style={{ display: "flex", gap: "12px" }}>
|
| 236 |
+
<input
|
| 237 |
+
type="text"
|
| 238 |
+
placeholder="Ask something..."
|
| 239 |
+
value={question}
|
| 240 |
+
onChange={e => setQuestion(e.target.value)}
|
| 241 |
+
onKeyDown={e => e.key === "Enter" && askQuestion()}
|
| 242 |
+
style={{
|
| 243 |
+
flex: 1, padding: "16px 20px", borderRadius: "14px",
|
| 244 |
+
border: "1px solid #222", background: "#161616", color: "white",
|
| 245 |
+
fontSize: "15px", outline: "none",
|
| 246 |
+
}}
|
| 247 |
+
/>
|
| 248 |
+
<button onClick={askQuestion} disabled={asking} style={{
|
| 249 |
+
padding: "16px 28px", borderRadius: "14px", border: "1px solid #222",
|
| 250 |
+
background: "#1e1e1e", color: asking ? "#555" : "white",
|
| 251 |
+
cursor: asking ? "not-allowed" : "pointer", fontSize: "15px",
|
| 252 |
+
}}>Send</button>
|
| 253 |
+
</div>
|
| 254 |
+
</div>
|
| 255 |
+
</div>
|
| 256 |
+
</div>
|
| 257 |
+
);
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
export default Dashboard;
|
frontend/src/docpilot/pages/ForgotPassword.jsx
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState } from "react";
|
| 2 |
+
|
| 3 |
+
import { apiRequest } from "../api";
|
| 4 |
+
|
| 5 |
+
function ForgotPassword({
|
| 6 |
+
goBack,
|
| 7 |
+
}) {
|
| 8 |
+
const [email,
|
| 9 |
+
setEmail] =
|
| 10 |
+
useState("");
|
| 11 |
+
|
| 12 |
+
const [token,
|
| 13 |
+
setToken] =
|
| 14 |
+
useState("");
|
| 15 |
+
|
| 16 |
+
const [newPassword,
|
| 17 |
+
setNewPassword] =
|
| 18 |
+
useState("");
|
| 19 |
+
|
| 20 |
+
const [generatedToken,
|
| 21 |
+
setGeneratedToken] =
|
| 22 |
+
useState("");
|
| 23 |
+
|
| 24 |
+
const requestReset =
|
| 25 |
+
async () => {
|
| 26 |
+
|
| 27 |
+
try {
|
| 28 |
+
|
| 29 |
+
const data =
|
| 30 |
+
await apiRequest(
|
| 31 |
+
"/auth/forgot-password",
|
| 32 |
+
"POST",
|
| 33 |
+
{
|
| 34 |
+
email,
|
| 35 |
+
}
|
| 36 |
+
);
|
| 37 |
+
|
| 38 |
+
setGeneratedToken(
|
| 39 |
+
data.reset_token
|
| 40 |
+
);
|
| 41 |
+
|
| 42 |
+
} catch (error) {
|
| 43 |
+
|
| 44 |
+
console.error(error);
|
| 45 |
+
|
| 46 |
+
alert(
|
| 47 |
+
"Email not found"
|
| 48 |
+
);
|
| 49 |
+
}
|
| 50 |
+
};
|
| 51 |
+
|
| 52 |
+
const resetPassword =
|
| 53 |
+
async () => {
|
| 54 |
+
|
| 55 |
+
try {
|
| 56 |
+
|
| 57 |
+
await apiRequest(
|
| 58 |
+
"/auth/reset-password",
|
| 59 |
+
"POST",
|
| 60 |
+
{
|
| 61 |
+
token,
|
| 62 |
+
new_password:
|
| 63 |
+
newPassword,
|
| 64 |
+
}
|
| 65 |
+
);
|
| 66 |
+
|
| 67 |
+
alert(
|
| 68 |
+
"Password reset successful"
|
| 69 |
+
);
|
| 70 |
+
|
| 71 |
+
goBack();
|
| 72 |
+
|
| 73 |
+
} catch (error) {
|
| 74 |
+
|
| 75 |
+
console.error(error);
|
| 76 |
+
|
| 77 |
+
alert(
|
| 78 |
+
"Invalid token"
|
| 79 |
+
);
|
| 80 |
+
}
|
| 81 |
+
};
|
| 82 |
+
|
| 83 |
+
return (
|
| 84 |
+
<div
|
| 85 |
+
style={{
|
| 86 |
+
width: "100vw",
|
| 87 |
+
height: "100vh",
|
| 88 |
+
|
| 89 |
+
background:
|
| 90 |
+
"#111",
|
| 91 |
+
|
| 92 |
+
display: "flex",
|
| 93 |
+
|
| 94 |
+
justifyContent:
|
| 95 |
+
"center",
|
| 96 |
+
|
| 97 |
+
alignItems:
|
| 98 |
+
"center",
|
| 99 |
+
}}
|
| 100 |
+
>
|
| 101 |
+
<div
|
| 102 |
+
style={{
|
| 103 |
+
width: "420px",
|
| 104 |
+
}}
|
| 105 |
+
>
|
| 106 |
+
<h1
|
| 107 |
+
style={{
|
| 108 |
+
fontSize:
|
| 109 |
+
"64px",
|
| 110 |
+
|
| 111 |
+
fontWeight:
|
| 112 |
+
"900",
|
| 113 |
+
|
| 114 |
+
color:
|
| 115 |
+
"white",
|
| 116 |
+
|
| 117 |
+
marginBottom:
|
| 118 |
+
"40px",
|
| 119 |
+
}}
|
| 120 |
+
>
|
| 121 |
+
Reset
|
| 122 |
+
</h1>
|
| 123 |
+
|
| 124 |
+
<input
|
| 125 |
+
placeholder="Email"
|
| 126 |
+
|
| 127 |
+
value={email}
|
| 128 |
+
|
| 129 |
+
onChange={(e) =>
|
| 130 |
+
setEmail(
|
| 131 |
+
e.target.value
|
| 132 |
+
)
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
style={inputStyle}
|
| 136 |
+
/>
|
| 137 |
+
|
| 138 |
+
<button
|
| 139 |
+
onClick={
|
| 140 |
+
requestReset
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
style={buttonStyle}
|
| 144 |
+
>
|
| 145 |
+
Generate Reset Token
|
| 146 |
+
</button>
|
| 147 |
+
|
| 148 |
+
{generatedToken && (
|
| 149 |
+
<div
|
| 150 |
+
style={{
|
| 151 |
+
color:
|
| 152 |
+
"#aaa",
|
| 153 |
+
|
| 154 |
+
marginTop:
|
| 155 |
+
"20px",
|
| 156 |
+
|
| 157 |
+
wordBreak:
|
| 158 |
+
"break-all",
|
| 159 |
+
}}
|
| 160 |
+
>
|
| 161 |
+
Reset Token:
|
| 162 |
+
<br />
|
| 163 |
+
{
|
| 164 |
+
generatedToken
|
| 165 |
+
}
|
| 166 |
+
</div>
|
| 167 |
+
)}
|
| 168 |
+
|
| 169 |
+
<input
|
| 170 |
+
placeholder="Paste Token"
|
| 171 |
+
|
| 172 |
+
value={token}
|
| 173 |
+
|
| 174 |
+
onChange={(e) =>
|
| 175 |
+
setToken(
|
| 176 |
+
e.target.value
|
| 177 |
+
)
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
style={{
|
| 181 |
+
...inputStyle,
|
| 182 |
+
marginTop:
|
| 183 |
+
"20px",
|
| 184 |
+
}}
|
| 185 |
+
/>
|
| 186 |
+
|
| 187 |
+
<input
|
| 188 |
+
type="password"
|
| 189 |
+
|
| 190 |
+
placeholder="New Password"
|
| 191 |
+
|
| 192 |
+
value={newPassword}
|
| 193 |
+
|
| 194 |
+
onChange={(e) =>
|
| 195 |
+
setNewPassword(
|
| 196 |
+
e.target.value
|
| 197 |
+
)
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
style={inputStyle}
|
| 201 |
+
/>
|
| 202 |
+
|
| 203 |
+
<button
|
| 204 |
+
onClick={
|
| 205 |
+
resetPassword
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
style={buttonStyle}
|
| 209 |
+
>
|
| 210 |
+
Reset Password
|
| 211 |
+
</button>
|
| 212 |
+
|
| 213 |
+
<p
|
| 214 |
+
onClick={goBack}
|
| 215 |
+
|
| 216 |
+
style={{
|
| 217 |
+
marginTop:
|
| 218 |
+
"20px",
|
| 219 |
+
|
| 220 |
+
color:
|
| 221 |
+
"#777",
|
| 222 |
+
|
| 223 |
+
cursor:
|
| 224 |
+
"pointer",
|
| 225 |
+
|
| 226 |
+
textAlign:
|
| 227 |
+
"center",
|
| 228 |
+
}}
|
| 229 |
+
>
|
| 230 |
+
Back to Login
|
| 231 |
+
</p>
|
| 232 |
+
</div>
|
| 233 |
+
</div>
|
| 234 |
+
);
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
const inputStyle = {
|
| 238 |
+
width: "100%",
|
| 239 |
+
|
| 240 |
+
padding: "18px",
|
| 241 |
+
|
| 242 |
+
marginBottom: "16px",
|
| 243 |
+
|
| 244 |
+
borderRadius: "14px",
|
| 245 |
+
|
| 246 |
+
border: "1px solid #333",
|
| 247 |
+
|
| 248 |
+
background: "#1d1d1d",
|
| 249 |
+
|
| 250 |
+
color: "white",
|
| 251 |
+
|
| 252 |
+
fontSize: "16px",
|
| 253 |
+
|
| 254 |
+
outline: "none",
|
| 255 |
+
};
|
| 256 |
+
|
| 257 |
+
const buttonStyle = {
|
| 258 |
+
width: "100%",
|
| 259 |
+
|
| 260 |
+
padding: "18px",
|
| 261 |
+
|
| 262 |
+
borderRadius: "14px",
|
| 263 |
+
|
| 264 |
+
border: "1px solid #333",
|
| 265 |
+
|
| 266 |
+
background: "#2a2a2a",
|
| 267 |
+
|
| 268 |
+
color: "white",
|
| 269 |
+
|
| 270 |
+
fontSize: "16px",
|
| 271 |
+
|
| 272 |
+
cursor: "pointer",
|
| 273 |
+
};
|
| 274 |
+
|
| 275 |
+
export default ForgotPassword;
|
frontend/src/docpilot/pages/Login.jsx
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState } from "react";
|
| 2 |
+
|
| 3 |
+
import { loginRequest } from "../api";
|
| 4 |
+
|
| 5 |
+
function Login({
|
| 6 |
+
onLogin,
|
| 7 |
+
goToSignup,
|
| 8 |
+
goToForgot,
|
| 9 |
+
}) {
|
| 10 |
+
|
| 11 |
+
const [email,
|
| 12 |
+
setEmail] =
|
| 13 |
+
useState("");
|
| 14 |
+
|
| 15 |
+
const [password,
|
| 16 |
+
setPassword] =
|
| 17 |
+
useState("");
|
| 18 |
+
|
| 19 |
+
const login =
|
| 20 |
+
async () => {
|
| 21 |
+
|
| 22 |
+
try {
|
| 23 |
+
|
| 24 |
+
const data =
|
| 25 |
+
await loginRequest(
|
| 26 |
+
email,
|
| 27 |
+
password
|
| 28 |
+
);
|
| 29 |
+
|
| 30 |
+
if (
|
| 31 |
+
!data.access_token
|
| 32 |
+
) {
|
| 33 |
+
|
| 34 |
+
alert(
|
| 35 |
+
"Invalid credentials"
|
| 36 |
+
);
|
| 37 |
+
|
| 38 |
+
return;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
localStorage.setItem(
|
| 42 |
+
"token",
|
| 43 |
+
data.access_token
|
| 44 |
+
);
|
| 45 |
+
|
| 46 |
+
onLogin();
|
| 47 |
+
|
| 48 |
+
} catch (error) {
|
| 49 |
+
|
| 50 |
+
console.error(error);
|
| 51 |
+
|
| 52 |
+
alert(
|
| 53 |
+
"Wrong email or password"
|
| 54 |
+
);
|
| 55 |
+
}
|
| 56 |
+
};
|
| 57 |
+
|
| 58 |
+
return (
|
| 59 |
+
<div
|
| 60 |
+
style={{
|
| 61 |
+
width: "100vw",
|
| 62 |
+
|
| 63 |
+
height: "100vh",
|
| 64 |
+
|
| 65 |
+
background:
|
| 66 |
+
"#111",
|
| 67 |
+
|
| 68 |
+
display: "flex",
|
| 69 |
+
|
| 70 |
+
justifyContent:
|
| 71 |
+
"center",
|
| 72 |
+
|
| 73 |
+
alignItems:
|
| 74 |
+
"center",
|
| 75 |
+
|
| 76 |
+
overflow:
|
| 77 |
+
"hidden",
|
| 78 |
+
}}
|
| 79 |
+
>
|
| 80 |
+
<div
|
| 81 |
+
style={{
|
| 82 |
+
width: "100%",
|
| 83 |
+
|
| 84 |
+
maxWidth:
|
| 85 |
+
"520px",
|
| 86 |
+
|
| 87 |
+
display:
|
| 88 |
+
"flex",
|
| 89 |
+
|
| 90 |
+
flexDirection:
|
| 91 |
+
"column",
|
| 92 |
+
|
| 93 |
+
alignItems:
|
| 94 |
+
"center",
|
| 95 |
+
|
| 96 |
+
padding:
|
| 97 |
+
"24px",
|
| 98 |
+
|
| 99 |
+
boxSizing:
|
| 100 |
+
"border-box",
|
| 101 |
+
}}
|
| 102 |
+
>
|
| 103 |
+
|
| 104 |
+
<h1
|
| 105 |
+
style={{
|
| 106 |
+
fontSize:
|
| 107 |
+
"72px",
|
| 108 |
+
|
| 109 |
+
fontFamily:
|
| 110 |
+
"Georgia, serif",
|
| 111 |
+
|
| 112 |
+
fontWeight:
|
| 113 |
+
"600",
|
| 114 |
+
|
| 115 |
+
marginBottom:
|
| 116 |
+
"36px",
|
| 117 |
+
|
| 118 |
+
letterSpacing:
|
| 119 |
+
"-2px",
|
| 120 |
+
|
| 121 |
+
color:
|
| 122 |
+
"white",
|
| 123 |
+
|
| 124 |
+
lineHeight:
|
| 125 |
+
"1",
|
| 126 |
+
|
| 127 |
+
textAlign:
|
| 128 |
+
"center",
|
| 129 |
+
}}
|
| 130 |
+
>
|
| 131 |
+
DocPilot
|
| 132 |
+
</h1>
|
| 133 |
+
|
| 134 |
+
<input
|
| 135 |
+
placeholder="Email"
|
| 136 |
+
|
| 137 |
+
value={email}
|
| 138 |
+
|
| 139 |
+
onChange={(e) =>
|
| 140 |
+
setEmail(
|
| 141 |
+
e.target.value
|
| 142 |
+
)
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
style={inputStyle}
|
| 146 |
+
/>
|
| 147 |
+
|
| 148 |
+
<input
|
| 149 |
+
type="password"
|
| 150 |
+
|
| 151 |
+
placeholder="Password"
|
| 152 |
+
|
| 153 |
+
value={password}
|
| 154 |
+
|
| 155 |
+
onChange={(e) =>
|
| 156 |
+
setPassword(
|
| 157 |
+
e.target.value
|
| 158 |
+
)
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
style={inputStyle}
|
| 162 |
+
/>
|
| 163 |
+
|
| 164 |
+
<button
|
| 165 |
+
onClick={login}
|
| 166 |
+
|
| 167 |
+
style={buttonStyle}
|
| 168 |
+
>
|
| 169 |
+
Login
|
| 170 |
+
</button>
|
| 171 |
+
|
| 172 |
+
<p
|
| 173 |
+
onClick={
|
| 174 |
+
goToSignup
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
style={{
|
| 178 |
+
marginTop:
|
| 179 |
+
"22px",
|
| 180 |
+
|
| 181 |
+
color:
|
| 182 |
+
"#888",
|
| 183 |
+
|
| 184 |
+
textAlign:
|
| 185 |
+
"center",
|
| 186 |
+
|
| 187 |
+
cursor:
|
| 188 |
+
"pointer",
|
| 189 |
+
|
| 190 |
+
fontWeight:
|
| 191 |
+
"600",
|
| 192 |
+
|
| 193 |
+
fontSize:
|
| 194 |
+
"18px",
|
| 195 |
+
}}
|
| 196 |
+
>
|
| 197 |
+
Don't have an account?
|
| 198 |
+
{" "}
|
| 199 |
+
Sign up
|
| 200 |
+
</p>
|
| 201 |
+
|
| 202 |
+
<p
|
| 203 |
+
onClick={
|
| 204 |
+
goToForgot
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
style={{
|
| 208 |
+
marginTop:
|
| 209 |
+
"14px",
|
| 210 |
+
|
| 211 |
+
color:
|
| 212 |
+
"#666",
|
| 213 |
+
|
| 214 |
+
textAlign:
|
| 215 |
+
"center",
|
| 216 |
+
|
| 217 |
+
cursor:
|
| 218 |
+
"pointer",
|
| 219 |
+
|
| 220 |
+
fontWeight:
|
| 221 |
+
"600",
|
| 222 |
+
|
| 223 |
+
fontSize:
|
| 224 |
+
"16px",
|
| 225 |
+
}}
|
| 226 |
+
>
|
| 227 |
+
Forgot password?
|
| 228 |
+
</p>
|
| 229 |
+
|
| 230 |
+
</div>
|
| 231 |
+
</div>
|
| 232 |
+
);
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
const inputStyle = {
|
| 236 |
+
width: "520px",
|
| 237 |
+
|
| 238 |
+
maxWidth:
|
| 239 |
+
"90vw",
|
| 240 |
+
|
| 241 |
+
padding:
|
| 242 |
+
"22px",
|
| 243 |
+
|
| 244 |
+
marginBottom:
|
| 245 |
+
"18px",
|
| 246 |
+
|
| 247 |
+
borderRadius:
|
| 248 |
+
"18px",
|
| 249 |
+
|
| 250 |
+
border:
|
| 251 |
+
"1px solid #2e2e2e",
|
| 252 |
+
|
| 253 |
+
background:
|
| 254 |
+
"#1d1d1d",
|
| 255 |
+
|
| 256 |
+
color:
|
| 257 |
+
"white",
|
| 258 |
+
|
| 259 |
+
fontSize:
|
| 260 |
+
"18px",
|
| 261 |
+
|
| 262 |
+
outline:
|
| 263 |
+
"none",
|
| 264 |
+
|
| 265 |
+
boxSizing:
|
| 266 |
+
"border-box",
|
| 267 |
+
};
|
| 268 |
+
|
| 269 |
+
const buttonStyle = {
|
| 270 |
+
width: "520px",
|
| 271 |
+
|
| 272 |
+
maxWidth:
|
| 273 |
+
"90vw",
|
| 274 |
+
|
| 275 |
+
padding:
|
| 276 |
+
"22px",
|
| 277 |
+
|
| 278 |
+
borderRadius:
|
| 279 |
+
"18px",
|
| 280 |
+
|
| 281 |
+
border:
|
| 282 |
+
"1px solid #333",
|
| 283 |
+
|
| 284 |
+
background:
|
| 285 |
+
"#2a2a2a",
|
| 286 |
+
|
| 287 |
+
color:
|
| 288 |
+
"white",
|
| 289 |
+
|
| 290 |
+
fontSize:
|
| 291 |
+
"18px",
|
| 292 |
+
|
| 293 |
+
cursor:
|
| 294 |
+
"pointer",
|
| 295 |
+
|
| 296 |
+
fontWeight:
|
| 297 |
+
"700",
|
| 298 |
+
|
| 299 |
+
boxSizing:
|
| 300 |
+
"border-box",
|
| 301 |
+
};
|
| 302 |
+
|
| 303 |
+
export default Login;
|