Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
<PayloadProvider>
<SiteBody>
<PayloadStatusBoundary>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
<TooltipProvider>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</TooltipProvider>
</PayloadStatusBoundary>
</SiteBody>
</PayloadProvider>
Expand Down
1 change: 1 addition & 0 deletions app/components/Dashboard/OverviewCards.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<StatisticalAnalysisTotalCard
v-for="card in cards"
:key="card.name"
:detail="card.detail"
:icon="card.icon"
:name="card.name"
:trend="card.trend"
Expand Down
13 changes: 12 additions & 1 deletion app/components/StatisticalAnalysis/TotalCard.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
<template>
<StatisticalAnalysisPanel compact :icon="icon" :title="name">
<div class="flex justify-between items-center">
<span class="text-2xl font-bold tracking-tight">{{ value }}</span>
<ValueTooltip :content="detail">
<span
:class="cn(
'text-2xl font-bold tracking-tight',
)"
>
{{ value }}
</span>
</ValueTooltip>
<div class="flex items-center gap-1">
<Icon :class="trendMeta.iconClass" mode="svg" :name="trendMeta.icon" />
<span :class="trendMeta.textClass">
Expand All @@ -13,11 +21,14 @@
</template>

<script setup lang="ts">
import { cn } from '~/lib/utils'

defineOptions({
name: 'StatisticalAnalysisTotalCard',
})

const props = withDefaults(defineProps<{
detail?: string
name: string
value: string
icon: string
Expand Down
25 changes: 25 additions & 0 deletions app/components/ValueTooltip.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<template>
<slot v-if="!content" />

<Tooltip v-else>
<TooltipTrigger as-child>
<slot />
</TooltipTrigger>

<TooltipContent side="top">
<p class="max-w-64 leading-relaxed">
{{ content }}
</p>
</TooltipContent>
</Tooltip>
</template>

<script setup lang="ts">
defineOptions({
name: 'ValueTooltip',
})

defineProps<{
content?: string
}>()
</script>
6 changes: 6 additions & 0 deletions app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
</template>

<script lang="ts" setup>
import { formatNumber } from '@lonewolfyx/utils'

const {
cachedInputTokens,
costGrowthTrend,
Expand All @@ -42,27 +44,31 @@ const {

const overviewCards = computed(() => [
{
detail: `${formatCurrency(totalCost.value)} total spend across all tools`,
icon: 'lucide:wallet',
name: 'Total Spend',
trend: costGrowthTrend.value.trend,
trendTone: costGrowthTrend.value.trendTone,
value: formatCurrency(totalCost.value),
},
{
detail: `${formatNumber(totalTokens.value)} total tokens across all tools`,
icon: 'solar:cpu-line-duotone',
name: 'Token Usage',
trend: tokenGrowthTrend.value.trend,
trendTone: tokenGrowthTrend.value.trendTone,
value: formatCompactNumber(totalTokens.value),
},
{
detail: `${formatNumber(cachedInputTokens.value)} of ${formatNumber(inputTokens.value)} input tokens were served from cache`,
icon: 'lucide:database-zap',
name: 'Cache Hit Rate',
trend: `${formatCompactNumber(cachedInputTokens.value)} cached`,
trendTone: 'neutral' as const,
value: formatPercent(inputTokens.value > 0 ? cachedInputTokens.value / inputTokens.value : 0),
},
{
detail: `${formatCurrency(totalCost.value)} across ${formatNumber(totalSessions.value)} sessions`,
icon: 'lucide:receipt-text',
name: 'Avg Session Cost',
trend: 'across all tools',
Expand Down
1 change: 1 addition & 0 deletions app/pages/project.vue
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@
<StatisticalAnalysisTotalCard
v-for="card in platformViews[tab.value].overviewCards"
:key="card.name"
:detail="card.detail"
:icon="card.icon"
:name="card.name"
:trend="card.trend"
Expand Down
1 change: 1 addition & 0 deletions shared/types/statistical-analysis.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ProjectUsageItem, TrendTone } from './usage-dashboard'

export interface DashboardOverviewCard {
detail?: string
icon: string
name: string
trend: string
Expand Down
1 change: 1 addition & 0 deletions shared/types/usage-dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface ModelTokenUsage {
export type TrendTone = 'down' | 'neutral' | 'up'

export interface UsageOverviewCard {
detail?: string
icon: string
name: string
trend: string
Expand Down
9 changes: 9 additions & 0 deletions shared/utils/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
roundCurrency,
uniqueItems,
} from '#shared/utils/usage-dashboard'
import { formatNumber } from '@lonewolfyx/utils'

/**
* Reads a JSONL file while ignoring empty lines and malformed JSON lines.
Expand Down Expand Up @@ -381,27 +382,35 @@ export function buildOverviewCards(options: {

return [
{
detail: `${formatNumber(options.todayTotalTokens)} tokens used today`,
icon: 'solar:cpu-line-duotone',
name: 'Today Tokens',
trend: tokenTrend.trend,
trendTone: tokenTrend.trendTone,
value: formatCompactNumber(options.todayTotalTokens),
},
{
detail: `${formatCurrency(options.todayTotalCost)} spent today`,
icon: 'lucide:wallet',
name: 'Today Spend',
trend: costTrend.trend,
trendTone: costTrend.trendTone,
value: formatCurrency(options.todayTotalCost),
},
{
detail: options.todayTopProject
? `${options.todayTopProject.project} with ${formatNumber(options.todayTopProject.sessionCount)} sessions today`
: 'No project sessions recorded today',
icon: 'lucide:folder-git-2',
name: 'Top Session Project',
trend: options.todayTopProject ? `${options.todayTopProject.sessionCount} sessions` : 'No sessions',
trendTone: 'up',
value: options.todayTopProject?.project ?? '-',
},
{
detail: options.todayTopModel
? `${options.todayTopModel.model} with ${formatNumber(options.todayTopModel.totalTokens)} tokens today`
: 'No model usage recorded today',
icon: 'lucide:bot',
name: 'Top Invoked Model',
trend: options.todayTopModel ? `${formatCompactNumber(options.todayTopModel.totalTokens)} tokens` : 'No usage',
Expand Down
12 changes: 12 additions & 0 deletions shared/utils/project-dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,34 +93,39 @@ export function buildProjectOverviewCards(sessions: ProjectSessionListItem[]): U

return [
{
detail: `${formatNumber(summary.totalTokens)} total tokens in this project`,
icon: 'solar:cpu-line-duotone',
name: 'Total Tokens',
trend: 'project total',
trendTone: 'neutral',
value: formatCompactNumber(summary.totalTokens),
},
{
detail: `${formatCurrency(summary.costUSD)} total project spend`,
icon: 'lucide:wallet',
name: 'Total Spend',
trend: 'all time',
trendTone: 'neutral',
value: formatCurrency(summary.costUSD),
},
{
detail: `${formatNumber(summary.sessions)} sessions across all tools`,
icon: 'lucide:messages-square',
name: 'Sessions',
trend: 'all tools',
trendTone: 'neutral',
value: String(summary.sessions),
},
{
detail: `${formatNumber(summary.cachedInputTokens)} of ${formatNumber(summary.inputTokens)} input tokens were served from cache`,
icon: 'lucide:database-zap',
name: 'Cache Hit Rate',
trend: `${formatCompactNumber(summary.cachedInputTokens)} cached`,
trendTone: 'neutral',
value: formatPercent(summary.inputTokens > 0 ? summary.cachedInputTokens / summary.inputTokens : 0),
},
{
detail: `${formatCurrency(summary.costUSD)} across ${formatNumber(summary.sessions)} sessions`,
icon: 'lucide:circle-dollar-sign',
name: 'Avg Session Cost',
trend: 'per session',
Expand All @@ -146,48 +151,55 @@ export function buildProjectPlatformOverviewCards(

return [
{
detail: `${formatNumber(todayUsage?.totalTokens ?? 0)} tokens used today`,
icon: 'solar:cpu-line-duotone',
name: 'Today Tokens',
trend: tokenTrend.trend,
trendTone: tokenTrend.trendTone,
value: formatCompactNumber(todayUsage?.totalTokens ?? 0),
},
{
detail: `${formatCurrency(todayUsage?.costUSD ?? 0)} spent today`,
icon: 'lucide:wallet',
name: 'Today Spend',
trend: costTrend.trend,
trendTone: costTrend.trendTone,
value: formatCurrency(todayUsage?.costUSD ?? 0),
},
{
detail: `${formatNumber(todaySessions)} sessions recorded today`,
icon: 'lucide:messages-square',
name: 'Today Sessions',
trend: sessionTrend.label,
trendTone: sessionTrend.tone,
value: String(todaySessions),
},
{
detail: `${formatCurrency(summary.costUSD)} total project spend`,
icon: 'lucide:receipt-text',
name: 'Total Spend',
trend: 'all time',
trendTone: 'neutral',
value: formatCurrency(summary.costUSD),
},
{
detail: `${formatNumber(summary.sessions)} total sessions for this platform`,
icon: 'lucide:list-checks',
name: 'Sessions',
trend: 'project total',
trendTone: 'neutral',
value: String(summary.sessions),
},
{
detail: `${formatNumber(summary.cachedInputTokens)} of ${formatNumber(summary.inputTokens)} input tokens were served from cache`,
icon: 'lucide:database-zap',
name: 'Cache Hit Rate',
trend: `${formatCompactNumber(summary.cachedInputTokens)} cached`,
trendTone: 'neutral',
value: formatPercent(summary.inputTokens > 0 ? summary.cachedInputTokens / summary.inputTokens : 0),
},
{
detail: `${formatCurrency(summary.costUSD)} across ${formatNumber(summary.sessions)} sessions`,
icon: 'lucide:circle-dollar-sign',
name: 'Avg Session Cost',
trend: 'per session',
Expand Down
Loading