From 42c29e1efbc99eb7216fc39b698a9f21c929f4c2 Mon Sep 17 00:00:00 2001 From: Nathan Brake Date: Thu, 6 Aug 2026 15:36:43 +0000 Subject: [PATCH 1/6] fix(dashboard): let the analytics filters take several models, users, or keys The Usage page's User, Model, and API key pickers committed one value each, so a spend question that is really a comparison ("these two models", "this team's three keys") could only be asked one entity at a time, and the chart could never show them side by side. The three entity filters are now repeatable on the analytics endpoints (/v1/usage/summary, /v1/usage/series, /v1/usage/summary.csv): several values match any of them, capped at 50 per call so a caller cannot post an unbounded IN list. A single value stays an equality test, so every existing caller and the existing wire form are unaffected. The pickers accumulate values, each pick becomes its own removable chip, and the request log's own filters stay single-value: its bulk delete / set-price selection is expressed one value per dimension, and widening it there would let a bulk op reach past the rows the operator was shown. A drill-down therefore carries an entity filter only while it holds a single value. Fixes #489 Co-Authored-By: Claude Opus 5 (1M context) --- docs/dashboard.md | 10 +- docs/public/openapi.json | 92 +++++++++----- docs/public/otari.postman_collection.json | 20 +-- src/gateway/api/routes/usage.py | 98 +++++++++++---- ...e-C28CtpkN.js => ActivityPage-BbEENQgu.js} | 2 +- ...ge-B9iEC7ec.js => BudgetsPage-DGkl3NSe.js} | 2 +- ...-mbnZRETP.js => ConfirmDialog-Dt_8xaSM.js} | 2 +- ...sPage-D53o1bCm.js => DocsPage-AglHrVWY.js} | 12 +- .../dashboard/assets/FilterChips-C0emi5Kg.js | 1 + .../dashboard/assets/FilterChips-CTE3I1G3.js | 1 - ...sPage-fg3Rz_lV.js => KeysPage-CEc7g4XL.js} | 2 +- ...X_HiM.js => ModelScopeControl-BhMRwgM-.js} | 2 +- ...age-uwVSUUQm.js => ModelsPage-299cCHBM.js} | 2 +- ...e-0PkW5qfi.js => OverviewPage-CHysnnsw.js} | 2 +- ...-B4LYozbD.js => ProvidersPage-BPyKQR5x.js} | 2 +- ...ge-D1os8M2m.js => RoutingPage-2qgzgln4.js} | 2 +- ...e-C2Hp1OPt.js => SettingsPage-CLw9HtK0.js} | 2 +- ...EmYAlSB.js => TablePagination-BynkRKqB.js} | 2 +- ...KsV.js => ToolsGuardrailsPage-CSbQtPkh.js} | 2 +- .../dashboard/assets/UsagePage-BTnJt3lF.js | 1 + .../dashboard/assets/UsagePage-tyubYvXE.js | 1 - ...Page-Be1Tcz9b.js => UsersPage-C_yR1ElB.js} | 2 +- .../static/dashboard/assets/index-D-R1nuKP.js | 2 - .../static/dashboard/assets/index-Dit1BUBh.js | 2 + src/gateway/static/dashboard/index.html | 2 +- tests/integration/test_usage_summary.py | 114 +++++++++++++++++- web/src/api/hooks.ts | 14 ++- web/src/api/types.ts | 10 +- web/src/components/FilterChips.tsx | 6 +- web/src/components/ui.test.tsx | 66 +++++++++- web/src/components/ui.tsx | 71 +++++++++++ web/src/pages/ActivityPage.tsx | 10 +- web/src/pages/UsagePage.test.tsx | 69 ++++++++++- web/src/pages/UsagePage.tsx | 112 ++++++++++++----- 34 files changed, 611 insertions(+), 129 deletions(-) rename src/gateway/static/dashboard/assets/{ActivityPage-C28CtpkN.js => ActivityPage-BbEENQgu.js} (70%) rename src/gateway/static/dashboard/assets/{BudgetsPage-B9iEC7ec.js => BudgetsPage-DGkl3NSe.js} (98%) rename src/gateway/static/dashboard/assets/{ConfirmDialog-mbnZRETP.js => ConfirmDialog-Dt_8xaSM.js} (92%) rename src/gateway/static/dashboard/assets/{DocsPage-D53o1bCm.js => DocsPage-AglHrVWY.js} (99%) create mode 100644 src/gateway/static/dashboard/assets/FilterChips-C0emi5Kg.js delete mode 100644 src/gateway/static/dashboard/assets/FilterChips-CTE3I1G3.js rename src/gateway/static/dashboard/assets/{KeysPage-fg3Rz_lV.js => KeysPage-CEc7g4XL.js} (98%) rename src/gateway/static/dashboard/assets/{ModelScopeControl-BBYX_HiM.js => ModelScopeControl-BhMRwgM-.js} (98%) rename src/gateway/static/dashboard/assets/{ModelsPage-uwVSUUQm.js => ModelsPage-299cCHBM.js} (99%) rename src/gateway/static/dashboard/assets/{OverviewPage-0PkW5qfi.js => OverviewPage-CHysnnsw.js} (99%) rename src/gateway/static/dashboard/assets/{ProvidersPage-B4LYozbD.js => ProvidersPage-BPyKQR5x.js} (99%) rename src/gateway/static/dashboard/assets/{RoutingPage-D1os8M2m.js => RoutingPage-2qgzgln4.js} (99%) rename src/gateway/static/dashboard/assets/{SettingsPage-C2Hp1OPt.js => SettingsPage-CLw9HtK0.js} (99%) rename src/gateway/static/dashboard/assets/{TablePagination-BEmYAlSB.js => TablePagination-BynkRKqB.js} (98%) rename src/gateway/static/dashboard/assets/{ToolsGuardrailsPage-C-E4XKsV.js => ToolsGuardrailsPage-CSbQtPkh.js} (99%) create mode 100644 src/gateway/static/dashboard/assets/UsagePage-BTnJt3lF.js delete mode 100644 src/gateway/static/dashboard/assets/UsagePage-tyubYvXE.js rename src/gateway/static/dashboard/assets/{UsersPage-Be1Tcz9b.js => UsersPage-C_yR1ElB.js} (92%) delete mode 100644 src/gateway/static/dashboard/assets/index-D-R1nuKP.js create mode 100644 src/gateway/static/dashboard/assets/index-Dit1BUBh.js diff --git a/docs/dashboard.md b/docs/dashboard.md index 29256801b..ae69d2a4b 100644 --- a/docs/dashboard.md +++ b/docs/dashboard.md @@ -197,8 +197,14 @@ gateway. is never stored: the log records counts and names only. - **Usage**: aggregate usage and analytics, showing spend and volume over time, broken down by model and by user, plus a switchable breakdown by session, - endpoint, provider, or source. Clicking any row opens the Activity log scoped - to that group, so "spend went up" leads straight to the requests behind it. + endpoint, provider, or source. The **User**, **Model**, and **API key** pickers + each take several values, so a chart can compare a set ("these two models across + this team's keys") rather than one entity at a time; every pick becomes its own + chip, and the chip's ✕ removes just that value. Clicking any row opens the + Activity log scoped to that group, so "spend went up" leads straight to the + requests behind it. The Activity log filters one value per dimension, so a + picker holding several values is left off that drill-down; the chips there show + what was carried over. When the window contains gateway-run tool calls, a **Gateway-run tools** table shows calls, failures, and spend per tool, so "what did search cost me last week" has an answer that is not one request at a time. MCP tools are excluded diff --git a/docs/public/openapi.json b/docs/public/openapi.json index fb8c4b2fd..79eaa865d 100644 --- a/docs/public/openapi.json +++ b/docs/public/openapi.json @@ -11603,20 +11603,24 @@ } }, { - "description": "Filter to a single user", + "description": "Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.", "in": "query", "name": "user_id", "required": false, "schema": { "anyOf": [ { - "type": "string" + "items": { + "type": "string" + }, + "maxItems": 50, + "type": "array" }, { "type": "null" } ], - "description": "Filter to a single user", + "description": "Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.", "title": "User Id" } }, @@ -11657,20 +11661,24 @@ } }, { - "description": "Filter to a single model", + "description": "Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.", "in": "query", "name": "model", "required": false, "schema": { "anyOf": [ { - "type": "string" + "items": { + "type": "string" + }, + "maxItems": 50, + "type": "array" }, { "type": "null" } ], - "description": "Filter to a single model", + "description": "Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.", "title": "Model" } }, @@ -11747,20 +11755,24 @@ } }, { - "description": "Filter to a single API key id", + "description": "Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.", "in": "query", "name": "api_key_id", "required": false, "schema": { "anyOf": [ { - "type": "string" + "items": { + "type": "string" + }, + "maxItems": 50, + "type": "array" }, { "type": "null" } ], - "description": "Filter to a single API key id", + "description": "Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.", "title": "Api Key Id" } }, @@ -11928,7 +11940,7 @@ }, "/v1/usage/summary": { "get": { - "description": "Aggregate spend, tokens, and request volume for the dashboard Usage page.\n\nRange-bounded (default last 30 days, hard-capped): unlike the raw ``/v1/usage``\nlist, every aggregate is scoped to a bounded window so it stays served by the\ntimestamp index. Returns grand totals, breakdowns by model / user / API key /\nsource / session (``source_label``) / endpoint / provider (top rows plus a\nreconciling ``other`` fold, billed token counts), the error taxonomy grouped\nby failure status code, and a UTC-bucketed time series carrying each bucket's\nerror count and billed token composition (input incl. cache, cache read/write,\noutput).\n\nEach breakdown is its own ``GROUP BY`` pass, so a caller that reads only the\ntotals or the series should narrow ``dimensions`` rather than pay for all eight\n(the dashboard's tiles, timeline context, and model typeahead all do). Omitting\nthe parameter keeps the full set.", + "description": "Aggregate spend, tokens, and request volume for the dashboard Usage page.\n\nRange-bounded (default last 30 days, hard-capped): unlike the raw ``/v1/usage``\nlist, every aggregate is scoped to a bounded window so it stays served by the\ntimestamp index. Returns grand totals, breakdowns by model / user / API key /\nsource / session (``source_label``) / endpoint / provider (top rows plus a\nreconciling ``other`` fold, billed token counts), the error taxonomy grouped\nby failure status code, and a UTC-bucketed time series carrying each bucket's\nerror count and billed token composition (input incl. cache, cache read/write,\noutput).\n\nEach breakdown is its own ``GROUP BY`` pass, so a caller that reads only the\ntotals or the series should narrow ``dimensions`` rather than pay for all eight\n(the dashboard's tiles, timeline context, and model typeahead all do). Omitting\nthe parameter keeps the full set.\n\n``model``, ``user_id``, and ``api_key_id`` are repeatable: several values match\nany of them, so one chart can compare a handful of models, users, or keys.", "operationId": "usage_summary_v1_usage_summary_get", "parameters": [ { @@ -11970,20 +11982,24 @@ } }, { - "description": "Filter to a single user", + "description": "Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.", "in": "query", "name": "user_id", "required": false, "schema": { "anyOf": [ { - "type": "string" + "items": { + "type": "string" + }, + "maxItems": 50, + "type": "array" }, { "type": "null" } ], - "description": "Filter to a single user", + "description": "Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.", "title": "User Id" } }, @@ -12024,20 +12040,24 @@ } }, { - "description": "Filter to a single model", + "description": "Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.", "in": "query", "name": "model", "required": false, "schema": { "anyOf": [ { - "type": "string" + "items": { + "type": "string" + }, + "maxItems": 50, + "type": "array" }, { "type": "null" } ], - "description": "Filter to a single model", + "description": "Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.", "title": "Model" } }, @@ -12114,20 +12134,24 @@ } }, { - "description": "Filter to a single API key id", + "description": "Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.", "in": "query", "name": "api_key_id", "required": false, "schema": { "anyOf": [ { - "type": "string" + "items": { + "type": "string" + }, + "maxItems": 50, + "type": "array" }, { "type": "null" } ], - "description": "Filter to a single API key id", + "description": "Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.", "title": "Api Key Id" } }, @@ -12320,20 +12344,24 @@ } }, { - "description": "Filter to a single user", + "description": "Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.", "in": "query", "name": "user_id", "required": false, "schema": { "anyOf": [ { - "type": "string" + "items": { + "type": "string" + }, + "maxItems": 50, + "type": "array" }, { "type": "null" } ], - "description": "Filter to a single user", + "description": "Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.", "title": "User Id" } }, @@ -12374,20 +12402,24 @@ } }, { - "description": "Filter to a single model", + "description": "Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.", "in": "query", "name": "model", "required": false, "schema": { "anyOf": [ { - "type": "string" + "items": { + "type": "string" + }, + "maxItems": 50, + "type": "array" }, { "type": "null" } ], - "description": "Filter to a single model", + "description": "Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.", "title": "Model" } }, @@ -12464,20 +12496,24 @@ } }, { - "description": "Filter to a single API key id", + "description": "Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.", "in": "query", "name": "api_key_id", "required": false, "schema": { "anyOf": [ { - "type": "string" + "items": { + "type": "string" + }, + "maxItems": 50, + "type": "array" }, { "type": "null" } ], - "description": "Filter to a single API key id", + "description": "Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.", "title": "Api Key Id" } }, diff --git a/docs/public/otari.postman_collection.json b/docs/public/otari.postman_collection.json index 36514f38a..7bf06fe95 100644 --- a/docs/public/otari.postman_collection.json +++ b/docs/public/otari.postman_collection.json @@ -2650,7 +2650,7 @@ "value": "" }, { - "description": "Filter to a single user", + "description": "Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.", "disabled": true, "key": "user_id", "value": "" @@ -2668,7 +2668,7 @@ "value": "" }, { - "description": "Filter to a single model", + "description": "Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.", "disabled": true, "key": "model", "value": "" @@ -2698,7 +2698,7 @@ "value": "" }, { - "description": "Filter to a single API key id", + "description": "Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.", "disabled": true, "key": "api_key_id", "value": "" @@ -2768,7 +2768,7 @@ { "name": "Usage Summary", "request": { - "description": "Aggregate spend, tokens, and request volume for the dashboard Usage page.\n\nRange-bounded (default last 30 days, hard-capped): unlike the raw ``/v1/usage``\nlist, every aggregate is scoped to a bounded window so it stays served by the\ntimestamp index. Returns grand totals, breakdowns by model / user / API key /\nsource / session (``source_label``) / endpoint / provider (top rows plus a\nreconciling ``other`` fold, billed token counts), the error taxonomy grouped\nby failure status code, and a UTC-bucketed time series carrying each bucket's\nerror count and billed token composition (input incl. cache, cache read/write,\noutput).\n\nEach breakdown is its own ``GROUP BY`` pass, so a caller that reads only the\ntotals or the series should narrow ``dimensions`` rather than pay for all eight\n(the dashboard's tiles, timeline context, and model typeahead all do). Omitting\nthe parameter keeps the full set.", + "description": "Aggregate spend, tokens, and request volume for the dashboard Usage page.\n\nRange-bounded (default last 30 days, hard-capped): unlike the raw ``/v1/usage``\nlist, every aggregate is scoped to a bounded window so it stays served by the\ntimestamp index. Returns grand totals, breakdowns by model / user / API key /\nsource / session (``source_label``) / endpoint / provider (top rows plus a\nreconciling ``other`` fold, billed token counts), the error taxonomy grouped\nby failure status code, and a UTC-bucketed time series carrying each bucket's\nerror count and billed token composition (input incl. cache, cache read/write,\noutput).\n\nEach breakdown is its own ``GROUP BY`` pass, so a caller that reads only the\ntotals or the series should narrow ``dimensions`` rather than pay for all eight\n(the dashboard's tiles, timeline context, and model typeahead all do). Omitting\nthe parameter keeps the full set.\n\n``model``, ``user_id``, and ``api_key_id`` are repeatable: several values match\nany of them, so one chart can compare a handful of models, users, or keys.", "header": [], "method": "GET", "url": { @@ -2794,7 +2794,7 @@ "value": "" }, { - "description": "Filter to a single user", + "description": "Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.", "disabled": true, "key": "user_id", "value": "" @@ -2812,7 +2812,7 @@ "value": "" }, { - "description": "Filter to a single model", + "description": "Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.", "disabled": true, "key": "model", "value": "" @@ -2842,7 +2842,7 @@ "value": "" }, { - "description": "Filter to a single API key id", + "description": "Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.", "disabled": true, "key": "api_key_id", "value": "" @@ -2911,7 +2911,7 @@ "value": "" }, { - "description": "Filter to a single user", + "description": "Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.", "disabled": true, "key": "user_id", "value": "" @@ -2929,7 +2929,7 @@ "value": "" }, { - "description": "Filter to a single model", + "description": "Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.", "disabled": true, "key": "model", "value": "" @@ -2959,7 +2959,7 @@ "value": "" }, { - "description": "Filter to a single API key id", + "description": "Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.", "disabled": true, "key": "api_key_id", "value": "" diff --git a/src/gateway/api/routes/usage.py b/src/gateway/api/routes/usage.py index f713ac43f..ce539d2e2 100644 --- a/src/gateway/api/routes/usage.py +++ b/src/gateway/api/routes/usage.py @@ -66,6 +66,12 @@ # keep a caller from posting an unbounded IN list. _MAX_REQUEST_GROUPS = 1000 +# How many values one repeatable entity filter (model / user / API key) may carry +# on the analytics endpoints. Far above what a chart can distinguish (a stacked +# series folds past eight groups), so it never binds a real comparison; it is +# there to keep a caller from posting an unbounded IN list. +_MAX_FILTER_VALUES = 50 + Bucket = Literal["hour", "day"] SeriesGroupBy = Literal["model", "user_id", "api_key_id", "source"] @@ -241,6 +247,24 @@ class UsageCount(BaseModel): "Filter by budget participation: true = only enforced gateway rows, " "false = only imported rows that never touch a budget" ) +# The analytics endpoints take the three entity filters repeatably, so one chart +# can compare a handful of models / users / keys instead of one at a time. The raw +# list and count endpoints keep the single-value form: they back the request log, +# whose bulk delete / set-price selection is expressed as a single value per +# dimension, and widening them there would let a bulk op reach past the rows the +# operator was shown. +_USER_MULTI_DESC = ( + "Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of " + f"them. At most {_MAX_FILTER_VALUES} per call." +) +_MODEL_MULTI_DESC = ( + "Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. " + f"At most {_MAX_FILTER_VALUES} per call." +) +_API_KEY_MULTI_DESC = ( + "Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values " + f"match any of them. At most {_MAX_FILTER_VALUES} per call." +) _DIMENSIONS_DESC = ( "Which breakdowns to compute; repeatable (dimensions=model&dimensions=user). Each value names the " "'by_' response field it fills, except 'status_code', which fills the failure taxonomy in " @@ -250,18 +274,35 @@ class UsageCount(BaseModel): ) +def _match_any(column: Any, value: str | list[str]) -> ColumnElement[bool]: + """Match a column against one value or any of several. + + A single value stays an equality test so it uses the column's index the way a + one-value filter always did; several become an ``IN``. An empty list would + match nothing, so callers skip the condition entirely rather than emitting + ``IN ()``. + """ + if isinstance(value, str): + condition = column == value + elif len(value) == 1: + condition = column == value[0] + else: + condition = column.in_(value) + return cast("ColumnElement[bool]", condition) + + def _usage_filters( *, start_date: datetime | None, end_date: datetime | None, - user_id: str | None, + user_id: str | list[str] | None, status: str | None, - model: str | None, + model: str | list[str] | None, endpoint: str | None, provider: str | None = None, source: str | None = None, source_label: str | None = None, - api_key_id: str | None = None, + api_key_id: str | list[str] | None = None, priced: bool | None = None, tool: str | None = None, counts_toward_budget: bool | None = None, @@ -278,8 +319,8 @@ def _usage_filters( conditions.append(UsageLog.timestamp >= start_date) if end_date is not None: conditions.append(UsageLog.timestamp < end_date) - if user_id is not None: - conditions.append(UsageLog.user_id == user_id) + if user_id is not None and user_id != []: + conditions.append(_match_any(UsageLog.user_id, user_id)) if status is not None: conditions.append(UsageLog.status == status) if status_code is not None: @@ -293,8 +334,8 @@ def _usage_filters( # window. An explicit ``status`` wins, so the combination stays a # literal query rather than a silently contradictory one. conditions.append(UsageLog.status == "error") - if model is not None: - conditions.append(UsageLog.model == model) + if model is not None and model != []: + conditions.append(_match_any(UsageLog.model, model)) if endpoint is not None: conditions.append(UsageLog.endpoint == endpoint) if provider is not None: @@ -303,8 +344,8 @@ def _usage_filters( conditions.append(UsageLog.source == source) if source_label is not None: conditions.append(UsageLog.source_label == source_label) - if api_key_id is not None: - conditions.append(UsageLog.api_key_id == api_key_id) + if api_key_id is not None and api_key_id != []: + conditions.append(_match_any(UsageLog.api_key_id, api_key_id)) if request_group_id: # A one-id lookup stays an equality test so it uses the index the same way # a single-row fetch would; the IN form is for the dashboard's batched @@ -1087,14 +1128,14 @@ async def _summary_context( *, start_date: datetime | None, end_date: datetime | None, - user_id: str | None, + user_id: list[str] | None, status: str | None, - model: str | None, + model: list[str] | None, endpoint: str | None, provider: str | None = None, source: str | None = None, source_label: str | None = None, - api_key_id: str | None = None, + api_key_id: list[str] | None = None, priced: bool | None = None, tool: str | None = None, counts_toward_budget: bool | None = None, @@ -1167,15 +1208,19 @@ async def usage_summary( db: Annotated[AsyncSession, Depends(get_db)], start_date: datetime | None = Query(default=None, description=_START_DESC), end_date: datetime | None = Query(default=None, description=_END_DESC), - user_id: str | None = Query(default=None, description=_USER_DESC), + user_id: Annotated[ + list[str] | None, Query(max_length=_MAX_FILTER_VALUES, description=_USER_MULTI_DESC) + ] = None, status: str | None = Query(default=None, description=_STATUS_DESC), status_code: int | None = Query(default=None, description=_STATUS_CODE_DESC), - model: str | None = Query(default=None, description=_MODEL_DESC), + model: Annotated[list[str] | None, Query(max_length=_MAX_FILTER_VALUES, description=_MODEL_MULTI_DESC)] = None, endpoint: str | None = Query(default=None, description=_ENDPOINT_DESC), provider: str | None = Query(default=None, description=_PROVIDER_DESC), source: str | None = Query(default=None, description=_SOURCE_DESC), source_label: str | None = Query(default=None, description=_SOURCE_LABEL_DESC), - api_key_id: str | None = Query(default=None, description=_API_KEY_DESC), + api_key_id: Annotated[ + list[str] | None, Query(max_length=_MAX_FILTER_VALUES, description=_API_KEY_MULTI_DESC) + ] = None, priced: bool | None = Query(default=None, description=_PRICED_DESC), tool: ToolFilter | None = Query(default=None, description=_TOOL_DESC), counts_toward_budget: bool | None = Query(default=None, description=_COUNTS_DESC), @@ -1197,6 +1242,9 @@ async def usage_summary( totals or the series should narrow ``dimensions`` rather than pay for all eight (the dashboard's tiles, timeline context, and model typeahead all do). Omitting the parameter keeps the full set. + + ``model``, ``user_id``, and ``api_key_id`` are repeatable: several values match + any of them, so one chart can compare a handful of models, users, or keys. """ start, end, conditions, totals = await _summary_context( db, @@ -1298,15 +1346,19 @@ async def usage_series( group_by: SeriesGroupBy = Query(description="Dimension to split the series by"), start_date: datetime | None = Query(default=None, description=_START_DESC), end_date: datetime | None = Query(default=None, description=_END_DESC), - user_id: str | None = Query(default=None, description=_USER_DESC), + user_id: Annotated[ + list[str] | None, Query(max_length=_MAX_FILTER_VALUES, description=_USER_MULTI_DESC) + ] = None, status: str | None = Query(default=None, description=_STATUS_DESC), status_code: int | None = Query(default=None, description=_STATUS_CODE_DESC), - model: str | None = Query(default=None, description=_MODEL_DESC), + model: Annotated[list[str] | None, Query(max_length=_MAX_FILTER_VALUES, description=_MODEL_MULTI_DESC)] = None, endpoint: str | None = Query(default=None, description=_ENDPOINT_DESC), provider: str | None = Query(default=None, description=_PROVIDER_DESC), source: str | None = Query(default=None, description=_SOURCE_DESC), source_label: str | None = Query(default=None, description=_SOURCE_LABEL_DESC), - api_key_id: str | None = Query(default=None, description=_API_KEY_DESC), + api_key_id: Annotated[ + list[str] | None, Query(max_length=_MAX_FILTER_VALUES, description=_API_KEY_MULTI_DESC) + ] = None, priced: bool | None = Query(default=None, description=_PRICED_DESC), tool: ToolFilter | None = Query(default=None, description=_TOOL_DESC), counts_toward_budget: bool | None = Query(default=None, description=_COUNTS_DESC), @@ -1429,15 +1481,19 @@ async def usage_summary_csv( db: Annotated[AsyncSession, Depends(get_db)], start_date: datetime | None = Query(default=None, description=_START_DESC), end_date: datetime | None = Query(default=None, description=_END_DESC), - user_id: str | None = Query(default=None, description=_USER_DESC), + user_id: Annotated[ + list[str] | None, Query(max_length=_MAX_FILTER_VALUES, description=_USER_MULTI_DESC) + ] = None, status: str | None = Query(default=None, description=_STATUS_DESC), status_code: int | None = Query(default=None, description=_STATUS_CODE_DESC), - model: str | None = Query(default=None, description=_MODEL_DESC), + model: Annotated[list[str] | None, Query(max_length=_MAX_FILTER_VALUES, description=_MODEL_MULTI_DESC)] = None, endpoint: str | None = Query(default=None, description=_ENDPOINT_DESC), provider: str | None = Query(default=None, description=_PROVIDER_DESC), source: str | None = Query(default=None, description=_SOURCE_DESC), source_label: str | None = Query(default=None, description=_SOURCE_LABEL_DESC), - api_key_id: str | None = Query(default=None, description=_API_KEY_DESC), + api_key_id: Annotated[ + list[str] | None, Query(max_length=_MAX_FILTER_VALUES, description=_API_KEY_MULTI_DESC) + ] = None, priced: bool | None = Query(default=None, description=_PRICED_DESC), tool: ToolFilter | None = Query(default=None, description=_TOOL_DESC), counts_toward_budget: bool | None = Query(default=None, description=_COUNTS_DESC), diff --git a/src/gateway/static/dashboard/assets/ActivityPage-C28CtpkN.js b/src/gateway/static/dashboard/assets/ActivityPage-BbEENQgu.js similarity index 70% rename from src/gateway/static/dashboard/assets/ActivityPage-C28CtpkN.js rename to src/gateway/static/dashboard/assets/ActivityPage-BbEENQgu.js index d160779f2..55505e3ca 100644 --- a/src/gateway/static/dashboard/assets/ActivityPage-C28CtpkN.js +++ b/src/gateway/static/dashboard/assets/ActivityPage-BbEENQgu.js @@ -1 +1 @@ -import{j as t}from"./tanstack-query-1t81HyiD.js";import{r as x,u as At}from"./react-dgEcD0HR.js";import{f as Et,b as Ot,a as Dt,r as Ft,u as qt,c as Lt,d as Ut,e as Je,g as Pe,h as H,i as Bt,j as nt,A as pe,k as Kt,l as zt,m as Wt,P as Gt,E as Vt,C as lt,n as z,R as Yt,F as ue,o as Ie,p as it,q as Zt,Y as Ht}from"./index-D-R1nuKP.js";import{C as Xt,T as Jt}from"./charts-D6upG8fh.js";import{B as U,S as Qt}from"./heroui-DhloIxuc.js";import{u as es,r as ts,B as ss}from"./tableSelection-B1umVgqc.js";import{C as rs}from"./ConfirmDialog-mbnZRETP.js";import{D as as}from"./DataTable-BHrpJHmX.js";import{F as os}from"./FilterChips-CTE3I1G3.js";import{T as ns,S as Qe,P as ls}from"./TablePagination-BEmYAlSB.js";import"./recharts-EeW53z2i.js";import"./Field-GEMwIhf7.js";function et(e,r){const o=new Date(e);return Number.isNaN(o.getTime())?e:r==="hour"?o.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit",timeZone:"UTC"}):o.toLocaleDateString(void 0,{month:"short",day:"numeric",timeZone:"UTC"})}const is={key:"success",label:"Succeeded",color:"var(--otari-brand)"},cs={key:"errors",label:"Failed",color:"var(--otari-danger)"},ds={key:"requests",label:"Requests",color:"var(--otari-brand)"};function us({presets:e,extentKey:r,onPreset:o,onSelectRange:a,onSelectFull:i,series:d,bucket:l,windowStart:m,windowEnd:p,loading:g=!1,ariaLabel:u="Request volume over the selected window",action:N}){const b=d.map(n=>n.bucketStart),h=d.length,I=Et(m,p),C=d.some(n=>(n.errors??0)>0),w=C?[is,cs]:[ds],ae=d.map(n=>{const f=Math.min(n.errors??0,n.requests);return C?{x:n.bucketStart,success:n.requests-f,errors:f}:{x:n.bucketStart,requests:n.requests}}),X=h>0?Ot(b,m,p):{startIndex:0,endIndex:0},[F,W]=x.useState(null),_=x.useRef(F),G=n=>{_.current=n,W(n)},j=F??X,P=j.endIndex-j.startIndex+1,V=j.startIndex===0&&j.endIndex>=h-1,Y=h>0&&!V,R=(n,f)=>{if(h===0)return;const S=Math.max(0,Math.min(n,f)),O=Math.min(h-1,Math.max(n,f));if(S===0&&O===h-1){i();return}const D=Ft(b,S,O,l);D&&a(D.startIso,D.endIso)},v=e.findIndex(n=>n.key===r),y=v>=0?e[v].seconds:h*Dt(l)/1e3,q=v>=0?e[v+1]:e.find(n=>n.seconds===null||y!==null&&n.seconds>y),J=n=>{const f=Math.max(1,Math.min(h,Math.round(n))),S=(j.startIndex+j.endIndex+1)/2;let O=Math.round(S-f/2);O=Math.max(0,Math.min(h-f,O)),R(O,O+f-1)},A=()=>{if(V){q&&o(q);return}J(P*2)},B=()=>J(P/2),Q=x.useRef(null),E=x.useRef(null),Z=n=>{const f=Math.max(0,Math.min(h-P,n));return{startIndex:f,endIndex:f+P-1}},oe=n=>{const f=n.key==="ArrowRight"||n.key==="ArrowUp"?1:n.key==="ArrowLeft"||n.key==="ArrowDown"?-1:n.key==="PageUp"?P:n.key==="PageDown"?-P:n.key==="Home"?-h:n.key==="End"?h:0;if(f===0)return;n.preventDefault();const S=Z(j.startIndex+f);S.startIndex!==j.startIndex&&R(S.startIndex,S.endIndex)},ge=n=>{if(!E.current||!Q.current)return;const f=Q.current.getBoundingClientRect().width;if(f<=0)return;const S=Math.round((n.clientX-E.current.x)/f*h);G(Z(E.current.startIndex+S))},ee=n=>{n.currentTarget.hasPointerCapture(n.pointerId)&&n.currentTarget.releasePointerCapture(n.pointerId),E.current=null;const f=_.current;G(null),f&&f.startIndex!==X.startIndex&&R(f.startIndex,f.endIndex)},te=h?j.startIndex/h*100:0,ne=h?(j.endIndex+1)/h*100:100,L=Math.max(0,h-P);return t.jsxs("div",{className:"flex flex-col gap-2",children:[t.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.map(n=>t.jsx(U,{size:"sm",variant:r===n.key?"primary":"outline",onPress:()=>o(n),children:n.label},n.key)),t.jsxs("div",{className:"ml-auto flex items-center gap-3",children:[t.jsxs("span",{className:"text-xs text-[var(--otari-muted)]",children:["Showing ",I," · UTC"]}),N]})]}),t.jsxs("div",{className:"rounded-xl border border-[var(--otari-line)] bg-[var(--otari-surface)] p-2",children:[t.jsxs("div",{className:"flex items-center justify-between gap-2 px-1 pb-1",children:[t.jsxs("span",{className:"flex items-center gap-3",children:[t.jsxs("span",{className:"text-[11px] font-medium uppercase tracking-wide text-[var(--otari-muted)]",children:["Requests / ",l==="hour"?"hour":"day"]}),t.jsx(Xt,{series:w})]}),t.jsxs("div",{className:"flex items-center gap-1.5",children:[t.jsx("span",{className:"hidden text-[11px] text-[var(--otari-muted)] sm:inline",children:"drag across the chart to zoom"}),t.jsx(U,{size:"sm",variant:"ghost",isIconOnly:!0,"aria-label":"Zoom in",isDisabled:h===0,onPress:B,children:t.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-4 w-4","aria-hidden":"true",children:t.jsx("path",{d:"M12 5v14M5 12h14",strokeLinecap:"round"})})}),t.jsx(U,{size:"sm",variant:"ghost",isIconOnly:!0,"aria-label":"Zoom out",isDisabled:h===0||V&&!q,onPress:A,children:t.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-4 w-4","aria-hidden":"true",children:t.jsx("path",{d:"M5 12h14",strokeLinecap:"round"})})}),Y?t.jsx(U,{size:"sm",variant:"ghost",onPress:()=>R(0,h-1),children:"Reset"}):null]})]}),g&&h===0?t.jsx("div",{className:"flex h-[90px] items-center justify-center",children:t.jsx(Qt,{size:"sm"})}):h===0?t.jsx("div",{className:"flex h-[90px] items-center justify-center text-xs text-[var(--otari-muted)]",children:"No activity in this range."}):t.jsxs("div",{className:"flex flex-col gap-1",children:[t.jsx(Jt,{data:ae,series:w,formatValue:n=>n.toLocaleString(),formatXTick:n=>et(n,l),ariaLabel:u,height:90,onSelectRange:R,window:Y||F?j:null}),Y||F?t.jsx("div",{ref:Q,className:"relative h-2.5 w-full rounded-full bg-[var(--otari-bg)]",children:t.jsx("div",{role:"slider","aria-label":"Pan the selected window","aria-valuemin":0,"aria-valuemax":L,"aria-valuenow":Math.min(j.startIndex,L),"aria-valuetext":`Window starting at ${et(b[j.startIndex]??b[0],l)}`,tabIndex:0,className:"absolute inset-y-0 cursor-grab touch-none rounded-full bg-[var(--otari-brand)]/40 outline-none hover:bg-[var(--otari-brand)]/60 focus-visible:ring-2 focus-visible:ring-[var(--otari-brand)] active:cursor-grabbing",style:{left:`${te}%`,width:`${Math.max(2,ne-te)}%`},onKeyDown:oe,onPointerDown:n=>{n.preventDefault(),E.current={x:n.clientX,startIndex:j.startIndex},G({...j}),n.currentTarget.setPointerCapture(n.pointerId)},onPointerMove:ge,onPointerUp:ee,onPointerCancel:ee})}):null]})]})]})}function ms(e){const[r,o]=At(),a=x.useCallback(l=>r.get(l)??e[l],[r,e]),i=x.useCallback(l=>{const m=Number.parseInt(r.get(l)??"",10);if(!Number.isNaN(m))return m;const p=Number.parseInt(e[l],10);return Number.isNaN(p)?0:p},[r,e]),d=x.useCallback(l=>{o(m=>{const p=new URLSearchParams(m);for(const[g,u]of Object.entries(l)){const N=String(u);N===""||N===e[g]?p.delete(g):p.set(g,N)}return p},{replace:!0})},[o,e]);return{get:a,getNumber:i,patch:d}}const Re=new Intl.NumberFormat(void 0,{style:"currency",currency:"USD",maximumFractionDigits:4});function K(e){return e===null?"—":Re.format(e)}function $(e){return e===null?"—":e.toLocaleString()}const ps=new Intl.NumberFormat(void 0,{style:"currency",currency:"USD",maximumSignificantDigits:3});function hs(e){return e===0?Re.format(0):e<1e-4?ps.format(e):Re.format(e)}function xs(e){return[...e].sort((r,o)=>+("unit_rate"in r)-+("unit_rate"in o))}function $e(e){return e===null?"—":e<1e3?`${e} ms`:`${(e/1e3).toFixed(e<1e4?2:1)} s`}function fs(e){const r=new Date(e);return Number.isNaN(r.getTime())?e:r.toLocaleString()}function gs(e){const r=new Date(e).getTime();if(Number.isNaN(r))return e;const o=Math.max(0,Math.round((Date.now()-r)/1e3));if(o<60)return`${o}s ago`;const a=Math.round(o/60);if(a<60)return`${a}m ago`;const i=Math.round(a/60);return i<24?`${i}h ago`:`${Math.round(i/24)}d ago`}const bs=e=>e.id,vs=e=>{if(e.status==="error")return"bg-red-50";if(e.status==="absorbed")return"bg-amber-50"},tt=[{label:"All",value:""},{label:"Success",value:"success"},{label:"Error",value:"error"},{label:"Absorbed",value:"absorbed"}],st=[{label:"All",value:""},{label:"Priced",value:"true"},{label:"Unpriced",value:"false"}],rt=[{label:"All",value:""},{label:"Any tool",value:"any"},{label:"Web search",value:"web_search"},{label:"Code execution",value:"code_execution"}],_s=["tool"],ks=50,js=["model","source"],ys=["source"],ws={range:pe,start_date:"",end_date:"",status:"",model:"",user_id:"",api_key_id:"",priced:"",source:"",source_label:"",endpoint:"",provider:"",tool:"",page:"0",size:String(ks)};function me(e,r,o){if(r||o)return{start:r||void 0,end:o||void 0};if(e===lt)return{};const a=H(z,e)??H(z,pe),i=(a==null?void 0:a.seconds)??null;return{start:i==null?void 0:it(i),end:void 0}}function Me(e){const r=me(e,"","");if(r.start)return r;const o=H(z,e);return(o==null?void 0:o.seconds)==null?{start:it(Ht)}:r}function Ss({status:e}){const r=e==="error"?"border-red-200 bg-red-50 text-red-700":e==="absorbed"?"border-amber-200 bg-amber-50 text-amber-700":"border-[var(--otari-line)] bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]";return t.jsx("span",{className:`inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium ${r}`,children:e})}const Ns={gateway:"Gateway",claude_code:"Claude Code",codex:"Codex"};function Te(e){return Ns[e]??e}function he(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:0}function ct(e){const r=e.billing_meters??null,o=(g,u)=>r&&typeof r[g]=="number"?he(r[g]):he(u),a=o("total_input_tokens",e.prompt_tokens),i=o("cache_read_tokens",e.cache_read_tokens),d=o("cache_write_tokens",e.cache_write_tokens),l=o("completion_tokens",e.completion_tokens),m=Math.max(0,a-i-d),p=m+i+d+l;return p>0?{fresh:m,cacheRead:i,cacheWrite:d,output:l,total:p}:null}function xe(e){var o;const r=(o=e.billing_meters)==null?void 0:o.tools;return!r||typeof r!="object"?[]:Object.entries(r).flatMap(([a,i])=>{if(!i||typeof i!="object")return[];const d=i,l=he(d.billed),m=he(d.errors);if(!l&&!m)return[];const p=d.unit_rate;return[{tool:a,billed:l,errors:m,unitRate:typeof p=="number"?p:null}]}).sort((a,i)=>i.billed-a.billed||a.tool.localeCompare(i.tool))}function dt(e){const r=e.tool.replaceAll("_"," "),o=e.billed?[`${r} ×${e.billed}`]:[r];return e.errors&&o.push(`${e.errors} failed`),o.join(", ")}function at(e){const r=xe(e).filter(o=>o.unitRate!==null);return r.length?r.reduce((o,a)=>o+a.billed*(a.unitRate??0),0):null}const Cs=[{key:"fresh",label:"Fresh input",fill:"var(--otari-ink)"},{key:"cacheRead",label:"Cache read",fill:"var(--otari-brand)"},{key:"cacheWrite",label:"Cache write",fill:"var(--otari-brand-soft)"},{key:"output",label:"Output",fill:"var(--otari-brand-dark)"}];function Ps({entry:e}){const r=ct(e);if(r===null)return t.jsx("span",{className:"tabular-nums",children:$(e.total_tokens)});const o=Cs.map(l=>({...l,value:r[l.key]})),a=o.filter(l=>l.value>0).map(l=>`${l.label} ${l.value.toLocaleString()}`).join(", ");let i=0;const d=o.map(l=>{const m=l.value/r.total*100,p={...l,x:i,width:m};return i+=m,p});return t.jsxs("span",{className:"inline-flex flex-col items-end gap-1",title:a,children:[t.jsx("span",{className:"tabular-nums",children:r.total.toLocaleString()}),t.jsx("svg",{viewBox:"0 0 100 4",preserveAspectRatio:"none",role:"img","aria-label":`Token composition: ${a}`,className:"h-1.5 w-20 overflow-hidden rounded-full bg-[var(--otari-brand-tint)]",children:d.filter(l=>l.width>0).map(l=>t.jsx("rect",{x:l.x,y:0,width:l.width,height:4,fill:l.fill},l.key))})]})}function ut(e){if(!e)return null;if(e==="static")return"the policy's only target";if(e==="default")return"the policy's default target";if(e==="on_failure")return"a fallback candidate";if(e.startsWith("condition:")){const r=e.slice(10).split(",").filter(Boolean).join(", ");return r?`matched on ${r}`:"matched a condition"}if(e.startsWith("router:")){const r=e.slice(7);return r?`chosen by router ${r}`:"chosen by a router"}return e.replaceAll("_"," ")}function ot(e){const r=new Map;for(const o of e)!o.request_group_id||o.status==="absorbed"||r.set(o.request_group_id,{servedBy:o.status==="success"?fe(o):null,servedPosition:o.status==="success"?o.attempt_position??null:null});return r}function Is(e,r){const o=ut(e.selection_reason),a=e.attempt_position,i=e.attempt_count;if(a==null||i==null||i<=1)return o;const d=`attempt ${a} of ${i}`;return e.status==="absorbed"?r!=null&&r.servedBy?`${d} failed, served by ${r.servedBy}`:r?`${d} failed, and the request ended in an error`:`${d} failed, fell back`:e.status==="error"?a(r.attempt_position??0)-(o.attempt_position??0)||r.timestamp.localeCompare(o.timestamp))}function $s({entry:e}){const r=e.request_group_id,a=nt(r?[r]:[]),i=r?(a.data??[]).filter(u=>u.request_group_id===r):[],d=Ts(i.length?i:[e]),l=i.length>0,m=d.find(u=>u.status==="success"),p=e.attempt_count??d.length,g=l?m?`Served by attempt ${m.attempt_position??"?"} of ${p}: ${fe(m)}`:d.some(u=>u.status==="error")?"No candidate served this request.":"This request has no outcome row yet.":a.isError?"Could not load this request's other attempts.":e.request_group_id?"Loading the rest of this request's attempts…":"This row carries no request group, so its other attempts cannot be found.";return t.jsxs("div",{className:"flex flex-col gap-2",children:[t.jsxs("span",{className:"text-[11px] font-medium uppercase tracking-wide text-[var(--otari-muted)]",children:["Routing plan · ",e.policy_name]}),t.jsx("span",{className:"text-sm text-[var(--otari-ink)]",children:g}),t.jsx("div",{className:"overflow-x-auto rounded-lg border border-[var(--otari-line)]",children:t.jsxs("table",{className:"w-full text-xs","aria-label":`Routing plan for policy ${e.policy_name}`,children:[t.jsx("thead",{className:"text-[var(--otari-muted)]",children:t.jsxs("tr",{className:"border-b border-[var(--otari-line)]",children:[t.jsx("th",{scope:"col",className:"px-3 py-2 text-left font-medium",children:"#"}),t.jsx("th",{scope:"col",className:"px-3 py-2 text-left font-medium",children:"Target"}),t.jsx("th",{scope:"col",className:"px-3 py-2 text-left font-medium",children:"Selected as"}),t.jsx("th",{scope:"col",className:"px-3 py-2 text-left font-medium",children:"Outcome"}),t.jsx("th",{scope:"col",className:"px-3 py-2 text-right font-medium",children:"Total time"}),t.jsx("th",{scope:"col",className:"px-3 py-2 text-right font-medium",children:"Cost"})]})}),t.jsx("tbody",{children:d.map(u=>t.jsxs("tr",{className:`border-t border-[var(--otari-line)] first:border-t-0 ${u.status==="success"?"bg-[var(--otari-brand-tint)]":""}`,children:[t.jsx("td",{className:"px-3 py-2 tabular-nums",children:u.attempt_position??"?"}),t.jsxs("td",{className:"px-3 py-2 break-all text-[var(--otari-ink)]",children:[fe(u),u.id===e.id?t.jsx("span",{className:"ml-2 rounded-full border border-[var(--otari-line)] px-1.5 py-0.5 text-[10px] text-[var(--otari-muted)]",children:"this row"}):null]}),t.jsx("td",{className:"px-3 py-2",children:ut(u.selection_reason)??"—"}),t.jsx("td",{className:`px-3 py-2 ${u.status==="success"?"":"text-amber-700"}`,children:Rs(u)}),t.jsx("td",{className:"px-3 py-2 text-right tabular-nums",children:$e(u.latency_ms)}),t.jsx("td",{className:"px-3 py-2 text-right tabular-nums",children:K(u.cost)})]},u.id))})]})}),t.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Cost and tool charges settle on the attempt that served, so a failed attempt carries its tokens and no charge."})]})}function k({label:e,copyValue:r,copyLabel:o,children:a}){return t.jsxs("div",{className:"flex flex-col gap-0.5",children:[t.jsx("span",{className:"text-[11px] font-medium uppercase tracking-wide text-[var(--otari-muted)]",children:e}),r?t.jsx(Zt,{value:r,label:o??e.toLowerCase(),className:"text-sm text-[var(--otari-ink)] break-all",children:a}):t.jsx("span",{className:"text-sm text-[var(--otari-ink)] break-all",children:a})]})}function fe(e){return e.provider?e.model.startsWith(`${e.provider}:`)?e.model:`${e.provider}:${e.model}`:e.model}function As({entry:e,onPriceModel:r}){var i,d;const o=e.cost===null,a=fe(e);return t.jsxs("div",{className:"flex flex-col gap-4 px-4 py-4",children:[e.error_message?t.jsxs("div",{className:"flex flex-col gap-1.5",children:[t.jsxs("span",{className:"text-[11px] font-medium uppercase tracking-wide text-[var(--otari-muted)]",children:["Error",e.status_code!==null?` (${e.status_code})`:""]}),t.jsx("pre",{className:"max-h-48 overflow-auto rounded-lg border border-red-200 bg-red-50 p-3 text-xs whitespace-pre-wrap break-all text-red-700",children:e.error_message})]}):null,e.policy_name!==null&&e.policy_name!==void 0?t.jsx($s,{entry:e}):null,t.jsxs("div",{className:"grid gap-4 sm:grid-cols-2 lg:grid-cols-4",children:[t.jsx(k,{label:"Provider",children:e.provider??"—"}),t.jsx(k,{label:"Endpoint",children:e.endpoint}),t.jsx(k,{label:"Source",children:Te(e.source)}),e.source_label?t.jsx(k,{label:"Session",children:e.source_label}):null,t.jsx(k,{label:"User",copyValue:e.user_id,copyLabel:"user id",children:e.user_id??"—"}),t.jsx(k,{label:"API key",copyValue:e.api_key_id,copyLabel:"api key id",children:e.api_key_id??"—"}),t.jsx(k,{label:"Prompt tokens",children:$(e.prompt_tokens)}),t.jsx(k,{label:"Completion tokens",children:$(e.completion_tokens)}),t.jsx(k,{label:"Total tokens",children:$(e.total_tokens)}),t.jsx(k,{label:"Billed tokens",children:t.jsx("span",{title:"Fresh input, cache reads and writes, and output: the tokens this request was priced on, and the total the activity row's bar splits.",children:$(((i=ct(e))==null?void 0:i.total)??null)})}),t.jsx(k,{label:"Cost",children:K(e.cost)}),xe(e).length?t.jsxs(t.Fragment,{children:[t.jsx(k,{label:"Tools",children:xe(e).map(dt).join(" · ")}),t.jsx(k,{label:"Tool cost",children:at(e)===null?t.jsx("span",{className:"text-[var(--otari-warning-ink,var(--otari-muted))]",title:"No per-request price is configured for this tool, so its calls were recorded at zero cost. Set one on the Tools & Guardrails screen.",children:"unpriced"}):K(at(e))})]}):null,t.jsx(k,{label:"Cache read tokens",children:$(e.cache_read_tokens)}),t.jsx(k,{label:"Cache write tokens",children:$(e.cache_write_tokens)}),t.jsx(k,{label:"1h cache writes",children:$(e.cache_write_1h_tokens??null)}),t.jsx(k,{label:"Total time",children:$e(e.latency_ms)}),t.jsx(k,{label:"Request ID",copyValue:e.id,children:e.id})]}),o?t.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[t.jsx(U,{size:"sm",variant:"outline",onPress:()=>r(a),children:"Price this model"}),t.jsxs("span",{className:"text-xs text-[var(--otari-muted)]",children:["This request carries no cost. Set a price for ",t.jsx("code",{className:"break-all",children:a})," so later requests are metered and count against budgets. Rows already logged keep the cost they were served with."]})]}):null,(d=e.pricing_breakdown)!=null&&d.length?t.jsxs("div",{className:"flex flex-col gap-2",children:[t.jsx("span",{className:"text-[11px] font-medium uppercase tracking-wide text-[var(--otari-muted)]",children:"Billed meters"}),t.jsx("div",{className:"grid gap-2 sm:grid-cols-2 lg:grid-cols-3",children:xs(e.pricing_breakdown).map(l=>t.jsx(k,{label:l.meter.replaceAll("_"," "),children:"unit_rate"in l?`${$(l.units)} at ${hs(l.unit_rate)} each, ${K(l.cost)}`:`${$(l.units)} at ${K(l.rate_per_million)} / 1M, ${K(l.cost)}`},l.meter))})]}):null]})}function Zs(){var Be,Ke,ze,We,Ge,Ve,Ye,Ze;const e=qt(),r=Lt(),o=x.useMemo(()=>{const s=new Map;for(const c of r.data??[])s.set(c.id,c.key_name??`${c.id.slice(0,8)}…`);return s},[r.data]),a=ms(ws),i=a.get("range"),d=a.get("start_date"),l=a.get("end_date"),m=a.get("status"),p=a.get("model"),g=a.get("user_id"),u=a.get("api_key_id"),N=a.get("priced"),b=a.get("source"),h=a.get("source_label"),I=a.get("endpoint"),C=a.get("provider"),w=a.get("tool"),ae=Math.max(0,a.getNumber("page")),X=a.getNumber("size"),F=ls.reduce((s,c)=>Math.abs(c-X)me(i,d,l)),j=x.useRef(W);x.useEffect(()=>{j.current!==W&&(j.current=W,G(me(i,d,l)))},[W,i,d,l]);const[P,V]=x.useState(()=>Me(i)),Y=x.useRef(i);x.useEffect(()=>{Y.current!==i&&(Y.current=i,V(Me(i)))},[i]);const R=N==="true"?!0:N==="false"?!1:void 0,v=x.useMemo(()=>({start_date:_.start,end_date:_.end,status:m||void 0,model:p.trim()||void 0,user_id:g||void 0,api_key_id:u||void 0,source:b||void 0,source_label:h||void 0,endpoint:I||void 0,provider:C||void 0,tool:w||void 0,priced:R}),[_,w,m,p,g,u,b,h,I,C,R]),y=es(),q=JSON.stringify(v),J=x.useRef(q);x.useEffect(()=>{J.current!==q&&(J.current=q,a.patch({page:0}),y.clear())},[q,a,y]);const A=Ut(v,ae,F),B=Je(v),Q=x.useMemo(()=>({start_date:_.start,end_date:_.end,status:m||void 0,user_id:g||void 0,api_key_id:u||void 0,source:b||void 0,source_label:h||void 0,endpoint:I||void 0,provider:C||void 0,tool:w||void 0}),[_,m,g,u,b,h,I,C,w]),E=Pe(Q,"day",js),Z=((Ke=(Be=E.data)==null?void 0:Be.by_model)==null?void 0:Ke.filter(s=>!s.is_other&&s.key!==null).map(s=>s.key))??[],oe=(r.data??[]).map(s=>({value:s.id,label:s.key_name??`${s.id.slice(0,8)}…`})),ge=x.useMemo(()=>({start_date:_.start,end_date:_.end,status:m||void 0,model:p.trim()||void 0,user_id:g||void 0,api_key_id:u||void 0}),[_,m,p,g,u]),ee=Pe(ge,"day",ys,!!b),te=(ze=b?ee.data:E.data)==null?void 0:ze.by_source,ne=x.useMemo(()=>{const s=(te??[]).filter(c=>!c.is_other&&c.key!==null).map(c=>c.key);return b&&!s.includes(b)?[b,...s]:s},[te,b]),L=H(z,i)??H(z,pe),n=!!(_.start&&P.start&&new Date(_.start).getTime()({start_date:n?_.start:P.start,end_date:n?_.end:void 0,status:m||void 0,model:p.trim()||void 0,user_id:g||void 0,api_key_id:u||void 0,source:b||void 0,source_label:h||void 0,endpoint:I||void 0,provider:C||void 0,tool:w||void 0,priced:R}),[n,w,_,P,m,p,g,u,b,h,I,C,R]),D=Pe(O,S,_s),mt=(((We=D.data)==null?void 0:We.series)??[]).map(s=>({bucketStart:s.bucket_start,requests:s.requests,errors:s.errors??0})),T=A.data??[],{pageOutcomes:be,unresolvedGroupIds:pt}=x.useMemo(()=>{const s=ot(T),c=new Set;for(const M of T)M.status==="absorbed"&&M.request_group_id&&!s.has(M.request_group_id)&&c.add(M.request_group_id);return{pageOutcomes:s,unresolvedGroupIds:[...c]}},[T]),ve=nt(pt),Ae=x.useMemo(()=>{var s;return(s=ve.data)!=null&&s.length?new Map([...be,...ot(ve.data)]):be},[be,ve.data]),ht=B.isSuccess&&!B.isPlaceholderData?((Ge=B.data)==null?void 0:Ge.total)??0:null,_e=H(z,i),xt=!!(d||l)||i!==pe&&(_e==null?void 0:_e.seconds)!=null,ft=!!(m||p.trim()||g||u||N||b||h||I||C||w||xt),se=(s,c)=>{var M;return((M=s.find(de=>de.value===c))==null?void 0:M.label)??c},Ee=(e.data??[]).map(s=>({value:s.user_id,label:s.alias?`${s.alias} (${s.user_id})`:s.user_id})),gt=()=>a.patch({status:"",priced:"",model:"",user_id:"",api_key_id:"",source:"",source_label:"",endpoint:"",provider:"",tool:""}),bt=[...m?[{key:"status",label:"Status",value:se(tt,m),onClear:()=>a.patch({status:""})}]:[],...N?[{key:"priced",label:"Priced",value:se(st,N),onClear:()=>a.patch({priced:""})}]:[],...g?[{key:"user",label:"User",value:se(Ee,g),onClear:()=>a.patch({user_id:""})}]:[],...p.trim()?[{key:"model",label:"Model",value:p.trim(),onClear:()=>a.patch({model:""})}]:[],...u?[{key:"key",label:"API key",value:se(oe,u),onClear:()=>a.patch({api_key_id:""})}]:[],...b?[{key:"source",label:"Source",value:Te(b),onClear:()=>a.patch({source:""})}]:[],...h?[{key:"session",label:"Session",value:h,onClear:()=>a.patch({source_label:""})}]:[],...I?[{key:"endpoint",label:"Endpoint",value:I,onClear:()=>a.patch({endpoint:""})}]:[],...C?[{key:"provider",label:"Provider",value:C,onClear:()=>a.patch({provider:""})}]:[],...w?[{key:"tool",label:"Tool",value:se(rt,w),onClear:()=>a.patch({tool:""})}]:[]],le=x.useMemo(()=>T.filter(s=>!s.counts_toward_budget).map(s=>s.id),[T]),vt=x.useMemo(()=>T.filter(s=>s.counts_toward_budget).map(s=>s.id),[T]),Oe=ts(y.selectedKeys,le),re=Oe.length,De=y.allMatching||re>0,_t=x.useMemo(()=>({...v,counts_toward_budget:!1}),[v]),Fe=Je(_t,De),ie=Fe.isSuccess?((Ve=Fe.data)==null?void 0:Ve.total)??null:null,kt=le.length>0&&re===le.length&&ie!=null&&ie>re,ce=y.allMatching?ie??re:re,jt=le.length>0||y.allMatching,ke=Kt(),je=zt(),ye=Wt(),[yt,we]=x.useState(!1),[wt,Se]=x.useState(!1),[Ne,Ce]=x.useState(null),[St,qe]=x.useState(null),Nt=x.useCallback(s=>t.jsxs("div",{children:[t.jsxs("div",{className:"flex items-center justify-between border-b border-[var(--otari-line)] px-4 py-2",children:[t.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Request detail"}),t.jsx(U,{size:"sm",variant:"ghost",onPress:()=>qe(null),children:"Close"})]}),t.jsx(As,{entry:s,onPriceModel:Ce})]}),[]),Le=()=>y.allMatching?{by_filter:!0,model:v.model,user_id:v.user_id,api_key_id:v.api_key_id,status:v.status,source:v.source,source_label:v.source_label,endpoint:v.endpoint,provider:v.provider,tool:v.tool,start_date:v.start_date,end_date:v.end_date,priced:v.priced}:{ids:Oe},Ct=()=>{ke.mutate(Le(),{onSuccess:()=>{we(!1),y.clear()}})},Pt=(s,c)=>{ye.mutate({model_key:c,input_price_per_million:s.input_price_per_million,output_price_per_million:s.output_price_per_million,cache_read_price_per_million:s.cache_read_price_per_million??null,cache_write_price_per_million:s.cache_write_price_per_million??null},{onSuccess:()=>Ce(null)})},It=s=>{je.mutate({...Le(),...s},{onSuccess:()=>{Se(!1),y.clear()}})},Mt=()=>{A.refetch(),B.refetch(),D.refetch(),E.refetch(),b&&ee.refetch()},Ue=s=>{if(s.key===i&&!d&&!l){G(me(s.key,"","")),V(Me(s.key));return}a.patch({range:s.key,start_date:"",end_date:""})},Rt=(s,c)=>a.patch({start_date:s,end_date:c}),Tt=x.useMemo(()=>{const s=c=>c===null?"—":o.get(c)??`${c.slice(0,8)}…`;return[{id:"time",header:"Time",cell:c=>t.jsx("span",{title:fs(c.timestamp),className:"text-[var(--otari-muted)]",children:gs(c.timestamp)})},{id:"user",header:"User",cell:c=>c.user_id??"—"},{id:"model",header:"Model",isRowHeader:!0,cell:c=>{const M=xe(c);if(!M.length)return c.model;const de=M.reduce(($t,Xe)=>$t+Xe.billed+Xe.errors,0),He=M.map(dt).join(" · ");return t.jsxs("span",{className:"inline-flex items-center gap-1.5",children:[c.model,t.jsxs("span",{className:"inline-flex items-center rounded-full border border-[var(--otari-line)] bg-[var(--otari-brand-tint)] px-1.5 py-0.5 text-[11px] font-medium text-[var(--otari-brand-dark)]",title:He,"aria-label":`Gateway tools: ${He}`,children:[de," ",de===1?"tool":"tools"]})]})}},{id:"routing",header:"Routing",cell:c=>t.jsx(Ms,{entry:c,outcome:Ae.get(c.request_group_id??"")??null})},{id:"api_key",header:"API key",cell:c=>t.jsx("span",{className:"text-[var(--otari-muted)]",children:s(c.api_key_id)})},{id:"tokens",header:"Tokens",align:"end",cell:c=>t.jsx(Ps,{entry:c})},{id:"cost",header:"Cost",align:"end",cell:c=>K(c.cost)},{id:"latency",header:"Total time",align:"end",cell:c=>$e(c.latency_ms)},{id:"status",header:"Status",cell:c=>t.jsx(Ss,{status:c.status})}]},[o,Ae]);return t.jsxs("div",{className:"flex flex-col gap-6",children:[t.jsx(Gt,{title:"Activity",description:"A per-request log of what the gateway served: tokens, cost, latency, and failures. No request or response content is stored."}),t.jsx(Vt,{error:A.error??B.error??D.error}),t.jsxs("div",{className:"flex flex-col gap-3",children:[t.jsx(us,{presets:z,extentKey:f,onPreset:Ue,onSelectRange:Rt,onSelectFull:()=>L?Ue(L):void 0,series:mt,bucket:S,windowStart:_.start,windowEnd:_.end,loading:D.isLoading,ariaLabel:"Activity request volume over the selected window",action:t.jsx(Yt,{onRefresh:Mt,isFetching:A.isFetching,updatedAt:A.dataUpdatedAt})}),t.jsxs(os,{chips:bt,onClearAll:gt,children:[t.jsx(ue,{id:"filter-status",label:"Status",value:m,onChange:s=>a.patch({status:s}),children:tt.map(s=>t.jsx("option",{value:s.value,children:s.label},s.value))}),t.jsx(ue,{id:"filter-priced",label:"Priced?",value:N,onChange:s=>a.patch({priced:s}),children:st.map(s=>t.jsx("option",{value:s.value,children:s.label},s.value))}),w||(Ze=(Ye=D.data)==null?void 0:Ye.by_tool)!=null&&Ze.length?t.jsx(ue,{id:"filter-tool",label:"Tool",value:w,onChange:s=>a.patch({tool:s}),children:rt.map(s=>t.jsx("option",{value:s.value,children:s.label},s.value))}):null,ne.length>1||b?t.jsxs(ue,{id:"filter-source",label:"Source",value:b,onChange:s=>a.patch({source:s}),children:[t.jsx("option",{value:"",children:"All"}),ne.map(s=>t.jsx("option",{value:s,children:Te(s)},s))]}):null,t.jsx(Ie,{label:"API key",value:u,onChange:s=>a.patch({api_key_id:s}),placeholder:"All keys",options:oe}),t.jsx(Ie,{label:"User",value:g,onChange:s=>a.patch({user_id:s}),placeholder:"All users",options:Ee}),t.jsx(Ie,{label:"Model",value:p,onChange:s=>a.patch({model:s}),allowsCustom:!0,placeholder:"Any model",options:(p&&!Z.includes(p)?[p,...Z]:Z).map(s=>({value:s,label:s}))})]})]}),De?t.jsxs(ss,{selectedCount:ce,allMatching:y.allMatching,matchingTotal:ie,canSelectAllMatching:kt,onSelectAllMatching:y.enableAllMatching,onClear:y.clear,children:[t.jsx(U,{size:"sm",variant:"primary",onPress:()=>Se(!0),children:"Set price"}),t.jsx(U,{size:"sm",variant:"danger",onPress:()=>we(!0),children:"Delete"})]}):null,t.jsx(as,{ariaLabel:"Activity log",columns:Tt,rows:T,getRowKey:bs,isLoading:A.isLoading,emptyContent:ft?"No requests match these filters.":"No requests recorded yet.",selectionMode:jt?"multiple":"none",selectedKeys:y.selectedKeys,onSelectionChange:y.onSelectionChange,disabledKeys:vt,onRowAction:s=>qe(c=>c===s?null:s),rowClassName:vs,detailKey:St,renderDetail:Nt}),t.jsx(ns,{page:ae,pageSize:F,total:ht,rowsOnPage:T.length,onPageChange:s=>a.patch({page:s}),onPageSizeChange:s=>a.patch({size:s,page:0}),isFetching:A.isFetching,hasNextFallback:T.length===F}),t.jsx(rs,{isOpen:yt,onOpenChange:we,heading:"Delete usage rows",body:`Delete ${ce.toLocaleString()} imported ${ce===1?"row":"rows"}? Only imported rows are removed, and this cannot be undone.`,confirmLabel:"Delete",isPending:ke.isPending,error:ke.error,onConfirm:Ct}),t.jsx(Qe,{isOpen:wt,onOpenChange:Se,targetCount:ce,isPending:je.isPending,error:je.error,onSubmit:It}),t.jsx(Qe,{isOpen:Ne!==null,onOpenChange:s=>Ce(s?Ne??"":null),isPending:ye.isPending,error:ye.error,onSubmit:Pt,collectModelKey:!0,initialModelKey:Ne??"",title:"Price this model",description:()=>"Set what this model costs, taken from the request you were looking at. Requests from now on are costed at these rates and counted against budgets; rows already logged keep the cost they were served with."})]})}export{Zs as ActivityPage}; +import{j as t}from"./tanstack-query-1t81HyiD.js";import{r as x,u as At}from"./react-dgEcD0HR.js";import{f as Et,b as Ot,a as Dt,r as Ft,u as qt,c as Lt,d as Ut,e as Je,g as Pe,h as H,i as Bt,j as nt,A as pe,k as Kt,l as zt,m as Wt,P as Gt,E as Vt,C as lt,n as z,R as Yt,F as ue,o as Ie,p as it,q as Zt,Y as Ht}from"./index-Dit1BUBh.js";import{C as Xt,T as Jt}from"./charts-D6upG8fh.js";import{B as U,S as Qt}from"./heroui-DhloIxuc.js";import{u as es,r as ts,B as ss}from"./tableSelection-B1umVgqc.js";import{C as rs}from"./ConfirmDialog-Dt_8xaSM.js";import{D as as}from"./DataTable-BHrpJHmX.js";import{F as os}from"./FilterChips-C0emi5Kg.js";import{T as ns,S as Qe,P as ls}from"./TablePagination-BynkRKqB.js";import"./recharts-EeW53z2i.js";import"./Field-GEMwIhf7.js";function et(e,r){const o=new Date(e);return Number.isNaN(o.getTime())?e:r==="hour"?o.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit",timeZone:"UTC"}):o.toLocaleDateString(void 0,{month:"short",day:"numeric",timeZone:"UTC"})}const is={key:"success",label:"Succeeded",color:"var(--otari-brand)"},cs={key:"errors",label:"Failed",color:"var(--otari-danger)"},ds={key:"requests",label:"Requests",color:"var(--otari-brand)"};function us({presets:e,extentKey:r,onPreset:o,onSelectRange:a,onSelectFull:i,series:d,bucket:l,windowStart:p,windowEnd:m,loading:g=!1,ariaLabel:u="Request volume over the selected window",action:N}){const b=d.map(n=>n.bucketStart),h=d.length,I=Et(p,m),C=d.some(n=>(n.errors??0)>0),w=C?[is,cs]:[ds],ae=d.map(n=>{const f=Math.min(n.errors??0,n.requests);return C?{x:n.bucketStart,success:n.requests-f,errors:f}:{x:n.bucketStart,requests:n.requests}}),X=h>0?Ot(b,p,m):{startIndex:0,endIndex:0},[F,W]=x.useState(null),v=x.useRef(F),G=n=>{v.current=n,W(n)},j=F??X,P=j.endIndex-j.startIndex+1,V=j.startIndex===0&&j.endIndex>=h-1,Y=h>0&&!V,R=(n,f)=>{if(h===0)return;const S=Math.max(0,Math.min(n,f)),O=Math.min(h-1,Math.max(n,f));if(S===0&&O===h-1){i();return}const D=Ft(b,S,O,l);D&&a(D.startIso,D.endIso)},_=e.findIndex(n=>n.key===r),y=_>=0?e[_].seconds:h*Dt(l)/1e3,q=_>=0?e[_+1]:e.find(n=>n.seconds===null||y!==null&&n.seconds>y),J=n=>{const f=Math.max(1,Math.min(h,Math.round(n))),S=(j.startIndex+j.endIndex+1)/2;let O=Math.round(S-f/2);O=Math.max(0,Math.min(h-f,O)),R(O,O+f-1)},A=()=>{if(V){q&&o(q);return}J(P*2)},B=()=>J(P/2),Q=x.useRef(null),E=x.useRef(null),Z=n=>{const f=Math.max(0,Math.min(h-P,n));return{startIndex:f,endIndex:f+P-1}},oe=n=>{const f=n.key==="ArrowRight"||n.key==="ArrowUp"?1:n.key==="ArrowLeft"||n.key==="ArrowDown"?-1:n.key==="PageUp"?P:n.key==="PageDown"?-P:n.key==="Home"?-h:n.key==="End"?h:0;if(f===0)return;n.preventDefault();const S=Z(j.startIndex+f);S.startIndex!==j.startIndex&&R(S.startIndex,S.endIndex)},ge=n=>{if(!E.current||!Q.current)return;const f=Q.current.getBoundingClientRect().width;if(f<=0)return;const S=Math.round((n.clientX-E.current.x)/f*h);G(Z(E.current.startIndex+S))},ee=n=>{n.currentTarget.hasPointerCapture(n.pointerId)&&n.currentTarget.releasePointerCapture(n.pointerId),E.current=null;const f=v.current;G(null),f&&f.startIndex!==X.startIndex&&R(f.startIndex,f.endIndex)},te=h?j.startIndex/h*100:0,ne=h?(j.endIndex+1)/h*100:100,L=Math.max(0,h-P);return t.jsxs("div",{className:"flex flex-col gap-2",children:[t.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.map(n=>t.jsx(U,{size:"sm",variant:r===n.key?"primary":"outline",onPress:()=>o(n),children:n.label},n.key)),t.jsxs("div",{className:"ml-auto flex items-center gap-3",children:[t.jsxs("span",{className:"text-xs text-[var(--otari-muted)]",children:["Showing ",I," · UTC"]}),N]})]}),t.jsxs("div",{className:"rounded-xl border border-[var(--otari-line)] bg-[var(--otari-surface)] p-2",children:[t.jsxs("div",{className:"flex items-center justify-between gap-2 px-1 pb-1",children:[t.jsxs("span",{className:"flex items-center gap-3",children:[t.jsxs("span",{className:"text-[11px] font-medium uppercase tracking-wide text-[var(--otari-muted)]",children:["Requests / ",l==="hour"?"hour":"day"]}),t.jsx(Xt,{series:w})]}),t.jsxs("div",{className:"flex items-center gap-1.5",children:[t.jsx("span",{className:"hidden text-[11px] text-[var(--otari-muted)] sm:inline",children:"drag across the chart to zoom"}),t.jsx(U,{size:"sm",variant:"ghost",isIconOnly:!0,"aria-label":"Zoom in",isDisabled:h===0,onPress:B,children:t.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-4 w-4","aria-hidden":"true",children:t.jsx("path",{d:"M12 5v14M5 12h14",strokeLinecap:"round"})})}),t.jsx(U,{size:"sm",variant:"ghost",isIconOnly:!0,"aria-label":"Zoom out",isDisabled:h===0||V&&!q,onPress:A,children:t.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-4 w-4","aria-hidden":"true",children:t.jsx("path",{d:"M5 12h14",strokeLinecap:"round"})})}),Y?t.jsx(U,{size:"sm",variant:"ghost",onPress:()=>R(0,h-1),children:"Reset"}):null]})]}),g&&h===0?t.jsx("div",{className:"flex h-[90px] items-center justify-center",children:t.jsx(Qt,{size:"sm"})}):h===0?t.jsx("div",{className:"flex h-[90px] items-center justify-center text-xs text-[var(--otari-muted)]",children:"No activity in this range."}):t.jsxs("div",{className:"flex flex-col gap-1",children:[t.jsx(Jt,{data:ae,series:w,formatValue:n=>n.toLocaleString(),formatXTick:n=>et(n,l),ariaLabel:u,height:90,onSelectRange:R,window:Y||F?j:null}),Y||F?t.jsx("div",{ref:Q,className:"relative h-2.5 w-full rounded-full bg-[var(--otari-bg)]",children:t.jsx("div",{role:"slider","aria-label":"Pan the selected window","aria-valuemin":0,"aria-valuemax":L,"aria-valuenow":Math.min(j.startIndex,L),"aria-valuetext":`Window starting at ${et(b[j.startIndex]??b[0],l)}`,tabIndex:0,className:"absolute inset-y-0 cursor-grab touch-none rounded-full bg-[var(--otari-brand)]/40 outline-none hover:bg-[var(--otari-brand)]/60 focus-visible:ring-2 focus-visible:ring-[var(--otari-brand)] active:cursor-grabbing",style:{left:`${te}%`,width:`${Math.max(2,ne-te)}%`},onKeyDown:oe,onPointerDown:n=>{n.preventDefault(),E.current={x:n.clientX,startIndex:j.startIndex},G({...j}),n.currentTarget.setPointerCapture(n.pointerId)},onPointerMove:ge,onPointerUp:ee,onPointerCancel:ee})}):null]})]})]})}function ms(e){const[r,o]=At(),a=x.useCallback(l=>r.get(l)??e[l],[r,e]),i=x.useCallback(l=>{const p=Number.parseInt(r.get(l)??"",10);if(!Number.isNaN(p))return p;const m=Number.parseInt(e[l],10);return Number.isNaN(m)?0:m},[r,e]),d=x.useCallback(l=>{o(p=>{const m=new URLSearchParams(p);for(const[g,u]of Object.entries(l)){const N=String(u);N===""||N===e[g]?m.delete(g):m.set(g,N)}return m},{replace:!0})},[o,e]);return{get:a,getNumber:i,patch:d}}const Re=new Intl.NumberFormat(void 0,{style:"currency",currency:"USD",maximumFractionDigits:4});function K(e){return e===null?"—":Re.format(e)}function $(e){return e===null?"—":e.toLocaleString()}const ps=new Intl.NumberFormat(void 0,{style:"currency",currency:"USD",maximumSignificantDigits:3});function hs(e){return e===0?Re.format(0):e<1e-4?ps.format(e):Re.format(e)}function xs(e){return[...e].sort((r,o)=>+("unit_rate"in r)-+("unit_rate"in o))}function $e(e){return e===null?"—":e<1e3?`${e} ms`:`${(e/1e3).toFixed(e<1e4?2:1)} s`}function fs(e){const r=new Date(e);return Number.isNaN(r.getTime())?e:r.toLocaleString()}function gs(e){const r=new Date(e).getTime();if(Number.isNaN(r))return e;const o=Math.max(0,Math.round((Date.now()-r)/1e3));if(o<60)return`${o}s ago`;const a=Math.round(o/60);if(a<60)return`${a}m ago`;const i=Math.round(a/60);return i<24?`${i}h ago`:`${Math.round(i/24)}d ago`}const bs=e=>e.id,vs=e=>{if(e.status==="error")return"bg-red-50";if(e.status==="absorbed")return"bg-amber-50"},tt=[{label:"All",value:""},{label:"Success",value:"success"},{label:"Error",value:"error"},{label:"Absorbed",value:"absorbed"}],st=[{label:"All",value:""},{label:"Priced",value:"true"},{label:"Unpriced",value:"false"}],rt=[{label:"All",value:""},{label:"Any tool",value:"any"},{label:"Web search",value:"web_search"},{label:"Code execution",value:"code_execution"}],_s=["tool"],ks=50,js=["model","source"],ys=["source"],ws={range:pe,start_date:"",end_date:"",status:"",model:"",user_id:"",api_key_id:"",priced:"",source:"",source_label:"",endpoint:"",provider:"",tool:"",page:"0",size:String(ks)};function me(e,r,o){if(r||o)return{start:r||void 0,end:o||void 0};if(e===lt)return{};const a=H(z,e)??H(z,pe),i=(a==null?void 0:a.seconds)??null;return{start:i==null?void 0:it(i),end:void 0}}function Me(e){const r=me(e,"","");if(r.start)return r;const o=H(z,e);return(o==null?void 0:o.seconds)==null?{start:it(Ht)}:r}function Ss({status:e}){const r=e==="error"?"border-red-200 bg-red-50 text-red-700":e==="absorbed"?"border-amber-200 bg-amber-50 text-amber-700":"border-[var(--otari-line)] bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]";return t.jsx("span",{className:`inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium ${r}`,children:e})}const Ns={gateway:"Gateway",claude_code:"Claude Code",codex:"Codex"};function Te(e){return Ns[e]??e}function he(e){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:0}function ct(e){const r=e.billing_meters??null,o=(g,u)=>r&&typeof r[g]=="number"?he(r[g]):he(u),a=o("total_input_tokens",e.prompt_tokens),i=o("cache_read_tokens",e.cache_read_tokens),d=o("cache_write_tokens",e.cache_write_tokens),l=o("completion_tokens",e.completion_tokens),p=Math.max(0,a-i-d),m=p+i+d+l;return m>0?{fresh:p,cacheRead:i,cacheWrite:d,output:l,total:m}:null}function xe(e){var o;const r=(o=e.billing_meters)==null?void 0:o.tools;return!r||typeof r!="object"?[]:Object.entries(r).flatMap(([a,i])=>{if(!i||typeof i!="object")return[];const d=i,l=he(d.billed),p=he(d.errors);if(!l&&!p)return[];const m=d.unit_rate;return[{tool:a,billed:l,errors:p,unitRate:typeof m=="number"?m:null}]}).sort((a,i)=>i.billed-a.billed||a.tool.localeCompare(i.tool))}function dt(e){const r=e.tool.replaceAll("_"," "),o=e.billed?[`${r} ×${e.billed}`]:[r];return e.errors&&o.push(`${e.errors} failed`),o.join(", ")}function at(e){const r=xe(e).filter(o=>o.unitRate!==null);return r.length?r.reduce((o,a)=>o+a.billed*(a.unitRate??0),0):null}const Cs=[{key:"fresh",label:"Fresh input",fill:"var(--otari-ink)"},{key:"cacheRead",label:"Cache read",fill:"var(--otari-brand)"},{key:"cacheWrite",label:"Cache write",fill:"var(--otari-brand-soft)"},{key:"output",label:"Output",fill:"var(--otari-brand-dark)"}];function Ps({entry:e}){const r=ct(e);if(r===null)return t.jsx("span",{className:"tabular-nums",children:$(e.total_tokens)});const o=Cs.map(l=>({...l,value:r[l.key]})),a=o.filter(l=>l.value>0).map(l=>`${l.label} ${l.value.toLocaleString()}`).join(", ");let i=0;const d=o.map(l=>{const p=l.value/r.total*100,m={...l,x:i,width:p};return i+=p,m});return t.jsxs("span",{className:"inline-flex flex-col items-end gap-1",title:a,children:[t.jsx("span",{className:"tabular-nums",children:r.total.toLocaleString()}),t.jsx("svg",{viewBox:"0 0 100 4",preserveAspectRatio:"none",role:"img","aria-label":`Token composition: ${a}`,className:"h-1.5 w-20 overflow-hidden rounded-full bg-[var(--otari-brand-tint)]",children:d.filter(l=>l.width>0).map(l=>t.jsx("rect",{x:l.x,y:0,width:l.width,height:4,fill:l.fill},l.key))})]})}function ut(e){if(!e)return null;if(e==="static")return"the policy's only target";if(e==="default")return"the policy's default target";if(e==="on_failure")return"a fallback candidate";if(e.startsWith("condition:")){const r=e.slice(10).split(",").filter(Boolean).join(", ");return r?`matched on ${r}`:"matched a condition"}if(e.startsWith("router:")){const r=e.slice(7);return r?`chosen by router ${r}`:"chosen by a router"}return e.replaceAll("_"," ")}function ot(e){const r=new Map;for(const o of e)!o.request_group_id||o.status==="absorbed"||r.set(o.request_group_id,{servedBy:o.status==="success"?fe(o):null,servedPosition:o.status==="success"?o.attempt_position??null:null});return r}function Is(e,r){const o=ut(e.selection_reason),a=e.attempt_position,i=e.attempt_count;if(a==null||i==null||i<=1)return o;const d=`attempt ${a} of ${i}`;return e.status==="absorbed"?r!=null&&r.servedBy?`${d} failed, served by ${r.servedBy}`:r?`${d} failed, and the request ended in an error`:`${d} failed, fell back`:e.status==="error"?a(r.attempt_position??0)-(o.attempt_position??0)||r.timestamp.localeCompare(o.timestamp))}function $s({entry:e}){const r=e.request_group_id,a=nt(r?[r]:[]),i=r?(a.data??[]).filter(u=>u.request_group_id===r):[],d=Ts(i.length?i:[e]),l=i.length>0,p=d.find(u=>u.status==="success"),m=e.attempt_count??d.length,g=l?p?`Served by attempt ${p.attempt_position??"?"} of ${m}: ${fe(p)}`:d.some(u=>u.status==="error")?"No candidate served this request.":"This request has no outcome row yet.":a.isError?"Could not load this request's other attempts.":e.request_group_id?"Loading the rest of this request's attempts…":"This row carries no request group, so its other attempts cannot be found.";return t.jsxs("div",{className:"flex flex-col gap-2",children:[t.jsxs("span",{className:"text-[11px] font-medium uppercase tracking-wide text-[var(--otari-muted)]",children:["Routing plan · ",e.policy_name]}),t.jsx("span",{className:"text-sm text-[var(--otari-ink)]",children:g}),t.jsx("div",{className:"overflow-x-auto rounded-lg border border-[var(--otari-line)]",children:t.jsxs("table",{className:"w-full text-xs","aria-label":`Routing plan for policy ${e.policy_name}`,children:[t.jsx("thead",{className:"text-[var(--otari-muted)]",children:t.jsxs("tr",{className:"border-b border-[var(--otari-line)]",children:[t.jsx("th",{scope:"col",className:"px-3 py-2 text-left font-medium",children:"#"}),t.jsx("th",{scope:"col",className:"px-3 py-2 text-left font-medium",children:"Target"}),t.jsx("th",{scope:"col",className:"px-3 py-2 text-left font-medium",children:"Selected as"}),t.jsx("th",{scope:"col",className:"px-3 py-2 text-left font-medium",children:"Outcome"}),t.jsx("th",{scope:"col",className:"px-3 py-2 text-right font-medium",children:"Total time"}),t.jsx("th",{scope:"col",className:"px-3 py-2 text-right font-medium",children:"Cost"})]})}),t.jsx("tbody",{children:d.map(u=>t.jsxs("tr",{className:`border-t border-[var(--otari-line)] first:border-t-0 ${u.status==="success"?"bg-[var(--otari-brand-tint)]":""}`,children:[t.jsx("td",{className:"px-3 py-2 tabular-nums",children:u.attempt_position??"?"}),t.jsxs("td",{className:"px-3 py-2 break-all text-[var(--otari-ink)]",children:[fe(u),u.id===e.id?t.jsx("span",{className:"ml-2 rounded-full border border-[var(--otari-line)] px-1.5 py-0.5 text-[10px] text-[var(--otari-muted)]",children:"this row"}):null]}),t.jsx("td",{className:"px-3 py-2",children:ut(u.selection_reason)??"—"}),t.jsx("td",{className:`px-3 py-2 ${u.status==="success"?"":"text-amber-700"}`,children:Rs(u)}),t.jsx("td",{className:"px-3 py-2 text-right tabular-nums",children:$e(u.latency_ms)}),t.jsx("td",{className:"px-3 py-2 text-right tabular-nums",children:K(u.cost)})]},u.id))})]})}),t.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Cost and tool charges settle on the attempt that served, so a failed attempt carries its tokens and no charge."})]})}function k({label:e,copyValue:r,copyLabel:o,children:a}){return t.jsxs("div",{className:"flex flex-col gap-0.5",children:[t.jsx("span",{className:"text-[11px] font-medium uppercase tracking-wide text-[var(--otari-muted)]",children:e}),r?t.jsx(Zt,{value:r,label:o??e.toLowerCase(),className:"text-sm text-[var(--otari-ink)] break-all",children:a}):t.jsx("span",{className:"text-sm text-[var(--otari-ink)] break-all",children:a})]})}function fe(e){return e.provider?e.model.startsWith(`${e.provider}:`)?e.model:`${e.provider}:${e.model}`:e.model}function As({entry:e,onPriceModel:r}){var i,d;const o=e.cost===null,a=fe(e);return t.jsxs("div",{className:"flex flex-col gap-4 px-4 py-4",children:[e.error_message?t.jsxs("div",{className:"flex flex-col gap-1.5",children:[t.jsxs("span",{className:"text-[11px] font-medium uppercase tracking-wide text-[var(--otari-muted)]",children:["Error",e.status_code!==null?` (${e.status_code})`:""]}),t.jsx("pre",{className:"max-h-48 overflow-auto rounded-lg border border-red-200 bg-red-50 p-3 text-xs whitespace-pre-wrap break-all text-red-700",children:e.error_message})]}):null,e.policy_name!==null&&e.policy_name!==void 0?t.jsx($s,{entry:e}):null,t.jsxs("div",{className:"grid gap-4 sm:grid-cols-2 lg:grid-cols-4",children:[t.jsx(k,{label:"Provider",children:e.provider??"—"}),t.jsx(k,{label:"Endpoint",children:e.endpoint}),t.jsx(k,{label:"Source",children:Te(e.source)}),e.source_label?t.jsx(k,{label:"Session",children:e.source_label}):null,t.jsx(k,{label:"User",copyValue:e.user_id,copyLabel:"user id",children:e.user_id??"—"}),t.jsx(k,{label:"API key",copyValue:e.api_key_id,copyLabel:"api key id",children:e.api_key_id??"—"}),t.jsx(k,{label:"Prompt tokens",children:$(e.prompt_tokens)}),t.jsx(k,{label:"Completion tokens",children:$(e.completion_tokens)}),t.jsx(k,{label:"Total tokens",children:$(e.total_tokens)}),t.jsx(k,{label:"Billed tokens",children:t.jsx("span",{title:"Fresh input, cache reads and writes, and output: the tokens this request was priced on, and the total the activity row's bar splits.",children:$(((i=ct(e))==null?void 0:i.total)??null)})}),t.jsx(k,{label:"Cost",children:K(e.cost)}),xe(e).length?t.jsxs(t.Fragment,{children:[t.jsx(k,{label:"Tools",children:xe(e).map(dt).join(" · ")}),t.jsx(k,{label:"Tool cost",children:at(e)===null?t.jsx("span",{className:"text-[var(--otari-warning-ink,var(--otari-muted))]",title:"No per-request price is configured for this tool, so its calls were recorded at zero cost. Set one on the Tools & Guardrails screen.",children:"unpriced"}):K(at(e))})]}):null,t.jsx(k,{label:"Cache read tokens",children:$(e.cache_read_tokens)}),t.jsx(k,{label:"Cache write tokens",children:$(e.cache_write_tokens)}),t.jsx(k,{label:"1h cache writes",children:$(e.cache_write_1h_tokens??null)}),t.jsx(k,{label:"Total time",children:$e(e.latency_ms)}),t.jsx(k,{label:"Request ID",copyValue:e.id,children:e.id})]}),o?t.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[t.jsx(U,{size:"sm",variant:"outline",onPress:()=>r(a),children:"Price this model"}),t.jsxs("span",{className:"text-xs text-[var(--otari-muted)]",children:["This request carries no cost. Set a price for ",t.jsx("code",{className:"break-all",children:a})," so later requests are metered and count against budgets. Rows already logged keep the cost they were served with."]})]}):null,(d=e.pricing_breakdown)!=null&&d.length?t.jsxs("div",{className:"flex flex-col gap-2",children:[t.jsx("span",{className:"text-[11px] font-medium uppercase tracking-wide text-[var(--otari-muted)]",children:"Billed meters"}),t.jsx("div",{className:"grid gap-2 sm:grid-cols-2 lg:grid-cols-3",children:xs(e.pricing_breakdown).map(l=>t.jsx(k,{label:l.meter.replaceAll("_"," "),children:"unit_rate"in l?`${$(l.units)} at ${hs(l.unit_rate)} each, ${K(l.cost)}`:`${$(l.units)} at ${K(l.rate_per_million)} / 1M, ${K(l.cost)}`},l.meter))})]}):null]})}function Zs(){var Be,Ke,ze,We,Ge,Ve,Ye,Ze;const e=qt(),r=Lt(),o=x.useMemo(()=>{const s=new Map;for(const c of r.data??[])s.set(c.id,c.key_name??`${c.id.slice(0,8)}…`);return s},[r.data]),a=ms(ws),i=a.get("range"),d=a.get("start_date"),l=a.get("end_date"),p=a.get("status"),m=a.get("model"),g=a.get("user_id"),u=a.get("api_key_id"),N=a.get("priced"),b=a.get("source"),h=a.get("source_label"),I=a.get("endpoint"),C=a.get("provider"),w=a.get("tool"),ae=Math.max(0,a.getNumber("page")),X=a.getNumber("size"),F=ls.reduce((s,c)=>Math.abs(c-X)me(i,d,l)),j=x.useRef(W);x.useEffect(()=>{j.current!==W&&(j.current=W,G(me(i,d,l)))},[W,i,d,l]);const[P,V]=x.useState(()=>Me(i)),Y=x.useRef(i);x.useEffect(()=>{Y.current!==i&&(Y.current=i,V(Me(i)))},[i]);const R=N==="true"?!0:N==="false"?!1:void 0,_=x.useMemo(()=>({start_date:v.start,end_date:v.end,status:p||void 0,model:m.trim()||void 0,user_id:g||void 0,api_key_id:u||void 0,source:b||void 0,source_label:h||void 0,endpoint:I||void 0,provider:C||void 0,tool:w||void 0,priced:R}),[v,w,p,m,g,u,b,h,I,C,R]),y=es(),q=JSON.stringify(_),J=x.useRef(q);x.useEffect(()=>{J.current!==q&&(J.current=q,a.patch({page:0}),y.clear())},[q,a,y]);const A=Ut(_,ae,F),B=Je(_),Q=x.useMemo(()=>({start_date:v.start,end_date:v.end,status:p||void 0,user_id:g||void 0,api_key_id:u||void 0,source:b||void 0,source_label:h||void 0,endpoint:I||void 0,provider:C||void 0,tool:w||void 0}),[v,p,g,u,b,h,I,C,w]),E=Pe(Q,"day",js),Z=((Ke=(Be=E.data)==null?void 0:Be.by_model)==null?void 0:Ke.filter(s=>!s.is_other&&s.key!==null).map(s=>s.key))??[],oe=(r.data??[]).map(s=>({value:s.id,label:s.key_name??`${s.id.slice(0,8)}…`})),ge=x.useMemo(()=>({start_date:v.start,end_date:v.end,status:p||void 0,model:m.trim()||void 0,user_id:g||void 0,api_key_id:u||void 0}),[v,p,m,g,u]),ee=Pe(ge,"day",ys,!!b),te=(ze=b?ee.data:E.data)==null?void 0:ze.by_source,ne=x.useMemo(()=>{const s=(te??[]).filter(c=>!c.is_other&&c.key!==null).map(c=>c.key);return b&&!s.includes(b)?[b,...s]:s},[te,b]),L=H(z,i)??H(z,pe),n=!!(v.start&&P.start&&new Date(v.start).getTime()({start_date:n?v.start:P.start,end_date:n?v.end:void 0,status:p||void 0,model:m.trim()||void 0,user_id:g||void 0,api_key_id:u||void 0,source:b||void 0,source_label:h||void 0,endpoint:I||void 0,provider:C||void 0,tool:w||void 0,priced:R}),[n,w,v,P,p,m,g,u,b,h,I,C,R]),D=Pe(O,S,_s),mt=(((We=D.data)==null?void 0:We.series)??[]).map(s=>({bucketStart:s.bucket_start,requests:s.requests,errors:s.errors??0})),T=A.data??[],{pageOutcomes:be,unresolvedGroupIds:pt}=x.useMemo(()=>{const s=ot(T),c=new Set;for(const M of T)M.status==="absorbed"&&M.request_group_id&&!s.has(M.request_group_id)&&c.add(M.request_group_id);return{pageOutcomes:s,unresolvedGroupIds:[...c]}},[T]),ve=nt(pt),Ae=x.useMemo(()=>{var s;return(s=ve.data)!=null&&s.length?new Map([...be,...ot(ve.data)]):be},[be,ve.data]),ht=B.isSuccess&&!B.isPlaceholderData?((Ge=B.data)==null?void 0:Ge.total)??0:null,_e=H(z,i),xt=!!(d||l)||i!==pe&&(_e==null?void 0:_e.seconds)!=null,ft=!!(p||m.trim()||g||u||N||b||h||I||C||w||xt),se=(s,c)=>{var M;return((M=s.find(de=>de.value===c))==null?void 0:M.label)??c},Ee=(e.data??[]).map(s=>({value:s.user_id,label:s.alias?`${s.alias} (${s.user_id})`:s.user_id})),gt=()=>a.patch({status:"",priced:"",model:"",user_id:"",api_key_id:"",source:"",source_label:"",endpoint:"",provider:"",tool:""}),bt=[...p?[{key:"status",label:"Status",value:se(tt,p),onClear:()=>a.patch({status:""})}]:[],...N?[{key:"priced",label:"Priced",value:se(st,N),onClear:()=>a.patch({priced:""})}]:[],...g?[{key:"user",label:"User",value:se(Ee,g),onClear:()=>a.patch({user_id:""})}]:[],...m.trim()?[{key:"model",label:"Model",value:m.trim(),onClear:()=>a.patch({model:""})}]:[],...u?[{key:"key",label:"API key",value:se(oe,u),onClear:()=>a.patch({api_key_id:""})}]:[],...b?[{key:"source",label:"Source",value:Te(b),onClear:()=>a.patch({source:""})}]:[],...h?[{key:"session",label:"Session",value:h,onClear:()=>a.patch({source_label:""})}]:[],...I?[{key:"endpoint",label:"Endpoint",value:I,onClear:()=>a.patch({endpoint:""})}]:[],...C?[{key:"provider",label:"Provider",value:C,onClear:()=>a.patch({provider:""})}]:[],...w?[{key:"tool",label:"Tool",value:se(rt,w),onClear:()=>a.patch({tool:""})}]:[]],le=x.useMemo(()=>T.filter(s=>!s.counts_toward_budget).map(s=>s.id),[T]),vt=x.useMemo(()=>T.filter(s=>s.counts_toward_budget).map(s=>s.id),[T]),Oe=ts(y.selectedKeys,le),re=Oe.length,De=y.allMatching||re>0,_t=x.useMemo(()=>({..._,counts_toward_budget:!1}),[_]),Fe=Je(_t,De),ie=Fe.isSuccess?((Ve=Fe.data)==null?void 0:Ve.total)??null:null,kt=le.length>0&&re===le.length&&ie!=null&&ie>re,ce=y.allMatching?ie??re:re,jt=le.length>0||y.allMatching,ke=Kt(),je=zt(),ye=Wt(),[yt,we]=x.useState(!1),[wt,Se]=x.useState(!1),[Ne,Ce]=x.useState(null),[St,qe]=x.useState(null),Nt=x.useCallback(s=>t.jsxs("div",{children:[t.jsxs("div",{className:"flex items-center justify-between border-b border-[var(--otari-line)] px-4 py-2",children:[t.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Request detail"}),t.jsx(U,{size:"sm",variant:"ghost",onPress:()=>qe(null),children:"Close"})]}),t.jsx(As,{entry:s,onPriceModel:Ce})]}),[]),Le=()=>y.allMatching?{by_filter:!0,model:m.trim()||void 0,user_id:g||void 0,api_key_id:u||void 0,status:_.status,source:_.source,source_label:_.source_label,endpoint:_.endpoint,provider:_.provider,tool:_.tool,start_date:_.start_date,end_date:_.end_date,priced:_.priced}:{ids:Oe},Ct=()=>{ke.mutate(Le(),{onSuccess:()=>{we(!1),y.clear()}})},Pt=(s,c)=>{ye.mutate({model_key:c,input_price_per_million:s.input_price_per_million,output_price_per_million:s.output_price_per_million,cache_read_price_per_million:s.cache_read_price_per_million??null,cache_write_price_per_million:s.cache_write_price_per_million??null},{onSuccess:()=>Ce(null)})},It=s=>{je.mutate({...Le(),...s},{onSuccess:()=>{Se(!1),y.clear()}})},Mt=()=>{A.refetch(),B.refetch(),D.refetch(),E.refetch(),b&&ee.refetch()},Ue=s=>{if(s.key===i&&!d&&!l){G(me(s.key,"","")),V(Me(s.key));return}a.patch({range:s.key,start_date:"",end_date:""})},Rt=(s,c)=>a.patch({start_date:s,end_date:c}),Tt=x.useMemo(()=>{const s=c=>c===null?"—":o.get(c)??`${c.slice(0,8)}…`;return[{id:"time",header:"Time",cell:c=>t.jsx("span",{title:fs(c.timestamp),className:"text-[var(--otari-muted)]",children:gs(c.timestamp)})},{id:"user",header:"User",cell:c=>c.user_id??"—"},{id:"model",header:"Model",isRowHeader:!0,cell:c=>{const M=xe(c);if(!M.length)return c.model;const de=M.reduce(($t,Xe)=>$t+Xe.billed+Xe.errors,0),He=M.map(dt).join(" · ");return t.jsxs("span",{className:"inline-flex items-center gap-1.5",children:[c.model,t.jsxs("span",{className:"inline-flex items-center rounded-full border border-[var(--otari-line)] bg-[var(--otari-brand-tint)] px-1.5 py-0.5 text-[11px] font-medium text-[var(--otari-brand-dark)]",title:He,"aria-label":`Gateway tools: ${He}`,children:[de," ",de===1?"tool":"tools"]})]})}},{id:"routing",header:"Routing",cell:c=>t.jsx(Ms,{entry:c,outcome:Ae.get(c.request_group_id??"")??null})},{id:"api_key",header:"API key",cell:c=>t.jsx("span",{className:"text-[var(--otari-muted)]",children:s(c.api_key_id)})},{id:"tokens",header:"Tokens",align:"end",cell:c=>t.jsx(Ps,{entry:c})},{id:"cost",header:"Cost",align:"end",cell:c=>K(c.cost)},{id:"latency",header:"Total time",align:"end",cell:c=>$e(c.latency_ms)},{id:"status",header:"Status",cell:c=>t.jsx(Ss,{status:c.status})}]},[o,Ae]);return t.jsxs("div",{className:"flex flex-col gap-6",children:[t.jsx(Gt,{title:"Activity",description:"A per-request log of what the gateway served: tokens, cost, latency, and failures. No request or response content is stored."}),t.jsx(Vt,{error:A.error??B.error??D.error}),t.jsxs("div",{className:"flex flex-col gap-3",children:[t.jsx(us,{presets:z,extentKey:f,onPreset:Ue,onSelectRange:Rt,onSelectFull:()=>L?Ue(L):void 0,series:mt,bucket:S,windowStart:v.start,windowEnd:v.end,loading:D.isLoading,ariaLabel:"Activity request volume over the selected window",action:t.jsx(Yt,{onRefresh:Mt,isFetching:A.isFetching,updatedAt:A.dataUpdatedAt})}),t.jsxs(os,{chips:bt,onClearAll:gt,children:[t.jsx(ue,{id:"filter-status",label:"Status",value:p,onChange:s=>a.patch({status:s}),children:tt.map(s=>t.jsx("option",{value:s.value,children:s.label},s.value))}),t.jsx(ue,{id:"filter-priced",label:"Priced?",value:N,onChange:s=>a.patch({priced:s}),children:st.map(s=>t.jsx("option",{value:s.value,children:s.label},s.value))}),w||(Ze=(Ye=D.data)==null?void 0:Ye.by_tool)!=null&&Ze.length?t.jsx(ue,{id:"filter-tool",label:"Tool",value:w,onChange:s=>a.patch({tool:s}),children:rt.map(s=>t.jsx("option",{value:s.value,children:s.label},s.value))}):null,ne.length>1||b?t.jsxs(ue,{id:"filter-source",label:"Source",value:b,onChange:s=>a.patch({source:s}),children:[t.jsx("option",{value:"",children:"All"}),ne.map(s=>t.jsx("option",{value:s,children:Te(s)},s))]}):null,t.jsx(Ie,{label:"API key",value:u,onChange:s=>a.patch({api_key_id:s}),placeholder:"All keys",options:oe}),t.jsx(Ie,{label:"User",value:g,onChange:s=>a.patch({user_id:s}),placeholder:"All users",options:Ee}),t.jsx(Ie,{label:"Model",value:m,onChange:s=>a.patch({model:s}),allowsCustom:!0,placeholder:"Any model",options:(m&&!Z.includes(m)?[m,...Z]:Z).map(s=>({value:s,label:s}))})]})]}),De?t.jsxs(ss,{selectedCount:ce,allMatching:y.allMatching,matchingTotal:ie,canSelectAllMatching:kt,onSelectAllMatching:y.enableAllMatching,onClear:y.clear,children:[t.jsx(U,{size:"sm",variant:"primary",onPress:()=>Se(!0),children:"Set price"}),t.jsx(U,{size:"sm",variant:"danger",onPress:()=>we(!0),children:"Delete"})]}):null,t.jsx(as,{ariaLabel:"Activity log",columns:Tt,rows:T,getRowKey:bs,isLoading:A.isLoading,emptyContent:ft?"No requests match these filters.":"No requests recorded yet.",selectionMode:jt?"multiple":"none",selectedKeys:y.selectedKeys,onSelectionChange:y.onSelectionChange,disabledKeys:vt,onRowAction:s=>qe(c=>c===s?null:s),rowClassName:vs,detailKey:St,renderDetail:Nt}),t.jsx(ns,{page:ae,pageSize:F,total:ht,rowsOnPage:T.length,onPageChange:s=>a.patch({page:s}),onPageSizeChange:s=>a.patch({size:s,page:0}),isFetching:A.isFetching,hasNextFallback:T.length===F}),t.jsx(rs,{isOpen:yt,onOpenChange:we,heading:"Delete usage rows",body:`Delete ${ce.toLocaleString()} imported ${ce===1?"row":"rows"}? Only imported rows are removed, and this cannot be undone.`,confirmLabel:"Delete",isPending:ke.isPending,error:ke.error,onConfirm:Ct}),t.jsx(Qe,{isOpen:wt,onOpenChange:Se,targetCount:ce,isPending:je.isPending,error:je.error,onSubmit:It}),t.jsx(Qe,{isOpen:Ne!==null,onOpenChange:s=>Ce(s?Ne??"":null),isPending:ye.isPending,error:ye.error,onSubmit:Pt,collectModelKey:!0,initialModelKey:Ne??"",title:"Price this model",description:()=>"Set what this model costs, taken from the request you were looking at. Requests from now on are costed at these rates and counted against budgets; rows already logged keep the cost they were served with."})]})}export{Zs as ActivityPage}; diff --git a/src/gateway/static/dashboard/assets/BudgetsPage-B9iEC7ec.js b/src/gateway/static/dashboard/assets/BudgetsPage-DGkl3NSe.js similarity index 98% rename from src/gateway/static/dashboard/assets/BudgetsPage-B9iEC7ec.js rename to src/gateway/static/dashboard/assets/BudgetsPage-DGkl3NSe.js index 68beb9890..b264809f7 100644 --- a/src/gateway/static/dashboard/assets/BudgetsPage-B9iEC7ec.js +++ b/src/gateway/static/dashboard/assets/BudgetsPage-DGkl3NSe.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as i}from"./react-dgEcD0HR.js";import{H as ne,u as re,I as le,J as ie,K as oe,L as de,q as ue,P as ce,E as H,M as me,z as xe,N as ge}from"./index-D-R1nuKP.js";import{u as he,r as pe,B as fe}from"./tableSelection-B1umVgqc.js";import{C as be}from"./ConfirmDialog-mbnZRETP.js";import{D as je}from"./DataTable-BHrpJHmX.js";import{F as $}from"./Field-GEMwIhf7.js";import{C as E,I as ve,a as ye,b as Ne,B as x,d as k,S as Se}from"./heroui-DhloIxuc.js";const _e=50;function Ce({value:t,onChange:r,users:n,label:o,description:l}){const[g,h]=i.useState(""),d=i.useMemo(()=>n.filter(s=>!s.user_id.startsWith("apikey-")).map(s=>({id:s.user_id,label:s.alias?`${s.user_id} (${s.alias})`:s.user_id})),[n]),p=i.useMemo(()=>{const s=g.trim().toLowerCase();return d.filter(u=>!t.includes(u.id)).filter(u=>!s||u.id.toLowerCase().includes(s)||u.label.toLowerCase().includes(s)).slice(0,_e)},[d,t,g]),c=s=>{t.includes(s)||r([...t,s]),h("")},m=s=>r(t.filter(u=>u!==s));return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsxs("div",{children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:o}),l?e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:l}):null]}),t.length>0?e.jsx("div",{className:"flex flex-wrap gap-1.5",children:t.map(s=>e.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full bg-[var(--otari-brand-tint)] px-2.5 py-1 font-mono text-xs text-[var(--otari-brand-dark)]",children:[s,e.jsx("button",{type:"button","aria-label":`Remove ${s}`,onClick:()=>m(s),className:"text-[var(--otari-brand-dark)] hover:text-red-700",children:"×"})]},s))}):null,d.length===0?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"No users yet. Create users first, then assign them here or from the Users page."}):e.jsxs(E.Root,{allowsEmptyCollection:!0,menuTrigger:"input",inputValue:g,onInputChange:h,selectedKey:null,onSelectionChange:s=>{s!=null&&c(String(s))},className:"flex flex-col gap-1",children:[e.jsxs(E.InputGroup,{children:[e.jsx(ve,{"aria-label":"Add a user",placeholder:"Search users…",autoComplete:"off"}),e.jsx(E.Trigger,{})]}),e.jsx(E.Popover,{children:e.jsx(ye,{items:p,className:"max-h-72 overflow-auto",children:s=>e.jsx(Ne,{id:s.id,textValue:s.label,children:s.label})})})]})]})}const we=new Intl.NumberFormat(void 0,{style:"currency",currency:"USD",maximumFractionDigits:2});function D(t){return we.format(t)}const j=86400,q=3600,O=[{label:"No reset",seconds:null},{label:"Daily",seconds:j},{label:"Weekly",seconds:7*j},{label:"Monthly",seconds:30*j}];function De(t){if(t===null)return"No reset";const r=O.find(n=>n.seconds===t);return r?r.label:t%j===0?`Every ${t/j} days`:t%q===0?`Every ${t/q} hours`:`Every ${t}s`}function W(t){if(!t)return"—";const r=new Date(t);return Number.isNaN(r.getTime())?"—":r.toLocaleString()}function Be(t){const r=t.trim();if(r==="")return{value:null,valid:!0};const n=Number(r);return!Number.isFinite(n)||n<0?{value:null,valid:!1}:{value:n,valid:!0}}function Y(t){return t!==null&&t%j===0?String(t/j):""}function Pe({value:t,onChange:r,onInvalidChange:n}){const o=O.some(s=>s.seconds===t),[l,g]=i.useState(!o),[h,d]=i.useState(()=>Y(t)),p=h.trim(),c=Number(p),m=p!==""&&(!Number.isSafeInteger(c)||c<=0);return i.useEffect(()=>{n==null||n(m)},[m,n]),e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Reset period"}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[O.map(s=>e.jsx(x,{size:"sm",variant:!l&&t===s.seconds?"primary":"outline",onPress:()=>{g(!1),d(Y(s.seconds)),r(s.seconds)},children:s.label},s.label)),e.jsx(x,{size:"sm",variant:l?"primary":"outline",onPress:()=>g(!0),children:"Custom"})]}),l?e.jsx("div",{className:"flex items-end gap-2",children:e.jsx($,{label:"Every N days",value:h,onChange:s=>{d(s);const u=Number(s.trim());r(s.trim()===""||!Number.isSafeInteger(u)||u<=0?null:u*j)},placeholder:"14",description:m?e.jsx("span",{className:"text-red-700",children:"Enter a whole number of days."}):"Whole days between resets."})}):null,e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Spend returns to zero each period. A user’s clock starts when the budget is assigned to them."})]})}function G({title:t,submitLabel:r,initial:n,error:o,isPending:l,onSubmit:g,onClose:h,assignUsers:d}){const[p,c]=i.useState(n.name??""),[m,s]=i.useState(n.max_budget===null?"":String(n.max_budget)),[u,b]=i.useState(n.budget_duration_sec),[S,v]=i.useState(!1),[B,P]=i.useState([]),f=Be(m),A=!l&&f.valid&&!S,C=()=>{A&&g({name:p.trim()||null,max_budget:f.value,budget_duration_sec:u},B)};return e.jsx(k,{children:e.jsxs(k.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsx("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:t}),e.jsx(H,{error:o}),e.jsx($,{label:"Name (optional)",value:p,onChange:c,autoFocus:!0,placeholder:"team-free-tier",description:"A label to recognize this budget later."}),e.jsx($,{label:"Spending limit (USD)",value:m,onChange:s,placeholder:"100.00",description:f.valid?"The most a single user on this budget may spend per period. Leave blank for no limit.":e.jsx("span",{className:"text-red-700",children:"Enter a non-negative number, or leave blank for no limit."})}),e.jsx(Pe,{value:u,onChange:b,onInvalidChange:v}),d?e.jsx(Ce,{label:"Assign to users (optional)",description:"Attach this budget to existing users now. You can also manage assignments later on the Users page.",value:B,onChange:P,users:d}):null,e.jsxs("div",{className:"flex gap-2",children:[e.jsx(x,{variant:"primary",isDisabled:!A,onPress:C,children:l?"Saving…":r}),e.jsx(x,{variant:"ghost",isDisabled:l,onPress:h,children:"Cancel"})]})]})})}function Ae({budget:t}){if(t.user_count===0)return e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"No users assigned"});const r=t.total_spend;if(t.max_budget===null)return e.jsxs("span",{className:"text-xs text-[var(--otari-ink)]",children:[D(r)," spent",e.jsx("span",{className:"text-[var(--otari-muted)]",children:" · no limit"})]});const n=t.max_budget*t.user_count,o=n>0?Math.min(100,r/n*100):0,l=r>n;return e.jsxs("div",{className:"flex min-w-[140px] flex-col gap-1",children:[e.jsxs("div",{className:"flex items-baseline justify-between gap-2 text-xs",children:[e.jsx("span",{className:"text-[var(--otari-ink)]",children:D(r)}),e.jsxs("span",{className:"text-[var(--otari-muted)]",children:["of ",D(n)]})]}),e.jsx("div",{className:"h-1.5 w-full overflow-hidden rounded-full bg-[var(--otari-line)]",role:"progressbar","aria-valuenow":Math.round(o),"aria-valuemin":0,"aria-valuemax":100,"aria-label":"Aggregate spend against total allocation",children:e.jsx("div",{className:`h-full rounded-full ${l?"bg-red-500":"bg-[var(--otari-brand)]"}`,style:{width:`${Math.max(o,l?100:2)}%`}})})]})}function Ee({budgetId:t}){const r=ge(t);if(r.isLoading)return e.jsxs("div",{className:"flex items-center gap-2 px-4 py-4 text-sm text-[var(--otari-muted)]",children:[e.jsx(Se,{size:"sm"})," Loading reset history…"]});if(r.error)return e.jsx("div",{className:"px-4 py-4",children:e.jsx(H,{error:r.error})});const n=r.data??[];return n.length===0?e.jsx("div",{className:"px-4 py-4 text-sm text-[var(--otari-muted)]",children:"No resets recorded yet for this budget."}):e.jsx("div",{className:"overflow-x-auto px-4 py-3",children:e.jsxs("table",{className:"w-full border-collapse text-xs",children:[e.jsx("thead",{className:"text-left text-[var(--otari-muted)]",children:e.jsxs("tr",{children:[e.jsx("th",{className:"py-1.5 pr-4 font-medium",children:"User"}),e.jsx("th",{className:"py-1.5 pr-4 font-medium",children:"Spend cleared"}),e.jsx("th",{className:"py-1.5 pr-4 font-medium",children:"Reset at"}),e.jsx("th",{className:"py-1.5 font-medium",children:"Next reset"})]})}),e.jsx("tbody",{children:n.map(o=>e.jsxs("tr",{className:"border-t border-[var(--otari-line)]",children:[e.jsx("td",{className:"py-1.5 pr-4",children:e.jsx("code",{children:o.user_id??"—"})}),e.jsx("td",{className:"py-1.5 pr-4 text-[var(--otari-ink)]",children:D(o.previous_spend)}),e.jsx("td",{className:"py-1.5 pr-4 text-[var(--otari-muted)]",children:W(o.reset_at)}),e.jsx("td",{className:"py-1.5 text-[var(--otari-muted)]",children:W(o.next_reset_at)})]},o.id))})]})})}function ke({label:t,isPending:r,onConfirm:n}){const[o,l]=i.useState(!1);return o?e.jsxs("div",{className:"flex flex-col items-end gap-1.5 rounded-lg border border-amber-200 bg-amber-50 p-2 text-right",children:[e.jsxs("span",{className:"max-w-xs text-xs text-amber-800",children:["Delete ",e.jsx("strong",{children:t}),"? Users keep their spend but lose this limit. Cannot be undone."]}),e.jsxs("span",{className:"inline-flex gap-1",children:[e.jsx(x,{size:"sm",variant:"danger",isDisabled:r,onPress:n,children:"Delete permanently"}),e.jsx(x,{size:"sm",variant:"ghost",isDisabled:r,onPress:()=>l(!1),children:"Cancel"})]})]}):e.jsx(x,{size:"sm",variant:"danger-soft",onPress:()=>l(!0),children:"Delete"})}const Ue=t=>t.budget_id;function J(t){return t.split("-")[0]}function z(t){return t.name??J(t.budget_id)}function Ke(){const t=ne(),r=re(),n=le(),o=ie(),l=oe(),g=de(),[h,d]=i.useState(!1),[p,c]=i.useState(null),[m,s]=i.useState(null),[u,b]=i.useState(null),[S,v]=i.useState(null),[B,P]=i.useState(!1),f=he(),[A,C]=i.useState(!1),[Q,K]=i.useState(void 0),[X,T]=i.useState(!1),w=t.data??[],F=t.isLoading,y=w.find(a=>a.budget_id===p)??null,U=w.find(a=>a.budget_id===m)??null,L=!F&&w.length===0&&!h,Z=w.map(a=>a.budget_id),_=pe(f.selectedKeys,Z),ee=async()=>{T(!0),K(void 0);try{for(const a of _)await l.mutateAsync(a);f.clear(),C(!1)}catch(a){K(a)}finally{T(!1)}},te=i.useMemo(()=>[{id:"budget",header:"Budget",isRowHeader:!0,cell:a=>e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsx("span",{className:"font-medium text-[var(--otari-ink)]",children:a.name??e.jsx("span",{className:"text-[var(--otari-muted)]",children:"(unnamed)"})}),e.jsx(ue,{value:a.budget_id,label:"budget id",children:e.jsx("code",{className:"text-[11px] text-[var(--otari-muted)]",title:a.budget_id,children:J(a.budget_id)})})]})},{id:"limit",header:"Limit (per user)",cell:a=>a.max_budget===null?e.jsx("span",{className:"text-[var(--otari-muted)]",children:"Unlimited"}):D(a.max_budget)},{id:"reset",header:"Reset",cell:a=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:De(a.budget_duration_sec)})},{id:"users",header:"Users",cell:a=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:a.user_count})},{id:"usage",header:"Usage",cell:a=>e.jsx(Ae,{budget:a})},{id:"actions",header:"Actions",align:"end",cell:a=>e.jsxs("div",{className:"flex items-center justify-end gap-1.5",children:[e.jsx(x,{size:"sm",variant:"ghost",onPress:()=>s(N=>N===a.budget_id?null:a.budget_id),children:m===a.budget_id?"Hide history":"History"}),e.jsx(x,{size:"sm",variant:"ghost",onPress:()=>{d(!1),c(a.budget_id)},children:"Edit"}),e.jsx(ke,{label:z(a),isPending:l.isPending,onConfirm:()=>l.mutate(a.budget_id)})]})}],[m,l.isPending,l.mutate]),V=async(a,N)=>{P(!0),b(null);const I=await Promise.allSettled(N.map(M=>g.mutateAsync({id:M,body:{budget_id:a}})));P(!1);const R=I.flatMap((M,ae)=>M.status==="rejected"?[N[ae]]:[]);if(R.length>0){v({budgetId:a,userIds:R}),b(new Error(`Budget created, but could not assign it to: ${R.join(", ")}. Retry to try again.`));return}v(null),d(!1)},se=(a,N)=>{if(S){V(S.budgetId,S.userIds);return}b(null),n.mutate(a,{onSuccess:async I=>{if(N.length>0){await V(I.budget_id,N);return}d(!1)}})};return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(ce,{title:"Budgets",description:"Define spending limits and reset schedules. Assign a budget to users to enforce it.",action:h||L?null:e.jsx(x,{variant:"primary",onPress:()=>{c(null),b(null),v(null),d(!0)},children:"Create budget"})}),e.jsx(H,{error:t.error??n.error??o.error??l.error??g.error}),e.jsx(me,{children:"Assign a budget to users when you create it, or later from the Users page. Each row’s usage aggregates the spend of the users currently on that budget."}),L?e.jsx(xe,{title:"No budgets yet",description:"A budget caps how much a user may spend and, optionally, resets that spend on a schedule. Create one, then assign it to users to enforce a limit.",actionLabel:"Create your first budget",onAction:()=>{c(null),b(null),v(null),d(!0)}}):null,h?e.jsx(G,{title:"Create budget",submitLabel:S?"Retry assignments":"Create budget",initial:{name:null,max_budget:null,budget_duration_sec:null},error:n.error??u,isPending:n.isPending||B,assignUsers:r.data??[],onSubmit:se,onClose:()=>{b(null),v(null),d(!1)}}):null,y?e.jsx(G,{title:`Edit budget ${z(y)}`,submitLabel:"Save changes",initial:{name:y.name,max_budget:y.max_budget,budget_duration_sec:y.budget_duration_sec},error:o.error,isPending:o.isPending,onSubmit:a=>o.mutate({id:y.budget_id,body:a},{onSuccess:()=>c(null)}),onClose:()=>c(null)},y.budget_id):null,_.length>0?e.jsx(fe,{selectedCount:_.length,allMatching:!1,matchingTotal:null,canSelectAllMatching:!1,onSelectAllMatching:()=>{},onClear:f.clear,children:e.jsx(x,{size:"sm",variant:"danger",onPress:()=>C(!0),children:"Delete"})}):null,L?null:e.jsx(je,{ariaLabel:"Budgets",columns:te,rows:w,getRowKey:Ue,isLoading:F,emptyContent:"No budgets yet. Create one to cap spending.",selectionMode:"multiple",selectedKeys:f.selectedKeys,onSelectionChange:f.onSelectionChange}),U?e.jsx(k,{children:e.jsxs(k.Content,{className:"p-0",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-[var(--otari-line)] px-4 py-2",children:[e.jsxs("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:["Reset history — ",z(U)]}),e.jsx(x,{size:"sm",variant:"ghost",onPress:()=>s(null),children:"Close"})]}),e.jsx(Ee,{budgetId:U.budget_id})]})}):null,e.jsx(be,{isOpen:A,onOpenChange:C,heading:"Delete budgets",body:`Delete ${_.length} ${_.length===1?"budget":"budgets"}? Users on ${_.length===1?"it":"them"} will no longer be capped.`,confirmLabel:"Delete",isPending:X,error:Q,onConfirm:ee})]})}export{Ke as BudgetsPage}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as i}from"./react-dgEcD0HR.js";import{H as ne,u as re,I as le,J as ie,K as oe,L as de,q as ue,P as ce,E as H,M as me,z as xe,N as ge}from"./index-Dit1BUBh.js";import{u as he,r as pe,B as fe}from"./tableSelection-B1umVgqc.js";import{C as be}from"./ConfirmDialog-Dt_8xaSM.js";import{D as je}from"./DataTable-BHrpJHmX.js";import{F as $}from"./Field-GEMwIhf7.js";import{C as E,I as ve,a as ye,b as Ne,B as x,d as k,S as Se}from"./heroui-DhloIxuc.js";const _e=50;function Ce({value:t,onChange:r,users:n,label:o,description:l}){const[g,h]=i.useState(""),d=i.useMemo(()=>n.filter(s=>!s.user_id.startsWith("apikey-")).map(s=>({id:s.user_id,label:s.alias?`${s.user_id} (${s.alias})`:s.user_id})),[n]),p=i.useMemo(()=>{const s=g.trim().toLowerCase();return d.filter(u=>!t.includes(u.id)).filter(u=>!s||u.id.toLowerCase().includes(s)||u.label.toLowerCase().includes(s)).slice(0,_e)},[d,t,g]),c=s=>{t.includes(s)||r([...t,s]),h("")},m=s=>r(t.filter(u=>u!==s));return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsxs("div",{children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:o}),l?e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:l}):null]}),t.length>0?e.jsx("div",{className:"flex flex-wrap gap-1.5",children:t.map(s=>e.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full bg-[var(--otari-brand-tint)] px-2.5 py-1 font-mono text-xs text-[var(--otari-brand-dark)]",children:[s,e.jsx("button",{type:"button","aria-label":`Remove ${s}`,onClick:()=>m(s),className:"text-[var(--otari-brand-dark)] hover:text-red-700",children:"×"})]},s))}):null,d.length===0?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"No users yet. Create users first, then assign them here or from the Users page."}):e.jsxs(E.Root,{allowsEmptyCollection:!0,menuTrigger:"input",inputValue:g,onInputChange:h,selectedKey:null,onSelectionChange:s=>{s!=null&&c(String(s))},className:"flex flex-col gap-1",children:[e.jsxs(E.InputGroup,{children:[e.jsx(ve,{"aria-label":"Add a user",placeholder:"Search users…",autoComplete:"off"}),e.jsx(E.Trigger,{})]}),e.jsx(E.Popover,{children:e.jsx(ye,{items:p,className:"max-h-72 overflow-auto",children:s=>e.jsx(Ne,{id:s.id,textValue:s.label,children:s.label})})})]})]})}const we=new Intl.NumberFormat(void 0,{style:"currency",currency:"USD",maximumFractionDigits:2});function D(t){return we.format(t)}const j=86400,q=3600,O=[{label:"No reset",seconds:null},{label:"Daily",seconds:j},{label:"Weekly",seconds:7*j},{label:"Monthly",seconds:30*j}];function De(t){if(t===null)return"No reset";const r=O.find(n=>n.seconds===t);return r?r.label:t%j===0?`Every ${t/j} days`:t%q===0?`Every ${t/q} hours`:`Every ${t}s`}function W(t){if(!t)return"—";const r=new Date(t);return Number.isNaN(r.getTime())?"—":r.toLocaleString()}function Be(t){const r=t.trim();if(r==="")return{value:null,valid:!0};const n=Number(r);return!Number.isFinite(n)||n<0?{value:null,valid:!1}:{value:n,valid:!0}}function Y(t){return t!==null&&t%j===0?String(t/j):""}function Pe({value:t,onChange:r,onInvalidChange:n}){const o=O.some(s=>s.seconds===t),[l,g]=i.useState(!o),[h,d]=i.useState(()=>Y(t)),p=h.trim(),c=Number(p),m=p!==""&&(!Number.isSafeInteger(c)||c<=0);return i.useEffect(()=>{n==null||n(m)},[m,n]),e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Reset period"}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[O.map(s=>e.jsx(x,{size:"sm",variant:!l&&t===s.seconds?"primary":"outline",onPress:()=>{g(!1),d(Y(s.seconds)),r(s.seconds)},children:s.label},s.label)),e.jsx(x,{size:"sm",variant:l?"primary":"outline",onPress:()=>g(!0),children:"Custom"})]}),l?e.jsx("div",{className:"flex items-end gap-2",children:e.jsx($,{label:"Every N days",value:h,onChange:s=>{d(s);const u=Number(s.trim());r(s.trim()===""||!Number.isSafeInteger(u)||u<=0?null:u*j)},placeholder:"14",description:m?e.jsx("span",{className:"text-red-700",children:"Enter a whole number of days."}):"Whole days between resets."})}):null,e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Spend returns to zero each period. A user’s clock starts when the budget is assigned to them."})]})}function G({title:t,submitLabel:r,initial:n,error:o,isPending:l,onSubmit:g,onClose:h,assignUsers:d}){const[p,c]=i.useState(n.name??""),[m,s]=i.useState(n.max_budget===null?"":String(n.max_budget)),[u,b]=i.useState(n.budget_duration_sec),[S,v]=i.useState(!1),[B,P]=i.useState([]),f=Be(m),A=!l&&f.valid&&!S,C=()=>{A&&g({name:p.trim()||null,max_budget:f.value,budget_duration_sec:u},B)};return e.jsx(k,{children:e.jsxs(k.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsx("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:t}),e.jsx(H,{error:o}),e.jsx($,{label:"Name (optional)",value:p,onChange:c,autoFocus:!0,placeholder:"team-free-tier",description:"A label to recognize this budget later."}),e.jsx($,{label:"Spending limit (USD)",value:m,onChange:s,placeholder:"100.00",description:f.valid?"The most a single user on this budget may spend per period. Leave blank for no limit.":e.jsx("span",{className:"text-red-700",children:"Enter a non-negative number, or leave blank for no limit."})}),e.jsx(Pe,{value:u,onChange:b,onInvalidChange:v}),d?e.jsx(Ce,{label:"Assign to users (optional)",description:"Attach this budget to existing users now. You can also manage assignments later on the Users page.",value:B,onChange:P,users:d}):null,e.jsxs("div",{className:"flex gap-2",children:[e.jsx(x,{variant:"primary",isDisabled:!A,onPress:C,children:l?"Saving…":r}),e.jsx(x,{variant:"ghost",isDisabled:l,onPress:h,children:"Cancel"})]})]})})}function Ae({budget:t}){if(t.user_count===0)return e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"No users assigned"});const r=t.total_spend;if(t.max_budget===null)return e.jsxs("span",{className:"text-xs text-[var(--otari-ink)]",children:[D(r)," spent",e.jsx("span",{className:"text-[var(--otari-muted)]",children:" · no limit"})]});const n=t.max_budget*t.user_count,o=n>0?Math.min(100,r/n*100):0,l=r>n;return e.jsxs("div",{className:"flex min-w-[140px] flex-col gap-1",children:[e.jsxs("div",{className:"flex items-baseline justify-between gap-2 text-xs",children:[e.jsx("span",{className:"text-[var(--otari-ink)]",children:D(r)}),e.jsxs("span",{className:"text-[var(--otari-muted)]",children:["of ",D(n)]})]}),e.jsx("div",{className:"h-1.5 w-full overflow-hidden rounded-full bg-[var(--otari-line)]",role:"progressbar","aria-valuenow":Math.round(o),"aria-valuemin":0,"aria-valuemax":100,"aria-label":"Aggregate spend against total allocation",children:e.jsx("div",{className:`h-full rounded-full ${l?"bg-red-500":"bg-[var(--otari-brand)]"}`,style:{width:`${Math.max(o,l?100:2)}%`}})})]})}function Ee({budgetId:t}){const r=ge(t);if(r.isLoading)return e.jsxs("div",{className:"flex items-center gap-2 px-4 py-4 text-sm text-[var(--otari-muted)]",children:[e.jsx(Se,{size:"sm"})," Loading reset history…"]});if(r.error)return e.jsx("div",{className:"px-4 py-4",children:e.jsx(H,{error:r.error})});const n=r.data??[];return n.length===0?e.jsx("div",{className:"px-4 py-4 text-sm text-[var(--otari-muted)]",children:"No resets recorded yet for this budget."}):e.jsx("div",{className:"overflow-x-auto px-4 py-3",children:e.jsxs("table",{className:"w-full border-collapse text-xs",children:[e.jsx("thead",{className:"text-left text-[var(--otari-muted)]",children:e.jsxs("tr",{children:[e.jsx("th",{className:"py-1.5 pr-4 font-medium",children:"User"}),e.jsx("th",{className:"py-1.5 pr-4 font-medium",children:"Spend cleared"}),e.jsx("th",{className:"py-1.5 pr-4 font-medium",children:"Reset at"}),e.jsx("th",{className:"py-1.5 font-medium",children:"Next reset"})]})}),e.jsx("tbody",{children:n.map(o=>e.jsxs("tr",{className:"border-t border-[var(--otari-line)]",children:[e.jsx("td",{className:"py-1.5 pr-4",children:e.jsx("code",{children:o.user_id??"—"})}),e.jsx("td",{className:"py-1.5 pr-4 text-[var(--otari-ink)]",children:D(o.previous_spend)}),e.jsx("td",{className:"py-1.5 pr-4 text-[var(--otari-muted)]",children:W(o.reset_at)}),e.jsx("td",{className:"py-1.5 text-[var(--otari-muted)]",children:W(o.next_reset_at)})]},o.id))})]})})}function ke({label:t,isPending:r,onConfirm:n}){const[o,l]=i.useState(!1);return o?e.jsxs("div",{className:"flex flex-col items-end gap-1.5 rounded-lg border border-amber-200 bg-amber-50 p-2 text-right",children:[e.jsxs("span",{className:"max-w-xs text-xs text-amber-800",children:["Delete ",e.jsx("strong",{children:t}),"? Users keep their spend but lose this limit. Cannot be undone."]}),e.jsxs("span",{className:"inline-flex gap-1",children:[e.jsx(x,{size:"sm",variant:"danger",isDisabled:r,onPress:n,children:"Delete permanently"}),e.jsx(x,{size:"sm",variant:"ghost",isDisabled:r,onPress:()=>l(!1),children:"Cancel"})]})]}):e.jsx(x,{size:"sm",variant:"danger-soft",onPress:()=>l(!0),children:"Delete"})}const Ue=t=>t.budget_id;function J(t){return t.split("-")[0]}function z(t){return t.name??J(t.budget_id)}function Ke(){const t=ne(),r=re(),n=le(),o=ie(),l=oe(),g=de(),[h,d]=i.useState(!1),[p,c]=i.useState(null),[m,s]=i.useState(null),[u,b]=i.useState(null),[S,v]=i.useState(null),[B,P]=i.useState(!1),f=he(),[A,C]=i.useState(!1),[Q,K]=i.useState(void 0),[X,T]=i.useState(!1),w=t.data??[],F=t.isLoading,y=w.find(a=>a.budget_id===p)??null,U=w.find(a=>a.budget_id===m)??null,L=!F&&w.length===0&&!h,Z=w.map(a=>a.budget_id),_=pe(f.selectedKeys,Z),ee=async()=>{T(!0),K(void 0);try{for(const a of _)await l.mutateAsync(a);f.clear(),C(!1)}catch(a){K(a)}finally{T(!1)}},te=i.useMemo(()=>[{id:"budget",header:"Budget",isRowHeader:!0,cell:a=>e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsx("span",{className:"font-medium text-[var(--otari-ink)]",children:a.name??e.jsx("span",{className:"text-[var(--otari-muted)]",children:"(unnamed)"})}),e.jsx(ue,{value:a.budget_id,label:"budget id",children:e.jsx("code",{className:"text-[11px] text-[var(--otari-muted)]",title:a.budget_id,children:J(a.budget_id)})})]})},{id:"limit",header:"Limit (per user)",cell:a=>a.max_budget===null?e.jsx("span",{className:"text-[var(--otari-muted)]",children:"Unlimited"}):D(a.max_budget)},{id:"reset",header:"Reset",cell:a=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:De(a.budget_duration_sec)})},{id:"users",header:"Users",cell:a=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:a.user_count})},{id:"usage",header:"Usage",cell:a=>e.jsx(Ae,{budget:a})},{id:"actions",header:"Actions",align:"end",cell:a=>e.jsxs("div",{className:"flex items-center justify-end gap-1.5",children:[e.jsx(x,{size:"sm",variant:"ghost",onPress:()=>s(N=>N===a.budget_id?null:a.budget_id),children:m===a.budget_id?"Hide history":"History"}),e.jsx(x,{size:"sm",variant:"ghost",onPress:()=>{d(!1),c(a.budget_id)},children:"Edit"}),e.jsx(ke,{label:z(a),isPending:l.isPending,onConfirm:()=>l.mutate(a.budget_id)})]})}],[m,l.isPending,l.mutate]),V=async(a,N)=>{P(!0),b(null);const I=await Promise.allSettled(N.map(M=>g.mutateAsync({id:M,body:{budget_id:a}})));P(!1);const R=I.flatMap((M,ae)=>M.status==="rejected"?[N[ae]]:[]);if(R.length>0){v({budgetId:a,userIds:R}),b(new Error(`Budget created, but could not assign it to: ${R.join(", ")}. Retry to try again.`));return}v(null),d(!1)},se=(a,N)=>{if(S){V(S.budgetId,S.userIds);return}b(null),n.mutate(a,{onSuccess:async I=>{if(N.length>0){await V(I.budget_id,N);return}d(!1)}})};return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(ce,{title:"Budgets",description:"Define spending limits and reset schedules. Assign a budget to users to enforce it.",action:h||L?null:e.jsx(x,{variant:"primary",onPress:()=>{c(null),b(null),v(null),d(!0)},children:"Create budget"})}),e.jsx(H,{error:t.error??n.error??o.error??l.error??g.error}),e.jsx(me,{children:"Assign a budget to users when you create it, or later from the Users page. Each row’s usage aggregates the spend of the users currently on that budget."}),L?e.jsx(xe,{title:"No budgets yet",description:"A budget caps how much a user may spend and, optionally, resets that spend on a schedule. Create one, then assign it to users to enforce a limit.",actionLabel:"Create your first budget",onAction:()=>{c(null),b(null),v(null),d(!0)}}):null,h?e.jsx(G,{title:"Create budget",submitLabel:S?"Retry assignments":"Create budget",initial:{name:null,max_budget:null,budget_duration_sec:null},error:n.error??u,isPending:n.isPending||B,assignUsers:r.data??[],onSubmit:se,onClose:()=>{b(null),v(null),d(!1)}}):null,y?e.jsx(G,{title:`Edit budget ${z(y)}`,submitLabel:"Save changes",initial:{name:y.name,max_budget:y.max_budget,budget_duration_sec:y.budget_duration_sec},error:o.error,isPending:o.isPending,onSubmit:a=>o.mutate({id:y.budget_id,body:a},{onSuccess:()=>c(null)}),onClose:()=>c(null)},y.budget_id):null,_.length>0?e.jsx(fe,{selectedCount:_.length,allMatching:!1,matchingTotal:null,canSelectAllMatching:!1,onSelectAllMatching:()=>{},onClear:f.clear,children:e.jsx(x,{size:"sm",variant:"danger",onPress:()=>C(!0),children:"Delete"})}):null,L?null:e.jsx(je,{ariaLabel:"Budgets",columns:te,rows:w,getRowKey:Ue,isLoading:F,emptyContent:"No budgets yet. Create one to cap spending.",selectionMode:"multiple",selectedKeys:f.selectedKeys,onSelectionChange:f.onSelectionChange}),U?e.jsx(k,{children:e.jsxs(k.Content,{className:"p-0",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-[var(--otari-line)] px-4 py-2",children:[e.jsxs("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:["Reset history — ",z(U)]}),e.jsx(x,{size:"sm",variant:"ghost",onPress:()=>s(null),children:"Close"})]}),e.jsx(Ee,{budgetId:U.budget_id})]})}):null,e.jsx(be,{isOpen:A,onOpenChange:C,heading:"Delete budgets",body:`Delete ${_.length} ${_.length===1?"budget":"budgets"}? Users on ${_.length===1?"it":"them"} will no longer be capped.`,confirmLabel:"Delete",isPending:X,error:Q,onConfirm:ee})]})}export{Ke as BudgetsPage}; diff --git a/src/gateway/static/dashboard/assets/ConfirmDialog-mbnZRETP.js b/src/gateway/static/dashboard/assets/ConfirmDialog-Dt_8xaSM.js similarity index 92% rename from src/gateway/static/dashboard/assets/ConfirmDialog-mbnZRETP.js rename to src/gateway/static/dashboard/assets/ConfirmDialog-Dt_8xaSM.js index b01d877a8..5587aca80 100644 --- a/src/gateway/static/dashboard/assets/ConfirmDialog-mbnZRETP.js +++ b/src/gateway/static/dashboard/assets/ConfirmDialog-Dt_8xaSM.js @@ -1 +1 @@ -import{j as r}from"./tanstack-query-1t81HyiD.js";import{E as j}from"./index-D-R1nuKP.js";import{A as e,B as l}from"./heroui-DhloIxuc.js";function p({isOpen:s,onOpenChange:a,heading:n,body:o,confirmLabel:t,confirmVariant:c="danger",isPending:i,error:d,onConfirm:x}){return r.jsx(e,{isOpen:s,onOpenChange:a,children:s?r.jsx(e.Backdrop,{children:r.jsx(e.Container,{placement:"center",size:"md",children:r.jsxs(e.Dialog,{children:[r.jsx(e.Header,{children:r.jsx(e.Heading,{children:n})}),r.jsxs(e.Body,{className:"flex flex-col gap-4",children:[r.jsx("div",{className:"text-sm text-[var(--otari-muted)]",children:o}),r.jsx(j,{error:d})]}),r.jsxs(e.Footer,{children:[r.jsx(l,{variant:"ghost",isDisabled:i,onPress:()=>a(!1),children:"Cancel"}),r.jsx(l,{variant:c,isPending:i,onPress:x,children:t})]})]})})}):null})}export{p as C}; +import{j as r}from"./tanstack-query-1t81HyiD.js";import{E as j}from"./index-Dit1BUBh.js";import{A as e,B as l}from"./heroui-DhloIxuc.js";function p({isOpen:s,onOpenChange:a,heading:n,body:o,confirmLabel:t,confirmVariant:c="danger",isPending:i,error:d,onConfirm:x}){return r.jsx(e,{isOpen:s,onOpenChange:a,children:s?r.jsx(e.Backdrop,{children:r.jsx(e.Container,{placement:"center",size:"md",children:r.jsxs(e.Dialog,{children:[r.jsx(e.Header,{children:r.jsx(e.Heading,{children:n})}),r.jsxs(e.Body,{className:"flex flex-col gap-4",children:[r.jsx("div",{className:"text-sm text-[var(--otari-muted)]",children:o}),r.jsx(j,{error:d})]}),r.jsxs(e.Footer,{children:[r.jsx(l,{variant:"ghost",isDisabled:i,onPress:()=>a(!1),children:"Cancel"}),r.jsx(l,{variant:c,isPending:i,onPress:x,children:t})]})]})})}):null})}export{p as C}; diff --git a/src/gateway/static/dashboard/assets/DocsPage-D53o1bCm.js b/src/gateway/static/dashboard/assets/DocsPage-AglHrVWY.js similarity index 99% rename from src/gateway/static/dashboard/assets/DocsPage-D53o1bCm.js rename to src/gateway/static/dashboard/assets/DocsPage-AglHrVWY.js index 678a33543..eb0424b14 100644 --- a/src/gateway/static/dashboard/assets/DocsPage-D53o1bCm.js +++ b/src/gateway/static/dashboard/assets/DocsPage-AglHrVWY.js @@ -1,4 +1,4 @@ -import{j as re}from"./tanstack-query-1t81HyiD.js";import{P as gi}from"./index-D-R1nuKP.js";import{d as ct}from"./heroui-DhloIxuc.js";import{g as nr}from"./react-dgEcD0HR.js";function yi(e,t){const n={};return(e[e.length-1]===""?[...e,""]:e).join((n.padRight?" ":"")+","+(n.padLeft===!1?"":" ")).trim()}const ki=/^[$_\p{ID_Start}][$_\u{200C}\u{200D}\p{ID_Continue}]*$/u,xi=/^[$_\p{ID_Start}][-$_\u{200C}\u{200D}\p{ID_Continue}]*$/u,bi={};function ht(e,t){return(bi.jsx?xi:ki).test(e)}const wi=/[ \t\n\f\r]/g;function Si(e){return typeof e=="object"?e.type==="text"?ft(e.value):!1:ft(e)}function ft(e){return e.replace(wi,"")===""}class Ke{constructor(t,n,r){this.normal=n,this.property=t,r&&(this.space=r)}}Ke.prototype.normal={};Ke.prototype.property={};Ke.prototype.space=void 0;function tr(e,t){const n={},r={};for(const i of e)Object.assign(n,i.property),Object.assign(r,i.normal);return new Ke(n,r,t)}function zn(e){return e.toLowerCase()}class ee{constructor(t,n){this.attribute=n,this.property=t}}ee.prototype.attribute="";ee.prototype.booleanish=!1;ee.prototype.boolean=!1;ee.prototype.commaOrSpaceSeparated=!1;ee.prototype.commaSeparated=!1;ee.prototype.defined=!1;ee.prototype.mustUseProperty=!1;ee.prototype.number=!1;ee.prototype.overloadedBoolean=!1;ee.prototype.property="";ee.prototype.spaceSeparated=!1;ee.prototype.space=void 0;let Ci=0;const D=Ie(),Y=Ie(),_n=Ie(),v=Ie(),$=Ie(),Ee=Ie(),te=Ie();function Ie(){return 2**++Ci}const Dn=Object.freeze(Object.defineProperty({__proto__:null,boolean:D,booleanish:Y,commaOrSpaceSeparated:te,commaSeparated:Ee,number:v,overloadedBoolean:_n,spaceSeparated:$},Symbol.toStringTag,{value:"Module"})),pn=Object.keys(Dn);class qn extends ee{constructor(t,n,r,i){let o=-1;if(super(t,n),pt(this,"space",i),typeof r=="number")for(;++o4&&n.slice(0,4)==="data"&&Ai.test(t)){if(t.charAt(4)==="-"){const o=t.slice(5).replace(dt,_i);r="data"+o.charAt(0).toUpperCase()+o.slice(1)}else{const o=t.slice(4);if(!dt.test(o)){let l=o.replace(Ii,zi);l.charAt(0)!=="-"&&(l="-"+l),t="data"+l}}i=qn}return new i(r,t)}function zi(e){return"-"+e.toLowerCase()}function _i(e){return e.charAt(1).toUpperCase()}const Di=tr([rr,vi,or,ar,sr],"html"),Hn=tr([rr,Ei,or,ar,sr],"svg");function Li(e){return e.join(" ").trim()}var _e={},dn,mt;function Ri(){if(mt)return dn;mt=1;var e=/\/\*[^*]*\*+([^/*][^*]*\*+)*\//g,t=/\n/g,n=/^\s*/,r=/^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/,i=/^:\s*/,o=/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/,l=/^[;\s]*/,a=/^\s+|\s+$/g,s=` +import{j as re}from"./tanstack-query-1t81HyiD.js";import{P as gi}from"./index-Dit1BUBh.js";import{d as ct}from"./heroui-DhloIxuc.js";import{g as nr}from"./react-dgEcD0HR.js";function yi(e,t){const n={};return(e[e.length-1]===""?[...e,""]:e).join((n.padRight?" ":"")+","+(n.padLeft===!1?"":" ")).trim()}const ki=/^[$_\p{ID_Start}][$_\u{200C}\u{200D}\p{ID_Continue}]*$/u,xi=/^[$_\p{ID_Start}][-$_\u{200C}\u{200D}\p{ID_Continue}]*$/u,bi={};function ht(e,t){return(bi.jsx?xi:ki).test(e)}const wi=/[ \t\n\f\r]/g;function Si(e){return typeof e=="object"?e.type==="text"?ft(e.value):!1:ft(e)}function ft(e){return e.replace(wi,"")===""}class Ke{constructor(t,n,r){this.normal=n,this.property=t,r&&(this.space=r)}}Ke.prototype.normal={};Ke.prototype.property={};Ke.prototype.space=void 0;function tr(e,t){const n={},r={};for(const i of e)Object.assign(n,i.property),Object.assign(r,i.normal);return new Ke(n,r,t)}function zn(e){return e.toLowerCase()}class ee{constructor(t,n){this.attribute=n,this.property=t}}ee.prototype.attribute="";ee.prototype.booleanish=!1;ee.prototype.boolean=!1;ee.prototype.commaOrSpaceSeparated=!1;ee.prototype.commaSeparated=!1;ee.prototype.defined=!1;ee.prototype.mustUseProperty=!1;ee.prototype.number=!1;ee.prototype.overloadedBoolean=!1;ee.prototype.property="";ee.prototype.spaceSeparated=!1;ee.prototype.space=void 0;let Ci=0;const D=Ie(),Y=Ie(),_n=Ie(),v=Ie(),$=Ie(),Ee=Ie(),te=Ie();function Ie(){return 2**++Ci}const Dn=Object.freeze(Object.defineProperty({__proto__:null,boolean:D,booleanish:Y,commaOrSpaceSeparated:te,commaSeparated:Ee,number:v,overloadedBoolean:_n,spaceSeparated:$},Symbol.toStringTag,{value:"Module"})),pn=Object.keys(Dn);class qn extends ee{constructor(t,n,r,i){let o=-1;if(super(t,n),pt(this,"space",i),typeof r=="number")for(;++o4&&n.slice(0,4)==="data"&&Ai.test(t)){if(t.charAt(4)==="-"){const o=t.slice(5).replace(dt,_i);r="data"+o.charAt(0).toUpperCase()+o.slice(1)}else{const o=t.slice(4);if(!dt.test(o)){let l=o.replace(Ii,zi);l.charAt(0)!=="-"&&(l="-"+l),t="data"+l}}i=qn}return new i(r,t)}function zi(e){return"-"+e.toLowerCase()}function _i(e){return e.charAt(1).toUpperCase()}const Di=tr([rr,vi,or,ar,sr],"html"),Hn=tr([rr,Ei,or,ar,sr],"svg");function Li(e){return e.join(" ").trim()}var _e={},dn,mt;function Ri(){if(mt)return dn;mt=1;var e=/\/\*[^*]*\*+([^/*][^*]*\*+)*\//g,t=/\n/g,n=/^\s*/,r=/^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/,i=/^:\s*/,o=/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/,l=/^[;\s]*/,a=/^\s+|\s+$/g,s=` `,u="/",h="*",c="",p="comment",f="declaration";function g(S,y){if(typeof S!="string")throw new TypeError("First argument must be a string");if(!S)return[];y=y||{};var E=1,C=1;function R(_){var I=_.match(t);I&&(E+=I.length);var U=_.lastIndexOf(s);C=~U?_.length-U:C+_.length}function F(){var _={line:E,column:C};return function(I){return I.position=new b(_),j(),I}}function b(_){this.start=_,this.end={line:E,column:C},this.source=y.source}b.prototype.content=S;function M(_){var I=new Error(y.source+":"+E+":"+C+": "+_);if(I.reason=_,I.filename=y.source,I.line=E,I.column=C,I.source=S,!y.silent)throw I}function q(_){var I=_.exec(S);if(I){var U=I[0];return R(U),S=S.slice(U.length),I}}function j(){q(n)}function k(_){var I;for(_=_||[];I=A();)I!==!1&&_.push(I);return _}function A(){var _=F();if(!(u!=S.charAt(0)||h!=S.charAt(1))){for(var I=2;c!=S.charAt(I)&&(h!=S.charAt(I)||u!=S.charAt(I+1));)++I;if(I+=2,c===S.charAt(I-1))return M("End of comment missing");var U=S.slice(2,I-2);return C+=2,R(U),S=S.slice(I),C+=2,_({type:p,comment:U})}}function P(){var _=F(),I=q(r);if(I){if(A(),!q(i))return M("property missing ':'");var U=q(o),K=_({type:f,property:w(I[0].replace(e,c)),value:U?w(U[0].replace(e,c)):c});return q(l),K}}function H(){var _=[];k(_);for(var I;I=P();)I!==!1&&(_.push(I),k(_));return _}return j(),H()}function w(S){return S?S.replace(a,c):c}return dn=g,dn}var gt;function Fi(){if(gt)return _e;gt=1;var e=_e&&_e.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(_e,"__esModule",{value:!0}),_e.default=n;const t=e(Ri());function n(r,i){let o=null;if(!r||typeof r!="string")return o;const l=(0,t.default)(r),a=typeof i=="function";return l.forEach(s=>{if(s.type!=="declaration")return;const{property:u,value:h}=s;a?i(u,h,s):h&&(o=o||{},o[u]=h)}),o}return _e}var Be={},yt;function Oi(){if(yt)return Be;yt=1,Object.defineProperty(Be,"__esModule",{value:!0}),Be.camelCase=void 0;var e=/^--[a-zA-Z0-9_-]+$/,t=/-([a-z])/g,n=/^[^-]+$/,r=/^-(webkit|moz|ms|o|khtml)-/,i=/^-(ms)-/,o=function(u){return!u||n.test(u)||e.test(u)},l=function(u,h){return h.toUpperCase()},a=function(u,h){return"".concat(h,"-")},s=function(u,h){return h===void 0&&(h={}),o(u)?u:(u=u.toLowerCase(),h.reactCompat?u=u.replace(i,a):u=u.replace(r,a),u.replace(t,l))};return Be.camelCase=s,Be}var je,kt;function Mi(){if(kt)return je;kt=1;var e=je&&je.__importDefault||function(i){return i&&i.__esModule?i:{default:i}},t=e(Fi()),n=Oi();function r(i,o){var l={};return!i||typeof i!="string"||(0,t.default)(i,function(a,s){a&&s&&(l[(0,n.camelCase)(a,o)]=s)}),l}return r.default=r,je=r,je}var Ni=Mi();const Bi=nr(Ni),ur=cr("end"),Un=cr("start");function cr(e){return t;function t(n){const r=n&&n.position&&n.position[e]||{};if(typeof r.line=="number"&&r.line>0&&typeof r.column=="number"&&r.column>0)return{line:r.line,column:r.column,offset:typeof r.offset=="number"&&r.offset>-1?r.offset:void 0}}}function ji(e){const t=Un(e),n=ur(e);if(t&&n)return{start:t,end:n}}function Ue(e){return!e||typeof e!="object"?"":"position"in e||"type"in e?xt(e.position):"start"in e||"end"in e?xt(e):"line"in e||"column"in e?Ln(e):""}function Ln(e){return bt(e&&e.line)+":"+bt(e&&e.column)}function xt(e){return Ln(e&&e.start)+"-"+Ln(e&&e.end)}function bt(e){return e&&typeof e=="number"?e:1}class G extends Error{constructor(t,n,r){super(),typeof n=="string"&&(r=n,n=void 0);let i="",o={},l=!1;if(n&&("line"in n&&"column"in n?o={place:n}:"start"in n&&"end"in n?o={place:n}:"type"in n?o={ancestors:[n],place:n.position}:o={...n}),typeof t=="string"?i=t:!o.cause&&t&&(l=!0,i=t.message,o.cause=t),!o.ruleId&&!o.source&&typeof r=="string"){const s=r.indexOf(":");s===-1?o.ruleId=r:(o.source=r.slice(0,s),o.ruleId=r.slice(s+1))}if(!o.place&&o.ancestors&&o.ancestors){const s=o.ancestors[o.ancestors.length-1];s&&(o.place=s.position)}const a=o.place&&"start"in o.place?o.place.start:o.place;this.ancestors=o.ancestors||void 0,this.cause=o.cause||void 0,this.column=a?a.column:void 0,this.fatal=void 0,this.file="",this.message=i,this.line=a?a.line:void 0,this.name=Ue(o.place)||"1:1",this.place=o.place||void 0,this.reason=this.message,this.ruleId=o.ruleId||void 0,this.source=o.source||void 0,this.stack=l&&o.cause&&typeof o.cause.stack=="string"?o.cause.stack:"",this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}}G.prototype.file="";G.prototype.name="";G.prototype.reason="";G.prototype.message="";G.prototype.stack="";G.prototype.column=void 0;G.prototype.line=void 0;G.prototype.ancestors=void 0;G.prototype.cause=void 0;G.prototype.fatal=void 0;G.prototype.place=void 0;G.prototype.ruleId=void 0;G.prototype.source=void 0;const Vn={}.hasOwnProperty,qi=new Map,Hi=/[A-Z]/g,Ui=new Set(["table","tbody","thead","tfoot","tr"]),Vi=new Set(["td","th"]),hr="https://github.com/syntax-tree/hast-util-to-jsx-runtime";function $i(e,t){if(!t||t.Fragment===void 0)throw new TypeError("Expected `Fragment` in options");const n=t.filePath||void 0;let r;if(t.development){if(typeof t.jsxDEV!="function")throw new TypeError("Expected `jsxDEV` in options when `development: true`");r=Zi(n,t.jsxDEV)}else{if(typeof t.jsx!="function")throw new TypeError("Expected `jsx` in production options");if(typeof t.jsxs!="function")throw new TypeError("Expected `jsxs` in production options");r=Ji(n,t.jsx,t.jsxs)}const i={Fragment:t.Fragment,ancestors:[],components:t.components||{},create:r,elementAttributeNameCase:t.elementAttributeNameCase||"react",evaluater:t.createEvaluater?t.createEvaluater():void 0,filePath:n,ignoreInvalidStyle:t.ignoreInvalidStyle||!1,passKeys:t.passKeys!==!1,passNode:t.passNode||!1,schema:t.space==="svg"?Hn:Di,stylePropertyNameCase:t.stylePropertyNameCase||"dom",tableCellAlignToStyle:t.tableCellAlignToStyle!==!1},o=fr(i,e,void 0);return o&&typeof o!="string"?o:i.create(e,i.Fragment,{children:o||void 0},void 0)}function fr(e,t,n){if(t.type==="element")return Wi(e,t,n);if(t.type==="mdxFlowExpression"||t.type==="mdxTextExpression")return Yi(e,t);if(t.type==="mdxJsxFlowElement"||t.type==="mdxJsxTextElement")return Qi(e,t,n);if(t.type==="mdxjsEsm")return Ki(e,t);if(t.type==="root")return Xi(e,t,n);if(t.type==="text")return Gi(e,t)}function Wi(e,t,n){const r=e.schema;let i=r;t.tagName.toLowerCase()==="svg"&&r.space==="html"&&(i=Hn,e.schema=i),e.ancestors.push(t);const o=dr(e,t.tagName,!1),l=el(e,t);let a=Wn(e,t);return Ui.has(t.tagName)&&(a=a.filter(function(s){return typeof s=="string"?!Si(s):!0})),pr(e,l,o,t),$n(l,a),e.ancestors.pop(),e.schema=r,e.create(t,o,l,n)}function Yi(e,t){if(t.data&&t.data.estree&&e.evaluater){const r=t.data.estree.body[0];return r.type,e.evaluater.evaluateExpression(r.expression)}We(e,t.position)}function Ki(e,t){if(t.data&&t.data.estree&&e.evaluater)return e.evaluater.evaluateProgram(t.data.estree);We(e,t.position)}function Qi(e,t,n){const r=e.schema;let i=r;t.name==="svg"&&r.space==="html"&&(i=Hn,e.schema=i),e.ancestors.push(t);const o=t.name===null?e.Fragment:dr(e,t.name,!0),l=nl(e,t),a=Wn(e,t);return pr(e,l,o,t),$n(l,a),e.ancestors.pop(),e.schema=r,e.create(t,o,l,n)}function Xi(e,t,n){const r={};return $n(r,Wn(e,t)),e.create(t,e.Fragment,r,n)}function Gi(e,t){return t.value}function pr(e,t,n,r){typeof n!="string"&&n!==e.Fragment&&e.passNode&&(t.node=r)}function $n(e,t){if(t.length>0){const n=t.length>1?t:t[0];n&&(e.children=n)}}function Ji(e,t,n){return r;function r(i,o,l,a){const u=Array.isArray(l.children)?n:t;return a?u(o,l,a):u(o,l)}}function Zi(e,t){return n;function n(r,i,o,l){const a=Array.isArray(o.children),s=Un(r);return t(i,o,l,a,{columnNumber:s?s.column-1:void 0,fileName:e,lineNumber:s?s.line:void 0},void 0)}}function el(e,t){const n={};let r,i;for(i in t.properties)if(i!=="children"&&Vn.call(t.properties,i)){const o=tl(e,i,t.properties[i]);if(o){const[l,a]=o;e.tableCellAlignToStyle&&l==="align"&&typeof a=="string"&&Vi.has(t.tagName)?r=a:n[l]=a}}if(r){const o=n.style||(n.style={});o[e.stylePropertyNameCase==="css"?"text-align":"textAlign"]=r}return n}function nl(e,t){const n={};for(const r of t.attributes)if(r.type==="mdxJsxExpressionAttribute")if(r.data&&r.data.estree&&e.evaluater){const o=r.data.estree.body[0];o.type;const l=o.expression;l.type;const a=l.properties[0];a.type,Object.assign(n,e.evaluater.evaluateExpression(a.argument))}else We(e,t.position);else{const i=r.name;let o;if(r.value&&typeof r.value=="object")if(r.value.data&&r.value.data.estree&&e.evaluater){const a=r.value.data.estree.body[0];a.type,o=e.evaluater.evaluateExpression(a.expression)}else We(e,t.position);else o=r.value===null?!0:r.value;n[i]=o}return n}function Wn(e,t){const n=[];let r=-1;const i=e.passKeys?new Map:qi;for(;++ri?0:i+t:t=t>i?i:t,n=n>0?n:0,r.length<1e4)l=Array.from(r),l.unshift(t,n),e.splice(...l);else for(n&&e.splice(t,n);o0?(ie(e,e.length,0,t),e):t}const Ct={}.hasOwnProperty;function gr(e){const t={};let n=-1;for(;++n13&&n<32||n>126&&n<160||n>55295&&n<57344||n>64975&&n<65008||(n&65535)===65535||(n&65535)===65534||n>1114111?"�":String.fromCodePoint(n)}function ce(e){return e.replace(/[\t\n\r ]+/g," ").replace(/^ | $/g,"").toLowerCase().toUpperCase()}const J=be(/[A-Za-z]/),X=be(/[\dA-Za-z]/),hl=be(/[#-'*+\--9=?A-Z^-~]/);function rn(e){return e!==null&&(e<32||e===127)}const Rn=be(/\d/),fl=be(/[\dA-Fa-f]/),pl=be(/[!-/:-@[-`{-~]/);function z(e){return e!==null&&e<-2}function W(e){return e!==null&&(e<0||e===32)}function O(e){return e===-2||e===-1||e===32}const sn=be(new RegExp("\\p{P}|\\p{S}","u")),Te=be(/\s/);function be(e){return t;function t(n){return n!==null&&n>-1&&e.test(String.fromCharCode(n))}}function Fe(e){const t=[];let n=-1,r=0,i=0;for(;++n55295&&o<57344){const a=e.charCodeAt(n+1);o<56320&&a>56319&&a<57344?(l=String.fromCharCode(o,a),i=1):l="�"}else l=String.fromCharCode(o);l&&(t.push(e.slice(r,n),encodeURIComponent(l)),r=n+i+1,l=""),i&&(n+=i,i=0)}return t.join("")+e.slice(r)}function B(e,t,n,r){const i=r?r-1:Number.POSITIVE_INFINITY;let o=0;return l;function l(s){return O(s)?(e.enter(n),a(s)):t(s)}function a(s){return O(s)&&o++l))return;const M=t.events.length;let q=M,j,k;for(;q--;)if(t.events[q][0]==="exit"&&t.events[q][1].type==="chunkFlow"){if(j){k=t.events[q][1].end;break}j=!0}for(y(r),b=M;bC;){const F=n[R];t.containerState=F[1],F[0].exit.call(t,e)}n.length=C}function E(){i.write([null]),o=void 0,i=void 0,t.containerState._closeFlow=void 0}}function kl(e,t,n){return B(e,e.attempt(this.parser.constructs.document,t,n),"linePrefix",this.parser.constructs.disable.null.includes("codeIndented")?void 0:4)}function Le(e){if(e===null||W(e)||Te(e))return 1;if(sn(e))return 2}function un(e,t,n){const r=[];let i=-1;for(;++i1&&e[n][1].end.offset-e[n][1].start.offset>1?2:1;const c={...e[r][1].end},p={...e[n][1].start};Et(c,-s),Et(p,s),l={type:s>1?"strongSequence":"emphasisSequence",start:c,end:{...e[r][1].end}},a={type:s>1?"strongSequence":"emphasisSequence",start:{...e[n][1].start},end:p},o={type:s>1?"strongText":"emphasisText",start:{...e[r][1].end},end:{...e[n][1].start}},i={type:s>1?"strong":"emphasis",start:{...l.start},end:{...a.end}},e[r][1].end={...l.start},e[n][1].start={...a.end},u=[],e[r][1].end.offset-e[r][1].start.offset&&(u=le(u,[["enter",e[r][1],t],["exit",e[r][1],t]])),u=le(u,[["enter",i,t],["enter",l,t],["exit",l,t],["enter",o,t]]),u=le(u,un(t.parser.constructs.insideSpan.null,e.slice(r+1,n),t)),u=le(u,[["exit",o,t],["enter",a,t],["exit",a,t],["exit",i,t]]),e[n][1].end.offset-e[n][1].start.offset?(h=2,u=le(u,[["enter",e[n][1],t],["exit",e[n][1],t]])):h=0,ie(e,r-1,n-r+3,u),n=r+u.length-h-2;break}}for(n=-1;++n0&&O(b)?B(e,E,"linePrefix",o+1)(b):E(b)}function E(b){return b===null||z(b)?e.check(Tt,w,R)(b):(e.enter("codeFlowValue"),C(b))}function C(b){return b===null||z(b)?(e.exit("codeFlowValue"),E(b)):(e.consume(b),C)}function R(b){return e.exit("codeFenced"),t(b)}function F(b,M,q){let j=0;return k;function k(I){return b.enter("lineEnding"),b.consume(I),b.exit("lineEnding"),A}function A(I){return b.enter("codeFencedFence"),O(I)?B(b,P,"linePrefix",r.parser.constructs.disable.null.includes("codeIndented")?void 0:4)(I):P(I)}function P(I){return I===a?(b.enter("codeFencedFenceSequence"),H(I)):q(I)}function H(I){return I===a?(j++,b.consume(I),H):j>=l?(b.exit("codeFencedFenceSequence"),O(I)?B(b,_,"whitespace")(I):_(I)):q(I)}function _(I){return I===null||z(I)?(b.exit("codeFencedFence"),M(I)):q(I)}}}function zl(e,t,n){const r=this;return i;function i(l){return l===null?n(l):(e.enter("lineEnding"),e.consume(l),e.exit("lineEnding"),o)}function o(l){return r.parser.lazy[r.now().line]?n(l):t(l)}}const gn={name:"codeIndented",tokenize:Dl},_l={partial:!0,tokenize:Ll};function Dl(e,t,n){const r=this;return i;function i(u){return e.enter("codeIndented"),B(e,o,"linePrefix",5)(u)}function o(u){const h=r.events[r.events.length-1];return h&&h[1].type==="linePrefix"&&h[2].sliceSerialize(h[1],!0).length>=4?l(u):n(u)}function l(u){return u===null?s(u):z(u)?e.attempt(_l,l,s)(u):(e.enter("codeFlowValue"),a(u))}function a(u){return u===null||z(u)?(e.exit("codeFlowValue"),l(u)):(e.consume(u),a)}function s(u){return e.exit("codeIndented"),t(u)}}function Ll(e,t,n){const r=this;return i;function i(l){return r.parser.lazy[r.now().line]?n(l):z(l)?(e.enter("lineEnding"),e.consume(l),e.exit("lineEnding"),i):B(e,o,"linePrefix",5)(l)}function o(l){const a=r.events[r.events.length-1];return a&&a[1].type==="linePrefix"&&a[2].sliceSerialize(a[1],!0).length>=4?t(l):z(l)?i(l):n(l)}}const Rl={name:"codeText",previous:Ol,resolve:Fl,tokenize:Ml};function Fl(e){let t=e.length-4,n=3,r,i;if((e[n][1].type==="lineEnding"||e[n][1].type==="space")&&(e[t][1].type==="lineEnding"||e[t][1].type==="space")){for(r=n;++r=this.left.length+this.right.length)throw new RangeError("Cannot access index `"+t+"` in a splice buffer of size `"+(this.left.length+this.right.length)+"`");return tthis.left.length?this.right.slice(this.right.length-r+this.left.length,this.right.length-t+this.left.length).reverse():this.left.slice(t).concat(this.right.slice(this.right.length-r+this.left.length).reverse())}splice(t,n,r){const i=n||0;this.setCursor(Math.trunc(t));const o=this.right.splice(this.right.length-i,Number.POSITIVE_INFINITY);return r&&qe(this.left,r),o.reverse()}pop(){return this.setCursor(Number.POSITIVE_INFINITY),this.left.pop()}push(t){this.setCursor(Number.POSITIVE_INFINITY),this.left.push(t)}pushMany(t){this.setCursor(Number.POSITIVE_INFINITY),qe(this.left,t)}unshift(t){this.setCursor(0),this.right.push(t)}unshiftMany(t){this.setCursor(0),qe(this.right,t.reverse())}setCursor(t){if(!(t===this.left.length||t>this.left.length&&this.right.length===0||t<0&&this.left.length===0))if(t=4?t(l):e.interrupt(r.parser.constructs.flow,n,t)(l)}}function Sr(e,t,n,r,i,o,l,a,s){const u=s||Number.POSITIVE_INFINITY;let h=0;return c;function c(y){return y===60?(e.enter(r),e.enter(i),e.enter(o),e.consume(y),e.exit(o),p):y===null||y===32||y===41||rn(y)?n(y):(e.enter(r),e.enter(l),e.enter(a),e.enter("chunkString",{contentType:"string"}),w(y))}function p(y){return y===62?(e.enter(o),e.consume(y),e.exit(o),e.exit(i),e.exit(r),t):(e.enter(a),e.enter("chunkString",{contentType:"string"}),f(y))}function f(y){return y===62?(e.exit("chunkString"),e.exit(a),p(y)):y===null||y===60||z(y)?n(y):(e.consume(y),y===92?g:f)}function g(y){return y===60||y===62||y===92?(e.consume(y),f):f(y)}function w(y){return!h&&(y===null||y===41||W(y))?(e.exit("chunkString"),e.exit(a),e.exit(l),e.exit(r),t(y)):h999||f===null||f===91||f===93&&!s||f===94&&!a&&"_hiddenFootnoteSupport"in l.parser.constructs?n(f):f===93?(e.exit(o),e.enter(i),e.consume(f),e.exit(i),e.exit(r),t):z(f)?(e.enter("lineEnding"),e.consume(f),e.exit("lineEnding"),h):(e.enter("chunkString",{contentType:"string"}),c(f))}function c(f){return f===null||f===91||f===93||z(f)||a++>999?(e.exit("chunkString"),h(f)):(e.consume(f),s||(s=!O(f)),f===92?p:c)}function p(f){return f===91||f===92||f===93?(e.consume(f),a++,c):c(f)}}function vr(e,t,n,r,i,o){let l;return a;function a(p){return p===34||p===39||p===40?(e.enter(r),e.enter(i),e.consume(p),e.exit(i),l=p===40?41:p,s):n(p)}function s(p){return p===l?(e.enter(i),e.consume(p),e.exit(i),e.exit(r),t):(e.enter(o),u(p))}function u(p){return p===l?(e.exit(o),s(l)):p===null?n(p):z(p)?(e.enter("lineEnding"),e.consume(p),e.exit("lineEnding"),B(e,u,"linePrefix")):(e.enter("chunkString",{contentType:"string"}),h(p))}function h(p){return p===l||p===null||z(p)?(e.exit("chunkString"),u(p)):(e.consume(p),p===92?c:h)}function c(p){return p===l||p===92?(e.consume(p),h):h(p)}}function Ve(e,t){let n;return r;function r(i){return z(i)?(e.enter("lineEnding"),e.consume(i),e.exit("lineEnding"),n=!0,r):O(i)?B(e,r,n?"linePrefix":"lineSuffix")(i):t(i)}}const $l={name:"definition",tokenize:Yl},Wl={partial:!0,tokenize:Kl};function Yl(e,t,n){const r=this;let i;return o;function o(f){return e.enter("definition"),l(f)}function l(f){return Cr.call(r,e,a,n,"definitionLabel","definitionLabelMarker","definitionLabelString")(f)}function a(f){return i=ce(r.sliceSerialize(r.events[r.events.length-1][1]).slice(1,-1)),f===58?(e.enter("definitionMarker"),e.consume(f),e.exit("definitionMarker"),s):n(f)}function s(f){return W(f)?Ve(e,u)(f):u(f)}function u(f){return Sr(e,h,n,"definitionDestination","definitionDestinationLiteral","definitionDestinationLiteralMarker","definitionDestinationRaw","definitionDestinationString")(f)}function h(f){return e.attempt(Wl,c,c)(f)}function c(f){return O(f)?B(e,p,"whitespace")(f):p(f)}function p(f){return f===null||z(f)?(e.exit("definition"),r.parser.defined.push(i),t(f)):n(f)}}function Kl(e,t,n){return r;function r(a){return W(a)?Ve(e,i)(a):n(a)}function i(a){return vr(e,o,n,"definitionTitle","definitionTitleMarker","definitionTitleString")(a)}function o(a){return O(a)?B(e,l,"whitespace")(a):l(a)}function l(a){return a===null||z(a)?t(a):n(a)}}const Ql={name:"hardBreakEscape",tokenize:Xl};function Xl(e,t,n){return r;function r(o){return e.enter("hardBreakEscape"),e.consume(o),i}function i(o){return z(o)?(e.exit("hardBreakEscape"),t(o)):n(o)}}const Gl={name:"headingAtx",resolve:Jl,tokenize:Zl};function Jl(e,t){let n=e.length-2,r=3,i,o;return e[r][1].type==="whitespace"&&(r+=2),n-2>r&&e[n][1].type==="whitespace"&&(n-=2),e[n][1].type==="atxHeadingSequence"&&(r===n-1||n-4>r&&e[n-2][1].type==="whitespace")&&(n-=r+1===n?2:4),n>r&&(i={type:"atxHeadingText",start:e[r][1].start,end:e[n][1].end},o={type:"chunkText",start:e[r][1].start,end:e[n][1].end,contentType:"text"},ie(e,r,n-r+1,[["enter",i,t],["enter",o,t],["exit",o,t],["exit",i,t]])),e}function Zl(e,t,n){let r=0;return i;function i(h){return e.enter("atxHeading"),o(h)}function o(h){return e.enter("atxHeadingSequence"),l(h)}function l(h){return h===35&&r++<6?(e.consume(h),l):h===null||W(h)?(e.exit("atxHeadingSequence"),a(h)):n(h)}function a(h){return h===35?(e.enter("atxHeadingSequence"),s(h)):h===null||z(h)?(e.exit("atxHeading"),t(h)):O(h)?B(e,a,"whitespace")(h):(e.enter("atxHeadingText"),u(h))}function s(h){return h===35?(e.consume(h),s):(e.exit("atxHeadingSequence"),a(h))}function u(h){return h===null||h===35||W(h)?(e.exit("atxHeadingText"),a(h)):(e.consume(h),u)}}const eo=["address","article","aside","base","basefont","blockquote","body","caption","center","col","colgroup","dd","details","dialog","dir","div","dl","dt","fieldset","figcaption","figure","footer","form","frame","frameset","h1","h2","h3","h4","h5","h6","head","header","hr","html","iframe","legend","li","link","main","menu","menuitem","nav","noframes","ol","optgroup","option","p","param","search","section","summary","table","tbody","td","tfoot","th","thead","title","tr","track","ul"],At=["pre","script","style","textarea"],no={concrete:!0,name:"htmlFlow",resolveTo:io,tokenize:lo},to={partial:!0,tokenize:ao},ro={partial:!0,tokenize:oo};function io(e){let t=e.length;for(;t--&&!(e[t][0]==="enter"&&e[t][1].type==="htmlFlow"););return t>1&&e[t-2][1].type==="linePrefix"&&(e[t][1].start=e[t-2][1].start,e[t+1][1].start=e[t-2][1].start,e.splice(t-2,2)),e}function lo(e,t,n){const r=this;let i,o,l,a,s;return u;function u(m){return h(m)}function h(m){return e.enter("htmlFlow"),e.enter("htmlFlowData"),e.consume(m),c}function c(m){return m===33?(e.consume(m),p):m===47?(e.consume(m),o=!0,w):m===63?(e.consume(m),i=3,r.interrupt?t:d):J(m)?(e.consume(m),l=String.fromCharCode(m),S):n(m)}function p(m){return m===45?(e.consume(m),i=2,f):m===91?(e.consume(m),i=5,a=0,g):J(m)?(e.consume(m),i=4,r.interrupt?t:d):n(m)}function f(m){return m===45?(e.consume(m),r.interrupt?t:d):n(m)}function g(m){const se="CDATA[";return m===se.charCodeAt(a++)?(e.consume(m),a===se.length?r.interrupt?t:P:g):n(m)}function w(m){return J(m)?(e.consume(m),l=String.fromCharCode(m),S):n(m)}function S(m){if(m===null||m===47||m===62||W(m)){const se=m===47,we=l.toLowerCase();return!se&&!o&&At.includes(we)?(i=1,r.interrupt?t(m):P(m)):eo.includes(l.toLowerCase())?(i=6,se?(e.consume(m),y):r.interrupt?t(m):P(m)):(i=7,r.interrupt&&!r.parser.lazy[r.now().line]?n(m):o?E(m):C(m))}return m===45||X(m)?(e.consume(m),l+=String.fromCharCode(m),S):n(m)}function y(m){return m===62?(e.consume(m),r.interrupt?t:P):n(m)}function E(m){return O(m)?(e.consume(m),E):k(m)}function C(m){return m===47?(e.consume(m),k):m===58||m===95||J(m)?(e.consume(m),R):O(m)?(e.consume(m),C):k(m)}function R(m){return m===45||m===46||m===58||m===95||X(m)?(e.consume(m),R):F(m)}function F(m){return m===61?(e.consume(m),b):O(m)?(e.consume(m),F):C(m)}function b(m){return m===null||m===60||m===61||m===62||m===96?n(m):m===34||m===39?(e.consume(m),s=m,M):O(m)?(e.consume(m),b):q(m)}function M(m){return m===s?(e.consume(m),s=null,j):m===null||z(m)?n(m):(e.consume(m),M)}function q(m){return m===null||m===34||m===39||m===47||m===60||m===61||m===62||m===96||W(m)?F(m):(e.consume(m),q)}function j(m){return m===47||m===62||O(m)?C(m):n(m)}function k(m){return m===62?(e.consume(m),A):n(m)}function A(m){return m===null||z(m)?P(m):O(m)?(e.consume(m),A):n(m)}function P(m){return m===45&&i===2?(e.consume(m),U):m===60&&i===1?(e.consume(m),K):m===62&&i===4?(e.consume(m),ae):m===63&&i===3?(e.consume(m),d):m===93&&i===5?(e.consume(m),pe):z(m)&&(i===6||i===7)?(e.exit("htmlFlowData"),e.check(to,de,H)(m)):m===null||z(m)?(e.exit("htmlFlowData"),H(m)):(e.consume(m),P)}function H(m){return e.check(ro,_,de)(m)}function _(m){return e.enter("lineEnding"),e.consume(m),e.exit("lineEnding"),I}function I(m){return m===null||z(m)?H(m):(e.enter("htmlFlowData"),P(m))}function U(m){return m===45?(e.consume(m),d):P(m)}function K(m){return m===47?(e.consume(m),l="",oe):P(m)}function oe(m){if(m===62){const se=l.toLowerCase();return At.includes(se)?(e.consume(m),ae):P(m)}return J(m)&&l.length<8?(e.consume(m),l+=String.fromCharCode(m),oe):P(m)}function pe(m){return m===93?(e.consume(m),d):P(m)}function d(m){return m===62?(e.consume(m),ae):m===45&&i===2?(e.consume(m),d):P(m)}function ae(m){return m===null||z(m)?(e.exit("htmlFlowData"),de(m)):(e.consume(m),ae)}function de(m){return e.exit("htmlFlow"),t(m)}}function oo(e,t,n){const r=this;return i;function i(l){return z(l)?(e.enter("lineEnding"),e.consume(l),e.exit("lineEnding"),o):n(l)}function o(l){return r.parser.lazy[r.now().line]?n(l):t(l)}}function ao(e,t,n){return r;function r(i){return e.enter("lineEnding"),e.consume(i),e.exit("lineEnding"),e.attempt(Qe,t,n)}}const so={name:"htmlText",tokenize:uo};function uo(e,t,n){const r=this;let i,o,l;return a;function a(d){return e.enter("htmlText"),e.enter("htmlTextData"),e.consume(d),s}function s(d){return d===33?(e.consume(d),u):d===47?(e.consume(d),F):d===63?(e.consume(d),C):J(d)?(e.consume(d),q):n(d)}function u(d){return d===45?(e.consume(d),h):d===91?(e.consume(d),o=0,g):J(d)?(e.consume(d),E):n(d)}function h(d){return d===45?(e.consume(d),f):n(d)}function c(d){return d===null?n(d):d===45?(e.consume(d),p):z(d)?(l=c,K(d)):(e.consume(d),c)}function p(d){return d===45?(e.consume(d),f):c(d)}function f(d){return d===62?U(d):d===45?p(d):c(d)}function g(d){const ae="CDATA[";return d===ae.charCodeAt(o++)?(e.consume(d),o===ae.length?w:g):n(d)}function w(d){return d===null?n(d):d===93?(e.consume(d),S):z(d)?(l=w,K(d)):(e.consume(d),w)}function S(d){return d===93?(e.consume(d),y):w(d)}function y(d){return d===62?U(d):d===93?(e.consume(d),y):w(d)}function E(d){return d===null||d===62?U(d):z(d)?(l=E,K(d)):(e.consume(d),E)}function C(d){return d===null?n(d):d===63?(e.consume(d),R):z(d)?(l=C,K(d)):(e.consume(d),C)}function R(d){return d===62?U(d):C(d)}function F(d){return J(d)?(e.consume(d),b):n(d)}function b(d){return d===45||X(d)?(e.consume(d),b):M(d)}function M(d){return z(d)?(l=M,K(d)):O(d)?(e.consume(d),M):U(d)}function q(d){return d===45||X(d)?(e.consume(d),q):d===47||d===62||W(d)?j(d):n(d)}function j(d){return d===47?(e.consume(d),U):d===58||d===95||J(d)?(e.consume(d),k):z(d)?(l=j,K(d)):O(d)?(e.consume(d),j):U(d)}function k(d){return d===45||d===46||d===58||d===95||X(d)?(e.consume(d),k):A(d)}function A(d){return d===61?(e.consume(d),P):z(d)?(l=A,K(d)):O(d)?(e.consume(d),A):j(d)}function P(d){return d===null||d===60||d===61||d===62||d===96?n(d):d===34||d===39?(e.consume(d),i=d,H):z(d)?(l=P,K(d)):O(d)?(e.consume(d),P):(e.consume(d),_)}function H(d){return d===i?(e.consume(d),i=void 0,I):d===null?n(d):z(d)?(l=H,K(d)):(e.consume(d),H)}function _(d){return d===null||d===34||d===39||d===60||d===61||d===96?n(d):d===47||d===62||W(d)?j(d):(e.consume(d),_)}function I(d){return d===47||d===62||W(d)?j(d):n(d)}function U(d){return d===62?(e.consume(d),e.exit("htmlTextData"),e.exit("htmlText"),t):n(d)}function K(d){return e.exit("htmlTextData"),e.enter("lineEnding"),e.consume(d),e.exit("lineEnding"),oe}function oe(d){return O(d)?B(e,pe,"linePrefix",r.parser.constructs.disable.null.includes("codeIndented")?void 0:4)(d):pe(d)}function pe(d){return e.enter("htmlTextData"),l(d)}}const Qn={name:"labelEnd",resolveAll:po,resolveTo:mo,tokenize:go},co={tokenize:yo},ho={tokenize:ko},fo={tokenize:xo};function po(e){let t=-1;const n=[];for(;++t=3&&(u===null||z(u))?(e.exit("thematicBreak"),t(u)):n(u)}function s(u){return u===i?(e.consume(u),r++,s):(e.exit("thematicBreakSequence"),O(u)?B(e,a,"whitespace")(u):a(u))}}const Z={continuation:{tokenize:Po},exit:_o,name:"list",tokenize:Ao},To={partial:!0,tokenize:Do},Io={partial:!0,tokenize:zo};function Ao(e,t,n){const r=this,i=r.events[r.events.length-1];let o=i&&i[1].type==="linePrefix"?i[2].sliceSerialize(i[1],!0).length:0,l=0;return a;function a(f){const g=r.containerState.type||(f===42||f===43||f===45?"listUnordered":"listOrdered");if(g==="listUnordered"?!r.containerState.marker||f===r.containerState.marker:Rn(f)){if(r.containerState.type||(r.containerState.type=g,e.enter(g,{_container:!0})),g==="listUnordered")return e.enter("listItemPrefix"),f===42||f===45?e.check(tn,n,u)(f):u(f);if(!r.interrupt||f===49)return e.enter("listItemPrefix"),e.enter("listItemValue"),s(f)}return n(f)}function s(f){return Rn(f)&&++l<10?(e.consume(f),s):(!r.interrupt||l<2)&&(r.containerState.marker?f===r.containerState.marker:f===41||f===46)?(e.exit("listItemValue"),u(f)):n(f)}function u(f){return e.enter("listItemMarker"),e.consume(f),e.exit("listItemMarker"),r.containerState.marker=r.containerState.marker||f,e.check(Qe,r.interrupt?n:h,e.attempt(To,p,c))}function h(f){return r.containerState.initialBlankLine=!0,o++,p(f)}function c(f){return O(f)?(e.enter("listItemPrefixWhitespace"),e.consume(f),e.exit("listItemPrefixWhitespace"),p):n(f)}function p(f){return r.containerState.size=o+r.sliceSerialize(e.exit("listItemPrefix"),!0).length,t(f)}}function Po(e,t,n){const r=this;return r.containerState._closeFlow=void 0,e.check(Qe,i,o);function i(a){return r.containerState.furtherBlankLines=r.containerState.furtherBlankLines||r.containerState.initialBlankLine,B(e,t,"listItemIndent",r.containerState.size+1)(a)}function o(a){return r.containerState.furtherBlankLines||!O(a)?(r.containerState.furtherBlankLines=void 0,r.containerState.initialBlankLine=void 0,l(a)):(r.containerState.furtherBlankLines=void 0,r.containerState.initialBlankLine=void 0,e.attempt(Io,t,l)(a))}function l(a){return r.containerState._closeFlow=!0,r.interrupt=void 0,B(e,e.attempt(Z,t,n),"linePrefix",r.parser.constructs.disable.null.includes("codeIndented")?void 0:4)(a)}}function zo(e,t,n){const r=this;return B(e,i,"listItemIndent",r.containerState.size+1);function i(o){const l=r.events[r.events.length-1];return l&&l[1].type==="listItemIndent"&&l[2].sliceSerialize(l[1],!0).length===r.containerState.size?t(o):n(o)}}function _o(e){e.exit(this.containerState.type)}function Do(e,t,n){const r=this;return B(e,i,"listItemPrefixWhitespace",r.parser.constructs.disable.null.includes("codeIndented")?void 0:5);function i(o){const l=r.events[r.events.length-1];return!O(o)&&l&&l[1].type==="listItemPrefixWhitespace"?t(o):n(o)}}const Pt={name:"setextUnderline",resolveTo:Lo,tokenize:Ro};function Lo(e,t){let n=e.length,r,i,o;for(;n--;)if(e[n][0]==="enter"){if(e[n][1].type==="content"){r=n;break}e[n][1].type==="paragraph"&&(i=n)}else e[n][1].type==="content"&&e.splice(n,1),!o&&e[n][1].type==="definition"&&(o=n);const l={type:"setextHeading",start:{...e[r][1].start},end:{...e[e.length-1][1].end}};return e[i][1].type="setextHeadingText",o?(e.splice(i,0,["enter",l,t]),e.splice(o+1,0,["exit",e[r][1],t]),e[r][1].end={...e[o][1].end}):e[r][1]=l,e.push(["exit",l,t]),e}function Ro(e,t,n){const r=this;let i;return o;function o(u){let h=r.events.length,c;for(;h--;)if(r.events[h][1].type!=="lineEnding"&&r.events[h][1].type!=="linePrefix"&&r.events[h][1].type!=="content"){c=r.events[h][1].type==="paragraph";break}return!r.parser.lazy[r.now().line]&&(r.interrupt||c)?(e.enter("setextHeadingLine"),i=u,l(u)):n(u)}function l(u){return e.enter("setextHeadingLineSequence"),a(u)}function a(u){return u===i?(e.consume(u),a):(e.exit("setextHeadingLineSequence"),O(u)?B(e,s,"lineSuffix")(u):s(u))}function s(u){return u===null||z(u)?(e.exit("setextHeadingLine"),t(u)):n(u)}}const Fo={tokenize:Oo};function Oo(e){const t=this,n=e.attempt(Qe,r,e.attempt(this.parser.constructs.flowInitial,i,B(e,e.attempt(this.parser.constructs.flow,i,e.attempt(jl,i)),"linePrefix")));return n;function r(o){if(o===null){e.consume(o);return}return e.enter("lineEndingBlank"),e.consume(o),e.exit("lineEndingBlank"),t.currentConstruct=void 0,n}function i(o){if(o===null){e.consume(o);return}return e.enter("lineEnding"),e.consume(o),e.exit("lineEnding"),t.currentConstruct=void 0,n}}const Mo={resolveAll:Tr()},No=Er("string"),Bo=Er("text");function Er(e){return{resolveAll:Tr(e==="text"?jo:void 0),tokenize:t};function t(n){const r=this,i=this.parser.constructs[e],o=n.attempt(i,l,a);return l;function l(h){return u(h)?o(h):a(h)}function a(h){if(h===null){n.consume(h);return}return n.enter("data"),n.consume(h),s}function s(h){return u(h)?(n.exit("data"),o(h)):(n.consume(h),s)}function u(h){if(h===null)return!0;const c=i[h];let p=-1;if(c)for(;++p-1){const a=l[0];typeof a=="string"?l[0]=a.slice(r):l.shift()}o>0&&l.push(e[i].slice(0,o))}return l}function Zo(e,t){let n=-1;const r=[];let i;for(;++n0){const ue=L.tokenStack[L.tokenStack.length-1];(ue[1]||_t).call(L,void 0,ue[0])}for(T.position={start:xe(x.length>0?x[0][1].start:{line:1,column:1,offset:0}),end:xe(x.length>0?x[x.length-2][1].end:{line:1,column:1,offset:0})},V=-1;++Vm(r=>!r),"aria-expanded":a,"aria-controls":n,children:a?"Done":"Add filter"}),s.map(r=>e.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full border border-[var(--otari-line)] bg-[var(--otari-brand-tint)] py-0.5 pl-2.5 pr-1 text-xs text-[var(--otari-brand-dark)]",children:[e.jsxs("span",{className:"text-[var(--otari-muted)]",children:[r.label,":"]}),e.jsx("span",{className:"font-medium",children:r.value}),e.jsx("button",{type:"button",onClick:r.onClear,"aria-label":r.clearLabel??`Remove ${r.label} filter`,className:"ml-0.5 inline-flex h-4 w-4 items-center justify-center rounded-full text-[var(--otari-muted)] outline-none hover:bg-[var(--otari-line)] hover:text-[var(--otari-ink)] focus-visible:ring-2 focus-visible:ring-[var(--otari-brand)]",children:e.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",className:"h-3 w-3","aria-hidden":"true",children:e.jsx("path",{d:"M6 6l12 12M18 6L6 18",strokeLinecap:"round"})})})]},r.key)),s.length>0&&t?e.jsx(o,{size:"sm",variant:"ghost",onPress:t,children:"Clear all"}):null,l?e.jsx("div",{className:"ml-auto flex items-center gap-3",children:l}):null]}),e.jsx("div",{id:n,className:a?"flex flex-wrap items-end gap-3":"hidden",children:d})]})}export{v as F}; diff --git a/src/gateway/static/dashboard/assets/FilterChips-CTE3I1G3.js b/src/gateway/static/dashboard/assets/FilterChips-CTE3I1G3.js deleted file mode 100644 index 2e32b2141..000000000 --- a/src/gateway/static/dashboard/assets/FilterChips-CTE3I1G3.js +++ /dev/null @@ -1 +0,0 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as i}from"./react-dgEcD0HR.js";import{B as o}from"./heroui-DhloIxuc.js";function p({chips:s,children:d,onClearAll:t,start:x,end:n}){const[a,m]=i.useState(!1),l=i.useId();return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[x,e.jsx(o,{size:"sm",variant:"outline",onPress:()=>m(r=>!r),"aria-expanded":a,"aria-controls":l,children:a?"Done":"Add filter"}),s.map(r=>e.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full border border-[var(--otari-line)] bg-[var(--otari-brand-tint)] py-0.5 pl-2.5 pr-1 text-xs text-[var(--otari-brand-dark)]",children:[e.jsxs("span",{className:"text-[var(--otari-muted)]",children:[r.label,":"]}),e.jsx("span",{className:"font-medium",children:r.value}),e.jsx("button",{type:"button",onClick:r.onClear,"aria-label":`Remove ${r.label} filter`,className:"ml-0.5 inline-flex h-4 w-4 items-center justify-center rounded-full text-[var(--otari-muted)] outline-none hover:bg-[var(--otari-line)] hover:text-[var(--otari-ink)] focus-visible:ring-2 focus-visible:ring-[var(--otari-brand)]",children:e.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",className:"h-3 w-3","aria-hidden":"true",children:e.jsx("path",{d:"M6 6l12 12M18 6L6 18",strokeLinecap:"round"})})})]},r.key)),s.length>0&&t?e.jsx(o,{size:"sm",variant:"ghost",onPress:t,children:"Clear all"}):null,n?e.jsx("div",{className:"ml-auto flex items-center gap-3",children:n}):null]}),e.jsx("div",{id:l,className:a?"flex flex-wrap items-end gap-3":"hidden",children:d})]})}export{p as F}; diff --git a/src/gateway/static/dashboard/assets/KeysPage-fg3Rz_lV.js b/src/gateway/static/dashboard/assets/KeysPage-CEc7g4XL.js similarity index 98% rename from src/gateway/static/dashboard/assets/KeysPage-fg3Rz_lV.js rename to src/gateway/static/dashboard/assets/KeysPage-CEc7g4XL.js index 21721f917..78992d1da 100644 --- a/src/gateway/static/dashboard/assets/KeysPage-fg3Rz_lV.js +++ b/src/gateway/static/dashboard/assets/KeysPage-CEc7g4XL.js @@ -1,4 +1,4 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as i}from"./react-dgEcD0HR.js";import{c as J,O as H,Q as X,S as Z,P as ee,E as K,z as te,T as se,u as q,M as ae}from"./index-D-R1nuKP.js";import{u as re,r as ne,B as ie}from"./tableSelection-B1umVgqc.js";import{C as le}from"./ConfirmDialog-mbnZRETP.js";import{D as oe}from"./DataTable-BHrpJHmX.js";import{F as M}from"./Field-GEMwIhf7.js";import{a as z,M as V}from"./ModelScopeControl-BBYX_HiM.js";import{U as ce}from"./UserComboBox-DWvRaj2b.js";import{g as I,B as f,d as E}from"./heroui-DhloIxuc.js";function $(t){if(!t)return"—";const a=new Date(t);return Number.isNaN(a.getTime())?"—":a.toLocaleDateString()}function de(t){if(!t)return null;const a=new Date(t).getTime();if(Number.isNaN(a))return null;const r=Math.round((a-Date.now())/1e3),n=Math.abs(r),d=[["day",86400],["hour",3600],["minute",60]],o=new Intl.RelativeTimeFormat(void 0,{numeric:"auto"});for(const[l,h]of d)if(n>=h)return o.format(Math.round(r/h),l);return o.format(r,"second")}function ue(t){if(!t.expires_at)return!1;const a=new Date(t.expires_at).getTime();return!Number.isNaN(a)&&aString(n).padStart(2,"0");return`${a.getFullYear()}-${r(a.getMonth()+1)}-${r(a.getDate())}T${r(a.getHours())}:${r(a.getMinutes())}`}const xe=t=>(t??"").startsWith("apikey-"),O=t=>t.key_name??t.id,he=t=>t.id;function F({label:t,value:a,multiline:r=!1,fieldRef:n}){const d=i.useRef(null),o=n??d,[l,h]=i.useState(!1),[p,g]=i.useState(!1),c=async()=>{var m,y,x;(m=o.current)==null||m.focus(),(y=o.current)==null||y.select();try{if((x=navigator.clipboard)!=null&&x.writeText){await navigator.clipboard.writeText(a),h(!0),g(!1),window.setTimeout(()=>h(!1),2e3);return}}catch{}g(!0)},u="w-full rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 font-mono text-xs text-[var(--otari-ink)]";return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-xs font-medium text-[var(--otari-muted)]",children:t}),e.jsx(f,{size:"sm",variant:"outline",onPress:c,children:l?"Copied":"Copy"})]}),r?e.jsx("textarea",{ref:o,readOnly:!0,rows:a.split(` +import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as i}from"./react-dgEcD0HR.js";import{c as J,O as H,Q as X,S as Z,P as ee,E as K,z as te,T as se,u as q,M as ae}from"./index-Dit1BUBh.js";import{u as re,r as ne,B as ie}from"./tableSelection-B1umVgqc.js";import{C as le}from"./ConfirmDialog-Dt_8xaSM.js";import{D as oe}from"./DataTable-BHrpJHmX.js";import{F as M}from"./Field-GEMwIhf7.js";import{a as z,M as V}from"./ModelScopeControl-BhMRwgM-.js";import{U as ce}from"./UserComboBox-DWvRaj2b.js";import{g as I,B as f,d as E}from"./heroui-DhloIxuc.js";function $(t){if(!t)return"—";const a=new Date(t);return Number.isNaN(a.getTime())?"—":a.toLocaleDateString()}function de(t){if(!t)return null;const a=new Date(t).getTime();if(Number.isNaN(a))return null;const r=Math.round((a-Date.now())/1e3),n=Math.abs(r),d=[["day",86400],["hour",3600],["minute",60]],o=new Intl.RelativeTimeFormat(void 0,{numeric:"auto"});for(const[l,h]of d)if(n>=h)return o.format(Math.round(r/h),l);return o.format(r,"second")}function ue(t){if(!t.expires_at)return!1;const a=new Date(t.expires_at).getTime();return!Number.isNaN(a)&&aString(n).padStart(2,"0");return`${a.getFullYear()}-${r(a.getMonth()+1)}-${r(a.getDate())}T${r(a.getHours())}:${r(a.getMinutes())}`}const xe=t=>(t??"").startsWith("apikey-"),O=t=>t.key_name??t.id,he=t=>t.id;function F({label:t,value:a,multiline:r=!1,fieldRef:n}){const d=i.useRef(null),o=n??d,[l,h]=i.useState(!1),[p,g]=i.useState(!1),c=async()=>{var m,y,x;(m=o.current)==null||m.focus(),(y=o.current)==null||y.select();try{if((x=navigator.clipboard)!=null&&x.writeText){await navigator.clipboard.writeText(a),h(!0),g(!1),window.setTimeout(()=>h(!1),2e3);return}}catch{}g(!0)},u="w-full rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 font-mono text-xs text-[var(--otari-ink)]";return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-xs font-medium text-[var(--otari-muted)]",children:t}),e.jsx(f,{size:"sm",variant:"outline",onPress:c,children:l?"Copied":"Copy"})]}),r?e.jsx("textarea",{ref:o,readOnly:!0,rows:a.split(` `).length,value:a,onFocus:m=>m.currentTarget.select(),className:`${u} resize-none whitespace-pre`}):e.jsx("input",{ref:o,readOnly:!0,value:a,onFocus:m=>m.currentTarget.select(),className:u}),e.jsx("span",{"aria-live":"polite",className:"text-xs text-green-700",children:l?"Copied to clipboard.":""}),p?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Selected. Press Ctrl/Cmd-C to copy."}):null]})}function fe({title:t,result:a,onClose:r}){const n=i.useRef(null),d=i.useRef(null),o=typeof window<"u"?window.location.origin:"",l=a.key;i.useEffect(()=>{var c,u;(c=d.current)==null||c.focus(),(u=d.current)==null||u.select()},[]);const h=c=>{var x;if(c.key!=="Tab")return;const u=(x=n.current)==null?void 0:x.querySelectorAll('button, input, textarea, a[href], [tabindex]:not([tabindex="-1"])');if(!u||u.length===0)return;const m=u[0],y=u[u.length-1];c.shiftKey&&document.activeElement===m?(c.preventDefault(),y.focus()):!c.shiftKey&&document.activeElement===y&&(c.preventDefault(),m.focus())},p=[`curl ${o}/v1/chat/completions \\`,` -H "Otari-Key: ${l}" \\`,' -H "Content-Type: application/json" \\',` -d '{"model": "your-model", "messages": [{"role": "user", "content": "Hello"}]}'`].join(` `),g=["from openai import OpenAI","",`client = OpenAI(base_url="${o}/v1", api_key="${l}")`,"resp = client.chat.completions.create(",' model="your-model",',' messages=[{"role": "user", "content": "Hello"}],',")","print(resp.choices[0].message.content)"].join(` `);return e.jsx("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4",role:"presentation",children:e.jsxs("div",{ref:n,role:"dialog","aria-modal":"true","aria-labelledby":"reveal-title",onKeyDown:h,className:"flex max-h-[90vh] w-full max-w-2xl flex-col gap-4 overflow-y-auto rounded-xl bg-[var(--otari-surface)] p-6 shadow-xl",children:[e.jsx("h2",{id:"reveal-title",className:"text-lg font-semibold text-[var(--otari-ink)]",children:t}),e.jsx(ae,{tone:"warning",children:"Copy this key now. For security it is shown only once and cannot be retrieved later. If you lose it, use Regenerate to issue a new secret."}),e.jsxs("p",{className:"text-xs text-[var(--otari-muted)]",children:["Model access: ",z(a.allowed_models).text,"."]}),e.jsx(F,{label:"Secret key",value:l,fieldRef:d}),e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Make your first call"}),e.jsxs("p",{className:"text-xs text-[var(--otari-muted)]",children:["Replace ",e.jsx("code",{children:"your-model"})," with a model from the Models page."]})]}),e.jsx(F,{label:"curl",value:p,multiline:!0}),e.jsx(F,{label:"Python (OpenAI SDK)",value:g,multiline:!0})]}),e.jsx("div",{className:"flex justify-end",children:e.jsx(f,{variant:"primary",onPress:r,children:"I’ve saved this key"})})]})})}function U({trigger:t,message:a,confirmLabel:r,isPending:n,onConfirm:d}){const[o,l]=i.useState(!1);return o?e.jsxs("div",{className:"flex flex-col items-end gap-1.5 rounded-lg border border-amber-200 bg-amber-50 p-2 text-right",children:[e.jsx("span",{className:"max-w-xs text-xs text-amber-800",children:a}),e.jsxs("span",{className:"inline-flex gap-1",children:[e.jsx(f,{size:"sm",variant:"danger",isDisabled:n,onPress:d,children:r}),e.jsx(f,{size:"sm",variant:"ghost",isDisabled:n,onPress:()=>l(!1),children:"Cancel"})]})]}):e.jsx(f,{size:"sm",variant:"danger-soft",onPress:()=>l(!0),children:t})}function W({userId:t,users:a}){const r=t.trim();if(r==="")return e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"Choose an owner above to see the models this key can inherit."});const n=a.find(l=>l.user_id===r);if(!n)return e.jsxs("p",{className:"text-xs text-[var(--otari-muted)]",children:["New user ",e.jsx("code",{children:r})," starts unrestricted, so this key may allow any model."]});const{text:d}=z(n.allowed_models),o=n.allowed_models&&n.allowed_models.length>0?n.allowed_models.join(", "):null;return e.jsxs("p",{className:"text-xs text-[var(--otari-muted)]",children:["Owner ",e.jsx("code",{children:r})," allows ",e.jsx("span",{className:"font-medium text-[var(--otari-ink)]",children:d.toLowerCase()}),o?e.jsxs(e.Fragment,{children:[" (",e.jsx("span",{className:"font-mono",children:o}),")"]}):null,". This key inherits that, or narrows within it."]})}function Q({checked:t,onChange:a}){return e.jsxs("label",{className:"flex items-start gap-2 rounded-lg border border-[var(--otari-line)] p-3 text-sm",children:[e.jsx("input",{type:"checkbox",checked:t,onChange:r=>a(r.target.checked),className:"mt-0.5 h-4 w-4 accent-[var(--otari-brand)]","aria-label":"Exempt this key from budget"}),e.jsxs("span",{className:"flex flex-col gap-0.5",children:[e.jsx("span",{className:"font-medium text-[var(--otari-ink)]",children:"Exempt from budget"}),e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Requests on this key are logged with their cost but never counted toward the owner's budget or spend, and never blocked by it."})]})]})}function Y({value:t,onChange:a}){const r="key-reject-user-mismatch";return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsxs("label",{htmlFor:r,className:"text-sm font-medium text-[var(--otari-ink)]",children:["Mismatched ",e.jsx("code",{children:"user"})," field"]}),e.jsxs("select",{id:r,value:t===null?"inherit":t?"reject":"accept",onChange:n=>a(n.target.value==="inherit"?null:n.target.value==="reject"),className:"w-full rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)]",children:[e.jsx("option",{value:"inherit",children:"Use the deployment setting (default)"}),e.jsx("option",{value:"reject",children:"Always reject (403)"}),e.jsx("option",{value:"accept",children:"Always accept"})]}),e.jsxs("span",{className:"text-xs text-[var(--otari-muted)]",children:["What happens when a request on this key names a different ",e.jsx("code",{children:"user"})," than its owner. Accept it for clients that send telemetry there rather than an identity, such as Claude Code. Spend binds to this key's owner either way."]})]})}function pe({onClose:t,onCreated:a}){const r=se(),n=q(),[d,o]=i.useState(""),[l,h]=i.useState(""),[p,g]=i.useState(!1),[c,u]=i.useState(""),[m,y]=i.useState(null),[x,N]=i.useState(!1),[v,k]=i.useState(null),[b,_]=i.useState(!0),P=l!==""&&new Date(l).getTime(){if(r.isPending||!b||A)return;const j={key_name:d.trim()||null,user_id:c.trim(),expires_at:l?new Date(l).toISOString():null,allowed_models:m,exclude_from_budget:x,reject_user_mismatch:v};r.mutate(j,{onSuccess:w=>{a(w),t()}})};return e.jsx(E,{children:e.jsxs(E.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsx("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Create API key"}),e.jsx(K,{error:r.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(M,{label:"Name",value:d,onChange:o,placeholder:"ci-bot",autoFocus:!0,description:"A label to recognize this key later."}),e.jsx(M,{label:"Expires (optional)",value:l,onChange:h,type:"datetime-local",description:P?e.jsx("span",{className:"text-red-700",children:"That time is in the past; the key would be rejected immediately."}):"Leave blank for a key that never expires."})]}),e.jsx(ce,{value:c,onChange:u,users:n.data??[]}),e.jsx("button",{type:"button",className:"self-start text-xs font-medium text-[var(--otari-brand-dark)]",onClick:()=>g(j=>!j),children:p?"Hide advanced":"Advanced"}),p?e.jsxs("div",{className:"flex flex-col gap-4 rounded-lg border border-[var(--otari-line)] p-4",children:[e.jsx(W,{userId:c,users:n.data??[]}),e.jsx(V,{title:"Restrict this key's models",description:"By default this key inherits its owner's access. Optionally narrow it to a subset; a key can never exceed its owner's allowed models.",anyLabel:"Inherit owner access",initial:null,onChange:(j,w)=>{y(j),_(w)}}),e.jsx(Q,{checked:x,onChange:N}),e.jsx(Y,{value:v,onChange:k})]}):null,e.jsxs("div",{className:"flex gap-2",children:[e.jsx(f,{variant:"primary",isDisabled:r.isPending||!b||A,onPress:C,children:r.isPending?"Creating…":"Create key"}),e.jsx(f,{variant:"ghost",onPress:t,children:"Cancel"})]})]})})}function ge({apiKey:t,onClose:a}){const r=H(),n=q(),[d,o]=i.useState(t.key_name??""),[l,h]=i.useState(me(t.expires_at)),[p,g]=i.useState(t.allowed_models),[c,u]=i.useState(t.exclude_from_budget),[m,y]=i.useState(t.reject_user_mismatch),[x,N]=i.useState(!0),v=()=>{r.isPending||!x||r.mutate({id:t.id,body:{key_name:d.trim()||null,expires_at:l?new Date(l).toISOString():null,allowed_models:p,exclude_from_budget:c,reject_user_mismatch:m}},{onSuccess:a})};return e.jsx(E,{children:e.jsxs(E.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsxs("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Edit ",e.jsx("code",{children:t.key_name??t.id})]}),e.jsx(K,{error:r.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(M,{label:"Name",value:d,onChange:o,placeholder:"ci-bot"}),e.jsx(M,{label:"Expires",value:l,onChange:h,type:"datetime-local",description:"Blank clears the expiry."})]}),t.user_id?e.jsx(W,{userId:t.user_id,users:n.data??[]}):null,e.jsx(V,{title:"Restrict this key's models",description:"This key inherits its owner's access by default. Narrow it to a subset here; it can never exceed the owner's allowed models.",anyLabel:"Inherit owner access",initial:t.allowed_models,onChange:(k,b)=>{g(k),N(b)}}),e.jsx(Q,{checked:c,onChange:u}),e.jsx(Y,{value:m,onChange:y}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(f,{variant:"primary",isDisabled:r.isPending||!x,onPress:v,children:r.isPending?"Saving…":"Save changes"}),e.jsx(f,{variant:"ghost",onPress:a,children:"Cancel"})]})]})})}function ye({apiKey:t}){return t.is_active?ue(t)?e.jsx(I,{size:"sm",color:"warning",children:"Expired"}):e.jsx(I,{size:"sm",color:"accent",children:"Active"}):e.jsx(I,{size:"sm",color:"default",children:"Disabled"})}function je({allowed:t}){const{text:a,tone:r}=z(t),n=r==="danger"?"text-red-700 font-medium":r==="muted"?"text-[var(--otari-muted)]":"text-[var(--otari-brand-dark)] font-medium",d=t&&t.length>0?t.join(", "):void 0;return e.jsx("span",{className:`text-xs ${n}`,title:d,children:a})}function De(){const t=J(),a=H(),r=X(),n=Z(),[d,o]=i.useState(!1),[l,h]=i.useState(null),[p,g]=i.useState(null),c=t.data??[],u=t.isLoading,m=c.find(s=>s.id===l)??null,y=!u&&c.length===0&&!d,x=re(),[N,v]=i.useState(!1),[k,b]=i.useState(void 0),[_,P]=i.useState(!1),A=c.map(s=>s.id),C=ne(x.selectedKeys,A),j=c.filter(s=>C.includes(s.id)),w=i.useCallback((s,S)=>a.mutate({id:s.id,body:{is_active:S}}),[a.mutate]),L=i.useCallback(s=>r.mutate(s.id,{onSuccess:S=>g({title:`New secret for ${O(s)}`,result:S})}),[r.mutate]),T=async(s,S,R)=>{P(!0),b(void 0);try{for(const B of s)await S(B);x.clear(),R==null||R()}catch(B){b(B)}finally{P(!1)}},G=i.useMemo(()=>[{id:"name",header:"Name",isRowHeader:!0,cell:s=>e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsx("span",{className:"font-medium text-[var(--otari-ink)]",children:s.key_name??e.jsx("span",{className:"text-[var(--otari-muted)]",children:"(unnamed)"})}),e.jsxs("div",{className:"flex flex-wrap items-center gap-1",children:[e.jsx(je,{allowed:s.allowed_models}),s.exclude_from_budget?e.jsx("span",{className:"inline-flex items-center rounded-full border border-[var(--otari-line)] bg-[var(--otari-brand-tint)] px-2 py-0.5 text-xs font-medium text-[var(--otari-brand-dark)]",title:"Requests on this key are logged with cost but never counted toward budget",children:"Budget-exempt"}):null,s.reject_user_mismatch===null?null:e.jsx("span",{className:"inline-flex items-center rounded-full border border-[var(--otari-line)] bg-[var(--otari-brand-tint)] px-2 py-0.5 text-xs font-medium text-[var(--otari-brand-dark)]",title:s.reject_user_mismatch?"This key always rejects a request naming a different user, whatever the deployment setting says":"This key always accepts a request naming a different user; spend still binds to its owner",children:s.reject_user_mismatch?"Strict user":"Lenient user"})]})]})},{id:"status",header:"Status",cell:s=>e.jsx(ye,{apiKey:s})},{id:"owner",header:"Owner",cell:s=>xe(s.user_id)?e.jsx(I,{size:"sm",color:"default",children:"virtual"}):e.jsx("code",{className:"text-xs text-[var(--otari-muted)]",children:s.user_id??"—"})},{id:"key",header:"Key",cell:s=>e.jsx("code",{className:"text-xs text-[var(--otari-muted)]",children:s.key_prefix?`${s.key_prefix}…`:"—"})},{id:"created",header:"Created",cell:s=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:$(s.created_at)})},{id:"last_used",header:"Last used",cell:s=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:de(s.last_used_at)??"never"})},{id:"expires",header:"Expires",cell:s=>e.jsx("span",{className:"text-[var(--otari-muted)]",title:s.expires_at?new Date(s.expires_at).toLocaleString():void 0,children:s.expires_at?$(s.expires_at):"never"})},{id:"actions",header:"Actions",align:"end",cell:s=>e.jsxs("div",{className:"flex items-center justify-end gap-1.5",children:[e.jsx(f,{size:"sm",variant:"outline",isDisabled:a.isPending,onPress:()=>w(s,!s.is_active),children:s.is_active?"Disable":"Enable"}),e.jsx(f,{size:"sm",variant:"ghost",onPress:()=>{o(!1),h(s.id)},children:"Edit"}),e.jsx(U,{trigger:"Regenerate",confirmLabel:"Regenerate",isPending:r.isPending,message:e.jsxs(e.Fragment,{children:["Regenerate the secret for ",e.jsx("strong",{children:O(s)}),"? The current secret stops working immediately, with no grace period."]}),onConfirm:()=>L(s)}),s.is_active?null:e.jsx(U,{trigger:"Delete",confirmLabel:"Delete permanently",isPending:n.isPending,message:e.jsxs(e.Fragment,{children:["Permanently delete ",e.jsx("strong",{children:O(s)}),"? This removes the key and unlinks its usage history. Cannot be undone."]}),onConfirm:()=>n.mutate(s.id)})]})}],[a.isPending,r.isPending,n.isPending,n.mutate,w,L]),D=j.filter(s=>!s.is_active);return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(ee,{title:"API keys",description:"Issue and revoke the keys that authenticate callers to this gateway. Secrets are shown once at creation.",action:d?null:e.jsx(f,{variant:"primary",onPress:()=>{h(null),o(!0)},children:"Create key"})}),e.jsx(K,{error:t.error??a.error??r.error??n.error}),y?e.jsx(te,{title:"No API keys yet",description:"An API key authenticates callers to this gateway. Create one to make your first request; the secret is shown once, so keep it somewhere safe.",actionLabel:"Create your first key",onAction:()=>{h(null),o(!0)}}):null,d?e.jsx(pe,{onClose:()=>o(!1),onCreated:s=>g({title:"API key created",result:s})}):null,m?e.jsx(ge,{apiKey:m,onClose:()=>h(null)},m.id):null,C.length>0?e.jsxs(ie,{selectedCount:C.length,allMatching:!1,matchingTotal:null,canSelectAllMatching:!1,onSelectAllMatching:()=>{},onClear:x.clear,children:[e.jsx(f,{size:"sm",variant:"outline",isDisabled:_,onPress:()=>void T(j,s=>a.mutateAsync({id:s.id,body:{is_active:!1}})),children:"Disable"}),e.jsx(f,{size:"sm",variant:"outline",isDisabled:_,onPress:()=>void T(j,s=>a.mutateAsync({id:s.id,body:{exclude_from_budget:!0}})),children:"Budget-exempt"}),e.jsx(f,{size:"sm",variant:"danger",isDisabled:D.length===0,onPress:()=>v(!0),children:"Delete"})]}):null,y?null:e.jsx(oe,{ariaLabel:"API keys",columns:G,rows:c,getRowKey:he,isLoading:u,emptyContent:"No API keys yet. Create one to authenticate a caller.",selectionMode:"multiple",selectedKeys:x.selectedKeys,onSelectionChange:x.onSelectionChange}),e.jsx(le,{isOpen:N,onOpenChange:v,heading:"Delete API keys",body:`Permanently delete ${D.length} disabled ${D.length===1?"key":"keys"}? This removes them and unlinks their usage history. Cannot be undone. Active keys in the selection are skipped; disable them first.`,confirmLabel:"Delete permanently",isPending:_,error:k,onConfirm:()=>void T(D,s=>n.mutateAsync(s.id),()=>v(!1))}),p?e.jsx(fe,{title:p.title,result:p.result,onClose:()=>{g(null),r.reset()}}):null]})}export{De as KeysPage}; diff --git a/src/gateway/static/dashboard/assets/ModelScopeControl-BBYX_HiM.js b/src/gateway/static/dashboard/assets/ModelScopeControl-BhMRwgM-.js similarity index 98% rename from src/gateway/static/dashboard/assets/ModelScopeControl-BBYX_HiM.js rename to src/gateway/static/dashboard/assets/ModelScopeControl-BhMRwgM-.js index a2d7c2345..1f59debe4 100644 --- a/src/gateway/static/dashboard/assets/ModelScopeControl-BBYX_HiM.js +++ b/src/gateway/static/dashboard/assets/ModelScopeControl-BhMRwgM-.js @@ -1 +1 @@ -import{j as t}from"./tanstack-query-1t81HyiD.js";import{r as n}from"./react-dgEcD0HR.js";import{a2 as A,s as $,v as P}from"./index-D-R1nuKP.js";import{C as d,I as R,a as T,b as V}from"./heroui-DhloIxuc.js";function q(r){return r===null?"any":r.length===0?"block":"only"}const O=50;function W({initial:r,onChange:m,title:N="Model access",description:w,anyLabel:C="Any model"}){const x=A(),u=$(),v=P(),[i,S]=n.useState(q(r)),[a,g]=n.useState(r??[]),[p,y]=n.useState(""),f=n.useMemo(()=>{var j,k;const e=new Set,s=[],l=(o,c)=>{o&&!e.has(o)&&(e.add(o),s.push({id:o,label:c}))};for(const o of((j=x.data)==null?void 0:j.providers)??[])l(`${o.instance}:*`,`${o.instance}:* · all ${o.instance} models`);for(const o of((k=u.data)==null?void 0:k.providers)??[])for(const c of o.models)l(c.key,c.key);for(const o of v.data??[])l(o.target,`${o.name} · alias`);return s},[x.data,u.data,v.data]),L=n.useMemo(()=>{const e=p.trim().toLowerCase();return f.filter(s=>!a.includes(s.id)).filter(s=>!e||s.id.toLowerCase().includes(e)||s.label.toLowerCase().includes(e)).slice(0,O)},[f,a,p]),h=(e,s)=>{e==="any"?m(null,!0):e==="block"?m([],!0):m(s,s.length>0)},B=e=>{S(e),h(e,a)},M=e=>{const s=a.includes(e)?a:[...a,e];g(s),y(""),h("only",s)},E=e=>{const s=a.filter(l=>l!==e);g(s),h("only",s)},b=(e,s)=>t.jsx("button",{type:"button","aria-pressed":i===e,onClick:()=>B(e),className:i===e?"rounded-md bg-white px-3 py-1.5 text-sm font-medium text-[var(--otari-ink)] shadow-sm":"rounded-md px-3 py-1.5 text-sm text-[var(--otari-muted)] hover:text-[var(--otari-ink)]",children:s}),I=!u.isLoading&&!x.isLoading&&f.length===0;return t.jsxs("div",{className:"flex flex-col gap-3",children:[t.jsxs("div",{children:[t.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:N}),t.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:w??"Which models this key may list and call. The master key is never restricted, so blocking a key cannot lock you out of the dashboard."})]}),t.jsxs("div",{className:"flex w-fit items-center gap-1 rounded-lg bg-[var(--otari-bg)] p-1",children:[b("any",C),b("only","Only selected"),b("block","Block all")]}),i==="block"?t.jsxs("div",{className:"rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800",children:["Blocked from ",t.jsx("strong",{children:"every"})," model until you change this access."]}):null,i==="only"?t.jsxs("div",{className:"flex flex-col gap-2",children:[t.jsx("div",{className:"flex flex-wrap gap-1.5",children:a.length===0?t.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Pick at least one model below, or choose “Block all”."}):a.map(e=>t.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full bg-[var(--otari-brand-tint)] px-2.5 py-1 font-mono text-xs text-[var(--otari-brand-dark)]",children:[e,t.jsx("button",{type:"button","aria-label":`Remove ${e}`,onClick:()=>E(e),className:"text-[var(--otari-brand-dark)] hover:text-red-700",children:"×"})]},e))}),I?t.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"No providers or models discovered yet. Configure a provider first, then scope this key."}):t.jsxs(d.Root,{allowsEmptyCollection:!0,menuTrigger:"input",inputValue:p,onInputChange:y,selectedKey:null,onSelectionChange:e=>{e!=null&&M(String(e))},className:"flex max-w-md flex-col gap-1",children:[t.jsxs(d.InputGroup,{children:[t.jsx(R,{"aria-label":"Add a model",placeholder:"Search providers, models, aliases…",autoComplete:"off"}),t.jsx(d.Trigger,{})]}),t.jsx(d.Popover,{children:t.jsx(T,{items:L,className:"max-h-72 overflow-auto",children:e=>t.jsx(V,{id:e.id,textValue:e.label,children:e.label})})})]})]}):null]})}function X(r){return r===null?{text:"All models",tone:"muted"}:r.length===0?{text:"No models",tone:"danger"}:{text:"Selected models",tone:"normal"}}export{W as M,X as a}; +import{j as t}from"./tanstack-query-1t81HyiD.js";import{r as n}from"./react-dgEcD0HR.js";import{a2 as A,s as $,v as P}from"./index-Dit1BUBh.js";import{C as d,I as R,a as T,b as V}from"./heroui-DhloIxuc.js";function q(r){return r===null?"any":r.length===0?"block":"only"}const O=50;function W({initial:r,onChange:m,title:N="Model access",description:w,anyLabel:C="Any model"}){const x=A(),u=$(),v=P(),[i,S]=n.useState(q(r)),[a,g]=n.useState(r??[]),[p,y]=n.useState(""),f=n.useMemo(()=>{var j,k;const e=new Set,s=[],l=(o,c)=>{o&&!e.has(o)&&(e.add(o),s.push({id:o,label:c}))};for(const o of((j=x.data)==null?void 0:j.providers)??[])l(`${o.instance}:*`,`${o.instance}:* · all ${o.instance} models`);for(const o of((k=u.data)==null?void 0:k.providers)??[])for(const c of o.models)l(c.key,c.key);for(const o of v.data??[])l(o.target,`${o.name} · alias`);return s},[x.data,u.data,v.data]),L=n.useMemo(()=>{const e=p.trim().toLowerCase();return f.filter(s=>!a.includes(s.id)).filter(s=>!e||s.id.toLowerCase().includes(e)||s.label.toLowerCase().includes(e)).slice(0,O)},[f,a,p]),h=(e,s)=>{e==="any"?m(null,!0):e==="block"?m([],!0):m(s,s.length>0)},B=e=>{S(e),h(e,a)},M=e=>{const s=a.includes(e)?a:[...a,e];g(s),y(""),h("only",s)},E=e=>{const s=a.filter(l=>l!==e);g(s),h("only",s)},b=(e,s)=>t.jsx("button",{type:"button","aria-pressed":i===e,onClick:()=>B(e),className:i===e?"rounded-md bg-white px-3 py-1.5 text-sm font-medium text-[var(--otari-ink)] shadow-sm":"rounded-md px-3 py-1.5 text-sm text-[var(--otari-muted)] hover:text-[var(--otari-ink)]",children:s}),I=!u.isLoading&&!x.isLoading&&f.length===0;return t.jsxs("div",{className:"flex flex-col gap-3",children:[t.jsxs("div",{children:[t.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:N}),t.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:w??"Which models this key may list and call. The master key is never restricted, so blocking a key cannot lock you out of the dashboard."})]}),t.jsxs("div",{className:"flex w-fit items-center gap-1 rounded-lg bg-[var(--otari-bg)] p-1",children:[b("any",C),b("only","Only selected"),b("block","Block all")]}),i==="block"?t.jsxs("div",{className:"rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800",children:["Blocked from ",t.jsx("strong",{children:"every"})," model until you change this access."]}):null,i==="only"?t.jsxs("div",{className:"flex flex-col gap-2",children:[t.jsx("div",{className:"flex flex-wrap gap-1.5",children:a.length===0?t.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Pick at least one model below, or choose “Block all”."}):a.map(e=>t.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full bg-[var(--otari-brand-tint)] px-2.5 py-1 font-mono text-xs text-[var(--otari-brand-dark)]",children:[e,t.jsx("button",{type:"button","aria-label":`Remove ${e}`,onClick:()=>E(e),className:"text-[var(--otari-brand-dark)] hover:text-red-700",children:"×"})]},e))}),I?t.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"No providers or models discovered yet. Configure a provider first, then scope this key."}):t.jsxs(d.Root,{allowsEmptyCollection:!0,menuTrigger:"input",inputValue:p,onInputChange:y,selectedKey:null,onSelectionChange:e=>{e!=null&&M(String(e))},className:"flex max-w-md flex-col gap-1",children:[t.jsxs(d.InputGroup,{children:[t.jsx(R,{"aria-label":"Add a model",placeholder:"Search providers, models, aliases…",autoComplete:"off"}),t.jsx(d.Trigger,{})]}),t.jsx(d.Popover,{children:t.jsx(T,{items:L,className:"max-h-72 overflow-auto",children:e=>t.jsx(V,{id:e.id,textValue:e.label,children:e.label})})})]})]}):null]})}function X(r){return r===null?{text:"All models",tone:"muted"}:r.length===0?{text:"No models",tone:"danger"}:{text:"Selected models",tone:"normal"}}export{W as M,X as a}; diff --git a/src/gateway/static/dashboard/assets/ModelsPage-uwVSUUQm.js b/src/gateway/static/dashboard/assets/ModelsPage-299cCHBM.js similarity index 99% rename from src/gateway/static/dashboard/assets/ModelsPage-uwVSUUQm.js rename to src/gateway/static/dashboard/assets/ModelsPage-299cCHBM.js index 5015c092c..acbd61992 100644 --- a/src/gateway/static/dashboard/assets/ModelsPage-uwVSUUQm.js +++ b/src/gateway/static/dashboard/assets/ModelsPage-299cCHBM.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{i as Mt,u as Wt,r as c}from"./react-dgEcD0HR.js";import{U as Tt,V as It,s as Lt,W as $t,m as Ne,P as At,E as Et,F as K,M as Ot,X as ae,q as Ue,Z as He,y as Ge,_ as Pe,$ as Dt,a0 as Ft,a1 as O}from"./index-D-R1nuKP.js";import{u as Kt,r as Bt,B as Rt}from"./tableSelection-B1umVgqc.js";import{D as zt}from"./DataTable-BHrpJHmX.js";import{i as wt,T as Vt,S as Re}from"./TablePagination-BEmYAlSB.js";import{B as W,d as ce,g as V}from"./heroui-DhloIxuc.js";import"./Field-GEMwIhf7.js";function ze(t){const i=t.indexOf(":");return i>0?t.slice(0,i):"—"}function qt(t,i=Date.now()){const r=new Map;for(const n of t){const s=r.get(n.model_key)??[];s.push(n),r.set(n.model_key,s)}const a=[];for(const n of r.values()){const s=[...n].sort((d,p)=>Date.parse(d.effective_at)-Date.parse(p.effective_at)),u=[...s].reverse().find(d=>Date.parse(d.effective_at)<=i);a.push(u??s[0])}return a.sort((n,s)=>n.model_key.localeCompare(s.model_key))}const Yt="otari",Ut="otari",we=[{value:"vision",label:"Vision",test:t=>Array.isArray(t.input_modalities)&&t.input_modalities.includes("image")},{value:"tool_call",label:"Tool calling",test:t=>!!t.tool_call},{value:"reasoning",label:"Reasoning",test:t=>!!t.reasoning},{value:"structured_output",label:"Structured output",test:t=>!!t.structured_output},{value:"attachment",label:"Attachments",test:t=>!!t.attachment},{value:"audio",label:"Audio",test:t=>Array.isArray(t.input_modalities)&&t.input_modalities.includes("audio")},{value:"pdf",label:"PDF",test:t=>Array.isArray(t.input_modalities)&&t.input_modalities.includes("pdf")}],Ht=[{key:"reasoning",label:"Reasoning"},{key:"tool_call",label:"Tool calling"},{key:"structured_output",label:"Structured output"},{key:"attachment",label:"Attachments"},{key:"temperature",label:"Temperature"}],Ve={text:"Text",image:"Image",audio:"Audio",video:"Video",pdf:"PDF"},Gt=[{value:"0",label:"Any context"},{value:"8000",label:"≥ 8K"},{value:"32000",label:"≥ 32K"},{value:"128000",label:"≥ 128K"},{value:"200000",label:"≥ 200K"},{value:"1000000",label:"≥ 1M"}],Jt=[{value:"",label:"Any price"},{value:"1",label:"≤ $1 / 1M in"},{value:"3",label:"≤ $3 / 1M in"},{value:"10",label:"≤ $10 / 1M in"},{value:"30",label:"≤ $30 / 1M in"}],Xt=[{value:"",label:"Base prices"},{value:"8000",label:"Compare at 8K"},{value:"128000",label:"Compare at 128K"},{value:"200000",label:"Compare at 200K"},{value:"500000",label:"Compare at 500K"},{value:"1000000",label:"Compare at 1M"}],Zt=[{value:"all",label:"Any release date"},{value:"365",label:"Past year"},{value:"730",label:"Past 2 years"},{value:"1095",label:"Past 3 years"}],Qt=1440*60*1e3,ei=t=>t.key;function qe(t,i){const r=`${i}:`;return t.startsWith(r)?t.slice(r.length):t}function Ye(t,i){const r={inputPrice:t.inputPrice,outputPrice:t.outputPrice,cacheReadPrice:t.cacheReadPrice,cacheWritePrice:t.cacheWritePrice,cacheWrite1hPrice:t.cacheWrite1hPrice};if(i==null)return r;const a=t.pricingTiers.filter(n=>n.min_input_tokens<=i).sort((n,s)=>s.min_input_tokens-n.min_input_tokens)[0];return a?{inputPrice:a.input_price_per_million??r.inputPrice,outputPrice:a.output_price_per_million??r.outputPrice,cacheReadPrice:a.cache_read_price_per_million??r.cacheReadPrice,cacheWritePrice:a.cache_write_price_per_million??r.cacheWritePrice,cacheWrite1hPrice:a.cache_write_1h_price_per_million??r.cacheWrite1hPrice}:r}function oe(t){const i=Number(t);return t.trim()!==""&&Number.isFinite(i)&&i>=0}function w(t){if(t.trim()==="")return!0;const i=Number(t);return Number.isFinite(i)&&i>=0}function q(t){return t.trim()===""?null:Number(t)}function Je(t){return t.map((i,r)=>({id:r,minInputTokens:String(i.min_input_tokens),input:i.input_price_per_million==null?"":String(i.input_price_per_million),output:i.output_price_per_million==null?"":String(i.output_price_per_million),cacheRead:i.cache_read_price_per_million==null?"":String(i.cache_read_price_per_million),cacheWrite:i.cache_write_price_per_million==null?"":String(i.cache_write_price_per_million),cacheWrite1h:i.cache_write_1h_price_per_million==null?"":String(i.cache_write_1h_price_per_million)}))}function Xe(t){const i=new Set;return t.every(r=>{const a=Number(r.minInputTokens),n=[r.input,r.output,r.cacheRead,r.cacheWrite,r.cacheWrite1h].some(s=>s.trim()!=="");return!Number.isInteger(a)||a<=0||i.has(a)||!n?!1:(i.add(a),[r.input,r.output,r.cacheRead,r.cacheWrite,r.cacheWrite1h].every(w))})}function Ze(t){return t.map(i=>({min_input_tokens:Number(i.minInputTokens),...i.input.trim()===""?{}:{input_price_per_million:Number(i.input)},...i.output.trim()===""?{}:{output_price_per_million:Number(i.output)},...i.cacheRead.trim()===""?{}:{cache_read_price_per_million:Number(i.cacheRead)},...i.cacheWrite.trim()===""?{}:{cache_write_price_per_million:Number(i.cacheWrite)},...i.cacheWrite1h.trim()===""?{}:{cache_write_1h_price_per_million:Number(i.cacheWrite1h)}}))}function b({value:t,onChange:i,ariaLabel:r}){return e.jsx("input",{type:"number",step:"any",min:"0",inputMode:"decimal","aria-label":r,value:t,onChange:a=>i(a.target.value),className:"w-28 rounded-md border border-[var(--otari-line)] bg-white px-2 py-1 text-right text-sm tabular-nums focus:border-[var(--otari-brand)] focus:outline-none"})}function Qe({tiers:t,onChange:i}){const r=(n,s,u)=>{i(t.map(d=>d.id===n?{...d,[s]:u}:d))},a=()=>{const n=t.reduce((s,u)=>Math.max(s,u.id),-1)+1;i([...t,{id:n,minInputTokens:"128000",input:"",output:"",cacheRead:"",cacheWrite:"",cacheWrite1h:""}])};return e.jsxs("div",{className:"flex flex-col gap-2 rounded-lg border border-[var(--otari-line)] p-3",children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-xs font-medium text-[var(--otari-ink)]",children:"Long-context price tiers"}),e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"At a threshold, listed rates replace the base rate for the whole request."})]}),e.jsx(W,{size:"sm",variant:"outline",onPress:a,children:"Add tier"})]}),t.map(n=>e.jsxs("div",{className:"flex flex-wrap items-end gap-2 border-t border-[var(--otari-line)] pt-2",children:[e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Context ≥ tokens",e.jsx("input",{type:"number",min:"1",step:"1",inputMode:"numeric","aria-label":"Tier context threshold",value:n.minInputTokens,onChange:s=>r(n.id,"minInputTokens",s.target.value),className:"w-28 rounded-md border border-[var(--otari-line)] bg-white px-2 py-1 text-right text-sm tabular-nums focus:border-[var(--otari-brand)] focus:outline-none"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Input",e.jsx(b,{value:n.input,onChange:s=>r(n.id,"input",s),ariaLabel:"Tier input price"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Output",e.jsx(b,{value:n.output,onChange:s=>r(n.id,"output",s),ariaLabel:"Tier output price"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Cache read",e.jsx(b,{value:n.cacheRead,onChange:s=>r(n.id,"cacheRead",s),ariaLabel:"Tier cache read price"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Cache write",e.jsx(b,{value:n.cacheWrite,onChange:s=>r(n.id,"cacheWrite",s),ariaLabel:"Tier cache write price"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["1h write",e.jsx(b,{value:n.cacheWrite1h,onChange:s=>r(n.id,"cacheWrite1h",s),ariaLabel:"Tier 1 hour cache write price"})]}),e.jsx(W,{size:"sm",variant:"ghost",onPress:()=>i(t.filter(s=>s.id!==n.id)),children:"Remove"})]},n.id))]})}function ti({source:t}){return t==="configured"?e.jsx(V,{size:"sm",color:"default",children:"configured"}):t==="default"||t==="alias"?e.jsx(V,{size:"sm",color:"accent",children:t}):e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"not priced"})}function ue({label:t,tone:i="info",children:r}){const a=c.useId();return e.jsxs("span",{className:"group relative inline-flex items-center font-normal normal-case",children:[e.jsx("button",{type:"button","aria-label":t,"aria-describedby":a,className:`inline-flex h-4 w-4 items-center justify-center rounded-full border text-[10px] leading-none ${i==="warning"?"border-[#c2843a] text-[#b45309]":"border-[var(--otari-line)] text-[var(--otari-muted)] hover:border-[var(--otari-brand)] hover:text-[var(--otari-brand)]"}`,children:"i"}),e.jsx("span",{id:a,role:"tooltip",className:"pointer-events-none absolute top-full right-0 z-20 mt-1.5 w-72 rounded-lg border border-[var(--otari-line)] bg-[var(--otari-surface)] px-3 py-2 text-left text-xs font-normal whitespace-normal break-words text-[var(--otari-ink)] opacity-0 shadow-lg transition-opacity group-hover:opacity-100 group-focus-within:opacity-100",children:r})]})}function ii(){const t=Ft();return t.data?t.data.default_pricing?e.jsx(ue,{label:"How unpriced models are metered",tone:"info",children:"Default pricing is on: models without a configured price are metered using community-maintained rates (the bundled genai-prices dataset). Set a price to override the fallback."}):e.jsxs(ue,{label:"How unpriced models are metered",tone:"warning",children:["Default pricing is off: only models with a configured price are metered.",t.data.require_pricing?" Requests for any other model are rejected (HTTP 402) because require_pricing is on.":" Other models are served without cost tracking."]}):null}function I({label:t,value:i}){return e.jsxs("div",{className:"flex items-baseline justify-between gap-3",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:t}),e.jsx("span",{className:"text-right text-sm text-[var(--otari-ink)] tabular-nums",children:i})]})}function se({title:t,children:i}){return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("span",{className:"text-xs font-semibold uppercase tracking-wide text-[var(--otari-muted)]",children:t}),i]})}function ri({row:t}){const i=Ne(),r=He(),[a,n]=c.useState(!1),[s,u]=c.useState(""),[d,p]=c.useState(""),[P,y]=c.useState(""),[k,L]=c.useState(""),[j,h]=c.useState(""),[N,A]=c.useState([]),S=()=>{u(t.inputPrice==null?"":String(t.inputPrice)),p(t.outputPrice==null?"":String(t.outputPrice)),y(t.cacheReadPrice==null?"":String(t.cacheReadPrice)),L(t.cacheWritePrice==null?"":String(t.cacheWritePrice)),h(t.cacheWrite1hPrice==null?"":String(t.cacheWrite1hPrice)),A(Je(t.pricingTiers)),n(!0)},Z=oe(s)&&oe(d)&&w(P)&&w(k)&&w(j)&&Xe(N),T=()=>{Z&&i.mutate({model_key:t.key,input_price_per_million:Number(s),output_price_per_million:Number(d),cache_read_price_per_million:q(P),cache_write_price_per_million:q(k),cache_write_1h_price_per_million:q(j),pricing_tiers:Ze(N)},{onSuccess:()=>n(!1)})};return a?e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Input $ / 1M"}),e.jsx(b,{value:s,onChange:u,ariaLabel:`Input price for ${t.key}`})]}),e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Output $ / 1M"}),e.jsx(b,{value:d,onChange:p,ariaLabel:`Output price for ${t.key}`})]}),e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Cache read $ / 1M"}),e.jsx(b,{value:P,onChange:y,ariaLabel:`Cache read price for ${t.key}`})]}),e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Cache write $ / 1M"}),e.jsx(b,{value:k,onChange:L,ariaLabel:`Cache write price for ${t.key}`})]}),e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"1h cache write $ / 1M"}),e.jsx(b,{value:j,onChange:h,ariaLabel:`1 hour cache write price for ${t.key}`})]}),e.jsx(Qe,{tiers:N,onChange:A}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(W,{size:"sm",variant:"primary",isDisabled:i.isPending||!Z,onPress:T,children:"Save"}),e.jsx(W,{size:"sm",variant:"ghost",isDisabled:i.isPending,onPress:()=>n(!1),children:"Cancel"})]}),i.error?e.jsx("span",{className:"text-xs text-red-700",children:Pe(i.error)}):null]}):e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx(I,{label:"Input",value:t.inputPrice==null?"—":`${O(t.inputPrice)} / 1M`}),e.jsx(I,{label:"Output",value:t.outputPrice==null?"—":`${O(t.outputPrice)} / 1M`}),e.jsx(I,{label:"Cache read",value:t.cacheReadPrice==null?"—":`${O(t.cacheReadPrice)} / 1M`}),e.jsx(I,{label:"Cache write",value:t.cacheWritePrice==null?"—":`${O(t.cacheWritePrice)} / 1M`}),e.jsx(I,{label:"1h cache write",value:t.cacheWrite1hPrice==null?"—":`${O(t.cacheWrite1hPrice)} / 1M`}),e.jsx(I,{label:"Context tiers",value:t.pricingTiers.length?`${t.pricingTiers.length} configured`:"—"}),e.jsxs("div",{className:"flex items-center gap-2 pt-1",children:[e.jsx(W,{size:"sm",variant:"outline",onPress:S,children:t.source==="configured"?"Edit price":"Set price"}),t.source==="configured"?e.jsxs(e.Fragment,{children:[e.jsx(Ge,{confirmLabel:"Reset",isPending:r.isPending,onConfirm:()=>r.mutate(t.key),children:"Reset"}),e.jsx(ue,{label:"What reset does",children:"Removes the custom price. The model reverts to the default rate (genai-prices) when default pricing is on, otherwise it is metered at no cost."})]}):null,r.error?e.jsx("span",{className:"text-xs text-red-700",children:Pe(r.error)}):null]})]})}function ni({row:t,metadata:i,metadataAvailable:r,onMakeAlias:a,onClose:n}){const s=(i==null?void 0:i.input_modalities)??[],u=(i==null?void 0:i.output_modalities)??[],d=Ht.filter(({key:p})=>i==null?void 0:i[p]);return e.jsx(ce,{children:e.jsxs(ce.Content,{className:"flex flex-col gap-5 p-5",children:[e.jsxs("div",{className:"flex items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("h2",{className:"text-base font-semibold break-all text-[var(--otari-ink)]",children:t.model}),i!=null&&i.deprecated?e.jsx(V,{size:"sm",color:"danger",children:"deprecated"}):null]}),e.jsxs("p",{className:"mt-1 text-xs break-all text-[var(--otari-muted)]",children:["Selector:"," ",e.jsx(Ue,{value:t.key,label:"model id",children:e.jsx("code",{children:t.key})})]}),i!=null&&i.family?e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:i.family}):null]}),e.jsx("button",{type:"button","aria-label":"Close model details",onClick:n,className:"-mt-1 -mr-1 shrink-0 rounded-md px-1.5 py-0.5 text-lg leading-none text-[var(--otari-muted)] hover:bg-[var(--otari-bg)] hover:text-[var(--otari-ink)]",children:"✕"})]}),i!=null&&i.description?e.jsx("p",{className:"text-sm text-[var(--otari-ink)]",children:i.description}):null,e.jsxs(se,{title:"Pricing",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(ti,{source:t.source}),t.isDiscovered?null:e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"not discovered"})]}),e.jsx(ri,{row:t},t.key),e.jsx(W,{size:"sm",variant:"outline",onPress:()=>a(t.key),children:"Make an alias"})]}),e.jsxs(se,{title:"Specs",children:[e.jsx(I,{label:"Context window",value:ae(t.contextWindow)}),e.jsx(I,{label:"Max output",value:ae((i==null?void 0:i.max_output_tokens)??null)}),e.jsx(I,{label:"Knowledge cutoff",value:(i==null?void 0:i.knowledge_cutoff)??"—"}),e.jsx(I,{label:"Released",value:Dt(i==null?void 0:i.release_date)}),e.jsx(I,{label:"Open weights",value:i?i.open_weights?"Yes":"No":"—"})]}),e.jsx(se,{title:"Modalities",children:s.length===0&&u.length===0?e.jsx("span",{className:"text-sm text-[var(--otari-muted)]",children:"Unknown."}):e.jsxs("div",{className:"flex flex-col gap-1.5 text-xs text-[var(--otari-muted)]",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-1",children:[e.jsx("span",{children:"In:"}),s.map(p=>e.jsx(V,{size:"sm",color:"default",children:Ve[p]??p},p))]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-1",children:[e.jsx("span",{children:"Out:"}),u.map(p=>e.jsx(V,{size:"sm",color:"default",children:Ve[p]??p},p))]})]})}),e.jsx(se,{title:"Capabilities",children:d.length>0?e.jsx("div",{className:"flex flex-wrap gap-1.5",children:d.map(({key:p,label:P})=>e.jsx(V,{size:"sm",color:"default",children:P},p))}):e.jsx("span",{className:"text-sm text-[var(--otari-muted)]",children:r?"None reported.":"Extended metadata unavailable (models.dev disabled or unreachable)."})})]})})}const li=15;function si({value:t,onChange:i,placeholder:r}){return e.jsx("input",{type:"search",value:t,onChange:a=>i(a.target.value),placeholder:r,"aria-label":r,className:"w-full max-w-xs rounded-md border border-[var(--otari-line)] bg-white px-3 py-1.5 text-sm focus:border-[var(--otari-brand)] focus:outline-none"})}const et="otari.dashboard.modelsSort",je={col:"model",dir:"asc"},ai=["model","released","input","output"];function ci(){if(typeof window>"u")return je;try{const t=window.localStorage.getItem(et);if(!t)return je;const i=JSON.parse(t);if(ai.includes(i.col)&&(i.dir==="asc"||i.dir==="desc"))return{col:i.col,dir:i.dir}}catch{}return je}function oi({row:t,onClose:i}){const r=Ne(),a=He(),[n,s]=c.useState(t.inputPrice==null?"":String(t.inputPrice)),[u,d]=c.useState(t.outputPrice==null?"":String(t.outputPrice)),[p,P]=c.useState(t.cacheReadPrice==null?"":String(t.cacheReadPrice)),[y,k]=c.useState(t.cacheWritePrice==null?"":String(t.cacheWritePrice)),[L,j]=c.useState(t.cacheWrite1hPrice==null?"":String(t.cacheWrite1hPrice)),[h,N]=c.useState(Je(t.pricingTiers)),A=oe(n)&&oe(u)&&w(p)&&w(y)&&w(L)&&Xe(h),S=()=>{A&&r.mutate({model_key:t.key,input_price_per_million:Number(n),output_price_per_million:Number(u),cache_read_price_per_million:q(p),cache_write_price_per_million:q(y),cache_write_1h_price_per_million:q(L),pricing_tiers:Ze(h)},{onSuccess:i})};return e.jsxs("div",{className:"flex flex-col gap-3 px-4 py-3",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[e.jsx("span",{className:"text-xs font-medium break-all text-[var(--otari-muted)]",children:t.key}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["Input $ / 1M",e.jsx(b,{value:n,onChange:s,ariaLabel:`Input price for ${t.key}`})]}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["Output $ / 1M",e.jsx(b,{value:u,onChange:d,ariaLabel:`Output price for ${t.key}`})]}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["Cache read $ / 1M",e.jsx(b,{value:p,onChange:P,ariaLabel:`Cache read price for ${t.key}`})]}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["Cache write $ / 1M",e.jsx(b,{value:y,onChange:k,ariaLabel:`Cache write price for ${t.key}`})]}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["1h cache write $ / 1M",e.jsx(b,{value:L,onChange:j,ariaLabel:`1 hour cache write price for ${t.key}`})]}),e.jsx(W,{size:"sm",variant:"primary",isDisabled:r.isPending||!A,onPress:S,children:r.isPending?"Saving…":"Save"}),e.jsx(W,{size:"sm",variant:"ghost",isDisabled:r.isPending,onPress:i,children:"Cancel"}),t.source==="configured"?e.jsxs("span",{className:"inline-flex items-center gap-1",children:[e.jsx(Ge,{confirmLabel:"Reset",isPending:a.isPending,onConfirm:()=>a.mutate(t.key,{onSuccess:i}),children:"Reset"}),e.jsx(ue,{label:"What reset does",children:"Removes the custom price. The model reverts to the default rate (genai-prices) when default pricing is on, otherwise it is metered at no cost."})]}):null,r.error||a.error?e.jsx("span",{className:"text-xs text-red-700",children:Pe(r.error??a.error)}):null]}),e.jsx(Qe,{tiers:h,onChange:N})]})}function ui({primary:t,secondary:i,rowKey:r,primaryLabel:a,secondaryLabel:n,onEdit:s}){const u=(d,p)=>e.jsx("button",{type:"button","aria-label":`Edit ${p} price for ${r}`,className:"tabular-nums hover:text-[var(--otari-brand-dark)] hover:underline",onClick:P=>{P.stopPropagation(),s()},children:d==null?"—":O(d)});return e.jsxs("span",{className:"inline-flex items-center justify-end gap-1",children:[u(t,a),e.jsx("span",{className:"text-[var(--otari-muted)]",children:"/"}),u(i,n)]})}function di({rates:t,rowKey:i,onEdit:r}){const a=[t.cacheReadPrice==null?null:`R ${O(t.cacheReadPrice)}`,t.cacheWritePrice==null?null:`W ${O(t.cacheWritePrice)}`,t.cacheWrite1hPrice==null?null:`1h ${O(t.cacheWrite1hPrice)}`].filter(n=>n!==null);return e.jsx("button",{type:"button","aria-label":`Edit caching price for ${i}`,className:"max-w-44 text-right text-xs leading-5 text-[var(--otari-muted)] hover:text-[var(--otari-brand-dark)] hover:underline",onClick:n=>{n.stopPropagation(),r()},children:a.length>0?a.join(" · "):"Input-rate fallback"})}function pi({row:t,onEdit:i}){const r=[...t.pricingTiers].sort((n,s)=>n.min_input_tokens-s.min_input_tokens).map(n=>ae(n.min_input_tokens)),a=r.length===0?"Base only":`${r.length} tier${r.length===1?"":"s"} · ≥ ${r.join(", ")}`;return e.jsx("button",{type:"button","aria-label":`Edit pricing policy for ${t.key}`,className:"max-w-40 text-right text-xs leading-5 text-[var(--otari-muted)] hover:text-[var(--otari-brand-dark)] hover:underline",onClick:n=>{n.stopPropagation(),i()},children:a})}function mi({rows:t,isLoading:i,empty:r,sortDescriptor:a,onSortChange:n,selectedKey:s,onSelect:u,onEditPricing:d,comparisonContextTokens:p,selectedKeys:P,onSelectionChange:y}){const k=c.useMemo(()=>{const j=p==null?"Base":`at ${ae(p)}`;return[{id:"model",header:"Model",isRowHeader:!0,allowsSorting:!0,cell:h=>e.jsxs(Ue,{value:h.key,label:"model id",className:"font-medium break-all",children:[h.model,e.jsx("span",{className:"sr-only select-none",children:h.key})]})},{id:"provider",header:"Provider",cell:h=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:h.provider})},{id:"input",header:e.jsxs("span",{className:"inline-flex items-center gap-1",children:[`${j} in / out $ / 1M`,e.jsx(ii,{})]}),align:"end",allowsSorting:!0,cell:h=>{const N=Ye(h,p);return e.jsx(ui,{primary:N.inputPrice,secondary:N.outputPrice,rowKey:h.key,primaryLabel:"input",secondaryLabel:"output",onEdit:()=>d(h.key)})}},{id:"caching",header:`Caching ${p==null?"policy":j}`,align:"end",cell:h=>e.jsx(di,{rates:Ye(h,p),rowKey:h.key,onEdit:()=>d(h.key)})},{id:"policy",header:"Pricing policy",align:"end",cell:h=>e.jsx(pi,{row:h,onEdit:()=>d(h.key)})}]},[p,d]),L=c.useCallback(j=>j.key===s?"bg-[var(--otari-brand-tint)]":void 0,[s]);return e.jsx(zt,{ariaLabel:"Models",columns:k,rows:t,getRowKey:ei,isLoading:i,emptyContent:r,selectionMode:"multiple",selectedKeys:P,onSelectionChange:y,sortDescriptor:a,onSortChange:n,onRowAction:u,rowClassName:L})}function hi({providers:t,onPriceModel:i}){if(t.length===0)return null;const r=t.filter(u=>!u.discovery_unsupported),a=t.filter(u=>u.discovery_unsupported),n=u=>u.map(d=>d.provider).join(", "),s=u=>u.length===1;return e.jsxs(Ot,{tone:"warning",children:[r.length>0?e.jsxs("span",{className:"block",children:["Could not list ",n(r),". Check ",s(r)?"that provider's":"those providers'"," ","credentials in config.yml; ",s(r)?"its":"their"," models are missing from the list below."]}):null,a.length>0?e.jsxs("span",{className:"block",children:[n(a)," ",s(a)?"does":"do"," not offer model discovery, so"," ",s(a)?"its":"their"," models are missing from the list below."," ",s(a)?"The provider":"They"," may still serve requests. Price a model by its selector to meter it here, or declare the model ids ",s(a)?"it serves":"they serve"," under the"," ",e.jsx("code",{children:"models:"})," key in config.yml to list them all.",e.jsx(W,{size:"sm",variant:"outline",className:"mt-2",onPress:()=>i(s(a)?`${a[0].provider}:`:""),children:"Price a model"})]}):null]})}function Ni(){var Oe,De,Fe;const t=Mt(),[i]=Wt(),r=Tt(),a=It(),n=Lt(),s=$t(),u=Ne(),[d,p]=c.useState(""),[P,y]=c.useState(0),[k,L]=c.useState(li),[j,h]=c.useState(null),[N,A]=c.useState(null),[S,Z]=c.useState(ci),T=Kt(),[tt,de]=c.useState(!1),[it,Se]=c.useState(!1),[rt,Ce]=c.useState(void 0),[pe,Q]=c.useState(null),[nt,ke]=c.useState(!1),[lt,Me]=c.useState(void 0);c.useEffect(()=>{try{window.localStorage.setItem(et,JSON.stringify(S))}catch{}},[S]);const[D,We]=c.useState(i.get("provider")||"all"),[B,st]=c.useState("all"),[Y,at]=c.useState("all"),[U,ct]=c.useState("all"),[me,ot]=c.useState("0"),[ee,ut]=c.useState(""),[te,dt]=c.useState("all"),[he,pt]=c.useState(""),F=((Oe=s.data)==null?void 0:Oe.models)??{},mt=((De=s.data)==null?void 0:De.available)??!1,Te=c.useMemo(()=>{var l;return new Set((((l=n.data)==null?void 0:l.providers)??[]).flatMap(x=>x.models.map(o=>o.key)))},[n.data]),ht=l=>{p(l),y(0)},R=l=>x=>{l(x),y(0)},ie=c.useMemo(()=>{var M,_,v,E,$,z,Ke;const l=new Map(qt(a.data??[]).map(m=>[m.model_key,m])),x=[],o=new Set,g=(m,ye,Be,f)=>{if(o.has(m))return;o.add(m);const C=l.get(m);x.push({key:m,model:qe(ye,Be),provider:Be,isDiscovered:Te.has(m),contextWindow:(f==null?void 0:f.contextWindow)??null,inputPrice:C?C.input_price_per_million:(f==null?void 0:f.inputPrice)??null,outputPrice:C?C.output_price_per_million:(f==null?void 0:f.outputPrice)??null,cacheReadPrice:C?C.cache_read_price_per_million:(f==null?void 0:f.cacheReadPrice)??null,cacheWritePrice:C?C.cache_write_price_per_million:(f==null?void 0:f.cacheWritePrice)??null,cacheWrite1hPrice:C?C.cache_write_1h_price_per_million??null:(f==null?void 0:f.cacheWrite1hPrice)??null,pricingTiers:C?C.pricing_tiers??[]:(f==null?void 0:f.pricingTiers)??[],source:C?"configured":(f==null?void 0:f.source)??"none"})};for(const m of((M=r.data)==null?void 0:M.data)??[]){if(m.owned_by===Ut)continue;const ye=m.pricing_source==="default"?"default":m.pricing?"configured":"none";g(m.id,m.id,m.owned_by||ze(m.id),{key:m.id,model:m.id,provider:m.owned_by,contextWindow:m.context_window,inputPrice:((_=m.pricing)==null?void 0:_.input_price_per_million)??null,outputPrice:((v=m.pricing)==null?void 0:v.output_price_per_million)??null,cacheReadPrice:((E=m.pricing)==null?void 0:E.cache_read_price_per_million)??null,cacheWritePrice:(($=m.pricing)==null?void 0:$.cache_write_price_per_million)??null,cacheWrite1hPrice:((z=m.pricing)==null?void 0:z.cache_write_1h_price_per_million)??null,pricingTiers:((Ke=m.pricing)==null?void 0:Ke.pricing_tiers)??[],source:ye})}for(const m of l.keys())m.startsWith(`${Yt}:`)||g(m,m,ze(m));return x},[r.data,a.data,Te]),Ie=c.useMemo(()=>new Map(ie.map(l=>[l.key,l])),[ie]),re=c.useMemo(()=>{var o,g,M;const l=ie.map(_=>{var v,E;return{..._,contextWindow:_.contextWindow??((v=F[_.key])==null?void 0:v.context_window)??null,releaseDate:((E=F[_.key])==null?void 0:E.release_date)??null}}),x=new Set(l.map(_=>_.key));for(const _ of((o=n.data)==null?void 0:o.providers)??[])for(const v of _.models)x.has(v.key)||(x.add(v.key),l.push({key:v.key,model:qe(v.key,_.provider),provider:_.provider,isDiscovered:!0,contextWindow:((g=F[v.key])==null?void 0:g.context_window)??null,releaseDate:((M=F[v.key])==null?void 0:M.release_date)??null,inputPrice:null,outputPrice:null,cacheReadPrice:null,cacheWritePrice:null,cacheWrite1hPrice:null,pricingTiers:[],source:"none"}));return l},[ie,n.data,F]),xt=(((Fe=n.data)==null?void 0:Fe.providers)??[]).filter(l=>!l.ok),ne=c.useMemo(()=>{const l=Array.from(new Set(re.map(x=>x.provider))).sort((x,o)=>x.localeCompare(o));return[{value:"all",label:"All providers"},...l.map(x=>({value:x,label:x}))]},[re]);c.useEffect(()=>{D==="all"||ne.length<=1||ne.some(l=>l.value===D)||We("all")},[ne,D]);const H=d.trim().toLowerCase(),xe=Number(me)||0,fe=ee===""?Number.POSITIVE_INFINITY:Number(ee),ft=he===""?null:Number(he),ge=te==="all"?null:Date.now()-Number(te)*Qt,G=c.useMemo(()=>{const l=o=>{if(H&&!o.key.toLowerCase().includes(H)&&!o.provider.toLowerCase().includes(H)||D!=="all"&&o.provider!==D||B==="configured"&&o.source!=="configured"||B==="default"&&o.source!=="default"||B==="priced"&&o.inputPrice==null||B==="unpriced"&&o.inputPrice!=null||Y==="discovered"&&!o.isDiscovered||Y==="custom"&&o.isDiscovered)return!1;if(U!=="all"){const g=we.find(_=>_.value===U),M=F[o.key];if(!g||!M||!g.test(M))return!1}if(xe>0&&(o.contextWindow==null||o.contextWindowfe))return!1;if(ge!=null){const g=o.releaseDate?Date.parse(o.releaseDate):Number.NaN;if(Number.isNaN(g)||g{const M=S.dir==="asc"?1:-1;if(S.col==="model")return o.model.localeCompare(g.model)*M;if(S.col==="released"){const $=o.releaseDate??null,z=g.releaseDate??null;return!$&&!z?o.model.localeCompare(g.model):$?z?($z?1:0)*M||o.model.localeCompare(g.model):-1:1}const _=$=>S.col==="input"?$.inputPrice:$.outputPrice,v=_(o),E=_(g);return v==null&&E==null?o.model.localeCompare(g.model):v==null?1:E==null?-1:(v-E)*M||o.model.localeCompare(g.model)};return re.filter(l).sort(x)},[re,H,D,B,Y,U,xe,fe,ge,F,S]),J=G.length,gt=Math.max(1,Math.ceil(J/k)),Le=Math.min(P,gt-1),$e=Le*k,_e=G.slice($e,$e+k),_t={column:S.col,direction:S.dir==="asc"?"ascending":"descending"},vt=l=>{Z({col:String(l.column),dir:l.direction==="ascending"?"asc":"desc"}),y(0)},bt=c.useCallback(l=>h(x=>x===l?null:l),[]),ve=_e.map(l=>l.key),X=Bt(T.selectedKeys,ve),yt=ve.length>0&&X.length===ve.length&&J>X.length,jt=T.allMatching?G.map(l=>l.key):X,Ae=T.allMatching?J:X.length,Ee=j?G.find(l=>l.key===j)??Ie.get(j)??null:null,Pt=async l=>{Se(!0),Ce(void 0);try{for(const x of jt)await u.mutateAsync({model_key:x,input_price_per_million:l.input_price_per_million,output_price_per_million:l.output_price_per_million,cache_read_price_per_million:l.cache_read_price_per_million??null,cache_write_price_per_million:l.cache_write_price_per_million??null,cache_write_1h_price_per_million:null,pricing_tiers:[]});T.clear(),de(!1)}catch(x){Ce(x)}finally{Se(!1)}},Nt=async(l,x)=>{ke(!0),Me(void 0);try{const o=await u.mutateAsync({model_key:x,input_price_per_million:l.input_price_per_million,output_price_per_million:l.output_price_per_million,cache_read_price_per_million:l.cache_read_price_per_million??null,cache_write_price_per_million:l.cache_write_price_per_million??null});Q(null),A(o.model_key)}catch(o){Me(o)}finally{ke(!1)}},St=r.isLoading||a.isLoading||n.isLoading,Ct=H!==""||D!=="all"||B!=="all"||Y!=="all"||U!=="all"||me!=="0"||ee!==""||te!=="all",be=wt(d)?d.trim():null,kt=e.jsxs("div",{className:"flex flex-col items-center gap-2 py-2",children:[e.jsx("span",{children:Ct?"No models match your filters.":"No models yet. Add a provider on the Providers page."}),e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"A provider that serves no model listing still answers requests, so a model you can call may not be listed here."}),e.jsx(W,{size:"sm",variant:"outline",onPress:()=>Q(be??""),children:be?`Price ${be}`:"Price a model by hand"})]}),le=N?G.find(l=>l.key===N)??Ie.get(N)??null:null;return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(At,{title:"Models",description:"Every model your providers can serve. Set a price on any model so budgets and usage tracking work."}),e.jsx(Et,{error:r.error??a.error??n.error??s.error}),e.jsxs("div",{className:`grid gap-4 lg:items-start ${le?"lg:grid-cols-[minmax(0,1fr)_360px]":"grid-cols-1"}`,children:[e.jsxs("div",{className:"flex min-w-0 flex-col gap-3",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx(si,{value:d,onChange:ht,placeholder:"Search models…"}),e.jsx(K,{ariaLabel:"Filter by provider",value:D,onChange:R(We),options:ne}),e.jsx(K,{ariaLabel:"Filter by pricing",value:B,onChange:R(st),options:[{value:"all",label:"Any pricing"},{value:"configured",label:"Custom price"},{value:"default",label:"Default price"},{value:"priced",label:"Priced"},{value:"unpriced",label:"Unpriced"}]}),e.jsx(K,{ariaLabel:"Filter by source",value:Y,onChange:R(at),options:[{value:"all",label:"Any source"},{value:"discovered",label:"Discovered"},{value:"custom",label:"Custom (not discovered)"}]}),e.jsx(K,{ariaLabel:"Filter by capability",value:U,onChange:R(ct),options:[{value:"all",label:"Any capability"},...we.map(l=>({value:l.value,label:l.label}))]}),e.jsx(K,{ariaLabel:"Minimum context window",value:me,onChange:R(ot),options:Gt}),e.jsx(K,{ariaLabel:"Maximum input price",value:ee,onChange:R(ut),options:Jt}),e.jsx(K,{ariaLabel:"Compare prices at context",value:he,onChange:pt,options:Xt}),e.jsx(K,{ariaLabel:"Filter by release date",value:te,onChange:R(dt),options:Zt})]}),e.jsx(hi,{providers:xt,onPriceModel:Q}),X.length>0?e.jsx(Rt,{selectedCount:Ae,allMatching:T.allMatching,matchingTotal:J,canSelectAllMatching:yt,onSelectAllMatching:T.enableAllMatching,onClear:T.clear,children:e.jsx(W,{size:"sm",variant:"primary",onPress:()=>de(!0),children:"Set pricing"})}):null,e.jsx(mi,{rows:_e,isLoading:St,empty:kt,sortDescriptor:_t,onSortChange:vt,selectedKey:N,onSelect:A,onEditPricing:bt,comparisonContextTokens:ft,selectedKeys:T.selectedKeys,onSelectionChange:T.onSelectionChange}),Ee?e.jsx(ce,{children:e.jsxs(ce.Content,{className:"p-0",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-[var(--otari-line)] px-4 py-2",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Edit pricing"}),e.jsx(W,{size:"sm",variant:"ghost",onPress:()=>h(null),children:"Close"})]}),e.jsx(oi,{row:Ee,onClose:()=>h(null)})]})}):null,e.jsx(Vt,{page:Le,pageSize:k,total:J,rowsOnPage:_e.length,onPageChange:y,onPageSizeChange:l=>{L(l),y(0)},pageSizeOptions:[15,25,50]})]}),le?e.jsx("aside",{className:"lg:sticky lg:top-4",children:e.jsx(ni,{row:le,metadata:F[le.key],metadataAvailable:mt,onMakeAlias:l=>t(`/routing?target=${encodeURIComponent(l)}`),onClose:()=>A(null)})}):null]}),e.jsx(Re,{isOpen:tt,onOpenChange:de,targetCount:Ae,isPending:it,error:rt,onSubmit:Pt,title:"Set pricing",description:l=>`Apply these per-1M rates to ${l.toLocaleString()} selected ${l===1?"model":"models"}. This replaces each model's price; pricing tiers and the 1h cache rate are cleared. Edit a single model for tiers.`}),e.jsx(Re,{isOpen:pe!==null,onOpenChange:l=>Q(l?pe??"":null),isPending:nt,error:lt,onSubmit:Nt,collectModelKey:!0,initialModelKey:pe??"",title:"Price a model",description:()=>"Meter a model the catalogue does not list, for a provider that serves no model listing. Type the selector you send as model and its rates; the model then appears here as custom, and its usage is costed and counted against budgets."})]})}export{Ni as ModelsPage}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{i as Mt,u as Wt,r as c}from"./react-dgEcD0HR.js";import{U as Tt,V as It,s as Lt,W as $t,m as Ne,P as At,E as Et,F as K,M as Ot,X as ae,q as Ue,Z as He,y as Ge,_ as Pe,$ as Dt,a0 as Ft,a1 as O}from"./index-Dit1BUBh.js";import{u as Kt,r as Bt,B as Rt}from"./tableSelection-B1umVgqc.js";import{D as zt}from"./DataTable-BHrpJHmX.js";import{i as wt,T as Vt,S as Re}from"./TablePagination-BynkRKqB.js";import{B as W,d as ce,g as V}from"./heroui-DhloIxuc.js";import"./Field-GEMwIhf7.js";function ze(t){const i=t.indexOf(":");return i>0?t.slice(0,i):"—"}function qt(t,i=Date.now()){const r=new Map;for(const n of t){const s=r.get(n.model_key)??[];s.push(n),r.set(n.model_key,s)}const a=[];for(const n of r.values()){const s=[...n].sort((d,p)=>Date.parse(d.effective_at)-Date.parse(p.effective_at)),u=[...s].reverse().find(d=>Date.parse(d.effective_at)<=i);a.push(u??s[0])}return a.sort((n,s)=>n.model_key.localeCompare(s.model_key))}const Yt="otari",Ut="otari",we=[{value:"vision",label:"Vision",test:t=>Array.isArray(t.input_modalities)&&t.input_modalities.includes("image")},{value:"tool_call",label:"Tool calling",test:t=>!!t.tool_call},{value:"reasoning",label:"Reasoning",test:t=>!!t.reasoning},{value:"structured_output",label:"Structured output",test:t=>!!t.structured_output},{value:"attachment",label:"Attachments",test:t=>!!t.attachment},{value:"audio",label:"Audio",test:t=>Array.isArray(t.input_modalities)&&t.input_modalities.includes("audio")},{value:"pdf",label:"PDF",test:t=>Array.isArray(t.input_modalities)&&t.input_modalities.includes("pdf")}],Ht=[{key:"reasoning",label:"Reasoning"},{key:"tool_call",label:"Tool calling"},{key:"structured_output",label:"Structured output"},{key:"attachment",label:"Attachments"},{key:"temperature",label:"Temperature"}],Ve={text:"Text",image:"Image",audio:"Audio",video:"Video",pdf:"PDF"},Gt=[{value:"0",label:"Any context"},{value:"8000",label:"≥ 8K"},{value:"32000",label:"≥ 32K"},{value:"128000",label:"≥ 128K"},{value:"200000",label:"≥ 200K"},{value:"1000000",label:"≥ 1M"}],Jt=[{value:"",label:"Any price"},{value:"1",label:"≤ $1 / 1M in"},{value:"3",label:"≤ $3 / 1M in"},{value:"10",label:"≤ $10 / 1M in"},{value:"30",label:"≤ $30 / 1M in"}],Xt=[{value:"",label:"Base prices"},{value:"8000",label:"Compare at 8K"},{value:"128000",label:"Compare at 128K"},{value:"200000",label:"Compare at 200K"},{value:"500000",label:"Compare at 500K"},{value:"1000000",label:"Compare at 1M"}],Zt=[{value:"all",label:"Any release date"},{value:"365",label:"Past year"},{value:"730",label:"Past 2 years"},{value:"1095",label:"Past 3 years"}],Qt=1440*60*1e3,ei=t=>t.key;function qe(t,i){const r=`${i}:`;return t.startsWith(r)?t.slice(r.length):t}function Ye(t,i){const r={inputPrice:t.inputPrice,outputPrice:t.outputPrice,cacheReadPrice:t.cacheReadPrice,cacheWritePrice:t.cacheWritePrice,cacheWrite1hPrice:t.cacheWrite1hPrice};if(i==null)return r;const a=t.pricingTiers.filter(n=>n.min_input_tokens<=i).sort((n,s)=>s.min_input_tokens-n.min_input_tokens)[0];return a?{inputPrice:a.input_price_per_million??r.inputPrice,outputPrice:a.output_price_per_million??r.outputPrice,cacheReadPrice:a.cache_read_price_per_million??r.cacheReadPrice,cacheWritePrice:a.cache_write_price_per_million??r.cacheWritePrice,cacheWrite1hPrice:a.cache_write_1h_price_per_million??r.cacheWrite1hPrice}:r}function oe(t){const i=Number(t);return t.trim()!==""&&Number.isFinite(i)&&i>=0}function w(t){if(t.trim()==="")return!0;const i=Number(t);return Number.isFinite(i)&&i>=0}function q(t){return t.trim()===""?null:Number(t)}function Je(t){return t.map((i,r)=>({id:r,minInputTokens:String(i.min_input_tokens),input:i.input_price_per_million==null?"":String(i.input_price_per_million),output:i.output_price_per_million==null?"":String(i.output_price_per_million),cacheRead:i.cache_read_price_per_million==null?"":String(i.cache_read_price_per_million),cacheWrite:i.cache_write_price_per_million==null?"":String(i.cache_write_price_per_million),cacheWrite1h:i.cache_write_1h_price_per_million==null?"":String(i.cache_write_1h_price_per_million)}))}function Xe(t){const i=new Set;return t.every(r=>{const a=Number(r.minInputTokens),n=[r.input,r.output,r.cacheRead,r.cacheWrite,r.cacheWrite1h].some(s=>s.trim()!=="");return!Number.isInteger(a)||a<=0||i.has(a)||!n?!1:(i.add(a),[r.input,r.output,r.cacheRead,r.cacheWrite,r.cacheWrite1h].every(w))})}function Ze(t){return t.map(i=>({min_input_tokens:Number(i.minInputTokens),...i.input.trim()===""?{}:{input_price_per_million:Number(i.input)},...i.output.trim()===""?{}:{output_price_per_million:Number(i.output)},...i.cacheRead.trim()===""?{}:{cache_read_price_per_million:Number(i.cacheRead)},...i.cacheWrite.trim()===""?{}:{cache_write_price_per_million:Number(i.cacheWrite)},...i.cacheWrite1h.trim()===""?{}:{cache_write_1h_price_per_million:Number(i.cacheWrite1h)}}))}function b({value:t,onChange:i,ariaLabel:r}){return e.jsx("input",{type:"number",step:"any",min:"0",inputMode:"decimal","aria-label":r,value:t,onChange:a=>i(a.target.value),className:"w-28 rounded-md border border-[var(--otari-line)] bg-white px-2 py-1 text-right text-sm tabular-nums focus:border-[var(--otari-brand)] focus:outline-none"})}function Qe({tiers:t,onChange:i}){const r=(n,s,u)=>{i(t.map(d=>d.id===n?{...d,[s]:u}:d))},a=()=>{const n=t.reduce((s,u)=>Math.max(s,u.id),-1)+1;i([...t,{id:n,minInputTokens:"128000",input:"",output:"",cacheRead:"",cacheWrite:"",cacheWrite1h:""}])};return e.jsxs("div",{className:"flex flex-col gap-2 rounded-lg border border-[var(--otari-line)] p-3",children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-xs font-medium text-[var(--otari-ink)]",children:"Long-context price tiers"}),e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"At a threshold, listed rates replace the base rate for the whole request."})]}),e.jsx(W,{size:"sm",variant:"outline",onPress:a,children:"Add tier"})]}),t.map(n=>e.jsxs("div",{className:"flex flex-wrap items-end gap-2 border-t border-[var(--otari-line)] pt-2",children:[e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Context ≥ tokens",e.jsx("input",{type:"number",min:"1",step:"1",inputMode:"numeric","aria-label":"Tier context threshold",value:n.minInputTokens,onChange:s=>r(n.id,"minInputTokens",s.target.value),className:"w-28 rounded-md border border-[var(--otari-line)] bg-white px-2 py-1 text-right text-sm tabular-nums focus:border-[var(--otari-brand)] focus:outline-none"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Input",e.jsx(b,{value:n.input,onChange:s=>r(n.id,"input",s),ariaLabel:"Tier input price"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Output",e.jsx(b,{value:n.output,onChange:s=>r(n.id,"output",s),ariaLabel:"Tier output price"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Cache read",e.jsx(b,{value:n.cacheRead,onChange:s=>r(n.id,"cacheRead",s),ariaLabel:"Tier cache read price"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["Cache write",e.jsx(b,{value:n.cacheWrite,onChange:s=>r(n.id,"cacheWrite",s),ariaLabel:"Tier cache write price"})]}),e.jsxs("label",{className:"flex flex-col gap-1 text-xs text-[var(--otari-muted)]",children:["1h write",e.jsx(b,{value:n.cacheWrite1h,onChange:s=>r(n.id,"cacheWrite1h",s),ariaLabel:"Tier 1 hour cache write price"})]}),e.jsx(W,{size:"sm",variant:"ghost",onPress:()=>i(t.filter(s=>s.id!==n.id)),children:"Remove"})]},n.id))]})}function ti({source:t}){return t==="configured"?e.jsx(V,{size:"sm",color:"default",children:"configured"}):t==="default"||t==="alias"?e.jsx(V,{size:"sm",color:"accent",children:t}):e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"not priced"})}function ue({label:t,tone:i="info",children:r}){const a=c.useId();return e.jsxs("span",{className:"group relative inline-flex items-center font-normal normal-case",children:[e.jsx("button",{type:"button","aria-label":t,"aria-describedby":a,className:`inline-flex h-4 w-4 items-center justify-center rounded-full border text-[10px] leading-none ${i==="warning"?"border-[#c2843a] text-[#b45309]":"border-[var(--otari-line)] text-[var(--otari-muted)] hover:border-[var(--otari-brand)] hover:text-[var(--otari-brand)]"}`,children:"i"}),e.jsx("span",{id:a,role:"tooltip",className:"pointer-events-none absolute top-full right-0 z-20 mt-1.5 w-72 rounded-lg border border-[var(--otari-line)] bg-[var(--otari-surface)] px-3 py-2 text-left text-xs font-normal whitespace-normal break-words text-[var(--otari-ink)] opacity-0 shadow-lg transition-opacity group-hover:opacity-100 group-focus-within:opacity-100",children:r})]})}function ii(){const t=Ft();return t.data?t.data.default_pricing?e.jsx(ue,{label:"How unpriced models are metered",tone:"info",children:"Default pricing is on: models without a configured price are metered using community-maintained rates (the bundled genai-prices dataset). Set a price to override the fallback."}):e.jsxs(ue,{label:"How unpriced models are metered",tone:"warning",children:["Default pricing is off: only models with a configured price are metered.",t.data.require_pricing?" Requests for any other model are rejected (HTTP 402) because require_pricing is on.":" Other models are served without cost tracking."]}):null}function I({label:t,value:i}){return e.jsxs("div",{className:"flex items-baseline justify-between gap-3",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:t}),e.jsx("span",{className:"text-right text-sm text-[var(--otari-ink)] tabular-nums",children:i})]})}function se({title:t,children:i}){return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("span",{className:"text-xs font-semibold uppercase tracking-wide text-[var(--otari-muted)]",children:t}),i]})}function ri({row:t}){const i=Ne(),r=He(),[a,n]=c.useState(!1),[s,u]=c.useState(""),[d,p]=c.useState(""),[P,y]=c.useState(""),[k,L]=c.useState(""),[j,h]=c.useState(""),[N,A]=c.useState([]),S=()=>{u(t.inputPrice==null?"":String(t.inputPrice)),p(t.outputPrice==null?"":String(t.outputPrice)),y(t.cacheReadPrice==null?"":String(t.cacheReadPrice)),L(t.cacheWritePrice==null?"":String(t.cacheWritePrice)),h(t.cacheWrite1hPrice==null?"":String(t.cacheWrite1hPrice)),A(Je(t.pricingTiers)),n(!0)},Z=oe(s)&&oe(d)&&w(P)&&w(k)&&w(j)&&Xe(N),T=()=>{Z&&i.mutate({model_key:t.key,input_price_per_million:Number(s),output_price_per_million:Number(d),cache_read_price_per_million:q(P),cache_write_price_per_million:q(k),cache_write_1h_price_per_million:q(j),pricing_tiers:Ze(N)},{onSuccess:()=>n(!1)})};return a?e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Input $ / 1M"}),e.jsx(b,{value:s,onChange:u,ariaLabel:`Input price for ${t.key}`})]}),e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Output $ / 1M"}),e.jsx(b,{value:d,onChange:p,ariaLabel:`Output price for ${t.key}`})]}),e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Cache read $ / 1M"}),e.jsx(b,{value:P,onChange:y,ariaLabel:`Cache read price for ${t.key}`})]}),e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Cache write $ / 1M"}),e.jsx(b,{value:k,onChange:L,ariaLabel:`Cache write price for ${t.key}`})]}),e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"1h cache write $ / 1M"}),e.jsx(b,{value:j,onChange:h,ariaLabel:`1 hour cache write price for ${t.key}`})]}),e.jsx(Qe,{tiers:N,onChange:A}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(W,{size:"sm",variant:"primary",isDisabled:i.isPending||!Z,onPress:T,children:"Save"}),e.jsx(W,{size:"sm",variant:"ghost",isDisabled:i.isPending,onPress:()=>n(!1),children:"Cancel"})]}),i.error?e.jsx("span",{className:"text-xs text-red-700",children:Pe(i.error)}):null]}):e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx(I,{label:"Input",value:t.inputPrice==null?"—":`${O(t.inputPrice)} / 1M`}),e.jsx(I,{label:"Output",value:t.outputPrice==null?"—":`${O(t.outputPrice)} / 1M`}),e.jsx(I,{label:"Cache read",value:t.cacheReadPrice==null?"—":`${O(t.cacheReadPrice)} / 1M`}),e.jsx(I,{label:"Cache write",value:t.cacheWritePrice==null?"—":`${O(t.cacheWritePrice)} / 1M`}),e.jsx(I,{label:"1h cache write",value:t.cacheWrite1hPrice==null?"—":`${O(t.cacheWrite1hPrice)} / 1M`}),e.jsx(I,{label:"Context tiers",value:t.pricingTiers.length?`${t.pricingTiers.length} configured`:"—"}),e.jsxs("div",{className:"flex items-center gap-2 pt-1",children:[e.jsx(W,{size:"sm",variant:"outline",onPress:S,children:t.source==="configured"?"Edit price":"Set price"}),t.source==="configured"?e.jsxs(e.Fragment,{children:[e.jsx(Ge,{confirmLabel:"Reset",isPending:r.isPending,onConfirm:()=>r.mutate(t.key),children:"Reset"}),e.jsx(ue,{label:"What reset does",children:"Removes the custom price. The model reverts to the default rate (genai-prices) when default pricing is on, otherwise it is metered at no cost."})]}):null,r.error?e.jsx("span",{className:"text-xs text-red-700",children:Pe(r.error)}):null]})]})}function ni({row:t,metadata:i,metadataAvailable:r,onMakeAlias:a,onClose:n}){const s=(i==null?void 0:i.input_modalities)??[],u=(i==null?void 0:i.output_modalities)??[],d=Ht.filter(({key:p})=>i==null?void 0:i[p]);return e.jsx(ce,{children:e.jsxs(ce.Content,{className:"flex flex-col gap-5 p-5",children:[e.jsxs("div",{className:"flex items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("h2",{className:"text-base font-semibold break-all text-[var(--otari-ink)]",children:t.model}),i!=null&&i.deprecated?e.jsx(V,{size:"sm",color:"danger",children:"deprecated"}):null]}),e.jsxs("p",{className:"mt-1 text-xs break-all text-[var(--otari-muted)]",children:["Selector:"," ",e.jsx(Ue,{value:t.key,label:"model id",children:e.jsx("code",{children:t.key})})]}),i!=null&&i.family?e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:i.family}):null]}),e.jsx("button",{type:"button","aria-label":"Close model details",onClick:n,className:"-mt-1 -mr-1 shrink-0 rounded-md px-1.5 py-0.5 text-lg leading-none text-[var(--otari-muted)] hover:bg-[var(--otari-bg)] hover:text-[var(--otari-ink)]",children:"✕"})]}),i!=null&&i.description?e.jsx("p",{className:"text-sm text-[var(--otari-ink)]",children:i.description}):null,e.jsxs(se,{title:"Pricing",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(ti,{source:t.source}),t.isDiscovered?null:e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"not discovered"})]}),e.jsx(ri,{row:t},t.key),e.jsx(W,{size:"sm",variant:"outline",onPress:()=>a(t.key),children:"Make an alias"})]}),e.jsxs(se,{title:"Specs",children:[e.jsx(I,{label:"Context window",value:ae(t.contextWindow)}),e.jsx(I,{label:"Max output",value:ae((i==null?void 0:i.max_output_tokens)??null)}),e.jsx(I,{label:"Knowledge cutoff",value:(i==null?void 0:i.knowledge_cutoff)??"—"}),e.jsx(I,{label:"Released",value:Dt(i==null?void 0:i.release_date)}),e.jsx(I,{label:"Open weights",value:i?i.open_weights?"Yes":"No":"—"})]}),e.jsx(se,{title:"Modalities",children:s.length===0&&u.length===0?e.jsx("span",{className:"text-sm text-[var(--otari-muted)]",children:"Unknown."}):e.jsxs("div",{className:"flex flex-col gap-1.5 text-xs text-[var(--otari-muted)]",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-1",children:[e.jsx("span",{children:"In:"}),s.map(p=>e.jsx(V,{size:"sm",color:"default",children:Ve[p]??p},p))]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-1",children:[e.jsx("span",{children:"Out:"}),u.map(p=>e.jsx(V,{size:"sm",color:"default",children:Ve[p]??p},p))]})]})}),e.jsx(se,{title:"Capabilities",children:d.length>0?e.jsx("div",{className:"flex flex-wrap gap-1.5",children:d.map(({key:p,label:P})=>e.jsx(V,{size:"sm",color:"default",children:P},p))}):e.jsx("span",{className:"text-sm text-[var(--otari-muted)]",children:r?"None reported.":"Extended metadata unavailable (models.dev disabled or unreachable)."})})]})})}const li=15;function si({value:t,onChange:i,placeholder:r}){return e.jsx("input",{type:"search",value:t,onChange:a=>i(a.target.value),placeholder:r,"aria-label":r,className:"w-full max-w-xs rounded-md border border-[var(--otari-line)] bg-white px-3 py-1.5 text-sm focus:border-[var(--otari-brand)] focus:outline-none"})}const et="otari.dashboard.modelsSort",je={col:"model",dir:"asc"},ai=["model","released","input","output"];function ci(){if(typeof window>"u")return je;try{const t=window.localStorage.getItem(et);if(!t)return je;const i=JSON.parse(t);if(ai.includes(i.col)&&(i.dir==="asc"||i.dir==="desc"))return{col:i.col,dir:i.dir}}catch{}return je}function oi({row:t,onClose:i}){const r=Ne(),a=He(),[n,s]=c.useState(t.inputPrice==null?"":String(t.inputPrice)),[u,d]=c.useState(t.outputPrice==null?"":String(t.outputPrice)),[p,P]=c.useState(t.cacheReadPrice==null?"":String(t.cacheReadPrice)),[y,k]=c.useState(t.cacheWritePrice==null?"":String(t.cacheWritePrice)),[L,j]=c.useState(t.cacheWrite1hPrice==null?"":String(t.cacheWrite1hPrice)),[h,N]=c.useState(Je(t.pricingTiers)),A=oe(n)&&oe(u)&&w(p)&&w(y)&&w(L)&&Xe(h),S=()=>{A&&r.mutate({model_key:t.key,input_price_per_million:Number(n),output_price_per_million:Number(u),cache_read_price_per_million:q(p),cache_write_price_per_million:q(y),cache_write_1h_price_per_million:q(L),pricing_tiers:Ze(h)},{onSuccess:i})};return e.jsxs("div",{className:"flex flex-col gap-3 px-4 py-3",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[e.jsx("span",{className:"text-xs font-medium break-all text-[var(--otari-muted)]",children:t.key}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["Input $ / 1M",e.jsx(b,{value:n,onChange:s,ariaLabel:`Input price for ${t.key}`})]}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["Output $ / 1M",e.jsx(b,{value:u,onChange:d,ariaLabel:`Output price for ${t.key}`})]}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["Cache read $ / 1M",e.jsx(b,{value:p,onChange:P,ariaLabel:`Cache read price for ${t.key}`})]}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["Cache write $ / 1M",e.jsx(b,{value:y,onChange:k,ariaLabel:`Cache write price for ${t.key}`})]}),e.jsxs("label",{className:"flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:["1h cache write $ / 1M",e.jsx(b,{value:L,onChange:j,ariaLabel:`1 hour cache write price for ${t.key}`})]}),e.jsx(W,{size:"sm",variant:"primary",isDisabled:r.isPending||!A,onPress:S,children:r.isPending?"Saving…":"Save"}),e.jsx(W,{size:"sm",variant:"ghost",isDisabled:r.isPending,onPress:i,children:"Cancel"}),t.source==="configured"?e.jsxs("span",{className:"inline-flex items-center gap-1",children:[e.jsx(Ge,{confirmLabel:"Reset",isPending:a.isPending,onConfirm:()=>a.mutate(t.key,{onSuccess:i}),children:"Reset"}),e.jsx(ue,{label:"What reset does",children:"Removes the custom price. The model reverts to the default rate (genai-prices) when default pricing is on, otherwise it is metered at no cost."})]}):null,r.error||a.error?e.jsx("span",{className:"text-xs text-red-700",children:Pe(r.error??a.error)}):null]}),e.jsx(Qe,{tiers:h,onChange:N})]})}function ui({primary:t,secondary:i,rowKey:r,primaryLabel:a,secondaryLabel:n,onEdit:s}){const u=(d,p)=>e.jsx("button",{type:"button","aria-label":`Edit ${p} price for ${r}`,className:"tabular-nums hover:text-[var(--otari-brand-dark)] hover:underline",onClick:P=>{P.stopPropagation(),s()},children:d==null?"—":O(d)});return e.jsxs("span",{className:"inline-flex items-center justify-end gap-1",children:[u(t,a),e.jsx("span",{className:"text-[var(--otari-muted)]",children:"/"}),u(i,n)]})}function di({rates:t,rowKey:i,onEdit:r}){const a=[t.cacheReadPrice==null?null:`R ${O(t.cacheReadPrice)}`,t.cacheWritePrice==null?null:`W ${O(t.cacheWritePrice)}`,t.cacheWrite1hPrice==null?null:`1h ${O(t.cacheWrite1hPrice)}`].filter(n=>n!==null);return e.jsx("button",{type:"button","aria-label":`Edit caching price for ${i}`,className:"max-w-44 text-right text-xs leading-5 text-[var(--otari-muted)] hover:text-[var(--otari-brand-dark)] hover:underline",onClick:n=>{n.stopPropagation(),r()},children:a.length>0?a.join(" · "):"Input-rate fallback"})}function pi({row:t,onEdit:i}){const r=[...t.pricingTiers].sort((n,s)=>n.min_input_tokens-s.min_input_tokens).map(n=>ae(n.min_input_tokens)),a=r.length===0?"Base only":`${r.length} tier${r.length===1?"":"s"} · ≥ ${r.join(", ")}`;return e.jsx("button",{type:"button","aria-label":`Edit pricing policy for ${t.key}`,className:"max-w-40 text-right text-xs leading-5 text-[var(--otari-muted)] hover:text-[var(--otari-brand-dark)] hover:underline",onClick:n=>{n.stopPropagation(),i()},children:a})}function mi({rows:t,isLoading:i,empty:r,sortDescriptor:a,onSortChange:n,selectedKey:s,onSelect:u,onEditPricing:d,comparisonContextTokens:p,selectedKeys:P,onSelectionChange:y}){const k=c.useMemo(()=>{const j=p==null?"Base":`at ${ae(p)}`;return[{id:"model",header:"Model",isRowHeader:!0,allowsSorting:!0,cell:h=>e.jsxs(Ue,{value:h.key,label:"model id",className:"font-medium break-all",children:[h.model,e.jsx("span",{className:"sr-only select-none",children:h.key})]})},{id:"provider",header:"Provider",cell:h=>e.jsx("span",{className:"text-[var(--otari-muted)]",children:h.provider})},{id:"input",header:e.jsxs("span",{className:"inline-flex items-center gap-1",children:[`${j} in / out $ / 1M`,e.jsx(ii,{})]}),align:"end",allowsSorting:!0,cell:h=>{const N=Ye(h,p);return e.jsx(ui,{primary:N.inputPrice,secondary:N.outputPrice,rowKey:h.key,primaryLabel:"input",secondaryLabel:"output",onEdit:()=>d(h.key)})}},{id:"caching",header:`Caching ${p==null?"policy":j}`,align:"end",cell:h=>e.jsx(di,{rates:Ye(h,p),rowKey:h.key,onEdit:()=>d(h.key)})},{id:"policy",header:"Pricing policy",align:"end",cell:h=>e.jsx(pi,{row:h,onEdit:()=>d(h.key)})}]},[p,d]),L=c.useCallback(j=>j.key===s?"bg-[var(--otari-brand-tint)]":void 0,[s]);return e.jsx(zt,{ariaLabel:"Models",columns:k,rows:t,getRowKey:ei,isLoading:i,emptyContent:r,selectionMode:"multiple",selectedKeys:P,onSelectionChange:y,sortDescriptor:a,onSortChange:n,onRowAction:u,rowClassName:L})}function hi({providers:t,onPriceModel:i}){if(t.length===0)return null;const r=t.filter(u=>!u.discovery_unsupported),a=t.filter(u=>u.discovery_unsupported),n=u=>u.map(d=>d.provider).join(", "),s=u=>u.length===1;return e.jsxs(Ot,{tone:"warning",children:[r.length>0?e.jsxs("span",{className:"block",children:["Could not list ",n(r),". Check ",s(r)?"that provider's":"those providers'"," ","credentials in config.yml; ",s(r)?"its":"their"," models are missing from the list below."]}):null,a.length>0?e.jsxs("span",{className:"block",children:[n(a)," ",s(a)?"does":"do"," not offer model discovery, so"," ",s(a)?"its":"their"," models are missing from the list below."," ",s(a)?"The provider":"They"," may still serve requests. Price a model by its selector to meter it here, or declare the model ids ",s(a)?"it serves":"they serve"," under the"," ",e.jsx("code",{children:"models:"})," key in config.yml to list them all.",e.jsx(W,{size:"sm",variant:"outline",className:"mt-2",onPress:()=>i(s(a)?`${a[0].provider}:`:""),children:"Price a model"})]}):null]})}function Ni(){var Oe,De,Fe;const t=Mt(),[i]=Wt(),r=Tt(),a=It(),n=Lt(),s=$t(),u=Ne(),[d,p]=c.useState(""),[P,y]=c.useState(0),[k,L]=c.useState(li),[j,h]=c.useState(null),[N,A]=c.useState(null),[S,Z]=c.useState(ci),T=Kt(),[tt,de]=c.useState(!1),[it,Se]=c.useState(!1),[rt,Ce]=c.useState(void 0),[pe,Q]=c.useState(null),[nt,ke]=c.useState(!1),[lt,Me]=c.useState(void 0);c.useEffect(()=>{try{window.localStorage.setItem(et,JSON.stringify(S))}catch{}},[S]);const[D,We]=c.useState(i.get("provider")||"all"),[B,st]=c.useState("all"),[Y,at]=c.useState("all"),[U,ct]=c.useState("all"),[me,ot]=c.useState("0"),[ee,ut]=c.useState(""),[te,dt]=c.useState("all"),[he,pt]=c.useState(""),F=((Oe=s.data)==null?void 0:Oe.models)??{},mt=((De=s.data)==null?void 0:De.available)??!1,Te=c.useMemo(()=>{var l;return new Set((((l=n.data)==null?void 0:l.providers)??[]).flatMap(x=>x.models.map(o=>o.key)))},[n.data]),ht=l=>{p(l),y(0)},R=l=>x=>{l(x),y(0)},ie=c.useMemo(()=>{var M,_,v,E,$,z,Ke;const l=new Map(qt(a.data??[]).map(m=>[m.model_key,m])),x=[],o=new Set,g=(m,ye,Be,f)=>{if(o.has(m))return;o.add(m);const C=l.get(m);x.push({key:m,model:qe(ye,Be),provider:Be,isDiscovered:Te.has(m),contextWindow:(f==null?void 0:f.contextWindow)??null,inputPrice:C?C.input_price_per_million:(f==null?void 0:f.inputPrice)??null,outputPrice:C?C.output_price_per_million:(f==null?void 0:f.outputPrice)??null,cacheReadPrice:C?C.cache_read_price_per_million:(f==null?void 0:f.cacheReadPrice)??null,cacheWritePrice:C?C.cache_write_price_per_million:(f==null?void 0:f.cacheWritePrice)??null,cacheWrite1hPrice:C?C.cache_write_1h_price_per_million??null:(f==null?void 0:f.cacheWrite1hPrice)??null,pricingTiers:C?C.pricing_tiers??[]:(f==null?void 0:f.pricingTiers)??[],source:C?"configured":(f==null?void 0:f.source)??"none"})};for(const m of((M=r.data)==null?void 0:M.data)??[]){if(m.owned_by===Ut)continue;const ye=m.pricing_source==="default"?"default":m.pricing?"configured":"none";g(m.id,m.id,m.owned_by||ze(m.id),{key:m.id,model:m.id,provider:m.owned_by,contextWindow:m.context_window,inputPrice:((_=m.pricing)==null?void 0:_.input_price_per_million)??null,outputPrice:((v=m.pricing)==null?void 0:v.output_price_per_million)??null,cacheReadPrice:((E=m.pricing)==null?void 0:E.cache_read_price_per_million)??null,cacheWritePrice:(($=m.pricing)==null?void 0:$.cache_write_price_per_million)??null,cacheWrite1hPrice:((z=m.pricing)==null?void 0:z.cache_write_1h_price_per_million)??null,pricingTiers:((Ke=m.pricing)==null?void 0:Ke.pricing_tiers)??[],source:ye})}for(const m of l.keys())m.startsWith(`${Yt}:`)||g(m,m,ze(m));return x},[r.data,a.data,Te]),Ie=c.useMemo(()=>new Map(ie.map(l=>[l.key,l])),[ie]),re=c.useMemo(()=>{var o,g,M;const l=ie.map(_=>{var v,E;return{..._,contextWindow:_.contextWindow??((v=F[_.key])==null?void 0:v.context_window)??null,releaseDate:((E=F[_.key])==null?void 0:E.release_date)??null}}),x=new Set(l.map(_=>_.key));for(const _ of((o=n.data)==null?void 0:o.providers)??[])for(const v of _.models)x.has(v.key)||(x.add(v.key),l.push({key:v.key,model:qe(v.key,_.provider),provider:_.provider,isDiscovered:!0,contextWindow:((g=F[v.key])==null?void 0:g.context_window)??null,releaseDate:((M=F[v.key])==null?void 0:M.release_date)??null,inputPrice:null,outputPrice:null,cacheReadPrice:null,cacheWritePrice:null,cacheWrite1hPrice:null,pricingTiers:[],source:"none"}));return l},[ie,n.data,F]),xt=(((Fe=n.data)==null?void 0:Fe.providers)??[]).filter(l=>!l.ok),ne=c.useMemo(()=>{const l=Array.from(new Set(re.map(x=>x.provider))).sort((x,o)=>x.localeCompare(o));return[{value:"all",label:"All providers"},...l.map(x=>({value:x,label:x}))]},[re]);c.useEffect(()=>{D==="all"||ne.length<=1||ne.some(l=>l.value===D)||We("all")},[ne,D]);const H=d.trim().toLowerCase(),xe=Number(me)||0,fe=ee===""?Number.POSITIVE_INFINITY:Number(ee),ft=he===""?null:Number(he),ge=te==="all"?null:Date.now()-Number(te)*Qt,G=c.useMemo(()=>{const l=o=>{if(H&&!o.key.toLowerCase().includes(H)&&!o.provider.toLowerCase().includes(H)||D!=="all"&&o.provider!==D||B==="configured"&&o.source!=="configured"||B==="default"&&o.source!=="default"||B==="priced"&&o.inputPrice==null||B==="unpriced"&&o.inputPrice!=null||Y==="discovered"&&!o.isDiscovered||Y==="custom"&&o.isDiscovered)return!1;if(U!=="all"){const g=we.find(_=>_.value===U),M=F[o.key];if(!g||!M||!g.test(M))return!1}if(xe>0&&(o.contextWindow==null||o.contextWindowfe))return!1;if(ge!=null){const g=o.releaseDate?Date.parse(o.releaseDate):Number.NaN;if(Number.isNaN(g)||g{const M=S.dir==="asc"?1:-1;if(S.col==="model")return o.model.localeCompare(g.model)*M;if(S.col==="released"){const $=o.releaseDate??null,z=g.releaseDate??null;return!$&&!z?o.model.localeCompare(g.model):$?z?($z?1:0)*M||o.model.localeCompare(g.model):-1:1}const _=$=>S.col==="input"?$.inputPrice:$.outputPrice,v=_(o),E=_(g);return v==null&&E==null?o.model.localeCompare(g.model):v==null?1:E==null?-1:(v-E)*M||o.model.localeCompare(g.model)};return re.filter(l).sort(x)},[re,H,D,B,Y,U,xe,fe,ge,F,S]),J=G.length,gt=Math.max(1,Math.ceil(J/k)),Le=Math.min(P,gt-1),$e=Le*k,_e=G.slice($e,$e+k),_t={column:S.col,direction:S.dir==="asc"?"ascending":"descending"},vt=l=>{Z({col:String(l.column),dir:l.direction==="ascending"?"asc":"desc"}),y(0)},bt=c.useCallback(l=>h(x=>x===l?null:l),[]),ve=_e.map(l=>l.key),X=Bt(T.selectedKeys,ve),yt=ve.length>0&&X.length===ve.length&&J>X.length,jt=T.allMatching?G.map(l=>l.key):X,Ae=T.allMatching?J:X.length,Ee=j?G.find(l=>l.key===j)??Ie.get(j)??null:null,Pt=async l=>{Se(!0),Ce(void 0);try{for(const x of jt)await u.mutateAsync({model_key:x,input_price_per_million:l.input_price_per_million,output_price_per_million:l.output_price_per_million,cache_read_price_per_million:l.cache_read_price_per_million??null,cache_write_price_per_million:l.cache_write_price_per_million??null,cache_write_1h_price_per_million:null,pricing_tiers:[]});T.clear(),de(!1)}catch(x){Ce(x)}finally{Se(!1)}},Nt=async(l,x)=>{ke(!0),Me(void 0);try{const o=await u.mutateAsync({model_key:x,input_price_per_million:l.input_price_per_million,output_price_per_million:l.output_price_per_million,cache_read_price_per_million:l.cache_read_price_per_million??null,cache_write_price_per_million:l.cache_write_price_per_million??null});Q(null),A(o.model_key)}catch(o){Me(o)}finally{ke(!1)}},St=r.isLoading||a.isLoading||n.isLoading,Ct=H!==""||D!=="all"||B!=="all"||Y!=="all"||U!=="all"||me!=="0"||ee!==""||te!=="all",be=wt(d)?d.trim():null,kt=e.jsxs("div",{className:"flex flex-col items-center gap-2 py-2",children:[e.jsx("span",{children:Ct?"No models match your filters.":"No models yet. Add a provider on the Providers page."}),e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"A provider that serves no model listing still answers requests, so a model you can call may not be listed here."}),e.jsx(W,{size:"sm",variant:"outline",onPress:()=>Q(be??""),children:be?`Price ${be}`:"Price a model by hand"})]}),le=N?G.find(l=>l.key===N)??Ie.get(N)??null:null;return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(At,{title:"Models",description:"Every model your providers can serve. Set a price on any model so budgets and usage tracking work."}),e.jsx(Et,{error:r.error??a.error??n.error??s.error}),e.jsxs("div",{className:`grid gap-4 lg:items-start ${le?"lg:grid-cols-[minmax(0,1fr)_360px]":"grid-cols-1"}`,children:[e.jsxs("div",{className:"flex min-w-0 flex-col gap-3",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx(si,{value:d,onChange:ht,placeholder:"Search models…"}),e.jsx(K,{ariaLabel:"Filter by provider",value:D,onChange:R(We),options:ne}),e.jsx(K,{ariaLabel:"Filter by pricing",value:B,onChange:R(st),options:[{value:"all",label:"Any pricing"},{value:"configured",label:"Custom price"},{value:"default",label:"Default price"},{value:"priced",label:"Priced"},{value:"unpriced",label:"Unpriced"}]}),e.jsx(K,{ariaLabel:"Filter by source",value:Y,onChange:R(at),options:[{value:"all",label:"Any source"},{value:"discovered",label:"Discovered"},{value:"custom",label:"Custom (not discovered)"}]}),e.jsx(K,{ariaLabel:"Filter by capability",value:U,onChange:R(ct),options:[{value:"all",label:"Any capability"},...we.map(l=>({value:l.value,label:l.label}))]}),e.jsx(K,{ariaLabel:"Minimum context window",value:me,onChange:R(ot),options:Gt}),e.jsx(K,{ariaLabel:"Maximum input price",value:ee,onChange:R(ut),options:Jt}),e.jsx(K,{ariaLabel:"Compare prices at context",value:he,onChange:pt,options:Xt}),e.jsx(K,{ariaLabel:"Filter by release date",value:te,onChange:R(dt),options:Zt})]}),e.jsx(hi,{providers:xt,onPriceModel:Q}),X.length>0?e.jsx(Rt,{selectedCount:Ae,allMatching:T.allMatching,matchingTotal:J,canSelectAllMatching:yt,onSelectAllMatching:T.enableAllMatching,onClear:T.clear,children:e.jsx(W,{size:"sm",variant:"primary",onPress:()=>de(!0),children:"Set pricing"})}):null,e.jsx(mi,{rows:_e,isLoading:St,empty:kt,sortDescriptor:_t,onSortChange:vt,selectedKey:N,onSelect:A,onEditPricing:bt,comparisonContextTokens:ft,selectedKeys:T.selectedKeys,onSelectionChange:T.onSelectionChange}),Ee?e.jsx(ce,{children:e.jsxs(ce.Content,{className:"p-0",children:[e.jsxs("div",{className:"flex items-center justify-between border-b border-[var(--otari-line)] px-4 py-2",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Edit pricing"}),e.jsx(W,{size:"sm",variant:"ghost",onPress:()=>h(null),children:"Close"})]}),e.jsx(oi,{row:Ee,onClose:()=>h(null)})]})}):null,e.jsx(Vt,{page:Le,pageSize:k,total:J,rowsOnPage:_e.length,onPageChange:y,onPageSizeChange:l=>{L(l),y(0)},pageSizeOptions:[15,25,50]})]}),le?e.jsx("aside",{className:"lg:sticky lg:top-4",children:e.jsx(ni,{row:le,metadata:F[le.key],metadataAvailable:mt,onMakeAlias:l=>t(`/routing?target=${encodeURIComponent(l)}`),onClose:()=>A(null)})}):null]}),e.jsx(Re,{isOpen:tt,onOpenChange:de,targetCount:Ae,isPending:it,error:rt,onSubmit:Pt,title:"Set pricing",description:l=>`Apply these per-1M rates to ${l.toLocaleString()} selected ${l===1?"model":"models"}. This replaces each model's price; pricing tiers and the 1h cache rate are cleared. Edit a single model for tiers.`}),e.jsx(Re,{isOpen:pe!==null,onOpenChange:l=>Q(l?pe??"":null),isPending:nt,error:lt,onSubmit:Nt,collectModelKey:!0,initialModelKey:pe??"",title:"Price a model",description:()=>"Meter a model the catalogue does not list, for a provider that serves no model listing. Type the selector you send as model and its rates; the model then appears here as custom, and its usage is costed and counted against budgets."})]})}export{Ni as ModelsPage}; diff --git a/src/gateway/static/dashboard/assets/OverviewPage-0PkW5qfi.js b/src/gateway/static/dashboard/assets/OverviewPage-CHysnnsw.js similarity index 99% rename from src/gateway/static/dashboard/assets/OverviewPage-0PkW5qfi.js rename to src/gateway/static/dashboard/assets/OverviewPage-CHysnnsw.js index ec211612e..1f7428793 100644 --- a/src/gateway/static/dashboard/assets/OverviewPage-0PkW5qfi.js +++ b/src/gateway/static/dashboard/assets/OverviewPage-CHysnnsw.js @@ -1 +1 @@ -import{j as t}from"./tanstack-query-1t81HyiD.js";import{r as y,i as it,N as J}from"./react-dgEcD0HR.js";import{a2 as lt,a3 as dt,g as R,a4 as ct,H as ut,c as mt,u as vt,d as ht,a5 as _,P as xt,R as ft,E as Q,a6 as g,a7 as k,a8 as E,a9 as D,aa as O,ab as gt,ac as C}from"./index-D-R1nuKP.js";import{S as W}from"./charts-D6upG8fh.js";import{D as pt}from"./DataTable-BHrpJHmX.js";import{d as G,B as bt}from"./heroui-DhloIxuc.js";import"./recharts-EeW53z2i.js";function I(e){return e==="neutral"?void 0:e}const yt=.02,jt=.1;function T(e){if(!e||e.request_count===0)return{rate:null,status:"neutral"};const l=e.error_count/e.request_count,a=l>=jt?"alert":l>=yt?"warn":"ok";return{rate:l,status:a}}function wt(e){return!e||e.total===0?"neutral":e.healthy>=e.total?"ok":e.healthy+e.degraded===0?"alert":"warn"}const St=.8;function Nt(e){if(e.length===0)return{status:"neutral",label:"No budgets configured",overCount:0,nearCount:0,cappedCount:0};const l=e.filter(i=>i.max_budget!==null&&i.user_count>0);if(l.length===0)return{status:"neutral",label:"No capped budgets",overCount:0,nearCount:0,cappedCount:0};let a=0,n=0,r,p=-1;for(const i of l){const s=i.max_budget*i.user_count,o=s>0?i.total_spend/s:0;o>=1?a+=1:o>=St&&(n+=1),o>p&&(p=o,r={name:i.name??i.budget_id,spent:i.total_spend,allocated:s,pct:o})}const b=a>0?"alert":n>0?"warn":"ok",j=a>0?`${a} over limit`:n>0?`${n} near limit`:"All within budget";return{status:b,label:j,overCount:a,nearCount:n,cappedCount:l.length,worst:r}}const Y=864e5,V=30;function z(){const e=new Date;return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`}function Rt(){const[e,l]=y.useState(z);return y.useEffect(()=>{const a=()=>{if(document.visibilityState==="visible"){const n=z();l(r=>r===n?r:n)}};return document.addEventListener("visibilitychange",a),window.addEventListener("focus",a),()=>{document.removeEventListener("visibilitychange",a),window.removeEventListener("focus",a)}},[]),y.useMemo(()=>{const a=Date.now(),n=new Date(a);return{today:new Date(n.getFullYear(),n.getMonth(),n.getDate()).toISOString(),periodStart:new Date(a-V*Y).toISOString(),prevStart:new Date(a-2*V*Y).toISOString()}},[e])}const _t={ok:"Healthy",warn:"Elevated",alert:"High"},Et={ok:"On track",warn:"Near limit",alert:"Over budget"};function Mt(){const e=lt();return e.isLoading?t.jsx(dt,{}):t.jsx(Dt,{needsSetup:e.isSuccess&&e.data.providers.length===0,setupError:e.error,refreshSetup:e.refetch,setupFetching:e.isFetching})}function Dt({needsSetup:e=!1,setupError:l,refreshSetup:a,setupFetching:n=!1}){var A,P,H,q,B,U,M,K;const r=Rt(),p=y.useMemo(()=>({start_date:r.today}),[r]),b=y.useMemo(()=>({start_date:r.periodStart}),[r]),j=y.useMemo(()=>({start_date:r.prevStart,end_date:r.periodStart}),[r]),i=R(p,"hour",C),s=R(b,"day",C),o=R(j,"day",C),d=ct(),c=ut(),w=mt(),S=vt(),h=ht({},0,5),F=(A=i.data)==null?void 0:A.totals,v=(P=s.data)==null?void 0:P.totals,x=(H=o.data)==null?void 0:H.totals,N=((q=s.data)==null?void 0:q.series)??[],L=N.length>1,u=T(v),$=T(x),X=u.rate!==null&&$.rate!==null?_(u.rate,$.rate):null,m=Nt(c.data??[]),Z=wt(d.data),tt=(w.data??[]).filter(f=>f.is_active).length,et=(S.data??[]).filter(f=>!f.blocked).length,rt=(((B=h.data)==null?void 0:B.length)??0)>0,at=e&&h.isSuccess&&!rt,st=l??i.error??s.error??d.error??c.error??w.error??S.error,nt=()=>{a==null||a(),i.refetch(),s.refetch(),o.refetch(),d.refetch(),c.refetch(),w.refetch(),S.refetch(),h.refetch()},ot=n||i.isFetching||s.isFetching||o.isFetching||d.isFetching||c.isFetching||w.isFetching||S.isFetching||h.isFetching;return t.jsxs("div",{className:"flex flex-col gap-6",children:[t.jsx(xt,{title:"Overview",description:"At-a-glance spend, traffic, and health across the gateway.",action:t.jsx(ft,{onRefresh:nt,isFetching:ot,updatedAt:s.dataUpdatedAt})}),at?t.jsx(Ct,{}):null,t.jsx(Q,{error:st}),t.jsx(Ot,{providerHealth:Z,healthy:((U=d.data)==null?void 0:U.healthy)??0,degraded:((M=d.data)==null?void 0:M.degraded)??0,total:((K=d.data)==null?void 0:K.total)??0,budget:m,errStatus:u.status,errRate:u.rate,ready:d.isSuccess&&c.isSuccess&&s.isSuccess,failed:d.isError||c.isError||s.isError}),t.jsxs("div",{className:"grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-4",children:[t.jsx(g,{label:"Spend today",value:F?k(F.cost):"—"}),t.jsx(g,{label:"Spend, last 30 days",value:v?k(v.cost):"—",hint:v?t.jsx(E,{fraction:_(v.cost,x==null?void 0:x.cost)}):null,chart:L?t.jsx(W,{values:N.map(f=>f.cost),ariaLabel:"Spend trend over the last 30 days"}):void 0}),t.jsx(g,{label:"Requests, last 30 days",value:v?D(v.request_count):"—",hint:v?t.jsx(E,{fraction:_(v.request_count,x==null?void 0:x.request_count)}):null,chart:L?t.jsx(W,{values:N.map(f=>f.requests),ariaLabel:"Request volume trend over the last 30 days"}):void 0}),t.jsx(g,{label:"Error rate, last 30 days",value:u.rate===null?"—":O(u.rate),status:I(u.status),statusLabel:u.status==="neutral"?void 0:_t[u.status],hint:u.rate!==null?t.jsx(E,{fraction:X}):null}),t.jsx(g,{label:"Budget health",value:c.data&&m.worst?O(m.worst.pct):"—",status:c.data?I(m.status):void 0,statusLabel:c.data&&m.status!=="neutral"?Et[m.status]:void 0,hint:c.data?m.worst?`${m.label} · worst: ${m.worst.name}`:m.label:void 0,to:"/budgets"}),t.jsx(g,{label:"Active keys",value:w.data?D(tt):"—",to:"/keys"}),t.jsx(g,{label:"Active users",value:S.data?D(et):"—",to:"/users"})]}),t.jsx(Lt,{entries:h.data??[],loading:h.isLoading,error:h.error})]})}function Ct(){const e=it();return t.jsx(G,{children:t.jsxs(G.Content,{className:"flex flex-col gap-3 p-6",children:[t.jsxs("div",{children:[t.jsx("h2",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"Get started with Otari"}),t.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"Add a provider to begin serving models. Once it is configured, this page will show your gateway’s traffic, spend, and health."})]}),t.jsx("div",{children:t.jsx(bt,{variant:"primary",onPress:()=>e("/providers"),children:"Add your first provider"})})]})})}function kt({text:e}){return t.jsx("div",{role:"status",className:"flex items-center gap-2 rounded-xl border border-[var(--otari-line)] bg-[var(--otari-bg)] px-4 py-3 text-sm text-[var(--otari-muted)]",children:e})}function Ot({providerHealth:e,healthy:l,degraded:a,total:n,budget:r,errStatus:p,errRate:b,ready:j,failed:i}){if(i)return t.jsx(kt,{text:"Some status data could not be loaded."});if(!j)return null;const s=[];if((e==="warn"||e==="alert")&&n>0){const o=n-l-a;o>0&&s.push({text:`${o} provider${o===1?"":"s"} unreachable`,to:"/providers"}),a>0&&s.push({text:`${a} provider${a===1?"":"s"} without model discovery`,to:"/providers"})}return r.overCount>0?s.push({text:`${r.overCount} budget${r.overCount===1?"":"s"} over limit`,to:"/budgets"}):r.nearCount>0&&s.push({text:`${r.nearCount} budget${r.nearCount===1?"":"s"} near limit`,to:"/budgets"}),p==="alert"&&b!==null&&s.push({text:`error rate ${O(b)}`,to:"/activity?status=error"}),s.length===0?null:t.jsxs("div",{role:"alert",className:"flex flex-col gap-2 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900 sm:flex-row sm:flex-wrap sm:items-center",children:[t.jsx("span",{className:"font-medium",children:"Needs attention:"}),s.map((o,d)=>t.jsxs("span",{className:"flex items-center gap-2",children:[d>0?t.jsx("span",{"aria-hidden":!0,className:"text-amber-400",children:"·"}):null,t.jsx(J,{to:o.to,className:"underline underline-offset-2 hover:text-amber-950",children:o.text})]},o.to+o.text))]})}function Ft(e){return e==="error"?"error":e==="absorbed"?"absorbed":"ok"}function Lt({entries:e,loading:l,error:a}){const n=[{id:"time",header:"Time",cell:r=>t.jsx("span",{className:"text-[var(--otari-muted)]",title:new Date(r.timestamp).toLocaleString(),children:gt(r.timestamp)})},{id:"model",header:"Model",isRowHeader:!0,cell:r=>t.jsx("span",{className:"text-[var(--otari-ink)]",children:r.model})},{id:"cost",header:"Cost",align:"end",cell:r=>r.cost===null?"—":k(r.cost)},{id:"status",header:"Status",cell:r=>t.jsx("span",{className:`inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium ${r.status==="error"?"border-red-200 bg-red-50 text-red-700":r.status==="absorbed"?"border-amber-200 bg-amber-50 text-amber-700":"border-[var(--otari-line)] bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]"}`,children:Ft(r.status)})}];return t.jsxs("div",{className:"flex flex-col gap-3",children:[t.jsxs("div",{className:"flex items-center justify-between",children:[t.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Recent activity"}),t.jsx(J,{to:"/activity",className:"text-sm text-[var(--otari-brand-dark)] hover:underline",children:"View all →"})]}),t.jsx(Q,{error:a}),t.jsx(pt,{ariaLabel:"Recent activity",columns:n,rows:e,getRowKey:r=>r.id,isLoading:l,emptyContent:"No requests yet. Once the gateway serves traffic, it appears here."})]})}export{Mt as OverviewIndex,Dt as OverviewPage,z as localDayKey}; +import{j as t}from"./tanstack-query-1t81HyiD.js";import{r as y,i as it,N as J}from"./react-dgEcD0HR.js";import{a2 as lt,a3 as dt,g as R,a4 as ct,H as ut,c as mt,u as vt,d as ht,a5 as _,P as xt,R as ft,E as Q,a6 as g,a7 as k,a8 as E,a9 as D,aa as O,ab as gt,ac as C}from"./index-Dit1BUBh.js";import{S as W}from"./charts-D6upG8fh.js";import{D as pt}from"./DataTable-BHrpJHmX.js";import{d as G,B as bt}from"./heroui-DhloIxuc.js";import"./recharts-EeW53z2i.js";function I(e){return e==="neutral"?void 0:e}const yt=.02,jt=.1;function T(e){if(!e||e.request_count===0)return{rate:null,status:"neutral"};const l=e.error_count/e.request_count,a=l>=jt?"alert":l>=yt?"warn":"ok";return{rate:l,status:a}}function wt(e){return!e||e.total===0?"neutral":e.healthy>=e.total?"ok":e.healthy+e.degraded===0?"alert":"warn"}const St=.8;function Nt(e){if(e.length===0)return{status:"neutral",label:"No budgets configured",overCount:0,nearCount:0,cappedCount:0};const l=e.filter(i=>i.max_budget!==null&&i.user_count>0);if(l.length===0)return{status:"neutral",label:"No capped budgets",overCount:0,nearCount:0,cappedCount:0};let a=0,n=0,r,p=-1;for(const i of l){const s=i.max_budget*i.user_count,o=s>0?i.total_spend/s:0;o>=1?a+=1:o>=St&&(n+=1),o>p&&(p=o,r={name:i.name??i.budget_id,spent:i.total_spend,allocated:s,pct:o})}const b=a>0?"alert":n>0?"warn":"ok",j=a>0?`${a} over limit`:n>0?`${n} near limit`:"All within budget";return{status:b,label:j,overCount:a,nearCount:n,cappedCount:l.length,worst:r}}const Y=864e5,V=30;function z(){const e=new Date;return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`}function Rt(){const[e,l]=y.useState(z);return y.useEffect(()=>{const a=()=>{if(document.visibilityState==="visible"){const n=z();l(r=>r===n?r:n)}};return document.addEventListener("visibilitychange",a),window.addEventListener("focus",a),()=>{document.removeEventListener("visibilitychange",a),window.removeEventListener("focus",a)}},[]),y.useMemo(()=>{const a=Date.now(),n=new Date(a);return{today:new Date(n.getFullYear(),n.getMonth(),n.getDate()).toISOString(),periodStart:new Date(a-V*Y).toISOString(),prevStart:new Date(a-2*V*Y).toISOString()}},[e])}const _t={ok:"Healthy",warn:"Elevated",alert:"High"},Et={ok:"On track",warn:"Near limit",alert:"Over budget"};function Mt(){const e=lt();return e.isLoading?t.jsx(dt,{}):t.jsx(Dt,{needsSetup:e.isSuccess&&e.data.providers.length===0,setupError:e.error,refreshSetup:e.refetch,setupFetching:e.isFetching})}function Dt({needsSetup:e=!1,setupError:l,refreshSetup:a,setupFetching:n=!1}){var A,P,H,q,B,U,M,K;const r=Rt(),p=y.useMemo(()=>({start_date:r.today}),[r]),b=y.useMemo(()=>({start_date:r.periodStart}),[r]),j=y.useMemo(()=>({start_date:r.prevStart,end_date:r.periodStart}),[r]),i=R(p,"hour",C),s=R(b,"day",C),o=R(j,"day",C),d=ct(),c=ut(),w=mt(),S=vt(),h=ht({},0,5),F=(A=i.data)==null?void 0:A.totals,v=(P=s.data)==null?void 0:P.totals,x=(H=o.data)==null?void 0:H.totals,N=((q=s.data)==null?void 0:q.series)??[],L=N.length>1,u=T(v),$=T(x),X=u.rate!==null&&$.rate!==null?_(u.rate,$.rate):null,m=Nt(c.data??[]),Z=wt(d.data),tt=(w.data??[]).filter(f=>f.is_active).length,et=(S.data??[]).filter(f=>!f.blocked).length,rt=(((B=h.data)==null?void 0:B.length)??0)>0,at=e&&h.isSuccess&&!rt,st=l??i.error??s.error??d.error??c.error??w.error??S.error,nt=()=>{a==null||a(),i.refetch(),s.refetch(),o.refetch(),d.refetch(),c.refetch(),w.refetch(),S.refetch(),h.refetch()},ot=n||i.isFetching||s.isFetching||o.isFetching||d.isFetching||c.isFetching||w.isFetching||S.isFetching||h.isFetching;return t.jsxs("div",{className:"flex flex-col gap-6",children:[t.jsx(xt,{title:"Overview",description:"At-a-glance spend, traffic, and health across the gateway.",action:t.jsx(ft,{onRefresh:nt,isFetching:ot,updatedAt:s.dataUpdatedAt})}),at?t.jsx(Ct,{}):null,t.jsx(Q,{error:st}),t.jsx(Ot,{providerHealth:Z,healthy:((U=d.data)==null?void 0:U.healthy)??0,degraded:((M=d.data)==null?void 0:M.degraded)??0,total:((K=d.data)==null?void 0:K.total)??0,budget:m,errStatus:u.status,errRate:u.rate,ready:d.isSuccess&&c.isSuccess&&s.isSuccess,failed:d.isError||c.isError||s.isError}),t.jsxs("div",{className:"grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-4",children:[t.jsx(g,{label:"Spend today",value:F?k(F.cost):"—"}),t.jsx(g,{label:"Spend, last 30 days",value:v?k(v.cost):"—",hint:v?t.jsx(E,{fraction:_(v.cost,x==null?void 0:x.cost)}):null,chart:L?t.jsx(W,{values:N.map(f=>f.cost),ariaLabel:"Spend trend over the last 30 days"}):void 0}),t.jsx(g,{label:"Requests, last 30 days",value:v?D(v.request_count):"—",hint:v?t.jsx(E,{fraction:_(v.request_count,x==null?void 0:x.request_count)}):null,chart:L?t.jsx(W,{values:N.map(f=>f.requests),ariaLabel:"Request volume trend over the last 30 days"}):void 0}),t.jsx(g,{label:"Error rate, last 30 days",value:u.rate===null?"—":O(u.rate),status:I(u.status),statusLabel:u.status==="neutral"?void 0:_t[u.status],hint:u.rate!==null?t.jsx(E,{fraction:X}):null}),t.jsx(g,{label:"Budget health",value:c.data&&m.worst?O(m.worst.pct):"—",status:c.data?I(m.status):void 0,statusLabel:c.data&&m.status!=="neutral"?Et[m.status]:void 0,hint:c.data?m.worst?`${m.label} · worst: ${m.worst.name}`:m.label:void 0,to:"/budgets"}),t.jsx(g,{label:"Active keys",value:w.data?D(tt):"—",to:"/keys"}),t.jsx(g,{label:"Active users",value:S.data?D(et):"—",to:"/users"})]}),t.jsx(Lt,{entries:h.data??[],loading:h.isLoading,error:h.error})]})}function Ct(){const e=it();return t.jsx(G,{children:t.jsxs(G.Content,{className:"flex flex-col gap-3 p-6",children:[t.jsxs("div",{children:[t.jsx("h2",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"Get started with Otari"}),t.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"Add a provider to begin serving models. Once it is configured, this page will show your gateway’s traffic, spend, and health."})]}),t.jsx("div",{children:t.jsx(bt,{variant:"primary",onPress:()=>e("/providers"),children:"Add your first provider"})})]})})}function kt({text:e}){return t.jsx("div",{role:"status",className:"flex items-center gap-2 rounded-xl border border-[var(--otari-line)] bg-[var(--otari-bg)] px-4 py-3 text-sm text-[var(--otari-muted)]",children:e})}function Ot({providerHealth:e,healthy:l,degraded:a,total:n,budget:r,errStatus:p,errRate:b,ready:j,failed:i}){if(i)return t.jsx(kt,{text:"Some status data could not be loaded."});if(!j)return null;const s=[];if((e==="warn"||e==="alert")&&n>0){const o=n-l-a;o>0&&s.push({text:`${o} provider${o===1?"":"s"} unreachable`,to:"/providers"}),a>0&&s.push({text:`${a} provider${a===1?"":"s"} without model discovery`,to:"/providers"})}return r.overCount>0?s.push({text:`${r.overCount} budget${r.overCount===1?"":"s"} over limit`,to:"/budgets"}):r.nearCount>0&&s.push({text:`${r.nearCount} budget${r.nearCount===1?"":"s"} near limit`,to:"/budgets"}),p==="alert"&&b!==null&&s.push({text:`error rate ${O(b)}`,to:"/activity?status=error"}),s.length===0?null:t.jsxs("div",{role:"alert",className:"flex flex-col gap-2 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900 sm:flex-row sm:flex-wrap sm:items-center",children:[t.jsx("span",{className:"font-medium",children:"Needs attention:"}),s.map((o,d)=>t.jsxs("span",{className:"flex items-center gap-2",children:[d>0?t.jsx("span",{"aria-hidden":!0,className:"text-amber-400",children:"·"}):null,t.jsx(J,{to:o.to,className:"underline underline-offset-2 hover:text-amber-950",children:o.text})]},o.to+o.text))]})}function Ft(e){return e==="error"?"error":e==="absorbed"?"absorbed":"ok"}function Lt({entries:e,loading:l,error:a}){const n=[{id:"time",header:"Time",cell:r=>t.jsx("span",{className:"text-[var(--otari-muted)]",title:new Date(r.timestamp).toLocaleString(),children:gt(r.timestamp)})},{id:"model",header:"Model",isRowHeader:!0,cell:r=>t.jsx("span",{className:"text-[var(--otari-ink)]",children:r.model})},{id:"cost",header:"Cost",align:"end",cell:r=>r.cost===null?"—":k(r.cost)},{id:"status",header:"Status",cell:r=>t.jsx("span",{className:`inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium ${r.status==="error"?"border-red-200 bg-red-50 text-red-700":r.status==="absorbed"?"border-amber-200 bg-amber-50 text-amber-700":"border-[var(--otari-line)] bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]"}`,children:Ft(r.status)})}];return t.jsxs("div",{className:"flex flex-col gap-3",children:[t.jsxs("div",{className:"flex items-center justify-between",children:[t.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Recent activity"}),t.jsx(J,{to:"/activity",className:"text-sm text-[var(--otari-brand-dark)] hover:underline",children:"View all →"})]}),t.jsx(Q,{error:a}),t.jsx(pt,{ariaLabel:"Recent activity",columns:n,rows:e,getRowKey:r=>r.id,isLoading:l,emptyContent:"No requests yet. Once the gateway serves traffic, it appears here."})]})}export{Mt as OverviewIndex,Dt as OverviewPage,z as localDayKey}; diff --git a/src/gateway/static/dashboard/assets/ProvidersPage-B4LYozbD.js b/src/gateway/static/dashboard/assets/ProvidersPage-BPyKQR5x.js similarity index 99% rename from src/gateway/static/dashboard/assets/ProvidersPage-B4LYozbD.js rename to src/gateway/static/dashboard/assets/ProvidersPage-BPyKQR5x.js index 738d588fd..d100d3010 100644 --- a/src/gateway/static/dashboard/assets/ProvidersPage-B4LYozbD.js +++ b/src/gateway/static/dashboard/assets/ProvidersPage-BPyKQR5x.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as m,L as J}from"./react-dgEcD0HR.js";import{a2 as Q,ad as X,a0 as Z,a4 as ee,ae as te,af as se,ag as ae,P as re,E as I,M as ne,ah as ie,ai as oe,ab as $,y as de,aj as F,ak as le,_ as H,al as ce,am as ue}from"./index-D-R1nuKP.js";import{F as C}from"./Field-GEMwIhf7.js";import{D as me}from"./DataTable-BHrpJHmX.js";import{B as j,d as w,g as xe,e as pe,L as z,I as M,D as he,S as ve,C as T,a as ge,b as fe}from"./heroui-DhloIxuc.js";function E({value:t,onChange:s,label:a,placeholder:n,description:i}){return e.jsxs(pe,{value:t,onChange:s,className:"flex max-w-md flex-col gap-1",children:[e.jsx(z,{className:"text-sm font-medium text-[var(--otari-ink)]",children:a}),e.jsx(M,{type:"password",placeholder:n??"sk-…",autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",spellCheck:!1,"data-1p-ignore":!0,"data-lpignore":"true"}),i?e.jsx(he,{className:"text-xs text-[var(--otari-muted)]",children:i}):null]})}function U({label:t,value:s,onChange:a,description:n,placeholder:i,extra:c=[],includeCatalog:d=!0}){var k;const b=ce(),x=m.useMemo(()=>d?[...c,...(b.data??[]).map(o=>({id:o.id,name:o.name}))]:c,[b.data,c,d]),[v,u]=m.useState(()=>{var o;return((o=x.find(y=>y.id===s))==null?void 0:o.name)??""}),f=((k=x.find(o=>o.id===s))==null?void 0:k.name)??"",g=v.trim()===f.trim()?"":v.trim().toLowerCase(),l=x.filter(o=>!g||o.name.toLowerCase().includes(g)||o.id.toLowerCase().includes(g)).slice(0,50);return e.jsxs(T.Root,{allowsEmptyCollection:!0,menuTrigger:"focus",inputValue:v,onInputChange:u,onSelectionChange:o=>{var y;o!=null?(a(String(o)),u(((y=x.find(N=>N.id===String(o)))==null?void 0:y.name)??"")):(a(""),u(""))},className:"flex max-w-md flex-col gap-1",children:[e.jsx(z,{className:"text-sm font-medium text-[var(--otari-ink)]",children:t}),e.jsxs(T.InputGroup,{children:[e.jsx(M,{placeholder:i??"Search providers…",autoComplete:"off","data-1p-ignore":!0,"data-lpignore":"true",onFocus:o=>o.currentTarget.select()}),e.jsx(T.Trigger,{})]}),e.jsx(T.Popover,{children:e.jsx(ge,{items:l,className:"max-h-72 overflow-auto",children:o=>e.jsx(fe,{id:o.id,textValue:o.name,children:o.name})})}),n?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:n}):null]})}function Y({getPayload:t}){const s=ue(),a=t();return e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx(j,{variant:"outline",isDisabled:a===null||s.isPending,onPress:()=>{a&&s.mutate(a)},children:s.isPending?"Testing…":"Test connection"}),e.jsx("span",{role:"status","aria-live":"polite",children:s.isPending?null:s.error?e.jsx("span",{className:"text-xs text-red-700",children:H(s.error)}):s.data?s.data.ok?e.jsxs("span",{className:"text-xs font-medium text-green-700",children:["Connected. ",s.data.model_count," model",s.data.model_count===1?"":"s"," available."]}):s.data.discovery_unsupported?e.jsxs("span",{className:"block max-w-md break-words text-xs text-amber-800",children:["This provider does not list models, so the key could not be verified here. Save it and use the provider; declare its model ids under ",e.jsx("code",{children:"models:"})," to have them show up in the catalogue. If you did not expect this, check the provider's reply below.",s.data.error?e.jsx("span",{className:"mt-0.5 block text-[var(--otari-muted)]",children:s.data.error}):null]}):e.jsx("span",{className:"block max-w-md break-words text-xs text-red-700",children:s.data.error??"Connection failed."}):null})]})}function je({onClose:t}){var _;const s=F(),[a,n]=m.useState(""),[i,c]=m.useState(""),[d,b]=m.useState(!1),[x,v]=m.useState(""),[u,f]=m.useState(""),g=le(a),l=((_=g.data)==null?void 0:_.id)===a?g.data:void 0;m.useEffect(()=>{l&&v(l.default_api_base??"")},[l]);const k=(l==null?void 0:l.env_key_present)??!1,o=((l==null?void 0:l.requires_api_key)??!0)&&!k,y=u.trim()!==""&&u.trim()!==a,N=/[:/]/.test(u),P=a!==""&&!N&&(!o||i.trim()!=="")&&!s.isPending,A=()=>{P&&s.mutate({instance:y?u.trim():a,provider_type:y?a:null,api_base:x.trim()||null,api_key:i.trim()||null},{onSuccess:t})};return e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx(I,{error:s.error}),e.jsx(U,{label:"Provider",value:a,onChange:S=>{n(S),f(""),v("")},description:"Its endpoint is built in."}),e.jsx(E,{value:i,onChange:c,label:l&&!o?"API key (optional)":"API key",description:l?o?`${l.name}'s endpoint is built in — just add your key.`:k?`${l.env_key} is set on the server, so a key is optional here. Paste one to override it.`:`${l.name} needs no API key.`:"Stored encrypted. Requires OTARI_SECRET_KEY on the server."}),e.jsx("button",{type:"button",className:"self-start text-xs font-medium text-[var(--otari-brand-dark)]",onClick:()=>b(S=>!S),children:d?"Hide advanced":"Advanced (API base, rename)"}),d?e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(C,{label:"API base",value:x,onChange:v,placeholder:(l==null?void 0:l.default_api_base)??"https://…/v1",description:"Only if you route through a proxy. Blank uses the built-in default."}),e.jsx(C,{label:"Name",value:u,onChange:f,placeholder:a||"instance name",description:N?e.jsx("span",{className:"text-red-700",children:"A name cannot contain “:” or “/”."}):"Rename to run two instances of the same provider."})]}):null,e.jsxs("div",{className:"flex flex-wrap items-start gap-2",children:[e.jsx(j,{variant:"primary",isDisabled:!P,onPress:A,children:s.isPending?"Adding…":"Add provider"}),e.jsx(j,{variant:"ghost",onPress:t,children:"Cancel"}),e.jsx(Y,{getPayload:()=>a===""?null:{instance:y?u.trim():a,provider_type:y?a:null,api_base:x.trim()||null,api_key:i.trim()||null}})]})]})}function be({onClose:t}){const s=F(),[a,n]=m.useState(""),[i,c]=m.useState("openai-compatible"),[d,b]=m.useState(""),[x,v]=m.useState(""),u=/[:/]/.test(a),f=a.trim()!==""&&!u&&d.trim()!==""&&!s.isPending,g=()=>{f&&s.mutate({instance:a.trim(),provider_type:i||"openai-compatible",api_base:d.trim(),api_key:x.trim()||null},{onSuccess:t})};return e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx(I,{error:s.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(C,{label:"Name",value:a,onChange:n,placeholder:"my-local-llm",isRequired:!0,autoFocus:!0,description:u?e.jsx("span",{className:"text-red-700",children:"A name cannot contain “:” or “/”."}):"Call it whatever you want."}),e.jsx(U,{label:"Compatible with",value:i,onChange:c,includeCatalog:!1,description:"The API this endpoint speaks.",extra:[{id:"openai-compatible",name:"OpenAI"},{id:"anthropic-compatible",name:"Anthropic"}]})]}),e.jsx(C,{label:"API base",value:d,onChange:b,placeholder:"http://localhost:8000/v1",isRequired:!0,description:"The endpoint URL of your server."}),e.jsx(E,{value:x,onChange:v,label:"API key (optional)",description:"Many local backends need none. Stored encrypted."}),e.jsxs("div",{className:"flex flex-wrap items-start gap-2",children:[e.jsx(j,{variant:"primary",isDisabled:!f,onPress:g,children:s.isPending?"Adding…":"Add provider"}),e.jsx(j,{variant:"ghost",onPress:t,children:"Cancel"}),e.jsx(Y,{getPayload:()=>a.trim()===""||d.trim()===""?null:{instance:a.trim(),provider_type:i||"openai-compatible",api_base:d.trim(),api_key:x.trim()||null}})]})]})}function ye({onClose:t}){const[s,a]=m.useState("known");return e.jsx(w,{children:e.jsxs(w.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsx("div",{className:"flex items-center justify-between",children:e.jsx("div",{className:"flex items-center gap-1 rounded-lg bg-[var(--otari-bg)] p-1",children:[["known","Known provider"],["custom","Custom endpoint"]].map(([n,i])=>e.jsx("button",{type:"button","aria-pressed":s===n,onClick:()=>a(n),className:s===n?"rounded-md bg-white px-3 py-1.5 text-sm font-medium text-[var(--otari-ink)] shadow-sm":"rounded-md px-3 py-1.5 text-sm text-[var(--otari-muted)] hover:text-[var(--otari-ink)]",children:i},n))})}),s==="known"?e.jsx(je,{onClose:t}):e.jsx(be,{onClose:t})]})})}function ke({provider:t,onClose:s,onSaved:a}){const n=ie(),[i,c]=m.useState(t.provider_type??""),[d,b]=m.useState(t.api_base??""),[x,v]=m.useState(!1),[u,f]=m.useState(""),g=()=>{if(n.isPending)return;const l={provider_type:i.trim()||null,api_base:d.trim()||null,expected_updated_at:t.updated_at};x&&u.trim()&&(l.api_key=u.trim()),n.mutate({instance:t.instance,body:l},{onSuccess:()=>{a(t.instance),s()}})};return e.jsx(w,{children:e.jsxs(w.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsxs("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Edit ",e.jsx("code",{children:t.instance})]}),e.jsx(I,{error:n.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(C,{label:"Provider type",value:i,onChange:c,placeholder:"openai"}),e.jsx(C,{label:"API base",value:d,onChange:b,placeholder:"https://api.openai.com/v1"})]}),e.jsx("div",{className:"flex flex-col gap-2",children:x?e.jsxs(e.Fragment,{children:[e.jsx(E,{value:u,onChange:f,label:"New API key",description:"Stored encrypted. The old key is replaced when you save."}),e.jsx("button",{type:"button",className:"self-start text-xs font-medium text-[var(--otari-brand-dark)]",onClick:()=>{v(!1),f("")},children:"Keep the current key"})]}):e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("span",{className:"text-sm text-[var(--otari-muted)]",children:["API key: ",e.jsx("code",{children:t.last4?`••••${t.last4}`:"none set"})]}),e.jsx(j,{size:"sm",variant:"outline",onPress:()=>v(!0),children:"Replace key"})]})}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(j,{variant:"primary",isDisabled:n.isPending,onPress:g,children:n.isPending?"Saving…":"Save changes"}),e.jsx(j,{variant:"ghost",onPress:s,children:"Cancel"})]})]})})}function Pe(t,s){const a=new Map((s??[]).map(c=>[c.instance,c])),n=new Map((t??[]).map(c=>[c.instance,c]));return[...new Set([...a.keys(),...n.keys()])].sort().map(c=>{const d=a.get(c);return{instance:c,source:d?"stored":"config",stored:d,meta:n.get(c)}})}function Ne({state:t}){return t?t.status==="pending"?e.jsxs("span",{className:"inline-flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:[e.jsx(ve,{size:"sm"})," Testing…"]}):t.ok?e.jsxs("span",{className:"text-xs font-medium text-green-700",children:["Connected. ",t.model_count," model",t.model_count===1?"":"s"," available."]}):t.discovery_unsupported?e.jsxs("span",{className:"block max-w-xs break-words text-xs text-amber-800",children:["Could not list models, so the key could not be verified. It may still work for requests.",t.error?e.jsx("span",{className:"mt-0.5 block text-[var(--otari-muted)]",children:t.error}):null]}):e.jsx("span",{className:"block max-w-xs break-words text-xs text-red-700",children:t.error??"Connection failed."}):null}function _e({health:t}){if(!t)return e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"—"});const s=!t.ok&&t.discovery_unsupported,a=t.ok?"border-green-200 bg-green-50 text-green-700":s?"border-amber-200 bg-amber-50 text-amber-800":"border-red-200 bg-red-50 text-red-700",n=t.ok?"bg-green-500":s?"bg-amber-500":"bg-red-500",i=t.checked_at?`Last checked ${$(t.checked_at)}`:"Not checked yet",c=s?`${t.error??"This provider does not list models."} Requests to it may still work.`:t.error??"Unreachable",d=t.ok?i:`${c} · ${i}`;return e.jsxs("span",{title:d,className:`inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium ${a}`,children:[e.jsx("span",{"aria-hidden":!0,className:`h-1.5 w-1.5 rounded-full ${n}`}),t.ok?"Reachable":s?"No model discovery":"Unreachable"]})}function Se({healthy:t,degraded:s,total:a,checkedAt:n}){const c=t===a?"bg-green-500":t+s===a?"bg-amber-500":"bg-red-500",d=oe();return e.jsxs("div",{className:"flex flex-wrap items-center gap-3 rounded-xl border border-[var(--otari-line)] bg-[var(--otari-surface)] px-4 py-2.5 text-sm",children:[e.jsx("span",{"aria-hidden":!0,className:`h-2 w-2 rounded-full ${c}`}),e.jsxs("span",{className:"font-medium text-[var(--otari-ink)]",children:[t," of ",a," provider",a===1?"":"s"," reachable"]}),s>0?e.jsxs("span",{className:"text-amber-800",children:[s," without model discovery"]}):null,n?e.jsxs("span",{className:"text-[var(--otari-muted)]",children:["Last checked ",$(n)]}):null,e.jsx(j,{size:"sm",variant:"ghost",className:"ml-auto",isDisabled:d.isPending,onPress:()=>d.mutate(),children:d.isPending?"Re-checking…":"Re-check all"})]})}function R({n:t,title:s,children:a}){return e.jsxs("li",{className:"flex gap-3",children:[e.jsx("span",{className:"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--otari-brand-tint)] text-xs font-semibold text-[var(--otari-brand-dark)]",children:t}),e.jsxs("div",{className:"text-sm",children:[e.jsx("div",{className:"font-medium text-[var(--otari-ink)]",children:s}),e.jsx("div",{className:"text-[var(--otari-muted)]",children:a})]})]})}function Ce({onAddProvider:t,needsPricing:s,onEnablePricing:a,enabling:n,secretKeyConfigured:i}){return e.jsx(w,{children:e.jsxs(w.Content,{className:"flex flex-col gap-4 p-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"Welcome to Otari"}),e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"You are signed in. Add a provider to start serving models: three quick steps."})]}),e.jsxs("ol",{className:"flex flex-col gap-3",children:[e.jsxs(R,{n:1,title:"Add a provider",children:["Enter a provider name (like ",e.jsx("code",{children:"openai"}),") and its API key. Keys are encrypted at rest."]}),e.jsxs(R,{n:2,title:"Test the connection",children:["Use ",e.jsx("strong",{children:"Test"})," on the provider row to confirm the key works and see how many models it serves."]}),e.jsxs(R,{n:3,title:"Send your first request",children:["Point your app at ",e.jsx("code",{children:"/v1"})," on this gateway with the API key printed in the server logs (",e.jsx("code",{children:"gw-…"}),"). See the"," ",e.jsx("a",{href:"/welcome",target:"_blank",rel:"noreferrer",className:"font-medium text-[var(--otari-brand-dark)]",children:"quickstart"}),"."]})]}),s?e.jsxs("p",{className:"text-sm text-[var(--otari-muted)]",children:["Tip: ",e.jsx("code",{children:"require_pricing"})," is on, so requests are rejected until pricing is set."," ",e.jsx("button",{type:"button",className:"font-medium text-[var(--otari-brand-dark)] disabled:opacity-50",disabled:n,onClick:a,children:"Enable default pricing"})," ","to meter new models with public rates."]}):null,e.jsx("div",{children:e.jsx(j,{variant:"primary",isDisabled:!i,onPress:t,children:"Add your first provider"})})]})})}function Ke(){var D,L,O,q;const t=Q(),s=X(),a=Z(),n=ee(),i=te(),c=se(),d=ae(),[b,x]=m.useState(!1),[v,u]=m.useState(null),[f,g]=m.useState({}),l=Pe((D=t.data)==null?void 0:D.providers,s.data),k=new Map((((L=n.data)==null?void 0:L.providers)??[]).map(r=>[r.instance,r])),o=t.isLoading||s.isLoading,y=((O=s.data)==null?void 0:O.find(r=>r.instance===v))??null,N=((q=a.data)==null?void 0:q.require_pricing)===!0&&a.data.default_pricing===!1,P=a.data?a.data.secret_key_configured!==!1:!a.isError,A=!o&&l.length===0&&!b,_=m.useRef({}),S=r=>{const p=(_.current[r]??0)+1;return _.current[r]=p,p},K=r=>{S(r),g(p=>{if(!Object.hasOwn(p,r))return p;const h={...p};return delete h[r],h})},B=(r,p,h)=>{_.current[r]===p&&g(W=>({...W,[r]:h}))},V=async r=>{const p=S(r);g(h=>({...h,[r]:{status:"pending"}}));try{const h=await c.mutateAsync(r);B(r,p,{status:"done",...h})}catch(h){B(r,p,{status:"done",ok:!1,model_count:0,error:H(h),discovery_unsupported:!1})}},G=[{id:"provider",header:"Provider",isRowHeader:!0,cell:r=>e.jsx(J,{to:`/models?provider=${encodeURIComponent(r.instance)}`,className:"font-medium text-[var(--otari-ink)] hover:text-[var(--otari-brand-dark)] hover:underline",children:r.instance})},{id:"type",header:"Type",cell:r=>{var p,h;return e.jsx("span",{className:"text-[var(--otari-muted)]",children:((p=r.meta)==null?void 0:p.provider_type)??((h=r.stored)==null?void 0:h.provider_type)??r.instance})}},{id:"source",header:"Source",cell:r=>e.jsx(xe,{size:"sm",color:r.source==="stored"?"accent":"default",children:r.source==="stored"?"stored":"config"})},{id:"api_key",header:"API key",cell:r=>{var p,h;return e.jsx("span",{className:"text-[var(--otari-muted)]",children:r.source==="stored"?r.stored&&!r.stored.decryptable?e.jsx("span",{className:"text-amber-700",title:"This key can't be decrypted with the current OTARI_SECRET_KEY. Replace the key, or restore the original OTARI_SECRET_KEY.",children:"⚠ key unreadable"}):e.jsx("code",{children:(p=r.stored)!=null&&p.last4?`••••${r.stored.last4}`:"none set"}):(h=r.meta)!=null&&h.env_key?e.jsxs("span",{children:["via ",e.jsx("code",{children:r.meta.env_key})]}):"config.yml"})}},{id:"status",header:"Status",cell:r=>e.jsx(_e,{health:k.get(r.instance)})},{id:"actions",header:"Actions",align:"end",cell:r=>{var p,h;return r.source==="stored"?e.jsxs("div",{className:"flex flex-col items-end gap-1.5",children:[e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx(j,{size:"sm",variant:"outline",isDisabled:((p=f[r.instance])==null?void 0:p.status)==="pending"||((h=r.stored)==null?void 0:h.decryptable)===!1,onPress:()=>void V(r.instance),children:"Test"}),e.jsx(j,{size:"sm",variant:"ghost",onPress:()=>{x(!1),u(r.instance)},children:"Edit"}),e.jsx(de,{confirmLabel:"Delete",isPending:i.isPending,onConfirm:()=>i.mutate(r.instance,{onSuccess:()=>K(r.instance)}),children:"Delete"})]}),e.jsx(Ne,{state:f[r.instance]})]}):e.jsx("span",{className:"block text-right text-xs text-[var(--otari-muted)]",children:"managed in config.yml"})}}];return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(re,{title:"Providers",description:"Add provider API keys here to serve models without editing config.yml. Keys are encrypted at rest.",action:b||A?null:e.jsx(j,{variant:"primary",isDisabled:!P,onPress:()=>{u(null),x(!0)},children:"Add provider"})}),e.jsx(I,{error:t.error??s.error??a.error??n.error??d.error??i.error}),P?null:e.jsxs(ne,{tone:"warning",children:[e.jsx("code",{children:"OTARI_SECRET_KEY"})," is not set, so provider keys can't be encrypted at rest and adding providers from the dashboard is disabled. Set it on the server and restart to add providers here. Providers defined in"," ",e.jsx("code",{children:"config.yml"})," keep working without it."]}),A?e.jsx(Ce,{onAddProvider:()=>{u(null),x(!0)},needsPricing:N,onEnablePricing:()=>d.mutate({default_pricing:!0}),enabling:d.isPending,secretKeyConfigured:P}):null,b&&P?e.jsx(ye,{onClose:()=>x(!1)}):null,y?e.jsx(ke,{provider:y,onClose:()=>u(null),onSaved:K}):null,!o&&l.length>0&&n.data&&n.data.total>0?e.jsx(Se,{healthy:n.data.healthy,degraded:n.data.degraded,total:n.data.total,checkedAt:n.data.checked_at}):null,A?null:e.jsx(me,{ariaLabel:"Providers",columns:G,rows:l,getRowKey:r=>r.instance,isLoading:o,emptyContent:"No providers yet. Add your first provider to start serving models."})]})}export{Ke as ProvidersPage}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as m,L as J}from"./react-dgEcD0HR.js";import{a2 as Q,ad as X,a0 as Z,a4 as ee,ae as te,af as se,ag as ae,P as re,E as I,M as ne,ah as ie,ai as oe,ab as $,y as de,aj as F,ak as le,_ as H,al as ce,am as ue}from"./index-Dit1BUBh.js";import{F as C}from"./Field-GEMwIhf7.js";import{D as me}from"./DataTable-BHrpJHmX.js";import{B as j,d as w,g as xe,e as pe,L as z,I as M,D as he,S as ve,C as T,a as ge,b as fe}from"./heroui-DhloIxuc.js";function E({value:t,onChange:s,label:a,placeholder:n,description:i}){return e.jsxs(pe,{value:t,onChange:s,className:"flex max-w-md flex-col gap-1",children:[e.jsx(z,{className:"text-sm font-medium text-[var(--otari-ink)]",children:a}),e.jsx(M,{type:"password",placeholder:n??"sk-…",autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",spellCheck:!1,"data-1p-ignore":!0,"data-lpignore":"true"}),i?e.jsx(he,{className:"text-xs text-[var(--otari-muted)]",children:i}):null]})}function U({label:t,value:s,onChange:a,description:n,placeholder:i,extra:c=[],includeCatalog:d=!0}){var k;const b=ce(),x=m.useMemo(()=>d?[...c,...(b.data??[]).map(o=>({id:o.id,name:o.name}))]:c,[b.data,c,d]),[v,u]=m.useState(()=>{var o;return((o=x.find(y=>y.id===s))==null?void 0:o.name)??""}),f=((k=x.find(o=>o.id===s))==null?void 0:k.name)??"",g=v.trim()===f.trim()?"":v.trim().toLowerCase(),l=x.filter(o=>!g||o.name.toLowerCase().includes(g)||o.id.toLowerCase().includes(g)).slice(0,50);return e.jsxs(T.Root,{allowsEmptyCollection:!0,menuTrigger:"focus",inputValue:v,onInputChange:u,onSelectionChange:o=>{var y;o!=null?(a(String(o)),u(((y=x.find(N=>N.id===String(o)))==null?void 0:y.name)??"")):(a(""),u(""))},className:"flex max-w-md flex-col gap-1",children:[e.jsx(z,{className:"text-sm font-medium text-[var(--otari-ink)]",children:t}),e.jsxs(T.InputGroup,{children:[e.jsx(M,{placeholder:i??"Search providers…",autoComplete:"off","data-1p-ignore":!0,"data-lpignore":"true",onFocus:o=>o.currentTarget.select()}),e.jsx(T.Trigger,{})]}),e.jsx(T.Popover,{children:e.jsx(ge,{items:l,className:"max-h-72 overflow-auto",children:o=>e.jsx(fe,{id:o.id,textValue:o.name,children:o.name})})}),n?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:n}):null]})}function Y({getPayload:t}){const s=ue(),a=t();return e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx(j,{variant:"outline",isDisabled:a===null||s.isPending,onPress:()=>{a&&s.mutate(a)},children:s.isPending?"Testing…":"Test connection"}),e.jsx("span",{role:"status","aria-live":"polite",children:s.isPending?null:s.error?e.jsx("span",{className:"text-xs text-red-700",children:H(s.error)}):s.data?s.data.ok?e.jsxs("span",{className:"text-xs font-medium text-green-700",children:["Connected. ",s.data.model_count," model",s.data.model_count===1?"":"s"," available."]}):s.data.discovery_unsupported?e.jsxs("span",{className:"block max-w-md break-words text-xs text-amber-800",children:["This provider does not list models, so the key could not be verified here. Save it and use the provider; declare its model ids under ",e.jsx("code",{children:"models:"})," to have them show up in the catalogue. If you did not expect this, check the provider's reply below.",s.data.error?e.jsx("span",{className:"mt-0.5 block text-[var(--otari-muted)]",children:s.data.error}):null]}):e.jsx("span",{className:"block max-w-md break-words text-xs text-red-700",children:s.data.error??"Connection failed."}):null})]})}function je({onClose:t}){var _;const s=F(),[a,n]=m.useState(""),[i,c]=m.useState(""),[d,b]=m.useState(!1),[x,v]=m.useState(""),[u,f]=m.useState(""),g=le(a),l=((_=g.data)==null?void 0:_.id)===a?g.data:void 0;m.useEffect(()=>{l&&v(l.default_api_base??"")},[l]);const k=(l==null?void 0:l.env_key_present)??!1,o=((l==null?void 0:l.requires_api_key)??!0)&&!k,y=u.trim()!==""&&u.trim()!==a,N=/[:/]/.test(u),P=a!==""&&!N&&(!o||i.trim()!=="")&&!s.isPending,A=()=>{P&&s.mutate({instance:y?u.trim():a,provider_type:y?a:null,api_base:x.trim()||null,api_key:i.trim()||null},{onSuccess:t})};return e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx(I,{error:s.error}),e.jsx(U,{label:"Provider",value:a,onChange:S=>{n(S),f(""),v("")},description:"Its endpoint is built in."}),e.jsx(E,{value:i,onChange:c,label:l&&!o?"API key (optional)":"API key",description:l?o?`${l.name}'s endpoint is built in — just add your key.`:k?`${l.env_key} is set on the server, so a key is optional here. Paste one to override it.`:`${l.name} needs no API key.`:"Stored encrypted. Requires OTARI_SECRET_KEY on the server."}),e.jsx("button",{type:"button",className:"self-start text-xs font-medium text-[var(--otari-brand-dark)]",onClick:()=>b(S=>!S),children:d?"Hide advanced":"Advanced (API base, rename)"}),d?e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(C,{label:"API base",value:x,onChange:v,placeholder:(l==null?void 0:l.default_api_base)??"https://…/v1",description:"Only if you route through a proxy. Blank uses the built-in default."}),e.jsx(C,{label:"Name",value:u,onChange:f,placeholder:a||"instance name",description:N?e.jsx("span",{className:"text-red-700",children:"A name cannot contain “:” or “/”."}):"Rename to run two instances of the same provider."})]}):null,e.jsxs("div",{className:"flex flex-wrap items-start gap-2",children:[e.jsx(j,{variant:"primary",isDisabled:!P,onPress:A,children:s.isPending?"Adding…":"Add provider"}),e.jsx(j,{variant:"ghost",onPress:t,children:"Cancel"}),e.jsx(Y,{getPayload:()=>a===""?null:{instance:y?u.trim():a,provider_type:y?a:null,api_base:x.trim()||null,api_key:i.trim()||null}})]})]})}function be({onClose:t}){const s=F(),[a,n]=m.useState(""),[i,c]=m.useState("openai-compatible"),[d,b]=m.useState(""),[x,v]=m.useState(""),u=/[:/]/.test(a),f=a.trim()!==""&&!u&&d.trim()!==""&&!s.isPending,g=()=>{f&&s.mutate({instance:a.trim(),provider_type:i||"openai-compatible",api_base:d.trim(),api_key:x.trim()||null},{onSuccess:t})};return e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx(I,{error:s.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(C,{label:"Name",value:a,onChange:n,placeholder:"my-local-llm",isRequired:!0,autoFocus:!0,description:u?e.jsx("span",{className:"text-red-700",children:"A name cannot contain “:” or “/”."}):"Call it whatever you want."}),e.jsx(U,{label:"Compatible with",value:i,onChange:c,includeCatalog:!1,description:"The API this endpoint speaks.",extra:[{id:"openai-compatible",name:"OpenAI"},{id:"anthropic-compatible",name:"Anthropic"}]})]}),e.jsx(C,{label:"API base",value:d,onChange:b,placeholder:"http://localhost:8000/v1",isRequired:!0,description:"The endpoint URL of your server."}),e.jsx(E,{value:x,onChange:v,label:"API key (optional)",description:"Many local backends need none. Stored encrypted."}),e.jsxs("div",{className:"flex flex-wrap items-start gap-2",children:[e.jsx(j,{variant:"primary",isDisabled:!f,onPress:g,children:s.isPending?"Adding…":"Add provider"}),e.jsx(j,{variant:"ghost",onPress:t,children:"Cancel"}),e.jsx(Y,{getPayload:()=>a.trim()===""||d.trim()===""?null:{instance:a.trim(),provider_type:i||"openai-compatible",api_base:d.trim(),api_key:x.trim()||null}})]})]})}function ye({onClose:t}){const[s,a]=m.useState("known");return e.jsx(w,{children:e.jsxs(w.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsx("div",{className:"flex items-center justify-between",children:e.jsx("div",{className:"flex items-center gap-1 rounded-lg bg-[var(--otari-bg)] p-1",children:[["known","Known provider"],["custom","Custom endpoint"]].map(([n,i])=>e.jsx("button",{type:"button","aria-pressed":s===n,onClick:()=>a(n),className:s===n?"rounded-md bg-white px-3 py-1.5 text-sm font-medium text-[var(--otari-ink)] shadow-sm":"rounded-md px-3 py-1.5 text-sm text-[var(--otari-muted)] hover:text-[var(--otari-ink)]",children:i},n))})}),s==="known"?e.jsx(je,{onClose:t}):e.jsx(be,{onClose:t})]})})}function ke({provider:t,onClose:s,onSaved:a}){const n=ie(),[i,c]=m.useState(t.provider_type??""),[d,b]=m.useState(t.api_base??""),[x,v]=m.useState(!1),[u,f]=m.useState(""),g=()=>{if(n.isPending)return;const l={provider_type:i.trim()||null,api_base:d.trim()||null,expected_updated_at:t.updated_at};x&&u.trim()&&(l.api_key=u.trim()),n.mutate({instance:t.instance,body:l},{onSuccess:()=>{a(t.instance),s()}})};return e.jsx(w,{children:e.jsxs(w.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsxs("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Edit ",e.jsx("code",{children:t.instance})]}),e.jsx(I,{error:n.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(C,{label:"Provider type",value:i,onChange:c,placeholder:"openai"}),e.jsx(C,{label:"API base",value:d,onChange:b,placeholder:"https://api.openai.com/v1"})]}),e.jsx("div",{className:"flex flex-col gap-2",children:x?e.jsxs(e.Fragment,{children:[e.jsx(E,{value:u,onChange:f,label:"New API key",description:"Stored encrypted. The old key is replaced when you save."}),e.jsx("button",{type:"button",className:"self-start text-xs font-medium text-[var(--otari-brand-dark)]",onClick:()=>{v(!1),f("")},children:"Keep the current key"})]}):e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("span",{className:"text-sm text-[var(--otari-muted)]",children:["API key: ",e.jsx("code",{children:t.last4?`••••${t.last4}`:"none set"})]}),e.jsx(j,{size:"sm",variant:"outline",onPress:()=>v(!0),children:"Replace key"})]})}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(j,{variant:"primary",isDisabled:n.isPending,onPress:g,children:n.isPending?"Saving…":"Save changes"}),e.jsx(j,{variant:"ghost",onPress:s,children:"Cancel"})]})]})})}function Pe(t,s){const a=new Map((s??[]).map(c=>[c.instance,c])),n=new Map((t??[]).map(c=>[c.instance,c]));return[...new Set([...a.keys(),...n.keys()])].sort().map(c=>{const d=a.get(c);return{instance:c,source:d?"stored":"config",stored:d,meta:n.get(c)}})}function Ne({state:t}){return t?t.status==="pending"?e.jsxs("span",{className:"inline-flex items-center gap-1.5 text-xs text-[var(--otari-muted)]",children:[e.jsx(ve,{size:"sm"})," Testing…"]}):t.ok?e.jsxs("span",{className:"text-xs font-medium text-green-700",children:["Connected. ",t.model_count," model",t.model_count===1?"":"s"," available."]}):t.discovery_unsupported?e.jsxs("span",{className:"block max-w-xs break-words text-xs text-amber-800",children:["Could not list models, so the key could not be verified. It may still work for requests.",t.error?e.jsx("span",{className:"mt-0.5 block text-[var(--otari-muted)]",children:t.error}):null]}):e.jsx("span",{className:"block max-w-xs break-words text-xs text-red-700",children:t.error??"Connection failed."}):null}function _e({health:t}){if(!t)return e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"—"});const s=!t.ok&&t.discovery_unsupported,a=t.ok?"border-green-200 bg-green-50 text-green-700":s?"border-amber-200 bg-amber-50 text-amber-800":"border-red-200 bg-red-50 text-red-700",n=t.ok?"bg-green-500":s?"bg-amber-500":"bg-red-500",i=t.checked_at?`Last checked ${$(t.checked_at)}`:"Not checked yet",c=s?`${t.error??"This provider does not list models."} Requests to it may still work.`:t.error??"Unreachable",d=t.ok?i:`${c} · ${i}`;return e.jsxs("span",{title:d,className:`inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium ${a}`,children:[e.jsx("span",{"aria-hidden":!0,className:`h-1.5 w-1.5 rounded-full ${n}`}),t.ok?"Reachable":s?"No model discovery":"Unreachable"]})}function Se({healthy:t,degraded:s,total:a,checkedAt:n}){const c=t===a?"bg-green-500":t+s===a?"bg-amber-500":"bg-red-500",d=oe();return e.jsxs("div",{className:"flex flex-wrap items-center gap-3 rounded-xl border border-[var(--otari-line)] bg-[var(--otari-surface)] px-4 py-2.5 text-sm",children:[e.jsx("span",{"aria-hidden":!0,className:`h-2 w-2 rounded-full ${c}`}),e.jsxs("span",{className:"font-medium text-[var(--otari-ink)]",children:[t," of ",a," provider",a===1?"":"s"," reachable"]}),s>0?e.jsxs("span",{className:"text-amber-800",children:[s," without model discovery"]}):null,n?e.jsxs("span",{className:"text-[var(--otari-muted)]",children:["Last checked ",$(n)]}):null,e.jsx(j,{size:"sm",variant:"ghost",className:"ml-auto",isDisabled:d.isPending,onPress:()=>d.mutate(),children:d.isPending?"Re-checking…":"Re-check all"})]})}function R({n:t,title:s,children:a}){return e.jsxs("li",{className:"flex gap-3",children:[e.jsx("span",{className:"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--otari-brand-tint)] text-xs font-semibold text-[var(--otari-brand-dark)]",children:t}),e.jsxs("div",{className:"text-sm",children:[e.jsx("div",{className:"font-medium text-[var(--otari-ink)]",children:s}),e.jsx("div",{className:"text-[var(--otari-muted)]",children:a})]})]})}function Ce({onAddProvider:t,needsPricing:s,onEnablePricing:a,enabling:n,secretKeyConfigured:i}){return e.jsx(w,{children:e.jsxs(w.Content,{className:"flex flex-col gap-4 p-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"Welcome to Otari"}),e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"You are signed in. Add a provider to start serving models: three quick steps."})]}),e.jsxs("ol",{className:"flex flex-col gap-3",children:[e.jsxs(R,{n:1,title:"Add a provider",children:["Enter a provider name (like ",e.jsx("code",{children:"openai"}),") and its API key. Keys are encrypted at rest."]}),e.jsxs(R,{n:2,title:"Test the connection",children:["Use ",e.jsx("strong",{children:"Test"})," on the provider row to confirm the key works and see how many models it serves."]}),e.jsxs(R,{n:3,title:"Send your first request",children:["Point your app at ",e.jsx("code",{children:"/v1"})," on this gateway with the API key printed in the server logs (",e.jsx("code",{children:"gw-…"}),"). See the"," ",e.jsx("a",{href:"/welcome",target:"_blank",rel:"noreferrer",className:"font-medium text-[var(--otari-brand-dark)]",children:"quickstart"}),"."]})]}),s?e.jsxs("p",{className:"text-sm text-[var(--otari-muted)]",children:["Tip: ",e.jsx("code",{children:"require_pricing"})," is on, so requests are rejected until pricing is set."," ",e.jsx("button",{type:"button",className:"font-medium text-[var(--otari-brand-dark)] disabled:opacity-50",disabled:n,onClick:a,children:"Enable default pricing"})," ","to meter new models with public rates."]}):null,e.jsx("div",{children:e.jsx(j,{variant:"primary",isDisabled:!i,onPress:t,children:"Add your first provider"})})]})})}function Ke(){var D,L,O,q;const t=Q(),s=X(),a=Z(),n=ee(),i=te(),c=se(),d=ae(),[b,x]=m.useState(!1),[v,u]=m.useState(null),[f,g]=m.useState({}),l=Pe((D=t.data)==null?void 0:D.providers,s.data),k=new Map((((L=n.data)==null?void 0:L.providers)??[]).map(r=>[r.instance,r])),o=t.isLoading||s.isLoading,y=((O=s.data)==null?void 0:O.find(r=>r.instance===v))??null,N=((q=a.data)==null?void 0:q.require_pricing)===!0&&a.data.default_pricing===!1,P=a.data?a.data.secret_key_configured!==!1:!a.isError,A=!o&&l.length===0&&!b,_=m.useRef({}),S=r=>{const p=(_.current[r]??0)+1;return _.current[r]=p,p},K=r=>{S(r),g(p=>{if(!Object.hasOwn(p,r))return p;const h={...p};return delete h[r],h})},B=(r,p,h)=>{_.current[r]===p&&g(W=>({...W,[r]:h}))},V=async r=>{const p=S(r);g(h=>({...h,[r]:{status:"pending"}}));try{const h=await c.mutateAsync(r);B(r,p,{status:"done",...h})}catch(h){B(r,p,{status:"done",ok:!1,model_count:0,error:H(h),discovery_unsupported:!1})}},G=[{id:"provider",header:"Provider",isRowHeader:!0,cell:r=>e.jsx(J,{to:`/models?provider=${encodeURIComponent(r.instance)}`,className:"font-medium text-[var(--otari-ink)] hover:text-[var(--otari-brand-dark)] hover:underline",children:r.instance})},{id:"type",header:"Type",cell:r=>{var p,h;return e.jsx("span",{className:"text-[var(--otari-muted)]",children:((p=r.meta)==null?void 0:p.provider_type)??((h=r.stored)==null?void 0:h.provider_type)??r.instance})}},{id:"source",header:"Source",cell:r=>e.jsx(xe,{size:"sm",color:r.source==="stored"?"accent":"default",children:r.source==="stored"?"stored":"config"})},{id:"api_key",header:"API key",cell:r=>{var p,h;return e.jsx("span",{className:"text-[var(--otari-muted)]",children:r.source==="stored"?r.stored&&!r.stored.decryptable?e.jsx("span",{className:"text-amber-700",title:"This key can't be decrypted with the current OTARI_SECRET_KEY. Replace the key, or restore the original OTARI_SECRET_KEY.",children:"⚠ key unreadable"}):e.jsx("code",{children:(p=r.stored)!=null&&p.last4?`••••${r.stored.last4}`:"none set"}):(h=r.meta)!=null&&h.env_key?e.jsxs("span",{children:["via ",e.jsx("code",{children:r.meta.env_key})]}):"config.yml"})}},{id:"status",header:"Status",cell:r=>e.jsx(_e,{health:k.get(r.instance)})},{id:"actions",header:"Actions",align:"end",cell:r=>{var p,h;return r.source==="stored"?e.jsxs("div",{className:"flex flex-col items-end gap-1.5",children:[e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx(j,{size:"sm",variant:"outline",isDisabled:((p=f[r.instance])==null?void 0:p.status)==="pending"||((h=r.stored)==null?void 0:h.decryptable)===!1,onPress:()=>void V(r.instance),children:"Test"}),e.jsx(j,{size:"sm",variant:"ghost",onPress:()=>{x(!1),u(r.instance)},children:"Edit"}),e.jsx(de,{confirmLabel:"Delete",isPending:i.isPending,onConfirm:()=>i.mutate(r.instance,{onSuccess:()=>K(r.instance)}),children:"Delete"})]}),e.jsx(Ne,{state:f[r.instance]})]}):e.jsx("span",{className:"block text-right text-xs text-[var(--otari-muted)]",children:"managed in config.yml"})}}];return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(re,{title:"Providers",description:"Add provider API keys here to serve models without editing config.yml. Keys are encrypted at rest.",action:b||A?null:e.jsx(j,{variant:"primary",isDisabled:!P,onPress:()=>{u(null),x(!0)},children:"Add provider"})}),e.jsx(I,{error:t.error??s.error??a.error??n.error??d.error??i.error}),P?null:e.jsxs(ne,{tone:"warning",children:[e.jsx("code",{children:"OTARI_SECRET_KEY"})," is not set, so provider keys can't be encrypted at rest and adding providers from the dashboard is disabled. Set it on the server and restart to add providers here. Providers defined in"," ",e.jsx("code",{children:"config.yml"})," keep working without it."]}),A?e.jsx(Ce,{onAddProvider:()=>{u(null),x(!0)},needsPricing:N,onEnablePricing:()=>d.mutate({default_pricing:!0}),enabling:d.isPending,secretKeyConfigured:P}):null,b&&P?e.jsx(ye,{onClose:()=>x(!1)}):null,y?e.jsx(ke,{provider:y,onClose:()=>u(null),onSaved:K}):null,!o&&l.length>0&&n.data&&n.data.total>0?e.jsx(Se,{healthy:n.data.healthy,degraded:n.data.degraded,total:n.data.total,checkedAt:n.data.checked_at}):null,A?null:e.jsx(me,{ariaLabel:"Providers",columns:G,rows:l,getRowKey:r=>r.instance,isLoading:o,emptyContent:"No providers yet. Add your first provider to start serving models."})]})}export{Ke as ProvidersPage}; diff --git a/src/gateway/static/dashboard/assets/RoutingPage-D1os8M2m.js b/src/gateway/static/dashboard/assets/RoutingPage-2qgzgln4.js similarity index 99% rename from src/gateway/static/dashboard/assets/RoutingPage-D1os8M2m.js rename to src/gateway/static/dashboard/assets/RoutingPage-2qgzgln4.js index 9bb1ea4f3..59ba51f77 100644 --- a/src/gateway/static/dashboard/assets/RoutingPage-D1os8M2m.js +++ b/src/gateway/static/dashboard/assets/RoutingPage-2qgzgln4.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as j,u as J,L as D}from"./react-dgEcD0HR.js";import{s as X,t as Q,v as Y,w as Z,x as ee,q as M,y as se,P as te,E as U,z as ae,B as re,D as le,G as ne,u as ie}from"./index-D-R1nuKP.js";import{D as oe}from"./DataTable-BHrpJHmX.js";import{F as L}from"./Field-GEMwIhf7.js";import{C as A,L as de,I as ce,a as ue,b as me,g as q,B as _,d as $}from"./heroui-DhloIxuc.js";import{U as he}from"./UserComboBox-DWvRaj2b.js";const xe=50;function E({label:s,value:a,onChange:r,description:n,placeholder:o="provider:model",autoFocus:h,isRequired:b}){const x=X(),{visible:p,total:k,failed:v}=j.useMemo(()=>{var y;const t=a.trim().toLowerCase(),f=((y=x.data)==null?void 0:y.providers)??[],u=f.flatMap(N=>N.models),C=t?u.filter(N=>N.key.toLowerCase().includes(t)):u;return{visible:C.slice(0,xe),total:C.length,failed:f.filter(N=>!N.ok)}},[x.data,a]),P=x.isLoading?"Loading models from your providers…":v.length>0?`Could not list models for ${v.map(f=>f.provider).join(", ")}. Check that provider's credentials, or type the model key directly.`:k>p.length?`Showing ${p.length} of ${k} matches. Keep typing to narrow them.`:n;return e.jsxs(A.Root,{allowsCustomValue:!0,allowsEmptyCollection:!0,menuTrigger:"input",inputValue:a,onInputChange:r,onSelectionChange:t=>{t!=null&&r(String(t))},isRequired:b,className:"flex max-w-md flex-col gap-1",children:[e.jsx(de,{className:"text-sm font-medium text-[var(--otari-ink)]",children:s}),e.jsxs(A.InputGroup,{children:[e.jsx(ce,{placeholder:o,autoFocus:h}),e.jsx(A.Trigger,{})]}),e.jsx(A.Popover,{children:e.jsx(ue,{items:p,className:"max-h-72 overflow-auto",children:t=>e.jsx(me,{id:t.key,textValue:t.key,children:t.key})})}),P?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:P}):null]})}function pe(s){return{kind:"alias",name:s.name,spec:{select:[{default:s.target}]},source:s.source,user_id:s.user_id,is_dynamic:!1,created_at:s.created_at,updated_at:s.updated_at}}const fe=s=>JSON.stringify([s.kind,s.user_id,s.name]);function ve(){var n;const s=ne(),a=(n=s.data)==null?void 0:n.fields.find(o=>o.key==="guardrails_url"),r=typeof(a==null?void 0:a.value)=="string"?a.value.trim():"";return{configured:s.isLoading||r!=="",isLoading:s.isLoading}}function ge(s){return s.select.every(a=>{var o;if(a.default!==void 0)return a.when===void 0;if(a.router!==void 0)return!1;const r=a.when;if(r===void 0||a.target===void 0)return!1;const n=Object.keys(r);return n.length===1&&n[0]==="budget_used_pct"&&((o=r.budget_used_pct)==null?void 0:o.gte)!==void 0})}function G(s){var a;return((a=s.select.find(r=>r.default!==void 0))==null?void 0:a.default)??""}function je(s){return s.select.filter(a=>{var r,n;return((n=(r=a.when)==null?void 0:r.budget_used_pct)==null?void 0:n.gte)!==void 0&&a.target!==void 0}).map(a=>({threshold:a.when.budget_used_pct.gte,target:a.target}))}function be(s){const a=s.spec.on_failure??[];if(s.is_dynamic){const n=1+a.length;return`Chosen per request · ${n} candidate${n===1?"":"s"}`}const r=G(s.spec);return a.length>0?`${r} +${a.length} on failure`:r}function ye({userId:s,onChange:a}){const r=ie(),n=s!==null,o=(h,b)=>e.jsx("button",{type:"button","aria-pressed":n===h,onClick:()=>a(h?"":null),className:n===h?"rounded-md bg-white px-3 py-1.5 text-sm font-medium text-[var(--otari-ink)] shadow-sm":"rounded-md px-3 py-1.5 text-sm text-[var(--otari-muted)] hover:text-[var(--otari-ink)]",children:b});return e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsxs("div",{children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Applies to"}),e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"A global policy resolves for every caller. A user-scoped one resolves only for that user, and takes precedence over a global policy of the same name."})]}),e.jsxs("div",{className:"flex w-fit items-center gap-1 rounded-lg bg-[var(--otari-bg)] p-1",children:[o(!1,"Every caller"),o(!0,"One user")]}),n?e.jsx(he,{label:"User",value:s??"",onChange:a,users:r.data??[],placeholder:"Pick a user…",description:"Only this user resolves the policy.",unknownHint:e.jsx("span",{className:"text-red-700",children:"No such user. Pick an existing one."})}):null]})}const Ne=["block","monitor"];function F({label:s,hint:a,value:r,onChange:n}){return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:s}),e.jsx("div",{className:"flex w-fit items-center gap-1 rounded-lg bg-[var(--otari-bg)] p-1",children:Ne.map(o=>e.jsx("button",{type:"button","aria-pressed":r===o,onClick:()=>n(o),className:r===o?"rounded-md bg-white px-3 py-1 text-sm font-medium text-[var(--otari-ink)] shadow-sm":"rounded-md px-3 py-1 text-sm text-[var(--otari-muted)] hover:text-[var(--otari-ink)]",children:o},o))}),a===void 0?null:e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:a})]})}function O({existing:s,initialTarget:a="",onClose:r}){const n=re(),o=le(),h=s!==null,b=(s==null?void 0:s.kind)==="alias",x=ve(),[p,k]=j.useState((s==null?void 0:s.name)??""),[v,P]=j.useState((s==null?void 0:s.user_id)??null),[t,f]=j.useState(s?G(s.spec):a),[u,C]=j.useState((s==null?void 0:s.spec.on_failure)??[]),[y,N]=j.useState(s?je(s.spec):[]),[w,S]=j.useState((s==null?void 0:s.spec.guardrails)??[]),I=/[:/]/.test(p),V=v===null||v.trim()!=="",z=y.every(l=>l.target.trim()!==""&&l.threshold>0&&l.threshold<100),H=w.every(l=>l.profile.trim()!==""),T=p.trim()!==""&&t.trim()!==""&&!I&&V&&z&&H&&u.every(l=>l.trim()!==""),K=j.useMemo(()=>({select:[...y.map(l=>({when:{budget_used_pct:{gte:l.threshold}},target:l.target.trim()})),{default:t.trim()}],...u.length>0?{on_failure:u.map(l=>l.trim())}:{},...w.length>0?{guardrails:w}:{}}),[y,t,u,w]),R=b&&(u.length>0||y.length>0||w.length>0),B=n.isPending||o.isPending,W=()=>{if(!T||R)return;const l=v===null?null:v.trim();if(b){o.mutate({name:p.trim(),target:t.trim(),user_id:l},{onSuccess:r});return}n.mutate({name:p.trim(),spec:K,user_id:l},{onSuccess:r})};return e.jsx("div",{className:"flex flex-col gap-4",children:e.jsx($,{children:e.jsxs($.Content,{className:"flex flex-col gap-5 p-5",children:[e.jsx("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:h?e.jsxs(e.Fragment,{children:["Edit ",s.kind==="alias"?"alias":"policy"," ",e.jsx("code",{children:s.name}),s.user_id?e.jsxs(e.Fragment,{children:[" ","for user ",e.jsx("code",{children:s.user_id})]}):null]}):"New routing policy"}),e.jsx(U,{error:n.error??o.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[h?e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Policy name"}),e.jsx("code",{className:"text-sm text-[var(--otari-muted)]",children:s.name}),e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"The name and who it applies to are the key and cannot be changed here. Delete and recreate to change either."})]}):e.jsx(L,{label:"Policy name",value:p,onChange:k,placeholder:"fast",isRequired:!0,autoFocus:!0,description:I?e.jsx("span",{className:"text-red-700",children:"A policy name cannot contain “:” or “/”."}):"What callers send as `model`."}),e.jsx(E,{label:"Serves",value:t,onChange:f,isRequired:!0,description:"The model that serves a normal request. Callers never see it."})]}),h?null:e.jsx(ye,{userId:v,onChange:P}),y.length>0?e.jsxs("div",{className:"flex flex-col gap-3 rounded-lg border border-[var(--otari-line)] p-3",children:[e.jsxs("div",{children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Instead, when the budget fills up"}),e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"Checked before the model above. A threshold must be under 100: the budget gate refuses a request before selection once the cap is reached, so a rule at 100 could never fire."})]}),y.map((l,c)=>e.jsxs("div",{className:"flex flex-wrap items-end gap-3",children:[e.jsx(L,{label:"Budget used at least (%)",value:String(l.threshold),onChange:d=>N(m=>m.map((i,g)=>g===c?{...i,threshold:Number(d)||0}:i)),description:l.threshold>=100?e.jsx("span",{className:"text-red-700",children:"Must be under 100."}):void 0}),e.jsx("div",{className:"min-w-56 flex-1",children:e.jsx(E,{label:"Use instead",value:l.target,onChange:d=>N(m=>m.map((i,g)=>g===c?{...i,target:d}:i)),isRequired:!0})}),e.jsx(_,{variant:"ghost",onPress:()=>N(d=>d.filter((m,i)=>i!==c)),children:"Remove"})]},c))]}):null,u.length>0?e.jsxs("div",{className:"flex flex-col gap-3 rounded-lg border border-[var(--otari-line)] p-3",children:[e.jsxs("div",{children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"If that fails, try"}),e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"Tried in order after a retryable failure. Not tried once tokens have started streaming, or after a 400/401/403, which every provider would reject the same way."})]}),u.map((l,c)=>e.jsxs("div",{className:"flex flex-wrap items-end gap-3",children:[e.jsx("div",{className:"min-w-56 flex-1",children:e.jsx(E,{label:`Fallback ${c+1}`,value:l,onChange:d=>C(m=>m.map((i,g)=>g===c?d:i)),isRequired:!0})}),e.jsx(_,{variant:"ghost",onPress:()=>C(d=>d.filter((m,i)=>i!==c)),children:"Remove"})]},c)),e.jsx("div",{children:e.jsx("button",{type:"button",className:"text-sm text-[var(--otari-brand)] hover:underline",onClick:()=>C(l=>[...l,""]),children:"+ Another fallback"})})]}):null,w.length>0?e.jsxs("div",{className:"flex flex-col gap-3 rounded-lg border border-[var(--otari-line)] p-3",children:[e.jsxs("div",{children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Always check"}),e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"Runs on every request through this policy. Callers can add their own guardrails but cannot weaken these."}),x.configured?null:e.jsxs("p",{className:"mt-1 text-xs text-amber-700",children:["No guardrails service is configured, so these cannot run. With `if the service is down` set to block, every request through this policy is refused until one is configured."," ",e.jsx(D,{to:"/tools",className:"underline",children:"Set one up"}),", or remove the guardrail."]})]}),w.map((l,c)=>e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsxs("div",{className:"flex flex-wrap items-end gap-3",children:[e.jsx(L,{label:"Profile",value:l.profile,onChange:d=>S(m=>m.map((i,g)=>g===c?{...i,profile:d}:i)),placeholder:"prompt-injection",isRequired:!0,description:"A profile configured on the guardrails service."}),e.jsx(F,{label:"Mode",value:l.mode,onChange:d=>S(m=>m.map((i,g)=>g===c?{...i,mode:d}:i)),hint:"block rejects a flagged request; monitor records it and serves anyway."}),e.jsx(F,{label:"If the service is down",value:l.on_unavailable??"block",onChange:d=>S(m=>m.map((i,g)=>g===c?{...i,on_unavailable:d}:i)),hint:"block fails closed, so a guardrails outage refuses every request through this policy."}),e.jsx(_,{variant:"ghost",onPress:()=>S(d=>d.filter((m,i)=>i!==c)),children:"Remove"})]}),l.mode==="block"&&(l.on_unavailable??"block")==="block"?e.jsx("div",{className:"text-xs text-amber-700",children:"With both set to block, a guardrails-service outage rejects every request through this policy, ahead of any fallback above."}):null]},c))]}):null,e.jsxs("div",{className:"flex flex-wrap gap-3 text-sm",children:[y.length===0?e.jsx("button",{type:"button",className:"text-[var(--otari-brand)] hover:underline",onClick:()=>N([{threshold:80,target:""}]),children:"+ Tier down when the budget fills up"}):null,u.length===0?e.jsx("button",{type:"button",className:"text-[var(--otari-brand)] hover:underline",onClick:()=>C([""]),children:"+ Add a fallback chain"}):null,w.length===0?e.jsxs("span",{className:"flex flex-wrap items-baseline gap-2",children:[e.jsx("button",{type:"button",disabled:!x.configured,"aria-describedby":x.configured?void 0:"guardrails-unavailable",className:x.configured?"text-[var(--otari-brand)] hover:underline":"cursor-not-allowed text-[var(--otari-muted)] opacity-60",onClick:()=>S([{profile:"",mode:"block",on_unavailable:"block"}]),children:"+ Add guardrails"}),x.configured?null:e.jsxs("span",{id:"guardrails-unavailable",className:"text-xs text-[var(--otari-muted)]",children:["No guardrails service is configured, so there would be nothing to call."," ",e.jsx(D,{to:"/tools",className:"text-[var(--otari-brand)] hover:underline",children:"Set one up in Tools & Guardrails"}),"."]})]}):null]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx(_,{variant:"primary",isDisabled:!T||B||R,onPress:W,children:B?"Saving…":h?"Save":"Create policy"}),e.jsx(_,{variant:"ghost",onPress:r,children:"Cancel"}),e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"In effect for new requests within 30s."}),R?e.jsx("span",{className:"text-xs text-amber-700",children:"An alias holds one target. To add a fallback, a condition, or a guardrail, delete this alias and create a policy with the same name."}):null]})]})})})}function Re(){const s=Q(),a=Y(),r=Z(),n=ee(),[o]=J(),h=o.get("target")??"",[b,x]=j.useState(h!==""),[p,k]=j.useState(null),v=[...(s.data??[]).map(t=>({...t,kind:"policy"})),...(a.data??[]).map(pe)].sort((t,f)=>t.name.localeCompare(f.name)||(t.user_id??"").localeCompare(f.user_id??"")),P=j.useMemo(()=>[{id:"name",header:"Policy",isRowHeader:!0,cell:t=>e.jsx(M,{value:t.name,label:"policy name"})},{id:"serves",header:"Serves",cell:t=>e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-sm text-[var(--otari-ink)]",children:be(t)}),t.is_dynamic?e.jsx(q,{size:"sm",color:"accent",children:"Dynamic"}):null]})},{id:"guards",header:"Guards",cell:t=>{const f=t.spec.guardrails??[];return f.length===0?e.jsx("span",{className:"text-[var(--otari-muted)]",children:"–"}):e.jsx("span",{className:"text-sm text-[var(--otari-ink)]",children:f.map(u=>`${u.profile} (${u.mode})`).join(", ")})}},{id:"scope",header:"Applies to",cell:t=>t.user_id===null?e.jsx("span",{className:"text-[var(--otari-muted)]",children:"Every caller"}):e.jsx(M,{value:t.user_id,label:"user id"})},{id:"source",header:"Source",cell:t=>e.jsxs("div",{className:"flex items-center gap-1",children:[e.jsx(q,{size:"sm",color:t.source==="config"?"default":"accent",children:t.source}),t.kind==="alias"?e.jsx(q,{size:"sm",color:"default",children:"alias"}):null]})},{id:"actions",header:"",cell:t=>t.source==="config"?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"set in config.yml"}):e.jsxs("div",{className:"flex items-center justify-end gap-2",children:[ge(t.spec)?e.jsx(_,{size:"sm",variant:"ghost",onPress:()=>{x(!1),k(t)},children:"Edit"}):e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Uses options this form cannot show yet. Edit it through the API so nothing is lost."}),e.jsx(se,{confirmLabel:"Confirm",isPending:r.isPending||n.isPending,onConfirm:()=>t.kind==="alias"?n.mutate({name:t.name,userId:t.user_id}):r.mutate({name:t.name,userId:t.user_id}),children:"Delete"})]})}],[r,n]);return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(te,{title:"Routing",description:"Named models your callers send as `model`. A policy decides which real model serves each request, what is tried if that fails, and which guardrails always run.",action:b||p!==null?void 0:e.jsx(_,{variant:"primary",onPress:()=>{k(null),x(!0)},children:"New policy"})}),e.jsx(U,{error:s.error??a.error??r.error??n.error}),b?e.jsx(O,{existing:null,initialTarget:h,onClose:()=>x(!1)}):null,p!==null?e.jsx(O,{existing:p,onClose:()=>k(null)}):null,v.length===0&&!s.isLoading&&!a.isLoading&&!b?e.jsx(ae,{title:"No routing policies yet",children:e.jsxs("ol",{className:"flex list-decimal flex-col gap-1 pl-5 text-sm text-[var(--otari-muted)]",children:[e.jsx("li",{children:"Create a policy and point it at the model that should normally serve."}),e.jsx("li",{children:"Add a fallback chain so a provider outage does not become a failed request."}),e.jsx("li",{children:"Have your callers send the policy name as their `model`."})]})}):e.jsx(oe,{ariaLabel:"Routing policies",columns:P,rows:v,getRowKey:fe,isLoading:s.isLoading||a.isLoading,emptyContent:"No routing policies yet."})]})}export{Re as RoutingPage}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as j,u as J,L as D}from"./react-dgEcD0HR.js";import{s as X,t as Q,v as Y,w as Z,x as ee,q as M,y as se,P as te,E as U,z as ae,B as re,D as le,G as ne,u as ie}from"./index-Dit1BUBh.js";import{D as oe}from"./DataTable-BHrpJHmX.js";import{F as L}from"./Field-GEMwIhf7.js";import{C as A,L as de,I as ce,a as ue,b as me,g as q,B as _,d as $}from"./heroui-DhloIxuc.js";import{U as he}from"./UserComboBox-DWvRaj2b.js";const xe=50;function E({label:s,value:a,onChange:r,description:n,placeholder:o="provider:model",autoFocus:h,isRequired:b}){const x=X(),{visible:p,total:k,failed:v}=j.useMemo(()=>{var y;const t=a.trim().toLowerCase(),f=((y=x.data)==null?void 0:y.providers)??[],u=f.flatMap(N=>N.models),C=t?u.filter(N=>N.key.toLowerCase().includes(t)):u;return{visible:C.slice(0,xe),total:C.length,failed:f.filter(N=>!N.ok)}},[x.data,a]),P=x.isLoading?"Loading models from your providers…":v.length>0?`Could not list models for ${v.map(f=>f.provider).join(", ")}. Check that provider's credentials, or type the model key directly.`:k>p.length?`Showing ${p.length} of ${k} matches. Keep typing to narrow them.`:n;return e.jsxs(A.Root,{allowsCustomValue:!0,allowsEmptyCollection:!0,menuTrigger:"input",inputValue:a,onInputChange:r,onSelectionChange:t=>{t!=null&&r(String(t))},isRequired:b,className:"flex max-w-md flex-col gap-1",children:[e.jsx(de,{className:"text-sm font-medium text-[var(--otari-ink)]",children:s}),e.jsxs(A.InputGroup,{children:[e.jsx(ce,{placeholder:o,autoFocus:h}),e.jsx(A.Trigger,{})]}),e.jsx(A.Popover,{children:e.jsx(ue,{items:p,className:"max-h-72 overflow-auto",children:t=>e.jsx(me,{id:t.key,textValue:t.key,children:t.key})})}),P?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:P}):null]})}function pe(s){return{kind:"alias",name:s.name,spec:{select:[{default:s.target}]},source:s.source,user_id:s.user_id,is_dynamic:!1,created_at:s.created_at,updated_at:s.updated_at}}const fe=s=>JSON.stringify([s.kind,s.user_id,s.name]);function ve(){var n;const s=ne(),a=(n=s.data)==null?void 0:n.fields.find(o=>o.key==="guardrails_url"),r=typeof(a==null?void 0:a.value)=="string"?a.value.trim():"";return{configured:s.isLoading||r!=="",isLoading:s.isLoading}}function ge(s){return s.select.every(a=>{var o;if(a.default!==void 0)return a.when===void 0;if(a.router!==void 0)return!1;const r=a.when;if(r===void 0||a.target===void 0)return!1;const n=Object.keys(r);return n.length===1&&n[0]==="budget_used_pct"&&((o=r.budget_used_pct)==null?void 0:o.gte)!==void 0})}function G(s){var a;return((a=s.select.find(r=>r.default!==void 0))==null?void 0:a.default)??""}function je(s){return s.select.filter(a=>{var r,n;return((n=(r=a.when)==null?void 0:r.budget_used_pct)==null?void 0:n.gte)!==void 0&&a.target!==void 0}).map(a=>({threshold:a.when.budget_used_pct.gte,target:a.target}))}function be(s){const a=s.spec.on_failure??[];if(s.is_dynamic){const n=1+a.length;return`Chosen per request · ${n} candidate${n===1?"":"s"}`}const r=G(s.spec);return a.length>0?`${r} +${a.length} on failure`:r}function ye({userId:s,onChange:a}){const r=ie(),n=s!==null,o=(h,b)=>e.jsx("button",{type:"button","aria-pressed":n===h,onClick:()=>a(h?"":null),className:n===h?"rounded-md bg-white px-3 py-1.5 text-sm font-medium text-[var(--otari-ink)] shadow-sm":"rounded-md px-3 py-1.5 text-sm text-[var(--otari-muted)] hover:text-[var(--otari-ink)]",children:b});return e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsxs("div",{children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Applies to"}),e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"A global policy resolves for every caller. A user-scoped one resolves only for that user, and takes precedence over a global policy of the same name."})]}),e.jsxs("div",{className:"flex w-fit items-center gap-1 rounded-lg bg-[var(--otari-bg)] p-1",children:[o(!1,"Every caller"),o(!0,"One user")]}),n?e.jsx(he,{label:"User",value:s??"",onChange:a,users:r.data??[],placeholder:"Pick a user…",description:"Only this user resolves the policy.",unknownHint:e.jsx("span",{className:"text-red-700",children:"No such user. Pick an existing one."})}):null]})}const Ne=["block","monitor"];function F({label:s,hint:a,value:r,onChange:n}){return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:s}),e.jsx("div",{className:"flex w-fit items-center gap-1 rounded-lg bg-[var(--otari-bg)] p-1",children:Ne.map(o=>e.jsx("button",{type:"button","aria-pressed":r===o,onClick:()=>n(o),className:r===o?"rounded-md bg-white px-3 py-1 text-sm font-medium text-[var(--otari-ink)] shadow-sm":"rounded-md px-3 py-1 text-sm text-[var(--otari-muted)] hover:text-[var(--otari-ink)]",children:o},o))}),a===void 0?null:e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:a})]})}function O({existing:s,initialTarget:a="",onClose:r}){const n=re(),o=le(),h=s!==null,b=(s==null?void 0:s.kind)==="alias",x=ve(),[p,k]=j.useState((s==null?void 0:s.name)??""),[v,P]=j.useState((s==null?void 0:s.user_id)??null),[t,f]=j.useState(s?G(s.spec):a),[u,C]=j.useState((s==null?void 0:s.spec.on_failure)??[]),[y,N]=j.useState(s?je(s.spec):[]),[w,S]=j.useState((s==null?void 0:s.spec.guardrails)??[]),I=/[:/]/.test(p),V=v===null||v.trim()!=="",z=y.every(l=>l.target.trim()!==""&&l.threshold>0&&l.threshold<100),H=w.every(l=>l.profile.trim()!==""),T=p.trim()!==""&&t.trim()!==""&&!I&&V&&z&&H&&u.every(l=>l.trim()!==""),K=j.useMemo(()=>({select:[...y.map(l=>({when:{budget_used_pct:{gte:l.threshold}},target:l.target.trim()})),{default:t.trim()}],...u.length>0?{on_failure:u.map(l=>l.trim())}:{},...w.length>0?{guardrails:w}:{}}),[y,t,u,w]),R=b&&(u.length>0||y.length>0||w.length>0),B=n.isPending||o.isPending,W=()=>{if(!T||R)return;const l=v===null?null:v.trim();if(b){o.mutate({name:p.trim(),target:t.trim(),user_id:l},{onSuccess:r});return}n.mutate({name:p.trim(),spec:K,user_id:l},{onSuccess:r})};return e.jsx("div",{className:"flex flex-col gap-4",children:e.jsx($,{children:e.jsxs($.Content,{className:"flex flex-col gap-5 p-5",children:[e.jsx("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:h?e.jsxs(e.Fragment,{children:["Edit ",s.kind==="alias"?"alias":"policy"," ",e.jsx("code",{children:s.name}),s.user_id?e.jsxs(e.Fragment,{children:[" ","for user ",e.jsx("code",{children:s.user_id})]}):null]}):"New routing policy"}),e.jsx(U,{error:n.error??o.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[h?e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Policy name"}),e.jsx("code",{className:"text-sm text-[var(--otari-muted)]",children:s.name}),e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"The name and who it applies to are the key and cannot be changed here. Delete and recreate to change either."})]}):e.jsx(L,{label:"Policy name",value:p,onChange:k,placeholder:"fast",isRequired:!0,autoFocus:!0,description:I?e.jsx("span",{className:"text-red-700",children:"A policy name cannot contain “:” or “/”."}):"What callers send as `model`."}),e.jsx(E,{label:"Serves",value:t,onChange:f,isRequired:!0,description:"The model that serves a normal request. Callers never see it."})]}),h?null:e.jsx(ye,{userId:v,onChange:P}),y.length>0?e.jsxs("div",{className:"flex flex-col gap-3 rounded-lg border border-[var(--otari-line)] p-3",children:[e.jsxs("div",{children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Instead, when the budget fills up"}),e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"Checked before the model above. A threshold must be under 100: the budget gate refuses a request before selection once the cap is reached, so a rule at 100 could never fire."})]}),y.map((l,c)=>e.jsxs("div",{className:"flex flex-wrap items-end gap-3",children:[e.jsx(L,{label:"Budget used at least (%)",value:String(l.threshold),onChange:d=>N(m=>m.map((i,g)=>g===c?{...i,threshold:Number(d)||0}:i)),description:l.threshold>=100?e.jsx("span",{className:"text-red-700",children:"Must be under 100."}):void 0}),e.jsx("div",{className:"min-w-56 flex-1",children:e.jsx(E,{label:"Use instead",value:l.target,onChange:d=>N(m=>m.map((i,g)=>g===c?{...i,target:d}:i)),isRequired:!0})}),e.jsx(_,{variant:"ghost",onPress:()=>N(d=>d.filter((m,i)=>i!==c)),children:"Remove"})]},c))]}):null,u.length>0?e.jsxs("div",{className:"flex flex-col gap-3 rounded-lg border border-[var(--otari-line)] p-3",children:[e.jsxs("div",{children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"If that fails, try"}),e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"Tried in order after a retryable failure. Not tried once tokens have started streaming, or after a 400/401/403, which every provider would reject the same way."})]}),u.map((l,c)=>e.jsxs("div",{className:"flex flex-wrap items-end gap-3",children:[e.jsx("div",{className:"min-w-56 flex-1",children:e.jsx(E,{label:`Fallback ${c+1}`,value:l,onChange:d=>C(m=>m.map((i,g)=>g===c?d:i)),isRequired:!0})}),e.jsx(_,{variant:"ghost",onPress:()=>C(d=>d.filter((m,i)=>i!==c)),children:"Remove"})]},c)),e.jsx("div",{children:e.jsx("button",{type:"button",className:"text-sm text-[var(--otari-brand)] hover:underline",onClick:()=>C(l=>[...l,""]),children:"+ Another fallback"})})]}):null,w.length>0?e.jsxs("div",{className:"flex flex-col gap-3 rounded-lg border border-[var(--otari-line)] p-3",children:[e.jsxs("div",{children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Always check"}),e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"Runs on every request through this policy. Callers can add their own guardrails but cannot weaken these."}),x.configured?null:e.jsxs("p",{className:"mt-1 text-xs text-amber-700",children:["No guardrails service is configured, so these cannot run. With `if the service is down` set to block, every request through this policy is refused until one is configured."," ",e.jsx(D,{to:"/tools",className:"underline",children:"Set one up"}),", or remove the guardrail."]})]}),w.map((l,c)=>e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsxs("div",{className:"flex flex-wrap items-end gap-3",children:[e.jsx(L,{label:"Profile",value:l.profile,onChange:d=>S(m=>m.map((i,g)=>g===c?{...i,profile:d}:i)),placeholder:"prompt-injection",isRequired:!0,description:"A profile configured on the guardrails service."}),e.jsx(F,{label:"Mode",value:l.mode,onChange:d=>S(m=>m.map((i,g)=>g===c?{...i,mode:d}:i)),hint:"block rejects a flagged request; monitor records it and serves anyway."}),e.jsx(F,{label:"If the service is down",value:l.on_unavailable??"block",onChange:d=>S(m=>m.map((i,g)=>g===c?{...i,on_unavailable:d}:i)),hint:"block fails closed, so a guardrails outage refuses every request through this policy."}),e.jsx(_,{variant:"ghost",onPress:()=>S(d=>d.filter((m,i)=>i!==c)),children:"Remove"})]}),l.mode==="block"&&(l.on_unavailable??"block")==="block"?e.jsx("div",{className:"text-xs text-amber-700",children:"With both set to block, a guardrails-service outage rejects every request through this policy, ahead of any fallback above."}):null]},c))]}):null,e.jsxs("div",{className:"flex flex-wrap gap-3 text-sm",children:[y.length===0?e.jsx("button",{type:"button",className:"text-[var(--otari-brand)] hover:underline",onClick:()=>N([{threshold:80,target:""}]),children:"+ Tier down when the budget fills up"}):null,u.length===0?e.jsx("button",{type:"button",className:"text-[var(--otari-brand)] hover:underline",onClick:()=>C([""]),children:"+ Add a fallback chain"}):null,w.length===0?e.jsxs("span",{className:"flex flex-wrap items-baseline gap-2",children:[e.jsx("button",{type:"button",disabled:!x.configured,"aria-describedby":x.configured?void 0:"guardrails-unavailable",className:x.configured?"text-[var(--otari-brand)] hover:underline":"cursor-not-allowed text-[var(--otari-muted)] opacity-60",onClick:()=>S([{profile:"",mode:"block",on_unavailable:"block"}]),children:"+ Add guardrails"}),x.configured?null:e.jsxs("span",{id:"guardrails-unavailable",className:"text-xs text-[var(--otari-muted)]",children:["No guardrails service is configured, so there would be nothing to call."," ",e.jsx(D,{to:"/tools",className:"text-[var(--otari-brand)] hover:underline",children:"Set one up in Tools & Guardrails"}),"."]})]}):null]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx(_,{variant:"primary",isDisabled:!T||B||R,onPress:W,children:B?"Saving…":h?"Save":"Create policy"}),e.jsx(_,{variant:"ghost",onPress:r,children:"Cancel"}),e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"In effect for new requests within 30s."}),R?e.jsx("span",{className:"text-xs text-amber-700",children:"An alias holds one target. To add a fallback, a condition, or a guardrail, delete this alias and create a policy with the same name."}):null]})]})})})}function Re(){const s=Q(),a=Y(),r=Z(),n=ee(),[o]=J(),h=o.get("target")??"",[b,x]=j.useState(h!==""),[p,k]=j.useState(null),v=[...(s.data??[]).map(t=>({...t,kind:"policy"})),...(a.data??[]).map(pe)].sort((t,f)=>t.name.localeCompare(f.name)||(t.user_id??"").localeCompare(f.user_id??"")),P=j.useMemo(()=>[{id:"name",header:"Policy",isRowHeader:!0,cell:t=>e.jsx(M,{value:t.name,label:"policy name"})},{id:"serves",header:"Serves",cell:t=>e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-sm text-[var(--otari-ink)]",children:be(t)}),t.is_dynamic?e.jsx(q,{size:"sm",color:"accent",children:"Dynamic"}):null]})},{id:"guards",header:"Guards",cell:t=>{const f=t.spec.guardrails??[];return f.length===0?e.jsx("span",{className:"text-[var(--otari-muted)]",children:"–"}):e.jsx("span",{className:"text-sm text-[var(--otari-ink)]",children:f.map(u=>`${u.profile} (${u.mode})`).join(", ")})}},{id:"scope",header:"Applies to",cell:t=>t.user_id===null?e.jsx("span",{className:"text-[var(--otari-muted)]",children:"Every caller"}):e.jsx(M,{value:t.user_id,label:"user id"})},{id:"source",header:"Source",cell:t=>e.jsxs("div",{className:"flex items-center gap-1",children:[e.jsx(q,{size:"sm",color:t.source==="config"?"default":"accent",children:t.source}),t.kind==="alias"?e.jsx(q,{size:"sm",color:"default",children:"alias"}):null]})},{id:"actions",header:"",cell:t=>t.source==="config"?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"set in config.yml"}):e.jsxs("div",{className:"flex items-center justify-end gap-2",children:[ge(t.spec)?e.jsx(_,{size:"sm",variant:"ghost",onPress:()=>{x(!1),k(t)},children:"Edit"}):e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Uses options this form cannot show yet. Edit it through the API so nothing is lost."}),e.jsx(se,{confirmLabel:"Confirm",isPending:r.isPending||n.isPending,onConfirm:()=>t.kind==="alias"?n.mutate({name:t.name,userId:t.user_id}):r.mutate({name:t.name,userId:t.user_id}),children:"Delete"})]})}],[r,n]);return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(te,{title:"Routing",description:"Named models your callers send as `model`. A policy decides which real model serves each request, what is tried if that fails, and which guardrails always run.",action:b||p!==null?void 0:e.jsx(_,{variant:"primary",onPress:()=>{k(null),x(!0)},children:"New policy"})}),e.jsx(U,{error:s.error??a.error??r.error??n.error}),b?e.jsx(O,{existing:null,initialTarget:h,onClose:()=>x(!1)}):null,p!==null?e.jsx(O,{existing:p,onClose:()=>k(null)}):null,v.length===0&&!s.isLoading&&!a.isLoading&&!b?e.jsx(ae,{title:"No routing policies yet",children:e.jsxs("ol",{className:"flex list-decimal flex-col gap-1 pl-5 text-sm text-[var(--otari-muted)]",children:[e.jsx("li",{children:"Create a policy and point it at the model that should normally serve."}),e.jsx("li",{children:"Add a fallback chain so a provider outage does not become a failed request."}),e.jsx("li",{children:"Have your callers send the policy name as their `model`."})]})}):e.jsx(oe,{ariaLabel:"Routing policies",columns:P,rows:v,getRowKey:fe,isLoading:s.isLoading||a.isLoading,emptyContent:"No routing policies yet."})]})}export{Re as RoutingPage}; diff --git a/src/gateway/static/dashboard/assets/SettingsPage-C2Hp1OPt.js b/src/gateway/static/dashboard/assets/SettingsPage-CLw9HtK0.js similarity index 99% rename from src/gateway/static/dashboard/assets/SettingsPage-C2Hp1OPt.js rename to src/gateway/static/dashboard/assets/SettingsPage-CLw9HtK0.js index c3dd0c662..06c5f15c8 100644 --- a/src/gateway/static/dashboard/assets/SettingsPage-C2Hp1OPt.js +++ b/src/gateway/static/dashboard/assets/SettingsPage-CLw9HtK0.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as u}from"./react-dgEcD0HR.js";import{a0 as R,ag as C,P,E as y,a3 as E,an as T,ao as _,ap as D,F as A,aq as O,ad as F,ar as K,M as w}from"./index-D-R1nuKP.js";import{d as v,B as h,A as d,h as I,I as M}from"./heroui-DhloIxuc.js";function b(t,r){return{[t]:r}}function z(t,r){let s=0;for(const a of r)if(a===t[s]&&(s+=1),s===t.length)return!0;return t.length===0}function L(t,r){const s=r.trim().toLowerCase();if(s==="")return!0;const a=`${t.key} ${t.description??""} ${t.group}`.toLowerCase(),n=t.key.toLowerCase().replace(/[^a-z0-9]/g,"");return s.split(/\s+/).every(i=>a.includes(i)||z(i,n))}function B({checked:t,onChange:r,label:s,disabled:a}){return e.jsx("button",{type:"button",role:"switch","aria-checked":t,"aria-label":s,disabled:a,onClick:()=>r(!t),className:`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors disabled:opacity-50 ${t?"bg-[var(--otari-brand)]":"bg-[var(--otari-line)]"}`,children:e.jsx("span",{className:`inline-block h-5 w-5 transform rounded-full bg-white shadow transition-transform ${t?"translate-x-5":"translate-x-0.5"}`})})}function $({field:t,onSave:r,disabled:s}){const a=typeof t.value=="number"?t.value:0,[n,i]=u.useState(String(a)),o=t.type==="float";u.useEffect(()=>{i(String(a))},[a]);const c=Number(n),p=n.trim()!==""&&Number.isFinite(c)&&(o||Number.isInteger(c)),m=t.minimum??void 0,x=t.exclusive_minimum??void 0,g=x!==void 0?c>x:m!==void 0?c>=m:c>=0,l=p&&g&&c!==a;return e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(M,{type:"number",min:"0",step:o?"any":"1",inputMode:o?"decimal":"numeric","aria-label":t.key,value:n,disabled:s,onChange:f=>i(f.target.value),className:"w-28 rounded-md border border-[var(--otari-line)] bg-white px-2 py-1 text-right text-sm tabular-nums focus:border-[var(--otari-brand)] focus:outline-none disabled:opacity-50"}),e.jsx(h,{size:"sm",variant:"primary","aria-label":`Save ${t.key}`,isDisabled:s||!l,onPress:()=>r(c),children:"Save"})]})}function H({field:t,onSave:r,disabled:s}){const a=typeof t.value=="string"?t.value:"",[n,i]=u.useState(a);u.useEffect(()=>{i(a)},[a]);const o=n!==a;return e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"text","aria-label":t.key,value:n,disabled:s,placeholder:"unset",onChange:c=>i(c.target.value),className:"w-56 rounded-md border border-[var(--otari-line)] bg-white px-2 py-1 text-sm focus:border-[var(--otari-brand)] focus:outline-none disabled:opacity-50"}),e.jsx(h,{size:"sm",variant:"primary","aria-label":`Save ${t.key}`,isDisabled:s||!o,onPress:()=>r(n.trim()===""?null:n),children:"Save"})]})}function Y(t){const{value:r}=t;return r==null?"unset":typeof r=="boolean"?r?"on":"off":Array.isArray(r)?r.length?r.join(", "):"none":String(r)}function q({field:t,patch:r,disabled:s}){return t.settable?t.type==="bool"?e.jsx(B,{checked:t.value===!0,onChange:a=>r(b(t.key,a)),label:t.key,disabled:s}):t.options&&t.options.length>0?e.jsx(A,{ariaLabel:t.key,value:String(t.value??""),onChange:a=>r(b(t.key,a)),options:t.options.map(a=>({value:a,label:a}))}):t.type==="int"||t.type==="float"?e.jsx($,{field:t,onSave:a=>r(b(t.key,a)),disabled:s}):e.jsx(H,{field:t,onSave:a=>r(b(t.key,a)),disabled:s}):e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-sm tabular-nums text-[var(--otari-ink)]",children:Y(t)}),e.jsx("span",{className:"rounded-full border border-[var(--otari-line)] px-2 py-0.5 text-xs text-[var(--otari-muted)]",children:"startup-only"})]})}function U({field:t,patch:r,disabled:s}){return e.jsxs("div",{className:"flex items-start justify-between gap-6 py-4",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("code",{className:"text-sm font-medium text-[var(--otari-ink)]",children:t.key}),t.description?e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:t.description}):null]}),e.jsx("div",{className:"shrink-0 pt-0.5",children:e.jsx(q,{field:t,patch:r,disabled:s})})]})}function V({value:t,fieldRef:r}){const s=u.useRef(null),a=r??s,[n,i]=u.useState(!1),[o,c]=u.useState(!1),p=async()=>{var m,x,g;(m=a.current)==null||m.focus(),(x=a.current)==null||x.select();try{if((g=navigator.clipboard)!=null&&g.writeText){await navigator.clipboard.writeText(t),i(!0),c(!1),window.setTimeout(()=>i(!1),2e3);return}}catch{}c(!0)};return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-xs font-medium text-[var(--otari-muted)]",children:"New master key"}),e.jsx(h,{size:"sm",variant:"outline",onPress:p,children:n?"Copied":"Copy"})]}),e.jsx("input",{ref:a,readOnly:!0,value:t,onFocus:m=>m.currentTarget.select(),autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",spellCheck:!1,"data-1p-ignore":!0,"data-lpignore":"true"}),e.jsx("span",{"aria-live":"polite",className:"text-xs text-[var(--otari-brand-dark)]",children:n?"Copied to clipboard.":""}),o?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Selected. Press Ctrl/Cmd-C to copy."}):null]})}function G({masterKey:t,error:r,isPending:s,onRegenerate:a,onClose:n}){const i=u.useRef(null);return u.useEffect(()=>{var o,c;t!==void 0&&((o=i.current)==null||o.focus(),(c=i.current)==null||c.select())},[t]),e.jsx(d.Backdrop,{children:e.jsx(d.Container,{placement:"center",size:"lg",children:e.jsxs(d.Dialog,{children:[e.jsx(d.Header,{children:e.jsx(d.Heading,{children:t!==void 0?"Master key regenerated":"Regenerate master key?"})}),e.jsx(d.Body,{className:"flex flex-col gap-4",children:t!==void 0?e.jsxs(e.Fragment,{children:[e.jsx(w,{tone:"warning",children:"Copy this key now. It is shown once and cannot be retrieved again after you close this dialog."}),e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:"The previous master key has stopped working. This browser tab now uses the new key."}),e.jsx(V,{value:t,fieldRef:i})]}):e.jsxs(e.Fragment,{children:[e.jsx(w,{tone:"warning",children:"This immediately invalidates the current dashboard master key. Other signed-in dashboard sessions will need the new key to continue."}),e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:"The replacement key will be shown once. Save it before closing the next screen."}),e.jsx(y,{error:r})]})}),e.jsx(d.Footer,{children:t!==void 0?e.jsx(h,{variant:"primary",onPress:n,children:"I’ve saved this key"}):e.jsxs(e.Fragment,{children:[e.jsx(h,{variant:"ghost",isDisabled:s,onPress:n,children:"Cancel"}),e.jsx(h,{variant:"danger",isPending:s,onPress:a,children:"Regenerate key"})]})})]})})})}function X({source:t}){const r=O(),[s,a]=u.useState(!1),[n,i]=u.useState(),o=t==="generated",c=()=>r.mutate(void 0,{onSuccess:x=>{i(x.master_key)}}),p=()=>{a(!1),i(void 0),r.reset()},m=x=>{x?(r.reset(),a(!0)):n===void 0&&p()};return e.jsx("div",{className:"flex flex-col gap-4 py-4",children:e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("code",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"master_key"}),e.jsx("p",{className:"mt-1 max-w-3xl text-sm text-[var(--otari-muted)]",children:o?"This gateway uses its first-run generated dashboard key. Regeneration invalidates the current key immediately.":"This gateway uses a key managed through OTARI_MASTER_KEY or config.yml. Rotate it in configuration, then restart the gateway."})]}),e.jsxs(d,{isOpen:s,onOpenChange:m,children:[o?e.jsx(d.Trigger,{className:I({size:"sm",variant:"danger-soft"}),children:"Regenerate"}):e.jsx(h,{size:"sm",variant:"danger-soft",isDisabled:!0,children:"Managed in configuration"}),s?e.jsx(G,{masterKey:n,error:r.error,isPending:r.isPending,onRegenerate:c,onClose:p}):null]})]})})}function J(){var o;const t=F(),r=K(),s=r.data,a=((o=t.data)==null?void 0:o.length)??0,n=(t.data??[]).filter(c=>!c.decryptable).length,i=a>0;return e.jsxs("div",{className:"flex flex-col gap-4 py-4",children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("code",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"OTARI_SECRET_KEY"}),e.jsxs("p",{className:"mt-1 max-w-3xl text-sm text-[var(--otari-muted)]",children:["Generate a new key with ",e.jsx("code",{children:"uv run otari gen-secret-key"}),", then restart with"," ",e.jsx("code",{children:"OTARI_SECRET_KEY=,"}),". Re-encrypt the stored provider keys, then restart with ",e.jsx("code",{children:"OTARI_SECRET_KEY="})," once none are unreadable."]})]}),e.jsx("div",{className:"shrink-0",children:e.jsx(h,{size:"sm",variant:"outline",isDisabled:!i||r.isPending,onPress:()=>r.mutate(),children:r.isPending?"Re-encrypting…":"Re-encrypt provider keys"})})]}),e.jsx(y,{error:t.error??r.error}),n>0?e.jsxs(w,{tone:"warning",children:[n," stored provider key",n===1?"":"s"," cannot be decrypted with the current"," ",e.jsx("code",{children:"OTARI_SECRET_KEY"}),". Restore the old secret key and re-encrypt, or edit each affected provider and replace its key."]}):null,s?e.jsxs("p",{className:"text-sm text-[var(--otari-muted)]",role:"status","aria-live":"polite",children:["Re-encrypted ",s.reencrypted," provider key",s.reencrypted===1?"":"s",".",s.unreadable>0?` ${s.unreadable} still need replacement.`:" All decryptable stored keys now use the primary secret key."]}):!t.isLoading&&!i?e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:"No stored provider keys need re-encryption."}):null]})}function Q({masterKeySource:t}){return e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsxs("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Credential security ",e.jsx("span",{className:"font-normal text-[var(--otari-muted)]",children:"(2)"})]}),e.jsx(v,{children:e.jsxs(v.Content,{className:"flex flex-col divide-y divide-[var(--otari-line)] px-5 py-1",children:[e.jsx(X,{source:t}),e.jsx(J,{})]})})]})}function W({preview:t,error:r,isPending:s,onAccept:a,onReject:n}){return e.jsx(d.Backdrop,{children:e.jsx(d.Container,{placement:"center",size:"lg",children:e.jsxs(d.Dialog,{children:[e.jsx(d.Header,{children:e.jsx(d.Heading,{children:"Review default price updates"})}),e.jsxs(d.Body,{className:"flex flex-col gap-4",children:[e.jsxs("p",{className:"text-sm text-[var(--otari-muted)]",children:[t.added_count," added, ",t.changed_count," changed, and ",t.removed_count," removed upstream model prices. The accepted catalog is saved in the database with source ",e.jsx("code",{children:"genai-prices"})," and reloads after a restart. Your ",t.protected_model_count," custom model price",t.protected_model_count===1?"":"s"," remain unchanged."]}),t.changes.length>0?e.jsx("ul",{className:"max-h-60 list-disc overflow-auto pl-5 text-sm text-[var(--otari-ink)]",children:t.changes.map(i=>e.jsxs("li",{children:[i.model_key,": ",i.change]},i.model_key))}):null,t.changes_truncated?e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"Only the first 100 changes are shown."}):null,e.jsx(y,{error:r})]}),e.jsxs(d.Footer,{children:[e.jsx(h,{variant:"ghost",isDisabled:s,onPress:n,children:"Reject changes"}),e.jsx(h,{variant:"primary",isPending:s,onPress:a,children:"Accept price updates"})]})]})})})}function Z(){const t=T(),r=_(),s=D(),a=t.data,n=r.isPending||s.isPending,i=()=>{a===void 0||n||s.mutate(void 0,{onSuccess:t.reset})};return e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Default pricing catalog"}),e.jsx(v,{children:e.jsxs(v.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"genai-prices defaults"}),e.jsxs("p",{className:"mt-1 max-w-3xl text-sm text-[var(--otari-muted)]",children:["Fetch the latest upstream catalog, review the proposed change summary, then accept or reject it. Accepted data is stored as ",e.jsx("code",{children:"genai-prices"}),"; custom prices remain separate and always take precedence."]})]}),e.jsx(h,{size:"sm",variant:"outline",isDisabled:t.isPending||n,onPress:()=>t.mutate(),children:t.isPending?"Checking prices…":"Check for price updates"})]}),e.jsx(y,{error:t.error})]})}),e.jsxs(d,{isOpen:a!==void 0,onOpenChange:o=>o?void 0:i(),children:[e.jsx(d.Trigger,{className:"hidden",children:"Review price updates"}),a?e.jsx(W,{preview:a,error:r.error??s.error,isPending:n,onAccept:()=>r.mutate(void 0,{onSuccess:t.reset}),onReject:i}):null]})]})}function ee(t){const r=[],s=new Map;for(const a of t){let n=s.get(a.group);n||(n={name:a.group,fields:[]},s.set(a.group,n),r.push(n)),n.fields.push(a)}return r}function ne(){const t=R(),r=C(),s=t.data,a=r.isPending,[n,i]=u.useState(""),[o,c]=u.useState(!1),p=u.useRef(null);u.useEffect(()=>{function l(f){var N;const j=f.target,S=j&&(j.tagName==="INPUT"||j.tagName==="TEXTAREA"||j.tagName==="SELECT");f.key==="/"&&!S&&(f.preventDefault(),(N=p.current)==null||N.focus())}return window.addEventListener("keydown",l),()=>window.removeEventListener("keydown",l)},[]);const m=l=>r.mutate(l),x=(s==null?void 0:s.config)??[],g=x.filter(l=>(o?l.settable:!0)&&L(l,n)),k=ee(g);return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(P,{title:"Settings",description:"Every effective gateway setting. Settable fields apply immediately and persist across restarts; startup-only fields are shown for reference and change only via config.yml or environment variables (then a restart)."}),e.jsx(y,{error:t.error??r.error}),e.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[e.jsx("input",{ref:p,type:"search","aria-label":"Search settings",placeholder:"Search settings (press / to focus)…",value:n,onChange:l=>i(l.target.value),onKeyDown:l=>{l.key==="Escape"&&i("")},className:"min-w-0 flex-1 rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none"}),e.jsxs("label",{className:"flex items-center gap-2 text-sm text-[var(--otari-muted)]",children:[e.jsx("input",{type:"checkbox",checked:o,onChange:l=>c(l.target.checked),className:"h-4 w-4 accent-[var(--otari-brand)]"}),"Settable only"]})]}),s?e.jsxs("p",{className:"text-xs text-[var(--otari-muted)]",children:["Showing ",g.length," of ",x.length," settings"]}):null,s&&g.length===0?e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:"No settings match your search."}):null,t.isLoading?e.jsx(E,{}):null,k.map(l=>e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsxs("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:[l.name," ",e.jsxs("span",{className:"font-normal text-[var(--otari-muted)]",children:["(",l.fields.length,")"]})]}),e.jsx(v,{children:e.jsx(v.Content,{className:"flex flex-col divide-y divide-[var(--otari-line)] px-5 py-1",children:l.fields.map(f=>e.jsx(U,{field:f,patch:m,disabled:!s||a},f.key))})})]},l.name)),s?e.jsx(Z,{}):null,s?e.jsx(Q,{masterKeySource:s.master_key_source}):null,s?e.jsxs("p",{className:"text-xs text-[var(--otari-muted)]",children:["Mode: ",s.mode," · Version ",s.version,s.require_pricing?" · require_pricing on":""]}):null]})}export{ne as SettingsPage,L as fieldMatches}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as u}from"./react-dgEcD0HR.js";import{a0 as R,ag as C,P,E as y,a3 as E,an as T,ao as _,ap as D,F as A,aq as O,ad as F,ar as K,M as w}from"./index-Dit1BUBh.js";import{d as v,B as h,A as d,h as I,I as M}from"./heroui-DhloIxuc.js";function b(t,r){return{[t]:r}}function z(t,r){let s=0;for(const a of r)if(a===t[s]&&(s+=1),s===t.length)return!0;return t.length===0}function L(t,r){const s=r.trim().toLowerCase();if(s==="")return!0;const a=`${t.key} ${t.description??""} ${t.group}`.toLowerCase(),n=t.key.toLowerCase().replace(/[^a-z0-9]/g,"");return s.split(/\s+/).every(i=>a.includes(i)||z(i,n))}function B({checked:t,onChange:r,label:s,disabled:a}){return e.jsx("button",{type:"button",role:"switch","aria-checked":t,"aria-label":s,disabled:a,onClick:()=>r(!t),className:`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors disabled:opacity-50 ${t?"bg-[var(--otari-brand)]":"bg-[var(--otari-line)]"}`,children:e.jsx("span",{className:`inline-block h-5 w-5 transform rounded-full bg-white shadow transition-transform ${t?"translate-x-5":"translate-x-0.5"}`})})}function $({field:t,onSave:r,disabled:s}){const a=typeof t.value=="number"?t.value:0,[n,i]=u.useState(String(a)),o=t.type==="float";u.useEffect(()=>{i(String(a))},[a]);const c=Number(n),p=n.trim()!==""&&Number.isFinite(c)&&(o||Number.isInteger(c)),m=t.minimum??void 0,x=t.exclusive_minimum??void 0,g=x!==void 0?c>x:m!==void 0?c>=m:c>=0,l=p&&g&&c!==a;return e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(M,{type:"number",min:"0",step:o?"any":"1",inputMode:o?"decimal":"numeric","aria-label":t.key,value:n,disabled:s,onChange:f=>i(f.target.value),className:"w-28 rounded-md border border-[var(--otari-line)] bg-white px-2 py-1 text-right text-sm tabular-nums focus:border-[var(--otari-brand)] focus:outline-none disabled:opacity-50"}),e.jsx(h,{size:"sm",variant:"primary","aria-label":`Save ${t.key}`,isDisabled:s||!l,onPress:()=>r(c),children:"Save"})]})}function H({field:t,onSave:r,disabled:s}){const a=typeof t.value=="string"?t.value:"",[n,i]=u.useState(a);u.useEffect(()=>{i(a)},[a]);const o=n!==a;return e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"text","aria-label":t.key,value:n,disabled:s,placeholder:"unset",onChange:c=>i(c.target.value),className:"w-56 rounded-md border border-[var(--otari-line)] bg-white px-2 py-1 text-sm focus:border-[var(--otari-brand)] focus:outline-none disabled:opacity-50"}),e.jsx(h,{size:"sm",variant:"primary","aria-label":`Save ${t.key}`,isDisabled:s||!o,onPress:()=>r(n.trim()===""?null:n),children:"Save"})]})}function Y(t){const{value:r}=t;return r==null?"unset":typeof r=="boolean"?r?"on":"off":Array.isArray(r)?r.length?r.join(", "):"none":String(r)}function q({field:t,patch:r,disabled:s}){return t.settable?t.type==="bool"?e.jsx(B,{checked:t.value===!0,onChange:a=>r(b(t.key,a)),label:t.key,disabled:s}):t.options&&t.options.length>0?e.jsx(A,{ariaLabel:t.key,value:String(t.value??""),onChange:a=>r(b(t.key,a)),options:t.options.map(a=>({value:a,label:a}))}):t.type==="int"||t.type==="float"?e.jsx($,{field:t,onSave:a=>r(b(t.key,a)),disabled:s}):e.jsx(H,{field:t,onSave:a=>r(b(t.key,a)),disabled:s}):e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-sm tabular-nums text-[var(--otari-ink)]",children:Y(t)}),e.jsx("span",{className:"rounded-full border border-[var(--otari-line)] px-2 py-0.5 text-xs text-[var(--otari-muted)]",children:"startup-only"})]})}function U({field:t,patch:r,disabled:s}){return e.jsxs("div",{className:"flex items-start justify-between gap-6 py-4",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("code",{className:"text-sm font-medium text-[var(--otari-ink)]",children:t.key}),t.description?e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:t.description}):null]}),e.jsx("div",{className:"shrink-0 pt-0.5",children:e.jsx(q,{field:t,patch:r,disabled:s})})]})}function V({value:t,fieldRef:r}){const s=u.useRef(null),a=r??s,[n,i]=u.useState(!1),[o,c]=u.useState(!1),p=async()=>{var m,x,g;(m=a.current)==null||m.focus(),(x=a.current)==null||x.select();try{if((g=navigator.clipboard)!=null&&g.writeText){await navigator.clipboard.writeText(t),i(!0),c(!1),window.setTimeout(()=>i(!1),2e3);return}}catch{}c(!0)};return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-xs font-medium text-[var(--otari-muted)]",children:"New master key"}),e.jsx(h,{size:"sm",variant:"outline",onPress:p,children:n?"Copied":"Copy"})]}),e.jsx("input",{ref:a,readOnly:!0,value:t,onFocus:m=>m.currentTarget.select(),autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",spellCheck:!1,"data-1p-ignore":!0,"data-lpignore":"true"}),e.jsx("span",{"aria-live":"polite",className:"text-xs text-[var(--otari-brand-dark)]",children:n?"Copied to clipboard.":""}),o?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"Selected. Press Ctrl/Cmd-C to copy."}):null]})}function G({masterKey:t,error:r,isPending:s,onRegenerate:a,onClose:n}){const i=u.useRef(null);return u.useEffect(()=>{var o,c;t!==void 0&&((o=i.current)==null||o.focus(),(c=i.current)==null||c.select())},[t]),e.jsx(d.Backdrop,{children:e.jsx(d.Container,{placement:"center",size:"lg",children:e.jsxs(d.Dialog,{children:[e.jsx(d.Header,{children:e.jsx(d.Heading,{children:t!==void 0?"Master key regenerated":"Regenerate master key?"})}),e.jsx(d.Body,{className:"flex flex-col gap-4",children:t!==void 0?e.jsxs(e.Fragment,{children:[e.jsx(w,{tone:"warning",children:"Copy this key now. It is shown once and cannot be retrieved again after you close this dialog."}),e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:"The previous master key has stopped working. This browser tab now uses the new key."}),e.jsx(V,{value:t,fieldRef:i})]}):e.jsxs(e.Fragment,{children:[e.jsx(w,{tone:"warning",children:"This immediately invalidates the current dashboard master key. Other signed-in dashboard sessions will need the new key to continue."}),e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:"The replacement key will be shown once. Save it before closing the next screen."}),e.jsx(y,{error:r})]})}),e.jsx(d.Footer,{children:t!==void 0?e.jsx(h,{variant:"primary",onPress:n,children:"I’ve saved this key"}):e.jsxs(e.Fragment,{children:[e.jsx(h,{variant:"ghost",isDisabled:s,onPress:n,children:"Cancel"}),e.jsx(h,{variant:"danger",isPending:s,onPress:a,children:"Regenerate key"})]})})]})})})}function X({source:t}){const r=O(),[s,a]=u.useState(!1),[n,i]=u.useState(),o=t==="generated",c=()=>r.mutate(void 0,{onSuccess:x=>{i(x.master_key)}}),p=()=>{a(!1),i(void 0),r.reset()},m=x=>{x?(r.reset(),a(!0)):n===void 0&&p()};return e.jsx("div",{className:"flex flex-col gap-4 py-4",children:e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("code",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"master_key"}),e.jsx("p",{className:"mt-1 max-w-3xl text-sm text-[var(--otari-muted)]",children:o?"This gateway uses its first-run generated dashboard key. Regeneration invalidates the current key immediately.":"This gateway uses a key managed through OTARI_MASTER_KEY or config.yml. Rotate it in configuration, then restart the gateway."})]}),e.jsxs(d,{isOpen:s,onOpenChange:m,children:[o?e.jsx(d.Trigger,{className:I({size:"sm",variant:"danger-soft"}),children:"Regenerate"}):e.jsx(h,{size:"sm",variant:"danger-soft",isDisabled:!0,children:"Managed in configuration"}),s?e.jsx(G,{masterKey:n,error:r.error,isPending:r.isPending,onRegenerate:c,onClose:p}):null]})]})})}function J(){var o;const t=F(),r=K(),s=r.data,a=((o=t.data)==null?void 0:o.length)??0,n=(t.data??[]).filter(c=>!c.decryptable).length,i=a>0;return e.jsxs("div",{className:"flex flex-col gap-4 py-4",children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("code",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"OTARI_SECRET_KEY"}),e.jsxs("p",{className:"mt-1 max-w-3xl text-sm text-[var(--otari-muted)]",children:["Generate a new key with ",e.jsx("code",{children:"uv run otari gen-secret-key"}),", then restart with"," ",e.jsx("code",{children:"OTARI_SECRET_KEY=,"}),". Re-encrypt the stored provider keys, then restart with ",e.jsx("code",{children:"OTARI_SECRET_KEY="})," once none are unreadable."]})]}),e.jsx("div",{className:"shrink-0",children:e.jsx(h,{size:"sm",variant:"outline",isDisabled:!i||r.isPending,onPress:()=>r.mutate(),children:r.isPending?"Re-encrypting…":"Re-encrypt provider keys"})})]}),e.jsx(y,{error:t.error??r.error}),n>0?e.jsxs(w,{tone:"warning",children:[n," stored provider key",n===1?"":"s"," cannot be decrypted with the current"," ",e.jsx("code",{children:"OTARI_SECRET_KEY"}),". Restore the old secret key and re-encrypt, or edit each affected provider and replace its key."]}):null,s?e.jsxs("p",{className:"text-sm text-[var(--otari-muted)]",role:"status","aria-live":"polite",children:["Re-encrypted ",s.reencrypted," provider key",s.reencrypted===1?"":"s",".",s.unreadable>0?` ${s.unreadable} still need replacement.`:" All decryptable stored keys now use the primary secret key."]}):!t.isLoading&&!i?e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:"No stored provider keys need re-encryption."}):null]})}function Q({masterKeySource:t}){return e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsxs("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Credential security ",e.jsx("span",{className:"font-normal text-[var(--otari-muted)]",children:"(2)"})]}),e.jsx(v,{children:e.jsxs(v.Content,{className:"flex flex-col divide-y divide-[var(--otari-line)] px-5 py-1",children:[e.jsx(X,{source:t}),e.jsx(J,{})]})})]})}function W({preview:t,error:r,isPending:s,onAccept:a,onReject:n}){return e.jsx(d.Backdrop,{children:e.jsx(d.Container,{placement:"center",size:"lg",children:e.jsxs(d.Dialog,{children:[e.jsx(d.Header,{children:e.jsx(d.Heading,{children:"Review default price updates"})}),e.jsxs(d.Body,{className:"flex flex-col gap-4",children:[e.jsxs("p",{className:"text-sm text-[var(--otari-muted)]",children:[t.added_count," added, ",t.changed_count," changed, and ",t.removed_count," removed upstream model prices. The accepted catalog is saved in the database with source ",e.jsx("code",{children:"genai-prices"})," and reloads after a restart. Your ",t.protected_model_count," custom model price",t.protected_model_count===1?"":"s"," remain unchanged."]}),t.changes.length>0?e.jsx("ul",{className:"max-h-60 list-disc overflow-auto pl-5 text-sm text-[var(--otari-ink)]",children:t.changes.map(i=>e.jsxs("li",{children:[i.model_key,": ",i.change]},i.model_key))}):null,t.changes_truncated?e.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"Only the first 100 changes are shown."}):null,e.jsx(y,{error:r})]}),e.jsxs(d.Footer,{children:[e.jsx(h,{variant:"ghost",isDisabled:s,onPress:n,children:"Reject changes"}),e.jsx(h,{variant:"primary",isPending:s,onPress:a,children:"Accept price updates"})]})]})})})}function Z(){const t=T(),r=_(),s=D(),a=t.data,n=r.isPending||s.isPending,i=()=>{a===void 0||n||s.mutate(void 0,{onSuccess:t.reset})};return e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Default pricing catalog"}),e.jsx(v,{children:e.jsxs(v.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"genai-prices defaults"}),e.jsxs("p",{className:"mt-1 max-w-3xl text-sm text-[var(--otari-muted)]",children:["Fetch the latest upstream catalog, review the proposed change summary, then accept or reject it. Accepted data is stored as ",e.jsx("code",{children:"genai-prices"}),"; custom prices remain separate and always take precedence."]})]}),e.jsx(h,{size:"sm",variant:"outline",isDisabled:t.isPending||n,onPress:()=>t.mutate(),children:t.isPending?"Checking prices…":"Check for price updates"})]}),e.jsx(y,{error:t.error})]})}),e.jsxs(d,{isOpen:a!==void 0,onOpenChange:o=>o?void 0:i(),children:[e.jsx(d.Trigger,{className:"hidden",children:"Review price updates"}),a?e.jsx(W,{preview:a,error:r.error??s.error,isPending:n,onAccept:()=>r.mutate(void 0,{onSuccess:t.reset}),onReject:i}):null]})]})}function ee(t){const r=[],s=new Map;for(const a of t){let n=s.get(a.group);n||(n={name:a.group,fields:[]},s.set(a.group,n),r.push(n)),n.fields.push(a)}return r}function ne(){const t=R(),r=C(),s=t.data,a=r.isPending,[n,i]=u.useState(""),[o,c]=u.useState(!1),p=u.useRef(null);u.useEffect(()=>{function l(f){var N;const j=f.target,S=j&&(j.tagName==="INPUT"||j.tagName==="TEXTAREA"||j.tagName==="SELECT");f.key==="/"&&!S&&(f.preventDefault(),(N=p.current)==null||N.focus())}return window.addEventListener("keydown",l),()=>window.removeEventListener("keydown",l)},[]);const m=l=>r.mutate(l),x=(s==null?void 0:s.config)??[],g=x.filter(l=>(o?l.settable:!0)&&L(l,n)),k=ee(g);return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(P,{title:"Settings",description:"Every effective gateway setting. Settable fields apply immediately and persist across restarts; startup-only fields are shown for reference and change only via config.yml or environment variables (then a restart)."}),e.jsx(y,{error:t.error??r.error}),e.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[e.jsx("input",{ref:p,type:"search","aria-label":"Search settings",placeholder:"Search settings (press / to focus)…",value:n,onChange:l=>i(l.target.value),onKeyDown:l=>{l.key==="Escape"&&i("")},className:"min-w-0 flex-1 rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none"}),e.jsxs("label",{className:"flex items-center gap-2 text-sm text-[var(--otari-muted)]",children:[e.jsx("input",{type:"checkbox",checked:o,onChange:l=>c(l.target.checked),className:"h-4 w-4 accent-[var(--otari-brand)]"}),"Settable only"]})]}),s?e.jsxs("p",{className:"text-xs text-[var(--otari-muted)]",children:["Showing ",g.length," of ",x.length," settings"]}):null,s&&g.length===0?e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:"No settings match your search."}):null,t.isLoading?e.jsx(E,{}):null,k.map(l=>e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsxs("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:[l.name," ",e.jsxs("span",{className:"font-normal text-[var(--otari-muted)]",children:["(",l.fields.length,")"]})]}),e.jsx(v,{children:e.jsx(v.Content,{className:"flex flex-col divide-y divide-[var(--otari-line)] px-5 py-1",children:l.fields.map(f=>e.jsx(U,{field:f,patch:m,disabled:!s||a},f.key))})})]},l.name)),s?e.jsx(Z,{}):null,s?e.jsx(Q,{masterKeySource:s.master_key_source}):null,s?e.jsxs("p",{className:"text-xs text-[var(--otari-muted)]",children:["Mode: ",s.mode," · Version ",s.version,s.require_pricing?" · require_pricing on":""]}):null]})}export{ne as SettingsPage,L as fieldMatches}; diff --git a/src/gateway/static/dashboard/assets/TablePagination-BEmYAlSB.js b/src/gateway/static/dashboard/assets/TablePagination-BynkRKqB.js similarity index 98% rename from src/gateway/static/dashboard/assets/TablePagination-BEmYAlSB.js rename to src/gateway/static/dashboard/assets/TablePagination-BynkRKqB.js index 6afd92c9d..359caa238 100644 --- a/src/gateway/static/dashboard/assets/TablePagination-BEmYAlSB.js +++ b/src/gateway/static/dashboard/assets/TablePagination-BynkRKqB.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as l}from"./react-dgEcD0HR.js";import{F as k}from"./Field-GEMwIhf7.js";import{M as y,E as C,F as T}from"./index-D-R1nuKP.js";import{A as c,B as p,e as P,L as B,I as A,S as q}from"./heroui-DhloIxuc.js";function E({label:t,value:n,onChange:a,isRequired:o,autoFocus:i}){return e.jsxs(P,{value:n,onChange:a,isRequired:o,className:"flex flex-col gap-1",children:[e.jsx(B,{className:"text-sm font-medium text-[var(--otari-ink)]",children:t}),e.jsx(A,{inputMode:"decimal",placeholder:"0.00",autoFocus:i})]})}function F(t){const n=t.trim();if(n==="")return null;const a=Number(n);return Number.isFinite(a)&&a>=0?a:Number.NaN}function z(t){return/^[^\s:/]+[:/][^\s]+$/.test(t.trim())}const W=t=>`Recompute cost for ${t.toLocaleString()} imported ${t===1?"row":"rows"} from each row's own token counts at these per-1M rates. Enforced gateway rows are never affected.`;function K({isOpen:t,onOpenChange:n,targetCount:a=0,isPending:o,error:i,onSubmit:I,title:L="Set price",description:w=W,collectModelKey:h=!1,initialModelKey:d=""}){const[r,f]=l.useState(d),[b,N]=l.useState(""),[j,g]=l.useState(""),[v,u]=l.useState(""),[S,s]=l.useState("");l.useEffect(()=>{t&&(f(d),N(""),g(""),u(""),s(""))},[t,d]);const x=F(b),m=F(j),_=F(v),R=F(S),M=h&&!z(r),$=M||x===null||Number.isNaN(x)||m===null||Number.isNaN(m)||Number.isNaN(_??0)||Number.isNaN(R??0),D=()=>{$||x===null||m===null||I({input_price_per_million:x,output_price_per_million:m,..._!==null&&!Number.isNaN(_)?{cache_read_price_per_million:_}:{},...R!==null&&!Number.isNaN(R)?{cache_write_price_per_million:R}:{}},r.trim())};return e.jsx(c,{isOpen:t,onOpenChange:n,children:t?e.jsx(c.Backdrop,{children:e.jsx(c.Container,{placement:"center",size:"lg",children:e.jsxs(c.Dialog,{children:[e.jsx(c.Header,{children:e.jsx(c.Heading,{children:L})}),e.jsxs(c.Body,{className:"flex flex-col gap-4",children:[e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:w(a)}),h?e.jsx(k,{label:"Model key",value:r,onChange:f,placeholder:"provider:model",isRequired:!0,autoFocus:!0,description:r.trim()!==""&&M?"Include the provider or instance prefix, as in ollama:llama3.2.":"The selector callers send as model, prefix included (for example vllm:mistral-small)."}):null,e.jsxs("div",{className:"grid gap-3 sm:grid-cols-2",children:[e.jsx(E,{label:"Input $ / 1M",value:b,onChange:N,isRequired:!0,autoFocus:!h}),e.jsx(E,{label:"Output $ / 1M",value:j,onChange:g,isRequired:!0}),e.jsx(E,{label:"Cache read $ / 1M",value:v,onChange:u}),e.jsx(E,{label:"Cache write $ / 1M",value:S,onChange:s})]}),e.jsx(y,{tone:"info",children:"Leave a cache rate blank to bill those tokens at the input rate."}),e.jsx(C,{error:i})]}),e.jsxs(c.Footer,{children:[e.jsx(p,{variant:"ghost",isDisabled:o,onPress:()=>n(!1),children:"Cancel"}),e.jsx(p,{variant:"primary",isDisabled:$,isPending:o,onPress:D,children:"Set price"})]})]})})}):null})}const G=[25,50,100];function Q({page:t,pageSize:n,total:a,rowsOnPage:o,onPageChange:i,onPageSizeChange:I,pageSizeOptions:L=G,isFetching:w=!1,hasNextFallback:h=!1}){const d=l.useId(),r=a!=null?Math.max(1,Math.ceil(a/n)):null,f=t===0,b=r!=null?t>=r-1:!h,N=o>0?t*n+1:0,j=t*n+o,g=a!=null?a===0?"0 of 0":`${N.toLocaleString()}–${j.toLocaleString()} of ${a.toLocaleString()}`:o>0?`${N.toLocaleString()}–${j.toLocaleString()}`:"0",[v,u]=l.useState(String(t+1));l.useEffect(()=>{u(String(t+1))},[t]);const S=()=>{const s=Number.parseInt(v,10);if(Number.isNaN(s)){u(String(t+1));return}const x=r??Number.MAX_SAFE_INTEGER,m=Math.min(Math.max(s,1),x);m-1!==t?i(m-1):u(String(t+1))};return e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("label",{htmlFor:d,className:"text-sm text-[var(--otari-muted)]",children:"Rows"}),e.jsx(T,{id:d,ariaLabel:"Rows per page",value:String(n),onChange:s=>I(Number.parseInt(s,10)),options:L.map(s=>({value:String(s),label:String(s)}))}),w?e.jsx(q,{size:"sm"}):null]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("span",{className:"text-sm text-[var(--otari-muted)] tabular-nums",children:g}),e.jsxs("div",{className:"flex items-center gap-1",children:[e.jsx(p,{size:"sm",variant:"outline","aria-label":"First page",isDisabled:f,onPress:()=>i(0),children:"«"}),e.jsx(p,{size:"sm",variant:"outline","aria-label":"Previous page",isDisabled:f,onPress:()=>i(t-1),children:"‹"}),e.jsxs("span",{className:"inline-flex items-center gap-1 text-sm text-[var(--otari-muted)]",children:[e.jsx("input",{"aria-label":"Page number",inputMode:"numeric",value:v,onChange:s=>u(s.target.value.replace(/[^0-9]/g,"")),onKeyDown:s=>{s.key==="Enter"&&s.currentTarget.blur()},onBlur:S,className:"w-12 rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-2 py-1 text-center text-sm text-[var(--otari-ink)] tabular-nums focus:border-[var(--otari-brand)] focus:outline-none"}),r!=null?e.jsxs("span",{className:"tabular-nums",children:["/ ",r.toLocaleString()]}):null]}),e.jsx(p,{size:"sm",variant:"outline","aria-label":"Next page",isDisabled:b,onPress:()=>i(t+1),children:"›"}),e.jsx(p,{size:"sm",variant:"outline","aria-label":"Last page",isDisabled:r==null||b,onPress:()=>r!=null&&i(r-1),children:"»"})]})]})]})}export{G as P,K as S,Q as T,z as i}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as l}from"./react-dgEcD0HR.js";import{F as k}from"./Field-GEMwIhf7.js";import{M as y,E as C,F as T}from"./index-Dit1BUBh.js";import{A as c,B as p,e as P,L as B,I as A,S as q}from"./heroui-DhloIxuc.js";function E({label:t,value:n,onChange:a,isRequired:o,autoFocus:i}){return e.jsxs(P,{value:n,onChange:a,isRequired:o,className:"flex flex-col gap-1",children:[e.jsx(B,{className:"text-sm font-medium text-[var(--otari-ink)]",children:t}),e.jsx(A,{inputMode:"decimal",placeholder:"0.00",autoFocus:i})]})}function F(t){const n=t.trim();if(n==="")return null;const a=Number(n);return Number.isFinite(a)&&a>=0?a:Number.NaN}function z(t){return/^[^\s:/]+[:/][^\s]+$/.test(t.trim())}const W=t=>`Recompute cost for ${t.toLocaleString()} imported ${t===1?"row":"rows"} from each row's own token counts at these per-1M rates. Enforced gateway rows are never affected.`;function K({isOpen:t,onOpenChange:n,targetCount:a=0,isPending:o,error:i,onSubmit:I,title:L="Set price",description:w=W,collectModelKey:h=!1,initialModelKey:d=""}){const[r,f]=l.useState(d),[b,N]=l.useState(""),[j,g]=l.useState(""),[v,u]=l.useState(""),[S,s]=l.useState("");l.useEffect(()=>{t&&(f(d),N(""),g(""),u(""),s(""))},[t,d]);const x=F(b),m=F(j),_=F(v),R=F(S),M=h&&!z(r),$=M||x===null||Number.isNaN(x)||m===null||Number.isNaN(m)||Number.isNaN(_??0)||Number.isNaN(R??0),D=()=>{$||x===null||m===null||I({input_price_per_million:x,output_price_per_million:m,..._!==null&&!Number.isNaN(_)?{cache_read_price_per_million:_}:{},...R!==null&&!Number.isNaN(R)?{cache_write_price_per_million:R}:{}},r.trim())};return e.jsx(c,{isOpen:t,onOpenChange:n,children:t?e.jsx(c.Backdrop,{children:e.jsx(c.Container,{placement:"center",size:"lg",children:e.jsxs(c.Dialog,{children:[e.jsx(c.Header,{children:e.jsx(c.Heading,{children:L})}),e.jsxs(c.Body,{className:"flex flex-col gap-4",children:[e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:w(a)}),h?e.jsx(k,{label:"Model key",value:r,onChange:f,placeholder:"provider:model",isRequired:!0,autoFocus:!0,description:r.trim()!==""&&M?"Include the provider or instance prefix, as in ollama:llama3.2.":"The selector callers send as model, prefix included (for example vllm:mistral-small)."}):null,e.jsxs("div",{className:"grid gap-3 sm:grid-cols-2",children:[e.jsx(E,{label:"Input $ / 1M",value:b,onChange:N,isRequired:!0,autoFocus:!h}),e.jsx(E,{label:"Output $ / 1M",value:j,onChange:g,isRequired:!0}),e.jsx(E,{label:"Cache read $ / 1M",value:v,onChange:u}),e.jsx(E,{label:"Cache write $ / 1M",value:S,onChange:s})]}),e.jsx(y,{tone:"info",children:"Leave a cache rate blank to bill those tokens at the input rate."}),e.jsx(C,{error:i})]}),e.jsxs(c.Footer,{children:[e.jsx(p,{variant:"ghost",isDisabled:o,onPress:()=>n(!1),children:"Cancel"}),e.jsx(p,{variant:"primary",isDisabled:$,isPending:o,onPress:D,children:"Set price"})]})]})})}):null})}const G=[25,50,100];function Q({page:t,pageSize:n,total:a,rowsOnPage:o,onPageChange:i,onPageSizeChange:I,pageSizeOptions:L=G,isFetching:w=!1,hasNextFallback:h=!1}){const d=l.useId(),r=a!=null?Math.max(1,Math.ceil(a/n)):null,f=t===0,b=r!=null?t>=r-1:!h,N=o>0?t*n+1:0,j=t*n+o,g=a!=null?a===0?"0 of 0":`${N.toLocaleString()}–${j.toLocaleString()} of ${a.toLocaleString()}`:o>0?`${N.toLocaleString()}–${j.toLocaleString()}`:"0",[v,u]=l.useState(String(t+1));l.useEffect(()=>{u(String(t+1))},[t]);const S=()=>{const s=Number.parseInt(v,10);if(Number.isNaN(s)){u(String(t+1));return}const x=r??Number.MAX_SAFE_INTEGER,m=Math.min(Math.max(s,1),x);m-1!==t?i(m-1):u(String(t+1))};return e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("label",{htmlFor:d,className:"text-sm text-[var(--otari-muted)]",children:"Rows"}),e.jsx(T,{id:d,ariaLabel:"Rows per page",value:String(n),onChange:s=>I(Number.parseInt(s,10)),options:L.map(s=>({value:String(s),label:String(s)}))}),w?e.jsx(q,{size:"sm"}):null]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("span",{className:"text-sm text-[var(--otari-muted)] tabular-nums",children:g}),e.jsxs("div",{className:"flex items-center gap-1",children:[e.jsx(p,{size:"sm",variant:"outline","aria-label":"First page",isDisabled:f,onPress:()=>i(0),children:"«"}),e.jsx(p,{size:"sm",variant:"outline","aria-label":"Previous page",isDisabled:f,onPress:()=>i(t-1),children:"‹"}),e.jsxs("span",{className:"inline-flex items-center gap-1 text-sm text-[var(--otari-muted)]",children:[e.jsx("input",{"aria-label":"Page number",inputMode:"numeric",value:v,onChange:s=>u(s.target.value.replace(/[^0-9]/g,"")),onKeyDown:s=>{s.key==="Enter"&&s.currentTarget.blur()},onBlur:S,className:"w-12 rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-2 py-1 text-center text-sm text-[var(--otari-ink)] tabular-nums focus:border-[var(--otari-brand)] focus:outline-none"}),r!=null?e.jsxs("span",{className:"tabular-nums",children:["/ ",r.toLocaleString()]}):null]}),e.jsx(p,{size:"sm",variant:"outline","aria-label":"Next page",isDisabled:b,onPress:()=>i(t+1),children:"›"}),e.jsx(p,{size:"sm",variant:"outline","aria-label":"Last page",isDisabled:r==null||b,onPress:()=>r!=null&&i(r-1),children:"»"})]})]})]})}export{G as P,K as S,Q as T,z as i}; diff --git a/src/gateway/static/dashboard/assets/ToolsGuardrailsPage-C-E4XKsV.js b/src/gateway/static/dashboard/assets/ToolsGuardrailsPage-CSbQtPkh.js similarity index 99% rename from src/gateway/static/dashboard/assets/ToolsGuardrailsPage-C-E4XKsV.js rename to src/gateway/static/dashboard/assets/ToolsGuardrailsPage-CSbQtPkh.js index fc21e96f7..6e244cc79 100644 --- a/src/gateway/static/dashboard/assets/ToolsGuardrailsPage-C-E4XKsV.js +++ b/src/gateway/static/dashboard/assets/ToolsGuardrailsPage-CSbQtPkh.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as m}from"./react-dgEcD0HR.js";import{G as B,V as F,m as I,as as K,P as z,E as G,a3 as O,_ as L,at as q,F as A}from"./index-D-R1nuKP.js";import{d as T,B as N}from"./heroui-DhloIxuc.js";function W(s,t){return{[s]:t}}const V=[{key:"web_search",label:"Web search",blurb:"Backend for otari_web_search tools (a SearXNG instance or a search adapter).",pricingKey:"otari:web_search",order:["web_search_url","web_search_engines","web_search_max_results","web_search_extract","web_search_purpose_hint"]},{key:"sandbox",label:"Code execution",blurb:"Backend for otari_code_execution tools (the sandbox that runs generated code).",pricingKey:"otari:code_execution",order:["sandbox_url","sandbox_purpose_hint"]},{key:"guardrails",label:"Guardrails",blurb:"Default input-guardrails service used when a request does not pass its own guardrail URL.",order:["guardrails_url"]}];function H(){const[s,t]=m.useState(null),n=m.useRef(void 0),l=r=>{t(r),window.clearTimeout(n.current),n.current=window.setTimeout(()=>t(null),2500)};return m.useEffect(()=>()=>window.clearTimeout(n.current),[]),[s,l]}function X({message:s}){return s?e.jsxs("div",{role:"status","aria-live":"polite",className:"fixed right-4 bottom-4 z-50 flex items-center gap-2 rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm font-medium text-green-700 shadow-lg",children:[e.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2","aria-hidden":!0,className:"h-5 w-5",children:e.jsx("path",{d:"M20 6 9 17l-5-5",strokeLinecap:"round",strokeLinejoin:"round"})}),s]}):null}const C="rounded-md border border-[var(--otari-line)] bg-[var(--otari-surface)] px-2 py-1 text-sm focus:border-[var(--otari-brand)] focus:outline-none disabled:opacity-50",_="grid gap-x-4 gap-y-1.5 py-4 sm:grid-cols-[minmax(0,1fr)_16rem_10rem] sm:items-start",E=`w-full sm:col-start-2 ${C}`,k="flex items-center gap-2 sm:col-start-3",S="flex flex-col gap-1 sm:col-span-2 sm:col-start-2";function w({message:s}){return s?e.jsx("span",{className:"break-words text-xs text-red-700",children:s}):null}function P({field:s,help:t}){return e.jsxs("div",{className:"min-w-0 sm:col-start-1",children:[e.jsx("code",{className:"text-sm font-medium text-[var(--otari-ink)]",children:s.key}),s.description?e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:s.description}):null,t?e.jsx("p",{className:"mt-1 text-xs text-[var(--otari-muted)]",children:t}):null]})}function J({field:s,onSave:t,saveError:n,disabled:l}){const r=typeof s.value=="string"?s.value:"",[o,c]=m.useState(r),[u,x]=m.useState(null),i=q();m.useEffect(()=>{c(r)},[r]);const g=o.trim()!==r,p=o.trim(),y=u!==null&&u===p;return e.jsxs("div",{className:_,children:[e.jsx(P,{field:s,help:"Leave blank and Save to fall back to the configured default."}),e.jsx("input",{type:"text",inputMode:"url","aria-label":s.key,value:o,disabled:l,placeholder:"unset",onChange:j=>{c(j.target.value),i.reset()},className:E}),e.jsxs("div",{className:k,children:[e.jsx(N,{size:"sm",variant:"primary","aria-label":`Save ${s.key}`,isDisabled:l||!g,onPress:()=>t(p===""?null:p),children:"Save"}),e.jsx(N,{size:"sm",variant:"outline","aria-label":`Test ${s.service}`,isDisabled:p===""||i.isPending,onPress:()=>{x(p),i.mutate({service:s.service,url:p})},children:i.isPending?"Testing…":"Test"})]}),e.jsxs("div",{className:S,children:[e.jsx("span",{role:"status","aria-live":"polite",className:"block break-words text-xs",children:i.isPending||!y?null:i.error?e.jsx("span",{className:"text-red-700",children:L(i.error)}):i.data?e.jsx("span",{className:i.data.ok?"font-medium text-green-700":"text-red-700",children:i.data.reason}):null}),e.jsx(w,{message:n})]})]})}function Q({field:s,onSave:t,saveError:n,disabled:l}){const r=typeof s.value=="string"?s.value:"",[o,c]=m.useState(r);m.useEffect(()=>{c(r)},[r]);const u=o!==r;return e.jsxs("div",{className:_,children:[e.jsx(P,{field:s}),e.jsx("input",{type:"text","aria-label":s.key,value:o,disabled:l,placeholder:"default",onChange:x=>c(x.target.value),className:E}),e.jsx("div",{className:k,children:e.jsx(N,{size:"sm",variant:"primary","aria-label":`Save ${s.key}`,isDisabled:l||!u,onPress:()=>t(o.trim()===""?null:o.trim()),children:"Save"})}),n?e.jsx("div",{className:S,children:e.jsx(w,{message:n})}):null]})}function Y({field:s,onSave:t,saveError:n,disabled:l}){const r=typeof s.value=="number"?String(s.value):"",[o,c]=m.useState(r);m.useEffect(()=>{c(r)},[r]);const u=o.trim(),x=Number(u),g=(u===""||Number.isInteger(x)&&x>=1)&&u!==r;return e.jsxs("div",{className:_,children:[e.jsx(P,{field:s,help:"Leave blank to use the backend default."}),e.jsx("input",{type:"number",min:"1",step:"1",inputMode:"numeric","aria-label":s.key,value:o,disabled:l,placeholder:"default",onChange:p=>c(p.target.value),className:`w-full text-right tabular-nums sm:col-start-2 sm:w-28 sm:justify-self-end ${C}`}),e.jsx("div",{className:k,children:e.jsx(N,{size:"sm",variant:"primary","aria-label":`Save ${s.key}`,isDisabled:l||!g,onPress:()=>t(u===""?null:x),children:"Save"})}),n?e.jsx("div",{className:S,children:e.jsx(w,{message:n})}):null]})}const R=1e6;function Z({pricingKey:s,configured:t,onSave:n,saving:l,saveError:r,disabled:o}){const c=t===null?"":String(t/R),[u,x]=m.useState(c);m.useEffect(()=>{x(c)},[c]);const i=u.trim(),g=Number(i),y=i!==""&&Number.isFinite(g)&&g>=0&&i!==c;return e.jsxs("div",{className:_,children:[e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Price per call"}),e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:t===null?e.jsxs(e.Fragment,{children:["Not priced. Calls are recorded but billed nothing, and with"," ",e.jsx("code",{className:"font-mono",children:"require_pricing"})," on they are refused. Stored as"," ",e.jsx("code",{className:"font-mono",children:s}),"."]}):e.jsxs(e.Fragment,{children:["Charged per call and added to the request that ran it. Stored as"," ",e.jsx("code",{className:"font-mono",children:s}),"."]})})]}),e.jsxs("div",{className:"flex items-center gap-1.5 sm:col-start-2 sm:justify-self-end",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"USD"}),e.jsx("input",{type:"number",min:"0",step:"0.0001",inputMode:"decimal","aria-label":`Price per call for ${s}`,value:u,disabled:o,placeholder:"0.00",onChange:j=>x(j.target.value),className:`w-full text-right tabular-nums sm:w-28 ${C}`})]}),e.jsx("div",{className:k,children:e.jsx(N,{size:"sm",variant:"primary","aria-label":`Save price for ${s}`,isDisabled:o||!y||l,onPress:()=>n(g),children:l?"Saving…":"Save"})}),r?e.jsx("div",{className:S,children:e.jsx(w,{message:r})}):null]})}function ee({field:s,onSave:t,saveError:n,disabled:l}){const r=s.value===!0?"on":s.value===!1?"off":"default";return e.jsxs("div",{className:_,children:[e.jsx(P,{field:s}),e.jsx("div",{className:"sm:col-start-2 sm:justify-self-start",children:e.jsx(A,{ariaLabel:s.key,value:r,onChange:o=>t(o==="default"?null:o==="on"),options:[{value:"default",label:"Default"},{value:"on",label:"On"},{value:"off",label:"Off"}],disabled:l})}),n?e.jsx("div",{className:S,children:e.jsx(w,{message:n})}):null]})}function se({field:s,onSave:t,saveError:n,disabled:l}){return s.type==="url"?e.jsx(J,{field:s,onSave:t,saveError:n,disabled:l}):s.type==="int"?e.jsx(Y,{field:s,onSave:t,saveError:n,disabled:l}):s.type==="bool"?e.jsx(ee,{field:s,onSave:t,saveError:n,disabled:l}):e.jsx(Q,{field:s,onSave:t,saveError:n,disabled:l})}function le(){const s=B(),t=F(),n=I(),[l,r]=m.useState(null),[o,c]=m.useState({}),u=new Map;for(const a of t.data??[])u.get(a.model_key)===void 0&&u.set(a.model_key,a.input_price_per_million);const x=(a,b)=>{r(a),c(h=>({...h,[a]:""})),n.mutate({model_key:a,input_price_per_million:b*R,output_price_per_million:0},{onSuccess:()=>{r(null),p("Price saved")},onError:h=>{r(null),c(f=>({...f,[a]:h instanceof Error?h.message:"Could not save the price"}))}})},i=K(),[g,p]=H(),[y,j]=m.useState({}),v=s.data,D=!v||i.isPending,M=new Map(((v==null?void 0:v.fields)??[]).map(a=>[a.key,a])),$=(a,b)=>{j(h=>{const{[a.key]:f,...d}=h;return d}),i.mutate(W(a.key,b),{onSuccess:()=>p(`${a.key} saved`),onError:h=>j(f=>({...f,[a.key]:L(h)}))})};return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(z,{title:"Tools & Guardrails",description:"Configure the built-in tool and guardrail service endpoints without a restart. Changes apply immediately and persist. URLs are validated for shape (http/https) and can be tested for reachability before saving; the network-safety gates for these services live on the Settings page."}),e.jsx(G,{error:s.error}),s.isLoading?e.jsx(O,{}):null,V.map(a=>{const b=a.order.map(d=>M.get(d)).filter(d=>d!==void 0),h=((v==null?void 0:v.fields)??[]).filter(d=>d.service===a.key&&!a.order.includes(d.key)),f=[...b,...h];return f.length===0?null:e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:a.label}),e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:a.blurb}),e.jsx(T,{children:e.jsxs(T.Content,{className:"flex flex-col divide-y divide-[var(--otari-line)] px-5 py-1",children:[a.pricingKey?e.jsx(Z,{pricingKey:a.pricingKey,configured:u.get(a.pricingKey)??null,onSave:d=>x(a.pricingKey,d),saving:l===a.pricingKey,saveError:o[a.pricingKey]||(t.error?"Could not load the current price. Reload before editing.":void 0),disabled:t.isLoading||!!t.error}):null,f.map(d=>e.jsx(se,{field:d,onSave:U=>$(d,U),saveError:y[d.key],disabled:D},d.key))]})})]},a.key)}),e.jsx(X,{message:g})]})}export{le as ToolsGuardrailsPage}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{r as m}from"./react-dgEcD0HR.js";import{G as B,V as F,m as I,as as K,P as z,E as G,a3 as O,_ as L,at as q,F as A}from"./index-Dit1BUBh.js";import{d as T,B as N}from"./heroui-DhloIxuc.js";function W(s,t){return{[s]:t}}const V=[{key:"web_search",label:"Web search",blurb:"Backend for otari_web_search tools (a SearXNG instance or a search adapter).",pricingKey:"otari:web_search",order:["web_search_url","web_search_engines","web_search_max_results","web_search_extract","web_search_purpose_hint"]},{key:"sandbox",label:"Code execution",blurb:"Backend for otari_code_execution tools (the sandbox that runs generated code).",pricingKey:"otari:code_execution",order:["sandbox_url","sandbox_purpose_hint"]},{key:"guardrails",label:"Guardrails",blurb:"Default input-guardrails service used when a request does not pass its own guardrail URL.",order:["guardrails_url"]}];function H(){const[s,t]=m.useState(null),n=m.useRef(void 0),l=r=>{t(r),window.clearTimeout(n.current),n.current=window.setTimeout(()=>t(null),2500)};return m.useEffect(()=>()=>window.clearTimeout(n.current),[]),[s,l]}function X({message:s}){return s?e.jsxs("div",{role:"status","aria-live":"polite",className:"fixed right-4 bottom-4 z-50 flex items-center gap-2 rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm font-medium text-green-700 shadow-lg",children:[e.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2","aria-hidden":!0,className:"h-5 w-5",children:e.jsx("path",{d:"M20 6 9 17l-5-5",strokeLinecap:"round",strokeLinejoin:"round"})}),s]}):null}const C="rounded-md border border-[var(--otari-line)] bg-[var(--otari-surface)] px-2 py-1 text-sm focus:border-[var(--otari-brand)] focus:outline-none disabled:opacity-50",_="grid gap-x-4 gap-y-1.5 py-4 sm:grid-cols-[minmax(0,1fr)_16rem_10rem] sm:items-start",E=`w-full sm:col-start-2 ${C}`,k="flex items-center gap-2 sm:col-start-3",S="flex flex-col gap-1 sm:col-span-2 sm:col-start-2";function w({message:s}){return s?e.jsx("span",{className:"break-words text-xs text-red-700",children:s}):null}function P({field:s,help:t}){return e.jsxs("div",{className:"min-w-0 sm:col-start-1",children:[e.jsx("code",{className:"text-sm font-medium text-[var(--otari-ink)]",children:s.key}),s.description?e.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:s.description}):null,t?e.jsx("p",{className:"mt-1 text-xs text-[var(--otari-muted)]",children:t}):null]})}function J({field:s,onSave:t,saveError:n,disabled:l}){const r=typeof s.value=="string"?s.value:"",[o,c]=m.useState(r),[u,x]=m.useState(null),i=q();m.useEffect(()=>{c(r)},[r]);const g=o.trim()!==r,p=o.trim(),y=u!==null&&u===p;return e.jsxs("div",{className:_,children:[e.jsx(P,{field:s,help:"Leave blank and Save to fall back to the configured default."}),e.jsx("input",{type:"text",inputMode:"url","aria-label":s.key,value:o,disabled:l,placeholder:"unset",onChange:j=>{c(j.target.value),i.reset()},className:E}),e.jsxs("div",{className:k,children:[e.jsx(N,{size:"sm",variant:"primary","aria-label":`Save ${s.key}`,isDisabled:l||!g,onPress:()=>t(p===""?null:p),children:"Save"}),e.jsx(N,{size:"sm",variant:"outline","aria-label":`Test ${s.service}`,isDisabled:p===""||i.isPending,onPress:()=>{x(p),i.mutate({service:s.service,url:p})},children:i.isPending?"Testing…":"Test"})]}),e.jsxs("div",{className:S,children:[e.jsx("span",{role:"status","aria-live":"polite",className:"block break-words text-xs",children:i.isPending||!y?null:i.error?e.jsx("span",{className:"text-red-700",children:L(i.error)}):i.data?e.jsx("span",{className:i.data.ok?"font-medium text-green-700":"text-red-700",children:i.data.reason}):null}),e.jsx(w,{message:n})]})]})}function Q({field:s,onSave:t,saveError:n,disabled:l}){const r=typeof s.value=="string"?s.value:"",[o,c]=m.useState(r);m.useEffect(()=>{c(r)},[r]);const u=o!==r;return e.jsxs("div",{className:_,children:[e.jsx(P,{field:s}),e.jsx("input",{type:"text","aria-label":s.key,value:o,disabled:l,placeholder:"default",onChange:x=>c(x.target.value),className:E}),e.jsx("div",{className:k,children:e.jsx(N,{size:"sm",variant:"primary","aria-label":`Save ${s.key}`,isDisabled:l||!u,onPress:()=>t(o.trim()===""?null:o.trim()),children:"Save"})}),n?e.jsx("div",{className:S,children:e.jsx(w,{message:n})}):null]})}function Y({field:s,onSave:t,saveError:n,disabled:l}){const r=typeof s.value=="number"?String(s.value):"",[o,c]=m.useState(r);m.useEffect(()=>{c(r)},[r]);const u=o.trim(),x=Number(u),g=(u===""||Number.isInteger(x)&&x>=1)&&u!==r;return e.jsxs("div",{className:_,children:[e.jsx(P,{field:s,help:"Leave blank to use the backend default."}),e.jsx("input",{type:"number",min:"1",step:"1",inputMode:"numeric","aria-label":s.key,value:o,disabled:l,placeholder:"default",onChange:p=>c(p.target.value),className:`w-full text-right tabular-nums sm:col-start-2 sm:w-28 sm:justify-self-end ${C}`}),e.jsx("div",{className:k,children:e.jsx(N,{size:"sm",variant:"primary","aria-label":`Save ${s.key}`,isDisabled:l||!g,onPress:()=>t(u===""?null:x),children:"Save"})}),n?e.jsx("div",{className:S,children:e.jsx(w,{message:n})}):null]})}const R=1e6;function Z({pricingKey:s,configured:t,onSave:n,saving:l,saveError:r,disabled:o}){const c=t===null?"":String(t/R),[u,x]=m.useState(c);m.useEffect(()=>{x(c)},[c]);const i=u.trim(),g=Number(i),y=i!==""&&Number.isFinite(g)&&g>=0&&i!==c;return e.jsxs("div",{className:_,children:[e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsx("span",{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Price per call"}),e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:t===null?e.jsxs(e.Fragment,{children:["Not priced. Calls are recorded but billed nothing, and with"," ",e.jsx("code",{className:"font-mono",children:"require_pricing"})," on they are refused. Stored as"," ",e.jsx("code",{className:"font-mono",children:s}),"."]}):e.jsxs(e.Fragment,{children:["Charged per call and added to the request that ran it. Stored as"," ",e.jsx("code",{className:"font-mono",children:s}),"."]})})]}),e.jsxs("div",{className:"flex items-center gap-1.5 sm:col-start-2 sm:justify-self-end",children:[e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"USD"}),e.jsx("input",{type:"number",min:"0",step:"0.0001",inputMode:"decimal","aria-label":`Price per call for ${s}`,value:u,disabled:o,placeholder:"0.00",onChange:j=>x(j.target.value),className:`w-full text-right tabular-nums sm:w-28 ${C}`})]}),e.jsx("div",{className:k,children:e.jsx(N,{size:"sm",variant:"primary","aria-label":`Save price for ${s}`,isDisabled:o||!y||l,onPress:()=>n(g),children:l?"Saving…":"Save"})}),r?e.jsx("div",{className:S,children:e.jsx(w,{message:r})}):null]})}function ee({field:s,onSave:t,saveError:n,disabled:l}){const r=s.value===!0?"on":s.value===!1?"off":"default";return e.jsxs("div",{className:_,children:[e.jsx(P,{field:s}),e.jsx("div",{className:"sm:col-start-2 sm:justify-self-start",children:e.jsx(A,{ariaLabel:s.key,value:r,onChange:o=>t(o==="default"?null:o==="on"),options:[{value:"default",label:"Default"},{value:"on",label:"On"},{value:"off",label:"Off"}],disabled:l})}),n?e.jsx("div",{className:S,children:e.jsx(w,{message:n})}):null]})}function se({field:s,onSave:t,saveError:n,disabled:l}){return s.type==="url"?e.jsx(J,{field:s,onSave:t,saveError:n,disabled:l}):s.type==="int"?e.jsx(Y,{field:s,onSave:t,saveError:n,disabled:l}):s.type==="bool"?e.jsx(ee,{field:s,onSave:t,saveError:n,disabled:l}):e.jsx(Q,{field:s,onSave:t,saveError:n,disabled:l})}function le(){const s=B(),t=F(),n=I(),[l,r]=m.useState(null),[o,c]=m.useState({}),u=new Map;for(const a of t.data??[])u.get(a.model_key)===void 0&&u.set(a.model_key,a.input_price_per_million);const x=(a,b)=>{r(a),c(h=>({...h,[a]:""})),n.mutate({model_key:a,input_price_per_million:b*R,output_price_per_million:0},{onSuccess:()=>{r(null),p("Price saved")},onError:h=>{r(null),c(f=>({...f,[a]:h instanceof Error?h.message:"Could not save the price"}))}})},i=K(),[g,p]=H(),[y,j]=m.useState({}),v=s.data,D=!v||i.isPending,M=new Map(((v==null?void 0:v.fields)??[]).map(a=>[a.key,a])),$=(a,b)=>{j(h=>{const{[a.key]:f,...d}=h;return d}),i.mutate(W(a.key,b),{onSuccess:()=>p(`${a.key} saved`),onError:h=>j(f=>({...f,[a.key]:L(h)}))})};return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(z,{title:"Tools & Guardrails",description:"Configure the built-in tool and guardrail service endpoints without a restart. Changes apply immediately and persist. URLs are validated for shape (http/https) and can be tested for reachability before saving; the network-safety gates for these services live on the Settings page."}),e.jsx(G,{error:s.error}),s.isLoading?e.jsx(O,{}):null,V.map(a=>{const b=a.order.map(d=>M.get(d)).filter(d=>d!==void 0),h=((v==null?void 0:v.fields)??[]).filter(d=>d.service===a.key&&!a.order.includes(d.key)),f=[...b,...h];return f.length===0?null:e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:a.label}),e.jsx("p",{className:"text-sm text-[var(--otari-muted)]",children:a.blurb}),e.jsx(T,{children:e.jsxs(T.Content,{className:"flex flex-col divide-y divide-[var(--otari-line)] px-5 py-1",children:[a.pricingKey?e.jsx(Z,{pricingKey:a.pricingKey,configured:u.get(a.pricingKey)??null,onSave:d=>x(a.pricingKey,d),saving:l===a.pricingKey,saveError:o[a.pricingKey]||(t.error?"Could not load the current price. Reload before editing.":void 0),disabled:t.isLoading||!!t.error}):null,f.map(d=>e.jsx(se,{field:d,onSave:U=>$(d,U),saveError:y[d.key],disabled:D},d.key))]})})]},a.key)}),e.jsx(X,{message:g})]})}export{le as ToolsGuardrailsPage}; diff --git a/src/gateway/static/dashboard/assets/UsagePage-BTnJt3lF.js b/src/gateway/static/dashboard/assets/UsagePage-BTnJt3lF.js new file mode 100644 index 000000000..9c0d0480a --- /dev/null +++ b/src/gateway/static/dashboard/assets/UsagePage-BTnJt3lF.js @@ -0,0 +1 @@ +import{j as s}from"./tanstack-query-1t81HyiD.js";import{i as ys,r as u}from"./react-dgEcD0HR.js";import{u as _s,c as js,p as fe,i as ws,g as ke,au as Ss,av as Ns,a5 as se,aw as Ye,P as Cs,E as Ls,ax as be,f as Ts,R as Es,ay as Ze,z as Rs,a6 as H,a8 as te,a7 as re,aa as Me,az as W,F as Fs,h as Os,ac as qs,r as As}from"./index-Dit1BUBh.js";import{S as ae,C as Ps,T as Ds}from"./charts-D6upG8fh.js";import{D as Qe}from"./DataTable-BHrpJHmX.js";import{F as $s}from"./FilterChips-C0emi5Kg.js";import{B as D,S as Ue}from"./heroui-DhloIxuc.js";import"./recharts-EeW53z2i.js";function $(c){return c.toLocaleString()}function Bs(c){return c===null?"—":c<1e3?`${Math.round(c)} ms`:`${(c/1e3).toFixed(2)} s`}function Ms(c,v){const y=new Date(c);return Number.isNaN(y.getTime())?c:v==="hour"?y.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit",timeZone:"UTC"}):y.toLocaleDateString(void 0,{month:"short",day:"numeric",timeZone:"UTC"})}const Ie=Os(Ze,Ye),ye=15,Ke=[{key:"cost",label:"Cost"},{key:"tokens",label:"Tokens"},{key:"requests",label:"Requests"}],Us=[{value:"",label:"None"},{value:"model",label:"Model"},{value:"user_id",label:"User"},{value:"api_key_id",label:"API key"},{value:"source",label:"Source"}],Is=[{key:"fresh",label:"Fresh input",color:"var(--otari-ink)"},{key:"cache_read",label:"Cache read",color:"var(--otari-brand)"},{key:"cache_write",label:"Cache write",color:"var(--otari-brand-soft)"},{key:"output",label:"Output",color:"var(--otari-brand-dark)"}],Ks=[{key:"success",label:"Succeeded",color:"var(--otari-brand)"},{key:"errors",label:"Failed",color:"var(--otari-danger)"}],ze=["var(--otari-cat-1)","var(--otari-cat-2)","var(--otari-cat-3)","var(--otari-cat-4)","var(--otari-cat-5)","var(--otari-cat-6)","var(--otari-cat-7)","var(--otari-cat-8)"],zs="var(--otari-cat-other)";function Gs(c){return c==="cost"?re:c==="tokens"?W:$}const Ge="__other__",He="__unknown__";function We({dimensionLabel:c,rows:v,totalCost:y,emptyLabel:g,unknownLabel:B="(unknown)",onDrill:n,loading:L}){const[f,M]=u.useState(!1),Y=f?v:v.slice(0,ye),Z=v.length-Y.length,oe=o=>o.is_other?Ge:o.key??He,Q=[{id:"name",header:c,isRowHeader:!0,cell:o=>{const U=y>0?o.cost/y:0;return s.jsxs("div",{className:"flex flex-col gap-1",children:[s.jsx("span",{className:"truncate text-[var(--otari-ink)]",children:o.is_other?`Other (${o.requests.toLocaleString()} req)`:o.key===null?B:o.key}),s.jsx("span",{className:"h-1 w-full overflow-hidden rounded-full bg-[var(--otari-line)]",children:s.jsx("span",{className:"block h-full rounded-full bg-[var(--otari-brand)]",style:{width:`${Math.min(100,U*100)}%`}})})]})}},{id:"requests",header:"Requests",align:"end",cell:o=>s.jsx("span",{className:"text-[var(--otari-muted)]",children:$(o.requests)})},{id:"tokens",header:"Tokens",align:"end",cell:o=>s.jsx("span",{className:"text-[var(--otari-muted)]",children:W(o.tokens)})},{id:"spend",header:"Spend",align:"end",cell:o=>s.jsx("span",{className:"text-[var(--otari-ink)]",children:re(o.cost)})}];return s.jsxs("div",{className:"flex flex-col gap-2",children:[s.jsx(Qe,{ariaLabel:`Spend by ${c.toLowerCase()}`,columns:Q,rows:Y,getRowKey:oe,isLoading:L,emptyContent:g,onRowAction:o=>{o!==Ge&&o!==He&&n(o)}}),!L&&Z>0?s.jsxs(D,{size:"sm",variant:"ghost",onPress:()=>M(!0),children:["Show all ",v.length]}):null,!L&&f&&v.length>ye?s.jsxs(D,{size:"sm",variant:"ghost",onPress:()=>M(!1),children:["Show top ",ye]}):null]})}function Hs({rows:c,totalCost:v,onDrill:y,loading:g}){const B=[{id:"tool",header:"Tool",isRowHeader:!0,cell:n=>{const L=v>0?n.cost/v:0;return s.jsxs("div",{className:"flex flex-col gap-1",children:[s.jsx("span",{className:"truncate text-[var(--otari-ink)]",children:n.tool.replaceAll("_"," ")}),s.jsx("span",{className:"h-1 w-full overflow-hidden rounded-full bg-[var(--otari-line)]",children:s.jsx("span",{className:"block h-full rounded-full bg-[var(--otari-brand)]",style:{width:`${Math.min(100,L*100)}%`}})})]})}},{id:"calls",header:"Calls",align:"end",cell:n=>s.jsx("span",{className:"text-[var(--otari-muted)]",children:$(n.calls)})},{id:"failed",header:"Failed",align:"end",cell:n=>s.jsx("span",{className:n.errors?"text-red-700":"text-[var(--otari-muted)]",children:$(n.errors)})},{id:"requests",header:"Requests",align:"end",cell:n=>s.jsx("span",{className:"text-[var(--otari-muted)]",children:$(n.requests)})},{id:"spend",header:"Spend",align:"end",cell:n=>s.jsx("span",{className:"text-[var(--otari-ink)]",children:re(n.cost)})}];return s.jsx(Qe,{ariaLabel:"Spend by gateway-run tool",columns:B,rows:c,getRowKey:n=>n.tool,isLoading:g,emptyContent:"No gateway-run tool calls in this range.",onRowAction:n=>y(String(n))})}const Ws=["model","user","source_label","endpoint","provider","source","tool"],Ys=["model"];function at(){var Ae,Pe,De,$e;const c=ys(),v=_s(),y=js(),[g,B]=u.useState(Ie),[n,L]=u.useState(()=>fe(Ie.seconds??0)),[f,M]=u.useState(!1),[Y,Z]=u.useState(),[oe,Q]=u.useState(),[o,U]=u.useState([]),[_,ne]=u.useState([]),[k,le]=u.useState([]),[h,Ve]=u.useState("cost"),[O,Xe]=u.useState(""),b=f?Y:n,S=f?oe:void 0,T=f?b?ws(b,S):"day":g.bucket,E=u.useMemo(()=>({start_date:b,end_date:S,model:o.length>0?o:void 0,user_id:_.length>0?_:void 0,api_key_id:k.length>0?k:void 0}),[b,S,o,_,k]),I=u.useMemo(()=>{if(f){if(!b||!S)return null;const e=new Date(S).getTime()-new Date(b).getTime();return e>0?{...E,start_date:new Date(new Date(b).getTime()-e).toISOString(),end_date:b}:null}return!n||g.seconds===null?null:{...E,start_date:new Date(new Date(n).getTime()-g.seconds*1e3).toISOString(),end_date:n}},[f,b,S,E,g.seconds,n]),w=ke(E,T,Ws),ie=ke(I??E,T,qs,I!==null),R=Ss(E,T,O||null),ce=O!==""&&R.error instanceof Ns&&R.error.status===404,q=ce?"":O,t=w.data,a=t==null?void 0:t.totals,j=I!==null?(Ae=ie.data)==null?void 0:Ae.totals:void 0,_e=a?se(a.cost,j==null?void 0:j.cost):null,Je=u.useMemo(()=>({...E,model:void 0}),[E]),je=ke(Je,T,Ys),es=((De=(Pe=je.data)==null?void 0:Pe.by_model)==null?void 0:De.filter(e=>!e.is_other&&e.key!==null).map(e=>e.key))??[],we=(v.data??[]).map(e=>({value:e.user_id,label:e.alias?`${e.alias} (${e.user_id})`:e.user_id})),de=(y.data??[]).map(e=>({value:e.id,label:e.key_name??`${e.id.slice(0,8)}…`})),ss=e=>{var l;return((l=de.find(i=>i.value===e))==null?void 0:l.label)??e},ts=[...o,...es.filter(e=>!o.includes(e))].map(e=>({value:e,label:e})),as=f||g.key!==Ye,ue=o.length>0||_.length>0||k.length>0||as,Se=(e,l)=>{var i;return((i=e.find(r=>r.value===l))==null?void 0:i.label)??l},rs=()=>{U([]),ne([]),le([])},me=(e,l,i,r,x)=>i.map(N=>({key:`${e}:${N}`,label:l,value:r(N),clearLabel:`Remove ${l} filter ${r(N)}`,onClear:()=>x(i.filter(P=>P!==N))})),os=[...me("user","User",_,e=>Se(we,e),ne),...me("model","Model",o,e=>e,U),...me("key","API key",k,e=>Se(de,e),le)],ns=!!(t&&a&&a.request_count===0&&!ue),ls=(t==null?void 0:t.start_date)??b,is=(t==null?void 0:t.end_date)??S,Ne=e=>{M(!1),B(e),L(fe(e.seconds??0)),Z(void 0),Q(void 0)},cs=(e,l)=>{M(!0),Z(e),Q(l)},ds=()=>{f||L(fe(g.seconds??0)),w.refetch(),je.refetch(),I!==null&&ie.refetch(),O&&R.refetch()},A=e=>{const l=new URLSearchParams;b&&l.set("start_date",b),S&&l.set("end_date",S);for(const[i,r]of Object.entries(e))r&&l.set(i,r);c(`/activity?${l.toString()}`)},m=e=>e.length===1?e[0]:void 0,us=a&&a.request_count>0?a.error_count/a.request_count:0,Ce=((t==null?void 0:t.by_source)??[]).filter(e=>!e.is_other).length>1,ms=Ce||O==="source",p=(t==null?void 0:t.series)??[],V=p.length>1,Le=a==null?void 0:a.billed_input_tokens,X=a===void 0?null:Le!==void 0?Le+(a.billed_output_tokens??a.completion_tokens):a.total_tokens,hs=j===void 0?null:j.billed_input_tokens!==void 0?j.billed_input_tokens+(j.billed_output_tokens??j.completion_tokens):j.total_tokens,Te=e=>{let l=0,i=0,r=0;for(const x of e)l+=x.input_tokens??0,i+=x.cache_read_tokens??0,r+=x.cache_write_tokens??0;return{input:l,read:i,write:r}},K=Te(p),J=K.input>0?K.read/K.input:null,he=Te(I!==null?(($e=ie.data)==null?void 0:$e.series)??[]:[]),Ee=he.input>0?he.read/he.input:void 0,Re=e=>e.input_tokens!==void 0?e.input_tokens+(e.output_tokens??0):e.tokens,pe=p.some(e=>(e.input_tokens??0)>0),Fe=p.some(e=>(e.errors??0)>0),F=u.useMemo(()=>{var i;const e=p.map(r=>r.bucket_start);if(q){const r=R.data;if(!r)return{series:[],data:[]};const x=r.groups.map((d,C)=>({key:`g${C}`,label:d.is_other?"Other":d.key===null?"(unknown)":q==="api_key_id"?ss(d.key):d.key,color:d.is_other?zs:ze[C%ze.length]})),N=new Map(r.groups.map((d,C)=>[`${d.is_other}|${d.key}`,`g${C}`])),P=new Map(e.map(d=>[d,{x:d,...Object.fromEntries(x.map(C=>[C.key,0]))}]));for(const d of r.points){const C=N.get(`${d.is_other}|${d.key}`),Be=P.get(d.bucket_start);!C||!Be||(Be[C]=h==="cost"?d.cost:h==="tokens"?d.tokens:d.requests)}return{series:x,data:[...P.values()]}}return h==="tokens"&&pe?{series:Is,data:p.map(r=>{const x=r.input_tokens??0,N=r.cache_read_tokens??0,P=r.cache_write_tokens??0;return{x:r.bucket_start,fresh:Math.max(0,x-N-P),cache_read:N,cache_write:P,output:r.output_tokens??0}})}:h==="requests"&&Fe?{series:Ks,data:p.map(r=>{const x=Math.min(r.errors??0,r.requests);return{x:r.bucket_start,success:r.requests-x,errors:x}})}:{series:[{key:h,label:((i=Ke.find(r=>r.key===h))==null?void 0:i.label)??h,color:"var(--otari-brand)"}],data:p.map(r=>({x:r.bucket_start,[h]:h==="cost"?r.cost:h==="tokens"?Re(r):r.requests}))}},[p,q,R.data,h,pe,Fe,y.data]),Oe=Gs(h),ps=w.isLoading||!!q&&R.isLoading,xs=F.data.length?Math.max(...F.data.map(e=>F.series.reduce((l,i)=>l+(typeof e[i.key]=="number"?e[i.key]:0),0))):0,vs=p.map(e=>e.bucket_start),gs=(e,l)=>{const i=As(vs,e,l,T);i&&cs(i.startIso,i.endIso)},xe=[{key:"model",label:"Model",rows:(t==null?void 0:t.by_model)??[],drill:e=>A({model:e,user_id:m(_),api_key_id:m(k)})},{key:"user",label:"User",rows:(t==null?void 0:t.by_user)??[],drill:e=>A({user_id:e,model:m(o),api_key_id:m(k)})}],fs=[{key:"source_label",label:"Session",rows:(t==null?void 0:t.by_source_label)??[],unknownLabel:"(no session)",drill:e=>A({source_label:e,model:m(o),user_id:m(_),api_key_id:m(k)})},{key:"endpoint",label:"Endpoint",rows:(t==null?void 0:t.by_endpoint)??[],drill:e=>A({endpoint:e,model:m(o),user_id:m(_),api_key_id:m(k)})},{key:"provider",label:"Provider",rows:(t==null?void 0:t.by_provider)??[],drill:e=>A({provider:e,model:m(o),user_id:m(_),api_key_id:m(k)})},{key:"source",label:"Source",rows:(t==null?void 0:t.by_source)??[],drill:e=>A({source:e,model:m(o),user_id:m(_),api_key_id:m(k)})}],qe=(t==null?void 0:t.by_tool)??[],[ve,ks]=u.useState("model"),[ee,bs]=u.useState("source_label"),z=xe.find(e=>e.key===ve)??xe[0],ge=fs.filter(e=>e.key!=="source"||Ce||ee==="source"),G=ge.find(e=>e.key===ee)??ge[0];return s.jsxs("div",{className:"flex flex-col gap-6",children:[s.jsx(Cs,{title:"Usage & analytics",description:"Spend, tokens, cache use, and request volume over time. Group the chart by model, user, key, or source, and click a breakdown row to drill into the request log."}),s.jsx(Ls,{error:w.error??(O!==""&&!ce?R.error:null)}),s.jsxs($s,{chips:os,onClearAll:rs,start:Ze.map(e=>s.jsx(D,{size:"sm",variant:!f&&g.key===e.key?"primary":"outline",onPress:()=>Ne(e),children:e.label},e.key)),end:s.jsxs(s.Fragment,{children:[s.jsxs("span",{className:"text-xs text-[var(--otari-muted)]",children:["Showing ",Ts(ls,is)," · UTC"]}),s.jsx(Es,{onRefresh:ds,isFetching:w.isFetching,updatedAt:w.dataUpdatedAt})]}),children:[s.jsx(be,{label:"User",values:_,onChange:ne,options:we,placeholder:"All users"}),s.jsx(be,{label:"Model",values:o,onChange:U,options:ts,placeholder:"All models"}),s.jsx(be,{label:"API key",values:k,onChange:le,options:de,placeholder:"All keys"})]}),ns?s.jsx(Rs,{title:"No usage yet",description:"Once the gateway serves requests, spend and volume appear here."}):s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-5",children:[s.jsx(H,{label:"Tracked cost",value:a?re(a.cost):"—",hint:a?s.jsxs("span",{className:"text-[var(--otari-muted)]",children:[s.jsx(te,{fraction:_e}),a.unpriced_requests?`${_e!==null?" · ":""}${$(a.unpriced_requests)} unpriced`:null]}):null,chart:V?s.jsx(ae,{values:p.map(e=>e.cost),ariaLabel:"Spend trend over the selected window"}):void 0}),s.jsx(H,{label:"Requests",value:a?$(a.request_count):"—",hint:a?s.jsxs("span",{className:"text-[var(--otari-muted)]",children:[Me(us)," errors",j?s.jsxs(s.Fragment,{children:[" · ",s.jsx(te,{fraction:se(a.request_count,j.request_count)})]}):null]}):null,chart:V?s.jsx(ae,{values:p.map(e=>e.requests),ariaLabel:"Request volume trend over the selected window"}):void 0}),s.jsx(H,{label:"Tokens (billed)",value:X!==null?W(X):"—",hint:X!==null?s.jsx(te,{fraction:se(X,hs??void 0)}):null,chart:V?s.jsx(ae,{values:p.map(Re),ariaLabel:"Billed token trend over the selected window"}):void 0}),s.jsx(H,{label:"Cache hit rate",value:J!==null?Me(J):"—",hint:a?s.jsxs("span",{className:"text-[var(--otari-muted)]",children:[J!==null&&Ee!==void 0?s.jsxs(s.Fragment,{children:[s.jsx(te,{fraction:se(J,Ee)})," · "]}):null,W(K.read)," read · ",W(K.write)," written"]}):null,chart:V&&pe?s.jsx(ae,{values:p.map(e=>(e.input_tokens??0)>0?(e.cache_read_tokens??0)/(e.input_tokens??1):0),ariaLabel:"Cache hit rate trend over the selected window"}):void 0}),s.jsx(H,{label:"Avg latency",value:a?Bs(a.avg_latency_ms):"—"})]}),s.jsxs("div",{className:"flex flex-col gap-3 rounded-xl border border-[var(--otari-line)] bg-[var(--otari-surface)] p-4",children:[s.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[s.jsx("div",{className:"inline-flex gap-1.5",children:Ke.map(e=>s.jsx(D,{size:"sm",variant:h===e.key?"primary":"outline","aria-pressed":h===e.key,onPress:()=>Ve(e.key),children:e.label},e.key))}),s.jsxs("div",{className:"flex items-center gap-2",children:[f?s.jsx(D,{size:"sm",variant:"ghost",onPress:()=>Ne(g),children:"Reset zoom"}):null,w.isFetching||q&&R.isFetching?s.jsx(Ue,{size:"sm"}):null,s.jsx(Fs,{ariaLabel:"Group by",value:O,onChange:e=>Xe(e),options:Us.filter(e=>e.value!=="source"||ms).map(e=>({value:e.value,label:e.value?`By ${e.label.toLowerCase()}`:"No grouping"}))})]})]}),ce?s.jsx("div",{className:"rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800",children:"The running gateway predates grouped series, so the chart shows ungrouped totals. Restart the gateway on this build to enable grouping."}):null,s.jsx(Ps,{series:F.series}),ps?s.jsx("div",{className:"flex h-64 items-center justify-center",children:s.jsx(Ue,{size:"sm"})}):F.data.length===0?s.jsx("div",{className:"flex h-64 items-center justify-center text-sm text-[var(--otari-muted)]",children:"No data in this range."}):s.jsxs("figure",{className:"flex flex-col gap-2",children:[s.jsx(Ds,{data:F.data,series:F.series,formatValue:Oe,formatXTick:e=>Ms(e,T),ariaLabel:`${h} per ${T}${q?`, grouped by ${q}`:""}`,height:260,showYAxis:!0,showTotal:!0,onSelectRange:gs}),s.jsxs("figcaption",{className:"text-xs text-[var(--otari-muted)]",children:[Oe(xs)," peak · ",F.data.length," ",T==="hour"?"hours":"days"," (times in UTC) · drag across the chart to zoom"]})]})]}),s.jsxs("div",{className:"grid gap-6 xl:grid-cols-2",children:[s.jsxs("div",{className:"flex flex-col gap-3",children:[s.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[s.jsxs("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Spend by ",z.label.toLowerCase()]}),s.jsx("div",{className:"inline-flex gap-1.5",children:xe.map(e=>s.jsx(D,{size:"sm",variant:ve===e.key?"primary":"outline","aria-pressed":ve===e.key,onPress:()=>ks(e.key),children:e.label},e.key))})]}),s.jsx(We,{dimensionLabel:z.label,rows:z.rows,totalCost:(a==null?void 0:a.cost)??0,emptyLabel:ue?"No usage matches these filters.":"No usage recorded yet.",unknownLabel:z.unknownLabel,onDrill:z.drill,loading:w.isLoading})]}),s.jsxs("div",{className:"flex flex-col gap-3",children:[s.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[s.jsxs("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Spend by ",G.label.toLowerCase()]}),s.jsx("div",{className:"inline-flex gap-1.5",children:ge.map(e=>s.jsx(D,{size:"sm",variant:ee===e.key?"primary":"outline","aria-pressed":ee===e.key,onPress:()=>bs(e.key),children:e.label},e.key))})]}),s.jsx(We,{dimensionLabel:G.label,rows:G.rows,totalCost:(a==null?void 0:a.cost)??0,emptyLabel:ue?"No usage matches these filters.":"No usage recorded yet.",unknownLabel:G.unknownLabel,onDrill:G.drill,loading:w.isLoading})]})]}),qe.length?s.jsxs("div",{className:"rounded-2xl border border-[var(--otari-line)] bg-[var(--otari-surface)] p-4",children:[s.jsxs("div",{className:"mb-3 flex flex-col gap-1",children:[s.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Gateway-run tools"}),s.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"Tools Otari ran itself, billed per call. MCP tools are not listed here: their names come from your own server, so they appear on each request instead."})]}),s.jsx(Hs,{rows:qe,totalCost:(a==null?void 0:a.cost)??0,onDrill:e=>A({tool:e}),loading:w.isLoading})]}):null]})]})}export{at as UsagePage}; diff --git a/src/gateway/static/dashboard/assets/UsagePage-tyubYvXE.js b/src/gateway/static/dashboard/assets/UsagePage-tyubYvXE.js deleted file mode 100644 index 113b5ff14..000000000 --- a/src/gateway/static/dashboard/assets/UsagePage-tyubYvXE.js +++ /dev/null @@ -1 +0,0 @@ -import{j as s}from"./tanstack-query-1t81HyiD.js";import{i as gs,r as u}from"./react-dgEcD0HR.js";import{u as ys,c as fs,p as ke,i as _s,g as be,au as js,av as ws,a5 as ee,aw as We,P as Ss,E as Ns,o as ge,f as Cs,R as Ls,ax as Ye,z as Ts,a6 as z,a8 as se,a7 as ae,aa as Ue,ay as G,F as Es,h as Rs,ac as Fs,r as Os}from"./index-D-R1nuKP.js";import{S as te,C as qs,T as As}from"./charts-D6upG8fh.js";import{D as Ze}from"./DataTable-BHrpJHmX.js";import{F as Ps}from"./FilterChips-CTE3I1G3.js";import{B as q,S as Me}from"./heroui-DhloIxuc.js";import"./recharts-EeW53z2i.js";function A(i){return i.toLocaleString()}function Ds(i){return i===null?"—":i<1e3?`${Math.round(i)} ms`:`${(i/1e3).toFixed(2)} s`}function Bs(i,p){const g=new Date(i);return Number.isNaN(g.getTime())?i:p==="hour"?g.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit",timeZone:"UTC"}):g.toLocaleDateString(void 0,{month:"short",day:"numeric",timeZone:"UTC"})}const $e=Rs(Ye,We),ye=15,Ie=[{key:"cost",label:"Cost"},{key:"tokens",label:"Tokens"},{key:"requests",label:"Requests"}],Us=[{value:"",label:"None"},{value:"model",label:"Model"},{value:"user_id",label:"User"},{value:"api_key_id",label:"API key"},{value:"source",label:"Source"}],Ms=[{key:"fresh",label:"Fresh input",color:"var(--otari-ink)"},{key:"cache_read",label:"Cache read",color:"var(--otari-brand)"},{key:"cache_write",label:"Cache write",color:"var(--otari-brand-soft)"},{key:"output",label:"Output",color:"var(--otari-brand-dark)"}],$s=[{key:"success",label:"Succeeded",color:"var(--otari-brand)"},{key:"errors",label:"Failed",color:"var(--otari-danger)"}],Ke=["var(--otari-cat-1)","var(--otari-cat-2)","var(--otari-cat-3)","var(--otari-cat-4)","var(--otari-cat-5)","var(--otari-cat-6)","var(--otari-cat-7)","var(--otari-cat-8)"],Is="var(--otari-cat-other)";function Ks(i){return i==="cost"?ae:i==="tokens"?G:A}const ze="__other__",Ge="__unknown__";function He({dimensionLabel:i,rows:p,totalCost:g,emptyLabel:v,unknownLabel:P="(unknown)",onDrill:n,loading:N}){const[x,D]=u.useState(!1),H=x?p:p.slice(0,ye),W=p.length-H.length,re=r=>r.is_other?ze:r.key??Ge,Y=[{id:"name",header:i,isRowHeader:!0,cell:r=>{const B=g>0?r.cost/g:0;return s.jsxs("div",{className:"flex flex-col gap-1",children:[s.jsx("span",{className:"truncate text-[var(--otari-ink)]",children:r.is_other?`Other (${r.requests.toLocaleString()} req)`:r.key===null?P:r.key}),s.jsx("span",{className:"h-1 w-full overflow-hidden rounded-full bg-[var(--otari-line)]",children:s.jsx("span",{className:"block h-full rounded-full bg-[var(--otari-brand)]",style:{width:`${Math.min(100,B*100)}%`}})})]})}},{id:"requests",header:"Requests",align:"end",cell:r=>s.jsx("span",{className:"text-[var(--otari-muted)]",children:A(r.requests)})},{id:"tokens",header:"Tokens",align:"end",cell:r=>s.jsx("span",{className:"text-[var(--otari-muted)]",children:G(r.tokens)})},{id:"spend",header:"Spend",align:"end",cell:r=>s.jsx("span",{className:"text-[var(--otari-ink)]",children:ae(r.cost)})}];return s.jsxs("div",{className:"flex flex-col gap-2",children:[s.jsx(Ze,{ariaLabel:`Spend by ${i.toLowerCase()}`,columns:Y,rows:H,getRowKey:re,isLoading:N,emptyContent:v,onRowAction:r=>{r!==ze&&r!==Ge&&n(r)}}),!N&&W>0?s.jsxs(q,{size:"sm",variant:"ghost",onPress:()=>D(!0),children:["Show all ",p.length]}):null,!N&&x&&p.length>ye?s.jsxs(q,{size:"sm",variant:"ghost",onPress:()=>D(!1),children:["Show top ",ye]}):null]})}function zs({rows:i,totalCost:p,onDrill:g,loading:v}){const P=[{id:"tool",header:"Tool",isRowHeader:!0,cell:n=>{const N=p>0?n.cost/p:0;return s.jsxs("div",{className:"flex flex-col gap-1",children:[s.jsx("span",{className:"truncate text-[var(--otari-ink)]",children:n.tool.replaceAll("_"," ")}),s.jsx("span",{className:"h-1 w-full overflow-hidden rounded-full bg-[var(--otari-line)]",children:s.jsx("span",{className:"block h-full rounded-full bg-[var(--otari-brand)]",style:{width:`${Math.min(100,N*100)}%`}})})]})}},{id:"calls",header:"Calls",align:"end",cell:n=>s.jsx("span",{className:"text-[var(--otari-muted)]",children:A(n.calls)})},{id:"failed",header:"Failed",align:"end",cell:n=>s.jsx("span",{className:n.errors?"text-red-700":"text-[var(--otari-muted)]",children:A(n.errors)})},{id:"requests",header:"Requests",align:"end",cell:n=>s.jsx("span",{className:"text-[var(--otari-muted)]",children:A(n.requests)})},{id:"spend",header:"Spend",align:"end",cell:n=>s.jsx("span",{className:"text-[var(--otari-ink)]",children:ae(n.cost)})}];return s.jsx(Ze,{ariaLabel:"Spend by gateway-run tool",columns:P,rows:i,getRowKey:n=>n.tool,isLoading:v,emptyContent:"No gateway-run tool calls in this range.",onRowAction:n=>g(String(n))})}const Gs=["model","user","source_label","endpoint","provider","source","tool"],Hs=["model"];function st(){var qe,Ae,Pe,De;const i=gs(),p=ys(),g=fs(),[v,P]=u.useState($e),[n,N]=u.useState(()=>ke($e.seconds??0)),[x,D]=u.useState(!1),[H,W]=u.useState(),[re,Y]=u.useState(),[r,B]=u.useState(""),[y,oe]=u.useState(""),[k,ne]=u.useState(""),[m,Qe]=u.useState("cost"),[R,Ve]=u.useState(""),b=x?H:n,w=x?re:void 0,C=x?b?_s(b,w):"day":v.bucket,L=u.useMemo(()=>({start_date:b,end_date:w,model:r.trim()||void 0,user_id:y||void 0,api_key_id:k||void 0}),[b,w,r,y,k]),U=u.useMemo(()=>{if(x){if(!b||!w)return null;const e=new Date(w).getTime()-new Date(b).getTime();return e>0?{...L,start_date:new Date(new Date(b).getTime()-e).toISOString(),end_date:b}:null}return!n||v.seconds===null?null:{...L,start_date:new Date(new Date(n).getTime()-v.seconds*1e3).toISOString(),end_date:n}},[x,b,w,L,v.seconds,n]),j=be(L,C,Gs),le=be(U??L,C,Fs,U!==null),T=js(L,C,R||null),ie=R!==""&&T.error instanceof ws&&T.error.status===404,F=ie?"":R,t=j.data,a=t==null?void 0:t.totals,f=U!==null?(qe=le.data)==null?void 0:qe.totals:void 0,fe=a?ee(a.cost,f==null?void 0:f.cost):null,Xe=u.useMemo(()=>({...L,model:void 0}),[L]),_e=be(Xe,C,Hs),ce=((Pe=(Ae=_e.data)==null?void 0:Ae.by_model)==null?void 0:Pe.filter(e=>!e.is_other&&e.key!==null).map(e=>e.key))??[],je=(p.data??[]).map(e=>({value:e.user_id,label:e.alias?`${e.alias} (${e.user_id})`:e.user_id})),de=(g.data??[]).map(e=>({value:e.id,label:e.key_name??`${e.id.slice(0,8)}…`})),Je=e=>{var l;return((l=de.find(c=>c.value===e))==null?void 0:l.label)??e},es=(r&&!ce.includes(r)?[r,...ce]:ce).map(e=>({value:e,label:e})),ss=x||v.key!==We,ue=!!(r.trim()||y||k||ss),we=(e,l)=>{var c;return((c=e.find(o=>o.value===l))==null?void 0:c.label)??l},ts=()=>{B(""),oe(""),ne("")},as=[...y?[{key:"user",label:"User",value:we(je,y),onClear:()=>oe("")}]:[],...r.trim()?[{key:"model",label:"Model",value:r.trim(),onClear:()=>B("")}]:[],...k?[{key:"key",label:"API key",value:we(de,k),onClear:()=>ne("")}]:[]],rs=!!(t&&a&&a.request_count===0&&!ue),os=(t==null?void 0:t.start_date)??b,ns=(t==null?void 0:t.end_date)??w,Se=e=>{D(!1),P(e),N(ke(e.seconds??0)),W(void 0),Y(void 0)},ls=(e,l)=>{D(!0),W(e),Y(l)},is=()=>{x||N(ke(v.seconds??0)),j.refetch(),_e.refetch(),U!==null&&le.refetch(),R&&T.refetch()},O=e=>{const l=new URLSearchParams;b&&l.set("start_date",b),w&&l.set("end_date",w);for(const[c,o]of Object.entries(e))o&&l.set(c,o);i(`/activity?${l.toString()}`)},cs=a&&a.request_count>0?a.error_count/a.request_count:0,Ne=((t==null?void 0:t.by_source)??[]).filter(e=>!e.is_other).length>1,ds=Ne||R==="source",h=(t==null?void 0:t.series)??[],Z=h.length>1,Ce=a==null?void 0:a.billed_input_tokens,Q=a===void 0?null:Ce!==void 0?Ce+(a.billed_output_tokens??a.completion_tokens):a.total_tokens,us=f===void 0?null:f.billed_input_tokens!==void 0?f.billed_input_tokens+(f.billed_output_tokens??f.completion_tokens):f.total_tokens,Le=e=>{let l=0,c=0,o=0;for(const _ of e)l+=_.input_tokens??0,c+=_.cache_read_tokens??0,o+=_.cache_write_tokens??0;return{input:l,read:c,write:o}},M=Le(h),V=M.input>0?M.read/M.input:null,me=Le(U!==null?((De=le.data)==null?void 0:De.series)??[]:[]),Te=me.input>0?me.read/me.input:void 0,Ee=e=>e.input_tokens!==void 0?e.input_tokens+(e.output_tokens??0):e.tokens,he=h.some(e=>(e.input_tokens??0)>0),Re=h.some(e=>(e.errors??0)>0),E=u.useMemo(()=>{var c;const e=h.map(o=>o.bucket_start);if(F){const o=T.data;if(!o)return{series:[],data:[]};const _=o.groups.map((d,S)=>({key:`g${S}`,label:d.is_other?"Other":d.key===null?"(unknown)":F==="api_key_id"?Je(d.key):d.key,color:d.is_other?Is:Ke[S%Ke.length]})),J=new Map(o.groups.map((d,S)=>[`${d.is_other}|${d.key}`,`g${S}`])),K=new Map(e.map(d=>[d,{x:d,...Object.fromEntries(_.map(S=>[S.key,0]))}]));for(const d of o.points){const S=J.get(`${d.is_other}|${d.key}`),Be=K.get(d.bucket_start);!S||!Be||(Be[S]=m==="cost"?d.cost:m==="tokens"?d.tokens:d.requests)}return{series:_,data:[...K.values()]}}return m==="tokens"&&he?{series:Ms,data:h.map(o=>{const _=o.input_tokens??0,J=o.cache_read_tokens??0,K=o.cache_write_tokens??0;return{x:o.bucket_start,fresh:Math.max(0,_-J-K),cache_read:J,cache_write:K,output:o.output_tokens??0}})}:m==="requests"&&Re?{series:$s,data:h.map(o=>{const _=Math.min(o.errors??0,o.requests);return{x:o.bucket_start,success:o.requests-_,errors:_}})}:{series:[{key:m,label:((c=Ie.find(o=>o.key===m))==null?void 0:c.label)??m,color:"var(--otari-brand)"}],data:h.map(o=>({x:o.bucket_start,[m]:m==="cost"?o.cost:m==="tokens"?Ee(o):o.requests}))}},[h,F,T.data,m,he,Re,g.data]),Fe=Ks(m),ms=j.isLoading||!!F&&T.isLoading,hs=E.data.length?Math.max(...E.data.map(e=>E.series.reduce((l,c)=>l+(typeof e[c.key]=="number"?e[c.key]:0),0))):0,ps=h.map(e=>e.bucket_start),vs=(e,l)=>{const c=Os(ps,e,l,C);c&&ls(c.startIso,c.endIso)},pe=[{key:"model",label:"Model",rows:(t==null?void 0:t.by_model)??[],drill:e=>O({model:e,user_id:y||void 0,api_key_id:k||void 0})},{key:"user",label:"User",rows:(t==null?void 0:t.by_user)??[],drill:e=>O({user_id:e,model:r.trim()||void 0,api_key_id:k||void 0})}],xs=[{key:"source_label",label:"Session",rows:(t==null?void 0:t.by_source_label)??[],unknownLabel:"(no session)",drill:e=>O({source_label:e,model:r.trim()||void 0,user_id:y||void 0,api_key_id:k||void 0})},{key:"endpoint",label:"Endpoint",rows:(t==null?void 0:t.by_endpoint)??[],drill:e=>O({endpoint:e,model:r.trim()||void 0,user_id:y||void 0,api_key_id:k||void 0})},{key:"provider",label:"Provider",rows:(t==null?void 0:t.by_provider)??[],drill:e=>O({provider:e,model:r.trim()||void 0,user_id:y||void 0,api_key_id:k||void 0})},{key:"source",label:"Source",rows:(t==null?void 0:t.by_source)??[],drill:e=>O({source:e,model:r.trim()||void 0,user_id:y||void 0,api_key_id:k||void 0})}],Oe=(t==null?void 0:t.by_tool)??[],[ve,ks]=u.useState("model"),[X,bs]=u.useState("source_label"),$=pe.find(e=>e.key===ve)??pe[0],xe=xs.filter(e=>e.key!=="source"||Ne||X==="source"),I=xe.find(e=>e.key===X)??xe[0];return s.jsxs("div",{className:"flex flex-col gap-6",children:[s.jsx(Ss,{title:"Usage & analytics",description:"Spend, tokens, cache use, and request volume over time. Group the chart by model, user, key, or source, and click a breakdown row to drill into the request log."}),s.jsx(Ns,{error:j.error??(R!==""&&!ie?T.error:null)}),s.jsxs(Ps,{chips:as,onClearAll:ts,start:Ye.map(e=>s.jsx(q,{size:"sm",variant:!x&&v.key===e.key?"primary":"outline",onPress:()=>Se(e),children:e.label},e.key)),end:s.jsxs(s.Fragment,{children:[s.jsxs("span",{className:"text-xs text-[var(--otari-muted)]",children:["Showing ",Cs(os,ns)," · UTC"]}),s.jsx(Ls,{onRefresh:is,isFetching:j.isFetching,updatedAt:j.dataUpdatedAt})]}),children:[s.jsx(ge,{label:"User",value:y,onChange:oe,options:je,placeholder:"All users"}),s.jsx(ge,{label:"Model",value:r,onChange:B,options:es,placeholder:"All models"}),s.jsx(ge,{label:"API key",value:k,onChange:ne,options:de,placeholder:"All keys"})]}),rs?s.jsx(Ts,{title:"No usage yet",description:"Once the gateway serves requests, spend and volume appear here."}):s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-5",children:[s.jsx(z,{label:"Tracked cost",value:a?ae(a.cost):"—",hint:a?s.jsxs("span",{className:"text-[var(--otari-muted)]",children:[s.jsx(se,{fraction:fe}),a.unpriced_requests?`${fe!==null?" · ":""}${A(a.unpriced_requests)} unpriced`:null]}):null,chart:Z?s.jsx(te,{values:h.map(e=>e.cost),ariaLabel:"Spend trend over the selected window"}):void 0}),s.jsx(z,{label:"Requests",value:a?A(a.request_count):"—",hint:a?s.jsxs("span",{className:"text-[var(--otari-muted)]",children:[Ue(cs)," errors",f?s.jsxs(s.Fragment,{children:[" · ",s.jsx(se,{fraction:ee(a.request_count,f.request_count)})]}):null]}):null,chart:Z?s.jsx(te,{values:h.map(e=>e.requests),ariaLabel:"Request volume trend over the selected window"}):void 0}),s.jsx(z,{label:"Tokens (billed)",value:Q!==null?G(Q):"—",hint:Q!==null?s.jsx(se,{fraction:ee(Q,us??void 0)}):null,chart:Z?s.jsx(te,{values:h.map(Ee),ariaLabel:"Billed token trend over the selected window"}):void 0}),s.jsx(z,{label:"Cache hit rate",value:V!==null?Ue(V):"—",hint:a?s.jsxs("span",{className:"text-[var(--otari-muted)]",children:[V!==null&&Te!==void 0?s.jsxs(s.Fragment,{children:[s.jsx(se,{fraction:ee(V,Te)})," · "]}):null,G(M.read)," read · ",G(M.write)," written"]}):null,chart:Z&&he?s.jsx(te,{values:h.map(e=>(e.input_tokens??0)>0?(e.cache_read_tokens??0)/(e.input_tokens??1):0),ariaLabel:"Cache hit rate trend over the selected window"}):void 0}),s.jsx(z,{label:"Avg latency",value:a?Ds(a.avg_latency_ms):"—"})]}),s.jsxs("div",{className:"flex flex-col gap-3 rounded-xl border border-[var(--otari-line)] bg-[var(--otari-surface)] p-4",children:[s.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[s.jsx("div",{className:"inline-flex gap-1.5",children:Ie.map(e=>s.jsx(q,{size:"sm",variant:m===e.key?"primary":"outline","aria-pressed":m===e.key,onPress:()=>Qe(e.key),children:e.label},e.key))}),s.jsxs("div",{className:"flex items-center gap-2",children:[x?s.jsx(q,{size:"sm",variant:"ghost",onPress:()=>Se(v),children:"Reset zoom"}):null,j.isFetching||F&&T.isFetching?s.jsx(Me,{size:"sm"}):null,s.jsx(Es,{ariaLabel:"Group by",value:R,onChange:e=>Ve(e),options:Us.filter(e=>e.value!=="source"||ds).map(e=>({value:e.value,label:e.value?`By ${e.label.toLowerCase()}`:"No grouping"}))})]})]}),ie?s.jsx("div",{className:"rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800",children:"The running gateway predates grouped series, so the chart shows ungrouped totals. Restart the gateway on this build to enable grouping."}):null,s.jsx(qs,{series:E.series}),ms?s.jsx("div",{className:"flex h-64 items-center justify-center",children:s.jsx(Me,{size:"sm"})}):E.data.length===0?s.jsx("div",{className:"flex h-64 items-center justify-center text-sm text-[var(--otari-muted)]",children:"No data in this range."}):s.jsxs("figure",{className:"flex flex-col gap-2",children:[s.jsx(As,{data:E.data,series:E.series,formatValue:Fe,formatXTick:e=>Bs(e,C),ariaLabel:`${m} per ${C}${F?`, grouped by ${F}`:""}`,height:260,showYAxis:!0,showTotal:!0,onSelectRange:vs}),s.jsxs("figcaption",{className:"text-xs text-[var(--otari-muted)]",children:[Fe(hs)," peak · ",E.data.length," ",C==="hour"?"hours":"days"," (times in UTC) · drag across the chart to zoom"]})]})]}),s.jsxs("div",{className:"grid gap-6 xl:grid-cols-2",children:[s.jsxs("div",{className:"flex flex-col gap-3",children:[s.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[s.jsxs("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Spend by ",$.label.toLowerCase()]}),s.jsx("div",{className:"inline-flex gap-1.5",children:pe.map(e=>s.jsx(q,{size:"sm",variant:ve===e.key?"primary":"outline","aria-pressed":ve===e.key,onPress:()=>ks(e.key),children:e.label},e.key))})]}),s.jsx(He,{dimensionLabel:$.label,rows:$.rows,totalCost:(a==null?void 0:a.cost)??0,emptyLabel:ue?"No usage matches these filters.":"No usage recorded yet.",unknownLabel:$.unknownLabel,onDrill:$.drill,loading:j.isLoading})]}),s.jsxs("div",{className:"flex flex-col gap-3",children:[s.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[s.jsxs("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Spend by ",I.label.toLowerCase()]}),s.jsx("div",{className:"inline-flex gap-1.5",children:xe.map(e=>s.jsx(q,{size:"sm",variant:X===e.key?"primary":"outline","aria-pressed":X===e.key,onPress:()=>bs(e.key),children:e.label},e.key))})]}),s.jsx(He,{dimensionLabel:I.label,rows:I.rows,totalCost:(a==null?void 0:a.cost)??0,emptyLabel:ue?"No usage matches these filters.":"No usage recorded yet.",unknownLabel:I.unknownLabel,onDrill:I.drill,loading:j.isLoading})]})]}),Oe.length?s.jsxs("div",{className:"rounded-2xl border border-[var(--otari-line)] bg-[var(--otari-surface)] p-4",children:[s.jsxs("div",{className:"mb-3 flex flex-col gap-1",children:[s.jsx("h2",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Gateway-run tools"}),s.jsx("p",{className:"text-xs text-[var(--otari-muted)]",children:"Tools Otari ran itself, billed per call. MCP tools are not listed here: their names come from your own server, so they appear on each request instead."})]}),s.jsx(zs,{rows:Oe,totalCost:(a==null?void 0:a.cost)??0,onDrill:e=>O({tool:e}),loading:j.isLoading})]}):null]})]})}export{st as UsagePage}; diff --git a/src/gateway/static/dashboard/assets/UsersPage-Be1Tcz9b.js b/src/gateway/static/dashboard/assets/UsersPage-C_yR1ElB.js similarity index 92% rename from src/gateway/static/dashboard/assets/UsersPage-Be1Tcz9b.js rename to src/gateway/static/dashboard/assets/UsersPage-C_yR1ElB.js index 2a809a24e..8a3393a4e 100644 --- a/src/gateway/static/dashboard/assets/UsersPage-Be1Tcz9b.js +++ b/src/gateway/static/dashboard/assets/UsersPage-C_yR1ElB.js @@ -1 +1 @@ -import{j as e}from"./tanstack-query-1t81HyiD.js";import{r}from"./react-dgEcD0HR.js";import{u as Q,H as M,L as K,az as X,q as Y,P as Z,E as A,z as ee,aA as se,F as te}from"./index-D-R1nuKP.js";import{u as ae,r as ne,B as re}from"./tableSelection-B1umVgqc.js";import{C as le}from"./ConfirmDialog-mbnZRETP.js";import{D as ie}from"./DataTable-BHrpJHmX.js";import{F as B}from"./Field-GEMwIhf7.js";import{a as de,M as R}from"./ModelScopeControl-BBYX_HiM.js";import{g as I,B as d,d as S,A as f}from"./heroui-DhloIxuc.js";const oe=new Intl.NumberFormat(void 0,{style:"currency",currency:"USD",maximumFractionDigits:4});function U(t){return oe.format(t)}const ce=t=>t.user_id,D=t=>t.startsWith("apikey-");function H(t){return t.split("-")[0]}function z(t){return t.name??H(t.budget_id)}function $({value:t,onChange:l,budgets:n}){return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("label",{htmlFor:"user-budget",className:"text-sm font-medium text-[var(--otari-ink)]",children:"Budget"}),e.jsxs("select",{id:"user-budget",value:t??"",onChange:a=>l(a.target.value||null),className:"w-full rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)]",children:[e.jsx("option",{value:"",children:"No budget (unlimited)"}),n.map(a=>e.jsxs("option",{value:a.budget_id,children:[z(a),a.max_budget===null?" · no limit":` · ${U(a.max_budget)}`]},a.budget_id))]}),e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"The spending limit this user is held to. Manage budgets on the Budgets page."})]})}function ue({onClose:t}){const l=se(),n=M(),[a,i]=r.useState(""),[o,u]=r.useState(""),[c,g]=r.useState(null),[p,m]=r.useState(null),[b,j]=r.useState(!0),x=()=>{if(l.isPending||!b||a.trim()==="")return;const h={user_id:a.trim(),alias:o.trim()||null,budget_id:c,allowed_models:p};l.mutate(h,{onSuccess:t})};return e.jsx(S,{children:e.jsxs(S.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsx("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Create user"}),e.jsx(A,{error:l.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(B,{label:"User ID",value:a,onChange:i,placeholder:"alice@example.com",isRequired:!0,autoFocus:!0,description:"The identifier callers send as the `user` field; spend and budgets track against it."}),e.jsx(B,{label:"Alias (optional)",value:o,onChange:u,placeholder:"Alice"})]}),e.jsx($,{value:c,onChange:g,budgets:n.data??[]}),e.jsx(R,{title:"Model access (default for this user's keys)",description:"The models this user's keys may list and call by default. A key can narrow this, but never exceed it.",initial:null,onChange:(h,k)=>{m(h),j(k)}}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(d,{variant:"primary",isDisabled:l.isPending||!b||a.trim()==="",onPress:x,children:l.isPending?"Creating…":"Create user"}),e.jsx(d,{variant:"ghost",onPress:t,children:"Cancel"})]})]})})}function ge({user:t,onClose:l}){const n=K(),a=M(),[i,o]=r.useState(t.alias??""),[u,c]=r.useState(t.budget_id),[g,p]=r.useState(t.allowed_models),[m,b]=r.useState(!0),j=()=>{if(n.isPending||!m)return;const x={alias:i.trim()||null,budget_id:u,allowed_models:g};n.mutate({id:t.user_id,body:x},{onSuccess:l})};return e.jsx(S,{children:e.jsxs(S.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsxs("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Edit ",e.jsx("code",{children:t.user_id})]}),e.jsx(A,{error:n.error}),e.jsx(B,{label:"Alias",value:i,onChange:o,placeholder:"Alice"}),e.jsx($,{value:u,onChange:c,budgets:a.data??[]}),e.jsx(R,{title:"Model access (default for this user's keys)",description:"The models this user's keys may list and call by default. A key can narrow this, but never exceed it.",initial:t.allowed_models,onChange:(x,h)=>{p(x),b(h)}}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(d,{variant:"primary",isDisabled:n.isPending||!m,onPress:j,children:n.isPending?"Saving…":"Save changes"}),e.jsx(d,{variant:"ghost",onPress:l,children:"Cancel"})]})]})})}function me({trigger:t,message:l,confirmLabel:n,isPending:a,onConfirm:i}){const[o,u]=r.useState(!1);return o?e.jsxs("div",{className:"flex flex-col items-end gap-1.5 rounded-lg border border-amber-200 bg-amber-50 p-2 text-right",children:[e.jsx("span",{className:"max-w-xs text-xs text-amber-800",children:l}),e.jsxs("span",{className:"inline-flex gap-1",children:[e.jsx(d,{size:"sm",variant:"danger",isDisabled:a,onPress:i,children:n}),e.jsx(d,{size:"sm",variant:"ghost",isDisabled:a,onPress:()=>u(!1),children:"Cancel"})]})]}):e.jsx(d,{size:"sm",variant:"danger-soft",onPress:()=>u(!0),children:t})}function xe({user:t}){return t.blocked?e.jsx(I,{size:"sm",color:"warning",children:"Blocked"}):e.jsx(I,{size:"sm",color:"accent",children:"Active"})}function he({allowed:t}){const{text:l,tone:n}=de(t),a=n==="danger"?"text-red-700 font-medium":n==="muted"?"text-[var(--otari-muted)]":"text-[var(--otari-brand-dark)] font-medium",i=t&&t.length>0?t.join(", "):void 0;return e.jsx("span",{className:`text-xs ${a}`,title:i,children:l})}function pe({isOpen:t,onOpenChange:l,budgets:n,count:a,isPending:i,error:o,onAssign:u}){const[c,g]=r.useState("");return r.useEffect(()=>{t&&g("")},[t]),e.jsx(f,{isOpen:t,onOpenChange:l,children:t?e.jsx(f.Backdrop,{children:e.jsx(f.Container,{placement:"center",size:"md",children:e.jsxs(f.Dialog,{children:[e.jsx(f.Header,{children:e.jsx(f.Heading,{children:"Assign budget"})}),e.jsxs(f.Body,{className:"flex flex-col gap-4",children:[e.jsxs("p",{className:"text-sm text-[var(--otari-muted)]",children:["Assign a budget to ",a," selected ",a===1?"user":"users","."]}),e.jsx(te,{label:"Budget",value:c,onChange:g,options:[{value:"",label:"Select a budget…"},...n.map(p=>({value:p.budget_id,label:z(p)}))]}),e.jsx(A,{error:o})]}),e.jsxs(f.Footer,{children:[e.jsx(d,{variant:"ghost",isDisabled:i,onPress:()=>l(!1),children:"Cancel"}),e.jsx(d,{variant:"primary",isDisabled:!c,isPending:i,onPress:()=>u(c),children:"Assign"})]})]})})}):null})}function Pe(){const t=Q(),l=M(),n=K(),a=X(),[i,o]=r.useState(!1),[u,c]=r.useState(null),[g,p]=r.useState(!1),m=t.data??[],b=t.isLoading,j=m.filter(s=>D(s.user_id)).length,x=g?m:m.filter(s=>!D(s.user_id)),h=m.find(s=>s.user_id===u)??null,k=!b&&x.length===0&&!i,P=r.useMemo(()=>new Map((l.data??[]).map(s=>[s.budget_id,s])),[l.data]),y=ae(),[q,_]=r.useState(!1),[W,N]=r.useState(!1),[E,F]=r.useState(void 0),[L,T]=r.useState(!1),G=x.map(s=>s.user_id),v=ne(y.selectedKeys,G),V=r.useCallback((s,C)=>n.mutate({id:s.user_id,body:{blocked:C}}),[n.mutate]),O=async(s,C)=>{T(!0),F(void 0);try{for(const w of v)await s(w);y.clear(),C()}catch(w){F(w)}finally{T(!1)}},J=r.useMemo(()=>[{id:"user",header:"User",isRowHeader:!0,cell:s=>e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsxs("span",{className:"inline-flex items-center gap-1.5",children:[e.jsx(Y,{value:s.user_id,label:"user id",children:e.jsx("code",{className:"text-xs font-medium text-[var(--otari-ink)]",children:s.user_id})}),D(s.user_id)?e.jsx(I,{size:"sm",color:"default",children:"virtual"}):null]}),s.alias?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:s.alias}):null]})},{id:"status",header:"Status",cell:s=>e.jsx(xe,{user:s})},{id:"budget",header:"Budget",cell:s=>s.budget_id?e.jsx("span",{className:"text-[var(--otari-muted)]",title:s.budget_id,children:P.get(s.budget_id)?z(P.get(s.budget_id)):H(s.budget_id)}):e.jsx("span",{className:"text-[var(--otari-muted)]",children:"—"})},{id:"spend",header:"Spend",cell:s=>e.jsxs("span",{className:"text-[var(--otari-muted)]",children:[U(s.spend),s.reserved>0?e.jsxs("span",{children:[" (+",U(s.reserved)," held)"]}):null]})},{id:"access",header:"Model access",cell:s=>e.jsx(he,{allowed:s.allowed_models})},{id:"actions",header:"Actions",align:"end",cell:s=>e.jsxs("div",{className:"flex items-center justify-end gap-1.5",children:[e.jsx(d,{size:"sm",variant:"outline",isDisabled:n.isPending,onPress:()=>V(s,!s.blocked),children:s.blocked?"Unblock":"Block"}),e.jsx(d,{size:"sm",variant:"ghost",onPress:()=>{o(!1),c(s.user_id)},children:"Edit"}),e.jsx(me,{trigger:"Delete",confirmLabel:"Delete user",isPending:a.isPending,message:e.jsxs(e.Fragment,{children:["Delete ",e.jsx("strong",{children:s.user_id}),"? This deactivates its API keys and hides the user; usage history is preserved."]}),onConfirm:()=>a.mutate(s.user_id)})]})}],[P,n.isPending,a.isPending,a.mutate,V]);return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(Z,{title:"Users",description:"People and teams that own API keys. Set each one's budget and default model access here; issue their keys on the API keys page.",action:i?null:e.jsx(d,{variant:"primary",onPress:()=>{c(null),o(!0)},children:"Create user"})}),e.jsx(A,{error:t.error??n.error??a.error}),k?e.jsx(ee,{title:"No users yet",description:"A user owns API keys and carries the budget and default model access those keys inherit. Create a user here, then issue its keys on the API keys page.",actionLabel:"Create your first user",onAction:()=>{c(null),o(!0)}}):null,j>0?e.jsxs("label",{className:"flex w-fit items-center gap-2 text-xs text-[var(--otari-muted)]",children:[e.jsx("input",{type:"checkbox",checked:g,onChange:s=>p(s.target.checked)}),"Show auto-created (virtual) users (",j,")"]}):null,i?e.jsx(ue,{onClose:()=>o(!1)}):null,h?e.jsx(ge,{user:h,onClose:()=>c(null)},h.user_id):null,v.length>0?e.jsxs(re,{selectedCount:v.length,allMatching:!1,matchingTotal:null,canSelectAllMatching:!1,onSelectAllMatching:()=>{},onClear:y.clear,children:[e.jsx(d,{size:"sm",variant:"primary",onPress:()=>N(!0),children:"Assign budget"}),e.jsx(d,{size:"sm",variant:"danger",onPress:()=>_(!0),children:"Delete"})]}):null,k?null:e.jsx(ie,{ariaLabel:"Users",columns:J,rows:x,getRowKey:ce,isLoading:b,emptyContent:"No users yet. Create one, or create an API key to auto-create one.",selectionMode:"multiple",selectedKeys:y.selectedKeys,onSelectionChange:y.onSelectionChange}),e.jsx(le,{isOpen:q,onOpenChange:_,heading:"Delete users",body:`Delete ${v.length} ${v.length===1?"user":"users"}? This deactivates their API keys and hides them; usage history is preserved.`,confirmLabel:"Delete",isPending:L,error:E,onConfirm:()=>O(s=>a.mutateAsync(s),()=>_(!1))}),e.jsx(pe,{isOpen:W,onOpenChange:N,budgets:l.data??[],count:v.length,isPending:L,error:E,onAssign:s=>O(C=>n.mutateAsync({id:C,body:{budget_id:s}}),()=>N(!1))})]})}export{Pe as UsersPage}; +import{j as e}from"./tanstack-query-1t81HyiD.js";import{r}from"./react-dgEcD0HR.js";import{u as Q,H as M,L as K,aA as X,q as Y,P as Z,E as A,z as ee,aB as se,F as te}from"./index-Dit1BUBh.js";import{u as ae,r as ne,B as re}from"./tableSelection-B1umVgqc.js";import{C as le}from"./ConfirmDialog-Dt_8xaSM.js";import{D as ie}from"./DataTable-BHrpJHmX.js";import{F as D}from"./Field-GEMwIhf7.js";import{a as de,M as R}from"./ModelScopeControl-BhMRwgM-.js";import{g as I,B as d,d as S,A as f}from"./heroui-DhloIxuc.js";const oe=new Intl.NumberFormat(void 0,{style:"currency",currency:"USD",maximumFractionDigits:4});function U(t){return oe.format(t)}const ce=t=>t.user_id,B=t=>t.startsWith("apikey-");function H(t){return t.split("-")[0]}function z(t){return t.name??H(t.budget_id)}function $({value:t,onChange:l,budgets:n}){return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("label",{htmlFor:"user-budget",className:"text-sm font-medium text-[var(--otari-ink)]",children:"Budget"}),e.jsxs("select",{id:"user-budget",value:t??"",onChange:a=>l(a.target.value||null),className:"w-full rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)]",children:[e.jsx("option",{value:"",children:"No budget (unlimited)"}),n.map(a=>e.jsxs("option",{value:a.budget_id,children:[z(a),a.max_budget===null?" · no limit":` · ${U(a.max_budget)}`]},a.budget_id))]}),e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:"The spending limit this user is held to. Manage budgets on the Budgets page."})]})}function ue({onClose:t}){const l=se(),n=M(),[a,i]=r.useState(""),[o,u]=r.useState(""),[c,g]=r.useState(null),[p,m]=r.useState(null),[b,j]=r.useState(!0),x=()=>{if(l.isPending||!b||a.trim()==="")return;const h={user_id:a.trim(),alias:o.trim()||null,budget_id:c,allowed_models:p};l.mutate(h,{onSuccess:t})};return e.jsx(S,{children:e.jsxs(S.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsx("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:"Create user"}),e.jsx(A,{error:l.error}),e.jsxs("div",{className:"grid gap-4 sm:grid-cols-2",children:[e.jsx(D,{label:"User ID",value:a,onChange:i,placeholder:"alice@example.com",isRequired:!0,autoFocus:!0,description:"The identifier callers send as the `user` field; spend and budgets track against it."}),e.jsx(D,{label:"Alias (optional)",value:o,onChange:u,placeholder:"Alice"})]}),e.jsx($,{value:c,onChange:g,budgets:n.data??[]}),e.jsx(R,{title:"Model access (default for this user's keys)",description:"The models this user's keys may list and call by default. A key can narrow this, but never exceed it.",initial:null,onChange:(h,k)=>{m(h),j(k)}}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(d,{variant:"primary",isDisabled:l.isPending||!b||a.trim()==="",onPress:x,children:l.isPending?"Creating…":"Create user"}),e.jsx(d,{variant:"ghost",onPress:t,children:"Cancel"})]})]})})}function ge({user:t,onClose:l}){const n=K(),a=M(),[i,o]=r.useState(t.alias??""),[u,c]=r.useState(t.budget_id),[g,p]=r.useState(t.allowed_models),[m,b]=r.useState(!0),j=()=>{if(n.isPending||!m)return;const x={alias:i.trim()||null,budget_id:u,allowed_models:g};n.mutate({id:t.user_id,body:x},{onSuccess:l})};return e.jsx(S,{children:e.jsxs(S.Content,{className:"flex flex-col gap-4 p-5",children:[e.jsxs("div",{className:"text-sm font-semibold text-[var(--otari-ink)]",children:["Edit ",e.jsx("code",{children:t.user_id})]}),e.jsx(A,{error:n.error}),e.jsx(D,{label:"Alias",value:i,onChange:o,placeholder:"Alice"}),e.jsx($,{value:u,onChange:c,budgets:a.data??[]}),e.jsx(R,{title:"Model access (default for this user's keys)",description:"The models this user's keys may list and call by default. A key can narrow this, but never exceed it.",initial:t.allowed_models,onChange:(x,h)=>{p(x),b(h)}}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(d,{variant:"primary",isDisabled:n.isPending||!m,onPress:j,children:n.isPending?"Saving…":"Save changes"}),e.jsx(d,{variant:"ghost",onPress:l,children:"Cancel"})]})]})})}function me({trigger:t,message:l,confirmLabel:n,isPending:a,onConfirm:i}){const[o,u]=r.useState(!1);return o?e.jsxs("div",{className:"flex flex-col items-end gap-1.5 rounded-lg border border-amber-200 bg-amber-50 p-2 text-right",children:[e.jsx("span",{className:"max-w-xs text-xs text-amber-800",children:l}),e.jsxs("span",{className:"inline-flex gap-1",children:[e.jsx(d,{size:"sm",variant:"danger",isDisabled:a,onPress:i,children:n}),e.jsx(d,{size:"sm",variant:"ghost",isDisabled:a,onPress:()=>u(!1),children:"Cancel"})]})]}):e.jsx(d,{size:"sm",variant:"danger-soft",onPress:()=>u(!0),children:t})}function xe({user:t}){return t.blocked?e.jsx(I,{size:"sm",color:"warning",children:"Blocked"}):e.jsx(I,{size:"sm",color:"accent",children:"Active"})}function he({allowed:t}){const{text:l,tone:n}=de(t),a=n==="danger"?"text-red-700 font-medium":n==="muted"?"text-[var(--otari-muted)]":"text-[var(--otari-brand-dark)] font-medium",i=t&&t.length>0?t.join(", "):void 0;return e.jsx("span",{className:`text-xs ${a}`,title:i,children:l})}function pe({isOpen:t,onOpenChange:l,budgets:n,count:a,isPending:i,error:o,onAssign:u}){const[c,g]=r.useState("");return r.useEffect(()=>{t&&g("")},[t]),e.jsx(f,{isOpen:t,onOpenChange:l,children:t?e.jsx(f.Backdrop,{children:e.jsx(f.Container,{placement:"center",size:"md",children:e.jsxs(f.Dialog,{children:[e.jsx(f.Header,{children:e.jsx(f.Heading,{children:"Assign budget"})}),e.jsxs(f.Body,{className:"flex flex-col gap-4",children:[e.jsxs("p",{className:"text-sm text-[var(--otari-muted)]",children:["Assign a budget to ",a," selected ",a===1?"user":"users","."]}),e.jsx(te,{label:"Budget",value:c,onChange:g,options:[{value:"",label:"Select a budget…"},...n.map(p=>({value:p.budget_id,label:z(p)}))]}),e.jsx(A,{error:o})]}),e.jsxs(f.Footer,{children:[e.jsx(d,{variant:"ghost",isDisabled:i,onPress:()=>l(!1),children:"Cancel"}),e.jsx(d,{variant:"primary",isDisabled:!c,isPending:i,onPress:()=>u(c),children:"Assign"})]})]})})}):null})}function Pe(){const t=Q(),l=M(),n=K(),a=X(),[i,o]=r.useState(!1),[u,c]=r.useState(null),[g,p]=r.useState(!1),m=t.data??[],b=t.isLoading,j=m.filter(s=>B(s.user_id)).length,x=g?m:m.filter(s=>!B(s.user_id)),h=m.find(s=>s.user_id===u)??null,k=!b&&x.length===0&&!i,P=r.useMemo(()=>new Map((l.data??[]).map(s=>[s.budget_id,s])),[l.data]),y=ae(),[q,_]=r.useState(!1),[W,N]=r.useState(!1),[E,F]=r.useState(void 0),[L,T]=r.useState(!1),G=x.map(s=>s.user_id),v=ne(y.selectedKeys,G),V=r.useCallback((s,C)=>n.mutate({id:s.user_id,body:{blocked:C}}),[n.mutate]),O=async(s,C)=>{T(!0),F(void 0);try{for(const w of v)await s(w);y.clear(),C()}catch(w){F(w)}finally{T(!1)}},J=r.useMemo(()=>[{id:"user",header:"User",isRowHeader:!0,cell:s=>e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsxs("span",{className:"inline-flex items-center gap-1.5",children:[e.jsx(Y,{value:s.user_id,label:"user id",children:e.jsx("code",{className:"text-xs font-medium text-[var(--otari-ink)]",children:s.user_id})}),B(s.user_id)?e.jsx(I,{size:"sm",color:"default",children:"virtual"}):null]}),s.alias?e.jsx("span",{className:"text-xs text-[var(--otari-muted)]",children:s.alias}):null]})},{id:"status",header:"Status",cell:s=>e.jsx(xe,{user:s})},{id:"budget",header:"Budget",cell:s=>s.budget_id?e.jsx("span",{className:"text-[var(--otari-muted)]",title:s.budget_id,children:P.get(s.budget_id)?z(P.get(s.budget_id)):H(s.budget_id)}):e.jsx("span",{className:"text-[var(--otari-muted)]",children:"—"})},{id:"spend",header:"Spend",cell:s=>e.jsxs("span",{className:"text-[var(--otari-muted)]",children:[U(s.spend),s.reserved>0?e.jsxs("span",{children:[" (+",U(s.reserved)," held)"]}):null]})},{id:"access",header:"Model access",cell:s=>e.jsx(he,{allowed:s.allowed_models})},{id:"actions",header:"Actions",align:"end",cell:s=>e.jsxs("div",{className:"flex items-center justify-end gap-1.5",children:[e.jsx(d,{size:"sm",variant:"outline",isDisabled:n.isPending,onPress:()=>V(s,!s.blocked),children:s.blocked?"Unblock":"Block"}),e.jsx(d,{size:"sm",variant:"ghost",onPress:()=>{o(!1),c(s.user_id)},children:"Edit"}),e.jsx(me,{trigger:"Delete",confirmLabel:"Delete user",isPending:a.isPending,message:e.jsxs(e.Fragment,{children:["Delete ",e.jsx("strong",{children:s.user_id}),"? This deactivates its API keys and hides the user; usage history is preserved."]}),onConfirm:()=>a.mutate(s.user_id)})]})}],[P,n.isPending,a.isPending,a.mutate,V]);return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx(Z,{title:"Users",description:"People and teams that own API keys. Set each one's budget and default model access here; issue their keys on the API keys page.",action:i?null:e.jsx(d,{variant:"primary",onPress:()=>{c(null),o(!0)},children:"Create user"})}),e.jsx(A,{error:t.error??n.error??a.error}),k?e.jsx(ee,{title:"No users yet",description:"A user owns API keys and carries the budget and default model access those keys inherit. Create a user here, then issue its keys on the API keys page.",actionLabel:"Create your first user",onAction:()=>{c(null),o(!0)}}):null,j>0?e.jsxs("label",{className:"flex w-fit items-center gap-2 text-xs text-[var(--otari-muted)]",children:[e.jsx("input",{type:"checkbox",checked:g,onChange:s=>p(s.target.checked)}),"Show auto-created (virtual) users (",j,")"]}):null,i?e.jsx(ue,{onClose:()=>o(!1)}):null,h?e.jsx(ge,{user:h,onClose:()=>c(null)},h.user_id):null,v.length>0?e.jsxs(re,{selectedCount:v.length,allMatching:!1,matchingTotal:null,canSelectAllMatching:!1,onSelectAllMatching:()=>{},onClear:y.clear,children:[e.jsx(d,{size:"sm",variant:"primary",onPress:()=>N(!0),children:"Assign budget"}),e.jsx(d,{size:"sm",variant:"danger",onPress:()=>_(!0),children:"Delete"})]}):null,k?null:e.jsx(ie,{ariaLabel:"Users",columns:J,rows:x,getRowKey:ce,isLoading:b,emptyContent:"No users yet. Create one, or create an API key to auto-create one.",selectionMode:"multiple",selectedKeys:y.selectedKeys,onSelectionChange:y.onSelectionChange}),e.jsx(le,{isOpen:q,onOpenChange:_,heading:"Delete users",body:`Delete ${v.length} ${v.length===1?"user":"users"}? This deactivates their API keys and hides them; usage history is preserved.`,confirmLabel:"Delete",isPending:L,error:E,onConfirm:()=>O(s=>a.mutateAsync(s),()=>_(!1))}),e.jsx(pe,{isOpen:W,onOpenChange:N,budgets:l.data??[],count:v.length,isPending:L,error:E,onAssign:s=>O(C=>n.mutateAsync({id:C,body:{budget_id:s}}),()=>N(!1))})]})}export{Pe as UsersPage}; diff --git a/src/gateway/static/dashboard/assets/index-D-R1nuKP.js b/src/gateway/static/dashboard/assets/index-D-R1nuKP.js deleted file mode 100644 index 50633fb21..000000000 --- a/src/gateway/static/dashboard/assets/index-D-R1nuKP.js +++ /dev/null @@ -1,2 +0,0 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/ActivityPage-C28CtpkN.js","assets/tanstack-query-1t81HyiD.js","assets/react-dgEcD0HR.js","assets/charts-D6upG8fh.js","assets/recharts-EeW53z2i.js","assets/heroui-DhloIxuc.js","assets/tableSelection-B1umVgqc.js","assets/ConfirmDialog-mbnZRETP.js","assets/DataTable-BHrpJHmX.js","assets/FilterChips-CTE3I1G3.js","assets/TablePagination-BEmYAlSB.js","assets/Field-GEMwIhf7.js","assets/RoutingPage-D1os8M2m.js","assets/UserComboBox-DWvRaj2b.js","assets/BudgetsPage-B9iEC7ec.js","assets/DocsPage-D53o1bCm.js","assets/KeysPage-fg3Rz_lV.js","assets/ModelScopeControl-BBYX_HiM.js","assets/ModelsPage-uwVSUUQm.js","assets/OverviewPage-0PkW5qfi.js","assets/ProvidersPage-B4LYozbD.js","assets/SettingsPage-C2Hp1OPt.js","assets/ToolsGuardrailsPage-C-E4XKsV.js","assets/UsagePage-tyubYvXE.js","assets/UsersPage-Be1Tcz9b.js"])))=>i.map(i=>d[i]); -var Oe=Object.defineProperty;var Me=(e,t,r)=>t in e?Oe(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var ue=(e,t,r)=>Me(e,typeof t!="symbol"?t+"":t,r);import{u as v,j as n,a as p,b as h,k as U,Q as Fe,c as Ue}from"./tanstack-query-1t81HyiD.js";import{d as Ke,r as i,N as ee,L as Be,O as $e,H as ze,e as Qe,f as w,h as de}from"./react-dgEcD0HR.js";import{B as C,C as B,L as ge,I as pe,a as Ve,b as We,d as q,S as Ge,T as me,c as N,e as He,f as Je}from"./heroui-DhloIxuc.js";(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))s(a);new MutationObserver(a=>{for(const o of a)if(o.type==="childList")for(const l of o.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&s(l)}).observe(document,{childList:!0,subtree:!0});function r(a){const o={};return a.integrity&&(o.integrity=a.integrity),a.referrerPolicy&&(o.referrerPolicy=a.referrerPolicy),a.crossOrigin==="use-credentials"?o.credentials="include":a.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function s(a){if(a.ep)return;a.ep=!0;const o=r(a);fetch(a.href,o)}})();var Ye=Ke();const Xe="modulepreload",Ze=function(e){return"/"+e},fe={},P=function(t,r,s){let a=Promise.resolve();if(r&&r.length>0){let l=function(y){return Promise.all(y.map(x=>Promise.resolve(x).then(b=>({status:"fulfilled",value:b}),b=>({status:"rejected",reason:b}))))};document.getElementsByTagName("link");const d=document.querySelector("meta[property=csp-nonce]"),f=(d==null?void 0:d.nonce)||(d==null?void 0:d.getAttribute("nonce"));a=l(r.map(y=>{if(y=Ze(y),y in fe)return;fe[y]=!0;const x=y.endsWith(".css"),b=x?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${y}"]${b}`))return;const m=document.createElement("link");if(m.rel=x?"stylesheet":Xe,x||(m.as="script"),m.crossOrigin="",m.href=y,f&&m.setAttribute("nonce",f),document.head.appendChild(m),x)return new Promise((T,M)=>{m.addEventListener("load",T),m.addEventListener("error",()=>M(new Error(`Unable to preload CSS for ${y}`)))})}))}function o(l){const d=new Event("vite:preloadError",{cancelable:!0});if(d.payload=l,window.dispatchEvent(d),!d.defaultPrevented)throw l}return a.then(l=>{for(const d of l||[])d.status==="rejected"&&o(d.reason);return t().catch(o)})};class D extends Error{constructor(r,s){super(s);ue(this,"status");this.name="ApiError",this.status=r}}let $=null;function he(e){$=e}async function te(e){try{const t=await e.json();if(typeof t.detail=="string")return t.detail;if(t.detail!=null)return JSON.stringify(t.detail)}catch{}return e.statusText||`Request failed (${e.status})`}async function et(e){let t;try{t=await fetch("/v1/auth/session",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({master_key:e})})}catch{throw new D(0,"Network error: could not reach the gateway.")}if(t.status===401||t.status===403)return!1;if(!t.ok)throw new D(t.status,await te(t));return!0}async function tt(){try{await fetch("/v1/auth/session",{method:"DELETE"})}catch{}}async function c(e,t={}){const r=new Headers(t.headers);r.set("Accept","application/json"),t.body!=null&&!r.has("Content-Type")&&r.set("Content-Type","application/json");let s;try{s=await fetch(e,{...t,headers:r})}catch{throw new D(0,"Network error: could not reach the gateway.")}if(s.status===401||s.status===403)throw $==null||$(),new D(s.status,await te(s));if(!s.ok)throw new D(s.status,await te(s));if(s.status!==204)return await s.json()}const ne="otari.dashboard.hasSession",be=i.createContext(null);function nt(){try{return window.localStorage.getItem(ne)==="1"}catch{return!1}}function rt({children:e}){const t=v(),[r,s]=i.useState(nt),a=i.useCallback(()=>{tt(),s(!1),t.clear();try{window.localStorage.removeItem(ne)}catch{}},[t]),o=i.useCallback(()=>{t.clear(),s(!0);try{window.localStorage.setItem(ne,"1")}catch{}},[t]);i.useEffect(()=>(he(a),()=>he(null)),[a]);const l=i.useMemo(()=>({isAuthenticated:r,login:o,logout:a}),[r,o,a]);return n.jsx(be.Provider,{value:l,children:e})}function re(){const e=i.useContext(be);if(!e)throw new Error("useAuth must be used within an AuthProvider");return e}function st(e){return e instanceof D&&e.status===0}function at(){const e=v(),[t,r]=i.useState(!1);return i.useEffect(()=>{const s=e.getQueryCache(),a=()=>s.getAll().some(o=>o.state.status==="error"&&st(o.state.error));return r(a()),s.subscribe(()=>r(a()))},[e]),t}function ot(){return at()?n.jsxs("div",{role:"alert","aria-live":"assertive",className:"fixed right-4 bottom-4 z-50 flex max-w-sm items-start gap-2.5 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 shadow-lg",children:[n.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2","aria-hidden":!0,className:"mt-0.5 h-5 w-5 shrink-0",children:[n.jsx("path",{d:"M12 9v4M12 17h.01",strokeLinecap:"round",strokeLinejoin:"round"}),n.jsx("path",{d:"M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z",strokeLinejoin:"round"})]}),n.jsxs("span",{children:[n.jsx("strong",{className:"font-semibold",children:"Can’t reach the gateway."})," The backend isn’t responding; data won’t load or save until the connection is restored."]})]}):null}const Q=3600,_=86400,it=365*_,vn=[{key:"1h",label:"Last hour",seconds:Q,bucket:"hour"},{key:"24h",label:"24h",seconds:_,bucket:"hour"},{key:"7d",label:"7d",seconds:7*_,bucket:"day"},{key:"30d",label:"30d",seconds:30*_,bucket:"day"},{key:"90d",label:"90d",seconds:90*_,bucket:"day"},{key:"12mo",label:"12mo",seconds:it,bucket:"day"}],gn="30d",pn=[{key:"1h",label:"1h",seconds:Q,bucket:"hour"},{key:"24h",label:"24h",seconds:_,bucket:"hour"},{key:"7d",label:"7d",seconds:7*_,bucket:"day"},{key:"30d",label:"30d",seconds:30*_,bucket:"day"},{key:"all",label:"All",seconds:null,bucket:"day"}],bn="24h",wn="custom";function jn(e,t){return e.find(r=>r.key===t)}function ct(e,t=Date.now()){return new Date(t-e*1e3).toISOString()}function lt(e){return(e==="hour"?Q:_)*1e3}function Sn(e,t,r=Date.now()){const s=new Date(e).getTime();return(t?new Date(t).getTime():r)-s<=_*1e3?"hour":"day"}function kn(e,t,r,s){if(e.length===0)return null;const a=Math.max(0,Math.min(t,r)),o=Math.min(e.length-1,Math.max(t,r)),l=new Date(e[a]).getTime(),d=new Date(e[o]).getTime()+lt(s);return{startIso:new Date(l).toISOString(),endIso:new Date(d).toISOString()}}function En(e,t,r){const s=e.length;if(s===0)return{startIndex:0,endIndex:0};const a=e.map(d=>new Date(d).getTime());let o=0;if(t){const d=new Date(t).getTime();for(let f=0;fc("/v1/models"),staleTime:6e4})}function ft(){return p({queryKey:[dt],queryFn:()=>c("/dashboard-build.json"),refetchInterval:mt,refetchOnWindowFocus:!0,staleTime:0,retry:!1})}function Tn(){return p({queryKey:[oe],queryFn:()=>c("/v1/models/discoverable"),staleTime:5*6e4})}function Nn(){return p({queryKey:[ie],queryFn:()=>c("/v1/providers"),staleTime:5*6e4})}function _n(){return p({queryKey:["provider-catalog"],queryFn:()=>c("/v1/providers/catalog"),staleTime:1/0})}function Ln(e){return p({queryKey:["provider-catalog",e],queryFn:()=>c(`/v1/providers/catalog/${encodeURIComponent(e)}`),enabled:e!=="",staleTime:1/0})}function Dn(){return p({queryKey:[ce],queryFn:()=>c("/v1/providers/health"),staleTime:xe,refetchInterval:xe})}function Rn(){const e=v();return h({mutationFn:()=>c("/v1/providers/health?refresh=true"),onSuccess:t=>e.setQueryData([ce],t)})}function In(){return p({queryKey:[Se],queryFn:()=>c("/v1/provider-credentials"),staleTime:6e4})}function W(e){e.invalidateQueries({queryKey:[Se]}),e.invalidateQueries({queryKey:[ie]}),e.invalidateQueries({queryKey:[L]}),e.invalidateQueries({queryKey:[oe]}),e.invalidateQueries({queryKey:[ce]})}function qn(){const e=v();return h({mutationFn:t=>c("/v1/provider-credentials",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>W(e)})}function An(){const e=v();return h({mutationFn:({instance:t,body:r})=>c(`/v1/provider-credentials/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify(r)}),onSuccess:()=>W(e)})}function On(){const e=v();return h({mutationFn:t=>c(`/v1/provider-credentials/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>W(e)})}function Mn(){const e=v();return h({mutationFn:()=>c("/v1/provider-credentials/reencrypt",{method:"POST"}),onSuccess:()=>W(e)})}function Fn(){return h({mutationFn:e=>c(`/v1/provider-credentials/${encodeURIComponent(e)}/test`,{method:"POST"})})}function Un(){return h({mutationFn:e=>c("/v1/provider-credentials/test",{method:"POST",body:JSON.stringify(e)})})}function Kn(){return p({queryKey:[ut],queryFn:()=>c("/v1/models/metadata"),staleTime:10*6e4})}function Bn(){return p({queryKey:[se],queryFn:()=>c("/v1/aliases"),staleTime:6e4})}function $n(){return p({queryKey:[ae],queryFn:()=>c("/v1/routing/policies"),staleTime:6e4})}function zn(){const e=v();return h({mutationFn:t=>c("/v1/routing/policies",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[ae]}),e.invalidateQueries({queryKey:[L]})}})}function Qn(){const e=v();return h({mutationFn:({name:t,userId:r})=>{const s=r==null?"":`?user_id=${encodeURIComponent(r)}`;return c(`/v1/routing/policies/${encodeURIComponent(t)}${s}`,{method:"DELETE"})},onSuccess:()=>{e.invalidateQueries({queryKey:[ae]}),e.invalidateQueries({queryKey:[L]})}})}function Vn(){const e=v();return h({mutationFn:t=>c("/v1/aliases",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[se]}),e.invalidateQueries({queryKey:[L]})}})}function Wn(){const e=v();return h({mutationFn:({name:t,userId:r})=>{const s=r==null?"":`?user_id=${encodeURIComponent(r)}`;return c(`/v1/aliases/${encodeURIComponent(t)}${s}`,{method:"DELETE"})},onSuccess:()=>{e.invalidateQueries({queryKey:[se]}),e.invalidateQueries({queryKey:[L]})}})}function ht(){return p({queryKey:[we],queryFn:()=>c("/v1/settings"),staleTime:6e4})}function xt(){const e=v();return h({mutationFn:t=>c("/v1/settings",{method:"PATCH",body:JSON.stringify(t)}),onSuccess:t=>{e.setQueryData([we],t),e.invalidateQueries({queryKey:[L]}),e.invalidateQueries({queryKey:[oe]})}})}function Gn(){return h({mutationFn:()=>c("/v1/settings/master-key/rotate",{method:"POST"})})}function Hn(){return p({queryKey:[je],queryFn:()=>c("/v1/tool-settings"),staleTime:6e4})}function Jn(){const e=v();return h({mutationFn:t=>c("/v1/tool-settings",{method:"PATCH",body:JSON.stringify(t)}),onSuccess:t=>{e.setQueryData([je],t)}})}function Yn(){return h({mutationFn:({service:e,url:t})=>c(`/v1/tool-settings/${encodeURIComponent(e)}/test`,{method:"POST",body:JSON.stringify({url:t})})})}const H=1e3,yt=100;async function vt(){const e=[];for(let t=0;tc("/v1/pricing",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[V]}),e.invalidateQueries({queryKey:[L]})}})}function er(){const e=v();return h({mutationFn:t=>c(`/v1/pricing/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>{e.invalidateQueries({queryKey:[V]}),e.invalidateQueries({queryKey:[L]})}})}function tr(){return h({mutationFn:()=>c("/v1/pricing/refresh",{method:"POST"})})}function nr(){const e=v();return h({mutationFn:()=>c("/v1/pricing/refresh/confirm",{method:"POST"}),onSuccess:()=>{e.invalidateQueries({queryKey:[V]}),e.invalidateQueries({queryKey:[L]}),e.invalidateQueries({queryKey:[ie]})}})}function rr(){return h({mutationFn:()=>c("/v1/pricing/refresh/reject",{method:"POST"})})}const J=1e3,gt=100;async function pt(){const e=[];for(let t=0;tc("/v1/keys",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>void e.invalidateQueries({queryKey:[A]})})}function or(){const e=v();return h({mutationFn:({id:t,body:r})=>c(`/v1/keys/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify(r)}),onSuccess:()=>void e.invalidateQueries({queryKey:[A]})})}function ir(){const e=v();return h({mutationFn:t=>c(`/v1/keys/${encodeURIComponent(t)}/rotate`,{method:"POST"}),onSuccess:()=>void e.invalidateQueries({queryKey:[A]})})}function cr(){const e=v();return h({mutationFn:t=>c(`/v1/keys/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>void e.invalidateQueries({queryKey:[A]})})}const Y=1e3,bt=100;async function wt(){const e=[];for(let t=0;tc(`/v1/budgets/${encodeURIComponent(e)}/reset-logs`),enabled:e!==null,staleTime:6e4})}function dr(){const e=v();return h({mutationFn:t=>c("/v1/budgets",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>void e.invalidateQueries({queryKey:[O]})})}function mr(){const e=v();return h({mutationFn:({id:t,body:r})=>c(`/v1/budgets/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify(r)}),onSuccess:()=>void e.invalidateQueries({queryKey:[O]})})}function fr(){const e=v();return h({mutationFn:t=>c(`/v1/budgets/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>void e.invalidateQueries({queryKey:[O]})})}const X=1e3,jt=100;async function St(){const e=[];for(let t=0;tc("/v1/users",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>le(e)})}function yr(){const e=v();return h({mutationFn:({id:t,body:r})=>c(`/v1/users/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify(r)}),onSuccess:()=>le(e)})}function vr(){const e=v();return h({mutationFn:t=>c(`/v1/users/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>{le(e),e.invalidateQueries({queryKey:[A]})}})}function K(e){const t=new URLSearchParams;return e.start_date&&t.set("start_date",e.start_date),e.end_date&&t.set("end_date",e.end_date),e.status&&t.set("status",e.status),e.model&&t.set("model",e.model),e.endpoint&&t.set("endpoint",e.endpoint),e.provider&&t.set("provider",e.provider),e.user_id&&t.set("user_id",e.user_id),e.api_key_id&&t.set("api_key_id",e.api_key_id),e.source&&t.set("source",e.source),e.source_label&&t.set("source_label",e.source_label),e.tool&&t.set("tool",e.tool),e.priced!==void 0&&t.set("priced",String(e.priced)),e.counts_toward_budget!==void 0&&t.set("counts_toward_budget",String(e.counts_toward_budget)),t}function gr(e,t,r){return p({queryKey:[R,"list",e,t,r],queryFn:()=>{const s=K(e);return s.set("skip",String(t*r)),s.set("limit",String(r)),c(`/v1/usage?${s.toString()}`)},placeholderData:U,staleTime:1e4})}function pr(e,t=!0){return p({queryKey:[R,"count",e],queryFn:()=>c(`/v1/usage/count?${K(e).toString()}`),enabled:t,placeholderData:U,staleTime:1e4})}const kt=6e4;function Et(e,t=!0){return p({queryKey:[R,"count","failures",e],queryFn:()=>{const r={status:"error",source:"gateway",start_date:ct(e)};return c(`/v1/usage/count?${K(r).toString()}`)},enabled:t,refetchInterval:kt,refetchOnWindowFocus:!0,staleTime:0,retry:!1})}const Ct=1e3;function br(e){const t=[...new Set(e)].sort();return p({queryKey:[R,"groups",t],queryFn:()=>{const r=new URLSearchParams;for(const s of t)r.append("request_group_id",s);return r.set("limit",String(Ct)),c(`/v1/usage?${r.toString()}`)},enabled:t.length>0,placeholderData:U,staleTime:3e4})}function wr(){const e=v();return h({mutationFn:t=>c("/v1/usage",{method:"DELETE",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[R]})}})}function jr(){const e=v();return h({mutationFn:t=>c("/v1/usage/set-price",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[R]})}})}const Sr=[];function kr(e,t,r,s=!0){return p({queryKey:[R,"summary",e,t,r??"all"],queryFn:()=>{const a=K(e);if(a.set("bucket",t),r)for(const o of r.length>0?r:["none"])a.append("dimensions",o);return c(`/v1/usage/summary?${a.toString()}`)},enabled:s,placeholderData:U,staleTime:3e4})}function Er(e,t,r,s=!0){return p({queryKey:[R,"series",e,t,r],queryFn:()=>{const a=K(e);return a.set("bucket",t),a.set("group_by",r),c(`/v1/usage/series?${a.toString()}`)},enabled:s&&r!==null,placeholderData:U,staleTime:3e4,retry:(a,o)=>!(o instanceof D&&o.status===404)&&a<3})}async function Pt(e,t=navigator.clipboard){if(t)try{return await t.writeText(e),!0}catch{}return Tt(e)}function Tt(e){const t=document.createElement("textarea");t.value=e,t.readOnly=!0,t.style.position="fixed",t.style.top="-1000px",t.style.opacity="0",document.body.appendChild(t);const r=document.getSelection(),s=r&&r.rangeCount>0?r.getRangeAt(0):null,a=document.activeElement instanceof HTMLElement?document.activeElement:null;t.select();let o=!1;try{o=document.execCommand("copy")}catch{o=!1}return t.remove(),r&&s&&(r.removeAllRanges(),r.addRange(s)),a==null||a.focus(),o}function Cr(e){return e==null?"0":new Intl.NumberFormat("en-US").format(e)}function Pr(e){if(e==null)return"$0.00";const t=e!==0&&Math.abs(e)<.01?4:2;return new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",minimumFractionDigits:2,maximumFractionDigits:t}).format(e)}function Tr(e){if(e==null)return"—";if(e>=1e6){const t=e/1e6;return`${Number.isInteger(t)?t:t.toFixed(1)}M`}if(e>=1e3){const t=Math.round(e/1e3);return t>=1e3?"1M":`${t}K`}return String(e)}const Nt=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function Nr(e){if(!e)return"—";const t=/^(\d{4})-(\d{2})/.exec(e);if(!t)return e;const r=Number(t[2])-1;return r<0||r>11?t[1]:`${Nt[r]} ${t[1]}`}const _t=new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",maximumFractionDigits:2});function _r(e){return _t.format(e)}function Lr(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function Lt(e){return`${(e*100).toFixed(1)}%`}function Dr(e,t){return t===void 0||t===0?null:(e-t)/t}function Dt(e,t=Date.now()){if(!e)return"never";const r=new Date(e);if(Number.isNaN(r.getTime()))return e;const s=Math.round((t-r.getTime())/1e3),a=s<0,o=Math.abs(s),l=[["second",60],["minute",60],["hour",24],["day",30],["month",12],["year",Number.POSITIVE_INFINITY]];let d=o,f="second";for(const[x,b]of l){if(f=x,d0?"▲":e<0?"▼":"•";return n.jsxs("span",{className:"text-[var(--otari-muted)]",children:[t," ",Lt(Math.abs(e))," vs prev"]})}function Rt(e){return e instanceof D||e instanceof Error?e.message:"Something went wrong."}function It({error:e}){return e?n.jsx("div",{role:"alert",className:"rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700",children:Rt(e)}):null}function qt({tone:e="info",children:t}){const r=e==="warning"?"border-amber-200 bg-amber-50 text-amber-800":"border-[var(--otari-brand)] bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]";return n.jsx("div",{className:`rounded-lg border px-4 py-3 text-sm ${r}`,children:t})}function qr({title:e,description:t,action:r}){return n.jsxs("div",{className:"flex flex-col gap-3",children:[n.jsxs("div",{children:[n.jsx("h1",{className:"text-xl font-semibold text-[var(--otari-ink)]",children:e}),t?n.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:t}):null]}),r?n.jsx("div",{className:"flex flex-wrap gap-2",children:r}):null]})}function At(e){const[t,r]=i.useState(()=>Date.now());return i.useEffect(()=>{let s;const a=()=>{s===void 0&&(s=setInterval(()=>r(Date.now()),e))},o=()=>{s!==void 0&&(clearInterval(s),s=void 0)},l=()=>{r(Date.now()),document.visibilityState==="visible"?a():o()};return l(),document.addEventListener("visibilitychange",l),()=>{o(),document.removeEventListener("visibilitychange",l)}},[e]),t}function Ar({onRefresh:e,isFetching:t=!1,updatedAt:r,label:s="Refresh"}){const a=At(15e3),o=r?Dt(new Date(r).toISOString(),a):null;return n.jsxs("span",{className:"inline-flex items-center gap-2",children:[o?n.jsxs("span",{className:"text-xs text-[var(--otari-muted)]",children:["Updated ",o]}):null,n.jsx(C,{variant:"outline",size:"sm",isIconOnly:!0,isDisabled:t,onPress:e,"aria-label":s,children:n.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:`h-4 w-4 ${t?"animate-spin":""}`,"aria-hidden":"true",children:[n.jsx("path",{d:"M20 11a8 8 0 1 0-.5 4",strokeLinecap:"round",strokeLinejoin:"round"}),n.jsx("path",{d:"M20 4v5h-5",strokeLinecap:"round",strokeLinejoin:"round"})]})})]})}function Or({value:e,label:t,className:r,children:s}){const a=o=>o.stopPropagation();return n.jsxs("span",{className:"inline-flex items-center gap-1",children:[n.jsx("span",{tabIndex:-1,className:`select-text outline-none ${r??""}`,onPointerDown:a,onMouseDown:a,children:s??e}),n.jsx(Ot,{value:e,label:t})]})}function Ot({value:e,label:t}){const[r,s]=i.useState("idle"),a=i.useRef(void 0);i.useEffect(()=>()=>clearTimeout(a.current),[]);const o=async()=>{const l=await Pt(e);s(l?"copied":"failed"),clearTimeout(a.current),a.current=setTimeout(()=>s("idle"),l?1500:5e3)};return n.jsxs(me.Root,{isOpen:r!=="idle",children:[n.jsx(C,{size:"sm",variant:"ghost",isIconOnly:!0,"aria-label":`Copy ${t}`,onPress:o,children:n.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-3.5 w-3.5","aria-hidden":"true",children:[n.jsx("rect",{x:"9",y:"9",width:"11",height:"11",rx:"2"}),n.jsx("path",{d:"M5 15V5a2 2 0 0 1 2-2h8",strokeLinecap:"round",strokeLinejoin:"round"})]})}),n.jsx(me.Content,{placement:"top",showArrow:!0,children:r==="failed"?"Copy blocked, select the value and press Ctrl/Cmd-C":"Copied!"})]})}function Mr({title:e,description:t,actionLabel:r,onAction:s,isActionDisabled:a,children:o}){return n.jsx(q,{children:n.jsxs(q.Content,{className:"flex flex-col gap-4 p-6",children:[n.jsxs("div",{children:[n.jsx("h2",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:e}),t?n.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:t}):null]}),o,r&&s?n.jsx("div",{children:n.jsx(C,{variant:"primary",isDisabled:a,onPress:s,children:r})}):null]})})}function Fr({label:e="Loading…"}){return n.jsxs("div",{role:"status",className:"flex items-center justify-center gap-2 px-4 py-10 text-sm text-[var(--otari-muted)]",children:[n.jsx(Ge,{size:"sm"}),n.jsx("span",{children:e})]})}function Ur({children:e,confirmLabel:t,onConfirm:r,isPending:s}){const[a,o]=i.useState(!1);return a?n.jsxs("span",{className:"inline-flex items-center gap-1",children:[n.jsx(C,{size:"sm",variant:"danger",isDisabled:s,onPress:r,children:t}),n.jsx(C,{size:"sm",variant:"ghost",isDisabled:s,onPress:()=>o(!1),children:"Cancel"})]}):n.jsx(C,{size:"sm",variant:"danger-soft",onPress:()=>o(!0),children:e})}const Mt="rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none";function Kr({id:e,label:t,ariaLabel:r,value:s,onChange:a,options:o,children:l,disabled:d}){const f=i.useId(),y=e??(t?f:void 0),x=n.jsx("select",{id:y,"aria-label":t?void 0:r,value:s,disabled:d,onChange:b=>a(b.target.value),className:Mt,children:o?o.map(b=>n.jsx("option",{value:b.value,children:b.label},b.value)):l});return t?n.jsxs("div",{className:"flex flex-col gap-1",children:[n.jsx("label",{htmlFor:y,className:"text-xs font-medium text-[var(--otari-muted)]",children:t}),x]}):x}function Br({label:e,value:t,onChange:r,options:s,placeholder:a,maxVisible:o=50,allowsCustom:l=!1}){const d=m=>{var T;return((T=s.find(M=>M.value===m))==null?void 0:T.label)??m},[f,y]=i.useState(()=>d(t));i.useEffect(()=>{y(d(t))},[t]);const x=f.trim().toLowerCase(),b=s.filter(m=>!x||m.value.toLowerCase().includes(x)||m.label.toLowerCase().includes(x)).slice(0,o);return n.jsxs(B.Root,{allowsEmptyCollection:!0,allowsCustomValue:l,menuTrigger:"focus",inputValue:f,onInputChange:m=>{y(m),l?r(m.trim()):m.trim()===""&&r("")},onSelectionChange:m=>{m!=null&&r(String(m))},className:"flex flex-col gap-1",children:[n.jsx(ge,{className:"text-xs font-medium text-[var(--otari-muted)]",children:e}),n.jsxs(B.InputGroup,{children:[n.jsx(pe,{placeholder:a,autoComplete:"off",onFocus:m=>m.currentTarget.select()}),n.jsx(B.Trigger,{})]}),n.jsx(B.Popover,{children:n.jsx(Ve,{items:b,className:"max-h-72 overflow-auto",children:m=>n.jsx(We,{id:m.value,textValue:m.label,children:m.label})})})]})}function Ft(){var f,y;const e=ht(),t=xt(),[r,s]=i.useState(!1),o=((f=e.data)==null?void 0:f.require_pricing)===!0&&e.data.default_pricing===!1&&!r,d=((y=Et(Q,o).data)==null?void 0:y.total)??0;return o?n.jsx("div",{className:"shrink-0 px-6 pt-3",children:n.jsx(qt,{tone:"warning",children:n.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[n.jsxs("span",{children:["Requests are rejected until pricing is set (",n.jsx("code",{children:"require_pricing"})," is on). Enable default pricing to meter new models with public rates right away.",d>0?n.jsxs(n.Fragment,{children:[" ",n.jsxs("strong",{className:"font-semibold",children:[d.toLocaleString()," ",d===1?"request":"requests"," failed in the last hour."]})," ",n.jsx(Be,{to:"/activity?status=error&range=1h&source=gateway",className:"underline underline-offset-2",children:"View failed requests"})]}):null]}),n.jsxs("span",{className:"flex items-center gap-2",children:[n.jsx(C,{size:"sm",variant:"primary",isDisabled:t.isPending,onPress:()=>t.mutate({default_pricing:!0}),children:t.isPending?"Enabling…":"Enable default pricing"}),n.jsx(C,{size:"sm",variant:"ghost",onPress:()=>s(!0),children:"Dismiss"})]})]})})}):null}function Ut(){const{data:e}=ft(),t=i.useRef(null);return e&&t.current===null&&(t.current=e.build),e!=null&&t.current!=null&&e.build!==t.current}function Kt(){const e=Ut(),[t,r]=i.useState(!1);return!e||t?null:n.jsx("div",{className:"pointer-events-none absolute inset-x-0 top-0 z-50 flex justify-center",children:n.jsxs("div",{role:"status",className:"pointer-events-auto mt-1.5 flex items-center gap-3 rounded-full border border-[var(--otari-brand)] bg-[var(--otari-brand-tint)] py-1.5 pr-1.5 pl-4 text-sm text-[var(--otari-brand-dark)] shadow-md",children:[n.jsxs("span",{children:[n.jsx("strong",{className:"font-semibold",children:"An update is available."})," Reloading keeps you signed in."]}),n.jsx(C,{size:"sm",variant:"primary",onPress:()=>window.location.reload(),children:"Update now"}),n.jsx(C,{size:"sm",variant:"ghost",onPress:()=>r(!0),children:"Later"})]})})}const Ee=200,Ce=480,Z=240,Bt=60,Pe="otari.dashboard.sidebarWidth",Te="otari.dashboard.sidebarCollapsed",ve=16,Ne="(max-width: 767px)",z=e=>Math.min(Ce,Math.max(Ee,e));function $t(){return typeof window>"u"||typeof window.matchMedia!="function"?!1:window.matchMedia(Ne).matches}const zt='a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])';function Qt(e){return e?Array.from(e.querySelectorAll(zt)).filter(t=>t.offsetParent!==null||t===document.activeElement):[]}function Vt(){if(typeof window>"u")return Z;try{const e=window.localStorage.getItem(Pe),t=e?Number.parseInt(e,10):Number.NaN;return Number.isNaN(t)?Z:z(t)}catch{return Z}}function Wt(){if(typeof window>"u")return!1;try{return window.localStorage.getItem(Te)==="1"}catch{return!1}}const Gt=[{key:"home"},{key:"observability",label:"Observability"},{key:"catalog",label:"Catalog"},{key:"access",label:"Access"},{key:"system"}],Ht=[{to:"/",section:"home",label:"Overview",end:!0,icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("rect",{x:"3.5",y:"3.5",width:"7",height:"7",rx:"1.5",strokeLinejoin:"round"}),n.jsx("rect",{x:"13.5",y:"3.5",width:"7",height:"7",rx:"1.5",strokeLinejoin:"round"}),n.jsx("rect",{x:"3.5",y:"13.5",width:"7",height:"7",rx:"1.5",strokeLinejoin:"round"}),n.jsx("rect",{x:"13.5",y:"13.5",width:"7",height:"7",rx:"1.5",strokeLinejoin:"round"})]})},{to:"/activity",section:"observability",label:"Activity",icon:n.jsx("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:n.jsx("path",{d:"M3 12h4l2.5-6 4 12 2.5-6H21",strokeLinecap:"round",strokeLinejoin:"round"})})},{to:"/usage",section:"observability",label:"Usage",icon:n.jsx("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:n.jsx("path",{d:"M4 20V10M10 20V4M16 20v-7M22 20H2",strokeLinecap:"round",strokeLinejoin:"round"})})},{to:"/providers",section:"catalog",label:"Providers",icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("rect",{x:"3.5",y:"4.5",width:"17",height:"6",rx:"1.5",strokeLinejoin:"round"}),n.jsx("rect",{x:"3.5",y:"13.5",width:"17",height:"6",rx:"1.5",strokeLinejoin:"round"}),n.jsx("path",{d:"M7 7.5h.01M7 16.5h.01",strokeLinecap:"round",strokeLinejoin:"round"})]})},{to:"/users",section:"access",label:"Users",icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("circle",{cx:"9",cy:"8",r:"3.2",strokeLinejoin:"round"}),n.jsx("path",{d:"M3.5 19a5.5 5.5 0 0 1 11 0",strokeLinecap:"round",strokeLinejoin:"round"}),n.jsx("path",{d:"M16 5.2a3.2 3.2 0 0 1 0 5.6M17.5 19a5.5 5.5 0 0 0-3-4.9",strokeLinecap:"round",strokeLinejoin:"round"})]})},{to:"/keys",section:"access",label:"API keys",icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("circle",{cx:"7.5",cy:"15.5",r:"3.5"}),n.jsx("path",{d:"M10 13l7-7M14 5l3 3M16.5 7.5l2-2",strokeLinecap:"round",strokeLinejoin:"round"})]})},{to:"/budgets",section:"access",label:"Budgets",icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("path",{d:"M3 7.5A1.5 1.5 0 0 1 4.5 6H18a1.5 1.5 0 0 1 1.5 1.5V9",strokeLinejoin:"round"}),n.jsx("rect",{x:"3",y:"7.5",width:"18",height:"12",rx:"1.5",strokeLinejoin:"round"}),n.jsx("path",{d:"M16 13.5h.01",strokeLinecap:"round",strokeLinejoin:"round"}),n.jsx("path",{d:"M21 12v3h-3.5a1.5 1.5 0 0 1 0-3H21z",strokeLinejoin:"round"})]})},{to:"/models",section:"catalog",label:"Models",icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("path",{d:"M12 3l8 4.5v9L12 21l-8-4.5v-9L12 3z",strokeLinejoin:"round"}),n.jsx("path",{d:"M12 12l8-4.5M12 12v9M12 12L4 7.5",strokeLinejoin:"round"})]})},{to:"/routing",section:"catalog",label:"Routing",icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("path",{d:"M4 5h4l4 7 4-7h4",strokeLinejoin:"round"}),n.jsx("path",{d:"M4 19h4l4-7",strokeLinejoin:"round"}),n.jsx("circle",{cx:"19",cy:"19",r:"2"}),n.jsx("circle",{cx:"19",cy:"5",r:"2"})]})},{to:"/tools",section:"system",label:"Tools & Guardrails",icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("path",{d:"M14.7 6.3a4 4 0 0 1 5 5l-8.4 8.4a2 2 0 0 1-2.8 0l-2.2-2.2a2 2 0 0 1 0-2.8z",strokeLinejoin:"round"}),n.jsx("path",{d:"M12 9 5 16",strokeLinecap:"round"})]})},{to:"/settings",section:"system",label:"Settings",icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("circle",{cx:"12",cy:"12",r:"3"}),n.jsx("path",{d:"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z",strokeLinejoin:"round"})]})}];function Jt(){const{logout:e}=re(),t=i.useRef(null),r=i.useRef(null),s=i.useRef(null),[a,o]=i.useState(Vt),[l,d]=i.useState(Wt),[f,y]=i.useState(!1),[x,b]=i.useState($t),[m,T]=i.useState(!1);i.useEffect(()=>{if(typeof window>"u"||typeof window.matchMedia!="function")return;const u=window.matchMedia(Ne),g=j=>{b(j.matches),j.matches||T(!1)};return typeof u.addEventListener=="function"?(u.addEventListener("change",g),()=>u.removeEventListener("change",g)):(u.addListener(g),()=>u.removeListener(g))},[]),i.useEffect(()=>{if(!m)return;const u=g=>{g.key==="Escape"&&T(!1)};return window.addEventListener("keydown",u),()=>window.removeEventListener("keydown",u)},[m]),i.useEffect(()=>{var u,g,j;x&&(m?(u=t.current)==null||u.focus():(g=t.current)!=null&&g.contains(document.activeElement)&&((j=s.current)==null||j.focus()))},[x,m]);const M=i.useCallback(u=>{if(u.key!=="Tab")return;const g=Qt(t.current);if(g.length===0)return;const j=g[0],k=g[g.length-1],F=document.activeElement;u.shiftKey&&(F===j||F===t.current)?(u.preventDefault(),k.focus()):!u.shiftKey&&F===k&&(u.preventDefault(),j.focus())},[]);i.useEffect(()=>{const u=window.setTimeout(()=>{try{window.localStorage.setItem(Pe,String(Math.round(a)))}catch{}},200);return()=>window.clearTimeout(u)},[a]),i.useEffect(()=>{try{window.localStorage.setItem(Te,l?"1":"0")}catch{}},[l]);const Le=i.useCallback(u=>{u.preventDefault(),u.currentTarget.setPointerCapture(u.pointerId),y(!0)},[]),De=i.useCallback(u=>{var j;if(!u.currentTarget.hasPointerCapture(u.pointerId))return;const g=((j=t.current)==null?void 0:j.getBoundingClientRect().left)??0;o(z(u.clientX-g))},[]),Re=i.useCallback(u=>{u.currentTarget.hasPointerCapture(u.pointerId)&&u.currentTarget.releasePointerCapture(u.pointerId),y(!1)},[]),Ie=i.useCallback(u=>{var g;u.preventDefault(),(g=r.current)==null||g.focus()},[]),qe=i.useCallback(u=>{u.key==="ArrowLeft"?(u.preventDefault(),o(g=>z(g-ve))):u.key==="ArrowRight"&&(u.preventDefault(),o(g=>z(g+ve)))},[]),Ae=l?Bt:a,S=x?!1:l,G=x&&m?!0:void 0;return n.jsxs("div",{className:N("relative flex h-full flex-col overflow-hidden",f&&"cursor-col-resize select-none"),children:[n.jsx("button",{type:"button",inert:G,onClick:Ie,className:"sr-only focus:not-sr-only focus:absolute focus:top-3 focus:left-3 focus:z-50 focus:rounded-lg focus:border focus:border-[var(--otari-brand)] focus:bg-[var(--otari-surface)] focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:text-[var(--otari-brand-dark)] focus:shadow-md focus:outline-none",children:"Skip to main content"}),n.jsxs("header",{inert:G,className:"flex shrink-0 items-center justify-between border-b border-[var(--otari-line)] bg-[var(--otari-surface)] px-5 py-3",children:[n.jsxs("div",{className:"flex items-center gap-2.5",children:[n.jsx("button",{type:"button",ref:s,onClick:()=>T(u=>!u),"aria-label":m?"Close navigation":"Open navigation","aria-expanded":m,"aria-controls":"app-sidebar",className:"-ml-1 flex h-8 w-8 items-center justify-center rounded-lg text-[var(--otari-muted)] transition-colors hover:bg-[var(--otari-bg)] hover:text-[var(--otari-ink)] md:hidden",children:n.jsx("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5",children:n.jsx("path",{d:"M4 6h16M4 12h16M4 18h16",strokeLinecap:"round",strokeLinejoin:"round"})})}),n.jsx("img",{src:"/favicon.svg",alt:"",className:"h-7 w-7 shrink-0"}),n.jsx("span",{className:"text-base font-semibold text-[var(--otari-ink)]",children:"Otari"})]}),n.jsx(C,{size:"sm",variant:"outline",onPress:e,"aria-label":"Sign out",children:"Sign out"})]}),n.jsx(Kt,{}),n.jsx(ot,{}),n.jsx(Ft,{}),n.jsxs("div",{className:"flex min-h-0 flex-1",children:[x&&m?n.jsx("div",{"aria-hidden":"true",onClick:()=>T(!1),className:"fixed inset-0 z-30 bg-black/40 md:hidden"}):null,n.jsxs("aside",{ref:t,id:"app-sidebar",role:x?"dialog":void 0,"aria-modal":x&&m?!0:void 0,"aria-label":x?"Navigation":void 0,tabIndex:x?-1:void 0,inert:x&&!m?!0:void 0,onKeyDown:x&&m?M:void 0,style:x?void 0:{width:Ae},className:N("flex flex-col border-r border-[var(--otari-line)] bg-[var(--otari-surface)] focus:outline-none",x?N("fixed inset-y-0 left-0 z-40 w-[17rem] shadow-xl transition-transform duration-200",m?"translate-x-0":"-translate-x-full"):N("relative shrink-0",!f&&"transition-[width] duration-150")),children:[n.jsx("button",{type:"button",onClick:()=>d(u=>!u),"aria-label":l?"Expand sidebar":"Collapse sidebar","aria-pressed":l,title:l?"Expand sidebar":"Collapse sidebar",className:"absolute -right-3 top-4 z-30 hidden h-6 w-6 items-center justify-center rounded-full border border-[var(--otari-line)] bg-[var(--otari-surface)] text-[var(--otari-muted)] shadow-sm transition-colors hover:border-[var(--otari-brand)] hover:text-[var(--otari-brand-dark)] md:flex",children:n.jsx("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",className:N("h-3.5 w-3.5 transition-transform",l&&"rotate-180"),children:n.jsx("path",{d:"M15 6l-6 6 6 6",strokeLinecap:"round",strokeLinejoin:"round"})})}),n.jsx("nav",{className:N("flex flex-col py-4",S?"px-2":"px-3"),children:Gt.map((u,g)=>{const j=Ht.filter(k=>k.section===u.key);return j.length===0?null:n.jsxs("div",{className:g>0?"mt-4":void 0,children:[!S&&u.label?n.jsx("div",{className:"px-3 pb-1 text-[11px] font-semibold tracking-wider text-[var(--otari-muted)] uppercase",children:u.label}):null,g>0&&(S||!u.label)?n.jsx("div",{className:"mx-1 mb-2 border-t border-[var(--otari-line)]"}):null,n.jsx("div",{className:"flex flex-col gap-1",children:j.map(k=>n.jsxs(ee,{to:k.to,end:k.end,onClick:()=>T(!1),"aria-label":S?k.label:void 0,title:S?k.label:void 0,className:({isActive:F})=>N("flex items-center rounded-lg py-2 text-sm font-medium transition-colors",S?"justify-center px-0":"gap-3 px-3",F?"bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]":"text-[var(--otari-muted)] hover:bg-[var(--otari-bg)] hover:text-[var(--otari-ink)]"),children:[k.icon,S?null:k.label]},k.to))})]},u.key)})}),n.jsxs("div",{className:"mt-auto flex flex-col gap-1 pb-3",children:[n.jsxs(ee,{to:"/docs",onClick:()=>T(!1),"aria-label":S?"User guide":void 0,title:S?"User guide":void 0,className:({isActive:u})=>N("flex items-center rounded-lg py-2 text-sm font-medium transition-colors",S?"mx-2 justify-center px-0":"mx-3 gap-3 px-3",u?"bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]":"text-[var(--otari-muted)] hover:bg-[var(--otari-bg)] hover:text-[var(--otari-ink)]"),children:[n.jsxs("svg",{"aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("path",{d:"M12 6.5C10.5 5 8 4.5 4 4.5V18c4 0 6.5.5 8 2 1.5-1.5 4-2 8-2V4.5c-4 0-6.5.5-8 2z",strokeLinejoin:"round"}),n.jsx("path",{d:"M12 6.5V20",strokeLinecap:"round"})]}),S?null:"User guide"]}),n.jsxs("a",{href:"https://otari.ai",target:"_blank",rel:"noreferrer",title:"otari.ai: the hosted Otari gateway",className:N("flex items-center rounded-lg py-2 text-xs font-medium text-[var(--otari-muted)] transition-colors hover:bg-[var(--otari-bg)] hover:text-[var(--otari-brand-dark)]",S?"mx-2 justify-center px-0":"mx-3 gap-2 px-3"),children:[n.jsx("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-4 w-4 shrink-0",children:n.jsx("path",{d:"M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z",strokeLinejoin:"round"})}),S?null:n.jsxs("span",{className:"flex-1",children:["otari.ai ",n.jsx("span",{"aria-hidden":!0,children:"↗"})]})]})]}),l||x?null:n.jsx("div",{role:"separator","aria-orientation":"vertical","aria-label":"Resize sidebar","aria-valuenow":Math.round(a),"aria-valuemin":Ee,"aria-valuemax":Ce,tabIndex:0,onPointerDown:Le,onPointerMove:De,onPointerUp:Re,onKeyDown:qe,className:N("absolute top-0 right-0 z-10 h-full w-1.5 cursor-col-resize touch-none transition-colors","hover:bg-[var(--otari-brand)] focus-visible:bg-[var(--otari-brand)] focus:outline-none",f?"bg-[var(--otari-brand)]":"bg-transparent")})]}),n.jsx("main",{ref:r,id:"main-content",tabIndex:-1,inert:G,className:"flex-1 overflow-y-auto focus:outline-none",children:n.jsx("div",{className:"mx-auto flex max-w-[1800px] flex-col gap-6 px-4 py-5 md:px-6 md:py-6",children:n.jsx($e,{})})})]})]})}function Yt(){const{login:e}=re(),[t,r]=i.useState(""),[s,a]=i.useState(null),[o,l]=i.useState(!1),d=async()=>{const f=t.trim();if(!(!f||o)){l(!0),a(null);try{await et(f)?e():a(new Error("Invalid master key."))}catch(y){a(y)}finally{l(!1)}}};return n.jsx("div",{className:"flex min-h-full items-center justify-center p-6",children:n.jsx(q,{className:"w-full max-w-md",children:n.jsxs(q.Content,{className:"flex flex-col gap-5 p-7",children:[n.jsxs("div",{className:"flex flex-col items-center gap-3 text-center",children:[n.jsx("img",{src:"/favicon.svg",alt:"Otari",className:"h-12 w-12"}),n.jsxs("div",{children:[n.jsx("h1",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"Otari Dashboard"}),n.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"Sign in with your master key to browse models, set pricing, and manage settings."})]})]}),n.jsxs("form",{className:"flex flex-col gap-4",onSubmit:f=>{f.preventDefault(),d()},children:[n.jsxs(He,{value:t,onChange:f=>{r(f),s&&a(null)},type:"password",isRequired:!0,className:"flex flex-col gap-1",children:[n.jsx(ge,{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Master key"}),n.jsx(pe,{placeholder:"otari-mk-… or your master key",autoFocus:!0,autoComplete:"off"})]}),n.jsxs("details",{className:"text-xs text-[var(--otari-muted)]",children:[n.jsx("summary",{className:"cursor-pointer font-medium text-[var(--otari-brand-dark)]",children:"First run? Where to find your key"}),n.jsxs("p",{className:"mt-2 leading-relaxed",children:["If you did not set ",n.jsx("code",{children:"OTARI_MASTER_KEY"}),", Otari generated one and printed it to the server logs on startup. Look for the line ",n.jsx("code",{children:"Your master key:"})," (for example, run"," ",n.jsx("code",{children:"docker logs "}),") and paste it above."]})]}),n.jsx(It,{error:s}),n.jsx(C,{type:"submit",variant:"primary",fullWidth:!0,isDisabled:!t.trim()||o,children:o?"Signing in…":"Sign in"})]}),n.jsx("p",{className:"text-center text-xs text-[var(--otari-muted)]",children:"The key is sent once to this gateway and exchanged for a session cookie; it is never stored in the browser."}),n.jsx("div",{className:"border-t border-[var(--otari-line)] pt-4 text-center",children:n.jsx(Je,{href:"/welcome",className:"text-sm font-medium text-[var(--otari-brand-dark)]",children:"New to Otari? Open the welcome guide"})})]})})})}const Xt=i.lazy(async()=>({default:(await P(async()=>{const{ActivityPage:e}=await import("./ActivityPage-C28CtpkN.js");return{ActivityPage:e}},__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11]))).ActivityPage})),Zt=i.lazy(async()=>({default:(await P(async()=>{const{RoutingPage:e}=await import("./RoutingPage-D1os8M2m.js");return{RoutingPage:e}},__vite__mapDeps([12,1,2,8,5,11,13]))).RoutingPage})),en=i.lazy(async()=>({default:(await P(async()=>{const{BudgetsPage:e}=await import("./BudgetsPage-B9iEC7ec.js");return{BudgetsPage:e}},__vite__mapDeps([14,1,2,6,5,7,8,11]))).BudgetsPage})),tn=i.lazy(async()=>({default:(await P(async()=>{const{DocsPage:e}=await import("./DocsPage-D53o1bCm.js");return{DocsPage:e}},__vite__mapDeps([15,1,2,5]))).DocsPage})),nn=i.lazy(async()=>({default:(await P(async()=>{const{KeysPage:e}=await import("./KeysPage-fg3Rz_lV.js");return{KeysPage:e}},__vite__mapDeps([16,1,2,6,5,7,8,11,17,13]))).KeysPage})),rn=i.lazy(async()=>({default:(await P(async()=>{const{ModelsPage:e}=await import("./ModelsPage-uwVSUUQm.js");return{ModelsPage:e}},__vite__mapDeps([18,1,2,6,5,8,10,11]))).ModelsPage})),sn=i.lazy(async()=>({default:(await P(async()=>{const{OverviewIndex:e}=await import("./OverviewPage-0PkW5qfi.js");return{OverviewIndex:e}},__vite__mapDeps([19,1,2,3,4,5,8]))).OverviewIndex})),an=i.lazy(async()=>({default:(await P(async()=>{const{ProvidersPage:e}=await import("./ProvidersPage-B4LYozbD.js");return{ProvidersPage:e}},__vite__mapDeps([20,1,2,11,5,8]))).ProvidersPage})),on=i.lazy(async()=>({default:(await P(async()=>{const{SettingsPage:e}=await import("./SettingsPage-C2Hp1OPt.js");return{SettingsPage:e}},__vite__mapDeps([21,1,2,5]))).SettingsPage})),cn=i.lazy(async()=>({default:(await P(async()=>{const{ToolsGuardrailsPage:e}=await import("./ToolsGuardrailsPage-C-E4XKsV.js");return{ToolsGuardrailsPage:e}},__vite__mapDeps([22,1,2,5]))).ToolsGuardrailsPage})),ln=i.lazy(async()=>({default:(await P(async()=>{const{UsagePage:e}=await import("./UsagePage-tyubYvXE.js");return{UsagePage:e}},__vite__mapDeps([23,1,2,3,4,5,8,9]))).UsagePage})),un=i.lazy(async()=>({default:(await P(async()=>{const{UsersPage:e}=await import("./UsersPage-Be1Tcz9b.js");return{UsersPage:e}},__vite__mapDeps([24,1,2,6,5,7,8,11,17]))).UsersPage}));function E(e){return n.jsx(i.Suspense,{fallback:n.jsx("div",{role:"status",children:"Loading page…"}),children:e})}function dn(){const{isAuthenticated:e}=re();return e?n.jsx(ze,{children:n.jsx(Qe,{children:n.jsxs(w,{element:n.jsx(Jt,{}),children:[n.jsx(w,{index:!0,element:E(n.jsx(sn,{}))}),n.jsx(w,{path:"providers",element:E(n.jsx(an,{}))}),n.jsx(w,{path:"keys",element:E(n.jsx(nn,{}))}),n.jsx(w,{path:"users",element:E(n.jsx(un,{}))}),n.jsx(w,{path:"budgets",element:E(n.jsx(en,{}))}),n.jsx(w,{path:"activity",element:E(n.jsx(Xt,{}))}),n.jsx(w,{path:"usage",element:E(n.jsx(ln,{}))}),n.jsx(w,{path:"models",element:E(n.jsx(rn,{}))}),n.jsx(w,{path:"aliases",element:n.jsx(de,{to:"/routing",replace:!0})}),n.jsx(w,{path:"routing",element:E(n.jsx(Zt,{}))}),n.jsx(w,{path:"tools",element:E(n.jsx(cn,{}))}),n.jsx(w,{path:"settings",element:E(n.jsx(on,{}))}),n.jsx(w,{path:"docs",element:E(n.jsx(tn,{}))}),n.jsx(w,{path:"*",element:n.jsx(de,{to:"/",replace:!0})})]})})}):n.jsx(Yt,{})}function mn({children:e}){const[t]=i.useState(()=>new Fe({defaultOptions:{queries:{refetchOnWindowFocus:!1,retry:(r,s)=>s instanceof D&&(s.status===401||s.status===403)?!1:r<2}}}));return n.jsx(Ue,{client:t,children:n.jsx(rt,{children:e})})}const _e=document.getElementById("root");if(!_e)throw new Error("Root element #root not found");Ye.createRoot(_e).render(n.jsx(i.StrictMode,{children:n.jsx(mn,{children:n.jsx(dn,{})})}));export{Nr as $,bn as A,zn as B,wn as C,Vn as D,It as E,Kr as F,Hn as G,lr as H,dr as I,mr as J,fr as K,yr as L,qt as M,ur as N,or as O,qr as P,ir as Q,Ar as R,cr as S,ar as T,Pn as U,Xn as V,Kn as W,Tr as X,it as Y,er as Z,Rt as _,lt as a,ht as a0,Pr as a1,Nn as a2,Fr as a3,Dn as a4,Dr as a5,Rr as a6,_r as a7,Ir as a8,Cr as a9,xr as aA,Lt as aa,Dt as ab,Sr as ac,In as ad,On as ae,Fn as af,xt as ag,An as ah,Rn as ai,qn as aj,Ln as ak,_n as al,Un as am,tr as an,nr as ao,rr as ap,Gn as aq,Mn as ar,Jn as as,Yn as at,Er as au,D as av,gn as aw,vn as ax,Lr as ay,vr as az,En as b,sr as c,gr as d,pr as e,Cn as f,kr as g,jn as h,Sn as i,br as j,wr as k,jr as l,Zn as m,pn as n,Br as o,ct as p,Or as q,kn as r,Tn as s,$n as t,hr as u,Bn as v,Qn as w,Wn as x,Ur as y,Mr as z}; diff --git a/src/gateway/static/dashboard/assets/index-Dit1BUBh.js b/src/gateway/static/dashboard/assets/index-Dit1BUBh.js new file mode 100644 index 000000000..b4bdc09a6 --- /dev/null +++ b/src/gateway/static/dashboard/assets/index-Dit1BUBh.js @@ -0,0 +1,2 @@ +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/ActivityPage-BbEENQgu.js","assets/tanstack-query-1t81HyiD.js","assets/react-dgEcD0HR.js","assets/charts-D6upG8fh.js","assets/recharts-EeW53z2i.js","assets/heroui-DhloIxuc.js","assets/tableSelection-B1umVgqc.js","assets/ConfirmDialog-Dt_8xaSM.js","assets/DataTable-BHrpJHmX.js","assets/FilterChips-C0emi5Kg.js","assets/TablePagination-BynkRKqB.js","assets/Field-GEMwIhf7.js","assets/RoutingPage-2qgzgln4.js","assets/UserComboBox-DWvRaj2b.js","assets/BudgetsPage-DGkl3NSe.js","assets/DocsPage-AglHrVWY.js","assets/KeysPage-CEc7g4XL.js","assets/ModelScopeControl-BhMRwgM-.js","assets/ModelsPage-299cCHBM.js","assets/OverviewPage-CHysnnsw.js","assets/ProvidersPage-BPyKQR5x.js","assets/SettingsPage-CLw9HtK0.js","assets/ToolsGuardrailsPage-CSbQtPkh.js","assets/UsagePage-BTnJt3lF.js","assets/UsersPage-C_yR1ElB.js"])))=>i.map(i=>d[i]); +var Fe=Object.defineProperty;var Ue=(e,t,r)=>t in e?Fe(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var me=(e,t,r)=>Ue(e,typeof t!="symbol"?t+"":t,r);import{u as g,j as n,a as v,b as x,k as K,Q as Ke,c as Be}from"./tanstack-query-1t81HyiD.js";import{d as $e,r as i,N as ee,L as ze,O as Qe,H as Ve,e as We,f as j,h as fe}from"./react-dgEcD0HR.js";import{B as C,C as R,L as re,I as se,a as be,b as je,d as A,S as Ge,T as he,c as N,e as He,f as Je}from"./heroui-DhloIxuc.js";(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))s(a);new MutationObserver(a=>{for(const o of a)if(o.type==="childList")for(const c of o.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&s(c)}).observe(document,{childList:!0,subtree:!0});function r(a){const o={};return a.integrity&&(o.integrity=a.integrity),a.referrerPolicy&&(o.referrerPolicy=a.referrerPolicy),a.crossOrigin==="use-credentials"?o.credentials="include":a.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function s(a){if(a.ep)return;a.ep=!0;const o=r(a);fetch(a.href,o)}})();var Ye=$e();const Xe="modulepreload",Ze=function(e){return"/"+e},xe={},P=function(t,r,s){let a=Promise.resolve();if(r&&r.length>0){let c=function(y){return Promise.all(y.map(m=>Promise.resolve(m).then(b=>({status:"fulfilled",value:b}),b=>({status:"rejected",reason:b}))))};document.getElementsByTagName("link");const d=document.querySelector("meta[property=csp-nonce]"),f=(d==null?void 0:d.nonce)||(d==null?void 0:d.getAttribute("nonce"));a=c(r.map(y=>{if(y=Ze(y),y in xe)return;xe[y]=!0;const m=y.endsWith(".css"),b=m?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${y}"]${b}`))return;const h=document.createElement("link");if(h.rel=m?"stylesheet":Xe,m||(h.as="script"),h.crossOrigin="",h.href=y,f&&h.setAttribute("nonce",f),document.head.appendChild(h),m)return new Promise((T,F)=>{h.addEventListener("load",T),h.addEventListener("error",()=>F(new Error(`Unable to preload CSS for ${y}`)))})}))}function o(c){const d=new Event("vite:preloadError",{cancelable:!0});if(d.payload=c,window.dispatchEvent(d),!d.defaultPrevented)throw c}return a.then(c=>{for(const d of c||[])d.status==="rejected"&&o(d.reason);return t().catch(o)})};class D extends Error{constructor(r,s){super(s);me(this,"status");this.name="ApiError",this.status=r}}let $=null;function ye(e){$=e}async function te(e){try{const t=await e.json();if(typeof t.detail=="string")return t.detail;if(t.detail!=null)return JSON.stringify(t.detail)}catch{}return e.statusText||`Request failed (${e.status})`}async function et(e){let t;try{t=await fetch("/v1/auth/session",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({master_key:e})})}catch{throw new D(0,"Network error: could not reach the gateway.")}if(t.status===401||t.status===403)return!1;if(!t.ok)throw new D(t.status,await te(t));return!0}async function tt(){try{await fetch("/v1/auth/session",{method:"DELETE"})}catch{}}async function l(e,t={}){const r=new Headers(t.headers);r.set("Accept","application/json"),t.body!=null&&!r.has("Content-Type")&&r.set("Content-Type","application/json");let s;try{s=await fetch(e,{...t,headers:r})}catch{throw new D(0,"Network error: could not reach the gateway.")}if(s.status===401||s.status===403)throw $==null||$(),new D(s.status,await te(s));if(!s.ok)throw new D(s.status,await te(s));if(s.status!==204)return await s.json()}const ne="otari.dashboard.hasSession",we=i.createContext(null);function nt(){try{return window.localStorage.getItem(ne)==="1"}catch{return!1}}function rt({children:e}){const t=g(),[r,s]=i.useState(nt),a=i.useCallback(()=>{tt(),s(!1),t.clear();try{window.localStorage.removeItem(ne)}catch{}},[t]),o=i.useCallback(()=>{t.clear(),s(!0);try{window.localStorage.setItem(ne,"1")}catch{}},[t]);i.useEffect(()=>(ye(a),()=>ye(null)),[a]);const c=i.useMemo(()=>({isAuthenticated:r,login:o,logout:a}),[r,o,a]);return n.jsx(we.Provider,{value:c,children:e})}function ae(){const e=i.useContext(we);if(!e)throw new Error("useAuth must be used within an AuthProvider");return e}function st(e){return e instanceof D&&e.status===0}function at(){const e=g(),[t,r]=i.useState(!1);return i.useEffect(()=>{const s=e.getQueryCache(),a=()=>s.getAll().some(o=>o.state.status==="error"&&st(o.state.error));return r(a()),s.subscribe(()=>r(a()))},[e]),t}function ot(){return at()?n.jsxs("div",{role:"alert","aria-live":"assertive",className:"fixed right-4 bottom-4 z-50 flex max-w-sm items-start gap-2.5 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 shadow-lg",children:[n.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2","aria-hidden":!0,className:"mt-0.5 h-5 w-5 shrink-0",children:[n.jsx("path",{d:"M12 9v4M12 17h.01",strokeLinecap:"round",strokeLinejoin:"round"}),n.jsx("path",{d:"M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z",strokeLinejoin:"round"})]}),n.jsxs("span",{children:[n.jsx("strong",{className:"font-semibold",children:"Can’t reach the gateway."})," The backend isn’t responding; data won’t load or save until the connection is restored."]})]}):null}const Q=3600,_=86400,it=365*_,gn=[{key:"1h",label:"Last hour",seconds:Q,bucket:"hour"},{key:"24h",label:"24h",seconds:_,bucket:"hour"},{key:"7d",label:"7d",seconds:7*_,bucket:"day"},{key:"30d",label:"30d",seconds:30*_,bucket:"day"},{key:"90d",label:"90d",seconds:90*_,bucket:"day"},{key:"12mo",label:"12mo",seconds:it,bucket:"day"}],pn="30d",vn=[{key:"1h",label:"1h",seconds:Q,bucket:"hour"},{key:"24h",label:"24h",seconds:_,bucket:"hour"},{key:"7d",label:"7d",seconds:7*_,bucket:"day"},{key:"30d",label:"30d",seconds:30*_,bucket:"day"},{key:"all",label:"All",seconds:null,bucket:"day"}],bn="24h",jn="custom";function wn(e,t){return e.find(r=>r.key===t)}function lt(e,t=Date.now()){return new Date(t-e*1e3).toISOString()}function ct(e){return(e==="hour"?Q:_)*1e3}function Sn(e,t,r=Date.now()){const s=new Date(e).getTime();return(t?new Date(t).getTime():r)-s<=_*1e3?"hour":"day"}function kn(e,t,r,s){if(e.length===0)return null;const a=Math.max(0,Math.min(t,r)),o=Math.min(e.length-1,Math.max(t,r)),c=new Date(e[a]).getTime(),d=new Date(e[o]).getTime()+ct(s);return{startIso:new Date(c).toISOString(),endIso:new Date(d).toISOString()}}function En(e,t,r){const s=e.length;if(s===0)return{startIndex:0,endIndex:0};const a=e.map(d=>new Date(d).getTime());let o=0;if(t){const d=new Date(t).getTime();for(let f=0;fl("/v1/models"),staleTime:6e4})}function ft(){return v({queryKey:[dt],queryFn:()=>l("/dashboard-build.json"),refetchInterval:mt,refetchOnWindowFocus:!0,staleTime:0,retry:!1})}function Tn(){return v({queryKey:[le],queryFn:()=>l("/v1/models/discoverable"),staleTime:5*6e4})}function Nn(){return v({queryKey:[ce],queryFn:()=>l("/v1/providers"),staleTime:5*6e4})}function _n(){return v({queryKey:["provider-catalog"],queryFn:()=>l("/v1/providers/catalog"),staleTime:1/0})}function Ln(e){return v({queryKey:["provider-catalog",e],queryFn:()=>l(`/v1/providers/catalog/${encodeURIComponent(e)}`),enabled:e!=="",staleTime:1/0})}function Dn(){return v({queryKey:[ue],queryFn:()=>l("/v1/providers/health"),staleTime:ge,refetchInterval:ge})}function Rn(){const e=g();return x({mutationFn:()=>l("/v1/providers/health?refresh=true"),onSuccess:t=>e.setQueryData([ue],t)})}function In(){return v({queryKey:[Ee],queryFn:()=>l("/v1/provider-credentials"),staleTime:6e4})}function W(e){e.invalidateQueries({queryKey:[Ee]}),e.invalidateQueries({queryKey:[ce]}),e.invalidateQueries({queryKey:[L]}),e.invalidateQueries({queryKey:[le]}),e.invalidateQueries({queryKey:[ue]})}function qn(){const e=g();return x({mutationFn:t=>l("/v1/provider-credentials",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>W(e)})}function An(){const e=g();return x({mutationFn:({instance:t,body:r})=>l(`/v1/provider-credentials/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify(r)}),onSuccess:()=>W(e)})}function On(){const e=g();return x({mutationFn:t=>l(`/v1/provider-credentials/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>W(e)})}function Mn(){const e=g();return x({mutationFn:()=>l("/v1/provider-credentials/reencrypt",{method:"POST"}),onSuccess:()=>W(e)})}function Fn(){return x({mutationFn:e=>l(`/v1/provider-credentials/${encodeURIComponent(e)}/test`,{method:"POST"})})}function Un(){return x({mutationFn:e=>l("/v1/provider-credentials/test",{method:"POST",body:JSON.stringify(e)})})}function Kn(){return v({queryKey:[ut],queryFn:()=>l("/v1/models/metadata"),staleTime:10*6e4})}function Bn(){return v({queryKey:[oe],queryFn:()=>l("/v1/aliases"),staleTime:6e4})}function $n(){return v({queryKey:[ie],queryFn:()=>l("/v1/routing/policies"),staleTime:6e4})}function zn(){const e=g();return x({mutationFn:t=>l("/v1/routing/policies",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[ie]}),e.invalidateQueries({queryKey:[L]})}})}function Qn(){const e=g();return x({mutationFn:({name:t,userId:r})=>{const s=r==null?"":`?user_id=${encodeURIComponent(r)}`;return l(`/v1/routing/policies/${encodeURIComponent(t)}${s}`,{method:"DELETE"})},onSuccess:()=>{e.invalidateQueries({queryKey:[ie]}),e.invalidateQueries({queryKey:[L]})}})}function Vn(){const e=g();return x({mutationFn:t=>l("/v1/aliases",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[oe]}),e.invalidateQueries({queryKey:[L]})}})}function Wn(){const e=g();return x({mutationFn:({name:t,userId:r})=>{const s=r==null?"":`?user_id=${encodeURIComponent(r)}`;return l(`/v1/aliases/${encodeURIComponent(t)}${s}`,{method:"DELETE"})},onSuccess:()=>{e.invalidateQueries({queryKey:[oe]}),e.invalidateQueries({queryKey:[L]})}})}function ht(){return v({queryKey:[Se],queryFn:()=>l("/v1/settings"),staleTime:6e4})}function xt(){const e=g();return x({mutationFn:t=>l("/v1/settings",{method:"PATCH",body:JSON.stringify(t)}),onSuccess:t=>{e.setQueryData([Se],t),e.invalidateQueries({queryKey:[L]}),e.invalidateQueries({queryKey:[le]})}})}function Gn(){return x({mutationFn:()=>l("/v1/settings/master-key/rotate",{method:"POST"})})}function Hn(){return v({queryKey:[ke],queryFn:()=>l("/v1/tool-settings"),staleTime:6e4})}function Jn(){const e=g();return x({mutationFn:t=>l("/v1/tool-settings",{method:"PATCH",body:JSON.stringify(t)}),onSuccess:t=>{e.setQueryData([ke],t)}})}function Yn(){return x({mutationFn:({service:e,url:t})=>l(`/v1/tool-settings/${encodeURIComponent(e)}/test`,{method:"POST",body:JSON.stringify({url:t})})})}const H=1e3,yt=100;async function gt(){const e=[];for(let t=0;tl("/v1/pricing",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[V]}),e.invalidateQueries({queryKey:[L]})}})}function er(){const e=g();return x({mutationFn:t=>l(`/v1/pricing/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>{e.invalidateQueries({queryKey:[V]}),e.invalidateQueries({queryKey:[L]})}})}function tr(){return x({mutationFn:()=>l("/v1/pricing/refresh",{method:"POST"})})}function nr(){const e=g();return x({mutationFn:()=>l("/v1/pricing/refresh/confirm",{method:"POST"}),onSuccess:()=>{e.invalidateQueries({queryKey:[V]}),e.invalidateQueries({queryKey:[L]}),e.invalidateQueries({queryKey:[ce]})}})}function rr(){return x({mutationFn:()=>l("/v1/pricing/refresh/reject",{method:"POST"})})}const J=1e3,pt=100;async function vt(){const e=[];for(let t=0;tl("/v1/keys",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>void e.invalidateQueries({queryKey:[O]})})}function or(){const e=g();return x({mutationFn:({id:t,body:r})=>l(`/v1/keys/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify(r)}),onSuccess:()=>void e.invalidateQueries({queryKey:[O]})})}function ir(){const e=g();return x({mutationFn:t=>l(`/v1/keys/${encodeURIComponent(t)}/rotate`,{method:"POST"}),onSuccess:()=>void e.invalidateQueries({queryKey:[O]})})}function lr(){const e=g();return x({mutationFn:t=>l(`/v1/keys/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>void e.invalidateQueries({queryKey:[O]})})}const Y=1e3,bt=100;async function jt(){const e=[];for(let t=0;tl(`/v1/budgets/${encodeURIComponent(e)}/reset-logs`),enabled:e!==null,staleTime:6e4})}function dr(){const e=g();return x({mutationFn:t=>l("/v1/budgets",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>void e.invalidateQueries({queryKey:[M]})})}function mr(){const e=g();return x({mutationFn:({id:t,body:r})=>l(`/v1/budgets/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify(r)}),onSuccess:()=>void e.invalidateQueries({queryKey:[M]})})}function fr(){const e=g();return x({mutationFn:t=>l(`/v1/budgets/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>void e.invalidateQueries({queryKey:[M]})})}const X=1e3,wt=100;async function St(){const e=[];for(let t=0;tl("/v1/users",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>de(e)})}function yr(){const e=g();return x({mutationFn:({id:t,body:r})=>l(`/v1/users/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify(r)}),onSuccess:()=>de(e)})}function gr(){const e=g();return x({mutationFn:t=>l(`/v1/users/${encodeURIComponent(t)}`,{method:"DELETE"}),onSuccess:()=>{de(e),e.invalidateQueries({queryKey:[O]})}})}function B(e){const t=new URLSearchParams,r=(s,a)=>{for(const o of typeof a=="string"?[a]:a??[])o&&t.append(s,o)};return e.start_date&&t.set("start_date",e.start_date),e.end_date&&t.set("end_date",e.end_date),e.status&&t.set("status",e.status),r("model",e.model),e.endpoint&&t.set("endpoint",e.endpoint),e.provider&&t.set("provider",e.provider),r("user_id",e.user_id),r("api_key_id",e.api_key_id),e.source&&t.set("source",e.source),e.source_label&&t.set("source_label",e.source_label),e.tool&&t.set("tool",e.tool),e.priced!==void 0&&t.set("priced",String(e.priced)),e.counts_toward_budget!==void 0&&t.set("counts_toward_budget",String(e.counts_toward_budget)),t}function pr(e,t,r){return v({queryKey:[I,"list",e,t,r],queryFn:()=>{const s=B(e);return s.set("skip",String(t*r)),s.set("limit",String(r)),l(`/v1/usage?${s.toString()}`)},placeholderData:K,staleTime:1e4})}function vr(e,t=!0){return v({queryKey:[I,"count",e],queryFn:()=>l(`/v1/usage/count?${B(e).toString()}`),enabled:t,placeholderData:K,staleTime:1e4})}const kt=6e4;function Et(e,t=!0){return v({queryKey:[I,"count","failures",e],queryFn:()=>{const r={status:"error",source:"gateway",start_date:lt(e)};return l(`/v1/usage/count?${B(r).toString()}`)},enabled:t,refetchInterval:kt,refetchOnWindowFocus:!0,staleTime:0,retry:!1})}const Ct=1e3;function br(e){const t=[...new Set(e)].sort();return v({queryKey:[I,"groups",t],queryFn:()=>{const r=new URLSearchParams;for(const s of t)r.append("request_group_id",s);return r.set("limit",String(Ct)),l(`/v1/usage?${r.toString()}`)},enabled:t.length>0,placeholderData:K,staleTime:3e4})}function jr(){const e=g();return x({mutationFn:t=>l("/v1/usage",{method:"DELETE",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[I]})}})}function wr(){const e=g();return x({mutationFn:t=>l("/v1/usage/set-price",{method:"POST",body:JSON.stringify(t)}),onSuccess:()=>{e.invalidateQueries({queryKey:[I]})}})}const Sr=[];function kr(e,t,r,s=!0){return v({queryKey:[I,"summary",e,t,r??"all"],queryFn:()=>{const a=B(e);if(a.set("bucket",t),r)for(const o of r.length>0?r:["none"])a.append("dimensions",o);return l(`/v1/usage/summary?${a.toString()}`)},enabled:s,placeholderData:K,staleTime:3e4})}function Er(e,t,r,s=!0){return v({queryKey:[I,"series",e,t,r],queryFn:()=>{const a=B(e);return a.set("bucket",t),a.set("group_by",r),l(`/v1/usage/series?${a.toString()}`)},enabled:s&&r!==null,placeholderData:K,staleTime:3e4,retry:(a,o)=>!(o instanceof D&&o.status===404)&&a<3})}async function Pt(e,t=navigator.clipboard){if(t)try{return await t.writeText(e),!0}catch{}return Tt(e)}function Tt(e){const t=document.createElement("textarea");t.value=e,t.readOnly=!0,t.style.position="fixed",t.style.top="-1000px",t.style.opacity="0",document.body.appendChild(t);const r=document.getSelection(),s=r&&r.rangeCount>0?r.getRangeAt(0):null,a=document.activeElement instanceof HTMLElement?document.activeElement:null;t.select();let o=!1;try{o=document.execCommand("copy")}catch{o=!1}return t.remove(),r&&s&&(r.removeAllRanges(),r.addRange(s)),a==null||a.focus(),o}function Cr(e){return e==null?"0":new Intl.NumberFormat("en-US").format(e)}function Pr(e){if(e==null)return"$0.00";const t=e!==0&&Math.abs(e)<.01?4:2;return new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",minimumFractionDigits:2,maximumFractionDigits:t}).format(e)}function Tr(e){if(e==null)return"—";if(e>=1e6){const t=e/1e6;return`${Number.isInteger(t)?t:t.toFixed(1)}M`}if(e>=1e3){const t=Math.round(e/1e3);return t>=1e3?"1M":`${t}K`}return String(e)}const Nt=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function Nr(e){if(!e)return"—";const t=/^(\d{4})-(\d{2})/.exec(e);if(!t)return e;const r=Number(t[2])-1;return r<0||r>11?t[1]:`${Nt[r]} ${t[1]}`}const _t=new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",maximumFractionDigits:2});function _r(e){return _t.format(e)}function Lr(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function Lt(e){return`${(e*100).toFixed(1)}%`}function Dr(e,t){return t===void 0||t===0?null:(e-t)/t}function Dt(e,t=Date.now()){if(!e)return"never";const r=new Date(e);if(Number.isNaN(r.getTime()))return e;const s=Math.round((t-r.getTime())/1e3),a=s<0,o=Math.abs(s),c=[["second",60],["minute",60],["hour",24],["day",30],["month",12],["year",Number.POSITIVE_INFINITY]];let d=o,f="second";for(const[m,b]of c){if(f=m,d0?"▲":e<0?"▼":"•";return n.jsxs("span",{className:"text-[var(--otari-muted)]",children:[t," ",Lt(Math.abs(e))," vs prev"]})}function Rt(e){return e instanceof D||e instanceof Error?e.message:"Something went wrong."}function It({error:e}){return e?n.jsx("div",{role:"alert",className:"rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700",children:Rt(e)}):null}function qt({tone:e="info",children:t}){const r=e==="warning"?"border-amber-200 bg-amber-50 text-amber-800":"border-[var(--otari-brand)] bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]";return n.jsx("div",{className:`rounded-lg border px-4 py-3 text-sm ${r}`,children:t})}function qr({title:e,description:t,action:r}){return n.jsxs("div",{className:"flex flex-col gap-3",children:[n.jsxs("div",{children:[n.jsx("h1",{className:"text-xl font-semibold text-[var(--otari-ink)]",children:e}),t?n.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:t}):null]}),r?n.jsx("div",{className:"flex flex-wrap gap-2",children:r}):null]})}function At(e){const[t,r]=i.useState(()=>Date.now());return i.useEffect(()=>{let s;const a=()=>{s===void 0&&(s=setInterval(()=>r(Date.now()),e))},o=()=>{s!==void 0&&(clearInterval(s),s=void 0)},c=()=>{r(Date.now()),document.visibilityState==="visible"?a():o()};return c(),document.addEventListener("visibilitychange",c),()=>{o(),document.removeEventListener("visibilitychange",c)}},[e]),t}function Ar({onRefresh:e,isFetching:t=!1,updatedAt:r,label:s="Refresh"}){const a=At(15e3),o=r?Dt(new Date(r).toISOString(),a):null;return n.jsxs("span",{className:"inline-flex items-center gap-2",children:[o?n.jsxs("span",{className:"text-xs text-[var(--otari-muted)]",children:["Updated ",o]}):null,n.jsx(C,{variant:"outline",size:"sm",isIconOnly:!0,isDisabled:t,onPress:e,"aria-label":s,children:n.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:`h-4 w-4 ${t?"animate-spin":""}`,"aria-hidden":"true",children:[n.jsx("path",{d:"M20 11a8 8 0 1 0-.5 4",strokeLinecap:"round",strokeLinejoin:"round"}),n.jsx("path",{d:"M20 4v5h-5",strokeLinecap:"round",strokeLinejoin:"round"})]})})]})}function Or({value:e,label:t,className:r,children:s}){const a=o=>o.stopPropagation();return n.jsxs("span",{className:"inline-flex items-center gap-1",children:[n.jsx("span",{tabIndex:-1,className:`select-text outline-none ${r??""}`,onPointerDown:a,onMouseDown:a,children:s??e}),n.jsx(Ot,{value:e,label:t})]})}function Ot({value:e,label:t}){const[r,s]=i.useState("idle"),a=i.useRef(void 0);i.useEffect(()=>()=>clearTimeout(a.current),[]);const o=async()=>{const c=await Pt(e);s(c?"copied":"failed"),clearTimeout(a.current),a.current=setTimeout(()=>s("idle"),c?1500:5e3)};return n.jsxs(he.Root,{isOpen:r!=="idle",children:[n.jsx(C,{size:"sm",variant:"ghost",isIconOnly:!0,"aria-label":`Copy ${t}`,onPress:o,children:n.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-3.5 w-3.5","aria-hidden":"true",children:[n.jsx("rect",{x:"9",y:"9",width:"11",height:"11",rx:"2"}),n.jsx("path",{d:"M5 15V5a2 2 0 0 1 2-2h8",strokeLinecap:"round",strokeLinejoin:"round"})]})}),n.jsx(he.Content,{placement:"top",showArrow:!0,children:r==="failed"?"Copy blocked, select the value and press Ctrl/Cmd-C":"Copied!"})]})}function Mr({title:e,description:t,actionLabel:r,onAction:s,isActionDisabled:a,children:o}){return n.jsx(A,{children:n.jsxs(A.Content,{className:"flex flex-col gap-4 p-6",children:[n.jsxs("div",{children:[n.jsx("h2",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:e}),t?n.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:t}):null]}),o,r&&s?n.jsx("div",{children:n.jsx(C,{variant:"primary",isDisabled:a,onPress:s,children:r})}):null]})})}function Fr({label:e="Loading…"}){return n.jsxs("div",{role:"status",className:"flex items-center justify-center gap-2 px-4 py-10 text-sm text-[var(--otari-muted)]",children:[n.jsx(Ge,{size:"sm"}),n.jsx("span",{children:e})]})}function Ur({children:e,confirmLabel:t,onConfirm:r,isPending:s}){const[a,o]=i.useState(!1);return a?n.jsxs("span",{className:"inline-flex items-center gap-1",children:[n.jsx(C,{size:"sm",variant:"danger",isDisabled:s,onPress:r,children:t}),n.jsx(C,{size:"sm",variant:"ghost",isDisabled:s,onPress:()=>o(!1),children:"Cancel"})]}):n.jsx(C,{size:"sm",variant:"danger-soft",onPress:()=>o(!0),children:e})}const Mt="rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none";function Kr({id:e,label:t,ariaLabel:r,value:s,onChange:a,options:o,children:c,disabled:d}){const f=i.useId(),y=e??(t?f:void 0),m=n.jsx("select",{id:y,"aria-label":t?void 0:r,value:s,disabled:d,onChange:b=>a(b.target.value),className:Mt,children:o?o.map(b=>n.jsx("option",{value:b.value,children:b.label},b.value)):c});return t?n.jsxs("div",{className:"flex flex-col gap-1",children:[n.jsx("label",{htmlFor:y,className:"text-xs font-medium text-[var(--otari-muted)]",children:t}),m]}):m}function Br({label:e,value:t,onChange:r,options:s,placeholder:a,maxVisible:o=50,allowsCustom:c=!1}){const d=h=>{var T;return((T=s.find(F=>F.value===h))==null?void 0:T.label)??h},[f,y]=i.useState(()=>d(t));i.useEffect(()=>{y(d(t))},[t]);const m=f.trim().toLowerCase(),b=s.filter(h=>!m||h.value.toLowerCase().includes(m)||h.label.toLowerCase().includes(m)).slice(0,o);return n.jsxs(R.Root,{allowsEmptyCollection:!0,allowsCustomValue:c,menuTrigger:"focus",inputValue:f,onInputChange:h=>{y(h),c?r(h.trim()):h.trim()===""&&r("")},onSelectionChange:h=>{h!=null&&r(String(h))},className:"flex flex-col gap-1",children:[n.jsx(re,{className:"text-xs font-medium text-[var(--otari-muted)]",children:e}),n.jsxs(R.InputGroup,{children:[n.jsx(se,{placeholder:a,autoComplete:"off",onFocus:h=>h.currentTarget.select()}),n.jsx(R.Trigger,{})]}),n.jsx(R.Popover,{children:n.jsx(be,{items:b,className:"max-h-72 overflow-auto",children:h=>n.jsx(je,{id:h.value,textValue:h.label,children:h.label})})})]})}function $r({label:e,values:t,onChange:r,options:s,placeholder:a,maxVisible:o=50}){const[c,d]=i.useState(""),f=c.trim().toLowerCase(),y=s.filter(m=>!t.includes(m.value)).filter(m=>!f||m.value.toLowerCase().includes(f)||m.label.toLowerCase().includes(f)).slice(0,o);return n.jsxs(R.Root,{allowsEmptyCollection:!0,menuTrigger:"focus",inputValue:c,onInputChange:d,selectedKey:null,onSelectionChange:m=>{if(m==null)return;const b=String(m);t.includes(b)||r([...t,b]),d("")},className:"flex flex-col gap-1",children:[n.jsx(re,{className:"text-xs font-medium text-[var(--otari-muted)]",children:e}),n.jsxs(R.InputGroup,{children:[n.jsx(se,{placeholder:t.length===0?a:`${t.length} selected`,autoComplete:"off"}),n.jsx(R.Trigger,{})]}),n.jsx(R.Popover,{children:n.jsx(be,{items:y,className:"max-h-72 overflow-auto",children:m=>n.jsx(je,{id:m.value,textValue:m.label,children:m.label})})})]})}function Ft(){var f,y;const e=ht(),t=xt(),[r,s]=i.useState(!1),o=((f=e.data)==null?void 0:f.require_pricing)===!0&&e.data.default_pricing===!1&&!r,d=((y=Et(Q,o).data)==null?void 0:y.total)??0;return o?n.jsx("div",{className:"shrink-0 px-6 pt-3",children:n.jsx(qt,{tone:"warning",children:n.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[n.jsxs("span",{children:["Requests are rejected until pricing is set (",n.jsx("code",{children:"require_pricing"})," is on). Enable default pricing to meter new models with public rates right away.",d>0?n.jsxs(n.Fragment,{children:[" ",n.jsxs("strong",{className:"font-semibold",children:[d.toLocaleString()," ",d===1?"request":"requests"," failed in the last hour."]})," ",n.jsx(ze,{to:"/activity?status=error&range=1h&source=gateway",className:"underline underline-offset-2",children:"View failed requests"})]}):null]}),n.jsxs("span",{className:"flex items-center gap-2",children:[n.jsx(C,{size:"sm",variant:"primary",isDisabled:t.isPending,onPress:()=>t.mutate({default_pricing:!0}),children:t.isPending?"Enabling…":"Enable default pricing"}),n.jsx(C,{size:"sm",variant:"ghost",onPress:()=>s(!0),children:"Dismiss"})]})]})})}):null}function Ut(){const{data:e}=ft(),t=i.useRef(null);return e&&t.current===null&&(t.current=e.build),e!=null&&t.current!=null&&e.build!==t.current}function Kt(){const e=Ut(),[t,r]=i.useState(!1);return!e||t?null:n.jsx("div",{className:"pointer-events-none absolute inset-x-0 top-0 z-50 flex justify-center",children:n.jsxs("div",{role:"status",className:"pointer-events-auto mt-1.5 flex items-center gap-3 rounded-full border border-[var(--otari-brand)] bg-[var(--otari-brand-tint)] py-1.5 pr-1.5 pl-4 text-sm text-[var(--otari-brand-dark)] shadow-md",children:[n.jsxs("span",{children:[n.jsx("strong",{className:"font-semibold",children:"An update is available."})," Reloading keeps you signed in."]}),n.jsx(C,{size:"sm",variant:"primary",onPress:()=>window.location.reload(),children:"Update now"}),n.jsx(C,{size:"sm",variant:"ghost",onPress:()=>r(!0),children:"Later"})]})})}const Pe=200,Te=480,Z=240,Bt=60,Ne="otari.dashboard.sidebarWidth",_e="otari.dashboard.sidebarCollapsed",ve=16,Le="(max-width: 767px)",z=e=>Math.min(Te,Math.max(Pe,e));function $t(){return typeof window>"u"||typeof window.matchMedia!="function"?!1:window.matchMedia(Le).matches}const zt='a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])';function Qt(e){return e?Array.from(e.querySelectorAll(zt)).filter(t=>t.offsetParent!==null||t===document.activeElement):[]}function Vt(){if(typeof window>"u")return Z;try{const e=window.localStorage.getItem(Ne),t=e?Number.parseInt(e,10):Number.NaN;return Number.isNaN(t)?Z:z(t)}catch{return Z}}function Wt(){if(typeof window>"u")return!1;try{return window.localStorage.getItem(_e)==="1"}catch{return!1}}const Gt=[{key:"home"},{key:"observability",label:"Observability"},{key:"catalog",label:"Catalog"},{key:"access",label:"Access"},{key:"system"}],Ht=[{to:"/",section:"home",label:"Overview",end:!0,icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("rect",{x:"3.5",y:"3.5",width:"7",height:"7",rx:"1.5",strokeLinejoin:"round"}),n.jsx("rect",{x:"13.5",y:"3.5",width:"7",height:"7",rx:"1.5",strokeLinejoin:"round"}),n.jsx("rect",{x:"3.5",y:"13.5",width:"7",height:"7",rx:"1.5",strokeLinejoin:"round"}),n.jsx("rect",{x:"13.5",y:"13.5",width:"7",height:"7",rx:"1.5",strokeLinejoin:"round"})]})},{to:"/activity",section:"observability",label:"Activity",icon:n.jsx("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:n.jsx("path",{d:"M3 12h4l2.5-6 4 12 2.5-6H21",strokeLinecap:"round",strokeLinejoin:"round"})})},{to:"/usage",section:"observability",label:"Usage",icon:n.jsx("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:n.jsx("path",{d:"M4 20V10M10 20V4M16 20v-7M22 20H2",strokeLinecap:"round",strokeLinejoin:"round"})})},{to:"/providers",section:"catalog",label:"Providers",icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("rect",{x:"3.5",y:"4.5",width:"17",height:"6",rx:"1.5",strokeLinejoin:"round"}),n.jsx("rect",{x:"3.5",y:"13.5",width:"17",height:"6",rx:"1.5",strokeLinejoin:"round"}),n.jsx("path",{d:"M7 7.5h.01M7 16.5h.01",strokeLinecap:"round",strokeLinejoin:"round"})]})},{to:"/users",section:"access",label:"Users",icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("circle",{cx:"9",cy:"8",r:"3.2",strokeLinejoin:"round"}),n.jsx("path",{d:"M3.5 19a5.5 5.5 0 0 1 11 0",strokeLinecap:"round",strokeLinejoin:"round"}),n.jsx("path",{d:"M16 5.2a3.2 3.2 0 0 1 0 5.6M17.5 19a5.5 5.5 0 0 0-3-4.9",strokeLinecap:"round",strokeLinejoin:"round"})]})},{to:"/keys",section:"access",label:"API keys",icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("circle",{cx:"7.5",cy:"15.5",r:"3.5"}),n.jsx("path",{d:"M10 13l7-7M14 5l3 3M16.5 7.5l2-2",strokeLinecap:"round",strokeLinejoin:"round"})]})},{to:"/budgets",section:"access",label:"Budgets",icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("path",{d:"M3 7.5A1.5 1.5 0 0 1 4.5 6H18a1.5 1.5 0 0 1 1.5 1.5V9",strokeLinejoin:"round"}),n.jsx("rect",{x:"3",y:"7.5",width:"18",height:"12",rx:"1.5",strokeLinejoin:"round"}),n.jsx("path",{d:"M16 13.5h.01",strokeLinecap:"round",strokeLinejoin:"round"}),n.jsx("path",{d:"M21 12v3h-3.5a1.5 1.5 0 0 1 0-3H21z",strokeLinejoin:"round"})]})},{to:"/models",section:"catalog",label:"Models",icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("path",{d:"M12 3l8 4.5v9L12 21l-8-4.5v-9L12 3z",strokeLinejoin:"round"}),n.jsx("path",{d:"M12 12l8-4.5M12 12v9M12 12L4 7.5",strokeLinejoin:"round"})]})},{to:"/routing",section:"catalog",label:"Routing",icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("path",{d:"M4 5h4l4 7 4-7h4",strokeLinejoin:"round"}),n.jsx("path",{d:"M4 19h4l4-7",strokeLinejoin:"round"}),n.jsx("circle",{cx:"19",cy:"19",r:"2"}),n.jsx("circle",{cx:"19",cy:"5",r:"2"})]})},{to:"/tools",section:"system",label:"Tools & Guardrails",icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("path",{d:"M14.7 6.3a4 4 0 0 1 5 5l-8.4 8.4a2 2 0 0 1-2.8 0l-2.2-2.2a2 2 0 0 1 0-2.8z",strokeLinejoin:"round"}),n.jsx("path",{d:"M12 9 5 16",strokeLinecap:"round"})]})},{to:"/settings",section:"system",label:"Settings",icon:n.jsxs("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("circle",{cx:"12",cy:"12",r:"3"}),n.jsx("path",{d:"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z",strokeLinejoin:"round"})]})}];function Jt(){const{logout:e}=ae(),t=i.useRef(null),r=i.useRef(null),s=i.useRef(null),[a,o]=i.useState(Vt),[c,d]=i.useState(Wt),[f,y]=i.useState(!1),[m,b]=i.useState($t),[h,T]=i.useState(!1);i.useEffect(()=>{if(typeof window>"u"||typeof window.matchMedia!="function")return;const u=window.matchMedia(Le),p=w=>{b(w.matches),w.matches||T(!1)};return typeof u.addEventListener=="function"?(u.addEventListener("change",p),()=>u.removeEventListener("change",p)):(u.addListener(p),()=>u.removeListener(p))},[]),i.useEffect(()=>{if(!h)return;const u=p=>{p.key==="Escape"&&T(!1)};return window.addEventListener("keydown",u),()=>window.removeEventListener("keydown",u)},[h]),i.useEffect(()=>{var u,p,w;m&&(h?(u=t.current)==null||u.focus():(p=t.current)!=null&&p.contains(document.activeElement)&&((w=s.current)==null||w.focus()))},[m,h]);const F=i.useCallback(u=>{if(u.key!=="Tab")return;const p=Qt(t.current);if(p.length===0)return;const w=p[0],k=p[p.length-1],U=document.activeElement;u.shiftKey&&(U===w||U===t.current)?(u.preventDefault(),k.focus()):!u.shiftKey&&U===k&&(u.preventDefault(),w.focus())},[]);i.useEffect(()=>{const u=window.setTimeout(()=>{try{window.localStorage.setItem(Ne,String(Math.round(a)))}catch{}},200);return()=>window.clearTimeout(u)},[a]),i.useEffect(()=>{try{window.localStorage.setItem(_e,c?"1":"0")}catch{}},[c]);const Re=i.useCallback(u=>{u.preventDefault(),u.currentTarget.setPointerCapture(u.pointerId),y(!0)},[]),Ie=i.useCallback(u=>{var w;if(!u.currentTarget.hasPointerCapture(u.pointerId))return;const p=((w=t.current)==null?void 0:w.getBoundingClientRect().left)??0;o(z(u.clientX-p))},[]),qe=i.useCallback(u=>{u.currentTarget.hasPointerCapture(u.pointerId)&&u.currentTarget.releasePointerCapture(u.pointerId),y(!1)},[]),Ae=i.useCallback(u=>{var p;u.preventDefault(),(p=r.current)==null||p.focus()},[]),Oe=i.useCallback(u=>{u.key==="ArrowLeft"?(u.preventDefault(),o(p=>z(p-ve))):u.key==="ArrowRight"&&(u.preventDefault(),o(p=>z(p+ve)))},[]),Me=c?Bt:a,S=m?!1:c,G=m&&h?!0:void 0;return n.jsxs("div",{className:N("relative flex h-full flex-col overflow-hidden",f&&"cursor-col-resize select-none"),children:[n.jsx("button",{type:"button",inert:G,onClick:Ae,className:"sr-only focus:not-sr-only focus:absolute focus:top-3 focus:left-3 focus:z-50 focus:rounded-lg focus:border focus:border-[var(--otari-brand)] focus:bg-[var(--otari-surface)] focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:text-[var(--otari-brand-dark)] focus:shadow-md focus:outline-none",children:"Skip to main content"}),n.jsxs("header",{inert:G,className:"flex shrink-0 items-center justify-between border-b border-[var(--otari-line)] bg-[var(--otari-surface)] px-5 py-3",children:[n.jsxs("div",{className:"flex items-center gap-2.5",children:[n.jsx("button",{type:"button",ref:s,onClick:()=>T(u=>!u),"aria-label":h?"Close navigation":"Open navigation","aria-expanded":h,"aria-controls":"app-sidebar",className:"-ml-1 flex h-8 w-8 items-center justify-center rounded-lg text-[var(--otari-muted)] transition-colors hover:bg-[var(--otari-bg)] hover:text-[var(--otari-ink)] md:hidden",children:n.jsx("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5",children:n.jsx("path",{d:"M4 6h16M4 12h16M4 18h16",strokeLinecap:"round",strokeLinejoin:"round"})})}),n.jsx("img",{src:"/favicon.svg",alt:"",className:"h-7 w-7 shrink-0"}),n.jsx("span",{className:"text-base font-semibold text-[var(--otari-ink)]",children:"Otari"})]}),n.jsx(C,{size:"sm",variant:"outline",onPress:e,"aria-label":"Sign out",children:"Sign out"})]}),n.jsx(Kt,{}),n.jsx(ot,{}),n.jsx(Ft,{}),n.jsxs("div",{className:"flex min-h-0 flex-1",children:[m&&h?n.jsx("div",{"aria-hidden":"true",onClick:()=>T(!1),className:"fixed inset-0 z-30 bg-black/40 md:hidden"}):null,n.jsxs("aside",{ref:t,id:"app-sidebar",role:m?"dialog":void 0,"aria-modal":m&&h?!0:void 0,"aria-label":m?"Navigation":void 0,tabIndex:m?-1:void 0,inert:m&&!h?!0:void 0,onKeyDown:m&&h?F:void 0,style:m?void 0:{width:Me},className:N("flex flex-col border-r border-[var(--otari-line)] bg-[var(--otari-surface)] focus:outline-none",m?N("fixed inset-y-0 left-0 z-40 w-[17rem] shadow-xl transition-transform duration-200",h?"translate-x-0":"-translate-x-full"):N("relative shrink-0",!f&&"transition-[width] duration-150")),children:[n.jsx("button",{type:"button",onClick:()=>d(u=>!u),"aria-label":c?"Expand sidebar":"Collapse sidebar","aria-pressed":c,title:c?"Expand sidebar":"Collapse sidebar",className:"absolute -right-3 top-4 z-30 hidden h-6 w-6 items-center justify-center rounded-full border border-[var(--otari-line)] bg-[var(--otari-surface)] text-[var(--otari-muted)] shadow-sm transition-colors hover:border-[var(--otari-brand)] hover:text-[var(--otari-brand-dark)] md:flex",children:n.jsx("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",className:N("h-3.5 w-3.5 transition-transform",c&&"rotate-180"),children:n.jsx("path",{d:"M15 6l-6 6 6 6",strokeLinecap:"round",strokeLinejoin:"round"})})}),n.jsx("nav",{className:N("flex flex-col py-4",S?"px-2":"px-3"),children:Gt.map((u,p)=>{const w=Ht.filter(k=>k.section===u.key);return w.length===0?null:n.jsxs("div",{className:p>0?"mt-4":void 0,children:[!S&&u.label?n.jsx("div",{className:"px-3 pb-1 text-[11px] font-semibold tracking-wider text-[var(--otari-muted)] uppercase",children:u.label}):null,p>0&&(S||!u.label)?n.jsx("div",{className:"mx-1 mb-2 border-t border-[var(--otari-line)]"}):null,n.jsx("div",{className:"flex flex-col gap-1",children:w.map(k=>n.jsxs(ee,{to:k.to,end:k.end,onClick:()=>T(!1),"aria-label":S?k.label:void 0,title:S?k.label:void 0,className:({isActive:U})=>N("flex items-center rounded-lg py-2 text-sm font-medium transition-colors",S?"justify-center px-0":"gap-3 px-3",U?"bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]":"text-[var(--otari-muted)] hover:bg-[var(--otari-bg)] hover:text-[var(--otari-ink)]"),children:[k.icon,S?null:k.label]},k.to))})]},u.key)})}),n.jsxs("div",{className:"mt-auto flex flex-col gap-1 pb-3",children:[n.jsxs(ee,{to:"/docs",onClick:()=>T(!1),"aria-label":S?"User guide":void 0,title:S?"User guide":void 0,className:({isActive:u})=>N("flex items-center rounded-lg py-2 text-sm font-medium transition-colors",S?"mx-2 justify-center px-0":"mx-3 gap-3 px-3",u?"bg-[var(--otari-brand-tint)] text-[var(--otari-brand-dark)]":"text-[var(--otari-muted)] hover:bg-[var(--otari-bg)] hover:text-[var(--otari-ink)]"),children:[n.jsxs("svg",{"aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-5 w-5 shrink-0",children:[n.jsx("path",{d:"M12 6.5C10.5 5 8 4.5 4 4.5V18c4 0 6.5.5 8 2 1.5-1.5 4-2 8-2V4.5c-4 0-6.5.5-8 2z",strokeLinejoin:"round"}),n.jsx("path",{d:"M12 6.5V20",strokeLinecap:"round"})]}),S?null:"User guide"]}),n.jsxs("a",{href:"https://otari.ai",target:"_blank",rel:"noreferrer",title:"otari.ai: the hosted Otari gateway",className:N("flex items-center rounded-lg py-2 text-xs font-medium text-[var(--otari-muted)] transition-colors hover:bg-[var(--otari-bg)] hover:text-[var(--otari-brand-dark)]",S?"mx-2 justify-center px-0":"mx-3 gap-2 px-3"),children:[n.jsx("svg",{"aria-hidden":!0,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",className:"h-4 w-4 shrink-0",children:n.jsx("path",{d:"M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z",strokeLinejoin:"round"})}),S?null:n.jsxs("span",{className:"flex-1",children:["otari.ai ",n.jsx("span",{"aria-hidden":!0,children:"↗"})]})]})]}),c||m?null:n.jsx("div",{role:"separator","aria-orientation":"vertical","aria-label":"Resize sidebar","aria-valuenow":Math.round(a),"aria-valuemin":Pe,"aria-valuemax":Te,tabIndex:0,onPointerDown:Re,onPointerMove:Ie,onPointerUp:qe,onKeyDown:Oe,className:N("absolute top-0 right-0 z-10 h-full w-1.5 cursor-col-resize touch-none transition-colors","hover:bg-[var(--otari-brand)] focus-visible:bg-[var(--otari-brand)] focus:outline-none",f?"bg-[var(--otari-brand)]":"bg-transparent")})]}),n.jsx("main",{ref:r,id:"main-content",tabIndex:-1,inert:G,className:"flex-1 overflow-y-auto focus:outline-none",children:n.jsx("div",{className:"mx-auto flex max-w-[1800px] flex-col gap-6 px-4 py-5 md:px-6 md:py-6",children:n.jsx(Qe,{})})})]})]})}function Yt(){const{login:e}=ae(),[t,r]=i.useState(""),[s,a]=i.useState(null),[o,c]=i.useState(!1),d=async()=>{const f=t.trim();if(!(!f||o)){c(!0),a(null);try{await et(f)?e():a(new Error("Invalid master key."))}catch(y){a(y)}finally{c(!1)}}};return n.jsx("div",{className:"flex min-h-full items-center justify-center p-6",children:n.jsx(A,{className:"w-full max-w-md",children:n.jsxs(A.Content,{className:"flex flex-col gap-5 p-7",children:[n.jsxs("div",{className:"flex flex-col items-center gap-3 text-center",children:[n.jsx("img",{src:"/favicon.svg",alt:"Otari",className:"h-12 w-12"}),n.jsxs("div",{children:[n.jsx("h1",{className:"text-lg font-semibold text-[var(--otari-ink)]",children:"Otari Dashboard"}),n.jsx("p",{className:"mt-1 text-sm text-[var(--otari-muted)]",children:"Sign in with your master key to browse models, set pricing, and manage settings."})]})]}),n.jsxs("form",{className:"flex flex-col gap-4",onSubmit:f=>{f.preventDefault(),d()},children:[n.jsxs(He,{value:t,onChange:f=>{r(f),s&&a(null)},type:"password",isRequired:!0,className:"flex flex-col gap-1",children:[n.jsx(re,{className:"text-sm font-medium text-[var(--otari-ink)]",children:"Master key"}),n.jsx(se,{placeholder:"otari-mk-… or your master key",autoFocus:!0,autoComplete:"off"})]}),n.jsxs("details",{className:"text-xs text-[var(--otari-muted)]",children:[n.jsx("summary",{className:"cursor-pointer font-medium text-[var(--otari-brand-dark)]",children:"First run? Where to find your key"}),n.jsxs("p",{className:"mt-2 leading-relaxed",children:["If you did not set ",n.jsx("code",{children:"OTARI_MASTER_KEY"}),", Otari generated one and printed it to the server logs on startup. Look for the line ",n.jsx("code",{children:"Your master key:"})," (for example, run"," ",n.jsx("code",{children:"docker logs "}),") and paste it above."]})]}),n.jsx(It,{error:s}),n.jsx(C,{type:"submit",variant:"primary",fullWidth:!0,isDisabled:!t.trim()||o,children:o?"Signing in…":"Sign in"})]}),n.jsx("p",{className:"text-center text-xs text-[var(--otari-muted)]",children:"The key is sent once to this gateway and exchanged for a session cookie; it is never stored in the browser."}),n.jsx("div",{className:"border-t border-[var(--otari-line)] pt-4 text-center",children:n.jsx(Je,{href:"/welcome",className:"text-sm font-medium text-[var(--otari-brand-dark)]",children:"New to Otari? Open the welcome guide"})})]})})})}const Xt=i.lazy(async()=>({default:(await P(async()=>{const{ActivityPage:e}=await import("./ActivityPage-BbEENQgu.js");return{ActivityPage:e}},__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11]))).ActivityPage})),Zt=i.lazy(async()=>({default:(await P(async()=>{const{RoutingPage:e}=await import("./RoutingPage-2qgzgln4.js");return{RoutingPage:e}},__vite__mapDeps([12,1,2,8,5,11,13]))).RoutingPage})),en=i.lazy(async()=>({default:(await P(async()=>{const{BudgetsPage:e}=await import("./BudgetsPage-DGkl3NSe.js");return{BudgetsPage:e}},__vite__mapDeps([14,1,2,6,5,7,8,11]))).BudgetsPage})),tn=i.lazy(async()=>({default:(await P(async()=>{const{DocsPage:e}=await import("./DocsPage-AglHrVWY.js");return{DocsPage:e}},__vite__mapDeps([15,1,2,5]))).DocsPage})),nn=i.lazy(async()=>({default:(await P(async()=>{const{KeysPage:e}=await import("./KeysPage-CEc7g4XL.js");return{KeysPage:e}},__vite__mapDeps([16,1,2,6,5,7,8,11,17,13]))).KeysPage})),rn=i.lazy(async()=>({default:(await P(async()=>{const{ModelsPage:e}=await import("./ModelsPage-299cCHBM.js");return{ModelsPage:e}},__vite__mapDeps([18,1,2,6,5,8,10,11]))).ModelsPage})),sn=i.lazy(async()=>({default:(await P(async()=>{const{OverviewIndex:e}=await import("./OverviewPage-CHysnnsw.js");return{OverviewIndex:e}},__vite__mapDeps([19,1,2,3,4,5,8]))).OverviewIndex})),an=i.lazy(async()=>({default:(await P(async()=>{const{ProvidersPage:e}=await import("./ProvidersPage-BPyKQR5x.js");return{ProvidersPage:e}},__vite__mapDeps([20,1,2,11,5,8]))).ProvidersPage})),on=i.lazy(async()=>({default:(await P(async()=>{const{SettingsPage:e}=await import("./SettingsPage-CLw9HtK0.js");return{SettingsPage:e}},__vite__mapDeps([21,1,2,5]))).SettingsPage})),ln=i.lazy(async()=>({default:(await P(async()=>{const{ToolsGuardrailsPage:e}=await import("./ToolsGuardrailsPage-CSbQtPkh.js");return{ToolsGuardrailsPage:e}},__vite__mapDeps([22,1,2,5]))).ToolsGuardrailsPage})),cn=i.lazy(async()=>({default:(await P(async()=>{const{UsagePage:e}=await import("./UsagePage-BTnJt3lF.js");return{UsagePage:e}},__vite__mapDeps([23,1,2,3,4,5,8,9]))).UsagePage})),un=i.lazy(async()=>({default:(await P(async()=>{const{UsersPage:e}=await import("./UsersPage-C_yR1ElB.js");return{UsersPage:e}},__vite__mapDeps([24,1,2,6,5,7,8,11,17]))).UsersPage}));function E(e){return n.jsx(i.Suspense,{fallback:n.jsx("div",{role:"status",children:"Loading page…"}),children:e})}function dn(){const{isAuthenticated:e}=ae();return e?n.jsx(Ve,{children:n.jsx(We,{children:n.jsxs(j,{element:n.jsx(Jt,{}),children:[n.jsx(j,{index:!0,element:E(n.jsx(sn,{}))}),n.jsx(j,{path:"providers",element:E(n.jsx(an,{}))}),n.jsx(j,{path:"keys",element:E(n.jsx(nn,{}))}),n.jsx(j,{path:"users",element:E(n.jsx(un,{}))}),n.jsx(j,{path:"budgets",element:E(n.jsx(en,{}))}),n.jsx(j,{path:"activity",element:E(n.jsx(Xt,{}))}),n.jsx(j,{path:"usage",element:E(n.jsx(cn,{}))}),n.jsx(j,{path:"models",element:E(n.jsx(rn,{}))}),n.jsx(j,{path:"aliases",element:n.jsx(fe,{to:"/routing",replace:!0})}),n.jsx(j,{path:"routing",element:E(n.jsx(Zt,{}))}),n.jsx(j,{path:"tools",element:E(n.jsx(ln,{}))}),n.jsx(j,{path:"settings",element:E(n.jsx(on,{}))}),n.jsx(j,{path:"docs",element:E(n.jsx(tn,{}))}),n.jsx(j,{path:"*",element:n.jsx(fe,{to:"/",replace:!0})})]})})}):n.jsx(Yt,{})}function mn({children:e}){const[t]=i.useState(()=>new Ke({defaultOptions:{queries:{refetchOnWindowFocus:!1,retry:(r,s)=>s instanceof D&&(s.status===401||s.status===403)?!1:r<2}}}));return n.jsx(Be,{client:t,children:n.jsx(rt,{children:e})})}const De=document.getElementById("root");if(!De)throw new Error("Root element #root not found");Ye.createRoot(De).render(n.jsx(i.StrictMode,{children:n.jsx(mn,{children:n.jsx(dn,{})})}));export{Nr as $,bn as A,zn as B,jn as C,Vn as D,It as E,Kr as F,Hn as G,cr as H,dr as I,mr as J,fr as K,yr as L,qt as M,ur as N,or as O,qr as P,ir as Q,Ar as R,lr as S,ar as T,Pn as U,Xn as V,Kn as W,Tr as X,it as Y,er as Z,Rt as _,ct as a,ht as a0,Pr as a1,Nn as a2,Fr as a3,Dn as a4,Dr as a5,Rr as a6,_r as a7,Ir as a8,Cr as a9,gr as aA,xr as aB,Lt as aa,Dt as ab,Sr as ac,In as ad,On as ae,Fn as af,xt as ag,An as ah,Rn as ai,qn as aj,Ln as ak,_n as al,Un as am,tr as an,nr as ao,rr as ap,Gn as aq,Mn as ar,Jn as as,Yn as at,Er as au,D as av,pn as aw,$r as ax,gn as ay,Lr as az,En as b,sr as c,pr as d,vr as e,Cn as f,kr as g,wn as h,Sn as i,br as j,jr as k,wr as l,Zn as m,vn as n,Br as o,lt as p,Or as q,kn as r,Tn as s,$n as t,hr as u,Bn as v,Qn as w,Wn as x,Ur as y,Mr as z}; diff --git a/src/gateway/static/dashboard/index.html b/src/gateway/static/dashboard/index.html index f0fe220e3..2dfb04f1b 100644 --- a/src/gateway/static/dashboard/index.html +++ b/src/gateway/static/dashboard/index.html @@ -19,7 +19,7 @@ insets to sit out of the way. --> Otari Dashboard - + diff --git a/tests/integration/test_usage_summary.py b/tests/integration/test_usage_summary.py index 50c0a8ecf..8419e5963 100644 --- a/tests/integration/test_usage_summary.py +++ b/tests/integration/test_usage_summary.py @@ -17,7 +17,8 @@ from fastapi.testclient import TestClient from sqlalchemy.orm import Session -from gateway.models.entities import UsageLog, User +from gateway.api.routes.usage import _MAX_FILTER_VALUES +from gateway.models.entities import APIKey, UsageLog, User SUMMARY_PATH = "/v1/usage/summary" CSV_PATH = "/v1/usage/summary.csv" @@ -29,6 +30,13 @@ def _ensure_user(db: Session, user_id: str) -> None: db.flush() +def _ensure_api_key(db: Session, key_id: str, user_id: str | None) -> None: + # usage_logs.api_key_id is a real FK, so a row attributed to a key needs one. + if db.query(APIKey).filter(APIKey.id == key_id).first() is None: + db.add(APIKey(id=key_id, key_hash=f"hash-{key_id}", key_name=key_id, user_id=user_id)) + db.flush() + + def _make_log( db: Session, *, @@ -54,6 +62,8 @@ def _make_log( ) -> None: if user_id is not None: _ensure_user(db, user_id) + if api_key_id is not None: + _ensure_api_key(db, api_key_id, user_id) db.add( UsageLog( id=str(uuid.uuid4()), @@ -415,6 +425,108 @@ def test_summary_filters_by_session_endpoint_and_provider( assert by_provider["totals"]["request_count"] == 1 +def test_summary_filters_by_several_models_users_and_keys( + client: TestClient, master_key_header: dict[str, str], db_session: Session +) -> None: + """The three entity filters are repeatable: several values match any of them. + + The analytics page compares a handful of models / users / keys in one chart, so + a single-value filter would force one request per value and make the tiles + disagree with the comparison the operator asked for. + """ + now = datetime.now(UTC) - timedelta(hours=1) + _make_log(db_session, user_id="multi-a", timestamp=now, model="gpt-4", api_key_id="key-a", cost=0.10) + _make_log(db_session, user_id="multi-b", timestamp=now, model="claude", api_key_id="key-b", cost=0.20) + _make_log(db_session, user_id="multi-c", timestamp=now, model="gemini", api_key_id="key-c", cost=0.40) + db_session.commit() + + everyone = ["multi-a", "multi-b", "multi-c"] + + two_users = client.get(SUMMARY_PATH, headers=master_key_header, params={"user_id": everyone[:2]}).json() + assert two_users["totals"]["request_count"] == 2 + assert two_users["totals"]["cost"] == pytest.approx(0.30) + assert {row["key"] for row in two_users["by_user"]} == {"multi-a", "multi-b"} + + two_models = client.get( + SUMMARY_PATH, headers=master_key_header, params={"user_id": everyone, "model": ["gpt-4", "gemini"]} + ).json() + assert two_models["totals"]["request_count"] == 2 + assert two_models["totals"]["cost"] == pytest.approx(0.50) + + two_keys = client.get( + SUMMARY_PATH, headers=master_key_header, params={"user_id": everyone, "api_key_id": ["key-a", "key-b"]} + ).json() + assert two_keys["totals"]["request_count"] == 2 + assert two_keys["totals"]["cost"] == pytest.approx(0.30) + + # A single value keeps working unchanged (the wire form every existing caller sends). + one_user = client.get(SUMMARY_PATH, headers=master_key_header, params={"user_id": "multi-c"}).json() + assert one_user["totals"]["request_count"] == 1 + assert one_user["totals"]["cost"] == pytest.approx(0.40) + + +def test_summary_and_series_cap_the_number_of_filter_values( + client: TestClient, master_key_header: dict[str, str] +) -> None: + # The cap exists so a caller cannot post an unbounded IN list; it sits far above + # any comparison a chart can render. + too_many = [f"m{index}" for index in range(_MAX_FILTER_VALUES + 1)] + assert client.get(SUMMARY_PATH, headers=master_key_header, params={"model": too_many}).status_code == 422 + assert ( + client.get( + SERIES_PATH, headers=master_key_header, params={"group_by": "model", "model": too_many} + ).status_code + == 422 + ) + at_cap = too_many[:_MAX_FILTER_VALUES] + assert client.get(SUMMARY_PATH, headers=master_key_header, params={"model": at_cap}).status_code == 200 + + +def test_grouped_series_filters_by_several_models( + client: TestClient, master_key_header: dict[str, str], db_session: Session +) -> None: + # /series claims filter parity with /summary, so the stacked chart must scope to + # the same value set the tiles beside it were computed over. + ts = datetime(2025, 9, 1, 12, 0, tzinfo=UTC) + _make_log(db_session, user_id="multiser", timestamp=ts, model="gpt-4", cost=0.10, total_tokens=15) + _make_log(db_session, user_id="multiser", timestamp=ts, model="claude", cost=0.20, total_tokens=15) + _make_log(db_session, user_id="multiser", timestamp=ts, model="gemini", cost=0.40, total_tokens=15) + db_session.commit() + + body = client.get( + SERIES_PATH, + headers=master_key_header, + params={ + "group_by": "model", + "user_id": "multiser", + "model": ["gpt-4", "claude"], + "start_date": "2025-09-01T00:00:00Z", + "end_date": "2025-09-02T00:00:00Z", + }, + ).json() + + assert {g["key"] for g in body["groups"]} == {"gpt-4", "claude"} + assert sum(p["cost"] for p in body["points"]) == pytest.approx(0.30) + + +def test_csv_export_filters_by_several_users( + client: TestClient, master_key_header: dict[str, str], db_session: Session +) -> None: + # The export takes the same window and filters as /summary, so a multi-value + # comparison can be downloaded rather than re-filtered by hand. + now = datetime.now(UTC) - timedelta(hours=1) + _make_log(db_session, user_id="csv-a", timestamp=now, model="gpt-4", cost=0.10) + _make_log(db_session, user_id="csv-b", timestamp=now, model="claude", cost=0.20) + _make_log(db_session, user_id="csv-c", timestamp=now, model="gemini", cost=0.40) + db_session.commit() + + resp = client.get(CSV_PATH, headers=master_key_header, params={"user_id": ["csv-a", "csv-b"]}) + assert resp.status_code == 200 + rows = list(csv.DictReader(io.StringIO(resp.text))) + users = {row["key"] for row in rows if row["dimension"] == "user"} + assert users == {"csv-a", "csv-b"} + + def test_usage_list_and_count_filter_by_session_and_provider( client: TestClient, master_key_header: dict[str, str], db_session: Session ) -> None: diff --git a/web/src/api/hooks.ts b/web/src/api/hooks.ts index c20e774d6..c4d2fed58 100644 --- a/web/src/api/hooks.ts +++ b/web/src/api/hooks.ts @@ -711,14 +711,22 @@ export function useDeleteUser() { // while the request goes out unfiltered and the table quietly shows everything. function usageParams(filters: UsageFilters): URLSearchParams { const params = new URLSearchParams(); + // A multi-value filter goes on the wire as a repeated param (the analytics + // endpoints match any of them); an empty array is no filter at all, not a + // filter matching nothing. + const appendAll = (key: string, value: string | string[] | undefined) => { + for (const one of typeof value === "string" ? [value] : (value ?? [])) { + if (one) params.append(key, one); + } + }; if (filters.start_date) params.set("start_date", filters.start_date); if (filters.end_date) params.set("end_date", filters.end_date); if (filters.status) params.set("status", filters.status); - if (filters.model) params.set("model", filters.model); + appendAll("model", filters.model); if (filters.endpoint) params.set("endpoint", filters.endpoint); if (filters.provider) params.set("provider", filters.provider); - if (filters.user_id) params.set("user_id", filters.user_id); - if (filters.api_key_id) params.set("api_key_id", filters.api_key_id); + appendAll("user_id", filters.user_id); + appendAll("api_key_id", filters.api_key_id); if (filters.source) params.set("source", filters.source); if (filters.source_label) params.set("source_label", filters.source_label); if (filters.tool) params.set("tool", filters.tool); diff --git a/web/src/api/types.ts b/web/src/api/types.ts index f37b59959..833dfe72b 100644 --- a/web/src/api/types.ts +++ b/web/src/api/types.ts @@ -557,11 +557,15 @@ export interface UsageFilters { // analytics previous-period query so its window does not overlap the current one. end_date?: string; status?: string; - model?: string; + // The three entity filters accept several values: the analytics endpoints take + // them as repeated query params and match any of them, so one chart can compare + // a handful of models / users / keys. The request-log endpoints (/v1/usage, + // /v1/usage/count) stay single-value, so the Activity page sends a bare string. + model?: string | string[]; endpoint?: string; provider?: string; - user_id?: string; - api_key_id?: string; + user_id?: string | string[]; + api_key_id?: string | string[]; source?: string; // Session/project attribution (a row's `source_label`), so the log can be // scoped to the one agent session a breakdown row points at. diff --git a/web/src/components/FilterChips.tsx b/web/src/components/FilterChips.tsx index 48f4c4d46..1e512ac39 100644 --- a/web/src/components/FilterChips.tsx +++ b/web/src/components/FilterChips.tsx @@ -14,6 +14,10 @@ export interface FilterChip { label: string; // The human-readable current value, e.g. "gpt-5.6". value: string; + // Accessible name for the ✕. Defaults to naming the dimension, which is enough + // while a dimension has one chip; a multi-value filter renders one chip per + // value and passes a name carrying the value, so the controls stay distinct. + clearLabel?: string; onClear: () => void; } @@ -61,7 +65,7 @@ export function FilterChips({