142 lines
3.8 KiB
TypeScript
142 lines
3.8 KiB
TypeScript
|
|
import { fetchSessions, type SessionSummary } from '@/api/sessions'
|
||
|
|
import { defineStore } from 'pinia'
|
||
|
|
import { computed, ref } from 'vue'
|
||
|
|
|
||
|
|
interface DailyUsage {
|
||
|
|
date: string
|
||
|
|
tokens: number
|
||
|
|
cache: number
|
||
|
|
sessions: number
|
||
|
|
cost: number
|
||
|
|
}
|
||
|
|
|
||
|
|
interface ModelUsage {
|
||
|
|
model: string
|
||
|
|
inputTokens: number
|
||
|
|
outputTokens: number
|
||
|
|
cacheTokens: number
|
||
|
|
totalTokens: number
|
||
|
|
sessions: number
|
||
|
|
}
|
||
|
|
|
||
|
|
export const useUsageStore = defineStore('usage', () => {
|
||
|
|
const sessions = ref<SessionSummary[]>([])
|
||
|
|
const isLoading = ref(false)
|
||
|
|
|
||
|
|
async function loadSessions() {
|
||
|
|
isLoading.value = true
|
||
|
|
try {
|
||
|
|
sessions.value = await fetchSessions()
|
||
|
|
} catch (err) {
|
||
|
|
console.error('Failed to load sessions for usage:', err)
|
||
|
|
} finally {
|
||
|
|
isLoading.value = false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const totalInputTokens = computed(() =>
|
||
|
|
sessions.value.reduce((sum, s) => sum + (s.input_tokens || 0), 0),
|
||
|
|
)
|
||
|
|
|
||
|
|
const totalOutputTokens = computed(() =>
|
||
|
|
sessions.value.reduce((sum, s) => sum + (s.output_tokens || 0), 0),
|
||
|
|
)
|
||
|
|
|
||
|
|
const totalTokens = computed(() => totalInputTokens.value + totalOutputTokens.value)
|
||
|
|
|
||
|
|
const totalSessions = computed(() => sessions.value.length)
|
||
|
|
|
||
|
|
const totalCacheTokens = computed(() =>
|
||
|
|
sessions.value.reduce((sum, s) => sum + (s.cache_read_tokens || 0), 0),
|
||
|
|
)
|
||
|
|
|
||
|
|
const cacheHitRate = computed(() => {
|
||
|
|
const total = totalInputTokens.value
|
||
|
|
if (total === 0) return null
|
||
|
|
return ((totalCacheTokens.value / total) * 100)
|
||
|
|
})
|
||
|
|
|
||
|
|
const estimatedCost = computed(() =>
|
||
|
|
sessions.value.reduce((sum, s) => {
|
||
|
|
const cost = s.actual_cost_usd ?? s.estimated_cost_usd ?? 0
|
||
|
|
return sum + cost
|
||
|
|
}, 0),
|
||
|
|
)
|
||
|
|
|
||
|
|
const modelUsage = computed<ModelUsage[]>(() => {
|
||
|
|
const map = new Map<string, ModelUsage>()
|
||
|
|
for (const s of sessions.value) {
|
||
|
|
const key = s.model || 'unknown'
|
||
|
|
if (!map.has(key)) {
|
||
|
|
map.set(key, {
|
||
|
|
model: key,
|
||
|
|
inputTokens: 0,
|
||
|
|
outputTokens: 0,
|
||
|
|
cacheTokens: 0,
|
||
|
|
totalTokens: 0,
|
||
|
|
sessions: 0,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
const entry = map.get(key)!
|
||
|
|
entry.inputTokens += s.input_tokens || 0
|
||
|
|
entry.outputTokens += s.output_tokens || 0
|
||
|
|
entry.cacheTokens += s.cache_read_tokens || 0
|
||
|
|
entry.totalTokens += (s.input_tokens || 0) + (s.output_tokens || 0)
|
||
|
|
entry.sessions += 1
|
||
|
|
}
|
||
|
|
return [...map.values()].sort((a, b) => b.totalTokens - a.totalTokens)
|
||
|
|
})
|
||
|
|
|
||
|
|
const dailyUsage = computed<DailyUsage[]>(() => {
|
||
|
|
const map = new Map<string, DailyUsage>()
|
||
|
|
const now = new Date()
|
||
|
|
|
||
|
|
// Initialize last 30 days
|
||
|
|
for (let i = 29; i >= 0; i--) {
|
||
|
|
const d = new Date(now)
|
||
|
|
d.setDate(d.getDate() - i)
|
||
|
|
const key = d.toISOString().slice(0, 10)
|
||
|
|
map.set(key, { date: key, tokens: 0, cache: 0, sessions: 0, cost: 0 })
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const s of sessions.value) {
|
||
|
|
const d = new Date(s.started_at * 1000)
|
||
|
|
const key = d.toISOString().slice(0, 10)
|
||
|
|
const entry = map.get(key)
|
||
|
|
if (entry) {
|
||
|
|
entry.tokens += (s.input_tokens || 0) + (s.output_tokens || 0)
|
||
|
|
entry.cache += s.cache_read_tokens || 0
|
||
|
|
entry.sessions += 1
|
||
|
|
const cost = s.actual_cost_usd ?? s.estimated_cost_usd ?? 0
|
||
|
|
entry.cost += cost
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return [...map.values()]
|
||
|
|
})
|
||
|
|
|
||
|
|
const avgSessionsPerDay = computed(() => {
|
||
|
|
const firstDate = sessions.value.length > 0
|
||
|
|
? new Date(sessions.value[sessions.value.length - 1].started_at * 1000)
|
||
|
|
: new Date()
|
||
|
|
const days = Math.max(1, Math.ceil((Date.now() - firstDate.getTime()) / (1000 * 60 * 60 * 24)))
|
||
|
|
return totalSessions.value / days
|
||
|
|
})
|
||
|
|
|
||
|
|
return {
|
||
|
|
sessions,
|
||
|
|
isLoading,
|
||
|
|
loadSessions,
|
||
|
|
totalInputTokens,
|
||
|
|
totalOutputTokens,
|
||
|
|
totalTokens,
|
||
|
|
totalSessions,
|
||
|
|
totalCacheTokens,
|
||
|
|
cacheHitRate,
|
||
|
|
estimatedCost,
|
||
|
|
modelUsage,
|
||
|
|
dailyUsage,
|
||
|
|
avgSessionsPerDay,
|
||
|
|
}
|
||
|
|
})
|