170 lines
8.0 KiB
JavaScript
170 lines
8.0 KiB
JavaScript
(function () {
|
|
"use strict";
|
|
|
|
const SDK = window.__HERMES_PLUGIN_SDK__;
|
|
if (!SDK) return;
|
|
const { React } = SDK;
|
|
const h = React.createElement;
|
|
const { Card, CardContent, Badge, Button } = SDK.components;
|
|
const { useCallback, useEffect, useState } = SDK.hooks;
|
|
const API = "/api/plugins/auto-router";
|
|
|
|
function number(value) {
|
|
return new Intl.NumberFormat().format(Number(value || 0));
|
|
}
|
|
|
|
function when(value) {
|
|
if (!value) return "not reported";
|
|
const parsed = new Date(value);
|
|
return Number.isNaN(parsed.valueOf()) ? String(value) : parsed.toLocaleString();
|
|
}
|
|
|
|
function Stat(props) {
|
|
return h("div", { className: "provider-status-stat" },
|
|
h("span", { className: "provider-status-stat-label" }, props.label),
|
|
h("strong", null, props.value)
|
|
);
|
|
}
|
|
|
|
function ProviderCard(props) {
|
|
const item = props.item || {};
|
|
const account = item.account || null;
|
|
const nativeHealth = item.native_health || null;
|
|
const authLabel = account && account.access_token_live === false && account.refreshable
|
|
? "Authentication refreshable"
|
|
: account && account.authenticated
|
|
? "Authentication ready"
|
|
: "Authentication unavailable";
|
|
return h(Card, { className: "provider-status-card" },
|
|
h(CardContent, { className: "provider-status-card-content" },
|
|
h("div", { className: "provider-status-card-heading" },
|
|
h("div", null,
|
|
h("h2", null, props.title),
|
|
account ? h("p", null, "Plan: ", h("strong", null, account.plan || "unknown")) : null
|
|
),
|
|
h(Badge, { className: "provider-status-badge provider-status-badge--" + (item.state || "unobserved") }, item.state || "unobserved")
|
|
),
|
|
account ? h("div", { className: "provider-status-auth" },
|
|
h("span", { className: account.authenticated ? "is-good" : "is-bad" }, authLabel),
|
|
account.rate_limit_tier && account.rate_limit_tier !== "unknown" ? h("span", null, "Tier: " + account.rate_limit_tier) : null,
|
|
h("span", null, "Access token: " + when(account.token_expires_at)),
|
|
nativeHealth && nativeHealth.transport ? h("span", null,
|
|
nativeHealth.transport === "claude-code-cli-subscription"
|
|
? "Transport: native Claude Code subscription"
|
|
: nativeHealth.transport === "codex-chatgpt-subscription"
|
|
? "Transport: native ChatGPT Codex subscription"
|
|
: "Transport: " + nativeHealth.transport
|
|
) : null,
|
|
nativeHealth && nativeHealth.rate_limit && nativeHealth.rate_limit.utilization != null
|
|
? h("span", null, "Observed utilization: " + Math.round(Number(nativeHealth.rate_limit.utilization) * 100) + "%")
|
|
: null,
|
|
account.usage_url ? h("a", {
|
|
className: "provider-status-usage-link",
|
|
href: account.usage_url,
|
|
target: "_blank",
|
|
rel: "noopener noreferrer"
|
|
}, "Open official usage ↗") : null
|
|
) : null,
|
|
h("div", { className: "provider-status-stats" },
|
|
h(Stat, { label: "Completed", value: number(item.calls) }),
|
|
h(Stat, { label: "Historical errors", value: number(item.errors) }),
|
|
h(Stat, { label: "Tokens", value: number(item.total_tokens) }),
|
|
h(Stat, { label: "Avg latency", value: item.avg_latency_ms ? number(item.avg_latency_ms) + " ms" : "—" })
|
|
),
|
|
item.configured_models && item.configured_models.length ? h("div", { className: "provider-status-models" },
|
|
h("strong", null, "Available models"),
|
|
item.supported_efforts && item.supported_efforts.length
|
|
? h("p", null, "Supported effort: " + item.supported_efforts.join(" · "))
|
|
: null,
|
|
item.configured_models.map(function (model) {
|
|
return h("div", { className: "provider-status-model", key: "configured-" + model },
|
|
h("code", null, model),
|
|
h("span", null, "selectable through AUTO or a manual override")
|
|
);
|
|
})
|
|
) : null,
|
|
item.models && item.models.length ? h("div", { className: "provider-status-models" },
|
|
h("strong", null, "Observed routes (history)"),
|
|
item.models.map(function (model) {
|
|
return h("div", { className: "provider-status-model", key: model.id },
|
|
h("code", null, model.id),
|
|
h("span", null, number(model.calls) + " completed · " + number(model.errors) + " historical errors · " + number(model.total_tokens) + " tokens")
|
|
);
|
|
})
|
|
) : h("p", { className: "provider-status-empty" }, "No calls observed in this router window.")
|
|
)
|
|
);
|
|
}
|
|
|
|
function ProviderStatus() {
|
|
const dataState = useState(null);
|
|
const data = dataState[0];
|
|
const setData = dataState[1];
|
|
const errorState = useState("");
|
|
const error = errorState[0];
|
|
const setError = errorState[1];
|
|
const loadingState = useState(true);
|
|
const loading = loadingState[0];
|
|
const setLoading = loadingState[1];
|
|
|
|
const load = useCallback(function () {
|
|
setLoading(true);
|
|
return SDK.fetchJSON(API + "/status")
|
|
.then(function (value) { setData(value); setError(""); })
|
|
.catch(function (err) { setError(err && err.message ? err.message : String(err)); })
|
|
.finally(function () { setLoading(false); });
|
|
}, []);
|
|
|
|
useEffect(function () {
|
|
load();
|
|
const timer = window.setInterval(load, 30000);
|
|
return function () { window.clearInterval(timer); };
|
|
}, [load]);
|
|
|
|
if (!data && loading) return h("div", { className: "provider-status-loading" }, "Loading provider status…");
|
|
|
|
const router = data ? data.router : {};
|
|
const providers = data ? data.providers : {};
|
|
return h("main", { className: "provider-status-page" },
|
|
h("header", { className: "provider-status-header" },
|
|
h("div", null,
|
|
h("h1", null, "Provider Status"),
|
|
h("p", null, "Current native provider health and the full selectable model catalog, plus historical activity Switchyard observed.")
|
|
),
|
|
h(Button, { onClick: load, disabled: loading }, loading ? "Refreshing…" : "Refresh")
|
|
),
|
|
error ? h("div", { className: "provider-status-error" }, error) : null,
|
|
data ? h(Card, { className: "provider-status-router" },
|
|
h(CardContent, null,
|
|
h("div", { className: "provider-status-card-heading" },
|
|
h("div", null, h("h2", null, "Switchyard"), h("p", null, data.window)),
|
|
h(Badge, { className: "provider-status-badge provider-status-badge--" + router.state }, router.state)
|
|
),
|
|
h("div", { className: "provider-status-stats" },
|
|
h(Stat, { label: "Requests", value: number(router.total_requests) }),
|
|
h(Stat, { label: "Historical errors", value: number(router.total_errors) }),
|
|
h(Stat, { label: "Tokens", value: number(router.total_tokens) }),
|
|
h(Stat, { label: "Fallbacks", value: number(Object.values(router.fallbacks || {}).reduce(function (a, b) { return a + Number(b || 0); }, 0)) }),
|
|
h(Stat, { label: "Classifier calls", value: number(router.classifier_calls) }),
|
|
h(Stat, { label: "Historical classifier errors", value: number(router.classifier_errors) })
|
|
)
|
|
)
|
|
) : null,
|
|
data ? h("section", { className: "provider-status-grid" },
|
|
h(ProviderCard, { title: "OpenAI Codex", item: providers.codex }),
|
|
h(ProviderCard, { title: "Anthropic Claude", item: providers.claude }),
|
|
h(ProviderCard, { title: "Local models", item: providers.local })
|
|
) : null,
|
|
data ? h("aside", { className: "provider-status-note" },
|
|
h("strong", null, "About provider quota"),
|
|
h("p", null, data.quota_note),
|
|
h("p", null, "The native /usage command remains scoped to direct calls made by the current Hermes process. Use /providers for this pool-wide view.")
|
|
) : null
|
|
);
|
|
}
|
|
|
|
if (window.__HERMES_PLUGINS__ && typeof window.__HERMES_PLUGINS__.register === "function") {
|
|
window.__HERMES_PLUGINS__.register("auto-router", ProviderStatus);
|
|
}
|
|
}());
|