mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
Merge 6e4c8f2aa7 into c599a41b84
This commit is contained in:
commit
8df14c5a6e
3 changed files with 105 additions and 4 deletions
74
web/src/lib/api.analytics.test.ts
Normal file
74
web/src/lib/api.analytics.test.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/// <reference types="node" />
|
||||
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { normalizeAnalyticsResponse, type AnalyticsResponse } from "./api.ts";
|
||||
|
||||
test("normalizeAnalyticsResponse fills in missing skill analytics for older backends", () => {
|
||||
const raw = {
|
||||
daily: [],
|
||||
by_model: [],
|
||||
totals: {
|
||||
total_input: 0,
|
||||
total_output: 0,
|
||||
total_cache_read: 0,
|
||||
total_reasoning: 0,
|
||||
total_estimated_cost: 0,
|
||||
total_actual_cost: 0,
|
||||
total_sessions: 0,
|
||||
total_api_calls: 0,
|
||||
},
|
||||
} as AnalyticsResponse;
|
||||
|
||||
const normalized = normalizeAnalyticsResponse(raw);
|
||||
|
||||
assert.deepEqual(normalized.skills, {
|
||||
summary: {
|
||||
total_skill_loads: 0,
|
||||
total_skill_edits: 0,
|
||||
total_skill_actions: 0,
|
||||
distinct_skills_used: 0,
|
||||
},
|
||||
top_skills: [],
|
||||
});
|
||||
});
|
||||
|
||||
test("normalizeAnalyticsResponse preserves populated skill analytics", () => {
|
||||
const raw: AnalyticsResponse = {
|
||||
daily: [],
|
||||
by_model: [],
|
||||
totals: {
|
||||
total_input: 0,
|
||||
total_output: 0,
|
||||
total_cache_read: 0,
|
||||
total_reasoning: 0,
|
||||
total_estimated_cost: 0,
|
||||
total_actual_cost: 0,
|
||||
total_sessions: 0,
|
||||
total_api_calls: 0,
|
||||
},
|
||||
skills: {
|
||||
summary: {
|
||||
total_skill_loads: 2,
|
||||
total_skill_edits: 1,
|
||||
total_skill_actions: 3,
|
||||
distinct_skills_used: 2,
|
||||
},
|
||||
top_skills: [
|
||||
{
|
||||
skill: "systematic-debugging",
|
||||
view_count: 2,
|
||||
manage_count: 1,
|
||||
total_count: 3,
|
||||
percentage: 100,
|
||||
last_used_at: 1713900000,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const normalized = normalizeAnalyticsResponse(raw);
|
||||
|
||||
assert.deepEqual(normalized.skills, raw.skills);
|
||||
});
|
||||
|
|
@ -61,8 +61,8 @@ export const api = {
|
|||
if (params.component && params.component !== "all") qs.set("component", params.component);
|
||||
return fetchJSON<LogsResponse>(`/api/logs?${qs.toString()}`);
|
||||
},
|
||||
getAnalytics: (days: number) =>
|
||||
fetchJSON<AnalyticsResponse>(`/api/analytics/usage?days=${days}`),
|
||||
getAnalytics: (days: number): Promise<NormalizedAnalyticsResponse> =>
|
||||
fetchJSON<AnalyticsResponse>(`/api/analytics/usage?days=${days}`).then(normalizeAnalyticsResponse),
|
||||
getConfig: () => fetchJSON<Record<string, unknown>>("/api/config"),
|
||||
getDefaults: () => fetchJSON<Record<string, unknown>>("/api/config/defaults"),
|
||||
getSchema: () => fetchJSON<{ fields: Record<string, unknown>; category_order: string[] }>("/api/config/schema"),
|
||||
|
|
@ -364,10 +364,32 @@ export interface AnalyticsResponse {
|
|||
total_sessions: number;
|
||||
total_api_calls: number;
|
||||
};
|
||||
skills?: {
|
||||
summary?: AnalyticsSkillsSummary;
|
||||
top_skills?: AnalyticsSkillEntry[];
|
||||
};
|
||||
}
|
||||
|
||||
export type NormalizedAnalyticsResponse = Omit<AnalyticsResponse, "skills"> & {
|
||||
skills: {
|
||||
summary: AnalyticsSkillsSummary;
|
||||
top_skills: AnalyticsSkillEntry[];
|
||||
};
|
||||
};
|
||||
|
||||
export function normalizeAnalyticsResponse(raw: AnalyticsResponse): NormalizedAnalyticsResponse {
|
||||
return {
|
||||
...raw,
|
||||
skills: {
|
||||
summary: {
|
||||
total_skill_loads: raw.skills?.summary?.total_skill_loads ?? 0,
|
||||
total_skill_edits: raw.skills?.summary?.total_skill_edits ?? 0,
|
||||
total_skill_actions: raw.skills?.summary?.total_skill_actions ?? 0,
|
||||
distinct_skills_used: raw.skills?.summary?.distinct_skills_used ?? 0,
|
||||
},
|
||||
top_skills: Array.isArray(raw.skills?.top_skills) ? raw.skills.top_skills : [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export interface CronJob {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,12 @@ import {
|
|||
TrendingUp,
|
||||
} from "lucide-react";
|
||||
import { api } from "@/lib/api";
|
||||
import type { AnalyticsResponse, AnalyticsDailyEntry, AnalyticsModelEntry, AnalyticsSkillEntry } from "@/lib/api";
|
||||
import type {
|
||||
AnalyticsDailyEntry,
|
||||
AnalyticsModelEntry,
|
||||
AnalyticsSkillEntry,
|
||||
NormalizedAnalyticsResponse,
|
||||
} from "@/lib/api";
|
||||
import { timeAgo } from "@/lib/utils";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
|
@ -280,7 +285,7 @@ function SkillTable({ skills }: { skills: AnalyticsSkillEntry[] }) {
|
|||
|
||||
export default function AnalyticsPage() {
|
||||
const [days, setDays] = useState(30);
|
||||
const [data, setData] = useState<AnalyticsResponse | null>(null);
|
||||
const [data, setData] = useState<NormalizedAnalyticsResponse | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { t } = useI18n();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue