fix(monitor): 精简缓存率展示

This commit is contained in:
tbphp committed 2026-08-06 14:49:19 +08:00
1 parent ed2c048170
commit 25ef613161
5 files changed
+47 -202

No files matched your search

+31 -31
View File
@@ -1,9 +1,7 @@
<script setup lang="ts">
import { CircleHelp } from '@lucide/vue'
import { useI18n } from 'vue-i18n'
import type { UsageAggregateDto } from '@/app/resources/usage'
import AppTooltip from '@/components/ui/AppTooltip.vue'
import { formatEstimatedCost, formatInteger, formatPercent, formatTokens } from '@/lib/format'
import { formatCacheHitRate } from '@/lib/cache-rate'
@@ -42,29 +40,23 @@ function inputTokens(): number {
<article class="usage-kpi usage-kpi--accent">
<span>{{ t('monitor.usage.kpi.requests') }}</span>
<strong>{{ formatInteger(summary.request_count, locale) }}</strong>
<small>
{{
t('monitor.usage.kpi.outcomeSummary', {
success: formatInteger(summary.success_count, locale),
failure: formatInteger(summary.failure_count, locale),
rate: formatPercent(summary.failure_count, summary.request_count, locale),
})
}}
<small class="usage-kpi__outcomes">
<span class="usage-kpi__success">
{{ t('monitor.usage.columns.success') }}
{{ formatInteger(summary.success_count, locale) }}
</span>
<span class="usage-kpi__separator">/</span>
<span class="usage-kpi__failure">
{{ t('monitor.usage.columns.failure') }}
{{ formatInteger(summary.failure_count, locale) }}
</span>
<span class="usage-kpi__rate">
({{ formatPercent(summary.failure_count, summary.request_count, locale) }})
</span>
</small>
</article>
<article class="usage-kpi usage-kpi--cache">
<span>
{{ t('monitor.usage.kpi.cacheHitRate') }}
<AppTooltip :content="t('monitor.usage.kpi.cacheHitRateHelp')" side="bottom">
<button
type="button"
class="usage-kpi__help"
:aria-label="t('monitor.usage.kpi.cacheHitRateHelp')"
>
<CircleHelp :size="13" aria-hidden="true" />
</button>
</AppTooltip>
</span>
<span>{{ t('monitor.usage.kpi.cacheHitRate') }}</span>
<strong>
{{ formatCacheHitRate(summary.cache_read_tokens, inputTokens(), locale) }}
</strong>
@@ -173,16 +165,24 @@ function inputTokens(): number {
line-height: 1.4;
}
.usage-kpi__help {
display: inline-grid;
width: 20px;
height: 20px;
padding: 0;
place-items: center;
border: 0;
background: transparent;
.usage-kpi__outcomes {
display: flex !important;
align-items: baseline;
gap: 5px;
white-space: nowrap;
}
.usage-kpi__success {
color: var(--color-success);
}
.usage-kpi__failure {
color: var(--color-danger);
}
.usage-kpi__separator,
.usage-kpi__rate {
color: var(--color-text-faint);
cursor: help;
}
.usage-kpi__quality {
+16 -150
View File
@@ -24,9 +24,8 @@ import EmptyState from '@/components/ui/EmptyState.vue'
import InlineFeedback from '@/components/ui/InlineFeedback.vue'
import QueryFeedback from '@/components/ui/QueryFeedback.vue'
import StatusBadge from '@/components/ui/StatusBadge.vue'
import { formatEstimatedCost, formatInteger, formatPercent, formatTokens } from '@/lib/format'
import { formatEstimatedCost, formatInteger, formatTokens } from '@/lib/format'
import { cacheHitRate, formatCacheHitRate } from '@/lib/cache-rate'
import MonitorSectionHeading from './MonitorSectionHeading.vue'
import {
applyUsageFilterDraft,
@@ -85,41 +84,6 @@ function formatCost(aggregate: UsageAggregateDto): string {
return formatEstimatedCost(aggregate.estimated_cost_nano_usd, locale.value)
}
function inputTokens(aggregate: UsageAggregateDto): number {
return (
aggregate.uncached_input_tokens +
aggregate.cache_read_tokens +
aggregate.cache_write_5m_tokens +
aggregate.cache_write_1h_tokens +
aggregate.cache_write_unknown_tokens
)
}
function nonHitInputTokens(aggregate: UsageAggregateDto): number {
return inputTokens(aggregate) - aggregate.cache_read_tokens
}
function cacheRate(aggregate: UsageAggregateDto): number | null {
return cacheHitRate(aggregate.cache_read_tokens, inputTokens(aggregate))
}
function cacheRateLabel(aggregate: UsageAggregateDto): string {
return formatCacheHitRate(aggregate.cache_read_tokens, inputTokens(aggregate), locale.value)
}
function nonHitRateLabel(aggregate: UsageAggregateDto): string {
const rate = cacheRate(aggregate)
if (rate === null) return '—'
return new Intl.NumberFormat(locale.value, {
style: 'percent',
maximumFractionDigits: 1,
}).format(1 - rate)
}
function cacheShareWidth(aggregate: UsageAggregateDto): string {
return `${(cacheRate(aggregate) ?? 0) * 100}%`
}
function groupName(groupID: number): string {
if (groupID === 0) return t('monitor.usage.breakdown.unattributedGroup')
return (
@@ -262,34 +226,6 @@ defineExpose({ openFilters })
:title="t('monitor.usage.tokens.title')"
:description="t('monitor.usage.tokens.description')"
/>
<div class="usage-token-composition">
<div class="usage-token-composition__legend">
<span>
<i class="usage-token-composition__dot usage-token-composition__dot--hit" />
{{ t('monitor.usage.tokens.cacheRead') }}
<strong>{{ formatTokens(report.summary.cache_read_tokens, locale) }}</strong>
<small>{{ cacheRateLabel(report.summary) }}</small>
</span>
<span>
<i class="usage-token-composition__dot" />
{{ t('monitor.usage.tokens.nonHitInput') }}
<strong>{{ formatTokens(nonHitInputTokens(report.summary), locale) }}</strong>
<small>{{ nonHitRateLabel(report.summary) }}</small>
</span>
</div>
<div
class="usage-token-composition__track"
role="img"
:aria-label="
t('monitor.usage.tokens.cacheCompositionLabel', {
hit: cacheRateLabel(report.summary),
other: nonHitRateLabel(report.summary),
})
"
>
<span :style="{ width: cacheShareWidth(report.summary) }" />
</div>
</div>
<dl class="usage-token-grid">
<div>
<dt>{{ t('monitor.usage.tokens.uncachedInput') }}</dt>
@@ -433,15 +369,17 @@ defineExpose({ openFilters })
<span class="usage-cell-label">{{ t('monitor.usage.columns.quality') }}</span>
<span>
{{
t('monitor.usage.columns.qualitySuccess', {
rate: formatPercent(row.success_count, row.request_count, locale),
t('monitor.usage.columns.qualityUsage', {
missing: formatInteger(row.usage_missing_count, locale),
partial: formatInteger(row.partial_count, locale),
})
}}
</span>
<small>
{{
t('monitor.usage.columns.qualityCache', {
rate: cacheRateLabel(row),
t('monitor.usage.columns.qualityPricing', {
unpriced: formatInteger(row.unpriced_request_count, locale),
partial: formatInteger(row.pricing_partial_count, locale),
})
}}
</small>
@@ -614,70 +552,6 @@ defineExpose({ openFilters })
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.usage-token-composition {
display: grid;
gap: 7px;
padding-inline: 2px;
}
.usage-token-composition__legend {
display: flex;
min-width: 0;
align-items: center;
justify-content: space-between;
gap: 18px;
color: var(--color-text-muted);
font-size: var(--text-label-xs);
}
.usage-token-composition__legend > span {
display: flex;
min-width: 0;
align-items: center;
gap: 6px;
}
.usage-token-composition__legend strong,
.usage-token-composition__legend small {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
}
.usage-token-composition__legend strong {
color: var(--color-text);
font-weight: 560;
}
.usage-token-composition__legend small {
color: var(--color-text-faint);
}
.usage-token-composition__dot {
width: 6px;
height: 6px;
flex: 0 0 6px;
border-radius: 50%;
background: var(--color-text-faint);
}
.usage-token-composition__dot--hit {
background: var(--color-success);
}
.usage-token-composition__track {
height: 5px;
overflow: hidden;
border-radius: 999px;
background: var(--color-surface-sunken);
}
.usage-token-composition__track > span {
display: block;
height: 100%;
border-radius: inherit;
background: var(--color-success);
}
.usage-quality-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@@ -734,17 +608,17 @@ defineExpose({ openFilters })
}
.usage-breakdown-record {
display: grid;
display: block;
min-width: 0;
grid-column: 1 / -1;
grid-template-columns: subgrid;
}
.usage-breakdown-record__summary {
--ledger-record-list-record-min-height: 72px;
--ledger-record-list-record-padding: 12px 0;
grid-template-columns: subgrid;
grid-template-columns: var(--ledger-record-list-grid);
column-gap: var(--ledger-record-list-column-gap);
cursor: pointer;
list-style: none;
}
@@ -842,18 +716,15 @@ defineExpose({ openFilters })
.usage-breakdown-record__detail {
display: grid;
grid-column: 1 / -1;
grid-template-columns: repeat(4, minmax(0, 1fr));
margin: 0 -14px;
grid-template-columns: repeat(6, minmax(0, 1fr));
margin: 0;
border-top: 1px solid var(--color-border-subtle);
background: var(--color-border-subtle);
gap: 1px;
gap: 14px 22px;
padding: 14px 0 16px;
}
.usage-breakdown-record__detail > div {
min-width: 0;
background: var(--color-surface-raised);
padding: 10px 14px;
}
.usage-breakdown-record__detail dt {
@@ -863,7 +734,7 @@ defineExpose({ openFilters })
.usage-breakdown-record__detail dd {
margin: 4px 0 0;
color: var(--color-text-muted);
color: var(--color-text);
font-family: var(--font-mono);
font-size: var(--text-sm);
font-variant-numeric: tabular-nums;
@@ -929,6 +800,7 @@ defineExpose({ openFilters })
.usage-breakdown-record__summary {
--ledger-record-list-card-grid: repeat(2, minmax(0, 1fr));
grid-template-columns: var(--ledger-record-list-card-grid);
position: relative;
padding-right: 52px;
}
@@ -965,12 +837,6 @@ defineExpose({ openFilters })
.usage-trend-panel__chart {
padding: 14px 12px 10px;
}
.usage-token-composition__legend {
align-items: flex-start;
flex-direction: column;
gap: 5px;
}
}
@media (max-width: 520px) {
-7
View File
@@ -240,11 +240,8 @@ export default {
requests: 'Requests',
outcomes: 'Success and failure',
outcomeCounts: 'Success {success} · Failure {failure}',
outcomeSummary: 'Success {success} / failure {failure} ({rate})',
successRate: 'Success rate {rate}',
cacheHitRate: 'Cache hit rate',
cacheHitRateHelp:
'Cache hit rate = cache-read tokens / input tokens (uncached input + cache reads + all cache writes). Output tokens are excluded; only reported usage is measured.',
cacheTokenSummary: 'Cache read {read} / input {input}',
totalTokens: 'Total reported tokens',
persistedWindow: 'Current reporting window',
@@ -258,8 +255,6 @@ export default {
cacheWrite5m: 'Cache write (5m)',
cacheWrite1h: 'Cache write (1h)',
cacheWriteUnknown: 'Cache write',
nonHitInput: 'Other input',
cacheCompositionLabel: 'Cache reads {hit}; other input {other}',
output: 'Output',
},
cost: {
@@ -340,8 +335,6 @@ export default {
estimatedCost: 'Estimated cost',
quality: 'Quality',
actions: 'Actions',
qualitySuccess: 'Success rate {rate}',
qualityCache: 'Cache hit rate {rate}',
qualityUsage: 'Missing {missing} · Partial {partial}',
qualityPricing: 'Unpriced {unpriced} · Partial {partial}',
qualityCompact:
-7
View File
@@ -241,11 +241,8 @@ export default {
requests: 'リクエスト数',
outcomes: '成功と失敗',
outcomeCounts: '成功 {success} · 失敗 {failure}',
outcomeSummary: '成功 {success} / 失敗 {failure}({rate})',
successRate: '成功率 {rate}',
cacheHitRate: 'キャッシュ率',
cacheHitRateHelp:
'キャッシュ率 = キャッシュ読み取り ÷ 入力 Token(キャッシュなし入力 + キャッシュ読み取り + すべてのキャッシュ書き込み)。出力 Token は含まず、報告済み使用量のみを対象とします。',
cacheTokenSummary: 'キャッシュ読み取り {read} / 入力 {input}',
totalTokens: '報告済みトークン合計',
persistedWindow: '現在の集計期間',
@@ -259,8 +256,6 @@ export default {
cacheWrite5m: 'キャッシュ書き込み(5 分)',
cacheWrite1h: 'キャッシュ書き込み(1 時間)',
cacheWriteUnknown: 'キャッシュ書き込み',
nonHitInput: 'その他の入力',
cacheCompositionLabel: 'キャッシュ読み取り {hit}、その他の入力 {other}',
output: '出力',
},
cost: {
@@ -341,8 +336,6 @@ export default {
estimatedCost: '推定コスト',
quality: '品質',
actions: '操作',
qualitySuccess: '成功率 {rate}',
qualityCache: 'キャッシュ率 {rate}',
qualityUsage: 'なし {missing} · 部分 {partial}',
qualityPricing: '未設定 {unpriced} · 一部 {partial}',
qualityCompact:
-7
View File
@@ -234,11 +234,8 @@ export default {
requests: '请求数',
outcomes: '成功与失败',
outcomeCounts: '成功 {success} · 失败 {failure}',
outcomeSummary: '成功 {success} / 失败 {failure}({rate})',
successRate: '成功率 {rate}',
cacheHitRate: '缓存率',
cacheHitRateHelp:
'缓存率 = 缓存读取 ÷ 输入 Token(非缓存输入 + 缓存读取 + 各类缓存写入),不含输出 Token;仅基于已报告用量。',
cacheTokenSummary: '缓存读取 {read} / 输入 {input}',
totalTokens: '已报告 Token 总数',
persistedWindow: '当前统计窗口',
@@ -252,8 +249,6 @@ export default {
cacheWrite5m: '缓存写入(5 分钟)',
cacheWrite1h: '缓存写入(1 小时)',
cacheWriteUnknown: '缓存写入',
nonHitInput: '其他输入',
cacheCompositionLabel: '缓存读取占 {hit},其他输入占 {other}',
output: '输出',
},
cost: {
@@ -328,8 +323,6 @@ export default {
estimatedCost: '预估成本',
quality: '质量',
actions: '操作',
qualitySuccess: '成功率 {rate}',
qualityCache: '缓存率 {rate}',
qualityUsage: '缺失 {missing} · 部分 {partial}',
qualityPricing: '未定价 {unpriced} · 部分定价 {partial}',
qualityCompact: