librechat-analytics/public/index.html
DJP 1673694b11 feat: Add User Lookup tab with search and full user workup
New tab lets you search for any user by name or email, then shows:
- Summary cards (cost, tokens, conversations, visits) for the period
- All their conversations with titles, models, and cost breakdown
- Model usage breakdown with prompt/completion split
- CSV export of the full user report

Two new API endpoints: /api/search-users and /api/user-detail

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 15:09:43 -04:00

298 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LibreChat Analytics</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/style.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
<script src="https://unpkg.com/lucide@latest"></script>
</head>
<body>
<div id="app">
<!-- Sidebar -->
<nav class="sidebar">
<div class="sidebar-header">
<svg class="logo-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/>
<path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/>
<path d="M18 12a2 2 0 0 0 0 4h4v-4Z"/>
</svg>
<span class="logo-text">Analytics</span>
</div>
<ul class="nav-links">
<li class="active" data-tab="overview">
<i data-lucide="bar-chart-3" style="width:20px;height:20px"></i>
Overview
</li>
<li data-tab="users">
<i data-lucide="users" style="width:20px;height:20px"></i>
Users
</li>
<li data-tab="models">
<i data-lucide="cpu" style="width:20px;height:20px"></i>
Models
</li>
<li data-tab="conversations">
<i data-lucide="message-circle" style="width:20px;height:20px"></i>
Conversations
</li>
<li data-tab="agents">
<i data-lucide="bot" style="width:20px;height:20px"></i>
Agents
</li>
<li data-tab="user-lookup">
<i data-lucide="search" style="width:20px;height:20px"></i>
User Lookup
</li>
</ul>
<div class="sidebar-footer">
Auto-refresh: 60s
</div>
</nav>
<!-- Main Content -->
<main class="content">
<div class="top-bar">
<h1>LibreChat Analytics</h1>
</div>
<!-- Filter Bar -->
<div class="filter-bar">
<div class="period-selector">
<button class="period-btn active" data-period="24h">24H</button>
<button class="period-btn" data-period="7d">7D</button>
<button class="period-btn" data-period="30d">30D</button>
<button class="period-btn" data-period="custom">Custom</button>
</div>
<div class="date-range-picker" id="dateRangePicker" style="display:none">
<input type="date" id="startDate">
<span class="date-separator">to</span>
<input type="date" id="endDate">
</div>
<span class="refresh-info" id="lastRefresh"></span>
</div>
<!-- Overview Tab -->
<div class="tab-content active" id="tab-overview">
<div class="tab-title-row">
<div></div>
<button class="btn-export" onclick="exportOverview()"><i data-lucide="download" style="width:16px;height:16px"></i> Export CSV</button>
</div>
<div class="stats-grid" id="summaryCards">
<div class="stat-card">
<div class="stat-icon cost"><i data-lucide="dollar-sign"></i></div>
<div><span class="stat-label">Total Cost</span><span class="stat-value" id="totalCost">--</span></div>
</div>
<div class="stat-card">
<div class="stat-icon tokens"><i data-lucide="zap"></i></div>
<div><span class="stat-label">Total Tokens</span><span class="stat-value" id="totalTokens">--</span></div>
</div>
<div class="stat-card">
<div class="stat-icon users"><i data-lucide="users"></i></div>
<div><span class="stat-label">Active Users</span><span class="stat-value" id="activeUsers">--</span></div>
</div>
<div class="stat-card">
<div class="stat-icon convos"><i data-lucide="message-square"></i></div>
<div><span class="stat-label">Conversations</span><span class="stat-value" id="conversations">--</span></div>
</div>
<div class="stat-card">
<div class="stat-icon visits"><i data-lucide="log-in"></i></div>
<div><span class="stat-label">Visits</span><span class="stat-value" id="visits">--</span></div>
</div>
<div class="stat-card">
<div class="stat-icon unique"><i data-lucide="user-check"></i></div>
<div><span class="stat-label">Unique Users</span><span class="stat-value" id="uniqueUsers">--</span></div>
</div>
</div>
<div class="charts-grid">
<div class="chart-container chart-wide">
<h3>Usage Over Time</h3>
<canvas id="usageChart"></canvas>
</div>
<div class="chart-container">
<h3>Cost by Model</h3>
<canvas id="costByModelChart"></canvas>
</div>
<div class="chart-container">
<h3>Cost Breakdown (Input vs Output)</h3>
<canvas id="costBreakdownChart"></canvas>
</div>
</div>
</div>
<!-- Users Tab -->
<div class="tab-content" id="tab-users">
<div class="table-container">
<div class="table-header"><h3>Top Users by Cost</h3><button class="btn-export" onclick="exportUsers()"><i data-lucide="download" style="width:16px;height:16px"></i> Export CSV</button></div>
<table>
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Email</th>
<th class="text-right">Tokens</th>
<th class="text-right">Cost</th>
<th class="text-right">Conversations</th>
</tr>
</thead>
<tbody id="usersTableBody"></tbody>
</table>
</div>
</div>
<!-- Models Tab -->
<div class="tab-content" id="tab-models">
<div class="table-container">
<div class="table-header"><h3>Top Models by Cost</h3><button class="btn-export" onclick="exportModels()"><i data-lucide="download" style="width:16px;height:16px"></i> Export CSV</button></div>
<table>
<thead>
<tr>
<th>#</th>
<th>Model</th>
<th class="text-right">Prompt Tokens</th>
<th class="text-right">Completion Tokens</th>
<th class="text-right">Prompt Cost</th>
<th class="text-right">Completion Cost</th>
<th class="text-right">Total Cost</th>
</tr>
</thead>
<tbody id="modelsTableBody"></tbody>
</table>
</div>
<div class="charts-grid">
<div class="chart-container chart-wide">
<h3>Model Cost Comparison (Input vs Output)</h3>
<canvas id="modelCostChart"></canvas>
</div>
</div>
</div>
<!-- Conversations Tab -->
<div class="tab-content" id="tab-conversations">
<div class="table-container">
<div class="table-header">
<h3 id="conversationsTitle">Most Expensive Conversations</h3>
<div style="display:flex;gap:0.75rem;align-items:center">
<button class="btn-export btn-filter-user" id="clearUserFilter" onclick="clearUserFilter()" style="display:none"><i data-lucide="x" style="width:14px;height:14px"></i> Clear filter</button>
<button class="btn-export" onclick="exportConversations()"><i data-lucide="download" style="width:16px;height:16px"></i> Export CSV</button>
</div>
</div>
<table>
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>User</th>
<th>Models</th>
<th class="text-right">Input Tokens</th>
<th class="text-right">Output Tokens</th>
<th class="text-right">Total Cost</th>
</tr>
</thead>
<tbody id="conversationsTableBody"></tbody>
</table>
</div>
</div>
<!-- Agents Tab -->
<div class="tab-content" id="tab-agents">
<div class="table-container">
<div class="table-header"><h3>Top Agents by Cost</h3><button class="btn-export" onclick="exportAgents()"><i data-lucide="download" style="width:16px;height:16px"></i> Export CSV</button></div>
<table>
<thead>
<tr>
<th>#</th>
<th>Agent Name</th>
<th>Underlying Model</th>
<th>Provider</th>
<th class="text-right">Tokens</th>
<th class="text-right">Cost</th>
<th class="text-right">Conversations</th>
</tr>
</thead>
<tbody id="agentsTableBody"></tbody>
</table>
</div>
</div>
<!-- User Lookup Tab -->
<div class="tab-content" id="tab-user-lookup">
<div class="user-search-bar">
<div class="search-input-wrap">
<i data-lucide="search" style="width:18px;height:18px"></i>
<input type="text" id="userSearchInput" placeholder="Search by name or email..." autocomplete="off">
</div>
<button class="btn-export" onclick="searchUser()"><i data-lucide="search" style="width:16px;height:16px"></i> Search</button>
</div>
<div id="userSearchResults" class="search-results" style="display:none"></div>
<div id="userDetailSection" style="display:none">
<div class="table-header" style="padding:0;border:none;margin-bottom:1.5rem">
<h3 id="userLookupTitle">--</h3>
<button class="btn-export" onclick="exportUserLookup()"><i data-lucide="download" style="width:16px;height:16px"></i> Export CSV</button>
</div>
<div class="stats-grid" id="userSummaryCards">
<div class="stat-card">
<div class="stat-icon cost"><i data-lucide="dollar-sign"></i></div>
<div><span class="stat-label">Total Cost</span><span class="stat-value" id="userTotalCost">--</span></div>
</div>
<div class="stat-card">
<div class="stat-icon tokens"><i data-lucide="zap"></i></div>
<div><span class="stat-label">Total Tokens</span><span class="stat-value" id="userTotalTokens">--</span></div>
</div>
<div class="stat-card">
<div class="stat-icon convos"><i data-lucide="message-square"></i></div>
<div><span class="stat-label">Conversations</span><span class="stat-value" id="userConversations">--</span></div>
</div>
<div class="stat-card">
<div class="stat-icon visits"><i data-lucide="log-in"></i></div>
<div><span class="stat-label">Visits</span><span class="stat-value" id="userVisits">--</span></div>
</div>
</div>
<div class="table-container">
<div class="table-header"><h3>Conversations</h3></div>
<table>
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Models</th>
<th class="text-right">Input Tokens</th>
<th class="text-right">Output Tokens</th>
<th class="text-right">Total Cost</th>
</tr>
</thead>
<tbody id="userConvosTableBody"></tbody>
</table>
</div>
<div class="table-container">
<div class="table-header"><h3>Model Breakdown</h3></div>
<table>
<thead>
<tr>
<th>#</th>
<th>Model</th>
<th class="text-right">Prompt Tokens</th>
<th class="text-right">Completion Tokens</th>
<th class="text-right">Prompt Cost</th>
<th class="text-right">Completion Cost</th>
<th class="text-right">Total Cost</th>
</tr>
</thead>
<tbody id="userModelsTableBody"></tbody>
</table>
</div>
</div>
</div>
</main>
</div>
<script src="js/app.js"></script>
</body>
</html>