fix
This commit is contained in:
parent
a6c24673bc
commit
ff5aa5f4f1
|
|
@ -142,6 +142,7 @@ async def test_provider_credential(
|
|||
account_key=account_key,
|
||||
api_key=payload.api_key,
|
||||
base_url=payload.base_url,
|
||||
session_key=payload.session_key,
|
||||
force_refresh=True,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -871,7 +871,7 @@ async def fetch_provider_usage(
|
|||
if sub_windows:
|
||||
result.subscription_windows = sub_windows
|
||||
result.reachable = True
|
||||
elif not result.reachable:
|
||||
else:
|
||||
result.error = result.error or "No subscription data returned."
|
||||
|
||||
elif provider in ("openai", "codex"):
|
||||
|
|
@ -895,6 +895,8 @@ async def fetch_provider_usage(
|
|||
result.subscription_windows = sub_windows
|
||||
result.subscription_plan = plan_label
|
||||
result.reachable = True
|
||||
else:
|
||||
result.error = result.error or "No subscription data returned."
|
||||
|
||||
elif provider == "ollama":
|
||||
result = await _fetch_ollama(base_url, api_key)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from app.models.organization_members import OrganizationMember
|
|||
from app.models.organizations import Organization
|
||||
from app.models.provider_credentials import ProviderCredential
|
||||
from app.services.organizations import OrganizationContext
|
||||
from app.services.provider_usage import ProviderUsageLive
|
||||
from app.services.provider_usage import ProviderUsageLive, SubscriptionWindow
|
||||
|
||||
|
||||
async def _make_engine() -> AsyncEngine:
|
||||
|
|
@ -153,6 +153,7 @@ async def test_test_endpoint_returns_live_result(monkeypatch: pytest.MonkeyPatch
|
|||
)
|
||||
|
||||
async def _fake_fetch_provider_usage(**kwargs: object) -> ProviderUsageLive:
|
||||
assert kwargs["session_key"] == "sk-ant-sid-test"
|
||||
result = ProviderUsageLive(
|
||||
provider=str(kwargs["provider"]),
|
||||
account_key=str(kwargs["account_key"]),
|
||||
|
|
@ -167,6 +168,14 @@ async def test_test_endpoint_returns_live_result(monkeypatch: pytest.MonkeyPatch
|
|||
result.raw_headers = {
|
||||
"anthropic-ratelimit-tokens-limit": "100000",
|
||||
}
|
||||
result.subscription_windows = [
|
||||
SubscriptionWindow(
|
||||
key="five_hour",
|
||||
label="Current session",
|
||||
pct_used=77.0,
|
||||
reset_at=utcnow(),
|
||||
),
|
||||
]
|
||||
return result
|
||||
|
||||
monkeypatch.setattr(
|
||||
|
|
@ -189,6 +198,7 @@ async def test_test_endpoint_returns_live_result(monkeypatch: pytest.MonkeyPatch
|
|||
"provider": "anthropic",
|
||||
"account_key": "Claude",
|
||||
"api_key": "sk-ant-test",
|
||||
"session_key": "sk-ant-sid-test",
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -204,6 +214,8 @@ async def test_test_endpoint_returns_live_result(monkeypatch: pytest.MonkeyPatch
|
|||
assert data["sample_input_tokens"] == 8
|
||||
assert data["sample_output_tokens"] == 1
|
||||
assert data["sample_latency_ms"] == 111
|
||||
assert data["subscription_windows"][0]["key"] == "five_hour"
|
||||
assert data["subscription_windows"][0]["pct_used"] == 77.0
|
||||
assert data["debug_rate_limit_headers"] == ["anthropic-ratelimit-tokens-limit"]
|
||||
finally:
|
||||
await engine.dispose()
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ import type {
|
|||
RuntimeUsageResponse,
|
||||
SystemHealthResponse,
|
||||
} from "@/api/generated/model";
|
||||
import {
|
||||
getProviderUsageLiveApiV1ProviderCredentialsCredentialIdUsageGet,
|
||||
listProviderCredentialsApiV1ProviderCredentialsGet,
|
||||
} from "@/api/generated/provider-credentials/provider-credentials";
|
||||
import {
|
||||
getGatewayCronApiV1GatewaysGatewayIdCronGet,
|
||||
getGatewayHealthApiV1GatewaysGatewayIdHealthGet,
|
||||
|
|
@ -753,7 +757,69 @@ export default function DashboardPage() {
|
|||
return windows;
|
||||
},
|
||||
});
|
||||
const providerUsageWindows = providerUsageQuery.data ?? [];
|
||||
const credentialUsageQuery = useQuery<ProviderNativeUsageWindow[], ApiError>({
|
||||
queryKey: ["dashboard", "provider-credential-usage"],
|
||||
enabled: Boolean(isSignedIn),
|
||||
refetchInterval: 30_000,
|
||||
refetchOnMount: "always",
|
||||
queryFn: async () => {
|
||||
const credentialsRes = await listProviderCredentialsApiV1ProviderCredentialsGet();
|
||||
if (credentialsRes.status !== 200) return [];
|
||||
|
||||
const credentials = (credentialsRes.data ?? []).filter(
|
||||
(cred) => cred.active && cred.has_session_key,
|
||||
);
|
||||
const settled = await Promise.allSettled(
|
||||
credentials.map((cred) =>
|
||||
getProviderUsageLiveApiV1ProviderCredentialsCredentialIdUsageGet(
|
||||
cred.id,
|
||||
).then((res) => ({ cred, res })),
|
||||
),
|
||||
);
|
||||
|
||||
const windows: ProviderNativeUsageWindow[] = [];
|
||||
for (const item of settled) {
|
||||
if (item.status !== "fulfilled") continue;
|
||||
const { cred, res } = item.value;
|
||||
if (res.status !== 200) continue;
|
||||
const usage = res.data;
|
||||
const accountLabel = cred.display_name || cred.account_key || cred.provider;
|
||||
const subscriptionWindows = usage.subscription_windows ?? [];
|
||||
for (const window of subscriptionWindows) {
|
||||
windows.push({
|
||||
key: window.key,
|
||||
label: window.label,
|
||||
pctUsed: window.pct_used,
|
||||
remainingMs: window.reset_in_ms ?? null,
|
||||
remainingLabel: null,
|
||||
source: window.source ?? "provider_native",
|
||||
confidence: window.confidence ?? "high",
|
||||
provider: usage.provider,
|
||||
gatewayLabel: accountLabel,
|
||||
});
|
||||
}
|
||||
if (subscriptionWindows.length === 0) {
|
||||
windows.push({
|
||||
key: `${cred.id}:subscription_unavailable`,
|
||||
label: "Subscription usage",
|
||||
pctUsed: null,
|
||||
remainingMs: null,
|
||||
remainingLabel: null,
|
||||
source: "provider_native",
|
||||
confidence: "low",
|
||||
provider: usage.provider,
|
||||
gatewayLabel: accountLabel,
|
||||
note: usage.error ?? "No subscription usage returned for this session key.",
|
||||
});
|
||||
}
|
||||
}
|
||||
return windows;
|
||||
},
|
||||
});
|
||||
const providerUsageWindows = [
|
||||
...(credentialUsageQuery.data ?? []),
|
||||
...(providerUsageQuery.data ?? []),
|
||||
];
|
||||
|
||||
// Gateway health — query the first gateway only for the compact dashboard panel
|
||||
const primaryGatewayId = gatewayTargets[0]?.gatewayId ?? null;
|
||||
|
|
@ -1200,7 +1266,11 @@ export default function DashboardPage() {
|
|||
usage={runtimeUsage}
|
||||
providerUsageWindows={providerUsageWindows}
|
||||
perGatewayUsage={perGatewayUsage}
|
||||
isLoading={runtimeUsageQuery.isLoading || providerUsageQuery.isLoading}
|
||||
isLoading={
|
||||
runtimeUsageQuery.isLoading
|
||||
|| providerUsageQuery.isLoading
|
||||
|| credentialUsageQuery.isLoading
|
||||
}
|
||||
hasGateways={hasConfiguredGateways}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ export interface ProviderNativeUsageWindow {
|
|||
confidence: string;
|
||||
provider: string;
|
||||
gatewayLabel: string;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -456,7 +457,7 @@ export function RuntimeUsageSection({
|
|||
/>
|
||||
</div>
|
||||
<p className="mt-1 text-[11px] text-muted">
|
||||
Resets in {resetText} · {window.provider} · {window.gatewayLabel}
|
||||
{window.note ?? `Resets in ${resetText}`} · {window.provider} · {window.gatewayLabel}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue