Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions apps/client/src/components/SystemPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
clip,
formatDuration,
formatPercent,
formatTokenCount,
prettyTs,
queueValue,
relativeMs,
Expand Down Expand Up @@ -66,6 +67,14 @@ function serializeUnknown(value: unknown, maxChars = 320): string {
}
}

function serviceLabel(service: string): string {
const normalized = service.trim().toLowerCase();
if (normalized === "localbuddy") return "LocalBuddy";
if (normalized === "remotebuddy") return "RemoteBuddy";
if (normalized === "workerpals") return "WorkerPals";
return service || "Unknown";
}

function initialDraftForQuestion(question: AutonomyQuestionRow): string {
const schema = question.expectedAnswerSchema ?? {};
if (question.questionType === "single_choice") {
Expand Down Expand Up @@ -197,6 +206,7 @@ export function SystemPane({
const recentEvents = useMemo(() => events.slice(-40).reverse(), [events]);
const requestSlo = systemSummary.slo?.requests;
const jobSlo = systemSummary.slo?.jobs;
const llmUsage = systemSummary.llmUsage;
const autonomyOps = systemSummary.autonomy ?? autonomyInsights.opsSummary;
const safety = autonomyOps?.safetyState ?? null;
const evaluator = autonomyOps?.latestEvaluatorScorecard ?? autonomyInsights.latestEvaluatorScorecard;
Expand All @@ -220,6 +230,28 @@ export function SystemPane({
.slice(0, 20),
[autonomyQuestions],
);
const llmUsageRows = useMemo(() => {
const defaults = ["localbuddy", "remotebuddy", "workerpals"];
const byService = new Map((llmUsage?.services ?? []).map((row) => [row.service.toLowerCase(), row]));
for (const service of defaults) {
if (!byService.has(service)) {
byService.set(service, {
service,
promptTokens: 0,
completionTokens: 0,
totalTokens: 0,
callCount: 0,
avgTokensPerCall: null,
estimatedCallCount: 0,
lastCallAt: null,
});
}
}
return [...byService.values()].sort((a, b) => {
if (b.totalTokens !== a.totalTokens) return b.totalTokens - a.totalTokens;
return serviceLabel(a.service).localeCompare(serviceLabel(b.service));
});
}, [llmUsage]);

useEffect(() => {
setQuestionDrafts((prev) => {
Expand Down Expand Up @@ -347,6 +379,13 @@ export function SystemPane({
tone="warning"
theme={theme}
/>
<MetricTile
title={`LLM Tokens (${llmUsage?.windowHours ?? 24}h)`}
value={formatTokenCount(llmUsage?.totalTokens)}
detail={`${llmUsage?.callCount ?? 0} calls | avg ${formatTokenCount(llmUsage?.avgTokensPerCall)}`}
tone={llmUsage && llmUsage.callCount > 0 ? "accent" : "warning"}
theme={theme}
/>
<MetricTile
title="Trusted Sources"
value={String(autonomyInsights.trustedInspirationShortlist.length)}
Expand Down Expand Up @@ -381,6 +420,83 @@ export function SystemPane({
/>
</View>

<View
style={[styles.insightPanel, { borderColor: theme.border, backgroundColor: theme.panel }]}
>
<View style={styles.rowBetween}>
<Text style={[styles.sectionTitle, { color: theme.text, fontFamily: theme.fontSans }]}>
LLM Usage
</Text>
<Text style={[styles.systemMeta, { color: theme.textMuted, fontFamily: theme.fontSans }]}>
{llmUsage?.windowHours ?? 24}h window | {llmUsage?.callCount ?? 0} calls
</Text>
</View>
<Text style={[styles.systemDetail, { color: theme.textMuted, fontFamily: theme.fontSans }]}>
total {formatTokenCount(llmUsage?.totalTokens)} tokens | prompt{" "}
{formatTokenCount(llmUsage?.promptTokens)} | completion{" "}
{formatTokenCount(llmUsage?.completionTokens)} | avg{" "}
{formatTokenCount(llmUsage?.avgTokensPerCall)} per call
</Text>
{llmUsage && llmUsage.estimatedCallCount > 0 ? (
<Text style={[styles.systemMeta, { color: theme.textMuted, fontFamily: theme.fontSans }]}>
{llmUsage.estimatedCallCount} call{llmUsage.estimatedCallCount === 1 ? "" : "s"} using estimated token counts.
</Text>
) : null}
<View style={styles.systemGrid}>
{llmUsageRows.map((row) => (
<View
key={`llm-${row.service}`}
style={[
styles.systemCard,
{ borderColor: theme.border, backgroundColor: theme.panelAlt },
]}
>
<View style={styles.rowBetween}>
<Text style={[styles.systemTitle, { color: theme.text, fontFamily: theme.fontSans }]}>
{serviceLabel(row.service)}
</Text>
<Text
style={[
styles.systemMeta,
{
color: row.callCount > 0 ? theme.accent : theme.textMuted,
fontFamily: theme.fontMono,
},
]}
>
{row.callCount} call{row.callCount === 1 ? "" : "s"}
</Text>
</View>
<Text
style={[
styles.tokenValue,
{
color: row.totalTokens > 0 ? theme.accent : theme.textMuted,
fontFamily: theme.fontSans,
},
]}
>
{formatTokenCount(row.totalTokens)}
</Text>
<Text style={[styles.systemDetail, { color: theme.textMuted, fontFamily: theme.fontSans }]}>
avg {formatTokenCount(row.avgTokensPerCall)} / call
</Text>
<Text style={[styles.systemMeta, { color: theme.textMuted, fontFamily: theme.fontMono }]}>
in {formatTokenCount(row.promptTokens)} | out {formatTokenCount(row.completionTokens)}
</Text>
<Text style={[styles.systemMeta, { color: theme.textMuted, fontFamily: theme.fontSans }]}>
{row.lastCallAt ? `last call ${relativeMs(row.lastCallAt)}` : "no usage recorded"}
</Text>
{row.estimatedCallCount > 0 ? (
<Text style={[styles.systemMeta, { color: theme.textMuted, fontFamily: theme.fontSans }]}>
estimated {row.estimatedCallCount} call{row.estimatedCallCount === 1 ? "" : "s"}
</Text>
) : null}
</View>
))}
</View>
</View>

<View
style={[styles.safetyPanel, { borderColor: theme.border, backgroundColor: theme.panel }]}
>
Expand Down Expand Up @@ -1059,6 +1175,7 @@ const styles = StyleSheet.create({
marginBottom: 8,
},
systemTitle: { fontSize: 14, fontWeight: "700" },
tokenValue: { fontSize: 24, fontWeight: "700", marginTop: 6 },
systemDetail: { fontSize: 12, marginTop: 7 },
systemMeta: { fontSize: 11, marginTop: 5 },
workerPanel: {
Expand Down
14 changes: 14 additions & 0 deletions apps/client/src/components/dashboardFormatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@ export function formatDuration(valueMs: number | null | undefined): string {
return `${Math.round(valueMs / 1000)}s`;
}

function trimTrailingZeroes(value: string): string {
return value.replace(/\.0+$/, "").replace(/(\.\d*[1-9])0+$/, "$1");
}

export function formatTokenCount(value: number | null | undefined): string {
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return "--";
if (value < 1_000) return Math.round(value).toLocaleString();
if (value < 1_000_000) return `${trimTrailingZeroes((value / 1_000).toFixed(value < 10_000 ? 1 : 0))}k`;
if (value < 1_000_000_000) {
return `${trimTrailingZeroes((value / 1_000_000).toFixed(value < 10_000_000 ? 1 : 0))}M`;
}
return value.toExponential(2).replace("+", "");
}

export function formatEtaMs(valueMs: number | null | undefined): string {
if (typeof valueMs !== "number" || !Number.isFinite(valueMs) || valueMs <= 0) return "now";
if (valueMs < 1_000) return `${Math.round(valueMs)}ms`;
Expand Down
24 changes: 24 additions & 0 deletions apps/client/src/lib/pushpalsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,28 @@ export interface SystemRepoSummary {
refreshedAt?: string;
}

export interface LlmUsageServiceSummary {
service: string;
promptTokens: number;
completionTokens: number;
totalTokens: number;
callCount: number;
avgTokensPerCall: number | null;
estimatedCallCount: number;
lastCallAt: string | null;
}

export interface LlmUsageSummary {
windowHours: number;
promptTokens: number;
completionTokens: number;
totalTokens: number;
callCount: number;
avgTokensPerCall: number | null;
estimatedCallCount: number;
services: LlmUsageServiceSummary[];
}

export interface SystemStatusSummary {
workers?: { total: number; online: number; busy: number; idle: number };
queues?: {
Expand All @@ -471,6 +493,7 @@ export interface SystemStatusSummary {
requests?: RequestSloSummary;
jobs?: JobSloSummary;
};
llmUsage?: LlmUsageSummary;
repo?: SystemRepoSummary;
autonomy?: AutonomyOpsSummary;
ts?: string;
Expand Down Expand Up @@ -746,6 +769,7 @@ export async function fetchSystemStatus(
workers: payload.workers,
queues: payload.queues,
slo: payload.slo,
llmUsage: payload.llmUsage,
autonomy: payload.autonomy,
repo: payload.repo,
ts: payload.ts,
Expand Down
2 changes: 2 additions & 0 deletions apps/localbuddy/src/localbuddy_main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,8 @@ class LocalBuddyServer {
endpoint: llmCfg.endpoint,
model: llmCfg.model,
apiKey: llmCfg.apiKey,
serverUrl: this.server,
authToken: this.authToken,
});
console.log(`[LocalBuddy] LLM client initialized`);
}
Expand Down
Loading