diff --git a/app/(app)/admin/jobs/page.tsx b/app/(app)/admin/jobs/page.tsx index b1c9920..76d0825 100644 --- a/app/(app)/admin/jobs/page.tsx +++ b/app/(app)/admin/jobs/page.tsx @@ -16,15 +16,34 @@ export default async function AdminJobsPage() { branch: true, pr_url: true, error: true, + model_id: true, + input_tokens: true, + output_tokens: true, + cache_read_tokens: true, + cache_write_tokens: true, user: { select: { username: true } }, product: { select: { name: true } }, }, }) + const prices = await prisma.modelPrice.findMany() + const priceMap = new Map(prices.map((p) => [p.model_id, p])) + + const jobsWithCost = jobs.map((job) => { + const p = job.model_id ? priceMap.get(job.model_id) : undefined + if (!p || job.input_tokens == null) return { ...job, cost_usd: null } + const cost = + (job.input_tokens ?? 0) * Number(p.input_price_per_1m) / 1_000_000 + + (job.output_tokens ?? 0) * Number(p.output_price_per_1m) / 1_000_000 + + (job.cache_read_tokens ?? 0) * Number(p.cache_read_price_per_1m) / 1_000_000 + + (job.cache_write_tokens ?? 0) * Number(p.cache_write_price_per_1m) / 1_000_000 + return { ...job, cost_usd: cost } + }) + return (

Claude Jobs

- +
) } diff --git a/components/admin/jobs-table.tsx b/components/admin/jobs-table.tsx index a241549..3b1c312 100644 --- a/components/admin/jobs-table.tsx +++ b/components/admin/jobs-table.tsx @@ -1,6 +1,6 @@ 'use client' -import { useTransition } from 'react' +import { useState, useTransition } from 'react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { @@ -23,6 +23,8 @@ type Job = { branch: string | null pr_url: string | null error: string | null + model_id: string | null + cost_usd: number | null } const STATUS_CLASS: Record = { @@ -92,7 +94,7 @@ function JobRow({ job }: { job: Job }) { ) } -export function JobsTable({ jobs }: { jobs: Job[] }) { +function StatusTable({ jobs }: { jobs: Job[] }) { return ( @@ -123,3 +125,86 @@ export function JobsTable({ jobs }: { jobs: Job[] }) {
) } + +function CostRow({ job }: { job: Job }) { + const [pending, startTransition] = useTransition() + function handleCancel() { startTransition(() => cancelJobAction(job.id)) } + function handleDelete() { startTransition(() => deleteJobAction(job.id)) } + const costLabel = job.cost_usd != null ? `$${job.cost_usd.toFixed(4)}` : '—' + return ( + + {job.id.slice(0, 8)} + {job.user.username} + {job.product.name} + {KIND_LABEL[job.kind] ?? job.kind} + {job.model_id ?? '—'} + {costLabel} + + {new Date(job.created_at).toLocaleString('nl-NL', { dateStyle: 'short', timeStyle: 'short' })} + + +
+ {ACTIVE_STATUSES.has(job.status) && ( + + )} + +
+
+
+ ) +} + +function CostsTable({ jobs }: { jobs: Job[] }) { + return ( + + + + ID + Gebruiker + Product + Type + Model + Kosten (USD) + Aangemaakt + Acties + + + + {jobs.length === 0 && ( + + + Geen jobs gevonden + + + )} + {jobs.map((job) => )} + +
+ ) +} + +export function JobsTable({ jobs }: { jobs: Job[] }) { + const [view, setView] = useState<'status' | 'costs'>('status') + + return ( +
+
+ + +
+ {view === 'status' ? : } +
+ ) +}