Spaces:
Running
Running
Merge branch 'main' of github-personal:CRIMSONHydra/stocker
Browse files- .gitignore +8 -2
- CLAUDE.md +22 -2
- app/api/council.py +30 -0
- app/api/frontend.py +16 -5
- app/api/ohlcv.py +37 -0
- app/api/router.py +4 -1
- app/api/training.py +60 -0
- app/config.py +5 -0
- app/core/environment.py +32 -7
- app/core/graders.py +83 -15
- app/core/tasks.py +43 -1
- app/main.py +17 -0
- data/ideal_profits/task_easy.json +97 -0
- data/ideal_profits/task_hard.json +95 -0
- data/ideal_profits/task_medium.json +93 -0
- frontend/.gitignore +8 -0
- frontend/index.html +13 -0
- frontend/package-lock.json +2605 -0
- frontend/package.json +31 -0
- frontend/src/App.tsx +1554 -0
- frontend/src/api.ts +255 -0
- frontend/src/index.css +54 -0
- frontend/src/main.tsx +10 -0
- frontend/tsconfig.json +26 -0
- frontend/vite.config.ts +26 -0
- scripts/build_ideal_profit.py +116 -0
- tests/test_api.py +35 -0
- tests/test_graders.py +121 -0
- uv.lock +0 -0
.gitignore
CHANGED
|
@@ -32,8 +32,10 @@ coverage.xml
|
|
| 32 |
.nox/
|
| 33 |
|
| 34 |
# --- Stocker runtime artifacts --------------------------------------------
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
| 37 |
!training/runs/.gitkeep
|
| 38 |
results.json
|
| 39 |
*.log
|
|
@@ -42,6 +44,10 @@ results.json
|
|
| 42 |
# 3-task dataset (data/*.parquet, data/charts/*.png) IS committed.
|
| 43 |
data/corpus/
|
| 44 |
data/charts/cache/
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
# --- Notebook noise --------------------------------------------------------
|
| 47 |
.ipynb_checkpoints/
|
|
|
|
| 32 |
.nox/
|
| 33 |
|
| 34 |
# --- Stocker runtime artifacts --------------------------------------------
|
| 35 |
+
# council vote cache (specialist + moderator)
|
| 36 |
+
.cache/
|
| 37 |
+
# GRPO runs, eval rollouts, plots, LoRA adapters
|
| 38 |
+
training/runs/
|
| 39 |
!training/runs/.gitkeep
|
| 40 |
results.json
|
| 41 |
*.log
|
|
|
|
| 44 |
# 3-task dataset (data/*.parquet, data/charts/*.png) IS committed.
|
| 45 |
data/corpus/
|
| 46 |
data/charts/cache/
|
| 47 |
+
# --- Frontend (React/Vite) -------------------------------------------------
|
| 48 |
+
frontend/node_modules/
|
| 49 |
+
frontend/dist/
|
| 50 |
+
frontend/.env.local
|
| 51 |
|
| 52 |
# --- Notebook noise --------------------------------------------------------
|
| 53 |
.ipynb_checkpoints/
|
CLAUDE.md
CHANGED
|
@@ -39,6 +39,7 @@ via TRL on top of `google/gemma-4-E4B-it`.
|
|
| 39 |
├── server/app.py # OpenEnv entry point (`server.app:main`)
|
| 40 |
├── inference.py # council-driven OpenEnv inference loop (root)
|
| 41 |
├── client.py
|
|
|
|
| 42 |
├── scripts/ # build_dataset, render_charts, serve_vllm, validate_tasks
|
| 43 |
├── training/ # eval_rollout, train_grpo, runs/
|
| 44 |
├── tests/
|
|
@@ -47,6 +48,23 @@ via TRL on top of `google/gemma-4-E4B-it`.
|
|
| 47 |
└── pyproject.toml
|
| 48 |
```
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
## Conventions
|
| 51 |
|
| 52 |
- All Pydantic schemas live in [app/models.py](app/models.py). Don't move
|
|
@@ -83,8 +101,6 @@ via TRL on top of `google/gemma-4-E4B-it`.
|
|
| 83 |
|
| 84 |
## Don't
|
| 85 |
|
| 86 |
-
- Don't introduce a frontend build step — the HTML lives inline in
|
| 87 |
-
[app/api/frontend.py](app/api/frontend.py).
|
| 88 |
- Don't bake live API calls into specialists. Council inputs come from the
|
| 89 |
bundled parquet dataset (`app/data/loader.py`). Live sources are the data
|
| 90 |
builder's job.
|
|
@@ -94,3 +110,7 @@ via TRL on top of `google/gemma-4-E4B-it`.
|
|
| 94 |
trainer's reward replay both depend on it.
|
| 95 |
- Don't make specialists rely on the moderator's LoRA. Specialists must
|
| 96 |
remain frozen so their cached votes are reusable across GRPO steps.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
├── server/app.py # OpenEnv entry point (`server.app:main`)
|
| 40 |
├── inference.py # council-driven OpenEnv inference loop (root)
|
| 41 |
├── client.py
|
| 42 |
+
├── frontend/ # Vite + React 19 + Tailwind v4 SPA (built artefact mounted by FastAPI)
|
| 43 |
├── scripts/ # build_dataset, render_charts, serve_vllm, validate_tasks
|
| 44 |
├── training/ # eval_rollout, train_grpo, runs/
|
| 45 |
├── tests/
|
|
|
|
| 48 |
└── pyproject.toml
|
| 49 |
```
|
| 50 |
|
| 51 |
+
## Frontend
|
| 52 |
+
|
| 53 |
+
- Source lives in [frontend/](frontend/) — Vite + React 19 + TypeScript +
|
| 54 |
+
Tailwind v4. Single `App.tsx` with six tabs (Terminal, Council, Training,
|
| 55 |
+
Gallery, Portfolio, Intelligence).
|
| 56 |
+
- Dev: `cd frontend && npm install && npm run dev` (HMR on :3000).
|
| 57 |
+
- Production: `cd frontend && npm run build` emits `frontend/dist/`. FastAPI
|
| 58 |
+
serves `frontend/dist/index.html` at `/` and `/web`, and mounts
|
| 59 |
+
`frontend/dist/assets/` at `/assets` (see [app/main.py](app/main.py) and
|
| 60 |
+
[app/api/frontend.py](app/api/frontend.py)).
|
| 61 |
+
- Builds are **manual**. Don't add auto-build to CI, hooks, or `npm` scripts
|
| 62 |
+
triggered from Python.
|
| 63 |
+
- When `frontend/dist/` is absent, FastAPI falls back to the inline
|
| 64 |
+
`FRONTEND_HTML` string in [app/api/frontend.py](app/api/frontend.py). That
|
| 65 |
+
fallback is the lowest-common-denominator UI for headless dev/tests and
|
| 66 |
+
must keep working without Node.
|
| 67 |
+
|
| 68 |
## Conventions
|
| 69 |
|
| 70 |
- All Pydantic schemas live in [app/models.py](app/models.py). Don't move
|
|
|
|
| 101 |
|
| 102 |
## Don't
|
| 103 |
|
|
|
|
|
|
|
| 104 |
- Don't bake live API calls into specialists. Council inputs come from the
|
| 105 |
bundled parquet dataset (`app/data/loader.py`). Live sources are the data
|
| 106 |
builder's job.
|
|
|
|
| 110 |
trainer's reward replay both depend on it.
|
| 111 |
- Don't make specialists rely on the moderator's LoRA. Specialists must
|
| 112 |
remain frozen so their cached votes are reusable across GRPO steps.
|
| 113 |
+
- Don't auto-build the React frontend from Python or CI. Builds are run by
|
| 114 |
+
hand with `npm run build`. Don't delete the inline `FRONTEND_HTML`
|
| 115 |
+
fallback in [app/api/frontend.py](app/api/frontend.py) — tests and
|
| 116 |
+
Node-less dev rely on it.
|
app/api/council.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Council preview endpoint: runs the 7 specialists + moderator on the
|
| 2 |
+
current env state and returns the resulting CouncilDecision.
|
| 3 |
+
|
| 4 |
+
Defaults to MockLLMClient so the endpoint works on a laptop with no API
|
| 5 |
+
keys / GPU. The mock is deterministic per (ticker, date) and the Council
|
| 6 |
+
runner caches results to .cache/council, so repeat hits are instant.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from fastapi import APIRouter, HTTPException
|
| 10 |
+
|
| 11 |
+
from app.council.llm import MockLLMClient
|
| 12 |
+
from app.council.runner import Council
|
| 13 |
+
from app.models import CouncilDecision
|
| 14 |
+
|
| 15 |
+
router = APIRouter(tags=["council"])
|
| 16 |
+
|
| 17 |
+
_council = Council(client=MockLLMClient(), use_cache=True)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@router.get("/council", response_model=CouncilDecision)
|
| 21 |
+
async def get_council() -> CouncilDecision:
|
| 22 |
+
import app.api.env as env_module
|
| 23 |
+
|
| 24 |
+
env = env_module.current_env
|
| 25 |
+
if not env.is_ready():
|
| 26 |
+
raise HTTPException(
|
| 27 |
+
status_code=409,
|
| 28 |
+
detail="env not initialized or episode complete — call /reset first",
|
| 29 |
+
)
|
| 30 |
+
return await _council.run_async(env.current_observation())
|
app/api/frontend.py
CHANGED
|
@@ -1,10 +1,19 @@
|
|
| 1 |
-
"""Serves the embedded HTML frontend.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
from fastapi import APIRouter
|
| 4 |
-
from fastapi.responses import HTMLResponse
|
| 5 |
|
| 6 |
router = APIRouter(tags=["frontend"])
|
| 7 |
|
|
|
|
|
|
|
| 8 |
FRONTEND_HTML = """<!DOCTYPE html>
|
| 9 |
<html lang="en">
|
| 10 |
<head>
|
|
@@ -119,7 +128,9 @@ async function submit() {
|
|
| 119 |
</html>"""
|
| 120 |
|
| 121 |
|
| 122 |
-
@router.get("/"
|
| 123 |
-
@router.get("/web"
|
| 124 |
-
async def index()
|
|
|
|
|
|
|
| 125 |
return HTMLResponse(content=FRONTEND_HTML)
|
|
|
|
| 1 |
+
"""Serves the embedded HTML frontend.
|
| 2 |
+
|
| 3 |
+
When a built React SPA exists at ``frontend/dist/index.html``, that is served at
|
| 4 |
+
``/`` and ``/web``. Otherwise the inline HTML below is served as a fallback so
|
| 5 |
+
the API is usable without Node tooling (and so tests do not require a build).
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from pathlib import Path
|
| 9 |
|
| 10 |
from fastapi import APIRouter
|
| 11 |
+
from fastapi.responses import FileResponse, HTMLResponse
|
| 12 |
|
| 13 |
router = APIRouter(tags=["frontend"])
|
| 14 |
|
| 15 |
+
DIST_INDEX = Path(__file__).resolve().parents[2] / "frontend" / "dist" / "index.html"
|
| 16 |
+
|
| 17 |
FRONTEND_HTML = """<!DOCTYPE html>
|
| 18 |
<html lang="en">
|
| 19 |
<head>
|
|
|
|
| 128 |
</html>"""
|
| 129 |
|
| 130 |
|
| 131 |
+
@router.get("/")
|
| 132 |
+
@router.get("/web")
|
| 133 |
+
async def index():
|
| 134 |
+
if DIST_INDEX.is_file():
|
| 135 |
+
return FileResponse(DIST_INDEX)
|
| 136 |
return HTMLResponse(content=FRONTEND_HTML)
|
app/api/ohlcv.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""OHLCV bars for chart rendering.
|
| 2 |
+
|
| 3 |
+
Returns the full per-task daily series (warmup + episode rows) so the chart
|
| 4 |
+
panel has historical context. Sourced from data/prices.parquet via
|
| 5 |
+
:mod:`app.data.loader`. The agent's MarketObservation is unaffected — this
|
| 6 |
+
endpoint is purely a UI concern.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from fastapi import APIRouter, HTTPException, Query
|
| 10 |
+
|
| 11 |
+
from app.data.loader import prices
|
| 12 |
+
|
| 13 |
+
router = APIRouter(tags=["ohlcv"])
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@router.get("/ohlcv")
|
| 17 |
+
async def get_ohlcv(task_id: str = Query(...)) -> dict:
|
| 18 |
+
df = prices()
|
| 19 |
+
sub = df[df["task_id"] == task_id].sort_values("date")
|
| 20 |
+
if sub.empty:
|
| 21 |
+
raise HTTPException(status_code=404, detail=f"no price rows for task {task_id}")
|
| 22 |
+
|
| 23 |
+
ticker = str(sub["ticker"].iloc[0])
|
| 24 |
+
cols = ["date", "open", "high", "low", "close", "volume", "in_episode"]
|
| 25 |
+
bars = [
|
| 26 |
+
{
|
| 27 |
+
"time": str(r["date"]),
|
| 28 |
+
"open": float(r["open"]),
|
| 29 |
+
"high": float(r["high"]),
|
| 30 |
+
"low": float(r["low"]),
|
| 31 |
+
"close": float(r["close"]),
|
| 32 |
+
"volume": float(r["volume"]),
|
| 33 |
+
"in_episode": bool(r["in_episode"]),
|
| 34 |
+
}
|
| 35 |
+
for r in sub[cols].to_dict("records")
|
| 36 |
+
]
|
| 37 |
+
return {"task_id": task_id, "ticker": ticker, "bars": bars}
|
app/api/router.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
|
| 3 |
from fastapi import APIRouter
|
| 4 |
|
| 5 |
-
from app.api import corpus, env, health, meta, state
|
| 6 |
|
| 7 |
api_router = APIRouter()
|
| 8 |
api_router.include_router(health.router)
|
|
@@ -10,3 +10,6 @@ api_router.include_router(meta.router)
|
|
| 10 |
api_router.include_router(env.router)
|
| 11 |
api_router.include_router(state.router)
|
| 12 |
api_router.include_router(corpus.router)
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
from fastapi import APIRouter
|
| 4 |
|
| 5 |
+
from app.api import corpus, council, env, health, meta, ohlcv, state, training
|
| 6 |
|
| 7 |
api_router = APIRouter()
|
| 8 |
api_router.include_router(health.router)
|
|
|
|
| 10 |
api_router.include_router(env.router)
|
| 11 |
api_router.include_router(state.router)
|
| 12 |
api_router.include_router(corpus.router)
|
| 13 |
+
api_router.include_router(ohlcv.router)
|
| 14 |
+
api_router.include_router(council.router)
|
| 15 |
+
api_router.include_router(training.router)
|
app/api/training.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Training metrics endpoint: surfaces what is actually on disk in
|
| 2 |
+
``training/runs/<run>/`` — currently a per-task ``summary.csv`` with
|
| 3 |
+
total_reward / final_portfolio / buy_and_hold / alpha_pct columns plus PNG
|
| 4 |
+
curve plots saved by ``training/eval_rollout.py``.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import csv
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
from fastapi import APIRouter
|
| 11 |
+
|
| 12 |
+
router = APIRouter(tags=["training"])
|
| 13 |
+
|
| 14 |
+
RUNS_DIR = Path(__file__).resolve().parents[2] / "training" / "runs"
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def _latest_run() -> Path | None:
|
| 18 |
+
if not RUNS_DIR.is_dir():
|
| 19 |
+
return None
|
| 20 |
+
candidates = [p for p in RUNS_DIR.iterdir() if p.is_dir()]
|
| 21 |
+
if not candidates:
|
| 22 |
+
return None
|
| 23 |
+
return max(candidates, key=lambda p: p.stat().st_mtime)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@router.get("/training/metrics")
|
| 27 |
+
async def get_metrics() -> dict:
|
| 28 |
+
run = _latest_run()
|
| 29 |
+
if run is None:
|
| 30 |
+
return {"status": "no_runs", "summary": [], "mean_alpha_pct": 0.0}
|
| 31 |
+
|
| 32 |
+
summary_path = run / "summary.csv"
|
| 33 |
+
summary: list[dict] = []
|
| 34 |
+
if summary_path.is_file():
|
| 35 |
+
with summary_path.open() as f:
|
| 36 |
+
for row in csv.DictReader(f):
|
| 37 |
+
summary.append({
|
| 38 |
+
"task_id": row["task_id"],
|
| 39 |
+
"total_reward": float(row["total_reward"]),
|
| 40 |
+
"final_portfolio": float(row["final_portfolio"]),
|
| 41 |
+
"buy_and_hold": float(row["buy_and_hold"]),
|
| 42 |
+
"alpha_pct": float(row["alpha_pct"]),
|
| 43 |
+
})
|
| 44 |
+
|
| 45 |
+
mean_alpha = (
|
| 46 |
+
sum(r["alpha_pct"] for r in summary) / len(summary) if summary else 0.0
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
def _png(name: str) -> str | None:
|
| 50 |
+
p = run / name
|
| 51 |
+
return f"/training/runs/{run.name}/{name}" if p.is_file() else None
|
| 52 |
+
|
| 53 |
+
return {
|
| 54 |
+
"status": "completed" if summary else "no_runs",
|
| 55 |
+
"run_name": run.name,
|
| 56 |
+
"summary": summary,
|
| 57 |
+
"mean_alpha_pct": round(mean_alpha, 2),
|
| 58 |
+
"reward_curve_png": _png("reward_curve.png"),
|
| 59 |
+
"portfolio_curve_png": _png("portfolio_curve.png"),
|
| 60 |
+
}
|
app/config.py
CHANGED
|
@@ -8,6 +8,11 @@ class Settings(BaseSettings):
|
|
| 8 |
allow_origins: list[str] = ["*"]
|
| 9 |
port: int = 7860
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
model_config = {"env_prefix": "STOCKER_", "case_sensitive": False}
|
| 12 |
|
| 13 |
|
|
|
|
| 8 |
allow_origins: list[str] = ["*"]
|
| 9 |
port: int = 7860
|
| 10 |
|
| 11 |
+
transaction_cost_rate: float = 0.001
|
| 12 |
+
annual_inflation_rate: float = 0.05
|
| 13 |
+
reward_weight_performance: float = 0.7
|
| 14 |
+
reward_weight_inflation: float = 0.3
|
| 15 |
+
|
| 16 |
model_config = {"env_prefix": "STOCKER_", "case_sensitive": False}
|
| 17 |
|
| 18 |
|
app/core/environment.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
| 3 |
|
| 4 |
import uuid
|
| 5 |
|
|
|
|
| 6 |
from app.core.graders import compute_step_reward, compute_trajectory_bonus
|
| 7 |
from app.core.tasks import get_task_definition
|
| 8 |
from app.data import loader
|
|
@@ -85,16 +86,19 @@ class StockerEnv:
|
|
| 85 |
)
|
| 86 |
|
| 87 |
price = self._prices[self._current_index]
|
| 88 |
-
prev_portfolio = self._cash + self._position * price
|
| 89 |
invalid = self._apply_action(action, price)
|
| 90 |
new_portfolio = self._cash + self._position * price
|
| 91 |
|
| 92 |
result = compute_step_reward(
|
| 93 |
action=action,
|
| 94 |
-
prev_portfolio=prev_portfolio,
|
| 95 |
new_portfolio=new_portfolio,
|
| 96 |
starting_cash=float(self._task["starting_cash"]),
|
| 97 |
invalid=invalid,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
)
|
| 99 |
reward = result.score
|
| 100 |
|
|
@@ -134,6 +138,19 @@ class StockerEnv:
|
|
| 134 |
},
|
| 135 |
)
|
| 136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
# ------------------------------------------------------------------ state
|
| 138 |
def state(self) -> EnvironmentState:
|
| 139 |
price = (
|
|
@@ -166,22 +183,30 @@ class StockerEnv:
|
|
| 166 |
|
| 167 |
# ---------------------------------------------------------------- helpers
|
| 168 |
def _apply_action(self, action: TradeAction, price: float) -> bool:
|
| 169 |
-
"""Apply trade. Returns True if invalid (insufficient cash/position).
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
if action.side == "hold" or action.quantity <= 0:
|
| 171 |
return False
|
| 172 |
|
|
|
|
|
|
|
| 173 |
if action.side == "buy":
|
| 174 |
-
|
| 175 |
-
|
|
|
|
| 176 |
return True
|
| 177 |
-
self._cash -=
|
| 178 |
self._position += action.quantity
|
| 179 |
return False
|
| 180 |
|
| 181 |
if action.side == "sell":
|
| 182 |
if action.quantity > self._position:
|
| 183 |
return True
|
| 184 |
-
|
|
|
|
| 185 |
self._position -= action.quantity
|
| 186 |
return False
|
| 187 |
|
|
|
|
| 3 |
|
| 4 |
import uuid
|
| 5 |
|
| 6 |
+
from app.config import settings
|
| 7 |
from app.core.graders import compute_step_reward, compute_trajectory_bonus
|
| 8 |
from app.core.tasks import get_task_definition
|
| 9 |
from app.data import loader
|
|
|
|
| 86 |
)
|
| 87 |
|
| 88 |
price = self._prices[self._current_index]
|
|
|
|
| 89 |
invalid = self._apply_action(action, price)
|
| 90 |
new_portfolio = self._cash + self._position * price
|
| 91 |
|
| 92 |
result = compute_step_reward(
|
| 93 |
action=action,
|
|
|
|
| 94 |
new_portfolio=new_portfolio,
|
| 95 |
starting_cash=float(self._task["starting_cash"]),
|
| 96 |
invalid=invalid,
|
| 97 |
+
step_index=self._current_index,
|
| 98 |
+
total_steps=len(self._prices),
|
| 99 |
+
ideal_pnl_pct_series=self._task.get("ideal_pnl_pct_series", []),
|
| 100 |
+
ideal_pnl_pct_total=float(self._task.get("ideal_pnl_pct_total", 0.0)),
|
| 101 |
+
settings=settings,
|
| 102 |
)
|
| 103 |
reward = result.score
|
| 104 |
|
|
|
|
| 138 |
},
|
| 139 |
)
|
| 140 |
|
| 141 |
+
# ----------------------------------------------------------------- public
|
| 142 |
+
def is_ready(self) -> bool:
|
| 143 |
+
"""True if the env has been reset and the episode is still live."""
|
| 144 |
+
return bool(self._prices) and not self._done
|
| 145 |
+
|
| 146 |
+
def current_observation(self) -> MarketObservation:
|
| 147 |
+
"""Public accessor for the agent-visible observation at the current step."""
|
| 148 |
+
if not self._prices:
|
| 149 |
+
raise RuntimeError("env has not been reset yet")
|
| 150 |
+
if self._done:
|
| 151 |
+
return self._terminal_observation()
|
| 152 |
+
return self._build_observation(self._current_index)
|
| 153 |
+
|
| 154 |
# ------------------------------------------------------------------ state
|
| 155 |
def state(self) -> EnvironmentState:
|
| 156 |
price = (
|
|
|
|
| 183 |
|
| 184 |
# ---------------------------------------------------------------- helpers
|
| 185 |
def _apply_action(self, action: TradeAction, price: float) -> bool:
|
| 186 |
+
"""Apply trade. Returns True if invalid (insufficient cash/position).
|
| 187 |
+
|
| 188 |
+
Buys and sells incur a transaction cost equal to
|
| 189 |
+
settings.transaction_cost_rate * trade_notional, deducted from cash.
|
| 190 |
+
"""
|
| 191 |
if action.side == "hold" or action.quantity <= 0:
|
| 192 |
return False
|
| 193 |
|
| 194 |
+
rate = settings.transaction_cost_rate
|
| 195 |
+
|
| 196 |
if action.side == "buy":
|
| 197 |
+
notional = action.quantity * price
|
| 198 |
+
total_cost = notional * (1.0 + rate)
|
| 199 |
+
if total_cost > self._cash:
|
| 200 |
return True
|
| 201 |
+
self._cash -= total_cost
|
| 202 |
self._position += action.quantity
|
| 203 |
return False
|
| 204 |
|
| 205 |
if action.side == "sell":
|
| 206 |
if action.quantity > self._position:
|
| 207 |
return True
|
| 208 |
+
notional = action.quantity * price
|
| 209 |
+
self._cash += notional * (1.0 - rate)
|
| 210 |
self._position -= action.quantity
|
| 211 |
return False
|
| 212 |
|
app/core/graders.py
CHANGED
|
@@ -1,39 +1,109 @@
|
|
| 1 |
"""Reward and shaping for the Stocker backtest.
|
| 2 |
|
| 3 |
-
Per-step reward
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
All rewards are clipped to [-1.0, 1.0] in the caller (StockerEnv.step).
|
| 9 |
"""
|
| 10 |
from __future__ import annotations
|
| 11 |
|
|
|
|
| 12 |
from app.models import RewardResult, TradeAction
|
| 13 |
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
def compute_step_reward(
|
|
|
|
| 16 |
action: TradeAction,
|
| 17 |
-
prev_portfolio: float,
|
| 18 |
new_portfolio: float,
|
| 19 |
starting_cash: float,
|
| 20 |
invalid: bool,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
) -> RewardResult:
|
| 22 |
breakdown: dict[str, float] = {}
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
-
penalty = 0.0
|
| 28 |
if invalid:
|
| 29 |
-
|
| 30 |
-
breakdown["invalid_action_penalty"] = -
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
| 34 |
if invalid:
|
| 35 |
feedback += " [invalid: insufficient cash/position]"
|
| 36 |
-
|
|
|
|
| 37 |
|
| 38 |
|
| 39 |
def compute_trajectory_bonus(
|
|
@@ -45,13 +115,11 @@ def compute_trajectory_bonus(
|
|
| 45 |
"""End-of-episode shaping: alpha bonus minus drawdown penalty."""
|
| 46 |
bonus = 0.0
|
| 47 |
|
| 48 |
-
# Alpha vs. buy-and-hold (capped at +0.10)
|
| 49 |
if buy_and_hold_value > 0:
|
| 50 |
alpha = (final_portfolio - buy_and_hold_value) / buy_and_hold_value
|
| 51 |
if alpha >= 0.01:
|
| 52 |
bonus += min(0.10, alpha)
|
| 53 |
|
| 54 |
-
# Drawdown penalty (only if the curve is provided)
|
| 55 |
if portfolio_curve:
|
| 56 |
peak = portfolio_curve[0]
|
| 57 |
max_dd = 0.0
|
|
|
|
| 1 |
"""Reward and shaping for the Stocker backtest.
|
| 2 |
|
| 3 |
+
Per-step reward is a weighted sum of two components:
|
| 4 |
+
|
| 5 |
+
1. Performance factor (W_PERF) — asymmetric piecewise-linear function of the
|
| 6 |
+
gap between the precomputed ideal PnL trajectory and the model's actual
|
| 7 |
+
real (inflation-adjusted) PnL at this step. Small underperformance is
|
| 8 |
+
rewarded; large underperformance is punished; outperformance gets a
|
| 9 |
+
bonus.
|
| 10 |
+
2. Inflation factor (W_INFLATION) — penalty for the share of nominal gain
|
| 11 |
+
eaten by inflation drag at this point in the episode.
|
| 12 |
+
|
| 13 |
+
A small invalid-action penalty is subtracted on top. Transaction cost is not
|
| 14 |
+
a separate term — it is deducted from cash at trade time inside the env, so
|
| 15 |
+
nominal_pnl_pct already reflects friction.
|
| 16 |
+
|
| 17 |
+
End-of-episode shaping (alpha vs. buy-and-hold + drawdown penalty) lives in
|
| 18 |
+
compute_trajectory_bonus and is unchanged.
|
| 19 |
|
| 20 |
All rewards are clipped to [-1.0, 1.0] in the caller (StockerEnv.step).
|
| 21 |
"""
|
| 22 |
from __future__ import annotations
|
| 23 |
|
| 24 |
+
from app.config import Settings
|
| 25 |
from app.models import RewardResult, TradeAction
|
| 26 |
|
| 27 |
+
TRADING_DAYS_PER_YEAR = 252
|
| 28 |
+
INVALID_ACTION_PENALTY = 0.01
|
| 29 |
+
|
| 30 |
|
| 31 |
def compute_step_reward(
|
| 32 |
+
*,
|
| 33 |
action: TradeAction,
|
|
|
|
| 34 |
new_portfolio: float,
|
| 35 |
starting_cash: float,
|
| 36 |
invalid: bool,
|
| 37 |
+
step_index: int,
|
| 38 |
+
total_steps: int,
|
| 39 |
+
ideal_pnl_pct_series: list[float],
|
| 40 |
+
ideal_pnl_pct_total: float,
|
| 41 |
+
settings: Settings,
|
| 42 |
) -> RewardResult:
|
| 43 |
breakdown: dict[str, float] = {}
|
| 44 |
|
| 45 |
+
# Cumulative nominal PnL fraction (relative to starting cash)
|
| 46 |
+
nominal_pnl_pct = (new_portfolio - starting_cash) / max(starting_cash, 1e-9)
|
| 47 |
+
|
| 48 |
+
# Inflation-adjusted real PnL, scaled by elapsed years in the episode
|
| 49 |
+
years_elapsed = max(step_index, 0) / TRADING_DAYS_PER_YEAR
|
| 50 |
+
inflation_growth = (1.0 + settings.annual_inflation_rate) ** years_elapsed - 1.0
|
| 51 |
+
real_pnl_pct = (1.0 + nominal_pnl_pct) / (1.0 + inflation_growth) - 1.0
|
| 52 |
+
|
| 53 |
+
breakdown["nominal_pnl_pct"] = round(nominal_pnl_pct, 6)
|
| 54 |
+
breakdown["real_pnl_pct"] = round(real_pnl_pct, 6)
|
| 55 |
+
breakdown["inflation_drag"] = round(inflation_growth, 6)
|
| 56 |
+
|
| 57 |
+
# Inflation factor: how much of the nominal gain was eaten by inflation.
|
| 58 |
+
# Always <= 0 (real <= nominal under positive inflation), clipped at -1.
|
| 59 |
+
inflation_factor = max(-1.0, real_pnl_pct - nominal_pnl_pct)
|
| 60 |
+
breakdown["inflation_factor"] = round(inflation_factor, 6)
|
| 61 |
+
|
| 62 |
+
# Performance factor: asymmetric piecewise linear in the gap to ideal.
|
| 63 |
+
ideal_at_step = (
|
| 64 |
+
ideal_pnl_pct_series[step_index]
|
| 65 |
+
if 0 <= step_index < len(ideal_pnl_pct_series)
|
| 66 |
+
else ideal_pnl_pct_total
|
| 67 |
+
)
|
| 68 |
+
gap = ideal_at_step - real_pnl_pct
|
| 69 |
+
scale = max(0.05, 0.5 * abs(ideal_pnl_pct_total))
|
| 70 |
+
|
| 71 |
+
if gap < 0.0:
|
| 72 |
+
# Model exceeded ideal — bonus up to +2.0 (env clip caps at +1.0).
|
| 73 |
+
performance_factor = 1.0 + min(1.0, abs(gap) / scale)
|
| 74 |
+
elif gap <= scale:
|
| 75 |
+
# Close to ideal — high reward decaying linearly toward 0.
|
| 76 |
+
performance_factor = 1.0 - gap / scale
|
| 77 |
+
else:
|
| 78 |
+
# Far behind ideal — punishment down to -1.0.
|
| 79 |
+
performance_factor = -min(1.0, (gap - scale) / scale)
|
| 80 |
+
|
| 81 |
+
breakdown["ideal_pnl_pct_at_step"] = round(ideal_at_step, 6)
|
| 82 |
+
breakdown["gap"] = round(gap, 6)
|
| 83 |
+
breakdown["performance_factor"] = round(performance_factor, 6)
|
| 84 |
+
|
| 85 |
+
# Weighted combination
|
| 86 |
+
w_perf = settings.reward_weight_performance
|
| 87 |
+
w_inf = settings.reward_weight_inflation
|
| 88 |
+
weighted_perf = w_perf * performance_factor
|
| 89 |
+
weighted_inf = w_inf * inflation_factor
|
| 90 |
+
breakdown["weighted_performance"] = round(weighted_perf, 6)
|
| 91 |
+
breakdown["weighted_inflation"] = round(weighted_inf, 6)
|
| 92 |
+
|
| 93 |
+
score = weighted_perf + weighted_inf
|
| 94 |
|
|
|
|
| 95 |
if invalid:
|
| 96 |
+
score -= INVALID_ACTION_PENALTY
|
| 97 |
+
breakdown["invalid_action_penalty"] = -INVALID_ACTION_PENALTY
|
| 98 |
|
| 99 |
+
feedback = (
|
| 100 |
+
f"{action.side}({action.quantity}) -> portfolio {new_portfolio:.2f} "
|
| 101 |
+
f"(real {real_pnl_pct:+.3%}, ideal {ideal_at_step:+.3%}, gap {gap:+.3%})"
|
| 102 |
+
)
|
| 103 |
if invalid:
|
| 104 |
feedback += " [invalid: insufficient cash/position]"
|
| 105 |
+
|
| 106 |
+
return RewardResult(score=round(score, 6), breakdown=breakdown, feedback=feedback)
|
| 107 |
|
| 108 |
|
| 109 |
def compute_trajectory_bonus(
|
|
|
|
| 115 |
"""End-of-episode shaping: alpha bonus minus drawdown penalty."""
|
| 116 |
bonus = 0.0
|
| 117 |
|
|
|
|
| 118 |
if buy_and_hold_value > 0:
|
| 119 |
alpha = (final_portfolio - buy_and_hold_value) / buy_and_hold_value
|
| 120 |
if alpha >= 0.01:
|
| 121 |
bonus += min(0.10, alpha)
|
| 122 |
|
|
|
|
| 123 |
if portfolio_curve:
|
| 124 |
peak = portfolio_curve[0]
|
| 125 |
max_dd = 0.0
|
app/core/tasks.py
CHANGED
|
@@ -10,13 +10,17 @@ The 3 stable IDs are kept so the OpenEnv contract doesn't change.
|
|
| 10 |
"""
|
| 11 |
from __future__ import annotations
|
| 12 |
|
|
|
|
| 13 |
import logging
|
|
|
|
| 14 |
|
| 15 |
from app.core import corpus_tasks
|
| 16 |
from app.data import loader
|
| 17 |
|
| 18 |
logger = logging.getLogger(__name__)
|
| 19 |
|
|
|
|
|
|
|
| 20 |
|
| 21 |
TASK_META = {
|
| 22 |
"task_easy": {
|
|
@@ -54,6 +58,40 @@ def list_task_ids() -> list[str]:
|
|
| 54 |
return list(TASK_META.keys()) + corpus_tasks.list_corpus_task_ids()
|
| 55 |
|
| 56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
def get_task_definition(task_id: str) -> dict:
|
| 58 |
if corpus_tasks.is_corpus_task(task_id):
|
| 59 |
return corpus_tasks.get_corpus_task_definition(task_id)
|
|
@@ -67,6 +105,8 @@ def get_task_definition(task_id: str) -> dict:
|
|
| 67 |
raise RuntimeError(
|
| 68 |
f"No episode data for {task_id}. Run scripts/build_dataset.py."
|
| 69 |
)
|
|
|
|
|
|
|
| 70 |
return {
|
| 71 |
"task_id": task_id,
|
| 72 |
"description": meta["description"],
|
|
@@ -74,5 +114,7 @@ def get_task_definition(task_id: str) -> dict:
|
|
| 74 |
"starting_cash": meta["starting_cash"],
|
| 75 |
"fundamentals": meta["fundamentals"],
|
| 76 |
"dates": rows["date"].tolist(),
|
| 77 |
-
"prices":
|
|
|
|
|
|
|
| 78 |
}
|
|
|
|
| 10 |
"""
|
| 11 |
from __future__ import annotations
|
| 12 |
|
| 13 |
+
import json
|
| 14 |
import logging
|
| 15 |
+
from pathlib import Path
|
| 16 |
|
| 17 |
from app.core import corpus_tasks
|
| 18 |
from app.data import loader
|
| 19 |
|
| 20 |
logger = logging.getLogger(__name__)
|
| 21 |
|
| 22 |
+
IDEAL_PROFITS_DIR = Path(__file__).resolve().parent.parent.parent / "data" / "ideal_profits"
|
| 23 |
+
|
| 24 |
|
| 25 |
TASK_META = {
|
| 26 |
"task_easy": {
|
|
|
|
| 58 |
return list(TASK_META.keys()) + corpus_tasks.list_corpus_task_ids()
|
| 59 |
|
| 60 |
|
| 61 |
+
def _load_ideal_profits(task_id: str, n_steps: int) -> tuple[list[float], float]:
|
| 62 |
+
"""Load per-step ideal PnL series for a task. Returns (series, total).
|
| 63 |
+
|
| 64 |
+
Falls back to a flat zero series if the sidecar is missing — the grader
|
| 65 |
+
still runs but the performance component collapses to zero gap signal.
|
| 66 |
+
"""
|
| 67 |
+
path = IDEAL_PROFITS_DIR / f"{task_id}.json"
|
| 68 |
+
if not path.exists():
|
| 69 |
+
logger.warning(
|
| 70 |
+
"Ideal-profit sidecar missing for %s at %s. "
|
| 71 |
+
"Run `python scripts/build_ideal_profit.py`. "
|
| 72 |
+
"Performance component will be flat for this episode.",
|
| 73 |
+
task_id,
|
| 74 |
+
path,
|
| 75 |
+
)
|
| 76 |
+
return [0.0] * n_steps, 0.0
|
| 77 |
+
|
| 78 |
+
payload = json.loads(path.read_text())
|
| 79 |
+
series = [float(v) for v in payload.get("ideal_pnl_pct_series", [])]
|
| 80 |
+
total = float(payload.get("ideal_pnl_pct_total", series[-1] if series else 0.0))
|
| 81 |
+
|
| 82 |
+
if len(series) != n_steps:
|
| 83 |
+
logger.warning(
|
| 84 |
+
"Ideal-profit length mismatch for %s: sidecar=%d, episode=%d. "
|
| 85 |
+
"Re-run scripts/build_ideal_profit.py.",
|
| 86 |
+
task_id, len(series), n_steps,
|
| 87 |
+
)
|
| 88 |
+
if len(series) < n_steps:
|
| 89 |
+
series = series + [series[-1] if series else 0.0] * (n_steps - len(series))
|
| 90 |
+
else:
|
| 91 |
+
series = series[:n_steps]
|
| 92 |
+
return series, total
|
| 93 |
+
|
| 94 |
+
|
| 95 |
def get_task_definition(task_id: str) -> dict:
|
| 96 |
if corpus_tasks.is_corpus_task(task_id):
|
| 97 |
return corpus_tasks.get_corpus_task_definition(task_id)
|
|
|
|
| 105 |
raise RuntimeError(
|
| 106 |
f"No episode data for {task_id}. Run scripts/build_dataset.py."
|
| 107 |
)
|
| 108 |
+
prices = rows["close"].astype(float).tolist()
|
| 109 |
+
ideal_series, ideal_total = _load_ideal_profits(task_id, len(prices))
|
| 110 |
return {
|
| 111 |
"task_id": task_id,
|
| 112 |
"description": meta["description"],
|
|
|
|
| 114 |
"starting_cash": meta["starting_cash"],
|
| 115 |
"fundamentals": meta["fundamentals"],
|
| 116 |
"dates": rows["date"].tolist(),
|
| 117 |
+
"prices": prices,
|
| 118 |
+
"ideal_pnl_pct_series": ideal_series,
|
| 119 |
+
"ideal_pnl_pct_total": ideal_total,
|
| 120 |
}
|
app/main.py
CHANGED
|
@@ -6,15 +6,20 @@ import logging
|
|
| 6 |
import sys
|
| 7 |
import time
|
| 8 |
import traceback
|
|
|
|
| 9 |
|
| 10 |
from fastapi import FastAPI, Request
|
| 11 |
from fastapi.middleware.cors import CORSMiddleware
|
| 12 |
from fastapi.responses import JSONResponse
|
|
|
|
| 13 |
|
| 14 |
from app.api.router import api_router
|
| 15 |
from app.api.frontend import router as frontend_router
|
| 16 |
from app.config import settings
|
| 17 |
|
|
|
|
|
|
|
|
|
|
| 18 |
logging.basicConfig(
|
| 19 |
level=logging.INFO,
|
| 20 |
format="%(levelname)s:\t%(name)s - %(message)s",
|
|
@@ -59,6 +64,18 @@ def create_app() -> FastAPI:
|
|
| 59 |
|
| 60 |
app.include_router(api_router)
|
| 61 |
app.include_router(frontend_router)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
return app
|
| 63 |
|
| 64 |
|
|
|
|
| 6 |
import sys
|
| 7 |
import time
|
| 8 |
import traceback
|
| 9 |
+
from pathlib import Path
|
| 10 |
|
| 11 |
from fastapi import FastAPI, Request
|
| 12 |
from fastapi.middleware.cors import CORSMiddleware
|
| 13 |
from fastapi.responses import JSONResponse
|
| 14 |
+
from fastapi.staticfiles import StaticFiles
|
| 15 |
|
| 16 |
from app.api.router import api_router
|
| 17 |
from app.api.frontend import router as frontend_router
|
| 18 |
from app.config import settings
|
| 19 |
|
| 20 |
+
FRONTEND_DIST = Path(__file__).resolve().parents[1] / "frontend" / "dist"
|
| 21 |
+
TRAINING_RUNS = Path(__file__).resolve().parents[1] / "training" / "runs"
|
| 22 |
+
|
| 23 |
logging.basicConfig(
|
| 24 |
level=logging.INFO,
|
| 25 |
format="%(levelname)s:\t%(name)s - %(message)s",
|
|
|
|
| 64 |
|
| 65 |
app.include_router(api_router)
|
| 66 |
app.include_router(frontend_router)
|
| 67 |
+
|
| 68 |
+
assets_dir = FRONTEND_DIST / "assets"
|
| 69 |
+
if assets_dir.is_dir():
|
| 70 |
+
app.mount("/assets", StaticFiles(directory=str(assets_dir)), name="assets")
|
| 71 |
+
|
| 72 |
+
if TRAINING_RUNS.is_dir():
|
| 73 |
+
app.mount(
|
| 74 |
+
"/training/runs",
|
| 75 |
+
StaticFiles(directory=str(TRAINING_RUNS)),
|
| 76 |
+
name="training-runs",
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
return app
|
| 80 |
|
| 81 |
|
data/ideal_profits/task_easy.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"task_id": "task_easy",
|
| 3 |
+
"ticker": "AAPL",
|
| 4 |
+
"starting_cash": 10000.0,
|
| 5 |
+
"transaction_cost_rate": 0.001,
|
| 6 |
+
"ideal_pnl_pct_series": [
|
| 7 |
+
0.0,
|
| 8 |
+
0.0,
|
| 9 |
+
0.0,
|
| 10 |
+
0.0,
|
| 11 |
+
-0.000984,
|
| 12 |
+
0.003252,
|
| 13 |
+
0.003252,
|
| 14 |
+
0.003252,
|
| 15 |
+
0.002257,
|
| 16 |
+
0.010604,
|
| 17 |
+
0.010604,
|
| 18 |
+
0.010604,
|
| 19 |
+
0.009595,
|
| 20 |
+
0.012437,
|
| 21 |
+
0.020267,
|
| 22 |
+
0.028329,
|
| 23 |
+
0.04984,
|
| 24 |
+
0.0488,
|
| 25 |
+
0.061956,
|
| 26 |
+
0.071279,
|
| 27 |
+
0.094465,
|
| 28 |
+
0.115292,
|
| 29 |
+
0.11659,
|
| 30 |
+
0.125972,
|
| 31 |
+
0.126268,
|
| 32 |
+
0.126268,
|
| 33 |
+
0.12515,
|
| 34 |
+
0.129056,
|
| 35 |
+
0.13536,
|
| 36 |
+
0.13536,
|
| 37 |
+
0.134227,
|
| 38 |
+
0.14303,
|
| 39 |
+
0.141892,
|
| 40 |
+
0.161132,
|
| 41 |
+
0.167119,
|
| 42 |
+
0.167119,
|
| 43 |
+
0.165953,
|
| 44 |
+
0.171715,
|
| 45 |
+
0.179179,
|
| 46 |
+
0.179179,
|
| 47 |
+
0.178003,
|
| 48 |
+
0.179797,
|
| 49 |
+
0.182203
|
| 50 |
+
],
|
| 51 |
+
"ideal_pnl_pct_total": 0.182203,
|
| 52 |
+
"ideal_portfolio_curve": [
|
| 53 |
+
10000.0,
|
| 54 |
+
10000.0,
|
| 55 |
+
10000.0,
|
| 56 |
+
10000.0,
|
| 57 |
+
9990.1632,
|
| 58 |
+
10032.5241,
|
| 59 |
+
10032.5241,
|
| 60 |
+
10032.5241,
|
| 61 |
+
10022.5678,
|
| 62 |
+
10106.0388,
|
| 63 |
+
10106.0388,
|
| 64 |
+
10106.0388,
|
| 65 |
+
10095.9468,
|
| 66 |
+
10124.3672,
|
| 67 |
+
10202.6666,
|
| 68 |
+
10283.2866,
|
| 69 |
+
10498.4016,
|
| 70 |
+
10487.9952,
|
| 71 |
+
10619.5649,
|
| 72 |
+
10712.785,
|
| 73 |
+
10944.6546,
|
| 74 |
+
11152.9245,
|
| 75 |
+
11165.9046,
|
| 76 |
+
11259.7153,
|
| 77 |
+
11262.6824,
|
| 78 |
+
11262.6824,
|
| 79 |
+
11251.4961,
|
| 80 |
+
11290.5558,
|
| 81 |
+
11353.5966,
|
| 82 |
+
11353.5966,
|
| 83 |
+
11342.273,
|
| 84 |
+
11430.2998,
|
| 85 |
+
11418.9242,
|
| 86 |
+
11611.3246,
|
| 87 |
+
11671.1854,
|
| 88 |
+
11671.1854,
|
| 89 |
+
11659.5321,
|
| 90 |
+
11717.1522,
|
| 91 |
+
11791.7854,
|
| 92 |
+
11791.7854,
|
| 93 |
+
11780.0257,
|
| 94 |
+
11797.9664,
|
| 95 |
+
11822.0332
|
| 96 |
+
]
|
| 97 |
+
}
|
data/ideal_profits/task_hard.json
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"task_id": "task_hard",
|
| 3 |
+
"ticker": "META",
|
| 4 |
+
"starting_cash": 10000.0,
|
| 5 |
+
"transaction_cost_rate": 0.001,
|
| 6 |
+
"ideal_pnl_pct_series": [
|
| 7 |
+
0.0,
|
| 8 |
+
0.0,
|
| 9 |
+
-0.000999,
|
| 10 |
+
0.010656,
|
| 11 |
+
0.021177,
|
| 12 |
+
0.064779,
|
| 13 |
+
0.064779,
|
| 14 |
+
0.064779,
|
| 15 |
+
0.064779,
|
| 16 |
+
0.064779,
|
| 17 |
+
0.063725,
|
| 18 |
+
0.075116,
|
| 19 |
+
0.075116,
|
| 20 |
+
0.07405,
|
| 21 |
+
0.078229,
|
| 22 |
+
0.078229,
|
| 23 |
+
0.078229,
|
| 24 |
+
0.077153,
|
| 25 |
+
0.133701,
|
| 26 |
+
0.133701,
|
| 27 |
+
0.132574,
|
| 28 |
+
0.156894,
|
| 29 |
+
0.16959,
|
| 30 |
+
0.168423,
|
| 31 |
+
0.168011,
|
| 32 |
+
0.16685,
|
| 33 |
+
0.168644,
|
| 34 |
+
0.168644,
|
| 35 |
+
0.167483,
|
| 36 |
+
0.191687,
|
| 37 |
+
0.190508,
|
| 38 |
+
0.256965,
|
| 39 |
+
0.255717,
|
| 40 |
+
0.258506,
|
| 41 |
+
0.258506,
|
| 42 |
+
0.258506,
|
| 43 |
+
0.257261,
|
| 44 |
+
0.330725,
|
| 45 |
+
0.330725,
|
| 46 |
+
0.329403,
|
| 47 |
+
0.345073,
|
| 48 |
+
0.345073
|
| 49 |
+
],
|
| 50 |
+
"ideal_pnl_pct_total": 0.345073,
|
| 51 |
+
"ideal_portfolio_curve": [
|
| 52 |
+
10000.0,
|
| 53 |
+
10000.0,
|
| 54 |
+
9990.012,
|
| 55 |
+
10106.5624,
|
| 56 |
+
10211.7722,
|
| 57 |
+
10647.7856,
|
| 58 |
+
10647.7856,
|
| 59 |
+
10647.7856,
|
| 60 |
+
10647.7856,
|
| 61 |
+
10647.7856,
|
| 62 |
+
10637.2527,
|
| 63 |
+
10751.156,
|
| 64 |
+
10751.156,
|
| 65 |
+
10740.497,
|
| 66 |
+
10782.2865,
|
| 67 |
+
10782.2865,
|
| 68 |
+
10782.2865,
|
| 69 |
+
10771.5345,
|
| 70 |
+
11337.0062,
|
| 71 |
+
11337.0062,
|
| 72 |
+
11325.7448,
|
| 73 |
+
11568.9354,
|
| 74 |
+
11695.902,
|
| 75 |
+
11684.2277,
|
| 76 |
+
11680.1068,
|
| 77 |
+
11668.4966,
|
| 78 |
+
11686.4366,
|
| 79 |
+
11686.4366,
|
| 80 |
+
11674.8341,
|
| 81 |
+
11916.8671,
|
| 82 |
+
11905.0784,
|
| 83 |
+
12569.6519,
|
| 84 |
+
12557.1687,
|
| 85 |
+
12585.0644,
|
| 86 |
+
12585.0644,
|
| 87 |
+
12585.0644,
|
| 88 |
+
12572.6112,
|
| 89 |
+
13307.2496,
|
| 90 |
+
13307.2496,
|
| 91 |
+
13294.0277,
|
| 92 |
+
13450.735,
|
| 93 |
+
13450.735
|
| 94 |
+
]
|
| 95 |
+
}
|
data/ideal_profits/task_medium.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"task_id": "task_medium",
|
| 3 |
+
"ticker": "INTC",
|
| 4 |
+
"starting_cash": 10000.0,
|
| 5 |
+
"transaction_cost_rate": 0.001,
|
| 6 |
+
"ideal_pnl_pct_series": [
|
| 7 |
+
0.0,
|
| 8 |
+
0.0,
|
| 9 |
+
-0.000998,
|
| 10 |
+
-0.000572,
|
| 11 |
+
0.031624,
|
| 12 |
+
0.031624,
|
| 13 |
+
0.030594,
|
| 14 |
+
0.033249,
|
| 15 |
+
0.033249,
|
| 16 |
+
0.033249,
|
| 17 |
+
0.032217,
|
| 18 |
+
0.047449,
|
| 19 |
+
0.079033,
|
| 20 |
+
0.080601,
|
| 21 |
+
0.095609,
|
| 22 |
+
0.100089,
|
| 23 |
+
0.109283,
|
| 24 |
+
0.108179,
|
| 25 |
+
0.111877,
|
| 26 |
+
0.110769,
|
| 27 |
+
0.114897,
|
| 28 |
+
0.121003,
|
| 29 |
+
0.119886,
|
| 30 |
+
0.12322,
|
| 31 |
+
0.1221,
|
| 32 |
+
0.122027,
|
| 33 |
+
0.120909,
|
| 34 |
+
0.142213,
|
| 35 |
+
0.15999,
|
| 36 |
+
0.158834,
|
| 37 |
+
0.185253,
|
| 38 |
+
0.185253,
|
| 39 |
+
0.18407,
|
| 40 |
+
0.210331,
|
| 41 |
+
0.210331,
|
| 42 |
+
0.209123,
|
| 43 |
+
0.208196,
|
| 44 |
+
0.208196,
|
| 45 |
+
0.208196,
|
| 46 |
+
0.206991,
|
| 47 |
+
0.236178
|
| 48 |
+
],
|
| 49 |
+
"ideal_pnl_pct_total": 0.236178,
|
| 50 |
+
"ideal_portfolio_curve": [
|
| 51 |
+
10000.0,
|
| 52 |
+
10000.0,
|
| 53 |
+
9990.0167,
|
| 54 |
+
9994.2768,
|
| 55 |
+
10316.2372,
|
| 56 |
+
10316.2372,
|
| 57 |
+
10305.9362,
|
| 58 |
+
10332.488,
|
| 59 |
+
10332.488,
|
| 60 |
+
10332.488,
|
| 61 |
+
10322.1705,
|
| 62 |
+
10474.4906,
|
| 63 |
+
10790.3306,
|
| 64 |
+
10806.0105,
|
| 65 |
+
10956.0901,
|
| 66 |
+
11000.8902,
|
| 67 |
+
11092.8308,
|
| 68 |
+
11081.7874,
|
| 69 |
+
11118.7655,
|
| 70 |
+
11107.6922,
|
| 71 |
+
11148.9731,
|
| 72 |
+
11210.0259,
|
| 73 |
+
11198.8647,
|
| 74 |
+
11232.1995,
|
| 75 |
+
11221.0016,
|
| 76 |
+
11220.2725,
|
| 77 |
+
11209.095,
|
| 78 |
+
11422.1254,
|
| 79 |
+
11599.9048,
|
| 80 |
+
11588.3379,
|
| 81 |
+
11852.5347,
|
| 82 |
+
11852.5347,
|
| 83 |
+
11840.6999,
|
| 84 |
+
12103.3111,
|
| 85 |
+
12103.3111,
|
| 86 |
+
12091.2337,
|
| 87 |
+
12081.9641,
|
| 88 |
+
12081.9641,
|
| 89 |
+
12081.9641,
|
| 90 |
+
12069.913,
|
| 91 |
+
12361.7769
|
| 92 |
+
]
|
| 93 |
+
}
|
frontend/.gitignore
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
node_modules/
|
| 2 |
+
build/
|
| 3 |
+
dist/
|
| 4 |
+
coverage/
|
| 5 |
+
.DS_Store
|
| 6 |
+
*.log
|
| 7 |
+
.env*
|
| 8 |
+
!.env.example
|
frontend/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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>Stocker AI | Autonomous Trading Terminal</title>
|
| 7 |
+
</head>
|
| 8 |
+
<body>
|
| 9 |
+
<div id="root"></div>
|
| 10 |
+
<script type="module" src="/src/main.tsx"></script>
|
| 11 |
+
</body>
|
| 12 |
+
</html>
|
| 13 |
+
|
frontend/package-lock.json
ADDED
|
@@ -0,0 +1,2605 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "stocker-ai-frontend",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "stocker-ai-frontend",
|
| 9 |
+
"version": "0.1.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"lightweight-charts": "^4.2.3",
|
| 12 |
+
"lucide-react": "^0.546.0",
|
| 13 |
+
"motion": "^12.23.24",
|
| 14 |
+
"react": "^19.0.0",
|
| 15 |
+
"react-dom": "^19.0.0"
|
| 16 |
+
},
|
| 17 |
+
"devDependencies": {
|
| 18 |
+
"@tailwindcss/vite": "^4.1.14",
|
| 19 |
+
"@types/node": "^22.14.0",
|
| 20 |
+
"@types/react": "^19.2.14",
|
| 21 |
+
"@types/react-dom": "^19.2.3",
|
| 22 |
+
"@vitejs/plugin-react": "^5.0.4",
|
| 23 |
+
"autoprefixer": "^10.4.21",
|
| 24 |
+
"tailwindcss": "^4.1.14",
|
| 25 |
+
"typescript": "~5.8.2",
|
| 26 |
+
"vite": "^6.2.0"
|
| 27 |
+
}
|
| 28 |
+
},
|
| 29 |
+
"node_modules/@babel/code-frame": {
|
| 30 |
+
"version": "7.29.0",
|
| 31 |
+
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
| 32 |
+
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
| 33 |
+
"dev": true,
|
| 34 |
+
"license": "MIT",
|
| 35 |
+
"dependencies": {
|
| 36 |
+
"@babel/helper-validator-identifier": "^7.28.5",
|
| 37 |
+
"js-tokens": "^4.0.0",
|
| 38 |
+
"picocolors": "^1.1.1"
|
| 39 |
+
},
|
| 40 |
+
"engines": {
|
| 41 |
+
"node": ">=6.9.0"
|
| 42 |
+
}
|
| 43 |
+
},
|
| 44 |
+
"node_modules/@babel/compat-data": {
|
| 45 |
+
"version": "7.29.0",
|
| 46 |
+
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
|
| 47 |
+
"integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
|
| 48 |
+
"dev": true,
|
| 49 |
+
"license": "MIT",
|
| 50 |
+
"engines": {
|
| 51 |
+
"node": ">=6.9.0"
|
| 52 |
+
}
|
| 53 |
+
},
|
| 54 |
+
"node_modules/@babel/core": {
|
| 55 |
+
"version": "7.29.0",
|
| 56 |
+
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
|
| 57 |
+
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
| 58 |
+
"dev": true,
|
| 59 |
+
"license": "MIT",
|
| 60 |
+
"dependencies": {
|
| 61 |
+
"@babel/code-frame": "^7.29.0",
|
| 62 |
+
"@babel/generator": "^7.29.0",
|
| 63 |
+
"@babel/helper-compilation-targets": "^7.28.6",
|
| 64 |
+
"@babel/helper-module-transforms": "^7.28.6",
|
| 65 |
+
"@babel/helpers": "^7.28.6",
|
| 66 |
+
"@babel/parser": "^7.29.0",
|
| 67 |
+
"@babel/template": "^7.28.6",
|
| 68 |
+
"@babel/traverse": "^7.29.0",
|
| 69 |
+
"@babel/types": "^7.29.0",
|
| 70 |
+
"@jridgewell/remapping": "^2.3.5",
|
| 71 |
+
"convert-source-map": "^2.0.0",
|
| 72 |
+
"debug": "^4.1.0",
|
| 73 |
+
"gensync": "^1.0.0-beta.2",
|
| 74 |
+
"json5": "^2.2.3",
|
| 75 |
+
"semver": "^6.3.1"
|
| 76 |
+
},
|
| 77 |
+
"engines": {
|
| 78 |
+
"node": ">=6.9.0"
|
| 79 |
+
},
|
| 80 |
+
"funding": {
|
| 81 |
+
"type": "opencollective",
|
| 82 |
+
"url": "https://opencollective.com/babel"
|
| 83 |
+
}
|
| 84 |
+
},
|
| 85 |
+
"node_modules/@babel/generator": {
|
| 86 |
+
"version": "7.29.1",
|
| 87 |
+
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
| 88 |
+
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
| 89 |
+
"dev": true,
|
| 90 |
+
"license": "MIT",
|
| 91 |
+
"dependencies": {
|
| 92 |
+
"@babel/parser": "^7.29.0",
|
| 93 |
+
"@babel/types": "^7.29.0",
|
| 94 |
+
"@jridgewell/gen-mapping": "^0.3.12",
|
| 95 |
+
"@jridgewell/trace-mapping": "^0.3.28",
|
| 96 |
+
"jsesc": "^3.0.2"
|
| 97 |
+
},
|
| 98 |
+
"engines": {
|
| 99 |
+
"node": ">=6.9.0"
|
| 100 |
+
}
|
| 101 |
+
},
|
| 102 |
+
"node_modules/@babel/helper-compilation-targets": {
|
| 103 |
+
"version": "7.28.6",
|
| 104 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
|
| 105 |
+
"integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
|
| 106 |
+
"dev": true,
|
| 107 |
+
"license": "MIT",
|
| 108 |
+
"dependencies": {
|
| 109 |
+
"@babel/compat-data": "^7.28.6",
|
| 110 |
+
"@babel/helper-validator-option": "^7.27.1",
|
| 111 |
+
"browserslist": "^4.24.0",
|
| 112 |
+
"lru-cache": "^5.1.1",
|
| 113 |
+
"semver": "^6.3.1"
|
| 114 |
+
},
|
| 115 |
+
"engines": {
|
| 116 |
+
"node": ">=6.9.0"
|
| 117 |
+
}
|
| 118 |
+
},
|
| 119 |
+
"node_modules/@babel/helper-globals": {
|
| 120 |
+
"version": "7.28.0",
|
| 121 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
| 122 |
+
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
| 123 |
+
"dev": true,
|
| 124 |
+
"license": "MIT",
|
| 125 |
+
"engines": {
|
| 126 |
+
"node": ">=6.9.0"
|
| 127 |
+
}
|
| 128 |
+
},
|
| 129 |
+
"node_modules/@babel/helper-module-imports": {
|
| 130 |
+
"version": "7.28.6",
|
| 131 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
| 132 |
+
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
|
| 133 |
+
"dev": true,
|
| 134 |
+
"license": "MIT",
|
| 135 |
+
"dependencies": {
|
| 136 |
+
"@babel/traverse": "^7.28.6",
|
| 137 |
+
"@babel/types": "^7.28.6"
|
| 138 |
+
},
|
| 139 |
+
"engines": {
|
| 140 |
+
"node": ">=6.9.0"
|
| 141 |
+
}
|
| 142 |
+
},
|
| 143 |
+
"node_modules/@babel/helper-module-transforms": {
|
| 144 |
+
"version": "7.28.6",
|
| 145 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
|
| 146 |
+
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
|
| 147 |
+
"dev": true,
|
| 148 |
+
"license": "MIT",
|
| 149 |
+
"dependencies": {
|
| 150 |
+
"@babel/helper-module-imports": "^7.28.6",
|
| 151 |
+
"@babel/helper-validator-identifier": "^7.28.5",
|
| 152 |
+
"@babel/traverse": "^7.28.6"
|
| 153 |
+
},
|
| 154 |
+
"engines": {
|
| 155 |
+
"node": ">=6.9.0"
|
| 156 |
+
},
|
| 157 |
+
"peerDependencies": {
|
| 158 |
+
"@babel/core": "^7.0.0"
|
| 159 |
+
}
|
| 160 |
+
},
|
| 161 |
+
"node_modules/@babel/helper-plugin-utils": {
|
| 162 |
+
"version": "7.28.6",
|
| 163 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
|
| 164 |
+
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
|
| 165 |
+
"dev": true,
|
| 166 |
+
"license": "MIT",
|
| 167 |
+
"engines": {
|
| 168 |
+
"node": ">=6.9.0"
|
| 169 |
+
}
|
| 170 |
+
},
|
| 171 |
+
"node_modules/@babel/helper-string-parser": {
|
| 172 |
+
"version": "7.27.1",
|
| 173 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
| 174 |
+
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
| 175 |
+
"dev": true,
|
| 176 |
+
"license": "MIT",
|
| 177 |
+
"engines": {
|
| 178 |
+
"node": ">=6.9.0"
|
| 179 |
+
}
|
| 180 |
+
},
|
| 181 |
+
"node_modules/@babel/helper-validator-identifier": {
|
| 182 |
+
"version": "7.28.5",
|
| 183 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
| 184 |
+
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
| 185 |
+
"dev": true,
|
| 186 |
+
"license": "MIT",
|
| 187 |
+
"engines": {
|
| 188 |
+
"node": ">=6.9.0"
|
| 189 |
+
}
|
| 190 |
+
},
|
| 191 |
+
"node_modules/@babel/helper-validator-option": {
|
| 192 |
+
"version": "7.27.1",
|
| 193 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
|
| 194 |
+
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
|
| 195 |
+
"dev": true,
|
| 196 |
+
"license": "MIT",
|
| 197 |
+
"engines": {
|
| 198 |
+
"node": ">=6.9.0"
|
| 199 |
+
}
|
| 200 |
+
},
|
| 201 |
+
"node_modules/@babel/helpers": {
|
| 202 |
+
"version": "7.29.2",
|
| 203 |
+
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
|
| 204 |
+
"integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
|
| 205 |
+
"dev": true,
|
| 206 |
+
"license": "MIT",
|
| 207 |
+
"dependencies": {
|
| 208 |
+
"@babel/template": "^7.28.6",
|
| 209 |
+
"@babel/types": "^7.29.0"
|
| 210 |
+
},
|
| 211 |
+
"engines": {
|
| 212 |
+
"node": ">=6.9.0"
|
| 213 |
+
}
|
| 214 |
+
},
|
| 215 |
+
"node_modules/@babel/parser": {
|
| 216 |
+
"version": "7.29.2",
|
| 217 |
+
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
|
| 218 |
+
"integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
|
| 219 |
+
"dev": true,
|
| 220 |
+
"license": "MIT",
|
| 221 |
+
"dependencies": {
|
| 222 |
+
"@babel/types": "^7.29.0"
|
| 223 |
+
},
|
| 224 |
+
"bin": {
|
| 225 |
+
"parser": "bin/babel-parser.js"
|
| 226 |
+
},
|
| 227 |
+
"engines": {
|
| 228 |
+
"node": ">=6.0.0"
|
| 229 |
+
}
|
| 230 |
+
},
|
| 231 |
+
"node_modules/@babel/plugin-transform-react-jsx-self": {
|
| 232 |
+
"version": "7.27.1",
|
| 233 |
+
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
|
| 234 |
+
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
|
| 235 |
+
"dev": true,
|
| 236 |
+
"license": "MIT",
|
| 237 |
+
"dependencies": {
|
| 238 |
+
"@babel/helper-plugin-utils": "^7.27.1"
|
| 239 |
+
},
|
| 240 |
+
"engines": {
|
| 241 |
+
"node": ">=6.9.0"
|
| 242 |
+
},
|
| 243 |
+
"peerDependencies": {
|
| 244 |
+
"@babel/core": "^7.0.0-0"
|
| 245 |
+
}
|
| 246 |
+
},
|
| 247 |
+
"node_modules/@babel/plugin-transform-react-jsx-source": {
|
| 248 |
+
"version": "7.27.1",
|
| 249 |
+
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
|
| 250 |
+
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
|
| 251 |
+
"dev": true,
|
| 252 |
+
"license": "MIT",
|
| 253 |
+
"dependencies": {
|
| 254 |
+
"@babel/helper-plugin-utils": "^7.27.1"
|
| 255 |
+
},
|
| 256 |
+
"engines": {
|
| 257 |
+
"node": ">=6.9.0"
|
| 258 |
+
},
|
| 259 |
+
"peerDependencies": {
|
| 260 |
+
"@babel/core": "^7.0.0-0"
|
| 261 |
+
}
|
| 262 |
+
},
|
| 263 |
+
"node_modules/@babel/template": {
|
| 264 |
+
"version": "7.28.6",
|
| 265 |
+
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
| 266 |
+
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
| 267 |
+
"dev": true,
|
| 268 |
+
"license": "MIT",
|
| 269 |
+
"dependencies": {
|
| 270 |
+
"@babel/code-frame": "^7.28.6",
|
| 271 |
+
"@babel/parser": "^7.28.6",
|
| 272 |
+
"@babel/types": "^7.28.6"
|
| 273 |
+
},
|
| 274 |
+
"engines": {
|
| 275 |
+
"node": ">=6.9.0"
|
| 276 |
+
}
|
| 277 |
+
},
|
| 278 |
+
"node_modules/@babel/traverse": {
|
| 279 |
+
"version": "7.29.0",
|
| 280 |
+
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
| 281 |
+
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
| 282 |
+
"dev": true,
|
| 283 |
+
"license": "MIT",
|
| 284 |
+
"dependencies": {
|
| 285 |
+
"@babel/code-frame": "^7.29.0",
|
| 286 |
+
"@babel/generator": "^7.29.0",
|
| 287 |
+
"@babel/helper-globals": "^7.28.0",
|
| 288 |
+
"@babel/parser": "^7.29.0",
|
| 289 |
+
"@babel/template": "^7.28.6",
|
| 290 |
+
"@babel/types": "^7.29.0",
|
| 291 |
+
"debug": "^4.3.1"
|
| 292 |
+
},
|
| 293 |
+
"engines": {
|
| 294 |
+
"node": ">=6.9.0"
|
| 295 |
+
}
|
| 296 |
+
},
|
| 297 |
+
"node_modules/@babel/types": {
|
| 298 |
+
"version": "7.29.0",
|
| 299 |
+
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
| 300 |
+
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
| 301 |
+
"dev": true,
|
| 302 |
+
"license": "MIT",
|
| 303 |
+
"dependencies": {
|
| 304 |
+
"@babel/helper-string-parser": "^7.27.1",
|
| 305 |
+
"@babel/helper-validator-identifier": "^7.28.5"
|
| 306 |
+
},
|
| 307 |
+
"engines": {
|
| 308 |
+
"node": ">=6.9.0"
|
| 309 |
+
}
|
| 310 |
+
},
|
| 311 |
+
"node_modules/@esbuild/aix-ppc64": {
|
| 312 |
+
"version": "0.25.12",
|
| 313 |
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
| 314 |
+
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
|
| 315 |
+
"cpu": [
|
| 316 |
+
"ppc64"
|
| 317 |
+
],
|
| 318 |
+
"dev": true,
|
| 319 |
+
"license": "MIT",
|
| 320 |
+
"optional": true,
|
| 321 |
+
"os": [
|
| 322 |
+
"aix"
|
| 323 |
+
],
|
| 324 |
+
"engines": {
|
| 325 |
+
"node": ">=18"
|
| 326 |
+
}
|
| 327 |
+
},
|
| 328 |
+
"node_modules/@esbuild/android-arm": {
|
| 329 |
+
"version": "0.25.12",
|
| 330 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
|
| 331 |
+
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
|
| 332 |
+
"cpu": [
|
| 333 |
+
"arm"
|
| 334 |
+
],
|
| 335 |
+
"dev": true,
|
| 336 |
+
"license": "MIT",
|
| 337 |
+
"optional": true,
|
| 338 |
+
"os": [
|
| 339 |
+
"android"
|
| 340 |
+
],
|
| 341 |
+
"engines": {
|
| 342 |
+
"node": ">=18"
|
| 343 |
+
}
|
| 344 |
+
},
|
| 345 |
+
"node_modules/@esbuild/android-arm64": {
|
| 346 |
+
"version": "0.25.12",
|
| 347 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
|
| 348 |
+
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
|
| 349 |
+
"cpu": [
|
| 350 |
+
"arm64"
|
| 351 |
+
],
|
| 352 |
+
"dev": true,
|
| 353 |
+
"license": "MIT",
|
| 354 |
+
"optional": true,
|
| 355 |
+
"os": [
|
| 356 |
+
"android"
|
| 357 |
+
],
|
| 358 |
+
"engines": {
|
| 359 |
+
"node": ">=18"
|
| 360 |
+
}
|
| 361 |
+
},
|
| 362 |
+
"node_modules/@esbuild/android-x64": {
|
| 363 |
+
"version": "0.25.12",
|
| 364 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
|
| 365 |
+
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
|
| 366 |
+
"cpu": [
|
| 367 |
+
"x64"
|
| 368 |
+
],
|
| 369 |
+
"dev": true,
|
| 370 |
+
"license": "MIT",
|
| 371 |
+
"optional": true,
|
| 372 |
+
"os": [
|
| 373 |
+
"android"
|
| 374 |
+
],
|
| 375 |
+
"engines": {
|
| 376 |
+
"node": ">=18"
|
| 377 |
+
}
|
| 378 |
+
},
|
| 379 |
+
"node_modules/@esbuild/darwin-arm64": {
|
| 380 |
+
"version": "0.25.12",
|
| 381 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
|
| 382 |
+
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
|
| 383 |
+
"cpu": [
|
| 384 |
+
"arm64"
|
| 385 |
+
],
|
| 386 |
+
"dev": true,
|
| 387 |
+
"license": "MIT",
|
| 388 |
+
"optional": true,
|
| 389 |
+
"os": [
|
| 390 |
+
"darwin"
|
| 391 |
+
],
|
| 392 |
+
"engines": {
|
| 393 |
+
"node": ">=18"
|
| 394 |
+
}
|
| 395 |
+
},
|
| 396 |
+
"node_modules/@esbuild/darwin-x64": {
|
| 397 |
+
"version": "0.25.12",
|
| 398 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
|
| 399 |
+
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
|
| 400 |
+
"cpu": [
|
| 401 |
+
"x64"
|
| 402 |
+
],
|
| 403 |
+
"dev": true,
|
| 404 |
+
"license": "MIT",
|
| 405 |
+
"optional": true,
|
| 406 |
+
"os": [
|
| 407 |
+
"darwin"
|
| 408 |
+
],
|
| 409 |
+
"engines": {
|
| 410 |
+
"node": ">=18"
|
| 411 |
+
}
|
| 412 |
+
},
|
| 413 |
+
"node_modules/@esbuild/freebsd-arm64": {
|
| 414 |
+
"version": "0.25.12",
|
| 415 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
|
| 416 |
+
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
|
| 417 |
+
"cpu": [
|
| 418 |
+
"arm64"
|
| 419 |
+
],
|
| 420 |
+
"dev": true,
|
| 421 |
+
"license": "MIT",
|
| 422 |
+
"optional": true,
|
| 423 |
+
"os": [
|
| 424 |
+
"freebsd"
|
| 425 |
+
],
|
| 426 |
+
"engines": {
|
| 427 |
+
"node": ">=18"
|
| 428 |
+
}
|
| 429 |
+
},
|
| 430 |
+
"node_modules/@esbuild/freebsd-x64": {
|
| 431 |
+
"version": "0.25.12",
|
| 432 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
|
| 433 |
+
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
|
| 434 |
+
"cpu": [
|
| 435 |
+
"x64"
|
| 436 |
+
],
|
| 437 |
+
"dev": true,
|
| 438 |
+
"license": "MIT",
|
| 439 |
+
"optional": true,
|
| 440 |
+
"os": [
|
| 441 |
+
"freebsd"
|
| 442 |
+
],
|
| 443 |
+
"engines": {
|
| 444 |
+
"node": ">=18"
|
| 445 |
+
}
|
| 446 |
+
},
|
| 447 |
+
"node_modules/@esbuild/linux-arm": {
|
| 448 |
+
"version": "0.25.12",
|
| 449 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
|
| 450 |
+
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
|
| 451 |
+
"cpu": [
|
| 452 |
+
"arm"
|
| 453 |
+
],
|
| 454 |
+
"dev": true,
|
| 455 |
+
"license": "MIT",
|
| 456 |
+
"optional": true,
|
| 457 |
+
"os": [
|
| 458 |
+
"linux"
|
| 459 |
+
],
|
| 460 |
+
"engines": {
|
| 461 |
+
"node": ">=18"
|
| 462 |
+
}
|
| 463 |
+
},
|
| 464 |
+
"node_modules/@esbuild/linux-arm64": {
|
| 465 |
+
"version": "0.25.12",
|
| 466 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
|
| 467 |
+
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
|
| 468 |
+
"cpu": [
|
| 469 |
+
"arm64"
|
| 470 |
+
],
|
| 471 |
+
"dev": true,
|
| 472 |
+
"license": "MIT",
|
| 473 |
+
"optional": true,
|
| 474 |
+
"os": [
|
| 475 |
+
"linux"
|
| 476 |
+
],
|
| 477 |
+
"engines": {
|
| 478 |
+
"node": ">=18"
|
| 479 |
+
}
|
| 480 |
+
},
|
| 481 |
+
"node_modules/@esbuild/linux-ia32": {
|
| 482 |
+
"version": "0.25.12",
|
| 483 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
|
| 484 |
+
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
|
| 485 |
+
"cpu": [
|
| 486 |
+
"ia32"
|
| 487 |
+
],
|
| 488 |
+
"dev": true,
|
| 489 |
+
"license": "MIT",
|
| 490 |
+
"optional": true,
|
| 491 |
+
"os": [
|
| 492 |
+
"linux"
|
| 493 |
+
],
|
| 494 |
+
"engines": {
|
| 495 |
+
"node": ">=18"
|
| 496 |
+
}
|
| 497 |
+
},
|
| 498 |
+
"node_modules/@esbuild/linux-loong64": {
|
| 499 |
+
"version": "0.25.12",
|
| 500 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
|
| 501 |
+
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
|
| 502 |
+
"cpu": [
|
| 503 |
+
"loong64"
|
| 504 |
+
],
|
| 505 |
+
"dev": true,
|
| 506 |
+
"license": "MIT",
|
| 507 |
+
"optional": true,
|
| 508 |
+
"os": [
|
| 509 |
+
"linux"
|
| 510 |
+
],
|
| 511 |
+
"engines": {
|
| 512 |
+
"node": ">=18"
|
| 513 |
+
}
|
| 514 |
+
},
|
| 515 |
+
"node_modules/@esbuild/linux-mips64el": {
|
| 516 |
+
"version": "0.25.12",
|
| 517 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
|
| 518 |
+
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
|
| 519 |
+
"cpu": [
|
| 520 |
+
"mips64el"
|
| 521 |
+
],
|
| 522 |
+
"dev": true,
|
| 523 |
+
"license": "MIT",
|
| 524 |
+
"optional": true,
|
| 525 |
+
"os": [
|
| 526 |
+
"linux"
|
| 527 |
+
],
|
| 528 |
+
"engines": {
|
| 529 |
+
"node": ">=18"
|
| 530 |
+
}
|
| 531 |
+
},
|
| 532 |
+
"node_modules/@esbuild/linux-ppc64": {
|
| 533 |
+
"version": "0.25.12",
|
| 534 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
|
| 535 |
+
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
|
| 536 |
+
"cpu": [
|
| 537 |
+
"ppc64"
|
| 538 |
+
],
|
| 539 |
+
"dev": true,
|
| 540 |
+
"license": "MIT",
|
| 541 |
+
"optional": true,
|
| 542 |
+
"os": [
|
| 543 |
+
"linux"
|
| 544 |
+
],
|
| 545 |
+
"engines": {
|
| 546 |
+
"node": ">=18"
|
| 547 |
+
}
|
| 548 |
+
},
|
| 549 |
+
"node_modules/@esbuild/linux-riscv64": {
|
| 550 |
+
"version": "0.25.12",
|
| 551 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
|
| 552 |
+
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
|
| 553 |
+
"cpu": [
|
| 554 |
+
"riscv64"
|
| 555 |
+
],
|
| 556 |
+
"dev": true,
|
| 557 |
+
"license": "MIT",
|
| 558 |
+
"optional": true,
|
| 559 |
+
"os": [
|
| 560 |
+
"linux"
|
| 561 |
+
],
|
| 562 |
+
"engines": {
|
| 563 |
+
"node": ">=18"
|
| 564 |
+
}
|
| 565 |
+
},
|
| 566 |
+
"node_modules/@esbuild/linux-s390x": {
|
| 567 |
+
"version": "0.25.12",
|
| 568 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
|
| 569 |
+
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
|
| 570 |
+
"cpu": [
|
| 571 |
+
"s390x"
|
| 572 |
+
],
|
| 573 |
+
"dev": true,
|
| 574 |
+
"license": "MIT",
|
| 575 |
+
"optional": true,
|
| 576 |
+
"os": [
|
| 577 |
+
"linux"
|
| 578 |
+
],
|
| 579 |
+
"engines": {
|
| 580 |
+
"node": ">=18"
|
| 581 |
+
}
|
| 582 |
+
},
|
| 583 |
+
"node_modules/@esbuild/linux-x64": {
|
| 584 |
+
"version": "0.25.12",
|
| 585 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
|
| 586 |
+
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
|
| 587 |
+
"cpu": [
|
| 588 |
+
"x64"
|
| 589 |
+
],
|
| 590 |
+
"dev": true,
|
| 591 |
+
"license": "MIT",
|
| 592 |
+
"optional": true,
|
| 593 |
+
"os": [
|
| 594 |
+
"linux"
|
| 595 |
+
],
|
| 596 |
+
"engines": {
|
| 597 |
+
"node": ">=18"
|
| 598 |
+
}
|
| 599 |
+
},
|
| 600 |
+
"node_modules/@esbuild/netbsd-arm64": {
|
| 601 |
+
"version": "0.25.12",
|
| 602 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
|
| 603 |
+
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
|
| 604 |
+
"cpu": [
|
| 605 |
+
"arm64"
|
| 606 |
+
],
|
| 607 |
+
"dev": true,
|
| 608 |
+
"license": "MIT",
|
| 609 |
+
"optional": true,
|
| 610 |
+
"os": [
|
| 611 |
+
"netbsd"
|
| 612 |
+
],
|
| 613 |
+
"engines": {
|
| 614 |
+
"node": ">=18"
|
| 615 |
+
}
|
| 616 |
+
},
|
| 617 |
+
"node_modules/@esbuild/netbsd-x64": {
|
| 618 |
+
"version": "0.25.12",
|
| 619 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
|
| 620 |
+
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
|
| 621 |
+
"cpu": [
|
| 622 |
+
"x64"
|
| 623 |
+
],
|
| 624 |
+
"dev": true,
|
| 625 |
+
"license": "MIT",
|
| 626 |
+
"optional": true,
|
| 627 |
+
"os": [
|
| 628 |
+
"netbsd"
|
| 629 |
+
],
|
| 630 |
+
"engines": {
|
| 631 |
+
"node": ">=18"
|
| 632 |
+
}
|
| 633 |
+
},
|
| 634 |
+
"node_modules/@esbuild/openbsd-arm64": {
|
| 635 |
+
"version": "0.25.12",
|
| 636 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
|
| 637 |
+
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
|
| 638 |
+
"cpu": [
|
| 639 |
+
"arm64"
|
| 640 |
+
],
|
| 641 |
+
"dev": true,
|
| 642 |
+
"license": "MIT",
|
| 643 |
+
"optional": true,
|
| 644 |
+
"os": [
|
| 645 |
+
"openbsd"
|
| 646 |
+
],
|
| 647 |
+
"engines": {
|
| 648 |
+
"node": ">=18"
|
| 649 |
+
}
|
| 650 |
+
},
|
| 651 |
+
"node_modules/@esbuild/openbsd-x64": {
|
| 652 |
+
"version": "0.25.12",
|
| 653 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
|
| 654 |
+
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
|
| 655 |
+
"cpu": [
|
| 656 |
+
"x64"
|
| 657 |
+
],
|
| 658 |
+
"dev": true,
|
| 659 |
+
"license": "MIT",
|
| 660 |
+
"optional": true,
|
| 661 |
+
"os": [
|
| 662 |
+
"openbsd"
|
| 663 |
+
],
|
| 664 |
+
"engines": {
|
| 665 |
+
"node": ">=18"
|
| 666 |
+
}
|
| 667 |
+
},
|
| 668 |
+
"node_modules/@esbuild/openharmony-arm64": {
|
| 669 |
+
"version": "0.25.12",
|
| 670 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
|
| 671 |
+
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
|
| 672 |
+
"cpu": [
|
| 673 |
+
"arm64"
|
| 674 |
+
],
|
| 675 |
+
"dev": true,
|
| 676 |
+
"license": "MIT",
|
| 677 |
+
"optional": true,
|
| 678 |
+
"os": [
|
| 679 |
+
"openharmony"
|
| 680 |
+
],
|
| 681 |
+
"engines": {
|
| 682 |
+
"node": ">=18"
|
| 683 |
+
}
|
| 684 |
+
},
|
| 685 |
+
"node_modules/@esbuild/sunos-x64": {
|
| 686 |
+
"version": "0.25.12",
|
| 687 |
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
|
| 688 |
+
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
|
| 689 |
+
"cpu": [
|
| 690 |
+
"x64"
|
| 691 |
+
],
|
| 692 |
+
"dev": true,
|
| 693 |
+
"license": "MIT",
|
| 694 |
+
"optional": true,
|
| 695 |
+
"os": [
|
| 696 |
+
"sunos"
|
| 697 |
+
],
|
| 698 |
+
"engines": {
|
| 699 |
+
"node": ">=18"
|
| 700 |
+
}
|
| 701 |
+
},
|
| 702 |
+
"node_modules/@esbuild/win32-arm64": {
|
| 703 |
+
"version": "0.25.12",
|
| 704 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
|
| 705 |
+
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
|
| 706 |
+
"cpu": [
|
| 707 |
+
"arm64"
|
| 708 |
+
],
|
| 709 |
+
"dev": true,
|
| 710 |
+
"license": "MIT",
|
| 711 |
+
"optional": true,
|
| 712 |
+
"os": [
|
| 713 |
+
"win32"
|
| 714 |
+
],
|
| 715 |
+
"engines": {
|
| 716 |
+
"node": ">=18"
|
| 717 |
+
}
|
| 718 |
+
},
|
| 719 |
+
"node_modules/@esbuild/win32-ia32": {
|
| 720 |
+
"version": "0.25.12",
|
| 721 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
|
| 722 |
+
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
|
| 723 |
+
"cpu": [
|
| 724 |
+
"ia32"
|
| 725 |
+
],
|
| 726 |
+
"dev": true,
|
| 727 |
+
"license": "MIT",
|
| 728 |
+
"optional": true,
|
| 729 |
+
"os": [
|
| 730 |
+
"win32"
|
| 731 |
+
],
|
| 732 |
+
"engines": {
|
| 733 |
+
"node": ">=18"
|
| 734 |
+
}
|
| 735 |
+
},
|
| 736 |
+
"node_modules/@esbuild/win32-x64": {
|
| 737 |
+
"version": "0.25.12",
|
| 738 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
|
| 739 |
+
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
|
| 740 |
+
"cpu": [
|
| 741 |
+
"x64"
|
| 742 |
+
],
|
| 743 |
+
"dev": true,
|
| 744 |
+
"license": "MIT",
|
| 745 |
+
"optional": true,
|
| 746 |
+
"os": [
|
| 747 |
+
"win32"
|
| 748 |
+
],
|
| 749 |
+
"engines": {
|
| 750 |
+
"node": ">=18"
|
| 751 |
+
}
|
| 752 |
+
},
|
| 753 |
+
"node_modules/@jridgewell/gen-mapping": {
|
| 754 |
+
"version": "0.3.13",
|
| 755 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
| 756 |
+
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
| 757 |
+
"dev": true,
|
| 758 |
+
"license": "MIT",
|
| 759 |
+
"dependencies": {
|
| 760 |
+
"@jridgewell/sourcemap-codec": "^1.5.0",
|
| 761 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 762 |
+
}
|
| 763 |
+
},
|
| 764 |
+
"node_modules/@jridgewell/remapping": {
|
| 765 |
+
"version": "2.3.5",
|
| 766 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
| 767 |
+
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
| 768 |
+
"dev": true,
|
| 769 |
+
"license": "MIT",
|
| 770 |
+
"dependencies": {
|
| 771 |
+
"@jridgewell/gen-mapping": "^0.3.5",
|
| 772 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 773 |
+
}
|
| 774 |
+
},
|
| 775 |
+
"node_modules/@jridgewell/resolve-uri": {
|
| 776 |
+
"version": "3.1.2",
|
| 777 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
| 778 |
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
| 779 |
+
"dev": true,
|
| 780 |
+
"license": "MIT",
|
| 781 |
+
"engines": {
|
| 782 |
+
"node": ">=6.0.0"
|
| 783 |
+
}
|
| 784 |
+
},
|
| 785 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
| 786 |
+
"version": "1.5.5",
|
| 787 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 788 |
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
| 789 |
+
"dev": true,
|
| 790 |
+
"license": "MIT"
|
| 791 |
+
},
|
| 792 |
+
"node_modules/@jridgewell/trace-mapping": {
|
| 793 |
+
"version": "0.3.31",
|
| 794 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
| 795 |
+
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
| 796 |
+
"dev": true,
|
| 797 |
+
"license": "MIT",
|
| 798 |
+
"dependencies": {
|
| 799 |
+
"@jridgewell/resolve-uri": "^3.1.0",
|
| 800 |
+
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 801 |
+
}
|
| 802 |
+
},
|
| 803 |
+
"node_modules/@rolldown/pluginutils": {
|
| 804 |
+
"version": "1.0.0-rc.3",
|
| 805 |
+
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
|
| 806 |
+
"integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
|
| 807 |
+
"dev": true,
|
| 808 |
+
"license": "MIT"
|
| 809 |
+
},
|
| 810 |
+
"node_modules/@rollup/rollup-android-arm-eabi": {
|
| 811 |
+
"version": "4.60.2",
|
| 812 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz",
|
| 813 |
+
"integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==",
|
| 814 |
+
"cpu": [
|
| 815 |
+
"arm"
|
| 816 |
+
],
|
| 817 |
+
"dev": true,
|
| 818 |
+
"license": "MIT",
|
| 819 |
+
"optional": true,
|
| 820 |
+
"os": [
|
| 821 |
+
"android"
|
| 822 |
+
]
|
| 823 |
+
},
|
| 824 |
+
"node_modules/@rollup/rollup-android-arm64": {
|
| 825 |
+
"version": "4.60.2",
|
| 826 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz",
|
| 827 |
+
"integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==",
|
| 828 |
+
"cpu": [
|
| 829 |
+
"arm64"
|
| 830 |
+
],
|
| 831 |
+
"dev": true,
|
| 832 |
+
"license": "MIT",
|
| 833 |
+
"optional": true,
|
| 834 |
+
"os": [
|
| 835 |
+
"android"
|
| 836 |
+
]
|
| 837 |
+
},
|
| 838 |
+
"node_modules/@rollup/rollup-darwin-arm64": {
|
| 839 |
+
"version": "4.60.2",
|
| 840 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz",
|
| 841 |
+
"integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==",
|
| 842 |
+
"cpu": [
|
| 843 |
+
"arm64"
|
| 844 |
+
],
|
| 845 |
+
"dev": true,
|
| 846 |
+
"license": "MIT",
|
| 847 |
+
"optional": true,
|
| 848 |
+
"os": [
|
| 849 |
+
"darwin"
|
| 850 |
+
]
|
| 851 |
+
},
|
| 852 |
+
"node_modules/@rollup/rollup-darwin-x64": {
|
| 853 |
+
"version": "4.60.2",
|
| 854 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz",
|
| 855 |
+
"integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==",
|
| 856 |
+
"cpu": [
|
| 857 |
+
"x64"
|
| 858 |
+
],
|
| 859 |
+
"dev": true,
|
| 860 |
+
"license": "MIT",
|
| 861 |
+
"optional": true,
|
| 862 |
+
"os": [
|
| 863 |
+
"darwin"
|
| 864 |
+
]
|
| 865 |
+
},
|
| 866 |
+
"node_modules/@rollup/rollup-freebsd-arm64": {
|
| 867 |
+
"version": "4.60.2",
|
| 868 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz",
|
| 869 |
+
"integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==",
|
| 870 |
+
"cpu": [
|
| 871 |
+
"arm64"
|
| 872 |
+
],
|
| 873 |
+
"dev": true,
|
| 874 |
+
"license": "MIT",
|
| 875 |
+
"optional": true,
|
| 876 |
+
"os": [
|
| 877 |
+
"freebsd"
|
| 878 |
+
]
|
| 879 |
+
},
|
| 880 |
+
"node_modules/@rollup/rollup-freebsd-x64": {
|
| 881 |
+
"version": "4.60.2",
|
| 882 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz",
|
| 883 |
+
"integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==",
|
| 884 |
+
"cpu": [
|
| 885 |
+
"x64"
|
| 886 |
+
],
|
| 887 |
+
"dev": true,
|
| 888 |
+
"license": "MIT",
|
| 889 |
+
"optional": true,
|
| 890 |
+
"os": [
|
| 891 |
+
"freebsd"
|
| 892 |
+
]
|
| 893 |
+
},
|
| 894 |
+
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
| 895 |
+
"version": "4.60.2",
|
| 896 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz",
|
| 897 |
+
"integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==",
|
| 898 |
+
"cpu": [
|
| 899 |
+
"arm"
|
| 900 |
+
],
|
| 901 |
+
"dev": true,
|
| 902 |
+
"license": "MIT",
|
| 903 |
+
"optional": true,
|
| 904 |
+
"os": [
|
| 905 |
+
"linux"
|
| 906 |
+
]
|
| 907 |
+
},
|
| 908 |
+
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
| 909 |
+
"version": "4.60.2",
|
| 910 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz",
|
| 911 |
+
"integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==",
|
| 912 |
+
"cpu": [
|
| 913 |
+
"arm"
|
| 914 |
+
],
|
| 915 |
+
"dev": true,
|
| 916 |
+
"license": "MIT",
|
| 917 |
+
"optional": true,
|
| 918 |
+
"os": [
|
| 919 |
+
"linux"
|
| 920 |
+
]
|
| 921 |
+
},
|
| 922 |
+
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
| 923 |
+
"version": "4.60.2",
|
| 924 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz",
|
| 925 |
+
"integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==",
|
| 926 |
+
"cpu": [
|
| 927 |
+
"arm64"
|
| 928 |
+
],
|
| 929 |
+
"dev": true,
|
| 930 |
+
"license": "MIT",
|
| 931 |
+
"optional": true,
|
| 932 |
+
"os": [
|
| 933 |
+
"linux"
|
| 934 |
+
]
|
| 935 |
+
},
|
| 936 |
+
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
| 937 |
+
"version": "4.60.2",
|
| 938 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz",
|
| 939 |
+
"integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==",
|
| 940 |
+
"cpu": [
|
| 941 |
+
"arm64"
|
| 942 |
+
],
|
| 943 |
+
"dev": true,
|
| 944 |
+
"license": "MIT",
|
| 945 |
+
"optional": true,
|
| 946 |
+
"os": [
|
| 947 |
+
"linux"
|
| 948 |
+
]
|
| 949 |
+
},
|
| 950 |
+
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
| 951 |
+
"version": "4.60.2",
|
| 952 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz",
|
| 953 |
+
"integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==",
|
| 954 |
+
"cpu": [
|
| 955 |
+
"loong64"
|
| 956 |
+
],
|
| 957 |
+
"dev": true,
|
| 958 |
+
"license": "MIT",
|
| 959 |
+
"optional": true,
|
| 960 |
+
"os": [
|
| 961 |
+
"linux"
|
| 962 |
+
]
|
| 963 |
+
},
|
| 964 |
+
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
| 965 |
+
"version": "4.60.2",
|
| 966 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz",
|
| 967 |
+
"integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==",
|
| 968 |
+
"cpu": [
|
| 969 |
+
"loong64"
|
| 970 |
+
],
|
| 971 |
+
"dev": true,
|
| 972 |
+
"license": "MIT",
|
| 973 |
+
"optional": true,
|
| 974 |
+
"os": [
|
| 975 |
+
"linux"
|
| 976 |
+
]
|
| 977 |
+
},
|
| 978 |
+
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
| 979 |
+
"version": "4.60.2",
|
| 980 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz",
|
| 981 |
+
"integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==",
|
| 982 |
+
"cpu": [
|
| 983 |
+
"ppc64"
|
| 984 |
+
],
|
| 985 |
+
"dev": true,
|
| 986 |
+
"license": "MIT",
|
| 987 |
+
"optional": true,
|
| 988 |
+
"os": [
|
| 989 |
+
"linux"
|
| 990 |
+
]
|
| 991 |
+
},
|
| 992 |
+
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
| 993 |
+
"version": "4.60.2",
|
| 994 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz",
|
| 995 |
+
"integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==",
|
| 996 |
+
"cpu": [
|
| 997 |
+
"ppc64"
|
| 998 |
+
],
|
| 999 |
+
"dev": true,
|
| 1000 |
+
"license": "MIT",
|
| 1001 |
+
"optional": true,
|
| 1002 |
+
"os": [
|
| 1003 |
+
"linux"
|
| 1004 |
+
]
|
| 1005 |
+
},
|
| 1006 |
+
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
| 1007 |
+
"version": "4.60.2",
|
| 1008 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz",
|
| 1009 |
+
"integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==",
|
| 1010 |
+
"cpu": [
|
| 1011 |
+
"riscv64"
|
| 1012 |
+
],
|
| 1013 |
+
"dev": true,
|
| 1014 |
+
"license": "MIT",
|
| 1015 |
+
"optional": true,
|
| 1016 |
+
"os": [
|
| 1017 |
+
"linux"
|
| 1018 |
+
]
|
| 1019 |
+
},
|
| 1020 |
+
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
| 1021 |
+
"version": "4.60.2",
|
| 1022 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz",
|
| 1023 |
+
"integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==",
|
| 1024 |
+
"cpu": [
|
| 1025 |
+
"riscv64"
|
| 1026 |
+
],
|
| 1027 |
+
"dev": true,
|
| 1028 |
+
"license": "MIT",
|
| 1029 |
+
"optional": true,
|
| 1030 |
+
"os": [
|
| 1031 |
+
"linux"
|
| 1032 |
+
]
|
| 1033 |
+
},
|
| 1034 |
+
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
| 1035 |
+
"version": "4.60.2",
|
| 1036 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz",
|
| 1037 |
+
"integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==",
|
| 1038 |
+
"cpu": [
|
| 1039 |
+
"s390x"
|
| 1040 |
+
],
|
| 1041 |
+
"dev": true,
|
| 1042 |
+
"license": "MIT",
|
| 1043 |
+
"optional": true,
|
| 1044 |
+
"os": [
|
| 1045 |
+
"linux"
|
| 1046 |
+
]
|
| 1047 |
+
},
|
| 1048 |
+
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
| 1049 |
+
"version": "4.60.2",
|
| 1050 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz",
|
| 1051 |
+
"integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==",
|
| 1052 |
+
"cpu": [
|
| 1053 |
+
"x64"
|
| 1054 |
+
],
|
| 1055 |
+
"dev": true,
|
| 1056 |
+
"license": "MIT",
|
| 1057 |
+
"optional": true,
|
| 1058 |
+
"os": [
|
| 1059 |
+
"linux"
|
| 1060 |
+
]
|
| 1061 |
+
},
|
| 1062 |
+
"node_modules/@rollup/rollup-linux-x64-musl": {
|
| 1063 |
+
"version": "4.60.2",
|
| 1064 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz",
|
| 1065 |
+
"integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==",
|
| 1066 |
+
"cpu": [
|
| 1067 |
+
"x64"
|
| 1068 |
+
],
|
| 1069 |
+
"dev": true,
|
| 1070 |
+
"license": "MIT",
|
| 1071 |
+
"optional": true,
|
| 1072 |
+
"os": [
|
| 1073 |
+
"linux"
|
| 1074 |
+
]
|
| 1075 |
+
},
|
| 1076 |
+
"node_modules/@rollup/rollup-openbsd-x64": {
|
| 1077 |
+
"version": "4.60.2",
|
| 1078 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz",
|
| 1079 |
+
"integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==",
|
| 1080 |
+
"cpu": [
|
| 1081 |
+
"x64"
|
| 1082 |
+
],
|
| 1083 |
+
"dev": true,
|
| 1084 |
+
"license": "MIT",
|
| 1085 |
+
"optional": true,
|
| 1086 |
+
"os": [
|
| 1087 |
+
"openbsd"
|
| 1088 |
+
]
|
| 1089 |
+
},
|
| 1090 |
+
"node_modules/@rollup/rollup-openharmony-arm64": {
|
| 1091 |
+
"version": "4.60.2",
|
| 1092 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz",
|
| 1093 |
+
"integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==",
|
| 1094 |
+
"cpu": [
|
| 1095 |
+
"arm64"
|
| 1096 |
+
],
|
| 1097 |
+
"dev": true,
|
| 1098 |
+
"license": "MIT",
|
| 1099 |
+
"optional": true,
|
| 1100 |
+
"os": [
|
| 1101 |
+
"openharmony"
|
| 1102 |
+
]
|
| 1103 |
+
},
|
| 1104 |
+
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
| 1105 |
+
"version": "4.60.2",
|
| 1106 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz",
|
| 1107 |
+
"integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==",
|
| 1108 |
+
"cpu": [
|
| 1109 |
+
"arm64"
|
| 1110 |
+
],
|
| 1111 |
+
"dev": true,
|
| 1112 |
+
"license": "MIT",
|
| 1113 |
+
"optional": true,
|
| 1114 |
+
"os": [
|
| 1115 |
+
"win32"
|
| 1116 |
+
]
|
| 1117 |
+
},
|
| 1118 |
+
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
| 1119 |
+
"version": "4.60.2",
|
| 1120 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz",
|
| 1121 |
+
"integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==",
|
| 1122 |
+
"cpu": [
|
| 1123 |
+
"ia32"
|
| 1124 |
+
],
|
| 1125 |
+
"dev": true,
|
| 1126 |
+
"license": "MIT",
|
| 1127 |
+
"optional": true,
|
| 1128 |
+
"os": [
|
| 1129 |
+
"win32"
|
| 1130 |
+
]
|
| 1131 |
+
},
|
| 1132 |
+
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
| 1133 |
+
"version": "4.60.2",
|
| 1134 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz",
|
| 1135 |
+
"integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==",
|
| 1136 |
+
"cpu": [
|
| 1137 |
+
"x64"
|
| 1138 |
+
],
|
| 1139 |
+
"dev": true,
|
| 1140 |
+
"license": "MIT",
|
| 1141 |
+
"optional": true,
|
| 1142 |
+
"os": [
|
| 1143 |
+
"win32"
|
| 1144 |
+
]
|
| 1145 |
+
},
|
| 1146 |
+
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
| 1147 |
+
"version": "4.60.2",
|
| 1148 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz",
|
| 1149 |
+
"integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==",
|
| 1150 |
+
"cpu": [
|
| 1151 |
+
"x64"
|
| 1152 |
+
],
|
| 1153 |
+
"dev": true,
|
| 1154 |
+
"license": "MIT",
|
| 1155 |
+
"optional": true,
|
| 1156 |
+
"os": [
|
| 1157 |
+
"win32"
|
| 1158 |
+
]
|
| 1159 |
+
},
|
| 1160 |
+
"node_modules/@tailwindcss/node": {
|
| 1161 |
+
"version": "4.2.4",
|
| 1162 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz",
|
| 1163 |
+
"integrity": "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==",
|
| 1164 |
+
"dev": true,
|
| 1165 |
+
"license": "MIT",
|
| 1166 |
+
"dependencies": {
|
| 1167 |
+
"@jridgewell/remapping": "^2.3.5",
|
| 1168 |
+
"enhanced-resolve": "^5.19.0",
|
| 1169 |
+
"jiti": "^2.6.1",
|
| 1170 |
+
"lightningcss": "1.32.0",
|
| 1171 |
+
"magic-string": "^0.30.21",
|
| 1172 |
+
"source-map-js": "^1.2.1",
|
| 1173 |
+
"tailwindcss": "4.2.4"
|
| 1174 |
+
}
|
| 1175 |
+
},
|
| 1176 |
+
"node_modules/@tailwindcss/oxide": {
|
| 1177 |
+
"version": "4.2.4",
|
| 1178 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.4.tgz",
|
| 1179 |
+
"integrity": "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==",
|
| 1180 |
+
"dev": true,
|
| 1181 |
+
"license": "MIT",
|
| 1182 |
+
"engines": {
|
| 1183 |
+
"node": ">= 20"
|
| 1184 |
+
},
|
| 1185 |
+
"optionalDependencies": {
|
| 1186 |
+
"@tailwindcss/oxide-android-arm64": "4.2.4",
|
| 1187 |
+
"@tailwindcss/oxide-darwin-arm64": "4.2.4",
|
| 1188 |
+
"@tailwindcss/oxide-darwin-x64": "4.2.4",
|
| 1189 |
+
"@tailwindcss/oxide-freebsd-x64": "4.2.4",
|
| 1190 |
+
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4",
|
| 1191 |
+
"@tailwindcss/oxide-linux-arm64-gnu": "4.2.4",
|
| 1192 |
+
"@tailwindcss/oxide-linux-arm64-musl": "4.2.4",
|
| 1193 |
+
"@tailwindcss/oxide-linux-x64-gnu": "4.2.4",
|
| 1194 |
+
"@tailwindcss/oxide-linux-x64-musl": "4.2.4",
|
| 1195 |
+
"@tailwindcss/oxide-wasm32-wasi": "4.2.4",
|
| 1196 |
+
"@tailwindcss/oxide-win32-arm64-msvc": "4.2.4",
|
| 1197 |
+
"@tailwindcss/oxide-win32-x64-msvc": "4.2.4"
|
| 1198 |
+
}
|
| 1199 |
+
},
|
| 1200 |
+
"node_modules/@tailwindcss/oxide-android-arm64": {
|
| 1201 |
+
"version": "4.2.4",
|
| 1202 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz",
|
| 1203 |
+
"integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==",
|
| 1204 |
+
"cpu": [
|
| 1205 |
+
"arm64"
|
| 1206 |
+
],
|
| 1207 |
+
"dev": true,
|
| 1208 |
+
"license": "MIT",
|
| 1209 |
+
"optional": true,
|
| 1210 |
+
"os": [
|
| 1211 |
+
"android"
|
| 1212 |
+
],
|
| 1213 |
+
"engines": {
|
| 1214 |
+
"node": ">= 20"
|
| 1215 |
+
}
|
| 1216 |
+
},
|
| 1217 |
+
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
| 1218 |
+
"version": "4.2.4",
|
| 1219 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz",
|
| 1220 |
+
"integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==",
|
| 1221 |
+
"cpu": [
|
| 1222 |
+
"arm64"
|
| 1223 |
+
],
|
| 1224 |
+
"dev": true,
|
| 1225 |
+
"license": "MIT",
|
| 1226 |
+
"optional": true,
|
| 1227 |
+
"os": [
|
| 1228 |
+
"darwin"
|
| 1229 |
+
],
|
| 1230 |
+
"engines": {
|
| 1231 |
+
"node": ">= 20"
|
| 1232 |
+
}
|
| 1233 |
+
},
|
| 1234 |
+
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
| 1235 |
+
"version": "4.2.4",
|
| 1236 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz",
|
| 1237 |
+
"integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==",
|
| 1238 |
+
"cpu": [
|
| 1239 |
+
"x64"
|
| 1240 |
+
],
|
| 1241 |
+
"dev": true,
|
| 1242 |
+
"license": "MIT",
|
| 1243 |
+
"optional": true,
|
| 1244 |
+
"os": [
|
| 1245 |
+
"darwin"
|
| 1246 |
+
],
|
| 1247 |
+
"engines": {
|
| 1248 |
+
"node": ">= 20"
|
| 1249 |
+
}
|
| 1250 |
+
},
|
| 1251 |
+
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
| 1252 |
+
"version": "4.2.4",
|
| 1253 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz",
|
| 1254 |
+
"integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==",
|
| 1255 |
+
"cpu": [
|
| 1256 |
+
"x64"
|
| 1257 |
+
],
|
| 1258 |
+
"dev": true,
|
| 1259 |
+
"license": "MIT",
|
| 1260 |
+
"optional": true,
|
| 1261 |
+
"os": [
|
| 1262 |
+
"freebsd"
|
| 1263 |
+
],
|
| 1264 |
+
"engines": {
|
| 1265 |
+
"node": ">= 20"
|
| 1266 |
+
}
|
| 1267 |
+
},
|
| 1268 |
+
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
| 1269 |
+
"version": "4.2.4",
|
| 1270 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz",
|
| 1271 |
+
"integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==",
|
| 1272 |
+
"cpu": [
|
| 1273 |
+
"arm"
|
| 1274 |
+
],
|
| 1275 |
+
"dev": true,
|
| 1276 |
+
"license": "MIT",
|
| 1277 |
+
"optional": true,
|
| 1278 |
+
"os": [
|
| 1279 |
+
"linux"
|
| 1280 |
+
],
|
| 1281 |
+
"engines": {
|
| 1282 |
+
"node": ">= 20"
|
| 1283 |
+
}
|
| 1284 |
+
},
|
| 1285 |
+
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
| 1286 |
+
"version": "4.2.4",
|
| 1287 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz",
|
| 1288 |
+
"integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==",
|
| 1289 |
+
"cpu": [
|
| 1290 |
+
"arm64"
|
| 1291 |
+
],
|
| 1292 |
+
"dev": true,
|
| 1293 |
+
"license": "MIT",
|
| 1294 |
+
"optional": true,
|
| 1295 |
+
"os": [
|
| 1296 |
+
"linux"
|
| 1297 |
+
],
|
| 1298 |
+
"engines": {
|
| 1299 |
+
"node": ">= 20"
|
| 1300 |
+
}
|
| 1301 |
+
},
|
| 1302 |
+
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
| 1303 |
+
"version": "4.2.4",
|
| 1304 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz",
|
| 1305 |
+
"integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==",
|
| 1306 |
+
"cpu": [
|
| 1307 |
+
"arm64"
|
| 1308 |
+
],
|
| 1309 |
+
"dev": true,
|
| 1310 |
+
"license": "MIT",
|
| 1311 |
+
"optional": true,
|
| 1312 |
+
"os": [
|
| 1313 |
+
"linux"
|
| 1314 |
+
],
|
| 1315 |
+
"engines": {
|
| 1316 |
+
"node": ">= 20"
|
| 1317 |
+
}
|
| 1318 |
+
},
|
| 1319 |
+
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
| 1320 |
+
"version": "4.2.4",
|
| 1321 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz",
|
| 1322 |
+
"integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==",
|
| 1323 |
+
"cpu": [
|
| 1324 |
+
"x64"
|
| 1325 |
+
],
|
| 1326 |
+
"dev": true,
|
| 1327 |
+
"license": "MIT",
|
| 1328 |
+
"optional": true,
|
| 1329 |
+
"os": [
|
| 1330 |
+
"linux"
|
| 1331 |
+
],
|
| 1332 |
+
"engines": {
|
| 1333 |
+
"node": ">= 20"
|
| 1334 |
+
}
|
| 1335 |
+
},
|
| 1336 |
+
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
| 1337 |
+
"version": "4.2.4",
|
| 1338 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz",
|
| 1339 |
+
"integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==",
|
| 1340 |
+
"cpu": [
|
| 1341 |
+
"x64"
|
| 1342 |
+
],
|
| 1343 |
+
"dev": true,
|
| 1344 |
+
"license": "MIT",
|
| 1345 |
+
"optional": true,
|
| 1346 |
+
"os": [
|
| 1347 |
+
"linux"
|
| 1348 |
+
],
|
| 1349 |
+
"engines": {
|
| 1350 |
+
"node": ">= 20"
|
| 1351 |
+
}
|
| 1352 |
+
},
|
| 1353 |
+
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
| 1354 |
+
"version": "4.2.4",
|
| 1355 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz",
|
| 1356 |
+
"integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==",
|
| 1357 |
+
"bundleDependencies": [
|
| 1358 |
+
"@napi-rs/wasm-runtime",
|
| 1359 |
+
"@emnapi/core",
|
| 1360 |
+
"@emnapi/runtime",
|
| 1361 |
+
"@tybys/wasm-util",
|
| 1362 |
+
"@emnapi/wasi-threads",
|
| 1363 |
+
"tslib"
|
| 1364 |
+
],
|
| 1365 |
+
"cpu": [
|
| 1366 |
+
"wasm32"
|
| 1367 |
+
],
|
| 1368 |
+
"dev": true,
|
| 1369 |
+
"license": "MIT",
|
| 1370 |
+
"optional": true,
|
| 1371 |
+
"dependencies": {
|
| 1372 |
+
"@emnapi/core": "^1.8.1",
|
| 1373 |
+
"@emnapi/runtime": "^1.8.1",
|
| 1374 |
+
"@emnapi/wasi-threads": "^1.1.0",
|
| 1375 |
+
"@napi-rs/wasm-runtime": "^1.1.1",
|
| 1376 |
+
"@tybys/wasm-util": "^0.10.1",
|
| 1377 |
+
"tslib": "^2.8.1"
|
| 1378 |
+
},
|
| 1379 |
+
"engines": {
|
| 1380 |
+
"node": ">=14.0.0"
|
| 1381 |
+
}
|
| 1382 |
+
},
|
| 1383 |
+
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
| 1384 |
+
"version": "4.2.4",
|
| 1385 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz",
|
| 1386 |
+
"integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==",
|
| 1387 |
+
"cpu": [
|
| 1388 |
+
"arm64"
|
| 1389 |
+
],
|
| 1390 |
+
"dev": true,
|
| 1391 |
+
"license": "MIT",
|
| 1392 |
+
"optional": true,
|
| 1393 |
+
"os": [
|
| 1394 |
+
"win32"
|
| 1395 |
+
],
|
| 1396 |
+
"engines": {
|
| 1397 |
+
"node": ">= 20"
|
| 1398 |
+
}
|
| 1399 |
+
},
|
| 1400 |
+
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
| 1401 |
+
"version": "4.2.4",
|
| 1402 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz",
|
| 1403 |
+
"integrity": "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==",
|
| 1404 |
+
"cpu": [
|
| 1405 |
+
"x64"
|
| 1406 |
+
],
|
| 1407 |
+
"dev": true,
|
| 1408 |
+
"license": "MIT",
|
| 1409 |
+
"optional": true,
|
| 1410 |
+
"os": [
|
| 1411 |
+
"win32"
|
| 1412 |
+
],
|
| 1413 |
+
"engines": {
|
| 1414 |
+
"node": ">= 20"
|
| 1415 |
+
}
|
| 1416 |
+
},
|
| 1417 |
+
"node_modules/@tailwindcss/vite": {
|
| 1418 |
+
"version": "4.2.4",
|
| 1419 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.4.tgz",
|
| 1420 |
+
"integrity": "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==",
|
| 1421 |
+
"dev": true,
|
| 1422 |
+
"license": "MIT",
|
| 1423 |
+
"dependencies": {
|
| 1424 |
+
"@tailwindcss/node": "4.2.4",
|
| 1425 |
+
"@tailwindcss/oxide": "4.2.4",
|
| 1426 |
+
"tailwindcss": "4.2.4"
|
| 1427 |
+
},
|
| 1428 |
+
"peerDependencies": {
|
| 1429 |
+
"vite": "^5.2.0 || ^6 || ^7 || ^8"
|
| 1430 |
+
}
|
| 1431 |
+
},
|
| 1432 |
+
"node_modules/@types/babel__core": {
|
| 1433 |
+
"version": "7.20.5",
|
| 1434 |
+
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
| 1435 |
+
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
|
| 1436 |
+
"dev": true,
|
| 1437 |
+
"license": "MIT",
|
| 1438 |
+
"dependencies": {
|
| 1439 |
+
"@babel/parser": "^7.20.7",
|
| 1440 |
+
"@babel/types": "^7.20.7",
|
| 1441 |
+
"@types/babel__generator": "*",
|
| 1442 |
+
"@types/babel__template": "*",
|
| 1443 |
+
"@types/babel__traverse": "*"
|
| 1444 |
+
}
|
| 1445 |
+
},
|
| 1446 |
+
"node_modules/@types/babel__generator": {
|
| 1447 |
+
"version": "7.27.0",
|
| 1448 |
+
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
|
| 1449 |
+
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
|
| 1450 |
+
"dev": true,
|
| 1451 |
+
"license": "MIT",
|
| 1452 |
+
"dependencies": {
|
| 1453 |
+
"@babel/types": "^7.0.0"
|
| 1454 |
+
}
|
| 1455 |
+
},
|
| 1456 |
+
"node_modules/@types/babel__template": {
|
| 1457 |
+
"version": "7.4.4",
|
| 1458 |
+
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
|
| 1459 |
+
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
|
| 1460 |
+
"dev": true,
|
| 1461 |
+
"license": "MIT",
|
| 1462 |
+
"dependencies": {
|
| 1463 |
+
"@babel/parser": "^7.1.0",
|
| 1464 |
+
"@babel/types": "^7.0.0"
|
| 1465 |
+
}
|
| 1466 |
+
},
|
| 1467 |
+
"node_modules/@types/babel__traverse": {
|
| 1468 |
+
"version": "7.28.0",
|
| 1469 |
+
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
|
| 1470 |
+
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
|
| 1471 |
+
"dev": true,
|
| 1472 |
+
"license": "MIT",
|
| 1473 |
+
"dependencies": {
|
| 1474 |
+
"@babel/types": "^7.28.2"
|
| 1475 |
+
}
|
| 1476 |
+
},
|
| 1477 |
+
"node_modules/@types/estree": {
|
| 1478 |
+
"version": "1.0.8",
|
| 1479 |
+
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
| 1480 |
+
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
| 1481 |
+
"dev": true,
|
| 1482 |
+
"license": "MIT"
|
| 1483 |
+
},
|
| 1484 |
+
"node_modules/@types/node": {
|
| 1485 |
+
"version": "22.19.17",
|
| 1486 |
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
|
| 1487 |
+
"integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
|
| 1488 |
+
"dev": true,
|
| 1489 |
+
"license": "MIT",
|
| 1490 |
+
"dependencies": {
|
| 1491 |
+
"undici-types": "~6.21.0"
|
| 1492 |
+
}
|
| 1493 |
+
},
|
| 1494 |
+
"node_modules/@types/react": {
|
| 1495 |
+
"version": "19.2.14",
|
| 1496 |
+
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
| 1497 |
+
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
| 1498 |
+
"dev": true,
|
| 1499 |
+
"license": "MIT",
|
| 1500 |
+
"dependencies": {
|
| 1501 |
+
"csstype": "^3.2.2"
|
| 1502 |
+
}
|
| 1503 |
+
},
|
| 1504 |
+
"node_modules/@types/react-dom": {
|
| 1505 |
+
"version": "19.2.3",
|
| 1506 |
+
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
| 1507 |
+
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
| 1508 |
+
"dev": true,
|
| 1509 |
+
"license": "MIT",
|
| 1510 |
+
"peerDependencies": {
|
| 1511 |
+
"@types/react": "^19.2.0"
|
| 1512 |
+
}
|
| 1513 |
+
},
|
| 1514 |
+
"node_modules/@vitejs/plugin-react": {
|
| 1515 |
+
"version": "5.2.0",
|
| 1516 |
+
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
|
| 1517 |
+
"integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==",
|
| 1518 |
+
"dev": true,
|
| 1519 |
+
"license": "MIT",
|
| 1520 |
+
"dependencies": {
|
| 1521 |
+
"@babel/core": "^7.29.0",
|
| 1522 |
+
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
|
| 1523 |
+
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
|
| 1524 |
+
"@rolldown/pluginutils": "1.0.0-rc.3",
|
| 1525 |
+
"@types/babel__core": "^7.20.5",
|
| 1526 |
+
"react-refresh": "^0.18.0"
|
| 1527 |
+
},
|
| 1528 |
+
"engines": {
|
| 1529 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 1530 |
+
},
|
| 1531 |
+
"peerDependencies": {
|
| 1532 |
+
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
| 1533 |
+
}
|
| 1534 |
+
},
|
| 1535 |
+
"node_modules/autoprefixer": {
|
| 1536 |
+
"version": "10.5.0",
|
| 1537 |
+
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz",
|
| 1538 |
+
"integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==",
|
| 1539 |
+
"dev": true,
|
| 1540 |
+
"funding": [
|
| 1541 |
+
{
|
| 1542 |
+
"type": "opencollective",
|
| 1543 |
+
"url": "https://opencollective.com/postcss/"
|
| 1544 |
+
},
|
| 1545 |
+
{
|
| 1546 |
+
"type": "tidelift",
|
| 1547 |
+
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
|
| 1548 |
+
},
|
| 1549 |
+
{
|
| 1550 |
+
"type": "github",
|
| 1551 |
+
"url": "https://github.com/sponsors/ai"
|
| 1552 |
+
}
|
| 1553 |
+
],
|
| 1554 |
+
"license": "MIT",
|
| 1555 |
+
"dependencies": {
|
| 1556 |
+
"browserslist": "^4.28.2",
|
| 1557 |
+
"caniuse-lite": "^1.0.30001787",
|
| 1558 |
+
"fraction.js": "^5.3.4",
|
| 1559 |
+
"picocolors": "^1.1.1",
|
| 1560 |
+
"postcss-value-parser": "^4.2.0"
|
| 1561 |
+
},
|
| 1562 |
+
"bin": {
|
| 1563 |
+
"autoprefixer": "bin/autoprefixer"
|
| 1564 |
+
},
|
| 1565 |
+
"engines": {
|
| 1566 |
+
"node": "^10 || ^12 || >=14"
|
| 1567 |
+
},
|
| 1568 |
+
"peerDependencies": {
|
| 1569 |
+
"postcss": "^8.1.0"
|
| 1570 |
+
}
|
| 1571 |
+
},
|
| 1572 |
+
"node_modules/baseline-browser-mapping": {
|
| 1573 |
+
"version": "2.10.22",
|
| 1574 |
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.22.tgz",
|
| 1575 |
+
"integrity": "sha512-6qruVrb5rse6WylFkU0FhBKKGuecWseqdpQfhkawn6ztyk2QlfwSRjsDxMCLJrkfmfN21qvhl9ABgaMeRkuwww==",
|
| 1576 |
+
"dev": true,
|
| 1577 |
+
"license": "Apache-2.0",
|
| 1578 |
+
"bin": {
|
| 1579 |
+
"baseline-browser-mapping": "dist/cli.cjs"
|
| 1580 |
+
},
|
| 1581 |
+
"engines": {
|
| 1582 |
+
"node": ">=6.0.0"
|
| 1583 |
+
}
|
| 1584 |
+
},
|
| 1585 |
+
"node_modules/browserslist": {
|
| 1586 |
+
"version": "4.28.2",
|
| 1587 |
+
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
|
| 1588 |
+
"integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
|
| 1589 |
+
"dev": true,
|
| 1590 |
+
"funding": [
|
| 1591 |
+
{
|
| 1592 |
+
"type": "opencollective",
|
| 1593 |
+
"url": "https://opencollective.com/browserslist"
|
| 1594 |
+
},
|
| 1595 |
+
{
|
| 1596 |
+
"type": "tidelift",
|
| 1597 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 1598 |
+
},
|
| 1599 |
+
{
|
| 1600 |
+
"type": "github",
|
| 1601 |
+
"url": "https://github.com/sponsors/ai"
|
| 1602 |
+
}
|
| 1603 |
+
],
|
| 1604 |
+
"license": "MIT",
|
| 1605 |
+
"dependencies": {
|
| 1606 |
+
"baseline-browser-mapping": "^2.10.12",
|
| 1607 |
+
"caniuse-lite": "^1.0.30001782",
|
| 1608 |
+
"electron-to-chromium": "^1.5.328",
|
| 1609 |
+
"node-releases": "^2.0.36",
|
| 1610 |
+
"update-browserslist-db": "^1.2.3"
|
| 1611 |
+
},
|
| 1612 |
+
"bin": {
|
| 1613 |
+
"browserslist": "cli.js"
|
| 1614 |
+
},
|
| 1615 |
+
"engines": {
|
| 1616 |
+
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
| 1617 |
+
}
|
| 1618 |
+
},
|
| 1619 |
+
"node_modules/caniuse-lite": {
|
| 1620 |
+
"version": "1.0.30001790",
|
| 1621 |
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz",
|
| 1622 |
+
"integrity": "sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==",
|
| 1623 |
+
"dev": true,
|
| 1624 |
+
"funding": [
|
| 1625 |
+
{
|
| 1626 |
+
"type": "opencollective",
|
| 1627 |
+
"url": "https://opencollective.com/browserslist"
|
| 1628 |
+
},
|
| 1629 |
+
{
|
| 1630 |
+
"type": "tidelift",
|
| 1631 |
+
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
| 1632 |
+
},
|
| 1633 |
+
{
|
| 1634 |
+
"type": "github",
|
| 1635 |
+
"url": "https://github.com/sponsors/ai"
|
| 1636 |
+
}
|
| 1637 |
+
],
|
| 1638 |
+
"license": "CC-BY-4.0"
|
| 1639 |
+
},
|
| 1640 |
+
"node_modules/convert-source-map": {
|
| 1641 |
+
"version": "2.0.0",
|
| 1642 |
+
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
| 1643 |
+
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
| 1644 |
+
"dev": true,
|
| 1645 |
+
"license": "MIT"
|
| 1646 |
+
},
|
| 1647 |
+
"node_modules/csstype": {
|
| 1648 |
+
"version": "3.2.3",
|
| 1649 |
+
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
| 1650 |
+
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
| 1651 |
+
"dev": true,
|
| 1652 |
+
"license": "MIT"
|
| 1653 |
+
},
|
| 1654 |
+
"node_modules/debug": {
|
| 1655 |
+
"version": "4.4.3",
|
| 1656 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 1657 |
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 1658 |
+
"dev": true,
|
| 1659 |
+
"license": "MIT",
|
| 1660 |
+
"dependencies": {
|
| 1661 |
+
"ms": "^2.1.3"
|
| 1662 |
+
},
|
| 1663 |
+
"engines": {
|
| 1664 |
+
"node": ">=6.0"
|
| 1665 |
+
},
|
| 1666 |
+
"peerDependenciesMeta": {
|
| 1667 |
+
"supports-color": {
|
| 1668 |
+
"optional": true
|
| 1669 |
+
}
|
| 1670 |
+
}
|
| 1671 |
+
},
|
| 1672 |
+
"node_modules/detect-libc": {
|
| 1673 |
+
"version": "2.1.2",
|
| 1674 |
+
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
| 1675 |
+
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
| 1676 |
+
"dev": true,
|
| 1677 |
+
"license": "Apache-2.0",
|
| 1678 |
+
"engines": {
|
| 1679 |
+
"node": ">=8"
|
| 1680 |
+
}
|
| 1681 |
+
},
|
| 1682 |
+
"node_modules/electron-to-chromium": {
|
| 1683 |
+
"version": "1.5.344",
|
| 1684 |
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz",
|
| 1685 |
+
"integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==",
|
| 1686 |
+
"dev": true,
|
| 1687 |
+
"license": "ISC"
|
| 1688 |
+
},
|
| 1689 |
+
"node_modules/enhanced-resolve": {
|
| 1690 |
+
"version": "5.21.0",
|
| 1691 |
+
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz",
|
| 1692 |
+
"integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==",
|
| 1693 |
+
"dev": true,
|
| 1694 |
+
"license": "MIT",
|
| 1695 |
+
"dependencies": {
|
| 1696 |
+
"graceful-fs": "^4.2.4",
|
| 1697 |
+
"tapable": "^2.3.3"
|
| 1698 |
+
},
|
| 1699 |
+
"engines": {
|
| 1700 |
+
"node": ">=10.13.0"
|
| 1701 |
+
}
|
| 1702 |
+
},
|
| 1703 |
+
"node_modules/esbuild": {
|
| 1704 |
+
"version": "0.25.12",
|
| 1705 |
+
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
| 1706 |
+
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
|
| 1707 |
+
"dev": true,
|
| 1708 |
+
"hasInstallScript": true,
|
| 1709 |
+
"license": "MIT",
|
| 1710 |
+
"bin": {
|
| 1711 |
+
"esbuild": "bin/esbuild"
|
| 1712 |
+
},
|
| 1713 |
+
"engines": {
|
| 1714 |
+
"node": ">=18"
|
| 1715 |
+
},
|
| 1716 |
+
"optionalDependencies": {
|
| 1717 |
+
"@esbuild/aix-ppc64": "0.25.12",
|
| 1718 |
+
"@esbuild/android-arm": "0.25.12",
|
| 1719 |
+
"@esbuild/android-arm64": "0.25.12",
|
| 1720 |
+
"@esbuild/android-x64": "0.25.12",
|
| 1721 |
+
"@esbuild/darwin-arm64": "0.25.12",
|
| 1722 |
+
"@esbuild/darwin-x64": "0.25.12",
|
| 1723 |
+
"@esbuild/freebsd-arm64": "0.25.12",
|
| 1724 |
+
"@esbuild/freebsd-x64": "0.25.12",
|
| 1725 |
+
"@esbuild/linux-arm": "0.25.12",
|
| 1726 |
+
"@esbuild/linux-arm64": "0.25.12",
|
| 1727 |
+
"@esbuild/linux-ia32": "0.25.12",
|
| 1728 |
+
"@esbuild/linux-loong64": "0.25.12",
|
| 1729 |
+
"@esbuild/linux-mips64el": "0.25.12",
|
| 1730 |
+
"@esbuild/linux-ppc64": "0.25.12",
|
| 1731 |
+
"@esbuild/linux-riscv64": "0.25.12",
|
| 1732 |
+
"@esbuild/linux-s390x": "0.25.12",
|
| 1733 |
+
"@esbuild/linux-x64": "0.25.12",
|
| 1734 |
+
"@esbuild/netbsd-arm64": "0.25.12",
|
| 1735 |
+
"@esbuild/netbsd-x64": "0.25.12",
|
| 1736 |
+
"@esbuild/openbsd-arm64": "0.25.12",
|
| 1737 |
+
"@esbuild/openbsd-x64": "0.25.12",
|
| 1738 |
+
"@esbuild/openharmony-arm64": "0.25.12",
|
| 1739 |
+
"@esbuild/sunos-x64": "0.25.12",
|
| 1740 |
+
"@esbuild/win32-arm64": "0.25.12",
|
| 1741 |
+
"@esbuild/win32-ia32": "0.25.12",
|
| 1742 |
+
"@esbuild/win32-x64": "0.25.12"
|
| 1743 |
+
}
|
| 1744 |
+
},
|
| 1745 |
+
"node_modules/escalade": {
|
| 1746 |
+
"version": "3.2.0",
|
| 1747 |
+
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
| 1748 |
+
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
| 1749 |
+
"dev": true,
|
| 1750 |
+
"license": "MIT",
|
| 1751 |
+
"engines": {
|
| 1752 |
+
"node": ">=6"
|
| 1753 |
+
}
|
| 1754 |
+
},
|
| 1755 |
+
"node_modules/fancy-canvas": {
|
| 1756 |
+
"version": "2.1.0",
|
| 1757 |
+
"resolved": "https://registry.npmjs.org/fancy-canvas/-/fancy-canvas-2.1.0.tgz",
|
| 1758 |
+
"integrity": "sha512-nifxXJ95JNLFR2NgRV4/MxVP45G9909wJTEKz5fg/TZS20JJZA6hfgRVh/bC9bwl2zBtBNcYPjiBE4njQHVBwQ==",
|
| 1759 |
+
"license": "MIT"
|
| 1760 |
+
},
|
| 1761 |
+
"node_modules/fdir": {
|
| 1762 |
+
"version": "6.5.0",
|
| 1763 |
+
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
| 1764 |
+
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
| 1765 |
+
"dev": true,
|
| 1766 |
+
"license": "MIT",
|
| 1767 |
+
"engines": {
|
| 1768 |
+
"node": ">=12.0.0"
|
| 1769 |
+
},
|
| 1770 |
+
"peerDependencies": {
|
| 1771 |
+
"picomatch": "^3 || ^4"
|
| 1772 |
+
},
|
| 1773 |
+
"peerDependenciesMeta": {
|
| 1774 |
+
"picomatch": {
|
| 1775 |
+
"optional": true
|
| 1776 |
+
}
|
| 1777 |
+
}
|
| 1778 |
+
},
|
| 1779 |
+
"node_modules/fraction.js": {
|
| 1780 |
+
"version": "5.3.4",
|
| 1781 |
+
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
|
| 1782 |
+
"integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
|
| 1783 |
+
"dev": true,
|
| 1784 |
+
"license": "MIT",
|
| 1785 |
+
"engines": {
|
| 1786 |
+
"node": "*"
|
| 1787 |
+
},
|
| 1788 |
+
"funding": {
|
| 1789 |
+
"type": "github",
|
| 1790 |
+
"url": "https://github.com/sponsors/rawify"
|
| 1791 |
+
}
|
| 1792 |
+
},
|
| 1793 |
+
"node_modules/framer-motion": {
|
| 1794 |
+
"version": "12.38.0",
|
| 1795 |
+
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz",
|
| 1796 |
+
"integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==",
|
| 1797 |
+
"license": "MIT",
|
| 1798 |
+
"dependencies": {
|
| 1799 |
+
"motion-dom": "^12.38.0",
|
| 1800 |
+
"motion-utils": "^12.36.0",
|
| 1801 |
+
"tslib": "^2.4.0"
|
| 1802 |
+
},
|
| 1803 |
+
"peerDependencies": {
|
| 1804 |
+
"@emotion/is-prop-valid": "*",
|
| 1805 |
+
"react": "^18.0.0 || ^19.0.0",
|
| 1806 |
+
"react-dom": "^18.0.0 || ^19.0.0"
|
| 1807 |
+
},
|
| 1808 |
+
"peerDependenciesMeta": {
|
| 1809 |
+
"@emotion/is-prop-valid": {
|
| 1810 |
+
"optional": true
|
| 1811 |
+
},
|
| 1812 |
+
"react": {
|
| 1813 |
+
"optional": true
|
| 1814 |
+
},
|
| 1815 |
+
"react-dom": {
|
| 1816 |
+
"optional": true
|
| 1817 |
+
}
|
| 1818 |
+
}
|
| 1819 |
+
},
|
| 1820 |
+
"node_modules/fsevents": {
|
| 1821 |
+
"version": "2.3.3",
|
| 1822 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 1823 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 1824 |
+
"dev": true,
|
| 1825 |
+
"hasInstallScript": true,
|
| 1826 |
+
"license": "MIT",
|
| 1827 |
+
"optional": true,
|
| 1828 |
+
"os": [
|
| 1829 |
+
"darwin"
|
| 1830 |
+
],
|
| 1831 |
+
"engines": {
|
| 1832 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 1833 |
+
}
|
| 1834 |
+
},
|
| 1835 |
+
"node_modules/gensync": {
|
| 1836 |
+
"version": "1.0.0-beta.2",
|
| 1837 |
+
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
| 1838 |
+
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
| 1839 |
+
"dev": true,
|
| 1840 |
+
"license": "MIT",
|
| 1841 |
+
"engines": {
|
| 1842 |
+
"node": ">=6.9.0"
|
| 1843 |
+
}
|
| 1844 |
+
},
|
| 1845 |
+
"node_modules/graceful-fs": {
|
| 1846 |
+
"version": "4.2.11",
|
| 1847 |
+
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
| 1848 |
+
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
| 1849 |
+
"dev": true,
|
| 1850 |
+
"license": "ISC"
|
| 1851 |
+
},
|
| 1852 |
+
"node_modules/jiti": {
|
| 1853 |
+
"version": "2.6.1",
|
| 1854 |
+
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
| 1855 |
+
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
| 1856 |
+
"dev": true,
|
| 1857 |
+
"license": "MIT",
|
| 1858 |
+
"bin": {
|
| 1859 |
+
"jiti": "lib/jiti-cli.mjs"
|
| 1860 |
+
}
|
| 1861 |
+
},
|
| 1862 |
+
"node_modules/js-tokens": {
|
| 1863 |
+
"version": "4.0.0",
|
| 1864 |
+
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
| 1865 |
+
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
| 1866 |
+
"dev": true,
|
| 1867 |
+
"license": "MIT"
|
| 1868 |
+
},
|
| 1869 |
+
"node_modules/jsesc": {
|
| 1870 |
+
"version": "3.1.0",
|
| 1871 |
+
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
| 1872 |
+
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
| 1873 |
+
"dev": true,
|
| 1874 |
+
"license": "MIT",
|
| 1875 |
+
"bin": {
|
| 1876 |
+
"jsesc": "bin/jsesc"
|
| 1877 |
+
},
|
| 1878 |
+
"engines": {
|
| 1879 |
+
"node": ">=6"
|
| 1880 |
+
}
|
| 1881 |
+
},
|
| 1882 |
+
"node_modules/json5": {
|
| 1883 |
+
"version": "2.2.3",
|
| 1884 |
+
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
| 1885 |
+
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
| 1886 |
+
"dev": true,
|
| 1887 |
+
"license": "MIT",
|
| 1888 |
+
"bin": {
|
| 1889 |
+
"json5": "lib/cli.js"
|
| 1890 |
+
},
|
| 1891 |
+
"engines": {
|
| 1892 |
+
"node": ">=6"
|
| 1893 |
+
}
|
| 1894 |
+
},
|
| 1895 |
+
"node_modules/lightningcss": {
|
| 1896 |
+
"version": "1.32.0",
|
| 1897 |
+
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
| 1898 |
+
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
|
| 1899 |
+
"dev": true,
|
| 1900 |
+
"license": "MPL-2.0",
|
| 1901 |
+
"dependencies": {
|
| 1902 |
+
"detect-libc": "^2.0.3"
|
| 1903 |
+
},
|
| 1904 |
+
"engines": {
|
| 1905 |
+
"node": ">= 12.0.0"
|
| 1906 |
+
},
|
| 1907 |
+
"funding": {
|
| 1908 |
+
"type": "opencollective",
|
| 1909 |
+
"url": "https://opencollective.com/parcel"
|
| 1910 |
+
},
|
| 1911 |
+
"optionalDependencies": {
|
| 1912 |
+
"lightningcss-android-arm64": "1.32.0",
|
| 1913 |
+
"lightningcss-darwin-arm64": "1.32.0",
|
| 1914 |
+
"lightningcss-darwin-x64": "1.32.0",
|
| 1915 |
+
"lightningcss-freebsd-x64": "1.32.0",
|
| 1916 |
+
"lightningcss-linux-arm-gnueabihf": "1.32.0",
|
| 1917 |
+
"lightningcss-linux-arm64-gnu": "1.32.0",
|
| 1918 |
+
"lightningcss-linux-arm64-musl": "1.32.0",
|
| 1919 |
+
"lightningcss-linux-x64-gnu": "1.32.0",
|
| 1920 |
+
"lightningcss-linux-x64-musl": "1.32.0",
|
| 1921 |
+
"lightningcss-win32-arm64-msvc": "1.32.0",
|
| 1922 |
+
"lightningcss-win32-x64-msvc": "1.32.0"
|
| 1923 |
+
}
|
| 1924 |
+
},
|
| 1925 |
+
"node_modules/lightningcss-android-arm64": {
|
| 1926 |
+
"version": "1.32.0",
|
| 1927 |
+
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
|
| 1928 |
+
"integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
|
| 1929 |
+
"cpu": [
|
| 1930 |
+
"arm64"
|
| 1931 |
+
],
|
| 1932 |
+
"dev": true,
|
| 1933 |
+
"license": "MPL-2.0",
|
| 1934 |
+
"optional": true,
|
| 1935 |
+
"os": [
|
| 1936 |
+
"android"
|
| 1937 |
+
],
|
| 1938 |
+
"engines": {
|
| 1939 |
+
"node": ">= 12.0.0"
|
| 1940 |
+
},
|
| 1941 |
+
"funding": {
|
| 1942 |
+
"type": "opencollective",
|
| 1943 |
+
"url": "https://opencollective.com/parcel"
|
| 1944 |
+
}
|
| 1945 |
+
},
|
| 1946 |
+
"node_modules/lightningcss-darwin-arm64": {
|
| 1947 |
+
"version": "1.32.0",
|
| 1948 |
+
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
|
| 1949 |
+
"integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
|
| 1950 |
+
"cpu": [
|
| 1951 |
+
"arm64"
|
| 1952 |
+
],
|
| 1953 |
+
"dev": true,
|
| 1954 |
+
"license": "MPL-2.0",
|
| 1955 |
+
"optional": true,
|
| 1956 |
+
"os": [
|
| 1957 |
+
"darwin"
|
| 1958 |
+
],
|
| 1959 |
+
"engines": {
|
| 1960 |
+
"node": ">= 12.0.0"
|
| 1961 |
+
},
|
| 1962 |
+
"funding": {
|
| 1963 |
+
"type": "opencollective",
|
| 1964 |
+
"url": "https://opencollective.com/parcel"
|
| 1965 |
+
}
|
| 1966 |
+
},
|
| 1967 |
+
"node_modules/lightningcss-darwin-x64": {
|
| 1968 |
+
"version": "1.32.0",
|
| 1969 |
+
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
|
| 1970 |
+
"integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
|
| 1971 |
+
"cpu": [
|
| 1972 |
+
"x64"
|
| 1973 |
+
],
|
| 1974 |
+
"dev": true,
|
| 1975 |
+
"license": "MPL-2.0",
|
| 1976 |
+
"optional": true,
|
| 1977 |
+
"os": [
|
| 1978 |
+
"darwin"
|
| 1979 |
+
],
|
| 1980 |
+
"engines": {
|
| 1981 |
+
"node": ">= 12.0.0"
|
| 1982 |
+
},
|
| 1983 |
+
"funding": {
|
| 1984 |
+
"type": "opencollective",
|
| 1985 |
+
"url": "https://opencollective.com/parcel"
|
| 1986 |
+
}
|
| 1987 |
+
},
|
| 1988 |
+
"node_modules/lightningcss-freebsd-x64": {
|
| 1989 |
+
"version": "1.32.0",
|
| 1990 |
+
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
|
| 1991 |
+
"integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
|
| 1992 |
+
"cpu": [
|
| 1993 |
+
"x64"
|
| 1994 |
+
],
|
| 1995 |
+
"dev": true,
|
| 1996 |
+
"license": "MPL-2.0",
|
| 1997 |
+
"optional": true,
|
| 1998 |
+
"os": [
|
| 1999 |
+
"freebsd"
|
| 2000 |
+
],
|
| 2001 |
+
"engines": {
|
| 2002 |
+
"node": ">= 12.0.0"
|
| 2003 |
+
},
|
| 2004 |
+
"funding": {
|
| 2005 |
+
"type": "opencollective",
|
| 2006 |
+
"url": "https://opencollective.com/parcel"
|
| 2007 |
+
}
|
| 2008 |
+
},
|
| 2009 |
+
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
| 2010 |
+
"version": "1.32.0",
|
| 2011 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
|
| 2012 |
+
"integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
|
| 2013 |
+
"cpu": [
|
| 2014 |
+
"arm"
|
| 2015 |
+
],
|
| 2016 |
+
"dev": true,
|
| 2017 |
+
"license": "MPL-2.0",
|
| 2018 |
+
"optional": true,
|
| 2019 |
+
"os": [
|
| 2020 |
+
"linux"
|
| 2021 |
+
],
|
| 2022 |
+
"engines": {
|
| 2023 |
+
"node": ">= 12.0.0"
|
| 2024 |
+
},
|
| 2025 |
+
"funding": {
|
| 2026 |
+
"type": "opencollective",
|
| 2027 |
+
"url": "https://opencollective.com/parcel"
|
| 2028 |
+
}
|
| 2029 |
+
},
|
| 2030 |
+
"node_modules/lightningcss-linux-arm64-gnu": {
|
| 2031 |
+
"version": "1.32.0",
|
| 2032 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
|
| 2033 |
+
"integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
|
| 2034 |
+
"cpu": [
|
| 2035 |
+
"arm64"
|
| 2036 |
+
],
|
| 2037 |
+
"dev": true,
|
| 2038 |
+
"license": "MPL-2.0",
|
| 2039 |
+
"optional": true,
|
| 2040 |
+
"os": [
|
| 2041 |
+
"linux"
|
| 2042 |
+
],
|
| 2043 |
+
"engines": {
|
| 2044 |
+
"node": ">= 12.0.0"
|
| 2045 |
+
},
|
| 2046 |
+
"funding": {
|
| 2047 |
+
"type": "opencollective",
|
| 2048 |
+
"url": "https://opencollective.com/parcel"
|
| 2049 |
+
}
|
| 2050 |
+
},
|
| 2051 |
+
"node_modules/lightningcss-linux-arm64-musl": {
|
| 2052 |
+
"version": "1.32.0",
|
| 2053 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
|
| 2054 |
+
"integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
|
| 2055 |
+
"cpu": [
|
| 2056 |
+
"arm64"
|
| 2057 |
+
],
|
| 2058 |
+
"dev": true,
|
| 2059 |
+
"license": "MPL-2.0",
|
| 2060 |
+
"optional": true,
|
| 2061 |
+
"os": [
|
| 2062 |
+
"linux"
|
| 2063 |
+
],
|
| 2064 |
+
"engines": {
|
| 2065 |
+
"node": ">= 12.0.0"
|
| 2066 |
+
},
|
| 2067 |
+
"funding": {
|
| 2068 |
+
"type": "opencollective",
|
| 2069 |
+
"url": "https://opencollective.com/parcel"
|
| 2070 |
+
}
|
| 2071 |
+
},
|
| 2072 |
+
"node_modules/lightningcss-linux-x64-gnu": {
|
| 2073 |
+
"version": "1.32.0",
|
| 2074 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
|
| 2075 |
+
"integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
|
| 2076 |
+
"cpu": [
|
| 2077 |
+
"x64"
|
| 2078 |
+
],
|
| 2079 |
+
"dev": true,
|
| 2080 |
+
"license": "MPL-2.0",
|
| 2081 |
+
"optional": true,
|
| 2082 |
+
"os": [
|
| 2083 |
+
"linux"
|
| 2084 |
+
],
|
| 2085 |
+
"engines": {
|
| 2086 |
+
"node": ">= 12.0.0"
|
| 2087 |
+
},
|
| 2088 |
+
"funding": {
|
| 2089 |
+
"type": "opencollective",
|
| 2090 |
+
"url": "https://opencollective.com/parcel"
|
| 2091 |
+
}
|
| 2092 |
+
},
|
| 2093 |
+
"node_modules/lightningcss-linux-x64-musl": {
|
| 2094 |
+
"version": "1.32.0",
|
| 2095 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
|
| 2096 |
+
"integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
|
| 2097 |
+
"cpu": [
|
| 2098 |
+
"x64"
|
| 2099 |
+
],
|
| 2100 |
+
"dev": true,
|
| 2101 |
+
"license": "MPL-2.0",
|
| 2102 |
+
"optional": true,
|
| 2103 |
+
"os": [
|
| 2104 |
+
"linux"
|
| 2105 |
+
],
|
| 2106 |
+
"engines": {
|
| 2107 |
+
"node": ">= 12.0.0"
|
| 2108 |
+
},
|
| 2109 |
+
"funding": {
|
| 2110 |
+
"type": "opencollective",
|
| 2111 |
+
"url": "https://opencollective.com/parcel"
|
| 2112 |
+
}
|
| 2113 |
+
},
|
| 2114 |
+
"node_modules/lightningcss-win32-arm64-msvc": {
|
| 2115 |
+
"version": "1.32.0",
|
| 2116 |
+
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
|
| 2117 |
+
"integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
|
| 2118 |
+
"cpu": [
|
| 2119 |
+
"arm64"
|
| 2120 |
+
],
|
| 2121 |
+
"dev": true,
|
| 2122 |
+
"license": "MPL-2.0",
|
| 2123 |
+
"optional": true,
|
| 2124 |
+
"os": [
|
| 2125 |
+
"win32"
|
| 2126 |
+
],
|
| 2127 |
+
"engines": {
|
| 2128 |
+
"node": ">= 12.0.0"
|
| 2129 |
+
},
|
| 2130 |
+
"funding": {
|
| 2131 |
+
"type": "opencollective",
|
| 2132 |
+
"url": "https://opencollective.com/parcel"
|
| 2133 |
+
}
|
| 2134 |
+
},
|
| 2135 |
+
"node_modules/lightningcss-win32-x64-msvc": {
|
| 2136 |
+
"version": "1.32.0",
|
| 2137 |
+
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
|
| 2138 |
+
"integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
|
| 2139 |
+
"cpu": [
|
| 2140 |
+
"x64"
|
| 2141 |
+
],
|
| 2142 |
+
"dev": true,
|
| 2143 |
+
"license": "MPL-2.0",
|
| 2144 |
+
"optional": true,
|
| 2145 |
+
"os": [
|
| 2146 |
+
"win32"
|
| 2147 |
+
],
|
| 2148 |
+
"engines": {
|
| 2149 |
+
"node": ">= 12.0.0"
|
| 2150 |
+
},
|
| 2151 |
+
"funding": {
|
| 2152 |
+
"type": "opencollective",
|
| 2153 |
+
"url": "https://opencollective.com/parcel"
|
| 2154 |
+
}
|
| 2155 |
+
},
|
| 2156 |
+
"node_modules/lightweight-charts": {
|
| 2157 |
+
"version": "4.2.3",
|
| 2158 |
+
"resolved": "https://registry.npmjs.org/lightweight-charts/-/lightweight-charts-4.2.3.tgz",
|
| 2159 |
+
"integrity": "sha512-5kS/2hY3wNYNzhnS8Gb+GAS07DX8GPF2YVDnd2NMC85gJVQ6RLU6YrXNgNJ6eg0AnWPwCnvaGtYmGky3HiLQEw==",
|
| 2160 |
+
"license": "Apache-2.0",
|
| 2161 |
+
"dependencies": {
|
| 2162 |
+
"fancy-canvas": "2.1.0"
|
| 2163 |
+
}
|
| 2164 |
+
},
|
| 2165 |
+
"node_modules/lru-cache": {
|
| 2166 |
+
"version": "5.1.1",
|
| 2167 |
+
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
| 2168 |
+
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
| 2169 |
+
"dev": true,
|
| 2170 |
+
"license": "ISC",
|
| 2171 |
+
"dependencies": {
|
| 2172 |
+
"yallist": "^3.0.2"
|
| 2173 |
+
}
|
| 2174 |
+
},
|
| 2175 |
+
"node_modules/lucide-react": {
|
| 2176 |
+
"version": "0.546.0",
|
| 2177 |
+
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.546.0.tgz",
|
| 2178 |
+
"integrity": "sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ==",
|
| 2179 |
+
"license": "ISC",
|
| 2180 |
+
"peerDependencies": {
|
| 2181 |
+
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 2182 |
+
}
|
| 2183 |
+
},
|
| 2184 |
+
"node_modules/magic-string": {
|
| 2185 |
+
"version": "0.30.21",
|
| 2186 |
+
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
| 2187 |
+
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
| 2188 |
+
"dev": true,
|
| 2189 |
+
"license": "MIT",
|
| 2190 |
+
"dependencies": {
|
| 2191 |
+
"@jridgewell/sourcemap-codec": "^1.5.5"
|
| 2192 |
+
}
|
| 2193 |
+
},
|
| 2194 |
+
"node_modules/motion": {
|
| 2195 |
+
"version": "12.38.0",
|
| 2196 |
+
"resolved": "https://registry.npmjs.org/motion/-/motion-12.38.0.tgz",
|
| 2197 |
+
"integrity": "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==",
|
| 2198 |
+
"license": "MIT",
|
| 2199 |
+
"dependencies": {
|
| 2200 |
+
"framer-motion": "^12.38.0",
|
| 2201 |
+
"tslib": "^2.4.0"
|
| 2202 |
+
},
|
| 2203 |
+
"peerDependencies": {
|
| 2204 |
+
"@emotion/is-prop-valid": "*",
|
| 2205 |
+
"react": "^18.0.0 || ^19.0.0",
|
| 2206 |
+
"react-dom": "^18.0.0 || ^19.0.0"
|
| 2207 |
+
},
|
| 2208 |
+
"peerDependenciesMeta": {
|
| 2209 |
+
"@emotion/is-prop-valid": {
|
| 2210 |
+
"optional": true
|
| 2211 |
+
},
|
| 2212 |
+
"react": {
|
| 2213 |
+
"optional": true
|
| 2214 |
+
},
|
| 2215 |
+
"react-dom": {
|
| 2216 |
+
"optional": true
|
| 2217 |
+
}
|
| 2218 |
+
}
|
| 2219 |
+
},
|
| 2220 |
+
"node_modules/motion-dom": {
|
| 2221 |
+
"version": "12.38.0",
|
| 2222 |
+
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz",
|
| 2223 |
+
"integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==",
|
| 2224 |
+
"license": "MIT",
|
| 2225 |
+
"dependencies": {
|
| 2226 |
+
"motion-utils": "^12.36.0"
|
| 2227 |
+
}
|
| 2228 |
+
},
|
| 2229 |
+
"node_modules/motion-utils": {
|
| 2230 |
+
"version": "12.36.0",
|
| 2231 |
+
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz",
|
| 2232 |
+
"integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==",
|
| 2233 |
+
"license": "MIT"
|
| 2234 |
+
},
|
| 2235 |
+
"node_modules/ms": {
|
| 2236 |
+
"version": "2.1.3",
|
| 2237 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 2238 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 2239 |
+
"dev": true,
|
| 2240 |
+
"license": "MIT"
|
| 2241 |
+
},
|
| 2242 |
+
"node_modules/nanoid": {
|
| 2243 |
+
"version": "3.3.11",
|
| 2244 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
| 2245 |
+
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
| 2246 |
+
"dev": true,
|
| 2247 |
+
"funding": [
|
| 2248 |
+
{
|
| 2249 |
+
"type": "github",
|
| 2250 |
+
"url": "https://github.com/sponsors/ai"
|
| 2251 |
+
}
|
| 2252 |
+
],
|
| 2253 |
+
"license": "MIT",
|
| 2254 |
+
"bin": {
|
| 2255 |
+
"nanoid": "bin/nanoid.cjs"
|
| 2256 |
+
},
|
| 2257 |
+
"engines": {
|
| 2258 |
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
| 2259 |
+
}
|
| 2260 |
+
},
|
| 2261 |
+
"node_modules/node-releases": {
|
| 2262 |
+
"version": "2.0.38",
|
| 2263 |
+
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz",
|
| 2264 |
+
"integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==",
|
| 2265 |
+
"dev": true,
|
| 2266 |
+
"license": "MIT"
|
| 2267 |
+
},
|
| 2268 |
+
"node_modules/picocolors": {
|
| 2269 |
+
"version": "1.1.1",
|
| 2270 |
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 2271 |
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 2272 |
+
"dev": true,
|
| 2273 |
+
"license": "ISC"
|
| 2274 |
+
},
|
| 2275 |
+
"node_modules/picomatch": {
|
| 2276 |
+
"version": "4.0.4",
|
| 2277 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
| 2278 |
+
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
| 2279 |
+
"dev": true,
|
| 2280 |
+
"license": "MIT",
|
| 2281 |
+
"engines": {
|
| 2282 |
+
"node": ">=12"
|
| 2283 |
+
},
|
| 2284 |
+
"funding": {
|
| 2285 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 2286 |
+
}
|
| 2287 |
+
},
|
| 2288 |
+
"node_modules/postcss": {
|
| 2289 |
+
"version": "8.5.10",
|
| 2290 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
|
| 2291 |
+
"integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
|
| 2292 |
+
"dev": true,
|
| 2293 |
+
"funding": [
|
| 2294 |
+
{
|
| 2295 |
+
"type": "opencollective",
|
| 2296 |
+
"url": "https://opencollective.com/postcss/"
|
| 2297 |
+
},
|
| 2298 |
+
{
|
| 2299 |
+
"type": "tidelift",
|
| 2300 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 2301 |
+
},
|
| 2302 |
+
{
|
| 2303 |
+
"type": "github",
|
| 2304 |
+
"url": "https://github.com/sponsors/ai"
|
| 2305 |
+
}
|
| 2306 |
+
],
|
| 2307 |
+
"license": "MIT",
|
| 2308 |
+
"dependencies": {
|
| 2309 |
+
"nanoid": "^3.3.11",
|
| 2310 |
+
"picocolors": "^1.1.1",
|
| 2311 |
+
"source-map-js": "^1.2.1"
|
| 2312 |
+
},
|
| 2313 |
+
"engines": {
|
| 2314 |
+
"node": "^10 || ^12 || >=14"
|
| 2315 |
+
}
|
| 2316 |
+
},
|
| 2317 |
+
"node_modules/postcss-value-parser": {
|
| 2318 |
+
"version": "4.2.0",
|
| 2319 |
+
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
| 2320 |
+
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
| 2321 |
+
"dev": true,
|
| 2322 |
+
"license": "MIT"
|
| 2323 |
+
},
|
| 2324 |
+
"node_modules/react": {
|
| 2325 |
+
"version": "19.2.5",
|
| 2326 |
+
"resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
|
| 2327 |
+
"integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
|
| 2328 |
+
"license": "MIT",
|
| 2329 |
+
"engines": {
|
| 2330 |
+
"node": ">=0.10.0"
|
| 2331 |
+
}
|
| 2332 |
+
},
|
| 2333 |
+
"node_modules/react-dom": {
|
| 2334 |
+
"version": "19.2.5",
|
| 2335 |
+
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz",
|
| 2336 |
+
"integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==",
|
| 2337 |
+
"license": "MIT",
|
| 2338 |
+
"dependencies": {
|
| 2339 |
+
"scheduler": "^0.27.0"
|
| 2340 |
+
},
|
| 2341 |
+
"peerDependencies": {
|
| 2342 |
+
"react": "^19.2.5"
|
| 2343 |
+
}
|
| 2344 |
+
},
|
| 2345 |
+
"node_modules/react-refresh": {
|
| 2346 |
+
"version": "0.18.0",
|
| 2347 |
+
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
|
| 2348 |
+
"integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
|
| 2349 |
+
"dev": true,
|
| 2350 |
+
"license": "MIT",
|
| 2351 |
+
"engines": {
|
| 2352 |
+
"node": ">=0.10.0"
|
| 2353 |
+
}
|
| 2354 |
+
},
|
| 2355 |
+
"node_modules/rollup": {
|
| 2356 |
+
"version": "4.60.2",
|
| 2357 |
+
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz",
|
| 2358 |
+
"integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==",
|
| 2359 |
+
"dev": true,
|
| 2360 |
+
"license": "MIT",
|
| 2361 |
+
"dependencies": {
|
| 2362 |
+
"@types/estree": "1.0.8"
|
| 2363 |
+
},
|
| 2364 |
+
"bin": {
|
| 2365 |
+
"rollup": "dist/bin/rollup"
|
| 2366 |
+
},
|
| 2367 |
+
"engines": {
|
| 2368 |
+
"node": ">=18.0.0",
|
| 2369 |
+
"npm": ">=8.0.0"
|
| 2370 |
+
},
|
| 2371 |
+
"optionalDependencies": {
|
| 2372 |
+
"@rollup/rollup-android-arm-eabi": "4.60.2",
|
| 2373 |
+
"@rollup/rollup-android-arm64": "4.60.2",
|
| 2374 |
+
"@rollup/rollup-darwin-arm64": "4.60.2",
|
| 2375 |
+
"@rollup/rollup-darwin-x64": "4.60.2",
|
| 2376 |
+
"@rollup/rollup-freebsd-arm64": "4.60.2",
|
| 2377 |
+
"@rollup/rollup-freebsd-x64": "4.60.2",
|
| 2378 |
+
"@rollup/rollup-linux-arm-gnueabihf": "4.60.2",
|
| 2379 |
+
"@rollup/rollup-linux-arm-musleabihf": "4.60.2",
|
| 2380 |
+
"@rollup/rollup-linux-arm64-gnu": "4.60.2",
|
| 2381 |
+
"@rollup/rollup-linux-arm64-musl": "4.60.2",
|
| 2382 |
+
"@rollup/rollup-linux-loong64-gnu": "4.60.2",
|
| 2383 |
+
"@rollup/rollup-linux-loong64-musl": "4.60.2",
|
| 2384 |
+
"@rollup/rollup-linux-ppc64-gnu": "4.60.2",
|
| 2385 |
+
"@rollup/rollup-linux-ppc64-musl": "4.60.2",
|
| 2386 |
+
"@rollup/rollup-linux-riscv64-gnu": "4.60.2",
|
| 2387 |
+
"@rollup/rollup-linux-riscv64-musl": "4.60.2",
|
| 2388 |
+
"@rollup/rollup-linux-s390x-gnu": "4.60.2",
|
| 2389 |
+
"@rollup/rollup-linux-x64-gnu": "4.60.2",
|
| 2390 |
+
"@rollup/rollup-linux-x64-musl": "4.60.2",
|
| 2391 |
+
"@rollup/rollup-openbsd-x64": "4.60.2",
|
| 2392 |
+
"@rollup/rollup-openharmony-arm64": "4.60.2",
|
| 2393 |
+
"@rollup/rollup-win32-arm64-msvc": "4.60.2",
|
| 2394 |
+
"@rollup/rollup-win32-ia32-msvc": "4.60.2",
|
| 2395 |
+
"@rollup/rollup-win32-x64-gnu": "4.60.2",
|
| 2396 |
+
"@rollup/rollup-win32-x64-msvc": "4.60.2",
|
| 2397 |
+
"fsevents": "~2.3.2"
|
| 2398 |
+
}
|
| 2399 |
+
},
|
| 2400 |
+
"node_modules/scheduler": {
|
| 2401 |
+
"version": "0.27.0",
|
| 2402 |
+
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
| 2403 |
+
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
| 2404 |
+
"license": "MIT"
|
| 2405 |
+
},
|
| 2406 |
+
"node_modules/semver": {
|
| 2407 |
+
"version": "6.3.1",
|
| 2408 |
+
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
| 2409 |
+
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
| 2410 |
+
"dev": true,
|
| 2411 |
+
"license": "ISC",
|
| 2412 |
+
"bin": {
|
| 2413 |
+
"semver": "bin/semver.js"
|
| 2414 |
+
}
|
| 2415 |
+
},
|
| 2416 |
+
"node_modules/source-map-js": {
|
| 2417 |
+
"version": "1.2.1",
|
| 2418 |
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
| 2419 |
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
| 2420 |
+
"dev": true,
|
| 2421 |
+
"license": "BSD-3-Clause",
|
| 2422 |
+
"engines": {
|
| 2423 |
+
"node": ">=0.10.0"
|
| 2424 |
+
}
|
| 2425 |
+
},
|
| 2426 |
+
"node_modules/tailwindcss": {
|
| 2427 |
+
"version": "4.2.4",
|
| 2428 |
+
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz",
|
| 2429 |
+
"integrity": "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==",
|
| 2430 |
+
"dev": true,
|
| 2431 |
+
"license": "MIT"
|
| 2432 |
+
},
|
| 2433 |
+
"node_modules/tapable": {
|
| 2434 |
+
"version": "2.3.3",
|
| 2435 |
+
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
|
| 2436 |
+
"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
|
| 2437 |
+
"dev": true,
|
| 2438 |
+
"license": "MIT",
|
| 2439 |
+
"engines": {
|
| 2440 |
+
"node": ">=6"
|
| 2441 |
+
},
|
| 2442 |
+
"funding": {
|
| 2443 |
+
"type": "opencollective",
|
| 2444 |
+
"url": "https://opencollective.com/webpack"
|
| 2445 |
+
}
|
| 2446 |
+
},
|
| 2447 |
+
"node_modules/tinyglobby": {
|
| 2448 |
+
"version": "0.2.16",
|
| 2449 |
+
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
| 2450 |
+
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
|
| 2451 |
+
"dev": true,
|
| 2452 |
+
"license": "MIT",
|
| 2453 |
+
"dependencies": {
|
| 2454 |
+
"fdir": "^6.5.0",
|
| 2455 |
+
"picomatch": "^4.0.4"
|
| 2456 |
+
},
|
| 2457 |
+
"engines": {
|
| 2458 |
+
"node": ">=12.0.0"
|
| 2459 |
+
},
|
| 2460 |
+
"funding": {
|
| 2461 |
+
"url": "https://github.com/sponsors/SuperchupuDev"
|
| 2462 |
+
}
|
| 2463 |
+
},
|
| 2464 |
+
"node_modules/tslib": {
|
| 2465 |
+
"version": "2.8.1",
|
| 2466 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 2467 |
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 2468 |
+
"license": "0BSD"
|
| 2469 |
+
},
|
| 2470 |
+
"node_modules/typescript": {
|
| 2471 |
+
"version": "5.8.3",
|
| 2472 |
+
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
| 2473 |
+
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
| 2474 |
+
"dev": true,
|
| 2475 |
+
"license": "Apache-2.0",
|
| 2476 |
+
"bin": {
|
| 2477 |
+
"tsc": "bin/tsc",
|
| 2478 |
+
"tsserver": "bin/tsserver"
|
| 2479 |
+
},
|
| 2480 |
+
"engines": {
|
| 2481 |
+
"node": ">=14.17"
|
| 2482 |
+
}
|
| 2483 |
+
},
|
| 2484 |
+
"node_modules/undici-types": {
|
| 2485 |
+
"version": "6.21.0",
|
| 2486 |
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
| 2487 |
+
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
| 2488 |
+
"dev": true,
|
| 2489 |
+
"license": "MIT"
|
| 2490 |
+
},
|
| 2491 |
+
"node_modules/update-browserslist-db": {
|
| 2492 |
+
"version": "1.2.3",
|
| 2493 |
+
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
| 2494 |
+
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
|
| 2495 |
+
"dev": true,
|
| 2496 |
+
"funding": [
|
| 2497 |
+
{
|
| 2498 |
+
"type": "opencollective",
|
| 2499 |
+
"url": "https://opencollective.com/browserslist"
|
| 2500 |
+
},
|
| 2501 |
+
{
|
| 2502 |
+
"type": "tidelift",
|
| 2503 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 2504 |
+
},
|
| 2505 |
+
{
|
| 2506 |
+
"type": "github",
|
| 2507 |
+
"url": "https://github.com/sponsors/ai"
|
| 2508 |
+
}
|
| 2509 |
+
],
|
| 2510 |
+
"license": "MIT",
|
| 2511 |
+
"dependencies": {
|
| 2512 |
+
"escalade": "^3.2.0",
|
| 2513 |
+
"picocolors": "^1.1.1"
|
| 2514 |
+
},
|
| 2515 |
+
"bin": {
|
| 2516 |
+
"update-browserslist-db": "cli.js"
|
| 2517 |
+
},
|
| 2518 |
+
"peerDependencies": {
|
| 2519 |
+
"browserslist": ">= 4.21.0"
|
| 2520 |
+
}
|
| 2521 |
+
},
|
| 2522 |
+
"node_modules/vite": {
|
| 2523 |
+
"version": "6.4.2",
|
| 2524 |
+
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
|
| 2525 |
+
"integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
|
| 2526 |
+
"dev": true,
|
| 2527 |
+
"license": "MIT",
|
| 2528 |
+
"dependencies": {
|
| 2529 |
+
"esbuild": "^0.25.0",
|
| 2530 |
+
"fdir": "^6.4.4",
|
| 2531 |
+
"picomatch": "^4.0.2",
|
| 2532 |
+
"postcss": "^8.5.3",
|
| 2533 |
+
"rollup": "^4.34.9",
|
| 2534 |
+
"tinyglobby": "^0.2.13"
|
| 2535 |
+
},
|
| 2536 |
+
"bin": {
|
| 2537 |
+
"vite": "bin/vite.js"
|
| 2538 |
+
},
|
| 2539 |
+
"engines": {
|
| 2540 |
+
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
| 2541 |
+
},
|
| 2542 |
+
"funding": {
|
| 2543 |
+
"url": "https://github.com/vitejs/vite?sponsor=1"
|
| 2544 |
+
},
|
| 2545 |
+
"optionalDependencies": {
|
| 2546 |
+
"fsevents": "~2.3.3"
|
| 2547 |
+
},
|
| 2548 |
+
"peerDependencies": {
|
| 2549 |
+
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
| 2550 |
+
"jiti": ">=1.21.0",
|
| 2551 |
+
"less": "*",
|
| 2552 |
+
"lightningcss": "^1.21.0",
|
| 2553 |
+
"sass": "*",
|
| 2554 |
+
"sass-embedded": "*",
|
| 2555 |
+
"stylus": "*",
|
| 2556 |
+
"sugarss": "*",
|
| 2557 |
+
"terser": "^5.16.0",
|
| 2558 |
+
"tsx": "^4.8.1",
|
| 2559 |
+
"yaml": "^2.4.2"
|
| 2560 |
+
},
|
| 2561 |
+
"peerDependenciesMeta": {
|
| 2562 |
+
"@types/node": {
|
| 2563 |
+
"optional": true
|
| 2564 |
+
},
|
| 2565 |
+
"jiti": {
|
| 2566 |
+
"optional": true
|
| 2567 |
+
},
|
| 2568 |
+
"less": {
|
| 2569 |
+
"optional": true
|
| 2570 |
+
},
|
| 2571 |
+
"lightningcss": {
|
| 2572 |
+
"optional": true
|
| 2573 |
+
},
|
| 2574 |
+
"sass": {
|
| 2575 |
+
"optional": true
|
| 2576 |
+
},
|
| 2577 |
+
"sass-embedded": {
|
| 2578 |
+
"optional": true
|
| 2579 |
+
},
|
| 2580 |
+
"stylus": {
|
| 2581 |
+
"optional": true
|
| 2582 |
+
},
|
| 2583 |
+
"sugarss": {
|
| 2584 |
+
"optional": true
|
| 2585 |
+
},
|
| 2586 |
+
"terser": {
|
| 2587 |
+
"optional": true
|
| 2588 |
+
},
|
| 2589 |
+
"tsx": {
|
| 2590 |
+
"optional": true
|
| 2591 |
+
},
|
| 2592 |
+
"yaml": {
|
| 2593 |
+
"optional": true
|
| 2594 |
+
}
|
| 2595 |
+
}
|
| 2596 |
+
},
|
| 2597 |
+
"node_modules/yallist": {
|
| 2598 |
+
"version": "3.1.1",
|
| 2599 |
+
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
| 2600 |
+
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
| 2601 |
+
"dev": true,
|
| 2602 |
+
"license": "ISC"
|
| 2603 |
+
}
|
| 2604 |
+
}
|
| 2605 |
+
}
|
frontend/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "stocker-ai-frontend",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.1.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite --port=3000 --host=0.0.0.0",
|
| 8 |
+
"build": "vite build",
|
| 9 |
+
"preview": "vite preview",
|
| 10 |
+
"clean": "rm -rf dist",
|
| 11 |
+
"lint": "tsc --noEmit"
|
| 12 |
+
},
|
| 13 |
+
"dependencies": {
|
| 14 |
+
"lightweight-charts": "^4.2.3",
|
| 15 |
+
"lucide-react": "^0.546.0",
|
| 16 |
+
"motion": "^12.23.24",
|
| 17 |
+
"react": "^19.0.0",
|
| 18 |
+
"react-dom": "^19.0.0"
|
| 19 |
+
},
|
| 20 |
+
"devDependencies": {
|
| 21 |
+
"@tailwindcss/vite": "^4.1.14",
|
| 22 |
+
"@types/node": "^22.14.0",
|
| 23 |
+
"@types/react": "^19.2.14",
|
| 24 |
+
"@types/react-dom": "^19.2.3",
|
| 25 |
+
"@vitejs/plugin-react": "^5.0.4",
|
| 26 |
+
"autoprefixer": "^10.4.21",
|
| 27 |
+
"tailwindcss": "^4.1.14",
|
| 28 |
+
"typescript": "~5.8.2",
|
| 29 |
+
"vite": "^6.2.0"
|
| 30 |
+
}
|
| 31 |
+
}
|
frontend/src/App.tsx
ADDED
|
@@ -0,0 +1,1554 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @license
|
| 3 |
+
* SPDX-License-Identifier: Apache-2.0
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
| 7 |
+
import {
|
| 8 |
+
Monitor,
|
| 9 |
+
Users,
|
| 10 |
+
TrendingUp,
|
| 11 |
+
Library,
|
| 12 |
+
Wallet,
|
| 13 |
+
Brain,
|
| 14 |
+
FileText,
|
| 15 |
+
LifeBuoy,
|
| 16 |
+
Bell,
|
| 17 |
+
CreditCard,
|
| 18 |
+
User,
|
| 19 |
+
Search,
|
| 20 |
+
ChevronUp,
|
| 21 |
+
Cpu,
|
| 22 |
+
BarChart3,
|
| 23 |
+
Globe,
|
| 24 |
+
Newspaper,
|
| 25 |
+
Zap,
|
| 26 |
+
Activity,
|
| 27 |
+
Calendar,
|
| 28 |
+
LineChart,
|
| 29 |
+
} from 'lucide-react';
|
| 30 |
+
import { motion, AnimatePresence } from 'motion/react';
|
| 31 |
+
import {
|
| 32 |
+
CrosshairMode,
|
| 33 |
+
ColorType,
|
| 34 |
+
createChart,
|
| 35 |
+
type IChartApi,
|
| 36 |
+
type ISeriesApi,
|
| 37 |
+
} from 'lightweight-charts';
|
| 38 |
+
|
| 39 |
+
import {
|
| 40 |
+
api,
|
| 41 |
+
SPECIALIST_DISPLAY,
|
| 42 |
+
statusFromSignal,
|
| 43 |
+
useStockerEnv,
|
| 44 |
+
type CouncilDecision,
|
| 45 |
+
type EnvironmentState,
|
| 46 |
+
type MarketObservation,
|
| 47 |
+
type OhlcvBar,
|
| 48 |
+
type OhlcvResponse,
|
| 49 |
+
type Side,
|
| 50 |
+
type StockerEnv,
|
| 51 |
+
type TrainingMetrics,
|
| 52 |
+
} from './api';
|
| 53 |
+
|
| 54 |
+
// --------------------------------------------------------------------- atoms
|
| 55 |
+
const SidebarItem = ({
|
| 56 |
+
icon: Icon,
|
| 57 |
+
label,
|
| 58 |
+
active,
|
| 59 |
+
onClick,
|
| 60 |
+
}: {
|
| 61 |
+
icon: any;
|
| 62 |
+
label: string;
|
| 63 |
+
active: boolean;
|
| 64 |
+
onClick: () => void;
|
| 65 |
+
}) => (
|
| 66 |
+
<button
|
| 67 |
+
onClick={onClick}
|
| 68 |
+
className={`w-full flex items-center gap-3 px-4 py-2.5 rounded-lg transition-all duration-200 group relative ${
|
| 69 |
+
active
|
| 70 |
+
? 'text-[#00FF41] bg-gradient-to-r from-[#00FF41]/10 to-transparent border-l-2 border-[#00FF41]'
|
| 71 |
+
: 'text-neutral-400 hover:bg-white/5 hover:text-[#00FF41] hover:translate-x-1'
|
| 72 |
+
}`}
|
| 73 |
+
>
|
| 74 |
+
<Icon className={`w-5 h-5 ${active ? 'text-[#00FF41]' : 'group-hover:text-[#00FF41]'}`} />
|
| 75 |
+
<span className="font-medium text-[13px] tracking-tight">{label}</span>
|
| 76 |
+
</button>
|
| 77 |
+
);
|
| 78 |
+
|
| 79 |
+
const AgentCard = ({
|
| 80 |
+
title,
|
| 81 |
+
icon: Icon,
|
| 82 |
+
stat,
|
| 83 |
+
color,
|
| 84 |
+
children,
|
| 85 |
+
active,
|
| 86 |
+
}: {
|
| 87 |
+
title: string;
|
| 88 |
+
icon: any;
|
| 89 |
+
stat?: string;
|
| 90 |
+
color: string;
|
| 91 |
+
children: any;
|
| 92 |
+
active?: boolean;
|
| 93 |
+
}) => (
|
| 94 |
+
<div
|
| 95 |
+
className={`glass-panel rounded-xl flex flex-col transition-all duration-300 group hover:border-${color}/40 ${
|
| 96 |
+
active ? `border-${color}/30 shadow-[0_0_30px_rgba(115,31,255,0.1)]` : ''
|
| 97 |
+
}`}
|
| 98 |
+
>
|
| 99 |
+
<div className="p-3 px-4 border-b border-white/5 flex justify-between items-center bg-white/[0.02]">
|
| 100 |
+
<div className="flex items-center gap-2">
|
| 101 |
+
<Icon className={`w-4 h-4 text-${color}`} />
|
| 102 |
+
<h3 className="font-semibold text-sm text-[#e5e2e1]">{title}</h3>
|
| 103 |
+
</div>
|
| 104 |
+
{stat && <span className={`font-mono text-xs font-bold text-${color}`}>{stat}</span>}
|
| 105 |
+
</div>
|
| 106 |
+
<div className="p-4 flex-1 flex flex-col gap-3">{children}</div>
|
| 107 |
+
</div>
|
| 108 |
+
);
|
| 109 |
+
|
| 110 |
+
// ---------------------------------------------------------------------- chart
|
| 111 |
+
|
| 112 |
+
const CandlestickChart = ({ bars }: { bars: OhlcvBar[] }) => {
|
| 113 |
+
const containerRef = useRef<HTMLDivElement | null>(null);
|
| 114 |
+
const chartRef = useRef<IChartApi | null>(null);
|
| 115 |
+
const candleRef = useRef<ISeriesApi<'Candlestick'> | null>(null);
|
| 116 |
+
const volumeRef = useRef<ISeriesApi<'Histogram'> | null>(null);
|
| 117 |
+
|
| 118 |
+
// mount once
|
| 119 |
+
useEffect(() => {
|
| 120 |
+
if (!containerRef.current) return;
|
| 121 |
+
const chart = createChart(containerRef.current, {
|
| 122 |
+
layout: {
|
| 123 |
+
background: { type: ColorType.Solid, color: 'transparent' },
|
| 124 |
+
textColor: '#a3a3a3',
|
| 125 |
+
fontFamily: 'JetBrains Mono, ui-monospace, monospace',
|
| 126 |
+
},
|
| 127 |
+
grid: {
|
| 128 |
+
vertLines: { color: 'rgba(255,255,255,0.04)' },
|
| 129 |
+
horzLines: { color: 'rgba(255,255,255,0.04)' },
|
| 130 |
+
},
|
| 131 |
+
timeScale: {
|
| 132 |
+
borderVisible: false,
|
| 133 |
+
timeVisible: false,
|
| 134 |
+
secondsVisible: false,
|
| 135 |
+
},
|
| 136 |
+
rightPriceScale: {
|
| 137 |
+
borderVisible: false,
|
| 138 |
+
scaleMargins: { top: 0.05, bottom: 0.28 },
|
| 139 |
+
},
|
| 140 |
+
crosshair: {
|
| 141 |
+
mode: CrosshairMode.Normal,
|
| 142 |
+
vertLine: { color: 'rgba(0,255,65,0.3)', width: 1, style: 3, labelBackgroundColor: '#00FF41' },
|
| 143 |
+
horzLine: { color: 'rgba(0,255,65,0.3)', width: 1, style: 3, labelBackgroundColor: '#00FF41' },
|
| 144 |
+
},
|
| 145 |
+
autoSize: false,
|
| 146 |
+
});
|
| 147 |
+
chartRef.current = chart;
|
| 148 |
+
|
| 149 |
+
candleRef.current = chart.addCandlestickSeries({
|
| 150 |
+
upColor: '#00FF41',
|
| 151 |
+
downColor: '#ffb4ab',
|
| 152 |
+
wickUpColor: '#00FF41',
|
| 153 |
+
wickDownColor: '#ffb4ab',
|
| 154 |
+
borderVisible: false,
|
| 155 |
+
});
|
| 156 |
+
|
| 157 |
+
volumeRef.current = chart.addHistogramSeries({
|
| 158 |
+
priceFormat: { type: 'volume' },
|
| 159 |
+
priceScaleId: '',
|
| 160 |
+
color: 'rgba(0,255,65,0.3)',
|
| 161 |
+
});
|
| 162 |
+
volumeRef.current.priceScale().applyOptions({
|
| 163 |
+
scaleMargins: { top: 0.78, bottom: 0 },
|
| 164 |
+
});
|
| 165 |
+
|
| 166 |
+
const ro = new ResizeObserver((entries) => {
|
| 167 |
+
const rect = entries[0]?.contentRect;
|
| 168 |
+
if (rect) chart.applyOptions({ width: Math.floor(rect.width), height: Math.floor(rect.height) });
|
| 169 |
+
});
|
| 170 |
+
ro.observe(containerRef.current);
|
| 171 |
+
|
| 172 |
+
return () => {
|
| 173 |
+
ro.disconnect();
|
| 174 |
+
chart.remove();
|
| 175 |
+
chartRef.current = null;
|
| 176 |
+
candleRef.current = null;
|
| 177 |
+
volumeRef.current = null;
|
| 178 |
+
};
|
| 179 |
+
}, []);
|
| 180 |
+
|
| 181 |
+
// push data when bars change
|
| 182 |
+
useEffect(() => {
|
| 183 |
+
if (!candleRef.current || !volumeRef.current) return;
|
| 184 |
+
if (!bars || bars.length === 0) {
|
| 185 |
+
candleRef.current.setData([]);
|
| 186 |
+
volumeRef.current.setData([]);
|
| 187 |
+
return;
|
| 188 |
+
}
|
| 189 |
+
candleRef.current.setData(
|
| 190 |
+
bars.map((b) => ({
|
| 191 |
+
time: b.time,
|
| 192 |
+
open: b.open,
|
| 193 |
+
high: b.high,
|
| 194 |
+
low: b.low,
|
| 195 |
+
close: b.close,
|
| 196 |
+
})),
|
| 197 |
+
);
|
| 198 |
+
volumeRef.current.setData(
|
| 199 |
+
bars.map((b) => ({
|
| 200 |
+
time: b.time,
|
| 201 |
+
value: b.volume,
|
| 202 |
+
color: b.close >= b.open ? 'rgba(0,255,65,0.35)' : 'rgba(255,180,171,0.35)',
|
| 203 |
+
})),
|
| 204 |
+
);
|
| 205 |
+
chartRef.current?.timeScale().fitContent();
|
| 206 |
+
}, [bars]);
|
| 207 |
+
|
| 208 |
+
return <div ref={containerRef} className="flex-1 min-h-[300px] w-full" />;
|
| 209 |
+
};
|
| 210 |
+
|
| 211 |
+
// ---------------------------------------------------------------- TerminalView
|
| 212 |
+
|
| 213 |
+
const TerminalView = ({ env }: { env: StockerEnv }) => {
|
| 214 |
+
const { observation, envState, council, ohlcv, startingCash, submitTrade, loading } = env;
|
| 215 |
+
|
| 216 |
+
const [side, setSide] = useState<Side>('buy');
|
| 217 |
+
const [qty, setQty] = useState<number>(10);
|
| 218 |
+
|
| 219 |
+
const change = useMemo(() => {
|
| 220 |
+
if (!ohlcv || ohlcv.bars.length < 2) return 0;
|
| 221 |
+
const last = ohlcv.bars[ohlcv.bars.length - 1];
|
| 222 |
+
const prev = ohlcv.bars[ohlcv.bars.length - 2];
|
| 223 |
+
return ((last.close - prev.close) / prev.close) * 100;
|
| 224 |
+
}, [ohlcv]);
|
| 225 |
+
|
| 226 |
+
const lastVolume = ohlcv?.bars[ohlcv.bars.length - 1]?.volume ?? 0;
|
| 227 |
+
const portfolioValue = envState?.portfolio_value ?? observation?.portfolio_value ?? 0;
|
| 228 |
+
const alphaPct = portfolioValue && startingCash > 0
|
| 229 |
+
? ((portfolioValue - startingCash) / startingCash) * 100
|
| 230 |
+
: 0;
|
| 231 |
+
|
| 232 |
+
const consensus = council && council.votes.length > 0
|
| 233 |
+
? council.votes.reduce((a, v) => a + v.signal, 0) / council.votes.length
|
| 234 |
+
: 0;
|
| 235 |
+
|
| 236 |
+
const submitDisabled = loading || !observation || envState?.done;
|
| 237 |
+
|
| 238 |
+
return (
|
| 239 |
+
<div className="flex-1 flex flex-col gap-4 min-h-0">
|
| 240 |
+
{/* Top Ticker Bar */}
|
| 241 |
+
<div className="h-14 shrink-0 bg-white/5 backdrop-blur-md border border-white/10 rounded-lg flex items-center justify-between px-4">
|
| 242 |
+
<div className="flex items-center gap-6">
|
| 243 |
+
<div className="flex items-baseline gap-2">
|
| 244 |
+
<span className="font-bold text-lg text-white">{observation?.ticker ?? '—'}</span>
|
| 245 |
+
<span className="font-mono text-neutral-400">
|
| 246 |
+
{observation ? `$${observation.price.toFixed(2)}` : '—'}
|
| 247 |
+
</span>
|
| 248 |
+
<span
|
| 249 |
+
className={`font-mono text-xs ${change >= 0 ? 'text-[#00FF41]' : 'text-[#ffb4ab]'}`}
|
| 250 |
+
>
|
| 251 |
+
({change >= 0 ? '+' : ''}
|
| 252 |
+
{change.toFixed(2)}%)
|
| 253 |
+
</span>
|
| 254 |
+
</div>
|
| 255 |
+
<div className="w-px h-6 bg-white/10" />
|
| 256 |
+
<div className="flex items-center gap-3">
|
| 257 |
+
<span className="text-[10px] uppercase font-bold text-neutral-500 tracking-widest">Vol:</span>
|
| 258 |
+
<span className="font-mono text-sm text-white">
|
| 259 |
+
{lastVolume ? `${(lastVolume / 1_000_000).toFixed(1)}M` : '—'}
|
| 260 |
+
</span>
|
| 261 |
+
</div>
|
| 262 |
+
<div className="w-px h-6 bg-white/10" />
|
| 263 |
+
<div className="flex items-center gap-3">
|
| 264 |
+
<span className="text-[10px] uppercase font-bold text-neutral-500 tracking-widest">Step:</span>
|
| 265 |
+
<span className="font-mono text-sm text-white">
|
| 266 |
+
{envState ? `${envState.current_step}/${envState.total_steps}` : '—'}
|
| 267 |
+
</span>
|
| 268 |
+
</div>
|
| 269 |
+
</div>
|
| 270 |
+
<div className="flex items-center gap-8">
|
| 271 |
+
<div className="flex flex-col items-end">
|
| 272 |
+
<span className="text-[10px] uppercase font-bold text-neutral-500 tracking-widest mb-0.5">Portfolio</span>
|
| 273 |
+
<span className="font-mono font-bold text-white">
|
| 274 |
+
${portfolioValue.toLocaleString(undefined, { maximumFractionDigits: 2 })}
|
| 275 |
+
</span>
|
| 276 |
+
</div>
|
| 277 |
+
<div className="w-px h-8 bg-white/10" />
|
| 278 |
+
<div className="flex flex-col items-end">
|
| 279 |
+
<span className="text-[10px] uppercase font-bold text-neutral-500 tracking-widest mb-0.5">Alpha</span>
|
| 280 |
+
<span
|
| 281 |
+
className={`font-mono font-bold ${alphaPct >= 0 ? 'text-[#00FF41]' : 'text-[#ffb4ab]'}`}
|
| 282 |
+
>
|
| 283 |
+
{alphaPct >= 0 ? '+' : ''}
|
| 284 |
+
{alphaPct.toFixed(2)}%
|
| 285 |
+
</span>
|
| 286 |
+
</div>
|
| 287 |
+
</div>
|
| 288 |
+
</div>
|
| 289 |
+
|
| 290 |
+
<div className="flex-1 flex gap-4 min-h-0">
|
| 291 |
+
{/* Chart Area */}
|
| 292 |
+
<div className="flex-1 glass-panel rounded-lg flex flex-col relative overflow-hidden group">
|
| 293 |
+
<div className="absolute inset-0 scanline z-0 pointer-events-none" />
|
| 294 |
+
<div className="h-10 border-b border-white/5 flex items-center justify-between px-4 bg-white/5 z-10">
|
| 295 |
+
<div className="flex gap-4 font-mono text-[10px] uppercase tracking-widest">
|
| 296 |
+
<button className="text-[#00FF41] border-b border-[#00FF41] py-3">1D</button>
|
| 297 |
+
<button className="text-neutral-500 hover:text-white py-3">1W</button>
|
| 298 |
+
<button className="text-neutral-500 hover:text-white py-3">1M</button>
|
| 299 |
+
<button className="text-neutral-500 hover:text-white py-3">3M</button>
|
| 300 |
+
</div>
|
| 301 |
+
<div className="flex gap-2">
|
| 302 |
+
<button className="p-1 hover:bg-white/10 rounded transition-colors text-neutral-400">
|
| 303 |
+
<Zap className="w-4 h-4" />
|
| 304 |
+
</button>
|
| 305 |
+
<button className="p-1 hover:bg-white/10 rounded transition-colors text-neutral-400">
|
| 306 |
+
<Monitor className="w-4 h-4" />
|
| 307 |
+
</button>
|
| 308 |
+
</div>
|
| 309 |
+
</div>
|
| 310 |
+
<CandlestickChart bars={ohlcv?.bars ?? []} />
|
| 311 |
+
</div>
|
| 312 |
+
|
| 313 |
+
{/* Council Feed Sidebar */}
|
| 314 |
+
<div className="w-80 glass-panel rounded-lg flex flex-col overflow-hidden shrink-0">
|
| 315 |
+
<div className="h-10 border-b border-white/5 px-4 flex items-center justify-between bg-white/5">
|
| 316 |
+
<span className="text-[10px] font-bold uppercase tracking-widest text-[#e5e2e1]">Council Live Feed</span>
|
| 317 |
+
<div className="w-2 h-2 rounded-full bg-[#00FF41] animate-pulse" />
|
| 318 |
+
</div>
|
| 319 |
+
<div className="flex-1 overflow-y-auto p-3 space-y-1">
|
| 320 |
+
{council?.votes.length ? (
|
| 321 |
+
council.votes.map((v) => {
|
| 322 |
+
const status = statusFromSignal(v.signal);
|
| 323 |
+
return (
|
| 324 |
+
<div
|
| 325 |
+
key={v.name}
|
| 326 |
+
className="p-3 rounded border border-transparent hover:border-white/10 hover:bg-white/5 transition-all group cursor-default"
|
| 327 |
+
>
|
| 328 |
+
<div className="flex justify-between items-center mb-1.5">
|
| 329 |
+
<span className="text-xs font-semibold group-hover:text-[#00FF41] transition-colors">
|
| 330 |
+
{SPECIALIST_DISPLAY[v.name] ?? v.name}
|
| 331 |
+
</span>
|
| 332 |
+
<div
|
| 333 |
+
className={`w-1.5 h-1.5 rounded-full ${
|
| 334 |
+
status === 'green'
|
| 335 |
+
? 'bg-[#00FF41]'
|
| 336 |
+
: status === 'red'
|
| 337 |
+
? 'bg-[#ffb4ab]'
|
| 338 |
+
: 'bg-neutral-500'
|
| 339 |
+
}`}
|
| 340 |
+
/>
|
| 341 |
+
</div>
|
| 342 |
+
<p className="text-[11px] text-neutral-400 leading-relaxed font-mono">{v.rationale}</p>
|
| 343 |
+
</div>
|
| 344 |
+
);
|
| 345 |
+
})
|
| 346 |
+
) : (
|
| 347 |
+
<div className="p-3 text-[11px] text-neutral-500 font-mono">
|
| 348 |
+
{observation ? 'Polling council…' : 'Waiting for environment.'}
|
| 349 |
+
</div>
|
| 350 |
+
)}
|
| 351 |
+
</div>
|
| 352 |
+
</div>
|
| 353 |
+
</div>
|
| 354 |
+
|
| 355 |
+
{/* Bottom Panel */}
|
| 356 |
+
<div className="h-32 shrink-0 glass-panel rounded-lg flex overflow-hidden border-[#731fff]/20">
|
| 357 |
+
<div className="w-48 border-r border-white/5 flex flex-col justify-center px-6 bg-[#731fff]/5 relative overflow-hidden">
|
| 358 |
+
<div className="absolute inset-0 bg-gradient-to-br from-[#731fff]/10 to-transparent pointer-events-none" />
|
| 359 |
+
<div className="flex items-center gap-1.5 mb-1">
|
| 360 |
+
<Activity className="w-3 h-3 text-[#731fff]" />
|
| 361 |
+
<span className="text-[9px] uppercase font-bold tracking-widest text-[#731fff]">Moderator</span>
|
| 362 |
+
</div>
|
| 363 |
+
<h3 className="font-bold text-lg text-white">Gemma-4</h3>
|
| 364 |
+
<span className="text-[10px] text-neutral-500 mt-0.5">
|
| 365 |
+
Consensus: {consensus >= 0 ? '+' : ''}
|
| 366 |
+
{consensus.toFixed(2)}
|
| 367 |
+
</span>
|
| 368 |
+
</div>
|
| 369 |
+
<div className="flex-1 p-6 relative flex flex-col justify-center">
|
| 370 |
+
<p className="font-mono text-[13px] text-neutral-300 leading-relaxed max-w-3xl">
|
| 371 |
+
<span className="text-[#731fff] mr-2 opacity-70">></span>
|
| 372 |
+
{council?.rationale ?? (observation ? 'Awaiting moderator synthesis.' : 'Run a task to see live moderator output.')}
|
| 373 |
+
<span className="inline-block w-2.5 h-4 bg-[#731fff] align-middle ml-1 animate-pulse" />
|
| 374 |
+
</p>
|
| 375 |
+
</div>
|
| 376 |
+
<div className="w-72 border-l border-white/5 flex flex-col items-stretch justify-center bg-[#00FF41]/5 backdrop-blur-sm px-4 gap-2">
|
| 377 |
+
<div className="flex items-center gap-2">
|
| 378 |
+
<select
|
| 379 |
+
value={side}
|
| 380 |
+
onChange={(e) => setSide(e.target.value as Side)}
|
| 381 |
+
className="bg-black/40 border border-white/10 rounded text-[11px] font-mono px-2 py-1 text-white"
|
| 382 |
+
>
|
| 383 |
+
<option value="buy">BUY</option>
|
| 384 |
+
<option value="sell">SELL</option>
|
| 385 |
+
<option value="hold">HOLD</option>
|
| 386 |
+
</select>
|
| 387 |
+
<input
|
| 388 |
+
type="number"
|
| 389 |
+
min={0}
|
| 390 |
+
value={qty}
|
| 391 |
+
onChange={(e) => setQty(Math.max(0, parseInt(e.target.value || '0', 10)))}
|
| 392 |
+
className="w-20 bg-black/40 border border-white/10 rounded text-[11px] font-mono px-2 py-1 text-white"
|
| 393 |
+
/>
|
| 394 |
+
</div>
|
| 395 |
+
<button
|
| 396 |
+
onClick={() => void submitTrade(side, qty)}
|
| 397 |
+
disabled={submitDisabled}
|
| 398 |
+
className="bg-[#00FF41] disabled:opacity-40 disabled:cursor-not-allowed text-black font-black text-sm px-6 py-2 rounded-md shadow-[0_0_20px_rgba(0,255,65,0.3)] hover:scale-105 transition-transform"
|
| 399 |
+
>
|
| 400 |
+
{loading ? 'EXECUTING…' : `${side.toUpperCase()} ${side === 'hold' ? '' : qty}`}
|
| 401 |
+
</button>
|
| 402 |
+
</div>
|
| 403 |
+
</div>
|
| 404 |
+
</div>
|
| 405 |
+
);
|
| 406 |
+
};
|
| 407 |
+
|
| 408 |
+
// ----------------------------------------------------------------- CouncilView
|
| 409 |
+
|
| 410 |
+
const SPECIALIST_VISUALS: Record<
|
| 411 |
+
string,
|
| 412 |
+
{ icon: any; color: string; render: () => any }
|
| 413 |
+
> = {
|
| 414 |
+
chart_pattern: {
|
| 415 |
+
icon: Zap,
|
| 416 |
+
color: '[#00FF41]',
|
| 417 |
+
render: () => (
|
| 418 |
+
<div className="h-20 w-full bg-black/40 rounded border border-white/5 relative overflow-hidden flex items-end p-2 pb-0">
|
| 419 |
+
<svg className="w-full h-full" viewBox="0 0 100 40" preserveAspectRatio="none">
|
| 420 |
+
<path d="M0,35 Q10,32 20,38 T40,20 T60,25 T100,5" fill="none" stroke="#00FF41" strokeWidth="2" />
|
| 421 |
+
<path d="M0,35 Q10,32 20,38 T40,20 T60,25 T100,5 V40 H0 Z" fill="url(#chart-grad)" />
|
| 422 |
+
<defs>
|
| 423 |
+
<linearGradient id="chart-grad" x1="0" x2="0" y1="0" y2="1">
|
| 424 |
+
<stop offset="0%" stopColor="#00FF41" stopOpacity="0.2" />
|
| 425 |
+
<stop offset="100%" stopColor="#00FF41" stopOpacity="0" />
|
| 426 |
+
</linearGradient>
|
| 427 |
+
</defs>
|
| 428 |
+
</svg>
|
| 429 |
+
</div>
|
| 430 |
+
),
|
| 431 |
+
},
|
| 432 |
+
indicator: {
|
| 433 |
+
icon: LineChart,
|
| 434 |
+
color: '[#00FF41]',
|
| 435 |
+
render: () => (
|
| 436 |
+
<div className="space-y-2 py-2">
|
| 437 |
+
{[
|
| 438 |
+
{ l: 'RSI', v: 62, c: '#00FF41' },
|
| 439 |
+
{ l: 'MACD', v: 70, c: '#00FF41' },
|
| 440 |
+
{ l: 'ATR', v: 35, c: 'white' },
|
| 441 |
+
].map((b, i) => (
|
| 442 |
+
<div key={i} className="flex items-center gap-2">
|
| 443 |
+
<span className="text-[8px] font-bold text-neutral-500 w-10">{b.l}</span>
|
| 444 |
+
<div className="flex-1 h-1.5 bg-white/5 rounded-full overflow-hidden">
|
| 445 |
+
<div className="h-full bg-current transition-all" style={{ width: `${b.v}%`, color: b.c }} />
|
| 446 |
+
</div>
|
| 447 |
+
</div>
|
| 448 |
+
))}
|
| 449 |
+
</div>
|
| 450 |
+
),
|
| 451 |
+
},
|
| 452 |
+
news: {
|
| 453 |
+
icon: Newspaper,
|
| 454 |
+
color: '[#00FF41]',
|
| 455 |
+
render: () => (
|
| 456 |
+
<div className="space-y-2 max-h-20 overflow-hidden relative">
|
| 457 |
+
<div className="flex gap-2 items-start opacity-90">
|
| 458 |
+
<div className="w-1 h-1 rounded-full bg-[#00FF41] mt-1.5" />
|
| 459 |
+
<span className="text-[10px] text-neutral-400">Latest sentiment-tagged headlines.</span>
|
| 460 |
+
</div>
|
| 461 |
+
<div className="flex gap-2 items-start opacity-70">
|
| 462 |
+
<div className="w-1 h-1 rounded-full bg-[#00FF41] mt-1.5" />
|
| 463 |
+
<span className="text-[10px] text-neutral-400">Tone aggregated by NewsAgent.</span>
|
| 464 |
+
</div>
|
| 465 |
+
<div className="absolute inset-0 bg-gradient-to-b from-transparent to-[#1c1b1b]" />
|
| 466 |
+
</div>
|
| 467 |
+
),
|
| 468 |
+
},
|
| 469 |
+
forum_sentiment: {
|
| 470 |
+
icon: Users,
|
| 471 |
+
color: 'neutral-400',
|
| 472 |
+
render: () => (
|
| 473 |
+
<div className="h-20 w-full bg-black/40 rounded border border-white/5 relative p-2 overflow-hidden">
|
| 474 |
+
<div className="absolute top-[20%] left-[70%] w-3 h-3 bg-[#00FF41] rounded-full blur-[2px] opacity-60" />
|
| 475 |
+
<div className="absolute top-[40%] left-[60%] w-2 h-2 bg-[#00FF41] rounded-full blur-[1px] opacity-40" />
|
| 476 |
+
<div className="absolute top-[10%] left-[80%] w-4 h-4 bg-[#00FF41] rounded-full blur-[3px] opacity-80" />
|
| 477 |
+
<div className="absolute bottom-2 left-2 right-2 flex justify-between text-[7px] font-bold text-neutral-700 border-t border-white/5 pt-1">
|
| 478 |
+
<span>BEAR</span>
|
| 479 |
+
<span>BULL</span>
|
| 480 |
+
</div>
|
| 481 |
+
</div>
|
| 482 |
+
),
|
| 483 |
+
},
|
| 484 |
+
peer_commodity: {
|
| 485 |
+
icon: TrendingUp,
|
| 486 |
+
color: '[#00FF41]',
|
| 487 |
+
render: () => (
|
| 488 |
+
<div className="grid grid-cols-2 gap-2 h-20">
|
| 489 |
+
{[
|
| 490 |
+
{ l: 'Peer 1', v: '+1.2% ▲' },
|
| 491 |
+
{ l: 'Peer 2', v: '+0.4%' },
|
| 492 |
+
{ l: 'Peer 3', v: '-0.1%' },
|
| 493 |
+
{ l: 'Comm.', v: '+0.7%' },
|
| 494 |
+
].map((m, i) => (
|
| 495 |
+
<div key={i} className="bg-white/5 rounded p-1.5 flex flex-col justify-center">
|
| 496 |
+
<span className="text-[7px] font-bold text-neutral-600 uppercase">{m.l}</span>
|
| 497 |
+
<span className="text-[10px] font-mono text-neutral-300">{m.v}</span>
|
| 498 |
+
</div>
|
| 499 |
+
))}
|
| 500 |
+
</div>
|
| 501 |
+
),
|
| 502 |
+
},
|
| 503 |
+
geopolitics: {
|
| 504 |
+
icon: Globe,
|
| 505 |
+
color: 'neutral-400',
|
| 506 |
+
render: () => (
|
| 507 |
+
<div className="grid grid-cols-2 gap-2 h-20">
|
| 508 |
+
{[
|
| 509 |
+
{ l: 'DXY', v: '104.2 ▲' },
|
| 510 |
+
{ l: '10Y', v: '4.32% ▲' },
|
| 511 |
+
{ l: 'SPX', v: '-0.2%' },
|
| 512 |
+
{ l: 'VIX', v: '14.1' },
|
| 513 |
+
].map((m, i) => (
|
| 514 |
+
<div key={i} className="bg-white/5 rounded p-1.5 flex flex-col justify-center">
|
| 515 |
+
<span className="text-[7px] font-bold text-neutral-600 uppercase">{m.l}</span>
|
| 516 |
+
<span className="text-[10px] font-mono text-neutral-300">{m.v}</span>
|
| 517 |
+
</div>
|
| 518 |
+
))}
|
| 519 |
+
</div>
|
| 520 |
+
),
|
| 521 |
+
},
|
| 522 |
+
seasonal_trend: {
|
| 523 |
+
icon: Calendar,
|
| 524 |
+
color: '[#00FF41]',
|
| 525 |
+
render: () => (
|
| 526 |
+
<div className="h-20 w-full bg-black/40 rounded border border-white/5 relative p-2 flex items-end gap-1">
|
| 527 |
+
{[30, 45, 35, 60, 55, 70, 50, 65, 75, 60, 80, 72].map((h, i) => (
|
| 528 |
+
<div key={i} className="flex-1 bg-[#00FF41]/40 rounded-t-sm" style={{ height: `${h}%` }} />
|
| 529 |
+
))}
|
| 530 |
+
<div className="absolute top-1 right-2 text-[8px] font-bold text-neutral-600 uppercase tracking-widest">12mo avg</div>
|
| 531 |
+
</div>
|
| 532 |
+
),
|
| 533 |
+
},
|
| 534 |
+
};
|
| 535 |
+
|
| 536 |
+
const CouncilView = ({ env }: { env: StockerEnv }) => {
|
| 537 |
+
const { observation, council } = env;
|
| 538 |
+
const consensus = council && council.votes.length > 0
|
| 539 |
+
? council.votes.reduce((a, v) => a + v.signal, 0) / council.votes.length
|
| 540 |
+
: 0;
|
| 541 |
+
const consensusLabel =
|
| 542 |
+
consensus > 0.2 ? 'BULLISH' : consensus < -0.2 ? 'BEARISH' : 'NEUTRAL';
|
| 543 |
+
const consensusColor =
|
| 544 |
+
consensus > 0.2 ? 'text-[#00FF41]' : consensus < -0.2 ? 'text-[#ffb4ab]' : 'text-neutral-400';
|
| 545 |
+
|
| 546 |
+
return (
|
| 547 |
+
<div className="flex-1 flex flex-col gap-6">
|
| 548 |
+
<header>
|
| 549 |
+
<h1 className="text-3xl font-bold tracking-tighter text-white mb-2">Council Deep-Dive</h1>
|
| 550 |
+
<p className="text-sm text-neutral-400 max-w-2xl">
|
| 551 |
+
Real-time analysis and consensus generation across 7 autonomous specialized agents.
|
| 552 |
+
Target asset:{' '}
|
| 553 |
+
<span className="text-[#00FF41] font-mono bg-[#00FF41]/10 px-1.5 py-0.5 rounded border border-[#00FF41]/20">
|
| 554 |
+
{observation?.ticker ?? '—'}
|
| 555 |
+
</span>
|
| 556 |
+
</p>
|
| 557 |
+
</header>
|
| 558 |
+
|
| 559 |
+
<div className="grid grid-cols-1 xl:grid-cols-3 gap-4 flex-1">
|
| 560 |
+
{/* Alpha Moderator */}
|
| 561 |
+
<div className="col-span-1 glass-panel rounded-xl flex flex-col relative overflow-hidden group border-[#731fff]/30 shadow-[0_0_30px_rgba(115,31,255,0.1)]">
|
| 562 |
+
<div className="absolute top-0 left-0 w-full h-[1px] bg-gradient-to-r from-transparent via-[#731fff] to-transparent opacity-50" />
|
| 563 |
+
<div className="p-4 border-b border-white/5 flex justify-between items-start z-10">
|
| 564 |
+
<div className="flex items-center gap-2">
|
| 565 |
+
<Brain className="w-5 h-5 text-[#731fff]" />
|
| 566 |
+
<h3 className="text-lg font-bold text-white">Alpha Moderator</h3>
|
| 567 |
+
</div>
|
| 568 |
+
<span className="text-[10px] font-bold text-[#731fff] border border-[#731fff]/30 px-2 py-1 rounded bg-[#731fff]/10">
|
| 569 |
+
CONSENSUS
|
| 570 |
+
</span>
|
| 571 |
+
</div>
|
| 572 |
+
|
| 573 |
+
<div className="p-6 flex-1 flex flex-col justify-between z-10">
|
| 574 |
+
<div className="flex flex-col items-center py-6">
|
| 575 |
+
<div className="relative w-40 h-20 overflow-hidden flex items-end justify-center mb-2">
|
| 576 |
+
<div className="absolute top-0 w-40 h-40 rounded-full border-[12px] border-white/5 border-b-transparent border-r-transparent rotate-45" />
|
| 577 |
+
<div
|
| 578 |
+
className={`absolute top-0 w-40 h-40 rounded-full border-[12px] ${
|
| 579 |
+
consensus > 0.2
|
| 580 |
+
? 'border-[#00FF41]'
|
| 581 |
+
: consensus < -0.2
|
| 582 |
+
? 'border-[#ffb4ab]'
|
| 583 |
+
: 'border-neutral-400'
|
| 584 |
+
} border-b-transparent border-r-transparent opacity-80`}
|
| 585 |
+
style={{
|
| 586 |
+
transform: `rotate(${45 + Math.max(-1, Math.min(1, consensus)) * 135}deg)`,
|
| 587 |
+
clipPath: 'polygon(50% 50%, 0% 0%, 100% 0%)',
|
| 588 |
+
}}
|
| 589 |
+
/>
|
| 590 |
+
<div className="flex flex-col items-center translate-y-2">
|
| 591 |
+
<span className="font-mono text-3xl text-white font-bold">
|
| 592 |
+
{consensus >= 0 ? '+' : ''}
|
| 593 |
+
{consensus.toFixed(2)}
|
| 594 |
+
</span>
|
| 595 |
+
<span className={`text-[10px] font-bold tracking-widest mt-1 uppercase ${consensusColor}`}>
|
| 596 |
+
{consensusLabel}
|
| 597 |
+
</span>
|
| 598 |
+
</div>
|
| 599 |
+
</div>
|
| 600 |
+
<div className="w-full flex justify-between px-4 font-mono text-[9px] text-neutral-500">
|
| 601 |
+
<span>-1.0 (BEAR)</span>
|
| 602 |
+
<span>0.0</span>
|
| 603 |
+
<span>+1.0 (BULL)</span>
|
| 604 |
+
</div>
|
| 605 |
+
</div>
|
| 606 |
+
|
| 607 |
+
<div className="space-y-2">
|
| 608 |
+
<span className="text-[10px] font-bold text-neutral-500 uppercase tracking-widest">Moderator Synthesis</span>
|
| 609 |
+
<p className="text-sm text-neutral-400 leading-relaxed">
|
| 610 |
+
{council?.rationale ??
|
| 611 |
+
'Run a task in Gallery to populate moderator synthesis from the live council.'}
|
| 612 |
+
</p>
|
| 613 |
+
{council && (
|
| 614 |
+
<p className="text-[10px] text-[#731fff] font-mono mt-2">
|
| 615 |
+
Action: {council.action.side.toUpperCase()}
|
| 616 |
+
{council.action.side !== 'hold' ? ` ${council.action.quantity}` : ''}
|
| 617 |
+
</p>
|
| 618 |
+
)}
|
| 619 |
+
</div>
|
| 620 |
+
</div>
|
| 621 |
+
<div className="absolute -bottom-20 -right-20 w-64 h-64 bg-[#731fff]/10 blur-[100px] rounded-full pointer-events-none" />
|
| 622 |
+
</div>
|
| 623 |
+
|
| 624 |
+
{/* Specialist cards */}
|
| 625 |
+
{Object.entries(SPECIALIST_VISUALS).map(([roleName, visual]) => {
|
| 626 |
+
const vote = council?.votes.find((v) => v.name === roleName);
|
| 627 |
+
const stat = vote ? `${vote.signal >= 0 ? '+' : ''}${vote.signal.toFixed(2)}` : '—';
|
| 628 |
+
const Icon = visual.icon;
|
| 629 |
+
return (
|
| 630 |
+
<AgentCard
|
| 631 |
+
key={roleName}
|
| 632 |
+
title={SPECIALIST_DISPLAY[roleName] ?? roleName}
|
| 633 |
+
icon={Icon}
|
| 634 |
+
stat={stat}
|
| 635 |
+
color={visual.color}
|
| 636 |
+
>
|
| 637 |
+
{visual.render()}
|
| 638 |
+
<p className="text-xs text-neutral-500 leading-snug">
|
| 639 |
+
{vote?.rationale ?? 'Awaiting council vote — run a task in Gallery.'}
|
| 640 |
+
</p>
|
| 641 |
+
</AgentCard>
|
| 642 |
+
);
|
| 643 |
+
})}
|
| 644 |
+
</div>
|
| 645 |
+
</div>
|
| 646 |
+
);
|
| 647 |
+
};
|
| 648 |
+
|
| 649 |
+
// ---------------------------------------------------------------- TrainingView
|
| 650 |
+
|
| 651 |
+
const MetricCard = ({
|
| 652 |
+
label,
|
| 653 |
+
value,
|
| 654 |
+
trend,
|
| 655 |
+
}: {
|
| 656 |
+
label: string;
|
| 657 |
+
value: string;
|
| 658 |
+
trend?: string;
|
| 659 |
+
}) => (
|
| 660 |
+
<div className={`glass-panel p-4 rounded-lg flex flex-col justify-center ${trend ? 'border-l-2 border-[#00FF41]/30' : ''}`}>
|
| 661 |
+
<span className={`text-[10px] font-bold uppercase tracking-widest mb-1 ${trend ? 'text-[#00FF41]' : 'text-neutral-500'}`}>{label}</span>
|
| 662 |
+
<span className={`text-xl font-bold flex items-center ${trend ? 'text-[#00FF41]' : 'text-white'}`}>
|
| 663 |
+
{value}
|
| 664 |
+
{trend && (
|
| 665 |
+
<span className="text-xs ml-1 flex items-center">
|
| 666 |
+
{trend} <ChevronUp className="w-3 h-3 ml-0.5" />
|
| 667 |
+
</span>
|
| 668 |
+
)}
|
| 669 |
+
</span>
|
| 670 |
+
</div>
|
| 671 |
+
);
|
| 672 |
+
|
| 673 |
+
const TrainingView = () => {
|
| 674 |
+
const [metrics, setMetrics] = useState<TrainingMetrics | null>(null);
|
| 675 |
+
|
| 676 |
+
useEffect(() => {
|
| 677 |
+
let cancel = false;
|
| 678 |
+
api
|
| 679 |
+
.trainingMetrics()
|
| 680 |
+
.then((m) => {
|
| 681 |
+
if (!cancel) setMetrics(m);
|
| 682 |
+
})
|
| 683 |
+
.catch(() => {
|
| 684 |
+
if (!cancel) setMetrics({ status: 'no_runs', summary: [], mean_alpha_pct: 0 });
|
| 685 |
+
});
|
| 686 |
+
return () => {
|
| 687 |
+
cancel = true;
|
| 688 |
+
};
|
| 689 |
+
}, []);
|
| 690 |
+
|
| 691 |
+
// Placeholder logs — there's no JSON log on disk, kept as decorative.
|
| 692 |
+
const logs = [
|
| 693 |
+
{ type: 'SYS', msg: 'Initializing GRPO policy trainer…' },
|
| 694 |
+
{ type: 'SYS', msg: 'Loading historical state tensors [BATCH_SIZE=256]…' },
|
| 695 |
+
{ type: 'OPT', msg: 'Step 4480: Adv = 0.124, Kl_div = 0.003', color: 'text-purple-400' },
|
| 696 |
+
{ type: 'REWARD', msg: 'Epoch 44 evaluation: Mean R = +1.24%', color: 'text-[#00FF41]' },
|
| 697 |
+
{ type: 'SYS', msg: 'Checkpoint saved.', pulse: true },
|
| 698 |
+
];
|
| 699 |
+
|
| 700 |
+
const best = metrics?.summary?.length
|
| 701 |
+
? metrics.summary.reduce((a, b) => (b.alpha_pct > a.alpha_pct ? b : a))
|
| 702 |
+
: null;
|
| 703 |
+
const worst = metrics?.summary?.length
|
| 704 |
+
? metrics.summary.reduce((a, b) => (b.alpha_pct < a.alpha_pct ? b : a))
|
| 705 |
+
: null;
|
| 706 |
+
|
| 707 |
+
return (
|
| 708 |
+
<div className="flex-1 flex flex-col gap-4 overflow-hidden">
|
| 709 |
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 shrink-0">
|
| 710 |
+
<MetricCard label="Run" value={metrics?.run_name ?? '—'} />
|
| 711 |
+
<MetricCard
|
| 712 |
+
label="Mean Alpha %"
|
| 713 |
+
value={metrics ? `${metrics.mean_alpha_pct.toFixed(2)}%` : '—'}
|
| 714 |
+
trend={metrics && metrics.mean_alpha_pct > 0 ? 'up' : undefined}
|
| 715 |
+
/>
|
| 716 |
+
<MetricCard
|
| 717 |
+
label="Best Task"
|
| 718 |
+
value={best ? `${best.task_id} (${best.alpha_pct.toFixed(1)}%)` : '—'}
|
| 719 |
+
/>
|
| 720 |
+
<MetricCard
|
| 721 |
+
label="Worst Task"
|
| 722 |
+
value={worst ? `${worst.task_id} (${worst.alpha_pct.toFixed(1)}%)` : '—'}
|
| 723 |
+
/>
|
| 724 |
+
</div>
|
| 725 |
+
|
| 726 |
+
<div className="flex-1 flex flex-col md:flex-row gap-4 overflow-hidden min-h-0">
|
| 727 |
+
{/* Left: status + logs */}
|
| 728 |
+
<div className="w-full md:w-1/3 flex flex-col gap-4 h-full min-h-0">
|
| 729 |
+
<div className="glass-panel rounded-lg p-6 shrink-0 relative overflow-hidden group">
|
| 730 |
+
<div className="absolute inset-0 bg-gradient-to-br from-[#731fff]/10 to-transparent pointer-events-none" />
|
| 731 |
+
<div className="flex items-center justify-between border-b border-white/5 pb-3 mb-4">
|
| 732 |
+
<h3 className="text-lg font-bold text-white">GRPO Status</h3>
|
| 733 |
+
<span
|
| 734 |
+
className={`px-2 py-1 text-[10px] font-bold rounded border ${
|
| 735 |
+
metrics?.status === 'completed'
|
| 736 |
+
? 'bg-[#00FF41]/20 text-[#00FF41] border-[#00FF41]/40'
|
| 737 |
+
: 'bg-neutral-500/20 text-neutral-400 border-neutral-500/40'
|
| 738 |
+
}`}
|
| 739 |
+
>
|
| 740 |
+
{metrics?.status === 'completed' ? 'EVAL DONE' : 'NO RUNS'}
|
| 741 |
+
</span>
|
| 742 |
+
</div>
|
| 743 |
+
<div className="space-y-4">
|
| 744 |
+
<div className="flex justify-between text-xs">
|
| 745 |
+
<span className="text-neutral-500">Tasks evaluated</span>
|
| 746 |
+
<span className="text-white font-mono">{metrics?.summary.length ?? 0}</span>
|
| 747 |
+
</div>
|
| 748 |
+
<div className="flex justify-between text-xs">
|
| 749 |
+
<span className="text-neutral-500">LoRA Adapters</span>
|
| 750 |
+
<span className="text-[#731fff] font-mono">Base (no LoRA loaded)</span>
|
| 751 |
+
</div>
|
| 752 |
+
<div className="flex justify-between text-xs">
|
| 753 |
+
<span className="text-neutral-500">Run directory</span>
|
| 754 |
+
<span className="text-white font-mono truncate">{metrics?.run_name ?? '—'}</span>
|
| 755 |
+
</div>
|
| 756 |
+
</div>
|
| 757 |
+
</div>
|
| 758 |
+
|
| 759 |
+
<div className="glass-panel rounded-lg flex-1 flex flex-col overflow-hidden border-[#00FF41]/20">
|
| 760 |
+
<div className="border-b border-white/5 px-4 py-2 bg-white/5 flex justify-between items-center text-[10px] font-bold uppercase tracking-widest text-neutral-500">
|
| 761 |
+
<span>Terminal Logs (decorative)</span>
|
| 762 |
+
<div className="w-2 h-2 rounded-full bg-[#00FF41] animate-pulse" />
|
| 763 |
+
</div>
|
| 764 |
+
<div className="flex-1 p-4 overflow-y-auto space-y-1 font-mono text-[11px] text-neutral-400">
|
| 765 |
+
{logs.map((log, i) => (
|
| 766 |
+
<div key={i} className="flex gap-2">
|
| 767 |
+
<span className={`${log.type === 'SYS' ? 'text-blue-400' : log.color || 'text-neutral-500'}`}>
|
| 768 |
+
[{log.type}]
|
| 769 |
+
</span>
|
| 770 |
+
<span className={log.pulse ? 'animate-pulse' : ''}>
|
| 771 |
+
{log.msg}
|
| 772 |
+
{log.pulse ? ' _' : ''}
|
| 773 |
+
</span>
|
| 774 |
+
</div>
|
| 775 |
+
))}
|
| 776 |
+
</div>
|
| 777 |
+
</div>
|
| 778 |
+
</div>
|
| 779 |
+
|
| 780 |
+
{/* Right: real curves from training/runs */}
|
| 781 |
+
<div className="w-full md:w-2/3 flex flex-col gap-4 h-full overflow-y-auto pr-1">
|
| 782 |
+
<AgentCard title="Reward Curve" icon={TrendingUp} color="[#00FF41]" active>
|
| 783 |
+
<div className="flex-1 h-full min-h-[220px] relative mt-2 flex items-center justify-center bg-black/30 rounded">
|
| 784 |
+
{metrics?.reward_curve_png ? (
|
| 785 |
+
<img
|
| 786 |
+
src={metrics.reward_curve_png}
|
| 787 |
+
alt="Reward curve"
|
| 788 |
+
className="max-h-[260px] max-w-full object-contain"
|
| 789 |
+
/>
|
| 790 |
+
) : (
|
| 791 |
+
<span className="text-[11px] text-neutral-500 font-mono">
|
| 792 |
+
No run yet — run <code>python training/eval_rollout.py --mock</code>.
|
| 793 |
+
</span>
|
| 794 |
+
)}
|
| 795 |
+
</div>
|
| 796 |
+
</AgentCard>
|
| 797 |
+
|
| 798 |
+
<AgentCard title="Portfolio Curve" icon={BarChart3} color="[#731fff]">
|
| 799 |
+
<div className="flex-1 h-full min-h-[220px] relative mt-2 flex items-center justify-center bg-black/30 rounded">
|
| 800 |
+
{metrics?.portfolio_curve_png ? (
|
| 801 |
+
<img
|
| 802 |
+
src={metrics.portfolio_curve_png}
|
| 803 |
+
alt="Portfolio curve"
|
| 804 |
+
className="max-h-[260px] max-w-full object-contain"
|
| 805 |
+
/>
|
| 806 |
+
) : (
|
| 807 |
+
<span className="text-[11px] text-neutral-500 font-mono">No run yet.</span>
|
| 808 |
+
)}
|
| 809 |
+
</div>
|
| 810 |
+
</AgentCard>
|
| 811 |
+
|
| 812 |
+
{metrics?.summary?.length ? (
|
| 813 |
+
<div className="glass-panel rounded-lg p-4">
|
| 814 |
+
<h4 className="text-xs font-bold uppercase tracking-widest text-neutral-500 mb-3">Per-task summary</h4>
|
| 815 |
+
<table className="w-full text-[11px] font-mono">
|
| 816 |
+
<thead className="text-neutral-500 text-left">
|
| 817 |
+
<tr>
|
| 818 |
+
<th className="py-1">Task</th>
|
| 819 |
+
<th className="py-1 text-right">Alpha %</th>
|
| 820 |
+
<th className="py-1 text-right">Final</th>
|
| 821 |
+
<th className="py-1 text-right">Buy & Hold</th>
|
| 822 |
+
</tr>
|
| 823 |
+
</thead>
|
| 824 |
+
<tbody>
|
| 825 |
+
{metrics.summary.map((row) => (
|
| 826 |
+
<tr key={row.task_id} className="border-t border-white/5">
|
| 827 |
+
<td className="py-1.5 text-white">{row.task_id}</td>
|
| 828 |
+
<td
|
| 829 |
+
className={`py-1.5 text-right ${
|
| 830 |
+
row.alpha_pct >= 0 ? 'text-[#00FF41]' : 'text-[#ffb4ab]'
|
| 831 |
+
}`}
|
| 832 |
+
>
|
| 833 |
+
{row.alpha_pct.toFixed(2)}%
|
| 834 |
+
</td>
|
| 835 |
+
<td className="py-1.5 text-right text-neutral-300">
|
| 836 |
+
${row.final_portfolio.toLocaleString()}
|
| 837 |
+
</td>
|
| 838 |
+
<td className="py-1.5 text-right text-neutral-400">
|
| 839 |
+
${row.buy_and_hold.toLocaleString()}
|
| 840 |
+
</td>
|
| 841 |
+
</tr>
|
| 842 |
+
))}
|
| 843 |
+
</tbody>
|
| 844 |
+
</table>
|
| 845 |
+
</div>
|
| 846 |
+
) : null}
|
| 847 |
+
</div>
|
| 848 |
+
</div>
|
| 849 |
+
</div>
|
| 850 |
+
);
|
| 851 |
+
};
|
| 852 |
+
|
| 853 |
+
// ---------------------------------------------------------------- GalleryView
|
| 854 |
+
|
| 855 |
+
const TASK_META: Record<string, { difficulty: string; name: string; color: string }> = {
|
| 856 |
+
task_easy: { difficulty: 'Easy', name: 'Steady Regime', color: '[#00FF41]' },
|
| 857 |
+
task_medium: { difficulty: 'Medium', name: 'Choppy Sideways', color: '[#731fff]' },
|
| 858 |
+
task_hard: { difficulty: 'Hard', name: 'Drawdown / Snapback', color: '[#ffb4ab]' },
|
| 859 |
+
};
|
| 860 |
+
|
| 861 |
+
const TaskCard = ({
|
| 862 |
+
taskId,
|
| 863 |
+
bars,
|
| 864 |
+
onDeploy,
|
| 865 |
+
active,
|
| 866 |
+
}: {
|
| 867 |
+
taskId: string;
|
| 868 |
+
bars: OhlcvBar[] | null;
|
| 869 |
+
onDeploy: () => void;
|
| 870 |
+
active: boolean;
|
| 871 |
+
}) => {
|
| 872 |
+
const meta = TASK_META[taskId] ?? { difficulty: '?', name: taskId, color: '[#00FF41]' };
|
| 873 |
+
const ticker = bars?.[0] ? '' : '';
|
| 874 |
+
const inEpisode = bars?.filter((b) => b.in_episode) ?? [];
|
| 875 |
+
const dates =
|
| 876 |
+
inEpisode.length > 0
|
| 877 |
+
? `${inEpisode[0].time} → ${inEpisode[inEpisode.length - 1].time}`
|
| 878 |
+
: 'Loading…';
|
| 879 |
+
|
| 880 |
+
// Compute a 9-bucket sparkline from in-episode closes
|
| 881 |
+
let sparkBars: number[] = [];
|
| 882 |
+
if (inEpisode.length > 0) {
|
| 883 |
+
const closes = inEpisode.map((b) => b.close);
|
| 884 |
+
const min = Math.min(...closes);
|
| 885 |
+
const max = Math.max(...closes);
|
| 886 |
+
const range = max - min || 1;
|
| 887 |
+
const buckets = 9;
|
| 888 |
+
const step = closes.length / buckets;
|
| 889 |
+
sparkBars = Array.from({ length: buckets }, (_, i) => {
|
| 890 |
+
const slice = closes.slice(Math.floor(i * step), Math.floor((i + 1) * step) || (i + 1));
|
| 891 |
+
const avg = slice.reduce((a, c) => a + c, 0) / Math.max(1, slice.length);
|
| 892 |
+
return ((avg - min) / range) * 100;
|
| 893 |
+
});
|
| 894 |
+
}
|
| 895 |
+
|
| 896 |
+
return (
|
| 897 |
+
<div
|
| 898 |
+
className={`group glass-panel rounded-xl p-6 flex flex-col relative overflow-hidden transition-all hover:bg-white/[0.08] hover:shadow-[0_0_30px_rgba(0,255,65,0.05)] ${
|
| 899 |
+
active ? `border-${meta.color}/50` : 'border-white/5'
|
| 900 |
+
} hover:border-${meta.color}/50`}
|
| 901 |
+
>
|
| 902 |
+
<div
|
| 903 |
+
className={`absolute inset-0 bg-gradient-to-br from-${meta.color}/5 to-transparent pointer-events-none group-hover:from-${meta.color}/15 transition-all`}
|
| 904 |
+
/>
|
| 905 |
+
|
| 906 |
+
<div className="flex justify-between items-start mb-4 relative z-10">
|
| 907 |
+
<div
|
| 908 |
+
className={`px-2 py-1 bg-${meta.color}/10 border border-${meta.color}/30 text-${meta.color} text-[10px] font-bold uppercase tracking-widest rounded flex items-center gap-1.5`}
|
| 909 |
+
>
|
| 910 |
+
<div className={`w-1.5 h-1.5 rounded-full bg-${meta.color}`} />
|
| 911 |
+
{meta.difficulty}
|
| 912 |
+
</div>
|
| 913 |
+
<span className="text-lg font-bold text-white font-mono">{bars?.[0] ? bars[0].time.slice(0, 4) : ''}</span>
|
| 914 |
+
</div>
|
| 915 |
+
|
| 916 |
+
<div className="mb-6 relative z-10">
|
| 917 |
+
<h3 className="text-xl font-bold text-white mb-1">{meta.name}</h3>
|
| 918 |
+
<div className="flex items-center gap-2 text-[10px] text-neutral-500 font-mono">
|
| 919 |
+
<Monitor className="w-3 h-3" />
|
| 920 |
+
{dates}
|
| 921 |
+
</div>
|
| 922 |
+
</div>
|
| 923 |
+
|
| 924 |
+
<div className="h-24 w-full bg-black/40 rounded-lg border border-white/5 mb-8 flex items-end px-2 pb-2 gap-1 overflow-hidden relative z-10">
|
| 925 |
+
<div className="absolute inset-x-0 bottom-0 h-[1px] bg-white/5" />
|
| 926 |
+
{sparkBars.length > 0 ? (
|
| 927 |
+
sparkBars.map((h, i) => (
|
| 928 |
+
<motion.div
|
| 929 |
+
key={i}
|
| 930 |
+
initial={{ height: 0 }}
|
| 931 |
+
animate={{ height: `${h}%` }}
|
| 932 |
+
className={`flex-1 rounded-t-[2px] transition-all bg-${meta.color}/40 group-hover:bg-${meta.color}/70`}
|
| 933 |
+
/>
|
| 934 |
+
))
|
| 935 |
+
) : (
|
| 936 |
+
<div className="w-full text-center text-[10px] text-neutral-600 font-mono">Loading bars…</div>
|
| 937 |
+
)}
|
| 938 |
+
</div>
|
| 939 |
+
|
| 940 |
+
<button
|
| 941 |
+
onClick={onDeploy}
|
| 942 |
+
className={`w-full py-3 rounded-lg border border-white/10 flex items-center justify-center gap-2 text-xs font-bold uppercase tracking-widest transition-all relative z-10 hover:text-${meta.color} hover:border-${meta.color}/50 hover:bg-${meta.color}/5 group/btn`}
|
| 943 |
+
>
|
| 944 |
+
<Zap className="w-4 h-4 group-hover/btn:fill-current" />
|
| 945 |
+
{active ? 'Active' : 'Deploy Agent'}
|
| 946 |
+
</button>
|
| 947 |
+
</div>
|
| 948 |
+
);
|
| 949 |
+
};
|
| 950 |
+
|
| 951 |
+
const GalleryView = ({
|
| 952 |
+
env,
|
| 953 |
+
onDeploy,
|
| 954 |
+
}: {
|
| 955 |
+
env: StockerEnv;
|
| 956 |
+
onDeploy: (taskId: string) => void;
|
| 957 |
+
}) => {
|
| 958 |
+
const [bars, setBars] = useState<Record<string, OhlcvResponse>>({});
|
| 959 |
+
|
| 960 |
+
useEffect(() => {
|
| 961 |
+
let cancel = false;
|
| 962 |
+
Promise.all(env.tasks.map((t) => api.ohlcv(t).then((r) => [t, r] as const))).then((pairs) => {
|
| 963 |
+
if (cancel) return;
|
| 964 |
+
const next: Record<string, OhlcvResponse> = {};
|
| 965 |
+
for (const [t, r] of pairs) next[t] = r;
|
| 966 |
+
setBars(next);
|
| 967 |
+
});
|
| 968 |
+
return () => {
|
| 969 |
+
cancel = true;
|
| 970 |
+
};
|
| 971 |
+
}, [env.tasks]);
|
| 972 |
+
|
| 973 |
+
return (
|
| 974 |
+
<div className="flex-1 flex flex-col gap-8">
|
| 975 |
+
<header className="space-y-3">
|
| 976 |
+
<div className="flex items-center gap-2 text-[#00FF41]">
|
| 977 |
+
<Brain className="w-5 h-5" />
|
| 978 |
+
<span className="text-[10px] uppercase font-bold tracking-[0.2em]">Agent Environment</span>
|
| 979 |
+
</div>
|
| 980 |
+
<h1 className="text-5xl font-black text-white tracking-tighter">Task Gallery</h1>
|
| 981 |
+
<p className="text-sm text-neutral-400 max-w-2xl leading-relaxed">
|
| 982 |
+
Select a historical market regime to deploy and evaluate your AI trading agent's performance in isolated, controlled conditions.
|
| 983 |
+
</p>
|
| 984 |
+
</header>
|
| 985 |
+
|
| 986 |
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
| 987 |
+
{env.tasks.map((t) => (
|
| 988 |
+
<TaskCard
|
| 989 |
+
key={t}
|
| 990 |
+
taskId={t}
|
| 991 |
+
bars={bars[t]?.bars ?? null}
|
| 992 |
+
active={env.taskId === t}
|
| 993 |
+
onDeploy={() => onDeploy(t)}
|
| 994 |
+
/>
|
| 995 |
+
))}
|
| 996 |
+
</div>
|
| 997 |
+
</div>
|
| 998 |
+
);
|
| 999 |
+
};
|
| 1000 |
+
|
| 1001 |
+
// --------------------------------------------------------------- PortfolioView
|
| 1002 |
+
|
| 1003 |
+
const PortfolioView = ({ env }: { env: StockerEnv }) => {
|
| 1004 |
+
const { observation, envState, startingCash } = env;
|
| 1005 |
+
|
| 1006 |
+
if (!observation || !envState) {
|
| 1007 |
+
return (
|
| 1008 |
+
<div className="flex-1 flex items-center justify-center">
|
| 1009 |
+
<p className="text-sm text-neutral-500">Run a task in Gallery to populate your portfolio.</p>
|
| 1010 |
+
</div>
|
| 1011 |
+
);
|
| 1012 |
+
}
|
| 1013 |
+
|
| 1014 |
+
const ticker = observation.ticker;
|
| 1015 |
+
const price = observation.price;
|
| 1016 |
+
const balance = observation.position;
|
| 1017 |
+
const value = balance * price;
|
| 1018 |
+
const portfolio = envState.portfolio_value;
|
| 1019 |
+
const cash = envState.cash;
|
| 1020 |
+
const pnl = portfolio - startingCash;
|
| 1021 |
+
const pnlPct = startingCash > 0 ? (pnl / startingCash) * 100 : 0;
|
| 1022 |
+
|
| 1023 |
+
const equityShare = portfolio > 0 ? (value / portfolio) * 100 : 0;
|
| 1024 |
+
const cashShare = portfolio > 0 ? (cash / portfolio) * 100 : 0;
|
| 1025 |
+
const reservedShare = Math.max(0, 100 - equityShare - cashShare);
|
| 1026 |
+
|
| 1027 |
+
// 24h change from the last two prices in price_history
|
| 1028 |
+
const ph = observation.price_history;
|
| 1029 |
+
const change24h =
|
| 1030 |
+
ph.length >= 2 ? ((ph[ph.length - 1] - ph[ph.length - 2]) / ph[ph.length - 2]) * 100 : 0;
|
| 1031 |
+
|
| 1032 |
+
// Sparkline = last 7 closes
|
| 1033 |
+
const spark = ph.slice(-7);
|
| 1034 |
+
const sMin = Math.min(...spark);
|
| 1035 |
+
const sMax = Math.max(...spark);
|
| 1036 |
+
const sRange = sMax - sMin || 1;
|
| 1037 |
+
const sparkPct = spark.map((p) => ((p - sMin) / sRange) * 100);
|
| 1038 |
+
|
| 1039 |
+
// Risk profile = action diversity
|
| 1040 |
+
const actions = envState.action_history;
|
| 1041 |
+
const totalActions = actions.length || 1;
|
| 1042 |
+
const tradeCount = actions.filter((a) => a.side !== 'hold').length;
|
| 1043 |
+
const riskScore = Math.min(100, Math.round((tradeCount / totalActions) * 100));
|
| 1044 |
+
const riskLabel =
|
| 1045 |
+
riskScore < 30 ? 'Conservative' : riskScore < 60 ? 'Moderate' : 'Aggressive';
|
| 1046 |
+
|
| 1047 |
+
return (
|
| 1048 |
+
<div className="flex-1 flex flex-col gap-6">
|
| 1049 |
+
<header className="flex justify-between items-end">
|
| 1050 |
+
<div className="space-y-1">
|
| 1051 |
+
<span className="text-[10px] font-bold text-[#00FF41] uppercase tracking-widest">Active Position</span>
|
| 1052 |
+
<h1 className="text-4xl font-bold text-white tracking-tight">Holdings & P&L</h1>
|
| 1053 |
+
</div>
|
| 1054 |
+
<div className="flex gap-4">
|
| 1055 |
+
<div className="glass-panel p-3 px-5 rounded-lg border-l-2 border-[#00FF41]">
|
| 1056 |
+
<span className="text-[9px] text-neutral-500 uppercase font-bold block mb-1">Total Balance</span>
|
| 1057 |
+
<span className="text-xl font-bold text-white font-mono tracking-tight">
|
| 1058 |
+
${portfolio.toLocaleString(undefined, { maximumFractionDigits: 2 })}
|
| 1059 |
+
</span>
|
| 1060 |
+
</div>
|
| 1061 |
+
<div
|
| 1062 |
+
className={`glass-panel p-3 px-5 rounded-lg border-l-2 ${
|
| 1063 |
+
pnl >= 0 ? 'border-[#00FF41]' : 'border-[#ffb4ab]'
|
| 1064 |
+
}`}
|
| 1065 |
+
>
|
| 1066 |
+
<span className="text-[9px] text-neutral-500 uppercase font-bold block mb-1">Total P&L</span>
|
| 1067 |
+
<span
|
| 1068 |
+
className={`text-xl font-bold font-mono tracking-tight ${
|
| 1069 |
+
pnl >= 0 ? 'text-[#00FF41]' : 'text-[#ffb4ab]'
|
| 1070 |
+
}`}
|
| 1071 |
+
>
|
| 1072 |
+
{pnl >= 0 ? '+' : ''}${pnl.toFixed(2)} ({pnlPct >= 0 ? '+' : ''}{pnlPct.toFixed(2)}%)
|
| 1073 |
+
</span>
|
| 1074 |
+
</div>
|
| 1075 |
+
</div>
|
| 1076 |
+
</header>
|
| 1077 |
+
|
| 1078 |
+
<div className="glass-panel rounded-xl overflow-hidden border-white/5">
|
| 1079 |
+
<table className="w-full text-left border-collapse">
|
| 1080 |
+
<thead>
|
| 1081 |
+
<tr className="bg-white/5 border-b border-white/10">
|
| 1082 |
+
<th className="px-6 py-4 text-[10px] font-bold text-neutral-500 uppercase tracking-widest">Asset</th>
|
| 1083 |
+
<th className="px-6 py-4 text-[10px] font-bold text-neutral-500 uppercase tracking-widest text-right">Price</th>
|
| 1084 |
+
<th className="px-6 py-4 text-[10px] font-bold text-neutral-500 uppercase tracking-widest text-right">24h Change</th>
|
| 1085 |
+
<th className="px-6 py-4 text-[10px] font-bold text-neutral-500 uppercase tracking-widest text-right">Balance</th>
|
| 1086 |
+
<th className="px-6 py-4 text-[10px] font-bold text-neutral-500 uppercase tracking-widest text-right">Value</th>
|
| 1087 |
+
<th className="px-6 py-4 text-[10px] font-bold text-neutral-500 uppercase tracking-widest text-center">7D Performance</th>
|
| 1088 |
+
</tr>
|
| 1089 |
+
</thead>
|
| 1090 |
+
<tbody className="divide-y divide-white/5 font-mono text-sm">
|
| 1091 |
+
<tr className="hover:bg-white/[0.04] transition-colors group">
|
| 1092 |
+
<td className="px-6 py-5">
|
| 1093 |
+
<div className="flex items-center gap-3">
|
| 1094 |
+
<div className="w-8 h-8 rounded bg-white/5 border border-white/10 flex items-center justify-center font-black group-hover:text-[#00FF41] transition-colors">
|
| 1095 |
+
{ticker[0]}
|
| 1096 |
+
</div>
|
| 1097 |
+
<div className="flex flex-col">
|
| 1098 |
+
<span className="text-white font-bold">{ticker}</span>
|
| 1099 |
+
<span className="text-[10px] text-neutral-500 font-sans tracking-tight">NASDAQ</span>
|
| 1100 |
+
</div>
|
| 1101 |
+
</div>
|
| 1102 |
+
</td>
|
| 1103 |
+
<td className="px-6 py-5 text-right font-bold text-neutral-300">${price.toFixed(2)}</td>
|
| 1104 |
+
<td
|
| 1105 |
+
className={`px-6 py-5 text-right font-bold ${
|
| 1106 |
+
change24h >= 0 ? 'text-[#00FF41]' : 'text-[#ffb4ab]'
|
| 1107 |
+
}`}
|
| 1108 |
+
>
|
| 1109 |
+
{change24h >= 0 ? '+' : ''}
|
| 1110 |
+
{change24h.toFixed(2)}%
|
| 1111 |
+
</td>
|
| 1112 |
+
<td className="px-6 py-5 text-right text-neutral-400">{balance} {ticker}</td>
|
| 1113 |
+
<td className="px-6 py-5 text-right text-white font-bold">
|
| 1114 |
+
${value.toLocaleString(undefined, { maximumFractionDigits: 2 })}
|
| 1115 |
+
</td>
|
| 1116 |
+
<td className="px-6 py-5">
|
| 1117 |
+
<div className="h-6 w-24 mx-auto flex items-end gap-1 px-1">
|
| 1118 |
+
{sparkPct.map((v, j) => (
|
| 1119 |
+
<div
|
| 1120 |
+
key={j}
|
| 1121 |
+
className="flex-1 rounded-t-[1px] opacity-60"
|
| 1122 |
+
style={{
|
| 1123 |
+
height: `${v}%`,
|
| 1124 |
+
backgroundColor: change24h >= 0 ? '#00FF41' : '#ffb4ab',
|
| 1125 |
+
}}
|
| 1126 |
+
/>
|
| 1127 |
+
))}
|
| 1128 |
+
</div>
|
| 1129 |
+
</td>
|
| 1130 |
+
</tr>
|
| 1131 |
+
</tbody>
|
| 1132 |
+
</table>
|
| 1133 |
+
</div>
|
| 1134 |
+
|
| 1135 |
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
| 1136 |
+
<div className="glass-panel rounded-xl p-6 relative overflow-hidden group">
|
| 1137 |
+
<div className="absolute top-0 right-0 p-4 opacity-10">
|
| 1138 |
+
<BarChart3 className="w-12 h-12" />
|
| 1139 |
+
</div>
|
| 1140 |
+
<h4 className="text-xs font-bold text-neutral-500 uppercase tracking-widest mb-4">Risk Profile</h4>
|
| 1141 |
+
<div className="flex items-center gap-4">
|
| 1142 |
+
<div
|
| 1143 |
+
className="w-16 h-16 rounded-full border-4 border-white/5 border-t-[#00FF41] relative"
|
| 1144 |
+
style={{ transform: `rotate(${(riskScore / 100) * 360 - 90}deg)` }}
|
| 1145 |
+
>
|
| 1146 |
+
<div
|
| 1147 |
+
className="absolute inset-0 flex items-center justify-center"
|
| 1148 |
+
style={{ transform: `rotate(${-((riskScore / 100) * 360 - 90)}deg)` }}
|
| 1149 |
+
>
|
| 1150 |
+
<span className="text-sm font-bold text-white">{riskScore}/100</span>
|
| 1151 |
+
</div>
|
| 1152 |
+
</div>
|
| 1153 |
+
<div>
|
| 1154 |
+
<p className="text-sm font-bold text-white mb-1 tracking-tight">{riskLabel}</p>
|
| 1155 |
+
<p className="text-[10px] text-neutral-400 leading-snug">
|
| 1156 |
+
{tradeCount} of {totalActions} steps were trades.
|
| 1157 |
+
</p>
|
| 1158 |
+
</div>
|
| 1159 |
+
</div>
|
| 1160 |
+
</div>
|
| 1161 |
+
|
| 1162 |
+
<div className="glass-panel rounded-xl p-6 col-span-2 relative overflow-hidden group">
|
| 1163 |
+
<h4 className="text-xs font-bold text-neutral-500 uppercase tracking-widest mb-4">Trade Allocation</h4>
|
| 1164 |
+
<div className="flex items-end h-16 gap-1.5 px-1 py-1">
|
| 1165 |
+
{[
|
| 1166 |
+
{ l: 'Equities', v: equityShare, c: '#00FF41' },
|
| 1167 |
+
{ l: 'Cash', v: cashShare, c: '#731fff' },
|
| 1168 |
+
{ l: 'Reserved', v: reservedShare, c: 'rgba(255,255,255,0.1)' },
|
| 1169 |
+
].map((a, i) => (
|
| 1170 |
+
<div key={i} className="flex-1 flex flex-col items-center">
|
| 1171 |
+
<div
|
| 1172 |
+
className="w-full bg-current opacity-30 rounded-t-sm"
|
| 1173 |
+
style={{ height: `${a.v}%`, color: a.c }}
|
| 1174 |
+
/>
|
| 1175 |
+
<span className="text-[8px] font-bold text-neutral-600 mt-1 uppercase tracking-tighter">
|
| 1176 |
+
{a.l} ({a.v.toFixed(0)}%)
|
| 1177 |
+
</span>
|
| 1178 |
+
</div>
|
| 1179 |
+
))}
|
| 1180 |
+
</div>
|
| 1181 |
+
</div>
|
| 1182 |
+
</div>
|
| 1183 |
+
</div>
|
| 1184 |
+
);
|
| 1185 |
+
};
|
| 1186 |
+
|
| 1187 |
+
// ----------------------------------------------------------- IntelligenceView
|
| 1188 |
+
|
| 1189 |
+
const SENTIMENT_SCORE: Record<string, number> = {
|
| 1190 |
+
positive: 1,
|
| 1191 |
+
bullish: 1,
|
| 1192 |
+
neutral: 0,
|
| 1193 |
+
negative: -1,
|
| 1194 |
+
bearish: -1,
|
| 1195 |
+
};
|
| 1196 |
+
|
| 1197 |
+
const IntelligenceView = ({ env }: { env: StockerEnv }) => {
|
| 1198 |
+
const { observation } = env;
|
| 1199 |
+
|
| 1200 |
+
const positiveHeadlines = (observation?.headlines ?? [])
|
| 1201 |
+
.filter((h) => (SENTIMENT_SCORE[h.sentiment_label?.toLowerCase()] ?? 0) > 0);
|
| 1202 |
+
const negativeHeadlines = (observation?.headlines ?? [])
|
| 1203 |
+
.filter((h) => (SENTIMENT_SCORE[h.sentiment_label?.toLowerCase()] ?? 0) < 0);
|
| 1204 |
+
|
| 1205 |
+
const peerEntries = observation?.peers?.peers ?? [];
|
| 1206 |
+
const macroEntries = observation?.macro ?? [];
|
| 1207 |
+
|
| 1208 |
+
const signals = [
|
| 1209 |
+
positiveHeadlines[0] && {
|
| 1210 |
+
t: 'Top Headline',
|
| 1211 |
+
msg: positiveHeadlines[0].headline,
|
| 1212 |
+
time: positiveHeadlines[0].date,
|
| 1213 |
+
color: '#00FF41',
|
| 1214 |
+
},
|
| 1215 |
+
positiveHeadlines[1] && {
|
| 1216 |
+
t: 'Secondary',
|
| 1217 |
+
msg: positiveHeadlines[1].headline,
|
| 1218 |
+
time: positiveHeadlines[1].date,
|
| 1219 |
+
color: '#00FF41',
|
| 1220 |
+
},
|
| 1221 |
+
negativeHeadlines[0] && {
|
| 1222 |
+
t: 'Risk Note',
|
| 1223 |
+
msg: negativeHeadlines[0].headline,
|
| 1224 |
+
time: negativeHeadlines[0].date,
|
| 1225 |
+
color: '#ffb4ab',
|
| 1226 |
+
},
|
| 1227 |
+
peerEntries.length > 0 && {
|
| 1228 |
+
t: 'Peer Watch',
|
| 1229 |
+
msg: `${observation?.ticker} vs ${peerEntries
|
| 1230 |
+
.slice(0, 3)
|
| 1231 |
+
.map((p) => `${p.peer_ticker}=${p.peer_close?.toFixed(2) ?? '?'}`)
|
| 1232 |
+
.join(', ')}`,
|
| 1233 |
+
time: observation?.date ?? '',
|
| 1234 |
+
color: '#731fff',
|
| 1235 |
+
},
|
| 1236 |
+
macroEntries[0] && {
|
| 1237 |
+
t: 'Macro',
|
| 1238 |
+
msg: `${macroEntries[0].country} — ${macroEntries[0].headline}`,
|
| 1239 |
+
time: macroEntries[0].date,
|
| 1240 |
+
color: '#731fff',
|
| 1241 |
+
},
|
| 1242 |
+
].filter(Boolean) as Array<{ t: string; msg: string; time: string; color: string }>;
|
| 1243 |
+
|
| 1244 |
+
// Sentiment hub: mean of headline sentiment scores
|
| 1245 |
+
const headlines = observation?.headlines ?? [];
|
| 1246 |
+
const meanTone = headlines.length
|
| 1247 |
+
? headlines.reduce((a, h) => a + (SENTIMENT_SCORE[h.sentiment_label?.toLowerCase()] ?? 0), 0) /
|
| 1248 |
+
headlines.length
|
| 1249 |
+
: 0;
|
| 1250 |
+
const toneScale = Math.round(((meanTone + 1) / 2) * 100); // 0..100
|
| 1251 |
+
const sentimentLabel =
|
| 1252 |
+
meanTone > 0.2 ? 'BULL' : meanTone < -0.2 ? 'BEAR' : 'NEUTRAL';
|
| 1253 |
+
const peerBeta = peerEntries.length ? '~1.00' : '—';
|
| 1254 |
+
|
| 1255 |
+
return (
|
| 1256 |
+
<div className="flex-1 flex flex-col gap-6">
|
| 1257 |
+
<header className="space-y-1">
|
| 1258 |
+
<h1 className="text-4xl font-bold text-white tracking-tight flex items-center gap-3">
|
| 1259 |
+
Intelligence Nexus
|
| 1260 |
+
<div className="flex gap-1">
|
| 1261 |
+
{[...Array(3)].map((_, i) => (
|
| 1262 |
+
<div
|
| 1263 |
+
key={i}
|
| 1264 |
+
className="w-1 h-4 bg-[#00FF41] animate-pulse"
|
| 1265 |
+
style={{ animationDelay: `${i * 0.2}s` }}
|
| 1266 |
+
/>
|
| 1267 |
+
))}
|
| 1268 |
+
</div>
|
| 1269 |
+
</h1>
|
| 1270 |
+
<p className="text-sm text-neutral-400">
|
| 1271 |
+
Aggregating headlines, peer rotation, and macro signals from the current observation.
|
| 1272 |
+
</p>
|
| 1273 |
+
</header>
|
| 1274 |
+
|
| 1275 |
+
<div className="flex-1 flex gap-6 min-h-0">
|
| 1276 |
+
{/* Neural map */}
|
| 1277 |
+
<div className="flex-1 glass-panel rounded-2xl relative overflow-hidden flex items-center justify-center bg-black/60 p-12">
|
| 1278 |
+
<div className="absolute inset-0 bg-aurora opacity-30 pointer-events-none" />
|
| 1279 |
+
<div className="absolute inset-0 scanline opacity-20 pointer-events-none" />
|
| 1280 |
+
|
| 1281 |
+
<svg className="w-full h-full opacity-60" viewBox="0 0 400 300">
|
| 1282 |
+
<defs>
|
| 1283 |
+
<filter id="glow">
|
| 1284 |
+
<feGaussianBlur stdDeviation="2" result="coloredBlur" />
|
| 1285 |
+
<feMerge>
|
| 1286 |
+
<feMergeNode in="coloredBlur" />
|
| 1287 |
+
<feMergeNode in="SourceGraphic" />
|
| 1288 |
+
</feMerge>
|
| 1289 |
+
</filter>
|
| 1290 |
+
</defs>
|
| 1291 |
+
<g stroke="white" strokeWidth="0.5" strokeOpacity="0.1">
|
| 1292 |
+
<line x1="200" y1="150" x2="100" y2="80" />
|
| 1293 |
+
<line x1="200" y1="150" x2="300" y2="80" />
|
| 1294 |
+
<line x1="200" y1="150" x2="100" y2="220" />
|
| 1295 |
+
<line x1="200" y1="150" x2="300" y2="220" />
|
| 1296 |
+
<line x1="100" y1="80" x2="50" y2="150" />
|
| 1297 |
+
<line x1="100" y1="220" x2="50" y2="150" />
|
| 1298 |
+
<line x1="300" y1="80" x2="350" y2="150" />
|
| 1299 |
+
<line x1="300" y1="220" x2="350" y2="150" />
|
| 1300 |
+
</g>
|
| 1301 |
+
<circle cx="200" cy="150" r="10" fill="#731fff" filter="url(#glow)">
|
| 1302 |
+
<animate attributeName="r" values="10;12;10" dur="3s" repeatCount="indefinite" />
|
| 1303 |
+
</circle>
|
| 1304 |
+
{[
|
| 1305 |
+
{ x: 100, y: 80, c: '#00FF41' },
|
| 1306 |
+
{ x: 300, y: 80, c: '#00FF41' },
|
| 1307 |
+
{ x: 100, y: 220, c: '#ffb4ab' },
|
| 1308 |
+
{ x: 300, y: 220, c: '#00FF41' },
|
| 1309 |
+
{ x: 50, y: 150, c: '#731fff' },
|
| 1310 |
+
{ x: 350, y: 150, c: '#731fff' },
|
| 1311 |
+
].map((n, i) => (
|
| 1312 |
+
<g key={i}>
|
| 1313 |
+
<circle cx={n.x} cy={n.y} r="4" fill={n.c} filter="url(#glow)">
|
| 1314 |
+
<animate attributeName="opacity" values="0.4;1;0.4" dur={`${2 + i}s`} repeatCount="indefinite" />
|
| 1315 |
+
</circle>
|
| 1316 |
+
<circle cx={n.x} cy={n.y} r="8" fill="none" stroke={n.c} strokeWidth="0.5" strokeOpacity="0.3">
|
| 1317 |
+
<animate attributeName="r" values="8;15;8" dur={`${4 + i}s`} repeatCount="indefinite" />
|
| 1318 |
+
</circle>
|
| 1319 |
+
</g>
|
| 1320 |
+
))}
|
| 1321 |
+
</svg>
|
| 1322 |
+
|
| 1323 |
+
<div className="absolute inset-0 flex flex-col items-center justify-center pointer-events-none">
|
| 1324 |
+
<Brain className="w-16 h-16 text-[#731fff] opacity-20" />
|
| 1325 |
+
<span className="font-mono text-[10px] text-[#731fff] tracking-[0.5em] mt-4 uppercase">
|
| 1326 |
+
Neural Consensus Active
|
| 1327 |
+
</span>
|
| 1328 |
+
</div>
|
| 1329 |
+
</div>
|
| 1330 |
+
|
| 1331 |
+
{/* Alpha signals — derived from observation */}
|
| 1332 |
+
<div className="w-96 flex flex-col gap-4 overflow-hidden shrink-0">
|
| 1333 |
+
<h3 className="text-xs font-bold text-neutral-500 uppercase tracking-[0.2em] px-2 flex items-center justify-between">
|
| 1334 |
+
Alpha Signals
|
| 1335 |
+
<Activity className="w-3 h-3 text-[#00FF41]" />
|
| 1336 |
+
</h3>
|
| 1337 |
+
<div className="flex-1 overflow-y-auto space-y-4 pr-2">
|
| 1338 |
+
{signals.length > 0 ? (
|
| 1339 |
+
signals.map((s, i) => (
|
| 1340 |
+
<div
|
| 1341 |
+
key={i}
|
| 1342 |
+
className="glass-panel p-4 rounded-xl border-l-2 hover:bg-white/[0.04] transition-all cursor-default group"
|
| 1343 |
+
style={{ borderColor: s.color }}
|
| 1344 |
+
>
|
| 1345 |
+
<div className="flex justify-between items-center mb-2">
|
| 1346 |
+
<span className="text-xs font-bold uppercase tracking-widest" style={{ color: s.color }}>
|
| 1347 |
+
{s.t}
|
| 1348 |
+
</span>
|
| 1349 |
+
<span className="text-[10px] text-neutral-500">{s.time}</span>
|
| 1350 |
+
</div>
|
| 1351 |
+
<p className="text-xs text-neutral-400 leading-relaxed font-mono group-hover:text-neutral-300 transition-colors">
|
| 1352 |
+
{s.msg}
|
| 1353 |
+
</p>
|
| 1354 |
+
</div>
|
| 1355 |
+
))
|
| 1356 |
+
) : (
|
| 1357 |
+
<div className="text-[11px] text-neutral-500 font-mono px-2">
|
| 1358 |
+
{observation ? 'No headlines/peers/macro for this step.' : 'Run a task to populate signals.'}
|
| 1359 |
+
</div>
|
| 1360 |
+
)}
|
| 1361 |
+
</div>
|
| 1362 |
+
</div>
|
| 1363 |
+
</div>
|
| 1364 |
+
|
| 1365 |
+
<div className="h-24 glass-panel rounded-xl flex items-center px-8 border-[#00FF41]/10 bg-[#00FF41]/[0.02]">
|
| 1366 |
+
<div className="flex-1 flex flex-col gap-1">
|
| 1367 |
+
<span className="text-[10px] font-bold text-neutral-500 uppercase tracking-widest">Sentiment Hub</span>
|
| 1368 |
+
<div className="flex items-center gap-4">
|
| 1369 |
+
<div className="flex items-baseline gap-2">
|
| 1370 |
+
<span className="text-2xl font-bold text-white">{toneScale}</span>
|
| 1371 |
+
<span
|
| 1372 |
+
className={`text-xs font-bold uppercase tracking-widest ${
|
| 1373 |
+
sentimentLabel === 'BULL'
|
| 1374 |
+
? 'text-[#00FF41]'
|
| 1375 |
+
: sentimentLabel === 'BEAR'
|
| 1376 |
+
? 'text-[#ffb4ab]'
|
| 1377 |
+
: 'text-neutral-400'
|
| 1378 |
+
}`}
|
| 1379 |
+
>
|
| 1380 |
+
{sentimentLabel}
|
| 1381 |
+
</span>
|
| 1382 |
+
</div>
|
| 1383 |
+
</div>
|
| 1384 |
+
</div>
|
| 1385 |
+
<div className="flex items-center gap-12">
|
| 1386 |
+
{[
|
| 1387 |
+
{ l: 'News Tone', v: meanTone.toFixed(2) },
|
| 1388 |
+
{ l: 'Sentiment', v: sentimentLabel },
|
| 1389 |
+
{ l: 'Peer Beta', v: peerBeta },
|
| 1390 |
+
].map((m, i) => (
|
| 1391 |
+
<div key={i} className="flex flex-col items-center">
|
| 1392 |
+
<span className="text-[9px] font-bold text-neutral-600 uppercase mb-1">{m.l}</span>
|
| 1393 |
+
<span className="text-xs font-mono font-bold text-neutral-300">{m.v}</span>
|
| 1394 |
+
</div>
|
| 1395 |
+
))}
|
| 1396 |
+
</div>
|
| 1397 |
+
</div>
|
| 1398 |
+
</div>
|
| 1399 |
+
);
|
| 1400 |
+
};
|
| 1401 |
+
|
| 1402 |
+
// ------------------------------------------------------------------------- App
|
| 1403 |
+
|
| 1404 |
+
type Tab = 'Terminal' | 'Council' | 'Training' | 'Gallery' | 'Portfolio' | 'Intelligence';
|
| 1405 |
+
|
| 1406 |
+
export default function App() {
|
| 1407 |
+
const [activeTab, setActiveTab] = useState<Tab>('Terminal');
|
| 1408 |
+
const env = useStockerEnv();
|
| 1409 |
+
|
| 1410 |
+
const onDeploy = (taskId: string) => {
|
| 1411 |
+
void env.selectTask(taskId);
|
| 1412 |
+
setActiveTab('Terminal');
|
| 1413 |
+
};
|
| 1414 |
+
|
| 1415 |
+
const renderView = () => {
|
| 1416 |
+
switch (activeTab) {
|
| 1417 |
+
case 'Terminal':
|
| 1418 |
+
return <TerminalView env={env} />;
|
| 1419 |
+
case 'Council':
|
| 1420 |
+
return <CouncilView env={env} />;
|
| 1421 |
+
case 'Training':
|
| 1422 |
+
return <TrainingView />;
|
| 1423 |
+
case 'Gallery':
|
| 1424 |
+
return <GalleryView env={env} onDeploy={onDeploy} />;
|
| 1425 |
+
case 'Portfolio':
|
| 1426 |
+
return <PortfolioView env={env} />;
|
| 1427 |
+
case 'Intelligence':
|
| 1428 |
+
return <IntelligenceView env={env} />;
|
| 1429 |
+
default:
|
| 1430 |
+
return <TerminalView env={env} />;
|
| 1431 |
+
}
|
| 1432 |
+
};
|
| 1433 |
+
|
| 1434 |
+
return (
|
| 1435 |
+
<div className="flex min-h-screen bg-[#050505] text-[#e5e2e1] font-sans selection:bg-[#00FF41]/20">
|
| 1436 |
+
<div className="bg-aurora fixed inset-0 pointer-events-none z-0" />
|
| 1437 |
+
<div className="fixed inset-0 pointer-events-none z-50 opacity-[0.03] scanline" />
|
| 1438 |
+
|
| 1439 |
+
{/* Sidebar */}
|
| 1440 |
+
<nav className="w-64 fixed left-0 top-0 h-screen bg-black/40 backdrop-blur-3xl border-r border-white/5 flex flex-col pt-20 pb-8 z-40 transition-all">
|
| 1441 |
+
<div className="px-6 mb-8 flex items-center gap-3">
|
| 1442 |
+
<div className="w-10 h-10 rounded-lg bg-white/5 border border-white/10 flex items-center justify-center relative overflow-hidden group">
|
| 1443 |
+
<Cpu className="w-6 h-6 text-[#00FF41] opacity-70 group-hover:scale-110 transition-transform" />
|
| 1444 |
+
<div className="absolute inset-0 bg-gradient-to-br from-[#00FF41]/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
|
| 1445 |
+
</div>
|
| 1446 |
+
<div>
|
| 1447 |
+
<h2 className="font-bold text-sm tracking-widest text-[#e5e2e1]">ALPHA ENGINE</h2>
|
| 1448 |
+
<p className="text-[10px] text-[#00FF41] font-mono tracking-widest mt-0.5 uppercase">
|
| 1449 |
+
{env.taskId ?? 'Booting…'}
|
| 1450 |
+
</p>
|
| 1451 |
+
</div>
|
| 1452 |
+
</div>
|
| 1453 |
+
|
| 1454 |
+
<div className="flex-1 px-3 space-y-1">
|
| 1455 |
+
<SidebarItem icon={Monitor} label="Terminal" active={activeTab === 'Terminal'} onClick={() => setActiveTab('Terminal')} />
|
| 1456 |
+
<SidebarItem icon={Users} label="Council" active={activeTab === 'Council'} onClick={() => setActiveTab('Council')} />
|
| 1457 |
+
<SidebarItem icon={TrendingUp} label="Training" active={activeTab === 'Training'} onClick={() => setActiveTab('Training')} />
|
| 1458 |
+
<SidebarItem icon={Library} label="Gallery" active={activeTab === 'Gallery'} onClick={() => setActiveTab('Gallery')} />
|
| 1459 |
+
<SidebarItem icon={Wallet} label="Portfolio" active={activeTab === 'Portfolio'} onClick={() => setActiveTab('Portfolio')} />
|
| 1460 |
+
<SidebarItem icon={Brain} label="Intelligence" active={activeTab === 'Intelligence'} onClick={() => setActiveTab('Intelligence')} />
|
| 1461 |
+
</div>
|
| 1462 |
+
|
| 1463 |
+
<div className="px-3 mt-auto space-y-1 border-t border-white/5 pt-4">
|
| 1464 |
+
<button className="w-full flex items-center gap-3 px-4 py-2 rounded-lg text-neutral-500 hover:bg-white/5 hover:text-[#00FF41] transition-all">
|
| 1465 |
+
<FileText className="w-4 h-4" />
|
| 1466 |
+
<span className="text-xs font-medium">Docs</span>
|
| 1467 |
+
</button>
|
| 1468 |
+
<button className="w-full flex items-center gap-3 px-4 py-2 rounded-lg text-neutral-500 hover:bg-white/5 hover:text-[#00FF41] transition-all">
|
| 1469 |
+
<LifeBuoy className="w-4 h-4" />
|
| 1470 |
+
<span className="text-xs font-medium">Support</span>
|
| 1471 |
+
</button>
|
| 1472 |
+
</div>
|
| 1473 |
+
|
| 1474 |
+
<div className="px-6 mt-6">
|
| 1475 |
+
<div
|
| 1476 |
+
className={`p-3 rounded-lg border flex items-center gap-3 ${
|
| 1477 |
+
env.error
|
| 1478 |
+
? 'bg-[#ffb4ab]/5 border-[#ffb4ab]/20'
|
| 1479 |
+
: env.loading
|
| 1480 |
+
? 'bg-[#731fff]/5 border-[#731fff]/20'
|
| 1481 |
+
: 'bg-[#00FF41]/5 border-[#00FF41]/20'
|
| 1482 |
+
}`}
|
| 1483 |
+
>
|
| 1484 |
+
<div
|
| 1485 |
+
className={`w-2 h-2 rounded-full animate-pulse ${
|
| 1486 |
+
env.error ? 'bg-[#ffb4ab]' : env.loading ? 'bg-[#731fff]' : 'bg-[#00FF41]'
|
| 1487 |
+
}`}
|
| 1488 |
+
/>
|
| 1489 |
+
<div className="flex flex-col">
|
| 1490 |
+
<span
|
| 1491 |
+
className={`text-[10px] font-bold uppercase tracking-widest ${
|
| 1492 |
+
env.error ? 'text-[#ffb4ab]' : env.loading ? 'text-[#731fff]' : 'text-[#00FF41]'
|
| 1493 |
+
}`}
|
| 1494 |
+
>
|
| 1495 |
+
{env.error ? 'API Error' : env.loading ? 'Working…' : 'System Ready'}
|
| 1496 |
+
</span>
|
| 1497 |
+
<span className="text-[8px] text-neutral-500 font-mono truncate max-w-[160px]">
|
| 1498 |
+
{env.error ?? `task: ${env.taskId ?? '—'}`}
|
| 1499 |
+
</span>
|
| 1500 |
+
</div>
|
| 1501 |
+
</div>
|
| 1502 |
+
</div>
|
| 1503 |
+
</nav>
|
| 1504 |
+
|
| 1505 |
+
{/* Main Content */}
|
| 1506 |
+
<div className="flex-1 ml-64 flex flex-col relative z-10">
|
| 1507 |
+
<header className="h-14 fixed top-0 right-0 left-64 bg-black/40 backdrop-blur-xl border-b border-white/5 px-6 flex items-center justify-between z-50">
|
| 1508 |
+
<div className="flex items-center gap-4">
|
| 1509 |
+
<span className="text-xl font-black tracking-widest text-[#00FF41] drop-shadow-[0_0_8px_rgba(0,255,65,0.4)]">STOCKER AI</span>
|
| 1510 |
+
</div>
|
| 1511 |
+
|
| 1512 |
+
<div className="flex items-center gap-6">
|
| 1513 |
+
<div className="relative group hidden md:block">
|
| 1514 |
+
<Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-neutral-500 group-focus-within:text-[#00FF41] transition-colors" />
|
| 1515 |
+
<input
|
| 1516 |
+
type="text"
|
| 1517 |
+
placeholder="Search parameters..."
|
| 1518 |
+
className="bg-white/5 border border-white/10 rounded-full py-1.5 pl-10 pr-4 text-xs font-mono text-[#e5e2e1] focus:outline-none focus:border-[#00FF41]/50 w-64 transition-all"
|
| 1519 |
+
/>
|
| 1520 |
+
</div>
|
| 1521 |
+
|
| 1522 |
+
<div className="flex items-center gap-4">
|
| 1523 |
+
<button className="text-neutral-500 hover:text-white transition-colors relative">
|
| 1524 |
+
<Bell className="w-5 h-5" />
|
| 1525 |
+
<span className="absolute -top-1 -right-1 w-2 h-2 bg-[#00FF41] rounded-full" />
|
| 1526 |
+
</button>
|
| 1527 |
+
<button className="text-neutral-500 hover:text-white transition-colors">
|
| 1528 |
+
<CreditCard className="w-5 h-5" />
|
| 1529 |
+
</button>
|
| 1530 |
+
<button className="text-neutral-500 hover:text-white transition-colors">
|
| 1531 |
+
<User className="w-5 h-5" />
|
| 1532 |
+
</button>
|
| 1533 |
+
</div>
|
| 1534 |
+
</div>
|
| 1535 |
+
</header>
|
| 1536 |
+
|
| 1537 |
+
<main className="pt-14 p-6 flex-1 flex flex-col min-h-screen">
|
| 1538 |
+
<AnimatePresence mode="wait">
|
| 1539 |
+
<motion.div
|
| 1540 |
+
key={activeTab}
|
| 1541 |
+
initial={{ opacity: 0, y: 10 }}
|
| 1542 |
+
animate={{ opacity: 1, y: 0 }}
|
| 1543 |
+
exit={{ opacity: 0, y: -10 }}
|
| 1544 |
+
transition={{ duration: 0.2 }}
|
| 1545 |
+
className="flex-1 flex flex-col"
|
| 1546 |
+
>
|
| 1547 |
+
{renderView()}
|
| 1548 |
+
</motion.div>
|
| 1549 |
+
</AnimatePresence>
|
| 1550 |
+
</main>
|
| 1551 |
+
</div>
|
| 1552 |
+
</div>
|
| 1553 |
+
);
|
| 1554 |
+
}
|
frontend/src/api.ts
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// TypeScript mirrors of the Pydantic models in app/models.py.
|
| 2 |
+
// Keep these in sync if the backend contract changes.
|
| 3 |
+
|
| 4 |
+
import { useCallback, useEffect, useState } from 'react';
|
| 5 |
+
|
| 6 |
+
export type Side = 'buy' | 'sell' | 'hold';
|
| 7 |
+
|
| 8 |
+
export interface MarketObservation {
|
| 9 |
+
ticker: string;
|
| 10 |
+
date: string;
|
| 11 |
+
price: number;
|
| 12 |
+
price_history: number[];
|
| 13 |
+
fundamentals: Record<string, unknown>;
|
| 14 |
+
cash: number;
|
| 15 |
+
position: number;
|
| 16 |
+
portfolio_value: number;
|
| 17 |
+
task_id: string;
|
| 18 |
+
step_number: number;
|
| 19 |
+
total_steps: number;
|
| 20 |
+
chart_path: string;
|
| 21 |
+
headlines: Array<{ date: string; headline: string; source: string; sentiment_label: string }>;
|
| 22 |
+
forum_excerpts: Array<{ date: string; subreddit: string; score: number; post_text: string }>;
|
| 23 |
+
indicators: Record<string, number | null>;
|
| 24 |
+
peers: {
|
| 25 |
+
peers: Array<{ peer_ticker: string; peer_close: number | null }>;
|
| 26 |
+
commodity?: string | null;
|
| 27 |
+
commodity_price?: number | null;
|
| 28 |
+
};
|
| 29 |
+
macro: Array<{ date: string; country: string; headline: string; policy_signal: string }>;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
export interface EnvironmentState {
|
| 33 |
+
task_id: string;
|
| 34 |
+
current_step: number;
|
| 35 |
+
total_steps: number;
|
| 36 |
+
done: boolean;
|
| 37 |
+
cash: number;
|
| 38 |
+
position: number;
|
| 39 |
+
portfolio_value: number;
|
| 40 |
+
action_history: Array<{ side: Side; quantity: number }>;
|
| 41 |
+
reward_history: number[];
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
export interface SpecialistVote {
|
| 45 |
+
name: string;
|
| 46 |
+
signal: number;
|
| 47 |
+
confidence: number;
|
| 48 |
+
rationale: string;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
export interface CouncilDecision {
|
| 52 |
+
votes: SpecialistVote[];
|
| 53 |
+
action: { side: Side; quantity: number };
|
| 54 |
+
rationale: string;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
export interface OhlcvBar {
|
| 58 |
+
time: string;
|
| 59 |
+
open: number;
|
| 60 |
+
high: number;
|
| 61 |
+
low: number;
|
| 62 |
+
close: number;
|
| 63 |
+
volume: number;
|
| 64 |
+
in_episode: boolean;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
export interface OhlcvResponse {
|
| 68 |
+
task_id: string;
|
| 69 |
+
ticker: string;
|
| 70 |
+
bars: OhlcvBar[];
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
export interface TrainingMetrics {
|
| 74 |
+
status: 'completed' | 'no_runs';
|
| 75 |
+
run_name?: string;
|
| 76 |
+
summary: Array<{
|
| 77 |
+
task_id: string;
|
| 78 |
+
total_reward: number;
|
| 79 |
+
final_portfolio: number;
|
| 80 |
+
buy_and_hold: number;
|
| 81 |
+
alpha_pct: number;
|
| 82 |
+
}>;
|
| 83 |
+
mean_alpha_pct: number;
|
| 84 |
+
reward_curve_png?: string | null;
|
| 85 |
+
portfolio_curve_png?: string | null;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
// ---------- fetch helpers --------------------------------------------------
|
| 89 |
+
|
| 90 |
+
async function jget<T>(url: string): Promise<T> {
|
| 91 |
+
const r = await fetch(url);
|
| 92 |
+
if (!r.ok) throw new Error(`${url}: ${r.status}`);
|
| 93 |
+
return r.json() as Promise<T>;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
async function jpost<T>(url: string, body: unknown): Promise<T> {
|
| 97 |
+
const r = await fetch(url, {
|
| 98 |
+
method: 'POST',
|
| 99 |
+
headers: { 'Content-Type': 'application/json' },
|
| 100 |
+
body: JSON.stringify(body),
|
| 101 |
+
});
|
| 102 |
+
if (!r.ok) throw new Error(`${url}: ${r.status}`);
|
| 103 |
+
return r.json() as Promise<T>;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
export const api = {
|
| 107 |
+
meta: () => jget<{ tasks: string[]; name: string; version: string }>('/meta'),
|
| 108 |
+
reset: (task_id: string) =>
|
| 109 |
+
jpost<{ observation: MarketObservation; info: Record<string, unknown> }>(
|
| 110 |
+
'/reset',
|
| 111 |
+
{ task_id },
|
| 112 |
+
),
|
| 113 |
+
step: (side: Side, quantity: number) =>
|
| 114 |
+
jpost<{ observation: MarketObservation; reward: number; done: boolean; info: Record<string, unknown> }>(
|
| 115 |
+
'/step',
|
| 116 |
+
{ side, quantity },
|
| 117 |
+
),
|
| 118 |
+
state: () => jget<EnvironmentState>('/state'),
|
| 119 |
+
ohlcv: (task_id: string) => jget<OhlcvResponse>(`/ohlcv?task_id=${encodeURIComponent(task_id)}`),
|
| 120 |
+
council: () => jget<CouncilDecision>('/council'),
|
| 121 |
+
trainingMetrics: () => jget<TrainingMetrics>('/training/metrics'),
|
| 122 |
+
};
|
| 123 |
+
|
| 124 |
+
// ---------- shared state hook ---------------------------------------------
|
| 125 |
+
|
| 126 |
+
export interface StockerEnv {
|
| 127 |
+
tasks: string[];
|
| 128 |
+
taskId: string | null;
|
| 129 |
+
observation: MarketObservation | null;
|
| 130 |
+
envState: EnvironmentState | null;
|
| 131 |
+
council: CouncilDecision | null;
|
| 132 |
+
ohlcv: OhlcvResponse | null;
|
| 133 |
+
/** Starting cash for the active task, captured from /reset info. */
|
| 134 |
+
startingCash: number;
|
| 135 |
+
loading: boolean;
|
| 136 |
+
error: string | null;
|
| 137 |
+
selectTask: (taskId: string) => Promise<void>;
|
| 138 |
+
submitTrade: (side: Side, quantity: number) => Promise<void>;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
export function useStockerEnv(): StockerEnv {
|
| 142 |
+
const [tasks, setTasks] = useState<string[]>([]);
|
| 143 |
+
const [taskId, setTaskId] = useState<string | null>(null);
|
| 144 |
+
const [observation, setObservation] = useState<MarketObservation | null>(null);
|
| 145 |
+
const [envState, setEnvState] = useState<EnvironmentState | null>(null);
|
| 146 |
+
const [council, setCouncil] = useState<CouncilDecision | null>(null);
|
| 147 |
+
const [ohlcv, setOhlcv] = useState<OhlcvResponse | null>(null);
|
| 148 |
+
const [startingCash, setStartingCash] = useState<number>(10000);
|
| 149 |
+
const [loading, setLoading] = useState<boolean>(false);
|
| 150 |
+
const [error, setError] = useState<string | null>(null);
|
| 151 |
+
|
| 152 |
+
const selectTask = useCallback(async (next: string) => {
|
| 153 |
+
setLoading(true);
|
| 154 |
+
setError(null);
|
| 155 |
+
try {
|
| 156 |
+
const reset = await api.reset(next);
|
| 157 |
+
setTaskId(next);
|
| 158 |
+
setObservation(reset.observation);
|
| 159 |
+
const sc = Number(reset.info?.starting_cash);
|
| 160 |
+
if (Number.isFinite(sc) && sc > 0) setStartingCash(sc);
|
| 161 |
+
const [bars, st] = await Promise.all([api.ohlcv(next), api.state()]);
|
| 162 |
+
setOhlcv(bars);
|
| 163 |
+
setEnvState(st);
|
| 164 |
+
try {
|
| 165 |
+
setCouncil(await api.council());
|
| 166 |
+
} catch (e) {
|
| 167 |
+
// council can fail silently — UI shows empty state
|
| 168 |
+
setCouncil(null);
|
| 169 |
+
console.warn('council fetch failed', e);
|
| 170 |
+
}
|
| 171 |
+
} catch (e: unknown) {
|
| 172 |
+
const msg = e instanceof Error ? e.message : String(e);
|
| 173 |
+
setError(msg);
|
| 174 |
+
} finally {
|
| 175 |
+
setLoading(false);
|
| 176 |
+
}
|
| 177 |
+
}, []);
|
| 178 |
+
|
| 179 |
+
const submitTrade = useCallback(async (side: Side, quantity: number) => {
|
| 180 |
+
setLoading(true);
|
| 181 |
+
setError(null);
|
| 182 |
+
try {
|
| 183 |
+
const stepRes = await api.step(side, quantity);
|
| 184 |
+
setObservation(stepRes.observation);
|
| 185 |
+
const st = await api.state();
|
| 186 |
+
setEnvState(st);
|
| 187 |
+
if (!stepRes.done) {
|
| 188 |
+
try {
|
| 189 |
+
setCouncil(await api.council());
|
| 190 |
+
} catch (e) {
|
| 191 |
+
setCouncil(null);
|
| 192 |
+
console.warn('council fetch failed', e);
|
| 193 |
+
}
|
| 194 |
+
}
|
| 195 |
+
} catch (e: unknown) {
|
| 196 |
+
const msg = e instanceof Error ? e.message : String(e);
|
| 197 |
+
setError(msg);
|
| 198 |
+
} finally {
|
| 199 |
+
setLoading(false);
|
| 200 |
+
}
|
| 201 |
+
}, []);
|
| 202 |
+
|
| 203 |
+
// bootstrap: load tasks, then auto-select first
|
| 204 |
+
useEffect(() => {
|
| 205 |
+
let cancelled = false;
|
| 206 |
+
api
|
| 207 |
+
.meta()
|
| 208 |
+
.then((m) => {
|
| 209 |
+
if (cancelled) return;
|
| 210 |
+
setTasks(m.tasks);
|
| 211 |
+
if (m.tasks.length > 0) {
|
| 212 |
+
void selectTask(m.tasks[0]);
|
| 213 |
+
}
|
| 214 |
+
})
|
| 215 |
+
.catch((e: unknown) => {
|
| 216 |
+
const msg = e instanceof Error ? e.message : String(e);
|
| 217 |
+
setError(msg);
|
| 218 |
+
});
|
| 219 |
+
return () => {
|
| 220 |
+
cancelled = true;
|
| 221 |
+
};
|
| 222 |
+
}, [selectTask]);
|
| 223 |
+
|
| 224 |
+
return {
|
| 225 |
+
tasks,
|
| 226 |
+
taskId,
|
| 227 |
+
observation,
|
| 228 |
+
envState,
|
| 229 |
+
council,
|
| 230 |
+
ohlcv,
|
| 231 |
+
startingCash,
|
| 232 |
+
loading,
|
| 233 |
+
error,
|
| 234 |
+
selectTask,
|
| 235 |
+
submitTrade,
|
| 236 |
+
};
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
// Display-name + role keyword mapping for the seven specialists.
|
| 240 |
+
// Backend `name` values come from app/council/specialists.py.
|
| 241 |
+
export const SPECIALIST_DISPLAY: Record<string, string> = {
|
| 242 |
+
chart_pattern: 'Chart Pattern',
|
| 243 |
+
seasonal_trend: 'Seasonal',
|
| 244 |
+
indicator: 'Indicator',
|
| 245 |
+
news: 'News',
|
| 246 |
+
forum_sentiment: 'Forum',
|
| 247 |
+
peer_commodity: 'Peer',
|
| 248 |
+
geopolitics: 'Geo',
|
| 249 |
+
};
|
| 250 |
+
|
| 251 |
+
export function statusFromSignal(signal: number): 'green' | 'red' | 'gray' {
|
| 252 |
+
if (signal > 0.1) return 'green';
|
| 253 |
+
if (signal < -0.1) return 'red';
|
| 254 |
+
return 'gray';
|
| 255 |
+
}
|
frontend/src/index.css
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
| 2 |
+
@import "tailwindcss";
|
| 3 |
+
|
| 4 |
+
@theme {
|
| 5 |
+
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
|
| 6 |
+
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
@layer base {
|
| 10 |
+
body {
|
| 11 |
+
@apply bg-[#050505] text-[#e5e2e1] antialiased min-h-screen overflow-x-hidden;
|
| 12 |
+
}
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
@layer components {
|
| 16 |
+
.glass-panel {
|
| 17 |
+
@apply bg-white/5 backdrop-blur-xl border border-white/10;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
.terminal-text {
|
| 21 |
+
@apply font-mono text-sm tracking-tight;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
.neon-green {
|
| 25 |
+
@apply text-[#00FF41] drop-shadow-[0_0_8px_rgba(0,255,65,0.4)];
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
.neon-border {
|
| 29 |
+
@apply border-[#00FF41]/30 hover:border-[#00FF41]/60 transition-colors;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
.bg-aurora {
|
| 33 |
+
background: radial-gradient(circle at 15% 50%, rgba(115, 31, 255, 0.08) 0%, transparent 50%),
|
| 34 |
+
radial-gradient(circle at 85% 30%, rgba(0, 255, 65, 0.05) 0%, transparent 50%);
|
| 35 |
+
background-attachment: fixed;
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
@keyframes scanline {
|
| 40 |
+
0% { transform: translateY(-100%); }
|
| 41 |
+
100% { transform: translateY(100%); }
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
.scanline::after {
|
| 45 |
+
content: "";
|
| 46 |
+
position: absolute;
|
| 47 |
+
top: 0;
|
| 48 |
+
left: 0;
|
| 49 |
+
width: 100%;
|
| 50 |
+
height: 2px;
|
| 51 |
+
background: rgba(0, 255, 65, 0.05);
|
| 52 |
+
animation: scanline 8s linear infinite;
|
| 53 |
+
pointer-events: none;
|
| 54 |
+
}
|
frontend/src/main.tsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import {StrictMode} from 'react';
|
| 2 |
+
import {createRoot} from 'react-dom/client';
|
| 3 |
+
import App from './App.tsx';
|
| 4 |
+
import './index.css';
|
| 5 |
+
|
| 6 |
+
createRoot(document.getElementById('root')!).render(
|
| 7 |
+
<StrictMode>
|
| 8 |
+
<App />
|
| 9 |
+
</StrictMode>,
|
| 10 |
+
);
|
frontend/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ES2022",
|
| 4 |
+
"experimentalDecorators": true,
|
| 5 |
+
"useDefineForClassFields": false,
|
| 6 |
+
"module": "ESNext",
|
| 7 |
+
"lib": [
|
| 8 |
+
"ES2022",
|
| 9 |
+
"DOM",
|
| 10 |
+
"DOM.Iterable"
|
| 11 |
+
],
|
| 12 |
+
"skipLibCheck": true,
|
| 13 |
+
"moduleResolution": "bundler",
|
| 14 |
+
"isolatedModules": true,
|
| 15 |
+
"moduleDetection": "force",
|
| 16 |
+
"allowJs": true,
|
| 17 |
+
"jsx": "react-jsx",
|
| 18 |
+
"paths": {
|
| 19 |
+
"@/*": [
|
| 20 |
+
"./*"
|
| 21 |
+
]
|
| 22 |
+
},
|
| 23 |
+
"allowImportingTsExtensions": true,
|
| 24 |
+
"noEmit": true
|
| 25 |
+
}
|
| 26 |
+
}
|
frontend/vite.config.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import tailwindcss from '@tailwindcss/vite';
|
| 2 |
+
import react from '@vitejs/plugin-react';
|
| 3 |
+
import path from 'path';
|
| 4 |
+
import {defineConfig} from 'vite';
|
| 5 |
+
|
| 6 |
+
export default defineConfig(() => ({
|
| 7 |
+
plugins: [react(), tailwindcss()],
|
| 8 |
+
resolve: {
|
| 9 |
+
alias: {
|
| 10 |
+
'@': path.resolve(__dirname, '.'),
|
| 11 |
+
},
|
| 12 |
+
},
|
| 13 |
+
server: {
|
| 14 |
+
hmr: process.env.DISABLE_HMR !== 'true',
|
| 15 |
+
proxy: {
|
| 16 |
+
'/health': 'http://localhost:8000',
|
| 17 |
+
'/meta': 'http://localhost:8000',
|
| 18 |
+
'/reset': 'http://localhost:8000',
|
| 19 |
+
'/step': 'http://localhost:8000',
|
| 20 |
+
'/state': 'http://localhost:8000',
|
| 21 |
+
'/ohlcv': 'http://localhost:8000',
|
| 22 |
+
'/council': 'http://localhost:8000',
|
| 23 |
+
'/training': 'http://localhost:8000',
|
| 24 |
+
},
|
| 25 |
+
},
|
| 26 |
+
}));
|
scripts/build_ideal_profit.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Precompute per-task ideal profit trajectories used by the grader.
|
| 3 |
+
|
| 4 |
+
For each task in TASK_META we simulate a perfect-foresight policy on the
|
| 5 |
+
episode price series: at every step we go max-long if the next bar is up
|
| 6 |
+
and flat otherwise. The resulting per-step cumulative PnL fraction is
|
| 7 |
+
written to data/ideal_profits/<task_id>.json. The grader (per-step
|
| 8 |
+
performance component) reads these sidecars and compares actual PnL to
|
| 9 |
+
the ideal trajectory.
|
| 10 |
+
|
| 11 |
+
Transaction cost is applied to the ideal trajectory at the same rate the
|
| 12 |
+
env uses, so the gap the model sees reflects only foresight quality.
|
| 13 |
+
|
| 14 |
+
Run:
|
| 15 |
+
uv run python scripts/build_ideal_profit.py
|
| 16 |
+
"""
|
| 17 |
+
from __future__ import annotations
|
| 18 |
+
|
| 19 |
+
import json
|
| 20 |
+
import sys
|
| 21 |
+
from pathlib import Path
|
| 22 |
+
|
| 23 |
+
ROOT = Path(__file__).resolve().parent.parent
|
| 24 |
+
sys.path.insert(0, str(ROOT))
|
| 25 |
+
|
| 26 |
+
from app.config import settings # noqa: E402
|
| 27 |
+
from app.core.tasks import TASK_META, list_task_ids # noqa: E402
|
| 28 |
+
from app.data import loader # noqa: E402
|
| 29 |
+
|
| 30 |
+
OUTPUT_DIR = ROOT / "data" / "ideal_profits"
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def simulate_perfect_foresight(
|
| 34 |
+
prices: list[float],
|
| 35 |
+
starting_cash: float,
|
| 36 |
+
txn_cost_rate: float,
|
| 37 |
+
) -> tuple[list[float], list[float]]:
|
| 38 |
+
"""Multi-transaction perfect foresight on a single ticker.
|
| 39 |
+
|
| 40 |
+
Returns (portfolio_curve, pnl_pct_curve) where each list has len(prices)
|
| 41 |
+
entries and represents the value (and pnl fraction) at the end of each
|
| 42 |
+
step after that step's action has been applied.
|
| 43 |
+
"""
|
| 44 |
+
cash = float(starting_cash)
|
| 45 |
+
position = 0
|
| 46 |
+
portfolio_curve: list[float] = []
|
| 47 |
+
pnl_curve: list[float] = []
|
| 48 |
+
|
| 49 |
+
n = len(prices)
|
| 50 |
+
for i in range(n):
|
| 51 |
+
price = prices[i]
|
| 52 |
+
going_up = (i + 1 < n) and (prices[i + 1] > price)
|
| 53 |
+
|
| 54 |
+
if going_up and position == 0:
|
| 55 |
+
# Affordable shares after paying txn cost: solve qty * price * (1 + r) <= cash.
|
| 56 |
+
qty = int(cash // (price * (1.0 + txn_cost_rate)))
|
| 57 |
+
if qty > 0:
|
| 58 |
+
notional = qty * price
|
| 59 |
+
cash -= notional + notional * txn_cost_rate
|
| 60 |
+
position += qty
|
| 61 |
+
elif (not going_up) and position > 0:
|
| 62 |
+
notional = position * price
|
| 63 |
+
cash += notional - notional * txn_cost_rate
|
| 64 |
+
position = 0
|
| 65 |
+
|
| 66 |
+
port_value = cash + position * price
|
| 67 |
+
portfolio_curve.append(port_value)
|
| 68 |
+
pnl_curve.append((port_value - starting_cash) / starting_cash)
|
| 69 |
+
|
| 70 |
+
return portfolio_curve, pnl_curve
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def build_for_task(task_id: str) -> dict:
|
| 74 |
+
meta = TASK_META[task_id]
|
| 75 |
+
rows = loader.episode_rows(task_id)
|
| 76 |
+
if rows.empty:
|
| 77 |
+
raise RuntimeError(
|
| 78 |
+
f"No episode data for {task_id}. Run scripts/build_dataset.py first."
|
| 79 |
+
)
|
| 80 |
+
prices = rows["close"].astype(float).tolist()
|
| 81 |
+
starting_cash = float(meta["starting_cash"])
|
| 82 |
+
|
| 83 |
+
portfolio_curve, pnl_curve = simulate_perfect_foresight(
|
| 84 |
+
prices=prices,
|
| 85 |
+
starting_cash=starting_cash,
|
| 86 |
+
txn_cost_rate=settings.transaction_cost_rate,
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
return {
|
| 90 |
+
"task_id": task_id,
|
| 91 |
+
"ticker": meta["ticker"],
|
| 92 |
+
"starting_cash": starting_cash,
|
| 93 |
+
"transaction_cost_rate": settings.transaction_cost_rate,
|
| 94 |
+
"ideal_pnl_pct_series": [round(v, 6) for v in pnl_curve],
|
| 95 |
+
"ideal_pnl_pct_total": round(pnl_curve[-1], 6) if pnl_curve else 0.0,
|
| 96 |
+
"ideal_portfolio_curve": [round(v, 4) for v in portfolio_curve],
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def main() -> int:
|
| 101 |
+
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
| 102 |
+
for task_id in list_task_ids():
|
| 103 |
+
payload = build_for_task(task_id)
|
| 104 |
+
out = OUTPUT_DIR / f"{task_id}.json"
|
| 105 |
+
out.write_text(json.dumps(payload, indent=2))
|
| 106 |
+
n = len(payload["ideal_pnl_pct_series"])
|
| 107 |
+
print(
|
| 108 |
+
f"{task_id:<14} steps={n:>3} "
|
| 109 |
+
f"ideal_total={payload['ideal_pnl_pct_total']:+.4f} "
|
| 110 |
+
f"-> {out.relative_to(ROOT)}"
|
| 111 |
+
)
|
| 112 |
+
return 0
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
if __name__ == "__main__":
|
| 116 |
+
sys.exit(main())
|
tests/test_api.py
CHANGED
|
@@ -34,3 +34,38 @@ def test_state_roundtrip():
|
|
| 34 |
s = client.get("/state").json()
|
| 35 |
r = client.post("/state", json=s)
|
| 36 |
assert r.status_code == 200
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
s = client.get("/state").json()
|
| 35 |
r = client.post("/state", json=s)
|
| 36 |
assert r.status_code == 200
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def test_ohlcv_returns_bars():
|
| 40 |
+
r = client.get("/ohlcv", params={"task_id": "task_easy"})
|
| 41 |
+
assert r.status_code == 200
|
| 42 |
+
body = r.json()
|
| 43 |
+
assert body["ticker"] == "AAPL"
|
| 44 |
+
assert len(body["bars"]) > 0
|
| 45 |
+
bar = body["bars"][0]
|
| 46 |
+
assert {"time", "open", "high", "low", "close", "volume", "in_episode"} <= bar.keys()
|
| 47 |
+
assert bar["high"] >= bar["low"]
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def test_ohlcv_unknown_task_404():
|
| 51 |
+
r = client.get("/ohlcv", params={"task_id": "nope"})
|
| 52 |
+
assert r.status_code == 404
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def test_council_after_reset():
|
| 56 |
+
client.post("/reset", json={"task_id": "task_easy"})
|
| 57 |
+
r = client.get("/council")
|
| 58 |
+
assert r.status_code == 200
|
| 59 |
+
body = r.json()
|
| 60 |
+
assert len(body["votes"]) == 7
|
| 61 |
+
assert body["action"]["side"] in {"buy", "sell", "hold"}
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def test_training_metrics_returns_summary():
|
| 65 |
+
r = client.get("/training/metrics")
|
| 66 |
+
assert r.status_code == 200
|
| 67 |
+
body = r.json()
|
| 68 |
+
assert body["status"] in {"completed", "no_runs"}
|
| 69 |
+
if body["status"] == "completed":
|
| 70 |
+
assert len(body["summary"]) >= 1
|
| 71 |
+
assert "alpha_pct" in body["summary"][0]
|
tests/test_graders.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Unit tests for the grader (compute_step_reward).
|
| 2 |
+
|
| 3 |
+
Covers the asymmetric performance shape, the inflation drag component, and
|
| 4 |
+
the env-level transaction cost deduction.
|
| 5 |
+
"""
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
from app.config import Settings
|
| 9 |
+
from app.core.environment import StockerEnv
|
| 10 |
+
from app.core.graders import compute_step_reward
|
| 11 |
+
from app.models import TradeAction
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _settings(**overrides) -> Settings:
|
| 15 |
+
base = dict(
|
| 16 |
+
transaction_cost_rate=0.001,
|
| 17 |
+
annual_inflation_rate=0.05,
|
| 18 |
+
reward_weight_performance=0.7,
|
| 19 |
+
reward_weight_inflation=0.3,
|
| 20 |
+
)
|
| 21 |
+
base.update(overrides)
|
| 22 |
+
return Settings(**base)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _call(
|
| 26 |
+
*,
|
| 27 |
+
actual_pnl_pct: float,
|
| 28 |
+
ideal_at_step: float,
|
| 29 |
+
ideal_total: float,
|
| 30 |
+
step_index: int = 5,
|
| 31 |
+
total_steps: int = 50,
|
| 32 |
+
starting_cash: float = 10000.0,
|
| 33 |
+
invalid: bool = False,
|
| 34 |
+
settings: Settings | None = None,
|
| 35 |
+
):
|
| 36 |
+
settings = settings or _settings()
|
| 37 |
+
series = [0.0] * total_steps
|
| 38 |
+
series[step_index] = ideal_at_step
|
| 39 |
+
new_portfolio = starting_cash * (1.0 + actual_pnl_pct)
|
| 40 |
+
return compute_step_reward(
|
| 41 |
+
action=TradeAction(side="hold", quantity=0),
|
| 42 |
+
new_portfolio=new_portfolio,
|
| 43 |
+
starting_cash=starting_cash,
|
| 44 |
+
invalid=invalid,
|
| 45 |
+
step_index=step_index,
|
| 46 |
+
total_steps=total_steps,
|
| 47 |
+
ideal_pnl_pct_series=series,
|
| 48 |
+
ideal_pnl_pct_total=ideal_total,
|
| 49 |
+
settings=settings,
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def test_at_ideal_step_yields_high_performance():
|
| 54 |
+
# actual real ~= ideal at this step -> gap ~= 0 -> perf ~= 1
|
| 55 |
+
result = _call(actual_pnl_pct=0.05, ideal_at_step=0.05, ideal_total=0.20)
|
| 56 |
+
assert result.breakdown["performance_factor"] > 0.95
|
| 57 |
+
assert result.score > 0.5
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def test_far_behind_ideal_yields_punishment():
|
| 61 |
+
# actual < ideal by much more than scale -> perf = -1
|
| 62 |
+
result = _call(actual_pnl_pct=-0.05, ideal_at_step=0.30, ideal_total=0.30)
|
| 63 |
+
assert result.breakdown["performance_factor"] == -1.0
|
| 64 |
+
assert result.breakdown["weighted_performance"] < 0
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def test_outperforming_ideal_yields_bonus():
|
| 68 |
+
# gap < 0 -> perf > 1.0 (env clip will cap, but raw breakdown shows the bonus)
|
| 69 |
+
result = _call(actual_pnl_pct=0.10, ideal_at_step=0.05, ideal_total=0.10)
|
| 70 |
+
assert result.breakdown["performance_factor"] > 1.0
|
| 71 |
+
assert result.breakdown["gap"] < 0
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def test_inflation_factor_is_negative_with_positive_pnl():
|
| 75 |
+
# Real < nominal under positive inflation, so inflation_factor < 0
|
| 76 |
+
result = _call(
|
| 77 |
+
actual_pnl_pct=0.05,
|
| 78 |
+
ideal_at_step=0.05,
|
| 79 |
+
ideal_total=0.20,
|
| 80 |
+
step_index=252, # one year in -> noticeable inflation drag
|
| 81 |
+
total_steps=300,
|
| 82 |
+
)
|
| 83 |
+
assert result.breakdown["inflation_factor"] < 0
|
| 84 |
+
assert result.breakdown["real_pnl_pct"] < result.breakdown["nominal_pnl_pct"]
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def test_invalid_action_subtracts_penalty():
|
| 88 |
+
fine = _call(actual_pnl_pct=0.05, ideal_at_step=0.05, ideal_total=0.20)
|
| 89 |
+
bad = _call(actual_pnl_pct=0.05, ideal_at_step=0.05, ideal_total=0.20, invalid=True)
|
| 90 |
+
assert bad.score < fine.score
|
| 91 |
+
assert "invalid_action_penalty" in bad.breakdown
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def test_buy_deducts_transaction_cost_from_cash():
|
| 95 |
+
"""End-to-end env check: a buy at 0.1% transaction cost shaves cash."""
|
| 96 |
+
env = StockerEnv(task_id="task_easy")
|
| 97 |
+
env.reset()
|
| 98 |
+
cash_before = env.state().cash
|
| 99 |
+
price = env._prices[0]
|
| 100 |
+
qty = 5
|
| 101 |
+
env.step({"side": "buy", "quantity": qty})
|
| 102 |
+
cash_after = env.state().cash
|
| 103 |
+
|
| 104 |
+
notional = qty * price
|
| 105 |
+
expected_cost = notional * 1.001 # 0.1% txn cost
|
| 106 |
+
assert abs((cash_before - cash_after) - expected_cost) < 1e-4
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def test_sell_deducts_transaction_cost_from_proceeds():
|
| 110 |
+
env = StockerEnv(task_id="task_easy")
|
| 111 |
+
env.reset()
|
| 112 |
+
qty = 5
|
| 113 |
+
env.step({"side": "buy", "quantity": qty})
|
| 114 |
+
cash_after_buy = env.state().cash
|
| 115 |
+
sell_price = env._prices[1] # next-step price (env advanced after the buy)
|
| 116 |
+
env.step({"side": "sell", "quantity": qty})
|
| 117 |
+
cash_after_sell = env.state().cash
|
| 118 |
+
|
| 119 |
+
notional = qty * sell_price
|
| 120 |
+
expected_proceeds = notional * 0.999
|
| 121 |
+
assert abs((cash_after_sell - cash_after_buy) - expected_proceeds) < 1e-4
|
uv.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|