@axplusb/kepler 0.0.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -0
- package/package.json +36 -4
- package/pulse/app/activity/page.tsx +190 -0
- package/pulse/app/api/activity/route.ts +138 -0
- package/pulse/app/api/costs/route.ts +88 -0
- package/pulse/app/api/export/route.ts +77 -0
- package/pulse/app/api/history/route.ts +11 -0
- package/pulse/app/api/import/route.ts +31 -0
- package/pulse/app/api/memory/route.ts +52 -0
- package/pulse/app/api/plans/route.ts +9 -0
- package/pulse/app/api/projects/[slug]/route.ts +96 -0
- package/pulse/app/api/projects/route.ts +121 -0
- package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
- package/pulse/app/api/sessions/[id]/route.ts +31 -0
- package/pulse/app/api/sessions/route.ts +112 -0
- package/pulse/app/api/settings/route.ts +14 -0
- package/pulse/app/api/stats/route.ts +143 -0
- package/pulse/app/api/todos/route.ts +9 -0
- package/pulse/app/api/tools/route.ts +160 -0
- package/pulse/app/costs/page.tsx +179 -0
- package/pulse/app/export/page.tsx +465 -0
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +263 -0
- package/pulse/app/help/page.tsx +142 -0
- package/pulse/app/history/page.tsx +157 -0
- package/pulse/app/layout.tsx +46 -0
- package/pulse/app/memory/page.tsx +365 -0
- package/pulse/app/overview-client.tsx +393 -0
- package/pulse/app/page.tsx +14 -0
- package/pulse/app/plans/page.tsx +308 -0
- package/pulse/app/projects/[slug]/page.tsx +390 -0
- package/pulse/app/projects/page.tsx +110 -0
- package/pulse/app/sessions/[id]/page.tsx +243 -0
- package/pulse/app/sessions/page.tsx +39 -0
- package/pulse/app/settings/page.tsx +188 -0
- package/pulse/app/todos/page.tsx +211 -0
- package/pulse/app/tools/page.tsx +249 -0
- package/pulse/cli.js +159 -0
- package/pulse/components/activity/day-of-week-chart.tsx +35 -0
- package/pulse/components/activity/streak-card.tsx +36 -0
- package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
- package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
- package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
- package/pulse/components/costs/model-token-table.tsx +60 -0
- package/pulse/components/global-search.tsx +193 -0
- package/pulse/components/keyboard-nav-provider.tsx +23 -0
- package/pulse/components/layout/bottom-nav.tsx +52 -0
- package/pulse/components/layout/client-layout.tsx +31 -0
- package/pulse/components/layout/sidebar-context.tsx +50 -0
- package/pulse/components/layout/sidebar.tsx +182 -0
- package/pulse/components/layout/top-bar.tsx +121 -0
- package/pulse/components/overview/activity-heatmap.tsx +107 -0
- package/pulse/components/overview/conversation-table.tsx +148 -0
- package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
- package/pulse/components/overview/peak-hours-chart.tsx +87 -0
- package/pulse/components/overview/project-activity-donut.tsx +96 -0
- package/pulse/components/overview/stat-card.tsx +102 -0
- package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
- package/pulse/components/projects/project-card.tsx +175 -0
- package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
- package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
- package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
- package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
- package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
- package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
- package/pulse/components/sessions/session-badges.tsx +49 -0
- package/pulse/components/sessions/session-table.tsx +299 -0
- package/pulse/components/theme-provider.tsx +44 -0
- package/pulse/components/tools/feature-adoption-table.tsx +58 -0
- package/pulse/components/tools/mcp-server-panel.tsx +45 -0
- package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
- package/pulse/components/tools/version-history-table.tsx +32 -0
- package/pulse/components/ui/alert.tsx +66 -0
- package/pulse/components/ui/badge.tsx +48 -0
- package/pulse/components/ui/breadcrumb.tsx +109 -0
- package/pulse/components/ui/button.tsx +64 -0
- package/pulse/components/ui/calendar.tsx +220 -0
- package/pulse/components/ui/card.tsx +92 -0
- package/pulse/components/ui/command.tsx +158 -0
- package/pulse/components/ui/dialog.tsx +158 -0
- package/pulse/components/ui/input.tsx +21 -0
- package/pulse/components/ui/popover.tsx +89 -0
- package/pulse/components/ui/progress.tsx +31 -0
- package/pulse/components/ui/select.tsx +190 -0
- package/pulse/components/ui/separator.tsx +28 -0
- package/pulse/components/ui/sheet.tsx +143 -0
- package/pulse/components/ui/skeleton.tsx +13 -0
- package/pulse/components/ui/table.tsx +116 -0
- package/pulse/components/ui/tabs.tsx +91 -0
- package/pulse/components/ui/tooltip.tsx +57 -0
- package/pulse/components/use-global-keyboard-nav.ts +79 -0
- package/pulse/components.json +23 -0
- package/pulse/eslint.config.mjs +18 -0
- package/pulse/lib/claude-reader.ts +594 -0
- package/pulse/lib/decode.ts +129 -0
- package/pulse/lib/pricing.ts +102 -0
- package/pulse/lib/replay-parser.ts +165 -0
- package/pulse/lib/tool-categories.ts +127 -0
- package/pulse/lib/utils.ts +6 -0
- package/pulse/next-env.d.ts +6 -0
- package/pulse/next.config.ts +16 -0
- package/pulse/package.json +45 -0
- package/pulse/postcss.config.mjs +7 -0
- package/pulse/public/activity.png +0 -0
- package/pulse/public/cc-lens.png +0 -0
- package/pulse/public/command-k.png +0 -0
- package/pulse/public/costs.png +0 -0
- package/pulse/public/dashboard-dark.png +0 -0
- package/pulse/public/dashboard-white.png +0 -0
- package/pulse/public/export.png +0 -0
- package/pulse/public/file.svg +1 -0
- package/pulse/public/globe.svg +1 -0
- package/pulse/public/next.svg +1 -0
- package/pulse/public/projects.png +0 -0
- package/pulse/public/session-chat.png +0 -0
- package/pulse/public/todos.png +0 -0
- package/pulse/public/tools.png +0 -0
- package/pulse/public/vercel.svg +1 -0
- package/pulse/public/window.svg +1 -0
- package/pulse/tsconfig.json +34 -0
- package/pulse/types/claude.ts +294 -0
- package/src/agents/loader.mjs +89 -0
- package/src/agents/parser.mjs +98 -0
- package/src/agents/teams.mjs +123 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/auth/tarang-auth.mjs +277 -0
- package/src/config/cli-args.mjs +173 -0
- package/src/config/env.mjs +263 -0
- package/src/config/settings.mjs +132 -0
- package/src/context/ast-parser.mjs +298 -0
- package/src/context/bm25.mjs +85 -0
- package/src/context/retriever.mjs +270 -0
- package/src/context/skeleton.mjs +134 -0
- package/src/core/agent-loop.mjs +480 -0
- package/src/core/approval.mjs +273 -0
- package/src/core/backend-url.mjs +57 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/callback-client.mjs +149 -0
- package/src/core/checkpoints.mjs +142 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/headless.mjs +168 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +351 -0
- package/src/core/local-agent.mjs +429 -0
- package/src/core/local-store.mjs +325 -0
- package/src/core/mode-selector.mjs +51 -0
- package/src/core/output-filter.mjs +177 -0
- package/src/core/paths.mjs +101 -0
- package/src/core/pricing.mjs +314 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/safety.mjs +200 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session-manager.mjs +317 -0
- package/src/core/session.mjs +143 -0
- package/src/core/settings-sync.mjs +85 -0
- package/src/core/stagnation.mjs +57 -0
- package/src/core/stream-client.mjs +367 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +135 -0
- package/src/core/tool-executor.mjs +725 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/index.mjs +370 -0
- package/src/mcp/client.mjs +253 -0
- package/src/mcp/transport-shttp.mjs +130 -0
- package/src/mcp/transport-sse.mjs +131 -0
- package/src/mcp/transport-ws.mjs +134 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +573 -0
- package/src/permissions/injection-check.mjs +60 -0
- package/src/permissions/path-check.mjs +102 -0
- package/src/permissions/prompt.mjs +73 -0
- package/src/permissions/sandbox.mjs +112 -0
- package/src/plugins/loader.mjs +138 -0
- package/src/skills/loader.mjs +147 -0
- package/src/skills/runner.mjs +55 -0
- package/src/telemetry/index.mjs +96 -0
- package/src/terminal/agents.mjs +177 -0
- package/src/terminal/analytics.mjs +292 -0
- package/src/terminal/ansi.mjs +421 -0
- package/src/terminal/main.mjs +150 -0
- package/src/terminal/repl.mjs +1484 -0
- package/src/terminal/tool-display.mjs +58 -0
- package/src/tools/agent.mjs +137 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +148 -0
- package/src/tools/cron-create.mjs +120 -0
- package/src/tools/cron-delete.mjs +49 -0
- package/src/tools/cron-list.mjs +37 -0
- package/src/tools/edit.mjs +82 -0
- package/src/tools/enter-worktree.mjs +69 -0
- package/src/tools/exit-worktree.mjs +57 -0
- package/src/tools/glob.mjs +117 -0
- package/src/tools/grep.mjs +129 -0
- package/src/tools/lint.mjs +71 -0
- package/src/tools/ls.mjs +58 -0
- package/src/tools/lsp.mjs +115 -0
- package/src/tools/multi-edit.mjs +94 -0
- package/src/tools/notebook-edit.mjs +96 -0
- package/src/tools/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +132 -0
- package/src/tools/remote-trigger.mjs +84 -0
- package/src/tools/send-message.mjs +64 -0
- package/src/tools/skill.mjs +52 -0
- package/src/tools/test-runner.mjs +49 -0
- package/src/tools/todo-write.mjs +68 -0
- package/src/tools/tool-search.mjs +77 -0
- package/src/tools/web-fetch.mjs +65 -0
- package/src/tools/web-search.mjs +89 -0
- package/src/tools/write.mjs +55 -0
- package/src/ui/banner.mjs +237 -0
- package/src/ui/commands.mjs +499 -0
- package/src/ui/formatter.mjs +379 -0
- package/src/ui/markdown.mjs +278 -0
- package/src/ui/slash-commands.mjs +258 -0
- package/index.js +0 -1
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useMemo } from 'react'
|
|
4
|
+
import useSWR from 'swr'
|
|
5
|
+
import { BarChart3, PieChart, Clock, CalendarDays } from 'lucide-react'
|
|
6
|
+
import { UsageOverTimeChart } from '@/components/overview/usage-over-time-chart'
|
|
7
|
+
import { ModelBreakdownDonut } from '@/components/overview/model-breakdown-donut'
|
|
8
|
+
import { ProjectActivityDonut } from '@/components/overview/project-activity-donut'
|
|
9
|
+
import { PeakHoursChart } from '@/components/overview/peak-hours-chart'
|
|
10
|
+
import { OverviewConversationTable } from '@/components/overview/conversation-table'
|
|
11
|
+
import { StatCard } from '@/components/overview/stat-card'
|
|
12
|
+
import { formatTokens, formatBytes } from '@/lib/decode'
|
|
13
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
14
|
+
import { Button } from '@/components/ui/button'
|
|
15
|
+
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
|
16
|
+
import { Skeleton } from '@/components/ui/skeleton'
|
|
17
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
|
18
|
+
import { Calendar } from '@/components/ui/calendar'
|
|
19
|
+
import type { StatsCache, DailyActivity, DailyTokens } from '@/types/claude'
|
|
20
|
+
import type { SessionWithFacet, ProjectSummary } from '@/types/claude'
|
|
21
|
+
import { format, subDays } from 'date-fns'
|
|
22
|
+
import { useTheme } from '@/components/theme-provider'
|
|
23
|
+
|
|
24
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
interface ApiResponse {
|
|
27
|
+
stats: StatsCache
|
|
28
|
+
computed: {
|
|
29
|
+
totalCost: number
|
|
30
|
+
totalCacheSavings: number
|
|
31
|
+
totalTokens: number
|
|
32
|
+
totalInputTokens: number
|
|
33
|
+
totalOutputTokens: number
|
|
34
|
+
totalCacheReadTokens: number
|
|
35
|
+
totalCacheWriteTokens: number
|
|
36
|
+
totalToolCalls: number
|
|
37
|
+
activeDays: number
|
|
38
|
+
avgSessionMinutes: number
|
|
39
|
+
sessionsThisMonth: number
|
|
40
|
+
sessionsThisWeek: number
|
|
41
|
+
storageBytes: number
|
|
42
|
+
sessionCount: number
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type DatePreset = '7d' | '30d' | '90d'
|
|
47
|
+
type CustomRange = { from?: Date; to?: Date }
|
|
48
|
+
|
|
49
|
+
const fetcher = (url: string) =>
|
|
50
|
+
fetch(url).then(r => {
|
|
51
|
+
if (!r.ok) throw new Error(`API error ${r.status}`)
|
|
52
|
+
return r.json()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
function computeTrend(
|
|
58
|
+
dailyActivity: DailyActivity[],
|
|
59
|
+
field: 'messageCount' | 'sessionCount',
|
|
60
|
+
days = 7,
|
|
61
|
+
): number | undefined {
|
|
62
|
+
const sorted = [...dailyActivity].sort((a, b) => a.date.localeCompare(b.date))
|
|
63
|
+
const recent = sorted.slice(-days)
|
|
64
|
+
const previous = sorted.slice(-(days * 2), -days)
|
|
65
|
+
if (!recent.length || !previous.length) return undefined
|
|
66
|
+
const recentSum = recent.reduce((s, d) => s + (d[field] ?? 0), 0)
|
|
67
|
+
const prevSum = previous.reduce((s, d) => s + (d[field] ?? 0), 0)
|
|
68
|
+
if (prevSum === 0) return undefined
|
|
69
|
+
return ((recentSum - prevSum) / prevSum) * 100
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getActivitySpark(dailyActivity: DailyActivity[], field: 'messageCount' | 'sessionCount', days = 14): number[] {
|
|
73
|
+
return [...dailyActivity]
|
|
74
|
+
.sort((a, b) => a.date.localeCompare(b.date))
|
|
75
|
+
.slice(-days)
|
|
76
|
+
.map(d => d[field] ?? 0)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getTokenSpark(tokensByDate: DailyTokens[], days = 14): number[] {
|
|
80
|
+
return [...tokensByDate]
|
|
81
|
+
.sort((a, b) => a.date.localeCompare(b.date))
|
|
82
|
+
.slice(-days)
|
|
83
|
+
.map(d => Object.values(d.tokensByModel ?? {}).reduce((s, v) => s + v, 0))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─── Component ────────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
export function OverviewClient() {
|
|
89
|
+
const { theme } = useTheme()
|
|
90
|
+
const [datePreset, setDatePreset] = useState<DatePreset>('30d')
|
|
91
|
+
const [customRange, setCustomRange] = useState<CustomRange>({})
|
|
92
|
+
const [pickerOpen, setPickerOpen] = useState(false)
|
|
93
|
+
|
|
94
|
+
const { data, error, isLoading } = useSWR<ApiResponse>('/api/stats', fetcher, {
|
|
95
|
+
refreshInterval: 5_000,
|
|
96
|
+
})
|
|
97
|
+
const { data: sessionsData } = useSWR<{ sessions: SessionWithFacet[] }>('/api/sessions', fetcher, {
|
|
98
|
+
refreshInterval: 5_000,
|
|
99
|
+
})
|
|
100
|
+
const { data: projectsData } = useSWR<{ projects: ProjectSummary[] }>('/api/projects', fetcher, {
|
|
101
|
+
refreshInterval: 5_000,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const sessions = sessionsData?.sessions ?? []
|
|
105
|
+
const projects = projectsData?.projects ?? []
|
|
106
|
+
const projectCount = projects.length
|
|
107
|
+
|
|
108
|
+
const usingCustom = !!(customRange.from && customRange.to)
|
|
109
|
+
const chartDays = usingCustom
|
|
110
|
+
? Math.ceil((customRange.to!.getTime() - customRange.from!.getTime()) / (24 * 60 * 60 * 1000))
|
|
111
|
+
: datePreset === '7d' ? 7 : datePreset === '30d' ? 30 : 90
|
|
112
|
+
const effectiveDateFrom = usingCustom
|
|
113
|
+
? format(customRange.from!, 'MM/dd/yyyy')
|
|
114
|
+
: format(subDays(new Date(), chartDays), 'MM/dd/yyyy')
|
|
115
|
+
const effectiveDateTo = usingCustom
|
|
116
|
+
? format(customRange.to!, 'MM/dd/yyyy')
|
|
117
|
+
: format(new Date(), 'MM/dd/yyyy')
|
|
118
|
+
|
|
119
|
+
const pickerLabel = usingCustom
|
|
120
|
+
? `${format(customRange.from!, 'MMM d')} – ${format(customRange.to!, 'MMM d, yyyy')}`
|
|
121
|
+
: 'Pick a date'
|
|
122
|
+
|
|
123
|
+
// ── Loading ──────────────────────────────────────────────────────────────
|
|
124
|
+
if (isLoading || !data || !data.computed) {
|
|
125
|
+
return (
|
|
126
|
+
<div className="px-6 py-6 space-y-6">
|
|
127
|
+
<div className="flex items-center justify-between">
|
|
128
|
+
<div className="space-y-2">
|
|
129
|
+
<Skeleton className="h-7 w-32" />
|
|
130
|
+
<Skeleton className="h-4 w-56" />
|
|
131
|
+
</div>
|
|
132
|
+
<Skeleton className="h-9 w-48" />
|
|
133
|
+
</div>
|
|
134
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
135
|
+
{[...Array(4)].map((_, i) => <Skeleton key={i} className="h-32 rounded-xl" />)}
|
|
136
|
+
</div>
|
|
137
|
+
<div className="grid grid-cols-1 lg:grid-cols-[1fr_320px] gap-6">
|
|
138
|
+
<Skeleton className="h-72 rounded-xl" />
|
|
139
|
+
<Skeleton className="h-72 rounded-xl" />
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (error) {
|
|
146
|
+
return (
|
|
147
|
+
<div className="px-6 py-6 text-destructive text-sm font-mono">
|
|
148
|
+
✗ error loading data: {String(error)}
|
|
149
|
+
</div>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const { stats, computed } = data
|
|
154
|
+
|
|
155
|
+
const inputBlue = theme === 'light' ? '#1d4ed8' : '#60a5fa'
|
|
156
|
+
const tokenSegs = [
|
|
157
|
+
{ label: 'input', value: computed.totalInputTokens, color: inputBlue },
|
|
158
|
+
{ label: 'output', value: computed.totalOutputTokens, color: '#d97706' },
|
|
159
|
+
{ label: 'cache read', value: computed.totalCacheReadTokens, color: '#34d399' },
|
|
160
|
+
{ label: 'cache write', value: computed.totalCacheWriteTokens, color: '#a78bfa' },
|
|
161
|
+
]
|
|
162
|
+
const totalTokens =
|
|
163
|
+
computed.totalInputTokens +
|
|
164
|
+
computed.totalOutputTokens +
|
|
165
|
+
computed.totalCacheReadTokens +
|
|
166
|
+
computed.totalCacheWriteTokens
|
|
167
|
+
|
|
168
|
+
const tokensByDate = stats.dailyModelTokens ?? stats.tokensByDate ?? []
|
|
169
|
+
|
|
170
|
+
// Trends compare last N days vs previous N days (capped at 30 to avoid sparse data)
|
|
171
|
+
const trendWindow = Math.min(Math.max(chartDays, 7), 30)
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<div className="px-6 py-6 space-y-6 bg-background">
|
|
175
|
+
|
|
176
|
+
{/* ── Page header ───────────────────────────────────────────────────── */}
|
|
177
|
+
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
|
178
|
+
<div>
|
|
179
|
+
<h2 className="text-2xl font-bold tracking-tight">Overview</h2>
|
|
180
|
+
<p className="text-sm text-muted-foreground mt-0.5">
|
|
181
|
+
{projectCount} projects · {formatBytes(computed.storageBytes)} stored
|
|
182
|
+
</p>
|
|
183
|
+
</div>
|
|
184
|
+
<div className="flex items-center gap-2">
|
|
185
|
+
<Tabs
|
|
186
|
+
value={usingCustom ? '' : datePreset}
|
|
187
|
+
onValueChange={v => {
|
|
188
|
+
setDatePreset(v as DatePreset)
|
|
189
|
+
setCustomRange({})
|
|
190
|
+
}}
|
|
191
|
+
>
|
|
192
|
+
<TabsList>
|
|
193
|
+
<TabsTrigger value="7d">7d</TabsTrigger>
|
|
194
|
+
<TabsTrigger value="30d">30d</TabsTrigger>
|
|
195
|
+
<TabsTrigger value="90d">90d</TabsTrigger>
|
|
196
|
+
</TabsList>
|
|
197
|
+
</Tabs>
|
|
198
|
+
|
|
199
|
+
<Popover open={pickerOpen} onOpenChange={setPickerOpen}>
|
|
200
|
+
<PopoverTrigger asChild>
|
|
201
|
+
<Button
|
|
202
|
+
variant={usingCustom ? 'default' : 'outline'}
|
|
203
|
+
size="sm"
|
|
204
|
+
className="gap-2"
|
|
205
|
+
>
|
|
206
|
+
<CalendarDays className="w-3.5 h-3.5" />
|
|
207
|
+
{pickerLabel}
|
|
208
|
+
</Button>
|
|
209
|
+
</PopoverTrigger>
|
|
210
|
+
<PopoverContent className="w-auto p-0" align="end">
|
|
211
|
+
<Calendar
|
|
212
|
+
mode="range"
|
|
213
|
+
selected={{ from: customRange.from, to: customRange.to }}
|
|
214
|
+
onSelect={range => {
|
|
215
|
+
setCustomRange({ from: range?.from, to: range?.to })
|
|
216
|
+
if (range?.from && range?.to) setPickerOpen(false)
|
|
217
|
+
}}
|
|
218
|
+
disabled={{ after: new Date() }}
|
|
219
|
+
initialFocus
|
|
220
|
+
/>
|
|
221
|
+
</PopoverContent>
|
|
222
|
+
</Popover>
|
|
223
|
+
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
{/* ── Stat cards ────────────────────────────────────────────────────── */}
|
|
228
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
229
|
+
<StatCard
|
|
230
|
+
title="Sessions"
|
|
231
|
+
value={computed.sessionCount.toLocaleString()}
|
|
232
|
+
description={`${computed.sessionsThisMonth} this month · ${computed.sessionsThisWeek} this week`}
|
|
233
|
+
trend={computeTrend(stats.dailyActivity, 'sessionCount', trendWindow)}
|
|
234
|
+
sparkData={getActivitySpark(stats.dailyActivity, 'sessionCount')}
|
|
235
|
+
accentColor="var(--foreground)"
|
|
236
|
+
/>
|
|
237
|
+
<StatCard
|
|
238
|
+
title="Messages"
|
|
239
|
+
value={stats.totalMessages.toLocaleString()}
|
|
240
|
+
description={`${computed.activeDays} active days`}
|
|
241
|
+
trend={computeTrend(stats.dailyActivity, 'messageCount', trendWindow)}
|
|
242
|
+
sparkData={getActivitySpark(stats.dailyActivity, 'messageCount')}
|
|
243
|
+
accentColor="#d97706"
|
|
244
|
+
/>
|
|
245
|
+
<StatCard
|
|
246
|
+
title="Tokens Used"
|
|
247
|
+
value={formatTokens(computed.totalTokens)}
|
|
248
|
+
description={`${formatTokens(computed.totalCacheReadTokens)} from cache`}
|
|
249
|
+
sparkData={getTokenSpark(tokensByDate)}
|
|
250
|
+
accentColor={inputBlue}
|
|
251
|
+
/>
|
|
252
|
+
<StatCard
|
|
253
|
+
title="Estimated Cost"
|
|
254
|
+
value={`$${computed.totalCost.toFixed(2)}`}
|
|
255
|
+
description={`$${computed.totalCacheSavings.toFixed(2)} saved via cache`}
|
|
256
|
+
sparkData={getTokenSpark(tokensByDate)}
|
|
257
|
+
accentColor="#34d399"
|
|
258
|
+
/>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
{/* ── Main charts row ───────────────────────────────────────────────── */}
|
|
262
|
+
<div className="grid grid-cols-1 lg:grid-cols-[1fr_320px] gap-6">
|
|
263
|
+
<Card>
|
|
264
|
+
<CardHeader>
|
|
265
|
+
<div className="flex items-start justify-between">
|
|
266
|
+
<div>
|
|
267
|
+
<CardTitle>Usage Over Time</CardTitle>
|
|
268
|
+
<CardDescription>
|
|
269
|
+
Messages and sessions — last {chartDays} days
|
|
270
|
+
</CardDescription>
|
|
271
|
+
</div>
|
|
272
|
+
<BarChart3 className="w-4 h-4 text-muted-foreground mt-0.5" />
|
|
273
|
+
</div>
|
|
274
|
+
</CardHeader>
|
|
275
|
+
<CardContent>
|
|
276
|
+
<UsageOverTimeChart
|
|
277
|
+
data={stats.dailyActivity}
|
|
278
|
+
days={chartDays}
|
|
279
|
+
dateFrom={effectiveDateFrom}
|
|
280
|
+
dateTo={effectiveDateTo}
|
|
281
|
+
/>
|
|
282
|
+
</CardContent>
|
|
283
|
+
</Card>
|
|
284
|
+
|
|
285
|
+
<Card>
|
|
286
|
+
<CardHeader>
|
|
287
|
+
<div className="flex items-start justify-between">
|
|
288
|
+
<div>
|
|
289
|
+
<CardTitle>Model Distribution</CardTitle>
|
|
290
|
+
<CardDescription>Token usage by model</CardDescription>
|
|
291
|
+
</div>
|
|
292
|
+
<PieChart className="w-4 h-4 text-muted-foreground mt-0.5" />
|
|
293
|
+
</div>
|
|
294
|
+
</CardHeader>
|
|
295
|
+
<CardContent>
|
|
296
|
+
<ModelBreakdownDonut modelUsage={stats.modelUsage} />
|
|
297
|
+
</CardContent>
|
|
298
|
+
</Card>
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
{/* ── Secondary charts row ──────────────────────────────────────────── */}
|
|
302
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
303
|
+
<Card>
|
|
304
|
+
<CardHeader>
|
|
305
|
+
<div className="flex items-start justify-between">
|
|
306
|
+
<div>
|
|
307
|
+
<CardTitle>Peak Hours</CardTitle>
|
|
308
|
+
<CardDescription>Activity by hour of day</CardDescription>
|
|
309
|
+
</div>
|
|
310
|
+
<Clock className="w-4 h-4 text-muted-foreground mt-0.5" />
|
|
311
|
+
</div>
|
|
312
|
+
</CardHeader>
|
|
313
|
+
<CardContent>
|
|
314
|
+
<PeakHoursChart hourCounts={stats.hourCounts ?? {}} />
|
|
315
|
+
</CardContent>
|
|
316
|
+
</Card>
|
|
317
|
+
|
|
318
|
+
<Card>
|
|
319
|
+
<CardHeader>
|
|
320
|
+
<div className="flex items-start justify-between">
|
|
321
|
+
<div>
|
|
322
|
+
<CardTitle>Project Activity</CardTitle>
|
|
323
|
+
<CardDescription>Distribution across projects</CardDescription>
|
|
324
|
+
</div>
|
|
325
|
+
<PieChart className="w-4 h-4 text-muted-foreground mt-0.5" />
|
|
326
|
+
</div>
|
|
327
|
+
</CardHeader>
|
|
328
|
+
<CardContent>
|
|
329
|
+
<ProjectActivityDonut projects={projects} />
|
|
330
|
+
</CardContent>
|
|
331
|
+
</Card>
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
{/* ── Token breakdown ───────────────────────────────────────────────── */}
|
|
335
|
+
<Card>
|
|
336
|
+
<CardHeader className="pb-3">
|
|
337
|
+
<CardTitle>Token Breakdown</CardTitle>
|
|
338
|
+
<CardDescription>Distribution across token types (all time)</CardDescription>
|
|
339
|
+
</CardHeader>
|
|
340
|
+
<CardContent className="space-y-3">
|
|
341
|
+
{totalTokens > 0 ? (
|
|
342
|
+
<>
|
|
343
|
+
<div className="flex h-2 rounded-full overflow-hidden w-full bg-muted/40">
|
|
344
|
+
{tokenSegs.map(({ label, value, color }) => (
|
|
345
|
+
<div
|
|
346
|
+
key={label}
|
|
347
|
+
title={`${label}: ${formatTokens(value)}`}
|
|
348
|
+
style={{
|
|
349
|
+
width: `${(value / totalTokens) * 100}%`,
|
|
350
|
+
minWidth: value > 0 ? 2 : 0,
|
|
351
|
+
backgroundColor: color,
|
|
352
|
+
}}
|
|
353
|
+
/>
|
|
354
|
+
))}
|
|
355
|
+
</div>
|
|
356
|
+
<div className="flex flex-wrap gap-x-8 gap-y-2">
|
|
357
|
+
{tokenSegs.map(({ label, value, color }) => (
|
|
358
|
+
<span key={label} className="inline-flex items-center gap-2">
|
|
359
|
+
<span
|
|
360
|
+
className="inline-block w-2 h-2 rounded-full shrink-0"
|
|
361
|
+
style={{ backgroundColor: color }}
|
|
362
|
+
/>
|
|
363
|
+
<span className="text-[12px] text-muted-foreground">{label}</span>
|
|
364
|
+
<span className="text-[13px] font-bold tabular-nums font-mono" style={{ color }}>
|
|
365
|
+
{formatTokens(value)}
|
|
366
|
+
</span>
|
|
367
|
+
<span className="text-[12px] text-muted-foreground/60">
|
|
368
|
+
{Math.round((value / totalTokens) * 100)}%
|
|
369
|
+
</span>
|
|
370
|
+
</span>
|
|
371
|
+
))}
|
|
372
|
+
</div>
|
|
373
|
+
</>
|
|
374
|
+
) : (
|
|
375
|
+
<p className="text-sm text-muted-foreground">No token usage recorded yet.</p>
|
|
376
|
+
)}
|
|
377
|
+
</CardContent>
|
|
378
|
+
</Card>
|
|
379
|
+
|
|
380
|
+
{/* ── Recent sessions ───────────────────────────────────────────────── */}
|
|
381
|
+
<Card>
|
|
382
|
+
<CardHeader>
|
|
383
|
+
<CardTitle>Recent Sessions</CardTitle>
|
|
384
|
+
<CardDescription>Your latest Orca sessions</CardDescription>
|
|
385
|
+
</CardHeader>
|
|
386
|
+
<CardContent>
|
|
387
|
+
<OverviewConversationTable sessions={sessions} />
|
|
388
|
+
</CardContent>
|
|
389
|
+
</Card>
|
|
390
|
+
|
|
391
|
+
</div>
|
|
392
|
+
)
|
|
393
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TopBar } from '@/components/layout/top-bar'
|
|
2
|
+
import { OverviewClient } from './overview-client'
|
|
3
|
+
|
|
4
|
+
export default function OverviewPage() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="flex flex-col min-h-screen">
|
|
7
|
+
<TopBar
|
|
8
|
+
title="Orca Pulse"
|
|
9
|
+
subtitle="Real-time analytics for your Orca AI agent sessions"
|
|
10
|
+
/>
|
|
11
|
+
<OverviewClient />
|
|
12
|
+
</div>
|
|
13
|
+
)
|
|
14
|
+
}
|