Commit ·
1866d85
0
Parent(s):
Duplicate from markov-ai/autocad-bench
Browse filesCo-authored-by: Dev Mandal <ShadowPenguin@users.noreply.huggingface.co>
This view is limited to 50 files because it contains too many changes. See raw diff
- .env.example +17 -0
- .gitattributes +37 -0
- .gitignore +30 -0
- .python-version +1 -0
- CONTRIBUTING.md +89 -0
- README.md +182 -0
- apps/dashboard/README.md +17 -0
- apps/dashboard/backfill-trace-summaries.mjs +85 -0
- apps/dashboard/cloud-metadata.mjs +123 -0
- apps/dashboard/index.html +14 -0
- apps/dashboard/package-lock.json +1910 -0
- apps/dashboard/package.json +26 -0
- apps/dashboard/server.d.mts +1 -0
- apps/dashboard/server.mjs +372 -0
- apps/dashboard/src/App.vue +722 -0
- apps/dashboard/src/main.ts +5 -0
- apps/dashboard/src/styles.css +451 -0
- apps/dashboard/src/types.ts +223 -0
- apps/dashboard/tests/cloud-metadata.test.mjs +117 -0
- apps/dashboard/tests/trace-api.test.mjs +872 -0
- apps/dashboard/trace-api.d.mts +71 -0
- apps/dashboard/trace-api.mjs +1080 -0
- apps/dashboard/tsconfig.app.json +17 -0
- apps/dashboard/tsconfig.json +7 -0
- apps/dashboard/tsconfig.node.json +15 -0
- apps/dashboard/vite.config.ts +22 -0
- apps/results-dashboard/.env.example +6 -0
- apps/results-dashboard/README.md +17 -0
- apps/results-dashboard/index.html +14 -0
- apps/results-dashboard/package-lock.json +1910 -0
- apps/results-dashboard/package.json +25 -0
- apps/results-dashboard/results-api.mjs +714 -0
- apps/results-dashboard/server.mjs +71 -0
- apps/results-dashboard/src/App.vue +831 -0
- apps/results-dashboard/src/components/ReliableImage.vue +91 -0
- apps/results-dashboard/src/env.d.ts +7 -0
- apps/results-dashboard/src/main.ts +5 -0
- apps/results-dashboard/src/styles.css +400 -0
- apps/results-dashboard/src/types.ts +164 -0
- apps/results-dashboard/tests/results-api.test.mjs +131 -0
- apps/results-dashboard/tsconfig.app.json +22 -0
- apps/results-dashboard/tsconfig.json +7 -0
- apps/results-dashboard/tsconfig.node.json +17 -0
- apps/results-dashboard/vite.config.ts +15 -0
- artifacts/gold-cache/README.md +31 -0
- artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-001/metadata.json +0 -0
- artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-001/render.png +3 -0
- artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-002/metadata.json +0 -0
- artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-002/render.png +3 -0
- artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-003/metadata.json +0 -0
.env.example
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model providers
|
| 2 |
+
OPENAI_API_KEY=
|
| 3 |
+
ANTHROPIC_API_KEY=
|
| 4 |
+
OPENROUTER_API_KEY=
|
| 5 |
+
FIREWORKS_API_KEY=
|
| 6 |
+
|
| 7 |
+
# AWS control plane
|
| 8 |
+
# AWS_PROFILE=my-profile
|
| 9 |
+
AWS_REGION=us-east-1
|
| 10 |
+
|
| 11 |
+
# Optional when invoking an installed wheel outside the repository root
|
| 12 |
+
AUTOCAD_BENCH_ROOT=
|
| 13 |
+
|
| 14 |
+
# Optional dashboard cloud artifact retrieval
|
| 15 |
+
# AUTOCAD_BENCH_S3_BUCKET=your-autocad-bench-bucket
|
| 16 |
+
AUTOCAD_BENCH_S3_RUNS_PREFIX=controller/runs
|
| 17 |
+
AUTOCAD_BENCH_CLOUD_ARTIFACTS=off
|
.gitattributes
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
*.dwg filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.venv/
|
| 2 |
+
node_modules/
|
| 3 |
+
.env
|
| 4 |
+
.env.*
|
| 5 |
+
!.env.example
|
| 6 |
+
.private/
|
| 7 |
+
.pytest_cache/
|
| 8 |
+
__pycache__/
|
| 9 |
+
*.py[cod]
|
| 10 |
+
.DS_Store
|
| 11 |
+
dist/
|
| 12 |
+
runs/
|
| 13 |
+
artifacts/gold-cache/*/
|
| 14 |
+
artifacts/gold-cache/summary.json
|
| 15 |
+
!artifacts/gold-cache/autocad-2019-r23.0.46-v4/
|
| 16 |
+
!artifacts/gold-cache/autocad-2019-r23.0.46-v4/**
|
| 17 |
+
apps/dashboard/dist/
|
| 18 |
+
apps/dashboard/.tsbuild/
|
| 19 |
+
apps/dashboard/*.tsbuildinfo
|
| 20 |
+
apps/dashboard/*.d.ts
|
| 21 |
+
apps/dashboard/vite.config.js
|
| 22 |
+
apps/dashboard/src/*.js
|
| 23 |
+
apps/results-dashboard/.tsbuild/
|
| 24 |
+
apps/results-dashboard/*.tsbuildinfo
|
| 25 |
+
apps/results-dashboard/*.d.ts
|
| 26 |
+
apps/results-dashboard/vite.config.js
|
| 27 |
+
apps/results-dashboard/src/**/*.js
|
| 28 |
+
apps/results-dashboard/src/**/*.d.ts
|
| 29 |
+
!apps/results-dashboard/src/env.d.ts
|
| 30 |
+
archive/source/*.zip
|
.python-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
3.12.12
|
CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Contributing
|
| 2 |
+
|
| 3 |
+
AutoCAD Bench separates task data, model execution, trusted evaluation, and infrastructure. Keep changes inside the narrowest applicable boundary and preserve the scorer/model trust split.
|
| 4 |
+
|
| 5 |
+
## Development setup
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
uv sync --dev
|
| 9 |
+
cd apps/dashboard && npm ci
|
| 10 |
+
```
|
| 11 |
+
|
| 12 |
+
## Common changes
|
| 13 |
+
|
| 14 |
+
### Add or update a task
|
| 15 |
+
|
| 16 |
+
1. Add the reference PNG and gold DWG under `tasks/corpus/2d/` or `tasks/corpus/3d/`.
|
| 17 |
+
2. Add or update the canonical row in `tasks/manifest.jsonl`.
|
| 18 |
+
3. Update `tasks/gold-audit.jsonl` with verified file metadata.
|
| 19 |
+
4. Rebuild the gold cache with the pinned trusted evaluator.
|
| 20 |
+
5. Run corpus, audit, cache, and test validation.
|
| 21 |
+
|
| 22 |
+
Never expose a gold path through `PublicTaskSpec`, `AutoCADTaskData`, a model prompt, or a model trace.
|
| 23 |
+
|
| 24 |
+
### Change the harness
|
| 25 |
+
|
| 26 |
+
Model-provider logic and the agent loop belong under `src/autocad_bench/harness/`. Computer actions must remain typed and fully validated before any broker call. Preserve the 7,000-action rollout budget and the distinction between audit-only intermediate frames and model-visible observations.
|
| 27 |
+
|
| 28 |
+
### Change evaluation
|
| 29 |
+
|
| 30 |
+
Trusted evaluation belongs under `src/autocad_bench/evaluation/`. A render-contract change requires a new evaluator version and gold cache; never reinterpret an old version in place. Windows evaluator changes also require a compiled fresh-clone gate before an AMI becomes active.
|
| 31 |
+
|
| 32 |
+
### Change infrastructure
|
| 33 |
+
|
| 34 |
+
Backend selection, registration, and lifecycle boundaries belong under
|
| 35 |
+
`src/autocad_bench/infrastructure/`. Keep the batch controller
|
| 36 |
+
backend-neutral. New external backends should implement the public plugin
|
| 37 |
+
contract and register an `autocad_bench.infrastructure` entry point; see
|
| 38 |
+
`docs/infrastructure-plugins.md`. Backends that retain reconnectable workers
|
| 39 |
+
should also implement the optional recovery protocol; do not put
|
| 40 |
+
provider-specific worker inspection back into `orchestration/resume.py`.
|
| 41 |
+
|
| 42 |
+
The built-in AWS lifecycle and broker-client code remains under
|
| 43 |
+
`src/autocad_bench/sandbox/`. Windows runtime source belongs under
|
| 44 |
+
`sandbox/windows/`; cloud-controller assets belong under
|
| 45 |
+
`infra/cloud-controller/`. Do not launch instances, deploy infrastructure, or
|
| 46 |
+
replace the active AMI without explicit approval.
|
| 47 |
+
|
| 48 |
+
## Validation
|
| 49 |
+
|
| 50 |
+
Run the complete offline gate before handing off a change:
|
| 51 |
+
|
| 52 |
+
```bash
|
| 53 |
+
uv run autocad-bench validate
|
| 54 |
+
uv run autocad-bench validate-audit
|
| 55 |
+
uv run pytest
|
| 56 |
+
|
| 57 |
+
cd apps/dashboard
|
| 58 |
+
npm test
|
| 59 |
+
npm run build
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
Before publishing, build the package and corpus into one new release directory,
|
| 63 |
+
then verify that complete directory:
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
uv run autocad-bench-release build \
|
| 67 |
+
--output dist/releases/0.1.0
|
| 68 |
+
uv run autocad-bench-release verify \
|
| 69 |
+
--root dist/releases/0.1.0
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
The builder uses the allowlist-based bundle exporter and pinned PEP 517
|
| 73 |
+
dependencies. It must never be expanded to include configs, environment files,
|
| 74 |
+
controller state, or run outputs. See `docs/releases.md` for the complete
|
| 75 |
+
manual publishing and reproducibility workflow.
|
| 76 |
+
|
| 77 |
+
When a current evaluator cache is available locally:
|
| 78 |
+
|
| 79 |
+
```bash
|
| 80 |
+
uv run autocad-bench verify-gold-cache \
|
| 81 |
+
--evaluator-version autocad-2019-r23.0.46-v4 \
|
| 82 |
+
--split all
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
Live provider or AWS checks are separate from the offline suite and require explicit credentials.
|
| 86 |
+
|
| 87 |
+
## Documentation
|
| 88 |
+
|
| 89 |
+
Update the relevant contract whenever behavior changes. Release evidence is append-only: add a new release record instead of editing historical results to describe a newer runtime.
|
README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
tags:
|
| 3 |
+
- benchmark
|
| 4 |
+
- computer-use
|
| 5 |
+
- autocad
|
| 6 |
+
- cad
|
| 7 |
+
- multimodal
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# AutoCAD Bench
|
| 11 |
+
|
| 12 |
+
AutoCAD Bench is a computer-use benchmark for evaluating whether multimodal agents can reproduce dimensioned 2D drawings and reconstruct dimensionally accurate 3D models in desktop AutoCAD.
|
| 13 |
+
|
| 14 |
+
Each rollout receives a task-specific instruction and reference image, operates an isolated AutoCAD session using mouse and keyboard actions, and is evaluated from the final DWG. Gold drawings and evaluator credentials never enter the model-visible session.
|
| 15 |
+
|
| 16 |
+
## How it works
|
| 17 |
+
|
| 18 |
+
```text
|
| 19 |
+
tasks/manifest.jsonl + reference PNG
|
| 20 |
+
│
|
| 21 |
+
▼
|
| 22 |
+
model harness + agent loop
|
| 23 |
+
│
|
| 24 |
+
▼
|
| 25 |
+
isolated Windows/AutoCAD sandbox
|
| 26 |
+
│
|
| 27 |
+
▼
|
| 28 |
+
final attempt.dwg
|
| 29 |
+
│
|
| 30 |
+
▼
|
| 31 |
+
trusted inspection + deterministic score + vision judgment
|
| 32 |
+
│
|
| 33 |
+
▼
|
| 34 |
+
runs/<benchmark>/ + dashboard
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
The benchmark contains exactly 50 tasks, numbered `task-001` through
|
| 38 |
+
`task-050` with no gaps: 21 2D drawings and 29 3D drawings. A 2D task is
|
| 39 |
+
completed in Model Space. A 3D task requires true geometry in Model Space and
|
| 40 |
+
the complete reference sheet on `Layout1`; evaluator v4 renders and compares
|
| 41 |
+
that required layout.
|
| 42 |
+
|
| 43 |
+
## Getting started
|
| 44 |
+
|
| 45 |
+
Requirements:
|
| 46 |
+
|
| 47 |
+
- Python 3.12
|
| 48 |
+
- [uv](https://docs.astral.sh/uv/)
|
| 49 |
+
- Node.js 20+ for the dashboard
|
| 50 |
+
- Infrastructure-backend and model-provider credentials only for live runs
|
| 51 |
+
|
| 52 |
+
Install the Python environment and validate the corpus:
|
| 53 |
+
|
| 54 |
+
```bash
|
| 55 |
+
# The development group includes the built-in AWS backend dependencies.
|
| 56 |
+
uv sync --dev
|
| 57 |
+
uv run autocad-bench validate
|
| 58 |
+
uv run autocad-bench validate-audit
|
| 59 |
+
uv run autocad-bench list --split all
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
Published installations that use the built-in backend should install
|
| 63 |
+
`autocad-bench[aws]`. Custom backends can depend on the base package and their
|
| 64 |
+
own infrastructure plugin without installing the AWS SDK.
|
| 65 |
+
|
| 66 |
+
The wheel contains code, while scorer-side benchmark data can come from either
|
| 67 |
+
a checkout or a checksum-bound bundle:
|
| 68 |
+
|
| 69 |
+
```bash
|
| 70 |
+
autocad-bench bundle-validate --root /path/to/autocad-bench-corpus-v1
|
| 71 |
+
export AUTOCAD_BENCH_ROOT=/path/to/autocad-bench-corpus-v1
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
See [Portable benchmark bundles](docs/benchmark-bundles.md) for the data
|
| 75 |
+
format and [Manual releases](docs/releases.md) for reproducible package and
|
| 76 |
+
corpus publishing.
|
| 77 |
+
|
| 78 |
+
Run the offline tests:
|
| 79 |
+
|
| 80 |
+
```bash
|
| 81 |
+
uv run pytest
|
| 82 |
+
cd apps/dashboard
|
| 83 |
+
npm ci
|
| 84 |
+
npm test
|
| 85 |
+
npm run build
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
## Run a benchmark
|
| 89 |
+
|
| 90 |
+
Copy the AWS example and replace every infrastructure, bucket, and model
|
| 91 |
+
placeholder with resources you own:
|
| 92 |
+
|
| 93 |
+
```bash
|
| 94 |
+
cp configs/aws.example.json configs/my-benchmark.json
|
| 95 |
+
|
| 96 |
+
uv run autocad-bench-batch \
|
| 97 |
+
--config configs/my-benchmark.json \
|
| 98 |
+
--skip-direct-model-check
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
Execute only after provider credentials are exported and preflight passes:
|
| 102 |
+
|
| 103 |
+
```bash
|
| 104 |
+
export OPENAI_API_KEY=...
|
| 105 |
+
|
| 106 |
+
uv run autocad-bench-batch \
|
| 107 |
+
--config configs/my-benchmark.json \
|
| 108 |
+
--output-dir runs/benchmarks/<run-id> \
|
| 109 |
+
--execute
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
Every rollout is evaluated automatically after its final DWG is retrieved and before its sandbox is cleaned up. Evaluation progress is written atomically for the dashboard.
|
| 113 |
+
|
| 114 |
+
## Inspect results
|
| 115 |
+
|
| 116 |
+
```bash
|
| 117 |
+
cd apps/dashboard
|
| 118 |
+
npm ci
|
| 119 |
+
npm run build
|
| 120 |
+
npm run preview
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
Open [http://127.0.0.1:4173](http://127.0.0.1:4173). The viewer reads local `runs/` data and can lazily retrieve missing trace artifacts from the encrypted benchmark bucket when configured.
|
| 124 |
+
|
| 125 |
+
## Repository layout
|
| 126 |
+
|
| 127 |
+
```text
|
| 128 |
+
autoCAD-bench/
|
| 129 |
+
├── tasks/ # Scorer-side manifest, audit inventory, and source corpus
|
| 130 |
+
├── src/autocad_bench/
|
| 131 |
+
│ ├── tasks/ # Task loading, validation, and public task models
|
| 132 |
+
│ ├── harness/ # Agent loop, model calls, and single-run execution
|
| 133 |
+
│ ├── evaluation/ # Trusted DWG evaluation and scoring
|
| 134 |
+
│ ├── infrastructure/ # Backend contract, registry, and built-in AWS plugin
|
| 135 |
+
│ ├── sandbox/ # Broker client and AWS/AutoCAD lifecycle
|
| 136 |
+
│ ├── orchestration/ # Parallel batches, resume, and recovery
|
| 137 |
+
│ └── common/ # Shared utilities
|
| 138 |
+
├── sandbox/windows/ # Windows broker, evaluator, and AMI bootstrap source
|
| 139 |
+
├── infra/cloud-controller/ # Persistent controller deployment assets
|
| 140 |
+
├── configs/ # Bring-your-own infrastructure examples
|
| 141 |
+
├── apps/dashboard/ # Local benchmark registry and trace viewer
|
| 142 |
+
├── artifacts/gold-cache/ # Versioned trusted evaluator outputs
|
| 143 |
+
├── docs/ # Contracts, architecture, operations, and releases
|
| 144 |
+
├── tests/ # Offline regression suite
|
| 145 |
+
└── runs/ # Generated benchmark results; ignored by git
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
## Active runtime contract
|
| 149 |
+
|
| 150 |
+
- Broker: `windows-autocad-2019-v10`
|
| 151 |
+
- Evaluator: `autocad-2019-r23.0.46-v4`
|
| 152 |
+
- Per-task action budget: 5,000
|
| 153 |
+
- Action batch size: bounded only by the remaining rollout budget
|
| 154 |
+
|
| 155 |
+
Worker images are operator-owned and must satisfy the pinned broker contract.
|
| 156 |
+
Broker v11 adds controller-owned DWG save but remains a separate candidate
|
| 157 |
+
runtime. The complete evaluator-v4 cache contains and verifies all 50 source
|
| 158 |
+
DWGs.
|
| 159 |
+
|
| 160 |
+
## Documentation
|
| 161 |
+
|
| 162 |
+
| Guide | Purpose |
|
| 163 |
+
|---|---|
|
| 164 |
+
| [Architecture](docs/architecture.md) | Component boundaries and data flow |
|
| 165 |
+
| [Benchmark contract](docs/benchmark-contract.md) | Model-visible and scorer-side task contract |
|
| 166 |
+
| [Sandbox contract](docs/deterministic-sandbox-contract.md) | AWS lifecycle, admission, and cleanup invariants |
|
| 167 |
+
| [Evaluator contract](docs/autocad-evaluator-contract.md) | DWG inspection and `Layout1` behavior |
|
| 168 |
+
| [Scoring](docs/final-dwg-scorer.md) | Deterministic and vision scoring |
|
| 169 |
+
| [Parallel rollouts](docs/parallel-rollouts.md) | Batch preflight and execution |
|
| 170 |
+
| [Infrastructure plugins](docs/infrastructure-plugins.md) | Bring-your-own worker backend contract |
|
| 171 |
+
| [Benchmark bundles](docs/benchmark-bundles.md) | Portable task and evaluator-data releases |
|
| 172 |
+
| [Manual releases](docs/releases.md) | Reproducible local assembly and verification |
|
| 173 |
+
| [Cloud controller](docs/cloud-controller.md) | Cloud-owned execution and recovery |
|
| 174 |
+
| [Contributing](CONTRIBUTING.md) | Development and validation workflow |
|
| 175 |
+
|
| 176 |
+
## Security boundary
|
| 177 |
+
|
| 178 |
+
`tasks/manifest.jsonl` and the corpus are scorer-side inputs and contain paths
|
| 179 |
+
to gold DWGs that are private from the model session. Model-visible task
|
| 180 |
+
serialization excludes gold paths and DWG names. A rollout receives only its
|
| 181 |
+
reference image and task statement; trusted evaluator exchange uses
|
| 182 |
+
request-scoped presigned S3 objects after model execution.
|
apps/dashboard/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Benchmark dashboard
|
| 2 |
+
|
| 3 |
+
The Vue dashboard is a read-only registry and trace viewer for every local benchmark under `runs/`. It displays normalized rollout outcomes, live evaluation stages, task evidence, model turns, action receipts, usage, cost, and timing.
|
| 4 |
+
|
| 5 |
+
```bash
|
| 6 |
+
npm ci
|
| 7 |
+
npm test
|
| 8 |
+
npm run build
|
| 9 |
+
npm run preview
|
| 10 |
+
```
|
| 11 |
+
|
| 12 |
+
The production viewer binds to `127.0.0.1:4173`. Set
|
| 13 |
+
`AUTOCAD_BENCH_RUNS_ROOT` to inspect another run tree. Optional cloud artifact
|
| 14 |
+
retrieval is disabled unless `AUTOCAD_BENCH_S3_BUCKET` is set. It uses
|
| 15 |
+
`AUTOCAD_BENCH_S3_RUNS_PREFIX`, optional `AWS_PROFILE`, and optional
|
| 16 |
+
`AWS_REGION`; without a named profile, the AWS SDK uses its standard credential
|
| 17 |
+
chain.
|
apps/dashboard/backfill-trace-summaries.mjs
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fs from 'node:fs'
|
| 2 |
+
import path from 'node:path'
|
| 3 |
+
import readline from 'node:readline'
|
| 4 |
+
import { once } from 'node:events'
|
| 5 |
+
import { fileURLToPath } from 'node:url'
|
| 6 |
+
|
| 7 |
+
import { _test } from './trace-api.mjs'
|
| 8 |
+
|
| 9 |
+
const dashboardRoot = path.dirname(fileURLToPath(import.meta.url))
|
| 10 |
+
const targetRoot = path.resolve(
|
| 11 |
+
process.argv[2] || path.join(dashboardRoot, '..', '..', 'runs'),
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
function discover(directory, output = []) {
|
| 15 |
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
| 16 |
+
const candidate = path.join(directory, entry.name)
|
| 17 |
+
if (entry.isDirectory()) discover(candidate, output)
|
| 18 |
+
else if (entry.isFile() && entry.name === 'model_turns.jsonl') output.push(candidate)
|
| 19 |
+
}
|
| 20 |
+
return output
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
async function writeSummary(sourcePath) {
|
| 24 |
+
const destination = path.join(path.dirname(sourcePath), 'model_turn_summaries.jsonl')
|
| 25 |
+
const sourceBefore = fs.statSync(sourcePath)
|
| 26 |
+
try {
|
| 27 |
+
const existing = fs.statSync(destination)
|
| 28 |
+
if (existing.mtimeMs >= sourceBefore.mtimeMs && existing.size > 0) {
|
| 29 |
+
return { sourcePath, destination, status: 'current', turns: null }
|
| 30 |
+
}
|
| 31 |
+
} catch {
|
| 32 |
+
// A missing index is expected on legacy traces.
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
const temporary = `${destination}.tmp`
|
| 36 |
+
const input = fs.createReadStream(sourcePath, { encoding: 'utf8' })
|
| 37 |
+
const lines = readline.createInterface({ input, crlfDelay: Infinity })
|
| 38 |
+
const output = fs.createWriteStream(temporary, { encoding: 'utf8' })
|
| 39 |
+
let turns = 0
|
| 40 |
+
try {
|
| 41 |
+
for await (const line of lines) {
|
| 42 |
+
if (!line.trim()) continue
|
| 43 |
+
let turn
|
| 44 |
+
try {
|
| 45 |
+
turn = JSON.parse(line.replace(/^\uFEFF/, ''))
|
| 46 |
+
} catch {
|
| 47 |
+
continue
|
| 48 |
+
}
|
| 49 |
+
if (!output.write(`${JSON.stringify(_test.compactTurn(turn))}\n`)) {
|
| 50 |
+
await once(output, 'drain')
|
| 51 |
+
}
|
| 52 |
+
turns += 1
|
| 53 |
+
}
|
| 54 |
+
output.end()
|
| 55 |
+
await once(output, 'finish')
|
| 56 |
+
const sourceAfter = fs.statSync(sourcePath)
|
| 57 |
+
if (
|
| 58 |
+
sourceAfter.size !== sourceBefore.size
|
| 59 |
+
|| sourceAfter.mtimeMs !== sourceBefore.mtimeMs
|
| 60 |
+
) {
|
| 61 |
+
fs.rmSync(temporary, { force: true })
|
| 62 |
+
return { sourcePath, destination, status: 'changed_during_index', turns }
|
| 63 |
+
}
|
| 64 |
+
fs.renameSync(temporary, destination)
|
| 65 |
+
return { sourcePath, destination, status: 'indexed', turns }
|
| 66 |
+
} catch (error) {
|
| 67 |
+
output.destroy()
|
| 68 |
+
fs.rmSync(temporary, { force: true })
|
| 69 |
+
throw error
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
const sources = fs.statSync(targetRoot).isFile()
|
| 74 |
+
? [targetRoot]
|
| 75 |
+
: discover(targetRoot)
|
| 76 |
+
|
| 77 |
+
let indexed = 0
|
| 78 |
+
let current = 0
|
| 79 |
+
for (const source of sources) {
|
| 80 |
+
const result = await writeSummary(source)
|
| 81 |
+
if (result.status === 'indexed') indexed += 1
|
| 82 |
+
if (result.status === 'current') current += 1
|
| 83 |
+
process.stdout.write(`${JSON.stringify(result)}\n`)
|
| 84 |
+
}
|
| 85 |
+
process.stdout.write(`${JSON.stringify({ indexed, current, total: sources.length })}\n`)
|
apps/dashboard/cloud-metadata.mjs
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fs from 'node:fs'
|
| 2 |
+
import path from 'node:path'
|
| 3 |
+
|
| 4 |
+
const BENCHMARK_ROLLOUT_METADATA = [
|
| 5 |
+
'run-state.json',
|
| 6 |
+
'result.json',
|
| 7 |
+
'evaluation/status.json',
|
| 8 |
+
'evaluation/score.json',
|
| 9 |
+
'evaluation/vision-judge/score.json',
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
const TRACE_METADATA = [
|
| 13 |
+
'run-state.json',
|
| 14 |
+
'result.json',
|
| 15 |
+
'model_turn_summaries.jsonl',
|
| 16 |
+
'input_receipts.jsonl',
|
| 17 |
+
'evaluation/status.json',
|
| 18 |
+
'evaluation/score.json',
|
| 19 |
+
'evaluation/vision-judge/score.json',
|
| 20 |
+
]
|
| 21 |
+
|
| 22 |
+
export function benchmarkMetadataPaths(state) {
|
| 23 |
+
const rolloutIds = state?.rollouts && typeof state.rollouts === 'object'
|
| 24 |
+
? Object.keys(state.rollouts)
|
| 25 |
+
: []
|
| 26 |
+
return [
|
| 27 |
+
'batch-state.json',
|
| 28 |
+
...rolloutIds.flatMap((rolloutId) => BENCHMARK_ROLLOUT_METADATA.map(
|
| 29 |
+
(relativePath) => `rollouts/${rolloutId}/${relativePath}`,
|
| 30 |
+
)),
|
| 31 |
+
]
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
export function traceMetadataPaths() {
|
| 35 |
+
return [...TRACE_METADATA]
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
function readJson(filePath) {
|
| 39 |
+
try {
|
| 40 |
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
| 41 |
+
} catch {
|
| 42 |
+
return null
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
export function metadataUpdatedAt(filePath) {
|
| 47 |
+
const value = readJson(filePath)
|
| 48 |
+
const timestamp = Date.parse(value?.updated_at || '')
|
| 49 |
+
return Number.isFinite(timestamp) ? timestamp : null
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
export function evaluatorVersionRank(value) {
|
| 53 |
+
const match = String(value || '').match(/-v(\d+)$/i)
|
| 54 |
+
return match ? Number(match[1]) : null
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
function evaluatorVersionForMetadata(filePath) {
|
| 58 |
+
const value = readJson(filePath)
|
| 59 |
+
const direct = value?.evaluator_version || value?.evaluation?.evaluator_version
|
| 60 |
+
if (direct) return direct
|
| 61 |
+
|
| 62 |
+
const marker = `${path.sep}evaluation${path.sep}`
|
| 63 |
+
const markerIndex = filePath.lastIndexOf(marker)
|
| 64 |
+
if (markerIndex < 0) return null
|
| 65 |
+
const evaluationRoot = filePath.slice(0, markerIndex + marker.length - 1)
|
| 66 |
+
for (const name of ['status.json', 'metadata.json']) {
|
| 67 |
+
const sibling = readJson(path.join(evaluationRoot, name))
|
| 68 |
+
if (sibling?.evaluator_version) return sibling.evaluator_version
|
| 69 |
+
}
|
| 70 |
+
return null
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
function rescoreJobForMetadata(filePath) {
|
| 74 |
+
const value = readJson(filePath)
|
| 75 |
+
const direct = value?.rescore_job_id || value?.evaluation?.rescore_job_id
|
| 76 |
+
if (direct) return direct
|
| 77 |
+
|
| 78 |
+
const marker = `${path.sep}evaluation${path.sep}`
|
| 79 |
+
const markerIndex = filePath.lastIndexOf(marker)
|
| 80 |
+
if (markerIndex < 0) return null
|
| 81 |
+
const rolloutRoot = filePath.slice(0, markerIndex)
|
| 82 |
+
return readJson(path.join(rolloutRoot, 'result.json'))?.evaluation
|
| 83 |
+
?.rescore_job_id || null
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
export function shouldPromoteMetadata(source, destination) {
|
| 87 |
+
if (!fs.existsSync(destination)) return true
|
| 88 |
+
if (path.extname(source) === '.jsonl') {
|
| 89 |
+
// Live JSONL traces are append-only. Never let an older S3 snapshot
|
| 90 |
+
// truncate a controller-local trace when the dashboard runs in-cloud.
|
| 91 |
+
return fs.statSync(source).size >= fs.statSync(destination).size
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
const sourceRank = evaluatorVersionRank(evaluatorVersionForMetadata(source))
|
| 95 |
+
const destinationRank = evaluatorVersionRank(
|
| 96 |
+
evaluatorVersionForMetadata(destination),
|
| 97 |
+
)
|
| 98 |
+
if (sourceRank != null && destinationRank != null) {
|
| 99 |
+
if (sourceRank < destinationRank) return false
|
| 100 |
+
if (sourceRank > destinationRank) return true
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
// A completed local rescore is an immutable, job-bound snapshot. The shared
|
| 105 |
+
// mirror can temporarily contain an earlier attempt from the same rescore
|
| 106 |
+
// job, and those files often have no useful top-level updated_at timestamp.
|
| 107 |
+
// Keep the local terminal snapshot when evaluator versions are equal.
|
| 108 |
+
const destinationRescoreJob = rescoreJobForMetadata(destination)
|
| 109 |
+
if (destinationRescoreJob) {
|
| 110 |
+
const sourceRescoreJob = rescoreJobForMetadata(source)
|
| 111 |
+
if (sourceRescoreJob !== destinationRescoreJob) return false
|
| 112 |
+
if (sourceRank == null || destinationRank == null || sourceRank === destinationRank) {
|
| 113 |
+
return false
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
const sourceUpdatedAt = metadataUpdatedAt(source)
|
| 118 |
+
const destinationUpdatedAt = metadataUpdatedAt(destination)
|
| 119 |
+
if (sourceUpdatedAt != null && destinationUpdatedAt != null) {
|
| 120 |
+
return sourceUpdatedAt >= destinationUpdatedAt
|
| 121 |
+
}
|
| 122 |
+
return true
|
| 123 |
+
}
|
apps/dashboard/index.html
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
<meta name="color-scheme" content="dark" />
|
| 7 |
+
<meta name="description" content="Inspect AutoCAD computer-use rollouts and full benchmark suites." />
|
| 8 |
+
<title>AutoCAD Evaluation Console</title>
|
| 9 |
+
</head>
|
| 10 |
+
<body>
|
| 11 |
+
<div id="app"></div>
|
| 12 |
+
<script type="module" src="/src/main.ts"></script>
|
| 13 |
+
</body>
|
| 14 |
+
</html>
|
apps/dashboard/package-lock.json
ADDED
|
@@ -0,0 +1,1910 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "autocad-bench-trace-dashboard",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "autocad-bench-trace-dashboard",
|
| 9 |
+
"version": "0.1.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"@aws-sdk/client-s3": "3.1090.0",
|
| 12 |
+
"@aws-sdk/credential-providers": "3.1090.0",
|
| 13 |
+
"@aws-sdk/s3-request-presigner": "3.1090.0",
|
| 14 |
+
"vue": "3.5.40"
|
| 15 |
+
},
|
| 16 |
+
"devDependencies": {
|
| 17 |
+
"@types/node": "20.19.43",
|
| 18 |
+
"@vitejs/plugin-vue": "5.2.4",
|
| 19 |
+
"typescript": "5.9.3",
|
| 20 |
+
"vite": "6.4.3",
|
| 21 |
+
"vue-tsc": "3.3.7"
|
| 22 |
+
}
|
| 23 |
+
},
|
| 24 |
+
"node_modules/@aws-sdk/checksums": {
|
| 25 |
+
"version": "3.1000.18",
|
| 26 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.18.tgz",
|
| 27 |
+
"integrity": "sha512-IImkbEyXdV6/uaF5r6Wkk+8718mQw1ll83j0a4a30R3JM/rHVFdWAiT4jtJpFjJiIwM/oJ6SxIxr0z2TaQUGqw==",
|
| 28 |
+
"license": "Apache-2.0",
|
| 29 |
+
"dependencies": {
|
| 30 |
+
"@aws-sdk/core": "^3.975.3",
|
| 31 |
+
"@aws-sdk/types": "^3.974.2",
|
| 32 |
+
"@smithy/core": "^3.29.4",
|
| 33 |
+
"@smithy/types": "^4.16.1",
|
| 34 |
+
"tslib": "^2.6.2"
|
| 35 |
+
},
|
| 36 |
+
"engines": {
|
| 37 |
+
"node": ">=20.0.0"
|
| 38 |
+
}
|
| 39 |
+
},
|
| 40 |
+
"node_modules/@aws-sdk/client-s3": {
|
| 41 |
+
"version": "3.1090.0",
|
| 42 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1090.0.tgz",
|
| 43 |
+
"integrity": "sha512-R6GX9cd1jljwzZ8xFmgAI/hHCuX1MobIKBdsymv7WL9SENvO9Vgz9KOR6avTnu0Ao+w1LmxnTe+jqmZXEn7Q/Q==",
|
| 44 |
+
"license": "Apache-2.0",
|
| 45 |
+
"dependencies": {
|
| 46 |
+
"@aws-sdk/checksums": "^3.1000.18",
|
| 47 |
+
"@aws-sdk/core": "^3.975.3",
|
| 48 |
+
"@aws-sdk/credential-provider-node": "^3.972.70",
|
| 49 |
+
"@aws-sdk/middleware-sdk-s3": "^3.972.64",
|
| 50 |
+
"@aws-sdk/signature-v4-multi-region": "^3.996.41",
|
| 51 |
+
"@aws-sdk/types": "^3.974.2",
|
| 52 |
+
"@smithy/core": "^3.29.4",
|
| 53 |
+
"@smithy/fetch-http-handler": "^5.6.6",
|
| 54 |
+
"@smithy/node-http-handler": "^4.9.6",
|
| 55 |
+
"@smithy/types": "^4.16.1",
|
| 56 |
+
"tslib": "^2.6.2"
|
| 57 |
+
},
|
| 58 |
+
"engines": {
|
| 59 |
+
"node": ">=20.0.0"
|
| 60 |
+
}
|
| 61 |
+
},
|
| 62 |
+
"node_modules/@aws-sdk/core": {
|
| 63 |
+
"version": "3.975.3",
|
| 64 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.975.3.tgz",
|
| 65 |
+
"integrity": "sha512-7ur3kCKuvPLqlsZ2XlvnNBVQ7KkpSu6Y6dOTwSPHLrFpTEfZM8isLBJc4cgv96WB7GifeVM436mpycwxBd2vEA==",
|
| 66 |
+
"license": "Apache-2.0",
|
| 67 |
+
"dependencies": {
|
| 68 |
+
"@aws-sdk/types": "^3.974.2",
|
| 69 |
+
"@aws-sdk/xml-builder": "^3.972.36",
|
| 70 |
+
"@aws/lambda-invoke-store": "^0.3.0",
|
| 71 |
+
"@smithy/core": "^3.29.4",
|
| 72 |
+
"@smithy/signature-v4": "^5.6.5",
|
| 73 |
+
"@smithy/types": "^4.16.1",
|
| 74 |
+
"bowser": "^2.11.0",
|
| 75 |
+
"tslib": "^2.6.2"
|
| 76 |
+
},
|
| 77 |
+
"engines": {
|
| 78 |
+
"node": ">=20.0.0"
|
| 79 |
+
}
|
| 80 |
+
},
|
| 81 |
+
"node_modules/@aws-sdk/credential-provider-cognito-identity": {
|
| 82 |
+
"version": "3.972.58",
|
| 83 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.58.tgz",
|
| 84 |
+
"integrity": "sha512-s5uoABv5eOzuH/S+XngHjHSrY8mK0UTBUFs8pm1ynBNuxXmYp176zarDyxN9lUS3Rry0wjzNvJUV09QROaO98g==",
|
| 85 |
+
"license": "Apache-2.0",
|
| 86 |
+
"dependencies": {
|
| 87 |
+
"@aws-sdk/nested-clients": "^3.997.33",
|
| 88 |
+
"@aws-sdk/types": "^3.974.2",
|
| 89 |
+
"@smithy/core": "^3.29.4",
|
| 90 |
+
"@smithy/types": "^4.16.1",
|
| 91 |
+
"tslib": "^2.6.2"
|
| 92 |
+
},
|
| 93 |
+
"engines": {
|
| 94 |
+
"node": ">=20.0.0"
|
| 95 |
+
}
|
| 96 |
+
},
|
| 97 |
+
"node_modules/@aws-sdk/credential-provider-env": {
|
| 98 |
+
"version": "3.972.59",
|
| 99 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.59.tgz",
|
| 100 |
+
"integrity": "sha512-Ny5e4Mfh3QPmiAc0AiUe+cbTXDlxkU3Rc+EpWOfyWeWEy6yp7Fa1KmfNeCc+1a8by9zQ9gtohmiQUkMPScF3ng==",
|
| 101 |
+
"license": "Apache-2.0",
|
| 102 |
+
"dependencies": {
|
| 103 |
+
"@aws-sdk/core": "^3.975.3",
|
| 104 |
+
"@aws-sdk/types": "^3.974.2",
|
| 105 |
+
"@smithy/core": "^3.29.4",
|
| 106 |
+
"@smithy/types": "^4.16.1",
|
| 107 |
+
"tslib": "^2.6.2"
|
| 108 |
+
},
|
| 109 |
+
"engines": {
|
| 110 |
+
"node": ">=20.0.0"
|
| 111 |
+
}
|
| 112 |
+
},
|
| 113 |
+
"node_modules/@aws-sdk/credential-provider-http": {
|
| 114 |
+
"version": "3.972.61",
|
| 115 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.61.tgz",
|
| 116 |
+
"integrity": "sha512-8jAjgStl5Ytq4+HF3X/9f+EmRinaRbGRRtQGktlPfBRVx73H+R1y48vIeXerQtYGFaUqkEp3fT6jP854rVO2yQ==",
|
| 117 |
+
"license": "Apache-2.0",
|
| 118 |
+
"dependencies": {
|
| 119 |
+
"@aws-sdk/core": "^3.975.3",
|
| 120 |
+
"@aws-sdk/types": "^3.974.2",
|
| 121 |
+
"@smithy/core": "^3.29.4",
|
| 122 |
+
"@smithy/fetch-http-handler": "^5.6.6",
|
| 123 |
+
"@smithy/node-http-handler": "^4.9.6",
|
| 124 |
+
"@smithy/types": "^4.16.1",
|
| 125 |
+
"tslib": "^2.6.2"
|
| 126 |
+
},
|
| 127 |
+
"engines": {
|
| 128 |
+
"node": ">=20.0.0"
|
| 129 |
+
}
|
| 130 |
+
},
|
| 131 |
+
"node_modules/@aws-sdk/credential-provider-ini": {
|
| 132 |
+
"version": "3.973.4",
|
| 133 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.4.tgz",
|
| 134 |
+
"integrity": "sha512-e6ZvVsj90aRALf1kHP+J4iqC1496ZpVgqI/+u0LJ5HL7q7ATauGy4gdDvRCP13L1pN/fMiZLah162PGIYkbUVQ==",
|
| 135 |
+
"license": "Apache-2.0",
|
| 136 |
+
"dependencies": {
|
| 137 |
+
"@aws-sdk/core": "^3.975.3",
|
| 138 |
+
"@aws-sdk/credential-provider-env": "^3.972.59",
|
| 139 |
+
"@aws-sdk/credential-provider-http": "^3.972.61",
|
| 140 |
+
"@aws-sdk/credential-provider-login": "^3.972.66",
|
| 141 |
+
"@aws-sdk/credential-provider-process": "^3.972.59",
|
| 142 |
+
"@aws-sdk/credential-provider-sso": "^3.973.3",
|
| 143 |
+
"@aws-sdk/credential-provider-web-identity": "^3.972.65",
|
| 144 |
+
"@aws-sdk/nested-clients": "^3.997.33",
|
| 145 |
+
"@aws-sdk/types": "^3.974.2",
|
| 146 |
+
"@smithy/core": "^3.29.4",
|
| 147 |
+
"@smithy/credential-provider-imds": "^4.4.9",
|
| 148 |
+
"@smithy/types": "^4.16.1",
|
| 149 |
+
"tslib": "^2.6.2"
|
| 150 |
+
},
|
| 151 |
+
"engines": {
|
| 152 |
+
"node": ">=20.0.0"
|
| 153 |
+
}
|
| 154 |
+
},
|
| 155 |
+
"node_modules/@aws-sdk/credential-provider-login": {
|
| 156 |
+
"version": "3.972.66",
|
| 157 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.66.tgz",
|
| 158 |
+
"integrity": "sha512-g2fsqm87r/nKthLZ0VkkDBElkGg0PvSa8d97HQ6EilMbJTZ6hxa8FxkSZyJfgPfFdZn0TTmkOffQmTSUcAHIng==",
|
| 159 |
+
"license": "Apache-2.0",
|
| 160 |
+
"dependencies": {
|
| 161 |
+
"@aws-sdk/core": "^3.975.3",
|
| 162 |
+
"@aws-sdk/nested-clients": "^3.997.33",
|
| 163 |
+
"@aws-sdk/types": "^3.974.2",
|
| 164 |
+
"@smithy/core": "^3.29.4",
|
| 165 |
+
"@smithy/types": "^4.16.1",
|
| 166 |
+
"tslib": "^2.6.2"
|
| 167 |
+
},
|
| 168 |
+
"engines": {
|
| 169 |
+
"node": ">=20.0.0"
|
| 170 |
+
}
|
| 171 |
+
},
|
| 172 |
+
"node_modules/@aws-sdk/credential-provider-node": {
|
| 173 |
+
"version": "3.972.70",
|
| 174 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.70.tgz",
|
| 175 |
+
"integrity": "sha512-3xzvkGdykBunxqh8WudmUpSyLWvIhfI6aBQo1b5rb3mDO5mNLadK+0hiI0qBQBMVynJbfLO+Ajy9dztMwy9O8w==",
|
| 176 |
+
"license": "Apache-2.0",
|
| 177 |
+
"dependencies": {
|
| 178 |
+
"@aws-sdk/credential-provider-env": "^3.972.59",
|
| 179 |
+
"@aws-sdk/credential-provider-http": "^3.972.61",
|
| 180 |
+
"@aws-sdk/credential-provider-ini": "^3.973.4",
|
| 181 |
+
"@aws-sdk/credential-provider-process": "^3.972.59",
|
| 182 |
+
"@aws-sdk/credential-provider-sso": "^3.973.3",
|
| 183 |
+
"@aws-sdk/credential-provider-web-identity": "^3.972.65",
|
| 184 |
+
"@aws-sdk/types": "^3.974.2",
|
| 185 |
+
"@smithy/core": "^3.29.4",
|
| 186 |
+
"@smithy/credential-provider-imds": "^4.4.9",
|
| 187 |
+
"@smithy/types": "^4.16.1",
|
| 188 |
+
"tslib": "^2.6.2"
|
| 189 |
+
},
|
| 190 |
+
"engines": {
|
| 191 |
+
"node": ">=20.0.0"
|
| 192 |
+
}
|
| 193 |
+
},
|
| 194 |
+
"node_modules/@aws-sdk/credential-provider-process": {
|
| 195 |
+
"version": "3.972.59",
|
| 196 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.59.tgz",
|
| 197 |
+
"integrity": "sha512-DlZF2/MhLlatDdlrIy3CUCpfdbLrKx+3SMjVo+WyHnPpwzkc/M3vwAHw4OVJf7DMvO+4vfRqSCMc/E9I1auN0g==",
|
| 198 |
+
"license": "Apache-2.0",
|
| 199 |
+
"dependencies": {
|
| 200 |
+
"@aws-sdk/core": "^3.975.3",
|
| 201 |
+
"@aws-sdk/types": "^3.974.2",
|
| 202 |
+
"@smithy/core": "^3.29.4",
|
| 203 |
+
"@smithy/types": "^4.16.1",
|
| 204 |
+
"tslib": "^2.6.2"
|
| 205 |
+
},
|
| 206 |
+
"engines": {
|
| 207 |
+
"node": ">=20.0.0"
|
| 208 |
+
}
|
| 209 |
+
},
|
| 210 |
+
"node_modules/@aws-sdk/credential-provider-sso": {
|
| 211 |
+
"version": "3.973.3",
|
| 212 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.3.tgz",
|
| 213 |
+
"integrity": "sha512-hmdDHoy2G5Es2e8IgelNMYUuSQI6uCIAKZMJ2u2PdKDhxvbk1uWD/g4+R7R5c/tJfKEB1+KjjWiaoCr/S+ZTiQ==",
|
| 214 |
+
"license": "Apache-2.0",
|
| 215 |
+
"dependencies": {
|
| 216 |
+
"@aws-sdk/core": "^3.975.3",
|
| 217 |
+
"@aws-sdk/nested-clients": "^3.997.33",
|
| 218 |
+
"@aws-sdk/token-providers": "3.1088.0",
|
| 219 |
+
"@aws-sdk/types": "^3.974.2",
|
| 220 |
+
"@smithy/core": "^3.29.4",
|
| 221 |
+
"@smithy/types": "^4.16.1",
|
| 222 |
+
"tslib": "^2.6.2"
|
| 223 |
+
},
|
| 224 |
+
"engines": {
|
| 225 |
+
"node": ">=20.0.0"
|
| 226 |
+
}
|
| 227 |
+
},
|
| 228 |
+
"node_modules/@aws-sdk/credential-provider-web-identity": {
|
| 229 |
+
"version": "3.972.65",
|
| 230 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.65.tgz",
|
| 231 |
+
"integrity": "sha512-gHQb/Kt0chjk/JQDa/GJDqmAvEuVn8n7z10wK2h0LFM9TUDRkohgOO4aEF+s2sBLM0br7Cl5W6P7phgjrrJvLQ==",
|
| 232 |
+
"license": "Apache-2.0",
|
| 233 |
+
"dependencies": {
|
| 234 |
+
"@aws-sdk/core": "^3.975.3",
|
| 235 |
+
"@aws-sdk/nested-clients": "^3.997.33",
|
| 236 |
+
"@aws-sdk/types": "^3.974.2",
|
| 237 |
+
"@smithy/core": "^3.29.4",
|
| 238 |
+
"@smithy/types": "^4.16.1",
|
| 239 |
+
"tslib": "^2.6.2"
|
| 240 |
+
},
|
| 241 |
+
"engines": {
|
| 242 |
+
"node": ">=20.0.0"
|
| 243 |
+
}
|
| 244 |
+
},
|
| 245 |
+
"node_modules/@aws-sdk/credential-providers": {
|
| 246 |
+
"version": "3.1090.0",
|
| 247 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.1090.0.tgz",
|
| 248 |
+
"integrity": "sha512-HEfLv6RdiFu8+HO5CUQjjQ/KCWWAHcQCtSPenaF26lYQSjjzKjgvjwAMGBV9/VMrO8V0faIRyoFWhF548UjTOg==",
|
| 249 |
+
"license": "Apache-2.0",
|
| 250 |
+
"dependencies": {
|
| 251 |
+
"@aws-sdk/core": "^3.975.3",
|
| 252 |
+
"@aws-sdk/credential-provider-cognito-identity": "^3.972.58",
|
| 253 |
+
"@aws-sdk/credential-provider-env": "^3.972.59",
|
| 254 |
+
"@aws-sdk/credential-provider-http": "^3.972.61",
|
| 255 |
+
"@aws-sdk/credential-provider-ini": "^3.973.4",
|
| 256 |
+
"@aws-sdk/credential-provider-login": "^3.972.66",
|
| 257 |
+
"@aws-sdk/credential-provider-node": "^3.972.70",
|
| 258 |
+
"@aws-sdk/credential-provider-process": "^3.972.59",
|
| 259 |
+
"@aws-sdk/credential-provider-sso": "^3.973.3",
|
| 260 |
+
"@aws-sdk/credential-provider-web-identity": "^3.972.65",
|
| 261 |
+
"@aws-sdk/nested-clients": "^3.997.33",
|
| 262 |
+
"@aws-sdk/types": "^3.974.2",
|
| 263 |
+
"@smithy/core": "^3.29.4",
|
| 264 |
+
"@smithy/credential-provider-imds": "^4.4.9",
|
| 265 |
+
"@smithy/types": "^4.16.1",
|
| 266 |
+
"tslib": "^2.6.2"
|
| 267 |
+
},
|
| 268 |
+
"engines": {
|
| 269 |
+
"node": ">=20.0.0"
|
| 270 |
+
}
|
| 271 |
+
},
|
| 272 |
+
"node_modules/@aws-sdk/middleware-sdk-s3": {
|
| 273 |
+
"version": "3.972.64",
|
| 274 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.64.tgz",
|
| 275 |
+
"integrity": "sha512-RBi43anhDBUv+HCfxCOXwGOE7GmT4n7ChV04Mwr22RhXTNcamW/iWnJlOotDPCZSrJ4dEvhZSiWWQMwLX+ZhFA==",
|
| 276 |
+
"license": "Apache-2.0",
|
| 277 |
+
"dependencies": {
|
| 278 |
+
"@aws-sdk/core": "^3.975.3",
|
| 279 |
+
"@aws-sdk/signature-v4-multi-region": "^3.996.41",
|
| 280 |
+
"@aws-sdk/types": "^3.974.2",
|
| 281 |
+
"@smithy/core": "^3.29.4",
|
| 282 |
+
"@smithy/types": "^4.16.1",
|
| 283 |
+
"tslib": "^2.6.2"
|
| 284 |
+
},
|
| 285 |
+
"engines": {
|
| 286 |
+
"node": ">=20.0.0"
|
| 287 |
+
}
|
| 288 |
+
},
|
| 289 |
+
"node_modules/@aws-sdk/nested-clients": {
|
| 290 |
+
"version": "3.997.33",
|
| 291 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.33.tgz",
|
| 292 |
+
"integrity": "sha512-dVZOroI/r3/ENvqNGgjMPul+jjlz9GddfVusgTXlVjfZj5isibOxecLkGQbRPp8XOuX+RAfjXLFgPkD1JS5xrw==",
|
| 293 |
+
"license": "Apache-2.0",
|
| 294 |
+
"dependencies": {
|
| 295 |
+
"@aws-sdk/core": "^3.975.3",
|
| 296 |
+
"@aws-sdk/signature-v4-multi-region": "^3.996.41",
|
| 297 |
+
"@aws-sdk/types": "^3.974.2",
|
| 298 |
+
"@smithy/core": "^3.29.4",
|
| 299 |
+
"@smithy/fetch-http-handler": "^5.6.6",
|
| 300 |
+
"@smithy/node-http-handler": "^4.9.6",
|
| 301 |
+
"@smithy/types": "^4.16.1",
|
| 302 |
+
"tslib": "^2.6.2"
|
| 303 |
+
},
|
| 304 |
+
"engines": {
|
| 305 |
+
"node": ">=20.0.0"
|
| 306 |
+
}
|
| 307 |
+
},
|
| 308 |
+
"node_modules/@aws-sdk/s3-request-presigner": {
|
| 309 |
+
"version": "3.1090.0",
|
| 310 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.1090.0.tgz",
|
| 311 |
+
"integrity": "sha512-DaYXp8GMptWJUDbvpoB51xZztOw6tcbpXjYAZMXTu17E0aR+5g8FWBU3t3t0KEhhrUiKyfLpVi6uNQxNSXfbhg==",
|
| 312 |
+
"license": "Apache-2.0",
|
| 313 |
+
"dependencies": {
|
| 314 |
+
"@aws-sdk/core": "^3.975.3",
|
| 315 |
+
"@aws-sdk/signature-v4-multi-region": "^3.996.41",
|
| 316 |
+
"@aws-sdk/types": "^3.974.2",
|
| 317 |
+
"@smithy/core": "^3.29.4",
|
| 318 |
+
"@smithy/types": "^4.16.1",
|
| 319 |
+
"tslib": "^2.6.2"
|
| 320 |
+
},
|
| 321 |
+
"engines": {
|
| 322 |
+
"node": ">=20.0.0"
|
| 323 |
+
}
|
| 324 |
+
},
|
| 325 |
+
"node_modules/@aws-sdk/signature-v4-multi-region": {
|
| 326 |
+
"version": "3.996.41",
|
| 327 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.41.tgz",
|
| 328 |
+
"integrity": "sha512-QMUytg+FQMGouc8gHS00KoYih3+N6cqmVI/pQGOIo7Nr7OpQaiXjSYOuL+vsPZ1tymY4LAQ8MYcHJmws5LRxng==",
|
| 329 |
+
"license": "Apache-2.0",
|
| 330 |
+
"dependencies": {
|
| 331 |
+
"@aws-sdk/types": "^3.974.2",
|
| 332 |
+
"@smithy/signature-v4": "^5.6.5",
|
| 333 |
+
"@smithy/types": "^4.16.1",
|
| 334 |
+
"tslib": "^2.6.2"
|
| 335 |
+
},
|
| 336 |
+
"engines": {
|
| 337 |
+
"node": ">=20.0.0"
|
| 338 |
+
}
|
| 339 |
+
},
|
| 340 |
+
"node_modules/@aws-sdk/token-providers": {
|
| 341 |
+
"version": "3.1088.0",
|
| 342 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1088.0.tgz",
|
| 343 |
+
"integrity": "sha512-4ObatWt2qpJg5FBk4LOOKrTQYzaqeewAtdO3r9ZO8lH9YqLtpTzLyIdy0mJ+nVdfYOnqISkKNfmzP22bNDhwyw==",
|
| 344 |
+
"license": "Apache-2.0",
|
| 345 |
+
"dependencies": {
|
| 346 |
+
"@aws-sdk/core": "^3.975.3",
|
| 347 |
+
"@aws-sdk/nested-clients": "^3.997.33",
|
| 348 |
+
"@aws-sdk/types": "^3.974.2",
|
| 349 |
+
"@smithy/core": "^3.29.4",
|
| 350 |
+
"@smithy/types": "^4.16.1",
|
| 351 |
+
"tslib": "^2.6.2"
|
| 352 |
+
},
|
| 353 |
+
"engines": {
|
| 354 |
+
"node": ">=20.0.0"
|
| 355 |
+
}
|
| 356 |
+
},
|
| 357 |
+
"node_modules/@aws-sdk/types": {
|
| 358 |
+
"version": "3.974.2",
|
| 359 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.2.tgz",
|
| 360 |
+
"integrity": "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==",
|
| 361 |
+
"license": "Apache-2.0",
|
| 362 |
+
"dependencies": {
|
| 363 |
+
"@smithy/types": "^4.16.1",
|
| 364 |
+
"tslib": "^2.6.2"
|
| 365 |
+
},
|
| 366 |
+
"engines": {
|
| 367 |
+
"node": ">=20.0.0"
|
| 368 |
+
}
|
| 369 |
+
},
|
| 370 |
+
"node_modules/@aws-sdk/xml-builder": {
|
| 371 |
+
"version": "3.972.36",
|
| 372 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.36.tgz",
|
| 373 |
+
"integrity": "sha512-RdGmS1GLrtaTOLE1ElSluMldNrpk9Emq6uYs8SS8iHlu5xTAmM9rRkM91o48+rIRryBtyO9t+uLYCoMG6jVMVA==",
|
| 374 |
+
"license": "Apache-2.0",
|
| 375 |
+
"dependencies": {
|
| 376 |
+
"@smithy/types": "^4.16.1",
|
| 377 |
+
"tslib": "^2.6.2"
|
| 378 |
+
},
|
| 379 |
+
"engines": {
|
| 380 |
+
"node": ">=20.0.0"
|
| 381 |
+
}
|
| 382 |
+
},
|
| 383 |
+
"node_modules/@aws/lambda-invoke-store": {
|
| 384 |
+
"version": "0.3.0",
|
| 385 |
+
"resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz",
|
| 386 |
+
"integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==",
|
| 387 |
+
"license": "Apache-2.0",
|
| 388 |
+
"engines": {
|
| 389 |
+
"node": ">=18.0.0"
|
| 390 |
+
}
|
| 391 |
+
},
|
| 392 |
+
"node_modules/@babel/helper-string-parser": {
|
| 393 |
+
"version": "7.29.7",
|
| 394 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
| 395 |
+
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
|
| 396 |
+
"license": "MIT",
|
| 397 |
+
"engines": {
|
| 398 |
+
"node": ">=6.9.0"
|
| 399 |
+
}
|
| 400 |
+
},
|
| 401 |
+
"node_modules/@babel/helper-validator-identifier": {
|
| 402 |
+
"version": "7.29.7",
|
| 403 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
| 404 |
+
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
| 405 |
+
"license": "MIT",
|
| 406 |
+
"engines": {
|
| 407 |
+
"node": ">=6.9.0"
|
| 408 |
+
}
|
| 409 |
+
},
|
| 410 |
+
"node_modules/@babel/parser": {
|
| 411 |
+
"version": "7.29.7",
|
| 412 |
+
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
|
| 413 |
+
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
|
| 414 |
+
"license": "MIT",
|
| 415 |
+
"dependencies": {
|
| 416 |
+
"@babel/types": "^7.29.7"
|
| 417 |
+
},
|
| 418 |
+
"bin": {
|
| 419 |
+
"parser": "bin/babel-parser.js"
|
| 420 |
+
},
|
| 421 |
+
"engines": {
|
| 422 |
+
"node": ">=6.0.0"
|
| 423 |
+
}
|
| 424 |
+
},
|
| 425 |
+
"node_modules/@babel/types": {
|
| 426 |
+
"version": "7.29.7",
|
| 427 |
+
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
|
| 428 |
+
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
|
| 429 |
+
"license": "MIT",
|
| 430 |
+
"dependencies": {
|
| 431 |
+
"@babel/helper-string-parser": "^7.29.7",
|
| 432 |
+
"@babel/helper-validator-identifier": "^7.29.7"
|
| 433 |
+
},
|
| 434 |
+
"engines": {
|
| 435 |
+
"node": ">=6.9.0"
|
| 436 |
+
}
|
| 437 |
+
},
|
| 438 |
+
"node_modules/@esbuild/aix-ppc64": {
|
| 439 |
+
"version": "0.25.12",
|
| 440 |
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
| 441 |
+
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
|
| 442 |
+
"cpu": [
|
| 443 |
+
"ppc64"
|
| 444 |
+
],
|
| 445 |
+
"dev": true,
|
| 446 |
+
"license": "MIT",
|
| 447 |
+
"optional": true,
|
| 448 |
+
"os": [
|
| 449 |
+
"aix"
|
| 450 |
+
],
|
| 451 |
+
"engines": {
|
| 452 |
+
"node": ">=18"
|
| 453 |
+
}
|
| 454 |
+
},
|
| 455 |
+
"node_modules/@esbuild/android-arm": {
|
| 456 |
+
"version": "0.25.12",
|
| 457 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
|
| 458 |
+
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
|
| 459 |
+
"cpu": [
|
| 460 |
+
"arm"
|
| 461 |
+
],
|
| 462 |
+
"dev": true,
|
| 463 |
+
"license": "MIT",
|
| 464 |
+
"optional": true,
|
| 465 |
+
"os": [
|
| 466 |
+
"android"
|
| 467 |
+
],
|
| 468 |
+
"engines": {
|
| 469 |
+
"node": ">=18"
|
| 470 |
+
}
|
| 471 |
+
},
|
| 472 |
+
"node_modules/@esbuild/android-arm64": {
|
| 473 |
+
"version": "0.25.12",
|
| 474 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
|
| 475 |
+
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
|
| 476 |
+
"cpu": [
|
| 477 |
+
"arm64"
|
| 478 |
+
],
|
| 479 |
+
"dev": true,
|
| 480 |
+
"license": "MIT",
|
| 481 |
+
"optional": true,
|
| 482 |
+
"os": [
|
| 483 |
+
"android"
|
| 484 |
+
],
|
| 485 |
+
"engines": {
|
| 486 |
+
"node": ">=18"
|
| 487 |
+
}
|
| 488 |
+
},
|
| 489 |
+
"node_modules/@esbuild/android-x64": {
|
| 490 |
+
"version": "0.25.12",
|
| 491 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
|
| 492 |
+
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
|
| 493 |
+
"cpu": [
|
| 494 |
+
"x64"
|
| 495 |
+
],
|
| 496 |
+
"dev": true,
|
| 497 |
+
"license": "MIT",
|
| 498 |
+
"optional": true,
|
| 499 |
+
"os": [
|
| 500 |
+
"android"
|
| 501 |
+
],
|
| 502 |
+
"engines": {
|
| 503 |
+
"node": ">=18"
|
| 504 |
+
}
|
| 505 |
+
},
|
| 506 |
+
"node_modules/@esbuild/darwin-arm64": {
|
| 507 |
+
"version": "0.25.12",
|
| 508 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
|
| 509 |
+
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
|
| 510 |
+
"cpu": [
|
| 511 |
+
"arm64"
|
| 512 |
+
],
|
| 513 |
+
"dev": true,
|
| 514 |
+
"license": "MIT",
|
| 515 |
+
"optional": true,
|
| 516 |
+
"os": [
|
| 517 |
+
"darwin"
|
| 518 |
+
],
|
| 519 |
+
"engines": {
|
| 520 |
+
"node": ">=18"
|
| 521 |
+
}
|
| 522 |
+
},
|
| 523 |
+
"node_modules/@esbuild/darwin-x64": {
|
| 524 |
+
"version": "0.25.12",
|
| 525 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
|
| 526 |
+
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
|
| 527 |
+
"cpu": [
|
| 528 |
+
"x64"
|
| 529 |
+
],
|
| 530 |
+
"dev": true,
|
| 531 |
+
"license": "MIT",
|
| 532 |
+
"optional": true,
|
| 533 |
+
"os": [
|
| 534 |
+
"darwin"
|
| 535 |
+
],
|
| 536 |
+
"engines": {
|
| 537 |
+
"node": ">=18"
|
| 538 |
+
}
|
| 539 |
+
},
|
| 540 |
+
"node_modules/@esbuild/freebsd-arm64": {
|
| 541 |
+
"version": "0.25.12",
|
| 542 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
|
| 543 |
+
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
|
| 544 |
+
"cpu": [
|
| 545 |
+
"arm64"
|
| 546 |
+
],
|
| 547 |
+
"dev": true,
|
| 548 |
+
"license": "MIT",
|
| 549 |
+
"optional": true,
|
| 550 |
+
"os": [
|
| 551 |
+
"freebsd"
|
| 552 |
+
],
|
| 553 |
+
"engines": {
|
| 554 |
+
"node": ">=18"
|
| 555 |
+
}
|
| 556 |
+
},
|
| 557 |
+
"node_modules/@esbuild/freebsd-x64": {
|
| 558 |
+
"version": "0.25.12",
|
| 559 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
|
| 560 |
+
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
|
| 561 |
+
"cpu": [
|
| 562 |
+
"x64"
|
| 563 |
+
],
|
| 564 |
+
"dev": true,
|
| 565 |
+
"license": "MIT",
|
| 566 |
+
"optional": true,
|
| 567 |
+
"os": [
|
| 568 |
+
"freebsd"
|
| 569 |
+
],
|
| 570 |
+
"engines": {
|
| 571 |
+
"node": ">=18"
|
| 572 |
+
}
|
| 573 |
+
},
|
| 574 |
+
"node_modules/@esbuild/linux-arm": {
|
| 575 |
+
"version": "0.25.12",
|
| 576 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
|
| 577 |
+
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
|
| 578 |
+
"cpu": [
|
| 579 |
+
"arm"
|
| 580 |
+
],
|
| 581 |
+
"dev": true,
|
| 582 |
+
"license": "MIT",
|
| 583 |
+
"optional": true,
|
| 584 |
+
"os": [
|
| 585 |
+
"linux"
|
| 586 |
+
],
|
| 587 |
+
"engines": {
|
| 588 |
+
"node": ">=18"
|
| 589 |
+
}
|
| 590 |
+
},
|
| 591 |
+
"node_modules/@esbuild/linux-arm64": {
|
| 592 |
+
"version": "0.25.12",
|
| 593 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
|
| 594 |
+
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
|
| 595 |
+
"cpu": [
|
| 596 |
+
"arm64"
|
| 597 |
+
],
|
| 598 |
+
"dev": true,
|
| 599 |
+
"license": "MIT",
|
| 600 |
+
"optional": true,
|
| 601 |
+
"os": [
|
| 602 |
+
"linux"
|
| 603 |
+
],
|
| 604 |
+
"engines": {
|
| 605 |
+
"node": ">=18"
|
| 606 |
+
}
|
| 607 |
+
},
|
| 608 |
+
"node_modules/@esbuild/linux-ia32": {
|
| 609 |
+
"version": "0.25.12",
|
| 610 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
|
| 611 |
+
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
|
| 612 |
+
"cpu": [
|
| 613 |
+
"ia32"
|
| 614 |
+
],
|
| 615 |
+
"dev": true,
|
| 616 |
+
"license": "MIT",
|
| 617 |
+
"optional": true,
|
| 618 |
+
"os": [
|
| 619 |
+
"linux"
|
| 620 |
+
],
|
| 621 |
+
"engines": {
|
| 622 |
+
"node": ">=18"
|
| 623 |
+
}
|
| 624 |
+
},
|
| 625 |
+
"node_modules/@esbuild/linux-loong64": {
|
| 626 |
+
"version": "0.25.12",
|
| 627 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
|
| 628 |
+
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
|
| 629 |
+
"cpu": [
|
| 630 |
+
"loong64"
|
| 631 |
+
],
|
| 632 |
+
"dev": true,
|
| 633 |
+
"license": "MIT",
|
| 634 |
+
"optional": true,
|
| 635 |
+
"os": [
|
| 636 |
+
"linux"
|
| 637 |
+
],
|
| 638 |
+
"engines": {
|
| 639 |
+
"node": ">=18"
|
| 640 |
+
}
|
| 641 |
+
},
|
| 642 |
+
"node_modules/@esbuild/linux-mips64el": {
|
| 643 |
+
"version": "0.25.12",
|
| 644 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
|
| 645 |
+
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
|
| 646 |
+
"cpu": [
|
| 647 |
+
"mips64el"
|
| 648 |
+
],
|
| 649 |
+
"dev": true,
|
| 650 |
+
"license": "MIT",
|
| 651 |
+
"optional": true,
|
| 652 |
+
"os": [
|
| 653 |
+
"linux"
|
| 654 |
+
],
|
| 655 |
+
"engines": {
|
| 656 |
+
"node": ">=18"
|
| 657 |
+
}
|
| 658 |
+
},
|
| 659 |
+
"node_modules/@esbuild/linux-ppc64": {
|
| 660 |
+
"version": "0.25.12",
|
| 661 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
|
| 662 |
+
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
|
| 663 |
+
"cpu": [
|
| 664 |
+
"ppc64"
|
| 665 |
+
],
|
| 666 |
+
"dev": true,
|
| 667 |
+
"license": "MIT",
|
| 668 |
+
"optional": true,
|
| 669 |
+
"os": [
|
| 670 |
+
"linux"
|
| 671 |
+
],
|
| 672 |
+
"engines": {
|
| 673 |
+
"node": ">=18"
|
| 674 |
+
}
|
| 675 |
+
},
|
| 676 |
+
"node_modules/@esbuild/linux-riscv64": {
|
| 677 |
+
"version": "0.25.12",
|
| 678 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
|
| 679 |
+
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
|
| 680 |
+
"cpu": [
|
| 681 |
+
"riscv64"
|
| 682 |
+
],
|
| 683 |
+
"dev": true,
|
| 684 |
+
"license": "MIT",
|
| 685 |
+
"optional": true,
|
| 686 |
+
"os": [
|
| 687 |
+
"linux"
|
| 688 |
+
],
|
| 689 |
+
"engines": {
|
| 690 |
+
"node": ">=18"
|
| 691 |
+
}
|
| 692 |
+
},
|
| 693 |
+
"node_modules/@esbuild/linux-s390x": {
|
| 694 |
+
"version": "0.25.12",
|
| 695 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
|
| 696 |
+
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
|
| 697 |
+
"cpu": [
|
| 698 |
+
"s390x"
|
| 699 |
+
],
|
| 700 |
+
"dev": true,
|
| 701 |
+
"license": "MIT",
|
| 702 |
+
"optional": true,
|
| 703 |
+
"os": [
|
| 704 |
+
"linux"
|
| 705 |
+
],
|
| 706 |
+
"engines": {
|
| 707 |
+
"node": ">=18"
|
| 708 |
+
}
|
| 709 |
+
},
|
| 710 |
+
"node_modules/@esbuild/linux-x64": {
|
| 711 |
+
"version": "0.25.12",
|
| 712 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
|
| 713 |
+
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
|
| 714 |
+
"cpu": [
|
| 715 |
+
"x64"
|
| 716 |
+
],
|
| 717 |
+
"dev": true,
|
| 718 |
+
"license": "MIT",
|
| 719 |
+
"optional": true,
|
| 720 |
+
"os": [
|
| 721 |
+
"linux"
|
| 722 |
+
],
|
| 723 |
+
"engines": {
|
| 724 |
+
"node": ">=18"
|
| 725 |
+
}
|
| 726 |
+
},
|
| 727 |
+
"node_modules/@esbuild/netbsd-arm64": {
|
| 728 |
+
"version": "0.25.12",
|
| 729 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
|
| 730 |
+
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
|
| 731 |
+
"cpu": [
|
| 732 |
+
"arm64"
|
| 733 |
+
],
|
| 734 |
+
"dev": true,
|
| 735 |
+
"license": "MIT",
|
| 736 |
+
"optional": true,
|
| 737 |
+
"os": [
|
| 738 |
+
"netbsd"
|
| 739 |
+
],
|
| 740 |
+
"engines": {
|
| 741 |
+
"node": ">=18"
|
| 742 |
+
}
|
| 743 |
+
},
|
| 744 |
+
"node_modules/@esbuild/netbsd-x64": {
|
| 745 |
+
"version": "0.25.12",
|
| 746 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
|
| 747 |
+
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
|
| 748 |
+
"cpu": [
|
| 749 |
+
"x64"
|
| 750 |
+
],
|
| 751 |
+
"dev": true,
|
| 752 |
+
"license": "MIT",
|
| 753 |
+
"optional": true,
|
| 754 |
+
"os": [
|
| 755 |
+
"netbsd"
|
| 756 |
+
],
|
| 757 |
+
"engines": {
|
| 758 |
+
"node": ">=18"
|
| 759 |
+
}
|
| 760 |
+
},
|
| 761 |
+
"node_modules/@esbuild/openbsd-arm64": {
|
| 762 |
+
"version": "0.25.12",
|
| 763 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
|
| 764 |
+
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
|
| 765 |
+
"cpu": [
|
| 766 |
+
"arm64"
|
| 767 |
+
],
|
| 768 |
+
"dev": true,
|
| 769 |
+
"license": "MIT",
|
| 770 |
+
"optional": true,
|
| 771 |
+
"os": [
|
| 772 |
+
"openbsd"
|
| 773 |
+
],
|
| 774 |
+
"engines": {
|
| 775 |
+
"node": ">=18"
|
| 776 |
+
}
|
| 777 |
+
},
|
| 778 |
+
"node_modules/@esbuild/openbsd-x64": {
|
| 779 |
+
"version": "0.25.12",
|
| 780 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
|
| 781 |
+
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
|
| 782 |
+
"cpu": [
|
| 783 |
+
"x64"
|
| 784 |
+
],
|
| 785 |
+
"dev": true,
|
| 786 |
+
"license": "MIT",
|
| 787 |
+
"optional": true,
|
| 788 |
+
"os": [
|
| 789 |
+
"openbsd"
|
| 790 |
+
],
|
| 791 |
+
"engines": {
|
| 792 |
+
"node": ">=18"
|
| 793 |
+
}
|
| 794 |
+
},
|
| 795 |
+
"node_modules/@esbuild/openharmony-arm64": {
|
| 796 |
+
"version": "0.25.12",
|
| 797 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
|
| 798 |
+
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
|
| 799 |
+
"cpu": [
|
| 800 |
+
"arm64"
|
| 801 |
+
],
|
| 802 |
+
"dev": true,
|
| 803 |
+
"license": "MIT",
|
| 804 |
+
"optional": true,
|
| 805 |
+
"os": [
|
| 806 |
+
"openharmony"
|
| 807 |
+
],
|
| 808 |
+
"engines": {
|
| 809 |
+
"node": ">=18"
|
| 810 |
+
}
|
| 811 |
+
},
|
| 812 |
+
"node_modules/@esbuild/sunos-x64": {
|
| 813 |
+
"version": "0.25.12",
|
| 814 |
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
|
| 815 |
+
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
|
| 816 |
+
"cpu": [
|
| 817 |
+
"x64"
|
| 818 |
+
],
|
| 819 |
+
"dev": true,
|
| 820 |
+
"license": "MIT",
|
| 821 |
+
"optional": true,
|
| 822 |
+
"os": [
|
| 823 |
+
"sunos"
|
| 824 |
+
],
|
| 825 |
+
"engines": {
|
| 826 |
+
"node": ">=18"
|
| 827 |
+
}
|
| 828 |
+
},
|
| 829 |
+
"node_modules/@esbuild/win32-arm64": {
|
| 830 |
+
"version": "0.25.12",
|
| 831 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
|
| 832 |
+
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
|
| 833 |
+
"cpu": [
|
| 834 |
+
"arm64"
|
| 835 |
+
],
|
| 836 |
+
"dev": true,
|
| 837 |
+
"license": "MIT",
|
| 838 |
+
"optional": true,
|
| 839 |
+
"os": [
|
| 840 |
+
"win32"
|
| 841 |
+
],
|
| 842 |
+
"engines": {
|
| 843 |
+
"node": ">=18"
|
| 844 |
+
}
|
| 845 |
+
},
|
| 846 |
+
"node_modules/@esbuild/win32-ia32": {
|
| 847 |
+
"version": "0.25.12",
|
| 848 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
|
| 849 |
+
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
|
| 850 |
+
"cpu": [
|
| 851 |
+
"ia32"
|
| 852 |
+
],
|
| 853 |
+
"dev": true,
|
| 854 |
+
"license": "MIT",
|
| 855 |
+
"optional": true,
|
| 856 |
+
"os": [
|
| 857 |
+
"win32"
|
| 858 |
+
],
|
| 859 |
+
"engines": {
|
| 860 |
+
"node": ">=18"
|
| 861 |
+
}
|
| 862 |
+
},
|
| 863 |
+
"node_modules/@esbuild/win32-x64": {
|
| 864 |
+
"version": "0.25.12",
|
| 865 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
|
| 866 |
+
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
|
| 867 |
+
"cpu": [
|
| 868 |
+
"x64"
|
| 869 |
+
],
|
| 870 |
+
"dev": true,
|
| 871 |
+
"license": "MIT",
|
| 872 |
+
"optional": true,
|
| 873 |
+
"os": [
|
| 874 |
+
"win32"
|
| 875 |
+
],
|
| 876 |
+
"engines": {
|
| 877 |
+
"node": ">=18"
|
| 878 |
+
}
|
| 879 |
+
},
|
| 880 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
| 881 |
+
"version": "1.5.5",
|
| 882 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 883 |
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
| 884 |
+
"license": "MIT"
|
| 885 |
+
},
|
| 886 |
+
"node_modules/@rollup/rollup-android-arm-eabi": {
|
| 887 |
+
"version": "4.62.2",
|
| 888 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
|
| 889 |
+
"integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
|
| 890 |
+
"cpu": [
|
| 891 |
+
"arm"
|
| 892 |
+
],
|
| 893 |
+
"dev": true,
|
| 894 |
+
"license": "MIT",
|
| 895 |
+
"optional": true,
|
| 896 |
+
"os": [
|
| 897 |
+
"android"
|
| 898 |
+
]
|
| 899 |
+
},
|
| 900 |
+
"node_modules/@rollup/rollup-android-arm64": {
|
| 901 |
+
"version": "4.62.2",
|
| 902 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
|
| 903 |
+
"integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
|
| 904 |
+
"cpu": [
|
| 905 |
+
"arm64"
|
| 906 |
+
],
|
| 907 |
+
"dev": true,
|
| 908 |
+
"license": "MIT",
|
| 909 |
+
"optional": true,
|
| 910 |
+
"os": [
|
| 911 |
+
"android"
|
| 912 |
+
]
|
| 913 |
+
},
|
| 914 |
+
"node_modules/@rollup/rollup-darwin-arm64": {
|
| 915 |
+
"version": "4.62.2",
|
| 916 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
|
| 917 |
+
"integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
|
| 918 |
+
"cpu": [
|
| 919 |
+
"arm64"
|
| 920 |
+
],
|
| 921 |
+
"dev": true,
|
| 922 |
+
"license": "MIT",
|
| 923 |
+
"optional": true,
|
| 924 |
+
"os": [
|
| 925 |
+
"darwin"
|
| 926 |
+
]
|
| 927 |
+
},
|
| 928 |
+
"node_modules/@rollup/rollup-darwin-x64": {
|
| 929 |
+
"version": "4.62.2",
|
| 930 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
|
| 931 |
+
"integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
|
| 932 |
+
"cpu": [
|
| 933 |
+
"x64"
|
| 934 |
+
],
|
| 935 |
+
"dev": true,
|
| 936 |
+
"license": "MIT",
|
| 937 |
+
"optional": true,
|
| 938 |
+
"os": [
|
| 939 |
+
"darwin"
|
| 940 |
+
]
|
| 941 |
+
},
|
| 942 |
+
"node_modules/@rollup/rollup-freebsd-arm64": {
|
| 943 |
+
"version": "4.62.2",
|
| 944 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
|
| 945 |
+
"integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
|
| 946 |
+
"cpu": [
|
| 947 |
+
"arm64"
|
| 948 |
+
],
|
| 949 |
+
"dev": true,
|
| 950 |
+
"license": "MIT",
|
| 951 |
+
"optional": true,
|
| 952 |
+
"os": [
|
| 953 |
+
"freebsd"
|
| 954 |
+
]
|
| 955 |
+
},
|
| 956 |
+
"node_modules/@rollup/rollup-freebsd-x64": {
|
| 957 |
+
"version": "4.62.2",
|
| 958 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
|
| 959 |
+
"integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
|
| 960 |
+
"cpu": [
|
| 961 |
+
"x64"
|
| 962 |
+
],
|
| 963 |
+
"dev": true,
|
| 964 |
+
"license": "MIT",
|
| 965 |
+
"optional": true,
|
| 966 |
+
"os": [
|
| 967 |
+
"freebsd"
|
| 968 |
+
]
|
| 969 |
+
},
|
| 970 |
+
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
| 971 |
+
"version": "4.62.2",
|
| 972 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
|
| 973 |
+
"integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
|
| 974 |
+
"cpu": [
|
| 975 |
+
"arm"
|
| 976 |
+
],
|
| 977 |
+
"dev": true,
|
| 978 |
+
"license": "MIT",
|
| 979 |
+
"optional": true,
|
| 980 |
+
"os": [
|
| 981 |
+
"linux"
|
| 982 |
+
]
|
| 983 |
+
},
|
| 984 |
+
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
| 985 |
+
"version": "4.62.2",
|
| 986 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
|
| 987 |
+
"integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
|
| 988 |
+
"cpu": [
|
| 989 |
+
"arm"
|
| 990 |
+
],
|
| 991 |
+
"dev": true,
|
| 992 |
+
"license": "MIT",
|
| 993 |
+
"optional": true,
|
| 994 |
+
"os": [
|
| 995 |
+
"linux"
|
| 996 |
+
]
|
| 997 |
+
},
|
| 998 |
+
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
| 999 |
+
"version": "4.62.2",
|
| 1000 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
|
| 1001 |
+
"integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
|
| 1002 |
+
"cpu": [
|
| 1003 |
+
"arm64"
|
| 1004 |
+
],
|
| 1005 |
+
"dev": true,
|
| 1006 |
+
"license": "MIT",
|
| 1007 |
+
"optional": true,
|
| 1008 |
+
"os": [
|
| 1009 |
+
"linux"
|
| 1010 |
+
]
|
| 1011 |
+
},
|
| 1012 |
+
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
| 1013 |
+
"version": "4.62.2",
|
| 1014 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
|
| 1015 |
+
"integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
|
| 1016 |
+
"cpu": [
|
| 1017 |
+
"arm64"
|
| 1018 |
+
],
|
| 1019 |
+
"dev": true,
|
| 1020 |
+
"license": "MIT",
|
| 1021 |
+
"optional": true,
|
| 1022 |
+
"os": [
|
| 1023 |
+
"linux"
|
| 1024 |
+
]
|
| 1025 |
+
},
|
| 1026 |
+
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
| 1027 |
+
"version": "4.62.2",
|
| 1028 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
|
| 1029 |
+
"integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
|
| 1030 |
+
"cpu": [
|
| 1031 |
+
"loong64"
|
| 1032 |
+
],
|
| 1033 |
+
"dev": true,
|
| 1034 |
+
"license": "MIT",
|
| 1035 |
+
"optional": true,
|
| 1036 |
+
"os": [
|
| 1037 |
+
"linux"
|
| 1038 |
+
]
|
| 1039 |
+
},
|
| 1040 |
+
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
| 1041 |
+
"version": "4.62.2",
|
| 1042 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
|
| 1043 |
+
"integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
|
| 1044 |
+
"cpu": [
|
| 1045 |
+
"loong64"
|
| 1046 |
+
],
|
| 1047 |
+
"dev": true,
|
| 1048 |
+
"license": "MIT",
|
| 1049 |
+
"optional": true,
|
| 1050 |
+
"os": [
|
| 1051 |
+
"linux"
|
| 1052 |
+
]
|
| 1053 |
+
},
|
| 1054 |
+
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
| 1055 |
+
"version": "4.62.2",
|
| 1056 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
|
| 1057 |
+
"integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
|
| 1058 |
+
"cpu": [
|
| 1059 |
+
"ppc64"
|
| 1060 |
+
],
|
| 1061 |
+
"dev": true,
|
| 1062 |
+
"license": "MIT",
|
| 1063 |
+
"optional": true,
|
| 1064 |
+
"os": [
|
| 1065 |
+
"linux"
|
| 1066 |
+
]
|
| 1067 |
+
},
|
| 1068 |
+
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
| 1069 |
+
"version": "4.62.2",
|
| 1070 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
|
| 1071 |
+
"integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
|
| 1072 |
+
"cpu": [
|
| 1073 |
+
"ppc64"
|
| 1074 |
+
],
|
| 1075 |
+
"dev": true,
|
| 1076 |
+
"license": "MIT",
|
| 1077 |
+
"optional": true,
|
| 1078 |
+
"os": [
|
| 1079 |
+
"linux"
|
| 1080 |
+
]
|
| 1081 |
+
},
|
| 1082 |
+
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
| 1083 |
+
"version": "4.62.2",
|
| 1084 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
|
| 1085 |
+
"integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
|
| 1086 |
+
"cpu": [
|
| 1087 |
+
"riscv64"
|
| 1088 |
+
],
|
| 1089 |
+
"dev": true,
|
| 1090 |
+
"license": "MIT",
|
| 1091 |
+
"optional": true,
|
| 1092 |
+
"os": [
|
| 1093 |
+
"linux"
|
| 1094 |
+
]
|
| 1095 |
+
},
|
| 1096 |
+
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
| 1097 |
+
"version": "4.62.2",
|
| 1098 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
|
| 1099 |
+
"integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
|
| 1100 |
+
"cpu": [
|
| 1101 |
+
"riscv64"
|
| 1102 |
+
],
|
| 1103 |
+
"dev": true,
|
| 1104 |
+
"license": "MIT",
|
| 1105 |
+
"optional": true,
|
| 1106 |
+
"os": [
|
| 1107 |
+
"linux"
|
| 1108 |
+
]
|
| 1109 |
+
},
|
| 1110 |
+
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
| 1111 |
+
"version": "4.62.2",
|
| 1112 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
|
| 1113 |
+
"integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
|
| 1114 |
+
"cpu": [
|
| 1115 |
+
"s390x"
|
| 1116 |
+
],
|
| 1117 |
+
"dev": true,
|
| 1118 |
+
"license": "MIT",
|
| 1119 |
+
"optional": true,
|
| 1120 |
+
"os": [
|
| 1121 |
+
"linux"
|
| 1122 |
+
]
|
| 1123 |
+
},
|
| 1124 |
+
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
| 1125 |
+
"version": "4.62.2",
|
| 1126 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
|
| 1127 |
+
"integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
|
| 1128 |
+
"cpu": [
|
| 1129 |
+
"x64"
|
| 1130 |
+
],
|
| 1131 |
+
"dev": true,
|
| 1132 |
+
"license": "MIT",
|
| 1133 |
+
"optional": true,
|
| 1134 |
+
"os": [
|
| 1135 |
+
"linux"
|
| 1136 |
+
]
|
| 1137 |
+
},
|
| 1138 |
+
"node_modules/@rollup/rollup-linux-x64-musl": {
|
| 1139 |
+
"version": "4.62.2",
|
| 1140 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
|
| 1141 |
+
"integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
|
| 1142 |
+
"cpu": [
|
| 1143 |
+
"x64"
|
| 1144 |
+
],
|
| 1145 |
+
"dev": true,
|
| 1146 |
+
"license": "MIT",
|
| 1147 |
+
"optional": true,
|
| 1148 |
+
"os": [
|
| 1149 |
+
"linux"
|
| 1150 |
+
]
|
| 1151 |
+
},
|
| 1152 |
+
"node_modules/@rollup/rollup-openbsd-x64": {
|
| 1153 |
+
"version": "4.62.2",
|
| 1154 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
|
| 1155 |
+
"integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
|
| 1156 |
+
"cpu": [
|
| 1157 |
+
"x64"
|
| 1158 |
+
],
|
| 1159 |
+
"dev": true,
|
| 1160 |
+
"license": "MIT",
|
| 1161 |
+
"optional": true,
|
| 1162 |
+
"os": [
|
| 1163 |
+
"openbsd"
|
| 1164 |
+
]
|
| 1165 |
+
},
|
| 1166 |
+
"node_modules/@rollup/rollup-openharmony-arm64": {
|
| 1167 |
+
"version": "4.62.2",
|
| 1168 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
|
| 1169 |
+
"integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
|
| 1170 |
+
"cpu": [
|
| 1171 |
+
"arm64"
|
| 1172 |
+
],
|
| 1173 |
+
"dev": true,
|
| 1174 |
+
"license": "MIT",
|
| 1175 |
+
"optional": true,
|
| 1176 |
+
"os": [
|
| 1177 |
+
"openharmony"
|
| 1178 |
+
]
|
| 1179 |
+
},
|
| 1180 |
+
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
| 1181 |
+
"version": "4.62.2",
|
| 1182 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
|
| 1183 |
+
"integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
|
| 1184 |
+
"cpu": [
|
| 1185 |
+
"arm64"
|
| 1186 |
+
],
|
| 1187 |
+
"dev": true,
|
| 1188 |
+
"license": "MIT",
|
| 1189 |
+
"optional": true,
|
| 1190 |
+
"os": [
|
| 1191 |
+
"win32"
|
| 1192 |
+
]
|
| 1193 |
+
},
|
| 1194 |
+
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
| 1195 |
+
"version": "4.62.2",
|
| 1196 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
|
| 1197 |
+
"integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
|
| 1198 |
+
"cpu": [
|
| 1199 |
+
"ia32"
|
| 1200 |
+
],
|
| 1201 |
+
"dev": true,
|
| 1202 |
+
"license": "MIT",
|
| 1203 |
+
"optional": true,
|
| 1204 |
+
"os": [
|
| 1205 |
+
"win32"
|
| 1206 |
+
]
|
| 1207 |
+
},
|
| 1208 |
+
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
| 1209 |
+
"version": "4.62.2",
|
| 1210 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
|
| 1211 |
+
"integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
|
| 1212 |
+
"cpu": [
|
| 1213 |
+
"x64"
|
| 1214 |
+
],
|
| 1215 |
+
"dev": true,
|
| 1216 |
+
"license": "MIT",
|
| 1217 |
+
"optional": true,
|
| 1218 |
+
"os": [
|
| 1219 |
+
"win32"
|
| 1220 |
+
]
|
| 1221 |
+
},
|
| 1222 |
+
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
| 1223 |
+
"version": "4.62.2",
|
| 1224 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
|
| 1225 |
+
"integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
|
| 1226 |
+
"cpu": [
|
| 1227 |
+
"x64"
|
| 1228 |
+
],
|
| 1229 |
+
"dev": true,
|
| 1230 |
+
"license": "MIT",
|
| 1231 |
+
"optional": true,
|
| 1232 |
+
"os": [
|
| 1233 |
+
"win32"
|
| 1234 |
+
]
|
| 1235 |
+
},
|
| 1236 |
+
"node_modules/@smithy/core": {
|
| 1237 |
+
"version": "3.29.5",
|
| 1238 |
+
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.5.tgz",
|
| 1239 |
+
"integrity": "sha512-i0dk2t5B+CwV/dcJdUHILYkOQF5lof8f44dFCfDWToGCxjT9YQ+CgHqTAvJxzc3+zqQwm2QtVoJ5IqiNar/CnQ==",
|
| 1240 |
+
"license": "Apache-2.0",
|
| 1241 |
+
"dependencies": {
|
| 1242 |
+
"@smithy/types": "^4.16.1",
|
| 1243 |
+
"tslib": "^2.6.2"
|
| 1244 |
+
},
|
| 1245 |
+
"engines": {
|
| 1246 |
+
"node": ">=18.0.0"
|
| 1247 |
+
}
|
| 1248 |
+
},
|
| 1249 |
+
"node_modules/@smithy/credential-provider-imds": {
|
| 1250 |
+
"version": "4.4.10",
|
| 1251 |
+
"resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.10.tgz",
|
| 1252 |
+
"integrity": "sha512-MJenAe4OKRZUo1LdYYFDCsSHxaHvInIU/z52GsheO9vl1/VSySVCr0zkyKD6TFiGkSUaWGxvKZ/70OvgUZR5HQ==",
|
| 1253 |
+
"license": "Apache-2.0",
|
| 1254 |
+
"dependencies": {
|
| 1255 |
+
"@smithy/core": "^3.29.5",
|
| 1256 |
+
"@smithy/types": "^4.16.1",
|
| 1257 |
+
"tslib": "^2.6.2"
|
| 1258 |
+
},
|
| 1259 |
+
"engines": {
|
| 1260 |
+
"node": ">=18.0.0"
|
| 1261 |
+
}
|
| 1262 |
+
},
|
| 1263 |
+
"node_modules/@smithy/fetch-http-handler": {
|
| 1264 |
+
"version": "5.6.7",
|
| 1265 |
+
"resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.7.tgz",
|
| 1266 |
+
"integrity": "sha512-3zpg8yqqyXzoK2TsRDdkqVOj2RDBFfLXwCczOZ5c7TWB4eiaebfSCsbMjDPYB3PJ9ihV62QaeadZ+wLadZtNGA==",
|
| 1267 |
+
"license": "Apache-2.0",
|
| 1268 |
+
"dependencies": {
|
| 1269 |
+
"@smithy/core": "^3.29.5",
|
| 1270 |
+
"@smithy/types": "^4.16.1",
|
| 1271 |
+
"tslib": "^2.6.2"
|
| 1272 |
+
},
|
| 1273 |
+
"engines": {
|
| 1274 |
+
"node": ">=18.0.0"
|
| 1275 |
+
}
|
| 1276 |
+
},
|
| 1277 |
+
"node_modules/@smithy/node-http-handler": {
|
| 1278 |
+
"version": "4.9.7",
|
| 1279 |
+
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.7.tgz",
|
| 1280 |
+
"integrity": "sha512-wCU8HCLjAtAVqxxe0j2xff9LcEPw3yjBbg5IdQDIYFnxnPxbxcSLc7rgex7kqm9L/WYOnJEgaWQlfDkZleozMA==",
|
| 1281 |
+
"license": "Apache-2.0",
|
| 1282 |
+
"dependencies": {
|
| 1283 |
+
"@smithy/core": "^3.29.5",
|
| 1284 |
+
"@smithy/types": "^4.16.1",
|
| 1285 |
+
"tslib": "^2.6.2"
|
| 1286 |
+
},
|
| 1287 |
+
"engines": {
|
| 1288 |
+
"node": ">=18.0.0"
|
| 1289 |
+
}
|
| 1290 |
+
},
|
| 1291 |
+
"node_modules/@smithy/signature-v4": {
|
| 1292 |
+
"version": "5.6.6",
|
| 1293 |
+
"resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.6.tgz",
|
| 1294 |
+
"integrity": "sha512-efP6DN3UTFrzIsGO42/xcabv8jU7+9nwEdphFUH7yL0k010ERyAWaO41KFQIDLcFZLZ8xzIQr4wplFxNzslSGQ==",
|
| 1295 |
+
"license": "Apache-2.0",
|
| 1296 |
+
"dependencies": {
|
| 1297 |
+
"@smithy/core": "^3.29.5",
|
| 1298 |
+
"@smithy/types": "^4.16.1",
|
| 1299 |
+
"tslib": "^2.6.2"
|
| 1300 |
+
},
|
| 1301 |
+
"engines": {
|
| 1302 |
+
"node": ">=18.0.0"
|
| 1303 |
+
}
|
| 1304 |
+
},
|
| 1305 |
+
"node_modules/@smithy/types": {
|
| 1306 |
+
"version": "4.16.1",
|
| 1307 |
+
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz",
|
| 1308 |
+
"integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==",
|
| 1309 |
+
"license": "Apache-2.0",
|
| 1310 |
+
"dependencies": {
|
| 1311 |
+
"tslib": "^2.6.2"
|
| 1312 |
+
},
|
| 1313 |
+
"engines": {
|
| 1314 |
+
"node": ">=18.0.0"
|
| 1315 |
+
}
|
| 1316 |
+
},
|
| 1317 |
+
"node_modules/@types/estree": {
|
| 1318 |
+
"version": "1.0.9",
|
| 1319 |
+
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
| 1320 |
+
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
|
| 1321 |
+
"dev": true,
|
| 1322 |
+
"license": "MIT"
|
| 1323 |
+
},
|
| 1324 |
+
"node_modules/@types/node": {
|
| 1325 |
+
"version": "20.19.43",
|
| 1326 |
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
|
| 1327 |
+
"integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
|
| 1328 |
+
"dev": true,
|
| 1329 |
+
"license": "MIT",
|
| 1330 |
+
"dependencies": {
|
| 1331 |
+
"undici-types": "~6.21.0"
|
| 1332 |
+
}
|
| 1333 |
+
},
|
| 1334 |
+
"node_modules/@vitejs/plugin-vue": {
|
| 1335 |
+
"version": "5.2.4",
|
| 1336 |
+
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
|
| 1337 |
+
"integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
|
| 1338 |
+
"dev": true,
|
| 1339 |
+
"license": "MIT",
|
| 1340 |
+
"engines": {
|
| 1341 |
+
"node": "^18.0.0 || >=20.0.0"
|
| 1342 |
+
},
|
| 1343 |
+
"peerDependencies": {
|
| 1344 |
+
"vite": "^5.0.0 || ^6.0.0",
|
| 1345 |
+
"vue": "^3.2.25"
|
| 1346 |
+
}
|
| 1347 |
+
},
|
| 1348 |
+
"node_modules/@volar/language-core": {
|
| 1349 |
+
"version": "2.4.28",
|
| 1350 |
+
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz",
|
| 1351 |
+
"integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==",
|
| 1352 |
+
"dev": true,
|
| 1353 |
+
"license": "MIT",
|
| 1354 |
+
"dependencies": {
|
| 1355 |
+
"@volar/source-map": "2.4.28"
|
| 1356 |
+
}
|
| 1357 |
+
},
|
| 1358 |
+
"node_modules/@volar/source-map": {
|
| 1359 |
+
"version": "2.4.28",
|
| 1360 |
+
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz",
|
| 1361 |
+
"integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==",
|
| 1362 |
+
"dev": true,
|
| 1363 |
+
"license": "MIT"
|
| 1364 |
+
},
|
| 1365 |
+
"node_modules/@volar/typescript": {
|
| 1366 |
+
"version": "2.4.28",
|
| 1367 |
+
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz",
|
| 1368 |
+
"integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==",
|
| 1369 |
+
"dev": true,
|
| 1370 |
+
"license": "MIT",
|
| 1371 |
+
"dependencies": {
|
| 1372 |
+
"@volar/language-core": "2.4.28",
|
| 1373 |
+
"path-browserify": "^1.0.1",
|
| 1374 |
+
"vscode-uri": "^3.0.8"
|
| 1375 |
+
}
|
| 1376 |
+
},
|
| 1377 |
+
"node_modules/@vue/compiler-core": {
|
| 1378 |
+
"version": "3.5.40",
|
| 1379 |
+
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.40.tgz",
|
| 1380 |
+
"integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==",
|
| 1381 |
+
"license": "MIT",
|
| 1382 |
+
"dependencies": {
|
| 1383 |
+
"@babel/parser": "^7.29.7",
|
| 1384 |
+
"@vue/shared": "3.5.40",
|
| 1385 |
+
"entities": "^7.0.1",
|
| 1386 |
+
"estree-walker": "^2.0.2",
|
| 1387 |
+
"source-map-js": "^1.2.1"
|
| 1388 |
+
}
|
| 1389 |
+
},
|
| 1390 |
+
"node_modules/@vue/compiler-dom": {
|
| 1391 |
+
"version": "3.5.40",
|
| 1392 |
+
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz",
|
| 1393 |
+
"integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==",
|
| 1394 |
+
"license": "MIT",
|
| 1395 |
+
"dependencies": {
|
| 1396 |
+
"@vue/compiler-core": "3.5.40",
|
| 1397 |
+
"@vue/shared": "3.5.40"
|
| 1398 |
+
}
|
| 1399 |
+
},
|
| 1400 |
+
"node_modules/@vue/compiler-sfc": {
|
| 1401 |
+
"version": "3.5.40",
|
| 1402 |
+
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz",
|
| 1403 |
+
"integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==",
|
| 1404 |
+
"license": "MIT",
|
| 1405 |
+
"dependencies": {
|
| 1406 |
+
"@babel/parser": "^7.29.7",
|
| 1407 |
+
"@vue/compiler-core": "3.5.40",
|
| 1408 |
+
"@vue/compiler-dom": "3.5.40",
|
| 1409 |
+
"@vue/compiler-ssr": "3.5.40",
|
| 1410 |
+
"@vue/shared": "3.5.40",
|
| 1411 |
+
"estree-walker": "^2.0.2",
|
| 1412 |
+
"magic-string": "^0.30.21",
|
| 1413 |
+
"postcss": "^8.5.19",
|
| 1414 |
+
"source-map-js": "^1.2.1"
|
| 1415 |
+
}
|
| 1416 |
+
},
|
| 1417 |
+
"node_modules/@vue/compiler-ssr": {
|
| 1418 |
+
"version": "3.5.40",
|
| 1419 |
+
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz",
|
| 1420 |
+
"integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==",
|
| 1421 |
+
"license": "MIT",
|
| 1422 |
+
"dependencies": {
|
| 1423 |
+
"@vue/compiler-dom": "3.5.40",
|
| 1424 |
+
"@vue/shared": "3.5.40"
|
| 1425 |
+
}
|
| 1426 |
+
},
|
| 1427 |
+
"node_modules/@vue/language-core": {
|
| 1428 |
+
"version": "3.3.7",
|
| 1429 |
+
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.7.tgz",
|
| 1430 |
+
"integrity": "sha512-LzmkKinXAMMoh8Jfi/jMUSDUjuPdv8mynH5WJGKfXyZtDw3hQ6GBaoI6Bcnl/Xqlu32q/0Z6i/trp4VXykzyLw==",
|
| 1431 |
+
"dev": true,
|
| 1432 |
+
"license": "MIT",
|
| 1433 |
+
"dependencies": {
|
| 1434 |
+
"@volar/language-core": "2.4.28",
|
| 1435 |
+
"@vue/compiler-dom": "^3.5.0",
|
| 1436 |
+
"@vue/shared": "^3.5.0",
|
| 1437 |
+
"alien-signals": "^3.2.1",
|
| 1438 |
+
"muggle-string": "^0.4.1",
|
| 1439 |
+
"path-browserify": "^1.0.1",
|
| 1440 |
+
"picomatch": "^4.0.4"
|
| 1441 |
+
}
|
| 1442 |
+
},
|
| 1443 |
+
"node_modules/@vue/reactivity": {
|
| 1444 |
+
"version": "3.5.40",
|
| 1445 |
+
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.40.tgz",
|
| 1446 |
+
"integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==",
|
| 1447 |
+
"license": "MIT",
|
| 1448 |
+
"dependencies": {
|
| 1449 |
+
"@vue/shared": "3.5.40"
|
| 1450 |
+
}
|
| 1451 |
+
},
|
| 1452 |
+
"node_modules/@vue/runtime-core": {
|
| 1453 |
+
"version": "3.5.40",
|
| 1454 |
+
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.40.tgz",
|
| 1455 |
+
"integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==",
|
| 1456 |
+
"license": "MIT",
|
| 1457 |
+
"dependencies": {
|
| 1458 |
+
"@vue/reactivity": "3.5.40",
|
| 1459 |
+
"@vue/shared": "3.5.40"
|
| 1460 |
+
}
|
| 1461 |
+
},
|
| 1462 |
+
"node_modules/@vue/runtime-dom": {
|
| 1463 |
+
"version": "3.5.40",
|
| 1464 |
+
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz",
|
| 1465 |
+
"integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==",
|
| 1466 |
+
"license": "MIT",
|
| 1467 |
+
"dependencies": {
|
| 1468 |
+
"@vue/reactivity": "3.5.40",
|
| 1469 |
+
"@vue/runtime-core": "3.5.40",
|
| 1470 |
+
"@vue/shared": "3.5.40",
|
| 1471 |
+
"csstype": "^3.2.3"
|
| 1472 |
+
}
|
| 1473 |
+
},
|
| 1474 |
+
"node_modules/@vue/server-renderer": {
|
| 1475 |
+
"version": "3.5.40",
|
| 1476 |
+
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.40.tgz",
|
| 1477 |
+
"integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==",
|
| 1478 |
+
"license": "MIT",
|
| 1479 |
+
"dependencies": {
|
| 1480 |
+
"@vue/compiler-ssr": "3.5.40",
|
| 1481 |
+
"@vue/runtime-dom": "3.5.40",
|
| 1482 |
+
"@vue/shared": "3.5.40"
|
| 1483 |
+
}
|
| 1484 |
+
},
|
| 1485 |
+
"node_modules/@vue/shared": {
|
| 1486 |
+
"version": "3.5.40",
|
| 1487 |
+
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz",
|
| 1488 |
+
"integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==",
|
| 1489 |
+
"license": "MIT"
|
| 1490 |
+
},
|
| 1491 |
+
"node_modules/alien-signals": {
|
| 1492 |
+
"version": "3.2.1",
|
| 1493 |
+
"resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz",
|
| 1494 |
+
"integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==",
|
| 1495 |
+
"dev": true,
|
| 1496 |
+
"license": "MIT"
|
| 1497 |
+
},
|
| 1498 |
+
"node_modules/bowser": {
|
| 1499 |
+
"version": "2.14.1",
|
| 1500 |
+
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz",
|
| 1501 |
+
"integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==",
|
| 1502 |
+
"license": "MIT"
|
| 1503 |
+
},
|
| 1504 |
+
"node_modules/csstype": {
|
| 1505 |
+
"version": "3.2.3",
|
| 1506 |
+
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
| 1507 |
+
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
| 1508 |
+
"license": "MIT"
|
| 1509 |
+
},
|
| 1510 |
+
"node_modules/entities": {
|
| 1511 |
+
"version": "7.0.1",
|
| 1512 |
+
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
| 1513 |
+
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
|
| 1514 |
+
"license": "BSD-2-Clause",
|
| 1515 |
+
"engines": {
|
| 1516 |
+
"node": ">=0.12"
|
| 1517 |
+
},
|
| 1518 |
+
"funding": {
|
| 1519 |
+
"url": "https://github.com/fb55/entities?sponsor=1"
|
| 1520 |
+
}
|
| 1521 |
+
},
|
| 1522 |
+
"node_modules/esbuild": {
|
| 1523 |
+
"version": "0.25.12",
|
| 1524 |
+
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
| 1525 |
+
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
|
| 1526 |
+
"dev": true,
|
| 1527 |
+
"hasInstallScript": true,
|
| 1528 |
+
"license": "MIT",
|
| 1529 |
+
"bin": {
|
| 1530 |
+
"esbuild": "bin/esbuild"
|
| 1531 |
+
},
|
| 1532 |
+
"engines": {
|
| 1533 |
+
"node": ">=18"
|
| 1534 |
+
},
|
| 1535 |
+
"optionalDependencies": {
|
| 1536 |
+
"@esbuild/aix-ppc64": "0.25.12",
|
| 1537 |
+
"@esbuild/android-arm": "0.25.12",
|
| 1538 |
+
"@esbuild/android-arm64": "0.25.12",
|
| 1539 |
+
"@esbuild/android-x64": "0.25.12",
|
| 1540 |
+
"@esbuild/darwin-arm64": "0.25.12",
|
| 1541 |
+
"@esbuild/darwin-x64": "0.25.12",
|
| 1542 |
+
"@esbuild/freebsd-arm64": "0.25.12",
|
| 1543 |
+
"@esbuild/freebsd-x64": "0.25.12",
|
| 1544 |
+
"@esbuild/linux-arm": "0.25.12",
|
| 1545 |
+
"@esbuild/linux-arm64": "0.25.12",
|
| 1546 |
+
"@esbuild/linux-ia32": "0.25.12",
|
| 1547 |
+
"@esbuild/linux-loong64": "0.25.12",
|
| 1548 |
+
"@esbuild/linux-mips64el": "0.25.12",
|
| 1549 |
+
"@esbuild/linux-ppc64": "0.25.12",
|
| 1550 |
+
"@esbuild/linux-riscv64": "0.25.12",
|
| 1551 |
+
"@esbuild/linux-s390x": "0.25.12",
|
| 1552 |
+
"@esbuild/linux-x64": "0.25.12",
|
| 1553 |
+
"@esbuild/netbsd-arm64": "0.25.12",
|
| 1554 |
+
"@esbuild/netbsd-x64": "0.25.12",
|
| 1555 |
+
"@esbuild/openbsd-arm64": "0.25.12",
|
| 1556 |
+
"@esbuild/openbsd-x64": "0.25.12",
|
| 1557 |
+
"@esbuild/openharmony-arm64": "0.25.12",
|
| 1558 |
+
"@esbuild/sunos-x64": "0.25.12",
|
| 1559 |
+
"@esbuild/win32-arm64": "0.25.12",
|
| 1560 |
+
"@esbuild/win32-ia32": "0.25.12",
|
| 1561 |
+
"@esbuild/win32-x64": "0.25.12"
|
| 1562 |
+
}
|
| 1563 |
+
},
|
| 1564 |
+
"node_modules/estree-walker": {
|
| 1565 |
+
"version": "2.0.2",
|
| 1566 |
+
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
| 1567 |
+
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
| 1568 |
+
"license": "MIT"
|
| 1569 |
+
},
|
| 1570 |
+
"node_modules/fdir": {
|
| 1571 |
+
"version": "6.5.0",
|
| 1572 |
+
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
| 1573 |
+
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
| 1574 |
+
"dev": true,
|
| 1575 |
+
"license": "MIT",
|
| 1576 |
+
"engines": {
|
| 1577 |
+
"node": ">=12.0.0"
|
| 1578 |
+
},
|
| 1579 |
+
"peerDependencies": {
|
| 1580 |
+
"picomatch": "^3 || ^4"
|
| 1581 |
+
},
|
| 1582 |
+
"peerDependenciesMeta": {
|
| 1583 |
+
"picomatch": {
|
| 1584 |
+
"optional": true
|
| 1585 |
+
}
|
| 1586 |
+
}
|
| 1587 |
+
},
|
| 1588 |
+
"node_modules/fsevents": {
|
| 1589 |
+
"version": "2.3.3",
|
| 1590 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 1591 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 1592 |
+
"dev": true,
|
| 1593 |
+
"hasInstallScript": true,
|
| 1594 |
+
"license": "MIT",
|
| 1595 |
+
"optional": true,
|
| 1596 |
+
"os": [
|
| 1597 |
+
"darwin"
|
| 1598 |
+
],
|
| 1599 |
+
"engines": {
|
| 1600 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 1601 |
+
}
|
| 1602 |
+
},
|
| 1603 |
+
"node_modules/magic-string": {
|
| 1604 |
+
"version": "0.30.21",
|
| 1605 |
+
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
| 1606 |
+
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
| 1607 |
+
"license": "MIT",
|
| 1608 |
+
"dependencies": {
|
| 1609 |
+
"@jridgewell/sourcemap-codec": "^1.5.5"
|
| 1610 |
+
}
|
| 1611 |
+
},
|
| 1612 |
+
"node_modules/muggle-string": {
|
| 1613 |
+
"version": "0.4.1",
|
| 1614 |
+
"resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
|
| 1615 |
+
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
|
| 1616 |
+
"dev": true,
|
| 1617 |
+
"license": "MIT"
|
| 1618 |
+
},
|
| 1619 |
+
"node_modules/nanoid": {
|
| 1620 |
+
"version": "3.3.16",
|
| 1621 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
|
| 1622 |
+
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
|
| 1623 |
+
"funding": [
|
| 1624 |
+
{
|
| 1625 |
+
"type": "github",
|
| 1626 |
+
"url": "https://github.com/sponsors/ai"
|
| 1627 |
+
}
|
| 1628 |
+
],
|
| 1629 |
+
"license": "MIT",
|
| 1630 |
+
"bin": {
|
| 1631 |
+
"nanoid": "bin/nanoid.cjs"
|
| 1632 |
+
},
|
| 1633 |
+
"engines": {
|
| 1634 |
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
| 1635 |
+
}
|
| 1636 |
+
},
|
| 1637 |
+
"node_modules/path-browserify": {
|
| 1638 |
+
"version": "1.0.1",
|
| 1639 |
+
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
|
| 1640 |
+
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
|
| 1641 |
+
"dev": true,
|
| 1642 |
+
"license": "MIT"
|
| 1643 |
+
},
|
| 1644 |
+
"node_modules/picocolors": {
|
| 1645 |
+
"version": "1.1.1",
|
| 1646 |
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 1647 |
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 1648 |
+
"license": "ISC"
|
| 1649 |
+
},
|
| 1650 |
+
"node_modules/picomatch": {
|
| 1651 |
+
"version": "4.0.5",
|
| 1652 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
|
| 1653 |
+
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
| 1654 |
+
"dev": true,
|
| 1655 |
+
"license": "MIT",
|
| 1656 |
+
"engines": {
|
| 1657 |
+
"node": ">=12"
|
| 1658 |
+
},
|
| 1659 |
+
"funding": {
|
| 1660 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 1661 |
+
}
|
| 1662 |
+
},
|
| 1663 |
+
"node_modules/postcss": {
|
| 1664 |
+
"version": "8.5.19",
|
| 1665 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz",
|
| 1666 |
+
"integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==",
|
| 1667 |
+
"funding": [
|
| 1668 |
+
{
|
| 1669 |
+
"type": "opencollective",
|
| 1670 |
+
"url": "https://opencollective.com/postcss/"
|
| 1671 |
+
},
|
| 1672 |
+
{
|
| 1673 |
+
"type": "tidelift",
|
| 1674 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 1675 |
+
},
|
| 1676 |
+
{
|
| 1677 |
+
"type": "github",
|
| 1678 |
+
"url": "https://github.com/sponsors/ai"
|
| 1679 |
+
}
|
| 1680 |
+
],
|
| 1681 |
+
"license": "MIT",
|
| 1682 |
+
"dependencies": {
|
| 1683 |
+
"nanoid": "^3.3.12",
|
| 1684 |
+
"picocolors": "^1.1.1",
|
| 1685 |
+
"source-map-js": "^1.2.1"
|
| 1686 |
+
},
|
| 1687 |
+
"engines": {
|
| 1688 |
+
"node": "^10 || ^12 || >=14"
|
| 1689 |
+
}
|
| 1690 |
+
},
|
| 1691 |
+
"node_modules/rollup": {
|
| 1692 |
+
"version": "4.62.2",
|
| 1693 |
+
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
|
| 1694 |
+
"integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
|
| 1695 |
+
"dev": true,
|
| 1696 |
+
"license": "MIT",
|
| 1697 |
+
"dependencies": {
|
| 1698 |
+
"@types/estree": "1.0.9"
|
| 1699 |
+
},
|
| 1700 |
+
"bin": {
|
| 1701 |
+
"rollup": "dist/bin/rollup"
|
| 1702 |
+
},
|
| 1703 |
+
"engines": {
|
| 1704 |
+
"node": ">=18.0.0",
|
| 1705 |
+
"npm": ">=8.0.0"
|
| 1706 |
+
},
|
| 1707 |
+
"optionalDependencies": {
|
| 1708 |
+
"@rollup/rollup-android-arm-eabi": "4.62.2",
|
| 1709 |
+
"@rollup/rollup-android-arm64": "4.62.2",
|
| 1710 |
+
"@rollup/rollup-darwin-arm64": "4.62.2",
|
| 1711 |
+
"@rollup/rollup-darwin-x64": "4.62.2",
|
| 1712 |
+
"@rollup/rollup-freebsd-arm64": "4.62.2",
|
| 1713 |
+
"@rollup/rollup-freebsd-x64": "4.62.2",
|
| 1714 |
+
"@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
|
| 1715 |
+
"@rollup/rollup-linux-arm-musleabihf": "4.62.2",
|
| 1716 |
+
"@rollup/rollup-linux-arm64-gnu": "4.62.2",
|
| 1717 |
+
"@rollup/rollup-linux-arm64-musl": "4.62.2",
|
| 1718 |
+
"@rollup/rollup-linux-loong64-gnu": "4.62.2",
|
| 1719 |
+
"@rollup/rollup-linux-loong64-musl": "4.62.2",
|
| 1720 |
+
"@rollup/rollup-linux-ppc64-gnu": "4.62.2",
|
| 1721 |
+
"@rollup/rollup-linux-ppc64-musl": "4.62.2",
|
| 1722 |
+
"@rollup/rollup-linux-riscv64-gnu": "4.62.2",
|
| 1723 |
+
"@rollup/rollup-linux-riscv64-musl": "4.62.2",
|
| 1724 |
+
"@rollup/rollup-linux-s390x-gnu": "4.62.2",
|
| 1725 |
+
"@rollup/rollup-linux-x64-gnu": "4.62.2",
|
| 1726 |
+
"@rollup/rollup-linux-x64-musl": "4.62.2",
|
| 1727 |
+
"@rollup/rollup-openbsd-x64": "4.62.2",
|
| 1728 |
+
"@rollup/rollup-openharmony-arm64": "4.62.2",
|
| 1729 |
+
"@rollup/rollup-win32-arm64-msvc": "4.62.2",
|
| 1730 |
+
"@rollup/rollup-win32-ia32-msvc": "4.62.2",
|
| 1731 |
+
"@rollup/rollup-win32-x64-gnu": "4.62.2",
|
| 1732 |
+
"@rollup/rollup-win32-x64-msvc": "4.62.2",
|
| 1733 |
+
"fsevents": "~2.3.2"
|
| 1734 |
+
}
|
| 1735 |
+
},
|
| 1736 |
+
"node_modules/source-map-js": {
|
| 1737 |
+
"version": "1.2.1",
|
| 1738 |
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
| 1739 |
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
| 1740 |
+
"license": "BSD-3-Clause",
|
| 1741 |
+
"engines": {
|
| 1742 |
+
"node": ">=0.10.0"
|
| 1743 |
+
}
|
| 1744 |
+
},
|
| 1745 |
+
"node_modules/tinyglobby": {
|
| 1746 |
+
"version": "0.2.17",
|
| 1747 |
+
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
| 1748 |
+
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
|
| 1749 |
+
"dev": true,
|
| 1750 |
+
"license": "MIT",
|
| 1751 |
+
"dependencies": {
|
| 1752 |
+
"fdir": "^6.5.0",
|
| 1753 |
+
"picomatch": "^4.0.4"
|
| 1754 |
+
},
|
| 1755 |
+
"engines": {
|
| 1756 |
+
"node": ">=12.0.0"
|
| 1757 |
+
},
|
| 1758 |
+
"funding": {
|
| 1759 |
+
"url": "https://github.com/sponsors/SuperchupuDev"
|
| 1760 |
+
}
|
| 1761 |
+
},
|
| 1762 |
+
"node_modules/tslib": {
|
| 1763 |
+
"version": "2.8.1",
|
| 1764 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 1765 |
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 1766 |
+
"license": "0BSD"
|
| 1767 |
+
},
|
| 1768 |
+
"node_modules/typescript": {
|
| 1769 |
+
"version": "5.9.3",
|
| 1770 |
+
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
| 1771 |
+
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
| 1772 |
+
"devOptional": true,
|
| 1773 |
+
"license": "Apache-2.0",
|
| 1774 |
+
"bin": {
|
| 1775 |
+
"tsc": "bin/tsc",
|
| 1776 |
+
"tsserver": "bin/tsserver"
|
| 1777 |
+
},
|
| 1778 |
+
"engines": {
|
| 1779 |
+
"node": ">=14.17"
|
| 1780 |
+
}
|
| 1781 |
+
},
|
| 1782 |
+
"node_modules/undici-types": {
|
| 1783 |
+
"version": "6.21.0",
|
| 1784 |
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
| 1785 |
+
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
| 1786 |
+
"dev": true,
|
| 1787 |
+
"license": "MIT"
|
| 1788 |
+
},
|
| 1789 |
+
"node_modules/vite": {
|
| 1790 |
+
"version": "6.4.3",
|
| 1791 |
+
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
|
| 1792 |
+
"integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
|
| 1793 |
+
"dev": true,
|
| 1794 |
+
"license": "MIT",
|
| 1795 |
+
"dependencies": {
|
| 1796 |
+
"esbuild": "^0.25.0",
|
| 1797 |
+
"fdir": "^6.4.4",
|
| 1798 |
+
"picomatch": "^4.0.2",
|
| 1799 |
+
"postcss": "^8.5.3",
|
| 1800 |
+
"rollup": "^4.34.9",
|
| 1801 |
+
"tinyglobby": "^0.2.13"
|
| 1802 |
+
},
|
| 1803 |
+
"bin": {
|
| 1804 |
+
"vite": "bin/vite.js"
|
| 1805 |
+
},
|
| 1806 |
+
"engines": {
|
| 1807 |
+
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
| 1808 |
+
},
|
| 1809 |
+
"funding": {
|
| 1810 |
+
"url": "https://github.com/vitejs/vite?sponsor=1"
|
| 1811 |
+
},
|
| 1812 |
+
"optionalDependencies": {
|
| 1813 |
+
"fsevents": "~2.3.3"
|
| 1814 |
+
},
|
| 1815 |
+
"peerDependencies": {
|
| 1816 |
+
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
| 1817 |
+
"jiti": ">=1.21.0",
|
| 1818 |
+
"less": "*",
|
| 1819 |
+
"lightningcss": "^1.21.0",
|
| 1820 |
+
"sass": "*",
|
| 1821 |
+
"sass-embedded": "*",
|
| 1822 |
+
"stylus": "*",
|
| 1823 |
+
"sugarss": "*",
|
| 1824 |
+
"terser": "^5.16.0",
|
| 1825 |
+
"tsx": "^4.8.1",
|
| 1826 |
+
"yaml": "^2.4.2"
|
| 1827 |
+
},
|
| 1828 |
+
"peerDependenciesMeta": {
|
| 1829 |
+
"@types/node": {
|
| 1830 |
+
"optional": true
|
| 1831 |
+
},
|
| 1832 |
+
"jiti": {
|
| 1833 |
+
"optional": true
|
| 1834 |
+
},
|
| 1835 |
+
"less": {
|
| 1836 |
+
"optional": true
|
| 1837 |
+
},
|
| 1838 |
+
"lightningcss": {
|
| 1839 |
+
"optional": true
|
| 1840 |
+
},
|
| 1841 |
+
"sass": {
|
| 1842 |
+
"optional": true
|
| 1843 |
+
},
|
| 1844 |
+
"sass-embedded": {
|
| 1845 |
+
"optional": true
|
| 1846 |
+
},
|
| 1847 |
+
"stylus": {
|
| 1848 |
+
"optional": true
|
| 1849 |
+
},
|
| 1850 |
+
"sugarss": {
|
| 1851 |
+
"optional": true
|
| 1852 |
+
},
|
| 1853 |
+
"terser": {
|
| 1854 |
+
"optional": true
|
| 1855 |
+
},
|
| 1856 |
+
"tsx": {
|
| 1857 |
+
"optional": true
|
| 1858 |
+
},
|
| 1859 |
+
"yaml": {
|
| 1860 |
+
"optional": true
|
| 1861 |
+
}
|
| 1862 |
+
}
|
| 1863 |
+
},
|
| 1864 |
+
"node_modules/vscode-uri": {
|
| 1865 |
+
"version": "3.1.0",
|
| 1866 |
+
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
|
| 1867 |
+
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
|
| 1868 |
+
"dev": true,
|
| 1869 |
+
"license": "MIT"
|
| 1870 |
+
},
|
| 1871 |
+
"node_modules/vue": {
|
| 1872 |
+
"version": "3.5.40",
|
| 1873 |
+
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.40.tgz",
|
| 1874 |
+
"integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==",
|
| 1875 |
+
"license": "MIT",
|
| 1876 |
+
"dependencies": {
|
| 1877 |
+
"@vue/compiler-dom": "3.5.40",
|
| 1878 |
+
"@vue/compiler-sfc": "3.5.40",
|
| 1879 |
+
"@vue/runtime-dom": "3.5.40",
|
| 1880 |
+
"@vue/server-renderer": "3.5.40",
|
| 1881 |
+
"@vue/shared": "3.5.40"
|
| 1882 |
+
},
|
| 1883 |
+
"peerDependencies": {
|
| 1884 |
+
"typescript": "*"
|
| 1885 |
+
},
|
| 1886 |
+
"peerDependenciesMeta": {
|
| 1887 |
+
"typescript": {
|
| 1888 |
+
"optional": true
|
| 1889 |
+
}
|
| 1890 |
+
}
|
| 1891 |
+
},
|
| 1892 |
+
"node_modules/vue-tsc": {
|
| 1893 |
+
"version": "3.3.7",
|
| 1894 |
+
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.7.tgz",
|
| 1895 |
+
"integrity": "sha512-+C+rgD49wAQ5bUTl2sp5a8Bzg4YoldMNXM+g7CFe604MYcQ8PrZPMQhIjJSzKXtPBCa+C5ayMipqjbA7splekQ==",
|
| 1896 |
+
"dev": true,
|
| 1897 |
+
"license": "MIT",
|
| 1898 |
+
"dependencies": {
|
| 1899 |
+
"@volar/typescript": "2.4.28",
|
| 1900 |
+
"@vue/language-core": "3.3.7"
|
| 1901 |
+
},
|
| 1902 |
+
"bin": {
|
| 1903 |
+
"vue-tsc": "bin/vue-tsc.js"
|
| 1904 |
+
},
|
| 1905 |
+
"peerDependencies": {
|
| 1906 |
+
"typescript": ">=5.0.0"
|
| 1907 |
+
}
|
| 1908 |
+
}
|
| 1909 |
+
}
|
| 1910 |
+
}
|
apps/dashboard/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "autocad-bench-trace-dashboard",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.1.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite --host 127.0.0.1",
|
| 8 |
+
"build": "vue-tsc -b && vite build",
|
| 9 |
+
"index-traces": "node backfill-trace-summaries.mjs",
|
| 10 |
+
"test": "node --test tests/*.test.mjs",
|
| 11 |
+
"preview": "node server.mjs"
|
| 12 |
+
},
|
| 13 |
+
"dependencies": {
|
| 14 |
+
"@aws-sdk/client-s3": "3.1090.0",
|
| 15 |
+
"@aws-sdk/credential-providers": "3.1090.0",
|
| 16 |
+
"@aws-sdk/s3-request-presigner": "3.1090.0",
|
| 17 |
+
"vue": "3.5.40"
|
| 18 |
+
},
|
| 19 |
+
"devDependencies": {
|
| 20 |
+
"@types/node": "20.19.43",
|
| 21 |
+
"@vitejs/plugin-vue": "5.2.4",
|
| 22 |
+
"typescript": "5.9.3",
|
| 23 |
+
"vite": "6.4.3",
|
| 24 |
+
"vue-tsc": "3.3.7"
|
| 25 |
+
}
|
| 26 |
+
}
|
apps/dashboard/server.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
export {};
|
apps/dashboard/server.mjs
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fs from 'node:fs'
|
| 2 |
+
import http from 'node:http'
|
| 3 |
+
import os from 'node:os'
|
| 4 |
+
import path from 'node:path'
|
| 5 |
+
import { execFile } from 'node:child_process'
|
| 6 |
+
import { createHash } from 'node:crypto'
|
| 7 |
+
import { fileURLToPath } from 'node:url'
|
| 8 |
+
import { promisify } from 'node:util'
|
| 9 |
+
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'
|
| 10 |
+
import { fromIni } from '@aws-sdk/credential-providers'
|
| 11 |
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
|
| 12 |
+
import { createTraceApi } from './trace-api.mjs'
|
| 13 |
+
import {
|
| 14 |
+
benchmarkMetadataPaths,
|
| 15 |
+
shouldPromoteMetadata,
|
| 16 |
+
traceMetadataPaths,
|
| 17 |
+
} from './cloud-metadata.mjs'
|
| 18 |
+
|
| 19 |
+
const root = path.dirname(fileURLToPath(import.meta.url))
|
| 20 |
+
const dist = path.join(root, 'dist')
|
| 21 |
+
const execFileAsync = promisify(execFile)
|
| 22 |
+
const cloudDownloads = new Map()
|
| 23 |
+
const cloudBucket = process.env.AUTOCAD_BENCH_S3_BUCKET || ''
|
| 24 |
+
const cloudPrefix = (process.env.AUTOCAD_BENCH_S3_RUNS_PREFIX || 'controller/runs')
|
| 25 |
+
.replace(/^\/+|\/+$/g, '')
|
| 26 |
+
const awsProfile = process.env.AWS_PROFILE || ''
|
| 27 |
+
const awsRegion = process.env.AWS_REGION || ''
|
| 28 |
+
const cloudArtifactsEnabled = Boolean(cloudBucket)
|
| 29 |
+
&& process.env.AUTOCAD_BENCH_CLOUD_ARTIFACTS !== 'off'
|
| 30 |
+
const signedArtifactLifetimeSeconds = 300
|
| 31 |
+
const s3 = new S3Client({
|
| 32 |
+
...(awsRegion ? { region: awsRegion } : {}),
|
| 33 |
+
...(awsProfile ? { credentials: fromIni({ profile: awsProfile }) } : {}),
|
| 34 |
+
})
|
| 35 |
+
const cloudMetadataDownloads = new Map()
|
| 36 |
+
const cloudMetadataFreshAt = new Map()
|
| 37 |
+
const cloudArtifactAttempts = Math.max(
|
| 38 |
+
1,
|
| 39 |
+
Math.min(5, Number(process.env.AUTOCAD_BENCH_CLOUD_ARTIFACT_ATTEMPTS || 3)),
|
| 40 |
+
)
|
| 41 |
+
const cloudArtifactTimeoutMs = Math.max(
|
| 42 |
+
5_000,
|
| 43 |
+
Math.min(60_000, Number(process.env.AUTOCAD_BENCH_CLOUD_ARTIFACT_TIMEOUT_MS || 15_000)),
|
| 44 |
+
)
|
| 45 |
+
const cloudMetadataCache = path.join(
|
| 46 |
+
os.tmpdir(),
|
| 47 |
+
`autocad-bench-dashboard-${createHash('sha256')
|
| 48 |
+
.update(`${cloudBucket}/${cloudPrefix}`)
|
| 49 |
+
.digest('hex')
|
| 50 |
+
.slice(0, 16)}`,
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
const cloudMetadataConcurrency = Math.max(
|
| 54 |
+
1,
|
| 55 |
+
Math.min(64, Number(process.env.AUTOCAD_BENCH_CLOUD_METADATA_CONCURRENCY || 24)),
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
function promoteCloudMetadata(shadowDirectory, destinationDirectory) {
|
| 59 |
+
if (!fs.existsSync(shadowDirectory)) return
|
| 60 |
+
const visit = (directory) => {
|
| 61 |
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
| 62 |
+
const source = path.join(directory, entry.name)
|
| 63 |
+
if (entry.isDirectory()) {
|
| 64 |
+
visit(source)
|
| 65 |
+
continue
|
| 66 |
+
}
|
| 67 |
+
if (!entry.isFile()) continue
|
| 68 |
+
const relativePath = path.relative(shadowDirectory, source)
|
| 69 |
+
const destination = path.resolve(destinationDirectory, relativePath)
|
| 70 |
+
if (!destination.startsWith(path.resolve(destinationDirectory) + path.sep)) continue
|
| 71 |
+
if (!shouldPromoteMetadata(source, destination)) continue
|
| 72 |
+
fs.mkdirSync(path.dirname(destination), { recursive: true })
|
| 73 |
+
const temporary = `${destination}.cloud-update-${process.pid}-${Date.now()}`
|
| 74 |
+
try {
|
| 75 |
+
fs.copyFileSync(source, temporary)
|
| 76 |
+
fs.renameSync(temporary, destination)
|
| 77 |
+
} finally {
|
| 78 |
+
try {
|
| 79 |
+
if (fs.existsSync(temporary)) fs.unlinkSync(temporary)
|
| 80 |
+
} catch {
|
| 81 |
+
// A failed cleanup cannot invalidate a successful atomic promotion.
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
visit(shadowDirectory)
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
function wait(milliseconds) {
|
| 90 |
+
return new Promise((resolve) => setTimeout(resolve, milliseconds))
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
async function cloudMetadataLoader({
|
| 94 |
+
id,
|
| 95 |
+
destinationDirectory,
|
| 96 |
+
relativePaths,
|
| 97 |
+
maxAgeMs = 0,
|
| 98 |
+
}) {
|
| 99 |
+
if (!cloudArtifactsEnabled) return
|
| 100 |
+
const safeRelativePaths = [...new Set(relativePaths)].filter((relativePath) => {
|
| 101 |
+
if (!relativePath || relativePath.includes('\\')) return false
|
| 102 |
+
return !relativePath.split('/').some((segment) => !segment || segment === '.' || segment === '..')
|
| 103 |
+
})
|
| 104 |
+
if (!safeRelativePaths.length) return
|
| 105 |
+
const downloadKey = `${id}\0${safeRelativePaths.join('\0')}`
|
| 106 |
+
const refreshedAt = cloudMetadataFreshAt.get(downloadKey) || 0
|
| 107 |
+
if (maxAgeMs > 0 && Date.now() - refreshedAt < maxAgeMs) return
|
| 108 |
+
if (cloudMetadataDownloads.has(downloadKey)) {
|
| 109 |
+
return cloudMetadataDownloads.get(downloadKey)
|
| 110 |
+
}
|
| 111 |
+
const download = (async () => {
|
| 112 |
+
const pathNamespace = createHash('sha256')
|
| 113 |
+
.update(safeRelativePaths.join('\0'))
|
| 114 |
+
.digest('hex')
|
| 115 |
+
.slice(0, 12)
|
| 116 |
+
const shadowDirectory = path.resolve(cloudMetadataCache, pathNamespace, id)
|
| 117 |
+
if (!shadowDirectory.startsWith(path.resolve(cloudMetadataCache) + path.sep)) {
|
| 118 |
+
throw new Error('Unsafe cloud metadata cache path')
|
| 119 |
+
}
|
| 120 |
+
if (
|
| 121 |
+
maxAgeMs > 0
|
| 122 |
+
&& fs.existsSync(shadowDirectory)
|
| 123 |
+
&& Date.now() - fs.statSync(shadowDirectory).mtimeMs < maxAgeMs
|
| 124 |
+
) {
|
| 125 |
+
cloudMetadataFreshAt.set(downloadKey, Date.now())
|
| 126 |
+
return
|
| 127 |
+
}
|
| 128 |
+
fs.mkdirSync(shadowDirectory, { recursive: true })
|
| 129 |
+
let nextIndex = 0
|
| 130 |
+
const errors = []
|
| 131 |
+
const workers = Array.from({
|
| 132 |
+
length: Math.min(cloudMetadataConcurrency, safeRelativePaths.length),
|
| 133 |
+
}, async () => {
|
| 134 |
+
while (nextIndex < safeRelativePaths.length) {
|
| 135 |
+
const relativePath = safeRelativePaths[nextIndex]
|
| 136 |
+
nextIndex += 1
|
| 137 |
+
const destination = path.resolve(shadowDirectory, relativePath)
|
| 138 |
+
if (!destination.startsWith(path.resolve(shadowDirectory) + path.sep)) continue
|
| 139 |
+
const key = [cloudPrefix, id, relativePath]
|
| 140 |
+
.join('/')
|
| 141 |
+
.replaceAll('\\', '/')
|
| 142 |
+
.replace(/\/{2,}/g, '/')
|
| 143 |
+
try {
|
| 144 |
+
const object = await s3.send(new GetObjectCommand({
|
| 145 |
+
Bucket: cloudBucket,
|
| 146 |
+
Key: key,
|
| 147 |
+
}))
|
| 148 |
+
if (!object.Body) continue
|
| 149 |
+
const bytes = await object.Body.transformToByteArray()
|
| 150 |
+
fs.mkdirSync(path.dirname(destination), { recursive: true })
|
| 151 |
+
const temporary = `${destination}.cloud-download-${process.pid}-${Date.now()}`
|
| 152 |
+
try {
|
| 153 |
+
fs.writeFileSync(temporary, bytes)
|
| 154 |
+
fs.renameSync(temporary, destination)
|
| 155 |
+
} finally {
|
| 156 |
+
try {
|
| 157 |
+
if (fs.existsSync(temporary)) fs.unlinkSync(temporary)
|
| 158 |
+
} catch {
|
| 159 |
+
// A failed temporary-file cleanup cannot invalidate the cache.
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
} catch (error) {
|
| 163 |
+
const statusCode = error?.$metadata?.httpStatusCode
|
| 164 |
+
if (statusCode === 404 || ['NoSuchKey', 'NotFound'].includes(error?.name)) continue
|
| 165 |
+
errors.push(error)
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
})
|
| 169 |
+
await Promise.all(workers)
|
| 170 |
+
promoteCloudMetadata(shadowDirectory, destinationDirectory)
|
| 171 |
+
if (errors.length) throw errors[0]
|
| 172 |
+
const refreshed = new Date()
|
| 173 |
+
fs.utimesSync(shadowDirectory, refreshed, refreshed)
|
| 174 |
+
cloudMetadataFreshAt.set(downloadKey, refreshed.valueOf())
|
| 175 |
+
})()
|
| 176 |
+
cloudMetadataDownloads.set(downloadKey, download)
|
| 177 |
+
try {
|
| 178 |
+
await download
|
| 179 |
+
} finally {
|
| 180 |
+
cloudMetadataDownloads.delete(downloadKey)
|
| 181 |
+
}
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
async function cloudBenchmarkLoader({ benchmarkId, benchmarkDirectory }) {
|
| 185 |
+
let state = null
|
| 186 |
+
try {
|
| 187 |
+
state = JSON.parse(fs.readFileSync(
|
| 188 |
+
path.join(benchmarkDirectory, 'batch-state.json'),
|
| 189 |
+
'utf8',
|
| 190 |
+
))
|
| 191 |
+
} catch {
|
| 192 |
+
// A new cloud-only benchmark still needs a blocking first bootstrap.
|
| 193 |
+
}
|
| 194 |
+
if (!state) {
|
| 195 |
+
// Fetch the one authoritative suite index directly. Returning as soon as
|
| 196 |
+
// this file is present makes a cold benchmark visible immediately; the
|
| 197 |
+
// rollout metadata is hydrated concurrently below.
|
| 198 |
+
await cloudMetadataLoader({
|
| 199 |
+
id: benchmarkId,
|
| 200 |
+
destinationDirectory: benchmarkDirectory,
|
| 201 |
+
relativePaths: ['batch-state.json'],
|
| 202 |
+
})
|
| 203 |
+
try {
|
| 204 |
+
state = JSON.parse(fs.readFileSync(
|
| 205 |
+
path.join(benchmarkDirectory, 'batch-state.json'),
|
| 206 |
+
'utf8',
|
| 207 |
+
))
|
| 208 |
+
} catch {
|
| 209 |
+
return
|
| 210 |
+
}
|
| 211 |
+
}
|
| 212 |
+
const terminal = !['starting', 'running'].includes(String(state?.state || ''))
|
| 213 |
+
const refresh = cloudMetadataLoader({
|
| 214 |
+
id: benchmarkId,
|
| 215 |
+
destinationDirectory: benchmarkDirectory,
|
| 216 |
+
relativePaths: benchmarkMetadataPaths(state),
|
| 217 |
+
maxAgeMs: state ? (terminal ? 5 * 60_000 : 10_000) : 0,
|
| 218 |
+
})
|
| 219 |
+
// Benchmark detail is always local-first. The direct metadata GETs continue
|
| 220 |
+
// in the background and the next 15-second UI refresh promotes the snapshot.
|
| 221 |
+
void refresh.catch(() => {})
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
async function cloudTraceLoader({ runId, runDirectory }) {
|
| 225 |
+
let state = null
|
| 226 |
+
try {
|
| 227 |
+
state = JSON.parse(fs.readFileSync(path.join(runDirectory, 'run-state.json'), 'utf8'))
|
| 228 |
+
} catch {
|
| 229 |
+
// A not-yet-mirrored rollout should always attempt its first refresh.
|
| 230 |
+
}
|
| 231 |
+
const terminal = [
|
| 232 |
+
'completed', 'recovered', 'budget_exhausted', 'model_failed',
|
| 233 |
+
'infrastructure_failed', 'artifact_missing', 'interrupted',
|
| 234 |
+
].includes(String(state?.state || state?.execution_status || ''))
|
| 235 |
+
const refresh = cloudMetadataLoader({
|
| 236 |
+
id: runId,
|
| 237 |
+
destinationDirectory: runDirectory,
|
| 238 |
+
relativePaths: traceMetadataPaths(),
|
| 239 |
+
maxAgeMs: state ? (terminal ? 5 * 60_000 : 5_000) : 0,
|
| 240 |
+
})
|
| 241 |
+
if (state) {
|
| 242 |
+
void refresh.catch(() => {})
|
| 243 |
+
return
|
| 244 |
+
}
|
| 245 |
+
await refresh
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
async function cloudArtifactLoader({
|
| 249 |
+
runId,
|
| 250 |
+
relativePath,
|
| 251 |
+
artifact,
|
| 252 |
+
expectedSha256 = null,
|
| 253 |
+
force = false,
|
| 254 |
+
}) {
|
| 255 |
+
if (!cloudArtifactsEnabled) return
|
| 256 |
+
if (!force && fs.existsSync(artifact) && fs.statSync(artifact).isFile()) return
|
| 257 |
+
if (cloudDownloads.has(artifact)) return cloudDownloads.get(artifact)
|
| 258 |
+
const download = (async () => {
|
| 259 |
+
fs.mkdirSync(path.dirname(artifact), { recursive: true })
|
| 260 |
+
let lastError = null
|
| 261 |
+
const key = [cloudPrefix, runId, relativePath]
|
| 262 |
+
.join('/')
|
| 263 |
+
.replaceAll('\\', '/')
|
| 264 |
+
.replace(/\/{2,}/g, '/')
|
| 265 |
+
for (let attempt = 1; attempt <= cloudArtifactAttempts; attempt += 1) {
|
| 266 |
+
const temporary = `${artifact}.download-${process.pid}-${Date.now()}-${attempt}`
|
| 267 |
+
try {
|
| 268 |
+
const awsArguments = [
|
| 269 |
+
's3', 'cp', `s3://${cloudBucket}/${key}`, temporary,
|
| 270 |
+
'--only-show-errors',
|
| 271 |
+
]
|
| 272 |
+
if (awsProfile) awsArguments.push('--profile', awsProfile)
|
| 273 |
+
if (awsRegion) awsArguments.push('--region', awsRegion)
|
| 274 |
+
await execFileAsync('aws', awsArguments, {
|
| 275 |
+
timeout: cloudArtifactTimeoutMs,
|
| 276 |
+
maxBuffer: 1024 * 1024,
|
| 277 |
+
})
|
| 278 |
+
if (expectedSha256) {
|
| 279 |
+
const actualSha256 = createHash('sha256')
|
| 280 |
+
.update(fs.readFileSync(temporary))
|
| 281 |
+
.digest('hex')
|
| 282 |
+
if (actualSha256 !== expectedSha256) {
|
| 283 |
+
throw new Error(
|
| 284 |
+
`Cloud artifact checksum mismatch for ${relativePath}: expected ${expectedSha256}, got ${actualSha256}`,
|
| 285 |
+
)
|
| 286 |
+
}
|
| 287 |
+
}
|
| 288 |
+
fs.renameSync(temporary, artifact)
|
| 289 |
+
return
|
| 290 |
+
} catch (error) {
|
| 291 |
+
lastError = error
|
| 292 |
+
} finally {
|
| 293 |
+
try {
|
| 294 |
+
if (fs.existsSync(temporary)) fs.unlinkSync(temporary)
|
| 295 |
+
} catch {
|
| 296 |
+
// A failed cleanup cannot change the artifact response.
|
| 297 |
+
}
|
| 298 |
+
}
|
| 299 |
+
if (attempt < cloudArtifactAttempts) {
|
| 300 |
+
await wait(150 * attempt * attempt)
|
| 301 |
+
}
|
| 302 |
+
}
|
| 303 |
+
throw lastError || new Error(`Cloud artifact download failed for ${relativePath}`)
|
| 304 |
+
})()
|
| 305 |
+
cloudDownloads.set(artifact, download)
|
| 306 |
+
try {
|
| 307 |
+
await download
|
| 308 |
+
} finally {
|
| 309 |
+
cloudDownloads.delete(artifact)
|
| 310 |
+
}
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
async function cloudArtifactUrl({ runId, relativePath }) {
|
| 314 |
+
if (!cloudArtifactsEnabled) return null
|
| 315 |
+
const key = [cloudPrefix, runId, relativePath]
|
| 316 |
+
.join('/')
|
| 317 |
+
.replaceAll('\\', '/')
|
| 318 |
+
.replace(/\/{2,}/g, '/')
|
| 319 |
+
const extension = path.extname(relativePath).toLowerCase()
|
| 320 |
+
const responseContentType = {
|
| 321 |
+
'.png': 'image/png',
|
| 322 |
+
'.json': 'application/json',
|
| 323 |
+
'.jsonl': 'application/x-ndjson',
|
| 324 |
+
'.dwg': 'application/acad',
|
| 325 |
+
}[extension] || 'application/octet-stream'
|
| 326 |
+
return getSignedUrl(
|
| 327 |
+
s3,
|
| 328 |
+
new GetObjectCommand({
|
| 329 |
+
Bucket: cloudBucket,
|
| 330 |
+
Key: key,
|
| 331 |
+
ResponseCacheControl: 'private, max-age=31536000, immutable',
|
| 332 |
+
ResponseContentType: responseContentType,
|
| 333 |
+
}),
|
| 334 |
+
{ expiresIn: signedArtifactLifetimeSeconds },
|
| 335 |
+
)
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
const api = createTraceApi({
|
| 339 |
+
cloudArtifactLoader,
|
| 340 |
+
cloudArtifactUrl,
|
| 341 |
+
cloudBenchmarkLoader,
|
| 342 |
+
cloudTraceLoader,
|
| 343 |
+
})
|
| 344 |
+
const host = process.env.HOST || '127.0.0.1'
|
| 345 |
+
const port = Number(process.env.PORT || 4173)
|
| 346 |
+
|
| 347 |
+
const mime = {
|
| 348 |
+
'.css': 'text/css; charset=utf-8',
|
| 349 |
+
'.html': 'text/html; charset=utf-8',
|
| 350 |
+
'.js': 'text/javascript; charset=utf-8',
|
| 351 |
+
'.json': 'application/json; charset=utf-8',
|
| 352 |
+
'.png': 'image/png',
|
| 353 |
+
'.svg': 'image/svg+xml',
|
| 354 |
+
'.woff2': 'font/woff2',
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
http
|
| 358 |
+
.createServer(async (request, response) => {
|
| 359 |
+
if (await api(request, response)) return
|
| 360 |
+
const pathname = decodeURIComponent(new URL(request.url || '/', `http://${host}`).pathname)
|
| 361 |
+
const requested = path.resolve(dist, pathname.replace(/^\/+/, ''))
|
| 362 |
+
const safe = requested.startsWith(dist + path.sep) ? requested : null
|
| 363 |
+
const filePath = safe && fs.existsSync(safe) && fs.statSync(safe).isFile()
|
| 364 |
+
? safe
|
| 365 |
+
: path.join(dist, 'index.html')
|
| 366 |
+
response.statusCode = 200
|
| 367 |
+
response.setHeader('Content-Type', mime[path.extname(filePath)] || 'application/octet-stream')
|
| 368 |
+
fs.createReadStream(filePath).pipe(response)
|
| 369 |
+
})
|
| 370 |
+
.listen(port, host, () => {
|
| 371 |
+
process.stdout.write(`AutoCAD Trace Console: http://${host}:${port}\n`)
|
| 372 |
+
})
|
apps/dashboard/src/App.vue
ADDED
|
@@ -0,0 +1,722 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<script setup lang="ts">
|
| 2 |
+
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
| 3 |
+
import type {
|
| 4 |
+
BenchmarkDetail,
|
| 5 |
+
BenchmarkSummary,
|
| 6 |
+
BenchmarkTask,
|
| 7 |
+
EvaluationScoreSummary,
|
| 8 |
+
FrameArtifact,
|
| 9 |
+
InputReceipt,
|
| 10 |
+
ModelTurn,
|
| 11 |
+
TraceDetail,
|
| 12 |
+
} from './types'
|
| 13 |
+
|
| 14 |
+
type ExpandedImage = { src: string; label: string }
|
| 15 |
+
type BenchmarkStatusFilter = 'all' | 'live' | 'completed' | 'failed'
|
| 16 |
+
type TaskTypeFilter = 'all' | '2d' | '3d'
|
| 17 |
+
|
| 18 |
+
const benchmarks = ref<BenchmarkSummary[]>([])
|
| 19 |
+
const selectedBenchmarkId = ref('')
|
| 20 |
+
const benchmark = ref<BenchmarkDetail | null>(null)
|
| 21 |
+
const selectedTaskId = ref('')
|
| 22 |
+
const trace = ref<TraceDetail | null>(null)
|
| 23 |
+
const selectedTurn = ref(0)
|
| 24 |
+
|
| 25 |
+
const benchmarkQuery = ref('')
|
| 26 |
+
const benchmarkStatus = ref<BenchmarkStatusFilter>('all')
|
| 27 |
+
const benchmarkModel = ref('all')
|
| 28 |
+
const taskQuery = ref('')
|
| 29 |
+
const taskType = ref<TaskTypeFilter>('all')
|
| 30 |
+
const taskLevel = ref('all')
|
| 31 |
+
const taskState = ref('all')
|
| 32 |
+
|
| 33 |
+
const loading = ref(true)
|
| 34 |
+
const detailLoading = ref(false)
|
| 35 |
+
const traceLoading = ref(false)
|
| 36 |
+
const error = ref('')
|
| 37 |
+
const lastRefresh = ref<Date | null>(null)
|
| 38 |
+
const expandedImage = ref<ExpandedImage | null>(null)
|
| 39 |
+
let refreshTimer: number | undefined
|
| 40 |
+
let refreshInFlight = false
|
| 41 |
+
let benchmarkLoadVersion = 0
|
| 42 |
+
let traceLoadVersion = 0
|
| 43 |
+
let routeSyncVersion = 0
|
| 44 |
+
const imageRetryTimers = new Set<number>()
|
| 45 |
+
const IMAGE_RETRY_LIMIT = 4
|
| 46 |
+
const API_TIMEOUT_MS = 20_000
|
| 47 |
+
|
| 48 |
+
async function fetchJson<T>(url: string): Promise<T> {
|
| 49 |
+
const controller = new AbortController()
|
| 50 |
+
const timeout = window.setTimeout(() => controller.abort(), API_TIMEOUT_MS)
|
| 51 |
+
try {
|
| 52 |
+
const response = await fetch(url, {
|
| 53 |
+
cache: 'no-store',
|
| 54 |
+
signal: controller.signal,
|
| 55 |
+
})
|
| 56 |
+
if (!response.ok) throw new Error(`Request failed (HTTP ${response.status})`)
|
| 57 |
+
return await response.json() as T
|
| 58 |
+
} catch (reason) {
|
| 59 |
+
if (reason instanceof DOMException && reason.name === 'AbortError') {
|
| 60 |
+
throw new Error('The dashboard request timed out. Retry to refresh the local cloud cache.')
|
| 61 |
+
}
|
| 62 |
+
throw reason
|
| 63 |
+
} finally {
|
| 64 |
+
window.clearTimeout(timeout)
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
const activeStatuses = new Set(['starting', 'resetting', 'handoff_ready', 'running', 'finalizing'])
|
| 69 |
+
|
| 70 |
+
const modelOptions = computed(() =>
|
| 71 |
+
[...new Set(benchmarks.value.flatMap((item) => item.models || [item.modelId]))].sort(),
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
const globalSummary = computed(() => {
|
| 75 |
+
const totalTasks = benchmarks.value.reduce((sum, item) => sum + item.aggregate.totalTasks, 0)
|
| 76 |
+
const scoredTasks = benchmarks.value.reduce((sum, item) => sum + item.aggregate.scoredTasks, 0)
|
| 77 |
+
const weightedScore = benchmarks.value.reduce(
|
| 78 |
+
(sum, item) => sum + (item.aggregate.meanScore || 0) * item.aggregate.scoredTasks,
|
| 79 |
+
0,
|
| 80 |
+
)
|
| 81 |
+
const visionScoredTasks = benchmarks.value.reduce(
|
| 82 |
+
(sum, item) => sum + item.aggregate.visionScoredTasks,
|
| 83 |
+
0,
|
| 84 |
+
)
|
| 85 |
+
const visionWeightedScore = benchmarks.value.reduce(
|
| 86 |
+
(sum, item) => sum
|
| 87 |
+
+ (item.aggregate.visionMeanScore || 0) * item.aggregate.visionScoredTasks,
|
| 88 |
+
0,
|
| 89 |
+
)
|
| 90 |
+
const deterministicScoredTasks = benchmarks.value.reduce(
|
| 91 |
+
(sum, item) => sum + item.aggregate.deterministicScoredTasks,
|
| 92 |
+
0,
|
| 93 |
+
)
|
| 94 |
+
const deterministicWeightedScore = benchmarks.value.reduce(
|
| 95 |
+
(sum, item) => sum
|
| 96 |
+
+ (item.aggregate.deterministicMeanScore || 0)
|
| 97 |
+
* item.aggregate.deterministicScoredTasks,
|
| 98 |
+
0,
|
| 99 |
+
)
|
| 100 |
+
return {
|
| 101 |
+
suites: benchmarks.value.length,
|
| 102 |
+
live: benchmarks.value.filter((item) => ['starting', 'running'].includes(item.status)).length,
|
| 103 |
+
totalTasks,
|
| 104 |
+
scoredTasks,
|
| 105 |
+
meanScore: scoredTasks ? weightedScore / scoredTasks : null,
|
| 106 |
+
visionScoredTasks,
|
| 107 |
+
visionMeanScore: visionScoredTasks ? visionWeightedScore / visionScoredTasks : null,
|
| 108 |
+
deterministicScoredTasks,
|
| 109 |
+
deterministicMeanScore: deterministicScoredTasks
|
| 110 |
+
? deterministicWeightedScore / deterministicScoredTasks
|
| 111 |
+
: null,
|
| 112 |
+
models: new Set(benchmarks.value.flatMap((item) => item.models || [item.modelId])).size,
|
| 113 |
+
}
|
| 114 |
+
})
|
| 115 |
+
|
| 116 |
+
const filteredBenchmarks = computed(() => {
|
| 117 |
+
const query = benchmarkQuery.value.trim().toLowerCase()
|
| 118 |
+
return benchmarks.value.filter((item) => {
|
| 119 |
+
const statusMatches = benchmarkStatus.value === 'all'
|
| 120 |
+
|| (benchmarkStatus.value === 'live' && ['starting', 'running'].includes(item.status))
|
| 121 |
+
|| (benchmarkStatus.value === 'completed' && ['completed', 'completed_with_issues'].includes(item.status))
|
| 122 |
+
|| (benchmarkStatus.value === 'failed' && ['failed', 'interrupted', 'error'].includes(item.status))
|
| 123 |
+
const modelMatches = benchmarkModel.value === 'all'
|
| 124 |
+
|| (item.models || [item.modelId]).includes(benchmarkModel.value)
|
| 125 |
+
const queryMatches = !query || [
|
| 126 |
+
item.name,
|
| 127 |
+
item.batchId,
|
| 128 |
+
item.modelId,
|
| 129 |
+
item.provider,
|
| 130 |
+
...(item.models || []),
|
| 131 |
+
].some((value) => String(value || '').toLowerCase().includes(query))
|
| 132 |
+
return statusMatches && modelMatches && queryMatches
|
| 133 |
+
})
|
| 134 |
+
})
|
| 135 |
+
|
| 136 |
+
const selectedTask = computed(() =>
|
| 137 |
+
benchmark.value?.tasks.find((task) => task.taskId === selectedTaskId.value) || null,
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
const taskLevelOptions = computed(() =>
|
| 141 |
+
[...new Set(benchmark.value?.tasks.map((task) => task.level) || [])].sort(),
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
const taskStateOptions = computed(() =>
|
| 145 |
+
[...new Set(benchmark.value?.tasks.map((task) => task.status) || [])].sort(),
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
const filteredTasks = computed(() => {
|
| 149 |
+
const query = taskQuery.value.trim().toLowerCase()
|
| 150 |
+
return (benchmark.value?.tasks || []).filter((task) => {
|
| 151 |
+
const queryMatches = !query || [
|
| 152 |
+
task.taskId,
|
| 153 |
+
task.displayName,
|
| 154 |
+
task.modelId,
|
| 155 |
+
task.provider,
|
| 156 |
+
].some((value) => value.toLowerCase().includes(query))
|
| 157 |
+
return queryMatches
|
| 158 |
+
&& (taskType.value === 'all' || task.drawingType === taskType.value)
|
| 159 |
+
&& (taskLevel.value === 'all' || task.level === taskLevel.value)
|
| 160 |
+
&& (taskState.value === 'all' || task.status === taskState.value)
|
| 161 |
+
})
|
| 162 |
+
})
|
| 163 |
+
|
| 164 |
+
const currentTurn = computed<ModelTurn | null>(() =>
|
| 165 |
+
trace.value?.turns[selectedTurn.value] || null,
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
const currentReceipts = computed<InputReceipt[]>(() => {
|
| 169 |
+
const toolUseId = currentTurn.value?.tool_use_id
|
| 170 |
+
const receipts = trace.value?.receipts || []
|
| 171 |
+
if (toolUseId && receipts.some((receipt) => receipt.tool_use_id)) {
|
| 172 |
+
return receipts.filter((receipt) => receipt.tool_use_id === toolUseId)
|
| 173 |
+
}
|
| 174 |
+
const legacy = receipts.find((receipt) => receipt.action_index === selectedTurn.value + 1)
|
| 175 |
+
return legacy ? [legacy] : []
|
| 176 |
+
})
|
| 177 |
+
|
| 178 |
+
const currentActions = computed<Record<string, unknown>[]>(() => {
|
| 179 |
+
const payload = currentTurn.value?.action
|
| 180 |
+
if (Array.isArray(payload?.actions)) {
|
| 181 |
+
return payload.actions.filter(
|
| 182 |
+
(action): action is Record<string, unknown> => Boolean(action) && typeof action === 'object',
|
| 183 |
+
)
|
| 184 |
+
}
|
| 185 |
+
return payload ? [payload] : []
|
| 186 |
+
})
|
| 187 |
+
|
| 188 |
+
function frameAt(sequence: number): FrameArtifact | null {
|
| 189 |
+
const name = `frame_${String(sequence).padStart(4, '0')}.png`
|
| 190 |
+
return trace.value?.frames.find((frame) => frame.name === name) || null
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
const latestFrame = computed(() => trace.value?.frames.at(-1) || null)
|
| 194 |
+
|
| 195 |
+
const turnInputFrame = computed(() => {
|
| 196 |
+
const match = JSON.stringify(currentTurn.value?.model_input || {}).match(/frames\/frame_(\d+)\.png/)
|
| 197 |
+
if (match) return frameAt(Number(match[1]))
|
| 198 |
+
const actionIndex = currentReceipts.value[0]?.action_index
|
| 199 |
+
return actionIndex != null ? frameAt(actionIndex - 1) : null
|
| 200 |
+
})
|
| 201 |
+
|
| 202 |
+
const turnOutputFrame = computed(() => {
|
| 203 |
+
const receipt = currentReceipts.value.at(-1)
|
| 204 |
+
if (receipt) return frameAt(receipt.action_index)
|
| 205 |
+
const nextTurn = trace.value?.turns[selectedTurn.value + 1]
|
| 206 |
+
const match = JSON.stringify(nextTurn?.model_input || {}).match(/frames\/frame_(\d+)\.png/)
|
| 207 |
+
return match ? frameAt(Number(match[1])) : latestFrame.value
|
| 208 |
+
})
|
| 209 |
+
|
| 210 |
+
function formatDate(value: string | null): string {
|
| 211 |
+
if (!value) return 'Unknown date'
|
| 212 |
+
const date = new Date(value)
|
| 213 |
+
return Number.isNaN(date.valueOf())
|
| 214 |
+
? value
|
| 215 |
+
: new Intl.DateTimeFormat(undefined, {
|
| 216 |
+
month: 'short',
|
| 217 |
+
day: 'numeric',
|
| 218 |
+
year: 'numeric',
|
| 219 |
+
hour: 'numeric',
|
| 220 |
+
minute: '2-digit',
|
| 221 |
+
}).format(date)
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
function formatDuration(seconds: number | null): string {
|
| 225 |
+
if (seconds == null) return '—'
|
| 226 |
+
if (seconds < 60) return `${Math.round(seconds)}s`
|
| 227 |
+
const hours = Math.floor(seconds / 3600)
|
| 228 |
+
const minutes = Math.floor((seconds % 3600) / 60)
|
| 229 |
+
return hours ? `${hours}h ${minutes}m` : `${minutes}m ${Math.round(seconds % 60)}s`
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
function formatCost(value: number | null): string {
|
| 233 |
+
if (value == null) return '—'
|
| 234 |
+
return new Intl.NumberFormat(undefined, {
|
| 235 |
+
style: 'currency',
|
| 236 |
+
currency: 'USD',
|
| 237 |
+
minimumFractionDigits: value < 1 ? 3 : 2,
|
| 238 |
+
maximumFractionDigits: value < 1 ? 3 : 2,
|
| 239 |
+
}).format(value)
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
function formatNumber(value: number): string {
|
| 243 |
+
return new Intl.NumberFormat().format(value)
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
function formatScore(score: number | null): string {
|
| 247 |
+
return score == null ? '—' : (score * 100).toFixed(1)
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
function readable(value: string): string {
|
| 251 |
+
return value.replaceAll('_', ' ')
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
function benchmarkProgress(item: BenchmarkSummary): number {
|
| 255 |
+
if (!item.aggregate.totalTasks) return 0
|
| 256 |
+
const settled = item.aggregate.settledTasks
|
| 257 |
+
return Math.max(0, Math.min(100, settled / item.aggregate.totalTasks * 100))
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
function stateTone(value: string): string {
|
| 261 |
+
if (['target_acquired', 'scored', 'completed', 'recovered'].includes(value)) return 'positive'
|
| 262 |
+
if (['mostly_acquired', 'awaiting_score', 'partial', 'budget_exhausted', 'completed_with_issues'].includes(value)) return 'warning'
|
| 263 |
+
if (['not_acquired', 'candidate_invalid', 'evaluation_failed', 'evaluation_interrupted', 'run_failed', 'model_failed', 'infrastructure_failed', 'artifact_missing', 'failed', 'error', 'interrupted', 'no_artifact'].includes(value)) return 'negative'
|
| 264 |
+
if (['running', 'evaluating', 'evaluation_retrying', 'starting', 'resetting', 'handoff_ready', 'finalizing'].includes(value)) return 'live'
|
| 265 |
+
return 'neutral'
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
function taskOutcome(task: BenchmarkTask): string {
|
| 269 |
+
return task.verdict ? readable(task.verdict) : readable(task.evaluationState)
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
function scoreSourceLabel(task: BenchmarkTask): string {
|
| 273 |
+
if (task.scoreSource === 'vision_judge') return 'Vision judge'
|
| 274 |
+
if (task.scoreSource === 'deterministic') return 'Deterministic'
|
| 275 |
+
if (task.evaluationState === 'evaluating') return readable(task.evaluationStage || 'evaluating')
|
| 276 |
+
if (task.evaluationState === 'evaluation_failed') return 'Automatic evaluation failed'
|
| 277 |
+
if (task.evaluationState === 'evaluation_interrupted') return 'Automatic evaluation interrupted'
|
| 278 |
+
if (task.evaluationState === 'evaluation_retrying') return 'Vision judge retry queued'
|
| 279 |
+
if (task.evaluationState === 'candidate_invalid') return 'Invalid saved DWG'
|
| 280 |
+
return 'Not scored'
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
function judgeResultLabel(score: EvaluationScoreSummary | null): string {
|
| 284 |
+
if (!score) return 'Not available'
|
| 285 |
+
if (score.valid === false) return readable(score.failureReason || 'invalid candidate')
|
| 286 |
+
if (score.verdict) return readable(score.verdict)
|
| 287 |
+
return score.score != null ? 'Valid diagnostic' : 'Not scored'
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
function judgeResultTone(score: EvaluationScoreSummary | null): string {
|
| 291 |
+
if (!score) return 'neutral'
|
| 292 |
+
if (score.valid === false) return 'negative'
|
| 293 |
+
return stateTone(score.verdict || (score.score != null ? 'scored' : 'neutral'))
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
function resetTaskFilters() {
|
| 297 |
+
taskQuery.value = ''
|
| 298 |
+
taskType.value = 'all'
|
| 299 |
+
taskLevel.value = 'all'
|
| 300 |
+
taskState.value = 'all'
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
function updateHash() {
|
| 304 |
+
const params = new URLSearchParams()
|
| 305 |
+
if (selectedBenchmarkId.value) params.set('benchmark', selectedBenchmarkId.value)
|
| 306 |
+
if (selectedTaskId.value) params.set('task', selectedTaskId.value)
|
| 307 |
+
window.history.replaceState(null, '', params.size ? `#${params.toString()}` : window.location.pathname)
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
async function loadBenchmarks(silent = false) {
|
| 311 |
+
if (!silent) loading.value = true
|
| 312 |
+
try {
|
| 313 |
+
const payload = await fetchJson<{ benchmarks: BenchmarkSummary[] }>('/api/benchmarks')
|
| 314 |
+
benchmarks.value = payload.benchmarks
|
| 315 |
+
lastRefresh.value = new Date()
|
| 316 |
+
error.value = ''
|
| 317 |
+
} catch (reason) {
|
| 318 |
+
error.value = reason instanceof Error ? reason.message : String(reason)
|
| 319 |
+
} finally {
|
| 320 |
+
loading.value = false
|
| 321 |
+
}
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
async function loadBenchmark(id: string, silent = false) {
|
| 325 |
+
const requestVersion = ++benchmarkLoadVersion
|
| 326 |
+
if (!id) {
|
| 327 |
+
benchmark.value = null
|
| 328 |
+
return
|
| 329 |
+
}
|
| 330 |
+
if (!silent) detailLoading.value = true
|
| 331 |
+
try {
|
| 332 |
+
const payload = await fetchJson<BenchmarkDetail>(
|
| 333 |
+
`/api/benchmarks/${encodeURIComponent(id)}`,
|
| 334 |
+
)
|
| 335 |
+
if (requestVersion !== benchmarkLoadVersion || selectedBenchmarkId.value !== id) return
|
| 336 |
+
benchmark.value = payload
|
| 337 |
+
error.value = ''
|
| 338 |
+
} catch (reason) {
|
| 339 |
+
if (requestVersion !== benchmarkLoadVersion || selectedBenchmarkId.value !== id) return
|
| 340 |
+
error.value = reason instanceof Error ? reason.message : String(reason)
|
| 341 |
+
} finally {
|
| 342 |
+
if (requestVersion === benchmarkLoadVersion) detailLoading.value = false
|
| 343 |
+
}
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
async function loadTrace(runId: string, silent = false) {
|
| 347 |
+
const requestVersion = ++traceLoadVersion
|
| 348 |
+
if (!runId) {
|
| 349 |
+
trace.value = null
|
| 350 |
+
return
|
| 351 |
+
}
|
| 352 |
+
if (!silent) traceLoading.value = true
|
| 353 |
+
try {
|
| 354 |
+
const payload = await fetchJson<TraceDetail>(`/api/runs/${encodeURIComponent(runId)}`)
|
| 355 |
+
if (requestVersion !== traceLoadVersion || selectedTask.value?.runId !== runId) return
|
| 356 |
+
trace.value = payload
|
| 357 |
+
selectedTurn.value = Math.min(selectedTurn.value, Math.max(0, trace.value.turns.length - 1))
|
| 358 |
+
error.value = ''
|
| 359 |
+
} catch (reason) {
|
| 360 |
+
if (requestVersion !== traceLoadVersion || selectedTask.value?.runId !== runId) return
|
| 361 |
+
trace.value = null
|
| 362 |
+
error.value = reason instanceof Error ? reason.message : String(reason)
|
| 363 |
+
} finally {
|
| 364 |
+
if (requestVersion === traceLoadVersion) traceLoading.value = false
|
| 365 |
+
}
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
async function syncRouteFromHash(force = false) {
|
| 369 |
+
const requestVersion = ++routeSyncVersion
|
| 370 |
+
const hash = new URLSearchParams(window.location.hash.replace(/^#/, ''))
|
| 371 |
+
const nextBenchmarkId = hash.get('benchmark') || ''
|
| 372 |
+
const nextTaskId = hash.get('task') || ''
|
| 373 |
+
const benchmarkChanged = nextBenchmarkId !== selectedBenchmarkId.value
|
| 374 |
+
const taskChanged = nextTaskId !== selectedTaskId.value
|
| 375 |
+
|
| 376 |
+
if (!force && !benchmarkChanged && !taskChanged) return
|
| 377 |
+
|
| 378 |
+
selectedBenchmarkId.value = nextBenchmarkId
|
| 379 |
+
selectedTaskId.value = nextTaskId
|
| 380 |
+
selectedTurn.value = 0
|
| 381 |
+
trace.value = null
|
| 382 |
+
if (benchmarkChanged) {
|
| 383 |
+
benchmark.value = null
|
| 384 |
+
resetTaskFilters()
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
if (!nextBenchmarkId) return
|
| 388 |
+
if (benchmarkChanged || force || !benchmark.value) await loadBenchmark(nextBenchmarkId)
|
| 389 |
+
if (requestVersion !== routeSyncVersion) return
|
| 390 |
+
|
| 391 |
+
const task = selectedTask.value
|
| 392 |
+
if (task && task.status !== 'queued') await loadTrace(task.runId)
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
function onHashChange() {
|
| 396 |
+
void syncRouteFromHash()
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
async function openBenchmark(id: string) {
|
| 400 |
+
selectedBenchmarkId.value = id
|
| 401 |
+
selectedTaskId.value = ''
|
| 402 |
+
benchmark.value = null
|
| 403 |
+
trace.value = null
|
| 404 |
+
resetTaskFilters()
|
| 405 |
+
updateHash()
|
| 406 |
+
await loadBenchmark(id)
|
| 407 |
+
window.scrollTo({ top: 0, behavior: 'smooth' })
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
function closeBenchmark() {
|
| 411 |
+
selectedBenchmarkId.value = ''
|
| 412 |
+
selectedTaskId.value = ''
|
| 413 |
+
benchmark.value = null
|
| 414 |
+
trace.value = null
|
| 415 |
+
updateHash()
|
| 416 |
+
requestAnimationFrame(() => window.scrollTo({ top: 0, behavior: 'smooth' }))
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
async function openTask(task: BenchmarkTask) {
|
| 420 |
+
if (task.status === 'queued') return
|
| 421 |
+
selectedTaskId.value = task.taskId
|
| 422 |
+
selectedTurn.value = 0
|
| 423 |
+
trace.value = null
|
| 424 |
+
updateHash()
|
| 425 |
+
await loadTrace(task.runId)
|
| 426 |
+
requestAnimationFrame(() => document.querySelector('#task-inspector')?.scrollIntoView({
|
| 427 |
+
behavior: 'smooth',
|
| 428 |
+
block: 'start',
|
| 429 |
+
}))
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
function closeTask() {
|
| 433 |
+
selectedTaskId.value = ''
|
| 434 |
+
trace.value = null
|
| 435 |
+
updateHash()
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
function chooseTurn(index: number) {
|
| 439 |
+
selectedTurn.value = Math.max(0, Math.min(index, Math.max(0, (trace.value?.turns.length || 1) - 1)))
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
function openImage(src: string | null | undefined, label: string) {
|
| 443 |
+
if (src) expandedImage.value = { src, label }
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
function artifactImageLoaded(event: Event) {
|
| 447 |
+
const image = event.currentTarget as HTMLImageElement
|
| 448 |
+
image.dataset.retryAttempts = '0'
|
| 449 |
+
image.dataset.loadState = 'loaded'
|
| 450 |
+
}
|
| 451 |
+
|
| 452 |
+
function retryArtifactImage(event: Event) {
|
| 453 |
+
const image = event.currentTarget as HTMLImageElement
|
| 454 |
+
const source = image.dataset.source || image.getAttribute('src') || ''
|
| 455 |
+
const attempts = Number(image.dataset.retryAttempts || 0)
|
| 456 |
+
if (!source || attempts >= IMAGE_RETRY_LIMIT) {
|
| 457 |
+
image.dataset.loadState = 'failed'
|
| 458 |
+
return
|
| 459 |
+
}
|
| 460 |
+
const nextAttempt = attempts + 1
|
| 461 |
+
image.dataset.retryAttempts = String(nextAttempt)
|
| 462 |
+
image.dataset.loadState = 'retrying'
|
| 463 |
+
const retryDelay = Math.min(2_000, 200 * 2 ** (nextAttempt - 1))
|
| 464 |
+
const timer = window.setTimeout(() => {
|
| 465 |
+
imageRetryTimers.delete(timer)
|
| 466 |
+
const retryUrl = new URL(source, window.location.origin)
|
| 467 |
+
retryUrl.searchParams.set('_image_retry', `${nextAttempt}-${Date.now()}`)
|
| 468 |
+
image.src = retryUrl.toString()
|
| 469 |
+
}, retryDelay)
|
| 470 |
+
imageRetryTimers.add(timer)
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
async function refresh() {
|
| 474 |
+
if (refreshInFlight || document.hidden) return
|
| 475 |
+
refreshInFlight = true
|
| 476 |
+
try {
|
| 477 |
+
await loadBenchmarks(true)
|
| 478 |
+
if (selectedBenchmarkId.value) await loadBenchmark(selectedBenchmarkId.value, true)
|
| 479 |
+
const task = selectedTask.value
|
| 480 |
+
if (task && (
|
| 481 |
+
activeStatuses.has(task.status) || task.evaluationState === 'evaluating'
|
| 482 |
+
)) {
|
| 483 |
+
await loadTrace(task.runId, true)
|
| 484 |
+
}
|
| 485 |
+
} finally {
|
| 486 |
+
refreshInFlight = false
|
| 487 |
+
}
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
function onKeydown(event: KeyboardEvent) {
|
| 491 |
+
if (event.key === 'Escape') {
|
| 492 |
+
if (expandedImage.value) expandedImage.value = null
|
| 493 |
+
else if (selectedTaskId.value) closeTask()
|
| 494 |
+
return
|
| 495 |
+
}
|
| 496 |
+
if (expandedImage.value) return
|
| 497 |
+
if (event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement) return
|
| 498 |
+
if (event.key === 'ArrowLeft') chooseTurn(selectedTurn.value - 1)
|
| 499 |
+
if (event.key === 'ArrowRight') chooseTurn(selectedTurn.value + 1)
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
onMounted(async () => {
|
| 503 |
+
window.addEventListener('hashchange', onHashChange)
|
| 504 |
+
window.addEventListener('keydown', onKeydown)
|
| 505 |
+
await Promise.all([
|
| 506 |
+
loadBenchmarks(),
|
| 507 |
+
syncRouteFromHash(true),
|
| 508 |
+
])
|
| 509 |
+
refreshTimer = window.setInterval(refresh, 15_000)
|
| 510 |
+
})
|
| 511 |
+
|
| 512 |
+
onBeforeUnmount(() => {
|
| 513 |
+
if (refreshTimer) window.clearInterval(refreshTimer)
|
| 514 |
+
for (const timer of imageRetryTimers) window.clearTimeout(timer)
|
| 515 |
+
imageRetryTimers.clear()
|
| 516 |
+
window.removeEventListener('hashchange', onHashChange)
|
| 517 |
+
window.removeEventListener('keydown', onKeydown)
|
| 518 |
+
})
|
| 519 |
+
</script>
|
| 520 |
+
|
| 521 |
+
<template>
|
| 522 |
+
<div class="app">
|
| 523 |
+
<header class="app-header">
|
| 524 |
+
<button class="brand" type="button" @click="closeBenchmark">
|
| 525 |
+
<span class="brand-cube" aria-hidden="true"><i></i><i></i><i></i></span>
|
| 526 |
+
<span>
|
| 527 |
+
<strong>AutoCAD Bench</strong>
|
| 528 |
+
<small>Model evaluation registry</small>
|
| 529 |
+
</span>
|
| 530 |
+
</button>
|
| 531 |
+
<div class="header-actions">
|
| 532 |
+
<span v-if="globalSummary.live" class="live-indicator"><i></i>{{ globalSummary.live }} live</span>
|
| 533 |
+
<span class="refresh-time">Updated {{ lastRefresh ? formatDate(lastRefresh.toISOString()) : '—' }}</span>
|
| 534 |
+
<button class="icon-button" type="button" title="Refresh benchmark data" @click="refresh">↻</button>
|
| 535 |
+
</div>
|
| 536 |
+
</header>
|
| 537 |
+
|
| 538 |
+
<main class="page">
|
| 539 |
+
<div v-if="error" class="error-banner">{{ error }}</div>
|
| 540 |
+
|
| 541 |
+
<template v-if="!selectedBenchmarkId">
|
| 542 |
+
<section class="page-heading">
|
| 543 |
+
<div>
|
| 544 |
+
<p class="kicker">BENCHMARK HISTORY</p>
|
| 545 |
+
<h1>Every AutoCAD evaluation,<br><span>in one place.</span></h1>
|
| 546 |
+
<p class="heading-copy">
|
| 547 |
+
Follow live suites, compare model outcomes, and inspect any task without switching between benchmark and rollout views.
|
| 548 |
+
</p>
|
| 549 |
+
</div>
|
| 550 |
+
<div class="corpus-badge">
|
| 551 |
+
<span>Current corpus</span>
|
| 552 |
+
<strong>65 tasks</strong>
|
| 553 |
+
<small>34 2D · 31 3D</small>
|
| 554 |
+
</div>
|
| 555 |
+
</section>
|
| 556 |
+
|
| 557 |
+
<section class="overview-grid" aria-label="Benchmark totals">
|
| 558 |
+
<article><span>Benchmark suites</span><strong>{{ globalSummary.suites }}</strong><small>{{ globalSummary.live ? `${globalSummary.live} currently live` : 'No active suites' }}</small></article>
|
| 559 |
+
<article><span>Task attempts</span><strong>{{ formatNumber(globalSummary.totalTasks) }}</strong><small>Across every recorded suite</small></article>
|
| 560 |
+
<article><span>Scored results</span><strong>{{ formatNumber(globalSummary.scoredTasks) }}</strong><small>{{ globalSummary.totalTasks ? Math.round(globalSummary.scoredTasks / globalSummary.totalTasks * 100) : 0 }}% evaluation coverage</small></article>
|
| 561 |
+
<article class="score-overview vision-score"><span>Vision mean</span><strong>{{ formatScore(globalSummary.visionMeanScore) }}<small v-if="globalSummary.visionMeanScore != null">/100</small></strong><small>{{ globalSummary.visionScoredTasks }} vision judgments</small></article>
|
| 562 |
+
<article class="score-overview deterministic-score"><span>Deterministic mean</span><strong>{{ formatScore(globalSummary.deterministicMeanScore) }}<small v-if="globalSummary.deterministicMeanScore != null">/100</small></strong><small>{{ globalSummary.deterministicScoredTasks }} diagnostics</small></article>
|
| 563 |
+
<article><span>Models evaluated</span><strong>{{ globalSummary.models }}</strong><small>Unique provider model IDs</small></article>
|
| 564 |
+
</section>
|
| 565 |
+
|
| 566 |
+
<section class="registry-panel">
|
| 567 |
+
<div class="registry-heading">
|
| 568 |
+
<div><p class="kicker">ALL RUNS</p><h2>Benchmark registry</h2></div>
|
| 569 |
+
<span>{{ filteredBenchmarks.length }} of {{ benchmarks.length }} suites</span>
|
| 570 |
+
</div>
|
| 571 |
+
<div class="filter-bar">
|
| 572 |
+
<label class="search-field"><span>Search</span><input v-model="benchmarkQuery" type="search" placeholder="Name, model, provider, batch ID…" /></label>
|
| 573 |
+
<label><span>Status</span><select v-model="benchmarkStatus"><option value="all">All statuses</option><option value="live">Live</option><option value="completed">Completed</option><option value="failed">Failed / interrupted</option></select></label>
|
| 574 |
+
<label><span>Model</span><select v-model="benchmarkModel"><option value="all">All models</option><option v-for="model in modelOptions" :key="model" :value="model">{{ model }}</option></select></label>
|
| 575 |
+
</div>
|
| 576 |
+
<div v-if="loading" class="empty-panel">Loading benchmark history…</div>
|
| 577 |
+
<div v-else-if="!filteredBenchmarks.length" class="empty-panel"><strong>No matching benchmarks</strong><span>Clear the filters to see the complete history.</span></div>
|
| 578 |
+
<div v-else class="benchmark-list">
|
| 579 |
+
<button v-for="item in filteredBenchmarks" :key="item.id" type="button" class="benchmark-row" @click="openBenchmark(item.id)">
|
| 580 |
+
<span class="row-status" :class="stateTone(item.status)"><i></i>{{ readable(item.status) }}</span>
|
| 581 |
+
<span class="row-identity"><strong>{{ item.name }}</strong><small>{{ item.modelId }} · {{ item.provider }} · {{ formatDate(item.startedAt || item.updatedAt) }}</small></span>
|
| 582 |
+
<span class="row-progress"><span><strong>{{ item.aggregate.settledTasks }}</strong>/{{ item.aggregate.totalTasks }} settled</span><i><b :style="{ width: `${benchmarkProgress(item)}%` }"></b></i></span>
|
| 583 |
+
<span class="row-result row-vision-score"><strong>{{ formatScore(item.aggregate.visionMeanScore) }}</strong><small>Vision · {{ item.aggregate.visionScoredTasks }} judged</small></span>
|
| 584 |
+
<span class="row-result row-deterministic-score"><strong>{{ formatScore(item.aggregate.deterministicMeanScore) }}</strong><small>Deterministic · {{ item.aggregate.deterministicScoredTasks }}</small></span>
|
| 585 |
+
<span class="row-coverage"><strong>{{ item.aggregate.evaluationStates?.evaluating || item.aggregate.evaluationStates?.awaiting_score || 0 }}</strong><small>{{ item.aggregate.evaluationStates?.evaluating ? 'evaluating now' : 'awaiting score' }}</small></span>
|
| 586 |
+
<span class="row-cost"><strong>{{ formatCost(item.aggregate.usage.apiCost) }}</strong><small>model spend</small></span>
|
| 587 |
+
<span class="row-arrow">→</span>
|
| 588 |
+
</button>
|
| 589 |
+
</div>
|
| 590 |
+
</section>
|
| 591 |
+
</template>
|
| 592 |
+
|
| 593 |
+
<template v-else>
|
| 594 |
+
<button class="back-button" type="button" @click="closeBenchmark">← All benchmarks</button>
|
| 595 |
+
<section v-if="detailLoading && !benchmark" class="empty-panel">Loading benchmark…</section>
|
| 596 |
+
<section v-else-if="!benchmark" class="empty-panel"><strong>Benchmark unavailable</strong><span>{{ error || 'The benchmark metadata could not be loaded.' }}</span><button type="button" @click="loadBenchmark(selectedBenchmarkId)">Retry</button></section>
|
| 597 |
+
<template v-else-if="benchmark">
|
| 598 |
+
<section class="detail-heading">
|
| 599 |
+
<div><div class="detail-meta"><span class="status-chip" :class="stateTone(benchmark.status)"><i></i>{{ readable(benchmark.status) }}</span><span>{{ formatDate(benchmark.startedAt) }}</span><span v-if="benchmark.batchId">{{ benchmark.batchId }}</span></div><h1>{{ benchmark.name }}</h1><p>{{ benchmark.modelId }} · {{ benchmark.provider }} · up to {{ benchmark.maxConcurrency || '—' }} concurrent desktops</p></div>
|
| 600 |
+
<div class="detail-score-pair">
|
| 601 |
+
<div class="vision-score"><span>Vision mean · official</span><strong>{{ formatScore(benchmark.aggregate.visionMeanScore) }}</strong><small>{{ benchmark.aggregate.visionScoredTasks }}/{{ benchmark.aggregate.totalTasks }} judged</small></div>
|
| 602 |
+
<div class="deterministic-score"><span>Deterministic mean</span><strong>{{ formatScore(benchmark.aggregate.deterministicMeanScore) }}</strong><small>{{ benchmark.aggregate.deterministicScoredTasks }}/{{ benchmark.aggregate.totalTasks }} diagnostics</small></div>
|
| 603 |
+
</div>
|
| 604 |
+
</section>
|
| 605 |
+
|
| 606 |
+
<section class="detail-metrics">
|
| 607 |
+
<article><span>Suite progress</span><strong>{{ benchmark.aggregate.settledTasks }}/{{ benchmark.aggregate.totalTasks }}</strong><small>{{ benchmark.aggregate.activeTasks }} active · {{ benchmark.aggregate.queuedTasks }} queued</small></article>
|
| 608 |
+
<article><span>Artifacts</span><strong>{{ benchmark.aggregate.artifactTasks }}</strong><small>{{ benchmark.aggregate.evaluationStates.evaluating || 0 }} evaluating · {{ benchmark.aggregate.evaluationStates.awaiting_score || 0 }} waiting</small></article>
|
| 609 |
+
<article><span>Acquired targets</span><strong>{{ benchmark.aggregate.verdicts.target_acquired || 0 }}</strong><small>{{ benchmark.aggregate.verdicts.mostly_acquired || 0 }} mostly acquired</small></article>
|
| 610 |
+
<article><span>Execution issues</span><strong>{{ benchmark.aggregate.failedTasks + benchmark.aggregate.infrastructureFailedTasks + benchmark.aggregate.artifactMissingTasks }}</strong><small>{{ benchmark.aggregate.failedTasks }} model · {{ benchmark.aggregate.infrastructureFailedTasks }} infrastructure · {{ benchmark.aggregate.artifactMissingTasks }} artifact</small></article>
|
| 611 |
+
<article><span>Actions</span><strong>{{ formatNumber(benchmark.aggregate.acceptedActions) }}</strong><small>{{ benchmark.aggregate.invalidActions }} rejected</small></article>
|
| 612 |
+
<article><span>Model spend</span><strong>{{ formatCost(benchmark.aggregate.usage.apiCost) }}</strong><small>{{ formatNumber(benchmark.aggregate.usage.totalTokens) }} tokens</small></article>
|
| 613 |
+
</section>
|
| 614 |
+
|
| 615 |
+
<section class="coverage-strip" aria-label="Evaluation result states">
|
| 616 |
+
<div><span>Execution outcomes</span><strong>{{ benchmark.aggregate.settledTasks }} settled</strong></div>
|
| 617 |
+
<span v-for="(count, state) in benchmark.aggregate.executionStates" :key="`execution-${state}`" class="coverage-chip" :class="stateTone(String(state))">{{ count }} {{ readable(String(state)) }}</span>
|
| 618 |
+
</section>
|
| 619 |
+
|
| 620 |
+
<section class="coverage-strip" aria-label="Evaluation result states">
|
| 621 |
+
<div><span>Evaluation coverage</span><strong>{{ benchmark.aggregate.scoredTasks }} scored</strong></div>
|
| 622 |
+
<span class="coverage-chip positive">{{ benchmark.aggregate.visionScoredTasks }} vision judged</span>
|
| 623 |
+
<span class="coverage-chip neutral">{{ benchmark.aggregate.deterministicScoredTasks }} deterministic</span>
|
| 624 |
+
<span v-for="(count, state) in benchmark.aggregate.evaluationStates" :key="state" class="coverage-chip" :class="stateTone(String(state))">{{ count }} {{ readable(String(state)) }}</span>
|
| 625 |
+
<span class="corpus-split">{{ benchmark.aggregate.drawingTypes['2d']?.total || 0 }} 2D · {{ benchmark.aggregate.drawingTypes['3d']?.total || 0 }} 3D</span>
|
| 626 |
+
</section>
|
| 627 |
+
|
| 628 |
+
<section class="task-panel">
|
| 629 |
+
<div class="registry-heading"><div><p class="kicker">TASK RESULTS</p><h2>All attempts</h2></div><span>{{ filteredTasks.length }} of {{ benchmark.tasks.length }} tasks</span></div>
|
| 630 |
+
<div class="filter-bar task-filters">
|
| 631 |
+
<label class="search-field"><span>Find task</span><input v-model="taskQuery" type="search" placeholder="Task ID or model…" /></label>
|
| 632 |
+
<label><span>Type</span><select v-model="taskType"><option value="all">2D + 3D</option><option value="2d">2D only</option><option value="3d">3D only</option></select></label>
|
| 633 |
+
<label><span>Level</span><select v-model="taskLevel"><option value="all">All levels</option><option v-for="level in taskLevelOptions" :key="level" :value="level">{{ readable(level) }}</option></select></label>
|
| 634 |
+
<label><span>Result</span><select v-model="taskState"><option value="all">All results</option><option v-for="state in taskStateOptions" :key="state" :value="state">{{ readable(state) }}</option></select></label>
|
| 635 |
+
</div>
|
| 636 |
+
|
| 637 |
+
<div class="task-table-head"><span>Task</span><span>Run state</span><span>Outcome</span><span>Vision score</span><span>Deterministic score</span><span>Official components</span><span>Actions</span><span>Time</span><span>Cost</span><span></span></div>
|
| 638 |
+
<div class="task-list">
|
| 639 |
+
<button v-for="task in filteredTasks" :key="task.rolloutId" type="button" class="task-row" :class="{ selected: task.taskId === selectedTaskId, disabled: task.status === 'queued' }" :disabled="task.status === 'queued'" @click="openTask(task)">
|
| 640 |
+
<span class="task-identity"><strong>{{ task.taskId }}</strong><small><b>{{ task.drawingType.toUpperCase() }}</b> {{ readable(task.level) }} · {{ task.split }}</small></span>
|
| 641 |
+
<span><em class="state-dot" :class="stateTone(task.status)"></em>{{ readable(task.status) }}</span>
|
| 642 |
+
<span><strong class="outcome" :class="stateTone(task.verdict || task.evaluationState)">{{ taskOutcome(task) }}</strong><small>{{ scoreSourceLabel(task) }}</small></span>
|
| 643 |
+
<span class="numeric-score vision-score">{{ formatScore(task.visionScore?.score ?? null) }}</span>
|
| 644 |
+
<span class="numeric-score deterministic-score">{{ formatScore(task.deterministicScore?.score ?? null) }}</span>
|
| 645 |
+
<span class="component-mini"><small v-if="task.geometrySimilarity != null">G {{ formatScore(task.geometrySimilarity) }}</small><small v-if="task.dimensionSimilarity != null">D {{ formatScore(task.dimensionSimilarity) }}</small><small v-if="task.geometrySimilarity == null">—</small></span>
|
| 646 |
+
<span>{{ formatNumber(task.acceptedActions) }}</span><span>{{ formatDuration(task.rolloutElapsedSeconds ?? task.elapsedSeconds) }}</span><span>{{ formatCost(task.usage.apiCost) }}</span><span class="row-arrow">→</span>
|
| 647 |
+
</button>
|
| 648 |
+
</div>
|
| 649 |
+
</section>
|
| 650 |
+
|
| 651 |
+
<section v-if="selectedTask" id="task-inspector" class="inspector-panel">
|
| 652 |
+
<header class="inspector-heading">
|
| 653 |
+
<div><p class="kicker">TASK INSPECTOR</p><h2>{{ selectedTask.taskId }}</h2><p>{{ selectedTask.modelId }} · {{ selectedTask.provider }} · {{ selectedTask.drawingType.toUpperCase() }} {{ selectedTask.level }}</p></div>
|
| 654 |
+
<div class="inspector-actions"><a v-if="trace?.artifacts.dwg" class="button secondary" :href="trace.artifacts.dwg">Download DWG</a><button class="icon-button" type="button" title="Close task inspector" @click="closeTask">×</button></div>
|
| 655 |
+
</header>
|
| 656 |
+
|
| 657 |
+
<div class="judge-results">
|
| 658 |
+
<section class="judge-result vision-result">
|
| 659 |
+
<header><div><span>Vision judge</span><strong>Official result</strong></div><div class="judge-total"><strong>{{ formatScore(selectedTask.visionScore?.score ?? null) }}</strong><small>/100</small></div></header>
|
| 660 |
+
<div v-if="selectedTask.visionScore" class="judge-result-body">
|
| 661 |
+
<p :class="judgeResultTone(selectedTask.visionScore)">{{ judgeResultLabel(selectedTask.visionScore) }}</p>
|
| 662 |
+
<div class="judge-components">
|
| 663 |
+
<div><span>Geometry</span><strong>{{ formatScore(selectedTask.visionScore.geometry) }}</strong></div>
|
| 664 |
+
<div><span>Dimensions</span><strong>{{ formatScore(selectedTask.visionScore.dimensions) }}</strong></div>
|
| 665 |
+
<div><span>Annotations</span><strong>{{ formatScore(selectedTask.visionScore.annotations) }}</strong></div>
|
| 666 |
+
<div><span>Presentation</span><strong>{{ formatScore(selectedTask.visionScore.presentation) }}</strong></div>
|
| 667 |
+
</div>
|
| 668 |
+
<small>{{ selectedTask.visionScore.confidence != null ? `${Math.round(selectedTask.visionScore.confidence * 100)}% confidence` : 'No confidence value' }}<template v-if="selectedTask.visionScore.needsHumanReview"> · human review flagged</template></small>
|
| 669 |
+
</div>
|
| 670 |
+
<div v-else class="judge-unavailable">No completed vision judgment</div>
|
| 671 |
+
</section>
|
| 672 |
+
|
| 673 |
+
<section class="judge-result deterministic-result">
|
| 674 |
+
<header><div><span>Deterministic scorer</span><strong>Diagnostic result</strong></div><div class="judge-total"><strong>{{ formatScore(selectedTask.deterministicScore?.score ?? null) }}</strong><small>/100</small></div></header>
|
| 675 |
+
<div v-if="selectedTask.deterministicScore" class="judge-result-body">
|
| 676 |
+
<p :class="judgeResultTone(selectedTask.deterministicScore)">{{ judgeResultLabel(selectedTask.deterministicScore) }}</p>
|
| 677 |
+
<div class="judge-components">
|
| 678 |
+
<div><span>Geometry</span><strong>{{ formatScore(selectedTask.deterministicScore.geometry) }}</strong></div>
|
| 679 |
+
<div><span>Dimensions</span><strong>{{ formatScore(selectedTask.deterministicScore.dimensions) }}</strong></div>
|
| 680 |
+
<div><span>Annotations</span><strong>{{ formatScore(selectedTask.deterministicScore.annotations) }}</strong></div>
|
| 681 |
+
<div><span>Visual</span><strong>{{ formatScore(selectedTask.deterministicScore.presentation) }}</strong></div>
|
| 682 |
+
</div>
|
| 683 |
+
<small>{{ selectedTask.deterministicScore.method || 'Unknown deterministic method' }}</small>
|
| 684 |
+
</div>
|
| 685 |
+
<div v-else class="judge-unavailable">No deterministic diagnostic</div>
|
| 686 |
+
</section>
|
| 687 |
+
</div>
|
| 688 |
+
|
| 689 |
+
<div v-if="traceLoading" class="empty-panel">Loading task artifacts…</div>
|
| 690 |
+
<template v-else-if="trace">
|
| 691 |
+
<div class="artifact-grid">
|
| 692 |
+
<figure><figcaption><span>Reference target</span><small>Task authority</small></figcaption><button type="button" @click="openImage(trace.referenceUrl, 'Reference target')"><img :src="trace.referenceUrl" :data-source="trace.referenceUrl" alt="Reference drawing" loading="eager" decoding="async" fetchpriority="high" @load="artifactImageLoaded" @error="retryArtifactImage" /></button></figure>
|
| 693 |
+
<figure><figcaption><span>Saved candidate</span><small>{{ trace.artifacts.candidateRender ? 'Trusted evaluator render' : 'Final desktop frame' }}</small></figcaption><button v-if="trace.artifacts.candidateRender" type="button" @click="openImage(trace.artifacts.candidateRender, 'Saved candidate render')"><img :src="trace.artifacts.candidateRender" :data-source="trace.artifacts.candidateRender" alt="Candidate DWG render" loading="eager" decoding="async" fetchpriority="high" @load="artifactImageLoaded" @error="retryArtifactImage" /></button><button v-else-if="latestFrame" type="button" @click="openImage(latestFrame.url, 'Final desktop frame')"><img :src="latestFrame.url" :data-source="latestFrame.url" alt="Final desktop frame" loading="eager" decoding="async" fetchpriority="high" @load="artifactImageLoaded" @error="retryArtifactImage" /></button><div v-else class="artifact-missing">No candidate render or frame yet</div></figure>
|
| 694 |
+
<figure v-if="trace.artifacts.diffRender"><figcaption><span>Deterministic diff</span><small>Diagnostic only</small></figcaption><button type="button" @click="openImage(trace.artifacts.diffRender, 'Deterministic diff')"><img :src="trace.artifacts.diffRender" :data-source="trace.artifacts.diffRender" alt="Candidate and gold diff" loading="eager" decoding="async" @load="artifactImageLoaded" @error="retryArtifactImage" /></button></figure>
|
| 695 |
+
</div>
|
| 696 |
+
|
| 697 |
+
<section v-if="trace.turns.length" class="trace-section">
|
| 698 |
+
<header><div><p class="kicker">MODEL TRACE</p><h3>Turn {{ selectedTurn + 1 }} of {{ trace.turns.length }}</h3></div><div class="turn-controls"><button type="button" :disabled="selectedTurn === 0" @click="chooseTurn(selectedTurn - 1)">←</button><select v-model.number="selectedTurn"><option v-for="(turn, index) in trace.turns" :key="turn.tool_use_id || index" :value="index">Turn {{ index + 1 }} · {{ Array.isArray(turn.action?.actions) ? turn.action.actions.length : 1 }} action(s)</option></select><button type="button" :disabled="selectedTurn >= trace.turns.length - 1" @click="chooseTurn(selectedTurn + 1)">→</button></div></header>
|
| 699 |
+
<div class="turn-frame-grid">
|
| 700 |
+
<figure><figcaption><span>Frame shown to model</span><small v-if="turnInputFrame">{{ turnInputFrame.name }}</small></figcaption><button v-if="turnInputFrame" :key="turnInputFrame.url" type="button" @click="openImage(turnInputFrame.url, 'Frame shown to model')"><img :src="turnInputFrame.url" :data-source="turnInputFrame.url" alt="Desktop frame shown to model" loading="eager" decoding="async" @load="artifactImageLoaded" @error="retryArtifactImage" /></button><div v-else class="artifact-missing">Frame unavailable</div></figure>
|
| 701 |
+
<figure><figcaption><span>Final frame after batch</span><small v-if="turnOutputFrame">{{ turnOutputFrame.name }}</small></figcaption><button v-if="turnOutputFrame" :key="turnOutputFrame.url" type="button" @click="openImage(turnOutputFrame.url, 'Final frame after batch')"><img :src="turnOutputFrame.url" :data-source="turnOutputFrame.url" alt="Final desktop frame after action batch" loading="eager" decoding="async" @load="artifactImageLoaded" @error="retryArtifactImage" /></button><div v-else class="artifact-missing">Frame unavailable</div></figure>
|
| 702 |
+
</div>
|
| 703 |
+
<div class="action-list"><article v-for="(action, index) in currentActions" :key="index"><span>{{ index + 1 }}</span><div><strong>{{ readable(String(action.action || 'unknown')) }}</strong><p>{{ String(action.intent || 'No intent recorded') }}</p></div><code>{{ Object.fromEntries(Object.entries(action).filter(([key]) => !['action', 'intent'].includes(key))) }}</code></article></div>
|
| 704 |
+
<details class="raw-details"><summary>Raw turn, both scores, and controller receipts</summary><div><pre>{{ JSON.stringify(currentTurn, null, 2) }}</pre><pre>{{ JSON.stringify(currentReceipts, null, 2) }}</pre><pre>{{ JSON.stringify(trace.scores, null, 2) }}</pre></div></details>
|
| 705 |
+
</section>
|
| 706 |
+
<div v-else class="empty-panel">
|
| 707 |
+
{{ trace.traceNotice || 'No model turns were recorded for this task.' }}
|
| 708 |
+
</div>
|
| 709 |
+
</template>
|
| 710 |
+
<div v-else class="empty-panel"><strong>No trace directory is available</strong><span>The batch row exists, but its rollout never produced inspectable artifacts.</span></div>
|
| 711 |
+
</section>
|
| 712 |
+
</template>
|
| 713 |
+
</template>
|
| 714 |
+
</main>
|
| 715 |
+
|
| 716 |
+
<Transition name="lightbox">
|
| 717 |
+
<div v-if="expandedImage" class="lightbox" role="dialog" aria-modal="true" @click.self="expandedImage = null">
|
| 718 |
+
<div><header><strong>{{ expandedImage.label }}</strong><button type="button" @click="expandedImage = null">×</button></header><img :src="expandedImage.src" :data-source="expandedImage.src" :alt="expandedImage.label" loading="eager" decoding="async" @load="artifactImageLoaded" @error="retryArtifactImage" /><small>Press Escape to close</small></div>
|
| 719 |
+
</div>
|
| 720 |
+
</Transition>
|
| 721 |
+
</div>
|
| 722 |
+
</template>
|
apps/dashboard/src/main.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { createApp } from 'vue'
|
| 2 |
+
import App from './App.vue'
|
| 3 |
+
import './styles.css'
|
| 4 |
+
|
| 5 |
+
createApp(App).mount('#app')
|
apps/dashboard/src/styles.css
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import url('https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Manrope:wght@400;500;600;700;800&display=swap');
|
| 2 |
+
|
| 3 |
+
:root {
|
| 4 |
+
color-scheme: dark;
|
| 5 |
+
font-family: 'Manrope', system-ui, sans-serif;
|
| 6 |
+
--bg: #080b0f;
|
| 7 |
+
--panel: #10151b;
|
| 8 |
+
--panel-2: #151b22;
|
| 9 |
+
--panel-3: #0c1116;
|
| 10 |
+
--line: #252d36;
|
| 11 |
+
--line-soft: #1c232b;
|
| 12 |
+
--text: #f5f7f8;
|
| 13 |
+
--muted: #8996a3;
|
| 14 |
+
--muted-2: #b1bbc4;
|
| 15 |
+
--lime: #c9f66b;
|
| 16 |
+
--cyan: #6dd7e8;
|
| 17 |
+
--amber: #f2bc66;
|
| 18 |
+
--red: #ff7676;
|
| 19 |
+
--green: #72dfa5;
|
| 20 |
+
background: var(--bg);
|
| 21 |
+
color: var(--text);
|
| 22 |
+
font-synthesis: none;
|
| 23 |
+
text-rendering: optimizeLegibility;
|
| 24 |
+
}
|
| 25 |
+
* { box-sizing: border-box; }
|
| 26 |
+
html { min-width: 320px; background: var(--bg); }
|
| 27 |
+
body { min-width: 320px; min-height: 100vh; margin: 0; background:
|
| 28 |
+
radial-gradient(circle at 20% -10%, rgba(109, 215, 232, .09), transparent 28rem),
|
| 29 |
+
radial-gradient(circle at 85% 10%, rgba(201, 246, 107, .055), transparent 24rem),
|
| 30 |
+
var(--bg); }
|
| 31 |
+
button, input, select { font: inherit; }
|
| 32 |
+
button, a { -webkit-tap-highlight-color: transparent; }
|
| 33 |
+
button { color: inherit; }
|
| 34 |
+
a { color: inherit; }
|
| 35 |
+
#app, .app { min-height: 100vh; }
|
| 36 |
+
|
| 37 |
+
.app-header {
|
| 38 |
+
position: sticky;
|
| 39 |
+
top: 0;
|
| 40 |
+
z-index: 20;
|
| 41 |
+
height: 72px;
|
| 42 |
+
display: flex;
|
| 43 |
+
align-items: center;
|
| 44 |
+
justify-content: space-between;
|
| 45 |
+
padding: 0 max(24px, calc((100vw - 1500px) / 2));
|
| 46 |
+
border-bottom: 1px solid rgba(71, 82, 94, .55);
|
| 47 |
+
background: rgba(8, 11, 15, .88);
|
| 48 |
+
backdrop-filter: blur(18px);
|
| 49 |
+
}
|
| 50 |
+
.brand {
|
| 51 |
+
display: flex;
|
| 52 |
+
align-items: center;
|
| 53 |
+
gap: 13px;
|
| 54 |
+
padding: 0;
|
| 55 |
+
border: 0;
|
| 56 |
+
background: transparent;
|
| 57 |
+
cursor: pointer;
|
| 58 |
+
text-align: left;
|
| 59 |
+
}
|
| 60 |
+
.brand > span:last-child { display: grid; gap: 2px; }
|
| 61 |
+
.brand strong { font-size: 14px; font-weight: 800; letter-spacing: -.01em; }
|
| 62 |
+
.brand small { color: var(--muted); font-size: 10px; }
|
| 63 |
+
.brand-cube { position: relative; width: 27px; height: 27px; display: block; transform: rotate(30deg); }
|
| 64 |
+
.brand-cube i { position: absolute; display: block; border: 1.5px solid var(--lime); background: rgba(201, 246, 107, .055); }
|
| 65 |
+
.brand-cube i:nth-child(1) { inset: 4px 9px 4px 1px; }
|
| 66 |
+
.brand-cube i:nth-child(2) { inset: 4px 1px 4px 17px; }
|
| 67 |
+
.brand-cube i:nth-child(3) { inset: 1px 5px 16px; }
|
| 68 |
+
.header-actions { display: flex; align-items: center; gap: 16px; }
|
| 69 |
+
.refresh-time { color: var(--muted); font-size: 10px; }
|
| 70 |
+
.live-indicator, .status-chip, .row-status {
|
| 71 |
+
display: inline-flex;
|
| 72 |
+
align-items: center;
|
| 73 |
+
gap: 7px;
|
| 74 |
+
color: var(--muted-2);
|
| 75 |
+
font-size: 10px;
|
| 76 |
+
font-weight: 700;
|
| 77 |
+
text-transform: capitalize;
|
| 78 |
+
}
|
| 79 |
+
.live-indicator i, .status-chip i, .row-status i, .state-dot {
|
| 80 |
+
width: 7px;
|
| 81 |
+
height: 7px;
|
| 82 |
+
display: inline-block;
|
| 83 |
+
flex: none;
|
| 84 |
+
border-radius: 50%;
|
| 85 |
+
background: #6d7883;
|
| 86 |
+
}
|
| 87 |
+
.live-indicator i, .live i, .state-dot.live { background: var(--cyan); box-shadow: 0 0 0 4px rgba(109, 215, 232, .1); }
|
| 88 |
+
.icon-button {
|
| 89 |
+
width: 34px;
|
| 90 |
+
height: 34px;
|
| 91 |
+
display: grid;
|
| 92 |
+
place-items: center;
|
| 93 |
+
padding: 0;
|
| 94 |
+
border: 1px solid var(--line);
|
| 95 |
+
border-radius: 9px;
|
| 96 |
+
background: var(--panel);
|
| 97 |
+
cursor: pointer;
|
| 98 |
+
font-size: 18px;
|
| 99 |
+
}
|
| 100 |
+
.icon-button:hover { border-color: #46535f; background: var(--panel-2); }
|
| 101 |
+
|
| 102 |
+
.page { width: min(1500px, calc(100% - 48px)); margin: 0 auto; padding: 46px 0 80px; }
|
| 103 |
+
.error-banner { margin-bottom: 20px; padding: 13px 15px; border: 1px solid rgba(255, 118, 118, .35); border-radius: 10px; background: rgba(255, 118, 118, .08); color: #ffaaaa; font-size: 12px; }
|
| 104 |
+
|
| 105 |
+
.page-heading { display: flex; align-items: flex-end; justify-content: space-between; gap: 40px; margin: 18px 0 40px; }
|
| 106 |
+
.kicker { margin: 0 0 9px; color: var(--lime); font: 500 9px 'DM Mono', monospace; letter-spacing: .16em; }
|
| 107 |
+
.page-heading h1, .detail-heading h1 {
|
| 108 |
+
margin: 0;
|
| 109 |
+
font-size: clamp(38px, 5vw, 68px);
|
| 110 |
+
font-weight: 600;
|
| 111 |
+
letter-spacing: -.055em;
|
| 112 |
+
line-height: 1.01;
|
| 113 |
+
}
|
| 114 |
+
.page-heading h1 span { color: #7e8b96; }
|
| 115 |
+
.heading-copy { max-width: 650px; margin: 20px 0 0; color: var(--muted-2); font-size: 14px; line-height: 1.65; }
|
| 116 |
+
.corpus-badge {
|
| 117 |
+
min-width: 210px;
|
| 118 |
+
padding: 21px 23px;
|
| 119 |
+
border: 1px solid rgba(201, 246, 107, .28);
|
| 120 |
+
border-radius: 14px;
|
| 121 |
+
background: linear-gradient(145deg, rgba(201, 246, 107, .09), rgba(201, 246, 107, .015));
|
| 122 |
+
}
|
| 123 |
+
.corpus-badge span, .corpus-badge small { display: block; color: var(--muted); font-size: 10px; }
|
| 124 |
+
.corpus-badge strong { display: block; margin: 7px 0 3px; font-size: 27px; letter-spacing: -.04em; }
|
| 125 |
+
|
| 126 |
+
.overview-grid { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); margin-bottom: 24px; border: 1px solid var(--line); border-radius: 14px; overflow: hidden; background: var(--panel); }
|
| 127 |
+
.overview-grid article, .detail-metrics article, .task-score-grid article {
|
| 128 |
+
min-width: 0;
|
| 129 |
+
padding: 19px 20px;
|
| 130 |
+
border-right: 1px solid var(--line);
|
| 131 |
+
}
|
| 132 |
+
.overview-grid article:last-child, .detail-metrics article:last-child, .task-score-grid article:last-child { border-right: 0; }
|
| 133 |
+
.overview-grid span, .detail-metrics span, .task-score-grid span, .detail-score span, .detail-score-pair span {
|
| 134 |
+
display: block;
|
| 135 |
+
margin-bottom: 8px;
|
| 136 |
+
color: var(--muted);
|
| 137 |
+
font-size: 9px;
|
| 138 |
+
font-weight: 700;
|
| 139 |
+
letter-spacing: .07em;
|
| 140 |
+
text-transform: uppercase;
|
| 141 |
+
}
|
| 142 |
+
.overview-grid strong, .detail-metrics strong, .task-score-grid strong { display: block; font-size: 25px; font-weight: 650; letter-spacing: -.035em; }
|
| 143 |
+
.overview-grid small, .detail-metrics small, .task-score-grid small, .detail-score small, .detail-score-pair small { display: block; margin-top: 6px; overflow: hidden; color: #778491; font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
|
| 144 |
+
.score-overview { background: linear-gradient(145deg, rgba(201, 246, 107, .07), transparent); }
|
| 145 |
+
.score-overview strong { color: var(--lime); }
|
| 146 |
+
.score-overview strong small { display: inline; margin-left: 3px; color: #788462; font-size: 11px; }
|
| 147 |
+
.score-overview.deterministic-score { background: linear-gradient(145deg, rgba(109, 215, 232, .07), transparent); }
|
| 148 |
+
.score-overview.deterministic-score strong { color: var(--cyan); }
|
| 149 |
+
|
| 150 |
+
.registry-panel, .task-panel, .inspector-panel {
|
| 151 |
+
border: 1px solid var(--line);
|
| 152 |
+
border-radius: 14px;
|
| 153 |
+
background: rgba(16, 21, 27, .9);
|
| 154 |
+
box-shadow: 0 22px 60px rgba(0, 0, 0, .18);
|
| 155 |
+
overflow: hidden;
|
| 156 |
+
}
|
| 157 |
+
.registry-heading { min-height: 76px; display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 16px 20px; border-bottom: 1px solid var(--line); }
|
| 158 |
+
.registry-heading .kicker { margin-bottom: 5px; }
|
| 159 |
+
.registry-heading h2 { margin: 0; font-size: 18px; letter-spacing: -.02em; }
|
| 160 |
+
.registry-heading > span { color: var(--muted); font-size: 10px; }
|
| 161 |
+
|
| 162 |
+
.filter-bar { display: grid; grid-template-columns: minmax(260px, 1fr) 190px 230px; gap: 10px; padding: 13px; border-bottom: 1px solid var(--line); background: var(--panel-3); }
|
| 163 |
+
.filter-bar label { min-width: 0; display: grid; gap: 5px; }
|
| 164 |
+
.filter-bar label > span { padding-left: 2px; color: #6f7b86; font-size: 8px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
|
| 165 |
+
.filter-bar input, .filter-bar select, .turn-controls select {
|
| 166 |
+
width: 100%;
|
| 167 |
+
height: 38px;
|
| 168 |
+
padding: 0 12px;
|
| 169 |
+
border: 1px solid var(--line);
|
| 170 |
+
border-radius: 8px;
|
| 171 |
+
outline: none;
|
| 172 |
+
background: #11171d;
|
| 173 |
+
color: var(--text);
|
| 174 |
+
font-size: 11px;
|
| 175 |
+
}
|
| 176 |
+
.filter-bar input:focus, .filter-bar select:focus, .turn-controls select:focus { border-color: var(--cyan); box-shadow: 0 0 0 3px rgba(109, 215, 232, .08); }
|
| 177 |
+
|
| 178 |
+
.benchmark-list { display: grid; }
|
| 179 |
+
.benchmark-row {
|
| 180 |
+
width: 100%;
|
| 181 |
+
min-height: 87px;
|
| 182 |
+
display: grid;
|
| 183 |
+
grid-template-columns: 92px minmax(280px, 2.2fr) minmax(150px, .9fr) 92px 112px 96px 92px 24px;
|
| 184 |
+
align-items: center;
|
| 185 |
+
gap: 18px;
|
| 186 |
+
padding: 14px 20px;
|
| 187 |
+
border: 0;
|
| 188 |
+
border-bottom: 1px solid var(--line-soft);
|
| 189 |
+
background: transparent;
|
| 190 |
+
cursor: pointer;
|
| 191 |
+
text-align: left;
|
| 192 |
+
transition: background .14s, transform .14s;
|
| 193 |
+
}
|
| 194 |
+
.benchmark-row:last-child { border-bottom: 0; }
|
| 195 |
+
.benchmark-row:hover { background: rgba(109, 215, 232, .035); }
|
| 196 |
+
.benchmark-row:focus-visible { outline: 2px solid var(--cyan); outline-offset: -2px; }
|
| 197 |
+
.row-status.positive i, .status-chip.positive i, .state-dot.positive { background: var(--green); }
|
| 198 |
+
.row-status.negative i, .status-chip.negative i, .state-dot.negative { background: var(--red); }
|
| 199 |
+
.row-status.warning i, .status-chip.warning i, .state-dot.warning { background: var(--amber); }
|
| 200 |
+
.row-identity { min-width: 0; }
|
| 201 |
+
.row-identity strong { display: block; overflow: hidden; font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
|
| 202 |
+
.row-identity small, .row-result small, .row-coverage small, .row-cost small { display: block; margin-top: 6px; overflow: hidden; color: var(--muted); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
|
| 203 |
+
.row-progress > span { display: block; margin-bottom: 8px; color: var(--muted-2); font-size: 9px; }
|
| 204 |
+
.row-progress > span strong { color: var(--text); }
|
| 205 |
+
.row-progress > i { height: 4px; display: block; overflow: hidden; border-radius: 9px; background: #252d35; }
|
| 206 |
+
.row-progress > i b { height: 100%; display: block; border-radius: inherit; background: linear-gradient(90deg, var(--cyan), var(--lime)); }
|
| 207 |
+
.row-result strong, .row-coverage strong, .row-cost strong { display: block; font-size: 14px; font-weight: 650; }
|
| 208 |
+
.row-result strong { color: var(--lime); }
|
| 209 |
+
.row-result strong { font: 650 17px 'DM Mono', monospace; }
|
| 210 |
+
.row-deterministic-score strong { color: var(--cyan); }
|
| 211 |
+
.row-arrow { color: #596671; font-size: 18px; transition: transform .14s, color .14s; }
|
| 212 |
+
.benchmark-row:hover .row-arrow, .task-row:hover .row-arrow { color: var(--cyan); transform: translateX(3px); }
|
| 213 |
+
|
| 214 |
+
.empty-panel { min-height: 150px; display: grid; place-content: center; gap: 7px; padding: 30px; color: var(--muted); font-size: 12px; text-align: center; }
|
| 215 |
+
.empty-panel strong { color: var(--text); font-size: 14px; }
|
| 216 |
+
.back-button { margin-bottom: 22px; padding: 7px 0; border: 0; background: transparent; color: var(--muted-2); cursor: pointer; font-size: 11px; }
|
| 217 |
+
.back-button:hover { color: var(--lime); }
|
| 218 |
+
|
| 219 |
+
.detail-heading { display: flex; align-items: flex-end; justify-content: space-between; gap: 40px; margin-bottom: 30px; }
|
| 220 |
+
.detail-heading h1 { max-width: 950px; margin-top: 14px; font-size: clamp(32px, 4vw, 55px); }
|
| 221 |
+
.detail-heading p { margin: 14px 0 0; color: var(--muted); font-size: 12px; }
|
| 222 |
+
.detail-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; color: var(--muted); font: 9px 'DM Mono', monospace; }
|
| 223 |
+
.status-chip { padding: 6px 9px; border: 1px solid var(--line); border-radius: 20px; background: var(--panel); }
|
| 224 |
+
.detail-score { min-width: 190px; padding: 18px 20px; border-left: 1px solid var(--line); }
|
| 225 |
+
.detail-score strong { display: block; color: var(--lime); font-size: 45px; font-weight: 600; letter-spacing: -.06em; line-height: 1; }
|
| 226 |
+
.detail-score-pair { min-width: 370px; display: grid; grid-template-columns: 1fr 1fr; overflow: hidden; border: 1px solid var(--line); border-radius: 12px; background: var(--panel); }
|
| 227 |
+
.detail-score-pair > div { min-width: 0; padding: 17px 18px; }
|
| 228 |
+
.detail-score-pair > div + div { border-left: 1px solid var(--line); }
|
| 229 |
+
.detail-score-pair strong { display: block; color: var(--lime); font-size: 35px; font-weight: 600; letter-spacing: -.06em; line-height: 1; }
|
| 230 |
+
.detail-score-pair .deterministic-score strong { color: var(--cyan); }
|
| 231 |
+
|
| 232 |
+
.detail-metrics { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); margin-bottom: 12px; border: 1px solid var(--line); border-radius: 14px; overflow: hidden; background: var(--panel); }
|
| 233 |
+
.detail-metrics article { padding: 17px 16px; }
|
| 234 |
+
.detail-metrics strong { font-size: 20px; }
|
| 235 |
+
.coverage-strip { min-height: 60px; display: flex; align-items: center; gap: 8px; margin-bottom: 24px; padding: 10px 13px; border: 1px solid var(--line); border-radius: 12px; background: var(--panel-3); }
|
| 236 |
+
.coverage-strip > div { min-width: 145px; padding: 0 8px; }
|
| 237 |
+
.coverage-strip > div span { display: block; color: var(--muted); font-size: 8px; text-transform: uppercase; }
|
| 238 |
+
.coverage-strip > div strong { display: block; margin-top: 3px; font-size: 12px; }
|
| 239 |
+
.coverage-chip { padding: 7px 9px; border: 1px solid var(--line); border-radius: 7px; color: var(--muted-2); font-size: 9px; text-transform: capitalize; }
|
| 240 |
+
.coverage-chip.positive { border-color: rgba(114, 223, 165, .26); color: var(--green); }
|
| 241 |
+
.coverage-chip.warning { border-color: rgba(242, 188, 102, .28); color: var(--amber); }
|
| 242 |
+
.coverage-chip.negative { border-color: rgba(255, 118, 118, .26); color: var(--red); }
|
| 243 |
+
.coverage-chip.live { border-color: rgba(109, 215, 232, .26); color: var(--cyan); }
|
| 244 |
+
.corpus-split { margin-left: auto; padding: 0 8px; color: var(--muted); font: 9px 'DM Mono', monospace; }
|
| 245 |
+
|
| 246 |
+
.task-panel { margin-bottom: 24px; }
|
| 247 |
+
.task-filters { grid-template-columns: minmax(230px, 1fr) 150px 170px 190px; }
|
| 248 |
+
.task-table-head, .task-row {
|
| 249 |
+
display: grid;
|
| 250 |
+
grid-template-columns: minmax(160px, 1.3fr) 100px 140px 76px 96px 105px 62px 72px 72px 20px;
|
| 251 |
+
align-items: center;
|
| 252 |
+
gap: 14px;
|
| 253 |
+
}
|
| 254 |
+
.task-table-head { min-height: 38px; padding: 0 17px; border-bottom: 1px solid var(--line); background: #0d1217; color: #697681; font-size: 8px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
|
| 255 |
+
.task-row {
|
| 256 |
+
width: 100%;
|
| 257 |
+
min-height: 67px;
|
| 258 |
+
padding: 10px 17px;
|
| 259 |
+
border: 0;
|
| 260 |
+
border-bottom: 1px solid var(--line-soft);
|
| 261 |
+
background: transparent;
|
| 262 |
+
cursor: pointer;
|
| 263 |
+
color: var(--muted-2);
|
| 264 |
+
font-size: 10px;
|
| 265 |
+
text-align: left;
|
| 266 |
+
}
|
| 267 |
+
.task-row:last-child { border-bottom: 0; }
|
| 268 |
+
.task-row:hover, .task-row.selected { background: rgba(109, 215, 232, .04); }
|
| 269 |
+
.task-row.selected { box-shadow: inset 3px 0 var(--cyan); }
|
| 270 |
+
.task-row.disabled { cursor: default; opacity: .52; }
|
| 271 |
+
.task-row > span { min-width: 0; }
|
| 272 |
+
.task-identity strong, .task-row > span > strong { display: block; color: var(--text); font-size: 10px; }
|
| 273 |
+
.task-identity small, .task-row > span > small { display: block; margin-top: 4px; color: var(--muted); font-size: 8px; text-transform: capitalize; }
|
| 274 |
+
.task-identity small b { margin-right: 4px; color: var(--cyan); }
|
| 275 |
+
.state-dot { margin-right: 7px; vertical-align: 1px; }
|
| 276 |
+
.outcome { color: var(--muted-2) !important; text-transform: capitalize; }
|
| 277 |
+
.outcome.positive, .task-score-grid strong.positive { color: var(--green) !important; }
|
| 278 |
+
.outcome.warning, .task-score-grid strong.warning { color: var(--amber) !important; }
|
| 279 |
+
.outcome.negative, .task-score-grid strong.negative { color: var(--red) !important; }
|
| 280 |
+
.outcome.live, .task-score-grid strong.live { color: var(--cyan) !important; }
|
| 281 |
+
.numeric-score { color: var(--lime); font: 650 17px 'DM Mono', monospace; letter-spacing: -.03em; }
|
| 282 |
+
.numeric-score.deterministic-score { color: var(--cyan); }
|
| 283 |
+
.component-mini { display: flex; gap: 6px; }
|
| 284 |
+
.component-mini small { margin: 0 !important; font-family: 'DM Mono', monospace; }
|
| 285 |
+
|
| 286 |
+
.inspector-panel { scroll-margin-top: 88px; }
|
| 287 |
+
.inspector-heading { min-height: 90px; display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 17px 20px; border-bottom: 1px solid var(--line); }
|
| 288 |
+
.inspector-heading .kicker { margin-bottom: 5px; }
|
| 289 |
+
.inspector-heading h2 { margin: 0; font-size: 22px; }
|
| 290 |
+
.inspector-heading p { margin: 6px 0 0; color: var(--muted); font-size: 10px; text-transform: capitalize; }
|
| 291 |
+
.inspector-actions { display: flex; align-items: center; gap: 8px; }
|
| 292 |
+
.button { min-height: 34px; display: inline-flex; align-items: center; justify-content: center; padding: 0 13px; border: 1px solid var(--line); border-radius: 8px; text-decoration: none; font-size: 10px; font-weight: 700; }
|
| 293 |
+
.button:hover { border-color: var(--cyan); }
|
| 294 |
+
.task-score-grid { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); border-bottom: 1px solid var(--line); background: var(--panel-3); }
|
| 295 |
+
.task-score-grid strong { font-size: 18px; text-transform: capitalize; }
|
| 296 |
+
.judge-results { display: grid; grid-template-columns: 1fr 1fr; border-bottom: 1px solid var(--line); background: var(--panel-3); }
|
| 297 |
+
.judge-result { min-width: 0; }
|
| 298 |
+
.judge-result + .judge-result { border-left: 1px solid var(--line); }
|
| 299 |
+
.judge-result > header { min-height: 78px; display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 15px 19px; border-bottom: 1px solid var(--line-soft); }
|
| 300 |
+
.judge-result > header span { display: block; margin-bottom: 6px; color: var(--muted); font-size: 8px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
|
| 301 |
+
.judge-result > header > div:first-child strong { font-size: 13px; }
|
| 302 |
+
.judge-total { display: flex; align-items: baseline; gap: 4px; }
|
| 303 |
+
.judge-total strong { color: var(--lime); font-size: 32px; letter-spacing: -.05em; }
|
| 304 |
+
.deterministic-result .judge-total strong { color: var(--cyan); }
|
| 305 |
+
.judge-total small { color: var(--muted); font-size: 9px; }
|
| 306 |
+
.judge-result-body { padding: 14px 19px 16px; }
|
| 307 |
+
.judge-result-body > p { margin: 0 0 12px; color: var(--muted-2); font-size: 10px; font-weight: 700; text-transform: capitalize; }
|
| 308 |
+
.judge-result-body > p.positive { color: var(--green); }
|
| 309 |
+
.judge-result-body > p.warning { color: var(--amber); }
|
| 310 |
+
.judge-result-body > p.negative { color: var(--red); }
|
| 311 |
+
.judge-result-body > small { display: block; margin-top: 12px; color: var(--muted); font: 9px 'DM Mono', monospace; }
|
| 312 |
+
.judge-components { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); overflow: hidden; border: 1px solid var(--line-soft); border-radius: 8px; background: #0b1015; }
|
| 313 |
+
.judge-components > div { min-width: 0; padding: 10px; border-right: 1px solid var(--line-soft); }
|
| 314 |
+
.judge-components > div:last-child { border-right: 0; }
|
| 315 |
+
.judge-components span { display: block; overflow: hidden; color: var(--muted); font-size: 7px; font-weight: 700; letter-spacing: .05em; text-overflow: ellipsis; text-transform: uppercase; white-space: nowrap; }
|
| 316 |
+
.judge-components strong { display: block; margin-top: 5px; font: 650 14px 'DM Mono', monospace; }
|
| 317 |
+
.judge-unavailable { min-height: 92px; display: grid; place-items: center; padding: 20px; color: var(--muted); font-size: 10px; }
|
| 318 |
+
|
| 319 |
+
.artifact-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; border-bottom: 1px solid var(--line); background: var(--line); }
|
| 320 |
+
.artifact-grid figure, .turn-frame-grid figure { min-width: 0; margin: 0; background: #090d11; }
|
| 321 |
+
.artifact-grid figcaption { height: 43px; display: flex; align-items: center; justify-content: space-between; padding: 0 12px; border-bottom: 1px solid var(--line-soft); font-size: 9px; }
|
| 322 |
+
.artifact-grid figcaption small { color: var(--muted); }
|
| 323 |
+
.artifact-grid figure > button, .turn-frame-grid figure > button { position: relative; width: 100%; height: 310px; display: block; overflow: hidden; padding: 0; border: 0; background: #050709; cursor: zoom-in; }
|
| 324 |
+
.artifact-grid img, .turn-frame-grid img { width: 100%; height: 100%; display: block; object-fit: contain; transition: opacity .18s; }
|
| 325 |
+
.artifact-grid img[data-load-state="retrying"], .turn-frame-grid img[data-load-state="retrying"], .lightbox img[data-load-state="retrying"] { opacity: .3; }
|
| 326 |
+
.artifact-grid img[data-load-state="failed"], .turn-frame-grid img[data-load-state="failed"], .lightbox img[data-load-state="failed"] { opacity: .08; }
|
| 327 |
+
.artifact-grid button:has(img[data-load-state="retrying"])::after, .turn-frame-grid button:has(img[data-load-state="retrying"])::after,
|
| 328 |
+
.artifact-grid button:has(img[data-load-state="failed"])::after, .turn-frame-grid button:has(img[data-load-state="failed"])::after {
|
| 329 |
+
position: absolute;
|
| 330 |
+
inset: 0;
|
| 331 |
+
display: grid;
|
| 332 |
+
place-items: center;
|
| 333 |
+
color: var(--muted-2);
|
| 334 |
+
font: 500 10px 'DM Mono', monospace;
|
| 335 |
+
pointer-events: none;
|
| 336 |
+
}
|
| 337 |
+
.artifact-grid button:has(img[data-load-state="retrying"])::after, .turn-frame-grid button:has(img[data-load-state="retrying"])::after { content: 'Retrying image…'; }
|
| 338 |
+
.artifact-grid button:has(img[data-load-state="failed"])::after, .turn-frame-grid button:has(img[data-load-state="failed"])::after { content: 'Image unavailable · refresh to retry'; color: var(--red); }
|
| 339 |
+
.artifact-missing { height: 310px; display: grid; place-items: center; color: #56616c; font-size: 10px; }
|
| 340 |
+
|
| 341 |
+
.trace-section { background: #0d1217; }
|
| 342 |
+
.trace-section > header { min-height: 72px; display: flex; align-items: center; justify-content: space-between; padding: 13px 17px; border-bottom: 1px solid var(--line); }
|
| 343 |
+
.trace-section > header .kicker { margin-bottom: 4px; }
|
| 344 |
+
.trace-section h3 { margin: 0; font-size: 15px; }
|
| 345 |
+
.turn-controls { display: flex; align-items: center; gap: 6px; }
|
| 346 |
+
.turn-controls button { width: 35px; height: 35px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel); cursor: pointer; }
|
| 347 |
+
.turn-controls button:disabled { opacity: .3; cursor: default; }
|
| 348 |
+
.turn-controls select { width: 180px; height: 35px; }
|
| 349 |
+
.turn-frame-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; border-bottom: 1px solid var(--line); background: var(--line); }
|
| 350 |
+
.turn-frame-grid figcaption { height: 37px; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 0 12px; color: var(--muted-2); font-size: 9px; }
|
| 351 |
+
.turn-frame-grid figcaption small { overflow: hidden; color: var(--muted); font-family: var(--mono); text-overflow: ellipsis; white-space: nowrap; }
|
| 352 |
+
.turn-frame-grid figure > button, .turn-frame-grid .artifact-missing { height: 360px; }
|
| 353 |
+
|
| 354 |
+
.action-list { display: grid; padding: 12px; }
|
| 355 |
+
.action-list article { display: grid; grid-template-columns: 24px minmax(180px, 1fr) minmax(160px, .7fr); align-items: center; gap: 12px; padding: 11px 10px; border-bottom: 1px solid var(--line-soft); }
|
| 356 |
+
.action-list article:last-child { border-bottom: 0; }
|
| 357 |
+
.action-list article > span { width: 22px; height: 22px; display: grid; place-items: center; border: 1px solid var(--line); border-radius: 6px; color: var(--muted); font: 9px 'DM Mono', monospace; }
|
| 358 |
+
.action-list strong { display: block; font-size: 10px; text-transform: capitalize; }
|
| 359 |
+
.action-list p { margin: 4px 0 0; color: var(--muted-2); font-size: 10px; }
|
| 360 |
+
.action-list code { overflow: hidden; color: #74818c; font: 9px 'DM Mono', monospace; text-overflow: ellipsis; white-space: nowrap; }
|
| 361 |
+
.raw-details { border-top: 1px solid var(--line); }
|
| 362 |
+
.raw-details summary { padding: 13px 17px; color: var(--muted-2); cursor: pointer; font-size: 10px; }
|
| 363 |
+
.raw-details > div { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; padding: 0 13px 13px; }
|
| 364 |
+
pre { max-height: 380px; margin: 0; padding: 12px; overflow: auto; border: 1px solid var(--line); border-radius: 8px; background: #070a0d; color: #aebac4; font: 9px/1.55 'DM Mono', monospace; white-space: pre-wrap; word-break: break-word; }
|
| 365 |
+
|
| 366 |
+
.lightbox { position: fixed; inset: 0; z-index: 100; display: grid; place-items: center; padding: 24px; background: rgba(2, 4, 6, .94); backdrop-filter: blur(15px); }
|
| 367 |
+
.lightbox > div { width: min(94vw, 1700px); height: min(92vh, 1100px); display: grid; grid-template-rows: 54px minmax(0, 1fr) 34px; border: 1px solid #394550; border-radius: 12px; overflow: hidden; background: #080c0f; box-shadow: 0 30px 100px rgba(0, 0, 0, .7); }
|
| 368 |
+
.lightbox header { display: flex; align-items: center; justify-content: space-between; padding: 0 15px; border-bottom: 1px solid var(--line); }
|
| 369 |
+
.lightbox header strong { font-size: 11px; }
|
| 370 |
+
.lightbox header button { border: 0; background: transparent; cursor: pointer; font-size: 24px; }
|
| 371 |
+
.lightbox img { width: 100%; height: 100%; min-height: 0; display: block; object-fit: contain; background: #030506; }
|
| 372 |
+
.lightbox small { display: grid; place-items: center; border-top: 1px solid var(--line); color: var(--muted); font-size: 9px; }
|
| 373 |
+
.lightbox-enter-active, .lightbox-leave-active { transition: opacity .16s; }
|
| 374 |
+
.lightbox-enter-from, .lightbox-leave-to { opacity: 0; }
|
| 375 |
+
|
| 376 |
+
@media (max-width: 1200px) {
|
| 377 |
+
.overview-grid { grid-template-columns: repeat(3, 1fr); }
|
| 378 |
+
.overview-grid article:nth-child(3) { border-right: 0; }
|
| 379 |
+
.overview-grid article:nth-child(n+4) { border-top: 1px solid var(--line); }
|
| 380 |
+
.benchmark-row { grid-template-columns: 85px minmax(250px, 2fr) minmax(135px, 1fr) 82px 98px 80px 20px; }
|
| 381 |
+
.row-coverage { display: none; }
|
| 382 |
+
.detail-metrics { grid-template-columns: repeat(3, 1fr); }
|
| 383 |
+
.detail-metrics article:nth-child(3) { border-right: 0; }
|
| 384 |
+
.detail-metrics article:nth-child(n+4) { border-top: 1px solid var(--line); }
|
| 385 |
+
.task-table-head, .task-row { grid-template-columns: minmax(150px, 1.3fr) 90px 130px 68px 80px 85px 60px 70px 18px; }
|
| 386 |
+
.task-table-head > span:nth-child(8), .task-row > span:nth-child(8) { display: none; }
|
| 387 |
+
.task-score-grid { grid-template-columns: repeat(3, 1fr); }
|
| 388 |
+
.task-score-grid article:nth-child(3) { border-right: 0; }
|
| 389 |
+
.task-score-grid article:nth-child(n+4) { border-top: 1px solid var(--line); }
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
@media (max-width: 860px) {
|
| 393 |
+
.app-header { padding: 0 16px; }
|
| 394 |
+
.refresh-time { display: none; }
|
| 395 |
+
.page { width: min(100% - 28px, 720px); padding-top: 28px; }
|
| 396 |
+
.page-heading, .detail-heading { align-items: flex-start; flex-direction: column; }
|
| 397 |
+
.corpus-badge { width: 100%; }
|
| 398 |
+
.overview-grid { grid-template-columns: 1fr 1fr; }
|
| 399 |
+
.overview-grid article { border-top: 1px solid var(--line); }
|
| 400 |
+
.overview-grid article:nth-child(odd) { border-right: 1px solid var(--line); }
|
| 401 |
+
.overview-grid article:nth-child(even) { border-right: 0; }
|
| 402 |
+
.overview-grid article:first-child, .overview-grid article:nth-child(2) { border-top: 0; }
|
| 403 |
+
.filter-bar, .task-filters { grid-template-columns: 1fr 1fr; }
|
| 404 |
+
.search-field { grid-column: 1 / -1; }
|
| 405 |
+
.benchmark-row { grid-template-columns: 82px minmax(0, 1fr) 62px 72px 20px; gap: 12px; }
|
| 406 |
+
.row-progress, .row-coverage, .row-cost { display: none; }
|
| 407 |
+
.detail-score { width: 100%; border-top: 1px solid var(--line); border-left: 0; }
|
| 408 |
+
.detail-score-pair { width: 100%; min-width: 0; }
|
| 409 |
+
.detail-metrics { grid-template-columns: 1fr 1fr; }
|
| 410 |
+
.detail-metrics article { border-top: 1px solid var(--line); }
|
| 411 |
+
.detail-metrics article:nth-child(odd) { border-right: 1px solid var(--line); }
|
| 412 |
+
.detail-metrics article:nth-child(even) { border-right: 0; }
|
| 413 |
+
.detail-metrics article:first-child, .detail-metrics article:nth-child(2) { border-top: 0; }
|
| 414 |
+
.coverage-strip { flex-wrap: wrap; }
|
| 415 |
+
.corpus-split { width: 100%; margin: 5px 0 0; }
|
| 416 |
+
.task-table-head { display: none; }
|
| 417 |
+
.task-row { grid-template-columns: minmax(130px, 1fr) 100px 58px 68px 18px; gap: 10px; padding: 12px 14px; }
|
| 418 |
+
.task-row > span:nth-child(2), .task-row > span:nth-child(6), .task-row > span:nth-child(7), .task-row > span:nth-child(8), .task-row > span:nth-child(9) { display: none; }
|
| 419 |
+
.artifact-grid, .turn-frame-grid { grid-template-columns: 1fr; }
|
| 420 |
+
.judge-results { grid-template-columns: 1fr; }
|
| 421 |
+
.judge-result + .judge-result { border-top: 1px solid var(--line); border-left: 0; }
|
| 422 |
+
.artifact-grid figure > button, .artifact-missing { height: 280px; }
|
| 423 |
+
.turn-frame-grid figure > button, .turn-frame-grid .artifact-missing { height: 280px; }
|
| 424 |
+
.raw-details > div { grid-template-columns: 1fr; }
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
@media (max-width: 540px) {
|
| 428 |
+
.brand small, .live-indicator { display: none; }
|
| 429 |
+
.page-heading h1 { font-size: 38px; }
|
| 430 |
+
.overview-grid { grid-template-columns: 1fr; }
|
| 431 |
+
.overview-grid article { border-right: 0 !important; border-top: 1px solid var(--line) !important; }
|
| 432 |
+
.overview-grid article:first-child { border-top: 0 !important; }
|
| 433 |
+
.filter-bar, .task-filters { grid-template-columns: 1fr; }
|
| 434 |
+
.search-field { grid-column: auto; }
|
| 435 |
+
.benchmark-row { grid-template-columns: 68px minmax(0, 1fr) 52px 60px 18px; gap: 8px; }
|
| 436 |
+
.row-result strong { font-size: 14px; }
|
| 437 |
+
.row-result small { font-size: 7px; }
|
| 438 |
+
.detail-metrics, .task-score-grid { grid-template-columns: 1fr 1fr; }
|
| 439 |
+
.detail-score-pair { grid-template-columns: 1fr; }
|
| 440 |
+
.detail-score-pair > div + div { border-top: 1px solid var(--line); border-left: 0; }
|
| 441 |
+
.judge-components { grid-template-columns: 1fr 1fr; }
|
| 442 |
+
.judge-components > div:nth-child(2) { border-right: 0; }
|
| 443 |
+
.judge-components > div:nth-child(n+3) { border-top: 1px solid var(--line-soft); }
|
| 444 |
+
.detail-metrics article, .task-score-grid article { border-top: 1px solid var(--line); }
|
| 445 |
+
.detail-metrics article:nth-child(odd), .task-score-grid article:nth-child(odd) { border-right: 1px solid var(--line); }
|
| 446 |
+
.detail-metrics article:nth-child(even), .task-score-grid article:nth-child(even) { border-right: 0; }
|
| 447 |
+
.detail-metrics article:first-child, .detail-metrics article:nth-child(2), .task-score-grid article:first-child, .task-score-grid article:nth-child(2) { border-top: 0; }
|
| 448 |
+
.inspector-heading { align-items: flex-start; }
|
| 449 |
+
.action-list article { grid-template-columns: 24px 1fr; }
|
| 450 |
+
.action-list code { grid-column: 2; }
|
| 451 |
+
}
|
apps/dashboard/src/types.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export interface EvaluationScoreSummary {
|
| 2 |
+
source: string | null
|
| 3 |
+
score: number | null
|
| 4 |
+
valid: boolean | null
|
| 5 |
+
failureReason: string | null
|
| 6 |
+
method: string | null
|
| 7 |
+
verdict: string | null
|
| 8 |
+
targetAcquired: boolean | null
|
| 9 |
+
confidence: number | null
|
| 10 |
+
needsHumanReview: boolean
|
| 11 |
+
geometry: number | null
|
| 12 |
+
dimensions: number | null
|
| 13 |
+
annotations: number | null
|
| 14 |
+
presentation: number | null
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
export interface RunSummary {
|
| 18 |
+
id: string
|
| 19 |
+
taskId: string
|
| 20 |
+
attemptId: string | null
|
| 21 |
+
modelId: string
|
| 22 |
+
provider: string
|
| 23 |
+
reasoningEffort: string | null
|
| 24 |
+
status: string
|
| 25 |
+
stopReason: string | null
|
| 26 |
+
failure: string | null
|
| 27 |
+
acceptedActions: number
|
| 28 |
+
invalidActions: number
|
| 29 |
+
screenshots: number
|
| 30 |
+
elapsedSeconds: number | null
|
| 31 |
+
artifactBytes: number
|
| 32 |
+
score: number | null
|
| 33 |
+
valid: boolean | null
|
| 34 |
+
scoreFailure: string | null
|
| 35 |
+
scoreMethod: string | null
|
| 36 |
+
scoreSource: string | null
|
| 37 |
+
verdict: string | null
|
| 38 |
+
targetAcquired: boolean | null
|
| 39 |
+
judgeConfidence: number | null
|
| 40 |
+
needsHumanReview: boolean
|
| 41 |
+
visionScore: EvaluationScoreSummary | null
|
| 42 |
+
deterministicScore: EvaluationScoreSummary | null
|
| 43 |
+
evaluationState: string
|
| 44 |
+
evaluationStage: string | null
|
| 45 |
+
evaluationError: string | null
|
| 46 |
+
updatedAt: string
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
export interface FrameArtifact {
|
| 50 |
+
name: string
|
| 51 |
+
sha256: string | null
|
| 52 |
+
url: string
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
export interface ModelTurn {
|
| 56 |
+
turn: number
|
| 57 |
+
tool_use_id?: string
|
| 58 |
+
action: Record<string, unknown>
|
| 59 |
+
stop_reason: string
|
| 60 |
+
usage: Record<string, number>
|
| 61 |
+
latency_ms: number
|
| 62 |
+
text: string[]
|
| 63 |
+
discarded_tool_uses: number
|
| 64 |
+
model_input?: Record<string, unknown> | null
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
export interface InputReceipt {
|
| 68 |
+
action_index: number
|
| 69 |
+
request_id?: string | null
|
| 70 |
+
idempotent_replay?: boolean
|
| 71 |
+
receipt: Record<string, unknown> | null
|
| 72 |
+
action?: Record<string, unknown> | null
|
| 73 |
+
tool_use_id?: string | null
|
| 74 |
+
batch_index?: number
|
| 75 |
+
batch_size?: number
|
| 76 |
+
batch_start_action_index?: number
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
export interface TraceDetail {
|
| 80 |
+
summary: RunSummary
|
| 81 |
+
state: Record<string, unknown> | null
|
| 82 |
+
result: Record<string, unknown> | null
|
| 83 |
+
score: Record<string, unknown> | null
|
| 84 |
+
scores: {
|
| 85 |
+
vision: Record<string, unknown> | null
|
| 86 |
+
deterministic: Record<string, unknown> | null
|
| 87 |
+
}
|
| 88 |
+
turns: ModelTurn[]
|
| 89 |
+
receipts: InputReceipt[]
|
| 90 |
+
traceIndexed: boolean
|
| 91 |
+
traceNotice: string | null
|
| 92 |
+
frames: FrameArtifact[]
|
| 93 |
+
referenceUrl: string
|
| 94 |
+
artifacts: {
|
| 95 |
+
dwg: string | null
|
| 96 |
+
candidateRender: string | null
|
| 97 |
+
diffRender: string | null
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
export interface BenchmarkUsage {
|
| 102 |
+
inputTokens: number
|
| 103 |
+
cachedInputTokens: number
|
| 104 |
+
outputTokens: number
|
| 105 |
+
reasoningOutputTokens: number
|
| 106 |
+
totalTokens: number
|
| 107 |
+
modelTurns: number
|
| 108 |
+
apiCost: number | null
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
export interface BenchmarkLevelSummary {
|
| 112 |
+
total: number
|
| 113 |
+
completed: number
|
| 114 |
+
scored: number
|
| 115 |
+
meanScore: number | null
|
| 116 |
+
cost: number
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
export interface BenchmarkDrawingTypeSummary {
|
| 120 |
+
total: number
|
| 121 |
+
scored: number
|
| 122 |
+
meanScore: number | null
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
export interface BenchmarkAggregate {
|
| 126 |
+
totalTasks: number
|
| 127 |
+
queuedTasks: number
|
| 128 |
+
activeTasks: number
|
| 129 |
+
settledTasks: number
|
| 130 |
+
completedTasks: number
|
| 131 |
+
recoveredTasks: number
|
| 132 |
+
budgetExhaustedTasks: number
|
| 133 |
+
failedTasks: number
|
| 134 |
+
infrastructureFailedTasks: number
|
| 135 |
+
artifactMissingTasks: number
|
| 136 |
+
artifactTasks: number
|
| 137 |
+
scoredTasks: number
|
| 138 |
+
meanScore: number | null
|
| 139 |
+
visionScoredTasks: number
|
| 140 |
+
visionMeanScore: number | null
|
| 141 |
+
deterministicScoredTasks: number
|
| 142 |
+
deterministicMeanScore: number | null
|
| 143 |
+
acceptedActions: number
|
| 144 |
+
invalidActions: number
|
| 145 |
+
aggregateElapsedSeconds: number
|
| 146 |
+
usage: BenchmarkUsage
|
| 147 |
+
levels: Record<string, BenchmarkLevelSummary>
|
| 148 |
+
drawingTypes: Record<string, BenchmarkDrawingTypeSummary>
|
| 149 |
+
evaluationStates: Record<string, number>
|
| 150 |
+
executionStates: Record<string, number>
|
| 151 |
+
verdicts: Record<string, number>
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
export interface BenchmarkSummary {
|
| 155 |
+
id: string
|
| 156 |
+
batchId: string | null
|
| 157 |
+
name: string
|
| 158 |
+
status: string
|
| 159 |
+
modelId: string
|
| 160 |
+
provider: string
|
| 161 |
+
models: string[]
|
| 162 |
+
providers: string[]
|
| 163 |
+
startedAt: string | null
|
| 164 |
+
updatedAt: string | null
|
| 165 |
+
maxConcurrency: number | null
|
| 166 |
+
aggregate: BenchmarkAggregate
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
export interface BenchmarkTask {
|
| 170 |
+
rolloutId: string
|
| 171 |
+
runId: string
|
| 172 |
+
taskId: string
|
| 173 |
+
level: string
|
| 174 |
+
drawingType: string
|
| 175 |
+
split: string
|
| 176 |
+
displayName: string
|
| 177 |
+
modelId: string
|
| 178 |
+
provider: string
|
| 179 |
+
status: string
|
| 180 |
+
stopReason: string | null
|
| 181 |
+
failure: string | null
|
| 182 |
+
acceptedActions: number
|
| 183 |
+
invalidActions: number
|
| 184 |
+
elapsedSeconds: number | null
|
| 185 |
+
setupElapsedSeconds: number | null
|
| 186 |
+
rolloutElapsedSeconds: number | null
|
| 187 |
+
artifactBytes: number
|
| 188 |
+
score: number | null
|
| 189 |
+
valid: boolean | null
|
| 190 |
+
scoreFailure: string | null
|
| 191 |
+
scoreMethod: string | null
|
| 192 |
+
scoreSource: string | null
|
| 193 |
+
verdict: string | null
|
| 194 |
+
targetAcquired: boolean | null
|
| 195 |
+
judgeConfidence: number | null
|
| 196 |
+
needsHumanReview: boolean
|
| 197 |
+
visionScore: EvaluationScoreSummary | null
|
| 198 |
+
deterministicScore: EvaluationScoreSummary | null
|
| 199 |
+
geometrySimilarity: number | null
|
| 200 |
+
dimensionSimilarity: number | null
|
| 201 |
+
annotationSimilarity: number | null
|
| 202 |
+
visualSimilarity: number | null
|
| 203 |
+
evaluationState: string
|
| 204 |
+
evaluationStage: string | null
|
| 205 |
+
evaluationError: string | null
|
| 206 |
+
startedAt: string | null
|
| 207 |
+
finishedAt: string | null
|
| 208 |
+
updatedAt: string | null
|
| 209 |
+
usage: BenchmarkUsage
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
export interface BenchmarkDetail extends BenchmarkSummary {
|
| 213 |
+
finishedAt: string | null
|
| 214 |
+
pricing: {
|
| 215 |
+
currency?: string
|
| 216 |
+
input_per_million: number
|
| 217 |
+
cached_input_per_million: number
|
| 218 |
+
output_per_million: number
|
| 219 |
+
source_url?: string | null
|
| 220 |
+
effective_date?: string | null
|
| 221 |
+
} | null
|
| 222 |
+
tasks: BenchmarkTask[]
|
| 223 |
+
}
|
apps/dashboard/tests/cloud-metadata.test.mjs
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import assert from 'node:assert/strict'
|
| 2 |
+
import fs from 'node:fs'
|
| 3 |
+
import os from 'node:os'
|
| 4 |
+
import path from 'node:path'
|
| 5 |
+
import test from 'node:test'
|
| 6 |
+
|
| 7 |
+
import {
|
| 8 |
+
benchmarkMetadataPaths,
|
| 9 |
+
shouldPromoteMetadata,
|
| 10 |
+
traceMetadataPaths,
|
| 11 |
+
} from '../cloud-metadata.mjs'
|
| 12 |
+
|
| 13 |
+
function writeJson(filePath, value) {
|
| 14 |
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
| 15 |
+
fs.writeFileSync(filePath, JSON.stringify(value))
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
test('plans direct benchmark metadata reads without wildcard prefix scans', () => {
|
| 19 |
+
const paths = benchmarkMetadataPaths({
|
| 20 |
+
rollouts: {
|
| 21 |
+
'task-001': {},
|
| 22 |
+
'task-022': {},
|
| 23 |
+
},
|
| 24 |
+
})
|
| 25 |
+
|
| 26 |
+
assert.equal(paths[0], 'batch-state.json')
|
| 27 |
+
assert.equal(paths.length, 11)
|
| 28 |
+
assert(paths.includes('rollouts/task-001/run-state.json'))
|
| 29 |
+
assert(paths.includes('rollouts/task-022/evaluation/vision-judge/score.json'))
|
| 30 |
+
assert.equal(paths.some((value) => value.includes('*')), false)
|
| 31 |
+
})
|
| 32 |
+
|
| 33 |
+
test('plans the compact trace metadata set without frame downloads', () => {
|
| 34 |
+
const paths = traceMetadataPaths()
|
| 35 |
+
|
| 36 |
+
assert(paths.includes('model_turn_summaries.jsonl'))
|
| 37 |
+
assert(paths.includes('input_receipts.jsonl'))
|
| 38 |
+
assert.equal(paths.some((value) => value.startsWith('frames/')), false)
|
| 39 |
+
})
|
| 40 |
+
|
| 41 |
+
test('shared V3 result cannot overwrite a local V4 rescore at the same timestamp', () => {
|
| 42 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-dashboard-version-'))
|
| 43 |
+
const source = path.join(root, 'source', 'result.json')
|
| 44 |
+
const destination = path.join(root, 'destination', 'result.json')
|
| 45 |
+
writeJson(source, {
|
| 46 |
+
updated_at: '2026-07-19T00:00:00Z',
|
| 47 |
+
evaluation: { evaluator_version: 'autocad-2019-r23.0.46-v3' },
|
| 48 |
+
})
|
| 49 |
+
writeJson(destination, {
|
| 50 |
+
updated_at: '2026-07-19T00:00:00Z',
|
| 51 |
+
evaluation: { evaluator_version: 'autocad-2019-r23.0.46-v4' },
|
| 52 |
+
})
|
| 53 |
+
|
| 54 |
+
assert.equal(shouldPromoteMetadata(source, destination), false)
|
| 55 |
+
})
|
| 56 |
+
|
| 57 |
+
test('nested vision artifact inherits the evaluator version from status', () => {
|
| 58 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-dashboard-vision-'))
|
| 59 |
+
const source = path.join(root, 'source', 'evaluation', 'vision-judge', 'score.json')
|
| 60 |
+
const destination = path.join(
|
| 61 |
+
root,
|
| 62 |
+
'destination',
|
| 63 |
+
'evaluation',
|
| 64 |
+
'vision-judge',
|
| 65 |
+
'score.json',
|
| 66 |
+
)
|
| 67 |
+
writeJson(source, { overall_score: 0.2 })
|
| 68 |
+
writeJson(path.join(root, 'source', 'evaluation', 'status.json'), {
|
| 69 |
+
evaluator_version: 'autocad-2019-r23.0.46-v3',
|
| 70 |
+
})
|
| 71 |
+
writeJson(destination, { overall_score: 0.8 })
|
| 72 |
+
writeJson(path.join(root, 'destination', 'evaluation', 'status.json'), {
|
| 73 |
+
evaluator_version: 'autocad-2019-r23.0.46-v4',
|
| 74 |
+
})
|
| 75 |
+
|
| 76 |
+
assert.equal(shouldPromoteMetadata(source, destination), false)
|
| 77 |
+
})
|
| 78 |
+
|
| 79 |
+
test('a higher evaluator version is promoted even with an older timestamp', () => {
|
| 80 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-dashboard-upgrade-'))
|
| 81 |
+
const source = path.join(root, 'source', 'evaluation', 'status.json')
|
| 82 |
+
const destination = path.join(root, 'destination', 'evaluation', 'status.json')
|
| 83 |
+
writeJson(source, {
|
| 84 |
+
updated_at: '2026-07-18T00:00:00Z',
|
| 85 |
+
evaluator_version: 'autocad-2019-r23.0.46-v4',
|
| 86 |
+
})
|
| 87 |
+
writeJson(destination, {
|
| 88 |
+
updated_at: '2026-07-19T00:00:00Z',
|
| 89 |
+
evaluator_version: 'autocad-2019-r23.0.46-v3',
|
| 90 |
+
})
|
| 91 |
+
|
| 92 |
+
assert.equal(shouldPromoteMetadata(source, destination), true)
|
| 93 |
+
})
|
| 94 |
+
|
| 95 |
+
test('an earlier snapshot from the same rescore job cannot replace the local result', () => {
|
| 96 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-dashboard-rescore-'))
|
| 97 |
+
const source = path.join(root, 'source', 'result.json')
|
| 98 |
+
const destination = path.join(root, 'destination', 'result.json')
|
| 99 |
+
writeJson(source, {
|
| 100 |
+
evaluation: {
|
| 101 |
+
evaluator_version: 'autocad-2019-r23.0.46-v4',
|
| 102 |
+
rescore_job_id: 'rescore-v4-test',
|
| 103 |
+
status: 'completed',
|
| 104 |
+
vision_score: 0.4,
|
| 105 |
+
},
|
| 106 |
+
})
|
| 107 |
+
writeJson(destination, {
|
| 108 |
+
evaluation: {
|
| 109 |
+
evaluator_version: 'autocad-2019-r23.0.46-v4',
|
| 110 |
+
rescore_job_id: 'rescore-v4-test',
|
| 111 |
+
status: 'completed',
|
| 112 |
+
vision_score: 0.8,
|
| 113 |
+
},
|
| 114 |
+
})
|
| 115 |
+
|
| 116 |
+
assert.equal(shouldPromoteMetadata(source, destination), false)
|
| 117 |
+
})
|
apps/dashboard/tests/trace-api.test.mjs
ADDED
|
@@ -0,0 +1,872 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import assert from 'node:assert/strict'
|
| 2 |
+
import { createHash } from 'node:crypto'
|
| 3 |
+
import fs from 'node:fs'
|
| 4 |
+
import os from 'node:os'
|
| 5 |
+
import path from 'node:path'
|
| 6 |
+
import { once } from 'node:events'
|
| 7 |
+
import { Writable } from 'node:stream'
|
| 8 |
+
import test from 'node:test'
|
| 9 |
+
|
| 10 |
+
import { _test, createTraceApi } from '../trace-api.mjs'
|
| 11 |
+
|
| 12 |
+
class TestResponse extends Writable {
|
| 13 |
+
constructor() {
|
| 14 |
+
super()
|
| 15 |
+
this.headers = {}
|
| 16 |
+
this.body = []
|
| 17 |
+
this.statusCode = 0
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
setHeader(name, value) {
|
| 21 |
+
this.headers[name] = value
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
_write(chunk, _encoding, callback) {
|
| 25 |
+
this.body.push(Buffer.from(chunk))
|
| 26 |
+
callback()
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
test('reads complete JSONL rows while a writer is appending a partial row', () => {
|
| 31 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-jsonl-'))
|
| 32 |
+
const file = path.join(root, 'events.json')
|
| 33 |
+
fs.writeFileSync(file, '{"actionIndex":1}\n{"actionIndex":2')
|
| 34 |
+
assert.deepEqual(_test.readJsonl(file), [{ actionIndex: 1 }])
|
| 35 |
+
})
|
| 36 |
+
|
| 37 |
+
test('rejects run and artifact path traversal', () => {
|
| 38 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-safe-'))
|
| 39 |
+
const run = path.join(root, 'run-001')
|
| 40 |
+
fs.mkdirSync(run)
|
| 41 |
+
assert.equal(_test.safeRunDirectory(root, '../outside'), null)
|
| 42 |
+
assert.equal(_test.safeRunDirectory(root, 'run-001'), run)
|
| 43 |
+
assert.equal(_test.safeRunDirectory(root, 'batch/rollouts/run-001'), path.join(root, 'batch', 'rollouts', 'run-001'))
|
| 44 |
+
assert.equal(_test.safeRunDirectory(root, 'batch//run-001'), null)
|
| 45 |
+
assert.equal(_test.safeArtifact(run, '../../secret'), null)
|
| 46 |
+
assert.equal(_test.safeArtifact(run, 'frames/frame_0000.png'), path.join(run, 'frames', 'frame_0000.png'))
|
| 47 |
+
})
|
| 48 |
+
|
| 49 |
+
test('recursively indexes batch rollout directories', () => {
|
| 50 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-batch-'))
|
| 51 |
+
const rollout = path.join(root, 'batch-001', 'rollouts', 'model-a')
|
| 52 |
+
fs.mkdirSync(rollout, { recursive: true })
|
| 53 |
+
fs.writeFileSync(path.join(rollout, 'run-state.json'), JSON.stringify({
|
| 54 |
+
task_id: 'task-001',
|
| 55 |
+
model_id: 'model-a',
|
| 56 |
+
provider: 'openai',
|
| 57 |
+
state: 'running',
|
| 58 |
+
updated_at: '2026-07-16T00:00:00Z',
|
| 59 |
+
}))
|
| 60 |
+
|
| 61 |
+
const runs = _test.listRuns(root)
|
| 62 |
+
|
| 63 |
+
assert.equal(runs.length, 1)
|
| 64 |
+
assert.equal(runs[0].id, 'batch-001/rollouts/model-a')
|
| 65 |
+
assert.equal(runs[0].status, 'running')
|
| 66 |
+
})
|
| 67 |
+
|
| 68 |
+
test('indexes an unsuccessful wall-time rollout as budget exhausted', () => {
|
| 69 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-run-'))
|
| 70 |
+
const run = path.join(root, 'gpt-run')
|
| 71 |
+
fs.mkdirSync(run)
|
| 72 |
+
fs.writeFileSync(path.join(run, 'result.json'), JSON.stringify({
|
| 73 |
+
task_id: 'task-001',
|
| 74 |
+
model_id: 'gpt-5.6-sol',
|
| 75 |
+
provider: 'openai',
|
| 76 |
+
reasoning_effort: 'xhigh',
|
| 77 |
+
completed: false,
|
| 78 |
+
stop_reason: 'wall_timeout',
|
| 79 |
+
accepted_actions: 40,
|
| 80 |
+
invalid_actions: 0,
|
| 81 |
+
screenshots: 41,
|
| 82 |
+
artifact_bytes: 0,
|
| 83 |
+
}))
|
| 84 |
+
const summary = _test.runSummary(root, 'gpt-run')
|
| 85 |
+
assert.equal(summary.status, 'budget_exhausted')
|
| 86 |
+
assert.equal(summary.stopReason, 'wall_timeout')
|
| 87 |
+
assert.equal(summary.acceptedActions, 40)
|
| 88 |
+
assert.equal(summary.evaluationState, 'no_artifact')
|
| 89 |
+
})
|
| 90 |
+
|
| 91 |
+
test('prefers the versioned vision judgment over the legacy deterministic score', () => {
|
| 92 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-vision-score-'))
|
| 93 |
+
const run = path.join(root, 'vision-run')
|
| 94 |
+
fs.mkdirSync(path.join(run, 'evaluation', 'vision-judge'), { recursive: true })
|
| 95 |
+
fs.writeFileSync(path.join(run, 'run-state.json'), JSON.stringify({
|
| 96 |
+
task_id: 'task-001',
|
| 97 |
+
model_id: 'gpt-5.6-sol',
|
| 98 |
+
provider: 'openai',
|
| 99 |
+
state: 'completed',
|
| 100 |
+
}))
|
| 101 |
+
fs.writeFileSync(path.join(run, 'evaluation', 'score.json'), JSON.stringify({
|
| 102 |
+
score: 0.52,
|
| 103 |
+
valid: true,
|
| 104 |
+
scoring_method: 'vector-semantic-v2',
|
| 105 |
+
}))
|
| 106 |
+
fs.writeFileSync(
|
| 107 |
+
path.join(run, 'evaluation', 'vision-judge', 'score.json'),
|
| 108 |
+
JSON.stringify({
|
| 109 |
+
prompt_version: 'autocad-vision-judge-v1',
|
| 110 |
+
overall_score: 0.93,
|
| 111 |
+
target_acquired: true,
|
| 112 |
+
confidence: 0.91,
|
| 113 |
+
needs_human_review: false,
|
| 114 |
+
component_scores: {
|
| 115 |
+
geometry: 0.98,
|
| 116 |
+
dimensions: 0.96,
|
| 117 |
+
annotations: 0.75,
|
| 118 |
+
presentation: 0.70,
|
| 119 |
+
},
|
| 120 |
+
}),
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
const summary = _test.runSummary(root, 'vision-run')
|
| 124 |
+
const detail = _test.traceDetail(root, 'vision-run')
|
| 125 |
+
|
| 126 |
+
assert.equal(summary.score, 0.93)
|
| 127 |
+
assert.equal(summary.scoreMethod, 'vision:autocad-vision-judge-v1')
|
| 128 |
+
assert.equal(summary.scoreSource, 'vision_judge')
|
| 129 |
+
assert.equal(summary.visionScore.score, 0.93)
|
| 130 |
+
assert.equal(summary.visionScore.geometry, 0.98)
|
| 131 |
+
assert.equal(summary.deterministicScore.score, 0.52)
|
| 132 |
+
assert.equal(summary.deterministicScore.method, 'vector-semantic-v2')
|
| 133 |
+
assert.equal(summary.targetAcquired, true)
|
| 134 |
+
assert.equal(summary.evaluationState, 'scored')
|
| 135 |
+
assert.equal(detail.score.geometry_similarity, 0.98)
|
| 136 |
+
assert.equal(detail.score.score_source, 'vision_judge')
|
| 137 |
+
assert.equal(detail.scores.vision.overall_score, 0.93)
|
| 138 |
+
assert.equal(detail.scores.deterministic.score, 0.52)
|
| 139 |
+
})
|
| 140 |
+
|
| 141 |
+
test('shows automatic evaluation progress immediately after model completion', () => {
|
| 142 |
+
const heartbeat = new Date().toISOString()
|
| 143 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-evaluating-'))
|
| 144 |
+
const run = path.join(root, 'evaluating-run')
|
| 145 |
+
fs.mkdirSync(path.join(run, 'evaluation'), { recursive: true })
|
| 146 |
+
fs.writeFileSync(path.join(run, 'result.json'), JSON.stringify({
|
| 147 |
+
task_id: 'task-001',
|
| 148 |
+
model_id: 'gpt-test',
|
| 149 |
+
provider: 'openai',
|
| 150 |
+
completed: true,
|
| 151 |
+
artifact_bytes: 1234,
|
| 152 |
+
}))
|
| 153 |
+
fs.writeFileSync(path.join(run, 'evaluation', 'status.json'), JSON.stringify({
|
| 154 |
+
stage: 'vision_judging',
|
| 155 |
+
updated_at: heartbeat,
|
| 156 |
+
}))
|
| 157 |
+
|
| 158 |
+
const summary = _test.runSummary(root, 'evaluating-run')
|
| 159 |
+
|
| 160 |
+
assert.equal(summary.evaluationState, 'evaluating')
|
| 161 |
+
assert.equal(summary.evaluationStage, 'vision_judging')
|
| 162 |
+
assert.equal(summary.updatedAt, heartbeat)
|
| 163 |
+
})
|
| 164 |
+
|
| 165 |
+
test('reports independent vision and deterministic benchmark means', () => {
|
| 166 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-dual-score-'))
|
| 167 |
+
const benchmark = path.join(root, 'benchmarks', 'dual-score')
|
| 168 |
+
const run = path.join(benchmark, 'rollouts', 'task-001')
|
| 169 |
+
fs.mkdirSync(path.join(run, 'evaluation', 'vision-judge'), { recursive: true })
|
| 170 |
+
fs.writeFileSync(path.join(benchmark, 'batch-state.json'), JSON.stringify({
|
| 171 |
+
state: 'completed',
|
| 172 |
+
rollouts: {
|
| 173 |
+
'task-001': {
|
| 174 |
+
task_id: 'task-001', model_id: 'model', provider: 'openai', state: 'completed',
|
| 175 |
+
},
|
| 176 |
+
},
|
| 177 |
+
}))
|
| 178 |
+
fs.writeFileSync(path.join(run, 'result.json'), JSON.stringify({
|
| 179 |
+
task_id: 'task-001', model_id: 'model', provider: 'openai',
|
| 180 |
+
completed: true, artifact_bytes: 100,
|
| 181 |
+
}))
|
| 182 |
+
fs.writeFileSync(path.join(run, 'evaluation', 'score.json'), JSON.stringify({
|
| 183 |
+
score: 0.4,
|
| 184 |
+
valid: true,
|
| 185 |
+
scoring_method: 'vector-semantic-v2',
|
| 186 |
+
geometry_similarity: 0.5,
|
| 187 |
+
}))
|
| 188 |
+
fs.writeFileSync(
|
| 189 |
+
path.join(run, 'evaluation', 'vision-judge', 'score.json'),
|
| 190 |
+
JSON.stringify({
|
| 191 |
+
overall_score: 0.8,
|
| 192 |
+
verdict: 'mostly_acquired',
|
| 193 |
+
component_scores: { geometry: 0.9, dimensions: 0.7, annotations: 0.6, presentation: 0.5 },
|
| 194 |
+
}),
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
const detail = _test.benchmarkDetail(root, 'benchmarks/dual-score')
|
| 198 |
+
|
| 199 |
+
assert.equal(detail.aggregate.meanScore, 0.8)
|
| 200 |
+
assert.equal(detail.aggregate.visionMeanScore, 0.8)
|
| 201 |
+
assert.equal(detail.aggregate.visionScoredTasks, 1)
|
| 202 |
+
assert.equal(detail.aggregate.deterministicMeanScore, 0.4)
|
| 203 |
+
assert.equal(detail.aggregate.deterministicScoredTasks, 1)
|
| 204 |
+
assert.equal(detail.tasks[0].visionScore.score, 0.8)
|
| 205 |
+
assert.equal(detail.tasks[0].deterministicScore.score, 0.4)
|
| 206 |
+
})
|
| 207 |
+
|
| 208 |
+
test('does not show a stale evaluator heartbeat as live forever', () => {
|
| 209 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-stale-evaluation-'))
|
| 210 |
+
const run = path.join(root, 'stale-evaluation')
|
| 211 |
+
fs.mkdirSync(path.join(run, 'evaluation'), { recursive: true })
|
| 212 |
+
fs.writeFileSync(path.join(run, 'result.json'), JSON.stringify({
|
| 213 |
+
task_id: 'task-001',
|
| 214 |
+
completed: true,
|
| 215 |
+
stop_reason: 'done',
|
| 216 |
+
artifact_bytes: 100,
|
| 217 |
+
}))
|
| 218 |
+
fs.writeFileSync(path.join(run, 'evaluation', 'status.json'), JSON.stringify({
|
| 219 |
+
stage: 'vision_judging',
|
| 220 |
+
updated_at: '2020-01-01T00:00:00Z',
|
| 221 |
+
}))
|
| 222 |
+
|
| 223 |
+
assert.equal(_test.runSummary(root, 'stale-evaluation').evaluationState, 'evaluation_interrupted')
|
| 224 |
+
})
|
| 225 |
+
|
| 226 |
+
test('labels a batch process without a pilot trace as startup, not handoff', () => {
|
| 227 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-startup-'))
|
| 228 |
+
const benchmark = path.join(root, 'benchmark-starting')
|
| 229 |
+
fs.mkdirSync(benchmark, { recursive: true })
|
| 230 |
+
fs.writeFileSync(path.join(benchmark, 'batch-state.json'), JSON.stringify({
|
| 231 |
+
batch_id: 'batch-starting',
|
| 232 |
+
state: 'running',
|
| 233 |
+
rollouts: {
|
| 234 |
+
'task-002': {
|
| 235 |
+
task_id: 'task-002',
|
| 236 |
+
model_id: 'model-test',
|
| 237 |
+
provider: 'openrouter',
|
| 238 |
+
state: 'running',
|
| 239 |
+
},
|
| 240 |
+
},
|
| 241 |
+
}))
|
| 242 |
+
|
| 243 |
+
const detail = _test.benchmarkDetail(root, 'benchmark-starting')
|
| 244 |
+
|
| 245 |
+
assert.equal(detail.tasks[0].status, 'starting')
|
| 246 |
+
assert.equal(detail.tasks[0].evaluationState, 'running')
|
| 247 |
+
})
|
| 248 |
+
|
| 249 |
+
test('retains batch grouping receipts and intermediate audit frames', () => {
|
| 250 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-batch-turn-'))
|
| 251 |
+
const run = path.join(root, 'batched-run')
|
| 252 |
+
const frames = path.join(run, 'frames')
|
| 253 |
+
fs.mkdirSync(frames, { recursive: true })
|
| 254 |
+
fs.writeFileSync(path.join(run, 'run-state.json'), JSON.stringify({
|
| 255 |
+
task_id: 'task-001',
|
| 256 |
+
model_id: 'gpt-test',
|
| 257 |
+
provider: 'openai',
|
| 258 |
+
state: 'running',
|
| 259 |
+
}))
|
| 260 |
+
fs.writeFileSync(path.join(run, 'model_turns.jsonl'), `${JSON.stringify({
|
| 261 |
+
turn: 1,
|
| 262 |
+
tool_use_id: 'call-batch',
|
| 263 |
+
action: {
|
| 264 |
+
actions: [
|
| 265 |
+
{ action: 'type', text: 'LINE', intent: 'Type LINE.' },
|
| 266 |
+
{ action: 'key', keys: ['ENTER'], intent: 'Submit LINE.' },
|
| 267 |
+
],
|
| 268 |
+
},
|
| 269 |
+
})}\n`)
|
| 270 |
+
fs.writeFileSync(path.join(run, 'input_receipts.jsonl'), [
|
| 271 |
+
{ action_index: 1, tool_use_id: 'call-batch', batch_index: 1, batch_size: 2 },
|
| 272 |
+
{ action_index: 2, tool_use_id: 'call-batch', batch_index: 2, batch_size: 2 },
|
| 273 |
+
].map(JSON.stringify).join('\n') + '\n')
|
| 274 |
+
for (const name of ['frame_0000.png', 'frame_0001.png', 'frame_0002.png']) {
|
| 275 |
+
fs.writeFileSync(path.join(frames, name), 'png')
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
const detail = _test.traceDetail(root, 'batched-run')
|
| 279 |
+
|
| 280 |
+
assert.equal(detail.turns[0].action.actions.length, 2)
|
| 281 |
+
assert.deepEqual(detail.receipts.map((receipt) => receipt.batch_index), [1, 2])
|
| 282 |
+
assert.deepEqual(detail.frames.map((frame) => frame.name), [
|
| 283 |
+
'frame_0000.png',
|
| 284 |
+
'frame_0001.png',
|
| 285 |
+
'frame_0002.png',
|
| 286 |
+
])
|
| 287 |
+
})
|
| 288 |
+
|
| 289 |
+
test('indexes cloud-backed frames referenced by compact resumed traces', () => {
|
| 290 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-cloud-frames-'))
|
| 291 |
+
const run = path.join(root, 'cloud-frame-run')
|
| 292 |
+
const frames = path.join(run, 'frames')
|
| 293 |
+
fs.mkdirSync(frames, { recursive: true })
|
| 294 |
+
fs.writeFileSync(path.join(frames, 'frame_0000.png'), 'png')
|
| 295 |
+
fs.writeFileSync(path.join(run, 'run-state.json'), JSON.stringify({
|
| 296 |
+
task_id: 'task-001',
|
| 297 |
+
model_id: 'gpt-test',
|
| 298 |
+
provider: 'openai',
|
| 299 |
+
state: 'completed',
|
| 300 |
+
accepted_actions: 12,
|
| 301 |
+
}))
|
| 302 |
+
fs.writeFileSync(path.join(run, 'model_turn_summaries.jsonl'), `${JSON.stringify({
|
| 303 |
+
turn: 2,
|
| 304 |
+
model_input: {
|
| 305 |
+
artifacts: [{ artifact_path: 'frames/frame_0010.png', sequence: 10 }],
|
| 306 |
+
},
|
| 307 |
+
})}\n`)
|
| 308 |
+
|
| 309 |
+
const detail = _test.traceDetail(root, 'cloud-frame-run')
|
| 310 |
+
|
| 311 |
+
assert.deepEqual(detail.frames.map((frame) => frame.name), [
|
| 312 |
+
'frame_0000.png',
|
| 313 |
+
'frame_0010.png',
|
| 314 |
+
'frame_0012.png',
|
| 315 |
+
])
|
| 316 |
+
})
|
| 317 |
+
|
| 318 |
+
test('advertises and lazily hydrates cloud reference and trusted render images', async () => {
|
| 319 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-cloud-images-'))
|
| 320 |
+
const runId = 'benchmarks/sol/rollouts/task-022'
|
| 321 |
+
const run = path.join(root, runId)
|
| 322 |
+
fs.mkdirSync(path.join(run, 'evaluation', 'vision-judge'), { recursive: true })
|
| 323 |
+
fs.writeFileSync(path.join(run, 'run-state.json'), JSON.stringify({
|
| 324 |
+
task_id: 'cloud-only-task',
|
| 325 |
+
model_id: 'gpt-test',
|
| 326 |
+
provider: 'openai',
|
| 327 |
+
state: 'completed',
|
| 328 |
+
}))
|
| 329 |
+
fs.writeFileSync(
|
| 330 |
+
path.join(run, 'evaluation', 'vision-judge', 'score.json'),
|
| 331 |
+
JSON.stringify({
|
| 332 |
+
prompt_version: 'autocad-vision-judge-v3',
|
| 333 |
+
overall_score: 0.9,
|
| 334 |
+
component_scores: { geometry: 0.9 },
|
| 335 |
+
}),
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
const detail = _test.traceDetail(root, runId)
|
| 339 |
+
assert.equal(
|
| 340 |
+
detail.referenceUrl,
|
| 341 |
+
`/api/runs/${encodeURIComponent(runId)}/file/reference.png`,
|
| 342 |
+
)
|
| 343 |
+
assert.equal(
|
| 344 |
+
detail.artifacts.candidateRender,
|
| 345 |
+
`/api/runs/${encodeURIComponent(runId)}/file/evaluation/render.png`,
|
| 346 |
+
)
|
| 347 |
+
|
| 348 |
+
const hydrated = []
|
| 349 |
+
const api = createTraceApi({
|
| 350 |
+
runsRoot: root,
|
| 351 |
+
cloudArtifactLoader: async (request) => {
|
| 352 |
+
hydrated.push(request.relativePath)
|
| 353 |
+
fs.mkdirSync(path.dirname(request.artifact), { recursive: true })
|
| 354 |
+
fs.writeFileSync(request.artifact, `cloud:${request.relativePath}`)
|
| 355 |
+
},
|
| 356 |
+
})
|
| 357 |
+
for (const relativePath of ['reference.png', 'evaluation/render.png']) {
|
| 358 |
+
const response = new TestResponse()
|
| 359 |
+
const finished = once(response, 'finish')
|
| 360 |
+
await api({
|
| 361 |
+
method: 'GET',
|
| 362 |
+
url: `/api/runs/${encodeURIComponent(runId)}/file/${relativePath}`,
|
| 363 |
+
}, response)
|
| 364 |
+
await finished
|
| 365 |
+
assert.equal(response.statusCode, 200)
|
| 366 |
+
assert.equal(response.headers['Content-Type'], 'image/png')
|
| 367 |
+
assert.equal(response.headers['Cache-Control'], 'private, max-age=31536000, immutable')
|
| 368 |
+
assert.equal(Buffer.concat(response.body).toString(), `cloud:${relativePath}`)
|
| 369 |
+
}
|
| 370 |
+
assert.deepEqual(hydrated, ['reference.png', 'evaluation/render.png'])
|
| 371 |
+
})
|
| 372 |
+
|
| 373 |
+
test('hydrates a missing frame from the cloud artifact loader on demand', async () => {
|
| 374 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-cloud-fetch-'))
|
| 375 |
+
const run = path.join(root, 'batch', 'rollouts', 'task-001')
|
| 376 |
+
fs.mkdirSync(run, { recursive: true })
|
| 377 |
+
let hydrated = null
|
| 378 |
+
const api = createTraceApi({
|
| 379 |
+
runsRoot: root,
|
| 380 |
+
cloudArtifactLoader: async (request) => {
|
| 381 |
+
hydrated = request
|
| 382 |
+
fs.mkdirSync(path.dirname(request.artifact), { recursive: true })
|
| 383 |
+
fs.writeFileSync(request.artifact, 'cloud-png')
|
| 384 |
+
},
|
| 385 |
+
})
|
| 386 |
+
const response = new TestResponse()
|
| 387 |
+
const finished = once(response, 'finish')
|
| 388 |
+
|
| 389 |
+
const handled = await api({
|
| 390 |
+
method: 'GET',
|
| 391 |
+
url: '/api/runs/batch%2Frollouts%2Ftask-001/file/frames/frame_0042.png',
|
| 392 |
+
}, response)
|
| 393 |
+
await finished
|
| 394 |
+
|
| 395 |
+
assert.equal(handled, true)
|
| 396 |
+
assert.equal(response.statusCode, 200)
|
| 397 |
+
assert.equal(response.headers['Content-Type'], 'image/png')
|
| 398 |
+
assert.equal(Buffer.concat(response.body).toString(), 'cloud-png')
|
| 399 |
+
assert.equal(hydrated.runId, 'batch/rollouts/task-001')
|
| 400 |
+
assert.equal(hydrated.relativePath, 'frames/frame_0042.png')
|
| 401 |
+
})
|
| 402 |
+
|
| 403 |
+
test('redirects a cold cloud image directly to a short-lived private URL', async () => {
|
| 404 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-cloud-redirect-'))
|
| 405 |
+
const runId = 'batch/rollouts/task-001'
|
| 406 |
+
fs.mkdirSync(path.join(root, runId), { recursive: true })
|
| 407 |
+
let signed = null
|
| 408 |
+
const api = createTraceApi({
|
| 409 |
+
runsRoot: root,
|
| 410 |
+
cloudArtifactUrl: async (request) => {
|
| 411 |
+
signed = request
|
| 412 |
+
return 'https://private-origin.example/signed-frame'
|
| 413 |
+
},
|
| 414 |
+
cloudArtifactLoader: async () => {
|
| 415 |
+
assert.fail('the proxy loader must not run after a successful signed redirect')
|
| 416 |
+
},
|
| 417 |
+
})
|
| 418 |
+
const response = new TestResponse()
|
| 419 |
+
const finished = once(response, 'finish')
|
| 420 |
+
|
| 421 |
+
await api({
|
| 422 |
+
method: 'GET',
|
| 423 |
+
url: `/api/runs/${encodeURIComponent(runId)}/file/frames/frame_0042.png`,
|
| 424 |
+
}, response)
|
| 425 |
+
await finished
|
| 426 |
+
|
| 427 |
+
assert.equal(response.statusCode, 307)
|
| 428 |
+
assert.equal(response.headers.Location, 'https://private-origin.example/signed-frame')
|
| 429 |
+
assert.equal(response.headers['Cache-Control'], 'private, max-age=240')
|
| 430 |
+
assert.deepEqual(signed, {
|
| 431 |
+
runId,
|
| 432 |
+
relativePath: 'frames/frame_0042.png',
|
| 433 |
+
})
|
| 434 |
+
})
|
| 435 |
+
|
| 436 |
+
test('bootstraps a missing live rollout trace from cloud metadata', async () => {
|
| 437 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-cloud-index-'))
|
| 438 |
+
const runId = 'benchmarks/live-suite/rollouts/task-004'
|
| 439 |
+
const run = path.join(root, runId)
|
| 440 |
+
let hydration = null
|
| 441 |
+
const api = createTraceApi({
|
| 442 |
+
runsRoot: root,
|
| 443 |
+
cloudTraceLoader: async (request) => {
|
| 444 |
+
hydration = request
|
| 445 |
+
fs.mkdirSync(request.runDirectory, { recursive: true })
|
| 446 |
+
fs.writeFileSync(path.join(request.runDirectory, 'run-state.json'), JSON.stringify({
|
| 447 |
+
task_id: 'task-004',
|
| 448 |
+
model_id: 'claude-fable-5',
|
| 449 |
+
provider: 'anthropic',
|
| 450 |
+
state: 'running',
|
| 451 |
+
accepted_actions: 42,
|
| 452 |
+
}))
|
| 453 |
+
fs.writeFileSync(path.join(request.runDirectory, 'model_turn_summaries.jsonl'), `${JSON.stringify({
|
| 454 |
+
turn: 1,
|
| 455 |
+
tool_use_id: 'tool-1',
|
| 456 |
+
action: { actions: [{ action: 'click', x: 1, y: 2 }] },
|
| 457 |
+
model_input: {
|
| 458 |
+
artifacts: [{ artifact_path: 'frames/frame_0041.png' }],
|
| 459 |
+
},
|
| 460 |
+
})}\n`)
|
| 461 |
+
},
|
| 462 |
+
})
|
| 463 |
+
const response = new TestResponse()
|
| 464 |
+
const finished = once(response, 'finish')
|
| 465 |
+
|
| 466 |
+
await api({
|
| 467 |
+
method: 'GET',
|
| 468 |
+
url: `/api/runs/${encodeURIComponent(runId)}`,
|
| 469 |
+
}, response)
|
| 470 |
+
await finished
|
| 471 |
+
|
| 472 |
+
assert.equal(response.statusCode, 200)
|
| 473 |
+
assert.equal(hydration.runId, runId)
|
| 474 |
+
assert.equal(hydration.runDirectory, run)
|
| 475 |
+
const detail = JSON.parse(Buffer.concat(response.body).toString())
|
| 476 |
+
assert.equal(detail.summary.acceptedActions, 42)
|
| 477 |
+
assert.equal(detail.turns.length, 1)
|
| 478 |
+
assert.deepEqual(detail.frames.map((frame) => frame.name), [
|
| 479 |
+
'frame_0041.png',
|
| 480 |
+
'frame_0042.png',
|
| 481 |
+
])
|
| 482 |
+
})
|
| 483 |
+
|
| 484 |
+
test('refreshes stale live benchmark metadata before returning its detail', async () => {
|
| 485 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-benchmark-cloud-refresh-'))
|
| 486 |
+
const benchmarkId = 'benchmarks/live-suite'
|
| 487 |
+
const benchmark = path.join(root, benchmarkId)
|
| 488 |
+
const run = path.join(benchmark, 'rollouts', 'task-004')
|
| 489 |
+
fs.mkdirSync(run, { recursive: true })
|
| 490 |
+
fs.writeFileSync(path.join(benchmark, 'batch-state.json'), JSON.stringify({
|
| 491 |
+
state: 'running',
|
| 492 |
+
updated_at: '2026-07-18T00:00:00Z',
|
| 493 |
+
rollouts: {
|
| 494 |
+
'task-004': {
|
| 495 |
+
task_id: 'task-004',
|
| 496 |
+
model_id: 'claude-fable-5',
|
| 497 |
+
provider: 'anthropic',
|
| 498 |
+
state: 'running',
|
| 499 |
+
},
|
| 500 |
+
},
|
| 501 |
+
}))
|
| 502 |
+
fs.writeFileSync(path.join(run, 'run-state.json'), JSON.stringify({
|
| 503 |
+
task_id: 'task-004',
|
| 504 |
+
model_id: 'claude-fable-5',
|
| 505 |
+
provider: 'anthropic',
|
| 506 |
+
state: 'running',
|
| 507 |
+
accepted_actions: 1,
|
| 508 |
+
}))
|
| 509 |
+
let refreshCount = 0
|
| 510 |
+
const api = createTraceApi({
|
| 511 |
+
runsRoot: root,
|
| 512 |
+
cloudBenchmarkLoader: async ({ benchmarkDirectory }) => {
|
| 513 |
+
refreshCount += 1
|
| 514 |
+
fs.writeFileSync(path.join(
|
| 515 |
+
benchmarkDirectory, 'rollouts', 'task-004', 'run-state.json',
|
| 516 |
+
), JSON.stringify({
|
| 517 |
+
task_id: 'task-004',
|
| 518 |
+
model_id: 'claude-fable-5',
|
| 519 |
+
provider: 'anthropic',
|
| 520 |
+
state: 'running',
|
| 521 |
+
accepted_actions: 87,
|
| 522 |
+
turn_count: 21,
|
| 523 |
+
}))
|
| 524 |
+
},
|
| 525 |
+
})
|
| 526 |
+
const response = new TestResponse()
|
| 527 |
+
const finished = once(response, 'finish')
|
| 528 |
+
|
| 529 |
+
await api({
|
| 530 |
+
method: 'GET',
|
| 531 |
+
url: `/api/benchmarks/${encodeURIComponent(benchmarkId)}`,
|
| 532 |
+
}, response)
|
| 533 |
+
await finished
|
| 534 |
+
|
| 535 |
+
assert.equal(response.statusCode, 200)
|
| 536 |
+
assert.equal(refreshCount, 1)
|
| 537 |
+
const detail = JSON.parse(Buffer.concat(response.body).toString())
|
| 538 |
+
assert.equal(detail.tasks[0].acceptedActions, 87)
|
| 539 |
+
assert.equal(detail.aggregate.acceptedActions, 87)
|
| 540 |
+
assert.equal(detail.aggregate.usage.modelTurns, 21)
|
| 541 |
+
})
|
| 542 |
+
|
| 543 |
+
test('content-addresses model frames and replaces a stale local frame', async () => {
|
| 544 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-stale-frame-'))
|
| 545 |
+
const runId = 'batch/rollouts/task-001'
|
| 546 |
+
const run = path.join(root, runId)
|
| 547 |
+
const frames = path.join(run, 'frames')
|
| 548 |
+
fs.mkdirSync(frames, { recursive: true })
|
| 549 |
+
const freshFrame = Buffer.from('fresh-cloud-frame')
|
| 550 |
+
const expectedSha256 = createHash('sha256').update(freshFrame).digest('hex')
|
| 551 |
+
fs.writeFileSync(path.join(frames, 'frame_0042.png'), 'stale-local-frame')
|
| 552 |
+
fs.writeFileSync(path.join(run, 'run-state.json'), JSON.stringify({
|
| 553 |
+
task_id: 'task-001',
|
| 554 |
+
model_id: 'gpt-test',
|
| 555 |
+
provider: 'openai',
|
| 556 |
+
state: 'completed',
|
| 557 |
+
}))
|
| 558 |
+
fs.writeFileSync(path.join(run, 'model_turn_summaries.jsonl'), `${JSON.stringify({
|
| 559 |
+
turn: 1,
|
| 560 |
+
model_input: {
|
| 561 |
+
artifacts: [{
|
| 562 |
+
artifact_path: 'frames/frame_0042.png',
|
| 563 |
+
sha256: expectedSha256,
|
| 564 |
+
sequence: 42,
|
| 565 |
+
}],
|
| 566 |
+
},
|
| 567 |
+
})}\n`)
|
| 568 |
+
|
| 569 |
+
const detail = _test.traceDetail(root, runId)
|
| 570 |
+
assert.equal(detail.frames[0].sha256, expectedSha256)
|
| 571 |
+
assert.equal(
|
| 572 |
+
detail.frames[0].url,
|
| 573 |
+
`/api/runs/batch%2Frollouts%2Ftask-001/file/frames/frame_0042.png?sha256=${expectedSha256}`,
|
| 574 |
+
)
|
| 575 |
+
|
| 576 |
+
let hydration = null
|
| 577 |
+
const api = createTraceApi({
|
| 578 |
+
runsRoot: root,
|
| 579 |
+
cloudArtifactLoader: async (request) => {
|
| 580 |
+
hydration = request
|
| 581 |
+
fs.writeFileSync(request.artifact, freshFrame)
|
| 582 |
+
},
|
| 583 |
+
})
|
| 584 |
+
const response = new TestResponse()
|
| 585 |
+
const finished = once(response, 'finish')
|
| 586 |
+
await api({
|
| 587 |
+
method: 'GET',
|
| 588 |
+
url: `${detail.frames[0].url}`,
|
| 589 |
+
}, response)
|
| 590 |
+
await finished
|
| 591 |
+
|
| 592 |
+
assert.equal(response.statusCode, 200)
|
| 593 |
+
assert.equal(Buffer.concat(response.body).toString(), freshFrame.toString())
|
| 594 |
+
assert.equal(response.headers['Cache-Control'], 'private, max-age=31536000, immutable')
|
| 595 |
+
assert.equal(hydration.force, true)
|
| 596 |
+
assert.equal(hydration.expectedSha256, expectedSha256)
|
| 597 |
+
})
|
| 598 |
+
|
| 599 |
+
test('never serves a stale frame when cloud refresh does not match its checksum', async () => {
|
| 600 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-reject-stale-frame-'))
|
| 601 |
+
const runId = 'batch/rollouts/task-001'
|
| 602 |
+
const run = path.join(root, runId)
|
| 603 |
+
const frames = path.join(run, 'frames')
|
| 604 |
+
fs.mkdirSync(frames, { recursive: true })
|
| 605 |
+
const expectedSha256 = createHash('sha256').update('expected-frame').digest('hex')
|
| 606 |
+
fs.writeFileSync(path.join(frames, 'frame_0001.png'), 'stale-frame')
|
| 607 |
+
|
| 608 |
+
const api = createTraceApi({
|
| 609 |
+
runsRoot: root,
|
| 610 |
+
cloudArtifactLoader: async () => {},
|
| 611 |
+
})
|
| 612 |
+
const response = new TestResponse()
|
| 613 |
+
const finished = once(response, 'finish')
|
| 614 |
+
await api({
|
| 615 |
+
method: 'GET',
|
| 616 |
+
url: `/api/runs/${encodeURIComponent(runId)}/file/frames/frame_0001.png?sha256=${expectedSha256}`,
|
| 617 |
+
}, response)
|
| 618 |
+
await finished
|
| 619 |
+
|
| 620 |
+
assert.equal(response.statusCode, 404)
|
| 621 |
+
assert.match(Buffer.concat(response.body).toString(), /artifact_not_found/)
|
| 622 |
+
})
|
| 623 |
+
|
| 624 |
+
test('uses compact turn summaries without returning repeated request history', () => {
|
| 625 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-compact-'))
|
| 626 |
+
const run = path.join(root, 'compact-run')
|
| 627 |
+
fs.mkdirSync(run)
|
| 628 |
+
fs.writeFileSync(path.join(run, 'run-state.json'), JSON.stringify({
|
| 629 |
+
task_id: 'task-001',
|
| 630 |
+
model_id: 'claude-test',
|
| 631 |
+
provider: 'anthropic',
|
| 632 |
+
state: 'completed',
|
| 633 |
+
turn_count: 1,
|
| 634 |
+
}))
|
| 635 |
+
fs.writeFileSync(path.join(run, 'model_turns.jsonl'), 'x'.repeat(9 * 1024 * 1024))
|
| 636 |
+
fs.writeFileSync(path.join(run, 'model_turn_summaries.jsonl'), `${JSON.stringify({
|
| 637 |
+
turn: 1,
|
| 638 |
+
tool_use_id: 'call-compact',
|
| 639 |
+
action: { action: 'done' },
|
| 640 |
+
model_input: {
|
| 641 |
+
model: 'claude-test',
|
| 642 |
+
artifacts: [{ artifact_path: 'frames/frame_0000.png' }],
|
| 643 |
+
},
|
| 644 |
+
})}\n`)
|
| 645 |
+
|
| 646 |
+
const detail = _test.traceDetail(root, 'compact-run')
|
| 647 |
+
|
| 648 |
+
assert.equal(detail.traceIndexed, true)
|
| 649 |
+
assert.equal(detail.turns.length, 1)
|
| 650 |
+
assert.equal(detail.turns[0].tool_use_id, 'call-compact')
|
| 651 |
+
assert.equal(JSON.stringify(detail).includes('repeated request history'), false)
|
| 652 |
+
assert.equal('events' in detail, false)
|
| 653 |
+
assert.equal('frameEvents' in detail, false)
|
| 654 |
+
})
|
| 655 |
+
|
| 656 |
+
test('does not synchronously parse a large unindexed trace for benchmark usage', () => {
|
| 657 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-large-'))
|
| 658 |
+
const run = path.join(root, 'large-run')
|
| 659 |
+
fs.mkdirSync(run)
|
| 660 |
+
const trace = path.join(run, 'model_turns.jsonl')
|
| 661 |
+
fs.writeFileSync(trace, '')
|
| 662 |
+
fs.truncateSync(trace, 9 * 1024 * 1024)
|
| 663 |
+
|
| 664 |
+
const usage = _test.runUsage(run, { input_per_million: 5 }, {
|
| 665 |
+
turn_count: 42,
|
| 666 |
+
usage: { input_tokens: 1200, output_tokens: 30, total_tokens: 1230 },
|
| 667 |
+
})
|
| 668 |
+
|
| 669 |
+
assert.equal(usage.modelTurns, 42)
|
| 670 |
+
assert.equal(usage.inputTokens, 1200)
|
| 671 |
+
assert.equal(usage.totalTokens, 1230)
|
| 672 |
+
assert.equal(usage.apiCost, null)
|
| 673 |
+
})
|
| 674 |
+
|
| 675 |
+
test('uses the live aggregate instead of parsing a large compact usage index', () => {
|
| 676 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-summary-large-'))
|
| 677 |
+
const run = path.join(root, 'large-summary-run')
|
| 678 |
+
fs.mkdirSync(run)
|
| 679 |
+
const summary = path.join(run, 'model_turn_summaries.jsonl')
|
| 680 |
+
fs.writeFileSync(summary, '')
|
| 681 |
+
fs.truncateSync(summary, 600 * 1024)
|
| 682 |
+
|
| 683 |
+
const usage = _test.runUsage(run, { input_per_million: 5 }, {
|
| 684 |
+
turn_count: 77,
|
| 685 |
+
usage: { input_tokens: 9000, output_tokens: 80, total_tokens: 9080 },
|
| 686 |
+
})
|
| 687 |
+
|
| 688 |
+
assert.equal(usage.modelTurns, 77)
|
| 689 |
+
assert.equal(usage.inputTokens, 9000)
|
| 690 |
+
assert.equal(usage.totalTokens, 9080)
|
| 691 |
+
assert.equal(usage.apiCost, null)
|
| 692 |
+
})
|
| 693 |
+
|
| 694 |
+
test('reconciles a stale running batch row with a completed rollout trace', () => {
|
| 695 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-stale-batch-'))
|
| 696 |
+
const benchmark = path.join(root, 'stale-batch')
|
| 697 |
+
const run = path.join(benchmark, 'rollouts', 'task-001')
|
| 698 |
+
fs.mkdirSync(run, { recursive: true })
|
| 699 |
+
fs.writeFileSync(path.join(benchmark, 'batch-state.json'), JSON.stringify({
|
| 700 |
+
state: 'running',
|
| 701 |
+
updated_at: '2020-01-01T00:00:00Z',
|
| 702 |
+
rollouts: {
|
| 703 |
+
'task-001': {
|
| 704 |
+
task_id: 'task-001',
|
| 705 |
+
model_id: 'claude-test',
|
| 706 |
+
provider: 'anthropic',
|
| 707 |
+
state: 'running',
|
| 708 |
+
},
|
| 709 |
+
'task-002': {
|
| 710 |
+
task_id: 'task-002',
|
| 711 |
+
model_id: 'claude-test',
|
| 712 |
+
provider: 'anthropic',
|
| 713 |
+
state: 'queued',
|
| 714 |
+
},
|
| 715 |
+
},
|
| 716 |
+
}))
|
| 717 |
+
fs.writeFileSync(path.join(run, 'run-state.json'), JSON.stringify({
|
| 718 |
+
task_id: 'task-001',
|
| 719 |
+
model_id: 'claude-test',
|
| 720 |
+
provider: 'anthropic',
|
| 721 |
+
state: 'completed',
|
| 722 |
+
turn_count: 1,
|
| 723 |
+
}))
|
| 724 |
+
|
| 725 |
+
const detail = _test.benchmarkDetail(root, 'stale-batch')
|
| 726 |
+
|
| 727 |
+
assert.equal(detail.tasks[0].status, 'completed')
|
| 728 |
+
assert.equal(detail.aggregate.completedTasks, 1)
|
| 729 |
+
assert.equal(detail.aggregate.activeTasks, 0)
|
| 730 |
+
assert.equal(detail.status, 'interrupted')
|
| 731 |
+
})
|
| 732 |
+
|
| 733 |
+
test('calculates cached and long-context token costs per model turn', () => {
|
| 734 |
+
const pricing = {
|
| 735 |
+
input_per_million: 5,
|
| 736 |
+
cached_input_per_million: 0.5,
|
| 737 |
+
output_per_million: 30,
|
| 738 |
+
long_context_threshold_tokens: 272000,
|
| 739 |
+
long_context_input_multiplier: 2,
|
| 740 |
+
long_context_output_multiplier: 1.5,
|
| 741 |
+
}
|
| 742 |
+
const usage = _test.usageSummary([
|
| 743 |
+
{ usage: {
|
| 744 |
+
input_tokens: 1000,
|
| 745 |
+
cached_input_tokens: 400,
|
| 746 |
+
output_tokens: 100,
|
| 747 |
+
reasoning_output_tokens: 60,
|
| 748 |
+
total_tokens: 1100,
|
| 749 |
+
} },
|
| 750 |
+
{ usage: { input_tokens: 300000, output_tokens: 1000, total_tokens: 301000 } },
|
| 751 |
+
], pricing)
|
| 752 |
+
|
| 753 |
+
assert.equal(usage.inputTokens, 301000)
|
| 754 |
+
assert.equal(usage.cachedInputTokens, 400)
|
| 755 |
+
assert.equal(usage.outputTokens, 1100)
|
| 756 |
+
assert.equal(usage.reasoningOutputTokens, 60)
|
| 757 |
+
assert.ok(Math.abs(usage.apiCost - 3.0512) < 1e-12)
|
| 758 |
+
})
|
| 759 |
+
|
| 760 |
+
test('aggregates a live multi-task benchmark from batch state and rollout traces', () => {
|
| 761 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-benchmark-'))
|
| 762 |
+
const benchmark = path.join(root, 'benchmarks', 'full-suite')
|
| 763 |
+
const completed = path.join(benchmark, 'rollouts', 'task-001')
|
| 764 |
+
fs.mkdirSync(completed, { recursive: true })
|
| 765 |
+
fs.writeFileSync(path.join(benchmark, 'batch-state.json'), JSON.stringify({
|
| 766 |
+
schema_version: 2,
|
| 767 |
+
batch_id: 'batch-full',
|
| 768 |
+
benchmark_name: 'Full suite',
|
| 769 |
+
state: 'running',
|
| 770 |
+
started_at: '2026-07-16T00:00:00Z',
|
| 771 |
+
updated_at: '2026-07-16T00:10:00Z',
|
| 772 |
+
max_concurrency: 10,
|
| 773 |
+
pricing: {
|
| 774 |
+
input_per_million: 5,
|
| 775 |
+
cached_input_per_million: 0.5,
|
| 776 |
+
output_per_million: 30,
|
| 777 |
+
},
|
| 778 |
+
rollouts: {
|
| 779 |
+
'task-001': {
|
| 780 |
+
task_id: 'task-001',
|
| 781 |
+
model_id: 'gpt-5.6-sol',
|
| 782 |
+
provider: 'openai',
|
| 783 |
+
state: 'completed',
|
| 784 |
+
},
|
| 785 |
+
'task-022': {
|
| 786 |
+
task_id: 'task-022',
|
| 787 |
+
model_id: 'gpt-5.6-sol',
|
| 788 |
+
provider: 'openai',
|
| 789 |
+
state: 'queued',
|
| 790 |
+
},
|
| 791 |
+
},
|
| 792 |
+
}))
|
| 793 |
+
fs.writeFileSync(path.join(completed, 'result.json'), JSON.stringify({
|
| 794 |
+
task_id: 'task-001',
|
| 795 |
+
model_id: 'gpt-5.6-sol',
|
| 796 |
+
provider: 'openai',
|
| 797 |
+
completed: true,
|
| 798 |
+
accepted_actions: 20,
|
| 799 |
+
artifact_bytes: 1234,
|
| 800 |
+
}))
|
| 801 |
+
fs.writeFileSync(path.join(completed, 'model_turns.jsonl'), `${JSON.stringify({
|
| 802 |
+
usage: { input_tokens: 1000, cached_input_tokens: 400, output_tokens: 100 },
|
| 803 |
+
})}\n`)
|
| 804 |
+
|
| 805 |
+
const detail = _test.benchmarkDetail(root, 'benchmarks/full-suite')
|
| 806 |
+
|
| 807 |
+
assert.equal(detail.name, 'Full suite')
|
| 808 |
+
assert.equal(detail.aggregate.totalTasks, 2)
|
| 809 |
+
assert.equal(detail.aggregate.completedTasks, 1)
|
| 810 |
+
assert.equal(detail.aggregate.queuedTasks, 1)
|
| 811 |
+
assert.equal(detail.aggregate.artifactTasks, 1)
|
| 812 |
+
assert.equal(detail.aggregate.usage.inputTokens, 1000)
|
| 813 |
+
assert.ok(Math.abs(detail.aggregate.usage.apiCost - 0.0062) < 1e-12)
|
| 814 |
+
assert.deepEqual(detail.tasks.map((task) => task.level), ['basic', 'basic'])
|
| 815 |
+
assert.deepEqual(detail.tasks.map((task) => task.drawingType), ['2d', '3d'])
|
| 816 |
+
assert.deepEqual(detail.aggregate.drawingTypes, {
|
| 817 |
+
'2d': { total: 1, scored: 0, meanScore: null },
|
| 818 |
+
'3d': { total: 1, scored: 0, meanScore: null },
|
| 819 |
+
})
|
| 820 |
+
assert.deepEqual(detail.aggregate.evaluationStates, {
|
| 821 |
+
awaiting_score: 1,
|
| 822 |
+
queued: 1,
|
| 823 |
+
})
|
| 824 |
+
assert.equal(_test.listBenchmarks(root)[0].id, 'benchmarks/full-suite')
|
| 825 |
+
})
|
| 826 |
+
|
| 827 |
+
test('keeps execution, recovery, budget, infrastructure, and evaluation states separate', () => {
|
| 828 |
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-outcomes-'))
|
| 829 |
+
const benchmark = path.join(root, 'benchmarks', 'outcomes')
|
| 830 |
+
const cases = {
|
| 831 |
+
completed: { completed: true, stop_reason: 'done', artifact_bytes: 10 },
|
| 832 |
+
recovered: { completed: false, stop_reason: 'recovered_finalized_session', artifact_bytes: 10 },
|
| 833 |
+
budget: { completed: false, stop_reason: 'max_actions', artifact_bytes: 10 },
|
| 834 |
+
infra: { completed: false, stop_reason: 'rollout_error', failure: 'ConnectError', artifact_bytes: 10 },
|
| 835 |
+
model: { completed: false, stop_reason: 'invalid_action_limit', failure: 'invalid', artifact_bytes: 10 },
|
| 836 |
+
}
|
| 837 |
+
fs.mkdirSync(benchmark, { recursive: true })
|
| 838 |
+
fs.writeFileSync(path.join(benchmark, 'batch-state.json'), JSON.stringify({
|
| 839 |
+
state: 'running',
|
| 840 |
+
updated_at: '2020-01-01T00:00:00Z',
|
| 841 |
+
rollouts: Object.fromEntries(Object.keys(cases).map((task) => [task, {
|
| 842 |
+
task_id: 'task-001', model_id: 'model', provider: 'openai', state: 'failed',
|
| 843 |
+
failure: 'stale wrapper failure',
|
| 844 |
+
}])),
|
| 845 |
+
}))
|
| 846 |
+
for (const [name, result] of Object.entries(cases)) {
|
| 847 |
+
const run = path.join(benchmark, 'rollouts', name)
|
| 848 |
+
fs.mkdirSync(path.join(run, 'evaluation'), { recursive: true })
|
| 849 |
+
fs.writeFileSync(path.join(run, 'result.json'), JSON.stringify({
|
| 850 |
+
task_id: 'task-001', model_id: 'model', provider: 'openai', ...result,
|
| 851 |
+
}))
|
| 852 |
+
}
|
| 853 |
+
fs.writeFileSync(
|
| 854 |
+
path.join(benchmark, 'rollouts', 'completed', 'evaluation', 'status.json'),
|
| 855 |
+
JSON.stringify({ stage: 'failed', error: 'VisionJudgeError: ReadTimeout' }),
|
| 856 |
+
)
|
| 857 |
+
|
| 858 |
+
const detail = _test.benchmarkDetail(root, 'benchmarks/outcomes')
|
| 859 |
+
|
| 860 |
+
assert.equal(detail.status, 'completed_with_issues')
|
| 861 |
+
assert.equal(detail.aggregate.settledTasks, 5)
|
| 862 |
+
assert.equal(detail.aggregate.completedTasks, 1)
|
| 863 |
+
assert.equal(detail.aggregate.recoveredTasks, 1)
|
| 864 |
+
assert.equal(detail.aggregate.budgetExhaustedTasks, 1)
|
| 865 |
+
assert.equal(detail.aggregate.infrastructureFailedTasks, 1)
|
| 866 |
+
assert.equal(detail.aggregate.failedTasks, 1)
|
| 867 |
+
assert.equal(detail.tasks.find((task) => task.rolloutId === 'completed').status, 'completed')
|
| 868 |
+
assert.equal(detail.tasks.find((task) => task.rolloutId === 'completed').failure, null)
|
| 869 |
+
assert.equal(detail.tasks.find((task) => task.rolloutId === 'budget').failure, null)
|
| 870 |
+
assert.equal(detail.tasks.find((task) => task.rolloutId === 'infra').failure, 'ConnectError')
|
| 871 |
+
assert.equal(detail.tasks.find((task) => task.rolloutId === 'completed').evaluationState, 'evaluation_failed')
|
| 872 |
+
})
|
apps/dashboard/trace-api.d.mts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export function createTraceApi(options?: {}): (request: any, response: any) => Promise<boolean>;
|
| 2 |
+
export namespace _test {
|
| 3 |
+
export { readJsonl };
|
| 4 |
+
export { safeRunDirectory };
|
| 5 |
+
export { safeArtifact };
|
| 6 |
+
export { runSummary };
|
| 7 |
+
export { traceDetail };
|
| 8 |
+
}
|
| 9 |
+
declare function readJsonl(filePath: any): any[];
|
| 10 |
+
declare function safeRunDirectory(runsRoot: any, runId: any): string;
|
| 11 |
+
declare function safeArtifact(runDirectory: any, relativePath: any): string;
|
| 12 |
+
declare function runSummary(runsRoot: any, runId: any): {
|
| 13 |
+
id: any;
|
| 14 |
+
taskId: any;
|
| 15 |
+
attemptId: any;
|
| 16 |
+
modelId: any;
|
| 17 |
+
provider: any;
|
| 18 |
+
reasoningEffort: any;
|
| 19 |
+
status: any;
|
| 20 |
+
stopReason: any;
|
| 21 |
+
failure: any;
|
| 22 |
+
acceptedActions: any;
|
| 23 |
+
invalidActions: any;
|
| 24 |
+
screenshots: any;
|
| 25 |
+
elapsedSeconds: any;
|
| 26 |
+
artifactBytes: any;
|
| 27 |
+
score: any;
|
| 28 |
+
valid: any;
|
| 29 |
+
scoreFailure: any;
|
| 30 |
+
updatedAt: any;
|
| 31 |
+
};
|
| 32 |
+
declare function traceDetail(runsRoot: any, runId: any): {
|
| 33 |
+
summary: {
|
| 34 |
+
id: any;
|
| 35 |
+
taskId: any;
|
| 36 |
+
attemptId: any;
|
| 37 |
+
modelId: any;
|
| 38 |
+
provider: any;
|
| 39 |
+
reasoningEffort: any;
|
| 40 |
+
status: any;
|
| 41 |
+
stopReason: any;
|
| 42 |
+
failure: any;
|
| 43 |
+
acceptedActions: any;
|
| 44 |
+
invalidActions: any;
|
| 45 |
+
screenshots: any;
|
| 46 |
+
elapsedSeconds: any;
|
| 47 |
+
artifactBytes: any;
|
| 48 |
+
score: any;
|
| 49 |
+
valid: any;
|
| 50 |
+
scoreFailure: any;
|
| 51 |
+
updatedAt: any;
|
| 52 |
+
};
|
| 53 |
+
state: any;
|
| 54 |
+
result: any;
|
| 55 |
+
score: any;
|
| 56 |
+
turns: any[];
|
| 57 |
+
receipts: any[];
|
| 58 |
+
events: any[];
|
| 59 |
+
frameEvents: any[];
|
| 60 |
+
frames: {
|
| 61 |
+
name: string;
|
| 62 |
+
url: string;
|
| 63 |
+
}[];
|
| 64 |
+
referenceUrl: string;
|
| 65 |
+
artifacts: {
|
| 66 |
+
dwg: string;
|
| 67 |
+
candidateRender: string;
|
| 68 |
+
diffRender: string;
|
| 69 |
+
};
|
| 70 |
+
};
|
| 71 |
+
export {};
|
apps/dashboard/trace-api.mjs
ADDED
|
@@ -0,0 +1,1080 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fs from 'node:fs'
|
| 2 |
+
import { createHash } from 'node:crypto'
|
| 3 |
+
import path from 'node:path'
|
| 4 |
+
import { fileURLToPath } from 'node:url'
|
| 5 |
+
|
| 6 |
+
const DASHBOARD_ROOT = path.dirname(fileURLToPath(import.meta.url))
|
| 7 |
+
const CHECKOUT_ROOT = path.resolve(DASHBOARD_ROOT, '..', '..')
|
| 8 |
+
const BENCHMARK_DATA_ROOT = path.resolve(
|
| 9 |
+
process.env.AUTOCAD_BENCH_ROOT || CHECKOUT_ROOT,
|
| 10 |
+
)
|
| 11 |
+
const DEFAULT_RUNS_ROOT = path.join(CHECKOUT_ROOT, 'runs')
|
| 12 |
+
const MANIFEST_PATH = path.join(BENCHMARK_DATA_ROOT, 'tasks', 'manifest.jsonl')
|
| 13 |
+
const TRACE_SUMMARY_NAME = 'model_turn_summaries.jsonl'
|
| 14 |
+
const MAX_INLINE_LEGACY_TRACE_BYTES = 8 * 1024 * 1024
|
| 15 |
+
const MAX_INLINE_USAGE_SUMMARY_BYTES = 512 * 1024
|
| 16 |
+
const usageCache = new Map()
|
| 17 |
+
|
| 18 |
+
function readJson(filePath) {
|
| 19 |
+
try {
|
| 20 |
+
return JSON.parse(fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, ''))
|
| 21 |
+
} catch {
|
| 22 |
+
return null
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
function readJsonl(filePath) {
|
| 27 |
+
try {
|
| 28 |
+
return fs
|
| 29 |
+
.readFileSync(filePath, 'utf8')
|
| 30 |
+
.split(/\r?\n/)
|
| 31 |
+
.filter((line) => line.trim())
|
| 32 |
+
.flatMap((line) => {
|
| 33 |
+
try {
|
| 34 |
+
return [JSON.parse(line.replace(/^\uFEFF/, ''))]
|
| 35 |
+
} catch {
|
| 36 |
+
return []
|
| 37 |
+
}
|
| 38 |
+
})
|
| 39 |
+
} catch {
|
| 40 |
+
return []
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
function modelInputArtifacts(value, output = []) {
|
| 45 |
+
if (Array.isArray(value)) {
|
| 46 |
+
for (const item of value) modelInputArtifacts(item, output)
|
| 47 |
+
return output
|
| 48 |
+
}
|
| 49 |
+
if (!value || typeof value !== 'object') return output
|
| 50 |
+
if (typeof value.artifact_path === 'string') {
|
| 51 |
+
output.push(Object.fromEntries(Object.entries(value).filter(([key]) => [
|
| 52 |
+
'type', 'artifact_path', 'sha256', 'bytes', 'width', 'height', 'sequence',
|
| 53 |
+
].includes(key))))
|
| 54 |
+
}
|
| 55 |
+
for (const item of Object.values(value)) modelInputArtifacts(item, output)
|
| 56 |
+
return output
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
function compactTurn(turn) {
|
| 60 |
+
if (!turn || typeof turn !== 'object') return turn
|
| 61 |
+
const compact = Object.fromEntries(
|
| 62 |
+
Object.entries(turn).filter(([key]) => key !== 'model_input'),
|
| 63 |
+
)
|
| 64 |
+
if (turn.model_input && typeof turn.model_input === 'object') {
|
| 65 |
+
compact.model_input = {
|
| 66 |
+
model: turn.model_input.model || null,
|
| 67 |
+
artifacts: modelInputArtifacts(turn.model_input),
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
return compact
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
function traceSummaryPath(runDirectory) {
|
| 74 |
+
return path.join(runDirectory, TRACE_SUMMARY_NAME)
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
function traceTurns(runDirectory) {
|
| 78 |
+
const summaryPath = traceSummaryPath(runDirectory)
|
| 79 |
+
if (fs.existsSync(summaryPath)) return readJsonl(summaryPath)
|
| 80 |
+
const legacyPath = path.join(runDirectory, 'model_turns.jsonl')
|
| 81 |
+
try {
|
| 82 |
+
if (fs.statSync(legacyPath).size <= MAX_INLINE_LEGACY_TRACE_BYTES) {
|
| 83 |
+
return readJsonl(legacyPath).map(compactTurn)
|
| 84 |
+
}
|
| 85 |
+
} catch {
|
| 86 |
+
// Missing or actively-created traces simply have no turns yet.
|
| 87 |
+
}
|
| 88 |
+
return []
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
function manifestByTask() {
|
| 92 |
+
const entries = readJsonl(MANIFEST_PATH)
|
| 93 |
+
return new Map(entries.map((entry) => [entry.task_id, entry]))
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
function safeRunDirectory(runsRoot, runId) {
|
| 97 |
+
if (!runId || runId.includes('\\')) {
|
| 98 |
+
return null
|
| 99 |
+
}
|
| 100 |
+
const segments = runId.split('/')
|
| 101 |
+
if (segments.some((segment) => !segment || segment === '.' || segment === '..')) return null
|
| 102 |
+
const candidate = path.resolve(runsRoot, runId)
|
| 103 |
+
return candidate.startsWith(path.resolve(runsRoot) + path.sep) ? candidate : null
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
function safeArtifact(runDirectory, relativePath) {
|
| 107 |
+
const candidate = path.resolve(runDirectory, relativePath)
|
| 108 |
+
return candidate.startsWith(path.resolve(runDirectory) + path.sep) ? candidate : null
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
function normalizeSha256(value) {
|
| 112 |
+
const sha256 = String(value || '').toLowerCase()
|
| 113 |
+
return /^[a-f0-9]{64}$/.test(sha256) ? sha256 : null
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
function fileSha256(filePath) {
|
| 117 |
+
try {
|
| 118 |
+
if (!fs.statSync(filePath).isFile()) return null
|
| 119 |
+
return createHash('sha256').update(fs.readFileSync(filePath)).digest('hex')
|
| 120 |
+
} catch {
|
| 121 |
+
return null
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
function contentType(filePath) {
|
| 126 |
+
const extension = path.extname(filePath).toLowerCase()
|
| 127 |
+
return {
|
| 128 |
+
'.png': 'image/png',
|
| 129 |
+
'.json': 'application/json; charset=utf-8',
|
| 130 |
+
'.jsonl': 'application/x-ndjson; charset=utf-8',
|
| 131 |
+
'.dwg': 'application/acad',
|
| 132 |
+
}[extension] || 'application/octet-stream'
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
function sendJson(response, status, value) {
|
| 136 |
+
response.statusCode = status
|
| 137 |
+
response.setHeader('Content-Type', 'application/json; charset=utf-8')
|
| 138 |
+
response.setHeader('Cache-Control', 'no-store')
|
| 139 |
+
response.end(JSON.stringify(value))
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
function sendFile(response, filePath, { immutable = false } = {}) {
|
| 143 |
+
if (!filePath || !fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
| 144 |
+
sendJson(response, 404, { error: 'artifact_not_found' })
|
| 145 |
+
return
|
| 146 |
+
}
|
| 147 |
+
response.statusCode = 200
|
| 148 |
+
response.setHeader('Content-Type', contentType(filePath))
|
| 149 |
+
response.setHeader(
|
| 150 |
+
'Cache-Control',
|
| 151 |
+
immutable ? 'private, max-age=31536000, immutable' : 'no-store',
|
| 152 |
+
)
|
| 153 |
+
fs.createReadStream(filePath).pipe(response)
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
function sendPrivateRedirect(response, location) {
|
| 157 |
+
response.statusCode = 307
|
| 158 |
+
response.setHeader('Location', location)
|
| 159 |
+
// Keep the stable local URL cached only inside this browser and comfortably
|
| 160 |
+
// below the five-minute lifetime of the signed origin URL.
|
| 161 |
+
response.setHeader('Cache-Control', 'private, max-age=240')
|
| 162 |
+
response.end()
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
function readRunScores(runDirectory) {
|
| 166 |
+
const visionArtifact = readJson(path.join(
|
| 167 |
+
runDirectory, 'evaluation', 'vision-judge', 'score.json',
|
| 168 |
+
))
|
| 169 |
+
const vision = visionArtifact
|
| 170 |
+
? {
|
| 171 |
+
...visionArtifact,
|
| 172 |
+
score: visionArtifact.overall_score ?? null,
|
| 173 |
+
valid: true,
|
| 174 |
+
failure_reason: null,
|
| 175 |
+
scoring_method: `vision:${visionArtifact.prompt_version || 'unknown'}`,
|
| 176 |
+
geometry_similarity: visionArtifact.component_scores?.geometry ?? null,
|
| 177 |
+
dimension_similarity: visionArtifact.component_scores?.dimensions ?? null,
|
| 178 |
+
annotation_similarity: visionArtifact.component_scores?.annotations ?? null,
|
| 179 |
+
visual_similarity: visionArtifact.component_scores?.presentation ?? null,
|
| 180 |
+
score_source: 'vision_judge',
|
| 181 |
+
}
|
| 182 |
+
: null
|
| 183 |
+
const deterministicArtifact = readJson(path.join(
|
| 184 |
+
runDirectory, 'evaluation', 'score.json',
|
| 185 |
+
))
|
| 186 |
+
const deterministic = deterministicArtifact
|
| 187 |
+
? { ...deterministicArtifact, score_source: 'deterministic' }
|
| 188 |
+
: null
|
| 189 |
+
return {
|
| 190 |
+
vision,
|
| 191 |
+
deterministic,
|
| 192 |
+
displayed: vision || deterministic,
|
| 193 |
+
}
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
function readRunScore(runDirectory) {
|
| 197 |
+
return readRunScores(runDirectory).displayed
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
function scoreSummary(score) {
|
| 201 |
+
if (!score) return null
|
| 202 |
+
return {
|
| 203 |
+
source: score.score_source ?? null,
|
| 204 |
+
score: score.score ?? null,
|
| 205 |
+
valid: score.valid ?? null,
|
| 206 |
+
failureReason: score.failure_reason ?? null,
|
| 207 |
+
method: score.scoring_method ?? null,
|
| 208 |
+
verdict: score.verdict ?? null,
|
| 209 |
+
targetAcquired: score.target_acquired ?? null,
|
| 210 |
+
confidence: score.confidence ?? null,
|
| 211 |
+
needsHumanReview: score.needs_human_review ?? false,
|
| 212 |
+
geometry: score.geometry_similarity ?? null,
|
| 213 |
+
dimensions: score.dimension_similarity ?? null,
|
| 214 |
+
annotations: score.annotation_similarity ?? null,
|
| 215 |
+
presentation: score.visual_similarity ?? null,
|
| 216 |
+
}
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
const ACTIVE_STATES = new Set([
|
| 220 |
+
'starting', 'resetting', 'handoff_ready', 'running', 'finalizing',
|
| 221 |
+
])
|
| 222 |
+
const TERMINAL_EXECUTION_STATES = new Set([
|
| 223 |
+
'completed', 'recovered', 'budget_exhausted', 'model_failed',
|
| 224 |
+
'infrastructure_failed', 'artifact_missing', 'interrupted',
|
| 225 |
+
])
|
| 226 |
+
const ACTIVE_EVALUATION_STAGES = new Set([
|
| 227 |
+
'inspecting', 'deterministic_scoring', 'vision_judging',
|
| 228 |
+
])
|
| 229 |
+
|
| 230 |
+
function executionState(result, state = null) {
|
| 231 |
+
if (TERMINAL_EXECUTION_STATES.has(result?.execution_status)) {
|
| 232 |
+
return result.execution_status
|
| 233 |
+
}
|
| 234 |
+
const stopReason = String(result?.stop_reason || '').toLowerCase()
|
| 235 |
+
const failure = String(result?.failure || '').toLowerCase()
|
| 236 |
+
const artifactBytes = Number(result?.artifact_bytes || 0)
|
| 237 |
+
if (result?.completed === true || stopReason === 'done') {
|
| 238 |
+
return artifactBytes > 0 ? 'completed' : 'artifact_missing'
|
| 239 |
+
}
|
| 240 |
+
if (stopReason === 'recovered_finalized_session' && artifactBytes > 0) return 'recovered'
|
| 241 |
+
if (['max_actions', 'wall_timeout'].includes(stopReason)) return 'budget_exhausted'
|
| 242 |
+
if (['operator_stopped', 'interrupted'].includes(stopReason)) return 'interrupted'
|
| 243 |
+
if (stopReason === 'rollout_error' || [
|
| 244 |
+
'brokererror', 'connecterror', 'connection attempts failed',
|
| 245 |
+
'nodename nor servname', 'transport unavailable', 'ssm',
|
| 246 |
+
].some((marker) => failure.includes(marker))) return 'infrastructure_failed'
|
| 247 |
+
if (result && artifactBytes <= 0 && !failure) return 'artifact_missing'
|
| 248 |
+
if (result) return 'model_failed'
|
| 249 |
+
if (ACTIVE_STATES.has(state?.state)) return state.state
|
| 250 |
+
if (TERMINAL_EXECUTION_STATES.has(state?.state)) return state.state
|
| 251 |
+
return state?.state || 'running'
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
function evaluationState({ status, artifactBytes, score, evaluationStatus = null }) {
|
| 255 |
+
if (status === 'queued') return 'queued'
|
| 256 |
+
if (ACTIVE_STATES.has(status)) return 'running'
|
| 257 |
+
if (ACTIVE_EVALUATION_STAGES.has(evaluationStatus?.stage)) {
|
| 258 |
+
const updatedAt = Date.parse(evaluationStatus?.updated_at || '')
|
| 259 |
+
if (Number.isFinite(updatedAt) && Date.now() - updatedAt > 30 * 60 * 1000) {
|
| 260 |
+
return 'evaluation_interrupted'
|
| 261 |
+
}
|
| 262 |
+
return 'evaluating'
|
| 263 |
+
}
|
| 264 |
+
if (evaluationStatus?.stage === 'retry_pending') return 'evaluation_retrying'
|
| 265 |
+
if (evaluationStatus?.stage === 'failed') return 'evaluation_failed'
|
| 266 |
+
if (score?.valid === false || score?.failure_reason) return 'candidate_invalid'
|
| 267 |
+
if (Number.isFinite(score?.score)) return 'scored'
|
| 268 |
+
if (artifactBytes > 0) return 'awaiting_score'
|
| 269 |
+
if (['model_failed', 'infrastructure_failed', 'artifact_missing', 'interrupted'].includes(status)) return 'run_failed'
|
| 270 |
+
return 'no_artifact'
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
function runSummary(runsRoot, runId) {
|
| 274 |
+
const runDirectory = safeRunDirectory(runsRoot, runId)
|
| 275 |
+
if (!runDirectory) return null
|
| 276 |
+
const result = readJson(path.join(runDirectory, 'result.json'))
|
| 277 |
+
const state = readJson(path.join(runDirectory, 'run-state.json'))
|
| 278 |
+
const evaluationStatus = readJson(path.join(
|
| 279 |
+
runDirectory, 'evaluation', 'status.json',
|
| 280 |
+
))
|
| 281 |
+
const scores = readRunScores(runDirectory)
|
| 282 |
+
const score = scores.displayed
|
| 283 |
+
const stats = fs.statSync(runDirectory)
|
| 284 |
+
const status = executionState(result, state)
|
| 285 |
+
const artifactBytes = result?.artifact_bytes ?? 0
|
| 286 |
+
const executionCanCarryFailure = [
|
| 287 |
+
'model_failed', 'infrastructure_failed', 'artifact_missing', 'interrupted',
|
| 288 |
+
].includes(status)
|
| 289 |
+
return {
|
| 290 |
+
id: runId,
|
| 291 |
+
taskId: state?.task_id || result?.task_id || 'unknown',
|
| 292 |
+
attemptId: state?.attempt_id || result?.attempt_id || null,
|
| 293 |
+
modelId: state?.model_id || result?.model_id || 'unknown',
|
| 294 |
+
provider: state?.provider || result?.provider || (String(result?.model_id || '').includes('nova') ? 'bedrock' : 'unknown'),
|
| 295 |
+
reasoningEffort: state?.reasoning_effort || result?.reasoning_effort || null,
|
| 296 |
+
status,
|
| 297 |
+
stopReason: result?.stop_reason || state?.stop_reason || null,
|
| 298 |
+
failure: executionCanCarryFailure
|
| 299 |
+
? (result?.failure || state?.failure || null)
|
| 300 |
+
: (result?.failure || null),
|
| 301 |
+
acceptedActions: state?.accepted_actions ?? result?.accepted_actions ?? readJsonl(path.join(runDirectory, 'input_receipts.jsonl')).length,
|
| 302 |
+
invalidActions: state?.invalid_actions ?? result?.invalid_actions ?? 0,
|
| 303 |
+
screenshots: state?.screenshots ?? result?.screenshots ?? 0,
|
| 304 |
+
elapsedSeconds: result?.elapsed_s ?? null,
|
| 305 |
+
artifactBytes,
|
| 306 |
+
score: score?.score ?? null,
|
| 307 |
+
valid: score?.valid ?? null,
|
| 308 |
+
scoreFailure: score?.failure_reason ?? null,
|
| 309 |
+
scoreMethod: score?.scoring_method ?? null,
|
| 310 |
+
scoreSource: score?.score_source ?? null,
|
| 311 |
+
verdict: score?.verdict ?? null,
|
| 312 |
+
targetAcquired: score?.target_acquired ?? null,
|
| 313 |
+
judgeConfidence: score?.confidence ?? null,
|
| 314 |
+
needsHumanReview: score?.needs_human_review ?? false,
|
| 315 |
+
visionScore: scoreSummary(scores.vision),
|
| 316 |
+
deterministicScore: scoreSummary(scores.deterministic),
|
| 317 |
+
evaluationState: evaluationState({
|
| 318 |
+
status, artifactBytes, score, evaluationStatus,
|
| 319 |
+
}),
|
| 320 |
+
evaluationStage: evaluationStatus?.stage || null,
|
| 321 |
+
evaluationError: evaluationStatus?.error || null,
|
| 322 |
+
updatedAt: evaluationStatus?.updated_at || state?.updated_at || stats.mtime.toISOString(),
|
| 323 |
+
}
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
function listRuns(runsRoot) {
|
| 327 |
+
if (!fs.existsSync(runsRoot)) return []
|
| 328 |
+
const runIds = []
|
| 329 |
+
const visit = (directory, relativeDirectory, depth) => {
|
| 330 |
+
if (depth > 4) return
|
| 331 |
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
| 332 |
+
if (!entry.isDirectory()) continue
|
| 333 |
+
const relativePath = relativeDirectory
|
| 334 |
+
? `${relativeDirectory}/${entry.name}`
|
| 335 |
+
: entry.name
|
| 336 |
+
const child = path.join(directory, entry.name)
|
| 337 |
+
const isRun = ['run-state.json', 'result.json', 'model_turns.jsonl']
|
| 338 |
+
.some((name) => fs.existsSync(path.join(child, name)))
|
| 339 |
+
if (isRun) runIds.push(relativePath)
|
| 340 |
+
else visit(child, relativePath, depth + 1)
|
| 341 |
+
}
|
| 342 |
+
}
|
| 343 |
+
visit(runsRoot, '', 1)
|
| 344 |
+
return runIds
|
| 345 |
+
.map((runId) => runSummary(runsRoot, runId))
|
| 346 |
+
.filter(Boolean)
|
| 347 |
+
.sort((left, right) => String(right.updatedAt).localeCompare(String(left.updatedAt)))
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
function usageSummary(turns, pricing = null) {
|
| 351 |
+
const totals = {
|
| 352 |
+
inputTokens: 0,
|
| 353 |
+
cachedInputTokens: 0,
|
| 354 |
+
outputTokens: 0,
|
| 355 |
+
reasoningOutputTokens: 0,
|
| 356 |
+
totalTokens: 0,
|
| 357 |
+
modelTurns: turns.length,
|
| 358 |
+
apiCost: pricing ? 0 : null,
|
| 359 |
+
}
|
| 360 |
+
for (const turn of turns) {
|
| 361 |
+
const usage = turn?.usage || {}
|
| 362 |
+
const input = Number(usage.input_tokens ?? usage.inputTokens ?? 0) || 0
|
| 363 |
+
const cached = Math.min(
|
| 364 |
+
input,
|
| 365 |
+
Number(usage.cached_input_tokens ?? usage.cachedInputTokens ?? 0) || 0,
|
| 366 |
+
)
|
| 367 |
+
const output = Number(usage.output_tokens ?? usage.outputTokens ?? 0) || 0
|
| 368 |
+
const reasoning = Number(
|
| 369 |
+
usage.reasoning_output_tokens ?? usage.reasoningOutputTokens ?? 0,
|
| 370 |
+
) || 0
|
| 371 |
+
totals.inputTokens += input
|
| 372 |
+
totals.cachedInputTokens += cached
|
| 373 |
+
totals.outputTokens += output
|
| 374 |
+
totals.reasoningOutputTokens += reasoning
|
| 375 |
+
totals.totalTokens += Number(usage.total_tokens ?? usage.totalTokens ?? input + output) || 0
|
| 376 |
+
if (pricing) {
|
| 377 |
+
const isLong = pricing.long_context_threshold_tokens != null
|
| 378 |
+
&& input > Number(pricing.long_context_threshold_tokens)
|
| 379 |
+
const inputMultiplier = isLong
|
| 380 |
+
? Number(pricing.long_context_input_multiplier ?? 1)
|
| 381 |
+
: 1
|
| 382 |
+
const outputMultiplier = isLong
|
| 383 |
+
? Number(pricing.long_context_output_multiplier ?? 1)
|
| 384 |
+
: 1
|
| 385 |
+
const uncached = Math.max(0, input - cached)
|
| 386 |
+
totals.apiCost += inputMultiplier * (
|
| 387 |
+
uncached * Number(pricing.input_per_million ?? 0)
|
| 388 |
+
+ cached * Number(pricing.cached_input_per_million ?? 0)
|
| 389 |
+
) / 1_000_000
|
| 390 |
+
totals.apiCost += outputMultiplier
|
| 391 |
+
* output * Number(pricing.output_per_million ?? 0) / 1_000_000
|
| 392 |
+
}
|
| 393 |
+
}
|
| 394 |
+
return totals
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
function cachedUsageSummary(filePath, pricing) {
|
| 398 |
+
let stats
|
| 399 |
+
try {
|
| 400 |
+
stats = fs.statSync(filePath)
|
| 401 |
+
} catch {
|
| 402 |
+
return null
|
| 403 |
+
}
|
| 404 |
+
const cacheKey = JSON.stringify([
|
| 405 |
+
stats.size, stats.mtimeMs, pricing || null,
|
| 406 |
+
])
|
| 407 |
+
const cached = usageCache.get(filePath)
|
| 408 |
+
if (cached?.key === cacheKey) return cached.value
|
| 409 |
+
const value = usageSummary(readJsonl(filePath), pricing)
|
| 410 |
+
usageCache.set(filePath, { key: cacheKey, value })
|
| 411 |
+
if (usageCache.size > 64) usageCache.delete(usageCache.keys().next().value)
|
| 412 |
+
return value
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
function runUsage(
|
| 416 |
+
runDirectory,
|
| 417 |
+
pricing,
|
| 418 |
+
runState = null,
|
| 419 |
+
result = null,
|
| 420 |
+
{ preferAggregate = false } = {},
|
| 421 |
+
) {
|
| 422 |
+
const summaryPath = traceSummaryPath(runDirectory)
|
| 423 |
+
if (!preferAggregate) {
|
| 424 |
+
try {
|
| 425 |
+
if (fs.statSync(summaryPath).size <= MAX_INLINE_USAGE_SUMMARY_BYTES) {
|
| 426 |
+
const compact = cachedUsageSummary(summaryPath, pricing)
|
| 427 |
+
if (compact) return compact
|
| 428 |
+
}
|
| 429 |
+
} catch {
|
| 430 |
+
// Fall through to the legacy trace or live aggregate.
|
| 431 |
+
}
|
| 432 |
+
}
|
| 433 |
+
|
| 434 |
+
const legacyPath = path.join(runDirectory, 'model_turns.jsonl')
|
| 435 |
+
if (!preferAggregate) {
|
| 436 |
+
try {
|
| 437 |
+
if (fs.statSync(legacyPath).size <= MAX_INLINE_LEGACY_TRACE_BYTES) {
|
| 438 |
+
return cachedUsageSummary(legacyPath, pricing)
|
| 439 |
+
}
|
| 440 |
+
} catch {
|
| 441 |
+
// Fall through to the live aggregate written by newer pilots.
|
| 442 |
+
}
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
const raw = runState?.usage || result?.usage
|
| 446 |
+
const modelTurns = Number(runState?.turn_count ?? 0) || 0
|
| 447 |
+
if (!raw || typeof raw !== 'object') {
|
| 448 |
+
return {
|
| 449 |
+
inputTokens: 0,
|
| 450 |
+
cachedInputTokens: 0,
|
| 451 |
+
outputTokens: 0,
|
| 452 |
+
reasoningOutputTokens: 0,
|
| 453 |
+
totalTokens: 0,
|
| 454 |
+
modelTurns,
|
| 455 |
+
apiCost: modelTurns ? null : (pricing ? 0 : null),
|
| 456 |
+
}
|
| 457 |
+
}
|
| 458 |
+
const inputTokens = Number(raw.input_tokens ?? raw.inputTokens ?? 0) || 0
|
| 459 |
+
const outputTokens = Number(raw.output_tokens ?? raw.outputTokens ?? 0) || 0
|
| 460 |
+
return {
|
| 461 |
+
inputTokens,
|
| 462 |
+
cachedInputTokens: Number(
|
| 463 |
+
raw.cached_input_tokens ?? raw.cache_read_input_tokens
|
| 464 |
+
?? raw.cachedInputTokens ?? 0,
|
| 465 |
+
) || 0,
|
| 466 |
+
outputTokens,
|
| 467 |
+
reasoningOutputTokens: Number(
|
| 468 |
+
raw.reasoning_output_tokens ?? raw.reasoningOutputTokens ?? 0,
|
| 469 |
+
) || 0,
|
| 470 |
+
totalTokens: Number(
|
| 471 |
+
raw.total_tokens ?? raw.totalTokens ?? inputTokens + outputTokens,
|
| 472 |
+
) || 0,
|
| 473 |
+
modelTurns,
|
| 474 |
+
// Exact cost can depend on per-request long-context thresholds. The compact
|
| 475 |
+
// per-turn sidecar is authoritative whenever pricing is configured.
|
| 476 |
+
apiCost: null,
|
| 477 |
+
}
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
function listBenchmarkIds(runsRoot) {
|
| 481 |
+
if (!fs.existsSync(runsRoot)) return []
|
| 482 |
+
const ids = []
|
| 483 |
+
const visit = (directory, relativeDirectory, depth) => {
|
| 484 |
+
if (depth > 4) return
|
| 485 |
+
if (fs.existsSync(path.join(directory, 'batch-state.json'))) {
|
| 486 |
+
ids.push(relativeDirectory)
|
| 487 |
+
return
|
| 488 |
+
}
|
| 489 |
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
| 490 |
+
if (!entry.isDirectory()) continue
|
| 491 |
+
const relativePath = relativeDirectory
|
| 492 |
+
? `${relativeDirectory}/${entry.name}`
|
| 493 |
+
: entry.name
|
| 494 |
+
visit(path.join(directory, entry.name), relativePath, depth + 1)
|
| 495 |
+
}
|
| 496 |
+
}
|
| 497 |
+
visit(runsRoot, '', 0)
|
| 498 |
+
return ids.filter(Boolean)
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
function benchmarkDetail(
|
| 502 |
+
runsRoot,
|
| 503 |
+
benchmarkId,
|
| 504 |
+
{ preferAggregateUsage = false } = {},
|
| 505 |
+
) {
|
| 506 |
+
const directory = safeRunDirectory(runsRoot, benchmarkId)
|
| 507 |
+
if (!directory || !fs.existsSync(directory)) return null
|
| 508 |
+
const state = readJson(path.join(directory, 'batch-state.json'))
|
| 509 |
+
if (!state || !state.rollouts || typeof state.rollouts !== 'object') return null
|
| 510 |
+
const manifest = manifestByTask()
|
| 511 |
+
const pricing = state.pricing || null
|
| 512 |
+
const tasks = Object.entries(state.rollouts).map(([rolloutId, row]) => {
|
| 513 |
+
const runId = `${benchmarkId}/rollouts/${rolloutId}`
|
| 514 |
+
const runDirectory = path.join(directory, 'rollouts', rolloutId)
|
| 515 |
+
const hasRun = fs.existsSync(runDirectory)
|
| 516 |
+
const summary = hasRun ? runSummary(runsRoot, runId) : null
|
| 517 |
+
const runState = hasRun ? readJson(path.join(runDirectory, 'run-state.json')) : null
|
| 518 |
+
const result = hasRun ? readJson(path.join(runDirectory, 'result.json')) : null
|
| 519 |
+
const evaluationStatus = hasRun ? readJson(path.join(
|
| 520 |
+
runDirectory, 'evaluation', 'status.json',
|
| 521 |
+
)) : null
|
| 522 |
+
const scores = hasRun
|
| 523 |
+
? readRunScores(runDirectory)
|
| 524 |
+
: { vision: null, deterministic: null, displayed: null }
|
| 525 |
+
const score = scores.displayed
|
| 526 |
+
const usage = hasRun
|
| 527 |
+
? runUsage(runDirectory, pricing, runState, result, {
|
| 528 |
+
preferAggregate: preferAggregateUsage,
|
| 529 |
+
})
|
| 530 |
+
: usageSummary([], pricing)
|
| 531 |
+
const taskId = row.task_id || summary?.taskId || state.task_id || 'unknown'
|
| 532 |
+
const task = manifest.get(taskId)
|
| 533 |
+
// Batch state covers the complete sandbox lifecycle, including automatic
|
| 534 |
+
// evaluation. The pilot's local result can say completed while its wrapper
|
| 535 |
+
// is still judging or has failed evaluation.
|
| 536 |
+
const rowStatus = row.state || 'queued'
|
| 537 |
+
const summaryIsTerminal = TERMINAL_EXECUTION_STATES.has(summary?.status)
|
| 538 |
+
const status = !hasRun && rowStatus === 'running'
|
| 539 |
+
? 'starting'
|
| 540 |
+
: summaryIsTerminal
|
| 541 |
+
? summary.status
|
| 542 |
+
: summary?.status || rowStatus || 'queued'
|
| 543 |
+
const artifactBytes = summary?.artifactBytes ?? result?.artifact_bytes ?? 0
|
| 544 |
+
const executionCanCarryFailure = [
|
| 545 |
+
'model_failed', 'infrastructure_failed', 'artifact_missing', 'interrupted',
|
| 546 |
+
].includes(status)
|
| 547 |
+
return {
|
| 548 |
+
rolloutId,
|
| 549 |
+
runId,
|
| 550 |
+
taskId,
|
| 551 |
+
level: task?.level || 'unknown',
|
| 552 |
+
drawingType: task?.drawing_type || '2d',
|
| 553 |
+
split: task?.split || 'unknown',
|
| 554 |
+
displayName: row.display_name || rolloutId,
|
| 555 |
+
modelId: row.model_id || summary?.modelId || 'unknown',
|
| 556 |
+
provider: row.provider || summary?.provider || 'unknown',
|
| 557 |
+
status,
|
| 558 |
+
stopReason: summary?.stopReason || result?.stop_reason || null,
|
| 559 |
+
failure: executionCanCarryFailure
|
| 560 |
+
? (summary?.failure || result?.failure || row.failure || null)
|
| 561 |
+
: (result?.failure || null),
|
| 562 |
+
acceptedActions: summary?.acceptedActions ?? result?.accepted_actions ?? 0,
|
| 563 |
+
invalidActions: summary?.invalidActions ?? result?.invalid_actions ?? 0,
|
| 564 |
+
elapsedSeconds: summary?.elapsedSeconds ?? result?.elapsed_s ?? null,
|
| 565 |
+
setupElapsedSeconds: result?.setup_elapsed_s ?? null,
|
| 566 |
+
rolloutElapsedSeconds: result?.rollout_elapsed_s ?? null,
|
| 567 |
+
artifactBytes,
|
| 568 |
+
score: score?.score ?? null,
|
| 569 |
+
valid: score?.valid ?? null,
|
| 570 |
+
scoreFailure: score?.failure_reason ?? null,
|
| 571 |
+
scoreMethod: score?.scoring_method ?? null,
|
| 572 |
+
scoreSource: score?.score_source ?? null,
|
| 573 |
+
verdict: score?.verdict ?? null,
|
| 574 |
+
targetAcquired: score?.target_acquired ?? null,
|
| 575 |
+
judgeConfidence: score?.confidence ?? null,
|
| 576 |
+
needsHumanReview: score?.needs_human_review ?? false,
|
| 577 |
+
visionScore: scoreSummary(scores.vision),
|
| 578 |
+
deterministicScore: scoreSummary(scores.deterministic),
|
| 579 |
+
geometrySimilarity: score?.geometry_similarity ?? null,
|
| 580 |
+
dimensionSimilarity: score?.dimension_similarity ?? null,
|
| 581 |
+
annotationSimilarity: score?.annotation_similarity ?? null,
|
| 582 |
+
visualSimilarity: score?.visual_similarity ?? null,
|
| 583 |
+
evaluationState: summary?.evaluationState || evaluationState({
|
| 584 |
+
status, artifactBytes, score, evaluationStatus,
|
| 585 |
+
}),
|
| 586 |
+
evaluationStage: summary?.evaluationStage || evaluationStatus?.stage || null,
|
| 587 |
+
evaluationError: summary?.evaluationError || evaluationStatus?.error || null,
|
| 588 |
+
startedAt: row.started_at || null,
|
| 589 |
+
finishedAt: row.finished_at || null,
|
| 590 |
+
updatedAt: summary?.updatedAt || row.finished_at || row.started_at || state.updated_at,
|
| 591 |
+
usage,
|
| 592 |
+
}
|
| 593 |
+
})
|
| 594 |
+
const numericScores = tasks
|
| 595 |
+
.map((task) => task.score)
|
| 596 |
+
.filter((score) => Number.isFinite(score))
|
| 597 |
+
const visionScores = tasks
|
| 598 |
+
.map((task) => task.visionScore?.score)
|
| 599 |
+
.filter((score) => Number.isFinite(score))
|
| 600 |
+
const deterministicScores = tasks
|
| 601 |
+
.map((task) => task.deterministicScore?.score)
|
| 602 |
+
.filter((score) => Number.isFinite(score))
|
| 603 |
+
const levels = {}
|
| 604 |
+
const drawingTypes = {}
|
| 605 |
+
const evaluationStates = {}
|
| 606 |
+
const executionStates = {}
|
| 607 |
+
const verdicts = {}
|
| 608 |
+
for (const task of tasks) {
|
| 609 |
+
const level = task.level
|
| 610 |
+
levels[level] ||= { total: 0, completed: 0, scored: 0, scoreSum: 0, cost: 0 }
|
| 611 |
+
levels[level].total += 1
|
| 612 |
+
if (task.status === 'completed') levels[level].completed += 1
|
| 613 |
+
if (Number.isFinite(task.score)) {
|
| 614 |
+
levels[level].scored += 1
|
| 615 |
+
levels[level].scoreSum += task.score
|
| 616 |
+
}
|
| 617 |
+
levels[level].cost += task.usage.apiCost || 0
|
| 618 |
+
const drawingType = task.drawingType
|
| 619 |
+
drawingTypes[drawingType] ||= { total: 0, scored: 0, scoreSum: 0 }
|
| 620 |
+
drawingTypes[drawingType].total += 1
|
| 621 |
+
if (Number.isFinite(task.score)) {
|
| 622 |
+
drawingTypes[drawingType].scored += 1
|
| 623 |
+
drawingTypes[drawingType].scoreSum += task.score
|
| 624 |
+
}
|
| 625 |
+
evaluationStates[task.evaluationState] = (
|
| 626 |
+
evaluationStates[task.evaluationState] || 0
|
| 627 |
+
) + 1
|
| 628 |
+
executionStates[task.status] = (executionStates[task.status] || 0) + 1
|
| 629 |
+
if (task.verdict) verdicts[task.verdict] = (verdicts[task.verdict] || 0) + 1
|
| 630 |
+
}
|
| 631 |
+
for (const level of Object.values(levels)) {
|
| 632 |
+
level.meanScore = level.scored ? level.scoreSum / level.scored : null
|
| 633 |
+
delete level.scoreSum
|
| 634 |
+
}
|
| 635 |
+
for (const drawingType of Object.values(drawingTypes)) {
|
| 636 |
+
drawingType.meanScore = drawingType.scored
|
| 637 |
+
? drawingType.scoreSum / drawingType.scored
|
| 638 |
+
: null
|
| 639 |
+
delete drawingType.scoreSum
|
| 640 |
+
}
|
| 641 |
+
const aggregateUsage = tasks.reduce((aggregate, task) => {
|
| 642 |
+
for (const key of [
|
| 643 |
+
'inputTokens', 'cachedInputTokens', 'outputTokens',
|
| 644 |
+
'reasoningOutputTokens', 'totalTokens', 'modelTurns',
|
| 645 |
+
]) aggregate[key] += task.usage[key]
|
| 646 |
+
if (aggregate.apiCost != null) {
|
| 647 |
+
if (task.usage.apiCost == null && task.usage.modelTurns > 0) {
|
| 648 |
+
aggregate.apiCost = null
|
| 649 |
+
} else {
|
| 650 |
+
aggregate.apiCost += task.usage.apiCost || 0
|
| 651 |
+
}
|
| 652 |
+
}
|
| 653 |
+
return aggregate
|
| 654 |
+
}, {
|
| 655 |
+
inputTokens: 0,
|
| 656 |
+
cachedInputTokens: 0,
|
| 657 |
+
outputTokens: 0,
|
| 658 |
+
reasoningOutputTokens: 0,
|
| 659 |
+
totalTokens: 0,
|
| 660 |
+
modelTurns: 0,
|
| 661 |
+
apiCost: pricing ? 0 : null,
|
| 662 |
+
})
|
| 663 |
+
const aggregate = {
|
| 664 |
+
totalTasks: tasks.length,
|
| 665 |
+
queuedTasks: tasks.filter((task) => task.status === 'queued').length,
|
| 666 |
+
activeTasks: tasks.filter((task) => ACTIVE_STATES.has(task.status)).length,
|
| 667 |
+
settledTasks: tasks.filter((task) => TERMINAL_EXECUTION_STATES.has(task.status)).length,
|
| 668 |
+
completedTasks: tasks.filter((task) => task.status === 'completed').length,
|
| 669 |
+
recoveredTasks: tasks.filter((task) => task.status === 'recovered').length,
|
| 670 |
+
budgetExhaustedTasks: tasks.filter((task) => task.status === 'budget_exhausted').length,
|
| 671 |
+
failedTasks: tasks.filter((task) => task.status === 'model_failed').length,
|
| 672 |
+
infrastructureFailedTasks: tasks.filter((task) => task.status === 'infrastructure_failed').length,
|
| 673 |
+
artifactMissingTasks: tasks.filter((task) => task.status === 'artifact_missing').length,
|
| 674 |
+
artifactTasks: tasks.filter((task) => task.artifactBytes > 0).length,
|
| 675 |
+
scoredTasks: numericScores.length,
|
| 676 |
+
meanScore: numericScores.length
|
| 677 |
+
? numericScores.reduce((sum, score) => sum + score, 0) / numericScores.length
|
| 678 |
+
: null,
|
| 679 |
+
visionScoredTasks: visionScores.length,
|
| 680 |
+
visionMeanScore: visionScores.length
|
| 681 |
+
? visionScores.reduce((sum, score) => sum + score, 0) / visionScores.length
|
| 682 |
+
: null,
|
| 683 |
+
deterministicScoredTasks: deterministicScores.length,
|
| 684 |
+
deterministicMeanScore: deterministicScores.length
|
| 685 |
+
? deterministicScores.reduce((sum, score) => sum + score, 0)
|
| 686 |
+
/ deterministicScores.length
|
| 687 |
+
: null,
|
| 688 |
+
acceptedActions: tasks.reduce((sum, task) => sum + task.acceptedActions, 0),
|
| 689 |
+
invalidActions: tasks.reduce((sum, task) => sum + task.invalidActions, 0),
|
| 690 |
+
aggregateElapsedSeconds: tasks.reduce(
|
| 691 |
+
(sum, task) => sum + (task.elapsedSeconds || 0), 0,
|
| 692 |
+
),
|
| 693 |
+
usage: aggregateUsage,
|
| 694 |
+
levels,
|
| 695 |
+
drawingTypes,
|
| 696 |
+
evaluationStates,
|
| 697 |
+
executionStates,
|
| 698 |
+
verdicts,
|
| 699 |
+
}
|
| 700 |
+
const models = [...new Set(tasks.map((task) => task.modelId))].sort()
|
| 701 |
+
const providers = [...new Set(tasks.map((task) => task.provider))].sort()
|
| 702 |
+
const reportedStatus = state.state || 'running'
|
| 703 |
+
const updatedAtMs = Date.parse(state.updated_at || '')
|
| 704 |
+
const controllerIsStale = ['starting', 'running'].includes(reportedStatus)
|
| 705 |
+
&& aggregate.activeTasks === 0
|
| 706 |
+
&& aggregate.queuedTasks > 0
|
| 707 |
+
&& !aggregate.evaluationStates.evaluating
|
| 708 |
+
&& Number.isFinite(updatedAtMs)
|
| 709 |
+
&& Date.now() - updatedAtMs > 5 * 60 * 1000
|
| 710 |
+
const hasExecutionIssues = aggregate.failedTasks > 0
|
| 711 |
+
|| aggregate.infrastructureFailedTasks > 0
|
| 712 |
+
|| aggregate.artifactMissingTasks > 0
|
| 713 |
+
|| aggregate.evaluationStates.evaluation_failed > 0
|
| 714 |
+
|| aggregate.evaluationStates.evaluation_interrupted > 0
|
| 715 |
+
const derivedStatus = aggregate.activeTasks > 0
|
| 716 |
+
? 'running'
|
| 717 |
+
: aggregate.queuedTasks > 0
|
| 718 |
+
? (controllerIsStale ? 'interrupted' : reportedStatus)
|
| 719 |
+
: aggregate.settledTasks === aggregate.totalTasks
|
| 720 |
+
? (hasExecutionIssues ? 'completed_with_issues' : 'completed')
|
| 721 |
+
: reportedStatus
|
| 722 |
+
return {
|
| 723 |
+
id: benchmarkId,
|
| 724 |
+
batchId: state.batch_id || null,
|
| 725 |
+
name: state.benchmark_name || state.batch_id || benchmarkId,
|
| 726 |
+
status: derivedStatus,
|
| 727 |
+
modelId: models.length === 1 ? models[0] : `${models.length} models`,
|
| 728 |
+
provider: providers.length === 1 ? providers[0] : `${providers.length} providers`,
|
| 729 |
+
models,
|
| 730 |
+
providers,
|
| 731 |
+
startedAt: state.started_at || null,
|
| 732 |
+
finishedAt: state.finished_at || null,
|
| 733 |
+
updatedAt: state.updated_at || null,
|
| 734 |
+
maxConcurrency: state.max_concurrency || state.preflight?.quota?.requested_instances || null,
|
| 735 |
+
pricing,
|
| 736 |
+
aggregate,
|
| 737 |
+
tasks,
|
| 738 |
+
}
|
| 739 |
+
}
|
| 740 |
+
|
| 741 |
+
function listBenchmarks(runsRoot) {
|
| 742 |
+
return listBenchmarkIds(runsRoot)
|
| 743 |
+
// Registry refreshes must never synchronously parse every model-turn file
|
| 744 |
+
// across every historical run. Live run-state aggregates are sufficient
|
| 745 |
+
// for registry totals; exact per-turn cost remains available in traces.
|
| 746 |
+
.map((id) => benchmarkDetail(runsRoot, id, { preferAggregateUsage: true }))
|
| 747 |
+
.filter(Boolean)
|
| 748 |
+
.map((benchmark) => ({
|
| 749 |
+
id: benchmark.id,
|
| 750 |
+
batchId: benchmark.batchId,
|
| 751 |
+
name: benchmark.name,
|
| 752 |
+
status: benchmark.status,
|
| 753 |
+
modelId: benchmark.modelId,
|
| 754 |
+
provider: benchmark.provider,
|
| 755 |
+
models: benchmark.models,
|
| 756 |
+
providers: benchmark.providers,
|
| 757 |
+
startedAt: benchmark.startedAt,
|
| 758 |
+
updatedAt: benchmark.updatedAt,
|
| 759 |
+
maxConcurrency: benchmark.maxConcurrency,
|
| 760 |
+
aggregate: benchmark.aggregate,
|
| 761 |
+
}))
|
| 762 |
+
.sort((left, right) => String(right.updatedAt).localeCompare(String(left.updatedAt)))
|
| 763 |
+
}
|
| 764 |
+
|
| 765 |
+
function traceDetail(runsRoot, runId) {
|
| 766 |
+
const runDirectory = safeRunDirectory(runsRoot, runId)
|
| 767 |
+
if (!runDirectory || !fs.existsSync(runDirectory)) return null
|
| 768 |
+
const summary = runSummary(runsRoot, runId)
|
| 769 |
+
const frameDirectory = path.join(runDirectory, 'frames')
|
| 770 |
+
const localFrames = fs.existsSync(frameDirectory)
|
| 771 |
+
? fs.readdirSync(frameDirectory).filter((name) => /^frame_\d+\.png$/.test(name)).sort()
|
| 772 |
+
: []
|
| 773 |
+
const turns = traceTurns(runDirectory)
|
| 774 |
+
const state = readJson(path.join(runDirectory, 'run-state.json'))
|
| 775 |
+
const frameNames = new Set(localFrames)
|
| 776 |
+
const frameSha256 = new Map()
|
| 777 |
+
let referenceSha256 = null
|
| 778 |
+
for (const turn of turns) {
|
| 779 |
+
for (const artifact of modelInputArtifacts(turn?.model_input)) {
|
| 780 |
+
if (String(artifact.artifact_path || '') === 'reference.png') {
|
| 781 |
+
referenceSha256 ||= normalizeSha256(artifact.sha256)
|
| 782 |
+
}
|
| 783 |
+
const match = String(artifact.artifact_path || '').match(/^frames\/(frame_\d+\.png)$/)
|
| 784 |
+
if (match) {
|
| 785 |
+
frameNames.add(match[1])
|
| 786 |
+
const sha256 = normalizeSha256(artifact.sha256)
|
| 787 |
+
if (sha256) frameSha256.set(match[1], sha256)
|
| 788 |
+
}
|
| 789 |
+
}
|
| 790 |
+
}
|
| 791 |
+
const acceptedActions = Number(state?.accepted_actions)
|
| 792 |
+
if (Number.isInteger(acceptedActions) && acceptedActions >= 0) {
|
| 793 |
+
frameNames.add(`frame_${String(acceptedActions).padStart(4, '0')}.png`)
|
| 794 |
+
}
|
| 795 |
+
const frames = [...frameNames].sort()
|
| 796 |
+
const fullTracePath = path.join(runDirectory, 'model_turns.jsonl')
|
| 797 |
+
const scores = readRunScores(runDirectory)
|
| 798 |
+
const score = scores.displayed
|
| 799 |
+
const hasTrustedRender = Boolean(
|
| 800 |
+
fs.existsSync(path.join(runDirectory, 'evaluation', 'render.png'))
|
| 801 |
+
|| fs.existsSync(path.join(runDirectory, 'evaluation', 'candidate-render.png'))
|
| 802 |
+
|| score?.score_source === 'vision_judge',
|
| 803 |
+
)
|
| 804 |
+
const traceIndexed = fs.existsSync(traceSummaryPath(runDirectory))
|
| 805 |
+
|| !fs.existsSync(fullTracePath)
|
| 806 |
+
|| fs.statSync(fullTracePath).size <= MAX_INLINE_LEGACY_TRACE_BYTES
|
| 807 |
+
return {
|
| 808 |
+
summary,
|
| 809 |
+
state,
|
| 810 |
+
result: readJson(path.join(runDirectory, 'result.json')),
|
| 811 |
+
score,
|
| 812 |
+
scores: {
|
| 813 |
+
vision: scores.vision,
|
| 814 |
+
deterministic: scores.deterministic,
|
| 815 |
+
},
|
| 816 |
+
turns,
|
| 817 |
+
receipts: readJsonl(path.join(runDirectory, 'input_receipts.jsonl')),
|
| 818 |
+
traceIndexed,
|
| 819 |
+
traceNotice: traceIndexed
|
| 820 |
+
? null
|
| 821 |
+
: 'This legacy trace needs a compact viewer index before its turns can be displayed.',
|
| 822 |
+
frames: frames.map((name) => {
|
| 823 |
+
const sha256 = frameSha256.get(name) || null
|
| 824 |
+
const version = sha256 ? `?sha256=${encodeURIComponent(sha256)}` : ''
|
| 825 |
+
return {
|
| 826 |
+
name,
|
| 827 |
+
sha256,
|
| 828 |
+
url: `/api/runs/${encodeURIComponent(runId)}/file/frames/${encodeURIComponent(name)}${version}`,
|
| 829 |
+
}
|
| 830 |
+
}),
|
| 831 |
+
// A rollout always publishes its exact model-visible reference to cloud
|
| 832 |
+
// storage. Keep the URL run-scoped so lightweight dashboard deployments do
|
| 833 |
+
// not need a second copy of the private task corpus. The file route falls
|
| 834 |
+
// back to the local manifest image for local-only development runs.
|
| 835 |
+
referenceUrl: `/api/runs/${encodeURIComponent(runId)}/file/reference.png${
|
| 836 |
+
referenceSha256 ? `?sha256=${encodeURIComponent(referenceSha256)}` : ''
|
| 837 |
+
}`,
|
| 838 |
+
artifacts: {
|
| 839 |
+
dwg: fs.existsSync(path.join(runDirectory, 'attempt.dwg'))
|
| 840 |
+
? `/api/runs/${encodeURIComponent(runId)}/file/attempt.dwg`
|
| 841 |
+
: null,
|
| 842 |
+
candidateRender: hasTrustedRender
|
| 843 |
+
? `/api/runs/${encodeURIComponent(runId)}/file/evaluation/render.png`
|
| 844 |
+
: null,
|
| 845 |
+
diffRender: fs.existsSync(path.join(runDirectory, 'evaluation', 'diff.png'))
|
| 846 |
+
? `/api/runs/${encodeURIComponent(runId)}/file/evaluation/diff.png`
|
| 847 |
+
: null,
|
| 848 |
+
},
|
| 849 |
+
}
|
| 850 |
+
}
|
| 851 |
+
|
| 852 |
+
export function createTraceApi(options = {}) {
|
| 853 |
+
const runsRoot = path.resolve(options.runsRoot || process.env.AUTOCAD_BENCH_RUNS_ROOT || DEFAULT_RUNS_ROOT)
|
| 854 |
+
const manifest = manifestByTask()
|
| 855 |
+
async function refreshCloudMetadata(loader, request) {
|
| 856 |
+
if (typeof loader !== 'function') return
|
| 857 |
+
try {
|
| 858 |
+
await loader(request)
|
| 859 |
+
} catch {
|
| 860 |
+
// Cloud mirroring is best effort. A transient AWS/auth failure must not
|
| 861 |
+
// hide an already-synchronized local trace or take down the registry.
|
| 862 |
+
}
|
| 863 |
+
}
|
| 864 |
+
return async function handleTraceApi(request, response) {
|
| 865 |
+
const url = new URL(request.url || '/', 'http://127.0.0.1')
|
| 866 |
+
if (!url.pathname.startsWith('/api/')) return false
|
| 867 |
+
if (request.method !== 'GET') {
|
| 868 |
+
sendJson(response, 405, { error: 'method_not_allowed' })
|
| 869 |
+
return true
|
| 870 |
+
}
|
| 871 |
+
if (url.pathname === '/api/health') {
|
| 872 |
+
sendJson(response, 200, { status: 'ok', runsRoot })
|
| 873 |
+
return true
|
| 874 |
+
}
|
| 875 |
+
if (url.pathname === '/api/runs') {
|
| 876 |
+
sendJson(response, 200, { runs: listRuns(runsRoot) })
|
| 877 |
+
return true
|
| 878 |
+
}
|
| 879 |
+
if (url.pathname === '/api/benchmarks') {
|
| 880 |
+
sendJson(response, 200, { benchmarks: listBenchmarks(runsRoot) })
|
| 881 |
+
return true
|
| 882 |
+
}
|
| 883 |
+
const benchmarkMatch = url.pathname.match(/^\/api\/benchmarks\/([^/]+)$/)
|
| 884 |
+
if (benchmarkMatch) {
|
| 885 |
+
const benchmarkId = decodeURIComponent(benchmarkMatch[1])
|
| 886 |
+
const benchmarkDirectory = safeRunDirectory(runsRoot, benchmarkId)
|
| 887 |
+
if (benchmarkDirectory) {
|
| 888 |
+
await refreshCloudMetadata(options.cloudBenchmarkLoader, {
|
| 889 |
+
benchmarkId,
|
| 890 |
+
benchmarkDirectory,
|
| 891 |
+
})
|
| 892 |
+
}
|
| 893 |
+
const detail = benchmarkDetail(runsRoot, benchmarkId)
|
| 894 |
+
sendJson(response, detail ? 200 : 404, detail || { error: 'benchmark_not_found' })
|
| 895 |
+
return true
|
| 896 |
+
}
|
| 897 |
+
const runFileMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/file\/(.+)$/)
|
| 898 |
+
if (runFileMatch) {
|
| 899 |
+
const runId = decodeURIComponent(runFileMatch[1])
|
| 900 |
+
const relativePath = decodeURIComponent(runFileMatch[2])
|
| 901 |
+
const runDirectory = safeRunDirectory(runsRoot, runId)
|
| 902 |
+
const artifact = runDirectory
|
| 903 |
+
? safeArtifact(runDirectory, relativePath)
|
| 904 |
+
: null
|
| 905 |
+
const requestedSha256 = normalizeSha256(url.searchParams.get('sha256'))
|
| 906 |
+
const artifactExists = Boolean(
|
| 907 |
+
artifact && fs.existsSync(artifact) && fs.statSync(artifact).isFile(),
|
| 908 |
+
)
|
| 909 |
+
const artifactMatches = artifactExists && (
|
| 910 |
+
!requestedSha256 || fileSha256(artifact) === requestedSha256
|
| 911 |
+
)
|
| 912 |
+
if (artifactMatches) {
|
| 913 |
+
sendFile(response, artifact, {
|
| 914 |
+
immutable: Boolean(requestedSha256) || relativePath.toLowerCase().endsWith('.png'),
|
| 915 |
+
})
|
| 916 |
+
return true
|
| 917 |
+
}
|
| 918 |
+
if (relativePath === 'reference.png' && runDirectory) {
|
| 919 |
+
const taskId = readJson(path.join(runDirectory, 'run-state.json'))?.task_id
|
| 920 |
+
|| readJson(path.join(runDirectory, 'result.json'))?.task_id
|
| 921 |
+
const entry = manifest.get(taskId)
|
| 922 |
+
const manifestImage = entry ? path.resolve(BENCHMARK_DATA_ROOT, entry.image_path) : null
|
| 923 |
+
if (
|
| 924 |
+
manifestImage
|
| 925 |
+
&& manifestImage.startsWith(BENCHMARK_DATA_ROOT + path.sep)
|
| 926 |
+
&& fs.existsSync(manifestImage)
|
| 927 |
+
&& fs.statSync(manifestImage).isFile()
|
| 928 |
+
&& (!requestedSha256 || fileSha256(manifestImage) === requestedSha256)
|
| 929 |
+
) {
|
| 930 |
+
sendFile(response, manifestImage, { immutable: true })
|
| 931 |
+
return true
|
| 932 |
+
}
|
| 933 |
+
}
|
| 934 |
+
if (relativePath === 'evaluation/render.png' && runDirectory) {
|
| 935 |
+
const legacyRender = safeArtifact(runDirectory, 'evaluation/candidate-render.png')
|
| 936 |
+
if (legacyRender && fs.existsSync(legacyRender) && fs.statSync(legacyRender).isFile()) {
|
| 937 |
+
sendFile(response, legacyRender, { immutable: true })
|
| 938 |
+
return true
|
| 939 |
+
}
|
| 940 |
+
}
|
| 941 |
+
if (
|
| 942 |
+
artifact
|
| 943 |
+
&& !artifactExists
|
| 944 |
+
&& typeof options.cloudArtifactUrl === 'function'
|
| 945 |
+
) {
|
| 946 |
+
try {
|
| 947 |
+
const location = await options.cloudArtifactUrl({ runId, relativePath })
|
| 948 |
+
if (location) {
|
| 949 |
+
sendPrivateRedirect(response, location)
|
| 950 |
+
return true
|
| 951 |
+
}
|
| 952 |
+
} catch {
|
| 953 |
+
// Fall through to the local caching proxy if signing is unavailable.
|
| 954 |
+
}
|
| 955 |
+
}
|
| 956 |
+
if (
|
| 957 |
+
artifact
|
| 958 |
+
&& !artifactMatches
|
| 959 |
+
&& typeof options.cloudArtifactLoader === 'function'
|
| 960 |
+
) {
|
| 961 |
+
try {
|
| 962 |
+
await options.cloudArtifactLoader({
|
| 963 |
+
runId,
|
| 964 |
+
relativePath,
|
| 965 |
+
artifact,
|
| 966 |
+
expectedSha256: requestedSha256,
|
| 967 |
+
force: artifactExists,
|
| 968 |
+
})
|
| 969 |
+
} catch {
|
| 970 |
+
// The normal 404 below is the stable contract when the cloud mirror
|
| 971 |
+
// also lacks an artifact or AWS is temporarily unavailable.
|
| 972 |
+
}
|
| 973 |
+
}
|
| 974 |
+
const refreshedMatches = Boolean(
|
| 975 |
+
artifact
|
| 976 |
+
&& fs.existsSync(artifact)
|
| 977 |
+
&& fs.statSync(artifact).isFile()
|
| 978 |
+
&& (!requestedSha256 || fileSha256(artifact) === requestedSha256),
|
| 979 |
+
)
|
| 980 |
+
let servedArtifact = refreshedMatches ? artifact : null
|
| 981 |
+
if (!servedArtifact && relativePath === 'evaluation/render.png' && runDirectory) {
|
| 982 |
+
const fallbackRelativePath = 'evaluation/candidate-render.png'
|
| 983 |
+
const fallbackArtifact = safeArtifact(runDirectory, fallbackRelativePath)
|
| 984 |
+
if (
|
| 985 |
+
fallbackArtifact
|
| 986 |
+
&& !fs.existsSync(fallbackArtifact)
|
| 987 |
+
&& typeof options.cloudArtifactLoader === 'function'
|
| 988 |
+
) {
|
| 989 |
+
try {
|
| 990 |
+
await options.cloudArtifactLoader({
|
| 991 |
+
runId,
|
| 992 |
+
relativePath: fallbackRelativePath,
|
| 993 |
+
artifact: fallbackArtifact,
|
| 994 |
+
})
|
| 995 |
+
} catch {
|
| 996 |
+
// Older evaluator runs may not have either render filename.
|
| 997 |
+
}
|
| 998 |
+
}
|
| 999 |
+
if (
|
| 1000 |
+
fallbackArtifact
|
| 1001 |
+
&& fs.existsSync(fallbackArtifact)
|
| 1002 |
+
&& fs.statSync(fallbackArtifact).isFile()
|
| 1003 |
+
) {
|
| 1004 |
+
servedArtifact = fallbackArtifact
|
| 1005 |
+
}
|
| 1006 |
+
}
|
| 1007 |
+
if (!servedArtifact && relativePath === 'reference.png') {
|
| 1008 |
+
const taskId = runDirectory
|
| 1009 |
+
? readJson(path.join(runDirectory, 'run-state.json'))?.task_id
|
| 1010 |
+
|| readJson(path.join(runDirectory, 'result.json'))?.task_id
|
| 1011 |
+
: null
|
| 1012 |
+
const entry = manifest.get(taskId)
|
| 1013 |
+
const manifestImage = entry ? path.resolve(BENCHMARK_DATA_ROOT, entry.image_path) : null
|
| 1014 |
+
if (
|
| 1015 |
+
manifestImage
|
| 1016 |
+
&& manifestImage.startsWith(BENCHMARK_DATA_ROOT + path.sep)
|
| 1017 |
+
&& fs.existsSync(manifestImage)
|
| 1018 |
+
&& fs.statSync(manifestImage).isFile()
|
| 1019 |
+
) {
|
| 1020 |
+
servedArtifact = manifestImage
|
| 1021 |
+
}
|
| 1022 |
+
}
|
| 1023 |
+
sendFile(response, servedArtifact, {
|
| 1024 |
+
// Run-scoped PNGs are append-only/final artifacts. Browser caching
|
| 1025 |
+
// makes reopening a rollout instant and avoids repeated S3 downloads.
|
| 1026 |
+
immutable: Boolean(requestedSha256) || relativePath.toLowerCase().endsWith('.png'),
|
| 1027 |
+
})
|
| 1028 |
+
return true
|
| 1029 |
+
}
|
| 1030 |
+
const runMatch = url.pathname.match(/^\/api\/runs\/([^/]+)$/)
|
| 1031 |
+
if (runMatch) {
|
| 1032 |
+
const runId = decodeURIComponent(runMatch[1])
|
| 1033 |
+
const runDirectory = safeRunDirectory(runsRoot, runId)
|
| 1034 |
+
if (runDirectory) {
|
| 1035 |
+
// Live cloud-controller runs may be present in batch-state before their
|
| 1036 |
+
// rollout directory has ever been mirrored locally. Bootstrap and
|
| 1037 |
+
// refresh the small trace index here; frame PNGs remain lazy-loaded by
|
| 1038 |
+
// the /file route below.
|
| 1039 |
+
await refreshCloudMetadata(options.cloudTraceLoader, {
|
| 1040 |
+
runId,
|
| 1041 |
+
runDirectory,
|
| 1042 |
+
})
|
| 1043 |
+
}
|
| 1044 |
+
const detail = traceDetail(runsRoot, runId)
|
| 1045 |
+
sendJson(response, detail ? 200 : 404, detail || { error: 'run_not_found' })
|
| 1046 |
+
return true
|
| 1047 |
+
}
|
| 1048 |
+
const referenceMatch = url.pathname.match(/^\/api\/reference\/([^/]+)$/)
|
| 1049 |
+
if (referenceMatch) {
|
| 1050 |
+
const taskId = decodeURIComponent(referenceMatch[1])
|
| 1051 |
+
const entry = manifest.get(taskId)
|
| 1052 |
+
const imagePath = entry ? path.resolve(BENCHMARK_DATA_ROOT, entry.image_path) : null
|
| 1053 |
+
const safeImage = imagePath && imagePath.startsWith(BENCHMARK_DATA_ROOT + path.sep) ? imagePath : null
|
| 1054 |
+
sendFile(response, safeImage)
|
| 1055 |
+
return true
|
| 1056 |
+
}
|
| 1057 |
+
sendJson(response, 404, { error: 'not_found' })
|
| 1058 |
+
return true
|
| 1059 |
+
}
|
| 1060 |
+
}
|
| 1061 |
+
|
| 1062 |
+
export const _test = {
|
| 1063 |
+
readJsonl,
|
| 1064 |
+
safeRunDirectory,
|
| 1065 |
+
safeArtifact,
|
| 1066 |
+
runSummary,
|
| 1067 |
+
traceDetail,
|
| 1068 |
+
traceTurns,
|
| 1069 |
+
listRuns,
|
| 1070 |
+
usageSummary,
|
| 1071 |
+
runUsage,
|
| 1072 |
+
compactTurn,
|
| 1073 |
+
normalizeSha256,
|
| 1074 |
+
fileSha256,
|
| 1075 |
+
benchmarkDetail,
|
| 1076 |
+
listBenchmarks,
|
| 1077 |
+
executionState,
|
| 1078 |
+
readRunScores,
|
| 1079 |
+
scoreSummary,
|
| 1080 |
+
}
|
apps/dashboard/tsconfig.app.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ES2022",
|
| 4 |
+
"useDefineForClassFields": true,
|
| 5 |
+
"module": "ESNext",
|
| 6 |
+
"moduleResolution": "Bundler",
|
| 7 |
+
"strict": true,
|
| 8 |
+
"jsx": "preserve",
|
| 9 |
+
"resolveJsonModule": true,
|
| 10 |
+
"isolatedModules": true,
|
| 11 |
+
"esModuleInterop": true,
|
| 12 |
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
| 13 |
+
"types": ["vite/client"],
|
| 14 |
+
"skipLibCheck": true
|
| 15 |
+
},
|
| 16 |
+
"include": ["src/**/*.ts", "src/**/*.vue"]
|
| 17 |
+
}
|
apps/dashboard/tsconfig.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"files": [],
|
| 3 |
+
"references": [
|
| 4 |
+
{ "path": "./tsconfig.app.json" },
|
| 5 |
+
{ "path": "./tsconfig.node.json" }
|
| 6 |
+
]
|
| 7 |
+
}
|
apps/dashboard/tsconfig.node.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"composite": true,
|
| 4 |
+
"target": "ES2022",
|
| 5 |
+
"module": "ESNext",
|
| 6 |
+
"moduleResolution": "Bundler",
|
| 7 |
+
"allowJs": true,
|
| 8 |
+
"checkJs": false,
|
| 9 |
+
"outDir": ".tsbuild/node",
|
| 10 |
+
"rootDir": ".",
|
| 11 |
+
"types": ["node"],
|
| 12 |
+
"skipLibCheck": true
|
| 13 |
+
},
|
| 14 |
+
"include": ["vite.config.ts", "trace-api.mjs", "server.mjs"]
|
| 15 |
+
}
|
apps/dashboard/vite.config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig, type Plugin } from 'vite'
|
| 2 |
+
import vue from '@vitejs/plugin-vue'
|
| 3 |
+
import { createTraceApi } from './trace-api.mjs'
|
| 4 |
+
|
| 5 |
+
function traceApiPlugin(): Plugin {
|
| 6 |
+
const handle = createTraceApi()
|
| 7 |
+
return {
|
| 8 |
+
name: 'autocad-trace-api',
|
| 9 |
+
configureServer(server) {
|
| 10 |
+
server.middlewares.use(async (request, response, next) => {
|
| 11 |
+
if (await handle(request, response)) return
|
| 12 |
+
next()
|
| 13 |
+
})
|
| 14 |
+
},
|
| 15 |
+
}
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
export default defineConfig({
|
| 19 |
+
plugins: [vue(), traceApiPlugin()],
|
| 20 |
+
server: { port: 5173 },
|
| 21 |
+
build: { target: 'es2022' },
|
| 22 |
+
})
|
apps/results-dashboard/.env.example
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
AUTOCAD_RESULTS_BUCKET=your-autocad-results-bucket
|
| 2 |
+
# AWS_PROFILE=my-profile
|
| 3 |
+
AWS_REGION=us-east-1
|
| 4 |
+
HOST=127.0.0.1
|
| 5 |
+
PORT=4273
|
| 6 |
+
AUTOCAD_RESULTS_SIGNED_URL_TTL_SECONDS=3600
|
apps/results-dashboard/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AutoCAD benchmark results dashboard
|
| 2 |
+
|
| 3 |
+
Read-only dashboard for the finalized, model-level aggregates in the AutoCAD benchmark results bucket. It discovers aggregate manifests, compares models and categories, and exposes each canonical rollout's trace, receipts, renders, DWG, and full screenshot sequence.
|
| 4 |
+
|
| 5 |
+
Media is served with short-lived S3 URLs and immutable browser cache headers. The dashboard server only signs requests; image bytes travel directly from S3 to the browser.
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
npm ci
|
| 9 |
+
npm test
|
| 10 |
+
npm run build
|
| 11 |
+
npm run preview
|
| 12 |
+
```
|
| 13 |
+
|
| 14 |
+
The local viewer listens on `http://127.0.0.1:4273`.
|
| 15 |
+
`AUTOCAD_RESULTS_BUCKET` is required and must name a bucket you own.
|
| 16 |
+
`AWS_PROFILE` and `AWS_REGION` are optional; the AWS SDK uses its standard
|
| 17 |
+
credential and region chains when they are omitted. See `.env.example`.
|
apps/results-dashboard/index.html
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
<meta name="color-scheme" content="dark" />
|
| 7 |
+
<meta name="theme-color" content="#0a0d0f" />
|
| 8 |
+
<title>AutoCAD Bench Results</title>
|
| 9 |
+
</head>
|
| 10 |
+
<body>
|
| 11 |
+
<div id="app"></div>
|
| 12 |
+
<script type="module" src="/src/main.ts"></script>
|
| 13 |
+
</body>
|
| 14 |
+
</html>
|
apps/results-dashboard/package-lock.json
ADDED
|
@@ -0,0 +1,1910 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "autocad-bench-results-dashboard",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "autocad-bench-results-dashboard",
|
| 9 |
+
"version": "0.1.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"@aws-sdk/client-s3": "3.1090.0",
|
| 12 |
+
"@aws-sdk/credential-providers": "3.1090.0",
|
| 13 |
+
"@aws-sdk/s3-request-presigner": "3.1090.0",
|
| 14 |
+
"vue": "3.5.40"
|
| 15 |
+
},
|
| 16 |
+
"devDependencies": {
|
| 17 |
+
"@types/node": "20.19.43",
|
| 18 |
+
"@vitejs/plugin-vue": "5.2.4",
|
| 19 |
+
"typescript": "5.9.3",
|
| 20 |
+
"vite": "6.4.3",
|
| 21 |
+
"vue-tsc": "3.3.7"
|
| 22 |
+
}
|
| 23 |
+
},
|
| 24 |
+
"node_modules/@aws-sdk/checksums": {
|
| 25 |
+
"version": "3.1000.19",
|
| 26 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.19.tgz",
|
| 27 |
+
"integrity": "sha512-Hc4N100RdkuWshKBnhPzmpdftfi9mCLz+OHFELHM1QIgMH4QRUUWyWgfiebta/YX2Bd62wTcm3EqAP8TeXv0gA==",
|
| 28 |
+
"license": "Apache-2.0",
|
| 29 |
+
"dependencies": {
|
| 30 |
+
"@aws-sdk/core": "^3.976.0",
|
| 31 |
+
"@aws-sdk/types": "^3.974.2",
|
| 32 |
+
"@smithy/core": "^3.29.4",
|
| 33 |
+
"@smithy/types": "^4.16.1",
|
| 34 |
+
"tslib": "^2.6.2"
|
| 35 |
+
},
|
| 36 |
+
"engines": {
|
| 37 |
+
"node": ">=20.0.0"
|
| 38 |
+
}
|
| 39 |
+
},
|
| 40 |
+
"node_modules/@aws-sdk/client-s3": {
|
| 41 |
+
"version": "3.1090.0",
|
| 42 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1090.0.tgz",
|
| 43 |
+
"integrity": "sha512-R6GX9cd1jljwzZ8xFmgAI/hHCuX1MobIKBdsymv7WL9SENvO9Vgz9KOR6avTnu0Ao+w1LmxnTe+jqmZXEn7Q/Q==",
|
| 44 |
+
"license": "Apache-2.0",
|
| 45 |
+
"dependencies": {
|
| 46 |
+
"@aws-sdk/checksums": "^3.1000.18",
|
| 47 |
+
"@aws-sdk/core": "^3.975.3",
|
| 48 |
+
"@aws-sdk/credential-provider-node": "^3.972.70",
|
| 49 |
+
"@aws-sdk/middleware-sdk-s3": "^3.972.64",
|
| 50 |
+
"@aws-sdk/signature-v4-multi-region": "^3.996.41",
|
| 51 |
+
"@aws-sdk/types": "^3.974.2",
|
| 52 |
+
"@smithy/core": "^3.29.4",
|
| 53 |
+
"@smithy/fetch-http-handler": "^5.6.6",
|
| 54 |
+
"@smithy/node-http-handler": "^4.9.6",
|
| 55 |
+
"@smithy/types": "^4.16.1",
|
| 56 |
+
"tslib": "^2.6.2"
|
| 57 |
+
},
|
| 58 |
+
"engines": {
|
| 59 |
+
"node": ">=20.0.0"
|
| 60 |
+
}
|
| 61 |
+
},
|
| 62 |
+
"node_modules/@aws-sdk/core": {
|
| 63 |
+
"version": "3.976.0",
|
| 64 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.976.0.tgz",
|
| 65 |
+
"integrity": "sha512-0cjRaEdlVoOrsNb9pP5q1Syyc8pXw5xSj2Np2ryReRTr9FppIIRVSdZK4lbnfmc2Hvgux/xBOUU6baB7z8//uA==",
|
| 66 |
+
"license": "Apache-2.0",
|
| 67 |
+
"dependencies": {
|
| 68 |
+
"@aws-sdk/types": "^3.974.2",
|
| 69 |
+
"@aws-sdk/xml-builder": "^3.972.36",
|
| 70 |
+
"@aws/lambda-invoke-store": "^0.3.0",
|
| 71 |
+
"@smithy/core": "^3.29.4",
|
| 72 |
+
"@smithy/signature-v4": "^5.6.5",
|
| 73 |
+
"@smithy/types": "^4.16.1",
|
| 74 |
+
"bowser": "^2.11.0",
|
| 75 |
+
"tslib": "^2.6.2"
|
| 76 |
+
},
|
| 77 |
+
"engines": {
|
| 78 |
+
"node": ">=20.0.0"
|
| 79 |
+
}
|
| 80 |
+
},
|
| 81 |
+
"node_modules/@aws-sdk/credential-provider-cognito-identity": {
|
| 82 |
+
"version": "3.972.59",
|
| 83 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.59.tgz",
|
| 84 |
+
"integrity": "sha512-iWPfye2ZOCmAHKmN1EwAyeHZdZxZymctAnEOD+7jzwqc5gZlK1lwG1lzGVtpH0+d/NnyrK670ycqBNKD4zUGZA==",
|
| 85 |
+
"license": "Apache-2.0",
|
| 86 |
+
"dependencies": {
|
| 87 |
+
"@aws-sdk/nested-clients": "^3.997.34",
|
| 88 |
+
"@aws-sdk/types": "^3.974.2",
|
| 89 |
+
"@smithy/core": "^3.29.4",
|
| 90 |
+
"@smithy/types": "^4.16.1",
|
| 91 |
+
"tslib": "^2.6.2"
|
| 92 |
+
},
|
| 93 |
+
"engines": {
|
| 94 |
+
"node": ">=20.0.0"
|
| 95 |
+
}
|
| 96 |
+
},
|
| 97 |
+
"node_modules/@aws-sdk/credential-provider-env": {
|
| 98 |
+
"version": "3.972.60",
|
| 99 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.60.tgz",
|
| 100 |
+
"integrity": "sha512-BAkxdoe7tpDDqCghGpuOeHQRbm/2znVvOQm0AvpQbA2tbfMN46doN4zx65fv85ImP3KADwc2zQPmbrlI9MPfMg==",
|
| 101 |
+
"license": "Apache-2.0",
|
| 102 |
+
"dependencies": {
|
| 103 |
+
"@aws-sdk/core": "^3.976.0",
|
| 104 |
+
"@aws-sdk/types": "^3.974.2",
|
| 105 |
+
"@smithy/core": "^3.29.4",
|
| 106 |
+
"@smithy/types": "^4.16.1",
|
| 107 |
+
"tslib": "^2.6.2"
|
| 108 |
+
},
|
| 109 |
+
"engines": {
|
| 110 |
+
"node": ">=20.0.0"
|
| 111 |
+
}
|
| 112 |
+
},
|
| 113 |
+
"node_modules/@aws-sdk/credential-provider-http": {
|
| 114 |
+
"version": "3.972.62",
|
| 115 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.62.tgz",
|
| 116 |
+
"integrity": "sha512-g/0fGqKTb9xpKdd9AtpmV5Eo3DFKbnkpA2+w0peISSlu7NfAoWOuYBFxsu+yWBtxU89ka55ezoZBCbFaS8pjYQ==",
|
| 117 |
+
"license": "Apache-2.0",
|
| 118 |
+
"dependencies": {
|
| 119 |
+
"@aws-sdk/core": "^3.976.0",
|
| 120 |
+
"@aws-sdk/types": "^3.974.2",
|
| 121 |
+
"@smithy/core": "^3.29.4",
|
| 122 |
+
"@smithy/fetch-http-handler": "^5.6.6",
|
| 123 |
+
"@smithy/node-http-handler": "^4.9.6",
|
| 124 |
+
"@smithy/types": "^4.16.1",
|
| 125 |
+
"tslib": "^2.6.2"
|
| 126 |
+
},
|
| 127 |
+
"engines": {
|
| 128 |
+
"node": ">=20.0.0"
|
| 129 |
+
}
|
| 130 |
+
},
|
| 131 |
+
"node_modules/@aws-sdk/credential-provider-ini": {
|
| 132 |
+
"version": "3.973.5",
|
| 133 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.5.tgz",
|
| 134 |
+
"integrity": "sha512-ylubazcRfq2TVus/qXucSXeC42Qdjp5HQxTu68K/BsdMiZlcSLD1zkpoCgApXZX1Y6YJhtGGs7ZHhO/GuIgBlw==",
|
| 135 |
+
"license": "Apache-2.0",
|
| 136 |
+
"dependencies": {
|
| 137 |
+
"@aws-sdk/core": "^3.976.0",
|
| 138 |
+
"@aws-sdk/credential-provider-env": "^3.972.60",
|
| 139 |
+
"@aws-sdk/credential-provider-http": "^3.972.62",
|
| 140 |
+
"@aws-sdk/credential-provider-login": "^3.972.67",
|
| 141 |
+
"@aws-sdk/credential-provider-process": "^3.972.60",
|
| 142 |
+
"@aws-sdk/credential-provider-sso": "^3.973.4",
|
| 143 |
+
"@aws-sdk/credential-provider-web-identity": "^3.972.66",
|
| 144 |
+
"@aws-sdk/nested-clients": "^3.997.34",
|
| 145 |
+
"@aws-sdk/types": "^3.974.2",
|
| 146 |
+
"@smithy/core": "^3.29.4",
|
| 147 |
+
"@smithy/credential-provider-imds": "^4.4.9",
|
| 148 |
+
"@smithy/types": "^4.16.1",
|
| 149 |
+
"tslib": "^2.6.2"
|
| 150 |
+
},
|
| 151 |
+
"engines": {
|
| 152 |
+
"node": ">=20.0.0"
|
| 153 |
+
}
|
| 154 |
+
},
|
| 155 |
+
"node_modules/@aws-sdk/credential-provider-login": {
|
| 156 |
+
"version": "3.972.67",
|
| 157 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.67.tgz",
|
| 158 |
+
"integrity": "sha512-CCygIKJ9YbI3n84OClSaSppkgKKHVj2TGT33c6FRORZrYNZQ1POmD+ip0FLYokiJAK7sSdc3YVkOsBm90oxWMQ==",
|
| 159 |
+
"license": "Apache-2.0",
|
| 160 |
+
"dependencies": {
|
| 161 |
+
"@aws-sdk/core": "^3.976.0",
|
| 162 |
+
"@aws-sdk/nested-clients": "^3.997.34",
|
| 163 |
+
"@aws-sdk/types": "^3.974.2",
|
| 164 |
+
"@smithy/core": "^3.29.4",
|
| 165 |
+
"@smithy/types": "^4.16.1",
|
| 166 |
+
"tslib": "^2.6.2"
|
| 167 |
+
},
|
| 168 |
+
"engines": {
|
| 169 |
+
"node": ">=20.0.0"
|
| 170 |
+
}
|
| 171 |
+
},
|
| 172 |
+
"node_modules/@aws-sdk/credential-provider-node": {
|
| 173 |
+
"version": "3.972.71",
|
| 174 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.71.tgz",
|
| 175 |
+
"integrity": "sha512-HIg7Q2osBzajQwL+1Vkyh2E7Gim3eTNb9RHIsOxDGjW0eZg4oEKtRs5sioCnc73ilhaOm4gX2lHVF8J7+nt2rg==",
|
| 176 |
+
"license": "Apache-2.0",
|
| 177 |
+
"dependencies": {
|
| 178 |
+
"@aws-sdk/credential-provider-env": "^3.972.60",
|
| 179 |
+
"@aws-sdk/credential-provider-http": "^3.972.62",
|
| 180 |
+
"@aws-sdk/credential-provider-ini": "^3.973.5",
|
| 181 |
+
"@aws-sdk/credential-provider-process": "^3.972.60",
|
| 182 |
+
"@aws-sdk/credential-provider-sso": "^3.973.4",
|
| 183 |
+
"@aws-sdk/credential-provider-web-identity": "^3.972.66",
|
| 184 |
+
"@aws-sdk/types": "^3.974.2",
|
| 185 |
+
"@smithy/core": "^3.29.4",
|
| 186 |
+
"@smithy/credential-provider-imds": "^4.4.9",
|
| 187 |
+
"@smithy/types": "^4.16.1",
|
| 188 |
+
"tslib": "^2.6.2"
|
| 189 |
+
},
|
| 190 |
+
"engines": {
|
| 191 |
+
"node": ">=20.0.0"
|
| 192 |
+
}
|
| 193 |
+
},
|
| 194 |
+
"node_modules/@aws-sdk/credential-provider-process": {
|
| 195 |
+
"version": "3.972.60",
|
| 196 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.60.tgz",
|
| 197 |
+
"integrity": "sha512-YIo3f99hM43QdYG8hDzwGemnR/pU95b0kramqSJUTleCqaB7+HwKf7YZFHqvOgTqZTPx/mRmNIqoDRr3U0Z3Tw==",
|
| 198 |
+
"license": "Apache-2.0",
|
| 199 |
+
"dependencies": {
|
| 200 |
+
"@aws-sdk/core": "^3.976.0",
|
| 201 |
+
"@aws-sdk/types": "^3.974.2",
|
| 202 |
+
"@smithy/core": "^3.29.4",
|
| 203 |
+
"@smithy/types": "^4.16.1",
|
| 204 |
+
"tslib": "^2.6.2"
|
| 205 |
+
},
|
| 206 |
+
"engines": {
|
| 207 |
+
"node": ">=20.0.0"
|
| 208 |
+
}
|
| 209 |
+
},
|
| 210 |
+
"node_modules/@aws-sdk/credential-provider-sso": {
|
| 211 |
+
"version": "3.973.4",
|
| 212 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.4.tgz",
|
| 213 |
+
"integrity": "sha512-BPdmL8sSBOCv4ngZ+3LHxyc3CNqDCEK37CHioCk7zGrTMY5sUtkH8q+o6qA80nn6w3/fyBPGNE7OIRlmoOxRQA==",
|
| 214 |
+
"license": "Apache-2.0",
|
| 215 |
+
"dependencies": {
|
| 216 |
+
"@aws-sdk/core": "^3.976.0",
|
| 217 |
+
"@aws-sdk/nested-clients": "^3.997.34",
|
| 218 |
+
"@aws-sdk/token-providers": "3.1092.0",
|
| 219 |
+
"@aws-sdk/types": "^3.974.2",
|
| 220 |
+
"@smithy/core": "^3.29.4",
|
| 221 |
+
"@smithy/types": "^4.16.1",
|
| 222 |
+
"tslib": "^2.6.2"
|
| 223 |
+
},
|
| 224 |
+
"engines": {
|
| 225 |
+
"node": ">=20.0.0"
|
| 226 |
+
}
|
| 227 |
+
},
|
| 228 |
+
"node_modules/@aws-sdk/credential-provider-web-identity": {
|
| 229 |
+
"version": "3.972.66",
|
| 230 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.66.tgz",
|
| 231 |
+
"integrity": "sha512-kSAziJboOmZmsR9/MTbiNjowl2BPes1bQuJpne4qAZ62ubi8fjfr/aupJSQje6udBoYxXTQbsL0e0kby2la3ng==",
|
| 232 |
+
"license": "Apache-2.0",
|
| 233 |
+
"dependencies": {
|
| 234 |
+
"@aws-sdk/core": "^3.976.0",
|
| 235 |
+
"@aws-sdk/nested-clients": "^3.997.34",
|
| 236 |
+
"@aws-sdk/types": "^3.974.2",
|
| 237 |
+
"@smithy/core": "^3.29.4",
|
| 238 |
+
"@smithy/types": "^4.16.1",
|
| 239 |
+
"tslib": "^2.6.2"
|
| 240 |
+
},
|
| 241 |
+
"engines": {
|
| 242 |
+
"node": ">=20.0.0"
|
| 243 |
+
}
|
| 244 |
+
},
|
| 245 |
+
"node_modules/@aws-sdk/credential-providers": {
|
| 246 |
+
"version": "3.1090.0",
|
| 247 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.1090.0.tgz",
|
| 248 |
+
"integrity": "sha512-HEfLv6RdiFu8+HO5CUQjjQ/KCWWAHcQCtSPenaF26lYQSjjzKjgvjwAMGBV9/VMrO8V0faIRyoFWhF548UjTOg==",
|
| 249 |
+
"license": "Apache-2.0",
|
| 250 |
+
"dependencies": {
|
| 251 |
+
"@aws-sdk/core": "^3.975.3",
|
| 252 |
+
"@aws-sdk/credential-provider-cognito-identity": "^3.972.58",
|
| 253 |
+
"@aws-sdk/credential-provider-env": "^3.972.59",
|
| 254 |
+
"@aws-sdk/credential-provider-http": "^3.972.61",
|
| 255 |
+
"@aws-sdk/credential-provider-ini": "^3.973.4",
|
| 256 |
+
"@aws-sdk/credential-provider-login": "^3.972.66",
|
| 257 |
+
"@aws-sdk/credential-provider-node": "^3.972.70",
|
| 258 |
+
"@aws-sdk/credential-provider-process": "^3.972.59",
|
| 259 |
+
"@aws-sdk/credential-provider-sso": "^3.973.3",
|
| 260 |
+
"@aws-sdk/credential-provider-web-identity": "^3.972.65",
|
| 261 |
+
"@aws-sdk/nested-clients": "^3.997.33",
|
| 262 |
+
"@aws-sdk/types": "^3.974.2",
|
| 263 |
+
"@smithy/core": "^3.29.4",
|
| 264 |
+
"@smithy/credential-provider-imds": "^4.4.9",
|
| 265 |
+
"@smithy/types": "^4.16.1",
|
| 266 |
+
"tslib": "^2.6.2"
|
| 267 |
+
},
|
| 268 |
+
"engines": {
|
| 269 |
+
"node": ">=20.0.0"
|
| 270 |
+
}
|
| 271 |
+
},
|
| 272 |
+
"node_modules/@aws-sdk/middleware-sdk-s3": {
|
| 273 |
+
"version": "3.972.65",
|
| 274 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.65.tgz",
|
| 275 |
+
"integrity": "sha512-udwNhRfDTfCB98mAHjjgsnKQlxygB4e0X+Obne/XjJpvVsF0YCQC8ZErd/8Z6IPoLQjtiKHzwqEDbZiLrJEnOg==",
|
| 276 |
+
"license": "Apache-2.0",
|
| 277 |
+
"dependencies": {
|
| 278 |
+
"@aws-sdk/core": "^3.976.0",
|
| 279 |
+
"@aws-sdk/signature-v4-multi-region": "^3.996.41",
|
| 280 |
+
"@aws-sdk/types": "^3.974.2",
|
| 281 |
+
"@smithy/core": "^3.29.4",
|
| 282 |
+
"@smithy/types": "^4.16.1",
|
| 283 |
+
"tslib": "^2.6.2"
|
| 284 |
+
},
|
| 285 |
+
"engines": {
|
| 286 |
+
"node": ">=20.0.0"
|
| 287 |
+
}
|
| 288 |
+
},
|
| 289 |
+
"node_modules/@aws-sdk/nested-clients": {
|
| 290 |
+
"version": "3.997.34",
|
| 291 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.34.tgz",
|
| 292 |
+
"integrity": "sha512-Y9REVrSwmLM+Qy6sZJ7ofMC2S3Hr3tPP/4CzL5U1olPP7OGoF+6+Px0E49cVQBtSxJtyeLJMf0UaBErfeSahAA==",
|
| 293 |
+
"license": "Apache-2.0",
|
| 294 |
+
"dependencies": {
|
| 295 |
+
"@aws-sdk/core": "^3.976.0",
|
| 296 |
+
"@aws-sdk/signature-v4-multi-region": "^3.996.41",
|
| 297 |
+
"@aws-sdk/types": "^3.974.2",
|
| 298 |
+
"@smithy/core": "^3.29.4",
|
| 299 |
+
"@smithy/fetch-http-handler": "^5.6.6",
|
| 300 |
+
"@smithy/node-http-handler": "^4.9.6",
|
| 301 |
+
"@smithy/types": "^4.16.1",
|
| 302 |
+
"tslib": "^2.6.2"
|
| 303 |
+
},
|
| 304 |
+
"engines": {
|
| 305 |
+
"node": ">=20.0.0"
|
| 306 |
+
}
|
| 307 |
+
},
|
| 308 |
+
"node_modules/@aws-sdk/s3-request-presigner": {
|
| 309 |
+
"version": "3.1090.0",
|
| 310 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.1090.0.tgz",
|
| 311 |
+
"integrity": "sha512-DaYXp8GMptWJUDbvpoB51xZztOw6tcbpXjYAZMXTu17E0aR+5g8FWBU3t3t0KEhhrUiKyfLpVi6uNQxNSXfbhg==",
|
| 312 |
+
"license": "Apache-2.0",
|
| 313 |
+
"dependencies": {
|
| 314 |
+
"@aws-sdk/core": "^3.975.3",
|
| 315 |
+
"@aws-sdk/signature-v4-multi-region": "^3.996.41",
|
| 316 |
+
"@aws-sdk/types": "^3.974.2",
|
| 317 |
+
"@smithy/core": "^3.29.4",
|
| 318 |
+
"@smithy/types": "^4.16.1",
|
| 319 |
+
"tslib": "^2.6.2"
|
| 320 |
+
},
|
| 321 |
+
"engines": {
|
| 322 |
+
"node": ">=20.0.0"
|
| 323 |
+
}
|
| 324 |
+
},
|
| 325 |
+
"node_modules/@aws-sdk/signature-v4-multi-region": {
|
| 326 |
+
"version": "3.996.41",
|
| 327 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.41.tgz",
|
| 328 |
+
"integrity": "sha512-QMUytg+FQMGouc8gHS00KoYih3+N6cqmVI/pQGOIo7Nr7OpQaiXjSYOuL+vsPZ1tymY4LAQ8MYcHJmws5LRxng==",
|
| 329 |
+
"license": "Apache-2.0",
|
| 330 |
+
"dependencies": {
|
| 331 |
+
"@aws-sdk/types": "^3.974.2",
|
| 332 |
+
"@smithy/signature-v4": "^5.6.5",
|
| 333 |
+
"@smithy/types": "^4.16.1",
|
| 334 |
+
"tslib": "^2.6.2"
|
| 335 |
+
},
|
| 336 |
+
"engines": {
|
| 337 |
+
"node": ">=20.0.0"
|
| 338 |
+
}
|
| 339 |
+
},
|
| 340 |
+
"node_modules/@aws-sdk/token-providers": {
|
| 341 |
+
"version": "3.1092.0",
|
| 342 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1092.0.tgz",
|
| 343 |
+
"integrity": "sha512-hBYUAr6iBLNFcsiWTgtBb0stdSw39VOUq4Sp4A5caCNf66BAZplWN4FleKrVpJx5li2YgdnK2DqoFSMWC642FQ==",
|
| 344 |
+
"license": "Apache-2.0",
|
| 345 |
+
"dependencies": {
|
| 346 |
+
"@aws-sdk/core": "^3.976.0",
|
| 347 |
+
"@aws-sdk/nested-clients": "^3.997.34",
|
| 348 |
+
"@aws-sdk/types": "^3.974.2",
|
| 349 |
+
"@smithy/core": "^3.29.4",
|
| 350 |
+
"@smithy/types": "^4.16.1",
|
| 351 |
+
"tslib": "^2.6.2"
|
| 352 |
+
},
|
| 353 |
+
"engines": {
|
| 354 |
+
"node": ">=20.0.0"
|
| 355 |
+
}
|
| 356 |
+
},
|
| 357 |
+
"node_modules/@aws-sdk/types": {
|
| 358 |
+
"version": "3.974.2",
|
| 359 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.2.tgz",
|
| 360 |
+
"integrity": "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==",
|
| 361 |
+
"license": "Apache-2.0",
|
| 362 |
+
"dependencies": {
|
| 363 |
+
"@smithy/types": "^4.16.1",
|
| 364 |
+
"tslib": "^2.6.2"
|
| 365 |
+
},
|
| 366 |
+
"engines": {
|
| 367 |
+
"node": ">=20.0.0"
|
| 368 |
+
}
|
| 369 |
+
},
|
| 370 |
+
"node_modules/@aws-sdk/xml-builder": {
|
| 371 |
+
"version": "3.972.36",
|
| 372 |
+
"resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.36.tgz",
|
| 373 |
+
"integrity": "sha512-RdGmS1GLrtaTOLE1ElSluMldNrpk9Emq6uYs8SS8iHlu5xTAmM9rRkM91o48+rIRryBtyO9t+uLYCoMG6jVMVA==",
|
| 374 |
+
"license": "Apache-2.0",
|
| 375 |
+
"dependencies": {
|
| 376 |
+
"@smithy/types": "^4.16.1",
|
| 377 |
+
"tslib": "^2.6.2"
|
| 378 |
+
},
|
| 379 |
+
"engines": {
|
| 380 |
+
"node": ">=20.0.0"
|
| 381 |
+
}
|
| 382 |
+
},
|
| 383 |
+
"node_modules/@aws/lambda-invoke-store": {
|
| 384 |
+
"version": "0.3.0",
|
| 385 |
+
"resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz",
|
| 386 |
+
"integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==",
|
| 387 |
+
"license": "Apache-2.0",
|
| 388 |
+
"engines": {
|
| 389 |
+
"node": ">=18.0.0"
|
| 390 |
+
}
|
| 391 |
+
},
|
| 392 |
+
"node_modules/@babel/helper-string-parser": {
|
| 393 |
+
"version": "7.29.7",
|
| 394 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
| 395 |
+
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
|
| 396 |
+
"license": "MIT",
|
| 397 |
+
"engines": {
|
| 398 |
+
"node": ">=6.9.0"
|
| 399 |
+
}
|
| 400 |
+
},
|
| 401 |
+
"node_modules/@babel/helper-validator-identifier": {
|
| 402 |
+
"version": "7.29.7",
|
| 403 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
| 404 |
+
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
| 405 |
+
"license": "MIT",
|
| 406 |
+
"engines": {
|
| 407 |
+
"node": ">=6.9.0"
|
| 408 |
+
}
|
| 409 |
+
},
|
| 410 |
+
"node_modules/@babel/parser": {
|
| 411 |
+
"version": "7.29.7",
|
| 412 |
+
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
|
| 413 |
+
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
|
| 414 |
+
"license": "MIT",
|
| 415 |
+
"dependencies": {
|
| 416 |
+
"@babel/types": "^7.29.7"
|
| 417 |
+
},
|
| 418 |
+
"bin": {
|
| 419 |
+
"parser": "bin/babel-parser.js"
|
| 420 |
+
},
|
| 421 |
+
"engines": {
|
| 422 |
+
"node": ">=6.0.0"
|
| 423 |
+
}
|
| 424 |
+
},
|
| 425 |
+
"node_modules/@babel/types": {
|
| 426 |
+
"version": "7.29.7",
|
| 427 |
+
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
|
| 428 |
+
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
|
| 429 |
+
"license": "MIT",
|
| 430 |
+
"dependencies": {
|
| 431 |
+
"@babel/helper-string-parser": "^7.29.7",
|
| 432 |
+
"@babel/helper-validator-identifier": "^7.29.7"
|
| 433 |
+
},
|
| 434 |
+
"engines": {
|
| 435 |
+
"node": ">=6.9.0"
|
| 436 |
+
}
|
| 437 |
+
},
|
| 438 |
+
"node_modules/@esbuild/aix-ppc64": {
|
| 439 |
+
"version": "0.25.12",
|
| 440 |
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
| 441 |
+
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
|
| 442 |
+
"cpu": [
|
| 443 |
+
"ppc64"
|
| 444 |
+
],
|
| 445 |
+
"dev": true,
|
| 446 |
+
"license": "MIT",
|
| 447 |
+
"optional": true,
|
| 448 |
+
"os": [
|
| 449 |
+
"aix"
|
| 450 |
+
],
|
| 451 |
+
"engines": {
|
| 452 |
+
"node": ">=18"
|
| 453 |
+
}
|
| 454 |
+
},
|
| 455 |
+
"node_modules/@esbuild/android-arm": {
|
| 456 |
+
"version": "0.25.12",
|
| 457 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
|
| 458 |
+
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
|
| 459 |
+
"cpu": [
|
| 460 |
+
"arm"
|
| 461 |
+
],
|
| 462 |
+
"dev": true,
|
| 463 |
+
"license": "MIT",
|
| 464 |
+
"optional": true,
|
| 465 |
+
"os": [
|
| 466 |
+
"android"
|
| 467 |
+
],
|
| 468 |
+
"engines": {
|
| 469 |
+
"node": ">=18"
|
| 470 |
+
}
|
| 471 |
+
},
|
| 472 |
+
"node_modules/@esbuild/android-arm64": {
|
| 473 |
+
"version": "0.25.12",
|
| 474 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
|
| 475 |
+
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
|
| 476 |
+
"cpu": [
|
| 477 |
+
"arm64"
|
| 478 |
+
],
|
| 479 |
+
"dev": true,
|
| 480 |
+
"license": "MIT",
|
| 481 |
+
"optional": true,
|
| 482 |
+
"os": [
|
| 483 |
+
"android"
|
| 484 |
+
],
|
| 485 |
+
"engines": {
|
| 486 |
+
"node": ">=18"
|
| 487 |
+
}
|
| 488 |
+
},
|
| 489 |
+
"node_modules/@esbuild/android-x64": {
|
| 490 |
+
"version": "0.25.12",
|
| 491 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
|
| 492 |
+
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
|
| 493 |
+
"cpu": [
|
| 494 |
+
"x64"
|
| 495 |
+
],
|
| 496 |
+
"dev": true,
|
| 497 |
+
"license": "MIT",
|
| 498 |
+
"optional": true,
|
| 499 |
+
"os": [
|
| 500 |
+
"android"
|
| 501 |
+
],
|
| 502 |
+
"engines": {
|
| 503 |
+
"node": ">=18"
|
| 504 |
+
}
|
| 505 |
+
},
|
| 506 |
+
"node_modules/@esbuild/darwin-arm64": {
|
| 507 |
+
"version": "0.25.12",
|
| 508 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
|
| 509 |
+
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
|
| 510 |
+
"cpu": [
|
| 511 |
+
"arm64"
|
| 512 |
+
],
|
| 513 |
+
"dev": true,
|
| 514 |
+
"license": "MIT",
|
| 515 |
+
"optional": true,
|
| 516 |
+
"os": [
|
| 517 |
+
"darwin"
|
| 518 |
+
],
|
| 519 |
+
"engines": {
|
| 520 |
+
"node": ">=18"
|
| 521 |
+
}
|
| 522 |
+
},
|
| 523 |
+
"node_modules/@esbuild/darwin-x64": {
|
| 524 |
+
"version": "0.25.12",
|
| 525 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
|
| 526 |
+
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
|
| 527 |
+
"cpu": [
|
| 528 |
+
"x64"
|
| 529 |
+
],
|
| 530 |
+
"dev": true,
|
| 531 |
+
"license": "MIT",
|
| 532 |
+
"optional": true,
|
| 533 |
+
"os": [
|
| 534 |
+
"darwin"
|
| 535 |
+
],
|
| 536 |
+
"engines": {
|
| 537 |
+
"node": ">=18"
|
| 538 |
+
}
|
| 539 |
+
},
|
| 540 |
+
"node_modules/@esbuild/freebsd-arm64": {
|
| 541 |
+
"version": "0.25.12",
|
| 542 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
|
| 543 |
+
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
|
| 544 |
+
"cpu": [
|
| 545 |
+
"arm64"
|
| 546 |
+
],
|
| 547 |
+
"dev": true,
|
| 548 |
+
"license": "MIT",
|
| 549 |
+
"optional": true,
|
| 550 |
+
"os": [
|
| 551 |
+
"freebsd"
|
| 552 |
+
],
|
| 553 |
+
"engines": {
|
| 554 |
+
"node": ">=18"
|
| 555 |
+
}
|
| 556 |
+
},
|
| 557 |
+
"node_modules/@esbuild/freebsd-x64": {
|
| 558 |
+
"version": "0.25.12",
|
| 559 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
|
| 560 |
+
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
|
| 561 |
+
"cpu": [
|
| 562 |
+
"x64"
|
| 563 |
+
],
|
| 564 |
+
"dev": true,
|
| 565 |
+
"license": "MIT",
|
| 566 |
+
"optional": true,
|
| 567 |
+
"os": [
|
| 568 |
+
"freebsd"
|
| 569 |
+
],
|
| 570 |
+
"engines": {
|
| 571 |
+
"node": ">=18"
|
| 572 |
+
}
|
| 573 |
+
},
|
| 574 |
+
"node_modules/@esbuild/linux-arm": {
|
| 575 |
+
"version": "0.25.12",
|
| 576 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
|
| 577 |
+
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
|
| 578 |
+
"cpu": [
|
| 579 |
+
"arm"
|
| 580 |
+
],
|
| 581 |
+
"dev": true,
|
| 582 |
+
"license": "MIT",
|
| 583 |
+
"optional": true,
|
| 584 |
+
"os": [
|
| 585 |
+
"linux"
|
| 586 |
+
],
|
| 587 |
+
"engines": {
|
| 588 |
+
"node": ">=18"
|
| 589 |
+
}
|
| 590 |
+
},
|
| 591 |
+
"node_modules/@esbuild/linux-arm64": {
|
| 592 |
+
"version": "0.25.12",
|
| 593 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
|
| 594 |
+
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
|
| 595 |
+
"cpu": [
|
| 596 |
+
"arm64"
|
| 597 |
+
],
|
| 598 |
+
"dev": true,
|
| 599 |
+
"license": "MIT",
|
| 600 |
+
"optional": true,
|
| 601 |
+
"os": [
|
| 602 |
+
"linux"
|
| 603 |
+
],
|
| 604 |
+
"engines": {
|
| 605 |
+
"node": ">=18"
|
| 606 |
+
}
|
| 607 |
+
},
|
| 608 |
+
"node_modules/@esbuild/linux-ia32": {
|
| 609 |
+
"version": "0.25.12",
|
| 610 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
|
| 611 |
+
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
|
| 612 |
+
"cpu": [
|
| 613 |
+
"ia32"
|
| 614 |
+
],
|
| 615 |
+
"dev": true,
|
| 616 |
+
"license": "MIT",
|
| 617 |
+
"optional": true,
|
| 618 |
+
"os": [
|
| 619 |
+
"linux"
|
| 620 |
+
],
|
| 621 |
+
"engines": {
|
| 622 |
+
"node": ">=18"
|
| 623 |
+
}
|
| 624 |
+
},
|
| 625 |
+
"node_modules/@esbuild/linux-loong64": {
|
| 626 |
+
"version": "0.25.12",
|
| 627 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
|
| 628 |
+
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
|
| 629 |
+
"cpu": [
|
| 630 |
+
"loong64"
|
| 631 |
+
],
|
| 632 |
+
"dev": true,
|
| 633 |
+
"license": "MIT",
|
| 634 |
+
"optional": true,
|
| 635 |
+
"os": [
|
| 636 |
+
"linux"
|
| 637 |
+
],
|
| 638 |
+
"engines": {
|
| 639 |
+
"node": ">=18"
|
| 640 |
+
}
|
| 641 |
+
},
|
| 642 |
+
"node_modules/@esbuild/linux-mips64el": {
|
| 643 |
+
"version": "0.25.12",
|
| 644 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
|
| 645 |
+
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
|
| 646 |
+
"cpu": [
|
| 647 |
+
"mips64el"
|
| 648 |
+
],
|
| 649 |
+
"dev": true,
|
| 650 |
+
"license": "MIT",
|
| 651 |
+
"optional": true,
|
| 652 |
+
"os": [
|
| 653 |
+
"linux"
|
| 654 |
+
],
|
| 655 |
+
"engines": {
|
| 656 |
+
"node": ">=18"
|
| 657 |
+
}
|
| 658 |
+
},
|
| 659 |
+
"node_modules/@esbuild/linux-ppc64": {
|
| 660 |
+
"version": "0.25.12",
|
| 661 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
|
| 662 |
+
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
|
| 663 |
+
"cpu": [
|
| 664 |
+
"ppc64"
|
| 665 |
+
],
|
| 666 |
+
"dev": true,
|
| 667 |
+
"license": "MIT",
|
| 668 |
+
"optional": true,
|
| 669 |
+
"os": [
|
| 670 |
+
"linux"
|
| 671 |
+
],
|
| 672 |
+
"engines": {
|
| 673 |
+
"node": ">=18"
|
| 674 |
+
}
|
| 675 |
+
},
|
| 676 |
+
"node_modules/@esbuild/linux-riscv64": {
|
| 677 |
+
"version": "0.25.12",
|
| 678 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
|
| 679 |
+
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
|
| 680 |
+
"cpu": [
|
| 681 |
+
"riscv64"
|
| 682 |
+
],
|
| 683 |
+
"dev": true,
|
| 684 |
+
"license": "MIT",
|
| 685 |
+
"optional": true,
|
| 686 |
+
"os": [
|
| 687 |
+
"linux"
|
| 688 |
+
],
|
| 689 |
+
"engines": {
|
| 690 |
+
"node": ">=18"
|
| 691 |
+
}
|
| 692 |
+
},
|
| 693 |
+
"node_modules/@esbuild/linux-s390x": {
|
| 694 |
+
"version": "0.25.12",
|
| 695 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
|
| 696 |
+
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
|
| 697 |
+
"cpu": [
|
| 698 |
+
"s390x"
|
| 699 |
+
],
|
| 700 |
+
"dev": true,
|
| 701 |
+
"license": "MIT",
|
| 702 |
+
"optional": true,
|
| 703 |
+
"os": [
|
| 704 |
+
"linux"
|
| 705 |
+
],
|
| 706 |
+
"engines": {
|
| 707 |
+
"node": ">=18"
|
| 708 |
+
}
|
| 709 |
+
},
|
| 710 |
+
"node_modules/@esbuild/linux-x64": {
|
| 711 |
+
"version": "0.25.12",
|
| 712 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
|
| 713 |
+
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
|
| 714 |
+
"cpu": [
|
| 715 |
+
"x64"
|
| 716 |
+
],
|
| 717 |
+
"dev": true,
|
| 718 |
+
"license": "MIT",
|
| 719 |
+
"optional": true,
|
| 720 |
+
"os": [
|
| 721 |
+
"linux"
|
| 722 |
+
],
|
| 723 |
+
"engines": {
|
| 724 |
+
"node": ">=18"
|
| 725 |
+
}
|
| 726 |
+
},
|
| 727 |
+
"node_modules/@esbuild/netbsd-arm64": {
|
| 728 |
+
"version": "0.25.12",
|
| 729 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
|
| 730 |
+
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
|
| 731 |
+
"cpu": [
|
| 732 |
+
"arm64"
|
| 733 |
+
],
|
| 734 |
+
"dev": true,
|
| 735 |
+
"license": "MIT",
|
| 736 |
+
"optional": true,
|
| 737 |
+
"os": [
|
| 738 |
+
"netbsd"
|
| 739 |
+
],
|
| 740 |
+
"engines": {
|
| 741 |
+
"node": ">=18"
|
| 742 |
+
}
|
| 743 |
+
},
|
| 744 |
+
"node_modules/@esbuild/netbsd-x64": {
|
| 745 |
+
"version": "0.25.12",
|
| 746 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
|
| 747 |
+
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
|
| 748 |
+
"cpu": [
|
| 749 |
+
"x64"
|
| 750 |
+
],
|
| 751 |
+
"dev": true,
|
| 752 |
+
"license": "MIT",
|
| 753 |
+
"optional": true,
|
| 754 |
+
"os": [
|
| 755 |
+
"netbsd"
|
| 756 |
+
],
|
| 757 |
+
"engines": {
|
| 758 |
+
"node": ">=18"
|
| 759 |
+
}
|
| 760 |
+
},
|
| 761 |
+
"node_modules/@esbuild/openbsd-arm64": {
|
| 762 |
+
"version": "0.25.12",
|
| 763 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
|
| 764 |
+
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
|
| 765 |
+
"cpu": [
|
| 766 |
+
"arm64"
|
| 767 |
+
],
|
| 768 |
+
"dev": true,
|
| 769 |
+
"license": "MIT",
|
| 770 |
+
"optional": true,
|
| 771 |
+
"os": [
|
| 772 |
+
"openbsd"
|
| 773 |
+
],
|
| 774 |
+
"engines": {
|
| 775 |
+
"node": ">=18"
|
| 776 |
+
}
|
| 777 |
+
},
|
| 778 |
+
"node_modules/@esbuild/openbsd-x64": {
|
| 779 |
+
"version": "0.25.12",
|
| 780 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
|
| 781 |
+
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
|
| 782 |
+
"cpu": [
|
| 783 |
+
"x64"
|
| 784 |
+
],
|
| 785 |
+
"dev": true,
|
| 786 |
+
"license": "MIT",
|
| 787 |
+
"optional": true,
|
| 788 |
+
"os": [
|
| 789 |
+
"openbsd"
|
| 790 |
+
],
|
| 791 |
+
"engines": {
|
| 792 |
+
"node": ">=18"
|
| 793 |
+
}
|
| 794 |
+
},
|
| 795 |
+
"node_modules/@esbuild/openharmony-arm64": {
|
| 796 |
+
"version": "0.25.12",
|
| 797 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
|
| 798 |
+
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
|
| 799 |
+
"cpu": [
|
| 800 |
+
"arm64"
|
| 801 |
+
],
|
| 802 |
+
"dev": true,
|
| 803 |
+
"license": "MIT",
|
| 804 |
+
"optional": true,
|
| 805 |
+
"os": [
|
| 806 |
+
"openharmony"
|
| 807 |
+
],
|
| 808 |
+
"engines": {
|
| 809 |
+
"node": ">=18"
|
| 810 |
+
}
|
| 811 |
+
},
|
| 812 |
+
"node_modules/@esbuild/sunos-x64": {
|
| 813 |
+
"version": "0.25.12",
|
| 814 |
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
|
| 815 |
+
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
|
| 816 |
+
"cpu": [
|
| 817 |
+
"x64"
|
| 818 |
+
],
|
| 819 |
+
"dev": true,
|
| 820 |
+
"license": "MIT",
|
| 821 |
+
"optional": true,
|
| 822 |
+
"os": [
|
| 823 |
+
"sunos"
|
| 824 |
+
],
|
| 825 |
+
"engines": {
|
| 826 |
+
"node": ">=18"
|
| 827 |
+
}
|
| 828 |
+
},
|
| 829 |
+
"node_modules/@esbuild/win32-arm64": {
|
| 830 |
+
"version": "0.25.12",
|
| 831 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
|
| 832 |
+
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
|
| 833 |
+
"cpu": [
|
| 834 |
+
"arm64"
|
| 835 |
+
],
|
| 836 |
+
"dev": true,
|
| 837 |
+
"license": "MIT",
|
| 838 |
+
"optional": true,
|
| 839 |
+
"os": [
|
| 840 |
+
"win32"
|
| 841 |
+
],
|
| 842 |
+
"engines": {
|
| 843 |
+
"node": ">=18"
|
| 844 |
+
}
|
| 845 |
+
},
|
| 846 |
+
"node_modules/@esbuild/win32-ia32": {
|
| 847 |
+
"version": "0.25.12",
|
| 848 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
|
| 849 |
+
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
|
| 850 |
+
"cpu": [
|
| 851 |
+
"ia32"
|
| 852 |
+
],
|
| 853 |
+
"dev": true,
|
| 854 |
+
"license": "MIT",
|
| 855 |
+
"optional": true,
|
| 856 |
+
"os": [
|
| 857 |
+
"win32"
|
| 858 |
+
],
|
| 859 |
+
"engines": {
|
| 860 |
+
"node": ">=18"
|
| 861 |
+
}
|
| 862 |
+
},
|
| 863 |
+
"node_modules/@esbuild/win32-x64": {
|
| 864 |
+
"version": "0.25.12",
|
| 865 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
|
| 866 |
+
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
|
| 867 |
+
"cpu": [
|
| 868 |
+
"x64"
|
| 869 |
+
],
|
| 870 |
+
"dev": true,
|
| 871 |
+
"license": "MIT",
|
| 872 |
+
"optional": true,
|
| 873 |
+
"os": [
|
| 874 |
+
"win32"
|
| 875 |
+
],
|
| 876 |
+
"engines": {
|
| 877 |
+
"node": ">=18"
|
| 878 |
+
}
|
| 879 |
+
},
|
| 880 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
| 881 |
+
"version": "1.5.5",
|
| 882 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 883 |
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
| 884 |
+
"license": "MIT"
|
| 885 |
+
},
|
| 886 |
+
"node_modules/@rollup/rollup-android-arm-eabi": {
|
| 887 |
+
"version": "4.62.2",
|
| 888 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
|
| 889 |
+
"integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
|
| 890 |
+
"cpu": [
|
| 891 |
+
"arm"
|
| 892 |
+
],
|
| 893 |
+
"dev": true,
|
| 894 |
+
"license": "MIT",
|
| 895 |
+
"optional": true,
|
| 896 |
+
"os": [
|
| 897 |
+
"android"
|
| 898 |
+
]
|
| 899 |
+
},
|
| 900 |
+
"node_modules/@rollup/rollup-android-arm64": {
|
| 901 |
+
"version": "4.62.2",
|
| 902 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
|
| 903 |
+
"integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
|
| 904 |
+
"cpu": [
|
| 905 |
+
"arm64"
|
| 906 |
+
],
|
| 907 |
+
"dev": true,
|
| 908 |
+
"license": "MIT",
|
| 909 |
+
"optional": true,
|
| 910 |
+
"os": [
|
| 911 |
+
"android"
|
| 912 |
+
]
|
| 913 |
+
},
|
| 914 |
+
"node_modules/@rollup/rollup-darwin-arm64": {
|
| 915 |
+
"version": "4.62.2",
|
| 916 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
|
| 917 |
+
"integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
|
| 918 |
+
"cpu": [
|
| 919 |
+
"arm64"
|
| 920 |
+
],
|
| 921 |
+
"dev": true,
|
| 922 |
+
"license": "MIT",
|
| 923 |
+
"optional": true,
|
| 924 |
+
"os": [
|
| 925 |
+
"darwin"
|
| 926 |
+
]
|
| 927 |
+
},
|
| 928 |
+
"node_modules/@rollup/rollup-darwin-x64": {
|
| 929 |
+
"version": "4.62.2",
|
| 930 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
|
| 931 |
+
"integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
|
| 932 |
+
"cpu": [
|
| 933 |
+
"x64"
|
| 934 |
+
],
|
| 935 |
+
"dev": true,
|
| 936 |
+
"license": "MIT",
|
| 937 |
+
"optional": true,
|
| 938 |
+
"os": [
|
| 939 |
+
"darwin"
|
| 940 |
+
]
|
| 941 |
+
},
|
| 942 |
+
"node_modules/@rollup/rollup-freebsd-arm64": {
|
| 943 |
+
"version": "4.62.2",
|
| 944 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
|
| 945 |
+
"integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
|
| 946 |
+
"cpu": [
|
| 947 |
+
"arm64"
|
| 948 |
+
],
|
| 949 |
+
"dev": true,
|
| 950 |
+
"license": "MIT",
|
| 951 |
+
"optional": true,
|
| 952 |
+
"os": [
|
| 953 |
+
"freebsd"
|
| 954 |
+
]
|
| 955 |
+
},
|
| 956 |
+
"node_modules/@rollup/rollup-freebsd-x64": {
|
| 957 |
+
"version": "4.62.2",
|
| 958 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
|
| 959 |
+
"integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
|
| 960 |
+
"cpu": [
|
| 961 |
+
"x64"
|
| 962 |
+
],
|
| 963 |
+
"dev": true,
|
| 964 |
+
"license": "MIT",
|
| 965 |
+
"optional": true,
|
| 966 |
+
"os": [
|
| 967 |
+
"freebsd"
|
| 968 |
+
]
|
| 969 |
+
},
|
| 970 |
+
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
| 971 |
+
"version": "4.62.2",
|
| 972 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
|
| 973 |
+
"integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
|
| 974 |
+
"cpu": [
|
| 975 |
+
"arm"
|
| 976 |
+
],
|
| 977 |
+
"dev": true,
|
| 978 |
+
"license": "MIT",
|
| 979 |
+
"optional": true,
|
| 980 |
+
"os": [
|
| 981 |
+
"linux"
|
| 982 |
+
]
|
| 983 |
+
},
|
| 984 |
+
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
| 985 |
+
"version": "4.62.2",
|
| 986 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
|
| 987 |
+
"integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
|
| 988 |
+
"cpu": [
|
| 989 |
+
"arm"
|
| 990 |
+
],
|
| 991 |
+
"dev": true,
|
| 992 |
+
"license": "MIT",
|
| 993 |
+
"optional": true,
|
| 994 |
+
"os": [
|
| 995 |
+
"linux"
|
| 996 |
+
]
|
| 997 |
+
},
|
| 998 |
+
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
| 999 |
+
"version": "4.62.2",
|
| 1000 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
|
| 1001 |
+
"integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
|
| 1002 |
+
"cpu": [
|
| 1003 |
+
"arm64"
|
| 1004 |
+
],
|
| 1005 |
+
"dev": true,
|
| 1006 |
+
"license": "MIT",
|
| 1007 |
+
"optional": true,
|
| 1008 |
+
"os": [
|
| 1009 |
+
"linux"
|
| 1010 |
+
]
|
| 1011 |
+
},
|
| 1012 |
+
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
| 1013 |
+
"version": "4.62.2",
|
| 1014 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
|
| 1015 |
+
"integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
|
| 1016 |
+
"cpu": [
|
| 1017 |
+
"arm64"
|
| 1018 |
+
],
|
| 1019 |
+
"dev": true,
|
| 1020 |
+
"license": "MIT",
|
| 1021 |
+
"optional": true,
|
| 1022 |
+
"os": [
|
| 1023 |
+
"linux"
|
| 1024 |
+
]
|
| 1025 |
+
},
|
| 1026 |
+
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
| 1027 |
+
"version": "4.62.2",
|
| 1028 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
|
| 1029 |
+
"integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
|
| 1030 |
+
"cpu": [
|
| 1031 |
+
"loong64"
|
| 1032 |
+
],
|
| 1033 |
+
"dev": true,
|
| 1034 |
+
"license": "MIT",
|
| 1035 |
+
"optional": true,
|
| 1036 |
+
"os": [
|
| 1037 |
+
"linux"
|
| 1038 |
+
]
|
| 1039 |
+
},
|
| 1040 |
+
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
| 1041 |
+
"version": "4.62.2",
|
| 1042 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
|
| 1043 |
+
"integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
|
| 1044 |
+
"cpu": [
|
| 1045 |
+
"loong64"
|
| 1046 |
+
],
|
| 1047 |
+
"dev": true,
|
| 1048 |
+
"license": "MIT",
|
| 1049 |
+
"optional": true,
|
| 1050 |
+
"os": [
|
| 1051 |
+
"linux"
|
| 1052 |
+
]
|
| 1053 |
+
},
|
| 1054 |
+
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
| 1055 |
+
"version": "4.62.2",
|
| 1056 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
|
| 1057 |
+
"integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
|
| 1058 |
+
"cpu": [
|
| 1059 |
+
"ppc64"
|
| 1060 |
+
],
|
| 1061 |
+
"dev": true,
|
| 1062 |
+
"license": "MIT",
|
| 1063 |
+
"optional": true,
|
| 1064 |
+
"os": [
|
| 1065 |
+
"linux"
|
| 1066 |
+
]
|
| 1067 |
+
},
|
| 1068 |
+
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
| 1069 |
+
"version": "4.62.2",
|
| 1070 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
|
| 1071 |
+
"integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
|
| 1072 |
+
"cpu": [
|
| 1073 |
+
"ppc64"
|
| 1074 |
+
],
|
| 1075 |
+
"dev": true,
|
| 1076 |
+
"license": "MIT",
|
| 1077 |
+
"optional": true,
|
| 1078 |
+
"os": [
|
| 1079 |
+
"linux"
|
| 1080 |
+
]
|
| 1081 |
+
},
|
| 1082 |
+
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
| 1083 |
+
"version": "4.62.2",
|
| 1084 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
|
| 1085 |
+
"integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
|
| 1086 |
+
"cpu": [
|
| 1087 |
+
"riscv64"
|
| 1088 |
+
],
|
| 1089 |
+
"dev": true,
|
| 1090 |
+
"license": "MIT",
|
| 1091 |
+
"optional": true,
|
| 1092 |
+
"os": [
|
| 1093 |
+
"linux"
|
| 1094 |
+
]
|
| 1095 |
+
},
|
| 1096 |
+
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
| 1097 |
+
"version": "4.62.2",
|
| 1098 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
|
| 1099 |
+
"integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
|
| 1100 |
+
"cpu": [
|
| 1101 |
+
"riscv64"
|
| 1102 |
+
],
|
| 1103 |
+
"dev": true,
|
| 1104 |
+
"license": "MIT",
|
| 1105 |
+
"optional": true,
|
| 1106 |
+
"os": [
|
| 1107 |
+
"linux"
|
| 1108 |
+
]
|
| 1109 |
+
},
|
| 1110 |
+
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
| 1111 |
+
"version": "4.62.2",
|
| 1112 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
|
| 1113 |
+
"integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
|
| 1114 |
+
"cpu": [
|
| 1115 |
+
"s390x"
|
| 1116 |
+
],
|
| 1117 |
+
"dev": true,
|
| 1118 |
+
"license": "MIT",
|
| 1119 |
+
"optional": true,
|
| 1120 |
+
"os": [
|
| 1121 |
+
"linux"
|
| 1122 |
+
]
|
| 1123 |
+
},
|
| 1124 |
+
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
| 1125 |
+
"version": "4.62.2",
|
| 1126 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
|
| 1127 |
+
"integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
|
| 1128 |
+
"cpu": [
|
| 1129 |
+
"x64"
|
| 1130 |
+
],
|
| 1131 |
+
"dev": true,
|
| 1132 |
+
"license": "MIT",
|
| 1133 |
+
"optional": true,
|
| 1134 |
+
"os": [
|
| 1135 |
+
"linux"
|
| 1136 |
+
]
|
| 1137 |
+
},
|
| 1138 |
+
"node_modules/@rollup/rollup-linux-x64-musl": {
|
| 1139 |
+
"version": "4.62.2",
|
| 1140 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
|
| 1141 |
+
"integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
|
| 1142 |
+
"cpu": [
|
| 1143 |
+
"x64"
|
| 1144 |
+
],
|
| 1145 |
+
"dev": true,
|
| 1146 |
+
"license": "MIT",
|
| 1147 |
+
"optional": true,
|
| 1148 |
+
"os": [
|
| 1149 |
+
"linux"
|
| 1150 |
+
]
|
| 1151 |
+
},
|
| 1152 |
+
"node_modules/@rollup/rollup-openbsd-x64": {
|
| 1153 |
+
"version": "4.62.2",
|
| 1154 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
|
| 1155 |
+
"integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
|
| 1156 |
+
"cpu": [
|
| 1157 |
+
"x64"
|
| 1158 |
+
],
|
| 1159 |
+
"dev": true,
|
| 1160 |
+
"license": "MIT",
|
| 1161 |
+
"optional": true,
|
| 1162 |
+
"os": [
|
| 1163 |
+
"openbsd"
|
| 1164 |
+
]
|
| 1165 |
+
},
|
| 1166 |
+
"node_modules/@rollup/rollup-openharmony-arm64": {
|
| 1167 |
+
"version": "4.62.2",
|
| 1168 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
|
| 1169 |
+
"integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
|
| 1170 |
+
"cpu": [
|
| 1171 |
+
"arm64"
|
| 1172 |
+
],
|
| 1173 |
+
"dev": true,
|
| 1174 |
+
"license": "MIT",
|
| 1175 |
+
"optional": true,
|
| 1176 |
+
"os": [
|
| 1177 |
+
"openharmony"
|
| 1178 |
+
]
|
| 1179 |
+
},
|
| 1180 |
+
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
| 1181 |
+
"version": "4.62.2",
|
| 1182 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
|
| 1183 |
+
"integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
|
| 1184 |
+
"cpu": [
|
| 1185 |
+
"arm64"
|
| 1186 |
+
],
|
| 1187 |
+
"dev": true,
|
| 1188 |
+
"license": "MIT",
|
| 1189 |
+
"optional": true,
|
| 1190 |
+
"os": [
|
| 1191 |
+
"win32"
|
| 1192 |
+
]
|
| 1193 |
+
},
|
| 1194 |
+
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
| 1195 |
+
"version": "4.62.2",
|
| 1196 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
|
| 1197 |
+
"integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
|
| 1198 |
+
"cpu": [
|
| 1199 |
+
"ia32"
|
| 1200 |
+
],
|
| 1201 |
+
"dev": true,
|
| 1202 |
+
"license": "MIT",
|
| 1203 |
+
"optional": true,
|
| 1204 |
+
"os": [
|
| 1205 |
+
"win32"
|
| 1206 |
+
]
|
| 1207 |
+
},
|
| 1208 |
+
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
| 1209 |
+
"version": "4.62.2",
|
| 1210 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
|
| 1211 |
+
"integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
|
| 1212 |
+
"cpu": [
|
| 1213 |
+
"x64"
|
| 1214 |
+
],
|
| 1215 |
+
"dev": true,
|
| 1216 |
+
"license": "MIT",
|
| 1217 |
+
"optional": true,
|
| 1218 |
+
"os": [
|
| 1219 |
+
"win32"
|
| 1220 |
+
]
|
| 1221 |
+
},
|
| 1222 |
+
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
| 1223 |
+
"version": "4.62.2",
|
| 1224 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
|
| 1225 |
+
"integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
|
| 1226 |
+
"cpu": [
|
| 1227 |
+
"x64"
|
| 1228 |
+
],
|
| 1229 |
+
"dev": true,
|
| 1230 |
+
"license": "MIT",
|
| 1231 |
+
"optional": true,
|
| 1232 |
+
"os": [
|
| 1233 |
+
"win32"
|
| 1234 |
+
]
|
| 1235 |
+
},
|
| 1236 |
+
"node_modules/@smithy/core": {
|
| 1237 |
+
"version": "3.29.7",
|
| 1238 |
+
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.7.tgz",
|
| 1239 |
+
"integrity": "sha512-BiEE2bnnGoPKdlGe3L+gOYORDHFGPuYVRLP7iUow/Sflm0B4hC4XY3FC1MRuc7ltzpW2xNnXopKi34TTkULlKQ==",
|
| 1240 |
+
"license": "Apache-2.0",
|
| 1241 |
+
"dependencies": {
|
| 1242 |
+
"@smithy/types": "^4.16.1",
|
| 1243 |
+
"tslib": "^2.6.2"
|
| 1244 |
+
},
|
| 1245 |
+
"engines": {
|
| 1246 |
+
"node": ">=18.0.0"
|
| 1247 |
+
}
|
| 1248 |
+
},
|
| 1249 |
+
"node_modules/@smithy/credential-provider-imds": {
|
| 1250 |
+
"version": "4.4.12",
|
| 1251 |
+
"resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.12.tgz",
|
| 1252 |
+
"integrity": "sha512-ZZPDbl/aRp77aycuoMlo3BTayT4CE2a3uoqETYZU5ySnVbhpl5IJiY7dCZedn+ZusyDLqVv44IvKBiXd2/nK0Q==",
|
| 1253 |
+
"license": "Apache-2.0",
|
| 1254 |
+
"dependencies": {
|
| 1255 |
+
"@smithy/core": "^3.29.7",
|
| 1256 |
+
"@smithy/types": "^4.16.1",
|
| 1257 |
+
"tslib": "^2.6.2"
|
| 1258 |
+
},
|
| 1259 |
+
"engines": {
|
| 1260 |
+
"node": ">=18.0.0"
|
| 1261 |
+
}
|
| 1262 |
+
},
|
| 1263 |
+
"node_modules/@smithy/fetch-http-handler": {
|
| 1264 |
+
"version": "5.6.9",
|
| 1265 |
+
"resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.9.tgz",
|
| 1266 |
+
"integrity": "sha512-EJktha5m5MXCwzdXrlWyqb9UCNHNFKlg+PmTpRsdX3dncJPTiqYleM9OKj2mLgdVJHR01d2tU4alG+z2NdH5rQ==",
|
| 1267 |
+
"license": "Apache-2.0",
|
| 1268 |
+
"dependencies": {
|
| 1269 |
+
"@smithy/core": "^3.29.7",
|
| 1270 |
+
"@smithy/types": "^4.16.1",
|
| 1271 |
+
"tslib": "^2.6.2"
|
| 1272 |
+
},
|
| 1273 |
+
"engines": {
|
| 1274 |
+
"node": ">=18.0.0"
|
| 1275 |
+
}
|
| 1276 |
+
},
|
| 1277 |
+
"node_modules/@smithy/node-http-handler": {
|
| 1278 |
+
"version": "4.9.9",
|
| 1279 |
+
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.9.tgz",
|
| 1280 |
+
"integrity": "sha512-xVBZ3hptB99iNO9XyWqEhC7KD9bP9UPXhuy3h5Y2ItCfBv160D9IIC/Fmmp3EbnWwit4C+KVqlSE+E29Nk/pPg==",
|
| 1281 |
+
"license": "Apache-2.0",
|
| 1282 |
+
"dependencies": {
|
| 1283 |
+
"@smithy/core": "^3.29.7",
|
| 1284 |
+
"@smithy/types": "^4.16.1",
|
| 1285 |
+
"tslib": "^2.6.2"
|
| 1286 |
+
},
|
| 1287 |
+
"engines": {
|
| 1288 |
+
"node": ">=18.0.0"
|
| 1289 |
+
}
|
| 1290 |
+
},
|
| 1291 |
+
"node_modules/@smithy/signature-v4": {
|
| 1292 |
+
"version": "5.6.8",
|
| 1293 |
+
"resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.8.tgz",
|
| 1294 |
+
"integrity": "sha512-iGBm6hIwD2MGvVRSgrjVWa4FXtXDq3akxu0DCpnkmBo0xtEHZ/siMRt7ycfZAefYr2UdywUgmGtoRLaq5u56pg==",
|
| 1295 |
+
"license": "Apache-2.0",
|
| 1296 |
+
"dependencies": {
|
| 1297 |
+
"@smithy/core": "^3.29.7",
|
| 1298 |
+
"@smithy/types": "^4.16.1",
|
| 1299 |
+
"tslib": "^2.6.2"
|
| 1300 |
+
},
|
| 1301 |
+
"engines": {
|
| 1302 |
+
"node": ">=18.0.0"
|
| 1303 |
+
}
|
| 1304 |
+
},
|
| 1305 |
+
"node_modules/@smithy/types": {
|
| 1306 |
+
"version": "4.16.1",
|
| 1307 |
+
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz",
|
| 1308 |
+
"integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==",
|
| 1309 |
+
"license": "Apache-2.0",
|
| 1310 |
+
"dependencies": {
|
| 1311 |
+
"tslib": "^2.6.2"
|
| 1312 |
+
},
|
| 1313 |
+
"engines": {
|
| 1314 |
+
"node": ">=18.0.0"
|
| 1315 |
+
}
|
| 1316 |
+
},
|
| 1317 |
+
"node_modules/@types/estree": {
|
| 1318 |
+
"version": "1.0.9",
|
| 1319 |
+
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
| 1320 |
+
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
|
| 1321 |
+
"dev": true,
|
| 1322 |
+
"license": "MIT"
|
| 1323 |
+
},
|
| 1324 |
+
"node_modules/@types/node": {
|
| 1325 |
+
"version": "20.19.43",
|
| 1326 |
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
|
| 1327 |
+
"integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
|
| 1328 |
+
"dev": true,
|
| 1329 |
+
"license": "MIT",
|
| 1330 |
+
"dependencies": {
|
| 1331 |
+
"undici-types": "~6.21.0"
|
| 1332 |
+
}
|
| 1333 |
+
},
|
| 1334 |
+
"node_modules/@vitejs/plugin-vue": {
|
| 1335 |
+
"version": "5.2.4",
|
| 1336 |
+
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
|
| 1337 |
+
"integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
|
| 1338 |
+
"dev": true,
|
| 1339 |
+
"license": "MIT",
|
| 1340 |
+
"engines": {
|
| 1341 |
+
"node": "^18.0.0 || >=20.0.0"
|
| 1342 |
+
},
|
| 1343 |
+
"peerDependencies": {
|
| 1344 |
+
"vite": "^5.0.0 || ^6.0.0",
|
| 1345 |
+
"vue": "^3.2.25"
|
| 1346 |
+
}
|
| 1347 |
+
},
|
| 1348 |
+
"node_modules/@volar/language-core": {
|
| 1349 |
+
"version": "2.4.28",
|
| 1350 |
+
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz",
|
| 1351 |
+
"integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==",
|
| 1352 |
+
"dev": true,
|
| 1353 |
+
"license": "MIT",
|
| 1354 |
+
"dependencies": {
|
| 1355 |
+
"@volar/source-map": "2.4.28"
|
| 1356 |
+
}
|
| 1357 |
+
},
|
| 1358 |
+
"node_modules/@volar/source-map": {
|
| 1359 |
+
"version": "2.4.28",
|
| 1360 |
+
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz",
|
| 1361 |
+
"integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==",
|
| 1362 |
+
"dev": true,
|
| 1363 |
+
"license": "MIT"
|
| 1364 |
+
},
|
| 1365 |
+
"node_modules/@volar/typescript": {
|
| 1366 |
+
"version": "2.4.28",
|
| 1367 |
+
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz",
|
| 1368 |
+
"integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==",
|
| 1369 |
+
"dev": true,
|
| 1370 |
+
"license": "MIT",
|
| 1371 |
+
"dependencies": {
|
| 1372 |
+
"@volar/language-core": "2.4.28",
|
| 1373 |
+
"path-browserify": "^1.0.1",
|
| 1374 |
+
"vscode-uri": "^3.0.8"
|
| 1375 |
+
}
|
| 1376 |
+
},
|
| 1377 |
+
"node_modules/@vue/compiler-core": {
|
| 1378 |
+
"version": "3.5.40",
|
| 1379 |
+
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.40.tgz",
|
| 1380 |
+
"integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==",
|
| 1381 |
+
"license": "MIT",
|
| 1382 |
+
"dependencies": {
|
| 1383 |
+
"@babel/parser": "^7.29.7",
|
| 1384 |
+
"@vue/shared": "3.5.40",
|
| 1385 |
+
"entities": "^7.0.1",
|
| 1386 |
+
"estree-walker": "^2.0.2",
|
| 1387 |
+
"source-map-js": "^1.2.1"
|
| 1388 |
+
}
|
| 1389 |
+
},
|
| 1390 |
+
"node_modules/@vue/compiler-dom": {
|
| 1391 |
+
"version": "3.5.40",
|
| 1392 |
+
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz",
|
| 1393 |
+
"integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==",
|
| 1394 |
+
"license": "MIT",
|
| 1395 |
+
"dependencies": {
|
| 1396 |
+
"@vue/compiler-core": "3.5.40",
|
| 1397 |
+
"@vue/shared": "3.5.40"
|
| 1398 |
+
}
|
| 1399 |
+
},
|
| 1400 |
+
"node_modules/@vue/compiler-sfc": {
|
| 1401 |
+
"version": "3.5.40",
|
| 1402 |
+
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz",
|
| 1403 |
+
"integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==",
|
| 1404 |
+
"license": "MIT",
|
| 1405 |
+
"dependencies": {
|
| 1406 |
+
"@babel/parser": "^7.29.7",
|
| 1407 |
+
"@vue/compiler-core": "3.5.40",
|
| 1408 |
+
"@vue/compiler-dom": "3.5.40",
|
| 1409 |
+
"@vue/compiler-ssr": "3.5.40",
|
| 1410 |
+
"@vue/shared": "3.5.40",
|
| 1411 |
+
"estree-walker": "^2.0.2",
|
| 1412 |
+
"magic-string": "^0.30.21",
|
| 1413 |
+
"postcss": "^8.5.19",
|
| 1414 |
+
"source-map-js": "^1.2.1"
|
| 1415 |
+
}
|
| 1416 |
+
},
|
| 1417 |
+
"node_modules/@vue/compiler-ssr": {
|
| 1418 |
+
"version": "3.5.40",
|
| 1419 |
+
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz",
|
| 1420 |
+
"integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==",
|
| 1421 |
+
"license": "MIT",
|
| 1422 |
+
"dependencies": {
|
| 1423 |
+
"@vue/compiler-dom": "3.5.40",
|
| 1424 |
+
"@vue/shared": "3.5.40"
|
| 1425 |
+
}
|
| 1426 |
+
},
|
| 1427 |
+
"node_modules/@vue/language-core": {
|
| 1428 |
+
"version": "3.3.7",
|
| 1429 |
+
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.7.tgz",
|
| 1430 |
+
"integrity": "sha512-LzmkKinXAMMoh8Jfi/jMUSDUjuPdv8mynH5WJGKfXyZtDw3hQ6GBaoI6Bcnl/Xqlu32q/0Z6i/trp4VXykzyLw==",
|
| 1431 |
+
"dev": true,
|
| 1432 |
+
"license": "MIT",
|
| 1433 |
+
"dependencies": {
|
| 1434 |
+
"@volar/language-core": "2.4.28",
|
| 1435 |
+
"@vue/compiler-dom": "^3.5.0",
|
| 1436 |
+
"@vue/shared": "^3.5.0",
|
| 1437 |
+
"alien-signals": "^3.2.1",
|
| 1438 |
+
"muggle-string": "^0.4.1",
|
| 1439 |
+
"path-browserify": "^1.0.1",
|
| 1440 |
+
"picomatch": "^4.0.4"
|
| 1441 |
+
}
|
| 1442 |
+
},
|
| 1443 |
+
"node_modules/@vue/reactivity": {
|
| 1444 |
+
"version": "3.5.40",
|
| 1445 |
+
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.40.tgz",
|
| 1446 |
+
"integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==",
|
| 1447 |
+
"license": "MIT",
|
| 1448 |
+
"dependencies": {
|
| 1449 |
+
"@vue/shared": "3.5.40"
|
| 1450 |
+
}
|
| 1451 |
+
},
|
| 1452 |
+
"node_modules/@vue/runtime-core": {
|
| 1453 |
+
"version": "3.5.40",
|
| 1454 |
+
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.40.tgz",
|
| 1455 |
+
"integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==",
|
| 1456 |
+
"license": "MIT",
|
| 1457 |
+
"dependencies": {
|
| 1458 |
+
"@vue/reactivity": "3.5.40",
|
| 1459 |
+
"@vue/shared": "3.5.40"
|
| 1460 |
+
}
|
| 1461 |
+
},
|
| 1462 |
+
"node_modules/@vue/runtime-dom": {
|
| 1463 |
+
"version": "3.5.40",
|
| 1464 |
+
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz",
|
| 1465 |
+
"integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==",
|
| 1466 |
+
"license": "MIT",
|
| 1467 |
+
"dependencies": {
|
| 1468 |
+
"@vue/reactivity": "3.5.40",
|
| 1469 |
+
"@vue/runtime-core": "3.5.40",
|
| 1470 |
+
"@vue/shared": "3.5.40",
|
| 1471 |
+
"csstype": "^3.2.3"
|
| 1472 |
+
}
|
| 1473 |
+
},
|
| 1474 |
+
"node_modules/@vue/server-renderer": {
|
| 1475 |
+
"version": "3.5.40",
|
| 1476 |
+
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.40.tgz",
|
| 1477 |
+
"integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==",
|
| 1478 |
+
"license": "MIT",
|
| 1479 |
+
"dependencies": {
|
| 1480 |
+
"@vue/compiler-ssr": "3.5.40",
|
| 1481 |
+
"@vue/runtime-dom": "3.5.40",
|
| 1482 |
+
"@vue/shared": "3.5.40"
|
| 1483 |
+
}
|
| 1484 |
+
},
|
| 1485 |
+
"node_modules/@vue/shared": {
|
| 1486 |
+
"version": "3.5.40",
|
| 1487 |
+
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz",
|
| 1488 |
+
"integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==",
|
| 1489 |
+
"license": "MIT"
|
| 1490 |
+
},
|
| 1491 |
+
"node_modules/alien-signals": {
|
| 1492 |
+
"version": "3.2.1",
|
| 1493 |
+
"resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz",
|
| 1494 |
+
"integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==",
|
| 1495 |
+
"dev": true,
|
| 1496 |
+
"license": "MIT"
|
| 1497 |
+
},
|
| 1498 |
+
"node_modules/bowser": {
|
| 1499 |
+
"version": "2.14.1",
|
| 1500 |
+
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz",
|
| 1501 |
+
"integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==",
|
| 1502 |
+
"license": "MIT"
|
| 1503 |
+
},
|
| 1504 |
+
"node_modules/csstype": {
|
| 1505 |
+
"version": "3.2.3",
|
| 1506 |
+
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
| 1507 |
+
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
| 1508 |
+
"license": "MIT"
|
| 1509 |
+
},
|
| 1510 |
+
"node_modules/entities": {
|
| 1511 |
+
"version": "7.0.1",
|
| 1512 |
+
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
| 1513 |
+
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
|
| 1514 |
+
"license": "BSD-2-Clause",
|
| 1515 |
+
"engines": {
|
| 1516 |
+
"node": ">=0.12"
|
| 1517 |
+
},
|
| 1518 |
+
"funding": {
|
| 1519 |
+
"url": "https://github.com/fb55/entities?sponsor=1"
|
| 1520 |
+
}
|
| 1521 |
+
},
|
| 1522 |
+
"node_modules/esbuild": {
|
| 1523 |
+
"version": "0.25.12",
|
| 1524 |
+
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
| 1525 |
+
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
|
| 1526 |
+
"dev": true,
|
| 1527 |
+
"hasInstallScript": true,
|
| 1528 |
+
"license": "MIT",
|
| 1529 |
+
"bin": {
|
| 1530 |
+
"esbuild": "bin/esbuild"
|
| 1531 |
+
},
|
| 1532 |
+
"engines": {
|
| 1533 |
+
"node": ">=18"
|
| 1534 |
+
},
|
| 1535 |
+
"optionalDependencies": {
|
| 1536 |
+
"@esbuild/aix-ppc64": "0.25.12",
|
| 1537 |
+
"@esbuild/android-arm": "0.25.12",
|
| 1538 |
+
"@esbuild/android-arm64": "0.25.12",
|
| 1539 |
+
"@esbuild/android-x64": "0.25.12",
|
| 1540 |
+
"@esbuild/darwin-arm64": "0.25.12",
|
| 1541 |
+
"@esbuild/darwin-x64": "0.25.12",
|
| 1542 |
+
"@esbuild/freebsd-arm64": "0.25.12",
|
| 1543 |
+
"@esbuild/freebsd-x64": "0.25.12",
|
| 1544 |
+
"@esbuild/linux-arm": "0.25.12",
|
| 1545 |
+
"@esbuild/linux-arm64": "0.25.12",
|
| 1546 |
+
"@esbuild/linux-ia32": "0.25.12",
|
| 1547 |
+
"@esbuild/linux-loong64": "0.25.12",
|
| 1548 |
+
"@esbuild/linux-mips64el": "0.25.12",
|
| 1549 |
+
"@esbuild/linux-ppc64": "0.25.12",
|
| 1550 |
+
"@esbuild/linux-riscv64": "0.25.12",
|
| 1551 |
+
"@esbuild/linux-s390x": "0.25.12",
|
| 1552 |
+
"@esbuild/linux-x64": "0.25.12",
|
| 1553 |
+
"@esbuild/netbsd-arm64": "0.25.12",
|
| 1554 |
+
"@esbuild/netbsd-x64": "0.25.12",
|
| 1555 |
+
"@esbuild/openbsd-arm64": "0.25.12",
|
| 1556 |
+
"@esbuild/openbsd-x64": "0.25.12",
|
| 1557 |
+
"@esbuild/openharmony-arm64": "0.25.12",
|
| 1558 |
+
"@esbuild/sunos-x64": "0.25.12",
|
| 1559 |
+
"@esbuild/win32-arm64": "0.25.12",
|
| 1560 |
+
"@esbuild/win32-ia32": "0.25.12",
|
| 1561 |
+
"@esbuild/win32-x64": "0.25.12"
|
| 1562 |
+
}
|
| 1563 |
+
},
|
| 1564 |
+
"node_modules/estree-walker": {
|
| 1565 |
+
"version": "2.0.2",
|
| 1566 |
+
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
| 1567 |
+
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
| 1568 |
+
"license": "MIT"
|
| 1569 |
+
},
|
| 1570 |
+
"node_modules/fdir": {
|
| 1571 |
+
"version": "6.5.0",
|
| 1572 |
+
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
| 1573 |
+
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
| 1574 |
+
"dev": true,
|
| 1575 |
+
"license": "MIT",
|
| 1576 |
+
"engines": {
|
| 1577 |
+
"node": ">=12.0.0"
|
| 1578 |
+
},
|
| 1579 |
+
"peerDependencies": {
|
| 1580 |
+
"picomatch": "^3 || ^4"
|
| 1581 |
+
},
|
| 1582 |
+
"peerDependenciesMeta": {
|
| 1583 |
+
"picomatch": {
|
| 1584 |
+
"optional": true
|
| 1585 |
+
}
|
| 1586 |
+
}
|
| 1587 |
+
},
|
| 1588 |
+
"node_modules/fsevents": {
|
| 1589 |
+
"version": "2.3.3",
|
| 1590 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 1591 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 1592 |
+
"dev": true,
|
| 1593 |
+
"hasInstallScript": true,
|
| 1594 |
+
"license": "MIT",
|
| 1595 |
+
"optional": true,
|
| 1596 |
+
"os": [
|
| 1597 |
+
"darwin"
|
| 1598 |
+
],
|
| 1599 |
+
"engines": {
|
| 1600 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 1601 |
+
}
|
| 1602 |
+
},
|
| 1603 |
+
"node_modules/magic-string": {
|
| 1604 |
+
"version": "0.30.21",
|
| 1605 |
+
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
| 1606 |
+
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
| 1607 |
+
"license": "MIT",
|
| 1608 |
+
"dependencies": {
|
| 1609 |
+
"@jridgewell/sourcemap-codec": "^1.5.5"
|
| 1610 |
+
}
|
| 1611 |
+
},
|
| 1612 |
+
"node_modules/muggle-string": {
|
| 1613 |
+
"version": "0.4.1",
|
| 1614 |
+
"resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
|
| 1615 |
+
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
|
| 1616 |
+
"dev": true,
|
| 1617 |
+
"license": "MIT"
|
| 1618 |
+
},
|
| 1619 |
+
"node_modules/nanoid": {
|
| 1620 |
+
"version": "3.3.16",
|
| 1621 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
|
| 1622 |
+
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
|
| 1623 |
+
"funding": [
|
| 1624 |
+
{
|
| 1625 |
+
"type": "github",
|
| 1626 |
+
"url": "https://github.com/sponsors/ai"
|
| 1627 |
+
}
|
| 1628 |
+
],
|
| 1629 |
+
"license": "MIT",
|
| 1630 |
+
"bin": {
|
| 1631 |
+
"nanoid": "bin/nanoid.cjs"
|
| 1632 |
+
},
|
| 1633 |
+
"engines": {
|
| 1634 |
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
| 1635 |
+
}
|
| 1636 |
+
},
|
| 1637 |
+
"node_modules/path-browserify": {
|
| 1638 |
+
"version": "1.0.1",
|
| 1639 |
+
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
|
| 1640 |
+
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
|
| 1641 |
+
"dev": true,
|
| 1642 |
+
"license": "MIT"
|
| 1643 |
+
},
|
| 1644 |
+
"node_modules/picocolors": {
|
| 1645 |
+
"version": "1.1.1",
|
| 1646 |
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 1647 |
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 1648 |
+
"license": "ISC"
|
| 1649 |
+
},
|
| 1650 |
+
"node_modules/picomatch": {
|
| 1651 |
+
"version": "4.0.5",
|
| 1652 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
|
| 1653 |
+
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
| 1654 |
+
"dev": true,
|
| 1655 |
+
"license": "MIT",
|
| 1656 |
+
"engines": {
|
| 1657 |
+
"node": ">=12"
|
| 1658 |
+
},
|
| 1659 |
+
"funding": {
|
| 1660 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 1661 |
+
}
|
| 1662 |
+
},
|
| 1663 |
+
"node_modules/postcss": {
|
| 1664 |
+
"version": "8.5.22",
|
| 1665 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.22.tgz",
|
| 1666 |
+
"integrity": "sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==",
|
| 1667 |
+
"funding": [
|
| 1668 |
+
{
|
| 1669 |
+
"type": "opencollective",
|
| 1670 |
+
"url": "https://opencollective.com/postcss/"
|
| 1671 |
+
},
|
| 1672 |
+
{
|
| 1673 |
+
"type": "tidelift",
|
| 1674 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 1675 |
+
},
|
| 1676 |
+
{
|
| 1677 |
+
"type": "github",
|
| 1678 |
+
"url": "https://github.com/sponsors/ai"
|
| 1679 |
+
}
|
| 1680 |
+
],
|
| 1681 |
+
"license": "MIT",
|
| 1682 |
+
"dependencies": {
|
| 1683 |
+
"nanoid": "^3.3.16",
|
| 1684 |
+
"picocolors": "^1.1.1",
|
| 1685 |
+
"source-map-js": "^1.2.1"
|
| 1686 |
+
},
|
| 1687 |
+
"engines": {
|
| 1688 |
+
"node": "^10 || ^12 || >=14"
|
| 1689 |
+
}
|
| 1690 |
+
},
|
| 1691 |
+
"node_modules/rollup": {
|
| 1692 |
+
"version": "4.62.2",
|
| 1693 |
+
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
|
| 1694 |
+
"integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
|
| 1695 |
+
"dev": true,
|
| 1696 |
+
"license": "MIT",
|
| 1697 |
+
"dependencies": {
|
| 1698 |
+
"@types/estree": "1.0.9"
|
| 1699 |
+
},
|
| 1700 |
+
"bin": {
|
| 1701 |
+
"rollup": "dist/bin/rollup"
|
| 1702 |
+
},
|
| 1703 |
+
"engines": {
|
| 1704 |
+
"node": ">=18.0.0",
|
| 1705 |
+
"npm": ">=8.0.0"
|
| 1706 |
+
},
|
| 1707 |
+
"optionalDependencies": {
|
| 1708 |
+
"@rollup/rollup-android-arm-eabi": "4.62.2",
|
| 1709 |
+
"@rollup/rollup-android-arm64": "4.62.2",
|
| 1710 |
+
"@rollup/rollup-darwin-arm64": "4.62.2",
|
| 1711 |
+
"@rollup/rollup-darwin-x64": "4.62.2",
|
| 1712 |
+
"@rollup/rollup-freebsd-arm64": "4.62.2",
|
| 1713 |
+
"@rollup/rollup-freebsd-x64": "4.62.2",
|
| 1714 |
+
"@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
|
| 1715 |
+
"@rollup/rollup-linux-arm-musleabihf": "4.62.2",
|
| 1716 |
+
"@rollup/rollup-linux-arm64-gnu": "4.62.2",
|
| 1717 |
+
"@rollup/rollup-linux-arm64-musl": "4.62.2",
|
| 1718 |
+
"@rollup/rollup-linux-loong64-gnu": "4.62.2",
|
| 1719 |
+
"@rollup/rollup-linux-loong64-musl": "4.62.2",
|
| 1720 |
+
"@rollup/rollup-linux-ppc64-gnu": "4.62.2",
|
| 1721 |
+
"@rollup/rollup-linux-ppc64-musl": "4.62.2",
|
| 1722 |
+
"@rollup/rollup-linux-riscv64-gnu": "4.62.2",
|
| 1723 |
+
"@rollup/rollup-linux-riscv64-musl": "4.62.2",
|
| 1724 |
+
"@rollup/rollup-linux-s390x-gnu": "4.62.2",
|
| 1725 |
+
"@rollup/rollup-linux-x64-gnu": "4.62.2",
|
| 1726 |
+
"@rollup/rollup-linux-x64-musl": "4.62.2",
|
| 1727 |
+
"@rollup/rollup-openbsd-x64": "4.62.2",
|
| 1728 |
+
"@rollup/rollup-openharmony-arm64": "4.62.2",
|
| 1729 |
+
"@rollup/rollup-win32-arm64-msvc": "4.62.2",
|
| 1730 |
+
"@rollup/rollup-win32-ia32-msvc": "4.62.2",
|
| 1731 |
+
"@rollup/rollup-win32-x64-gnu": "4.62.2",
|
| 1732 |
+
"@rollup/rollup-win32-x64-msvc": "4.62.2",
|
| 1733 |
+
"fsevents": "~2.3.2"
|
| 1734 |
+
}
|
| 1735 |
+
},
|
| 1736 |
+
"node_modules/source-map-js": {
|
| 1737 |
+
"version": "1.2.1",
|
| 1738 |
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
| 1739 |
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
| 1740 |
+
"license": "BSD-3-Clause",
|
| 1741 |
+
"engines": {
|
| 1742 |
+
"node": ">=0.10.0"
|
| 1743 |
+
}
|
| 1744 |
+
},
|
| 1745 |
+
"node_modules/tinyglobby": {
|
| 1746 |
+
"version": "0.2.17",
|
| 1747 |
+
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
| 1748 |
+
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
|
| 1749 |
+
"dev": true,
|
| 1750 |
+
"license": "MIT",
|
| 1751 |
+
"dependencies": {
|
| 1752 |
+
"fdir": "^6.5.0",
|
| 1753 |
+
"picomatch": "^4.0.4"
|
| 1754 |
+
},
|
| 1755 |
+
"engines": {
|
| 1756 |
+
"node": ">=12.0.0"
|
| 1757 |
+
},
|
| 1758 |
+
"funding": {
|
| 1759 |
+
"url": "https://github.com/sponsors/SuperchupuDev"
|
| 1760 |
+
}
|
| 1761 |
+
},
|
| 1762 |
+
"node_modules/tslib": {
|
| 1763 |
+
"version": "2.8.1",
|
| 1764 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 1765 |
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 1766 |
+
"license": "0BSD"
|
| 1767 |
+
},
|
| 1768 |
+
"node_modules/typescript": {
|
| 1769 |
+
"version": "5.9.3",
|
| 1770 |
+
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
| 1771 |
+
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
| 1772 |
+
"devOptional": true,
|
| 1773 |
+
"license": "Apache-2.0",
|
| 1774 |
+
"bin": {
|
| 1775 |
+
"tsc": "bin/tsc",
|
| 1776 |
+
"tsserver": "bin/tsserver"
|
| 1777 |
+
},
|
| 1778 |
+
"engines": {
|
| 1779 |
+
"node": ">=14.17"
|
| 1780 |
+
}
|
| 1781 |
+
},
|
| 1782 |
+
"node_modules/undici-types": {
|
| 1783 |
+
"version": "6.21.0",
|
| 1784 |
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
| 1785 |
+
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
| 1786 |
+
"dev": true,
|
| 1787 |
+
"license": "MIT"
|
| 1788 |
+
},
|
| 1789 |
+
"node_modules/vite": {
|
| 1790 |
+
"version": "6.4.3",
|
| 1791 |
+
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
|
| 1792 |
+
"integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
|
| 1793 |
+
"dev": true,
|
| 1794 |
+
"license": "MIT",
|
| 1795 |
+
"dependencies": {
|
| 1796 |
+
"esbuild": "^0.25.0",
|
| 1797 |
+
"fdir": "^6.4.4",
|
| 1798 |
+
"picomatch": "^4.0.2",
|
| 1799 |
+
"postcss": "^8.5.3",
|
| 1800 |
+
"rollup": "^4.34.9",
|
| 1801 |
+
"tinyglobby": "^0.2.13"
|
| 1802 |
+
},
|
| 1803 |
+
"bin": {
|
| 1804 |
+
"vite": "bin/vite.js"
|
| 1805 |
+
},
|
| 1806 |
+
"engines": {
|
| 1807 |
+
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
| 1808 |
+
},
|
| 1809 |
+
"funding": {
|
| 1810 |
+
"url": "https://github.com/vitejs/vite?sponsor=1"
|
| 1811 |
+
},
|
| 1812 |
+
"optionalDependencies": {
|
| 1813 |
+
"fsevents": "~2.3.3"
|
| 1814 |
+
},
|
| 1815 |
+
"peerDependencies": {
|
| 1816 |
+
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
| 1817 |
+
"jiti": ">=1.21.0",
|
| 1818 |
+
"less": "*",
|
| 1819 |
+
"lightningcss": "^1.21.0",
|
| 1820 |
+
"sass": "*",
|
| 1821 |
+
"sass-embedded": "*",
|
| 1822 |
+
"stylus": "*",
|
| 1823 |
+
"sugarss": "*",
|
| 1824 |
+
"terser": "^5.16.0",
|
| 1825 |
+
"tsx": "^4.8.1",
|
| 1826 |
+
"yaml": "^2.4.2"
|
| 1827 |
+
},
|
| 1828 |
+
"peerDependenciesMeta": {
|
| 1829 |
+
"@types/node": {
|
| 1830 |
+
"optional": true
|
| 1831 |
+
},
|
| 1832 |
+
"jiti": {
|
| 1833 |
+
"optional": true
|
| 1834 |
+
},
|
| 1835 |
+
"less": {
|
| 1836 |
+
"optional": true
|
| 1837 |
+
},
|
| 1838 |
+
"lightningcss": {
|
| 1839 |
+
"optional": true
|
| 1840 |
+
},
|
| 1841 |
+
"sass": {
|
| 1842 |
+
"optional": true
|
| 1843 |
+
},
|
| 1844 |
+
"sass-embedded": {
|
| 1845 |
+
"optional": true
|
| 1846 |
+
},
|
| 1847 |
+
"stylus": {
|
| 1848 |
+
"optional": true
|
| 1849 |
+
},
|
| 1850 |
+
"sugarss": {
|
| 1851 |
+
"optional": true
|
| 1852 |
+
},
|
| 1853 |
+
"terser": {
|
| 1854 |
+
"optional": true
|
| 1855 |
+
},
|
| 1856 |
+
"tsx": {
|
| 1857 |
+
"optional": true
|
| 1858 |
+
},
|
| 1859 |
+
"yaml": {
|
| 1860 |
+
"optional": true
|
| 1861 |
+
}
|
| 1862 |
+
}
|
| 1863 |
+
},
|
| 1864 |
+
"node_modules/vscode-uri": {
|
| 1865 |
+
"version": "3.1.0",
|
| 1866 |
+
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
|
| 1867 |
+
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
|
| 1868 |
+
"dev": true,
|
| 1869 |
+
"license": "MIT"
|
| 1870 |
+
},
|
| 1871 |
+
"node_modules/vue": {
|
| 1872 |
+
"version": "3.5.40",
|
| 1873 |
+
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.40.tgz",
|
| 1874 |
+
"integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==",
|
| 1875 |
+
"license": "MIT",
|
| 1876 |
+
"dependencies": {
|
| 1877 |
+
"@vue/compiler-dom": "3.5.40",
|
| 1878 |
+
"@vue/compiler-sfc": "3.5.40",
|
| 1879 |
+
"@vue/runtime-dom": "3.5.40",
|
| 1880 |
+
"@vue/server-renderer": "3.5.40",
|
| 1881 |
+
"@vue/shared": "3.5.40"
|
| 1882 |
+
},
|
| 1883 |
+
"peerDependencies": {
|
| 1884 |
+
"typescript": "*"
|
| 1885 |
+
},
|
| 1886 |
+
"peerDependenciesMeta": {
|
| 1887 |
+
"typescript": {
|
| 1888 |
+
"optional": true
|
| 1889 |
+
}
|
| 1890 |
+
}
|
| 1891 |
+
},
|
| 1892 |
+
"node_modules/vue-tsc": {
|
| 1893 |
+
"version": "3.3.7",
|
| 1894 |
+
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.7.tgz",
|
| 1895 |
+
"integrity": "sha512-+C+rgD49wAQ5bUTl2sp5a8Bzg4YoldMNXM+g7CFe604MYcQ8PrZPMQhIjJSzKXtPBCa+C5ayMipqjbA7splekQ==",
|
| 1896 |
+
"dev": true,
|
| 1897 |
+
"license": "MIT",
|
| 1898 |
+
"dependencies": {
|
| 1899 |
+
"@volar/typescript": "2.4.28",
|
| 1900 |
+
"@vue/language-core": "3.3.7"
|
| 1901 |
+
},
|
| 1902 |
+
"bin": {
|
| 1903 |
+
"vue-tsc": "bin/vue-tsc.js"
|
| 1904 |
+
},
|
| 1905 |
+
"peerDependencies": {
|
| 1906 |
+
"typescript": ">=5.0.0"
|
| 1907 |
+
}
|
| 1908 |
+
}
|
| 1909 |
+
}
|
| 1910 |
+
}
|
apps/results-dashboard/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "autocad-bench-results-dashboard",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.1.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite --host 127.0.0.1 --port 4273",
|
| 8 |
+
"build": "vue-tsc -b && vite build",
|
| 9 |
+
"test": "node --test tests/*.test.mjs",
|
| 10 |
+
"preview": "node server.mjs"
|
| 11 |
+
},
|
| 12 |
+
"dependencies": {
|
| 13 |
+
"@aws-sdk/client-s3": "3.1090.0",
|
| 14 |
+
"@aws-sdk/credential-providers": "3.1090.0",
|
| 15 |
+
"@aws-sdk/s3-request-presigner": "3.1090.0",
|
| 16 |
+
"vue": "3.5.40"
|
| 17 |
+
},
|
| 18 |
+
"devDependencies": {
|
| 19 |
+
"@types/node": "20.19.43",
|
| 20 |
+
"@vitejs/plugin-vue": "5.2.4",
|
| 21 |
+
"typescript": "5.9.3",
|
| 22 |
+
"vite": "6.4.3",
|
| 23 |
+
"vue-tsc": "3.3.7"
|
| 24 |
+
}
|
| 25 |
+
}
|
apps/results-dashboard/results-api.mjs
ADDED
|
@@ -0,0 +1,714 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import {
|
| 2 |
+
GetObjectCommand,
|
| 3 |
+
ListObjectsV2Command,
|
| 4 |
+
} from '@aws-sdk/client-s3'
|
| 5 |
+
|
| 6 |
+
const MANIFEST_SUFFIX = 'aggregate/manifest.json'
|
| 7 |
+
const DEFAULT_CACHE_TTL_MS = 5 * 60_000
|
| 8 |
+
const DEFAULT_TRACE_CACHE_ENTRIES = 12
|
| 9 |
+
|
| 10 |
+
function asNumber(value) {
|
| 11 |
+
if (value == null || value === '') return null
|
| 12 |
+
const parsed = Number(value)
|
| 13 |
+
return Number.isFinite(parsed) ? parsed : null
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
function mean(values) {
|
| 17 |
+
const usable = values.filter((value) => Number.isFinite(value))
|
| 18 |
+
return usable.length ? usable.reduce((sum, value) => sum + value, 0) / usable.length : null
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
function median(values) {
|
| 22 |
+
const usable = values.filter((value) => Number.isFinite(value)).sort((a, b) => a - b)
|
| 23 |
+
if (!usable.length) return null
|
| 24 |
+
const middle = Math.floor(usable.length / 2)
|
| 25 |
+
return usable.length % 2 ? usable[middle] : (usable[middle - 1] + usable[middle]) / 2
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
function slugify(value) {
|
| 29 |
+
return String(value || 'model')
|
| 30 |
+
.toLowerCase()
|
| 31 |
+
.replace(/[^a-z0-9]+/g, '-')
|
| 32 |
+
.replace(/^-+|-+$/g, '')
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
const CANONICAL_TASK_RANGES = [
|
| 36 |
+
{ first: 1, last: 3, drawingType: '2d', level: 'basic' },
|
| 37 |
+
{ first: 4, last: 9, drawingType: '2d', level: 'intermediate' },
|
| 38 |
+
{ first: 10, last: 15, drawingType: '2d', level: 'advanced' },
|
| 39 |
+
{ first: 16, last: 21, drawingType: '2d', level: 'project' },
|
| 40 |
+
{ first: 22, last: 28, drawingType: '3d', level: 'basic' },
|
| 41 |
+
{ first: 29, last: 34, drawingType: '3d', level: 'intermediate' },
|
| 42 |
+
{ first: 35, last: 42, drawingType: '3d', level: 'advanced' },
|
| 43 |
+
{ first: 43, last: 50, drawingType: '3d', level: 'project' },
|
| 44 |
+
]
|
| 45 |
+
|
| 46 |
+
function taskDimensions(taskId) {
|
| 47 |
+
const canonical = String(taskId).match(/^task-(\d{3})$/)
|
| 48 |
+
if (canonical) {
|
| 49 |
+
const number = Number(canonical[1])
|
| 50 |
+
const range = CANONICAL_TASK_RANGES.find(
|
| 51 |
+
(candidate) => number >= candidate.first && number <= candidate.last,
|
| 52 |
+
)
|
| 53 |
+
if (range) return { drawingType: range.drawingType, level: range.level }
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
// Preserve readability for imported result manifests created before the
|
| 57 |
+
// benchmark switched to opaque, globally consecutive IDs.
|
| 58 |
+
const drawingType = taskId.startsWith('3d-') ? '3d' : '2d'
|
| 59 |
+
const withoutDimension = taskId.replace(/^3d-/, '')
|
| 60 |
+
const level = withoutDimension.split('-')[0] || 'other'
|
| 61 |
+
return { drawingType, level }
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
function scoreStats(values) {
|
| 65 |
+
const usable = values.filter((value) => Number.isFinite(value))
|
| 66 |
+
return {
|
| 67 |
+
count: usable.length,
|
| 68 |
+
mean: mean(usable),
|
| 69 |
+
median: median(usable),
|
| 70 |
+
min: usable.length ? Math.min(...usable) : null,
|
| 71 |
+
max: usable.length ? Math.max(...usable) : null,
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
function histogram(values, buckets = 10) {
|
| 76 |
+
const counts = Array.from({ length: buckets }, () => 0)
|
| 77 |
+
for (const value of values) {
|
| 78 |
+
if (!Number.isFinite(value)) continue
|
| 79 |
+
const index = Math.max(0, Math.min(buckets - 1, Math.floor(value * buckets)))
|
| 80 |
+
counts[index] += 1
|
| 81 |
+
}
|
| 82 |
+
return counts
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
export function normalizeManifest(manifest, { manifestKey = MANIFEST_SUFFIX } = {}) {
|
| 86 |
+
const modelPrefix = manifestKey.slice(0, -MANIFEST_SUFFIX.length)
|
| 87 |
+
const modelId = String(manifest.model_id || modelPrefix.replace(/\/$/, '') || 'unknown')
|
| 88 |
+
const slug = modelPrefix ? slugify(modelPrefix.replace(/\/$/, '')) : slugify(modelId)
|
| 89 |
+
const tasks = (Array.isArray(manifest.tasks) ? manifest.tasks : []).map((task, index) => {
|
| 90 |
+
const taskId = String(task.task_id || `task-${index + 1}`)
|
| 91 |
+
const derivedDimensions = taskDimensions(taskId)
|
| 92 |
+
const drawingType = String(task.drawing_type || derivedDimensions.drawingType).toLowerCase() === '3d'
|
| 93 |
+
? '3d'
|
| 94 |
+
: '2d'
|
| 95 |
+
const level = String(task.level || derivedDimensions.level)
|
| 96 |
+
const executionStatus = String(
|
| 97 |
+
task.execution_status || task.execution_state || task.controller_state || 'unknown',
|
| 98 |
+
)
|
| 99 |
+
const scoreIsVision = !task.score_source || task.score_source === 'vision_judge'
|
| 100 |
+
return {
|
| 101 |
+
index,
|
| 102 |
+
taskId,
|
| 103 |
+
drawingType,
|
| 104 |
+
level,
|
| 105 |
+
category: `${drawingType}-${level}`,
|
| 106 |
+
included: task.included !== false,
|
| 107 |
+
executionStatus,
|
| 108 |
+
evaluationStatus: String(task.evaluation_status || task.evaluation_stage || 'unknown'),
|
| 109 |
+
selectedAttempt: String(task.selected_attempt || 'selected'),
|
| 110 |
+
selectedRunId: String(task.selected_run_id || ''),
|
| 111 |
+
destinationPrefix: String(task.destination_prefix || ''),
|
| 112 |
+
sourcePrefix: String(task.source_prefix || ''),
|
| 113 |
+
objectCount: asNumber(task.object_count) || 0,
|
| 114 |
+
bytes: asNumber(task.bytes) || 0,
|
| 115 |
+
deterministicScore: asNumber(task.deterministic_score),
|
| 116 |
+
deterministicValid: task.deterministic_valid !== false,
|
| 117 |
+
visionScore: asNumber(task.vision_score ?? (scoreIsVision ? task.score : null)),
|
| 118 |
+
visionVerdict: String(task.vision_verdict || task.outcome || 'unavailable'),
|
| 119 |
+
evaluatorVersion: String(task.evaluator_version || ''),
|
| 120 |
+
controllerClean: typeof task.controller_clean === 'boolean'
|
| 121 |
+
? task.controller_clean
|
| 122 |
+
: executionStatus === 'completed',
|
| 123 |
+
exclusionReason: task.exclusion_reason ? String(task.exclusion_reason) : null,
|
| 124 |
+
}
|
| 125 |
+
})
|
| 126 |
+
const included = tasks.filter((task) => task.included)
|
| 127 |
+
const categoryMap = new Map()
|
| 128 |
+
for (const task of included) {
|
| 129 |
+
if (!categoryMap.has(task.category)) categoryMap.set(task.category, [])
|
| 130 |
+
categoryMap.get(task.category).push(task)
|
| 131 |
+
}
|
| 132 |
+
const categories = [...categoryMap.entries()]
|
| 133 |
+
.map(([category, members]) => ({
|
| 134 |
+
category,
|
| 135 |
+
drawingType: members[0].drawingType,
|
| 136 |
+
level: members[0].level,
|
| 137 |
+
tasks: members.length,
|
| 138 |
+
deterministic: scoreStats(members.map((task) => task.deterministicScore)),
|
| 139 |
+
vision: scoreStats(members.map((task) => task.visionScore)),
|
| 140 |
+
}))
|
| 141 |
+
.sort((a, b) => a.category.localeCompare(b.category))
|
| 142 |
+
const deterministicValues = included.map((task) => task.deterministicScore)
|
| 143 |
+
const visionValues = included.map((task) => task.visionScore)
|
| 144 |
+
return {
|
| 145 |
+
slug,
|
| 146 |
+
manifestKey,
|
| 147 |
+
modelPrefix,
|
| 148 |
+
modelId,
|
| 149 |
+
aggregateId: String(manifest.aggregate_id || ''),
|
| 150 |
+
createdAt: manifest.created_at ? String(manifest.created_at) : null,
|
| 151 |
+
sourceBucket: String(manifest.source_bucket || ''),
|
| 152 |
+
destinationBucket: String(manifest.destination_bucket || ''),
|
| 153 |
+
evaluatorVersion: String(included.find((task) => task.evaluatorVersion)?.evaluatorVersion || ''),
|
| 154 |
+
tasks,
|
| 155 |
+
categories,
|
| 156 |
+
totals: {
|
| 157 |
+
expected: asNumber(manifest.summary?.expected_tasks) ?? tasks.length,
|
| 158 |
+
included: included.length,
|
| 159 |
+
excluded: tasks.length - included.length,
|
| 160 |
+
clean: included.filter((task) => task.controllerClean).length,
|
| 161 |
+
budgetExhausted: included.filter((task) => task.executionStatus === 'budget_exhausted').length,
|
| 162 |
+
bytes: included.reduce((sum, task) => sum + task.bytes, 0),
|
| 163 |
+
objects: included.reduce((sum, task) => sum + task.objectCount, 0),
|
| 164 |
+
},
|
| 165 |
+
scores: {
|
| 166 |
+
deterministic: scoreStats(deterministicValues),
|
| 167 |
+
vision: scoreStats(visionValues),
|
| 168 |
+
deterministicHistogram: histogram(deterministicValues),
|
| 169 |
+
visionHistogram: histogram(visionValues),
|
| 170 |
+
},
|
| 171 |
+
selectionPolicy: manifest.selection_policy || null,
|
| 172 |
+
sourceRuns: Array.isArray(manifest.source_runs) ? manifest.source_runs : [],
|
| 173 |
+
}
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
function parseJsonLines(text) {
|
| 177 |
+
const rows = []
|
| 178 |
+
for (const line of String(text || '').split(/\r?\n/)) {
|
| 179 |
+
if (!line.trim()) continue
|
| 180 |
+
try {
|
| 181 |
+
rows.push(JSON.parse(line))
|
| 182 |
+
} catch {
|
| 183 |
+
// A partially written final line must not hide the rest of the trace.
|
| 184 |
+
}
|
| 185 |
+
}
|
| 186 |
+
return rows
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
function imageSequence(value) {
|
| 190 |
+
const match = String(value || '').match(/frames\/frame_(\d+)\.png/)
|
| 191 |
+
return match ? Number(match[1]) : null
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
function compactReceipt(receipt) {
|
| 195 |
+
return {
|
| 196 |
+
actionIndex: asNumber(receipt?.action_index),
|
| 197 |
+
batchIndex: asNumber(receipt?.batch_index),
|
| 198 |
+
batchSize: asNumber(receipt?.batch_size),
|
| 199 |
+
toolUseId: receipt?.tool_use_id || null,
|
| 200 |
+
action: receipt?.action || null,
|
| 201 |
+
receipt: receipt?.receipt || null,
|
| 202 |
+
requestId: receipt?.request_id || null,
|
| 203 |
+
idempotentReplay: Boolean(receipt?.idempotent_replay),
|
| 204 |
+
}
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
async function mapConcurrent(items, concurrency, mapper) {
|
| 208 |
+
const results = new Array(items.length)
|
| 209 |
+
let next = 0
|
| 210 |
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
| 211 |
+
while (next < items.length) {
|
| 212 |
+
const index = next
|
| 213 |
+
next += 1
|
| 214 |
+
results[index] = await mapper(items[index], index)
|
| 215 |
+
}
|
| 216 |
+
}))
|
| 217 |
+
return results
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
function notFound(error) {
|
| 221 |
+
return error?.name === 'NoSuchKey'
|
| 222 |
+
|| error?.name === 'NotFound'
|
| 223 |
+
|| error?.$metadata?.httpStatusCode === 404
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
export class ResultsRepository {
|
| 227 |
+
constructor({
|
| 228 |
+
s3,
|
| 229 |
+
bucket,
|
| 230 |
+
signUrl,
|
| 231 |
+
cacheTtlMs = DEFAULT_CACHE_TTL_MS,
|
| 232 |
+
traceCacheEntries = DEFAULT_TRACE_CACHE_ENTRIES,
|
| 233 |
+
signedUrlTtlSeconds = 3600,
|
| 234 |
+
}) {
|
| 235 |
+
this.s3 = s3
|
| 236 |
+
this.bucket = bucket
|
| 237 |
+
this.signUrl = signUrl
|
| 238 |
+
this.cacheTtlMs = cacheTtlMs
|
| 239 |
+
this.traceCacheEntries = traceCacheEntries
|
| 240 |
+
this.signedUrlTtlSeconds = signedUrlTtlSeconds
|
| 241 |
+
this.cache = new Map()
|
| 242 |
+
this.traceCache = new Map()
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
async cached(key, loader, ttlMs = this.cacheTtlMs) {
|
| 246 |
+
const current = this.cache.get(key)
|
| 247 |
+
if (current && current.expiresAt > Date.now()) return current.value
|
| 248 |
+
const promise = Promise.resolve().then(loader)
|
| 249 |
+
this.cache.set(key, { expiresAt: Date.now() + ttlMs, value: promise })
|
| 250 |
+
try {
|
| 251 |
+
return await promise
|
| 252 |
+
} catch (error) {
|
| 253 |
+
this.cache.delete(key)
|
| 254 |
+
throw error
|
| 255 |
+
}
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
async text(key, { optional = false, ttlMs = this.cacheTtlMs } = {}) {
|
| 259 |
+
return this.cached(`object:${key}`, async () => {
|
| 260 |
+
try {
|
| 261 |
+
const response = await this.s3.send(new GetObjectCommand({
|
| 262 |
+
Bucket: this.bucket,
|
| 263 |
+
Key: key,
|
| 264 |
+
}))
|
| 265 |
+
if (!response.Body) return ''
|
| 266 |
+
if (typeof response.Body.transformToString === 'function') {
|
| 267 |
+
return response.Body.transformToString()
|
| 268 |
+
}
|
| 269 |
+
return String(response.Body)
|
| 270 |
+
} catch (error) {
|
| 271 |
+
if (optional && notFound(error)) return null
|
| 272 |
+
throw error
|
| 273 |
+
}
|
| 274 |
+
}, ttlMs)
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
async json(key, options = {}) {
|
| 278 |
+
const value = await this.text(key, options)
|
| 279 |
+
if (value == null || value === '') return null
|
| 280 |
+
return JSON.parse(value)
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
async discoverModels({ force = false } = {}) {
|
| 284 |
+
if (force) this.cache.delete('models')
|
| 285 |
+
return this.cached('models', async () => {
|
| 286 |
+
const root = await this.s3.send(new ListObjectsV2Command({
|
| 287 |
+
Bucket: this.bucket,
|
| 288 |
+
Delimiter: '/',
|
| 289 |
+
MaxKeys: 1000,
|
| 290 |
+
}))
|
| 291 |
+
const prefixes = (root.CommonPrefixes || [])
|
| 292 |
+
.map((entry) => String(entry.Prefix || ''))
|
| 293 |
+
.filter((prefix) => prefix && !['aggregate/', 'artifacts/', 'batch/'].includes(prefix))
|
| 294 |
+
const candidates = [MANIFEST_SUFFIX, ...prefixes.map((prefix) => `${prefix}${MANIFEST_SUFFIX}`)]
|
| 295 |
+
const manifests = await Promise.all(candidates.map(async (manifestKey) => {
|
| 296 |
+
try {
|
| 297 |
+
const manifest = await this.json(manifestKey)
|
| 298 |
+
return manifest ? normalizeManifest(manifest, { manifestKey }) : null
|
| 299 |
+
} catch (error) {
|
| 300 |
+
if (notFound(error)) return null
|
| 301 |
+
throw error
|
| 302 |
+
}
|
| 303 |
+
}))
|
| 304 |
+
return manifests.filter(Boolean).sort((a, b) => a.modelId.localeCompare(b.modelId))
|
| 305 |
+
}, 60_000)
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
async model(slug) {
|
| 309 |
+
const models = await this.discoverModels()
|
| 310 |
+
return models.find((model) => model.slug === slug) || null
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
async task(slug, taskId) {
|
| 314 |
+
const model = await this.model(slug)
|
| 315 |
+
if (!model) return { model: null, task: null }
|
| 316 |
+
return { model, task: model.tasks.find((candidate) => candidate.taskId === taskId) || null }
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
taskKey(task, relativePath) {
|
| 320 |
+
const clean = String(relativePath || '').replace(/^\/+/, '')
|
| 321 |
+
if (!clean || clean.includes('\\') || clean.split('/').some((part) => part === '..' || part === '.')) {
|
| 322 |
+
throw new Error('Unsafe task artifact path')
|
| 323 |
+
}
|
| 324 |
+
if (!task.destinationPrefix) throw new Error('Task has no finalized artifact prefix')
|
| 325 |
+
return `${task.destinationPrefix}${clean}`
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
async taskJson(task, relativePath, { optional = true } = {}) {
|
| 329 |
+
return this.json(this.taskKey(task, relativePath), { optional })
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
async modelMetrics(slug) {
|
| 333 |
+
const model = await this.model(slug)
|
| 334 |
+
if (!model) return null
|
| 335 |
+
const included = model.tasks.filter((task) => task.included && task.destinationPrefix)
|
| 336 |
+
const taskMetrics = await mapConcurrent(included, 12, async (task) => {
|
| 337 |
+
const [result, state, evaluationScore, evaluationStatus] = await Promise.all([
|
| 338 |
+
this.taskJson(task, 'result.json'),
|
| 339 |
+
this.taskJson(task, 'run-state.json'),
|
| 340 |
+
this.taskJson(task, 'evaluation/score.json'),
|
| 341 |
+
this.taskJson(task, 'evaluation/status.json'),
|
| 342 |
+
])
|
| 343 |
+
const usage = result?.usage || state?.usage || {}
|
| 344 |
+
const inputTokens = asNumber(usage.input_tokens)
|
| 345 |
+
const cachedInputTokens = asNumber(usage.cached_input_tokens ?? usage.cache_read_input_tokens)
|
| 346 |
+
const cacheCreationTokens = asNumber(usage.cache_creation_input_tokens)
|
| 347 |
+
const outputTokens = asNumber(usage.output_tokens)
|
| 348 |
+
const reportedTotalTokens = asNumber(usage.total_tokens)
|
| 349 |
+
return {
|
| 350 |
+
taskId: task.taskId,
|
| 351 |
+
drawingType: task.drawingType,
|
| 352 |
+
level: task.level,
|
| 353 |
+
acceptedActions: asNumber(result?.accepted_actions ?? state?.accepted_actions),
|
| 354 |
+
elapsedSeconds: asNumber(result?.elapsed_s),
|
| 355 |
+
rolloutSeconds: asNumber(result?.rollout_elapsed_s),
|
| 356 |
+
turns: asNumber(state?.turn_count),
|
| 357 |
+
screenshots: asNumber(result?.screenshots ?? state?.screenshots),
|
| 358 |
+
inputTokens,
|
| 359 |
+
cachedInputTokens,
|
| 360 |
+
cacheCreationTokens,
|
| 361 |
+
outputTokens,
|
| 362 |
+
reasoningTokens: asNumber(usage.reasoning_output_tokens),
|
| 363 |
+
totalTokens: reportedTotalTokens
|
| 364 |
+
?? [inputTokens, cachedInputTokens, cacheCreationTokens, outputTokens]
|
| 365 |
+
.reduce((total, value) => total + (value || 0), 0),
|
| 366 |
+
artifactBytes: asNumber(result?.artifact_bytes),
|
| 367 |
+
stopReason: String(result?.stop_reason || state?.stop_reason || ''),
|
| 368 |
+
deterministicScore: task.deterministicScore ?? asNumber(evaluationScore?.score),
|
| 369 |
+
visionScore: task.visionScore
|
| 370 |
+
?? asNumber(evaluationStatus?.vision_score ?? result?.evaluation?.vision_score),
|
| 371 |
+
visionVerdict: task.visionVerdict !== 'unavailable'
|
| 372 |
+
? task.visionVerdict
|
| 373 |
+
: String(evaluationStatus?.vision_verdict || result?.evaluation?.vision_verdict || 'unavailable'),
|
| 374 |
+
}
|
| 375 |
+
})
|
| 376 |
+
const sum = (key) => taskMetrics.reduce((total, task) => total + (task[key] || 0), 0)
|
| 377 |
+
const categoryMap = new Map()
|
| 378 |
+
for (const task of taskMetrics) {
|
| 379 |
+
const category = `${task.drawingType}-${task.level}`
|
| 380 |
+
if (!categoryMap.has(category)) categoryMap.set(category, [])
|
| 381 |
+
categoryMap.get(category).push(task)
|
| 382 |
+
}
|
| 383 |
+
const deterministicValues = taskMetrics.map((task) => task.deterministicScore)
|
| 384 |
+
const visionValues = taskMetrics.map((task) => task.visionScore)
|
| 385 |
+
return {
|
| 386 |
+
tasks: taskMetrics,
|
| 387 |
+
totals: {
|
| 388 |
+
acceptedActions: sum('acceptedActions'),
|
| 389 |
+
elapsedSeconds: sum('elapsedSeconds'),
|
| 390 |
+
turns: sum('turns'),
|
| 391 |
+
screenshots: sum('screenshots'),
|
| 392 |
+
inputTokens: sum('inputTokens'),
|
| 393 |
+
cachedInputTokens: sum('cachedInputTokens'),
|
| 394 |
+
cacheCreationTokens: sum('cacheCreationTokens'),
|
| 395 |
+
outputTokens: sum('outputTokens'),
|
| 396 |
+
reasoningTokens: sum('reasoningTokens'),
|
| 397 |
+
totalTokens: sum('totalTokens'),
|
| 398 |
+
},
|
| 399 |
+
medians: {
|
| 400 |
+
acceptedActions: median(taskMetrics.map((task) => task.acceptedActions)),
|
| 401 |
+
elapsedSeconds: median(taskMetrics.map((task) => task.elapsedSeconds)),
|
| 402 |
+
turns: median(taskMetrics.map((task) => task.turns)),
|
| 403 |
+
},
|
| 404 |
+
scores: {
|
| 405 |
+
deterministic: scoreStats(deterministicValues),
|
| 406 |
+
vision: scoreStats(visionValues),
|
| 407 |
+
deterministicHistogram: histogram(deterministicValues),
|
| 408 |
+
visionHistogram: histogram(visionValues),
|
| 409 |
+
},
|
| 410 |
+
categories: [...categoryMap.entries()].map(([category, members]) => ({
|
| 411 |
+
category,
|
| 412 |
+
drawingType: members[0].drawingType,
|
| 413 |
+
level: members[0].level,
|
| 414 |
+
tasks: members.length,
|
| 415 |
+
deterministic: scoreStats(members.map((task) => task.deterministicScore)),
|
| 416 |
+
vision: scoreStats(members.map((task) => task.visionScore)),
|
| 417 |
+
})).sort((a, b) => a.category.localeCompare(b.category)),
|
| 418 |
+
}
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
async taskDetail(slug, taskId) {
|
| 422 |
+
const { model, task } = await this.task(slug, taskId)
|
| 423 |
+
if (!model || !task) return null
|
| 424 |
+
const [result, runState, evaluationScore, evaluationStatus, visionScore] = await Promise.all([
|
| 425 |
+
this.taskJson(task, 'result.json'),
|
| 426 |
+
this.taskJson(task, 'run-state.json'),
|
| 427 |
+
this.taskJson(task, 'evaluation/score.json'),
|
| 428 |
+
this.taskJson(task, 'evaluation/status.json'),
|
| 429 |
+
this.taskJson(task, 'evaluation/vision-judge/score.json'),
|
| 430 |
+
])
|
| 431 |
+
return {
|
| 432 |
+
model: {
|
| 433 |
+
slug: model.slug,
|
| 434 |
+
modelId: model.modelId,
|
| 435 |
+
aggregateId: model.aggregateId,
|
| 436 |
+
},
|
| 437 |
+
task,
|
| 438 |
+
result,
|
| 439 |
+
runState,
|
| 440 |
+
evaluationScore,
|
| 441 |
+
evaluationStatus,
|
| 442 |
+
visionScore,
|
| 443 |
+
frameCount: asNumber(result?.screenshots ?? runState?.screenshots) || 0,
|
| 444 |
+
turnCount: asNumber(runState?.turn_count) || 0,
|
| 445 |
+
media: {
|
| 446 |
+
reference: 'reference.png',
|
| 447 |
+
final: 'evaluation/render.png',
|
| 448 |
+
diff: 'evaluation/diff.png',
|
| 449 |
+
dwg: 'attempt.dwg',
|
| 450 |
+
},
|
| 451 |
+
downloads: [
|
| 452 |
+
['Final DWG', 'attempt.dwg'],
|
| 453 |
+
['Result', 'result.json'],
|
| 454 |
+
['Run state', 'run-state.json'],
|
| 455 |
+
['Model turns', 'model_turns.jsonl'],
|
| 456 |
+
['Turn summaries', 'model_turn_summaries.jsonl'],
|
| 457 |
+
['Action receipts', 'input_receipts.jsonl'],
|
| 458 |
+
['Events', 'events.json'],
|
| 459 |
+
['Frame events', 'frame_events.json'],
|
| 460 |
+
['Evaluator score', 'evaluation/score.json'],
|
| 461 |
+
['Evaluator metadata', 'evaluation/metadata.json'],
|
| 462 |
+
].map(([label, path]) => ({ label, path })),
|
| 463 |
+
}
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
async traceBundle(task) {
|
| 467 |
+
const key = task.destinationPrefix
|
| 468 |
+
if (this.traceCache.has(key)) {
|
| 469 |
+
const cached = this.traceCache.get(key)
|
| 470 |
+
this.traceCache.delete(key)
|
| 471 |
+
this.traceCache.set(key, cached)
|
| 472 |
+
return cached
|
| 473 |
+
}
|
| 474 |
+
const bundle = Promise.all([
|
| 475 |
+
this.text(this.taskKey(task, 'model_turn_summaries.jsonl'), { optional: true }),
|
| 476 |
+
this.text(this.taskKey(task, 'input_receipts.jsonl'), { optional: true }),
|
| 477 |
+
]).then(async ([turnText, receiptText]) => {
|
| 478 |
+
const completeTurnText = turnText || await this.text(
|
| 479 |
+
this.taskKey(task, 'model_turns.jsonl'),
|
| 480 |
+
{ optional: true },
|
| 481 |
+
)
|
| 482 |
+
return {
|
| 483 |
+
turns: parseJsonLines(completeTurnText),
|
| 484 |
+
receipts: parseJsonLines(receiptText),
|
| 485 |
+
}
|
| 486 |
+
})
|
| 487 |
+
this.traceCache.set(key, bundle)
|
| 488 |
+
while (this.traceCache.size > this.traceCacheEntries) {
|
| 489 |
+
this.traceCache.delete(this.traceCache.keys().next().value)
|
| 490 |
+
}
|
| 491 |
+
try {
|
| 492 |
+
return await bundle
|
| 493 |
+
} catch (error) {
|
| 494 |
+
this.traceCache.delete(key)
|
| 495 |
+
throw error
|
| 496 |
+
}
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
async traceIndex(slug, taskId) {
|
| 500 |
+
const { model, task } = await this.task(slug, taskId)
|
| 501 |
+
if (!model || !task) return null
|
| 502 |
+
const bundle = await this.traceBundle(task)
|
| 503 |
+
const receiptsByTool = new Map()
|
| 504 |
+
for (const receipt of bundle.receipts) {
|
| 505 |
+
const key = String(receipt.tool_use_id || '')
|
| 506 |
+
if (!receiptsByTool.has(key)) receiptsByTool.set(key, [])
|
| 507 |
+
receiptsByTool.get(key).push(receipt)
|
| 508 |
+
}
|
| 509 |
+
const turns = bundle.turns.map((turn, index) => {
|
| 510 |
+
const toolUseId = String(turn.tool_use_id || '')
|
| 511 |
+
const receipts = receiptsByTool.get(toolUseId) || []
|
| 512 |
+
const actionIndices = receipts.map((receipt) => asNumber(receipt.action_index)).filter(Number.isFinite)
|
| 513 |
+
const artifacts = Array.isArray(turn.model_input?.artifacts) ? turn.model_input.artifacts : []
|
| 514 |
+
const inputArtifact = artifacts.find((artifact) => imageSequence(artifact?.artifact_path) != null)
|
| 515 |
+
const declaredActions = Array.isArray(turn.action?.actions)
|
| 516 |
+
? turn.action.actions
|
| 517 |
+
: (turn.action ? [turn.action] : [])
|
| 518 |
+
return {
|
| 519 |
+
index,
|
| 520 |
+
turn: asNumber(turn.turn) ?? index + 1,
|
| 521 |
+
toolUseId,
|
| 522 |
+
latencyMs: asNumber(turn.latency_ms),
|
| 523 |
+
stopReason: String(turn.stop_reason || ''),
|
| 524 |
+
requestAttempts: asNumber(turn.request_attempts),
|
| 525 |
+
validationRetries: asNumber(turn.response_validation_retries),
|
| 526 |
+
discardedToolUses: asNumber(turn.discarded_tool_uses),
|
| 527 |
+
usage: turn.usage || null,
|
| 528 |
+
text: turn.text || null,
|
| 529 |
+
actions: declaredActions,
|
| 530 |
+
actionCount: receipts.length || declaredActions.length,
|
| 531 |
+
inputFrameSequence: imageSequence(inputArtifact?.artifact_path),
|
| 532 |
+
outputFrameSequence: actionIndices.length ? Math.max(...actionIndices) : null,
|
| 533 |
+
firstActionIndex: actionIndices.length ? Math.min(...actionIndices) : null,
|
| 534 |
+
lastActionIndex: actionIndices.length ? Math.max(...actionIndices) : null,
|
| 535 |
+
}
|
| 536 |
+
})
|
| 537 |
+
return { taskId, turns, receiptCount: bundle.receipts.length }
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
async turnDetail(slug, taskId, turnNumber) {
|
| 541 |
+
const { task } = await this.task(slug, taskId)
|
| 542 |
+
if (!task) return null
|
| 543 |
+
const bundle = await this.traceBundle(task)
|
| 544 |
+
const index = Math.max(0, Number(turnNumber) - 1)
|
| 545 |
+
const turn = bundle.turns.find((candidate) => Number(candidate.turn) === Number(turnNumber))
|
| 546 |
+
|| bundle.turns[index]
|
| 547 |
+
if (!turn) return null
|
| 548 |
+
const toolUseId = String(turn.tool_use_id || '')
|
| 549 |
+
const receipts = bundle.receipts
|
| 550 |
+
.filter((receipt) => String(receipt.tool_use_id || '') === toolUseId)
|
| 551 |
+
.map(compactReceipt)
|
| 552 |
+
return { turn, receipts }
|
| 553 |
+
}
|
| 554 |
+
|
| 555 |
+
async framePage(slug, taskId, { offset = 0, limit = 120 } = {}) {
|
| 556 |
+
const detail = await this.taskDetail(slug, taskId)
|
| 557 |
+
if (!detail) return null
|
| 558 |
+
const safeOffset = Math.max(0, Math.floor(Number(offset) || 0))
|
| 559 |
+
const safeLimit = Math.max(1, Math.min(240, Math.floor(Number(limit) || 120)))
|
| 560 |
+
const end = Math.min(detail.frameCount, safeOffset + safeLimit)
|
| 561 |
+
return {
|
| 562 |
+
offset: safeOffset,
|
| 563 |
+
limit: safeLimit,
|
| 564 |
+
total: detail.frameCount,
|
| 565 |
+
hasMore: end < detail.frameCount,
|
| 566 |
+
frames: Array.from({ length: Math.max(0, end - safeOffset) }, (_, index) => {
|
| 567 |
+
const sequence = safeOffset + index
|
| 568 |
+
return {
|
| 569 |
+
sequence,
|
| 570 |
+
name: `frame_${String(sequence).padStart(4, '0')}.png`,
|
| 571 |
+
path: `frames/frame_${String(sequence).padStart(4, '0')}.png`,
|
| 572 |
+
}
|
| 573 |
+
}),
|
| 574 |
+
}
|
| 575 |
+
}
|
| 576 |
+
|
| 577 |
+
async mediaUrl(slug, taskId, relativePath, { download = false } = {}) {
|
| 578 |
+
const { task } = await this.task(slug, taskId)
|
| 579 |
+
if (!task) return null
|
| 580 |
+
const key = this.taskKey(task, relativePath)
|
| 581 |
+
const extension = relativePath.split('.').at(-1)?.toLowerCase()
|
| 582 |
+
const contentType = {
|
| 583 |
+
png: 'image/png',
|
| 584 |
+
json: 'application/json',
|
| 585 |
+
jsonl: 'application/x-ndjson',
|
| 586 |
+
dwg: 'application/acad',
|
| 587 |
+
}[extension] || 'application/octet-stream'
|
| 588 |
+
const command = new GetObjectCommand({
|
| 589 |
+
Bucket: this.bucket,
|
| 590 |
+
Key: key,
|
| 591 |
+
ResponseContentType: contentType,
|
| 592 |
+
ResponseCacheControl: relativePath.endsWith('.png')
|
| 593 |
+
? 'private, max-age=31536000, immutable'
|
| 594 |
+
: 'private, max-age=3600',
|
| 595 |
+
...(download ? {
|
| 596 |
+
ResponseContentDisposition: `attachment; filename="${relativePath.split('/').at(-1)}"`,
|
| 597 |
+
} : {}),
|
| 598 |
+
})
|
| 599 |
+
const cacheLifetime = Math.max(
|
| 600 |
+
60_000,
|
| 601 |
+
Math.min(10 * 60_000, (this.signedUrlTtlSeconds - 60) * 1000),
|
| 602 |
+
)
|
| 603 |
+
return this.cached(
|
| 604 |
+
`signed:${key}:${download ? 'download' : 'inline'}`,
|
| 605 |
+
() => this.signUrl(command, { expiresIn: this.signedUrlTtlSeconds }),
|
| 606 |
+
cacheLifetime,
|
| 607 |
+
)
|
| 608 |
+
}
|
| 609 |
+
}
|
| 610 |
+
|
| 611 |
+
function sendJson(response, status, body, cacheControl = 'private, no-store') {
|
| 612 |
+
const payload = JSON.stringify(body)
|
| 613 |
+
response.statusCode = status
|
| 614 |
+
response.setHeader('Content-Type', 'application/json; charset=utf-8')
|
| 615 |
+
response.setHeader('Content-Length', Buffer.byteLength(payload))
|
| 616 |
+
response.setHeader('Cache-Control', cacheControl)
|
| 617 |
+
response.end(payload)
|
| 618 |
+
}
|
| 619 |
+
|
| 620 |
+
function decode(value) {
|
| 621 |
+
try {
|
| 622 |
+
return decodeURIComponent(value)
|
| 623 |
+
} catch {
|
| 624 |
+
return ''
|
| 625 |
+
}
|
| 626 |
+
}
|
| 627 |
+
|
| 628 |
+
export function createResultsApi({ repository }) {
|
| 629 |
+
return async (request, response) => {
|
| 630 |
+
const url = new URL(request.url || '/', 'http://results.local')
|
| 631 |
+
if (!url.pathname.startsWith('/api/results')) return false
|
| 632 |
+
try {
|
| 633 |
+
if (url.pathname === '/api/results/health') {
|
| 634 |
+
sendJson(response, 200, { ok: true, bucket: repository.bucket })
|
| 635 |
+
return true
|
| 636 |
+
}
|
| 637 |
+
if (url.pathname === '/api/results/models') {
|
| 638 |
+
const models = await repository.discoverModels({ force: url.searchParams.get('refresh') === '1' })
|
| 639 |
+
sendJson(response, 200, { models }, 'private, max-age=30')
|
| 640 |
+
return true
|
| 641 |
+
}
|
| 642 |
+
const modelMatch = url.pathname.match(/^\/api\/results\/models\/([^/]+)$/)
|
| 643 |
+
if (modelMatch) {
|
| 644 |
+
const model = await repository.model(decode(modelMatch[1]))
|
| 645 |
+
sendJson(response, model ? 200 : 404, model || { error: 'model_not_found' })
|
| 646 |
+
return true
|
| 647 |
+
}
|
| 648 |
+
const metricsMatch = url.pathname.match(/^\/api\/results\/models\/([^/]+)\/metrics$/)
|
| 649 |
+
if (metricsMatch) {
|
| 650 |
+
const metrics = await repository.modelMetrics(decode(metricsMatch[1]))
|
| 651 |
+
sendJson(response, metrics ? 200 : 404, metrics || { error: 'model_not_found' })
|
| 652 |
+
return true
|
| 653 |
+
}
|
| 654 |
+
const taskMatch = url.pathname.match(/^\/api\/results\/models\/([^/]+)\/tasks\/([^/]+)$/)
|
| 655 |
+
if (taskMatch) {
|
| 656 |
+
const detail = await repository.taskDetail(decode(taskMatch[1]), decode(taskMatch[2]))
|
| 657 |
+
sendJson(response, detail ? 200 : 404, detail || { error: 'task_not_found' })
|
| 658 |
+
return true
|
| 659 |
+
}
|
| 660 |
+
const traceMatch = url.pathname.match(/^\/api\/results\/models\/([^/]+)\/tasks\/([^/]+)\/trace$/)
|
| 661 |
+
if (traceMatch) {
|
| 662 |
+
const trace = await repository.traceIndex(decode(traceMatch[1]), decode(traceMatch[2]))
|
| 663 |
+
sendJson(response, trace ? 200 : 404, trace || { error: 'trace_not_found' })
|
| 664 |
+
return true
|
| 665 |
+
}
|
| 666 |
+
const turnMatch = url.pathname.match(/^\/api\/results\/models\/([^/]+)\/tasks\/([^/]+)\/turns\/(\d+)$/)
|
| 667 |
+
if (turnMatch) {
|
| 668 |
+
const turn = await repository.turnDetail(
|
| 669 |
+
decode(turnMatch[1]),
|
| 670 |
+
decode(turnMatch[2]),
|
| 671 |
+
Number(turnMatch[3]),
|
| 672 |
+
)
|
| 673 |
+
sendJson(response, turn ? 200 : 404, turn || { error: 'turn_not_found' })
|
| 674 |
+
return true
|
| 675 |
+
}
|
| 676 |
+
const framesMatch = url.pathname.match(/^\/api\/results\/models\/([^/]+)\/tasks\/([^/]+)\/frames$/)
|
| 677 |
+
if (framesMatch) {
|
| 678 |
+
const frames = await repository.framePage(decode(framesMatch[1]), decode(framesMatch[2]), {
|
| 679 |
+
offset: url.searchParams.get('offset'),
|
| 680 |
+
limit: url.searchParams.get('limit'),
|
| 681 |
+
})
|
| 682 |
+
sendJson(response, frames ? 200 : 404, frames || { error: 'task_not_found' }, 'private, max-age=300')
|
| 683 |
+
return true
|
| 684 |
+
}
|
| 685 |
+
const mediaMatch = url.pathname.match(/^\/api\/results\/models\/([^/]+)\/tasks\/([^/]+)\/media$/)
|
| 686 |
+
if (mediaMatch) {
|
| 687 |
+
const relativePath = url.searchParams.get('path') || ''
|
| 688 |
+
const signedUrl = await repository.mediaUrl(
|
| 689 |
+
decode(mediaMatch[1]),
|
| 690 |
+
decode(mediaMatch[2]),
|
| 691 |
+
relativePath,
|
| 692 |
+
{ download: url.searchParams.get('download') === '1' },
|
| 693 |
+
)
|
| 694 |
+
if (!signedUrl) {
|
| 695 |
+
sendJson(response, 404, { error: 'task_not_found' })
|
| 696 |
+
return true
|
| 697 |
+
}
|
| 698 |
+
response.statusCode = 307
|
| 699 |
+
response.setHeader('Location', signedUrl)
|
| 700 |
+
response.setHeader('Cache-Control', 'private, max-age=300, stale-while-revalidate=60')
|
| 701 |
+
response.end()
|
| 702 |
+
return true
|
| 703 |
+
}
|
| 704 |
+
sendJson(response, 404, { error: 'route_not_found' })
|
| 705 |
+
return true
|
| 706 |
+
} catch (error) {
|
| 707 |
+
sendJson(response, 500, {
|
| 708 |
+
error: 'results_api_failed',
|
| 709 |
+
message: error instanceof Error ? error.message : String(error),
|
| 710 |
+
})
|
| 711 |
+
return true
|
| 712 |
+
}
|
| 713 |
+
}
|
| 714 |
+
}
|
apps/results-dashboard/server.mjs
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fs from 'node:fs'
|
| 2 |
+
import http from 'node:http'
|
| 3 |
+
import path from 'node:path'
|
| 4 |
+
import { fileURLToPath } from 'node:url'
|
| 5 |
+
import { S3Client } from '@aws-sdk/client-s3'
|
| 6 |
+
import { fromIni } from '@aws-sdk/credential-providers'
|
| 7 |
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
|
| 8 |
+
import { createResultsApi, ResultsRepository } from './results-api.mjs'
|
| 9 |
+
|
| 10 |
+
const root = path.dirname(fileURLToPath(import.meta.url))
|
| 11 |
+
const dist = path.join(root, 'dist')
|
| 12 |
+
const host = process.env.HOST || '127.0.0.1'
|
| 13 |
+
const port = Number(process.env.PORT || 4273)
|
| 14 |
+
const region = process.env.AWS_REGION || ''
|
| 15 |
+
const profile = process.env.AWS_PROFILE || ''
|
| 16 |
+
const bucket = process.env.AUTOCAD_RESULTS_BUCKET || ''
|
| 17 |
+
if (!bucket) {
|
| 18 |
+
throw new Error('AUTOCAD_RESULTS_BUCKET is required')
|
| 19 |
+
}
|
| 20 |
+
const signedUrlTtlSeconds = Math.max(
|
| 21 |
+
300,
|
| 22 |
+
Math.min(604800, Number(process.env.AUTOCAD_RESULTS_SIGNED_URL_TTL_SECONDS || 3600)),
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
const s3 = new S3Client({
|
| 26 |
+
...(region ? { region } : {}),
|
| 27 |
+
...(profile ? { credentials: fromIni({ profile }) } : {}),
|
| 28 |
+
maxAttempts: 5,
|
| 29 |
+
})
|
| 30 |
+
const repository = new ResultsRepository({
|
| 31 |
+
s3,
|
| 32 |
+
bucket,
|
| 33 |
+
signedUrlTtlSeconds,
|
| 34 |
+
signUrl: (command, options) => getSignedUrl(s3, command, options),
|
| 35 |
+
})
|
| 36 |
+
const api = createResultsApi({ repository })
|
| 37 |
+
|
| 38 |
+
const mime = {
|
| 39 |
+
'.css': 'text/css; charset=utf-8',
|
| 40 |
+
'.html': 'text/html; charset=utf-8',
|
| 41 |
+
'.js': 'text/javascript; charset=utf-8',
|
| 42 |
+
'.json': 'application/json; charset=utf-8',
|
| 43 |
+
'.map': 'application/json; charset=utf-8',
|
| 44 |
+
'.png': 'image/png',
|
| 45 |
+
'.woff2': 'font/woff2',
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
http.createServer(async (request, response) => {
|
| 49 |
+
if (await api(request, response)) return
|
| 50 |
+
const pathname = decodeURIComponent(new URL(request.url || '/', `http://${host}`).pathname)
|
| 51 |
+
const requested = path.resolve(dist, pathname.replace(/^\/+/, ''))
|
| 52 |
+
const safe = requested.startsWith(`${dist}${path.sep}`) ? requested : null
|
| 53 |
+
const file = safe && fs.existsSync(safe) && fs.statSync(safe).isFile()
|
| 54 |
+
? safe
|
| 55 |
+
: path.join(dist, 'index.html')
|
| 56 |
+
if (!fs.existsSync(file)) {
|
| 57 |
+
response.statusCode = 503
|
| 58 |
+
response.setHeader('Content-Type', 'text/plain; charset=utf-8')
|
| 59 |
+
response.end('Dashboard build not found. Run npm run build first.')
|
| 60 |
+
return
|
| 61 |
+
}
|
| 62 |
+
response.statusCode = 200
|
| 63 |
+
response.setHeader('Content-Type', mime[path.extname(file)] || 'application/octet-stream')
|
| 64 |
+
response.setHeader(
|
| 65 |
+
'Cache-Control',
|
| 66 |
+
path.basename(file) === 'index.html' ? 'no-cache' : 'public, max-age=31536000, immutable',
|
| 67 |
+
)
|
| 68 |
+
fs.createReadStream(file).pipe(response)
|
| 69 |
+
}).listen(port, host, () => {
|
| 70 |
+
process.stdout.write(`AutoCAD Results: http://${host}:${port}\n`)
|
| 71 |
+
})
|
apps/results-dashboard/src/App.vue
ADDED
|
@@ -0,0 +1,831 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<script setup lang="ts">
|
| 2 |
+
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
| 3 |
+
import ReliableImage from './components/ReliableImage.vue'
|
| 4 |
+
import type {
|
| 5 |
+
Frame,
|
| 6 |
+
FramePage,
|
| 7 |
+
ModelMetrics,
|
| 8 |
+
ModelResult,
|
| 9 |
+
ResultTask,
|
| 10 |
+
TaskDetail,
|
| 11 |
+
TraceIndex,
|
| 12 |
+
TraceTurn,
|
| 13 |
+
TurnDetail,
|
| 14 |
+
} from './types'
|
| 15 |
+
|
| 16 |
+
type View = 'overview' | 'tasks' | 'trace' | 'frames'
|
| 17 |
+
type ExpandedImage = { src: string; label: string }
|
| 18 |
+
|
| 19 |
+
const models = ref<ModelResult[]>([])
|
| 20 |
+
const selectedModelSlug = ref('')
|
| 21 |
+
const selectedTaskId = ref('')
|
| 22 |
+
const view = ref<View>('overview')
|
| 23 |
+
const metrics = ref<ModelMetrics | null>(null)
|
| 24 |
+
const taskDetail = ref<TaskDetail | null>(null)
|
| 25 |
+
const trace = ref<TraceIndex | null>(null)
|
| 26 |
+
const selectedTurnIndex = ref(0)
|
| 27 |
+
const turnDetail = ref<TurnDetail | null>(null)
|
| 28 |
+
const frames = ref<Frame[]>([])
|
| 29 |
+
const frameTotal = ref(0)
|
| 30 |
+
const frameHasMore = ref(false)
|
| 31 |
+
|
| 32 |
+
const loadingModels = ref(true)
|
| 33 |
+
const loadingMetrics = ref(false)
|
| 34 |
+
const loadingTask = ref(false)
|
| 35 |
+
const loadingTurn = ref(false)
|
| 36 |
+
const loadingFrames = ref(false)
|
| 37 |
+
const error = ref('')
|
| 38 |
+
const expandedImage = ref<ExpandedImage | null>(null)
|
| 39 |
+
const taskSearch = ref('')
|
| 40 |
+
const taskDimension = ref('all')
|
| 41 |
+
const taskLevel = ref('all')
|
| 42 |
+
const taskStatus = ref('all')
|
| 43 |
+
const taskSort = ref('task')
|
| 44 |
+
|
| 45 |
+
let taskLoadVersion = 0
|
| 46 |
+
let turnLoadVersion = 0
|
| 47 |
+
|
| 48 |
+
async function fetchJson<T>(url: string, timeoutMs = 30_000): Promise<T> {
|
| 49 |
+
let lastError: unknown
|
| 50 |
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
| 51 |
+
const controller = new AbortController()
|
| 52 |
+
const timeout = window.setTimeout(() => controller.abort(), timeoutMs)
|
| 53 |
+
try {
|
| 54 |
+
const response = await fetch(url, { cache: 'no-store', signal: controller.signal })
|
| 55 |
+
if (!response.ok) throw new Error(`Request failed (HTTP ${response.status})`)
|
| 56 |
+
return await response.json() as T
|
| 57 |
+
} catch (reason) {
|
| 58 |
+
lastError = reason
|
| 59 |
+
if (attempt < 2) await new Promise((resolve) => window.setTimeout(resolve, 250 * 2 ** attempt))
|
| 60 |
+
} finally {
|
| 61 |
+
window.clearTimeout(timeout)
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
throw lastError instanceof Error ? lastError : new Error(String(lastError))
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
const selectedModel = computed(() =>
|
| 68 |
+
models.value.find((model) => model.slug === selectedModelSlug.value) || null,
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
const selectedTask = computed(() =>
|
| 72 |
+
selectedModel.value?.tasks.find((task) => task.taskId === selectedTaskId.value) || null,
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
const selectedTurn = computed<TraceTurn | null>(() =>
|
| 76 |
+
trace.value?.turns[selectedTurnIndex.value] || null,
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
const metricByTask = computed(() => new Map(
|
| 80 |
+
(metrics.value?.tasks || []).map((metric) => [metric.taskId, metric]),
|
| 81 |
+
))
|
| 82 |
+
|
| 83 |
+
const activeScores = computed(() => metrics.value?.scores || selectedModel.value?.scores || null)
|
| 84 |
+
const activeCategories = computed(() => metrics.value?.categories || selectedModel.value?.categories || [])
|
| 85 |
+
|
| 86 |
+
const selectedVisionScore = computed(() => {
|
| 87 |
+
if (!selectedTask.value) return null
|
| 88 |
+
return metricByTask.value.get(selectedTask.value.taskId)?.visionScore
|
| 89 |
+
?? selectedTask.value.visionScore
|
| 90 |
+
?? numberField(taskDetail.value?.evaluationStatus || null, 'vision_score')
|
| 91 |
+
})
|
| 92 |
+
|
| 93 |
+
const selectedDeterministicScore = computed(() => {
|
| 94 |
+
if (!selectedTask.value) return null
|
| 95 |
+
return metricByTask.value.get(selectedTask.value.taskId)?.deterministicScore
|
| 96 |
+
?? selectedTask.value.deterministicScore
|
| 97 |
+
?? numberField(taskDetail.value?.evaluationScore || null, 'score')
|
| 98 |
+
})
|
| 99 |
+
|
| 100 |
+
const levels = computed(() =>
|
| 101 |
+
[...new Set(selectedModel.value?.tasks.map((task) => task.level) || [])].sort(),
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
const statuses = computed(() =>
|
| 105 |
+
[...new Set(selectedModel.value?.tasks.map((task) => task.executionStatus) || [])].sort(),
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
const filteredTasks = computed(() => {
|
| 109 |
+
const query = taskSearch.value.trim().toLowerCase()
|
| 110 |
+
const result = (selectedModel.value?.tasks || []).filter((task) => (
|
| 111 |
+
(!query || task.taskId.toLowerCase().includes(query))
|
| 112 |
+
&& (taskDimension.value === 'all' || task.drawingType === taskDimension.value)
|
| 113 |
+
&& (taskLevel.value === 'all' || task.level === taskLevel.value)
|
| 114 |
+
&& (taskStatus.value === 'all' || task.executionStatus === taskStatus.value)
|
| 115 |
+
))
|
| 116 |
+
return [...result].sort((a, b) => {
|
| 117 |
+
if (taskSort.value === 'vision') return (taskVisionScore(b) ?? -1) - (taskVisionScore(a) ?? -1)
|
| 118 |
+
if (taskSort.value === 'deterministic') {
|
| 119 |
+
return (taskDeterministicScore(b) ?? -1) - (taskDeterministicScore(a) ?? -1)
|
| 120 |
+
}
|
| 121 |
+
if (taskSort.value === 'actions') {
|
| 122 |
+
return (metricByTask.value.get(b.taskId)?.acceptedActions ?? -1)
|
| 123 |
+
- (metricByTask.value.get(a.taskId)?.acceptedActions ?? -1)
|
| 124 |
+
}
|
| 125 |
+
return a.taskId.localeCompare(b.taskId, undefined, { numeric: true })
|
| 126 |
+
})
|
| 127 |
+
})
|
| 128 |
+
|
| 129 |
+
const suiteTotals = computed(() => ({
|
| 130 |
+
models: models.value.length,
|
| 131 |
+
tasks: models.value.reduce((sum, model) => sum + model.totals.included, 0),
|
| 132 |
+
objects: models.value.reduce((sum, model) => sum + model.totals.objects, 0),
|
| 133 |
+
bytes: models.value.reduce((sum, model) => sum + model.totals.bytes, 0),
|
| 134 |
+
}))
|
| 135 |
+
|
| 136 |
+
const scoreBreakdown = computed(() => {
|
| 137 |
+
const score = taskDetail.value?.evaluationScore || {}
|
| 138 |
+
return [
|
| 139 |
+
['Linework', numberField(score, 'linework_similarity')],
|
| 140 |
+
['Geometry', numberField(score, 'geometry_similarity')],
|
| 141 |
+
['Extents', numberField(score, 'geometry_extent_similarity') ?? numberField(score, 'extent_similarity')],
|
| 142 |
+
['Dimensions', numberField(score, 'dimension_similarity')],
|
| 143 |
+
['Annotations', numberField(score, 'annotation_similarity')],
|
| 144 |
+
] as Array<[string, number | null]>
|
| 145 |
+
})
|
| 146 |
+
|
| 147 |
+
function numberField(value: Record<string, unknown> | null, key: string): number | null {
|
| 148 |
+
const number = Number(value?.[key])
|
| 149 |
+
return Number.isFinite(number) ? number : null
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
function readRoute() {
|
| 153 |
+
const params = new URLSearchParams(window.location.hash.replace(/^#/, ''))
|
| 154 |
+
return {
|
| 155 |
+
model: params.get('model') || '',
|
| 156 |
+
task: params.get('task') || '',
|
| 157 |
+
view: (params.get('view') || 'overview') as View,
|
| 158 |
+
turn: Math.max(1, Number(params.get('turn') || 1)),
|
| 159 |
+
}
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
function writeRoute() {
|
| 163 |
+
const params = new URLSearchParams()
|
| 164 |
+
if (selectedModelSlug.value) params.set('model', selectedModelSlug.value)
|
| 165 |
+
if (selectedTaskId.value) params.set('task', selectedTaskId.value)
|
| 166 |
+
params.set('view', view.value)
|
| 167 |
+
if (view.value === 'trace' && selectedTurn.value) params.set('turn', String(selectedTurn.value.turn))
|
| 168 |
+
window.history.replaceState(null, '', `${window.location.pathname}${window.location.search}#${params}`)
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
async function loadModels() {
|
| 172 |
+
loadingModels.value = true
|
| 173 |
+
error.value = ''
|
| 174 |
+
try {
|
| 175 |
+
const response = await fetchJson<{ models: ModelResult[] }>('/api/results/models')
|
| 176 |
+
models.value = response.models
|
| 177 |
+
const route = readRoute()
|
| 178 |
+
selectedModelSlug.value = models.value.some((model) => model.slug === route.model)
|
| 179 |
+
? route.model
|
| 180 |
+
: (models.value[0]?.slug || '')
|
| 181 |
+
view.value = ['overview', 'tasks', 'trace', 'frames'].includes(route.view)
|
| 182 |
+
? route.view
|
| 183 |
+
: 'overview'
|
| 184 |
+
await loadMetrics()
|
| 185 |
+
if (route.task && selectedModel.value?.tasks.some((task) => task.taskId === route.task)) {
|
| 186 |
+
selectedTaskId.value = route.task
|
| 187 |
+
await loadTask(route.turn)
|
| 188 |
+
if (view.value === 'frames') await loadFrames(true)
|
| 189 |
+
} else if (view.value === 'trace' || view.value === 'frames') {
|
| 190 |
+
view.value = 'tasks'
|
| 191 |
+
}
|
| 192 |
+
writeRoute()
|
| 193 |
+
} catch (reason) {
|
| 194 |
+
error.value = reason instanceof Error ? reason.message : String(reason)
|
| 195 |
+
} finally {
|
| 196 |
+
loadingModels.value = false
|
| 197 |
+
}
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
async function refreshModels() {
|
| 201 |
+
loadingModels.value = true
|
| 202 |
+
try {
|
| 203 |
+
const response = await fetchJson<{ models: ModelResult[] }>('/api/results/models?refresh=1')
|
| 204 |
+
models.value = response.models
|
| 205 |
+
if (!selectedModel.value) selectedModelSlug.value = models.value[0]?.slug || ''
|
| 206 |
+
await loadMetrics()
|
| 207 |
+
} catch (reason) {
|
| 208 |
+
error.value = reason instanceof Error ? reason.message : String(reason)
|
| 209 |
+
} finally {
|
| 210 |
+
loadingModels.value = false
|
| 211 |
+
}
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
async function loadMetrics() {
|
| 215 |
+
metrics.value = null
|
| 216 |
+
if (!selectedModelSlug.value) return
|
| 217 |
+
loadingMetrics.value = true
|
| 218 |
+
try {
|
| 219 |
+
metrics.value = await fetchJson<ModelMetrics>(
|
| 220 |
+
`/api/results/models/${encodeURIComponent(selectedModelSlug.value)}/metrics`,
|
| 221 |
+
60_000,
|
| 222 |
+
)
|
| 223 |
+
} catch {
|
| 224 |
+
// The manifest scores remain fully usable when optional usage enrichment fails.
|
| 225 |
+
} finally {
|
| 226 |
+
loadingMetrics.value = false
|
| 227 |
+
}
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
async function selectModel(slug: string) {
|
| 231 |
+
if (slug === selectedModelSlug.value) return
|
| 232 |
+
selectedModelSlug.value = slug
|
| 233 |
+
selectedTaskId.value = ''
|
| 234 |
+
taskDetail.value = null
|
| 235 |
+
trace.value = null
|
| 236 |
+
frames.value = []
|
| 237 |
+
view.value = 'overview'
|
| 238 |
+
writeRoute()
|
| 239 |
+
await loadMetrics()
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
async function openTask(taskId: string) {
|
| 243 |
+
selectedTaskId.value = taskId
|
| 244 |
+
view.value = 'trace'
|
| 245 |
+
writeRoute()
|
| 246 |
+
await loadTask(1)
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
async function loadTask(preferredTurn = 1) {
|
| 250 |
+
const version = ++taskLoadVersion
|
| 251 |
+
loadingTask.value = true
|
| 252 |
+
taskDetail.value = null
|
| 253 |
+
trace.value = null
|
| 254 |
+
turnDetail.value = null
|
| 255 |
+
frames.value = []
|
| 256 |
+
error.value = ''
|
| 257 |
+
const base = `/api/results/models/${encodeURIComponent(selectedModelSlug.value)}`
|
| 258 |
+
+ `/tasks/${encodeURIComponent(selectedTaskId.value)}`
|
| 259 |
+
try {
|
| 260 |
+
const [detail, traceIndex] = await Promise.all([
|
| 261 |
+
fetchJson<TaskDetail>(base),
|
| 262 |
+
fetchJson<TraceIndex>(`${base}/trace`, 60_000),
|
| 263 |
+
])
|
| 264 |
+
if (version !== taskLoadVersion) return
|
| 265 |
+
taskDetail.value = detail
|
| 266 |
+
trace.value = traceIndex
|
| 267 |
+
const preferredIndex = Math.max(0, traceIndex.turns.findIndex((turn) => turn.turn === preferredTurn))
|
| 268 |
+
await selectTurn(preferredIndex)
|
| 269 |
+
} catch (reason) {
|
| 270 |
+
if (version === taskLoadVersion) {
|
| 271 |
+
error.value = reason instanceof Error ? reason.message : String(reason)
|
| 272 |
+
}
|
| 273 |
+
} finally {
|
| 274 |
+
if (version === taskLoadVersion) loadingTask.value = false
|
| 275 |
+
}
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
async function selectTurn(index: number) {
|
| 279 |
+
if (!trace.value?.turns.length) return
|
| 280 |
+
const safe = Math.max(0, Math.min(trace.value.turns.length - 1, index))
|
| 281 |
+
selectedTurnIndex.value = safe
|
| 282 |
+
turnDetail.value = null
|
| 283 |
+
const turn = trace.value.turns[safe]
|
| 284 |
+
const version = ++turnLoadVersion
|
| 285 |
+
loadingTurn.value = true
|
| 286 |
+
writeRoute()
|
| 287 |
+
prefetchTurnFrames(turn)
|
| 288 |
+
try {
|
| 289 |
+
const detail = await fetchJson<TurnDetail>(
|
| 290 |
+
`/api/results/models/${encodeURIComponent(selectedModelSlug.value)}`
|
| 291 |
+
+ `/tasks/${encodeURIComponent(selectedTaskId.value)}/turns/${turn.turn}`,
|
| 292 |
+
)
|
| 293 |
+
if (version === turnLoadVersion) turnDetail.value = detail
|
| 294 |
+
} catch {
|
| 295 |
+
// Turn actions remain visible from the compact trace index.
|
| 296 |
+
} finally {
|
| 297 |
+
if (version === turnLoadVersion) loadingTurn.value = false
|
| 298 |
+
}
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
async function showView(nextView: View) {
|
| 302 |
+
view.value = nextView
|
| 303 |
+
writeRoute()
|
| 304 |
+
if (nextView === 'frames' && selectedTaskId.value && !frames.value.length) {
|
| 305 |
+
await loadFrames(true)
|
| 306 |
+
}
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
async function loadFrames(reset = false) {
|
| 310 |
+
if (!selectedTaskId.value || loadingFrames.value) return
|
| 311 |
+
if (reset) frames.value = []
|
| 312 |
+
loadingFrames.value = true
|
| 313 |
+
try {
|
| 314 |
+
const offset = frames.value.length
|
| 315 |
+
const page = await fetchJson<FramePage>(
|
| 316 |
+
`/api/results/models/${encodeURIComponent(selectedModelSlug.value)}`
|
| 317 |
+
+ `/tasks/${encodeURIComponent(selectedTaskId.value)}/frames?offset=${offset}&limit=120`,
|
| 318 |
+
)
|
| 319 |
+
frames.value.push(...page.frames)
|
| 320 |
+
frameTotal.value = page.total
|
| 321 |
+
frameHasMore.value = page.hasMore
|
| 322 |
+
} catch (reason) {
|
| 323 |
+
error.value = reason instanceof Error ? reason.message : String(reason)
|
| 324 |
+
} finally {
|
| 325 |
+
loadingFrames.value = false
|
| 326 |
+
}
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
function mediaUrl(path: string, download = false): string {
|
| 330 |
+
if (!selectedModelSlug.value || !selectedTaskId.value) return ''
|
| 331 |
+
const base = `/api/results/models/${encodeURIComponent(selectedModelSlug.value)}`
|
| 332 |
+
+ `/tasks/${encodeURIComponent(selectedTaskId.value)}/media`
|
| 333 |
+
return `${base}?path=${encodeURIComponent(path)}${download ? '&download=1' : ''}`
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
function frameUrl(sequence: number | null): string {
|
| 337 |
+
if (sequence == null) return ''
|
| 338 |
+
return mediaUrl(`frames/frame_${String(sequence).padStart(4, '0')}.png`)
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
function prefetchTurnFrames(turn: TraceTurn) {
|
| 342 |
+
const sequences = new Set([
|
| 343 |
+
turn.inputFrameSequence,
|
| 344 |
+
turn.outputFrameSequence,
|
| 345 |
+
turn.outputFrameSequence == null ? null : turn.outputFrameSequence + 1,
|
| 346 |
+
])
|
| 347 |
+
for (const sequence of sequences) {
|
| 348 |
+
if (sequence == null || sequence < 0) continue
|
| 349 |
+
const image = new Image()
|
| 350 |
+
image.decoding = 'async'
|
| 351 |
+
image.src = frameUrl(sequence)
|
| 352 |
+
}
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
function formatScore(value: number | null | undefined): string {
|
| 356 |
+
return value == null ? '—' : `${(value * 100).toFixed(1)}`
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
function formatNumber(value: number | null | undefined): string {
|
| 360 |
+
return value == null ? '—' : new Intl.NumberFormat().format(Math.round(value))
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
function formatBytes(value: number | null | undefined): string {
|
| 364 |
+
if (value == null) return '—'
|
| 365 |
+
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
| 366 |
+
let size = value
|
| 367 |
+
let unit = 0
|
| 368 |
+
while (size >= 1000 && unit < units.length - 1) {
|
| 369 |
+
size /= 1000
|
| 370 |
+
unit += 1
|
| 371 |
+
}
|
| 372 |
+
return `${size.toFixed(unit > 1 ? 1 : 0)} ${units[unit]}`
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
function formatDuration(seconds: number | null | undefined): string {
|
| 376 |
+
if (seconds == null) return '—'
|
| 377 |
+
const hours = Math.floor(seconds / 3600)
|
| 378 |
+
const minutes = Math.floor((seconds % 3600) / 60)
|
| 379 |
+
return hours ? `${hours}h ${minutes}m` : `${minutes}m`
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
function formatLatency(milliseconds: number | null | undefined): string {
|
| 383 |
+
if (milliseconds == null) return '—'
|
| 384 |
+
return milliseconds > 1000 ? `${(milliseconds / 1000).toFixed(1)}s` : `${Math.round(milliseconds)}ms`
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
function formatDate(value: string | null): string {
|
| 388 |
+
if (!value) return 'Unknown date'
|
| 389 |
+
const date = new Date(value)
|
| 390 |
+
return Number.isNaN(date.valueOf())
|
| 391 |
+
? value
|
| 392 |
+
: new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric', year: 'numeric' }).format(date)
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
function readable(value: string): string {
|
| 396 |
+
return value.replaceAll('_', ' ')
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
function pretty(value: unknown): string {
|
| 400 |
+
return JSON.stringify(value, null, 2)
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
function scoreWidth(value: number | null | undefined): string {
|
| 404 |
+
return `${Math.max(0, Math.min(100, (value || 0) * 100))}%`
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
function histogramHeight(count: number, values: number[]): string {
|
| 408 |
+
const maximum = Math.max(...values, 1)
|
| 409 |
+
return `${Math.max(3, count / maximum * 100)}%`
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
+
function taskVisionScore(task: ResultTask): number | null {
|
| 413 |
+
return metricByTask.value.get(task.taskId)?.visionScore ?? task.visionScore
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
function taskDeterministicScore(task: ResultTask): number | null {
|
| 417 |
+
return metricByTask.value.get(task.taskId)?.deterministicScore ?? task.deterministicScore
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
function taskDeterministicVerdict(task: ResultTask): string {
|
| 421 |
+
const score = taskDeterministicScore(task)
|
| 422 |
+
if (score == null) return 'unavailable'
|
| 423 |
+
if (score >= 0.9) return 'target_acquired'
|
| 424 |
+
if (score >= 0.75) return 'mostly_acquired'
|
| 425 |
+
if (score >= 0.4) return 'partial'
|
| 426 |
+
return 'not_acquired'
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
function verdictTone(value: string): string {
|
| 430 |
+
if (value === 'target_acquired') return 'positive'
|
| 431 |
+
if (value === 'mostly_acquired') return 'good'
|
| 432 |
+
if (value === 'partial') return 'warning'
|
| 433 |
+
if (value === 'not_acquired') return 'negative'
|
| 434 |
+
return 'neutral'
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
function statusTone(task: ResultTask): string {
|
| 438 |
+
if (!task.included) return 'neutral'
|
| 439 |
+
if (task.executionStatus === 'completed') return 'positive'
|
| 440 |
+
if (task.executionStatus === 'budget_exhausted') return 'warning'
|
| 441 |
+
return 'negative'
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
function openExpanded(payload: ExpandedImage) {
|
| 445 |
+
expandedImage.value = payload
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
function closeExpanded() {
|
| 449 |
+
expandedImage.value = null
|
| 450 |
+
}
|
| 451 |
+
|
| 452 |
+
function onKeydown(event: KeyboardEvent) {
|
| 453 |
+
if (event.key === 'Escape' && expandedImage.value) {
|
| 454 |
+
closeExpanded()
|
| 455 |
+
return
|
| 456 |
+
}
|
| 457 |
+
if (view.value !== 'trace' || expandedImage.value) return
|
| 458 |
+
const target = event.target as HTMLElement | null
|
| 459 |
+
if (target?.matches('input, select, textarea')) return
|
| 460 |
+
if (event.key === 'ArrowLeft') void selectTurn(selectedTurnIndex.value - 1)
|
| 461 |
+
if (event.key === 'ArrowRight') void selectTurn(selectedTurnIndex.value + 1)
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
onMounted(async () => {
|
| 465 |
+
window.addEventListener('keydown', onKeydown)
|
| 466 |
+
await nextTick()
|
| 467 |
+
await loadModels()
|
| 468 |
+
})
|
| 469 |
+
|
| 470 |
+
onBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))
|
| 471 |
+
</script>
|
| 472 |
+
|
| 473 |
+
<template>
|
| 474 |
+
<div class="app-shell">
|
| 475 |
+
<header class="topbar">
|
| 476 |
+
<button class="brand" type="button" @click="showView('overview')">
|
| 477 |
+
<span class="brand-mark">A</span>
|
| 478 |
+
<span>
|
| 479 |
+
<strong>AutoCAD Bench</strong>
|
| 480 |
+
<small>Final results registry</small>
|
| 481 |
+
</span>
|
| 482 |
+
</button>
|
| 483 |
+
<div class="topbar-meta">
|
| 484 |
+
<span class="health"><i /> Direct S3 media</span>
|
| 485 |
+
<span>{{ suiteTotals.models }} models</span>
|
| 486 |
+
<span>{{ formatNumber(suiteTotals.tasks) }} finalized tasks</span>
|
| 487 |
+
<button class="icon-button" type="button" :disabled="loadingModels" @click="refreshModels">
|
| 488 |
+
{{ loadingModels ? 'Refreshing…' : 'Refresh' }}
|
| 489 |
+
</button>
|
| 490 |
+
</div>
|
| 491 |
+
</header>
|
| 492 |
+
|
| 493 |
+
<div v-if="loadingModels && !models.length" class="loading-screen">
|
| 494 |
+
<div class="loading-orbit"><span /></div>
|
| 495 |
+
<p>Reading finalized benchmark manifests…</p>
|
| 496 |
+
</div>
|
| 497 |
+
|
| 498 |
+
<main v-else class="workspace">
|
| 499 |
+
<aside class="model-rail">
|
| 500 |
+
<div class="rail-heading">
|
| 501 |
+
<span>Models</span>
|
| 502 |
+
<b>{{ models.length }}</b>
|
| 503 |
+
</div>
|
| 504 |
+
<button
|
| 505 |
+
v-for="model in models"
|
| 506 |
+
:key="model.slug"
|
| 507 |
+
type="button"
|
| 508 |
+
class="model-item"
|
| 509 |
+
:class="{ active: model.slug === selectedModelSlug }"
|
| 510 |
+
@click="selectModel(model.slug)"
|
| 511 |
+
>
|
| 512 |
+
<span class="model-avatar">{{ model.modelId.slice(0, 1).toUpperCase() }}</span>
|
| 513 |
+
<span class="model-copy">
|
| 514 |
+
<strong>{{ model.modelId }}</strong>
|
| 515 |
+
<small>{{ model.totals.included }}/{{ model.totals.expected }} tasks · {{ formatScore(model.scores.vision.mean) }} vision audit</small>
|
| 516 |
+
</span>
|
| 517 |
+
<span class="model-score">{{ formatScore(model.scores.deterministic.mean) }}</span>
|
| 518 |
+
</button>
|
| 519 |
+
<div class="rail-footnote">
|
| 520 |
+
<span>{{ formatNumber(suiteTotals.objects) }} objects</span>
|
| 521 |
+
<span>{{ formatBytes(suiteTotals.bytes) }}</span>
|
| 522 |
+
<small>Canonical artifacts only</small>
|
| 523 |
+
</div>
|
| 524 |
+
</aside>
|
| 525 |
+
|
| 526 |
+
<section v-if="selectedModel" class="content">
|
| 527 |
+
<div class="content-heading">
|
| 528 |
+
<div>
|
| 529 |
+
<div class="eyebrow">Final aggregate · {{ formatDate(selectedModel.createdAt) }}</div>
|
| 530 |
+
<h1>{{ selectedModel.modelId }}</h1>
|
| 531 |
+
<p>{{ selectedModel.aggregateId }}</p>
|
| 532 |
+
</div>
|
| 533 |
+
<div class="heading-actions">
|
| 534 |
+
<span class="registry-badge"><i /> Verified registry</span>
|
| 535 |
+
<button type="button" class="secondary-button" @click="showView('tasks')">Browse tasks</button>
|
| 536 |
+
</div>
|
| 537 |
+
</div>
|
| 538 |
+
|
| 539 |
+
<nav class="section-tabs" aria-label="Result sections">
|
| 540 |
+
<button type="button" :class="{ active: view === 'overview' }" @click="showView('overview')">Overview</button>
|
| 541 |
+
<button type="button" :class="{ active: view === 'tasks' }" @click="showView('tasks')">Tasks <span>{{ selectedModel.totals.included }}</span></button>
|
| 542 |
+
<button v-if="selectedTaskId" type="button" :class="{ active: view === 'trace' }" @click="showView('trace')">Trace</button>
|
| 543 |
+
<button v-if="selectedTaskId" type="button" :class="{ active: view === 'frames' }" @click="showView('frames')">All frames <span>{{ formatNumber(taskDetail?.frameCount) }}</span></button>
|
| 544 |
+
</nav>
|
| 545 |
+
|
| 546 |
+
<div v-if="error" class="error-banner">
|
| 547 |
+
<span>{{ error }}</span>
|
| 548 |
+
<button type="button" @click="error = ''">Dismiss</button>
|
| 549 |
+
</div>
|
| 550 |
+
|
| 551 |
+
<template v-if="view === 'overview'">
|
| 552 |
+
<section class="stat-grid">
|
| 553 |
+
<article class="stat-card hero-stat">
|
| 554 |
+
<span>Deterministic score</span>
|
| 555 |
+
<strong>{{ loadingMetrics ? '…' : formatScore(activeScores?.deterministic.mean) }}</strong>
|
| 556 |
+
<small>median {{ formatScore(activeScores?.deterministic.median) }} · {{ activeScores?.deterministic.count || 0 }} scored</small>
|
| 557 |
+
<div class="score-track"><i :style="{ width: scoreWidth(activeScores?.deterministic.mean) }" /></div>
|
| 558 |
+
</article>
|
| 559 |
+
<article class="stat-card">
|
| 560 |
+
<span>Vision audit</span>
|
| 561 |
+
<strong>{{ loadingMetrics ? '…' : formatScore(activeScores?.vision.mean) }}</strong>
|
| 562 |
+
<small>median {{ formatScore(activeScores?.vision.median) }}</small>
|
| 563 |
+
</article>
|
| 564 |
+
<article class="stat-card">
|
| 565 |
+
<span>Finalized</span>
|
| 566 |
+
<strong>{{ selectedModel.totals.included }}<em>/{{ selectedModel.totals.expected }}</em></strong>
|
| 567 |
+
<small>{{ selectedModel.totals.clean }} clean · {{ selectedModel.totals.budgetExhausted }} budget exhausted</small>
|
| 568 |
+
</article>
|
| 569 |
+
<article class="stat-card">
|
| 570 |
+
<span>Median actions</span>
|
| 571 |
+
<strong>{{ loadingMetrics ? '…' : formatNumber(metrics?.medians.acceptedActions) }}</strong>
|
| 572 |
+
<small>{{ loadingMetrics ? 'Loading trace totals' : `${formatNumber(metrics?.totals.screenshots)} screenshots total` }}</small>
|
| 573 |
+
</article>
|
| 574 |
+
<article class="stat-card">
|
| 575 |
+
<span>Runtime</span>
|
| 576 |
+
<strong>{{ loadingMetrics ? '…' : formatDuration(metrics?.totals.elapsedSeconds) }}</strong>
|
| 577 |
+
<small>{{ formatBytes(selectedModel.totals.bytes) }} retained</small>
|
| 578 |
+
</article>
|
| 579 |
+
</section>
|
| 580 |
+
|
| 581 |
+
<div class="overview-grid">
|
| 582 |
+
<section class="panel category-panel">
|
| 583 |
+
<div class="panel-heading">
|
| 584 |
+
<div>
|
| 585 |
+
<span class="eyebrow">Performance map</span>
|
| 586 |
+
<h2>Score by category</h2>
|
| 587 |
+
</div>
|
| 588 |
+
<div class="legend"><i class="deterministic" /> Deterministic <i class="vision" /> Vision audit</div>
|
| 589 |
+
</div>
|
| 590 |
+
<div class="category-list">
|
| 591 |
+
<div v-for="category in activeCategories" :key="category.category" class="category-row">
|
| 592 |
+
<div class="category-name">
|
| 593 |
+
<strong>{{ category.drawingType.toUpperCase() }} {{ category.level }}</strong>
|
| 594 |
+
<small>{{ category.tasks }} tasks</small>
|
| 595 |
+
</div>
|
| 596 |
+
<div class="dual-bars">
|
| 597 |
+
<div><i class="deterministic" :style="{ width: scoreWidth(category.deterministic.mean) }" /></div>
|
| 598 |
+
<div><i class="vision" :style="{ width: scoreWidth(category.vision.mean) }" /></div>
|
| 599 |
+
</div>
|
| 600 |
+
<div class="category-values">
|
| 601 |
+
<strong>{{ formatScore(category.deterministic.mean) }}</strong>
|
| 602 |
+
<span>{{ formatScore(category.vision.mean) }}</span>
|
| 603 |
+
</div>
|
| 604 |
+
</div>
|
| 605 |
+
</div>
|
| 606 |
+
</section>
|
| 607 |
+
|
| 608 |
+
<section class="panel distribution-panel">
|
| 609 |
+
<div class="panel-heading">
|
| 610 |
+
<div>
|
| 611 |
+
<span class="eyebrow">Deterministic evaluator</span>
|
| 612 |
+
<h2>Score distribution</h2>
|
| 613 |
+
</div>
|
| 614 |
+
</div>
|
| 615 |
+
<div class="histogram">
|
| 616 |
+
<div v-for="(count, index) in activeScores?.deterministicHistogram || []" :key="index" class="histogram-column">
|
| 617 |
+
<span>{{ count || '' }}</span>
|
| 618 |
+
<i :style="{ height: histogramHeight(count, activeScores?.deterministicHistogram || []) }" />
|
| 619 |
+
<small>{{ index * 10 }}</small>
|
| 620 |
+
</div>
|
| 621 |
+
</div>
|
| 622 |
+
<div class="distribution-summary">
|
| 623 |
+
<div><span>Lowest</span><strong>{{ formatScore(activeScores?.deterministic.min) }}</strong></div>
|
| 624 |
+
<div><span>Median</span><strong>{{ formatScore(activeScores?.deterministic.median) }}</strong></div>
|
| 625 |
+
<div><span>Highest</span><strong>{{ formatScore(activeScores?.deterministic.max) }}</strong></div>
|
| 626 |
+
</div>
|
| 627 |
+
</section>
|
| 628 |
+
</div>
|
| 629 |
+
|
| 630 |
+
<section class="panel comparison-panel">
|
| 631 |
+
<div class="panel-heading">
|
| 632 |
+
<div>
|
| 633 |
+
<span class="eyebrow">Registry comparison</span>
|
| 634 |
+
<h2>All models</h2>
|
| 635 |
+
</div>
|
| 636 |
+
<span class="panel-note">Scores are shown independently; they are not blended.</span>
|
| 637 |
+
</div>
|
| 638 |
+
<div class="comparison-table table-scroll">
|
| 639 |
+
<table>
|
| 640 |
+
<thead><tr><th>Model</th><th>Finalized</th><th>Deterministic mean</th><th>Vision audit</th><th>Deterministic range</th><th>Artifacts</th></tr></thead>
|
| 641 |
+
<tbody>
|
| 642 |
+
<tr v-for="model in models" :key="model.slug" :class="{ selected: model.slug === selectedModelSlug }" @click="selectModel(model.slug)">
|
| 643 |
+
<td><strong>{{ model.modelId }}</strong><small>{{ model.aggregateId }}</small></td>
|
| 644 |
+
<td>{{ model.totals.included }}/{{ model.totals.expected }}</td>
|
| 645 |
+
<td><b>{{ formatScore(model.scores.deterministic.mean) }}</b></td>
|
| 646 |
+
<td>{{ formatScore(model.scores.vision.mean) }}</td>
|
| 647 |
+
<td>{{ formatScore(model.scores.deterministic.min) }}–{{ formatScore(model.scores.deterministic.max) }}</td>
|
| 648 |
+
<td>{{ formatBytes(model.totals.bytes) }}</td>
|
| 649 |
+
</tr>
|
| 650 |
+
</tbody>
|
| 651 |
+
</table>
|
| 652 |
+
</div>
|
| 653 |
+
</section>
|
| 654 |
+
</template>
|
| 655 |
+
|
| 656 |
+
<template v-else-if="view === 'tasks'">
|
| 657 |
+
<section class="task-toolbar">
|
| 658 |
+
<label class="search-field">
|
| 659 |
+
<span>Search</span>
|
| 660 |
+
<input v-model="taskSearch" type="search" placeholder="Find a task…" />
|
| 661 |
+
</label>
|
| 662 |
+
<label><span>Drawing</span><select v-model="taskDimension"><option value="all">All</option><option value="2d">2D</option><option value="3d">3D</option></select></label>
|
| 663 |
+
<label><span>Level</span><select v-model="taskLevel"><option value="all">All levels</option><option v-for="level in levels" :key="level" :value="level">{{ level }}</option></select></label>
|
| 664 |
+
<label><span>Status</span><select v-model="taskStatus"><option value="all">All statuses</option><option v-for="status in statuses" :key="status" :value="status">{{ readable(status) }}</option></select></label>
|
| 665 |
+
<label><span>Sort</span><select v-model="taskSort"><option value="task">Task ID</option><option value="deterministic">Deterministic score</option><option value="vision">Vision audit</option><option value="actions">Actions</option></select></label>
|
| 666 |
+
<div class="result-count">{{ filteredTasks.length }} results</div>
|
| 667 |
+
</section>
|
| 668 |
+
<section class="panel task-table-panel">
|
| 669 |
+
<div class="table-scroll">
|
| 670 |
+
<table class="task-table">
|
| 671 |
+
<thead><tr><th>Task</th><th>Type</th><th>Status</th><th>Deterministic</th><th>Vision audit</th><th>Verdict</th><th>Actions</th><th>Frames</th><th /></tr></thead>
|
| 672 |
+
<tbody>
|
| 673 |
+
<tr v-for="task in filteredTasks" :key="task.taskId" tabindex="0" @click="openTask(task.taskId)" @keydown.enter="openTask(task.taskId)">
|
| 674 |
+
<td><strong>{{ task.taskId }}</strong><small>{{ task.selectedAttempt }}</small></td>
|
| 675 |
+
<td><span class="type-chip">{{ task.drawingType.toUpperCase() }}</span> {{ task.level }}</td>
|
| 676 |
+
<td><span class="status-pill" :class="statusTone(task)"><i />{{ task.included ? readable(task.executionStatus) : 'excluded' }}</span></td>
|
| 677 |
+
<td><strong>{{ formatScore(taskDeterministicScore(task)) }}</strong></td>
|
| 678 |
+
<td>{{ formatScore(taskVisionScore(task)) }}</td>
|
| 679 |
+
<td><span class="verdict" :class="verdictTone(taskDeterministicVerdict(task))">{{ readable(taskDeterministicVerdict(task)) }}</span></td>
|
| 680 |
+
<td>{{ loadingMetrics ? '…' : formatNumber(metricByTask.get(task.taskId)?.acceptedActions) }}</td>
|
| 681 |
+
<td>{{ loadingMetrics ? '…' : formatNumber(metricByTask.get(task.taskId)?.screenshots) }}</td>
|
| 682 |
+
<td><button type="button" class="row-arrow" aria-label="Open task">→</button></td>
|
| 683 |
+
</tr>
|
| 684 |
+
</tbody>
|
| 685 |
+
</table>
|
| 686 |
+
</div>
|
| 687 |
+
</section>
|
| 688 |
+
</template>
|
| 689 |
+
|
| 690 |
+
<template v-else-if="view === 'trace'">
|
| 691 |
+
<div v-if="loadingTask" class="task-loading"><div class="loading-orbit"><span /></div><p>Loading canonical trace…</p></div>
|
| 692 |
+
<template v-else-if="taskDetail && selectedTask">
|
| 693 |
+
<section class="task-titlebar">
|
| 694 |
+
<button type="button" class="back-button" @click="showView('tasks')">← Tasks</button>
|
| 695 |
+
<div><span class="eyebrow">{{ selectedTask.drawingType.toUpperCase() }} · {{ selectedTask.level }}</span><h2>{{ selectedTask.taskId }}</h2></div>
|
| 696 |
+
<div class="task-title-scores">
|
| 697 |
+
<span>Deterministic <strong>{{ formatScore(selectedDeterministicScore) }}</strong></span>
|
| 698 |
+
<span>Vision audit <strong>{{ formatScore(selectedVisionScore) }}</strong></span>
|
| 699 |
+
<span class="status-pill" :class="statusTone(selectedTask)"><i />{{ readable(selectedTask.executionStatus) }}</span>
|
| 700 |
+
</div>
|
| 701 |
+
</section>
|
| 702 |
+
|
| 703 |
+
<section class="artifact-strip">
|
| 704 |
+
<ReliableImage :src="mediaUrl(taskDetail.media.reference)" alt="Reference drawing" label="Reference" eager @expand="openExpanded" />
|
| 705 |
+
<ReliableImage :src="mediaUrl(taskDetail.media.final)" alt="Final model drawing" label="Model output" eager @expand="openExpanded" />
|
| 706 |
+
<ReliableImage :src="mediaUrl(taskDetail.media.diff)" alt="Evaluation difference" label="Difference" eager @expand="openExpanded" />
|
| 707 |
+
<div class="rubric-card">
|
| 708 |
+
<div class="rubric-score"><span>Evaluator</span><strong>{{ formatScore(selectedDeterministicScore) }}</strong></div>
|
| 709 |
+
<div v-for="([label, score]) in scoreBreakdown" :key="label" class="rubric-row">
|
| 710 |
+
<span>{{ label }}</span><div><i :style="{ width: scoreWidth(score) }" /></div><b>{{ formatScore(score) }}</b>
|
| 711 |
+
</div>
|
| 712 |
+
</div>
|
| 713 |
+
</section>
|
| 714 |
+
|
| 715 |
+
<section class="trace-workbench">
|
| 716 |
+
<aside class="turn-list">
|
| 717 |
+
<div class="turn-list-heading">
|
| 718 |
+
<div><span>Model trace</span><strong>{{ trace?.turns.length || 0 }} turns</strong></div>
|
| 719 |
+
<small>{{ formatNumber(trace?.receiptCount) }} actions</small>
|
| 720 |
+
</div>
|
| 721 |
+
<button
|
| 722 |
+
v-for="(turn, index) in trace?.turns || []"
|
| 723 |
+
:key="turn.toolUseId || index"
|
| 724 |
+
type="button"
|
| 725 |
+
class="turn-item"
|
| 726 |
+
:class="{ active: index === selectedTurnIndex }"
|
| 727 |
+
@click="selectTurn(index)"
|
| 728 |
+
>
|
| 729 |
+
<span class="turn-number">{{ String(turn.turn).padStart(2, '0') }}</span>
|
| 730 |
+
<span class="turn-summary"><strong>{{ turn.actionCount }} action{{ turn.actionCount === 1 ? '' : 's' }}</strong><small>frames {{ turn.inputFrameSequence ?? '—' }} → {{ turn.outputFrameSequence ?? '—' }}</small></span>
|
| 731 |
+
<span class="turn-latency">{{ formatLatency(turn.latencyMs) }}</span>
|
| 732 |
+
</button>
|
| 733 |
+
</aside>
|
| 734 |
+
|
| 735 |
+
<div v-if="selectedTurn" class="turn-stage">
|
| 736 |
+
<div class="turn-stage-heading">
|
| 737 |
+
<div><span class="eyebrow">Turn {{ selectedTurn.turn }}</span><h3>{{ selectedTurn.actionCount }} recorded actions</h3></div>
|
| 738 |
+
<div class="turn-controls">
|
| 739 |
+
<button type="button" :disabled="selectedTurnIndex === 0" @click="selectTurn(selectedTurnIndex - 1)">←</button>
|
| 740 |
+
<span>{{ selectedTurnIndex + 1 }} / {{ trace?.turns.length }}</span>
|
| 741 |
+
<button type="button" :disabled="selectedTurnIndex + 1 >= (trace?.turns.length || 0)" @click="selectTurn(selectedTurnIndex + 1)">→</button>
|
| 742 |
+
</div>
|
| 743 |
+
</div>
|
| 744 |
+
<div class="turn-images">
|
| 745 |
+
<ReliableImage v-if="selectedTurn.inputFrameSequence != null" :src="frameUrl(selectedTurn.inputFrameSequence)" :alt="`Turn ${selectedTurn.turn} input frame`" :label="`Input · frame ${selectedTurn.inputFrameSequence}`" eager @expand="openExpanded" />
|
| 746 |
+
<div v-else class="empty-frame">No input frame recorded</div>
|
| 747 |
+
<ReliableImage v-if="selectedTurn.outputFrameSequence != null" :src="frameUrl(selectedTurn.outputFrameSequence)" :alt="`Turn ${selectedTurn.turn} output frame`" :label="`Output · frame ${selectedTurn.outputFrameSequence}`" eager @expand="openExpanded" />
|
| 748 |
+
<div v-else class="empty-frame">No output frame recorded</div>
|
| 749 |
+
</div>
|
| 750 |
+
<div class="turn-facts">
|
| 751 |
+
<span>Latency <strong>{{ formatLatency(selectedTurn.latencyMs) }}</strong></span>
|
| 752 |
+
<span>Input tokens <strong>{{ formatNumber(selectedTurn.usage?.input_tokens) }}</strong></span>
|
| 753 |
+
<span>Output tokens <strong>{{ formatNumber(selectedTurn.usage?.output_tokens) }}</strong></span>
|
| 754 |
+
<span>Attempts <strong>{{ selectedTurn.requestAttempts ?? '—' }}</strong></span>
|
| 755 |
+
<span>Validation retries <strong>{{ selectedTurn.validationRetries ?? '—' }}</strong></span>
|
| 756 |
+
</div>
|
| 757 |
+
<div class="trace-detail-grid">
|
| 758 |
+
<section class="trace-card">
|
| 759 |
+
<div class="trace-card-heading"><h4>Model response</h4><span>{{ selectedTurn.stopReason }}</span></div>
|
| 760 |
+
<pre>{{ pretty(selectedTurn.text) }}</pre>
|
| 761 |
+
</section>
|
| 762 |
+
<section class="trace-card">
|
| 763 |
+
<div class="trace-card-heading"><h4>Requested actions</h4><span>{{ selectedTurn.actions.length }}</span></div>
|
| 764 |
+
<div v-if="selectedTurn.actions.length" class="action-list">
|
| 765 |
+
<div v-for="(action, index) in selectedTurn.actions" :key="index" class="action-item"><span>{{ index + 1 }}</span><pre>{{ pretty(action) }}</pre></div>
|
| 766 |
+
</div>
|
| 767 |
+
<div v-else class="empty-copy">No valid action payload in this turn.</div>
|
| 768 |
+
</section>
|
| 769 |
+
</div>
|
| 770 |
+
<section class="trace-card receipts-card">
|
| 771 |
+
<div class="trace-card-heading"><h4>Execution receipts</h4><span>{{ loadingTurn ? 'Loading…' : `${turnDetail?.receipts.length || 0} receipts` }}</span></div>
|
| 772 |
+
<div v-if="turnDetail?.receipts.length" class="receipt-list">
|
| 773 |
+
<details v-for="receipt in turnDetail.receipts" :key="receipt.actionIndex || 0">
|
| 774 |
+
<summary><span>#{{ receipt.actionIndex }}</span><strong>{{ String(receipt.action?.action || 'action') }}</strong><small>{{ receipt.receipt?.ok === false ? 'failed' : 'accepted' }}</small></summary>
|
| 775 |
+
<pre>{{ pretty(receipt) }}</pre>
|
| 776 |
+
</details>
|
| 777 |
+
</div>
|
| 778 |
+
<div v-else-if="!loadingTurn" class="empty-copy">No execution receipts matched this model turn.</div>
|
| 779 |
+
</section>
|
| 780 |
+
<details v-if="turnDetail?.turn" class="raw-turn">
|
| 781 |
+
<summary>Raw model-turn record</summary>
|
| 782 |
+
<pre>{{ pretty(turnDetail.turn) }}</pre>
|
| 783 |
+
</details>
|
| 784 |
+
</div>
|
| 785 |
+
<div v-else class="empty-trace">No model turns were recorded for this canonical task.</div>
|
| 786 |
+
</section>
|
| 787 |
+
|
| 788 |
+
<section class="panel downloads-panel">
|
| 789 |
+
<div class="panel-heading"><div><span class="eyebrow">Raw evidence</span><h2>Artifacts & trace files</h2></div><button type="button" class="secondary-button" @click="showView('frames')">Open all {{ formatNumber(taskDetail.frameCount) }} frames</button></div>
|
| 790 |
+
<div class="download-grid">
|
| 791 |
+
<a v-for="download in taskDetail.downloads" :key="download.path" :href="mediaUrl(download.path, true)"><span>{{ download.label }}</span><small>{{ download.path }}</small><b>↓</b></a>
|
| 792 |
+
</div>
|
| 793 |
+
</section>
|
| 794 |
+
</template>
|
| 795 |
+
</template>
|
| 796 |
+
|
| 797 |
+
<template v-else-if="view === 'frames'">
|
| 798 |
+
<section class="task-titlebar">
|
| 799 |
+
<button type="button" class="back-button" @click="showView('trace')">← Trace</button>
|
| 800 |
+
<div><span class="eyebrow">Complete screenshot sequence</span><h2>{{ selectedTaskId }}</h2></div>
|
| 801 |
+
<div class="frame-progress"><strong>{{ formatNumber(frames.length) }}</strong><span>of {{ formatNumber(frameTotal || taskDetail?.frameCount) }} loaded</span></div>
|
| 802 |
+
</section>
|
| 803 |
+
<div class="frame-gallery-note"><span><i /> Images load only as they approach the viewport</span><span>Direct, signed S3 delivery · automatic retry · immutable browser cache</span></div>
|
| 804 |
+
<section class="frame-gallery">
|
| 805 |
+
<button v-for="frame in frames" :key="frame.sequence" type="button" class="frame-tile" @click="openExpanded({ src: mediaUrl(frame.path), label: `${selectedTaskId} · frame ${frame.sequence}` })">
|
| 806 |
+
<ReliableImage :src="mediaUrl(frame.path)" :alt="`${selectedTaskId} frame ${frame.sequence}`" :label="`Frame ${frame.sequence}`" :expandable="false" />
|
| 807 |
+
</button>
|
| 808 |
+
</section>
|
| 809 |
+
<div class="load-more">
|
| 810 |
+
<button v-if="frameHasMore" type="button" class="primary-button" :disabled="loadingFrames" @click="loadFrames(false)">{{ loadingFrames ? 'Loading…' : `Load next ${Math.min(120, frameTotal - frames.length)} frames` }}</button>
|
| 811 |
+
<span v-else-if="frames.length">All {{ formatNumber(frameTotal) }} frames loaded</span>
|
| 812 |
+
<span v-else-if="loadingFrames">Loading frames…</span>
|
| 813 |
+
</div>
|
| 814 |
+
</template>
|
| 815 |
+
</section>
|
| 816 |
+
|
| 817 |
+
<section v-else class="empty-registry">
|
| 818 |
+
<h1>No result manifests found</h1>
|
| 819 |
+
<p>The configured bucket does not contain an aggregate manifest.</p>
|
| 820 |
+
</section>
|
| 821 |
+
</main>
|
| 822 |
+
|
| 823 |
+
<div v-if="expandedImage" class="lightbox" role="dialog" aria-modal="true" :aria-label="expandedImage.label" @click.self="closeExpanded">
|
| 824 |
+
<button type="button" class="lightbox-close" aria-label="Close image" @click="closeExpanded">×</button>
|
| 825 |
+
<div class="lightbox-content">
|
| 826 |
+
<img :src="expandedImage.src" :alt="expandedImage.label" />
|
| 827 |
+
<span>{{ expandedImage.label }}</span>
|
| 828 |
+
</div>
|
| 829 |
+
</div>
|
| 830 |
+
</div>
|
| 831 |
+
</template>
|
apps/results-dashboard/src/components/ReliableImage.vue
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<script setup lang="ts">
|
| 2 |
+
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
| 3 |
+
|
| 4 |
+
const props = withDefaults(defineProps<{
|
| 5 |
+
src: string
|
| 6 |
+
alt: string
|
| 7 |
+
label?: string
|
| 8 |
+
eager?: boolean
|
| 9 |
+
expandable?: boolean
|
| 10 |
+
}>(), {
|
| 11 |
+
label: '',
|
| 12 |
+
eager: false,
|
| 13 |
+
expandable: true,
|
| 14 |
+
})
|
| 15 |
+
|
| 16 |
+
const emit = defineEmits<{
|
| 17 |
+
expand: [payload: { src: string; label: string }]
|
| 18 |
+
}>()
|
| 19 |
+
|
| 20 |
+
const attempt = ref(0)
|
| 21 |
+
const loaded = ref(false)
|
| 22 |
+
const failed = ref(false)
|
| 23 |
+
let retryTimer: number | undefined
|
| 24 |
+
|
| 25 |
+
const resolvedSrc = computed(() => {
|
| 26 |
+
const separator = props.src.includes('?') ? '&' : '?'
|
| 27 |
+
return attempt.value ? `${props.src}${separator}retry=${attempt.value}` : props.src
|
| 28 |
+
})
|
| 29 |
+
|
| 30 |
+
function reset() {
|
| 31 |
+
if (retryTimer) window.clearTimeout(retryTimer)
|
| 32 |
+
attempt.value = 0
|
| 33 |
+
loaded.value = false
|
| 34 |
+
failed.value = false
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
function onLoad() {
|
| 38 |
+
loaded.value = true
|
| 39 |
+
failed.value = false
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
function onError() {
|
| 43 |
+
loaded.value = false
|
| 44 |
+
if (attempt.value >= 4) {
|
| 45 |
+
failed.value = true
|
| 46 |
+
return
|
| 47 |
+
}
|
| 48 |
+
const delay = 250 * 2 ** attempt.value
|
| 49 |
+
retryTimer = window.setTimeout(() => {
|
| 50 |
+
attempt.value += 1
|
| 51 |
+
}, delay)
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
function retry() {
|
| 55 |
+
attempt.value += 1
|
| 56 |
+
failed.value = false
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
function expand() {
|
| 60 |
+
if (loaded.value && props.expandable) {
|
| 61 |
+
emit('expand', { src: resolvedSrc.value, label: props.label || props.alt })
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
watch(() => props.src, reset)
|
| 66 |
+
onBeforeUnmount(() => {
|
| 67 |
+
if (retryTimer) window.clearTimeout(retryTimer)
|
| 68 |
+
})
|
| 69 |
+
</script>
|
| 70 |
+
|
| 71 |
+
<template>
|
| 72 |
+
<div class="reliable-image" :class="{ 'is-loaded': loaded, 'is-failed': failed }">
|
| 73 |
+
<div v-if="!loaded && !failed" class="image-skeleton" aria-hidden="true">
|
| 74 |
+
<span />
|
| 75 |
+
</div>
|
| 76 |
+
<img
|
| 77 |
+
:src="resolvedSrc"
|
| 78 |
+
:alt="alt"
|
| 79 |
+
:loading="eager ? 'eager' : 'lazy'"
|
| 80 |
+
:fetchpriority="eager ? 'high' : 'auto'"
|
| 81 |
+
decoding="async"
|
| 82 |
+
@load="onLoad"
|
| 83 |
+
@error="onError"
|
| 84 |
+
@click="expand"
|
| 85 |
+
/>
|
| 86 |
+
<button v-if="failed" type="button" class="image-retry" @click="retry">
|
| 87 |
+
Image unavailable · retry
|
| 88 |
+
</button>
|
| 89 |
+
<div v-if="label" class="image-label">{{ label }}</div>
|
| 90 |
+
</div>
|
| 91 |
+
</template>
|
apps/results-dashboard/src/env.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/// <reference types="vite/client" />
|
| 2 |
+
|
| 3 |
+
declare module '*.vue' {
|
| 4 |
+
import type { DefineComponent } from 'vue'
|
| 5 |
+
const component: DefineComponent<object, object, unknown>
|
| 6 |
+
export default component
|
| 7 |
+
}
|
apps/results-dashboard/src/main.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { createApp } from 'vue'
|
| 2 |
+
import App from './App.vue'
|
| 3 |
+
import './styles.css'
|
| 4 |
+
|
| 5 |
+
createApp(App).mount('#app')
|
apps/results-dashboard/src/styles.css
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
| 3 |
+
color: #e8edef;
|
| 4 |
+
background: #090c0e;
|
| 5 |
+
font-synthesis: none;
|
| 6 |
+
text-rendering: optimizeLegibility;
|
| 7 |
+
--bg: #090c0e;
|
| 8 |
+
--surface: #101518;
|
| 9 |
+
--surface-2: #151b1f;
|
| 10 |
+
--surface-3: #1b2328;
|
| 11 |
+
--line: #263037;
|
| 12 |
+
--line-soft: #1c252a;
|
| 13 |
+
--text: #e8edef;
|
| 14 |
+
--muted: #8b999f;
|
| 15 |
+
--quiet: #5d6b72;
|
| 16 |
+
--mint: #8fffc9;
|
| 17 |
+
--mint-strong: #43d891;
|
| 18 |
+
--cyan: #70d4ed;
|
| 19 |
+
--amber: #f0ba69;
|
| 20 |
+
--red: #ff8a80;
|
| 21 |
+
--radius: 14px;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
* { box-sizing: border-box; }
|
| 25 |
+
|
| 26 |
+
html { min-width: 320px; background: var(--bg); }
|
| 27 |
+
body { margin: 0; min-width: 320px; min-height: 100vh; background: var(--bg); }
|
| 28 |
+
button, input, select { font: inherit; }
|
| 29 |
+
button, a { -webkit-tap-highlight-color: transparent; }
|
| 30 |
+
button { color: inherit; }
|
| 31 |
+
|
| 32 |
+
.app-shell { min-height: 100vh; }
|
| 33 |
+
|
| 34 |
+
.topbar {
|
| 35 |
+
position: sticky;
|
| 36 |
+
z-index: 50;
|
| 37 |
+
top: 0;
|
| 38 |
+
display: flex;
|
| 39 |
+
height: 68px;
|
| 40 |
+
align-items: center;
|
| 41 |
+
justify-content: space-between;
|
| 42 |
+
padding: 0 24px;
|
| 43 |
+
border-bottom: 1px solid var(--line-soft);
|
| 44 |
+
background: rgba(9, 12, 14, .92);
|
| 45 |
+
backdrop-filter: blur(18px);
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
.brand {
|
| 49 |
+
display: flex;
|
| 50 |
+
align-items: center;
|
| 51 |
+
gap: 11px;
|
| 52 |
+
padding: 0;
|
| 53 |
+
border: 0;
|
| 54 |
+
background: none;
|
| 55 |
+
text-align: left;
|
| 56 |
+
cursor: pointer;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.brand-mark {
|
| 60 |
+
display: grid;
|
| 61 |
+
width: 34px;
|
| 62 |
+
height: 34px;
|
| 63 |
+
place-items: center;
|
| 64 |
+
border: 1px solid #487660;
|
| 65 |
+
border-radius: 9px;
|
| 66 |
+
color: var(--mint);
|
| 67 |
+
background: linear-gradient(145deg, #183426, #101b17);
|
| 68 |
+
font-weight: 800;
|
| 69 |
+
letter-spacing: -.04em;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.brand strong, .brand small { display: block; }
|
| 73 |
+
.brand strong { font-size: 14px; letter-spacing: -.01em; }
|
| 74 |
+
.brand small { margin-top: 2px; color: var(--muted); font-size: 10px; letter-spacing: .08em; text-transform: uppercase; }
|
| 75 |
+
|
| 76 |
+
.topbar-meta { display: flex; align-items: center; gap: 22px; color: var(--muted); font-size: 12px; }
|
| 77 |
+
.health, .registry-badge { display: inline-flex; align-items: center; gap: 7px; }
|
| 78 |
+
.health i, .registry-badge i, .frame-gallery-note i {
|
| 79 |
+
display: block;
|
| 80 |
+
width: 7px;
|
| 81 |
+
height: 7px;
|
| 82 |
+
border-radius: 50%;
|
| 83 |
+
background: var(--mint-strong);
|
| 84 |
+
box-shadow: 0 0 0 4px rgba(67, 216, 145, .1);
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
.icon-button, .secondary-button, .primary-button, .back-button {
|
| 88 |
+
border: 1px solid var(--line);
|
| 89 |
+
border-radius: 8px;
|
| 90 |
+
background: var(--surface-2);
|
| 91 |
+
cursor: pointer;
|
| 92 |
+
}
|
| 93 |
+
.icon-button { padding: 7px 11px; color: var(--text); font-size: 11px; }
|
| 94 |
+
.secondary-button { padding: 9px 13px; color: var(--text); font-size: 12px; font-weight: 650; }
|
| 95 |
+
.primary-button { padding: 11px 18px; border-color: #4e8f70; color: #07110c; background: var(--mint); font-weight: 750; }
|
| 96 |
+
.icon-button:hover, .secondary-button:hover, .back-button:hover { border-color: #41515a; background: var(--surface-3); }
|
| 97 |
+
button:disabled { opacity: .42; cursor: not-allowed; }
|
| 98 |
+
|
| 99 |
+
.workspace { display: grid; min-height: calc(100vh - 68px); grid-template-columns: 256px minmax(0, 1fr); }
|
| 100 |
+
|
| 101 |
+
.model-rail {
|
| 102 |
+
position: sticky;
|
| 103 |
+
top: 68px;
|
| 104 |
+
display: flex;
|
| 105 |
+
height: calc(100vh - 68px);
|
| 106 |
+
flex-direction: column;
|
| 107 |
+
padding: 22px 14px;
|
| 108 |
+
border-right: 1px solid var(--line-soft);
|
| 109 |
+
background: #0b0f11;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
.rail-heading { display: flex; align-items: center; justify-content: space-between; padding: 0 9px 12px; color: var(--muted); font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; }
|
| 113 |
+
.rail-heading b { display: grid; min-width: 21px; height: 21px; place-items: center; border-radius: 6px; background: var(--surface-2); color: var(--text); font-size: 10px; }
|
| 114 |
+
|
| 115 |
+
.model-item {
|
| 116 |
+
display: grid;
|
| 117 |
+
width: 100%;
|
| 118 |
+
grid-template-columns: 34px minmax(0, 1fr) auto;
|
| 119 |
+
align-items: center;
|
| 120 |
+
gap: 10px;
|
| 121 |
+
padding: 11px 9px;
|
| 122 |
+
border: 1px solid transparent;
|
| 123 |
+
border-radius: 10px;
|
| 124 |
+
color: var(--muted);
|
| 125 |
+
background: transparent;
|
| 126 |
+
text-align: left;
|
| 127 |
+
cursor: pointer;
|
| 128 |
+
}
|
| 129 |
+
.model-item + .model-item { margin-top: 3px; }
|
| 130 |
+
.model-item:hover { background: rgba(255, 255, 255, .025); }
|
| 131 |
+
.model-item.active { border-color: #284337; color: var(--text); background: linear-gradient(100deg, rgba(65, 180, 119, .12), rgba(65, 180, 119, .025)); }
|
| 132 |
+
.model-avatar { display: grid; width: 34px; height: 34px; place-items: center; border: 1px solid var(--line); border-radius: 9px; color: var(--muted); background: var(--surface); font-size: 12px; font-weight: 750; }
|
| 133 |
+
.model-item.active .model-avatar { border-color: #3c6a54; color: var(--mint); background: #12241b; }
|
| 134 |
+
.model-copy { min-width: 0; }
|
| 135 |
+
.model-copy strong, .model-copy small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
| 136 |
+
.model-copy strong { font-size: 12px; font-weight: 650; }
|
| 137 |
+
.model-copy small { margin-top: 4px; color: var(--quiet); font-size: 9px; }
|
| 138 |
+
.model-score { color: var(--text); font-size: 12px; font-variant-numeric: tabular-nums; }
|
| 139 |
+
.rail-footnote { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: auto; padding: 14px 10px 0; border-top: 1px solid var(--line-soft); color: var(--muted); font-size: 10px; }
|
| 140 |
+
.rail-footnote small { grid-column: 1 / -1; color: var(--quiet); }
|
| 141 |
+
|
| 142 |
+
.content { min-width: 0; padding: 34px clamp(24px, 4vw, 62px) 80px; }
|
| 143 |
+
.content-heading { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; }
|
| 144 |
+
.eyebrow { color: var(--mint-strong); font-size: 10px; font-weight: 750; letter-spacing: .12em; text-transform: uppercase; }
|
| 145 |
+
.content-heading h1 { margin: 9px 0 5px; font-size: clamp(28px, 3vw, 42px); font-weight: 680; letter-spacing: -.045em; }
|
| 146 |
+
.content-heading p { margin: 0; color: var(--muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
|
| 147 |
+
.heading-actions { display: flex; align-items: center; gap: 12px; }
|
| 148 |
+
.registry-badge { padding: 8px 10px; border: 1px solid #264333; border-radius: 8px; color: #a7ddbe; background: rgba(38, 86, 57, .14); font-size: 10px; font-weight: 650; text-transform: uppercase; letter-spacing: .06em; }
|
| 149 |
+
|
| 150 |
+
.section-tabs { display: flex; gap: 24px; margin: 27px 0 25px; border-bottom: 1px solid var(--line-soft); }
|
| 151 |
+
.section-tabs button { position: relative; padding: 0 1px 12px; border: 0; color: var(--muted); background: none; font-size: 12px; font-weight: 650; cursor: pointer; }
|
| 152 |
+
.section-tabs button span { margin-left: 4px; color: var(--quiet); }
|
| 153 |
+
.section-tabs button.active { color: var(--text); }
|
| 154 |
+
.section-tabs button.active::after { position: absolute; right: 0; bottom: -1px; left: 0; height: 2px; border-radius: 2px; background: var(--mint-strong); content: ""; }
|
| 155 |
+
|
| 156 |
+
.error-banner { display: flex; align-items: center; justify-content: space-between; margin: 0 0 18px; padding: 10px 12px; border: 1px solid #5d3533; border-radius: 9px; color: #ffb2ab; background: #211311; font-size: 12px; }
|
| 157 |
+
.error-banner button { border: 0; color: inherit; background: none; cursor: pointer; }
|
| 158 |
+
|
| 159 |
+
.stat-grid { display: grid; grid-template-columns: 1.4fr repeat(4, minmax(0, 1fr)); gap: 10px; }
|
| 160 |
+
.stat-card { min-width: 0; padding: 18px; border: 1px solid var(--line-soft); border-radius: var(--radius); background: linear-gradient(145deg, #12181b, #0f1417); }
|
| 161 |
+
.stat-card > span { display: block; color: var(--muted); font-size: 10px; font-weight: 650; letter-spacing: .05em; text-transform: uppercase; }
|
| 162 |
+
.stat-card > strong { display: block; margin: 16px 0 8px; font-size: 29px; font-weight: 600; line-height: 1; letter-spacing: -.04em; font-variant-numeric: tabular-nums; }
|
| 163 |
+
.stat-card > strong em { color: var(--quiet); font-size: 15px; font-style: normal; font-weight: 500; }
|
| 164 |
+
.stat-card > small { display: block; overflow: hidden; color: var(--quiet); font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
|
| 165 |
+
.hero-stat { border-color: #284538; background: radial-gradient(circle at 15% 0, rgba(85, 223, 151, .12), transparent 55%), #111916; }
|
| 166 |
+
.hero-stat > strong { color: var(--mint); }
|
| 167 |
+
.score-track { height: 3px; margin-top: 13px; overflow: hidden; border-radius: 10px; background: #24302a; }
|
| 168 |
+
.score-track i { display: block; height: 100%; border-radius: inherit; background: var(--mint-strong); }
|
| 169 |
+
|
| 170 |
+
.overview-grid { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(300px, .8fr); gap: 12px; margin-top: 12px; }
|
| 171 |
+
.panel { border: 1px solid var(--line-soft); border-radius: var(--radius); background: var(--surface); }
|
| 172 |
+
.panel-heading { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 20px 21px 17px; border-bottom: 1px solid var(--line-soft); }
|
| 173 |
+
.panel-heading h2 { margin: 5px 0 0; font-size: 15px; font-weight: 650; letter-spacing: -.015em; }
|
| 174 |
+
.panel-note { color: var(--quiet); font-size: 10px; }
|
| 175 |
+
.legend { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 9px; }
|
| 176 |
+
.legend i { width: 7px; height: 7px; border-radius: 2px; }
|
| 177 |
+
.legend i.deterministic { margin-left: 7px; }
|
| 178 |
+
.vision { background: var(--mint-strong) !important; }
|
| 179 |
+
.deterministic { background: var(--cyan) !important; }
|
| 180 |
+
|
| 181 |
+
.category-list { padding: 9px 20px 16px; }
|
| 182 |
+
.category-row { display: grid; grid-template-columns: 112px minmax(80px, 1fr) 76px; align-items: center; gap: 14px; padding: 10px 0; }
|
| 183 |
+
.category-name strong, .category-name small { display: block; }
|
| 184 |
+
.category-name strong { font-size: 10px; font-weight: 680; }
|
| 185 |
+
.category-name small { margin-top: 3px; color: var(--quiet); font-size: 9px; }
|
| 186 |
+
.dual-bars { display: grid; gap: 4px; }
|
| 187 |
+
.dual-bars div { height: 5px; overflow: hidden; border-radius: 4px; background: #20292e; }
|
| 188 |
+
.dual-bars i { display: block; height: 100%; border-radius: inherit; }
|
| 189 |
+
.category-values { display: flex; justify-content: flex-end; gap: 10px; font-size: 10px; font-variant-numeric: tabular-nums; }
|
| 190 |
+
.category-values strong { color: var(--mint); }
|
| 191 |
+
.category-values span { color: var(--cyan); }
|
| 192 |
+
|
| 193 |
+
.histogram { display: flex; height: 160px; align-items: flex-end; gap: 6px; padding: 26px 20px 20px; }
|
| 194 |
+
.histogram-column { display: grid; height: 100%; flex: 1; grid-template-rows: 18px minmax(4px, 1fr) 14px; align-items: end; text-align: center; }
|
| 195 |
+
.histogram-column span { align-self: start; color: var(--muted); font-size: 8px; }
|
| 196 |
+
.histogram-column i { display: block; min-height: 3px; border-radius: 4px 4px 1px 1px; background: linear-gradient(#6be5a6, #2e9b67); }
|
| 197 |
+
.histogram-column small { margin-top: 4px; color: var(--quiet); font-size: 8px; }
|
| 198 |
+
.distribution-summary { display: grid; grid-template-columns: repeat(3, 1fr); border-top: 1px solid var(--line-soft); }
|
| 199 |
+
.distribution-summary div { padding: 14px 16px; }
|
| 200 |
+
.distribution-summary div + div { border-left: 1px solid var(--line-soft); }
|
| 201 |
+
.distribution-summary span, .distribution-summary strong { display: block; }
|
| 202 |
+
.distribution-summary span { color: var(--quiet); font-size: 8px; text-transform: uppercase; }
|
| 203 |
+
.distribution-summary strong { margin-top: 6px; font-size: 14px; font-weight: 600; }
|
| 204 |
+
|
| 205 |
+
.comparison-panel { margin-top: 12px; }
|
| 206 |
+
.table-scroll { overflow-x: auto; }
|
| 207 |
+
table { width: 100%; border-collapse: collapse; }
|
| 208 |
+
th { padding: 11px 18px; color: var(--quiet); font-size: 8px; font-weight: 700; letter-spacing: .07em; text-align: left; text-transform: uppercase; white-space: nowrap; }
|
| 209 |
+
td { padding: 14px 18px; border-top: 1px solid var(--line-soft); color: var(--muted); font-size: 11px; white-space: nowrap; }
|
| 210 |
+
td strong, td small { display: block; }
|
| 211 |
+
td strong { color: var(--text); font-weight: 620; }
|
| 212 |
+
td small { max-width: 360px; margin-top: 4px; overflow: hidden; color: var(--quiet); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 8px; text-overflow: ellipsis; }
|
| 213 |
+
.comparison-table tbody tr { cursor: pointer; }
|
| 214 |
+
.comparison-table tbody tr:hover, .comparison-table tbody tr.selected { background: rgba(86, 208, 143, .035); }
|
| 215 |
+
|
| 216 |
+
.task-toolbar { display: grid; grid-template-columns: minmax(220px, 1.4fr) repeat(4, minmax(110px, .55fr)) auto; align-items: end; gap: 9px; margin-bottom: 12px; }
|
| 217 |
+
.task-toolbar label { display: grid; gap: 6px; }
|
| 218 |
+
.task-toolbar label > span { color: var(--quiet); font-size: 8px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
|
| 219 |
+
.task-toolbar input, .task-toolbar select { width: 100%; height: 35px; padding: 0 10px; border: 1px solid var(--line); border-radius: 8px; outline: none; color: var(--text); background: var(--surface); font-size: 11px; }
|
| 220 |
+
.task-toolbar input:focus, .task-toolbar select:focus { border-color: #47725e; box-shadow: 0 0 0 3px rgba(72, 176, 123, .08); }
|
| 221 |
+
.result-count { padding: 0 4px 10px; color: var(--muted); font-size: 10px; white-space: nowrap; }
|
| 222 |
+
.task-table-panel { overflow: hidden; }
|
| 223 |
+
.task-table tbody tr { cursor: pointer; outline: none; }
|
| 224 |
+
.task-table tbody tr:hover, .task-table tbody tr:focus { background: #141b1e; }
|
| 225 |
+
.type-chip { display: inline-grid; min-width: 25px; height: 20px; margin-right: 6px; place-items: center; border: 1px solid var(--line); border-radius: 5px; color: var(--text); background: var(--surface-2); font-size: 8px; font-weight: 750; }
|
| 226 |
+
.status-pill { display: inline-flex; align-items: center; gap: 6px; padding: 4px 7px; border: 1px solid var(--line); border-radius: 20px; font-size: 8px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; }
|
| 227 |
+
.status-pill i { width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
|
| 228 |
+
.status-pill.positive { border-color: #2b543e; color: #75dba2; background: #102219; }
|
| 229 |
+
.status-pill.warning { border-color: #5e4828; color: #f0ba69; background: #211a10; }
|
| 230 |
+
.status-pill.negative { border-color: #5b302d; color: #ff8a80; background: #221210; }
|
| 231 |
+
.status-pill.neutral { color: var(--muted); }
|
| 232 |
+
.verdict { color: var(--muted); font-size: 9px; }
|
| 233 |
+
.verdict.positive { color: var(--mint); }
|
| 234 |
+
.verdict.good { color: #92d8e8; }
|
| 235 |
+
.verdict.warning { color: var(--amber); }
|
| 236 |
+
.verdict.negative { color: var(--red); }
|
| 237 |
+
.row-arrow { border: 0; color: var(--quiet); background: none; cursor: pointer; }
|
| 238 |
+
|
| 239 |
+
.task-titlebar { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 16px; margin-bottom: 15px; }
|
| 240 |
+
.back-button { padding: 8px 10px; color: var(--muted); font-size: 10px; }
|
| 241 |
+
.task-titlebar h2 { margin: 4px 0 0; font-size: 20px; letter-spacing: -.025em; }
|
| 242 |
+
.task-title-scores { display: flex; align-items: center; gap: 16px; }
|
| 243 |
+
.task-title-scores > span:not(.status-pill) { color: var(--muted); font-size: 9px; }
|
| 244 |
+
.task-title-scores > span strong { margin-left: 4px; color: var(--text); font-size: 13px; }
|
| 245 |
+
|
| 246 |
+
.artifact-strip { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)) minmax(240px, .78fr); gap: 9px; margin-bottom: 12px; }
|
| 247 |
+
.artifact-strip .reliable-image { min-height: 180px; }
|
| 248 |
+
.rubric-card { padding: 15px; border: 1px solid var(--line-soft); border-radius: var(--radius); background: var(--surface); }
|
| 249 |
+
.rubric-score { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; }
|
| 250 |
+
.rubric-score span { color: var(--muted); font-size: 9px; text-transform: uppercase; }
|
| 251 |
+
.rubric-score strong { color: var(--mint); font-size: 24px; font-weight: 600; }
|
| 252 |
+
.rubric-row { display: grid; grid-template-columns: 67px 1fr 31px; align-items: center; gap: 7px; margin-top: 9px; color: var(--muted); font-size: 8px; }
|
| 253 |
+
.rubric-row > div { height: 3px; overflow: hidden; border-radius: 4px; background: #263038; }
|
| 254 |
+
.rubric-row i { display: block; height: 100%; background: var(--mint-strong); }
|
| 255 |
+
.rubric-row b { color: var(--text); text-align: right; font-weight: 550; }
|
| 256 |
+
|
| 257 |
+
.reliable-image { position: relative; overflow: hidden; min-height: 110px; border: 1px solid var(--line-soft); border-radius: 11px; background: #080b0d; }
|
| 258 |
+
.reliable-image img { display: block; width: 100%; height: 100%; min-height: inherit; object-fit: contain; opacity: 0; transition: opacity .18s ease; cursor: zoom-in; }
|
| 259 |
+
.reliable-image.is-loaded img { opacity: 1; }
|
| 260 |
+
.image-label { position: absolute; right: 8px; bottom: 8px; padding: 5px 7px; border: 1px solid rgba(255,255,255,.1); border-radius: 6px; color: #dfe8e4; background: rgba(8, 12, 13, .78); backdrop-filter: blur(8px); font-size: 8px; font-weight: 650; text-transform: uppercase; letter-spacing: .06em; }
|
| 261 |
+
.image-skeleton { position: absolute; inset: 0; overflow: hidden; background: #0d1214; }
|
| 262 |
+
.image-skeleton span { position: absolute; inset: 0; transform: translateX(-100%); background: linear-gradient(90deg, transparent, rgba(255,255,255,.045), transparent); animation: shimmer 1.5s infinite; }
|
| 263 |
+
.image-retry { position: absolute; inset: 0; width: 100%; border: 0; color: var(--red); background: #151011; font-size: 10px; cursor: pointer; }
|
| 264 |
+
@keyframes shimmer { to { transform: translateX(100%); } }
|
| 265 |
+
|
| 266 |
+
.trace-workbench { display: grid; min-height: 670px; grid-template-columns: 226px minmax(0, 1fr); overflow: hidden; border: 1px solid var(--line-soft); border-radius: var(--radius); background: var(--surface); }
|
| 267 |
+
.turn-list { max-height: 760px; overflow-y: auto; border-right: 1px solid var(--line-soft); background: #0d1214; }
|
| 268 |
+
.turn-list-heading { position: sticky; z-index: 2; top: 0; display: flex; align-items: flex-end; justify-content: space-between; padding: 15px 13px; border-bottom: 1px solid var(--line-soft); background: rgba(13, 18, 20, .96); backdrop-filter: blur(12px); }
|
| 269 |
+
.turn-list-heading span, .turn-list-heading strong { display: block; }
|
| 270 |
+
.turn-list-heading span { color: var(--quiet); font-size: 8px; text-transform: uppercase; }
|
| 271 |
+
.turn-list-heading strong { margin-top: 4px; font-size: 11px; }
|
| 272 |
+
.turn-list-heading small { color: var(--quiet); font-size: 8px; }
|
| 273 |
+
.turn-item { display: grid; width: 100%; grid-template-columns: 27px 1fr auto; align-items: center; gap: 8px; padding: 10px 11px; border: 0; border-bottom: 1px solid #171e21; color: var(--muted); background: transparent; text-align: left; cursor: pointer; }
|
| 274 |
+
.turn-item:hover { background: rgba(255,255,255,.02); }
|
| 275 |
+
.turn-item.active { color: var(--text); background: linear-gradient(90deg, rgba(61, 186, 121, .15), transparent); box-shadow: inset 2px 0 var(--mint-strong); }
|
| 276 |
+
.turn-number { color: var(--quiet); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 9px; }
|
| 277 |
+
.turn-summary strong, .turn-summary small { display: block; }
|
| 278 |
+
.turn-summary strong { font-size: 9px; font-weight: 650; }
|
| 279 |
+
.turn-summary small { margin-top: 3px; color: var(--quiet); font-size: 8px; }
|
| 280 |
+
.turn-latency { color: var(--quiet); font-size: 8px; }
|
| 281 |
+
|
| 282 |
+
.turn-stage { min-width: 0; padding: 18px; }
|
| 283 |
+
.turn-stage-heading { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
|
| 284 |
+
.turn-stage-heading h3 { margin: 5px 0 0; font-size: 15px; }
|
| 285 |
+
.turn-controls { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 9px; }
|
| 286 |
+
.turn-controls button { display: grid; width: 27px; height: 27px; place-items: center; border: 1px solid var(--line); border-radius: 7px; background: var(--surface-2); cursor: pointer; }
|
| 287 |
+
.turn-images { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; }
|
| 288 |
+
.turn-images .reliable-image, .empty-frame { aspect-ratio: 16 / 9; }
|
| 289 |
+
.empty-frame { display: grid; place-items: center; border: 1px dashed var(--line); border-radius: 11px; color: var(--quiet); font-size: 10px; }
|
| 290 |
+
.turn-facts { display: flex; flex-wrap: wrap; gap: 19px; margin: 11px 0; padding: 9px 11px; border: 1px solid var(--line-soft); border-radius: 8px; background: #0e1315; color: var(--quiet); font-size: 8px; }
|
| 291 |
+
.turn-facts strong { margin-left: 4px; color: var(--text); font-weight: 550; }
|
| 292 |
+
.trace-detail-grid { display: grid; grid-template-columns: .85fr 1.15fr; gap: 9px; }
|
| 293 |
+
.trace-card { min-width: 0; overflow: hidden; border: 1px solid var(--line-soft); border-radius: 10px; background: #0d1214; }
|
| 294 |
+
.trace-card-heading { display: flex; align-items: center; justify-content: space-between; padding: 10px 12px; border-bottom: 1px solid var(--line-soft); }
|
| 295 |
+
.trace-card-heading h4 { margin: 0; font-size: 10px; font-weight: 650; }
|
| 296 |
+
.trace-card-heading span { color: var(--quiet); font-size: 8px; }
|
| 297 |
+
.trace-card > pre, .action-item pre, details pre { max-height: 270px; margin: 0; overflow: auto; padding: 12px; color: #b7c3c7; font: 9px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; word-break: break-word; }
|
| 298 |
+
.action-list { max-height: 295px; overflow-y: auto; }
|
| 299 |
+
.action-item { display: grid; grid-template-columns: 27px 1fr; }
|
| 300 |
+
.action-item + .action-item { border-top: 1px solid var(--line-soft); }
|
| 301 |
+
.action-item > span { padding: 13px 0 0 10px; color: var(--mint-strong); font: 8px ui-monospace, SFMono-Regular, Menlo, monospace; }
|
| 302 |
+
.action-item pre { padding-left: 5px; }
|
| 303 |
+
.receipts-card { margin-top: 9px; }
|
| 304 |
+
.receipt-list { max-height: 310px; overflow-y: auto; }
|
| 305 |
+
.receipt-list details + details { border-top: 1px solid var(--line-soft); }
|
| 306 |
+
.receipt-list summary { display: grid; grid-template-columns: 48px 1fr auto; gap: 9px; padding: 10px 12px; color: var(--muted); font-size: 9px; cursor: pointer; list-style: none; }
|
| 307 |
+
.receipt-list summary::-webkit-details-marker { display: none; }
|
| 308 |
+
.receipt-list summary span { color: var(--quiet); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
| 309 |
+
.receipt-list summary strong { color: var(--text); }
|
| 310 |
+
.receipt-list summary small { color: var(--mint-strong); }
|
| 311 |
+
.empty-copy, .empty-trace { padding: 25px; color: var(--quiet); font-size: 10px; text-align: center; }
|
| 312 |
+
.empty-trace { display: grid; place-items: center; }
|
| 313 |
+
.raw-turn { margin-top: 9px; overflow: hidden; border: 1px solid var(--line-soft); border-radius: 9px; background: #0d1214; }
|
| 314 |
+
.raw-turn summary { padding: 10px 12px; color: var(--muted); font-size: 9px; cursor: pointer; }
|
| 315 |
+
.raw-turn pre { max-height: 420px; margin: 0; overflow: auto; padding: 12px; border-top: 1px solid var(--line-soft); color: #b7c3c7; font: 9px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; word-break: break-word; }
|
| 316 |
+
|
| 317 |
+
.downloads-panel { margin-top: 12px; }
|
| 318 |
+
.download-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 1px; background: var(--line-soft); }
|
| 319 |
+
.download-grid a { display: grid; min-width: 0; grid-template-columns: 1fr auto; padding: 13px 14px; color: var(--text); background: var(--surface); text-decoration: none; }
|
| 320 |
+
.download-grid a:hover { background: var(--surface-2); }
|
| 321 |
+
.download-grid span, .download-grid small { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
| 322 |
+
.download-grid span { font-size: 9px; font-weight: 650; }
|
| 323 |
+
.download-grid small { grid-column: 1; margin-top: 4px; color: var(--quiet); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 7px; }
|
| 324 |
+
.download-grid b { grid-row: 1 / 3; grid-column: 2; align-self: center; color: var(--mint-strong); font-size: 14px; }
|
| 325 |
+
|
| 326 |
+
.frame-progress { text-align: right; }
|
| 327 |
+
.frame-progress strong, .frame-progress span { display: block; }
|
| 328 |
+
.frame-progress strong { font-size: 18px; }
|
| 329 |
+
.frame-progress span { margin-top: 3px; color: var(--quiet); font-size: 9px; }
|
| 330 |
+
.frame-gallery-note { display: flex; justify-content: space-between; margin-bottom: 12px; padding: 9px 11px; border: 1px solid var(--line-soft); border-radius: 8px; color: var(--quiet); background: var(--surface); font-size: 9px; }
|
| 331 |
+
.frame-gallery-note span { display: flex; align-items: center; gap: 8px; }
|
| 332 |
+
.frame-gallery { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
|
| 333 |
+
.frame-tile { padding: 0; overflow: hidden; border: 0; border-radius: 11px; background: none; cursor: zoom-in; }
|
| 334 |
+
.frame-tile .reliable-image { aspect-ratio: 16 / 9; pointer-events: none; }
|
| 335 |
+
.frame-tile:hover .reliable-image { border-color: #48725f; box-shadow: 0 0 0 2px rgba(67, 216, 145, .08); }
|
| 336 |
+
.load-more { display: flex; justify-content: center; padding: 24px; color: var(--muted); font-size: 10px; }
|
| 337 |
+
|
| 338 |
+
.lightbox { position: fixed; z-index: 100; inset: 0; display: grid; place-items: center; padding: 44px; background: rgba(2, 4, 5, .94); backdrop-filter: blur(12px); }
|
| 339 |
+
.lightbox-close { position: absolute; top: 18px; right: 23px; width: 36px; height: 36px; border: 1px solid #344048; border-radius: 50%; color: var(--text); background: #101518; font-size: 23px; cursor: pointer; }
|
| 340 |
+
.lightbox-content { display: grid; max-width: min(96vw, 1700px); max-height: 92vh; gap: 9px; justify-items: center; }
|
| 341 |
+
.lightbox-content img { max-width: 100%; max-height: calc(92vh - 30px); border: 1px solid #29343a; border-radius: 9px; background: #000; object-fit: contain; }
|
| 342 |
+
.lightbox-content span { color: var(--muted); font-size: 10px; }
|
| 343 |
+
|
| 344 |
+
.loading-screen, .task-loading, .empty-registry { display: grid; min-height: 60vh; place-content: center; justify-items: center; color: var(--muted); font-size: 11px; }
|
| 345 |
+
.task-loading { min-height: 420px; }
|
| 346 |
+
.loading-orbit { position: relative; width: 34px; height: 34px; border: 1px solid #2a3932; border-radius: 50%; }
|
| 347 |
+
.loading-orbit span { position: absolute; top: -2px; left: 15px; width: 4px; height: 4px; border-radius: 50%; background: var(--mint); animation: orbit 1s linear infinite; transform-origin: 2px 19px; }
|
| 348 |
+
@keyframes orbit { to { transform: rotate(360deg); } }
|
| 349 |
+
|
| 350 |
+
@media (max-width: 1180px) {
|
| 351 |
+
.stat-grid { grid-template-columns: repeat(3, 1fr); }
|
| 352 |
+
.hero-stat { grid-column: span 1; }
|
| 353 |
+
.overview-grid { grid-template-columns: 1fr; }
|
| 354 |
+
.artifact-strip { grid-template-columns: repeat(3, 1fr); }
|
| 355 |
+
.rubric-card { grid-column: 1 / -1; }
|
| 356 |
+
.download-grid { grid-template-columns: repeat(3, 1fr); }
|
| 357 |
+
.task-toolbar { grid-template-columns: repeat(3, 1fr); }
|
| 358 |
+
.search-field { grid-column: span 2; }
|
| 359 |
+
.frame-gallery { grid-template-columns: repeat(3, 1fr); }
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
@media (max-width: 850px) {
|
| 363 |
+
.topbar-meta > span:not(.health) { display: none; }
|
| 364 |
+
.workspace { grid-template-columns: 1fr; }
|
| 365 |
+
.model-rail { position: static; display: flex; width: 100%; height: auto; flex-direction: row; overflow-x: auto; padding: 10px 14px; border-right: 0; border-bottom: 1px solid var(--line-soft); }
|
| 366 |
+
.rail-heading, .rail-footnote { display: none; }
|
| 367 |
+
.model-item { min-width: 205px; }
|
| 368 |
+
.content { padding: 25px 16px 60px; }
|
| 369 |
+
.content-heading { align-items: flex-start; }
|
| 370 |
+
.registry-badge { display: none; }
|
| 371 |
+
.stat-grid { grid-template-columns: repeat(2, 1fr); }
|
| 372 |
+
.trace-workbench { grid-template-columns: 1fr; }
|
| 373 |
+
.turn-list { display: flex; max-height: none; overflow-x: auto; border-right: 0; border-bottom: 1px solid var(--line-soft); }
|
| 374 |
+
.turn-list-heading { min-width: 130px; }
|
| 375 |
+
.turn-item { min-width: 150px; border-right: 1px solid var(--line-soft); border-bottom: 0; }
|
| 376 |
+
.turn-images, .trace-detail-grid { grid-template-columns: 1fr; }
|
| 377 |
+
.download-grid { grid-template-columns: repeat(2, 1fr); }
|
| 378 |
+
.frame-gallery { grid-template-columns: repeat(2, 1fr); }
|
| 379 |
+
.task-title-scores { display: none; }
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
@media (max-width: 560px) {
|
| 383 |
+
.topbar { padding: 0 14px; }
|
| 384 |
+
.health { display: none; }
|
| 385 |
+
.content-heading h1 { font-size: 28px; }
|
| 386 |
+
.heading-actions .secondary-button { display: none; }
|
| 387 |
+
.section-tabs { gap: 15px; overflow-x: auto; }
|
| 388 |
+
.stat-grid { grid-template-columns: 1fr; }
|
| 389 |
+
.category-row { grid-template-columns: 92px 1fr 60px; }
|
| 390 |
+
.task-toolbar { grid-template-columns: 1fr 1fr; }
|
| 391 |
+
.search-field { grid-column: 1 / -1; }
|
| 392 |
+
.result-count { display: none; }
|
| 393 |
+
.artifact-strip { grid-template-columns: 1fr; }
|
| 394 |
+
.rubric-card { grid-column: auto; }
|
| 395 |
+
.turn-stage { padding: 12px; }
|
| 396 |
+
.download-grid { grid-template-columns: 1fr; }
|
| 397 |
+
.frame-gallery { grid-template-columns: 1fr; }
|
| 398 |
+
.frame-gallery-note { display: grid; gap: 6px; }
|
| 399 |
+
.lightbox { padding: 48px 8px 20px; }
|
| 400 |
+
}
|
apps/results-dashboard/src/types.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export type ScoreStats = {
|
| 2 |
+
count: number
|
| 3 |
+
mean: number | null
|
| 4 |
+
median: number | null
|
| 5 |
+
min: number | null
|
| 6 |
+
max: number | null
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
export type ResultTask = {
|
| 10 |
+
index: number
|
| 11 |
+
taskId: string
|
| 12 |
+
drawingType: '2d' | '3d'
|
| 13 |
+
level: string
|
| 14 |
+
category: string
|
| 15 |
+
included: boolean
|
| 16 |
+
executionStatus: string
|
| 17 |
+
evaluationStatus: string
|
| 18 |
+
selectedAttempt: string
|
| 19 |
+
selectedRunId: string
|
| 20 |
+
destinationPrefix: string
|
| 21 |
+
sourcePrefix: string
|
| 22 |
+
objectCount: number
|
| 23 |
+
bytes: number
|
| 24 |
+
deterministicScore: number | null
|
| 25 |
+
deterministicValid: boolean
|
| 26 |
+
visionScore: number | null
|
| 27 |
+
visionVerdict: string
|
| 28 |
+
evaluatorVersion: string
|
| 29 |
+
controllerClean: boolean
|
| 30 |
+
exclusionReason: string | null
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
export type CategorySummary = {
|
| 34 |
+
category: string
|
| 35 |
+
drawingType: '2d' | '3d'
|
| 36 |
+
level: string
|
| 37 |
+
tasks: number
|
| 38 |
+
deterministic: ScoreStats
|
| 39 |
+
vision: ScoreStats
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
export type ModelResult = {
|
| 43 |
+
slug: string
|
| 44 |
+
manifestKey: string
|
| 45 |
+
modelPrefix: string
|
| 46 |
+
modelId: string
|
| 47 |
+
aggregateId: string
|
| 48 |
+
createdAt: string | null
|
| 49 |
+
sourceBucket: string
|
| 50 |
+
destinationBucket: string
|
| 51 |
+
evaluatorVersion: string
|
| 52 |
+
tasks: ResultTask[]
|
| 53 |
+
categories: CategorySummary[]
|
| 54 |
+
totals: {
|
| 55 |
+
expected: number
|
| 56 |
+
included: number
|
| 57 |
+
excluded: number
|
| 58 |
+
clean: number
|
| 59 |
+
budgetExhausted: number
|
| 60 |
+
bytes: number
|
| 61 |
+
objects: number
|
| 62 |
+
}
|
| 63 |
+
scores: {
|
| 64 |
+
deterministic: ScoreStats
|
| 65 |
+
vision: ScoreStats
|
| 66 |
+
deterministicHistogram: number[]
|
| 67 |
+
visionHistogram: number[]
|
| 68 |
+
}
|
| 69 |
+
selectionPolicy: Record<string, unknown> | null
|
| 70 |
+
sourceRuns: Record<string, unknown>[]
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
export type TaskMetric = {
|
| 74 |
+
taskId: string
|
| 75 |
+
drawingType: '2d' | '3d'
|
| 76 |
+
level: string
|
| 77 |
+
acceptedActions: number | null
|
| 78 |
+
elapsedSeconds: number | null
|
| 79 |
+
rolloutSeconds: number | null
|
| 80 |
+
turns: number | null
|
| 81 |
+
screenshots: number | null
|
| 82 |
+
inputTokens: number | null
|
| 83 |
+
cachedInputTokens: number | null
|
| 84 |
+
cacheCreationTokens: number | null
|
| 85 |
+
outputTokens: number | null
|
| 86 |
+
reasoningTokens: number | null
|
| 87 |
+
totalTokens: number | null
|
| 88 |
+
artifactBytes: number | null
|
| 89 |
+
stopReason: string
|
| 90 |
+
deterministicScore: number | null
|
| 91 |
+
visionScore: number | null
|
| 92 |
+
visionVerdict: string
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
export type ModelMetrics = {
|
| 96 |
+
tasks: TaskMetric[]
|
| 97 |
+
totals: Record<string, number>
|
| 98 |
+
medians: Record<string, number | null>
|
| 99 |
+
scores: ModelResult['scores']
|
| 100 |
+
categories: CategorySummary[]
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
export type TaskDetail = {
|
| 104 |
+
model: { slug: string; modelId: string; aggregateId: string }
|
| 105 |
+
task: ResultTask
|
| 106 |
+
result: Record<string, unknown> | null
|
| 107 |
+
runState: Record<string, unknown> | null
|
| 108 |
+
evaluationScore: Record<string, unknown> | null
|
| 109 |
+
evaluationStatus: Record<string, unknown> | null
|
| 110 |
+
visionScore: Record<string, unknown> | null
|
| 111 |
+
frameCount: number
|
| 112 |
+
turnCount: number
|
| 113 |
+
media: Record<'reference' | 'final' | 'diff' | 'dwg', string>
|
| 114 |
+
downloads: Array<{ label: string; path: string }>
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
export type TraceTurn = {
|
| 118 |
+
index: number
|
| 119 |
+
turn: number
|
| 120 |
+
toolUseId: string
|
| 121 |
+
latencyMs: number | null
|
| 122 |
+
stopReason: string
|
| 123 |
+
requestAttempts: number | null
|
| 124 |
+
validationRetries: number | null
|
| 125 |
+
discardedToolUses: number | null
|
| 126 |
+
usage: Record<string, number> | null
|
| 127 |
+
text: unknown
|
| 128 |
+
actions: Record<string, unknown>[]
|
| 129 |
+
actionCount: number
|
| 130 |
+
inputFrameSequence: number | null
|
| 131 |
+
outputFrameSequence: number | null
|
| 132 |
+
firstActionIndex: number | null
|
| 133 |
+
lastActionIndex: number | null
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
export type TraceIndex = {
|
| 137 |
+
taskId: string
|
| 138 |
+
turns: TraceTurn[]
|
| 139 |
+
receiptCount: number
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
export type TurnDetail = {
|
| 143 |
+
turn: Record<string, unknown>
|
| 144 |
+
receipts: Array<{
|
| 145 |
+
actionIndex: number | null
|
| 146 |
+
batchIndex: number | null
|
| 147 |
+
batchSize: number | null
|
| 148 |
+
toolUseId: string | null
|
| 149 |
+
action: Record<string, unknown> | null
|
| 150 |
+
receipt: Record<string, unknown> | null
|
| 151 |
+
requestId: string | null
|
| 152 |
+
idempotentReplay: boolean
|
| 153 |
+
}>
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
export type Frame = { sequence: number; name: string; path: string }
|
| 157 |
+
|
| 158 |
+
export type FramePage = {
|
| 159 |
+
offset: number
|
| 160 |
+
limit: number
|
| 161 |
+
total: number
|
| 162 |
+
hasMore: boolean
|
| 163 |
+
frames: Frame[]
|
| 164 |
+
}
|
apps/results-dashboard/tests/results-api.test.mjs
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import assert from 'node:assert/strict'
|
| 2 |
+
import test from 'node:test'
|
| 3 |
+
import { normalizeManifest, ResultsRepository } from '../results-api.mjs'
|
| 4 |
+
|
| 5 |
+
const manifest = {
|
| 6 |
+
aggregate_id: 'aggregate-1',
|
| 7 |
+
model_id: 'gpt-5.6-terra',
|
| 8 |
+
created_at: '2026-07-22T00:00:00Z',
|
| 9 |
+
summary: { expected_tasks: 2 },
|
| 10 |
+
tasks: [
|
| 11 |
+
{
|
| 12 |
+
task_id: 'task-001',
|
| 13 |
+
included: true,
|
| 14 |
+
execution_status: 'completed',
|
| 15 |
+
destination_prefix: 'terra/artifacts//controller/run/task-001/',
|
| 16 |
+
object_count: 20,
|
| 17 |
+
bytes: 100,
|
| 18 |
+
deterministic_score: 0.4,
|
| 19 |
+
vision_score: 0.8,
|
| 20 |
+
vision_verdict: 'mostly_acquired',
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
task_id: 'task-049',
|
| 24 |
+
included: true,
|
| 25 |
+
execution_status: 'budget_exhausted',
|
| 26 |
+
controller_clean: false,
|
| 27 |
+
destination_prefix: 'terra/artifacts//controller/run/task-049/',
|
| 28 |
+
object_count: 30,
|
| 29 |
+
bytes: 200,
|
| 30 |
+
deterministic_score: 0.2,
|
| 31 |
+
vision_score: 0.6,
|
| 32 |
+
vision_verdict: 'partial',
|
| 33 |
+
},
|
| 34 |
+
],
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
test('normalizes model and category statistics without blending evaluator scores', () => {
|
| 38 |
+
const result = normalizeManifest(manifest, { manifestKey: 'terra/aggregate/manifest.json' })
|
| 39 |
+
assert.equal(result.slug, 'terra')
|
| 40 |
+
assert.equal(result.totals.included, 2)
|
| 41 |
+
assert.equal(result.totals.budgetExhausted, 1)
|
| 42 |
+
assert.equal(result.totals.objects, 50)
|
| 43 |
+
assert.equal(result.scores.vision.mean, 0.7)
|
| 44 |
+
assert.equal(result.scores.deterministic.mean, 0.30000000000000004)
|
| 45 |
+
assert.deepEqual(result.categories.map((category) => category.category), ['2d-basic', '3d-project'])
|
| 46 |
+
})
|
| 47 |
+
|
| 48 |
+
test('normalizes the original aggregate manifest score and status field names', () => {
|
| 49 |
+
const result = normalizeManifest({
|
| 50 |
+
model_id: 'claude-fable-5',
|
| 51 |
+
summary: { expected_tasks: 1 },
|
| 52 |
+
tasks: [{
|
| 53 |
+
task_id: 'task-022',
|
| 54 |
+
included: true,
|
| 55 |
+
execution_state: 'budget_exhausted',
|
| 56 |
+
drawing_type: '3d',
|
| 57 |
+
level: 'basic',
|
| 58 |
+
score: 0.55,
|
| 59 |
+
outcome: 'partial',
|
| 60 |
+
score_source: 'vision_judge',
|
| 61 |
+
destination_prefix: 'fable/artifacts//run/task-022/',
|
| 62 |
+
}],
|
| 63 |
+
}, { manifestKey: 'fable/aggregate/manifest.json' })
|
| 64 |
+
assert.equal(result.tasks[0].executionStatus, 'budget_exhausted')
|
| 65 |
+
assert.equal(result.tasks[0].visionScore, 0.55)
|
| 66 |
+
assert.equal(result.tasks[0].visionVerdict, 'partial')
|
| 67 |
+
assert.equal(result.tasks[0].controllerClean, false)
|
| 68 |
+
assert.equal(result.scores.deterministic.count, 0)
|
| 69 |
+
})
|
| 70 |
+
|
| 71 |
+
test('preserves the aggregate bucket double slash in signed artifact keys', async () => {
|
| 72 |
+
let signedCommand = null
|
| 73 |
+
const repository = new ResultsRepository({
|
| 74 |
+
bucket: 'results',
|
| 75 |
+
s3: {
|
| 76 |
+
async send(command) {
|
| 77 |
+
if (command.constructor.name === 'ListObjectsV2Command') {
|
| 78 |
+
return { CommonPrefixes: [{ Prefix: 'terra/' }] }
|
| 79 |
+
}
|
| 80 |
+
const key = command.input.Key
|
| 81 |
+
if (key === 'terra/aggregate/manifest.json') {
|
| 82 |
+
return { Body: { transformToString: async () => JSON.stringify(manifest) } }
|
| 83 |
+
}
|
| 84 |
+
const error = new Error('missing')
|
| 85 |
+
error.name = 'NoSuchKey'
|
| 86 |
+
throw error
|
| 87 |
+
},
|
| 88 |
+
},
|
| 89 |
+
signUrl: async (command) => {
|
| 90 |
+
signedCommand = command
|
| 91 |
+
return 'https://signed.example/frame'
|
| 92 |
+
},
|
| 93 |
+
})
|
| 94 |
+
const url = await repository.mediaUrl('terra', 'task-001', 'frames/frame_0001.png')
|
| 95 |
+
assert.equal(url, 'https://signed.example/frame')
|
| 96 |
+
assert.equal(
|
| 97 |
+
signedCommand.input.Key,
|
| 98 |
+
'terra/artifacts//controller/run/task-001/frames/frame_0001.png',
|
| 99 |
+
)
|
| 100 |
+
})
|
| 101 |
+
|
| 102 |
+
test('derives a deterministic frame page from the finalized screenshot count', async () => {
|
| 103 |
+
const objects = new Map([
|
| 104 |
+
['terra/aggregate/manifest.json', manifest],
|
| 105 |
+
['terra/artifacts//controller/run/task-001/result.json', { screenshots: 4 }],
|
| 106 |
+
['terra/artifacts//controller/run/task-001/run-state.json', { turn_count: 2 }],
|
| 107 |
+
])
|
| 108 |
+
const repository = new ResultsRepository({
|
| 109 |
+
bucket: 'results',
|
| 110 |
+
signUrl: async () => 'signed',
|
| 111 |
+
s3: {
|
| 112 |
+
async send(command) {
|
| 113 |
+
if (command.constructor.name === 'ListObjectsV2Command') {
|
| 114 |
+
return { CommonPrefixes: [{ Prefix: 'terra/' }] }
|
| 115 |
+
}
|
| 116 |
+
const value = objects.get(command.input.Key)
|
| 117 |
+
if (value) return { Body: { transformToString: async () => JSON.stringify(value) } }
|
| 118 |
+
const error = new Error('missing')
|
| 119 |
+
error.name = 'NoSuchKey'
|
| 120 |
+
throw error
|
| 121 |
+
},
|
| 122 |
+
},
|
| 123 |
+
})
|
| 124 |
+
const page = await repository.framePage('terra', 'task-001', { offset: 1, limit: 2 })
|
| 125 |
+
assert.equal(page.total, 4)
|
| 126 |
+
assert.equal(page.hasMore, true)
|
| 127 |
+
assert.deepEqual(page.frames.map((frame) => frame.path), [
|
| 128 |
+
'frames/frame_0001.png',
|
| 129 |
+
'frames/frame_0002.png',
|
| 130 |
+
])
|
| 131 |
+
})
|
apps/results-dashboard/tsconfig.app.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"composite": true,
|
| 4 |
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
| 5 |
+
"target": "ES2022",
|
| 6 |
+
"useDefineForClassFields": true,
|
| 7 |
+
"module": "ESNext",
|
| 8 |
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
| 9 |
+
"skipLibCheck": true,
|
| 10 |
+
"moduleResolution": "Bundler",
|
| 11 |
+
"allowImportingTsExtensions": true,
|
| 12 |
+
"noEmit": true,
|
| 13 |
+
"resolveJsonModule": true,
|
| 14 |
+
"isolatedModules": true,
|
| 15 |
+
"esModuleInterop": true,
|
| 16 |
+
"jsx": "preserve",
|
| 17 |
+
"strict": true,
|
| 18 |
+
"noUnusedLocals": true,
|
| 19 |
+
"noUnusedParameters": true
|
| 20 |
+
},
|
| 21 |
+
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
| 22 |
+
}
|
apps/results-dashboard/tsconfig.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"files": [],
|
| 3 |
+
"references": [
|
| 4 |
+
{ "path": "./tsconfig.app.json" },
|
| 5 |
+
{ "path": "./tsconfig.node.json" }
|
| 6 |
+
]
|
| 7 |
+
}
|
apps/results-dashboard/tsconfig.node.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"composite": true,
|
| 4 |
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
| 5 |
+
"target": "ES2023",
|
| 6 |
+
"lib": ["ES2023"],
|
| 7 |
+
"module": "ESNext",
|
| 8 |
+
"skipLibCheck": true,
|
| 9 |
+
"moduleResolution": "Bundler",
|
| 10 |
+
"allowImportingTsExtensions": true,
|
| 11 |
+
"verbatimModuleSyntax": true,
|
| 12 |
+
"moduleDetection": "force",
|
| 13 |
+
"noEmit": true,
|
| 14 |
+
"strict": true
|
| 15 |
+
},
|
| 16 |
+
"include": ["vite.config.ts"]
|
| 17 |
+
}
|
apps/results-dashboard/vite.config.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig } from 'vite'
|
| 2 |
+
import vue from '@vitejs/plugin-vue'
|
| 3 |
+
|
| 4 |
+
export default defineConfig({
|
| 5 |
+
plugins: [vue()],
|
| 6 |
+
server: {
|
| 7 |
+
proxy: {
|
| 8 |
+
'/api': 'http://127.0.0.1:4274',
|
| 9 |
+
},
|
| 10 |
+
},
|
| 11 |
+
build: {
|
| 12 |
+
target: 'es2022',
|
| 13 |
+
sourcemap: true,
|
| 14 |
+
},
|
| 15 |
+
})
|
artifacts/gold-cache/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Gold cache
|
| 2 |
+
|
| 3 |
+
This directory holds trusted, evaluator-versioned render and metadata artifacts:
|
| 4 |
+
|
| 5 |
+
```text
|
| 6 |
+
artifacts/gold-cache/<evaluator-version>/<task-id>/
|
| 7 |
+
├── metadata.json
|
| 8 |
+
└── render.png
|
| 9 |
+
```
|
| 10 |
+
|
| 11 |
+
Production entries are generated only by the pinned Windows/AutoCAD evaluator from the corresponding private gold DWG. Reference PNGs are never used to construct cache entries.
|
| 12 |
+
|
| 13 |
+
Historical evaluator-version directories are generated artifacts and ignored by
|
| 14 |
+
git. The public distribution includes the active
|
| 15 |
+
`autocad-2019-r23.0.46-v4` cache so a checkout or exported bundle is
|
| 16 |
+
evaluation-ready without using somebody else's infrastructure. It verifies all
|
| 17 |
+
50 task source checksums and pins 3D rendering to `Layout1`.
|
| 18 |
+
|
| 19 |
+
Portable bundle export checksum-binds the selected cache alongside the source
|
| 20 |
+
corpus:
|
| 21 |
+
|
| 22 |
+
```bash
|
| 23 |
+
uv run autocad-bench bundle-export \
|
| 24 |
+
--output dist/autocad-bench-corpus-v1
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
uv run autocad-bench verify-gold-cache \
|
| 29 |
+
--evaluator-version autocad-2019-r23.0.46-v4 \
|
| 30 |
+
--split all
|
| 31 |
+
```
|
artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-001/metadata.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-001/render.png
ADDED
|
Git LFS Details
|
artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-002/metadata.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-002/render.png
ADDED
|
Git LFS Details
|
artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-003/metadata.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|