UNPKG

@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
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;