feat: add usage statistics page, CLI improvements, and UI enhancements

- Add Usage Stats page with token breakdown, model distribution, and 30-day trend
- Pass through cache/cost token fields in BFF (cache_read/write_tokens, reasoning_tokens, actual_cost_usd)
- Add CLI commands: -v/--version, -h/--help, update/upgrade with auto-restart
- Auto-open browser on startup, auto-kill port conflicts (cross-platform)
- Validate all api_server config fields on startup (enabled, host, port, key, cors_origins)
- Add streaming thinking video animation with tool calls panel
- Add context token usage display (used / total) in chat header
- Sidebar: white logo area with shadow, dance video beside logo (canvas seamless loop)
- Fix sidebar nav scroll (app-main overflow-y: auto)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ekko
2026-04-14 14:47:18 +08:00
parent f8fc64ff5c
commit 9dd5fca9f9
24 changed files with 1433 additions and 137 deletions
+15 -7
View File
@@ -84,7 +84,7 @@ async function ensureApiServerConfig() {
const yaml = (await import('js-yaml')).default
const configPath = resolve(homedir(), '.hermes/config.yaml')
const apiServerConfig = {
const apiServerDefaults: Record<string, any> = {
enabled: true,
host: '127.0.0.1',
port: 8642,
@@ -101,8 +101,20 @@ async function ensureApiServerConfig() {
const content = readFileSync(configPath, 'utf-8')
const config = yaml.load(content) as any || {}
// Check if api_server is already correct
if (config.platforms?.api_server?.enabled === true) {
if (!config.platforms) config.platforms = {}
if (!config.platforms.api_server) config.platforms.api_server = {}
const api = config.platforms.api_server
let needsUpdate = false
for (const [key, value] of Object.entries(apiServerDefaults)) {
if (api[key] === undefined || api[key] === null) {
api[key] = value
needsUpdate = true
}
}
if (!needsUpdate) {
console.log(' ✓ api_server config is correct')
return
}
@@ -110,10 +122,6 @@ async function ensureApiServerConfig() {
// Backup before modifying
copyFileSync(configPath, configPath + '.bak')
// Ensure platforms.api_server with correct values
if (!config.platforms) config.platforms = {}
config.platforms.api_server = apiServerConfig
const updated = yaml.dump(config, { lineWidth: -1, noRefs: true, quotingType: '"' })
writeFileSync(configPath, updated, 'utf-8')
console.log(' ✓ api_server config ensured (backup saved to config.yaml.bak)')
+33 -3
View File
@@ -16,19 +16,39 @@ export interface HermesSession {
tool_call_count: number
input_tokens: number
output_tokens: number
cache_read_tokens: number
cache_write_tokens: number
reasoning_tokens: number
billing_provider: string | null
estimated_cost_usd: number
actual_cost_usd: number | null
cost_status: string
messages?: any[]
}
interface HermesSessionFull extends HermesSession {
system_prompt?: string
model_config?: string
interface HermesSessionFull {
id: string
source: string
user_id: string | null
model: string
title: string | null
started_at: number
ended_at: number | null
end_reason: string | null
message_count: number
tool_call_count: number
input_tokens: number
output_tokens: number
cache_read_tokens?: number
cache_write_tokens?: number
reasoning_tokens?: number
billing_provider: string | null
estimated_cost_usd: number
actual_cost_usd?: number | null
cost_status?: string
messages?: any[]
system_prompt?: string
model_config?: string
cost_source?: string
pricing_version?: string | null
[key: string]: any
@@ -74,8 +94,13 @@ export async function listSessions(source?: string, limit?: number): Promise<Her
tool_call_count: raw.tool_call_count,
input_tokens: raw.input_tokens,
output_tokens: raw.output_tokens,
cache_read_tokens: raw.cache_read_tokens || 0,
cache_write_tokens: raw.cache_write_tokens || 0,
reasoning_tokens: raw.reasoning_tokens || 0,
billing_provider: raw.billing_provider,
estimated_cost_usd: raw.estimated_cost_usd,
actual_cost_usd: raw.actual_cost_usd ?? null,
cost_status: raw.cost_status || '',
})
} catch { /* skip malformed lines */ }
}
@@ -122,8 +147,13 @@ export async function getSession(id: string): Promise<HermesSession | null> {
tool_call_count: raw.tool_call_count,
input_tokens: raw.input_tokens,
output_tokens: raw.output_tokens,
cache_read_tokens: raw.cache_read_tokens || 0,
cache_write_tokens: raw.cache_write_tokens || 0,
reasoning_tokens: raw.reasoning_tokens || 0,
billing_provider: raw.billing_provider,
estimated_cost_usd: raw.estimated_cost_usd,
actual_cost_usd: raw.actual_cost_usd ?? null,
cost_status: raw.cost_status || '',
messages: raw.messages,
}
} catch (err: any) {