feat: redesign token usage dashboard
This commit is contained in:
parent
8c8b2d3a20
commit
fd797e3e36
@ -333,99 +333,93 @@
|
||||
<div class="token-drawer" v-if="currentConversationId" :class="{ collapsed: tokenPanelCollapsed }">
|
||||
<div class="token-display-panel">
|
||||
<div class="token-panel-content">
|
||||
<div class="token-panel-grid">
|
||||
<div class="panel-row">
|
||||
<div class="token-card compact panel-card">
|
||||
<div class="panel-row-title">Token 统计</div>
|
||||
<div class="token-stats inline-row">
|
||||
<div class="token-item">
|
||||
<span class="token-label">当前上下文</span>
|
||||
<span class="token-value current">{{ formatTokenCount(currentContextTokens || 0) }}</span>
|
||||
<div class="usage-dashboard">
|
||||
<div class="usage-cell usage-cell--left usage-cell--token panel-card">
|
||||
<div class="usage-title">Token 统计</div>
|
||||
<div class="stat-grid stat-grid--triple">
|
||||
<div class="stat-block">
|
||||
<div class="stat-label">当前上下文</div>
|
||||
<div class="stat-value stat-value--accent">{{ formatTokenCount(currentContextTokens || 0) }}</div>
|
||||
</div>
|
||||
<div class="token-item">
|
||||
<span class="token-label">累计输入</span>
|
||||
<span class="token-value input">{{ formatTokenCount(currentConversationTokens.cumulative_input_tokens || 0) }}</span>
|
||||
<div class="stat-block">
|
||||
<div class="stat-label">累计输入</div>
|
||||
<div class="stat-value stat-value--success">{{ formatTokenCount(currentConversationTokens.cumulative_input_tokens || 0) }}</div>
|
||||
</div>
|
||||
<div class="token-item">
|
||||
<span class="token-label">累计输出</span>
|
||||
<span class="token-value output">{{ formatTokenCount(currentConversationTokens.cumulative_output_tokens || 0) }}</span>
|
||||
<div class="stat-block">
|
||||
<div class="stat-label">累计输出</div>
|
||||
<div class="stat-value stat-value--warning">{{ formatTokenCount(currentConversationTokens.cumulative_output_tokens || 0) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-card compact panel-card" v-if="containerStatus">
|
||||
<div class="panel-row-title">
|
||||
<span class="token-label">CPU / 内存</span>
|
||||
<span class="status-pill" :class="containerStatusClass()">{{ containerStatusText() }}</span>
|
||||
<div class="usage-cell usage-cell--right usage-cell--performance panel-card">
|
||||
<div class="usage-title">
|
||||
<span>性能统计</span>
|
||||
<span class="status-pill" v-if="containerStatus" :class="containerStatusClass()">{{ containerStatusText() }}</span>
|
||||
</div>
|
||||
<div class="double-metric">
|
||||
<div class="container-metric">
|
||||
<span class="metric-label">CPU</span>
|
||||
<span class="metric-value">{{ formatPercentage(containerStatus.stats?.cpu_percent) }}</span>
|
||||
<template v-if="hasContainerStats()">
|
||||
<div class="stat-grid stat-grid--double">
|
||||
<div class="stat-block">
|
||||
<div class="stat-label">CPU</div>
|
||||
<div class="stat-value">{{ formatPercentage(containerStatus.stats.cpu_percent) }}</div>
|
||||
</div>
|
||||
<div class="container-metric">
|
||||
<span class="metric-label">内存</span>
|
||||
<span class="metric-value">
|
||||
{{ formatBytes(containerStatus.stats?.memory?.used_bytes) }}
|
||||
<template v-if="containerStatus.stats?.memory?.limit_bytes">
|
||||
<div class="stat-block">
|
||||
<div class="stat-label">内存</div>
|
||||
<div class="stat-value stat-value--mono">
|
||||
{{ formatBytes(containerStatus.stats.memory.used_bytes) }}
|
||||
<template v-if="containerStatus.stats.memory.limit_bytes">
|
||||
/ {{ formatBytes(containerStatus.stats.memory.limit_bytes) }}
|
||||
</template>
|
||||
</span>
|
||||
<span class="metric-subtext" v-if="containerStatus.stats?.memory?.percent">
|
||||
</div>
|
||||
<div class="stat-foot" v-if="containerStatus.stats.memory.percent">
|
||||
{{ formatPercentage(containerStatus.stats.memory.percent) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-card compact panel-card empty" v-else>
|
||||
<div class="container-empty">
|
||||
</template>
|
||||
<div class="usage-placeholder" v-else>
|
||||
当前运行在宿主机模式,暂无容器指标。
|
||||
</div>
|
||||
</div>
|
||||
<div class="usage-cell usage-cell--left usage-cell--quota panel-card">
|
||||
<div class="usage-title">额度统计</div>
|
||||
<div class="stat-grid stat-grid--triple">
|
||||
<div class="stat-block">
|
||||
<div class="stat-label">常规模型</div>
|
||||
<div class="stat-value">{{ formatQuotaValue(usageQuota.fast) }}</div>
|
||||
<div class="stat-foot" v-if="(usageQuota.fast.count || 0) > 0">重置 {{ formatResetTime(usageQuota.fast.reset_at) }}</div>
|
||||
</div>
|
||||
<div class="panel-row">
|
||||
<div class="token-card compact panel-card">
|
||||
<div class="panel-row-title">模型 / 搜索 用量</div>
|
||||
<div class="quota-inline">
|
||||
<div class="quota-inline-item">
|
||||
<div class="quota-label">常规模型</div>
|
||||
<div class="quota-value">{{ formatQuotaValue(usageQuota.fast) }}</div>
|
||||
<div class="quota-reset" v-if="(usageQuota.fast.count || 0) > 0">重置 {{ formatResetTime(usageQuota.fast.reset_at) }}</div>
|
||||
<div class="stat-block">
|
||||
<div class="stat-label">思考模型</div>
|
||||
<div class="stat-value">{{ formatQuotaValue(usageQuota.thinking) }}</div>
|
||||
<div class="stat-foot" v-if="(usageQuota.thinking.count || 0) > 0">重置 {{ formatResetTime(usageQuota.thinking.reset_at) }}</div>
|
||||
</div>
|
||||
<div class="quota-inline-item">
|
||||
<div class="quota-label">思考模型</div>
|
||||
<div class="quota-value">{{ formatQuotaValue(usageQuota.thinking) }}</div>
|
||||
<div class="quota-reset" v-if="(usageQuota.thinking.count || 0) > 0">重置 {{ formatResetTime(usageQuota.thinking.reset_at) }}</div>
|
||||
</div>
|
||||
<div class="quota-inline-item">
|
||||
<div class="quota-label">搜索</div>
|
||||
<div class="quota-value">{{ formatQuotaValue(usageQuota.search) }}</div>
|
||||
<div class="quota-reset" v-if="(usageQuota.search.count || 0) > 0">重置 {{ formatResetTime(usageQuota.search.reset_at) }}</div>
|
||||
<div class="stat-block">
|
||||
<div class="stat-label">搜索</div>
|
||||
<div class="stat-value">{{ formatQuotaValue(usageQuota.search) }}</div>
|
||||
<div class="stat-foot" v-if="(usageQuota.search.count || 0) > 0">重置 {{ formatResetTime(usageQuota.search.reset_at) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-card compact panel-card">
|
||||
<div class="panel-row-title">
|
||||
<span class="token-label">网络 / 存储</span>
|
||||
</div>
|
||||
<div class="double-metric">
|
||||
<div class="container-metric">
|
||||
<span class="metric-label">网络</span>
|
||||
<span class="metric-value">
|
||||
<div class="usage-cell usage-cell--right usage-cell--resource panel-card">
|
||||
<div class="usage-title">资源统计</div>
|
||||
<div class="stat-grid stat-grid--double">
|
||||
<div class="stat-block">
|
||||
<div class="stat-label">网络</div>
|
||||
<div class="stat-value stat-value--mono">
|
||||
↓{{ formatRate(containerNetRate.down_bps) }}
|
||||
↑{{ formatRate(containerNetRate.up_bps) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="container-metric">
|
||||
<span class="metric-label">存储</span>
|
||||
<span class="metric-value">
|
||||
</div>
|
||||
<div class="stat-block">
|
||||
<div class="stat-label">存储</div>
|
||||
<div class="stat-value stat-value--mono">
|
||||
{{ formatBytes(projectStorage.used_bytes) }}
|
||||
<template v-if="projectStorage.limit_bytes">
|
||||
/ {{ formatBytes(projectStorage.limit_bytes) }}
|
||||
</template>
|
||||
</span>
|
||||
<span class="metric-subtext" v-if="typeof projectStorage.usage_percent === 'number'">
|
||||
</div>
|
||||
<div class="stat-foot" v-if="typeof projectStorage.usage_percent === 'number'">
|
||||
{{ projectStorage.usage_percent.toFixed(1) }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
141
static/style.css
141
static/style.css
@ -2430,6 +2430,128 @@ o-files {
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.usage-dashboard {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
grid-auto-rows: auto;
|
||||
column-gap: 48px;
|
||||
row-gap: 32px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.usage-dashboard::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: 1px;
|
||||
background: var(--claude-border);
|
||||
transform: translateX(-0.5px);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.usage-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.usage-dashboard .panel-card {
|
||||
border-left: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.usage-cell--left {
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
.usage-cell--right {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.usage-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--claude-text);
|
||||
}
|
||||
|
||||
.stat-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stat-grid--triple {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.stat-grid--double {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.stat-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--claude-text-secondary);
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--claude-text);
|
||||
}
|
||||
|
||||
.stat-value--accent {
|
||||
color: var(--claude-accent);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.stat-value--success {
|
||||
color: var(--claude-success);
|
||||
}
|
||||
|
||||
.stat-value--warning {
|
||||
color: var(--claude-warning);
|
||||
}
|
||||
|
||||
.stat-value--mono {
|
||||
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.stat-meta,
|
||||
.stat-foot {
|
||||
font-size: 12px;
|
||||
color: var(--claude-text-secondary);
|
||||
}
|
||||
|
||||
.stat-meta {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.stat-foot {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.usage-placeholder {
|
||||
font-size: 13px;
|
||||
color: var(--claude-text-secondary);
|
||||
}
|
||||
|
||||
.panel-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
|
||||
@ -2662,6 +2784,25 @@ o-files {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.usage-dashboard {
|
||||
grid-template-columns: 1fr;
|
||||
row-gap: 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.usage-dashboard::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.usage-cell--left,
|
||||
.usage-cell--right {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.stat-grid--triple {
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
}
|
||||
|
||||
.token-item {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user