329 lines
9.3 KiB
HTML
329 lines
9.3 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Token Usage Test</title>
|
|
<style>
|
|
body {
|
|
font-family: system-ui, -apple-system, sans-serif;
|
|
max-width: 800px;
|
|
margin: 40px auto;
|
|
padding: 20px;
|
|
background: #f5f5f5;
|
|
}
|
|
.card {
|
|
background: white;
|
|
padding: 24px;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
margin-bottom: 20px;
|
|
}
|
|
h1 {
|
|
margin: 0 0 20px;
|
|
color: #333;
|
|
}
|
|
.controls {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin-bottom: 20px;
|
|
flex-wrap: wrap;
|
|
}
|
|
input[type="number"] {
|
|
padding: 8px 12px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 6px;
|
|
flex: 1;
|
|
min-width: 150px;
|
|
}
|
|
button {
|
|
padding: 8px 16px;
|
|
background: #008060;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-weight: 600;
|
|
}
|
|
button:hover {
|
|
background: #006e51;
|
|
}
|
|
button:disabled {
|
|
background: #ccc;
|
|
cursor: not-allowed;
|
|
}
|
|
.usage-meter {
|
|
margin: 20px 0;
|
|
}
|
|
.usage-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
font-size: 14px;
|
|
color: #666;
|
|
}
|
|
.usage-bar {
|
|
height: 24px;
|
|
background: #e0e0e0;
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
.usage-fill {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, #008060, #00a572);
|
|
transition: width 0.3s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: white;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
}
|
|
.status {
|
|
padding: 12px;
|
|
border-radius: 6px;
|
|
margin-top: 12px;
|
|
font-size: 14px;
|
|
}
|
|
.status.success {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
border: 1px solid #c3e6cb;
|
|
}
|
|
.status.error {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
border: 1px solid #f5c6cb;
|
|
}
|
|
.status.info {
|
|
background: #d1ecf1;
|
|
color: #0c5460;
|
|
border: 1px solid #bee5eb;
|
|
}
|
|
.stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
gap: 16px;
|
|
margin-top: 20px;
|
|
}
|
|
.stat {
|
|
background: #f8f9fa;
|
|
padding: 12px;
|
|
border-radius: 8px;
|
|
border: 1px solid #e9ecef;
|
|
}
|
|
.stat-label {
|
|
font-size: 12px;
|
|
color: #666;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
margin-bottom: 4px;
|
|
}
|
|
.stat-value {
|
|
font-size: 20px;
|
|
font-weight: 700;
|
|
color: #333;
|
|
}
|
|
pre {
|
|
background: #f8f9fa;
|
|
padding: 12px;
|
|
border-radius: 6px;
|
|
overflow-x: auto;
|
|
font-size: 12px;
|
|
margin: 12px 0 0;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="card">
|
|
<h1>Token Usage Test</h1>
|
|
<p>Test the token usage tracking and progress bar updates. This simulates token consumption without actually running AI models.</p>
|
|
|
|
<div class="controls">
|
|
<input type="number" id="tokenAmount" value="1000" min="1" step="100" placeholder="Tokens to add">
|
|
<button id="simulateBtn">Simulate Token Usage</button>
|
|
<button id="refreshBtn">Refresh Usage</button>
|
|
</div>
|
|
|
|
<div class="usage-meter">
|
|
<div class="usage-header">
|
|
<span>Monthly Token Usage</span>
|
|
<span id="usagePercent">—</span>
|
|
</div>
|
|
<div class="usage-bar">
|
|
<div class="usage-fill" id="usageFill" style="width: 0%">
|
|
<span id="usageText">0%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stats">
|
|
<div class="stat">
|
|
<div class="stat-label">Used</div>
|
|
<div class="stat-value" id="usedTokens">—</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-label">Limit</div>
|
|
<div class="stat-value" id="limitTokens">—</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-label">Remaining</div>
|
|
<div class="stat-value" id="remainingTokens">—</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-label">Plan</div>
|
|
<div class="stat-value" id="planName">—</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="statusMessage" style="display: none;"></div>
|
|
<pre id="debugOutput" style="display: none;"></pre>
|
|
</div>
|
|
|
|
<script>
|
|
const tokenAmountInput = document.getElementById('tokenAmount');
|
|
const simulateBtn = document.getElementById('simulateBtn');
|
|
const refreshBtn = document.getElementById('refreshBtn');
|
|
const usageFill = document.getElementById('usageFill');
|
|
const usageText = document.getElementById('usageText');
|
|
const usagePercent = document.getElementById('usagePercent');
|
|
const usedTokens = document.getElementById('usedTokens');
|
|
const limitTokens = document.getElementById('limitTokens');
|
|
const remainingTokens = document.getElementById('remainingTokens');
|
|
const planName = document.getElementById('planName');
|
|
const statusMessage = document.getElementById('statusMessage');
|
|
const debugOutput = document.getElementById('debugOutput');
|
|
|
|
function showStatus(message, type = 'info') {
|
|
statusMessage.textContent = message;
|
|
statusMessage.className = `status ${type}`;
|
|
statusMessage.style.display = 'block';
|
|
setTimeout(() => {
|
|
statusMessage.style.display = 'none';
|
|
}, 5000);
|
|
}
|
|
|
|
function updateUsageDisplay(summary) {
|
|
if (!summary) {
|
|
showStatus('No usage data available', 'error');
|
|
return;
|
|
}
|
|
|
|
const used = summary.used || 0;
|
|
const limit = summary.limit || 0;
|
|
const percent = summary.percent || 0;
|
|
const remaining = summary.remaining || 0;
|
|
const plan = summary.plan || 'unknown';
|
|
|
|
// Update progress bar
|
|
usageFill.style.width = `${Math.min(100, percent)}%`;
|
|
usageText.textContent = `${percent}%`;
|
|
usagePercent.textContent = `${percent}% used`;
|
|
|
|
// Update stats
|
|
usedTokens.textContent = used.toLocaleString();
|
|
limitTokens.textContent = limit.toLocaleString();
|
|
remainingTokens.textContent = remaining.toLocaleString();
|
|
planName.textContent = plan.charAt(0).toUpperCase() + plan.slice(1);
|
|
|
|
// Change color based on usage
|
|
if (percent >= 90) {
|
|
usageFill.style.background = 'linear-gradient(90deg, #dc3545, #c82333)';
|
|
} else if (percent >= 75) {
|
|
usageFill.style.background = 'linear-gradient(90deg, #ffc107, #e0a800)';
|
|
} else {
|
|
usageFill.style.background = 'linear-gradient(90deg, #008060, #00a572)';
|
|
}
|
|
|
|
debugOutput.textContent = JSON.stringify(summary, null, 2);
|
|
debugOutput.style.display = 'block';
|
|
}
|
|
|
|
async function fetchUsage() {
|
|
try {
|
|
const response = await fetch('/api/account/usage?_t=' + Date.now());
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
const data = await response.json();
|
|
console.log('[TEST] Usage data received:', data);
|
|
return data.summary;
|
|
} catch (error) {
|
|
console.error('[TEST] Failed to fetch usage:', error);
|
|
showStatus(`Failed to fetch usage: ${error.message}`, 'error');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function simulateTokenUsage() {
|
|
const tokens = parseInt(tokenAmountInput.value) || 1000;
|
|
|
|
simulateBtn.disabled = true;
|
|
simulateBtn.textContent = 'Simulating...';
|
|
|
|
try {
|
|
const response = await fetch('/api/test/simulate-tokens', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ tokens }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log('[TEST] Simulation response:', data);
|
|
|
|
showStatus(`✓ Simulated ${tokens.toLocaleString()} tokens successfully`, 'success');
|
|
|
|
// Update display with returned summary
|
|
if (data.summary) {
|
|
updateUsageDisplay(data.summary);
|
|
}
|
|
} catch (error) {
|
|
console.error('[TEST] Failed to simulate:', error);
|
|
showStatus(`Failed to simulate: ${error.message}`, 'error');
|
|
} finally {
|
|
simulateBtn.disabled = false;
|
|
simulateBtn.textContent = 'Simulate Token Usage';
|
|
}
|
|
}
|
|
|
|
async function refreshUsage() {
|
|
refreshBtn.disabled = true;
|
|
refreshBtn.textContent = 'Refreshing...';
|
|
|
|
try {
|
|
const summary = await fetchUsage();
|
|
if (summary) {
|
|
updateUsageDisplay(summary);
|
|
showStatus('Usage data refreshed', 'success');
|
|
}
|
|
} catch (error) {
|
|
console.error('[TEST] Refresh failed:', error);
|
|
} finally {
|
|
refreshBtn.disabled = false;
|
|
refreshBtn.textContent = 'Refresh Usage';
|
|
}
|
|
}
|
|
|
|
// Event listeners
|
|
simulateBtn.addEventListener('click', simulateTokenUsage);
|
|
refreshBtn.addEventListener('click', refreshUsage);
|
|
|
|
// Load initial usage on page load
|
|
window.addEventListener('DOMContentLoaded', async () => {
|
|
console.log('[TEST] Page loaded, fetching initial usage...');
|
|
const summary = await fetchUsage();
|
|
if (summary) {
|
|
updateUsageDisplay(summary);
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|