mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-04 02:21:47 +00:00
chore: remove comments
This commit is contained in:
parent
0348a69c51
commit
e1027134cd
12 changed files with 721 additions and 378 deletions
|
|
@ -1,13 +1,12 @@
|
|||
import { useCallback, useEffect, useLayoutEffect, useState } from "react";
|
||||
import {
|
||||
BarChart3,
|
||||
Brain,
|
||||
Cpu,
|
||||
RefreshCw,
|
||||
TrendingUp,
|
||||
} from "lucide-react";
|
||||
import { BarChart3, Brain, Cpu, RefreshCw, TrendingUp } from "lucide-react";
|
||||
import { api } from "@/lib/api";
|
||||
import type { AnalyticsResponse, AnalyticsDailyEntry, AnalyticsModelEntry, AnalyticsSkillEntry } from "@/lib/api";
|
||||
import type {
|
||||
AnalyticsResponse,
|
||||
AnalyticsDailyEntry,
|
||||
AnalyticsModelEntry,
|
||||
AnalyticsSkillEntry,
|
||||
} from "@/lib/api";
|
||||
import { timeAgo } from "@/lib/utils";
|
||||
import { Button, Stats } from "@nous-research/ui";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
|
@ -43,16 +42,21 @@ function TokenBarChart({ daily }: { daily: AnalyticsDailyEntry[] }) {
|
|||
const { t } = useI18n();
|
||||
if (daily.length === 0) return null;
|
||||
|
||||
const maxTokens = Math.max(...daily.map((d) => d.input_tokens + d.output_tokens), 1);
|
||||
const maxTokens = Math.max(
|
||||
...daily.map((d) => d.input_tokens + d.output_tokens),
|
||||
1,
|
||||
);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<BarChart3 className="h-5 w-5 text-muted-foreground" />
|
||||
<CardTitle className="text-base">{t.analytics.dailyTokenUsage}</CardTitle>
|
||||
<CardTitle className="text-base">
|
||||
{t.analytics.dailyTokenUsage}
|
||||
</CardTitle>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
||||
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="h-2.5 w-2.5 bg-[#ffe6cb]" />
|
||||
{t.analytics.input}
|
||||
|
|
@ -64,47 +68,63 @@ function TokenBarChart({ daily }: { daily: AnalyticsDailyEntry[] }) {
|
|||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-end gap-[2px]" style={{ height: CHART_HEIGHT_PX }}>
|
||||
<div
|
||||
className="flex items-end gap-[2px]"
|
||||
style={{ height: CHART_HEIGHT_PX }}
|
||||
>
|
||||
{daily.map((d) => {
|
||||
const total = d.input_tokens + d.output_tokens;
|
||||
const inputH = Math.round((d.input_tokens / maxTokens) * CHART_HEIGHT_PX);
|
||||
const outputH = Math.round((d.output_tokens / maxTokens) * CHART_HEIGHT_PX);
|
||||
const inputH = Math.round(
|
||||
(d.input_tokens / maxTokens) * CHART_HEIGHT_PX,
|
||||
);
|
||||
const outputH = Math.round(
|
||||
(d.output_tokens / maxTokens) * CHART_HEIGHT_PX,
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={d.day}
|
||||
className="flex-1 min-w-0 group relative flex flex-col justify-end"
|
||||
style={{ height: CHART_HEIGHT_PX }}
|
||||
>
|
||||
{/* Tooltip */}
|
||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 hidden group-hover:block z-10 pointer-events-none">
|
||||
<div className="bg-card border border-border px-2.5 py-1.5 text-[10px] text-foreground shadow-lg whitespace-nowrap">
|
||||
<div className="font-medium">{formatDate(d.day)}</div>
|
||||
<div>{t.analytics.input}: {formatTokens(d.input_tokens)}</div>
|
||||
<div>{t.analytics.output}: {formatTokens(d.output_tokens)}</div>
|
||||
<div>{t.analytics.total}: {formatTokens(total)}</div>
|
||||
<div>
|
||||
{t.analytics.input}: {formatTokens(d.input_tokens)}
|
||||
</div>
|
||||
<div>
|
||||
{t.analytics.output}: {formatTokens(d.output_tokens)}
|
||||
</div>
|
||||
<div>
|
||||
{t.analytics.total}: {formatTokens(total)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Input bar */}
|
||||
|
||||
<div
|
||||
className="w-full bg-[#ffe6cb]/70"
|
||||
style={{ height: Math.max(inputH, total > 0 ? 1 : 0) }}
|
||||
/>
|
||||
{/* Output bar */}
|
||||
|
||||
<div
|
||||
className="w-full bg-emerald-500/70"
|
||||
style={{ height: Math.max(outputH, d.output_tokens > 0 ? 1 : 0) }}
|
||||
style={{
|
||||
height: Math.max(outputH, d.output_tokens > 0 ? 1 : 0),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* X-axis labels */}
|
||||
|
||||
<div className="flex justify-between mt-2 text-[10px] text-muted-foreground">
|
||||
<span>{daily.length > 0 ? formatDate(daily[0].day) : ""}</span>
|
||||
{daily.length > 2 && (
|
||||
<span>{formatDate(daily[Math.floor(daily.length / 2)].day)}</span>
|
||||
)}
|
||||
<span>{daily.length > 1 ? formatDate(daily[daily.length - 1].day) : ""}</span>
|
||||
<span>
|
||||
{daily.length > 1 ? formatDate(daily[daily.length - 1].day) : ""}
|
||||
</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
@ -122,7 +142,9 @@ function DailyTable({ daily }: { daily: AnalyticsDailyEntry[] }) {
|
|||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<TrendingUp className="h-5 w-5 text-muted-foreground" />
|
||||
<CardTitle className="text-base">{t.analytics.dailyBreakdown}</CardTitle>
|
||||
<CardTitle className="text-base">
|
||||
{t.analytics.dailyBreakdown}
|
||||
</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
|
|
@ -130,23 +152,42 @@ function DailyTable({ daily }: { daily: AnalyticsDailyEntry[] }) {
|
|||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-border text-muted-foreground text-xs">
|
||||
<th className="text-left py-2 pr-4 font-medium">{t.analytics.date}</th>
|
||||
<th className="text-right py-2 px-4 font-medium">{t.sessions.title}</th>
|
||||
<th className="text-right py-2 px-4 font-medium">{t.analytics.input}</th>
|
||||
<th className="text-right py-2 pl-4 font-medium">{t.analytics.output}</th>
|
||||
<th className="text-left py-2 pr-4 font-medium">
|
||||
{t.analytics.date}
|
||||
</th>
|
||||
<th className="text-right py-2 px-4 font-medium">
|
||||
{t.sessions.title}
|
||||
</th>
|
||||
<th className="text-right py-2 px-4 font-medium">
|
||||
{t.analytics.input}
|
||||
</th>
|
||||
<th className="text-right py-2 pl-4 font-medium">
|
||||
{t.analytics.output}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sorted.map((d) => {
|
||||
return (
|
||||
<tr key={d.day} className="border-b border-border/50 hover:bg-secondary/20 transition-colors">
|
||||
<td className="py-2 pr-4 font-medium">{formatDate(d.day)}</td>
|
||||
<td className="text-right py-2 px-4 text-muted-foreground">{d.sessions}</td>
|
||||
<tr
|
||||
key={d.day}
|
||||
className="border-b border-border/50 hover:bg-secondary/20 transition-colors"
|
||||
>
|
||||
<td className="py-2 pr-4 font-medium">
|
||||
{formatDate(d.day)}
|
||||
</td>
|
||||
<td className="text-right py-2 px-4 text-muted-foreground">
|
||||
{d.sessions}
|
||||
</td>
|
||||
<td className="text-right py-2 px-4">
|
||||
<span className="text-[#ffe6cb]">{formatTokens(d.input_tokens)}</span>
|
||||
<span className="text-[#ffe6cb]">
|
||||
{formatTokens(d.input_tokens)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="text-right py-2 pl-4">
|
||||
<span className="text-emerald-400">{formatTokens(d.output_tokens)}</span>
|
||||
<span className="text-emerald-400">
|
||||
{formatTokens(d.output_tokens)}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
|
@ -164,7 +205,8 @@ function ModelTable({ models }: { models: AnalyticsModelEntry[] }) {
|
|||
if (models.length === 0) return null;
|
||||
|
||||
const sorted = [...models].sort(
|
||||
(a, b) => b.input_tokens + b.output_tokens - (a.input_tokens + a.output_tokens),
|
||||
(a, b) =>
|
||||
b.input_tokens + b.output_tokens - (a.input_tokens + a.output_tokens),
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
@ -172,7 +214,9 @@ function ModelTable({ models }: { models: AnalyticsModelEntry[] }) {
|
|||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Cpu className="h-5 w-5 text-muted-foreground" />
|
||||
<CardTitle className="text-base">{t.analytics.perModelBreakdown}</CardTitle>
|
||||
<CardTitle className="text-base">
|
||||
{t.analytics.perModelBreakdown}
|
||||
</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
|
|
@ -180,22 +224,37 @@ function ModelTable({ models }: { models: AnalyticsModelEntry[] }) {
|
|||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-border text-muted-foreground text-xs">
|
||||
<th className="text-left py-2 pr-4 font-medium">{t.analytics.model}</th>
|
||||
<th className="text-right py-2 px-4 font-medium">{t.sessions.title}</th>
|
||||
<th className="text-right py-2 pl-4 font-medium">{t.analytics.tokens}</th>
|
||||
<th className="text-left py-2 pr-4 font-medium">
|
||||
{t.analytics.model}
|
||||
</th>
|
||||
<th className="text-right py-2 px-4 font-medium">
|
||||
{t.sessions.title}
|
||||
</th>
|
||||
<th className="text-right py-2 pl-4 font-medium">
|
||||
{t.analytics.tokens}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sorted.map((m) => (
|
||||
<tr key={m.model} className="border-b border-border/50 hover:bg-secondary/20 transition-colors">
|
||||
<tr
|
||||
key={m.model}
|
||||
className="border-b border-border/50 hover:bg-secondary/20 transition-colors"
|
||||
>
|
||||
<td className="py-2 pr-4">
|
||||
<span className="font-mono-ui text-xs">{m.model}</span>
|
||||
</td>
|
||||
<td className="text-right py-2 px-4 text-muted-foreground">{m.sessions}</td>
|
||||
<td className="text-right py-2 px-4 text-muted-foreground">
|
||||
{m.sessions}
|
||||
</td>
|
||||
<td className="text-right py-2 pl-4">
|
||||
<span className="text-[#ffe6cb]">{formatTokens(m.input_tokens)}</span>
|
||||
<span className="text-[#ffe6cb]">
|
||||
{formatTokens(m.input_tokens)}
|
||||
</span>
|
||||
{" / "}
|
||||
<span className="text-emerald-400">{formatTokens(m.output_tokens)}</span>
|
||||
<span className="text-emerald-400">
|
||||
{formatTokens(m.output_tokens)}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
|
@ -224,21 +283,38 @@ function SkillTable({ skills }: { skills: AnalyticsSkillEntry[] }) {
|
|||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-border text-muted-foreground text-xs">
|
||||
<th className="text-left py-2 pr-4 font-medium">{t.analytics.skill}</th>
|
||||
<th className="text-right py-2 px-4 font-medium">{t.analytics.loads}</th>
|
||||
<th className="text-right py-2 px-4 font-medium">{t.analytics.edits}</th>
|
||||
<th className="text-right py-2 px-4 font-medium">{t.analytics.total}</th>
|
||||
<th className="text-right py-2 pl-4 font-medium">{t.analytics.lastUsed}</th>
|
||||
<th className="text-left py-2 pr-4 font-medium">
|
||||
{t.analytics.skill}
|
||||
</th>
|
||||
<th className="text-right py-2 px-4 font-medium">
|
||||
{t.analytics.loads}
|
||||
</th>
|
||||
<th className="text-right py-2 px-4 font-medium">
|
||||
{t.analytics.edits}
|
||||
</th>
|
||||
<th className="text-right py-2 px-4 font-medium">
|
||||
{t.analytics.total}
|
||||
</th>
|
||||
<th className="text-right py-2 pl-4 font-medium">
|
||||
{t.analytics.lastUsed}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{skills.map((skill) => (
|
||||
<tr key={skill.skill} className="border-b border-border/50 hover:bg-secondary/20 transition-colors">
|
||||
<tr
|
||||
key={skill.skill}
|
||||
className="border-b border-border/50 hover:bg-secondary/20 transition-colors"
|
||||
>
|
||||
<td className="py-2 pr-4">
|
||||
<span className="font-mono-ui text-xs">{skill.skill}</span>
|
||||
</td>
|
||||
<td className="text-right py-2 px-4 text-muted-foreground">{skill.view_count}</td>
|
||||
<td className="text-right py-2 px-4 text-muted-foreground">{skill.manage_count}</td>
|
||||
<td className="text-right py-2 px-4 text-muted-foreground">
|
||||
{skill.view_count}
|
||||
</td>
|
||||
<td className="text-right py-2 px-4 text-muted-foreground">
|
||||
{skill.manage_count}
|
||||
</td>
|
||||
<td className="text-right py-2 px-4">{skill.total_count}</td>
|
||||
<td className="text-right py-2 pl-4 text-muted-foreground">
|
||||
{skill.last_used_at ? timeAgo(skill.last_used_at) : "—"}
|
||||
|
|
@ -338,7 +414,6 @@ export default function AnalyticsPage() {
|
|||
|
||||
{data && (
|
||||
<>
|
||||
{/* Summary stats + bar chart side-by-side on lg+ */}
|
||||
<div className="grid gap-6 lg:grid-cols-2">
|
||||
<Card>
|
||||
<CardContent className="py-6">
|
||||
|
|
@ -377,24 +452,28 @@ export default function AnalyticsPage() {
|
|||
<TokenBarChart daily={data.daily} />
|
||||
</div>
|
||||
|
||||
{/* Tables */}
|
||||
<DailyTable daily={data.daily} />
|
||||
<ModelTable models={data.by_model} />
|
||||
<SkillTable skills={data.skills.top_skills} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{data && data.daily.length === 0 && data.by_model.length === 0 && data.skills.top_skills.length === 0 && (
|
||||
<Card>
|
||||
<CardContent className="py-12">
|
||||
<div className="flex flex-col items-center text-muted-foreground">
|
||||
<BarChart3 className="h-8 w-8 mb-3 opacity-40" />
|
||||
<p className="text-sm font-medium">{t.analytics.noUsageData}</p>
|
||||
<p className="text-xs mt-1 text-muted-foreground/60">{t.analytics.startSession}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
{data &&
|
||||
data.daily.length === 0 &&
|
||||
data.by_model.length === 0 &&
|
||||
data.skills.top_skills.length === 0 && (
|
||||
<Card>
|
||||
<CardContent className="py-12">
|
||||
<div className="flex flex-col items-center text-muted-foreground">
|
||||
<BarChart3 className="h-8 w-8 mb-3 opacity-40" />
|
||||
<p className="text-sm font-medium">{t.analytics.noUsageData}</p>
|
||||
<p className="text-xs mt-1 text-muted-foreground/60">
|
||||
{t.analytics.startSession}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
<PluginSlot name="analytics:bottom" />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue