mirror of
https://github.com/Routstr/routstrd.git
synced 2026-10-06 12:48:22 +00:00
Add wallet balance display to TUI overview tab
- Fetch balance from /keys/balance endpoint showing wallet, cached tokens, and API key balances - Display total balance and individual balances per source with provider URLs - Show 'No funds available' message when balance is zero
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { TABS } from "./constants.ts";
|
||||
import { fetchUsage } from "./data.ts";
|
||||
import { fetchBalance, fetchUsage, type BalanceInfo } from "./data.ts";
|
||||
import {
|
||||
applyScrollToContent,
|
||||
exitSearchMode,
|
||||
@@ -44,6 +44,7 @@ export async function runUsageTui(): Promise<void> {
|
||||
|
||||
let currentTab: TabId = "overview";
|
||||
let stats: UsageStats | null = null;
|
||||
let balance: BalanceInfo | null = null;
|
||||
let refreshInterval: ReturnType<typeof setInterval> | null = null;
|
||||
let shouldUpdate = true;
|
||||
let autoRefresh = true;
|
||||
@@ -86,6 +87,7 @@ export async function runUsageTui(): Promise<void> {
|
||||
|
||||
if (forceFetch || shouldUpdate) {
|
||||
stats = await fetchUsage(1000);
|
||||
balance = await fetchBalance();
|
||||
shouldUpdate = false;
|
||||
}
|
||||
|
||||
@@ -100,7 +102,7 @@ export async function runUsageTui(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = renderTabContent(currentTab, stats, width);
|
||||
const content = renderTabContent(currentTab, stats, balance, width);
|
||||
const footer = `${COLORS.dim}Press [Q] to quit, [R] to refresh, [A] to toggle auto-refresh${autoRefresh ? " (on)" : " (off)"} scroll:${vimState.scrollPos}${COLORS.reset}${vimState.mode === "normal" ? ` ${COLORS.yellow}vim: hjkl/arrows, / search, g top, gg bottom${COLORS.reset}` : ""}`;
|
||||
const chrome = renderHeader(currentTab, width) + renderTabs(currentTab) + renderSeparator(width) + renderSearchBar();
|
||||
const chromeLines = chrome.split("\n").length - 1;
|
||||
|
||||
@@ -2,6 +2,45 @@ import type { UsageTrackingEntry } from "../../daemon/types.ts";
|
||||
import { callDaemon, isDaemonRunning } from "../../cli-shared.ts";
|
||||
import type { ClientStats, DayStats, ModelStats, ProviderStats, UsageStats } from "./types.ts";
|
||||
|
||||
export interface BalanceKey {
|
||||
id: string;
|
||||
name: string;
|
||||
balance: number;
|
||||
}
|
||||
|
||||
export interface BalanceInfo {
|
||||
keys: BalanceKey[];
|
||||
total: number;
|
||||
unit: "sat";
|
||||
apikeysCalled: number;
|
||||
}
|
||||
|
||||
export async function fetchBalance(): Promise<BalanceInfo | null> {
|
||||
try {
|
||||
const running = await isDaemonRunning();
|
||||
if (!running) return null;
|
||||
|
||||
const result = await callDaemon("/keys/balance");
|
||||
if (result.error) return null;
|
||||
|
||||
const output = result.output as {
|
||||
keys?: BalanceKey[];
|
||||
total?: number;
|
||||
unit?: string;
|
||||
apikeysCalled?: number;
|
||||
};
|
||||
|
||||
return {
|
||||
keys: output?.keys || [],
|
||||
total: output?.total || 0,
|
||||
unit: (output?.unit as "sat") || "sat",
|
||||
apikeysCalled: output?.apikeysCalled || 0,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchUsage(limit = 1000): Promise<UsageStats | null> {
|
||||
try {
|
||||
const running = await isDaemonRunning();
|
||||
|
||||
+41
-3
@@ -13,6 +13,7 @@ import {
|
||||
} from "./data.ts";
|
||||
import { vimState } from "./state.ts";
|
||||
import { stripAnsi } from "./terminal.ts";
|
||||
import type { BalanceInfo } from "./data.ts";
|
||||
import type { TabId, UsageStats } from "./types.ts";
|
||||
|
||||
/** Format a cost value: 0.12, 1.23, 12.34, 123.45, 1.23k, 1.23m */
|
||||
@@ -106,7 +107,7 @@ export function renderBarChart(
|
||||
}
|
||||
|
||||
|
||||
export function renderOverview(stats: UsageStats, width: number): string {
|
||||
export function renderOverview(stats: UsageStats, balance: BalanceInfo | null, width: number): string {
|
||||
const totals = getTotals(stats.entries);
|
||||
const entryCount = stats.entries.length;
|
||||
const totalVisibleCost = totals.satsCost;
|
||||
@@ -129,6 +130,43 @@ export function renderOverview(stats: UsageStats, width: number): string {
|
||||
let output = renderBox(leftBox, width, "Summary Stats");
|
||||
output += "\n" + renderBox(rightBox, width, "Token Stats");
|
||||
|
||||
// Display all balances (wallet, cached tokens, API keys) if available
|
||||
if (balance && balance.keys.length > 0) {
|
||||
const balanceLines: string[] = [];
|
||||
const totalBalance = balance.total;
|
||||
|
||||
if (totalBalance > 0) {
|
||||
balanceLines.push(`${COLORS.bold}Total Balance:${COLORS.reset} ${COLORS.green}${totalBalance.toLocaleString()} sat${COLORS.reset}`);
|
||||
} else {
|
||||
balanceLines.push(`${COLORS.bold}Total Balance:${COLORS.reset} ${COLORS.red}0 sat${COLORS.reset}`);
|
||||
}
|
||||
|
||||
for (const key of balance.keys) {
|
||||
const color = key.id === "wallet" ? COLORS.green : COLORS.cyan;
|
||||
if (key.id === "wallet") {
|
||||
balanceLines.push(`${color}Wallet:${COLORS.reset} ${key.balance.toLocaleString()} sat`);
|
||||
} else {
|
||||
// Extract provider URL from name (e.g., "API Key: https://..." or "Cached: https://...")
|
||||
const providerUrl = key.name.replace(/^(API Key|Cached):\s*/, "");
|
||||
const shortProvider = providerUrl.replace("https://", "").replace("http://", "");
|
||||
const label = key.id.startsWith("cached:") ? "Cached" : "API Key";
|
||||
balanceLines.push(`${color}${label}:${COLORS.reset} ${shortProvider} (${key.balance.toLocaleString()} sat)`);
|
||||
}
|
||||
}
|
||||
|
||||
if (totalBalance === 0) {
|
||||
balanceLines.push(`${COLORS.dim}No funds available${COLORS.reset}`);
|
||||
}
|
||||
|
||||
output = renderBox(balanceLines, width, "Balance") + "\n" + output;
|
||||
} else if (balance && balance.keys.length === 0) {
|
||||
const balanceLines: string[] = [
|
||||
`${COLORS.bold}Total Balance:${COLORS.reset} ${COLORS.red}0 sat${COLORS.reset}`,
|
||||
`${COLORS.dim}No funds available${COLORS.reset}`,
|
||||
];
|
||||
output = renderBox(balanceLines, width, "Balance") + "\n" + output;
|
||||
}
|
||||
|
||||
const modelStats = getModelStats(stats.entries);
|
||||
if (modelStats.length > 0) {
|
||||
const maxCost = modelStats[0]!.satsCost;
|
||||
@@ -404,9 +442,9 @@ export function renderRecent(stats: UsageStats, width: number): string {
|
||||
return renderBox(lines, width, `Recent Requests (${stats.entries.length} shown)`);
|
||||
}
|
||||
|
||||
export function renderTabContent(activeTab: TabId, stats: UsageStats, width: number): string {
|
||||
export function renderTabContent(activeTab: TabId, stats: UsageStats, balance: BalanceInfo | null, width: number): string {
|
||||
switch (activeTab) {
|
||||
case "overview": return renderOverview(stats, width);
|
||||
case "overview": return renderOverview(stats, balance, width);
|
||||
case "today": return renderToday(stats, width);
|
||||
case "models": return renderModels(stats, width);
|
||||
case "providers": return renderProviders(stats, width);
|
||||
|
||||
Reference in New Issue
Block a user