docs: add analytics documentation and screenshot
- Add Analytics section to Control UI docs explaining quota and usage features - Add screenshot script with dynamic token loading from config - Update screenshot showing the analytics dashboard Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
36523e7746
commit
b493adce73
Binary file not shown.
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 59 KiB |
@ -44,6 +44,34 @@ The onboarding wizard generates a gateway token by default, so paste it here on
|
||||
- Debug: status/health/models snapshots + event log + manual RPC calls (`status`, `health`, `models.list`)
|
||||
- Logs: live tail of gateway file logs with filter/export (`logs.tail`)
|
||||
- Update: run a package/git update + restart (`update.run`) with a restart report
|
||||
- Analytics: token usage stats, cost estimates, and plan quota display (`usage.cost`, `usage.status`)
|
||||
|
||||
## Analytics
|
||||
|
||||
The Analytics tab provides visibility into your token usage and costs:
|
||||
|
||||

|
||||
|
||||
### Plan Quota
|
||||
|
||||
Shows your current usage against plan limits (for providers that expose quota data):
|
||||
|
||||
- **Usage bars**: Visual progress bars for each rate limit window (5-hour, weekly, model-specific)
|
||||
- **Reset timers**: Time remaining until each limit resets
|
||||
- **Provider support**: Currently supports Anthropic API keys with quota access
|
||||
|
||||
For Claude Code OAuth tokens (which use the `user:inference` scope only), plan quota is not available programmatically. The UI displays a link to [claude.ai/settings/usage](https://claude.ai/settings/usage) where you can check your usage directly.
|
||||
|
||||
### Usage Summary
|
||||
|
||||
Aggregates token consumption from your session transcripts:
|
||||
|
||||
- **Summary cards**: Total tokens, input/output breakdown, cache read/write, estimated cost
|
||||
- **Daily charts**: Bar charts showing token usage and cost over time
|
||||
- **Time periods**: Select 7, 14, 30, or 90 day views
|
||||
- **Refresh**: Click to reload data from the gateway
|
||||
|
||||
Cost estimates are based on published model pricing and may not reflect actual billing (discounts, commitments, etc.).
|
||||
|
||||
## Chat behavior
|
||||
|
||||
|
||||
94
ui/src/ui/analytics-screenshot.e2e.mjs
Normal file
94
ui/src/ui/analytics-screenshot.e2e.mjs
Normal file
@ -0,0 +1,94 @@
|
||||
import { chromium } from 'playwright-core';
|
||||
import { readdirSync, existsSync, readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
function getTokenFromConfig() {
|
||||
const configPath = join(process.env.HOME, '.clawdbot/clawdbot.json');
|
||||
if (!existsSync(configPath)) {
|
||||
throw new Error('Config file not found at ~/.clawdbot/clawdbot.json');
|
||||
}
|
||||
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
||||
const token = config?.gateway?.auth?.token;
|
||||
if (!token) {
|
||||
throw new Error('No gateway.auth.token found in config');
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
const TOKEN = getTokenFromConfig();
|
||||
const BASE_URL = process.env.GATEWAY_URL || 'http://127.0.0.1:18789';
|
||||
|
||||
function findChromiumExecutable() {
|
||||
const cacheDir = join(process.env.HOME, '.cache/ms-playwright');
|
||||
if (!existsSync(cacheDir)) return undefined;
|
||||
|
||||
const dirs = readdirSync(cacheDir)
|
||||
.filter(d => d.startsWith('chromium-') && !d.includes('headless'))
|
||||
.sort()
|
||||
.reverse();
|
||||
|
||||
if (dirs.length === 0) return undefined;
|
||||
|
||||
const chromiumDir = join(cacheDir, dirs[0]);
|
||||
const linuxPath = join(chromiumDir, 'chrome-linux64/chrome');
|
||||
const macPath = join(chromiumDir, 'chrome-mac/Chromium.app/Contents/MacOS/Chromium');
|
||||
|
||||
if (existsSync(linuxPath)) return linuxPath;
|
||||
if (existsSync(macPath)) return macPath;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function takeScreenshot() {
|
||||
const executablePath = findChromiumExecutable();
|
||||
console.log('Using chromium:', executablePath || 'bundled');
|
||||
|
||||
const launchOptions = { headless: true };
|
||||
if (executablePath) {
|
||||
launchOptions.executablePath = executablePath;
|
||||
}
|
||||
|
||||
const browser = await chromium.launch(launchOptions);
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1280, height: 900 }
|
||||
});
|
||||
|
||||
const page = await context.newPage();
|
||||
|
||||
page.on('console', msg => console.log('PAGE:', msg.text()));
|
||||
|
||||
// Navigate and set token
|
||||
await page.goto(`${BASE_URL}/?token=${TOKEN}`);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.evaluate((token) => {
|
||||
const settings = JSON.parse(localStorage.getItem('clawdbot-control-ui-settings') || '{}');
|
||||
settings.token = token;
|
||||
localStorage.setItem('clawdbot-control-ui-settings', JSON.stringify(settings));
|
||||
}, TOKEN);
|
||||
|
||||
// Navigate to analytics
|
||||
await page.goto(`${BASE_URL}/analytics`);
|
||||
await page.waitForTimeout(4000);
|
||||
|
||||
// Click refresh
|
||||
const refreshButton = page.locator('button', { hasText: 'Refresh' });
|
||||
try {
|
||||
await refreshButton.click();
|
||||
console.log('Clicked refresh');
|
||||
await page.waitForTimeout(3000);
|
||||
} catch (e) {
|
||||
console.log('Could not click refresh:', e.message);
|
||||
}
|
||||
|
||||
const outputPath = process.argv[2] || '/tmp/analytics-screenshot.png';
|
||||
await page.screenshot({ path: outputPath, fullPage: false });
|
||||
console.log('Screenshot saved to', outputPath);
|
||||
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
takeScreenshot().catch(err => {
|
||||
console.error('Error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user