@hhoangphuoc/escape-room-cli
Version:
A CLI for playing AI-generated escape room games. Install globally with: npm install -g @hhoangphuoc/escape-room-cli
110 lines (109 loc) âĸ 12.8 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { Box, Text } from 'ink';
const CostDashboard = ({ usageData }) => {
// Handle case where usageData is undefined or null
if (!usageData) {
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "red", children: "\u274C DASHBOARD ERROR" }) }), _jsx(Text, { color: "gray", children: "Unable to load usage data. Please try again." })] }));
}
const formatCost = (cost) => {
if (cost === undefined || cost === null || isNaN(cost))
return '$0.00';
if (cost === 0)
return '$0.00';
return cost < 0.01 ? `$${cost.toFixed(5)}` : `$${cost.toFixed(3)}`;
};
const formatTokens = (tokens) => {
if (tokens === undefined || tokens === null || isNaN(tokens))
return '0';
return tokens.toLocaleString();
};
const formatDuration = (startTime) => {
if (!startTime)
return 'Unknown';
const start = new Date(startTime);
const now = new Date();
const durationMs = now.getTime() - start.getTime();
const minutes = Math.floor(durationMs / 60000);
const hours = Math.floor(minutes / 60);
const remainingMinutes = minutes % 60;
if (hours > 0) {
return `${hours}h ${remainingMinutes}m`;
}
return `${minutes}m`;
};
const calculateCostRate = (cost, startTime) => {
if (!startTime || !cost || cost === 0 || isNaN(cost))
return 'N/A';
const start = new Date(startTime);
const now = new Date();
const minutes = (now.getTime() - start.getTime()) / 60000;
if (minutes > 0) {
const hourlyRate = (cost / minutes) * 60;
return formatCost(hourlyRate) + '/hour';
}
return 'N/A';
};
const getCostColor = (cost) => {
if (!cost || cost === 0 || isNaN(cost))
return 'gray';
if (cost < 0.01)
return 'green';
if (cost < 0.10)
return 'yellow';
return 'red';
};
const getAlertColor = (type) => {
switch (type) {
case 'warning': return 'yellow';
case 'limit': return 'red';
default: return 'blue';
}
};
const getAlertEmoji = (type) => {
switch (type) {
case 'warning': return 'â ī¸';
case 'limit': return 'đĢ';
default: return 'âšī¸';
}
};
const getBudgetProgress = (current, limit) => {
if (!current || !limit || current === 0 || limit === 0 || isNaN(current) || isNaN(limit)) {
return { percentage: 0, color: 'gray' };
}
const percentage = (current / limit) * 100;
let color = 'green';
if (percentage >= 90)
color = 'red';
else if (percentage >= 75)
color = 'yellow';
return { percentage, color };
};
const renderProgressBar = (percentage,
// color: string,
width = 20) => {
const filled = Math.round((percentage / 100) * width);
const empty = width - filled;
// const filledColor = color === 'red' ? 'red' : 'green';
// const emptyColor = color === 'red' ? 'gray' : 'green';
return 'â'.repeat(filled) + 'â'.repeat(empty);
};
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "\uD83D\uDCCA DETAILED USAGE DASHBOARD" }) }), usageData.alerts && usageData.alerts.length > 0 && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "red", children: "\uD83D\uDEA8 ACTIVE ALERTS" }) }), usageData.alerts.map((alert) => (_jsx(Box, { marginBottom: 1, paddingLeft: 2, children: _jsxs(Text, { color: getAlertColor(alert.type), children: [getAlertEmoji(alert.type), " ", alert.message] }) }, alert.id))), _jsx(Box, { marginBottom: 2, children: _jsx(Text, { color: "gray", children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }) })] })), usageData.budgetSettings && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "magenta", children: "\uD83D\uDCB3 BUDGET STATUS" }) }), usageData.budgetSettings.dailyLimit && (_jsxs(Box, { marginBottom: 1, paddingLeft: 2, children: [_jsx(Box, { width: 15, children: _jsx(Text, { color: "white", children: "Daily Limit:" }) }), _jsx(Box, { width: 12, children: _jsx(Text, { color: "cyan", children: formatCost(usageData.budgetSettings.dailyLimit) }) }), _jsx(Box, { width: 25, children: (() => {
const progress = getBudgetProgress(usageData.currentSessionCost, usageData.budgetSettings.dailyLimit);
return (_jsxs(Text, { color: progress.color, children: [renderProgressBar(progress.percentage), " ", progress.percentage.toFixed(1), "%"] }));
})() })] })), usageData.budgetSettings.monthlyLimit && (_jsxs(Box, { marginBottom: 1, paddingLeft: 2, children: [_jsx(Box, { width: 15, children: _jsx(Text, { color: "white", children: "Monthly Limit:" }) }), _jsx(Box, { width: 12, children: _jsx(Text, { color: "cyan", children: formatCost(usageData.budgetSettings.monthlyLimit) }) }), _jsx(Box, { width: 25, children: (() => {
const progress = getBudgetProgress(usageData.userTotalCost, usageData.budgetSettings.monthlyLimit);
return (_jsxs(Text, { color: progress.color, children: [renderProgressBar(progress.percentage), " ", progress.percentage.toFixed(1), "%"] }));
})() })] })), _jsx(Box, { marginBottom: 2, children: _jsx(Text, { color: "gray", children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }) })] })), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "green", children: "\uD83D\uDCCA SESSION OVERVIEW" }) }), _jsxs(Box, { marginBottom: 1, paddingLeft: 2, children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "green", children: "Session Cost:" }) }), _jsx(Box, { width: 15, children: _jsx(Text, { bold: true, color: getCostColor(usageData.currentSessionCost), children: formatCost(usageData.currentSessionCost) }) }), _jsx(Box, { children: _jsxs(Text, { color: "gray", children: ["(", formatTokens(usageData.currentSessionTokens), " tokens)"] }) })] }), usageData.sessionStartTime && (_jsxs(Box, { marginBottom: 1, paddingLeft: 2, children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "cyan", children: "Session Duration:" }) }), _jsx(Box, { width: 15, children: _jsx(Text, { color: "white", children: formatDuration(usageData.sessionStartTime) }) }), _jsx(Box, { children: _jsxs(Text, { color: "gray", children: ["(Rate: ", calculateCostRate(usageData.currentSessionCost, usageData.sessionStartTime), ")"] }) })] })), (() => {
const totalRequests = Object.values(usageData.modelUsageBreakdown || {}).reduce((sum, model) => sum + model.requests, 0);
if (totalRequests > 0 && usageData.currentSessionCost) {
return (_jsxs(Box, { marginBottom: 1, paddingLeft: 2, children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "magenta", children: "Total Requests:" }) }), _jsx(Box, { width: 15, children: _jsx(Text, { color: "white", children: totalRequests }) }), _jsx(Box, { children: _jsxs(Text, { color: "gray", children: ["(", formatCost(usageData.currentSessionCost / totalRequests), "/req avg)"] }) })] }));
}
return null;
})(), _jsx(Box, { marginBottom: 2, children: _jsx(Text, { color: "gray", children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "green", children: "\uD83D\uDCB0 COST SUMMARY" }) }), _jsxs(Box, { marginBottom: 1, paddingLeft: 2, children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "blue", children: "Total Lifetime:" }) }), _jsx(Box, { width: 15, children: _jsx(Text, { bold: true, color: getCostColor(usageData.userTotalCost), children: formatCost(usageData.userTotalCost) }) }), _jsx(Box, { children: _jsxs(Text, { color: "gray", children: ["(", formatTokens(usageData.userTotalTokens), " tokens)"] }) })] }), _jsxs(Box, { marginBottom: 1, paddingLeft: 2, children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "yellow", children: "Last Request:" }) }), _jsx(Box, { width: 15, children: _jsx(Text, { color: getCostColor(usageData.lastRequestCost), children: formatCost(usageData.lastRequestCost) }) }), _jsx(Box, { children: _jsxs(Text, { color: "gray", children: ["(", formatTokens(usageData.lastRequestTokens), " tokens)"] }) })] }), _jsxs(Box, { marginBottom: 2, paddingLeft: 2, children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "magenta", children: "Average/Request:" }) }), _jsx(Box, { width: 15, children: _jsx(Text, { color: getCostColor(usageData.averageCostPerRequest), children: formatCost(usageData.averageCostPerRequest) }) })] }), usageData.modelUsageBreakdown && Object.keys(usageData.modelUsageBreakdown).length > 0 && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "blue", children: "\uD83E\uDD16 MODEL ANALYTICS" }) }), _jsxs(Box, { marginBottom: 1, paddingLeft: 2, children: [_jsx(Box, { width: 12, children: _jsx(Text, { bold: true, color: "cyan", children: "Model" }) }), _jsx(Box, { width: 10, children: _jsx(Text, { bold: true, color: "cyan", children: "Requests" }) }), _jsx(Box, { width: 12, children: _jsx(Text, { bold: true, color: "cyan", children: "Total Cost" }) }), _jsx(Box, { width: 15, children: _jsx(Text, { bold: true, color: "cyan", children: "Tokens" }) }), _jsx(Box, { children: _jsx(Text, { bold: true, color: "cyan", children: "Avg/Req" }) })] }), _jsx(Box, { marginBottom: 1, paddingLeft: 2, children: _jsx(Text, { color: "gray", children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }) }), Object.entries(usageData.modelUsageBreakdown)
.sort(([, a], [, b]) => b.cost - a.cost)
.map(([model, data]) => {
const avgCost = data.requests > 0 ? data.cost / data.requests : 0;
return (_jsxs(Box, { marginBottom: 0, paddingLeft: 2, children: [_jsx(Box, { width: 12, children: _jsx(Text, { color: "white", children: model.length > 10 ? model.substring(0, 10) + '...' : model }) }), _jsx(Box, { width: 10, children: _jsx(Text, { color: "gray", children: data.requests.toString().padStart(7, ' ') }) }), _jsx(Box, { width: 12, children: _jsx(Text, { color: getCostColor(data.cost), children: formatCost(data.cost).padStart(10, ' ') }) }), _jsx(Box, { width: 15, children: _jsx(Text, { color: "gray", children: formatTokens(data.tokens).padStart(12, ' ') }) }), _jsx(Box, { children: _jsx(Text, { color: getCostColor(avgCost), children: formatCost(avgCost) }) })] }, model));
})] })), _jsxs(Box, { marginTop: 2, flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "yellow", children: "\uD83D\uDCA1 COST OPTIMIZATION TIPS" }) }), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { color: "gray", children: "\u2022 Use gpt-4.1-mini for simple questions (90% cheaper than gpt-4o)" }) }), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { color: "gray", children: "\u2022 Reserve o3/o3-mini for complex reasoning tasks" }) }), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { color: "gray", children: "\u2022 Monitor session costs with \"/cost\" command regularly" }) }), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { color: "gray", children: "\u2022 Set daily/monthly budget limits to avoid surprises" }) })] })] }));
};
export default CostDashboard;