feat(#537): analytics screen — usage + per-model stats (#558)

* feat(#537): analytics screen — usage + per-model stats from /api/analytics/*

* fix(#537): address PR review — cancel-in-flight loads, retain cache on refresh, drop redundant async

* fix(#537): surface real analytics failure, mirror backend shape (period_days/tools), round-trip test

* fix(#537): tolerate float last_used_at in analytics (backend emits epoch as number)

* fix(#537): type tools[] as AnalyticsToolUsage (tool/count/percentage), render Top tools section

* fix(analytics): fix crashes and refactor tab layout

* perf(analytics): cache responses and prefetch day windows

---------

Co-authored-by: M57 <m57@hermes-control.app>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-07-12 00:04:56 +03:00
1 parent afa82ceec2
commit 9156064d2b
9 files changed
+1031 -1

No files matched your search

@@ -6,6 +6,7 @@ import com.m57.hermescontrol.data.model.ActionStatusResponse
import com.m57.hermescontrol.data.model.ActiveProfileResponse
import com.m57.hermescontrol.data.model.AddMcpServerRequest
import com.m57.hermescontrol.data.model.AgentPluginInstallBody
import com.m57.hermescontrol.data.model.AnalyticsResponse
import com.m57.hermescontrol.data.model.AuxiliaryModelsResponse
import com.m57.hermescontrol.data.model.BulkDeleteRequest
import com.m57.hermescontrol.data.model.CheckpointsResponse
@@ -45,6 +46,7 @@ import com.m57.hermescontrol.data.model.MoaConfigResponse
import com.m57.hermescontrol.data.model.ModelAssignmentRequest
import com.m57.hermescontrol.data.model.ModelAssignmentResponse
import com.m57.hermescontrol.data.model.ModelOptionsResponse
import com.m57.hermescontrol.data.model.ModelsAnalyticsResponse
import com.m57.hermescontrol.data.model.PairingApproveRequest
import com.m57.hermescontrol.data.model.PairingResponse
import com.m57.hermescontrol.data.model.PairingRevokeRequest
@@ -169,6 +171,18 @@ interface HermesApiService {
@GET("api/system/stats")
suspend fun getSystemStats(): Response<SystemStatsResponse>
@GET("api/analytics/usage")
suspend fun getAnalytics(
@Query("days") days: Int,
@Query("profile") profile: String? = null,
): Response<AnalyticsResponse>
@GET("api/analytics/models")
suspend fun getModelsAnalytics(
@Query("days") days: Int,
@Query("profile") profile: String? = null,
): Response<ModelsAnalyticsResponse>
@GET("api/skills")
suspend fun getSkills(): Response<List<Skill>>