feat: Add Visits and Unique Users cards to overview

Visits counts total conversations created in the timeframe (from
conversations collection). Unique Users counts distinct users who
created at least one conversation in the period.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
DJP 2026-03-19 13:48:46 -04:00
parent e74f21d57e
commit 3d3c8a1a54
4 changed files with 21 additions and 1 deletions

View file

@ -268,6 +268,8 @@ body {
.stat-icon.tokens { background: rgba(255, 196, 7, 0.08); color: #FFD54F; }
.stat-icon.users { background: rgba(192, 132, 252, 0.1); color: var(--accent-purple); }
.stat-icon.convos { background: rgba(255, 196, 7, 0.06); color: #FFAB00; }
.stat-icon.visits { background: rgba(74, 222, 128, 0.1); color: var(--accent-green); }
.stat-icon.unique { background: rgba(248, 113, 113, 0.1); color: var(--accent-red); }
.stat-label {
display: block;

View file

@ -94,6 +94,14 @@
<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">

View file

@ -176,6 +176,8 @@ async function loadSummary() {
document.getElementById('totalTokens').textContent = fmtTokens(d.totalTokens);
document.getElementById('activeUsers').textContent = fmtNum(d.activeUsers);
document.getElementById('conversations').textContent = fmtNum(d.conversations);
document.getElementById('visits').textContent = fmtNum(d.visits);
document.getElementById('uniqueUsers').textContent = fmtNum(d.uniqueUsers);
} catch (e) { console.error('Summary:', e); }
}

View file

@ -52,7 +52,7 @@ async function getSummary(db, query) {
await refreshAgentsCache(db);
const { startDate, endDate } = getDateRange(query);
const [tokenResult, userCount, convCount] = await Promise.all([
const [tokenResult, userCount, convCount, visits, uniqueUsers] = await Promise.all([
db.collection('transactions').aggregate([
{ $match: { createdAt: { $gte: startDate, $lte: endDate } } },
{
@ -69,6 +69,12 @@ async function getSummary(db, query) {
db.collection('transactions').distinct('conversationId', {
createdAt: { $gte: startDate, $lte: endDate }
}),
db.collection('conversations').countDocuments({
createdAt: { $gte: startDate, $lte: endDate }
}),
db.collection('conversations').distinct('user', {
createdAt: { $gte: startDate, $lte: endDate }
}),
]);
const t = tokenResult[0] || { totalTokens: 0, totalCost: 0 };
@ -77,6 +83,8 @@ async function getSummary(db, query) {
totalCost: t.totalCost / 1_000_000,
activeUsers: userCount.length,
conversations: convCount.length,
visits: visits,
uniqueUsers: uniqueUsers.length,
};
}