refactor(ai): simplify usage metrics display and enhance styling

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-11-01 22:19:20 +08:00
parent 7f1e9c9f3d
commit 9688b20a00
No known key found for this signature in database
6 changed files with 49 additions and 56 deletions

View File

@ -57,10 +57,7 @@ export const DetailedUsageModal = () => {
const byModel = analysis?.patterns?.byModel ?? []
const dailyTotals = daily.map((d: any) => Number(d.totalTokens) || 0)
const avgDaily =
dailyTotals.length > 0
? Math.round(dailyTotals.reduce((a, b) => a + b, 0) / dailyTotals.length)
: 0
const peakDay = daily.reduce(
(acc: any, cur: any) => (cur.totalTokens > (acc?.totalTokens ?? -1) ? cur : acc),
null,
@ -81,16 +78,11 @@ export const DetailedUsageModal = () => {
const formattedUsageTokens = formatTokenCount(usage.used)
const formattedRemainingTokens = formatTokenCount(rateLimit.remainingTokens)
const formattedTotalTokens = formatTokenCount(usage.total)
const formattedAvgDaily = formatTokenCount(avgDaily)
return (
<div className="flex max-h-[80vh] min-h-[640px] w-[500px] max-w-full flex-col space-y-6 overflow-hidden">
<div className="space-y-6 px-4">
<p className="text-sm text-text-secondary">
{t("usage_analysis.detailed_description", {
defaultValue: "Track your AI credits usage, patterns, and efficiency.",
})}
</p>
<p className="text-sm text-text-secondary">{t("usage_analysis.detailed_description")}</p>
{rateLimit?.warningLevel && rateLimit.warningLevel !== "safe" && (
<UsageWarningBanner
@ -104,35 +96,35 @@ export const DetailedUsageModal = () => {
{/* Layout Option 4: Asymmetric Modern */}
<div className="space-y-4">
<div className="grid grid-cols-5 gap-4">
<div className="col-span-2 flex items-center justify-center rounded-xl bg-gradient-to-br from-material-opaque/40 to-material-opaque/60 p-6">
<div className="col-span-2 flex items-center justify-center rounded-xl bg-fill-secondary/50 p-6">
<UsageProgressRing percentage={usagePercentage} size={130} />
</div>
<div className="col-span-3 space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="rounded-lg bg-fill-secondary/40 p-4">
<div className="col-span-3 flex h-full flex-col gap-3">
<div className="grid h-1/2 grid-cols-2 gap-4">
<div className="rounded-lg bg-fill-secondary/50 p-4">
<Metric
label={t("usage_analysis.tokens_used")}
value={formattedUsageTokens.value}
unit={formattedUsageTokens.unit}
/>
</div>
<div className="rounded-lg bg-fill-secondary/40 p-4">
<div className="rounded-lg bg-fill-secondary/50 p-4">
<Metric
label={t("usage_analysis.tokens_remaining")}
value={formattedRemainingTokens.value}
unit={formattedRemainingTokens.unit}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="rounded-lg bg-fill-secondary/20 p-3">
<StatCompact
label={t("usage_analysis.total_limit")}
label={t("usage_analysis.total_credits")}
value={formattedTotalTokens.value}
unit={formattedTotalTokens.unit}
/>
</div>
<div className="rounded-lg bg-fill-secondary/20 p-3">
</div>
<div className="grid h-1/2 grid-cols-2 gap-4">
<div className="rounded-lg bg-fill-secondary/30 p-3">
<StatCompact
label={t("usage_analysis.window_remaining")}
value={formattedRemainingTokens.value}
unit={formattedRemainingTokens.unit}
/>
</div>
<div className="rounded-lg bg-fill-secondary/30 p-3">
<StatCompact
label={t("usage_analysis.resets_in")}
value={formatTimeRemaining(rateLimit.windowResetTime - Date.now())}
@ -141,26 +133,6 @@ export const DetailedUsageModal = () => {
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="rounded-lg border border-fill-tertiary bg-fill-secondary/10 p-4">
<StatCompact
label={t("analytics.avg_per_day", { defaultValue: "Avg/day" })}
value={formattedAvgDaily.value}
unit={formattedAvgDaily.unit}
/>
</div>
<div className="rounded-lg border border-fill-tertiary bg-fill-secondary/10 p-4">
<StatCompact
label={t("analytics.peak_day", { defaultValue: "Peak day" })}
value={peakDay?.date ? new Date(peakDay.date).toLocaleDateString() : "-"}
hint={
peakDay?.peakHour != null
? t("analytics.at_hour", { defaultValue: "@ {{h}}:00", h: peakDay.peakHour })
: undefined
}
/>
</div>
</div>
</div>
</div>
<Tabs defaultValue="overview" className="relative flex min-h-0 grow flex-col space-y-4">

View File

@ -26,7 +26,7 @@ export const HistoryTab = ({ analysis }: HistoryTabProps) => {
<div className="flex items-center gap-2">
{t("analytics.event", { defaultValue: "Event" })}
</div>
<div className="flex items-center justify-center gap-2">
<div className="ml-5 flex items-center justify-start gap-2">
{t("analytics.tokens", { defaultValue: "Tokens" })}
</div>
<div className="flex items-center justify-end gap-2">
@ -52,7 +52,7 @@ export const HistoryTab = ({ analysis }: HistoryTabProps) => {
</div>
</div>
<div className="flex justify-center">
<div className="flex justify-start">
<span
className={cn(
"inline-flex items-center gap-1 rounded-md px-2 py-1 text-sm font-medium tabular-nums",

View File

@ -19,13 +19,19 @@ export const UsageProgressRing = ({
}: UsageProgressRingProps) => {
const normalized = Math.max(0, Math.min(100, Number.isFinite(percentage) ? percentage : 0))
const dimension = typeof size === "number" ? size : sizeMap[size]
const strokeWidth = size === "sm" ? 6 : size === "md" ? 8 : 10
const strokeWidth = size === "sm" ? 8 : size === "md" ? 12 : 14
const radius = (dimension - strokeWidth) / 2
const circumference = 2 * Math.PI * radius
const offset = circumference - (normalized / 100) * circumference
// Color coding by percentage
const color = normalized >= 90 ? "#ef4444" : normalized >= 70 ? "#f59e0b" : "#22c55e"
// Gradient colors by percentage
const gradientId = `gradient-${Math.random().toString(36).slice(2, 9)}`
const gradientColors =
normalized >= 90
? { start: "#ef4444", end: "#dc2626" } // red
: normalized >= 70
? { start: "#f59e0b", end: "#d97706" } // amber
: { start: "#22c55e", end: "#16a34a" } // green
return (
<div
@ -33,6 +39,12 @@ export const UsageProgressRing = ({
style={{ width: dimension, height: dimension }}
>
<svg width={dimension} height={dimension} className="block -scale-100">
<defs>
<linearGradient id={gradientId} x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor={gradientColors.start} />
<stop offset="100%" stopColor={gradientColors.end} />
</linearGradient>
</defs>
<circle
className="stroke-fill-secondary"
fill="transparent"
@ -42,7 +54,7 @@ export const UsageProgressRing = ({
cy={dimension / 2}
/>
<circle
stroke={color}
stroke={`url(#${gradientId})`}
fill="transparent"
strokeWidth={strokeWidth}
strokeLinecap="round"
@ -51,10 +63,13 @@ export const UsageProgressRing = ({
r={radius}
cx={dimension / 2}
cy={dimension / 2}
style={{ transition: "stroke-dashoffset 0.3s ease" }}
style={{
transition: "stroke-dashoffset 0.6s cubic-bezier(0.4, 0, 0.2, 1)",
opacity: 0.95,
}}
/>
</svg>
<div className="absolute inset-0 grid place-items-center text-sm font-medium text-text">
<div className="absolute inset-0 grid place-items-center text-sm font-semibold text-text">
{Math.round(normalized)}%
</div>
</div>

View File

@ -300,12 +300,14 @@
"usage_analysis.title": "AI Credits Usage",
"usage_analysis.tokens_remaining": "credits remaining",
"usage_analysis.tokens_used": "Credits Used",
"usage_analysis.total_credits": "Total Credits",
"usage_analysis.total_limit": "Total Limit",
"usage_analysis.view_details": "View Details",
"usage_analysis.warning.general": "Consider reducing usage to avoid hitting limits",
"usage_analysis.warning.projected": "Projected limit in {{eta}}",
"usage_analysis.warning.rate": "Rate: {{rate}} tok/min",
"usage_analysis.warning.title": "High AI usage detected",
"usage_analysis.window_remaining": "Window credits left",
"welcome_description": "I'm here to help you with your reading needs. How can I assist you today?",
"welcome_description_contextual": "Let's discuss this entry together"
}

View File

@ -260,12 +260,14 @@
"usage_analysis.title": "トークン使用状況",
"usage_analysis.tokens_remaining": "残りトークン",
"usage_analysis.tokens_used": "使用済みトークン",
"usage_analysis.total_credits": "総クレジット",
"usage_analysis.total_limit": "総上限",
"usage_analysis.view_details": "詳細を表示",
"usage_analysis.warning.general": "上限に達しないよう使用量の削減を検討してください",
"usage_analysis.warning.projected": "{{eta}}で上限に達する可能性があります",
"usage_analysis.warning.rate": "レート: {{rate}} トークン/分",
"usage_analysis.warning.title": "AIの高い使用量が検出されました",
"usage_analysis.warning.rate": "レート:{{rate}} トークン/分",
"usage_analysis.warning.title": "AI の高い使用量が検出されました",
"usage_analysis.window_remaining": "ウィンドウ内の残りクレジット",
"welcome_description": "私はあなたの読書と執筆のニーズをサポートするためにここにいます。今日はどのようにお手伝いできますか?",
"welcome_description_contextual": "このエントリについて一緒に話し合いましょう"
}

View File

@ -293,12 +293,14 @@
"usage_analysis.title": "令牌使用情况",
"usage_analysis.tokens_remaining": "剩余令牌",
"usage_analysis.tokens_used": "已使用令牌",
"usage_analysis.total_credits": "总额度",
"usage_analysis.total_limit": "总限制",
"usage_analysis.view_details": "查看详情",
"usage_analysis.warning.general": "请考虑降低使用以避免达到限制",
"usage_analysis.warning.projected": "预计在 {{eta}} 达到限制",
"usage_analysis.warning.rate": "速率:{{rate}} 令牌/分钟",
"usage_analysis.warning.title": "检测到较高的 AI 使用量",
"usage_analysis.window_remaining": "窗口内剩余额度",
"welcome_description": "我在这里帮助您处理阅读需求。今天我能为您做些什么?",
"welcome_description_contextual": "让我们一起讨论这个条目"
}