mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-29 01:31:41 +00:00
feat: add skill analytics to the dashboard
Expose skill usage in analytics so the dashboard and insights output can
show which skills the agent loads and manages over time.
This adds skill aggregation to the InsightsEngine by extracting
`skill_view` and `skill_manage` calls from assistant tool_calls,
computing per-skill totals, and including the results in both terminal
and gateway insights formatting. It also extends the dashboard analytics
API and Analytics page to render a Top Skills table.
Terminology is aligned with the skills docs:
- Agent Loaded = `skill_view` events
- Agent Managed = `skill_manage` actions
Architecture:
- agent/insights.py collects and aggregates per-skill usage
- hermes_cli/web_server.py exposes `skills` on `/api/analytics/usage`
- web/src/lib/api.ts adds analytics skill response types
- web/src/pages/AnalyticsPage.tsx renders the Top Skills table
- web/src/i18n/{en,zh}.ts updates user-facing labels
Tests:
- tests/agent/test_insights.py covers skill aggregation and formatting
- tests/hermes_cli/test_web_server.py covers analytics API contract
including the `skills` payload
- verified with `cd web && npm run build`
Files changed:
- agent/insights.py
- hermes_cli/web_server.py
- tests/agent/test_insights.py
- tests/hermes_cli/test_web_server.py
- web/src/i18n/en.ts
- web/src/i18n/types.ts
- web/src/i18n/zh.ts
- web/src/lib/api.ts
- web/src/pages/AnalyticsPage.tsx
This commit is contained in:
parent
1525624904
commit
857b543543
9 changed files with 399 additions and 6 deletions
|
|
@ -1,12 +1,14 @@
|
|||
import { useEffect, useState, useCallback } from "react";
|
||||
import {
|
||||
BarChart3,
|
||||
Brain,
|
||||
Cpu,
|
||||
Hash,
|
||||
TrendingUp,
|
||||
} from "lucide-react";
|
||||
import { api } from "@/lib/api";
|
||||
import type { AnalyticsResponse, AnalyticsDailyEntry, AnalyticsModelEntry } from "@/lib/api";
|
||||
import type { AnalyticsResponse, AnalyticsDailyEntry, AnalyticsModelEntry, AnalyticsSkillEntry } from "@/lib/api";
|
||||
import { timeAgo } from "@/lib/utils";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useI18n } from "@/i18n";
|
||||
|
|
@ -227,6 +229,52 @@ function ModelTable({ models }: { models: AnalyticsModelEntry[] }) {
|
|||
);
|
||||
}
|
||||
|
||||
function SkillTable({ skills }: { skills: AnalyticsSkillEntry[] }) {
|
||||
const { t } = useI18n();
|
||||
if (skills.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Brain className="h-5 w-5 text-muted-foreground" />
|
||||
<CardTitle className="text-base">{t.analytics.topSkills}</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-x-auto">
|
||||
<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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{skills.map((skill) => (
|
||||
<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">{skill.total_count}</td>
|
||||
<td className="text-right py-2 pl-4 text-muted-foreground">
|
||||
{skill.last_used_at ? timeAgo(skill.last_used_at) : "—"}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AnalyticsPage() {
|
||||
const [days, setDays] = useState(30);
|
||||
const [data, setData] = useState<AnalyticsResponse | null>(null);
|
||||
|
|
@ -310,10 +358,11 @@ export default function AnalyticsPage() {
|
|||
{/* 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 && 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">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue