From c974699024af2c5af21582a41090a9efa4e46969 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Mon, 13 Jul 2026 06:20:30 +0800 Subject: [PATCH] feat: add TDP to quick stats + open-on-DeckyVault button --- apps/web/lib/api/plugin-public.ts | 8 +++-- .../src/components/LibraryAppPanel.tsx | 33 ++++++++++++++----- plugins/decky-vault/src/lib/plugin-api.ts | 2 +- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/apps/web/lib/api/plugin-public.ts b/apps/web/lib/api/plugin-public.ts index c2e9a41..cb601a2 100644 --- a/apps/web/lib/api/plugin-public.ts +++ b/apps/web/lib/api/plugin-public.ts @@ -39,7 +39,7 @@ export type PluginEntryRow = { export type PluginGameResponse = { game: { id: string; steamAppId: number | null; title: string; slug: string | null } | null - estFps: { avg: number; low: number | null; onePct: number | null; high: number | null; count: number } | null + estFps: { avg: number; low: number | null; onePct: number | null; high: number | null; count: number; tdpAvg: number | null } | null topEntries: ReturnType[] recentEntries: ReturnType[] error?: string @@ -85,6 +85,9 @@ export async function buildPluginGameResponse(args: { onePct: entries.reduce((m, e) => (m == null ? e.fpsOnePercentLow : Math.min(m, e.fpsOnePercentLow ?? m)), null), high: entries.reduce((m, e) => (m == null ? e.fpsHigh : Math.max(m, e.fpsHigh ?? m)), null), count: entries.length, + tdpAvg: entries.length > 0 + ? Math.round((entries.filter((e) => e.tdpWatts != null).reduce((a, b) => a + (b.tdpWatts ?? 0), 0) / Math.max(1, entries.filter((e) => e.tdpWatts != null).length)) * 10) / 10 + : null, } : null) if (!estFps) { return { game: { ...args.game }, estFps: null, topEntries: [], recentEntries: [] } @@ -215,6 +218,7 @@ export const pluginPublicRoutes = new Elysia({ onePct: sql`min(${performanceEntries.fpsOnePercentLow})`, high: sql`max(${performanceEntries.fpsHigh})`, count: sql`count(*)::int`, + tdpAvg: sql`round(avg(${performanceEntries.tdpWatts})::numeric, 1)`, }) .from(performanceEntries) .where(baseWhere) @@ -225,7 +229,7 @@ export const pluginPublicRoutes = new Elysia({ game, entries: topRows as unknown as PluginEntryRow[], recent: recentRows as unknown as PluginEntryRow[], - estFps: agg.avg ? { avg: agg.avg, low: agg.low, onePct: agg.onePct, high: agg.high, count: agg.count } : null, + estFps: agg.avg ? { avg: agg.avg, low: agg.low, onePct: agg.onePct, high: agg.high, count: agg.count, tdpAvg: agg.tdpAvg ?? null } : null, }) }, { diff --git a/plugins/decky-vault/src/components/LibraryAppPanel.tsx b/plugins/decky-vault/src/components/LibraryAppPanel.tsx index abe32a4..020efda 100644 --- a/plugins/decky-vault/src/components/LibraryAppPanel.tsx +++ b/plugins/decky-vault/src/components/LibraryAppPanel.tsx @@ -1,5 +1,6 @@ import { useEffect, useState, useRef } from "react" -import { PanelSectionRow, DropdownItem, staticClasses } from "@decky/ui" +import { PanelSectionRow, DropdownItem, ButtonItem, staticClasses } from "@decky/ui" +import { Router } from "@decky/ui" import { fetchPluginGame, fetchPluginDevices, @@ -57,7 +58,10 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: .map((d) => ({ label: `${d.name} (${d.count})`, data: d.slug })), ] - // Minimal single-row stats — no header, no entry cards + const gameUrl = data?.game?.slug + ? `${baseUrl}/games/${data.game.slug}` + : `${baseUrl}/games` + if (loading) { return ( @@ -90,14 +94,18 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: return ( <> + {/* Quick stats row */} -
+
{device && data.estFps ? ( - <> - DeckyVault est FPS: - {data.estFps.avg} - avg · {data.estFps.low ?? "—"} low · {data.estFps.high ?? "—"} high · {data.estFps.count} entries - + + {data.estFps.avg} avg FPS + {data.estFps.onePct != null && {data.estFps.onePct} 1% low} + {data.estFps.low != null && {data.estFps.low} min} + {data.estFps.high != null && {data.estFps.high} max} + {data.estFps.tdpAvg != null && {data.estFps.tdpAvg}W TDP} + {data.estFps.count} entries + ) : ( {device @@ -107,6 +115,8 @@ export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: )}
+ + {/* Device dropdown */} setDevice(opt.data as string)} /> + + {/* Open on DeckyVault button */} + + Router.NavigateToExternalWeb(gameUrl)}> + View on DeckyVault + + ) } \ No newline at end of file diff --git a/plugins/decky-vault/src/lib/plugin-api.ts b/plugins/decky-vault/src/lib/plugin-api.ts index 3ddd9ed..614c56f 100644 --- a/plugins/decky-vault/src/lib/plugin-api.ts +++ b/plugins/decky-vault/src/lib/plugin-api.ts @@ -22,7 +22,7 @@ export interface PluginEntry { export interface PluginGameResponse { game: { id: string; steamAppId: number | null; title: string; slug: string | null } | null - estFps: { avg: number; low: number | null; onePct: number | null; high: number | null; count: number } | null + estFps: { avg: number; low: number | null; onePct: number | null; high: number | null; count: number; tdpAvg: number | null } | null topEntries: PluginEntry[] recentEntries: PluginEntry[] error?: string