Compare commits

..
4 Commits
Author SHA1 Message Date
ZyyandCodex 6074cff68c chore: bump version to 3.2.2
Co-authored-by: Codex <noreply@openai.com>
2026-08-14 00:43:13 +08:00
ZyyandCodex 6ab8e7d0a6 fix(frontend): grade model success rate colors
Co-authored-by: Codex <noreply@openai.com>
2026-08-14 00:42:20 +08:00
ZyyandCodex 17f9ef8915 fix(observability): omit inactive accounts from daily usage
Co-authored-by: Codex <noreply@openai.com>
2026-08-14 00:39:59 +08:00
ZyyandCodex 15e0a28bd0 fix(frontend): refine refresh loading and group alignment
Co-authored-by: Codex <noreply@openai.com>
2026-08-14 00:39:50 +08:00
11 changed files with 48 additions and 15 deletions
@@ -52,9 +52,12 @@ pub(crate) async fn provider_account_usage(
}
statement.push(
" group by pa.id, pa.provider_kind, pa.authentication_kind, pa.name, pa.email,
pa.plan_type
order by max(mr.started_at) desc nulls last, pa.name, pa.id limit ",
pa.plan_type",
);
if query.account_ids.is_none() {
statement.push(" having count(mr.id) > 0");
}
statement.push(" order by max(mr.started_at) desc nulls last, pa.name, pa.id limit ");
statement.push_bind(i64::from(query.limit));
let rows = statement
.build()
@@ -730,8 +730,18 @@ async fn dashboard_summary_totals_include_history_outside_selected_range() {
.await
.expect("dashboard summary");
assert_eq!(dashboard.requests.request_count, 2);
assert_eq!(dashboard.account_usage[0].request_count, 0);
assert_eq!(dashboard.account_usage[0].request_buckets.len(), 2);
assert!(dashboard.account_usage.is_empty());
let explicit_account_usage = repository
.provider_account_usage(
ProviderAccountUsageQuery::for_accounts(range, vec!["acct_observe".to_owned()])
.expect("account usage query"),
)
.await
.expect("provider account usage");
assert_eq!(explicit_account_usage.len(), 1);
assert_eq!(explicit_account_usage[0].request_count, 0);
assert_eq!(
(
dashboard.totals.request_count,
+4 -2
View File
@@ -10,6 +10,7 @@ const props = withDefaults(
variant?: ButtonVariant
size?: ButtonSize
loading?: boolean
spinIconOnLoading?: boolean
disabled?: boolean
active?: boolean
iconOnly?: boolean
@@ -21,6 +22,7 @@ const props = withDefaults(
variant: 'primary',
size: 'default',
loading: false,
spinIconOnLoading: false,
disabled: false,
active: false,
iconOnly: false,
@@ -124,7 +126,7 @@ const loadingIconClasses = computed(() => [
:aria-busy="loading"
>
<span
v-if="loading"
v-if="loading && !spinIconOnLoading"
:class="loadingIconClasses"
:style="loadingIndicatorStyle"
aria-hidden="true"
@@ -140,7 +142,7 @@ const loadingIconClasses = computed(() => [
<slot />
</span>
<span
v-if="iconOnly && !$slots.icon && !loading"
v-if="iconOnly && !$slots.icon && (!loading || spinIconOnLoading)"
:class="loadingIconClasses"
aria-hidden="true"
>
@@ -1,5 +1,6 @@
<script setup lang="ts">
import type { AccountRow } from '../constants'
import { modelSuccessRateTextClass } from '../constants'
defineProps<{
account: AccountRow
@@ -62,7 +63,7 @@ defineProps<{
</div>
<div
class="grid grid-cols-[1.5fr_0.7fr_0.8fr_1fr_1fr_1fr_1fr_1fr_1.4fr] gap-3 pb-2 text-[11px] font-heavy text-(--cp-text-secondary) shadow-[inset_0_-1px_0_var(--cp-divider-subtle)]"
class="grid grid-cols-[1.5fr_0.7fr_0.8fr_1fr_1fr_1fr_1fr_1fr_1.2fr] gap-3 pb-2 text-[11px] font-heavy text-(--cp-text-secondary) shadow-[inset_0_-1px_0_var(--cp-divider-subtle)]"
>
<span>模型</span>
<span>调用</span>
@@ -84,11 +85,13 @@ defineProps<{
<div
v-for="model in account.usage.models"
:key="model.model"
class="grid grid-cols-[1.5fr_0.7fr_0.8fr_1fr_1fr_1fr_1fr_1fr_1.4fr] gap-3 pt-3 text-[12px] font-emphasis text-(--cp-text-primary)"
class="grid grid-cols-[1.5fr_0.7fr_0.8fr_1fr_1fr_1fr_1fr_1fr_1.2fr] gap-3 pt-3 text-[12px] font-emphasis text-(--cp-text-primary)"
>
<span class="truncate">{{ model.model }}</span>
<span>{{ model.requestCountDisplay }}</span>
<span class="text-(--cp-warning-text)">{{ model.successRateDisplay }}</span>
<span :class="modelSuccessRateTextClass(model.successRate)">
{{ model.successRateDisplay }}
</span>
<span>{{ model.inputTokensDisplay }}</span>
<span>{{ model.outputTokensDisplay }}</span>
<span>{{ model.cachedTokensDisplay }}</span>
+12
View File
@@ -211,6 +211,18 @@ export function quotaWindowPercentTextClass(window: AccountQuotaWindow) {
return 'text-(--cp-success-text)'
}
export function modelSuccessRateTextClass(successRate: number | null) {
if (successRate === null)
return 'text-(--cp-text-muted)'
if (successRate >= 99.5)
return 'text-(--cp-success-text)'
if (successRate >= 98)
return 'text-(--cp-normal-text)'
if (successRate >= 95)
return 'text-(--cp-warning-text)'
return 'text-(--cp-danger-text)'
}
function compareQuotaWindows(left: AccountQuotaWindow, right: AccountQuotaWindow) {
const groupDifference = groupOrder(left) - groupOrder(right)
if (groupDifference !== 0)
+3 -3
View File
@@ -262,9 +262,9 @@ const {
</template>
<template #groups="{ row }">
<AccountGroupMarks
:groups="row.groups"
/>
<div class="flex w-full justify-center">
<AccountGroupMarks :groups="row.groups" />
</div>
</template>
<template #lastUsedAt="{ row }">
@@ -9,8 +9,8 @@ defineProps<{
</script>
<template>
<div class="grid gap-1.5">
<div v-if="apiKey.routingScope === 'all'" class="flex items-center gap-2">
<div class="grid w-full justify-items-center gap-1.5">
<div v-if="apiKey.routingScope === 'all'" class="flex items-center justify-center gap-2">
<span class="inline-flex h-6 items-center rounded-lg bg-(--cp-warning-bg) px-2 text-[11px] font-bold text-(--cp-warning-text)">
全部账号权限
</span>
+1
View File
@@ -46,6 +46,7 @@ const {
class="text-(--cp-normal)"
size="md"
label="刷新概览"
spin-icon-on-loading
:loading="loading || refreshing"
:disabled="loading || refreshing"
@click="refresh"
@@ -69,6 +69,7 @@ function showDetail(record: Awaited<ReturnType<typeof getOpsErrors>>['items'][nu
variant="ghost"
size="md"
label="刷新错误明细"
spin-icon-on-loading
:loading="refreshing"
:disabled="loading || refreshing"
@click="refresh"
@@ -32,6 +32,7 @@ const search = defineModel<string>('search', { required: true })
variant="ghost"
size="md"
label="刷新使用记录"
spin-icon-on-loading
:loading="refreshing"
:disabled="loading || refreshing"
@click="emit('refresh')"
+1 -1
View File
@@ -1 +1 @@
version: "3.2.1"
version: "3.2.2"