From dde1f902b63d8da3ed156bfbfd483b61493fb019 Mon Sep 17 00:00:00 2001 From: whit3rabbit Date: Mon, 6 Apr 2026 21:24:44 -0500 Subject: [PATCH] feat: model persistence, model discovery, config directory, and admin UI updates Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 37 +++- README.md | 51 +++-- crates/proxy/admin-ui/dist/index.html | 24 ++- crates/proxy/admin-ui/src/App.tsx | 11 +- crates/proxy/admin-ui/src/api/queries.ts | 20 +- crates/proxy/admin-ui/src/api/types.ts | 15 ++ .../proxy/admin-ui/src/tabs/models/Models.tsx | 108 +++++++++- .../admin-ui/src/tabs/settings/Settings.tsx | 38 +++- crates/proxy/src/admin/db.rs | 81 ++++++++ crates/proxy/src/admin/routes/mod.rs | 3 + crates/proxy/src/admin/routes/models.rs | 192 ++++++++++++++++++ crates/proxy/src/admin/routes/status.rs | 41 ++++ crates/proxy/src/env_parser.rs | 2 + crates/proxy/src/main.rs | 166 +++++++++++++-- docs/CONFIG.md | 103 ++++++++++ 15 files changed, 844 insertions(+), 48 deletions(-) create mode 100644 crates/proxy/src/admin/routes/status.rs create mode 100644 docs/CONFIG.md diff --git a/CLAUDE.md b/CLAUDE.md index 09aab79..4e350c1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,6 +37,9 @@ All implementation phases are complete. - Security hardening: plaintext HTTP startup warning, 1MB admin body limit, CSP header, model name validation - Security fixes (2026-03-30 audit): `AWS_ACCESS_KEY_ID`/`GOOGLE_ACCESS_TOKEN` redacted in env endpoint; admin rate limiter uses sliding window; all audit entries include `source_ip`; OIDC discovery and webhook callbacks use SSRF-safe HTTP client and validate URLs against private IP ranges; CSRF public-route decision documented; non-Unix token file warning already present - Admin rate limiter: 10 RPM per source IP (60-second sliding window, in-memory). Resets on process restart. `set_admin_rpm()` overrides the limit for tests. +- Model persistence: models added via admin API stored in SQLite `model_deployment` table, survive restarts; YAML config models loaded first, admin-added models merged on top +- Model discovery: `POST /admin/api/models/discover` fetches available models from providers (OpenRouter, DeepInfra public; Ollama local; configured backend with key). Admin UI has discover section on Models tab. +- Config directory: `~/.anyllm/` stores admin.db, .admin_token, .anyllm.env, config.yaml by default. Override with `ANYLLM_HOME` env var or individual file env vars. - Model mapping and lossy-translation warnings - `POST /v1/embeddings` passthrough: forwards directly to the backend with no translation; works with OpenAI, Vertex, Gemini (`gemini-embedding-exp-03-07`), and vLLM/HuggingFace models. Not mounted for the Anthropic passthrough backend. - `x-anyllm-degradation` response header: set when features are silently dropped during translation (opt-in via `ANYLLM_DEGRADATION_WARNINGS=true`; auto-enabled when `PROXY_CONFIG` is set). Examples: `top_k`, `thinking_config`, `cache_control`, `document_blocks`, `stop_sequences_truncated` @@ -70,8 +73,8 @@ docker compose up Key Docker env vars: - `WEBUI=1` or `ADMIN=1`: enable admin UI (also requires `ADMIN_BIND=0.0.0.0` when in Docker) - `ADMIN_BIND`: bind address for admin server (default `127.0.0.1`; set `0.0.0.0` in Docker) -- `ADMIN_DB_PATH`: SQLite path (default `admin.db` in CWD; compose sets `/data/admin.db`) -- `ADMIN_TOKEN_PATH`: where the auto-generated admin token is written (compose sets `/data/.admin_token`) +- `ADMIN_DB_PATH`: SQLite path (default `~/.anyllm/admin.db`; compose sets `/data/admin.db`) +- `ADMIN_TOKEN_PATH`: where the auto-generated admin token is written (default `~/.anyllm/.admin_token`; compose sets `/data/.admin_token`) CI: `.github/workflows/docker.yml` builds linux/amd64 + linux/arm64 on native runners, merges into a multi-arch manifest. Requires `DOCKERHUB_USERNAME` and `DOCKERHUB_TOKEN` secrets in the repo. @@ -105,6 +108,31 @@ Package contents: After installing: `sudo systemctl enable --now anyllm-proxy`, then edit `/etc/default/anyllm-proxy` with your API keys. +## Config Directory + +Data lives in `~/.anyllm/` by default. Override with `ANYLLM_HOME` or per-file env vars. + +``` +~/.anyllm/ + admin.db SQLite (keys, models, audit, env imports) + .admin_token Auto-generated admin auth token + .anyllm.env Environment file (optional, auto-loaded) + config.yaml Proxy config (optional, auto-detected) +``` + +Lookup order (first match wins): + +| File | 1st | 2nd | 3rd | +|------|-----|-----|-----| +| Env file | `--env-file` flag | CWD `.anyllm.env` | `~/.anyllm/.anyllm.env` | +| Config | `PROXY_CONFIG` env | `~/.anyllm/config.yaml` | -- | +| Database | `ADMIN_DB_PATH` env | `~/.anyllm/admin.db` | -- | +| Token | `ADMIN_TOKEN_PATH` env | `~/.anyllm/.admin_token` | -- | + +Docker sets explicit paths (`/data/admin.db`, `/data/.admin_token`) via env vars, so the home directory convention does not apply in containers. + +See [docs/CONFIG.md](docs/CONFIG.md) for full details. + ## Build and Test ```bash @@ -128,6 +156,7 @@ OPENAI_API_KEY=sk-... cargo run -p anyllm_proxy ## Environment Variables +- `ANYLLM_HOME`: Override the data directory (default: `~/.anyllm`). All default file paths resolve relative to this directory. - `BACKEND`: Backend provider: `openai` (default), `azure`, `vertex`, `gemini`, `anthropic` (passthrough), or `bedrock` (SigV4-signed, Anthropic format) - `OPENAI_API_KEY`: OpenAI API key (required when BACKEND=openai, empty default) - `OPENAI_BASE_URL`: OpenAI base URL (default: `https://api.openai.com`) @@ -135,8 +164,8 @@ OPENAI_API_KEY=sk-... cargo run -p anyllm_proxy - `LISTEN_PORT`: Server port (default: `3000`) - `ADMIN_PORT`: Admin server port (default: `3001`; must differ from `LISTEN_PORT`) - `ADMIN_BIND`: Admin server bind address (default: `127.0.0.1`; set `0.0.0.0` in Docker) -- `ADMIN_DB_PATH`: SQLite database path (default: `admin.db` in CWD) -- `ADMIN_TOKEN_PATH`: Path for auto-generated admin token file (default: `.admin_token` in CWD) +- `ADMIN_DB_PATH`: SQLite database path (default: `~/.anyllm/admin.db`) +- `ADMIN_TOKEN_PATH`: Path for auto-generated admin token file (default: `~/.anyllm/.admin_token`) - `DISABLE_ADMIN`: Set to `1` to force-disable admin UI even when `--webui` flag is passed - `BIG_MODEL`: Backend model for sonnet/opus requests (default: `gpt-4o` for OpenAI, `gemini-2.5-pro` for Vertex/Gemini) - `SMALL_MODEL`: Backend model for haiku requests (default: `gpt-4o-mini` for OpenAI, `gemini-2.5-flash` for Vertex/Gemini) diff --git a/README.md b/README.md index 9b4db7e..2204041 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Download a binary from the [releases page](https://github.com/whit3rabbit/anyllm cargo install anyllm_proxy ``` -Create a `.anyllm.env` config file: +Create a `.anyllm.env` config file in `~/.anyllm/` (or the current directory): ```env OPENAI_API_KEY=unused @@ -23,7 +23,7 @@ BIG_MODEL=qwen2.5-coder:32b SMALL_MODEL=qwen2.5-coder:32b ``` -Run the proxy (auto-loads `.anyllm.env` from the current directory): +Run the proxy (auto-loads `.anyllm.env` from `~/.anyllm/` or the current directory): ```bash anyllm_proxy @@ -36,7 +36,7 @@ anyllm_proxy |---|---|---| | **Config** | 3 env vars or `.anyllm.env` | `config.toml` / `config.yaml` | | **Routing** | Single backend | Multi-backend with path prefixes | -| **Admin UI** | Not started | `--webui` flag | +| **Admin UI** | `--webui` (guided setup if no config) | `--webui` (full dashboard) | | **Translation warnings** | Silent (never exposed to clients) | `x-anyllm-degradation` header active | | **How to enable** | Default | Pass `--webui`, set `PROXY_CONFIG`, or `ANYLLM_DEGRADATION_WARNINGS=true` | @@ -59,20 +59,22 @@ Pass `--webui` (or `--admin`) to start the admin dashboard alongside the proxy: ```bash anyllm_proxy --webui # Proxy: http://localhost:3000 -# Admin UI: http://127.0.0.1:3001/admin/?token=$(cat .admin_token) +# Admin UI: http://127.0.0.1:3001/admin/?token=$(cat ~/.anyllm/.admin_token) ``` +If no backend is configured, the UI opens on the **Settings** tab with a getting-started guide and env file import. + The admin server binds to `127.0.0.1:3001` by default (localhost only). The dashboard tabs: - **Dashboard:** Live RPM, error rate, P50/P95 latency, per-backend cards, filterable live request feed. - **Request Log:** Historical log with filters (backend, status, key, date range), paginated, with per-request cost and token detail. - **Access Control:** Virtual key CRUD — create, edit (RPM/TPM limits, budget, expiry, model allowlist), revoke without restarting. - **Backends:** Configured backends and their status. -- **Models:** Add/remove model routing deployments (LiteLLM config mode only). +- **Models:** Discover models from providers (OpenRouter, DeepInfra, Ollama, or configured backend), add/remove deployments. Changes are persisted to SQLite and survive restarts. - **Audit:** All admin config mutations and key lifecycle events. -- **Settings:** Mutable config (log level, log_bodies, model mappings), read-only env vars (secrets masked), **Export .env** to generate a `.anyllm.env` template. +- **Settings:** Mutable config (log level, log_bodies, model mappings), read-only env vars (secrets masked), **Import/Export .anyllm.env**. Shows a getting-started guide when no backend is configured. -**Token:** On first start an admin token is auto-generated and written to `.admin_token`. Pass it as `?token=` in the URL or `Authorization: Bearer` for API calls. To set a fixed token instead: +**Token:** On first start an admin token is auto-generated and written to `~/.anyllm/.admin_token`. Pass it as `?token=` in the URL or `Authorization: Bearer` for API calls. To set a fixed token instead: ```bash ADMIN_TOKEN=mysecret anyllm_proxy --webui @@ -104,14 +106,33 @@ docker compose up | `ADMIN_PORT` | `3001` | Admin server port | | `ADMIN_BIND` | `127.0.0.1` | Bind address (`0.0.0.0` in Docker) | | `ADMIN_TOKEN` | auto-generated | Fixed token (min 32 chars recommended) | -| `ADMIN_TOKEN_PATH` | `.admin_token` | Where the auto-generated token is written | -| `ADMIN_DB_PATH` | `admin.db` | SQLite database path | +| `ADMIN_TOKEN_PATH` | `~/.anyllm/.admin_token` | Where the auto-generated token is written | +| `ADMIN_DB_PATH` | `~/.anyllm/admin.db` | SQLite database path | +| `ANYLLM_HOME` | `~/.anyllm` | Data directory for all default file paths | | `ADMIN_LOG_RETENTION_DAYS` | `7` | Request log retention | | `DISABLE_ADMIN` | — | Set to `1` to force-disable | | `WEBUI` / `ADMIN` | — | Docker entrypoint shorthand for `--webui` | **CSRF:** State-mutating admin API calls (POST/PUT/DELETE) require an `X-CSRF-Token` header. Fetch a one-time token from `GET /admin/csrf-token` before each mutating request. The SPA handles this automatically; scripts must do it explicitly. Admin endpoints are rate-limited to 10 requests/minute per IP. +### Config Directory + +All data files live in `~/.anyllm/` by default. The directory is created on first run. + +``` +~/.anyllm/ + admin.db SQLite (keys, models, audit, env imports) + .admin_token Auto-generated admin auth token + .anyllm.env Environment file (auto-loaded if present) + config.yaml Proxy config (auto-detected if present) +``` + +Override the directory with `ANYLLM_HOME=/path/to/dir`, or override individual files with `ADMIN_DB_PATH`, `ADMIN_TOKEN_PATH`, `--env-file`, or `PROXY_CONFIG`. + +The proxy looks for `.anyllm.env` in three places (first match wins): `--env-file` flag, then the current directory, then `~/.anyllm/`. Similarly, `config.yaml` is auto-detected in `~/.anyllm/` when `PROXY_CONFIG` is not set. + +Docker Compose sets explicit paths (`/data/admin.db`, `/data/.admin_token`) so the home directory convention does not apply in containers. See [docs/CONFIG.md](docs/CONFIG.md) for full details. + ## Advanced Mode ### Multiple backends on one proxy (recommended) @@ -361,10 +382,10 @@ ANTHROPIC_BASE_URL=http://localhost:3000/deepseek_api claude ### The Admin Dashboard -See [Admin Web Interface](#admin-web-interface-optional) for the full reference. When using a LiteLLM config, the **Models** tab lets you add/remove deployments live. All mutations are recorded in the **Audit** tab. +See [Admin Web Interface](#admin-web-interface-optional) for the full reference. The **Models** tab lets you discover models from providers and add/remove deployments (persisted to SQLite). All mutations are recorded in the **Audit** tab. ```bash -open http://127.0.0.1:3001/admin/?token=$(cat .admin_token) +open http://127.0.0.1:3001/admin/?token=$(cat ~/.anyllm/.admin_token) ``` --- @@ -457,7 +478,7 @@ Create short-lived, rate-limited, or budget-capped API keys without restarting t ```bash # Create a key with RPM/TPM limits, a monthly budget, and a model allowlist curl -X POST http://localhost:3001/admin/api/keys \ - -H "Authorization: Bearer $(cat .admin_token)" \ + -H "Authorization: Bearer $(cat ~/.anyllm/.admin_token)" \ -H "Content-Type: application/json" \ -d '{ "description": "dev key", @@ -477,17 +498,17 @@ curl http://localhost:3000/v1/messages \ # Update limits on an existing key (no restart needed) curl -X PUT http://localhost:3001/admin/api/keys/1 \ - -H "Authorization: Bearer $(cat .admin_token)" \ + -H "Authorization: Bearer $(cat ~/.anyllm/.admin_token)" \ -H "Content-Type: application/json" \ -d '{"rpm_limit": 120, "max_budget_usd": 20.00}' # Check spend for a key curl http://localhost:3001/admin/api/keys/1/spend \ - -H "Authorization: Bearer $(cat .admin_token)" + -H "Authorization: Bearer $(cat ~/.anyllm/.admin_token)" # Revoke immediately (no restart needed) curl -X DELETE http://localhost:3001/admin/api/keys/1 \ - -H "Authorization: Bearer $(cat .admin_token)" + -H "Authorization: Bearer $(cat ~/.anyllm/.admin_token)" ``` `budget_duration` accepts `daily`, `monthly`, or `lifetime`. `allowed_models` supports exact names and `prefix/*` wildcards. A key at 100% of its budget returns 429 with period reset information. Webhook notifications fire at 80%, 95%, and 100% of the budget via `WEBHOOK_URLS`. diff --git a/crates/proxy/admin-ui/dist/index.html b/crates/proxy/admin-ui/dist/index.html index d173f5a..6279ad4 100644 --- a/crates/proxy/admin-ui/dist/index.html +++ b/crates/proxy/admin-ui/dist/index.html @@ -4,7 +4,7 @@ Proxy Admin - +`+a.stack}}var js=Object.prototype.hasOwnProperty,Es=s.unstable_scheduleCallback,Ts=s.unstable_cancelCallback,xv=s.unstable_shouldYield,jv=s.unstable_requestPaint,be=s.unstable_now,Ev=s.unstable_getCurrentPriorityLevel,pr=s.unstable_ImmediatePriority,br=s.unstable_UserBlockingPriority,ku=s.unstable_NormalPriority,Tv=s.unstable_LowPriority,Sr=s.unstable_IdlePriority,_v=s.log,Ov=s.unstable_setDisableYieldValue,Vn=null,Se=null;function Cl(t){if(typeof _v=="function"&&Ov(t),Se&&typeof Se.setStrictMode=="function")try{Se.setStrictMode(Vn,t)}catch{}}var xe=Math.clz32?Math.clz32:Av,Nv=Math.log,zv=Math.LN2;function Av(t){return t>>>=0,t===0?32:31-(Nv(t)/zv|0)|0}var Fu=256,Wu=262144,$u=4194304;function ga(t){var e=t&42;if(e!==0)return e;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function Iu(t,e,l){var a=t.pendingLanes;if(a===0)return 0;var n=0,u=t.suspendedLanes,c=t.pingedLanes;t=t.warmLanes;var d=a&134217727;return d!==0?(a=d&~u,a!==0?n=ga(a):(c&=d,c!==0?n=ga(c):l||(l=d&~t,l!==0&&(n=ga(l))))):(d=a&~u,d!==0?n=ga(d):c!==0?n=ga(c):l||(l=a&~t,l!==0&&(n=ga(l)))),n===0?0:e!==0&&e!==n&&(e&u)===0&&(u=n&-n,l=e&-e,u>=l||u===32&&(l&4194048)!==0)?e:n}function wn(t,e){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&e)===0}function Mv(t,e){switch(t){case 1:case 2:case 4:case 8:case 64:return e+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function xr(){var t=$u;return $u<<=1,($u&62914560)===0&&($u=4194304),t}function _s(t){for(var e=[],l=0;31>l;l++)e.push(t);return e}function Jn(t,e){t.pendingLanes|=e,e!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function Rv(t,e,l,a,n,u){var c=t.pendingLanes;t.pendingLanes=l,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=l,t.entangledLanes&=l,t.errorRecoveryDisabledLanes&=l,t.shellSuspendCounter=0;var d=t.entanglements,m=t.expirationTimes,j=t.hiddenUpdates;for(l=c&~l;0"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var Bv=/[\n"\\]/g;function Ce(t){return t.replace(Bv,function(e){return"\\"+e.charCodeAt(0).toString(16)+" "})}function Rs(t,e,l,a,n,u,c,d){t.name="",c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"?t.type=c:t.removeAttribute("type"),e!=null?c==="number"?(e===0&&t.value===""||t.value!=e)&&(t.value=""+Re(e)):t.value!==""+Re(e)&&(t.value=""+Re(e)):c!=="submit"&&c!=="reset"||t.removeAttribute("value"),e!=null?Cs(t,c,Re(e)):l!=null?Cs(t,c,Re(l)):a!=null&&t.removeAttribute("value"),n==null&&u!=null&&(t.defaultChecked=!!u),n!=null&&(t.checked=n&&typeof n!="function"&&typeof n!="symbol"),d!=null&&typeof d!="function"&&typeof d!="symbol"&&typeof d!="boolean"?t.name=""+Re(d):t.removeAttribute("name")}function Ur(t,e,l,a,n,u,c,d){if(u!=null&&typeof u!="function"&&typeof u!="symbol"&&typeof u!="boolean"&&(t.type=u),e!=null||l!=null){if(!(u!=="submit"&&u!=="reset"||e!=null)){Ms(t);return}l=l!=null?""+Re(l):"",e=e!=null?""+Re(e):l,d||e===t.value||(t.value=e),t.defaultValue=e}a=a??n,a=typeof a!="function"&&typeof a!="symbol"&&!!a,t.checked=d?t.checked:!!a,t.defaultChecked=!!a,c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"&&(t.name=c),Ms(t)}function Cs(t,e,l){e==="number"&&ei(t.ownerDocument)===t||t.defaultValue===""+l||(t.defaultValue=""+l)}function $a(t,e,l,a){if(t=t.options,e){e={};for(var n=0;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Bs=!1;if(sl)try{var $n={};Object.defineProperty($n,"passive",{get:function(){Bs=!0}}),window.addEventListener("test",$n,$n),window.removeEventListener("test",$n,$n)}catch{Bs=!1}var Ul=null,Qs=null,ai=null;function Gr(){if(ai)return ai;var t,e=Qs,l=e.length,a,n="value"in Ul?Ul.value:Ul.textContent,u=n.length;for(t=0;t=tu),Jr=" ",kr=!1;function Fr(t,e){switch(t){case"keyup":return oy.indexOf(e.keyCode)!==-1;case"keydown":return e.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Wr(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var en=!1;function hy(t,e){switch(t){case"compositionend":return Wr(e);case"keypress":return e.which!==32?null:(kr=!0,Jr);case"textInput":return t=e.data,t===Jr&&kr?null:t;default:return null}}function my(t,e){if(en)return t==="compositionend"||!Ks&&Fr(t,e)?(t=Gr(),ai=Qs=Ul=null,en=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(e.ctrlKey||e.altKey||e.metaKey)||e.ctrlKey&&e.altKey){if(e.char&&1=e)return{node:l,offset:e-t};t=a}t:{for(;l;){if(l.nextSibling){l=l.nextSibling;break t}l=l.parentNode}l=void 0}l=no(l)}}function io(t,e){return t&&e?t===e?!0:t&&t.nodeType===3?!1:e&&e.nodeType===3?io(t,e.parentNode):"contains"in t?t.contains(e):t.compareDocumentPosition?!!(t.compareDocumentPosition(e)&16):!1:!1}function so(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var e=ei(t.document);e instanceof t.HTMLIFrameElement;){try{var l=typeof e.contentWindow.location.href=="string"}catch{l=!1}if(l)t=e.contentWindow;else break;e=ei(t.document)}return e}function ws(t){var e=t&&t.nodeName&&t.nodeName.toLowerCase();return e&&(e==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||e==="textarea"||t.contentEditable==="true")}var jy=sl&&"documentMode"in document&&11>=document.documentMode,ln=null,Js=null,nu=null,ks=!1;function co(t,e,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;ks||ln==null||ln!==ei(a)||(a=ln,"selectionStart"in a&&ws(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),nu&&au(nu,a)||(nu=a,a=Wi(Js,"onSelect"),0>=c,n-=c,$e=1<<32-xe(e)+n|l<lt?(ft=Z,Z=null):ft=Z.sibling;var mt=E(b,Z,x[lt],M);if(mt===null){Z===null&&(Z=ft);break}t&&Z&&mt.alternate===null&&e(b,Z),y=u(mt,y,lt),ht===null?J=mt:ht.sibling=mt,ht=mt,Z=ft}if(lt===x.length)return l(b,Z),rt&&fl(b,lt),J;if(Z===null){for(;ltlt?(ft=Z,Z=null):ft=Z.sibling;var la=E(b,Z,mt.value,M);if(la===null){Z===null&&(Z=ft);break}t&&Z&&la.alternate===null&&e(b,Z),y=u(la,y,lt),ht===null?J=la:ht.sibling=la,ht=la,Z=ft}if(mt.done)return l(b,Z),rt&&fl(b,lt),J;if(Z===null){for(;!mt.done;lt++,mt=x.next())mt=R(b,mt.value,M),mt!==null&&(y=u(mt,y,lt),ht===null?J=mt:ht.sibling=mt,ht=mt);return rt&&fl(b,lt),J}for(Z=a(Z);!mt.done;lt++,mt=x.next())mt=N(Z,b,lt,mt.value,M),mt!==null&&(t&&mt.alternate!==null&&Z.delete(mt.key===null?lt:mt.key),y=u(mt,y,lt),ht===null?J=mt:ht.sibling=mt,ht=mt);return t&&Z.forEach(function(X0){return e(b,X0)}),rt&&fl(b,lt),J}function _t(b,y,x,M){if(typeof x=="object"&&x!==null&&x.type===G&&x.key===null&&(x=x.props.children),typeof x=="object"&&x!==null){switch(x.$$typeof){case k:t:{for(var J=x.key;y!==null;){if(y.key===J){if(J=x.type,J===G){if(y.tag===7){l(b,y.sibling),M=n(y,x.props.children),M.return=b,b=M;break t}}else if(y.elementType===J||typeof J=="object"&&J!==null&&J.$$typeof===nt&&za(J)===y.type){l(b,y.sibling),M=n(y,x.props),ru(M,x),M.return=b,b=M;break t}l(b,y);break}else e(b,y);y=y.sibling}x.type===G?(M=Ea(x.props.children,b.mode,M,x.key),M.return=b,b=M):(M=hi(x.type,x.key,x.props,null,b.mode,M),ru(M,x),M.return=b,b=M)}return c(b);case $:t:{for(J=x.key;y!==null;){if(y.key===J)if(y.tag===4&&y.stateNode.containerInfo===x.containerInfo&&y.stateNode.implementation===x.implementation){l(b,y.sibling),M=n(y,x.children||[]),M.return=b,b=M;break t}else{l(b,y);break}else e(b,y);y=y.sibling}M=ec(x,b.mode,M),M.return=b,b=M}return c(b);case nt:return x=za(x),_t(b,y,x,M)}if(we(x))return L(b,y,x,M);if(Ft(x)){if(J=Ft(x),typeof J!="function")throw Error(r(150));return x=J.call(x),F(b,y,x,M)}if(typeof x.then=="function")return _t(b,y,Si(x),M);if(x.$$typeof===K)return _t(b,y,yi(b,x),M);xi(b,x)}return typeof x=="string"&&x!==""||typeof x=="number"||typeof x=="bigint"?(x=""+x,y!==null&&y.tag===6?(l(b,y.sibling),M=n(y,x),M.return=b,b=M):(l(b,y),M=tc(x,b.mode,M),M.return=b,b=M),c(b)):l(b,y)}return function(b,y,x,M){try{fu=0;var J=_t(b,y,x,M);return mn=null,J}catch(Z){if(Z===hn||Z===pi)throw Z;var ht=Ee(29,Z,null,b.mode);return ht.lanes=M,ht.return=b,ht}finally{}}}var Ma=Co(!0),Do=Co(!1),Yl=!1;function hc(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function mc(t,e){t=t.updateQueue,e.updateQueue===t&&(e.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function Ll(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function Gl(t,e,l){var a=t.updateQueue;if(a===null)return null;if(a=a.shared,(yt&2)!==0){var n=a.pending;return n===null?e.next=e:(e.next=n.next,n.next=e),a.pending=e,e=di(t),yo(t,null,l),e}return oi(t,a,e,l),di(t)}function ou(t,e,l){if(e=e.updateQueue,e!==null&&(e=e.shared,(l&4194048)!==0)){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,Er(t,l)}}function vc(t,e){var l=t.updateQueue,a=t.alternate;if(a!==null&&(a=a.updateQueue,l===a)){var n=null,u=null;if(l=l.firstBaseUpdate,l!==null){do{var c={lane:l.lane,tag:l.tag,payload:l.payload,callback:null,next:null};u===null?n=u=c:u=u.next=c,l=l.next}while(l!==null);u===null?n=u=e:u=u.next=e}else n=u=e;l={baseState:a.baseState,firstBaseUpdate:n,lastBaseUpdate:u,shared:a.shared,callbacks:a.callbacks},t.updateQueue=l;return}t=l.lastBaseUpdate,t===null?l.firstBaseUpdate=e:t.next=e,l.lastBaseUpdate=e}var yc=!1;function du(){if(yc){var t=dn;if(t!==null)throw t}}function hu(t,e,l,a){yc=!1;var n=t.updateQueue;Yl=!1;var u=n.firstBaseUpdate,c=n.lastBaseUpdate,d=n.shared.pending;if(d!==null){n.shared.pending=null;var m=d,j=m.next;m.next=null,c===null?u=j:c.next=j,c=m;var z=t.alternate;z!==null&&(z=z.updateQueue,d=z.lastBaseUpdate,d!==c&&(d===null?z.firstBaseUpdate=j:d.next=j,z.lastBaseUpdate=m))}if(u!==null){var R=n.baseState;c=0,z=j=m=null,d=u;do{var E=d.lane&-536870913,N=E!==d.lane;if(N?(ct&E)===E:(a&E)===E){E!==0&&E===on&&(yc=!0),z!==null&&(z=z.next={lane:0,tag:d.tag,payload:d.payload,callback:null,next:null});t:{var L=t,F=d;E=e;var _t=l;switch(F.tag){case 1:if(L=F.payload,typeof L=="function"){R=L.call(_t,R,E);break t}R=L;break t;case 3:L.flags=L.flags&-65537|128;case 0:if(L=F.payload,E=typeof L=="function"?L.call(_t,R,E):L,E==null)break t;R=D({},R,E);break t;case 2:Yl=!0}}E=d.callback,E!==null&&(t.flags|=64,N&&(t.flags|=8192),N=n.callbacks,N===null?n.callbacks=[E]:N.push(E))}else N={lane:E,tag:d.tag,payload:d.payload,callback:d.callback,next:null},z===null?(j=z=N,m=R):z=z.next=N,c|=E;if(d=d.next,d===null){if(d=n.shared.pending,d===null)break;N=d,d=N.next,N.next=null,n.lastBaseUpdate=N,n.shared.pending=null}}while(!0);z===null&&(m=R),n.baseState=m,n.firstBaseUpdate=j,n.lastBaseUpdate=z,u===null&&(n.shared.lanes=0),wl|=c,t.lanes=c,t.memoizedState=R}}function Uo(t,e){if(typeof t!="function")throw Error(r(191,t));t.call(e)}function qo(t,e){var l=t.callbacks;if(l!==null)for(t.callbacks=null,t=0;tu?u:8;var c=A.T,d={};A.T=d,qc(t,!1,e,l);try{var m=n(),j=A.S;if(j!==null&&j(d,m),m!==null&&typeof m=="object"&&typeof m.then=="function"){var z=Ry(m,a);yu(t,e,z,ze(t))}else yu(t,e,a,ze(t))}catch(R){yu(t,e,{then:function(){},status:"rejected",reason:R},ze())}finally{q.p=u,c!==null&&d.types!==null&&(c.types=d.types),A.T=c}}function By(){}function Dc(t,e,l,a){if(t.tag!==5)throw Error(r(476));var n=hd(t).queue;dd(t,n,e,W,l===null?By:function(){return md(t),l(a)})}function hd(t){var e=t.memoizedState;if(e!==null)return e;e={memoizedState:W,baseState:W,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:hl,lastRenderedState:W},next:null};var l={};return e.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:hl,lastRenderedState:l},next:null},t.memoizedState=e,t=t.alternate,t!==null&&(t.memoizedState=e),e}function md(t){var e=hd(t);e.next===null&&(e=t.alternate.memoizedState),yu(t,e.next.queue,{},ze())}function Uc(){return It(Du)}function vd(){return Qt().memoizedState}function yd(){return Qt().memoizedState}function Qy(t){for(var e=t.return;e!==null;){switch(e.tag){case 24:case 3:var l=ze();t=Ll(l);var a=Gl(e,t,l);a!==null&&(ye(a,e,l),ou(a,e,l)),e={cache:fc()},t.payload=e;return}e=e.return}}function Yy(t,e,l){var a=ze();l={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},Ri(t)?pd(e,l):(l=Is(t,e,l,a),l!==null&&(ye(l,t,a),bd(l,e,a)))}function gd(t,e,l){var a=ze();yu(t,e,l,a)}function yu(t,e,l,a){var n={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null};if(Ri(t))pd(e,n);else{var u=t.alternate;if(t.lanes===0&&(u===null||u.lanes===0)&&(u=e.lastRenderedReducer,u!==null))try{var c=e.lastRenderedState,d=u(c,l);if(n.hasEagerState=!0,n.eagerState=d,je(d,c))return oi(t,e,n,0),zt===null&&ri(),!1}catch{}finally{}if(l=Is(t,e,n,a),l!==null)return ye(l,t,a),bd(l,e,a),!0}return!1}function qc(t,e,l,a){if(a={lane:2,revertLane:mf(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},Ri(t)){if(e)throw Error(r(479))}else e=Is(t,l,a,2),e!==null&&ye(e,t,2)}function Ri(t){var e=t.alternate;return t===et||e!==null&&e===et}function pd(t,e){yn=Ti=!0;var l=t.pending;l===null?e.next=e:(e.next=l.next,l.next=e),t.pending=e}function bd(t,e,l){if((l&4194048)!==0){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,Er(t,l)}}var gu={readContext:It,use:Ni,useCallback:Dt,useContext:Dt,useEffect:Dt,useImperativeHandle:Dt,useLayoutEffect:Dt,useInsertionEffect:Dt,useMemo:Dt,useReducer:Dt,useRef:Dt,useState:Dt,useDebugValue:Dt,useDeferredValue:Dt,useTransition:Dt,useSyncExternalStore:Dt,useId:Dt,useHostTransitionStatus:Dt,useFormState:Dt,useActionState:Dt,useOptimistic:Dt,useMemoCache:Dt,useCacheRefresh:Dt};gu.useEffectEvent=Dt;var Sd={readContext:It,use:Ni,useCallback:function(t,e){return ie().memoizedState=[t,e===void 0?null:e],t},useContext:It,useEffect:ad,useImperativeHandle:function(t,e,l){l=l!=null?l.concat([t]):null,Ai(4194308,4,sd.bind(null,e,t),l)},useLayoutEffect:function(t,e){return Ai(4194308,4,t,e)},useInsertionEffect:function(t,e){Ai(4,2,t,e)},useMemo:function(t,e){var l=ie();e=e===void 0?null:e;var a=t();if(Ra){Cl(!0);try{t()}finally{Cl(!1)}}return l.memoizedState=[a,e],a},useReducer:function(t,e,l){var a=ie();if(l!==void 0){var n=l(e);if(Ra){Cl(!0);try{l(e)}finally{Cl(!1)}}}else n=e;return a.memoizedState=a.baseState=n,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:n},a.queue=t,t=t.dispatch=Yy.bind(null,et,t),[a.memoizedState,t]},useRef:function(t){var e=ie();return t={current:t},e.memoizedState=t},useState:function(t){t=zc(t);var e=t.queue,l=gd.bind(null,et,e);return e.dispatch=l,[t.memoizedState,l]},useDebugValue:Rc,useDeferredValue:function(t,e){var l=ie();return Cc(l,t,e)},useTransition:function(){var t=zc(!1);return t=dd.bind(null,et,t.queue,!0,!1),ie().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,e,l){var a=et,n=ie();if(rt){if(l===void 0)throw Error(r(407));l=l()}else{if(l=e(),zt===null)throw Error(r(349));(ct&127)!==0||Go(a,e,l)}n.memoizedState=l;var u={value:l,getSnapshot:e};return n.queue=u,ad(Ko.bind(null,a,u,t),[t]),a.flags|=2048,pn(9,{destroy:void 0},Xo.bind(null,a,u,l,e),null),l},useId:function(){var t=ie(),e=zt.identifierPrefix;if(rt){var l=Ie,a=$e;l=(a&~(1<<32-xe(a)-1)).toString(32)+l,e="_"+e+"R_"+l,l=_i++,0<\/script>",u=u.removeChild(u.firstChild);break;case"select":u=typeof a.is=="string"?c.createElement("select",{is:a.is}):c.createElement("select"),a.multiple?u.multiple=!0:a.size&&(u.size=a.size);break;default:u=typeof a.is=="string"?c.createElement(n,{is:a.is}):c.createElement(n)}}u[Wt]=e,u[re]=a;t:for(c=e.child;c!==null;){if(c.tag===5||c.tag===6)u.appendChild(c.stateNode);else if(c.tag!==4&&c.tag!==27&&c.child!==null){c.child.return=c,c=c.child;continue}if(c===e)break t;for(;c.sibling===null;){if(c.return===null||c.return===e)break t;c=c.return}c.sibling.return=c.return,c=c.sibling}e.stateNode=u;t:switch(te(u,n,a),n){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break t;case"img":a=!0;break t;default:a=!1}a&&vl(e)}}return Mt(e),Fc(e,e.type,t===null?null:t.memoizedProps,e.pendingProps,l),null;case 6:if(t&&e.stateNode!=null)t.memoizedProps!==a&&vl(e);else{if(typeof a!="string"&&e.stateNode===null)throw Error(r(166));if(t=ut.current,fn(e)){if(t=e.stateNode,l=e.memoizedProps,a=null,n=$t,n!==null)switch(n.tag){case 27:case 5:a=n.memoizedProps}t[Wt]=e,t=!!(t.nodeValue===l||a!==null&&a.suppressHydrationWarning===!0||Yh(t.nodeValue,l)),t||Bl(e,!0)}else t=$i(t).createTextNode(a),t[Wt]=e,e.stateNode=t}return Mt(e),null;case 31:if(l=e.memoizedState,t===null||t.memoizedState!==null){if(a=fn(e),l!==null){if(t===null){if(!a)throw Error(r(318));if(t=e.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(r(557));t[Wt]=e}else Ta(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;Mt(e),t=!1}else l=uc(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=l),t=!0;if(!t)return e.flags&256?(_e(e),e):(_e(e),null);if((e.flags&128)!==0)throw Error(r(558))}return Mt(e),null;case 13:if(a=e.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(n=fn(e),a!==null&&a.dehydrated!==null){if(t===null){if(!n)throw Error(r(318));if(n=e.memoizedState,n=n!==null?n.dehydrated:null,!n)throw Error(r(317));n[Wt]=e}else Ta(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;Mt(e),n=!1}else n=uc(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=n),n=!0;if(!n)return e.flags&256?(_e(e),e):(_e(e),null)}return _e(e),(e.flags&128)!==0?(e.lanes=l,e):(l=a!==null,t=t!==null&&t.memoizedState!==null,l&&(a=e.child,n=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(n=a.alternate.memoizedState.cachePool.pool),u=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(u=a.memoizedState.cachePool.pool),u!==n&&(a.flags|=2048)),l!==t&&l&&(e.child.flags|=8192),Hi(e,e.updateQueue),Mt(e),null);case 4:return Ht(),t===null&&pf(e.stateNode.containerInfo),Mt(e),null;case 10:return ol(e.type),Mt(e),null;case 19:if(C(Bt),a=e.memoizedState,a===null)return Mt(e),null;if(n=(e.flags&128)!==0,u=a.rendering,u===null)if(n)bu(a,!1);else{if(Ut!==0||t!==null&&(t.flags&128)!==0)for(t=e.child;t!==null;){if(u=Ei(t),u!==null){for(e.flags|=128,bu(a,!1),t=u.updateQueue,e.updateQueue=t,Hi(e,t),e.subtreeFlags=0,t=l,l=e.child;l!==null;)go(l,t),l=l.sibling;return H(Bt,Bt.current&1|2),rt&&fl(e,a.treeForkCount),e.child}t=t.sibling}a.tail!==null&&be()>Gi&&(e.flags|=128,n=!0,bu(a,!1),e.lanes=4194304)}else{if(!n)if(t=Ei(u),t!==null){if(e.flags|=128,n=!0,t=t.updateQueue,e.updateQueue=t,Hi(e,t),bu(a,!0),a.tail===null&&a.tailMode==="hidden"&&!u.alternate&&!rt)return Mt(e),null}else 2*be()-a.renderingStartTime>Gi&&l!==536870912&&(e.flags|=128,n=!0,bu(a,!1),e.lanes=4194304);a.isBackwards?(u.sibling=e.child,e.child=u):(t=a.last,t!==null?t.sibling=u:e.child=u,a.last=u)}return a.tail!==null?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=be(),t.sibling=null,l=Bt.current,H(Bt,n?l&1|2:l&1),rt&&fl(e,a.treeForkCount),t):(Mt(e),null);case 22:case 23:return _e(e),pc(),a=e.memoizedState!==null,t!==null?t.memoizedState!==null!==a&&(e.flags|=8192):a&&(e.flags|=8192),a?(l&536870912)!==0&&(e.flags&128)===0&&(Mt(e),e.subtreeFlags&6&&(e.flags|=8192)):Mt(e),l=e.updateQueue,l!==null&&Hi(e,l.retryQueue),l=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(l=t.memoizedState.cachePool.pool),a=null,e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(a=e.memoizedState.cachePool.pool),a!==l&&(e.flags|=2048),t!==null&&C(Na),null;case 24:return l=null,t!==null&&(l=t.memoizedState.cache),e.memoizedState.cache!==l&&(e.flags|=2048),ol(Lt),Mt(e),null;case 25:return null;case 30:return null}throw Error(r(156,e.tag))}function Zy(t,e){switch(ac(e),e.tag){case 1:return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 3:return ol(Lt),Ht(),t=e.flags,(t&65536)!==0&&(t&128)===0?(e.flags=t&-65537|128,e):null;case 26:case 27:case 5:return Ju(e),null;case 31:if(e.memoizedState!==null){if(_e(e),e.alternate===null)throw Error(r(340));Ta()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 13:if(_e(e),t=e.memoizedState,t!==null&&t.dehydrated!==null){if(e.alternate===null)throw Error(r(340));Ta()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 19:return C(Bt),null;case 4:return Ht(),null;case 10:return ol(e.type),null;case 22:case 23:return _e(e),pc(),t!==null&&C(Na),t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 24:return ol(Lt),null;case 25:return null;default:return null}}function Zd(t,e){switch(ac(e),e.tag){case 3:ol(Lt),Ht();break;case 26:case 27:case 5:Ju(e);break;case 4:Ht();break;case 31:e.memoizedState!==null&&_e(e);break;case 13:_e(e);break;case 19:C(Bt);break;case 10:ol(e.type);break;case 22:case 23:_e(e),pc(),t!==null&&C(Na);break;case 24:ol(Lt)}}function Su(t,e){try{var l=e.updateQueue,a=l!==null?l.lastEffect:null;if(a!==null){var n=a.next;l=n;do{if((l.tag&t)===t){a=void 0;var u=l.create,c=l.inst;a=u(),c.destroy=a}l=l.next}while(l!==n)}}catch(d){xt(e,e.return,d)}}function Zl(t,e,l){try{var a=e.updateQueue,n=a!==null?a.lastEffect:null;if(n!==null){var u=n.next;a=u;do{if((a.tag&t)===t){var c=a.inst,d=c.destroy;if(d!==void 0){c.destroy=void 0,n=e;var m=l,j=d;try{j()}catch(z){xt(n,m,z)}}}a=a.next}while(a!==u)}}catch(z){xt(e,e.return,z)}}function Vd(t){var e=t.updateQueue;if(e!==null){var l=t.stateNode;try{qo(e,l)}catch(a){xt(t,t.return,a)}}}function wd(t,e,l){l.props=Ca(t.type,t.memoizedProps),l.state=t.memoizedState;try{l.componentWillUnmount()}catch(a){xt(t,e,a)}}function xu(t,e){try{var l=t.ref;if(l!==null){switch(t.tag){case 26:case 27:case 5:var a=t.stateNode;break;case 30:a=t.stateNode;break;default:a=t.stateNode}typeof l=="function"?t.refCleanup=l(a):l.current=a}}catch(n){xt(t,e,n)}}function Pe(t,e){var l=t.ref,a=t.refCleanup;if(l!==null)if(typeof a=="function")try{a()}catch(n){xt(t,e,n)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof l=="function")try{l(null)}catch(n){xt(t,e,n)}else l.current=null}function Jd(t){var e=t.type,l=t.memoizedProps,a=t.stateNode;try{t:switch(e){case"button":case"input":case"select":case"textarea":l.autoFocus&&a.focus();break t;case"img":l.src?a.src=l.src:l.srcSet&&(a.srcset=l.srcSet)}}catch(n){xt(t,t.return,n)}}function Wc(t,e,l){try{var a=t.stateNode;d0(a,t.type,l,e),a[re]=e}catch(n){xt(t,t.return,n)}}function kd(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&$l(t.type)||t.tag===4}function $c(t){t:for(;;){for(;t.sibling===null;){if(t.return===null||kd(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&$l(t.type)||t.flags&2||t.child===null||t.tag===4)continue t;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function Ic(t,e,l){var a=t.tag;if(a===5||a===6)t=t.stateNode,e?(l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l).insertBefore(t,e):(e=l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l,e.appendChild(t),l=l._reactRootContainer,l!=null||e.onclick!==null||(e.onclick=il));else if(a!==4&&(a===27&&$l(t.type)&&(l=t.stateNode,e=null),t=t.child,t!==null))for(Ic(t,e,l),t=t.sibling;t!==null;)Ic(t,e,l),t=t.sibling}function Bi(t,e,l){var a=t.tag;if(a===5||a===6)t=t.stateNode,e?l.insertBefore(t,e):l.appendChild(t);else if(a!==4&&(a===27&&$l(t.type)&&(l=t.stateNode),t=t.child,t!==null))for(Bi(t,e,l),t=t.sibling;t!==null;)Bi(t,e,l),t=t.sibling}function Fd(t){var e=t.stateNode,l=t.memoizedProps;try{for(var a=t.type,n=e.attributes;n.length;)e.removeAttributeNode(n[0]);te(e,a,l),e[Wt]=t,e[re]=l}catch(u){xt(t,t.return,u)}}var yl=!1,Kt=!1,Pc=!1,Wd=typeof WeakSet=="function"?WeakSet:Set,Jt=null;function Vy(t,e){if(t=t.containerInfo,xf=ns,t=so(t),ws(t)){if("selectionStart"in t)var l={start:t.selectionStart,end:t.selectionEnd};else t:{l=(l=t.ownerDocument)&&l.defaultView||window;var a=l.getSelection&&l.getSelection();if(a&&a.rangeCount!==0){l=a.anchorNode;var n=a.anchorOffset,u=a.focusNode;a=a.focusOffset;try{l.nodeType,u.nodeType}catch{l=null;break t}var c=0,d=-1,m=-1,j=0,z=0,R=t,E=null;e:for(;;){for(var N;R!==l||n!==0&&R.nodeType!==3||(d=c+n),R!==u||a!==0&&R.nodeType!==3||(m=c+a),R.nodeType===3&&(c+=R.nodeValue.length),(N=R.firstChild)!==null;)E=R,R=N;for(;;){if(R===t)break e;if(E===l&&++j===n&&(d=c),E===u&&++z===a&&(m=c),(N=R.nextSibling)!==null)break;R=E,E=R.parentNode}R=N}l=d===-1||m===-1?null:{start:d,end:m}}else l=null}l=l||{start:0,end:0}}else l=null;for(jf={focusedElem:t,selectionRange:l},ns=!1,Jt=e;Jt!==null;)if(e=Jt,t=e.child,(e.subtreeFlags&1028)!==0&&t!==null)t.return=e,Jt=t;else for(;Jt!==null;){switch(e=Jt,u=e.alternate,t=e.flags,e.tag){case 0:if((t&4)!==0&&(t=e.updateQueue,t=t!==null?t.events:null,t!==null))for(l=0;l title"))),te(u,a,l),u[Wt]=t,wt(u),a=u;break t;case"link":var c=lm("link","href",n).get(a+(l.href||""));if(c){for(var d=0;d_t&&(c=_t,_t=F,F=c);var b=uo(d,F),y=uo(d,_t);if(b&&y&&(N.rangeCount!==1||N.anchorNode!==b.node||N.anchorOffset!==b.offset||N.focusNode!==y.node||N.focusOffset!==y.offset)){var x=R.createRange();x.setStart(b.node,b.offset),N.removeAllRanges(),F>_t?(N.addRange(x),N.extend(y.node,y.offset)):(x.setEnd(y.node,y.offset),N.addRange(x))}}}}for(R=[],N=d;N=N.parentNode;)N.nodeType===1&&R.push({element:N,left:N.scrollLeft,top:N.scrollTop});for(typeof d.focus=="function"&&d.focus(),d=0;dl?32:l,A.T=null,l=sf,sf=null;var u=kl,c=xl;if(Zt=0,En=kl=null,xl=0,(yt&6)!==0)throw Error(r(331));var d=yt;if(yt|=4,sh(u.current),nh(u,u.current,c,l),yt=d,Nu(0,!1),Se&&typeof Se.onPostCommitFiberRoot=="function")try{Se.onPostCommitFiberRoot(Vn,u)}catch{}return!0}finally{q.p=n,A.T=a,_h(t,e)}}function Nh(t,e,l){e=Ue(l,e),e=Yc(t.stateNode,e,2),t=Gl(t,e,2),t!==null&&(Jn(t,2),tl(t))}function xt(t,e,l){if(t.tag===3)Nh(t,t,l);else for(;e!==null;){if(e.tag===3){Nh(e,t,l);break}else if(e.tag===1){var a=e.stateNode;if(typeof e.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(Jl===null||!Jl.has(a))){t=Ue(l,t),l=zd(2),a=Gl(e,l,2),a!==null&&(Ad(l,a,e,t),Jn(a,2),tl(a));break}}e=e.return}}function of(t,e,l){var a=t.pingCache;if(a===null){a=t.pingCache=new ky;var n=new Set;a.set(e,n)}else n=a.get(e),n===void 0&&(n=new Set,a.set(e,n));n.has(l)||(lf=!0,n.add(l),t=Py.bind(null,t,e,l),e.then(t,t))}function Py(t,e,l){var a=t.pingCache;a!==null&&a.delete(e),t.pingedLanes|=t.suspendedLanes&l,t.warmLanes&=~l,zt===t&&(ct&l)===l&&(Ut===4||Ut===3&&(ct&62914560)===ct&&300>be()-Li?(yt&2)===0&&Tn(t,0):af|=l,jn===ct&&(jn=0)),tl(t)}function zh(t,e){e===0&&(e=xr()),t=ja(t,e),t!==null&&(Jn(t,e),tl(t))}function t0(t){var e=t.memoizedState,l=0;e!==null&&(l=e.retryLane),zh(t,l)}function e0(t,e){var l=0;switch(t.tag){case 31:case 13:var a=t.stateNode,n=t.memoizedState;n!==null&&(l=n.retryLane);break;case 19:a=t.stateNode;break;case 22:a=t.stateNode._retryCache;break;default:throw Error(r(314))}a!==null&&a.delete(e),zh(t,l)}function l0(t,e){return Es(t,e)}var Ji=null,On=null,df=!1,ki=!1,hf=!1,Wl=0;function tl(t){t!==On&&t.next===null&&(On===null?Ji=On=t:On=On.next=t),ki=!0,df||(df=!0,n0())}function Nu(t,e){if(!hf&&ki){hf=!0;do for(var l=!1,a=Ji;a!==null;){if(t!==0){var n=a.pendingLanes;if(n===0)var u=0;else{var c=a.suspendedLanes,d=a.pingedLanes;u=(1<<31-xe(42|t)+1)-1,u&=n&~(c&~d),u=u&201326741?u&201326741|1:u?u|2:0}u!==0&&(l=!0,Ch(a,u))}else u=ct,u=Iu(a,a===zt?u:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(u&3)===0||wn(a,u)||(l=!0,Ch(a,u));a=a.next}while(l);hf=!1}}function a0(){Ah()}function Ah(){ki=df=!1;var t=0;Wl!==0&&m0()&&(t=Wl);for(var e=be(),l=null,a=Ji;a!==null;){var n=a.next,u=Mh(a,e);u===0?(a.next=null,l===null?Ji=n:l.next=n,n===null&&(On=l)):(l=a,(t!==0||(u&3)!==0)&&(ki=!0)),a=n}Zt!==0&&Zt!==5||Nu(t),Wl!==0&&(Wl=0)}function Mh(t,e){for(var l=t.suspendedLanes,a=t.pingedLanes,n=t.expirationTimes,u=t.pendingLanes&-62914561;0d)break;var z=m.transferSize,R=m.initiatorType;z&&Lh(R)&&(m=m.responseEnd,c+=z*(m"u"?null:document;function Ih(t,e,l){var a=Nn;if(a&&typeof e=="string"&&e){var n=Ce(e);n='link[rel="'+t+'"][href="'+n+'"]',typeof l=="string"&&(n+='[crossorigin="'+l+'"]'),$h.has(n)||($h.add(n),t={rel:t,crossOrigin:l,href:e},a.querySelector(n)===null&&(e=a.createElement("link"),te(e,"link",t),wt(e),a.head.appendChild(e)))}}function E0(t){jl.D(t),Ih("dns-prefetch",t,null)}function T0(t,e){jl.C(t,e),Ih("preconnect",t,e)}function _0(t,e,l){jl.L(t,e,l);var a=Nn;if(a&&t&&e){var n='link[rel="preload"][as="'+Ce(e)+'"]';e==="image"&&l&&l.imageSrcSet?(n+='[imagesrcset="'+Ce(l.imageSrcSet)+'"]',typeof l.imageSizes=="string"&&(n+='[imagesizes="'+Ce(l.imageSizes)+'"]')):n+='[href="'+Ce(t)+'"]';var u=n;switch(e){case"style":u=zn(t);break;case"script":u=An(t)}Le.has(u)||(t=D({rel:"preload",href:e==="image"&&l&&l.imageSrcSet?void 0:t,as:e},l),Le.set(u,t),a.querySelector(n)!==null||e==="style"&&a.querySelector(Ru(u))||e==="script"&&a.querySelector(Cu(u))||(e=a.createElement("link"),te(e,"link",t),wt(e),a.head.appendChild(e)))}}function O0(t,e){jl.m(t,e);var l=Nn;if(l&&t){var a=e&&typeof e.as=="string"?e.as:"script",n='link[rel="modulepreload"][as="'+Ce(a)+'"][href="'+Ce(t)+'"]',u=n;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":u=An(t)}if(!Le.has(u)&&(t=D({rel:"modulepreload",href:t},e),Le.set(u,t),l.querySelector(n)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(l.querySelector(Cu(u)))return}a=l.createElement("link"),te(a,"link",t),wt(a),l.head.appendChild(a)}}}function N0(t,e,l){jl.S(t,e,l);var a=Nn;if(a&&t){var n=Fa(a).hoistableStyles,u=zn(t);e=e||"default";var c=n.get(u);if(!c){var d={loading:0,preload:null};if(c=a.querySelector(Ru(u)))d.loading=5;else{t=D({rel:"stylesheet",href:t,"data-precedence":e},l),(l=Le.get(u))&&Af(t,l);var m=c=a.createElement("link");wt(m),te(m,"link",t),m._p=new Promise(function(j,z){m.onload=j,m.onerror=z}),m.addEventListener("load",function(){d.loading|=1}),m.addEventListener("error",function(){d.loading|=2}),d.loading|=4,Pi(c,e,a)}c={type:"stylesheet",instance:c,count:1,state:d},n.set(u,c)}}}function z0(t,e){jl.X(t,e);var l=Nn;if(l&&t){var a=Fa(l).hoistableScripts,n=An(t),u=a.get(n);u||(u=l.querySelector(Cu(n)),u||(t=D({src:t,async:!0},e),(e=Le.get(n))&&Mf(t,e),u=l.createElement("script"),wt(u),te(u,"link",t),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function A0(t,e){jl.M(t,e);var l=Nn;if(l&&t){var a=Fa(l).hoistableScripts,n=An(t),u=a.get(n);u||(u=l.querySelector(Cu(n)),u||(t=D({src:t,async:!0,type:"module"},e),(e=Le.get(n))&&Mf(t,e),u=l.createElement("script"),wt(u),te(u,"link",t),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function Ph(t,e,l,a){var n=(n=ut.current)?Ii(n):null;if(!n)throw Error(r(446));switch(t){case"meta":case"title":return null;case"style":return typeof l.precedence=="string"&&typeof l.href=="string"?(e=zn(l.href),l=Fa(n).hoistableStyles,a=l.get(e),a||(a={type:"style",instance:null,count:0,state:null},l.set(e,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(l.rel==="stylesheet"&&typeof l.href=="string"&&typeof l.precedence=="string"){t=zn(l.href);var u=Fa(n).hoistableStyles,c=u.get(t);if(c||(n=n.ownerDocument||n,c={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},u.set(t,c),(u=n.querySelector(Ru(t)))&&!u._p&&(c.instance=u,c.state.loading=5),Le.has(t)||(l={rel:"preload",as:"style",href:l.href,crossOrigin:l.crossOrigin,integrity:l.integrity,media:l.media,hrefLang:l.hrefLang,referrerPolicy:l.referrerPolicy},Le.set(t,l),u||M0(n,t,l,c.state))),e&&a===null)throw Error(r(528,""));return c}if(e&&a!==null)throw Error(r(529,""));return null;case"script":return e=l.async,l=l.src,typeof l=="string"&&e&&typeof e!="function"&&typeof e!="symbol"?(e=An(l),l=Fa(n).hoistableScripts,a=l.get(e),a||(a={type:"script",instance:null,count:0,state:null},l.set(e,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,t))}}function zn(t){return'href="'+Ce(t)+'"'}function Ru(t){return'link[rel="stylesheet"]['+t+"]"}function tm(t){return D({},t,{"data-precedence":t.precedence,precedence:null})}function M0(t,e,l,a){t.querySelector('link[rel="preload"][as="style"]['+e+"]")?a.loading=1:(e=t.createElement("link"),a.preload=e,e.addEventListener("load",function(){return a.loading|=1}),e.addEventListener("error",function(){return a.loading|=2}),te(e,"link",l),wt(e),t.head.appendChild(e))}function An(t){return'[src="'+Ce(t)+'"]'}function Cu(t){return"script[async]"+t}function em(t,e,l){if(e.count++,e.instance===null)switch(e.type){case"style":var a=t.querySelector('style[data-href~="'+Ce(l.href)+'"]');if(a)return e.instance=a,wt(a),a;var n=D({},l,{"data-href":l.href,"data-precedence":l.precedence,href:null,precedence:null});return a=(t.ownerDocument||t).createElement("style"),wt(a),te(a,"style",n),Pi(a,l.precedence,t),e.instance=a;case"stylesheet":n=zn(l.href);var u=t.querySelector(Ru(n));if(u)return e.state.loading|=4,e.instance=u,wt(u),u;a=tm(l),(n=Le.get(n))&&Af(a,n),u=(t.ownerDocument||t).createElement("link"),wt(u);var c=u;return c._p=new Promise(function(d,m){c.onload=d,c.onerror=m}),te(u,"link",a),e.state.loading|=4,Pi(u,l.precedence,t),e.instance=u;case"script":return u=An(l.src),(n=t.querySelector(Cu(u)))?(e.instance=n,wt(n),n):(a=l,(n=Le.get(u))&&(a=D({},l),Mf(a,n)),t=t.ownerDocument||t,n=t.createElement("script"),wt(n),te(n,"link",a),t.head.appendChild(n),e.instance=n);case"void":return null;default:throw Error(r(443,e.type))}else e.type==="stylesheet"&&(e.state.loading&4)===0&&(a=e.instance,e.state.loading|=4,Pi(a,l.precedence,t));return e.instance}function Pi(t,e,l){for(var a=l.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),n=a.length?a[a.length-1]:null,u=n,c=0;c title"):null)}function R0(t,e,l){if(l===1||e.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof e.precedence!="string"||typeof e.href!="string"||e.href==="")break;return!0;case"link":if(typeof e.rel!="string"||typeof e.href!="string"||e.href===""||e.onLoad||e.onError)break;switch(e.rel){case"stylesheet":return t=e.disabled,typeof e.precedence=="string"&&t==null;default:return!0}case"script":if(e.async&&typeof e.async!="function"&&typeof e.async!="symbol"&&!e.onLoad&&!e.onError&&e.src&&typeof e.src=="string")return!0}return!1}function nm(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function C0(t,e,l,a){if(l.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(l.state.loading&4)===0){if(l.instance===null){var n=zn(a.href),u=e.querySelector(Ru(n));if(u){e=u._p,e!==null&&typeof e=="object"&&typeof e.then=="function"&&(t.count++,t=es.bind(t),e.then(t,t)),l.state.loading|=4,l.instance=u,wt(u);return}u=e.ownerDocument||e,a=tm(a),(n=Le.get(n))&&Af(a,n),u=u.createElement("link"),wt(u);var c=u;c._p=new Promise(function(d,m){c.onload=d,c.onerror=m}),te(u,"link",a),l.instance=u}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(l,e),(e=l.state.preload)&&(l.state.loading&3)===0&&(t.count++,l=es.bind(t),e.addEventListener("load",l),e.addEventListener("error",l))}}var Rf=0;function D0(t,e){return t.stylesheets&&t.count===0&&as(t,t.stylesheets),0Rf?50:800)+e);return t.unsuspend=l,function(){t.unsuspend=null,clearTimeout(a),clearTimeout(n)}}:null}function es(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)as(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var ls=null;function as(t,e){t.stylesheets=null,t.unsuspend!==null&&(t.count++,ls=new Map,e.forEach(U0,t),ls=null,es.call(t))}function U0(t,e){if(!(e.state.loading&4)){var l=ls.get(t);if(l)var a=l.get(null);else{l=new Map,ls.set(t,l);for(var n=t.querySelectorAll("link[data-precedence],style[data-precedence]"),u=0;u"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(s)}catch(i){console.error(i)}}return s(),Gf.exports=$0(),Gf.exports}var P0=I0(),Kn=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(s){return this.listeners.add(s),this.onSubscribe(),()=>{this.listeners.delete(s),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}},Ha,na,Rn,Vm,tg=(Vm=class extends Kn{constructor(){super();w(this,Ha);w(this,na);w(this,Rn);B(this,Rn,i=>{if(typeof window<"u"&&window.addEventListener){const o=()=>i();return window.addEventListener("visibilitychange",o,!1),()=>{window.removeEventListener("visibilitychange",o)}}})}onSubscribe(){v(this,na)||this.setEventListener(v(this,Rn))}onUnsubscribe(){var i;this.hasListeners()||((i=v(this,na))==null||i.call(this),B(this,na,void 0))}setEventListener(i){var o;B(this,Rn,i),(o=v(this,na))==null||o.call(this),B(this,na,i(r=>{typeof r=="boolean"?this.setFocused(r):this.onFocus()}))}setFocused(i){v(this,Ha)!==i&&(B(this,Ha,i),this.onFocus())}onFocus(){const i=this.isFocused();this.listeners.forEach(o=>{o(i)})}isFocused(){return typeof v(this,Ha)=="boolean"?v(this,Ha):globalThis.document?.visibilityState!=="hidden"}},Ha=new WeakMap,na=new WeakMap,Rn=new WeakMap,Vm),or=new tg,eg={setTimeout:(s,i)=>setTimeout(s,i),clearTimeout:s=>clearTimeout(s),setInterval:(s,i)=>setInterval(s,i),clearInterval:s=>clearInterval(s)},ua,fr,wm,lg=(wm=class{constructor(){w(this,ua,eg);w(this,fr,!1)}setTimeoutProvider(s){B(this,ua,s)}setTimeout(s,i){return v(this,ua).setTimeout(s,i)}clearTimeout(s){v(this,ua).clearTimeout(s)}setInterval(s,i){return v(this,ua).setInterval(s,i)}clearInterval(s){v(this,ua).clearInterval(s)}},ua=new WeakMap,fr=new WeakMap,wm),qa=new lg;function ag(s){setTimeout(s,0)}var ng=typeof window>"u"||"Deno"in globalThis;function ce(){}function ug(s,i){return typeof s=="function"?s(i):s}function wf(s){return typeof s=="number"&&s>=0&&s!==1/0}function lv(s,i){return Math.max(s+(i||0)-Date.now(),0)}function ma(s,i){return typeof s=="function"?s(i):s}function Xe(s,i){return typeof s=="function"?s(i):s}function Am(s,i){const{type:o="all",exact:r,fetchStatus:h,predicate:S,queryKey:_,stale:O}=s;if(_){if(r){if(i.queryHash!==dr(_,i.options))return!1}else if(!Lu(i.queryKey,_))return!1}if(o!=="all"){const T=i.isActive();if(o==="active"&&!T||o==="inactive"&&T)return!1}return!(typeof O=="boolean"&&i.isStale()!==O||h&&h!==i.state.fetchStatus||S&&!S(i))}function Mm(s,i){const{exact:o,status:r,predicate:h,mutationKey:S}=s;if(S){if(!i.options.mutationKey)return!1;if(o){if(Va(i.options.mutationKey)!==Va(S))return!1}else if(!Lu(i.options.mutationKey,S))return!1}return!(r&&i.state.status!==r||h&&!h(i))}function dr(s,i){return(i?.queryKeyHashFn||Va)(s)}function Va(s){return JSON.stringify(s,(i,o)=>Jf(o)?Object.keys(o).sort().reduce((r,h)=>(r[h]=o[h],r),{}):o)}function Lu(s,i){return s===i?!0:typeof s!=typeof i?!1:s&&i&&typeof s=="object"&&typeof i=="object"?Object.keys(i).every(o=>Lu(s[o],i[o])):!1}var ig=Object.prototype.hasOwnProperty;function av(s,i,o=0){if(s===i)return s;if(o>500)return i;const r=Rm(s)&&Rm(i);if(!r&&!(Jf(s)&&Jf(i)))return i;const S=(r?s:Object.keys(s)).length,_=r?i:Object.keys(i),O=_.length,T=r?new Array(O):{};let p=0;for(let U=0;U{qa.setTimeout(i,s)})}function kf(s,i,o){return typeof o.structuralSharing=="function"?o.structuralSharing(s,i):o.structuralSharing!==!1?av(s,i):i}function cg(s,i,o=0){const r=[...s,i];return o&&r.length>o?r.slice(1):r}function fg(s,i,o=0){const r=[i,...s];return o&&r.length>o?r.slice(0,-1):r}var hr=Symbol();function nv(s,i){return!s.queryFn&&i?.initialPromise?()=>i.initialPromise:!s.queryFn||s.queryFn===hr?()=>Promise.reject(new Error(`Missing queryFn: '${s.queryHash}'`)):s.queryFn}function mr(s,i){return typeof s=="function"?s(...i):!!s}function rg(s,i,o){let r=!1,h;return Object.defineProperty(s,"signal",{enumerable:!0,get:()=>(h??(h=i()),r||(r=!0,h.aborted?o():h.addEventListener("abort",o,{once:!0})),h)}),s}var Gu=(()=>{let s=()=>ng;return{isServer(){return s()},setIsServer(i){s=i}}})();function Ff(){let s,i;const o=new Promise((h,S)=>{s=h,i=S});o.status="pending",o.catch(()=>{});function r(h){Object.assign(o,h),delete o.resolve,delete o.reject}return o.resolve=h=>{r({status:"fulfilled",value:h}),s(h)},o.reject=h=>{r({status:"rejected",reason:h}),i(h)},o}var og=ag;function dg(){let s=[],i=0,o=O=>{O()},r=O=>{O()},h=og;const S=O=>{i?s.push(O):h(()=>{o(O)})},_=()=>{const O=s;s=[],O.length&&h(()=>{r(()=>{O.forEach(T=>{o(T)})})})};return{batch:O=>{let T;i++;try{T=O()}finally{i--,i||_()}return T},batchCalls:O=>(...T)=>{S(()=>{O(...T)})},schedule:S,setNotifyFunction:O=>{o=O},setBatchNotifyFunction:O=>{r=O},setScheduler:O=>{h=O}}}var Vt=dg(),Cn,ia,Dn,Jm,hg=(Jm=class extends Kn{constructor(){super();w(this,Cn,!0);w(this,ia);w(this,Dn);B(this,Dn,i=>{if(typeof window<"u"&&window.addEventListener){const o=()=>i(!0),r=()=>i(!1);return window.addEventListener("online",o,!1),window.addEventListener("offline",r,!1),()=>{window.removeEventListener("online",o),window.removeEventListener("offline",r)}}})}onSubscribe(){v(this,ia)||this.setEventListener(v(this,Dn))}onUnsubscribe(){var i;this.hasListeners()||((i=v(this,ia))==null||i.call(this),B(this,ia,void 0))}setEventListener(i){var o;B(this,Dn,i),(o=v(this,ia))==null||o.call(this),B(this,ia,i(this.setOnline.bind(this)))}setOnline(i){v(this,Cn)!==i&&(B(this,Cn,i),this.listeners.forEach(r=>{r(i)}))}isOnline(){return v(this,Cn)}},Cn=new WeakMap,ia=new WeakMap,Dn=new WeakMap,Jm),gs=new hg;function mg(s){return Math.min(1e3*2**s,3e4)}function uv(s){return(s??"online")==="online"?gs.isOnline():!0}var Wf=class extends Error{constructor(s){super("CancelledError"),this.revert=s?.revert,this.silent=s?.silent}};function iv(s){let i=!1,o=0,r;const h=Ff(),S=()=>h.status!=="pending",_=G=>{if(!S()){const P=new Wf(G);Y(P),s.onCancel?.(P)}},O=()=>{i=!0},T=()=>{i=!1},p=()=>or.isFocused()&&(s.networkMode==="always"||gs.isOnline())&&s.canRun(),U=()=>uv(s.networkMode)&&s.canRun(),D=G=>{S()||(r?.(),h.resolve(G))},Y=G=>{S()||(r?.(),h.reject(G))},k=()=>new Promise(G=>{r=P=>{(S()||p())&&G(P)},s.onPause?.()}).then(()=>{r=void 0,S()||s.onContinue?.()}),$=()=>{if(S())return;let G;const P=o===0?s.initialPromise:void 0;try{G=P??s.fn()}catch(dt){G=Promise.reject(dt)}Promise.resolve(G).then(D).catch(dt=>{if(S())return;const Ot=s.retry??(Gu.isServer()?0:3),K=s.retryDelay??mg,vt=typeof K=="function"?K(o,dt):K,Yt=Ot===!0||typeof Ot=="number"&&op()?void 0:k()).then(()=>{i?Y(dt):$()})})};return{promise:h,status:()=>h.status,cancel:_,continue:()=>(r?.(),h),cancelRetry:O,continueRetry:T,canStart:U,start:()=>(U()?$():k().then($),h)}}var Ba,km,sv=(km=class{constructor(){w(this,Ba)}destroy(){this.clearGcTimeout()}scheduleGc(){this.clearGcTimeout(),wf(this.gcTime)&&B(this,Ba,qa.setTimeout(()=>{this.optionalRemove()},this.gcTime))}updateGcTime(s){this.gcTime=Math.max(this.gcTime||0,s??(Gu.isServer()?1/0:300*1e3))}clearGcTimeout(){v(this,Ba)&&(qa.clearTimeout(v(this,Ba)),B(this,Ba,void 0))}},Ba=new WeakMap,km),Qa,Un,Ge,Ya,kt,Xu,La,Ae,cv,El,Fm,vg=(Fm=class extends sv{constructor(i){super();w(this,Ae);w(this,Qa);w(this,Un);w(this,Ge);w(this,Ya);w(this,kt);w(this,Xu);w(this,La);B(this,La,!1),B(this,Xu,i.defaultOptions),this.setOptions(i.options),this.observers=[],B(this,Ya,i.client),B(this,Ge,v(this,Ya).getQueryCache()),this.queryKey=i.queryKey,this.queryHash=i.queryHash,B(this,Qa,Um(this.options)),this.state=i.state??v(this,Qa),this.scheduleGc()}get meta(){return this.options.meta}get promise(){return v(this,kt)?.promise}setOptions(i){if(this.options={...v(this,Xu),...i},this.updateGcTime(this.options.gcTime),this.state&&this.state.data===void 0){const o=Um(this.options);o.data!==void 0&&(this.setState(Dm(o.data,o.dataUpdatedAt)),B(this,Qa,o))}}optionalRemove(){!this.observers.length&&this.state.fetchStatus==="idle"&&v(this,Ge).remove(this)}setData(i,o){const r=kf(this.state.data,i,this.options);return at(this,Ae,El).call(this,{data:r,type:"success",dataUpdatedAt:o?.updatedAt,manual:o?.manual}),r}setState(i,o){at(this,Ae,El).call(this,{type:"setState",state:i,setStateOptions:o})}cancel(i){const o=v(this,kt)?.promise;return v(this,kt)?.cancel(i),o?o.then(ce).catch(ce):Promise.resolve()}destroy(){super.destroy(),this.cancel({silent:!0})}get resetState(){return v(this,Qa)}reset(){this.destroy(),this.setState(this.resetState)}isActive(){return this.observers.some(i=>Xe(i.options.enabled,this)!==!1)}isDisabled(){return this.getObserversCount()>0?!this.isActive():this.options.queryFn===hr||!this.isFetched()}isFetched(){return this.state.dataUpdateCount+this.state.errorUpdateCount>0}isStatic(){return this.getObserversCount()>0?this.observers.some(i=>ma(i.options.staleTime,this)==="static"):!1}isStale(){return this.getObserversCount()>0?this.observers.some(i=>i.getCurrentResult().isStale):this.state.data===void 0||this.state.isInvalidated}isStaleByTime(i=0){return this.state.data===void 0?!0:i==="static"?!1:this.state.isInvalidated?!0:!lv(this.state.dataUpdatedAt,i)}onFocus(){this.observers.find(o=>o.shouldFetchOnWindowFocus())?.refetch({cancelRefetch:!1}),v(this,kt)?.continue()}onOnline(){this.observers.find(o=>o.shouldFetchOnReconnect())?.refetch({cancelRefetch:!1}),v(this,kt)?.continue()}addObserver(i){this.observers.includes(i)||(this.observers.push(i),this.clearGcTimeout(),v(this,Ge).notify({type:"observerAdded",query:this,observer:i}))}removeObserver(i){this.observers.includes(i)&&(this.observers=this.observers.filter(o=>o!==i),this.observers.length||(v(this,kt)&&(v(this,La)||at(this,Ae,cv).call(this)?v(this,kt).cancel({revert:!0}):v(this,kt).cancelRetry()),this.scheduleGc()),v(this,Ge).notify({type:"observerRemoved",query:this,observer:i}))}getObserversCount(){return this.observers.length}invalidate(){this.state.isInvalidated||at(this,Ae,El).call(this,{type:"invalidate"})}async fetch(i,o){if(this.state.fetchStatus!=="idle"&&v(this,kt)?.status()!=="rejected"){if(this.state.data!==void 0&&o?.cancelRefetch)this.cancel({silent:!0});else if(v(this,kt))return v(this,kt).continueRetry(),v(this,kt).promise}if(i&&this.setOptions(i),!this.options.queryFn){const T=this.observers.find(p=>p.options.queryFn);T&&this.setOptions(T.options)}const r=new AbortController,h=T=>{Object.defineProperty(T,"signal",{enumerable:!0,get:()=>(B(this,La,!0),r.signal)})},S=()=>{const T=nv(this.options,o),U=(()=>{const D={client:v(this,Ya),queryKey:this.queryKey,meta:this.meta};return h(D),D})();return B(this,La,!1),this.options.persister?this.options.persister(T,U,this):T(U)},O=(()=>{const T={fetchOptions:o,options:this.options,queryKey:this.queryKey,client:v(this,Ya),state:this.state,fetchFn:S};return h(T),T})();this.options.behavior?.onFetch(O,this),B(this,Un,this.state),(this.state.fetchStatus==="idle"||this.state.fetchMeta!==O.fetchOptions?.meta)&&at(this,Ae,El).call(this,{type:"fetch",meta:O.fetchOptions?.meta}),B(this,kt,iv({initialPromise:o?.initialPromise,fn:O.fetchFn,onCancel:T=>{T instanceof Wf&&T.revert&&this.setState({...v(this,Un),fetchStatus:"idle"}),r.abort()},onFail:(T,p)=>{at(this,Ae,El).call(this,{type:"failed",failureCount:T,error:p})},onPause:()=>{at(this,Ae,El).call(this,{type:"pause"})},onContinue:()=>{at(this,Ae,El).call(this,{type:"continue"})},retry:O.options.retry,retryDelay:O.options.retryDelay,networkMode:O.options.networkMode,canRun:()=>!0}));try{const T=await v(this,kt).start();if(T===void 0)throw new Error(`${this.queryHash} data is undefined`);return this.setData(T),v(this,Ge).config.onSuccess?.(T,this),v(this,Ge).config.onSettled?.(T,this.state.error,this),T}catch(T){if(T instanceof Wf){if(T.silent)return v(this,kt).promise;if(T.revert){if(this.state.data===void 0)throw T;return this.state.data}}throw at(this,Ae,El).call(this,{type:"error",error:T}),v(this,Ge).config.onError?.(T,this),v(this,Ge).config.onSettled?.(this.state.data,T,this),T}finally{this.scheduleGc()}}},Qa=new WeakMap,Un=new WeakMap,Ge=new WeakMap,Ya=new WeakMap,kt=new WeakMap,Xu=new WeakMap,La=new WeakMap,Ae=new WeakSet,cv=function(){return this.state.fetchStatus==="paused"&&this.state.status==="pending"},El=function(i){const o=r=>{switch(i.type){case"failed":return{...r,fetchFailureCount:i.failureCount,fetchFailureReason:i.error};case"pause":return{...r,fetchStatus:"paused"};case"continue":return{...r,fetchStatus:"fetching"};case"fetch":return{...r,...fv(r.data,this.options),fetchMeta:i.meta??null};case"success":const h={...r,...Dm(i.data,i.dataUpdatedAt),dataUpdateCount:r.dataUpdateCount+1,...!i.manual&&{fetchStatus:"idle",fetchFailureCount:0,fetchFailureReason:null}};return B(this,Un,i.manual?h:void 0),h;case"error":const S=i.error;return{...r,error:S,errorUpdateCount:r.errorUpdateCount+1,errorUpdatedAt:Date.now(),fetchFailureCount:r.fetchFailureCount+1,fetchFailureReason:S,fetchStatus:"idle",status:"error",isInvalidated:!0};case"invalidate":return{...r,isInvalidated:!0};case"setState":return{...r,...i.state}}};this.state=o(this.state),Vt.batch(()=>{this.observers.forEach(r=>{r.onQueryUpdate()}),v(this,Ge).notify({query:this,type:"updated",action:i})})},Fm);function fv(s,i){return{fetchFailureCount:0,fetchFailureReason:null,fetchStatus:uv(i.networkMode)?"fetching":"paused",...s===void 0&&{error:null,status:"pending"}}}function Dm(s,i){return{data:s,dataUpdatedAt:i??Date.now(),error:null,isInvalidated:!1,status:"success"}}function Um(s){const i=typeof s.initialData=="function"?s.initialData():s.initialData,o=i!==void 0,r=o?typeof s.initialDataUpdatedAt=="function"?s.initialDataUpdatedAt():s.initialDataUpdatedAt:0;return{data:i,dataUpdateCount:0,dataUpdatedAt:o?r??Date.now():0,error:null,errorUpdateCount:0,errorUpdatedAt:0,fetchFailureCount:0,fetchFailureReason:null,fetchMeta:null,isInvalidated:!1,status:o?"success":"pending",fetchStatus:"idle"}}var ge,ot,Ku,se,Ga,qn,Tl,sa,Zu,Hn,Bn,Xa,Ka,ca,Qn,bt,Yu,$f,If,Pf,tr,er,lr,ar,rv,Wm,yg=(Wm=class extends Kn{constructor(i,o){super();w(this,bt);w(this,ge);w(this,ot);w(this,Ku);w(this,se);w(this,Ga);w(this,qn);w(this,Tl);w(this,sa);w(this,Zu);w(this,Hn);w(this,Bn);w(this,Xa);w(this,Ka);w(this,ca);w(this,Qn,new Set);this.options=o,B(this,ge,i),B(this,sa,null),B(this,Tl,Ff()),this.bindMethods(),this.setOptions(o)}bindMethods(){this.refetch=this.refetch.bind(this)}onSubscribe(){this.listeners.size===1&&(v(this,ot).addObserver(this),qm(v(this,ot),this.options)?at(this,bt,Yu).call(this):this.updateResult(),at(this,bt,tr).call(this))}onUnsubscribe(){this.hasListeners()||this.destroy()}shouldFetchOnReconnect(){return nr(v(this,ot),this.options,this.options.refetchOnReconnect)}shouldFetchOnWindowFocus(){return nr(v(this,ot),this.options,this.options.refetchOnWindowFocus)}destroy(){this.listeners=new Set,at(this,bt,er).call(this),at(this,bt,lr).call(this),v(this,ot).removeObserver(this)}setOptions(i){const o=this.options,r=v(this,ot);if(this.options=v(this,ge).defaultQueryOptions(i),this.options.enabled!==void 0&&typeof this.options.enabled!="boolean"&&typeof this.options.enabled!="function"&&typeof Xe(this.options.enabled,v(this,ot))!="boolean")throw new Error("Expected enabled to be a boolean or a callback that returns a boolean");at(this,bt,ar).call(this),v(this,ot).setOptions(this.options),o._defaulted&&!ys(this.options,o)&&v(this,ge).getQueryCache().notify({type:"observerOptionsUpdated",query:v(this,ot),observer:this});const h=this.hasListeners();h&&Hm(v(this,ot),r,this.options,o)&&at(this,bt,Yu).call(this),this.updateResult(),h&&(v(this,ot)!==r||Xe(this.options.enabled,v(this,ot))!==Xe(o.enabled,v(this,ot))||ma(this.options.staleTime,v(this,ot))!==ma(o.staleTime,v(this,ot)))&&at(this,bt,$f).call(this);const S=at(this,bt,If).call(this);h&&(v(this,ot)!==r||Xe(this.options.enabled,v(this,ot))!==Xe(o.enabled,v(this,ot))||S!==v(this,ca))&&at(this,bt,Pf).call(this,S)}getOptimisticResult(i){const o=v(this,ge).getQueryCache().build(v(this,ge),i),r=this.createResult(o,i);return pg(this,r)&&(B(this,se,r),B(this,qn,this.options),B(this,Ga,v(this,ot).state)),r}getCurrentResult(){return v(this,se)}trackResult(i,o){return new Proxy(i,{get:(r,h)=>(this.trackProp(h),o?.(h),h==="promise"&&(this.trackProp("data"),!this.options.experimental_prefetchInRender&&v(this,Tl).status==="pending"&&v(this,Tl).reject(new Error("experimental_prefetchInRender feature flag is not enabled"))),Reflect.get(r,h))})}trackProp(i){v(this,Qn).add(i)}getCurrentQuery(){return v(this,ot)}refetch({...i}={}){return this.fetch({...i})}fetchOptimistic(i){const o=v(this,ge).defaultQueryOptions(i),r=v(this,ge).getQueryCache().build(v(this,ge),o);return r.fetch().then(()=>this.createResult(r,o))}fetch(i){return at(this,bt,Yu).call(this,{...i,cancelRefetch:i.cancelRefetch??!0}).then(()=>(this.updateResult(),v(this,se)))}createResult(i,o){const r=v(this,ot),h=this.options,S=v(this,se),_=v(this,Ga),O=v(this,qn),p=i!==r?i.state:v(this,Ku),{state:U}=i;let D={...U},Y=!1,k;if(o._optimisticResults){const gt=this.hasListeners(),ae=!gt&&qm(i,o),ne=gt&&Hm(i,r,o,h);(ae||ne)&&(D={...D,...fv(U.data,i.options)}),o._optimisticResults==="isRestoring"&&(D.fetchStatus="idle")}let{error:$,errorUpdatedAt:G,status:P}=D;k=D.data;let dt=!1;if(o.placeholderData!==void 0&&k===void 0&&P==="pending"){let gt;S?.isPlaceholderData&&o.placeholderData===O?.placeholderData?(gt=S.data,dt=!0):gt=typeof o.placeholderData=="function"?o.placeholderData(v(this,Bn)?.state.data,v(this,Bn)):o.placeholderData,gt!==void 0&&(P="success",k=kf(S?.data,gt,o),Y=!0)}if(o.select&&k!==void 0&&!dt)if(S&&k===_?.data&&o.select===v(this,Zu))k=v(this,Hn);else try{B(this,Zu,o.select),k=o.select(k),k=kf(S?.data,k,o),B(this,Hn,k),B(this,sa,null)}catch(gt){B(this,sa,gt)}v(this,sa)&&($=v(this,sa),k=v(this,Hn),G=Date.now(),P="error");const Ot=D.fetchStatus==="fetching",K=P==="pending",vt=P==="error",Yt=K&&Ot,Ct=k!==void 0,nt={status:P,fetchStatus:D.fetchStatus,isPending:K,isSuccess:P==="success",isError:vt,isInitialLoading:Yt,isLoading:Yt,data:k,dataUpdatedAt:D.dataUpdatedAt,error:$,errorUpdatedAt:G,failureCount:D.fetchFailureCount,failureReason:D.fetchFailureReason,errorUpdateCount:D.errorUpdateCount,isFetched:i.isFetched(),isFetchedAfterMount:D.dataUpdateCount>p.dataUpdateCount||D.errorUpdateCount>p.errorUpdateCount,isFetching:Ot,isRefetching:Ot&&!K,isLoadingError:vt&&!Ct,isPaused:D.fetchStatus==="paused",isPlaceholderData:Y,isRefetchError:vt&&Ct,isStale:vr(i,o),refetch:this.refetch,promise:v(this,Tl),isEnabled:Xe(o.enabled,i)!==!1};if(this.options.experimental_prefetchInRender){const gt=nt.data!==void 0,ae=nt.status==="error"&&!gt,ne=fe=>{ae?fe.reject(nt.error):gt&&fe.resolve(nt.data)},Ft=()=>{const fe=B(this,Tl,nt.promise=Ff());ne(fe)},Me=v(this,Tl);switch(Me.status){case"pending":i.queryHash===r.queryHash&&ne(Me);break;case"fulfilled":(ae||nt.data!==Me.value)&&Ft();break;case"rejected":(!ae||nt.error!==Me.reason)&&Ft();break}}return nt}updateResult(){const i=v(this,se),o=this.createResult(v(this,ot),this.options);if(B(this,Ga,v(this,ot).state),B(this,qn,this.options),v(this,Ga).data!==void 0&&B(this,Bn,v(this,ot)),ys(o,i))return;B(this,se,o);const r=()=>{if(!i)return!0;const{notifyOnChangeProps:h}=this.options,S=typeof h=="function"?h():h;if(S==="all"||!S&&!v(this,Qn).size)return!0;const _=new Set(S??v(this,Qn));return this.options.throwOnError&&_.add("error"),Object.keys(v(this,se)).some(O=>{const T=O;return v(this,se)[T]!==i[T]&&_.has(T)})};at(this,bt,rv).call(this,{listeners:r()})}onQueryUpdate(){this.updateResult(),this.hasListeners()&&at(this,bt,tr).call(this)}},ge=new WeakMap,ot=new WeakMap,Ku=new WeakMap,se=new WeakMap,Ga=new WeakMap,qn=new WeakMap,Tl=new WeakMap,sa=new WeakMap,Zu=new WeakMap,Hn=new WeakMap,Bn=new WeakMap,Xa=new WeakMap,Ka=new WeakMap,ca=new WeakMap,Qn=new WeakMap,bt=new WeakSet,Yu=function(i){at(this,bt,ar).call(this);let o=v(this,ot).fetch(this.options,i);return i?.throwOnError||(o=o.catch(ce)),o},$f=function(){at(this,bt,er).call(this);const i=ma(this.options.staleTime,v(this,ot));if(Gu.isServer()||v(this,se).isStale||!wf(i))return;const r=lv(v(this,se).dataUpdatedAt,i)+1;B(this,Xa,qa.setTimeout(()=>{v(this,se).isStale||this.updateResult()},r))},If=function(){return(typeof this.options.refetchInterval=="function"?this.options.refetchInterval(v(this,ot)):this.options.refetchInterval)??!1},Pf=function(i){at(this,bt,lr).call(this),B(this,ca,i),!(Gu.isServer()||Xe(this.options.enabled,v(this,ot))===!1||!wf(v(this,ca))||v(this,ca)===0)&&B(this,Ka,qa.setInterval(()=>{(this.options.refetchIntervalInBackground||or.isFocused())&&at(this,bt,Yu).call(this)},v(this,ca)))},tr=function(){at(this,bt,$f).call(this),at(this,bt,Pf).call(this,at(this,bt,If).call(this))},er=function(){v(this,Xa)&&(qa.clearTimeout(v(this,Xa)),B(this,Xa,void 0))},lr=function(){v(this,Ka)&&(qa.clearInterval(v(this,Ka)),B(this,Ka,void 0))},ar=function(){const i=v(this,ge).getQueryCache().build(v(this,ge),this.options);if(i===v(this,ot))return;const o=v(this,ot);B(this,ot,i),B(this,Ku,i.state),this.hasListeners()&&(o?.removeObserver(this),i.addObserver(this))},rv=function(i){Vt.batch(()=>{i.listeners&&this.listeners.forEach(o=>{o(v(this,se))}),v(this,ge).getQueryCache().notify({query:v(this,ot),type:"observerResultsUpdated"})})},Wm);function gg(s,i){return Xe(i.enabled,s)!==!1&&s.state.data===void 0&&!(s.state.status==="error"&&i.retryOnMount===!1)}function qm(s,i){return gg(s,i)||s.state.data!==void 0&&nr(s,i,i.refetchOnMount)}function nr(s,i,o){if(Xe(i.enabled,s)!==!1&&ma(i.staleTime,s)!=="static"){const r=typeof o=="function"?o(s):o;return r==="always"||r!==!1&&vr(s,i)}return!1}function Hm(s,i,o,r){return(s!==i||Xe(r.enabled,s)===!1)&&(!o.suspense||s.state.status!=="error")&&vr(s,o)}function vr(s,i){return Xe(i.enabled,s)!==!1&&s.isStaleByTime(ma(i.staleTime,s))}function pg(s,i){return!ys(s.getCurrentResult(),i)}function Bm(s){return{onFetch:(i,o)=>{const r=i.options,h=i.fetchOptions?.meta?.fetchMore?.direction,S=i.state.data?.pages||[],_=i.state.data?.pageParams||[];let O={pages:[],pageParams:[]},T=0;const p=async()=>{let U=!1;const D=$=>{rg($,()=>i.signal,()=>U=!0)},Y=nv(i.options,i.fetchOptions),k=async($,G,P)=>{if(U)return Promise.reject();if(G==null&&$.pages.length)return Promise.resolve($);const Ot=(()=>{const Ct={client:i.client,queryKey:i.queryKey,pageParam:G,direction:P?"backward":"forward",meta:i.options.meta};return D(Ct),Ct})(),K=await Y(Ot),{maxPages:vt}=i.options,Yt=P?fg:cg;return{pages:Yt($.pages,K,vt),pageParams:Yt($.pageParams,G,vt)}};if(h&&S.length){const $=h==="backward",G=$?bg:Qm,P={pages:S,pageParams:_},dt=G(r,P);O=await k(P,dt,$)}else{const $=s??S.length;do{const G=T===0?_[0]??r.initialPageParam:Qm(r,O);if(T>0&&G==null)break;O=await k(O,G),T++}while(T<$)}return O};i.options.persister?i.fetchFn=()=>i.options.persister?.(p,{client:i.client,queryKey:i.queryKey,meta:i.options.meta,signal:i.signal},o):i.fetchFn=p}}}function Qm(s,{pages:i,pageParams:o}){const r=i.length-1;return i.length>0?s.getNextPageParam(i[r],i,o[r],o):void 0}function bg(s,{pages:i,pageParams:o}){return i.length>0?s.getPreviousPageParam?.(i[0],i,o[0],o):void 0}var Vu,el,le,Za,ll,aa,$m,Sg=($m=class extends sv{constructor(i){super();w(this,ll);w(this,Vu);w(this,el);w(this,le);w(this,Za);B(this,Vu,i.client),this.mutationId=i.mutationId,B(this,le,i.mutationCache),B(this,el,[]),this.state=i.state||ov(),this.setOptions(i.options),this.scheduleGc()}setOptions(i){this.options=i,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(i){v(this,el).includes(i)||(v(this,el).push(i),this.clearGcTimeout(),v(this,le).notify({type:"observerAdded",mutation:this,observer:i}))}removeObserver(i){B(this,el,v(this,el).filter(o=>o!==i)),this.scheduleGc(),v(this,le).notify({type:"observerRemoved",mutation:this,observer:i})}optionalRemove(){v(this,el).length||(this.state.status==="pending"?this.scheduleGc():v(this,le).remove(this))}continue(){return v(this,Za)?.continue()??this.execute(this.state.variables)}async execute(i){const o=()=>{at(this,ll,aa).call(this,{type:"continue"})},r={client:v(this,Vu),meta:this.options.meta,mutationKey:this.options.mutationKey};B(this,Za,iv({fn:()=>this.options.mutationFn?this.options.mutationFn(i,r):Promise.reject(new Error("No mutationFn found")),onFail:(_,O)=>{at(this,ll,aa).call(this,{type:"failed",failureCount:_,error:O})},onPause:()=>{at(this,ll,aa).call(this,{type:"pause"})},onContinue:o,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>v(this,le).canRun(this)}));const h=this.state.status==="pending",S=!v(this,Za).canStart();try{if(h)o();else{at(this,ll,aa).call(this,{type:"pending",variables:i,isPaused:S}),v(this,le).config.onMutate&&await v(this,le).config.onMutate(i,this,r);const O=await this.options.onMutate?.(i,r);O!==this.state.context&&at(this,ll,aa).call(this,{type:"pending",context:O,variables:i,isPaused:S})}const _=await v(this,Za).start();return await v(this,le).config.onSuccess?.(_,i,this.state.context,this,r),await this.options.onSuccess?.(_,i,this.state.context,r),await v(this,le).config.onSettled?.(_,null,this.state.variables,this.state.context,this,r),await this.options.onSettled?.(_,null,i,this.state.context,r),at(this,ll,aa).call(this,{type:"success",data:_}),_}catch(_){try{await v(this,le).config.onError?.(_,i,this.state.context,this,r)}catch(O){Promise.reject(O)}try{await this.options.onError?.(_,i,this.state.context,r)}catch(O){Promise.reject(O)}try{await v(this,le).config.onSettled?.(void 0,_,this.state.variables,this.state.context,this,r)}catch(O){Promise.reject(O)}try{await this.options.onSettled?.(void 0,_,i,this.state.context,r)}catch(O){Promise.reject(O)}throw at(this,ll,aa).call(this,{type:"error",error:_}),_}finally{v(this,le).runNext(this)}}},Vu=new WeakMap,el=new WeakMap,le=new WeakMap,Za=new WeakMap,ll=new WeakSet,aa=function(i){const o=r=>{switch(i.type){case"failed":return{...r,failureCount:i.failureCount,failureReason:i.error};case"pause":return{...r,isPaused:!0};case"continue":return{...r,isPaused:!1};case"pending":return{...r,context:i.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:i.isPaused,status:"pending",variables:i.variables,submittedAt:Date.now()};case"success":return{...r,data:i.data,failureCount:0,failureReason:null,error:null,status:"success",isPaused:!1};case"error":return{...r,data:void 0,error:i.error,failureCount:r.failureCount+1,failureReason:i.error,isPaused:!1,status:"error"}}};this.state=o(this.state),Vt.batch(()=>{v(this,el).forEach(r=>{r.onMutationUpdate(i)}),v(this,le).notify({mutation:this,type:"updated",action:i})})},$m);function ov(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:"idle",variables:void 0,submittedAt:0}}var _l,We,wu,Im,xg=(Im=class extends Kn{constructor(i={}){super();w(this,_l);w(this,We);w(this,wu);this.config=i,B(this,_l,new Set),B(this,We,new Map),B(this,wu,0)}build(i,o,r){const h=new Sg({client:i,mutationCache:this,mutationId:++os(this,wu)._,options:i.defaultMutationOptions(o),state:r});return this.add(h),h}add(i){v(this,_l).add(i);const o=hs(i);if(typeof o=="string"){const r=v(this,We).get(o);r?r.push(i):v(this,We).set(o,[i])}this.notify({type:"added",mutation:i})}remove(i){if(v(this,_l).delete(i)){const o=hs(i);if(typeof o=="string"){const r=v(this,We).get(o);if(r)if(r.length>1){const h=r.indexOf(i);h!==-1&&r.splice(h,1)}else r[0]===i&&v(this,We).delete(o)}}this.notify({type:"removed",mutation:i})}canRun(i){const o=hs(i);if(typeof o=="string"){const h=v(this,We).get(o)?.find(S=>S.state.status==="pending");return!h||h===i}else return!0}runNext(i){const o=hs(i);return typeof o=="string"?v(this,We).get(o)?.find(h=>h!==i&&h.state.isPaused)?.continue()??Promise.resolve():Promise.resolve()}clear(){Vt.batch(()=>{v(this,_l).forEach(i=>{this.notify({type:"removed",mutation:i})}),v(this,_l).clear(),v(this,We).clear()})}getAll(){return Array.from(v(this,_l))}find(i){const o={exact:!0,...i};return this.getAll().find(r=>Mm(o,r))}findAll(i={}){return this.getAll().filter(o=>Mm(i,o))}notify(i){Vt.batch(()=>{this.listeners.forEach(o=>{o(i)})})}resumePausedMutations(){const i=this.getAll().filter(o=>o.state.isPaused);return Vt.batch(()=>Promise.all(i.map(o=>o.continue().catch(ce))))}},_l=new WeakMap,We=new WeakMap,wu=new WeakMap,Im);function hs(s){return s.options.scope?.id}var Ol,fa,pe,Nl,zl,ms,ur,Pm,jg=(Pm=class extends Kn{constructor(o,r){super();w(this,zl);w(this,Ol);w(this,fa);w(this,pe);w(this,Nl);B(this,Ol,o),this.setOptions(r),this.bindMethods(),at(this,zl,ms).call(this)}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(o){const r=this.options;this.options=v(this,Ol).defaultMutationOptions(o),ys(this.options,r)||v(this,Ol).getMutationCache().notify({type:"observerOptionsUpdated",mutation:v(this,pe),observer:this}),r?.mutationKey&&this.options.mutationKey&&Va(r.mutationKey)!==Va(this.options.mutationKey)?this.reset():v(this,pe)?.state.status==="pending"&&v(this,pe).setOptions(this.options)}onUnsubscribe(){this.hasListeners()||v(this,pe)?.removeObserver(this)}onMutationUpdate(o){at(this,zl,ms).call(this),at(this,zl,ur).call(this,o)}getCurrentResult(){return v(this,fa)}reset(){v(this,pe)?.removeObserver(this),B(this,pe,void 0),at(this,zl,ms).call(this),at(this,zl,ur).call(this)}mutate(o,r){return B(this,Nl,r),v(this,pe)?.removeObserver(this),B(this,pe,v(this,Ol).getMutationCache().build(v(this,Ol),this.options)),v(this,pe).addObserver(this),v(this,pe).execute(o)}},Ol=new WeakMap,fa=new WeakMap,pe=new WeakMap,Nl=new WeakMap,zl=new WeakSet,ms=function(){const o=v(this,pe)?.state??ov();B(this,fa,{...o,isPending:o.status==="pending",isSuccess:o.status==="success",isError:o.status==="error",isIdle:o.status==="idle",mutate:this.mutate,reset:this.reset})},ur=function(o){Vt.batch(()=>{if(v(this,Nl)&&this.hasListeners()){const r=v(this,fa).variables,h=v(this,fa).context,S={client:v(this,Ol),meta:this.options.meta,mutationKey:this.options.mutationKey};if(o?.type==="success"){try{v(this,Nl).onSuccess?.(o.data,r,h,S)}catch(_){Promise.reject(_)}try{v(this,Nl).onSettled?.(o.data,null,r,h,S)}catch(_){Promise.reject(_)}}else if(o?.type==="error"){try{v(this,Nl).onError?.(o.error,r,h,S)}catch(_){Promise.reject(_)}try{v(this,Nl).onSettled?.(void 0,o.error,r,h,S)}catch(_){Promise.reject(_)}}}this.listeners.forEach(r=>{r(v(this,fa))})})},Pm),al,tv,Eg=(tv=class extends Kn{constructor(i={}){super();w(this,al);this.config=i,B(this,al,new Map)}build(i,o,r){const h=o.queryKey,S=o.queryHash??dr(h,o);let _=this.get(S);return _||(_=new vg({client:i,queryKey:h,queryHash:S,options:i.defaultQueryOptions(o),state:r,defaultOptions:i.getQueryDefaults(h)}),this.add(_)),_}add(i){v(this,al).has(i.queryHash)||(v(this,al).set(i.queryHash,i),this.notify({type:"added",query:i}))}remove(i){const o=v(this,al).get(i.queryHash);o&&(i.destroy(),o===i&&v(this,al).delete(i.queryHash),this.notify({type:"removed",query:i}))}clear(){Vt.batch(()=>{this.getAll().forEach(i=>{this.remove(i)})})}get(i){return v(this,al).get(i)}getAll(){return[...v(this,al).values()]}find(i){const o={exact:!0,...i};return this.getAll().find(r=>Am(o,r))}findAll(i={}){const o=this.getAll();return Object.keys(i).length>0?o.filter(r=>Am(i,r)):o}notify(i){Vt.batch(()=>{this.listeners.forEach(o=>{o(i)})})}onFocus(){Vt.batch(()=>{this.getAll().forEach(i=>{i.onFocus()})})}onOnline(){Vt.batch(()=>{this.getAll().forEach(i=>{i.onOnline()})})}},al=new WeakMap,tv),qt,ra,oa,Yn,Ln,da,Gn,Xn,ev,Tg=(ev=class{constructor(s={}){w(this,qt);w(this,ra);w(this,oa);w(this,Yn);w(this,Ln);w(this,da);w(this,Gn);w(this,Xn);B(this,qt,s.queryCache||new Eg),B(this,ra,s.mutationCache||new xg),B(this,oa,s.defaultOptions||{}),B(this,Yn,new Map),B(this,Ln,new Map),B(this,da,0)}mount(){os(this,da)._++,v(this,da)===1&&(B(this,Gn,or.subscribe(async s=>{s&&(await this.resumePausedMutations(),v(this,qt).onFocus())})),B(this,Xn,gs.subscribe(async s=>{s&&(await this.resumePausedMutations(),v(this,qt).onOnline())})))}unmount(){var s,i;os(this,da)._--,v(this,da)===0&&((s=v(this,Gn))==null||s.call(this),B(this,Gn,void 0),(i=v(this,Xn))==null||i.call(this),B(this,Xn,void 0))}isFetching(s){return v(this,qt).findAll({...s,fetchStatus:"fetching"}).length}isMutating(s){return v(this,ra).findAll({...s,status:"pending"}).length}getQueryData(s){const i=this.defaultQueryOptions({queryKey:s});return v(this,qt).get(i.queryHash)?.state.data}ensureQueryData(s){const i=this.defaultQueryOptions(s),o=v(this,qt).build(this,i),r=o.state.data;return r===void 0?this.fetchQuery(s):(s.revalidateIfStale&&o.isStaleByTime(ma(i.staleTime,o))&&this.prefetchQuery(i),Promise.resolve(r))}getQueriesData(s){return v(this,qt).findAll(s).map(({queryKey:i,state:o})=>{const r=o.data;return[i,r]})}setQueryData(s,i,o){const r=this.defaultQueryOptions({queryKey:s}),S=v(this,qt).get(r.queryHash)?.state.data,_=ug(i,S);if(_!==void 0)return v(this,qt).build(this,r).setData(_,{...o,manual:!0})}setQueriesData(s,i,o){return Vt.batch(()=>v(this,qt).findAll(s).map(({queryKey:r})=>[r,this.setQueryData(r,i,o)]))}getQueryState(s){const i=this.defaultQueryOptions({queryKey:s});return v(this,qt).get(i.queryHash)?.state}removeQueries(s){const i=v(this,qt);Vt.batch(()=>{i.findAll(s).forEach(o=>{i.remove(o)})})}resetQueries(s,i){const o=v(this,qt);return Vt.batch(()=>(o.findAll(s).forEach(r=>{r.reset()}),this.refetchQueries({type:"active",...s},i)))}cancelQueries(s,i={}){const o={revert:!0,...i},r=Vt.batch(()=>v(this,qt).findAll(s).map(h=>h.cancel(o)));return Promise.all(r).then(ce).catch(ce)}invalidateQueries(s,i={}){return Vt.batch(()=>(v(this,qt).findAll(s).forEach(o=>{o.invalidate()}),s?.refetchType==="none"?Promise.resolve():this.refetchQueries({...s,type:s?.refetchType??s?.type??"active"},i)))}refetchQueries(s,i={}){const o={...i,cancelRefetch:i.cancelRefetch??!0},r=Vt.batch(()=>v(this,qt).findAll(s).filter(h=>!h.isDisabled()&&!h.isStatic()).map(h=>{let S=h.fetch(void 0,o);return o.throwOnError||(S=S.catch(ce)),h.state.fetchStatus==="paused"?Promise.resolve():S}));return Promise.all(r).then(ce)}fetchQuery(s){const i=this.defaultQueryOptions(s);i.retry===void 0&&(i.retry=!1);const o=v(this,qt).build(this,i);return o.isStaleByTime(ma(i.staleTime,o))?o.fetch(i):Promise.resolve(o.state.data)}prefetchQuery(s){return this.fetchQuery(s).then(ce).catch(ce)}fetchInfiniteQuery(s){return s.behavior=Bm(s.pages),this.fetchQuery(s)}prefetchInfiniteQuery(s){return this.fetchInfiniteQuery(s).then(ce).catch(ce)}ensureInfiniteQueryData(s){return s.behavior=Bm(s.pages),this.ensureQueryData(s)}resumePausedMutations(){return gs.isOnline()?v(this,ra).resumePausedMutations():Promise.resolve()}getQueryCache(){return v(this,qt)}getMutationCache(){return v(this,ra)}getDefaultOptions(){return v(this,oa)}setDefaultOptions(s){B(this,oa,s)}setQueryDefaults(s,i){v(this,Yn).set(Va(s),{queryKey:s,defaultOptions:i})}getQueryDefaults(s){const i=[...v(this,Yn).values()],o={};return i.forEach(r=>{Lu(s,r.queryKey)&&Object.assign(o,r.defaultOptions)}),o}setMutationDefaults(s,i){v(this,Ln).set(Va(s),{mutationKey:s,defaultOptions:i})}getMutationDefaults(s){const i=[...v(this,Ln).values()],o={};return i.forEach(r=>{Lu(s,r.mutationKey)&&Object.assign(o,r.defaultOptions)}),o}defaultQueryOptions(s){if(s._defaulted)return s;const i={...v(this,oa).queries,...this.getQueryDefaults(s.queryKey),...s,_defaulted:!0};return i.queryHash||(i.queryHash=dr(i.queryKey,i)),i.refetchOnReconnect===void 0&&(i.refetchOnReconnect=i.networkMode!=="always"),i.throwOnError===void 0&&(i.throwOnError=!!i.suspense),!i.networkMode&&i.persister&&(i.networkMode="offlineFirst"),i.queryFn===hr&&(i.enabled=!1),i}defaultMutationOptions(s){return s?._defaulted?s:{...v(this,oa).mutations,...s?.mutationKey&&this.getMutationDefaults(s.mutationKey),...s,_defaulted:!0}}clear(){v(this,qt).clear(),v(this,ra).clear()}},qt=new WeakMap,ra=new WeakMap,oa=new WeakMap,Yn=new WeakMap,Ln=new WeakMap,da=new WeakMap,Gn=new WeakMap,Xn=new WeakMap,ev),dv=V.createContext(void 0),nl=s=>{const i=V.useContext(dv);if(!i)throw new Error("No QueryClient set, use QueryClientProvider to set one");return i},_g=({client:s,children:i})=>(V.useEffect(()=>(s.mount(),()=>{s.unmount()}),[s]),f.jsx(dv.Provider,{value:s,children:i})),hv=V.createContext(!1),Og=()=>V.useContext(hv);hv.Provider;function Ng(){let s=!1;return{clearReset:()=>{s=!1},reset:()=>{s=!0},isReset:()=>s}}var zg=V.createContext(Ng()),Ag=()=>V.useContext(zg),Mg=(s,i,o)=>{const r=o?.state.error&&typeof s.throwOnError=="function"?mr(s.throwOnError,[o.state.error,o]):s.throwOnError;(s.suspense||s.experimental_prefetchInRender||r)&&(i.isReset()||(s.retryOnMount=!1))},Rg=s=>{V.useEffect(()=>{s.clearReset()},[s])},Cg=({result:s,errorResetBoundary:i,throwOnError:o,query:r,suspense:h})=>s.isError&&!i.isReset()&&!s.isFetching&&r&&(h&&s.data===void 0||mr(o,[s.error,r])),Dg=s=>{if(s.suspense){const o=h=>h==="static"?h:Math.max(h??1e3,1e3),r=s.staleTime;s.staleTime=typeof r=="function"?(...h)=>o(r(...h)):o(r),typeof s.gcTime=="number"&&(s.gcTime=Math.max(s.gcTime,1e3))}},Ug=(s,i)=>s.isLoading&&s.isFetching&&!i,qg=(s,i)=>s?.suspense&&i.isPending,Ym=(s,i,o)=>i.fetchOptimistic(s).catch(()=>{o.clearReset()});function Hg(s,i,o){const r=Og(),h=Ag(),S=nl(),_=S.defaultQueryOptions(s);S.getDefaultOptions().queries?._experimental_beforeQuery?.(_);const O=S.getQueryCache().get(_.queryHash);_._optimisticResults=r?"isRestoring":"optimistic",Dg(_),Mg(_,h,O),Rg(h);const T=!S.getQueryCache().get(_.queryHash),[p]=V.useState(()=>new i(S,_)),U=p.getOptimisticResult(_),D=!r&&s.subscribed!==!1;if(V.useSyncExternalStore(V.useCallback(Y=>{const k=D?p.subscribe(Vt.batchCalls(Y)):ce;return p.updateResult(),k},[p,D]),()=>p.getCurrentResult(),()=>p.getCurrentResult()),V.useEffect(()=>{p.setOptions(_)},[_,p]),qg(_,U))throw Ym(_,p,h);if(Cg({result:U,errorResetBoundary:h,throwOnError:_.throwOnError,query:O,suspense:_.suspense}))throw U.error;return S.getDefaultOptions().queries?._experimental_afterQuery?.(_,U),_.experimental_prefetchInRender&&!Gu.isServer()&&Ug(U,r)&&(T?Ym(_,p,h):O?.promise)?.catch(ce).finally(()=>{p.updateResult()}),_.notifyOnChangeProps?U:p.trackResult(U)}function Ze(s,i){return Hg(s,yg)}function Ml(s,i){const o=nl(),[r]=V.useState(()=>new jg(o,s));V.useEffect(()=>{r.setOptions(s)},[r,s]);const h=V.useSyncExternalStore(V.useCallback(_=>r.subscribe(Vt.batchCalls(_)),[r]),()=>r.getCurrentResult(),()=>r.getCurrentResult()),S=V.useCallback((_,O)=>{r.mutate(_,O).catch(ce)},[r]);if(h.error&&mr(r.options.throwOnError,[h.error]))throw h.error;return{...h,mutate:S,mutateAsync:h.mutate}}const Lm=s=>{let i;const o=new Set,r=(p,U)=>{const D=typeof p=="function"?p(i):p;if(!Object.is(D,i)){const Y=i;i=U??(typeof D!="object"||D===null)?D:Object.assign({},i,D),o.forEach(k=>k(i,Y))}},h=()=>i,O={setState:r,getState:h,getInitialState:()=>T,subscribe:p=>(o.add(p),()=>o.delete(p))},T=i=s(r,h,O);return O},Bg=(s=>s?Lm(s):Lm),Qg=s=>s;function Yg(s,i=Qg){const o=ds.useSyncExternalStore(s.subscribe,ds.useCallback(()=>i(s.getState()),[s,i]),ds.useCallback(()=>i(s.getInitialState()),[s,i]));return ds.useDebugValue(o),o}const Gm=s=>{const i=Bg(s),o=r=>Yg(i,r);return Object.assign(o,i),o},mv=(s=>s?Gm(s):Gm);function Lg(s){const i=document.cookie.match(new RegExp("(?:^|; )"+s+"=([^;]*)"));return i?decodeURIComponent(i[1]):null}function Gg(s,i){document.cookie=`${s}=${encodeURIComponent(i)}; Path=/admin; SameSite=Strict; Max-Age=604800`}function Xg(s){document.cookie=`${s}=; Path=/admin; SameSite=Strict; Max-Age=0`}const Al=mv(s=>({token:Lg("admin_session"),login(i){Gg("admin_session",i),s({token:i})},logout(){Xg("admin_session"),s({token:null})}})),ha=mv(s=>({status:"disconnected",lastEvent:null,setStatus:i=>s({status:i}),pushEvent:i=>s({lastEvent:i})})),Kg=3e4,Zg=1e3;let Ke=null,ir=null,Vf=0,ps=!1;function Vg(){ps=!1,!(Ke&&(Ke.readyState===WebSocket.OPEN||Ke.readyState===WebSocket.CONNECTING))&&vv()}function wg(){ps=!0,ir&&clearTimeout(ir),Ke?.close(),Ke=null,ha.getState().setStatus("disconnected")}function vv(){if(ps)return;const s=Al.getState().token;if(!s)return;ha.getState().setStatus("connecting");const i=location.protocol==="https:"?"wss:":"ws:";Ke=new WebSocket(`${i}//${location.host}/admin/ws`),Ke.onopen=()=>{Ke.send(JSON.stringify({token:s}))},Ke.onmessage=o=>{let r;try{r=JSON.parse(o.data)}catch{return}if(r!==null&&typeof r=="object"&&"status"in r&&r.status==="authenticated"){Vf=0,ha.getState().setStatus("connected");return}r!==null&&typeof r=="object"&&"type"in r&&ha.getState().pushEvent(r)},Ke.onclose=()=>{if(ps)return;ha.getState().setStatus("disconnected");const o=Math.min(Zg*2**Vf,Kg);Vf++,ir=setTimeout(vv,o)},Ke.onerror=()=>{Ke?.close()}}function sr(){return Al.getState().token??""}async function Ve(s,i){const o=await fetch(s,{...i,headers:{Authorization:`Bearer ${sr()}`}});if(o.status===401)throw Al.getState().logout(),new Error("Unauthorized");if(!o.ok){const r=await o.text().catch(()=>o.statusText);throw new Error(r||`HTTP ${o.status}`)}return o.json()}async function yv(s,i,o,r){const h=await fetch("/admin/csrf-token",{headers:{Authorization:`Bearer ${sr()}`}});if(!h.ok)throw new Error("Failed to fetch CSRF token");const{csrf_token:S}=await h.json(),_=await fetch(i,{method:s,headers:{Authorization:`Bearer ${sr()}`,"X-CSRF-Token":S,...r?{"Content-Type":r}:{}},body:o});if(_.status===401)throw Al.getState().logout(),new Error("Unauthorized");if(!_.ok){const O=await _.text().catch(()=>_.statusText);throw new Error(O||`HTTP ${_.status}`)}if(!(_.status===204||_.headers.get("content-length")==="0"))return _.json()}function va(s,i,o){return yv(s,i,o!==void 0?JSON.stringify(o):void 0,o!==void 0?"application/json":void 0)}function Jg(s,i){return yv("POST",s,i)}function kg(s=!0){return Ze({queryKey:["status"],queryFn:()=>Ve("/admin/api/status"),enabled:s})}function Fg(){return Ze({queryKey:["metrics"],queryFn:()=>Ve("/admin/api/metrics"),refetchInterval:5e3})}function Wg(s,i){return Ze({queryKey:["observability",s,i],queryFn:()=>Ve(`/admin/api/observability/overview?window=${s}&backend=${encodeURIComponent(i)}`),refetchInterval:3e4})}function $g(s){const i=new URLSearchParams;return i.set("page",String(s.page)),i.set("page_size",String(s.page_size)),s.backend&&i.set("backend",s.backend),s.status&&i.set("status",s.status),s.since&&i.set("since",s.since),s.until&&i.set("until",s.until),s.model&&i.set("model",s.model),Ze({queryKey:["requests",s],queryFn:()=>Ve(`/admin/api/requests?${i}`),staleTime:1/0})}function Ig(){return Ze({queryKey:["keys"],queryFn:()=>Ve("/admin/api/keys"),staleTime:1/0})}function Pg(){const s=nl();return Ml({mutationFn:i=>va("POST","/admin/api/keys",i),onSuccess:()=>{s.invalidateQueries({queryKey:["keys"]})}})}function tp(){const s=nl();return Ml({mutationFn:({id:i,body:o})=>va("PUT",`/admin/api/keys/${i}`,o),onSuccess:()=>{s.invalidateQueries({queryKey:["keys"]})}})}function ep(){const s=nl();return Ml({mutationFn:i=>va("DELETE",`/admin/api/keys/${i}`),onSuccess:()=>{s.invalidateQueries({queryKey:["keys"]})}})}function lp(){return Ze({queryKey:["backends"],queryFn:()=>Ve("/admin/api/backends"),staleTime:1/0})}function ap(){return Ze({queryKey:["config"],queryFn:()=>Ve("/admin/api/config"),staleTime:1/0})}function np(){const s=nl();return Ml({mutationFn:i=>va("PUT","/admin/api/config",i),onSuccess:()=>{s.invalidateQueries({queryKey:["config"]})}})}function up(){const s=nl();return Ml({mutationFn:i=>va("DELETE",`/admin/api/config/overrides/${encodeURIComponent(i)}`),onSuccess:()=>{s.invalidateQueries({queryKey:["config"]})}})}function ip(){return Ze({queryKey:["env"],queryFn:()=>Ve("/admin/api/env"),staleTime:1/0})}function sp(){return Ze({queryKey:["models"],queryFn:()=>Ve("/admin/api/models"),staleTime:1/0})}function cp(){const s=nl();return Ml({mutationFn:i=>va("POST","/admin/api/models",i),onSuccess:()=>{s.invalidateQueries({queryKey:["models"]})}})}function fp(){const s=nl();return Ml({mutationFn:i=>va("DELETE",`/admin/api/models/${encodeURIComponent(i)}`),onSuccess:()=>{s.invalidateQueries({queryKey:["models"]})}})}function rp(){return Ml({mutationFn:s=>va("POST","/admin/api/models/discover",s)})}function op(s){return Ze({queryKey:["audit",s],queryFn:()=>Ve(`/admin/api/audit?page=${s.page}&page_size=${s.page_size}`),staleTime:1/0})}function dp(s){return Ze({queryKey:["traffic",s],queryFn:()=>Ve(`/admin/api/traffic?window=${s}`),refetchInterval:3e4})}function hp(){return Ze({queryKey:["uptime"],queryFn:()=>Ve("/admin/api/uptime"),refetchInterval:3e4})}function mp(){return Ml({mutationFn:s=>{const i=new FormData;return i.append("file",s),Jg("/admin/api/env/import",i)}})}async function vp(){const s=Al.getState().token??"",i=await fetch("/admin/api/env/export",{headers:{Authorization:`Bearer ${s}`}});if(!i.ok)throw new Error(`Export failed: HTTP ${i.status}`);const o=await i.blob(),r=URL.createObjectURL(o),h=document.createElement("a");h.href=r,h.download=".anyllm.env",document.body.appendChild(h),h.click(),document.body.removeChild(h),URL.revokeObjectURL(r)}function yp(){const s=Al(_=>_.login),[i,o]=V.useState(""),[r,h]=V.useState(!1);async function S(_){_.preventDefault();const O=_.currentTarget.elements.namedItem("token").value.trim();if(O){h(!0),o("");try{if(!(await fetch("/admin/api/metrics",{headers:{Authorization:`Bearer ${O}`}})).ok)throw new Error("Invalid token");s(O)}catch{o("Invalid token")}finally{h(!1)}}}return f.jsx("div",{className:"login-overlay",children:f.jsxs("div",{className:"login-card",children:[f.jsxs("div",{className:"login-title",children:[f.jsx("span",{className:"prompt",children:"> "}),"proxy admin"]}),f.jsxs("form",{onSubmit:S,children:[f.jsx("input",{type:"password",name:"token",placeholder:"Admin token",autoComplete:"current-password",autoFocus:!0}),f.jsx("button",{type:"submit",className:"btn btn-primary",disabled:r,children:r?"Signing in…":"Sign in"})]}),f.jsx("div",{className:"login-error",children:i})]})})}const gp=[{id:"dashboard",label:"Dashboard"},{id:"requests",label:"Request Log"},{id:"settings",label:"Settings"},{id:"backends",label:"Backends"},{id:"keys",label:"Access Control"},{id:"models",label:"Models"},{id:"audit",label:"Audit"},{id:"traffic",label:"Traffic"},{id:"uptime",label:"Uptime"}];function pp({activeTab:s,onTabChange:i}){const o=Al(h=>h.logout),r=ha(h=>h.status);return f.jsxs("nav",{className:"nav",children:[f.jsx("div",{className:"nav-brand",children:"anyllm"}),gp.map(h=>f.jsx("div",{className:`nav-item${s===h.id?" active":""}`,onClick:()=>i(h.id),children:h.label},h.id)),f.jsxs("div",{className:"nav-right",children:[f.jsx("span",{className:`ws-status ${r==="connected"?"connected":"disconnected"}`,children:r==="connected"?"Live":"Offline"}),f.jsx("button",{className:"btn btn-secondary btn-sm",style:{marginLeft:12},onClick:o,children:"Sign out"})]})]})}function bp({req:s}){return f.jsxs("div",{className:"feed-detail",children:[f.jsx("span",{className:"label",children:"Request ID"}),f.jsx("span",{className:"val",children:s.request_id}),f.jsx("span",{className:"label",children:"Backend"}),f.jsx("span",{className:"val",children:s.backend}),f.jsx("span",{className:"label",children:"Model (req)"}),f.jsx("span",{className:"val",children:s.model_requested??"—"}),f.jsx("span",{className:"label",children:"Model (mapped)"}),f.jsx("span",{className:"val",children:s.model_mapped??"—"}),f.jsx("span",{className:"label",children:"Latency"}),f.jsxs("span",{className:"val",children:[s.latency_ms," ms"]}),f.jsx("span",{className:"label",children:"Tokens in/out"}),f.jsxs("span",{className:"val",children:[s.input_tokens??"—"," / ",s.output_tokens??"—"]}),f.jsx("span",{className:"label",children:"Cost"}),f.jsx("span",{className:"val",children:s.cost_usd!=null?`$${s.cost_usd.toFixed(6)}`:"—"}),s.error_message&&f.jsx("div",{className:"error-msg",children:s.error_message})]})}function Sp(s){return s<300?"status-2xx":s<500?"status-4xx":"status-5xx"}function gv({req:s}){const[i,o]=V.useState(!1);return f.jsxs(f.Fragment,{children:[f.jsxs("div",{className:"feed-row",onClick:()=>o(r=>!r),children:[f.jsx("span",{className:"mono dim",children:s.timestamp.slice(11,19)}),f.jsx("span",{className:`mono ${Sp(s.status_code)}`,children:s.status_code}),f.jsxs("span",{className:"mono",children:[s.latency_ms,"ms"]}),f.jsxs("span",{className:"mono",style:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:[s.model_requested??s.backend,s.is_streaming&&f.jsx("span",{className:"streaming-badge",children:"stream"})]}),f.jsx("span",{className:"mono dim",children:s.input_tokens??"—"}),f.jsx("span",{className:"mono dim",children:s.output_tokens??"—"}),f.jsx("span",{className:"mono dim",children:s.cost_usd!=null?`$${s.cost_usd.toFixed(5)}`:"—"})]}),i&&f.jsx(bp,{req:s})]})}const xp=200;function jp({initial:s}){const[i,o]=V.useState(s??[]),[r,h]=V.useState(!1),S=V.useRef(r);S.current=r;const _=ha(O=>O.lastEvent);return V.useEffect(()=>{!_||_.type!=="request_completed"||S.current||o(O=>[_.data,...O].slice(0,xp))},[_]),f.jsxs("div",{children:[f.jsxs("div",{className:"section-header",children:[f.jsx("span",{className:"section-label",children:"Live Feed"}),f.jsx("button",{className:`btn btn-sm ${r?"btn-primary":"btn-secondary"}`,onClick:()=>h(O=>!O),children:r?"Resume":"Pause"})]}),f.jsxs("div",{className:"feed",children:[f.jsxs("div",{className:"feed-header",children:[f.jsx("span",{children:"Time"}),f.jsx("span",{children:"Status"}),f.jsx("span",{children:"Latency"}),f.jsx("span",{children:"Model"}),f.jsx("span",{children:"In"}),f.jsx("span",{children:"Out"}),f.jsx("span",{children:"Cost"})]}),i.length===0?f.jsx("div",{className:"empty",children:"Waiting for requests…"}):i.map(O=>f.jsx(gv,{req:O},O.request_id))]})]})}function vs({series:s,gridColor:i="var(--border-sub)",height:o=130}){const h=o,S={top:8,right:8,bottom:0,left:0},_=600-S.left-S.right,O=h-S.top-S.bottom,T=s.flatMap(G=>G.data),p=Math.max(...T,1),U=Math.max(...s.map(G=>G.data.length),2);function D(G){return S.left+G/(U-1)*_}function Y(G){return S.top+O-G/p*O}const k=4,$=Array.from({length:k},(G,P)=>S.top+P/(k-1)*O);return f.jsxs("svg",{className:"chart-svg",viewBox:`0 0 600 ${h}`,preserveAspectRatio:"none",style:{height:o},children:[$.map((G,P)=>f.jsx("line",{className:"chart-grid-line",x1:S.left,y1:G,x2:600-S.right,y2:G,stroke:i},P)),s.map((G,P)=>{if(G.data.length<2)return null;const dt=G.data.map((K,vt)=>`${D(vt)},${Y(K)}`).join(" "),Ot=[`${D(0)},${S.top+O}`,...G.data.map((K,vt)=>`${D(vt)},${Y(K)}`),`${D(G.data.length-1)},${S.top+O}`].join(" ");return f.jsxs("g",{children:[f.jsx("polygon",{className:"chart-area",points:Ot,fill:G.color}),f.jsx("polyline",{className:`chart-line${G.secondary?" secondary":""}`,points:dt,stroke:G.color})]},P)})]})}function Rl({loading:s,error:i,empty:o,message:r}){return s?f.jsx("div",{className:"empty",children:"Loading…"}):i?f.jsx("div",{className:"empty error",children:i}):o?f.jsx("div",{className:"empty",children:r??"No data"}):null}function Ep(){const[s,i]=V.useState(6),[o,r]=V.useState(""),{data:h,isLoading:S,error:_}=Wg(s,o),O=h?[{label:"Requests",color:"#e8a030",data:h.series.map(U=>U.requests)},{label:"Errors",color:"#e05252",data:h.series.map(U=>U.errors),secondary:!0}]:[],T=h?[{label:"Input",color:"#4caf6e",data:h.series.map(U=>U.input_tokens)},{label:"Output",color:"#6eb5c0",data:h.series.map(U=>U.output_tokens),secondary:!0}]:[],p=h?[{label:"Cost",color:"#c87dd4",data:h.series.map(U=>U.cost_usd)}]:[];return f.jsxs("div",{children:[f.jsxs("div",{className:"operator-controls",children:[f.jsx("span",{className:"section-label",style:{marginBottom:0},children:"Operator View"}),f.jsxs("div",{className:"form-row",style:{flexWrap:"wrap",gap:6,marginTop:0},children:[f.jsxs("select",{value:s,onChange:U=>i(Number(U.target.value)),children:[f.jsx("option",{value:1,children:"Last 1 hour"}),f.jsx("option",{value:6,children:"Last 6 hours"}),f.jsx("option",{value:24,children:"Last 24 hours"})]}),f.jsx("select",{value:o,onChange:U=>r(U.target.value),children:f.jsx("option",{value:"",children:"All backends"})})]})]}),h&&f.jsxs("div",{className:"stats-row",children:[f.jsxs("div",{className:"stat",children:[f.jsx("div",{className:"stat-label",children:"Input Tokens"}),f.jsx("div",{className:"stat-value",children:h.total_input_tokens.toLocaleString()})]}),f.jsxs("div",{className:"stat",children:[f.jsx("div",{className:"stat-label",children:"Output Tokens"}),f.jsx("div",{className:"stat-value",children:h.total_output_tokens.toLocaleString()})]}),f.jsxs("div",{className:"stat",children:[f.jsx("div",{className:"stat-label",children:"Window Failures"}),f.jsx("div",{className:"stat-value",children:h.total_errors})]}),f.jsxs("div",{className:"stat",children:[f.jsx("div",{className:"stat-label",children:"Window Cost"}),f.jsxs("div",{className:"stat-value",children:["$",h.total_cost_usd.toFixed(2)]})]})]}),f.jsx(Rl,{loading:S,error:_?.message}),h&&f.jsxs("div",{className:"operator-grid",children:[f.jsxs("div",{className:"chart-card",children:[f.jsxs("div",{className:"chart-header",children:[f.jsxs("div",{children:[f.jsx("div",{className:"chart-title",children:"Request Volume"}),f.jsx("div",{className:"chart-subtitle",children:"Rolling request count and errors"})]}),f.jsx("div",{className:"chart-value",children:h.total_requests})]}),f.jsx(vs,{series:O})]}),f.jsxs("div",{className:"chart-card",children:[f.jsxs("div",{className:"chart-header",children:[f.jsxs("div",{children:[f.jsx("div",{className:"chart-title",children:"Tokens"}),f.jsx("div",{className:"chart-subtitle",children:"Input and output usage"})]}),f.jsx("div",{className:"chart-value",children:(h.total_input_tokens+h.total_output_tokens).toLocaleString()})]}),f.jsx(vs,{series:T})]}),f.jsxs("div",{className:"chart-card",children:[f.jsxs("div",{className:"chart-header",children:[f.jsxs("div",{children:[f.jsx("div",{className:"chart-title",children:"Estimated Cost"}),f.jsx("div",{className:"chart-subtitle",children:"USD by minute bucket"})]}),f.jsxs("div",{className:"chart-value",children:["$",h.total_cost_usd.toFixed(4)]})]}),f.jsx(vs,{series:p})]})]})]})}function Tp(){const{data:s}=Fg();return f.jsxs("div",{children:[f.jsxs("div",{className:"stats-row",children:[f.jsxs("div",{className:"stat",children:[f.jsx("div",{className:"stat-label",children:"Requests/min"}),f.jsx("div",{className:"stat-value",children:s?s.requests_per_minute.toFixed(1):"—"})]}),f.jsxs("div",{className:"stat",children:[f.jsx("div",{className:"stat-label",children:"Error Rate"}),f.jsx("div",{className:"stat-value",children:s?`${(s.error_rate*100).toFixed(1)}%`:"—"})]}),f.jsxs("div",{className:"stat",children:[f.jsx("div",{className:"stat-label",children:"P50 Latency"}),f.jsx("div",{className:"stat-value",children:s?`${s.p50_latency_ms??0}ms`:"—"})]}),f.jsxs("div",{className:"stat",children:[f.jsx("div",{className:"stat-label",children:"P95 Latency"}),f.jsx("div",{className:"stat-value",children:s?`${s.p95_latency_ms??0}ms`:"—"})]}),f.jsxs("div",{className:"stat",children:[f.jsx("div",{className:"stat-label",children:"Total Requests"}),f.jsx("div",{className:"stat-value",children:s?s.total_requests.toLocaleString():"0"})]})]}),f.jsxs("div",{className:"stats-row",style:{marginBottom:16},children:[f.jsxs("div",{className:"stat",children:[f.jsx("div",{className:"stat-label",children:"Streams Started"}),f.jsx("div",{className:"stat-value",children:s?.streams_started??0})]}),f.jsxs("div",{className:"stat",children:[f.jsx("div",{className:"stat-label",children:"Completed"}),f.jsx("div",{className:"stat-value ok",children:s?.streams_completed??0})]}),f.jsxs("div",{className:"stat",children:[f.jsx("div",{className:"stat-label",children:"Failed"}),f.jsx("div",{className:"stat-value",style:{color:"var(--err)"},children:s?.streams_failed??0})]}),f.jsxs("div",{className:"stat",children:[f.jsx("div",{className:"stat-label",children:"Client Disconnects"}),f.jsx("div",{className:"stat-value",style:{color:"var(--warn)"},children:s?.streams_client_disconnected??0})]})]}),f.jsx(Ep,{}),f.jsx("div",{style:{marginTop:16},children:f.jsx(jp,{})})]})}function pv({page:s,hasMore:i,onPrev:o,onNext:r}){return f.jsxs("div",{className:"pagination",children:[f.jsx("button",{className:"btn btn-secondary btn-sm",onClick:o,disabled:s<=1,children:"Prev"}),f.jsxs("span",{children:["Page ",s]}),f.jsx("button",{className:"btn btn-secondary btn-sm",onClick:r,disabled:!i,children:"Next"})]})}function _p(){const[s,i]=V.useState(1),[o,r]=V.useState(""),[h,S]=V.useState(""),{data:_,isLoading:O,error:T}=$g({page:s,page_size:50,backend:o,status:h});return f.jsxs("div",{children:[f.jsxs("div",{className:"section-header",children:[f.jsx("span",{className:"section-label",children:"Request Log"}),f.jsxs("div",{className:"form-row",style:{marginTop:0},children:[f.jsx("select",{value:o,onChange:p=>{r(p.target.value),i(1)},children:f.jsx("option",{value:"",children:"All backends"})}),f.jsxs("select",{value:h,onChange:p=>{S(p.target.value),i(1)},children:[f.jsx("option",{value:"",children:"All status"}),f.jsx("option",{value:"ok",children:"2xx"}),f.jsx("option",{value:"error",children:"4xx/5xx"})]})]})]}),f.jsx(Rl,{loading:O,error:T?.message}),_&&f.jsxs(f.Fragment,{children:[f.jsxs("div",{className:"feed",children:[f.jsxs("div",{className:"feed-header",children:[f.jsx("span",{children:"Time"}),f.jsx("span",{children:"Status"}),f.jsx("span",{children:"Latency"}),f.jsx("span",{children:"Model"}),f.jsx("span",{children:"In"}),f.jsx("span",{children:"Out"}),f.jsx("span",{children:"Cost"})]}),_.requests.map(p=>f.jsx(gv,{req:p},p.request_id))]}),f.jsx(pv,{page:s,hasMore:_.has_more,onPrev:()=>i(p=>Math.max(1,p-1)),onNext:()=>i(p=>p+1)})]})]})}const cr="env_import_pending_restart";function Op(){return sessionStorage.getItem(cr)==="1"}function Np({configured:s=!0}){const{data:i,isLoading:o,error:r}=ap(),{data:h}=ip(),S=np(),_=up(),O=mp(),T=V.useRef(null),[p,U]=V.useState({}),[D,Y]=V.useState(null),[k,$]=V.useState(null),[G,P]=V.useState(null),[dt,Ot]=V.useState(Op);function K(Q,nt){S.mutate({[Q]:p[Q]??nt})}function vt(Q){const nt=Q.target.files?.[0];nt&&(Y(null),$(null),O.mutate(nt,{onSuccess(gt){Y(gt),sessionStorage.setItem(cr,"1"),Ot(!0)},onError(gt){try{const ae=JSON.parse(gt.message);if(ae.hard_errors){$(ae);return}}catch{}$({hard_errors:[gt.message],warnings:[]})}}),T.current&&(T.current.value=""))}async function Yt(){P(null);try{await vp()}catch(Q){P(Q instanceof Error?Q.message:String(Q))}}function Ct(){sessionStorage.removeItem(cr),Ot(!1)}return f.jsxs("div",{children:[!s&&f.jsxs("div",{style:{marginBottom:20,padding:"12px 16px",border:"1px solid var(--border)",borderLeft:"3px solid var(--warn)",borderRadius:"var(--r)",fontSize:13},children:[f.jsx("div",{style:{fontWeight:600,marginBottom:8},children:"No proxy configuration found — nothing to forward requests to."}),f.jsxs("div",{style:{marginBottom:10},children:["The proxy needs a backend endpoint (where to forward) and a listen port (where to accept). LISTEN_PORT defaults to 3000. Create a ",f.jsx("span",{className:"mono",children:".anyllm.env"})," and import it below, or pass it at startup: ",f.jsx("span",{className:"mono",children:"anyllm-proxy --webui --env-file .anyllm.env"})]}),f.jsxs("div",{style:{display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gap:10},children:[f.jsxs("div",{children:[f.jsx("div",{style:{fontWeight:600,marginBottom:4,fontSize:12},children:"OpenAI"}),f.jsx("pre",{style:{margin:0,padding:"6px 10px",background:"var(--surface-2)",borderRadius:"var(--r)",fontSize:11,overflowX:"auto"},children:`OPENAI_API_KEY=sk-... +PROXY_API_KEYS=my-key`})]}),f.jsxs("div",{children:[f.jsx("div",{style:{fontWeight:600,marginBottom:4,fontSize:12},children:"Ollama / local LLM"}),f.jsx("pre",{style:{margin:0,padding:"6px 10px",background:"var(--surface-2)",borderRadius:"var(--r)",fontSize:11,overflowX:"auto"},children:`OPENAI_BASE_URL=http://localhost:11434/v1 +PROXY_OPEN_RELAY=true`})]}),f.jsxs("div",{children:[f.jsx("div",{style:{fontWeight:600,marginBottom:4,fontSize:12},children:"OpenRouter / custom"}),f.jsx("pre",{style:{margin:0,padding:"6px 10px",background:"var(--surface-2)",borderRadius:"var(--r)",fontSize:11,overflowX:"auto"},children:`OPENAI_BASE_URL=https://openrouter.ai/api/v1 +OPENAI_API_KEY=sk-or-... +PROXY_API_KEYS=my-key`})]})]})]}),dt&&f.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:16,padding:"8px 12px",background:"var(--warn-dim)",borderLeft:"3px solid var(--warn)",borderRadius:"var(--r)",fontSize:13},children:[f.jsx("span",{children:"Restart the proxy for imported env vars to take effect."}),f.jsx("button",{className:"btn btn-secondary btn-sm",onClick:Ct,children:"Dismiss"})]}),f.jsxs("div",{style:{marginBottom:24},children:[f.jsx("div",{className:"section-label",style:{marginBottom:8},children:"Env File"}),f.jsxs("div",{style:{display:"flex",gap:8,alignItems:"center"},children:[f.jsx("input",{ref:T,type:"file",accept:".env,.anyllm.env,text/plain",style:{display:"none"},onChange:vt}),f.jsx("button",{className:"btn btn-secondary btn-sm",onClick:()=>T.current?.click(),disabled:O.isPending,children:O.isPending?"Importing…":"Import .anyllm.env"}),f.jsx("button",{className:"btn btn-secondary btn-sm",onClick:Yt,children:"Export .anyllm.env"})]}),D&&f.jsxs("div",{style:{marginTop:10},children:[f.jsxs("div",{className:"dim",style:{marginBottom:4},children:[D.applied," variable",D.applied!==1?"s":""," imported.",D.warnings.length===0&&" No issues."]}),D.warnings.length>0&&f.jsxs("div",{style:{marginTop:8,padding:"8px 12px",background:"var(--warn-dim)",borderLeft:"3px solid var(--warn)",borderRadius:"var(--r)",fontSize:12},children:[f.jsx("div",{style:{fontWeight:600,marginBottom:4},children:"Warnings"}),D.warnings.map((Q,nt)=>f.jsxs("div",{className:"mono",style:{fontSize:12},children:[Q.line!=null&&f.jsxs("span",{className:"dim",children:["[line ",Q.line,"] "]}),Q.key&&f.jsxs("span",{children:[Q.key,": "]}),Q.message]},nt))]})]}),k&&f.jsxs("div",{style:{marginTop:10,padding:"8px 12px",background:"var(--err-dim)",borderLeft:"3px solid var(--err)",borderRadius:"var(--r)",fontSize:12},children:[f.jsx("div",{style:{fontWeight:600,marginBottom:4},children:"Import rejected"}),k.hard_errors.map((Q,nt)=>f.jsx("div",{className:"mono",style:{fontSize:12},children:Q},nt)),k.warnings.length>0&&f.jsxs(f.Fragment,{children:[f.jsx("div",{style:{fontWeight:600,marginTop:8,marginBottom:4},children:"Warnings (from partial parse)"}),k.warnings.map((Q,nt)=>f.jsxs("div",{className:"mono",style:{fontSize:12},children:[Q.line!=null&&f.jsxs("span",{className:"dim",children:["[line ",Q.line,"] "]}),Q.message]},nt))]})]}),G&&f.jsxs("div",{style:{marginTop:10,padding:"8px 12px",background:"var(--err-dim)",borderLeft:"3px solid var(--err)",borderRadius:"var(--r)",fontSize:12},children:["Export failed: ",G]})]}),f.jsx(Rl,{loading:o,error:r?.message}),i&&f.jsx("div",{children:i.entries.map(Q=>f.jsxs("div",{className:"form-group",children:[f.jsx("div",{className:"form-label",children:Q.key}),f.jsxs("div",{className:"form-row",children:[f.jsx("input",{value:p[Q.key]??Q.value,onChange:nt=>U(gt=>({...gt,[Q.key]:nt.target.value}))}),f.jsx("button",{className:"btn btn-primary btn-sm",onClick:()=>K(Q.key,Q.value),children:"Save"}),f.jsx("button",{className:"btn btn-secondary btn-sm",onClick:()=>_.mutate(Q.key),children:"Reset"})]})]},Q.key))}),h&&f.jsxs("div",{className:"readonly-section",style:{marginTop:16},children:[f.jsx("div",{className:"section-label",children:"Environment"}),f.jsx("div",{style:{display:"grid",gridTemplateColumns:"220px 1fr",gap:"4px 12px",marginTop:8,fontSize:12},children:Object.entries(h).map(([Q,nt])=>f.jsxs(V.Fragment,{children:[f.jsx("span",{className:"dim",children:Q}),f.jsx("span",{className:"mono",children:nt})]},Q))})]})]})}const zp={ok:"var(--ok)",warn:"var(--warn)",err:"var(--err)",dim:"var(--text-3)"};function bv({status:s,pulse:i}){return f.jsx("span",{style:{display:"inline-block",width:7,height:7,borderRadius:"50%",background:zp[s],animation:i?"pulse 2s ease-in-out infinite":void 0,verticalAlign:"middle",marginRight:6}})}function Ap(){const{data:s,isLoading:i,error:o}=lp();return f.jsxs("div",{children:[f.jsx(Rl,{loading:i,error:o?.message,empty:s?.length===0}),f.jsx("div",{className:"backend-cards",children:s?.map(r=>f.jsxs("div",{className:"card",children:[f.jsxs("div",{className:"card-header",children:[f.jsx("span",{className:"card-name",children:r.name}),f.jsx(bv,{status:r.status==="ok"?"ok":"err",pulse:r.status==="ok"})]}),f.jsxs("div",{className:"card-body",children:[f.jsx("div",{className:"mono",children:r.model}),f.jsxs("div",{style:{marginTop:6,display:"grid",gridTemplateColumns:"1fr 1fr",gap:4},children:[f.jsx("span",{className:"dim",children:"Requests"}),f.jsx("span",{className:"mono",children:r.requests_total}),f.jsx("span",{className:"dim",children:"P50"}),f.jsxs("span",{className:"mono",children:[r.p50_ms,"ms"]}),f.jsx("span",{className:"dim",children:"P95"}),f.jsxs("span",{className:"mono",children:[r.p95_ms,"ms"]}),f.jsx("span",{className:"dim",children:"Errors"}),f.jsx("span",{className:"mono",style:{color:r.requests_err>0?"var(--err)":void 0},children:r.requests_err})]})]})]},r.name))})]})}function Mp({variant:s}){return f.jsx("span",{className:`badge badge-${s}`,children:s})}function Rp({spent:s,limit:i}){if(!i)return f.jsx("span",{className:"dim",children:"—"});const o=Math.min(s/i*100,100),r=o>=95?"danger":o>=80?"warn":"";return f.jsxs("div",{children:[f.jsx("div",{className:"budget-bar",children:f.jsx("div",{className:`budget-bar-fill${r?` ${r}`:""}`,style:{width:`${o}%`}})}),f.jsxs("span",{className:"dim",style:{fontSize:10},children:["$",s.toFixed(4)," / $",i.toFixed(2)]})]})}function Cp({onCreated:s}){const i=Pg(),[o,r]=V.useState(""),[h,S]=V.useState(""),[_,O]=V.useState("");function T(){i.mutate({description:o||null,spend_limit:h?Number(h):null,rpm_limit:_?Number(_):null},{onSuccess:p=>{r(""),S(""),O(""),s(p.key)}})}return f.jsxs("div",{className:"form-group",children:[f.jsx("div",{className:"form-label",children:"Create Key"}),f.jsx("form",{onSubmit:p=>{p.preventDefault(),T()},children:f.jsxs("div",{className:"form-row",style:{flexWrap:"wrap"},children:[f.jsx("input",{placeholder:"Description",value:o,onChange:p=>r(p.target.value)}),f.jsx("input",{placeholder:"Spend limit USD",type:"number",value:h,onChange:p=>S(p.target.value),style:{width:120}}),f.jsx("input",{placeholder:"RPM limit",type:"number",value:_,onChange:p=>O(p.target.value),style:{width:100}}),f.jsx("button",{type:"submit",className:"btn btn-primary",disabled:i.isPending,children:i.isPending?"Creating…":"Create"})]})})]})}function Dp({vk:s,onClose:i}){const o=tp(),r=ep(),[h,S]=V.useState(s.description??""),[_,O]=V.useState(s.spend_limit?.toString()??""),[T,p]=V.useState(s.rpm_limit?.toString()??"");function U(){o.mutate({id:s.id,body:{description:h||null,spend_limit:_?Number(_):null,rpm_limit:T?Number(T):null}},{onSuccess:i})}function D(){confirm("Revoke this key?")&&r.mutate(s.id,{onSuccess:i})}return f.jsx("div",{className:"modal-backdrop",onClick:i,children:f.jsxs("div",{className:"modal",onClick:Y=>Y.stopPropagation(),children:[f.jsxs("div",{className:"modal-title",children:["Edit Key — ",s.key_prefix,"…"]}),f.jsxs("div",{className:"form-group",children:[f.jsx("div",{className:"form-label",children:"Description"}),f.jsx("input",{value:h,onChange:Y=>S(Y.target.value),style:{width:"100%"}})]}),f.jsxs("div",{className:"form-group",children:[f.jsx("div",{className:"form-label",children:"Spend limit (USD)"}),f.jsx("input",{value:_,onChange:Y=>O(Y.target.value),type:"number",min:"0",step:"0.01"})]}),f.jsxs("div",{className:"form-group",children:[f.jsx("div",{className:"form-label",children:"RPM limit"}),f.jsx("input",{value:T,onChange:Y=>p(Y.target.value),type:"number",min:"0"})]}),f.jsxs("div",{className:"form-row",children:[f.jsx("button",{className:"btn btn-primary",onClick:U,children:"Save"}),f.jsx("button",{className:"btn btn-secondary",onClick:i,children:"Cancel"}),f.jsx("button",{className:"btn btn-danger",style:{marginLeft:"auto"},onClick:D,children:"Revoke"})]})]})})}function Up(){const{data:s,isLoading:i,error:o}=Ig(),[r,h]=V.useState(null),[S,_]=V.useState(null);return f.jsxs("div",{children:[f.jsx(Cp,{onCreated:h}),r&&f.jsxs("div",{className:"key-result",children:[f.jsx("div",{className:"key-result-label",children:"New key (copy now — not shown again)"}),r]}),f.jsx(Rl,{loading:i,error:o?.message,empty:s?.length===0,message:"No keys"}),s&&s.length>0&&f.jsxs("table",{className:"keys-grid",children:[f.jsx("thead",{children:f.jsxs("tr",{children:[f.jsx("th",{children:"Prefix"}),f.jsx("th",{children:"Description"}),f.jsx("th",{children:"Status"}),f.jsx("th",{children:"Spend"}),f.jsx("th",{children:"Requests"}),f.jsx("th",{children:"Created"})]})}),f.jsx("tbody",{children:s.map(O=>f.jsxs("tr",{style:{cursor:"pointer"},onClick:()=>_(O),children:[f.jsxs("td",{className:"mono",children:[O.key_prefix,"…"]}),f.jsx("td",{className:"dim",children:O.description??"—"}),f.jsx("td",{children:f.jsx(Mp,{variant:O.status})}),f.jsx("td",{children:f.jsx(Rp,{spent:O.total_spend,limit:O.spend_limit})}),f.jsx("td",{className:"mono",children:O.total_requests.toLocaleString()}),f.jsx("td",{className:"mono dim",children:O.created_at.slice(0,10)})]},O.id))})]}),S&&f.jsx(Dp,{vk:S,onClose:()=>_(null)},S.id)]})}const Xm={openrouter:{text:"Public, no key needed",needsKey:!1},deepinfra:{text:"Public, no key needed",needsKey:!1},ollama:{text:"No key needed (local)",needsKey:!1},configured:{text:"API key required",needsKey:!0},custom:{text:"API key may be required",needsKey:!0}};function qp(){return f.jsx("svg",{width:"12",height:"12",viewBox:"0 0 16 16",fill:"none",style:{verticalAlign:"-1px",marginRight:3},children:f.jsx("path",{d:"M10.5 1a4.5 4.5 0 0 0-4.1 6.35L2 11.75V15h3.25v-2H7v-1.75h1.75L9.65 10.4A4.5 4.5 0 1 0 10.5 1zm1 3a1 1 0 1 1 0-2 1 1 0 0 1 0 2z",fill:"currentColor"})})}function Hp(){const{data:s,isLoading:i,error:o}=sp(),r=cp(),h=fp(),S=rp(),[_,O]=V.useState(""),[T,p]=V.useState(""),[U,D]=V.useState("openai"),[Y,k]=V.useState("openrouter"),[$,G]=V.useState(""),P=Xm[Y]??Xm.custom;function dt(){S.mutate({source:Y,...Y==="custom"?{url:$}:{}})}function Ot(K){p(K)}return f.jsxs("div",{children:[f.jsxs("div",{style:{marginBottom:20},children:[f.jsx("div",{className:"section-label",style:{marginBottom:8},children:"Discover Models"}),f.jsxs("div",{style:{display:"flex",gap:8,alignItems:"center",flexWrap:"wrap"},children:[f.jsxs("select",{value:Y,onChange:K=>{k(K.target.value),S.reset()},children:[f.jsx("option",{value:"openrouter",children:"OpenRouter"}),f.jsx("option",{value:"deepinfra",children:"DeepInfra"}),f.jsx("option",{value:"ollama",children:"Ollama (local)"}),f.jsx("option",{value:"configured",children:"Configured backend"}),f.jsx("option",{value:"custom",children:"Custom URL"})]}),Y==="custom"&&f.jsx("input",{placeholder:"https://api.example.com",value:$,onChange:K=>G(K.target.value),style:{minWidth:220}}),f.jsx("button",{className:"btn btn-secondary",onClick:dt,disabled:S.isPending||Y==="custom"&&!$,children:S.isPending?"Fetching...":"Fetch"}),f.jsxs("span",{className:"dim",style:{fontSize:12},children:[P.needsKey&&f.jsx(qp,{}),P.text]})]}),S.isError&&f.jsx("div",{style:{marginTop:8,padding:"6px 10px",background:"var(--err-dim)",borderLeft:"3px solid var(--err)",borderRadius:"var(--r)",fontSize:12},children:S.error.message}),S.data&&S.data.models.length>0&&f.jsxs("div",{style:{marginTop:8},children:[f.jsxs("div",{className:"dim",style:{fontSize:12,marginBottom:4},children:[S.data.models.length," model",S.data.models.length!==1?"s":""," found. Click to populate the form below."]}),f.jsx("div",{style:{maxHeight:200,overflowY:"auto",border:"1px solid var(--border)",borderRadius:"var(--r)",fontSize:12},children:S.data.models.map(K=>f.jsxs("div",{onClick:()=>Ot(K.id),style:{padding:"4px 8px",cursor:"pointer",borderBottom:"1px solid var(--border)",background:T===K.id?"var(--accent-dim)":void 0},onMouseEnter:vt=>{vt.target.style.background="var(--surface-2)"},onMouseLeave:vt=>{vt.target.style.background=T===K.id?"var(--accent-dim)":""},children:[f.jsx("span",{className:"mono",children:K.id}),K.name&&K.name!==K.id&&f.jsx("span",{className:"dim",style:{marginLeft:8},children:K.name})]},K.id))})]}),S.data&&S.data.models.length===0&&f.jsx("div",{className:"dim",style:{marginTop:8,fontSize:12},children:"No models returned."})]}),f.jsxs("div",{className:"form-group",children:[f.jsx("div",{className:"form-label",children:"Add Model"}),f.jsxs("div",{className:"form-row",style:{flexWrap:"wrap"},children:[f.jsx("input",{placeholder:"Virtual name",value:_,onChange:K=>O(K.target.value)}),f.jsx("input",{placeholder:"Model ID",value:T,onChange:K=>p(K.target.value)}),f.jsxs("select",{value:U,onChange:K=>D(K.target.value),children:[f.jsx("option",{value:"openai",children:"openai"}),f.jsx("option",{value:"anthropic",children:"anthropic"}),f.jsx("option",{value:"gemini",children:"gemini"}),f.jsx("option",{value:"vertex",children:"vertex"}),f.jsx("option",{value:"azure",children:"azure"}),f.jsx("option",{value:"bedrock",children:"bedrock"})]}),f.jsx("button",{className:"btn btn-primary",onClick:()=>r.mutate({name:_,model:T,provider:U}),disabled:!_||!T||r.isPending,children:"Add"})]})]}),f.jsx(Rl,{loading:i,error:o?.message}),s&&f.jsxs("table",{className:"route-table",children:[f.jsx("thead",{children:f.jsxs("tr",{children:[f.jsx("th",{children:"Virtual Name"}),f.jsx("th",{children:"Model"}),f.jsx("th",{children:"Provider"}),f.jsx("th",{children:"Strategy"}),f.jsx("th",{})]})}),f.jsx("tbody",{children:s.models.map(K=>f.jsxs("tr",{children:[f.jsx("td",{className:"mono",children:K.name}),f.jsx("td",{className:"mono",children:K.model}),f.jsx("td",{className:"dim",children:K.provider}),f.jsx("td",{className:"dim",children:s.routing_strategy}),f.jsx("td",{children:f.jsx("button",{className:"btn btn-danger btn-sm",onClick:()=>h.mutate(K.name),children:"Remove"})})]},`${K.name}-${K.model}`))})]})]})}function Bp(){const[s,i]=V.useState(1),{data:o,isLoading:r,error:h}=op({page:s,page_size:50});return f.jsxs("div",{children:[f.jsx(Rl,{loading:r,error:h?.message}),o&&f.jsxs(f.Fragment,{children:[f.jsxs("table",{className:"route-table",children:[f.jsx("thead",{children:f.jsxs("tr",{children:[f.jsx("th",{children:"Time"}),f.jsx("th",{children:"Action"}),f.jsx("th",{children:"Target"}),f.jsx("th",{children:"Detail"}),f.jsx("th",{children:"IP"})]})}),f.jsx("tbody",{children:o.entries.map(S=>f.jsxs("tr",{children:[f.jsx("td",{className:"mono dim",children:S.timestamp.slice(0,19)}),f.jsx("td",{className:"mono",children:S.action}),f.jsxs("td",{className:"dim",children:[S.target_type,S.target_id?` #${S.target_id}`:""]}),f.jsx("td",{className:"dim",style:{maxWidth:300,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:S.detail??"—"}),f.jsx("td",{className:"mono dim",children:S.source_ip??"—"})]},S.id))})]}),f.jsx(pv,{page:s,hasMore:o.has_more,onPrev:()=>i(S=>Math.max(1,S-1)),onNext:()=>i(S=>S+1)})]})]})}function Qp({routes:s}){const i=[...s].sort((o,r)=>r.requests_per_min-o.requests_per_min);return f.jsxs("table",{className:"route-table",children:[f.jsx("thead",{children:f.jsxs("tr",{children:[f.jsx("th",{children:"Route"}),f.jsx("th",{children:"Req/min"}),f.jsx("th",{children:"Error rate"}),f.jsx("th",{children:"Avg latency"}),f.jsx("th",{children:"P95 latency"}),f.jsx("th",{children:"Total"})]})}),f.jsx("tbody",{children:i.map(o=>f.jsxs("tr",{children:[f.jsx("td",{className:"mono",children:o.path}),f.jsx("td",{className:"mono",children:o.requests_per_min.toFixed(2)}),f.jsxs("td",{className:"mono",style:{color:o.error_rate>.05?"var(--err)":o.error_rate>.01?"var(--warn)":void 0},children:[(o.error_rate*100).toFixed(1),"%"]}),f.jsxs("td",{className:"mono",children:[o.avg_latency_ms.toFixed(0),"ms"]}),f.jsxs("td",{className:"mono",children:[o.p95_latency_ms,"ms"]}),f.jsx("td",{className:"mono",children:o.total_requests.toLocaleString()})]},o.path))})]})}const Km=["#e8a030","#d4922b","#c07820","#a86015","#8c500a"],Zm=["#6eb5c0","#5aa0ab","#468b96","#327681","#1e616c"];function Yp(){const[s,i]=V.useState(6),{data:o,isLoading:r,error:h}=dp(s),S=o?.routes??[],_=S.slice(0,5).map((O,T)=>{const p=(o?.series??[]).filter(U=>U.path===O.path).map(U=>U.requests);return{label:O.path,color:Km[T%Km.length],data:p}});return f.jsxs("div",{children:[f.jsxs("div",{className:"section-header",children:[f.jsx("span",{className:"section-label",children:"Traffic"}),f.jsxs("select",{value:s,onChange:O=>i(Number(O.target.value)),children:[f.jsx("option",{value:1,children:"Last 1 hour"}),f.jsx("option",{value:6,children:"Last 6 hours"}),f.jsx("option",{value:24,children:"Last 24 hours"})]})]}),f.jsx(Rl,{loading:r,error:h?.message}),o&&f.jsxs(f.Fragment,{children:[f.jsx(Qp,{routes:o.routes}),f.jsxs("div",{className:"operator-grid",style:{marginTop:16},children:[f.jsxs("div",{className:"chart-card",children:[f.jsx("div",{className:"chart-header",children:f.jsxs("div",{children:[f.jsx("div",{className:"chart-title",children:"Requests / min by route"}),f.jsx("div",{className:"chart-subtitle",children:"Stacked over time window"})]})}),f.jsx(vs,{series:_})]}),f.jsxs("div",{className:"chart-card",children:[f.jsx("div",{className:"chart-header",children:f.jsxs("div",{children:[f.jsx("div",{className:"chart-title",children:"Avg latency per route"}),f.jsx("div",{className:"chart-subtitle",children:"ms"})]})}),S.length===0?f.jsx("div",{className:"empty",children:"No routes"}):f.jsx("div",{style:{display:"flex",flexDirection:"column",gap:8,paddingTop:8},children:S.slice(0,5).map((O,T)=>{const p=Math.max(...S.slice(0,5).map(D=>D.avg_latency_ms),1),U=O.avg_latency_ms/p*100;return f.jsxs("div",{children:[f.jsxs("div",{style:{display:"flex",justifyContent:"space-between",fontSize:11,marginBottom:2},children:[f.jsx("span",{className:"mono dim",style:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"70%"},children:O.path}),f.jsxs("span",{className:"mono",children:[O.avg_latency_ms.toFixed(0),"ms"]})]}),f.jsx("div",{style:{height:6,background:"var(--border)",borderRadius:0},children:f.jsx("div",{style:{height:"100%",width:`${U}%`,background:Zm[T%Zm.length],borderRadius:0}})})]},O.path)})})]})]})]})]})}function Lp(s){const i=Math.floor(Date.now()/1e3-s),o=Math.floor(i/86400),r=Math.floor(i%86400/3600),h=Math.floor(i%3600/60);return o>0?`${o}d ${r}h ${h}m`:r>0?`${r}h ${h}m`:`${h}m`}function Gp({proxy:s}){return f.jsxs("div",{className:"uptime-proxy",children:[f.jsxs("div",{className:"uptime-proxy-stats",children:[f.jsxs("div",{children:[f.jsx("div",{className:"section-label",children:"Uptime (30d)"}),f.jsxs("div",{className:"uptime-pct",children:[s.uptime_pct_30d.toFixed(2),"%"]})]}),f.jsxs("div",{children:[f.jsx("div",{className:"section-label",children:"Running"}),f.jsx("div",{className:"stat-value",style:{fontSize:16},children:Lp(s.started_at)})]})]}),f.jsx("div",{className:"section-label",style:{marginBottom:4},children:"30-day history"}),f.jsx("div",{className:"history-bar",children:s.history.map(i=>f.jsx("div",{className:`history-day ${i.status}`,title:`${i.date}: ${i.status}`},i.date))})]})}function Xp({b:s}){const i=s.status==="up"?"ok":s.status==="down"?"err":"dim",o=s.last_checked_at?new Date(s.last_checked_at*1e3).toLocaleTimeString():"—";return f.jsxs("tr",{children:[f.jsx("td",{className:"mono",children:s.name}),f.jsxs("td",{children:[f.jsx(bv,{status:i,pulse:s.status==="up"}),s.status]}),f.jsxs("td",{className:"mono",children:[s.uptime_pct_30d.toFixed(2),"%"]}),f.jsx("td",{className:"mono dim",children:o}),f.jsx("td",{className:"mono dim",children:s.last_latency_ms!=null?`${s.last_latency_ms}ms`:"—"}),f.jsx("td",{children:f.jsx("div",{className:"history-bar",style:{height:12},children:s.history.map(r=>f.jsx("div",{className:`history-day ${r.status}`,title:`${r.date}: ${r.status}`},r.date))})})]})}function Kp(){const{data:s,isLoading:i,error:o}=hp();return f.jsxs("div",{children:[f.jsx(Rl,{loading:i,error:o?.message}),s&&f.jsxs(f.Fragment,{children:[f.jsx(Gp,{proxy:s.proxy}),f.jsx("div",{className:"section-label",style:{marginTop:16,marginBottom:8},children:"Backend Availability"}),f.jsxs("table",{className:"backend-health-table",children:[f.jsx("thead",{children:f.jsxs("tr",{children:[f.jsx("th",{children:"Backend"}),f.jsx("th",{children:"Status"}),f.jsx("th",{children:"Uptime (30d)"}),f.jsx("th",{children:"Last checked"}),f.jsx("th",{children:"Latency"}),f.jsx("th",{children:"History"})]})}),f.jsx("tbody",{children:s.backends.slice().sort((r,h)=>r.name.localeCompare(h.name)).map(r=>f.jsx(Xp,{b:r},r.name))})]})]})]})}function Zp(){const s=Al(p=>p.token),i=Al(p=>p.login),o=ha(p=>p.lastEvent),r=nl(),[h,S]=V.useState("dashboard"),[_,O]=V.useState(!0),{data:T}=kg(!!s);return V.useEffect(()=>{const U=new URLSearchParams(location.search).get("token");U&&!s?fetch("/admin/api/metrics",{headers:{Authorization:`Bearer ${U}`}}).then(D=>{D.ok&&(i(U),history.replaceState(null,"",location.pathname))}).catch(()=>{}).finally(()=>O(!1)):O(!1)},[]),V.useEffect(()=>{s?Vg():wg()},[s]),V.useEffect(()=>{s&&T&&!T.configured&&S("settings")},[T?.configured,s]),V.useEffect(()=>{o&&(o.type==="metrics_snapshot"?r.setQueryData(["metrics"],o.data):o.type==="backend_health_changed"&&r.invalidateQueries({queryKey:["uptime"]}))},[o,r]),_?null:s?f.jsxs("div",{children:[f.jsx(pp,{activeTab:h,onTabChange:S}),f.jsxs("div",{className:"tab-content",children:[h==="dashboard"&&f.jsx(Tp,{}),h==="requests"&&f.jsx(_p,{}),h==="settings"&&f.jsx(Np,{configured:T?.configured??!0}),h==="backends"&&f.jsx(Ap,{}),h==="keys"&&f.jsx(Up,{}),h==="models"&&f.jsx(Hp,{}),h==="audit"&&f.jsx(Bp,{}),h==="traffic"&&f.jsx(Yp,{}),h==="uptime"&&f.jsx(Kp,{})]})]}):f.jsx(yp,{})}const Vp=new Tg({defaultOptions:{queries:{retry:1,refetchOnWindowFocus:!1}}});P0.createRoot(document.getElementById("root")).render(f.jsx(V.StrictMode,{children:f.jsx(_g,{client:Vp,children:f.jsx(Zp,{})})})); diff --git a/crates/proxy/admin-ui/src/App.tsx b/crates/proxy/admin-ui/src/App.tsx index 537bb9c..943249e 100644 --- a/crates/proxy/admin-ui/src/App.tsx +++ b/crates/proxy/admin-ui/src/App.tsx @@ -3,6 +3,7 @@ import { useQueryClient } from '@tanstack/react-query' import { useAuthStore } from './store/auth' import { useWsStore } from './store/ws' import { connectWs, disconnectWs } from './api/websocket' +import { useStatus } from './api/queries' import LoginPage from './components/layout/LoginPage' import Nav from './components/layout/Nav' import Dashboard from './tabs/dashboard/Dashboard' @@ -24,6 +25,7 @@ export default function App() { const qc = useQueryClient() const [activeTab, setActiveTab] = useState('dashboard') const [bootstrapping, setBootstrapping] = useState(true) + const { data: status } = useStatus(!!token) // On mount: if ?token= is in the URL and no token is stored, validate and log in. useEffect(() => { @@ -54,6 +56,13 @@ export default function App() { } }, [token]) + // Default to the Settings tab on first load when nothing is configured. + useEffect(() => { + if (token && status && !status.configured) { + setActiveTab('settings') + } + }, [status?.configured, token]) // eslint-disable-line react-hooks/exhaustive-deps -- run when status first arrives + // Invalidate query cache on relevant WS events. useEffect(() => { if (!lastEvent) return @@ -73,7 +82,7 @@ export default function App() {
{activeTab === 'dashboard' && } {activeTab === 'requests' && } - {activeTab === 'settings' && } + {activeTab === 'settings' && } {activeTab === 'backends' && } {activeTab === 'keys' && } {activeTab === 'models' && } diff --git a/crates/proxy/admin-ui/src/api/queries.ts b/crates/proxy/admin-ui/src/api/queries.ts index 50d8c7d..ddce420 100644 --- a/crates/proxy/admin-ui/src/api/queries.ts +++ b/crates/proxy/admin-ui/src/api/queries.ts @@ -5,9 +5,20 @@ import type { Metrics, RequestsResponse, VirtualKey, KeySpend, Backend, ConfigResponse, ObservabilityResponse, ModelsResponse, AuditResponse, TrafficResponse, UptimeResponse, - EnvImportResponse, + EnvImportResponse, ProxyStatus, DiscoverResponse, } from './types' +// ── Status ─────────────────────────────────────────────────────────────────── + +export function useStatus(enabled = true) { + return useQuery({ + queryKey: ['status'], + queryFn: () => apiFetch('/admin/api/status'), + enabled, + staleTime: Infinity, + }) +} + // ── Dashboard ──────────────────────────────────────────────────────────────── export function useMetrics() { @@ -171,6 +182,13 @@ export function useRemoveModel() { }) } +export function useDiscoverModels() { + return useMutation({ + mutationFn: (body) => + mutatingFetch('POST', '/admin/api/models/discover', body), + }) +} + // ── Audit ───────────────────────────────────────────────────────────────────── export function useAudit(params: { page: number; page_size: number }) { diff --git a/crates/proxy/admin-ui/src/api/types.ts b/crates/proxy/admin-ui/src/api/types.ts index fcc6a4f..17e6863 100644 --- a/crates/proxy/admin-ui/src/api/types.ts +++ b/crates/proxy/admin-ui/src/api/types.ts @@ -1,6 +1,10 @@ // Mirrors the JSON shapes returned by /admin/api/* endpoints. // Keep in sync with Rust structs in crates/proxy/src/admin/state.rs and routes/. +export interface ProxyStatus { + configured: boolean +} + export interface Metrics { total_requests: number successful_requests: number @@ -140,6 +144,17 @@ export interface ModelsResponse { routing_strategy: string } +export interface DiscoveredModel { + id: string + name: string | null +} + +export interface DiscoverResponse { + models: DiscoveredModel[] + source: string + auth_used: boolean +} + export interface AuditEntry { id: number timestamp: string diff --git a/crates/proxy/admin-ui/src/tabs/models/Models.tsx b/crates/proxy/admin-ui/src/tabs/models/Models.tsx index 66d24e6..8237c40 100644 --- a/crates/proxy/admin-ui/src/tabs/models/Models.tsx +++ b/crates/proxy/admin-ui/src/tabs/models/Models.tsx @@ -1,17 +1,123 @@ import { useState } from 'react' -import { useModels, useAddModel, useRemoveModel } from '../../api/queries' +import { useModels, useAddModel, useRemoveModel, useDiscoverModels } from '../../api/queries' import EmptyState from '../../components/shared/EmptyState' +const AUTH_HINTS: Record = { + openrouter: { text: 'Public, no key needed', needsKey: false }, + deepinfra: { text: 'Public, no key needed', needsKey: false }, + ollama: { text: 'No key needed (local)', needsKey: false }, + configured: { text: 'API key required', needsKey: true }, + custom: { text: 'API key may be required', needsKey: true }, +} + +// Inline SVG key icon (12x12), used as auth indicator next to sources that need a key. +function KeyIcon() { + return ( + + + + ) +} + export default function Models() { const { data, isLoading, error } = useModels() const add = useAddModel() const remove = useRemoveModel() + const discover = useDiscoverModels() const [name, setName] = useState('') const [model, setModel] = useState('') const [provider, setProvider] = useState('openai') + const [discoverSource, setDiscoverSource] = useState('openrouter') + const [customUrl, setCustomUrl] = useState('') + + const hint = AUTH_HINTS[discoverSource] ?? AUTH_HINTS.custom + + function handleDiscover() { + discover.mutate({ + source: discoverSource, + ...(discoverSource === 'custom' ? { url: customUrl } : {}), + }) + } + return (
+ {/* Discover models section */} +
+
Discover Models
+
+ + {discoverSource === 'custom' && ( + setCustomUrl(e.target.value)} + style={{ minWidth: 220 }} + /> + )} + + + {hint.needsKey && }{hint.text} + +
+ + {/* Discovery error */} + {discover.isError && ( +
+ {discover.error.message} +
+ )} + + {/* Discovery results */} + {discover.data && discover.data.models.length > 0 && ( +
+
+ {discover.data.models.length} model{discover.data.models.length !== 1 ? 's' : ''} found. + Click to populate the form below. +
+
+ {discover.data.models.map((m) => ( +
setModel(m.id)} + style={{ + padding: '4px 8px', + cursor: 'pointer', + borderBottom: '1px solid var(--border)', + background: model === m.id ? 'var(--accent-dim)' : undefined, + }} + onMouseEnter={(e) => { (e.target as HTMLElement).style.background = 'var(--surface-2)' }} + onMouseLeave={(e) => { (e.target as HTMLElement).style.background = model === m.id ? 'var(--accent-dim)' : '' }} + > + {m.id} + {m.name && m.name !== m.id && {m.name}} +
+ ))} +
+
+ )} + + {discover.data && discover.data.models.length === 0 && ( +
No models returned.
+ )} +
+ + {/* Manual add model form */}
Add Model
diff --git a/crates/proxy/admin-ui/src/tabs/settings/Settings.tsx b/crates/proxy/admin-ui/src/tabs/settings/Settings.tsx index cc4f27c..681bdd0 100644 --- a/crates/proxy/admin-ui/src/tabs/settings/Settings.tsx +++ b/crates/proxy/admin-ui/src/tabs/settings/Settings.tsx @@ -12,7 +12,7 @@ function restartPending() { return sessionStorage.getItem(RESTART_KEY) === '1' } -export default function Settings() { +export default function Settings({ configured = true }: { configured?: boolean }) { const { data: cfg, isLoading, error } = useConfig() const { data: envData } = useEnv() const save = useSaveConfig() @@ -77,6 +77,42 @@ export default function Settings() { return (
+ {/* Getting-started notice — shown when no backend is configured */} + {!configured && ( +
+
No proxy configuration found — nothing to forward requests to.
+
+ The proxy needs a backend endpoint (where to forward) and a listen port (where to accept). + LISTEN_PORT defaults to 3000. Create a .anyllm.env and import it below, + or pass it at startup: anyllm-proxy --webui --env-file .anyllm.env +
+
+
+
OpenAI
+
+{`OPENAI_API_KEY=sk-...
+PROXY_API_KEYS=my-key`}
+              
+
+
+
Ollama / local LLM
+
+{`OPENAI_BASE_URL=http://localhost:11434/v1
+PROXY_OPEN_RELAY=true`}
+              
+
+
+
OpenRouter / custom
+
+{`OPENAI_BASE_URL=https://openrouter.ai/api/v1
+OPENAI_API_KEY=sk-or-...
+PROXY_API_KEYS=my-key`}
+              
+
+
+
+ )} + {/* Restart-required banner — shown after a successful import */} {showRestartBanner && (
diff --git a/crates/proxy/src/admin/db.rs b/crates/proxy/src/admin/db.rs index af90abb..6fe2522 100644 --- a/crates/proxy/src/admin/db.rs +++ b/crates/proxy/src/admin/db.rs @@ -143,6 +143,21 @@ pub fn init_db(conn: &Connection) -> rusqlite::Result<()> { );", )?; + // model_deployment: models added via the admin API, persisted across restarts. + conn.execute_batch( + "CREATE TABLE IF NOT EXISTS model_deployment ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + model_name TEXT NOT NULL, + backend_name TEXT NOT NULL, + actual_model TEXT NOT NULL, + rpm_limit INTEGER, + tpm_limit INTEGER, + weight INTEGER NOT NULL DEFAULT 1, + created_at TEXT NOT NULL, + UNIQUE(model_name, backend_name, actual_model) + );", + )?; + Ok(()) } @@ -176,6 +191,72 @@ pub fn list_env_import(conn: &Connection) -> rusqlite::Result, + pub tpm_limit: Option, + pub weight: u32, +} + +/// Insert or ignore a model deployment (unique constraint prevents duplicates). +pub fn insert_model_deployment( + conn: &Connection, + model_name: &str, + backend_name: &str, + actual_model: &str, + rpm: Option, + tpm: Option, + weight: u32, +) -> rusqlite::Result<()> { + conn.execute( + "INSERT OR IGNORE INTO model_deployment + (model_name, backend_name, actual_model, rpm_limit, tpm_limit, weight, created_at) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", + params![ + model_name, + backend_name, + actual_model, + rpm, + tpm, + weight, + chrono_now() + ], + )?; + Ok(()) +} + +/// Delete all deployments for a given model name. Returns the number of rows deleted. +pub fn delete_model_deployments(conn: &Connection, model_name: &str) -> rusqlite::Result { + conn.execute( + "DELETE FROM model_deployment WHERE model_name = ?1", + [model_name], + ) +} + +/// Return all persisted model deployments, ordered by model name. +pub fn list_model_deployments(conn: &Connection) -> rusqlite::Result> { + let mut stmt = conn.prepare( + "SELECT model_name, backend_name, actual_model, rpm_limit, tpm_limit, weight + FROM model_deployment ORDER BY model_name, backend_name", + )?; + let rows = stmt.query_map([], |r| { + Ok(ModelDeploymentRow { + model_name: r.get(0)?, + backend_name: r.get(1)?, + actual_model: r.get(2)?, + rpm_limit: r.get(3)?, + tpm_limit: r.get(4)?, + weight: r.get(5)?, + }) + })?; + rows.collect() +} + fn now_unix_secs() -> i64 { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) diff --git a/crates/proxy/src/admin/routes/mod.rs b/crates/proxy/src/admin/routes/mod.rs index 451eaed..0f50104 100644 --- a/crates/proxy/src/admin/routes/mod.rs +++ b/crates/proxy/src/admin/routes/mod.rs @@ -7,6 +7,7 @@ pub mod keys; pub mod logs; pub mod mcp; pub mod models; +pub mod status; pub mod traffic; pub mod uptime; @@ -399,6 +400,7 @@ pub fn admin_router(shared: SharedState, token: Arc>) "/admin/api/models", get(models::list_models).post(models::add_model), ) + .route("/admin/api/models/discover", post(models::discover_models)) .route("/admin/api/models/{name}", delete(models::remove_model)) .route("/admin/api/audit", get(audit::get_audit_log)) .route( @@ -409,6 +411,7 @@ pub fn admin_router(shared: SharedState, token: Arc>) "/admin/api/mcp-servers/{name}", delete(mcp::remove_mcp_server), ) + .route("/admin/api/status", get(status::get_status)) .route("/admin/api/traffic", get(traffic::get_traffic)) .route("/admin/api/uptime", get(uptime::get_uptime)) .with_state(shared.clone()) diff --git a/crates/proxy/src/admin/routes/models.rs b/crates/proxy/src/admin/routes/models.rs index 41545bc..09fe780 100644 --- a/crates/proxy/src/admin/routes/models.rs +++ b/crates/proxy/src/admin/routes/models.rs @@ -6,6 +6,17 @@ use axum::{ Json, }; use std::net::SocketAddr; +use std::sync::LazyLock; + +/// Shared HTTP client for model discovery (lightweight, short timeout). +static DISCOVER_CLIENT: LazyLock = LazyLock::new(|| { + reqwest::Client::builder() + .connect_timeout(std::time::Duration::from_secs(10)) + .timeout(std::time::Duration::from_secs(15)) + .redirect(reqwest::redirect::Policy::none()) + .build() + .expect("failed to build discover HTTP client") +}); /// GET /admin/api/models -- list all routed model names and deployment counts. pub(super) async fn list_models(State(shared): State) -> impl IntoResponse { @@ -114,6 +125,21 @@ pub(super) async fn add_model( let mut router = router_lock.write().unwrap_or_else(|e| e.into_inner()); router.add_deployment(body.model_name.clone(), deployment); + // Persist to SQLite so the deployment survives restarts. + if let Ok(db) = shared.db.lock() { + if let Err(e) = crate::admin::db::insert_model_deployment( + &db, + &body.model_name, + &body.backend_name, + &body.actual_model, + body.rpm, + body.tpm, + body.weight, + ) { + tracing::warn!(error = %e, "failed to persist model deployment to SQLite"); + } + } + tracing::info!( model_name = %body.model_name, backend = %body.backend_name, @@ -165,6 +191,12 @@ pub(super) async fn remove_model( let mut router = router_lock.write().unwrap_or_else(|e| e.into_inner()); if router.remove_model(&name) { + // Remove from SQLite as well. + if let Ok(db) = shared.db.lock() { + if let Err(e) = crate::admin::db::delete_model_deployments(&db, &name) { + tracing::warn!(error = %e, "failed to remove model deployment from SQLite"); + } + } tracing::info!(model_name = %name, "removed model via admin API"); super::emit_audit( &shared, @@ -191,3 +223,163 @@ pub(super) async fn remove_model( .into_response() } } + +// ── Model discovery ────────────────────────────────────────────────────────── + +#[derive(serde::Deserialize)] +pub(super) struct DiscoverRequest { + source: String, + #[serde(default)] + url: Option, +} + +#[derive(serde::Serialize)] +struct DiscoverResponse { + models: Vec, + source: String, + auth_used: bool, +} + +#[derive(serde::Serialize)] +struct DiscoveredModel { + id: String, + #[serde(skip_serializing_if = "Option::is_none")] + name: Option, +} + +/// POST /admin/api/models/discover -- fetch available models from a provider. +pub(super) async fn discover_models(Json(body): Json) -> impl IntoResponse { + let (url, api_key) = match resolve_discover_target(&body) { + Ok(v) => v, + Err(msg) => { + return ( + StatusCode::BAD_REQUEST, + Json(serde_json::json!({ "error": msg })), + ) + .into_response(); + } + }; + + let auth_used = api_key.is_some(); + let mut req = DISCOVER_CLIENT.get(&url); + if let Some(ref key) = api_key { + req = req.header("Authorization", format!("Bearer {key}")); + } + + let resp = match req.send().await { + Ok(r) => r, + Err(e) => { + let msg = if e.is_connect() { + format!("connection refused: {url}") + } else if e.is_timeout() { + format!("request timed out: {url}") + } else { + format!("request failed: {e}") + }; + return ( + StatusCode::BAD_GATEWAY, + Json(serde_json::json!({ "error": msg })), + ) + .into_response(); + } + }; + + if resp.status() == reqwest::StatusCode::UNAUTHORIZED { + return ( + StatusCode::UNAUTHORIZED, + Json(serde_json::json!({ + "error": "API key required. Configure a key in Settings, then try again." + })), + ) + .into_response(); + } + + if !resp.status().is_success() { + return ( + StatusCode::BAD_GATEWAY, + Json(serde_json::json!({ + "error": format!("upstream returned {}", resp.status()) + })), + ) + .into_response(); + } + + let json: serde_json::Value = match resp.json().await { + Ok(v) => v, + Err(e) => { + return ( + StatusCode::BAD_GATEWAY, + Json(serde_json::json!({ "error": format!("invalid JSON: {e}") })), + ) + .into_response(); + } + }; + + // Standard OpenAI format: { "data": [{ "id": "...", "name": "..." }, ...] } + let mut models: Vec = json + .get("data") + .and_then(|d| d.as_array()) + .map(|arr| { + arr.iter() + .filter_map(|m| { + let id = m.get("id")?.as_str()?.to_string(); + let name = m.get("name").and_then(|n| n.as_str()).map(String::from); + Some(DiscoveredModel { id, name }) + }) + .collect() + }) + .unwrap_or_default(); + + models.sort_unstable_by(|a, b| a.id.cmp(&b.id)); + + ( + StatusCode::OK, + Json(DiscoverResponse { + models, + source: body.source, + auth_used, + }), + ) + .into_response() +} + +/// Map the source name to a (URL, optional API key) pair. +fn resolve_discover_target(body: &DiscoverRequest) -> Result<(String, Option), String> { + match body.source.as_str() { + "openrouter" => Ok(("https://openrouter.ai/api/v1/models".into(), None)), + "deepinfra" => Ok(("https://api.deepinfra.com/v1/openai/models".into(), None)), + "ollama" => { + let base = std::env::var("OPENAI_BASE_URL") + .unwrap_or_else(|_| "http://localhost:11434".into()); + let base = base.trim_end_matches('/'); + // Ollama exposes /v1/models when running in OpenAI-compat mode, + // but the native endpoint is /api/tags. Try /v1/models first. + Ok((format!("{base}/v1/models"), None)) + } + "configured" => { + let base = std::env::var("OPENAI_BASE_URL") + .unwrap_or_else(|_| "https://api.openai.com".into()); + let base = base.trim_end_matches('/'); + let key = std::env::var("OPENAI_API_KEY") + .ok() + .filter(|k| !k.is_empty()); + Ok((format!("{base}/v1/models"), key)) + } + "custom" => { + let url = body + .url + .as_deref() + .filter(|u| !u.is_empty()) + .ok_or("url is required for custom source")?; + let url = url.trim_end_matches('/'); + // If the URL already ends with /models, use as-is; otherwise append. + let url = if url.ends_with("/models") { + url.to_string() + } else { + format!("{url}/v1/models") + }; + Ok((url, None)) + } + other => Err(format!("unknown source: {other}")), + } +} diff --git a/crates/proxy/src/admin/routes/status.rs b/crates/proxy/src/admin/routes/status.rs new file mode 100644 index 0000000..87a450a --- /dev/null +++ b/crates/proxy/src/admin/routes/status.rs @@ -0,0 +1,41 @@ +use axum::Json; +use serde::Serialize; + +#[derive(Serialize)] +pub struct ProxyStatus { + pub configured: bool, +} + +/// GET /admin/api/status -- returns whether the proxy has a backend configured. +/// "Configured" means the user set at least one backend-relevant env var +/// (API key, base URL, provider choice) or pointed to a config file. +pub async fn get_status() -> Json { + Json(ProxyStatus { + configured: is_backend_configured(), + }) +} + +/// Returns true when the user has provided enough information for the proxy to +/// know where to forward requests: an API key, a custom base URL, an explicit +/// backend choice, or a config file. +pub fn is_backend_configured() -> bool { + const BACKEND_SIGNALS: &[&str] = &[ + "OPENAI_API_KEY", + "OPENAI_BASE_URL", + "BACKEND", + "ANTHROPIC_API_KEY", + "GEMINI_API_KEY", + "VERTEX_API_KEY", + "GOOGLE_ACCESS_TOKEN", + "AWS_ACCESS_KEY_ID", + "AZURE_OPENAI_API_KEY", + "AZURE_OPENAI_ENDPOINT", + ]; + let has_signal = BACKEND_SIGNALS + .iter() + .any(|k| std::env::var(k).map(|v| !v.is_empty()).unwrap_or(false)); + let has_proxy_config = std::env::var("PROXY_CONFIG") + .map(|p| std::path::Path::new(&p).exists()) + .unwrap_or(false); + has_signal || has_proxy_config +} diff --git a/crates/proxy/src/env_parser.rs b/crates/proxy/src/env_parser.rs index 651213b..4f29b85 100644 --- a/crates/proxy/src/env_parser.rs +++ b/crates/proxy/src/env_parser.rs @@ -14,6 +14,8 @@ use std::sync::LazyLock; /// All known env var names accepted by anyllm_proxy. /// Import warns on keys not in this list but still applies them. pub(crate) const KNOWN_KEYS: &[&str] = &[ + // Data directory + "ANYLLM_HOME", // Core proxy "BACKEND", "LISTEN_PORT", diff --git a/crates/proxy/src/main.rs b/crates/proxy/src/main.rs index 9e89b9d..b068487 100644 --- a/crates/proxy/src/main.rs +++ b/crates/proxy/src/main.rs @@ -3,6 +3,7 @@ use anyllm_proxy::{ server::{routes, state}, tools, }; +use std::path::PathBuf; use std::sync::Arc; use tracing_subscriber::prelude::*; @@ -16,19 +17,28 @@ fn main() { // inherited from the parent process; skip env file loading to avoid duplicate messages. let is_run_child = std::env::var("_ANYLLM_RUN_CHILD").is_ok(); + // Resolve the data directory early so all path defaults can use it. + let data_dir = resolve_data_dir(); if !is_run_child { + eprintln!("anyllm_proxy: data directory: {}", data_dir.display()); + let data_dir_env = data_dir.join(".anyllm.env"); let env_file_path = args .windows(2) .find(|w| w[0] == "--env-file") - .map(|w| w[1].as_str()) + .map(|w| w[1].to_string()) .or_else(|| { if std::path::Path::new(".anyllm.env").exists() { - Some(".anyllm.env") + Some(".anyllm.env".into()) + } else if data_dir_env.exists() { + Some(data_dir_env.to_string_lossy().into_owned()) } else { None } }); - let env_file_vars = env_file_path.map(parse_env_file).unwrap_or_default(); + let env_file_vars = env_file_path + .as_deref() + .map(parse_env_file) + .unwrap_or_default(); // SAFETY: genuinely single-threaded here (no tokio runtime yet). unsafe { @@ -56,7 +66,7 @@ fn main() { // Runs after .anyllm.env so the file still takes precedence over DB imports, // and before the async runtime to keep set_var single-threaded safe. if !is_run_child { - let db_path = std::env::var("ADMIN_DB_PATH").unwrap_or_else(|_| "admin.db".to_string()); + let db_path = resolve_db_path(&data_dir); let db_vars = load_env_from_sqlite(&db_path); if !db_vars.is_empty() { unsafe { @@ -71,6 +81,16 @@ fn main() { } } + // Auto-detect config file in data directory if PROXY_CONFIG is not set. + if std::env::var("PROXY_CONFIG").is_err() { + let data_config = data_dir.join("config.yaml"); + if data_config.exists() { + let path_str = data_config.to_string_lossy().into_owned(); + unsafe { std::env::set_var("PROXY_CONFIG", &path_str) }; + eprintln!("anyllm_proxy: auto-detected config: {path_str}"); + } + } + // Extract litellm master_key before the runtime starts (still single-threaded). if std::env::var("PROXY_API_KEYS").is_err() { if let Ok(ref config_path) = std::env::var("PROXY_CONFIG") { @@ -82,6 +102,37 @@ fn main() { } } + // Warn when no backend is configured so users aren't left guessing why + // requests fail. Skip when spawned as a child of the "run" subcommand. + if !is_run_child && !anyllm_proxy::admin::routes::status::is_backend_configured() { + eprintln!( + "\n\ +anyllm-proxy: no backend configured. The proxy has nothing to forward requests to.\n\ +\n\ +The proxy needs an endpoint to forward to (backend) and a port to listen on (front).\n\ +LISTEN_PORT defaults to 3000. Pick a backend:\n\ +\n\ + # OpenAI (remote, needs API key)\n\ + OPENAI_API_KEY=sk-...\n\ + PROXY_API_KEYS=my-key # key your clients send\n\ +\n\ + # Ollama / local LLM (no API key required)\n\ + OPENAI_BASE_URL=http://localhost:11434/v1\n\ + PROXY_OPEN_RELAY=true # allow any key (local dev only)\n\ +\n\ + # OpenRouter / any OpenAI-compatible endpoint\n\ + OPENAI_BASE_URL=https://openrouter.ai/api/v1\n\ + OPENAI_API_KEY=sk-or-...\n\ + PROXY_API_KEYS=my-key\n\ +\n\ +Save to ~/.anyllm/.anyllm.env or load explicitly:\n\ +\n\ + anyllm-proxy --env-file /path/to/.anyllm.env\n\ +\n\ +Configure via UI: anyllm-proxy --webui\n" + ); + } + // Detect "run" subcommand: anyllm_proxy [proxy_opts...] run [args...] // Starts the proxy in the background and launches with the proxy's // ANTHROPIC_* env vars pre-configured, then exits when exits. @@ -101,10 +152,10 @@ fn main() { .enable_all() .build() .expect("failed to build tokio runtime") - .block_on(async_main(args)); + .block_on(async_main(args, data_dir)); } -async fn async_main(args: Vec) { +async fn async_main(args: Vec, data_dir: PathBuf) { // ---- Phase 3: Init tracing (needs RUST_LOG from env file) ---- let env_filter = tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")); @@ -131,7 +182,39 @@ async fn async_main(args: Vec) { // Env aliases were already applied in sync main(). Load config. let load_result = config::MultiConfig::load(); let multi_config = load_result.multi_config; - let model_router = load_result.model_router; + + // Ensure a ModelRouter always exists (empty if no config file). + // Then merge persisted model deployments from SQLite. + let model_router = { + let router = load_result.model_router.unwrap_or_else(|| { + Arc::new(std::sync::RwLock::new( + config::model_router::ModelRouter::new(std::collections::HashMap::new()), + )) + }); + // Load persisted deployments from the DB (best-effort, non-fatal). + let db_path = resolve_db_path(&data_dir); + if let Ok(conn) = rusqlite::Connection::open(&db_path) { + if let Ok(rows) = admin::db::list_model_deployments(&conn) { + if !rows.is_empty() { + let mut rw = router.write().unwrap_or_else(|e| e.into_inner()); + for row in &rows { + rw.add_deployment( + row.model_name.clone(), + Arc::new(config::model_router::Deployment::with_weight( + row.backend_name.clone(), + row.actual_model.clone(), + row.rpm_limit, + row.tpm_limit, + row.weight, + )), + ); + } + tracing::info!(count = rows.len(), "loaded persisted model deployments from DB"); + } + } + } + Some(router) + }; // litellm master_key was already applied in fn main() (single-threaded). // Log confirmation if it was set. @@ -355,8 +438,7 @@ async fn async_main(args: Vec) { panic!("ADMIN_PORT ({admin_port}) must differ from LISTEN_PORT ({listen_port})"); } - // SQLite: open or create the database file in the current directory. - let db_path = std::env::var("ADMIN_DB_PATH").unwrap_or_else(|_| "admin.db".into()); + let db_path = resolve_db_path(&data_dir); let conn = rusqlite::Connection::open(&db_path).expect("failed to open SQLite database for admin"); admin::db::init_db(&conn).expect("failed to initialize admin database schema"); @@ -545,7 +627,7 @@ async fn async_main(args: Vec) { let mut buf = [0u8; 32]; getrandom::fill(&mut buf).expect("getrandom failed"); let token = hex::encode(buf); - let token_path = resolve_admin_token_path(); + let token_path = resolve_admin_token_path(&data_dir); let token_path = token_path.to_string_lossy().to_string(); // Write token to file with restrictive permissions instead of stderr, // because stderr is captured by container log drivers in production. @@ -687,7 +769,7 @@ async fn async_main(args: Vec) { >, >, > = if enable_admin { - let db_path = std::env::var("ADMIN_DB_PATH").unwrap_or_else(|_| "admin.db".into()); + let db_path = resolve_db_path(&data_dir); let batch_conn = rusqlite::Connection::open(&db_path) .expect("failed to open second SQLite connection for batch engine"); anyllm_batch_engine::db::migrate_old_tables(&batch_conn) @@ -823,6 +905,7 @@ async fn async_main(args: Vec) { /// Parse a `.env`-format file and return `(key, value)` pairs to set. /// /// Delegates parsing to `anyllm_proxy::env_parser::parse_env_content` (pure, no side effects). +/// /// Hard errors are printed to stderr and result in an empty list; warnings are printed as-is. /// Already-set environment variables are skipped so the real environment always wins. /// Compatible with Docker `--env-file` and standard dotenv tooling. @@ -892,12 +975,65 @@ fn load_env_from_sqlite(db_path: &str) -> Vec<(String, String)> { .collect() } +/// Resolve the data directory where config, DB, and token files live. +/// Priority: ANYLLM_HOME env var > ~/.anyllm/ > CWD (fallback if HOME unresolvable). +/// Creates the directory on first use (mode 0700 on Unix). +fn resolve_data_dir() -> PathBuf { + let dir = if let Ok(home) = std::env::var("ANYLLM_HOME") { + PathBuf::from(home) + } else if let Some(home) = home_dir() { + home.join(".anyllm") + } else { + // No home directory (unusual). Fall back to CWD. + PathBuf::from(".") + }; + + if !dir.exists() { + #[cfg(unix)] + { + use std::os::unix::fs::DirBuilderExt; + let mut builder = std::fs::DirBuilder::new(); + builder.recursive(true).mode(0o700); + if let Err(e) = builder.create(&dir) { + eprintln!( + "anyllm_proxy: could not create data directory '{}': {e}", + dir.display() + ); + } + } + #[cfg(not(unix))] + { + if let Err(e) = std::fs::create_dir_all(&dir) { + eprintln!( + "anyllm_proxy: could not create data directory '{}': {e}", + dir.display() + ); + } + } + } + dir +} + +/// Cross-platform home directory lookup. +fn home_dir() -> Option { + std::env::var("HOME") + .or_else(|_| std::env::var("USERPROFILE")) + .ok() + .map(PathBuf::from) +} + +/// Resolve SQLite DB path: ADMIN_DB_PATH env var > data_dir/admin.db. +fn resolve_db_path(data_dir: &std::path::Path) -> String { + std::env::var("ADMIN_DB_PATH") + .unwrap_or_else(|_| data_dir.join("admin.db").to_string_lossy().into_owned()) +} + /// Resolve admin token file path from `ADMIN_TOKEN_PATH` env var, -/// falling back to `.admin_token` in the current directory. -fn resolve_admin_token_path() -> std::path::PathBuf { +/// falling back to `~/.anyllm/.admin_token`. +fn resolve_admin_token_path(data_dir: &std::path::Path) -> PathBuf { match std::env::var("ADMIN_TOKEN_PATH") { Ok(p) => { - let path = std::path::PathBuf::from(&p); + let path = PathBuf::from(&p); // Reject paths containing traversal sequences to prevent writing // the admin token to unexpected locations via misconfigured env vars. if p.contains("..") { @@ -905,7 +1041,7 @@ fn resolve_admin_token_path() -> std::path::PathBuf { } path } - Err(_) => std::path::PathBuf::from(".admin_token"), + Err(_) => data_dir.join(".admin_token"), } } diff --git a/docs/CONFIG.md b/docs/CONFIG.md new file mode 100644 index 0000000..ca2fadb --- /dev/null +++ b/docs/CONFIG.md @@ -0,0 +1,103 @@ +# Configuration + +## Config Directory + +anyllm-proxy stores its data in `~/.anyllm/` by default: + +``` +~/.anyllm/ + admin.db SQLite database (keys, models, audit, env imports) + .admin_token Auto-generated admin auth token + .anyllm.env Environment file (optional) + config.yaml Proxy config (optional) +``` + +Override the entire directory with `ANYLLM_HOME=/path/to/dir`. + +Override individual files with their respective env vars (see below). + +## File Lookup Order + +Each file has a specific resolution order. The first match wins. + +| File | Priority 1 (highest) | Priority 2 | Priority 3 | +|------|---------------------|------------|------------| +| Env file | `--env-file` CLI flag | `.anyllm.env` in CWD | `~/.anyllm/.anyllm.env` | +| Config file | `PROXY_CONFIG` env var | `~/.anyllm/config.yaml` | (none) | +| Database | `ADMIN_DB_PATH` env var | `~/.anyllm/admin.db` | | +| Token | `ADMIN_TOKEN_PATH` env var | `~/.anyllm/.admin_token` | | + +The data directory path itself is logged at startup: + +``` +anyllm_proxy: data directory: /home/user/.anyllm +``` + +## Model Persistence + +Models added via the admin API (`POST /admin/api/models`) are stored in +the SQLite database and survive restarts. + +If a YAML config file (`config.yaml` or `PROXY_CONFIG`) also defines +models, those are loaded first as the base layer. Models added through +the admin UI are merged on top. On conflict (same model name + backend + +actual model), the YAML definition takes priority. + +To reset to YAML-only models, remove the admin-added entries via +`DELETE /admin/api/models/{name}`. + +## Docker + +Docker Compose sets explicit paths via environment variables: + +```yaml +environment: + ADMIN_DB_PATH: /data/admin.db + ADMIN_TOKEN_PATH: /data/.admin_token +``` + +These override the `~/.anyllm/` convention. The home directory layout +does not apply inside containers. + +## Quick Start + +### Minimal .anyllm.env + +```env +# OpenAI (remote) +OPENAI_API_KEY=sk-your-key-here +PROXY_API_KEYS=my-proxy-key + +# Or: Ollama (local, no API key) +# OPENAI_BASE_URL=http://localhost:11434/v1 +# PROXY_OPEN_RELAY=true +``` + +Save this to `~/.anyllm/.anyllm.env` and the proxy picks it up +automatically on next start. + +### Startup options + +```bash +# Auto-loads ~/.anyllm/.anyllm.env if present +anyllm-proxy + +# Explicit env file +anyllm-proxy --env-file /path/to/my.env + +# With admin UI +anyllm-proxy --webui + +# Both +anyllm-proxy --webui --env-file /path/to/my.env +``` + +## Environment Variables + +See [ENV.md](ENV.md) for the full list. Key additions: + +| Variable | Default | Description | +|----------|---------|-------------| +| `ANYLLM_HOME` | `~/.anyllm` | Override the data directory path | +| `ADMIN_DB_PATH` | `$ANYLLM_HOME/admin.db` | SQLite database file | +| `ADMIN_TOKEN_PATH` | `$ANYLLM_HOME/.admin_token` | Admin auth token file |