tokentrace 0.8.2 → 0.8.4
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/CHANGELOG.md +13 -0
- package/app/api/prices/route.ts +9 -2
- package/app/evidence/page.tsx +95 -6
- package/app/page.tsx +288 -48
- package/app/pricing/page.tsx +2 -1
- package/app/repair/page.tsx +135 -17
- package/components/period-filter.tsx +26 -24
- package/components/pricing-settings.tsx +74 -2
- package/dist/runtime/digest.mjs +10 -6
- package/dist/runtime/doctor.mjs +10 -6
- package/dist/runtime/evidence.mjs +68 -15
- package/dist/runtime/insights.mjs +10 -6
- package/dist/runtime/repair.mjs +25 -2
- package/dist/runtime/scan.mjs +6 -6
- package/package.json +2 -1
- package/scripts/visual-smoke.mjs +30 -0
- package/src/ingestion/adapters/codex-cli.ts +6 -6
- package/src/lib/analytics.ts +10 -6
- package/src/lib/date-range.ts +22 -0
- package/src/lib/evidence-trail.ts +112 -16
- package/src/lib/pricing.ts +36 -3
- package/src/lib/unknown-cost-repair.ts +29 -2
package/app/page.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Link from "next/link";
|
|
2
|
-
import { ArrowRight, Coins, Database, Gauge, MessageSquare, Minus,
|
|
2
|
+
import { ArrowRight, Coins, Database, Gauge, Layers, MessageSquare, Minus, TrendingDown, TrendingUp } from "lucide-react";
|
|
3
3
|
import { RankBarChart } from "@/components/charts/rank-bar-chart";
|
|
4
4
|
import { TrendChart } from "@/components/charts/trend-chart";
|
|
5
5
|
import { PeriodFilter } from "@/components/period-filter";
|
|
@@ -10,11 +10,11 @@ import { HelpTooltip } from "@/components/ui/help-tooltip";
|
|
|
10
10
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
11
11
|
import { DataValue, FieldLabel, MonoText, PageHeader } from "@/components/ui/typography";
|
|
12
12
|
import { getAnalyticsData, getScanTrustData } from "@/src/lib/analytics";
|
|
13
|
-
import { buildDoctorReport } from "@/src/lib/doctor";
|
|
13
|
+
import { buildDoctorReport, type DoctorReport } from "@/src/lib/doctor";
|
|
14
14
|
import { getDefaultSearchRoots } from "@/src/ingestion/discovery";
|
|
15
15
|
import { buildFirstRunStatus, type FirstRunStatus } from "@/src/lib/first-run-status";
|
|
16
16
|
import { buildUnknownCostRepairWorkbench } from "@/src/lib/unknown-cost-repair";
|
|
17
|
-
import { resolveDateRange } from "@/src/lib/date-range";
|
|
17
|
+
import { dateRangeQueryParams, mergeHrefParams, resolveDateRange } from "@/src/lib/date-range";
|
|
18
18
|
import { formatCurrency, formatSignedTokens, formatTokens, percent } from "@/src/lib/format";
|
|
19
19
|
import { cn } from "@/src/lib/utils";
|
|
20
20
|
import type { UsageGuardrailMetric } from "@/src/lib/usage-guardrails";
|
|
@@ -28,6 +28,8 @@ function MetricCard({
|
|
|
28
28
|
description,
|
|
29
29
|
href,
|
|
30
30
|
actionLabel = "Inspect sessions",
|
|
31
|
+
secondaryHref,
|
|
32
|
+
secondaryActionLabel,
|
|
31
33
|
icon: Icon,
|
|
32
34
|
className,
|
|
33
35
|
valueClassName
|
|
@@ -38,6 +40,8 @@ function MetricCard({
|
|
|
38
40
|
description?: string;
|
|
39
41
|
href?: string;
|
|
40
42
|
actionLabel?: string;
|
|
43
|
+
secondaryHref?: string;
|
|
44
|
+
secondaryActionLabel?: string;
|
|
41
45
|
icon: typeof Database;
|
|
42
46
|
className?: string;
|
|
43
47
|
valueClassName?: string;
|
|
@@ -45,35 +49,127 @@ function MetricCard({
|
|
|
45
49
|
const tooltipId = `metric-${label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}-help`;
|
|
46
50
|
|
|
47
51
|
return (
|
|
48
|
-
<Card className={cn("h-full", className)}>
|
|
49
|
-
<CardHeader className="flex flex-row items-
|
|
52
|
+
<Card className={cn("flex h-full flex-col overflow-hidden", className)}>
|
|
53
|
+
<CardHeader className="flex flex-row items-start justify-between gap-3 space-y-0 pb-3">
|
|
50
54
|
<div className="flex min-w-0 items-center gap-1.5">
|
|
51
55
|
<CardTitle className="leading-tight">{label}</CardTitle>
|
|
52
56
|
{description ? (
|
|
53
57
|
<HelpTooltip id={tooltipId} label={label} description={description} />
|
|
54
58
|
) : null}
|
|
55
59
|
</div>
|
|
56
|
-
<
|
|
60
|
+
<span className="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground">
|
|
61
|
+
<Icon className="h-4 w-4" aria-hidden="true" />
|
|
62
|
+
</span>
|
|
57
63
|
</CardHeader>
|
|
58
|
-
<CardContent>
|
|
59
|
-
<DataValue size="lg" className={valueClassName}>{value}</DataValue>
|
|
64
|
+
<CardContent className="flex flex-1 flex-col">
|
|
65
|
+
<DataValue size="lg" className={cn("break-words leading-none", valueClassName)}>{value}</DataValue>
|
|
60
66
|
{detailItems?.length ? (
|
|
61
|
-
<div className="mt-3 flex flex-wrap gap-x-2 gap-y-1 text-xs leading-snug text-muted-foreground">
|
|
62
|
-
{detailItems.map((item) => (
|
|
63
|
-
<span key={item}>
|
|
67
|
+
<div className="mt-3 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs leading-snug text-muted-foreground">
|
|
68
|
+
{detailItems.map((item, index) => (
|
|
69
|
+
<span key={item} className="inline-flex min-w-0 items-center gap-2">
|
|
70
|
+
<span className="min-w-0">{item}</span>
|
|
71
|
+
{index < detailItems.length - 1 ? <span className="hidden text-border sm:inline">/</span> : null}
|
|
72
|
+
</span>
|
|
64
73
|
))}
|
|
65
74
|
</div>
|
|
66
75
|
) : null}
|
|
67
|
-
{href ? (
|
|
68
|
-
<
|
|
69
|
-
{
|
|
70
|
-
|
|
76
|
+
{href || secondaryHref ? (
|
|
77
|
+
<div className="mt-auto flex flex-wrap items-center gap-x-3 gap-y-1 pt-4">
|
|
78
|
+
{href ? (
|
|
79
|
+
<Link href={href} className="inline-flex w-fit items-center gap-1.5 text-xs font-medium text-primary underline-offset-4 hover:underline">
|
|
80
|
+
{actionLabel}
|
|
81
|
+
<ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
|
|
82
|
+
</Link>
|
|
83
|
+
) : null}
|
|
84
|
+
{secondaryHref ? (
|
|
85
|
+
<Link href={secondaryHref} className="inline-flex w-fit items-center gap-1.5 text-xs font-medium text-muted-foreground underline-offset-4 hover:text-foreground hover:underline">
|
|
86
|
+
{secondaryActionLabel ?? "View details"}
|
|
87
|
+
</Link>
|
|
88
|
+
) : null}
|
|
89
|
+
</div>
|
|
71
90
|
) : null}
|
|
72
91
|
</CardContent>
|
|
73
92
|
</Card>
|
|
74
93
|
);
|
|
75
94
|
}
|
|
76
95
|
|
|
96
|
+
function OverviewTrustStrip({
|
|
97
|
+
latestScan,
|
|
98
|
+
confidence,
|
|
99
|
+
repairHref,
|
|
100
|
+
processedTokensHref
|
|
101
|
+
}: {
|
|
102
|
+
latestScan: { id: string | null; filesScanned: number; recordsImported: number };
|
|
103
|
+
confidence: {
|
|
104
|
+
interactions: number;
|
|
105
|
+
exactTokenInteractions: number;
|
|
106
|
+
exactCostInteractions: number;
|
|
107
|
+
estimatedCostInteractions: number;
|
|
108
|
+
unknownCostInteractions: number;
|
|
109
|
+
};
|
|
110
|
+
repairHref: string;
|
|
111
|
+
processedTokensHref: string;
|
|
112
|
+
}) {
|
|
113
|
+
const exactTokenShare = confidence.interactions > 0 ? percent(confidence.exactTokenInteractions / confidence.interactions) : "0%";
|
|
114
|
+
const costCoverage =
|
|
115
|
+
confidence.interactions > 0
|
|
116
|
+
? percent((confidence.exactCostInteractions + confidence.estimatedCostInteractions) / confidence.interactions)
|
|
117
|
+
: "0%";
|
|
118
|
+
const trustItems = [
|
|
119
|
+
{
|
|
120
|
+
label: "Latest scan",
|
|
121
|
+
value: latestScan.id ? `${latestScan.recordsImported.toLocaleString()} imports` : "No scan",
|
|
122
|
+
detail: latestScan.id ? `${latestScan.filesScanned.toLocaleString()} files scanned` : "Waiting for imported usage",
|
|
123
|
+
href: "/discovery",
|
|
124
|
+
icon: Database
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
label: "Exact tokens",
|
|
128
|
+
value: exactTokenShare,
|
|
129
|
+
detail: `${confidence.exactTokenInteractions.toLocaleString()} interactions with provider counts`,
|
|
130
|
+
href: processedTokensHref,
|
|
131
|
+
icon: Gauge
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
label: "Cost coverage",
|
|
135
|
+
value: costCoverage,
|
|
136
|
+
detail:
|
|
137
|
+
confidence.unknownCostInteractions > 0
|
|
138
|
+
? `${confidence.unknownCostInteractions.toLocaleString()} interactions need repair`
|
|
139
|
+
: "All imported interactions priced",
|
|
140
|
+
href: confidence.unknownCostInteractions > 0 ? repairHref : "/pricing",
|
|
141
|
+
icon: Coins
|
|
142
|
+
}
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<section className="grid overflow-hidden rounded-lg border border-border bg-card md:grid-cols-3" aria-label="Overview data confidence">
|
|
147
|
+
{trustItems.map((item, index) => {
|
|
148
|
+
const Icon = item.icon;
|
|
149
|
+
return (
|
|
150
|
+
<Link
|
|
151
|
+
key={item.label}
|
|
152
|
+
href={item.href}
|
|
153
|
+
className={cn(
|
|
154
|
+
"group flex min-w-0 items-start gap-3 p-4 transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
155
|
+
index > 0 ? "border-t border-border md:border-l md:border-t-0" : ""
|
|
156
|
+
)}
|
|
157
|
+
>
|
|
158
|
+
<span className="mt-0.5 inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground">
|
|
159
|
+
<Icon className="h-4 w-4" aria-hidden="true" />
|
|
160
|
+
</span>
|
|
161
|
+
<span className="min-w-0 space-y-1">
|
|
162
|
+
<FieldLabel>{item.label}</FieldLabel>
|
|
163
|
+
<span className="block truncate text-base font-semibold text-foreground">{item.value}</span>
|
|
164
|
+
<span className="block text-sm text-muted-foreground">{item.detail}</span>
|
|
165
|
+
</span>
|
|
166
|
+
</Link>
|
|
167
|
+
);
|
|
168
|
+
})}
|
|
169
|
+
</section>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
77
173
|
function formatSignedCurrency(value: number) {
|
|
78
174
|
if (value === 0) return "$0.00";
|
|
79
175
|
return `${value > 0 ? "+" : "-"}${formatCurrency(Math.abs(value))}`;
|
|
@@ -113,15 +209,15 @@ function DeltaMetric({
|
|
|
113
209
|
: "text-muted-foreground";
|
|
114
210
|
|
|
115
211
|
return (
|
|
116
|
-
<div className="min-w-
|
|
117
|
-
<div className="flex items-center justify-between gap-3">
|
|
118
|
-
<FieldLabel>{label}</FieldLabel>
|
|
119
|
-
<span className={cn("inline-flex items-center gap-1 text-xs font-semibold", toneClass)}>
|
|
120
|
-
<ToneIcon className="h-3.5 w-3.5" />
|
|
212
|
+
<div className="min-w-0 border-t px-4 py-4 first:border-t-0 sm:border-l sm:border-t-0 sm:first:border-l-0">
|
|
213
|
+
<div className="flex min-w-0 items-center justify-between gap-3">
|
|
214
|
+
<FieldLabel className="truncate">{label}</FieldLabel>
|
|
215
|
+
<span className={cn("inline-flex shrink-0 items-center gap-1 text-xs font-semibold tabular-nums", toneClass)}>
|
|
216
|
+
<ToneIcon className="h-3.5 w-3.5" aria-hidden="true" />
|
|
121
217
|
{formatPercentValue(percentValue)}
|
|
122
218
|
</span>
|
|
123
219
|
</div>
|
|
124
|
-
<DataValue className="mt-1" size="md">{value}</DataValue>
|
|
220
|
+
<DataValue className="mt-1 truncate" size="md">{value}</DataValue>
|
|
125
221
|
<div className="mt-1 text-xs text-muted-foreground">
|
|
126
222
|
{delta} vs {previous}
|
|
127
223
|
</div>
|
|
@@ -254,6 +350,97 @@ function FirstRunPanel({ status }: { status: FirstRunStatus }) {
|
|
|
254
350
|
);
|
|
255
351
|
}
|
|
256
352
|
|
|
353
|
+
function readinessVariant(state: "ready" | "review" | "blocked") {
|
|
354
|
+
if (state === "ready") return "success";
|
|
355
|
+
if (state === "blocked") return "destructive";
|
|
356
|
+
return "warning";
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function DataReadinessPanel({
|
|
360
|
+
report,
|
|
361
|
+
selectedInteractions,
|
|
362
|
+
selectedCachedTokens,
|
|
363
|
+
repairHref,
|
|
364
|
+
cachedEvidenceHref
|
|
365
|
+
}: {
|
|
366
|
+
report: DoctorReport;
|
|
367
|
+
selectedInteractions: number;
|
|
368
|
+
selectedCachedTokens: number;
|
|
369
|
+
repairHref: string;
|
|
370
|
+
cachedEvidenceHref: string;
|
|
371
|
+
}) {
|
|
372
|
+
const parserReviewFiles = report.parserCoverage.parserReviewFiles + report.parserCoverage.failureFiles;
|
|
373
|
+
const items = [
|
|
374
|
+
{
|
|
375
|
+
label: "Local scans",
|
|
376
|
+
value: report.latestScan.id ? `${report.latestScan.recordsImported.toLocaleString()} imports` : "No scan",
|
|
377
|
+
detail: report.latestScan.zeroImportExplanation ?? `${report.latestScan.filesScanned.toLocaleString()} files checked in the latest scan.`,
|
|
378
|
+
state: report.latestScan.id ? "ready" as const : "blocked" as const,
|
|
379
|
+
href: "/discovery"
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
label: "Parser coverage",
|
|
383
|
+
value: parserReviewFiles > 0 ? `${parserReviewFiles.toLocaleString()} files need review` : "No parser blockers",
|
|
384
|
+
detail: "Discovery and Parser Debug explain ignored, unsupported, failed, and imported files.",
|
|
385
|
+
state: report.parserCoverage.failureFiles > 0 ? "blocked" as const : parserReviewFiles > 0 ? "review" as const : "ready" as const,
|
|
386
|
+
href: parserReviewFiles > 0 ? "/parser-debug" : "/discovery"
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
label: "Pricing",
|
|
390
|
+
value: report.pricing.unknown > 0 ? `${report.pricing.unknown.toLocaleString()} unknown` : "Priced",
|
|
391
|
+
detail: `${report.pricing.priced.toLocaleString()} priced interactions across ${report.pricing.pricedModelCount.toLocaleString()} priced models.`,
|
|
392
|
+
state: report.pricing.unknown > 0 ? "blocked" as const : "ready" as const,
|
|
393
|
+
href: report.pricing.unknown > 0 ? repairHref : "/pricing"
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
label: "Cache accounting",
|
|
397
|
+
value: selectedCachedTokens > 0 ? formatTokens(selectedCachedTokens) : "No cache tokens",
|
|
398
|
+
detail: selectedCachedTokens > 0
|
|
399
|
+
? "Cache read and write tokens are separated from fresh input/output."
|
|
400
|
+
: selectedInteractions > 0 ? "The selected period has no imported cache read/write counts." : "Scan usage before cache accounting appears.",
|
|
401
|
+
state: selectedCachedTokens > 0 ? "ready" as const : "review" as const,
|
|
402
|
+
href: cachedEvidenceHref
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
label: "Boundaries",
|
|
406
|
+
value: `${report.supportMatrix.summary.stable} stable, ${report.supportMatrix.summary.bestEffort} best effort`,
|
|
407
|
+
detail: "Unsupported desktop scraping, packet capture, proxying, and telemetry stay out of scope.",
|
|
408
|
+
state: "ready" as const,
|
|
409
|
+
href: "/doctor"
|
|
410
|
+
}
|
|
411
|
+
];
|
|
412
|
+
|
|
413
|
+
return (
|
|
414
|
+
<Card>
|
|
415
|
+
<CardHeader>
|
|
416
|
+
<CardTitle>Data Readiness</CardTitle>
|
|
417
|
+
<CardDescription>Current trust checks before acting on cost, token, parser, or cache numbers.</CardDescription>
|
|
418
|
+
</CardHeader>
|
|
419
|
+
<CardContent className="p-0">
|
|
420
|
+
<div className="grid border-t md:grid-cols-2 xl:grid-cols-5">
|
|
421
|
+
{items.map((item, index) => (
|
|
422
|
+
<Link
|
|
423
|
+
key={item.label}
|
|
424
|
+
href={item.href}
|
|
425
|
+
className={cn(
|
|
426
|
+
"block min-w-0 p-4 transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
427
|
+
index > 0 ? "border-t border-border md:border-l md:border-t-0 xl:border-l" : ""
|
|
428
|
+
)}
|
|
429
|
+
>
|
|
430
|
+
<div className="flex min-w-0 items-start justify-between gap-2">
|
|
431
|
+
<FieldLabel className="truncate">{item.label}</FieldLabel>
|
|
432
|
+
<Badge variant={readinessVariant(item.state)}>{item.state}</Badge>
|
|
433
|
+
</div>
|
|
434
|
+
<div className="mt-2 text-sm font-semibold text-foreground">{item.value}</div>
|
|
435
|
+
<div className="mt-1 text-xs leading-relaxed text-muted-foreground">{item.detail}</div>
|
|
436
|
+
</Link>
|
|
437
|
+
))}
|
|
438
|
+
</div>
|
|
439
|
+
</CardContent>
|
|
440
|
+
</Card>
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
257
444
|
type OverviewPageProps = {
|
|
258
445
|
searchParams?: Promise<Record<string, string | string[] | undefined>>;
|
|
259
446
|
};
|
|
@@ -262,10 +449,20 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
262
449
|
const params = (await searchParams) ?? {};
|
|
263
450
|
const range = resolveDateRange(params);
|
|
264
451
|
const data = getAnalyticsData(range.filters);
|
|
265
|
-
const
|
|
452
|
+
const rangeLinkParams = dateRangeQueryParams(range);
|
|
453
|
+
const evidenceLinks = Object.fromEntries(
|
|
454
|
+
Object.entries(data.evidenceLinks).map(([key, href]) => [key, mergeHrefParams(href, rangeLinkParams)])
|
|
455
|
+
) as typeof data.evidenceLinks;
|
|
456
|
+
const trust = getScanTrustData(range.filters);
|
|
266
457
|
const roots = await getDefaultSearchRoots();
|
|
267
458
|
const doctorReport = buildDoctorReport({ ...trust, roots });
|
|
268
|
-
const repairWorkbench = buildUnknownCostRepairWorkbench();
|
|
459
|
+
const repairWorkbench = buildUnknownCostRepairWorkbench(range.filters);
|
|
460
|
+
const nextRepairGroup =
|
|
461
|
+
repairWorkbench.groups.find((group) => group.review.status !== "ignored" && group.review.status !== "resolved")
|
|
462
|
+
?? repairWorkbench.groups[0]
|
|
463
|
+
?? null;
|
|
464
|
+
const repairFocusHref = mergeHrefParams(nextRepairGroup?.itemHref ?? "/repair", rangeLinkParams);
|
|
465
|
+
const unknownCostEvidenceHref = evidenceLinks["unknown-cost"];
|
|
269
466
|
const firstRunStatus = buildFirstRunStatus({
|
|
270
467
|
rootCount: roots.length,
|
|
271
468
|
pricedModelCount: trust.pricedModelCount,
|
|
@@ -360,7 +557,7 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
360
557
|
`${formatTokens(summary.outputTokens)} output`,
|
|
361
558
|
`${formatTokens(summary.cachedTokens)} cached`
|
|
362
559
|
]}
|
|
363
|
-
href={
|
|
560
|
+
href={evidenceLinks["processed-tokens"]}
|
|
364
561
|
actionLabel="View evidence"
|
|
365
562
|
icon={Database}
|
|
366
563
|
/>
|
|
@@ -373,7 +570,7 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
373
570
|
`${formatTokens(summary.outputTokens)} output`,
|
|
374
571
|
`${formatTokens(summary.reasoningTokens)} reasoning`
|
|
375
572
|
]}
|
|
376
|
-
href={
|
|
573
|
+
href={evidenceLinks["non-cache-tokens"]}
|
|
377
574
|
actionLabel="View evidence"
|
|
378
575
|
icon={Database}
|
|
379
576
|
/>
|
|
@@ -385,21 +582,24 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
385
582
|
`${formatTokens(summary.cacheReadTokens)} read`,
|
|
386
583
|
`${formatTokens(summary.cacheWriteTokens)} write`
|
|
387
584
|
]}
|
|
388
|
-
href={
|
|
585
|
+
href={evidenceLinks["cached-tokens"]}
|
|
389
586
|
actionLabel="View evidence"
|
|
390
|
-
icon={
|
|
587
|
+
icon={Layers}
|
|
391
588
|
/>
|
|
392
589
|
<MetricCard
|
|
393
590
|
label="Estimated cost"
|
|
394
591
|
value={formatCurrency(summary.totalCost)}
|
|
592
|
+
valueClassName="break-normal whitespace-nowrap text-2xl"
|
|
395
593
|
description="Cost is calculated from editable model prices. Unknown means a model price or usable token count is missing."
|
|
396
594
|
detailItems={[
|
|
397
595
|
`${formatCurrency(summary.exactCost)} exact`,
|
|
398
596
|
`${formatCurrency(summary.estimatedCost)} estimated`,
|
|
399
597
|
`${summary.unknownCostInteractions.toLocaleString()} unknown`
|
|
400
598
|
]}
|
|
401
|
-
href={summary.unknownCostInteractions > 0 ?
|
|
402
|
-
actionLabel={summary.unknownCostInteractions > 0 ? "
|
|
599
|
+
href={summary.unknownCostInteractions > 0 ? repairFocusHref : evidenceLinks["estimated-cost"]}
|
|
600
|
+
actionLabel={summary.unknownCostInteractions > 0 ? "Open next repair item" : "View evidence"}
|
|
601
|
+
secondaryHref={summary.unknownCostInteractions > 0 ? unknownCostEvidenceHref : undefined}
|
|
602
|
+
secondaryActionLabel="View unknown-cost evidence"
|
|
403
603
|
icon={Coins}
|
|
404
604
|
/>
|
|
405
605
|
<MetricCard
|
|
@@ -407,7 +607,7 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
407
607
|
value={summary.sessions.toLocaleString()}
|
|
408
608
|
description="Imported local CLI sessions and interactions in the selected period."
|
|
409
609
|
detailItems={[`${summary.interactions.toLocaleString()} interactions`]}
|
|
410
|
-
href={
|
|
610
|
+
href={evidenceLinks.sessions}
|
|
411
611
|
actionLabel="View evidence"
|
|
412
612
|
icon={MessageSquare}
|
|
413
613
|
/>
|
|
@@ -434,6 +634,25 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
434
634
|
</Card>
|
|
435
635
|
</div>
|
|
436
636
|
|
|
637
|
+
{summary.interactions > 0 ? (
|
|
638
|
+
<OverviewTrustStrip
|
|
639
|
+
latestScan={doctorReport.latestScan}
|
|
640
|
+
confidence={trust.confidence}
|
|
641
|
+
repairHref={repairFocusHref}
|
|
642
|
+
processedTokensHref={evidenceLinks["processed-tokens"]}
|
|
643
|
+
/>
|
|
644
|
+
) : null}
|
|
645
|
+
|
|
646
|
+
{summary.interactions > 0 ? (
|
|
647
|
+
<DataReadinessPanel
|
|
648
|
+
report={doctorReport}
|
|
649
|
+
selectedInteractions={summary.interactions}
|
|
650
|
+
selectedCachedTokens={summary.cachedTokens}
|
|
651
|
+
repairHref={repairFocusHref}
|
|
652
|
+
cachedEvidenceHref={evidenceLinks["cached-tokens"]}
|
|
653
|
+
/>
|
|
654
|
+
) : null}
|
|
655
|
+
|
|
437
656
|
<UsageGuardrailsPanel progress={data.usageGuardrails} />
|
|
438
657
|
|
|
439
658
|
<Card>
|
|
@@ -443,19 +662,34 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
443
662
|
Local rules ranked from your scan, pricing, parser, project, and cache data.
|
|
444
663
|
</CardDescription>
|
|
445
664
|
</CardHeader>
|
|
446
|
-
<CardContent className="
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
<
|
|
450
|
-
<
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
665
|
+
<CardContent className="p-0">
|
|
666
|
+
<ol className="grid divide-y overflow-hidden lg:grid-cols-3 lg:divide-x lg:divide-y-0">
|
|
667
|
+
{data.recommendations.slice(0, 3).map((item, index) => (
|
|
668
|
+
<li key={item.id} className="min-w-0">
|
|
669
|
+
<Link
|
|
670
|
+
href={item.href}
|
|
671
|
+
className="group flex h-full min-w-0 gap-3 px-4 py-4 transition-colors hover:bg-muted/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
672
|
+
>
|
|
673
|
+
<span className="mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-border text-xs font-semibold tabular-nums text-muted-foreground">
|
|
674
|
+
{index + 1}
|
|
675
|
+
</span>
|
|
676
|
+
<span className="min-w-0">
|
|
677
|
+
<span className="flex min-w-0 flex-wrap items-center gap-2">
|
|
678
|
+
<span className="text-sm font-semibold text-foreground">{item.title}</span>
|
|
679
|
+
<Badge variant={item.severity === "high" ? "destructive" : item.severity === "medium" ? "warning" : "secondary"}>
|
|
680
|
+
{item.severity}
|
|
681
|
+
</Badge>
|
|
682
|
+
</span>
|
|
683
|
+
<span className="mt-1 block text-xs leading-relaxed text-muted-foreground">{item.evidence}</span>
|
|
684
|
+
<span className="mt-2 inline-flex items-center gap-1.5 text-xs font-medium text-primary">
|
|
685
|
+
{item.action}
|
|
686
|
+
<ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
|
|
687
|
+
</span>
|
|
688
|
+
</span>
|
|
689
|
+
</Link>
|
|
690
|
+
</li>
|
|
691
|
+
))}
|
|
692
|
+
</ol>
|
|
459
693
|
</CardContent>
|
|
460
694
|
</Card>
|
|
461
695
|
|
|
@@ -521,8 +755,8 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
521
755
|
</CardDescription>
|
|
522
756
|
</div>
|
|
523
757
|
<Button asChild variant="outline" size="sm">
|
|
524
|
-
<Link href=
|
|
525
|
-
Open
|
|
758
|
+
<Link href={repairFocusHref}>
|
|
759
|
+
Open next repair <ArrowRight className="h-4 w-4" />
|
|
526
760
|
</Link>
|
|
527
761
|
</Button>
|
|
528
762
|
</CardHeader>
|
|
@@ -558,17 +792,23 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
558
792
|
<TableCell>{row.interactions.toLocaleString()}</TableCell>
|
|
559
793
|
<TableCell>{formatTokens(row.totalTokens)}</TableCell>
|
|
560
794
|
<TableCell className="max-w-72 truncate">
|
|
561
|
-
<Link href={row.sourceHref} title={row.sourceFile}>
|
|
795
|
+
<Link href={mergeHrefParams(row.sourceHref, rangeLinkParams)} title={row.sourceFile}>
|
|
562
796
|
<MonoText className="text-muted-foreground underline-offset-4 hover:underline">{row.sourceFile}</MonoText>
|
|
563
797
|
</Link>
|
|
564
798
|
</TableCell>
|
|
565
799
|
<TableCell>
|
|
566
800
|
<div className="flex flex-wrap gap-2">
|
|
567
|
-
<Link href={row.repairHref} className="font-medium text-primary underline-offset-4 hover:underline">
|
|
801
|
+
<Link href={mergeHrefParams(row.repairHref, rangeLinkParams)} className="font-medium text-primary underline-offset-4 hover:underline">
|
|
568
802
|
{row.pricingHref ? "Configure price" : "Review parser"}
|
|
569
803
|
</Link>
|
|
804
|
+
<Link href={mergeHrefParams(row.itemHref, rangeLinkParams)} className="font-medium text-muted-foreground underline-offset-4 hover:underline">
|
|
805
|
+
Item
|
|
806
|
+
</Link>
|
|
807
|
+
<Link href={mergeHrefParams(row.sourceHref, rangeLinkParams)} className="font-medium text-muted-foreground underline-offset-4 hover:underline">
|
|
808
|
+
Evidence
|
|
809
|
+
</Link>
|
|
570
810
|
{row.pricingHref ? (
|
|
571
|
-
<Link href={row.parserHref} className="font-medium text-muted-foreground underline-offset-4 hover:underline">
|
|
811
|
+
<Link href={mergeHrefParams(row.parserHref, rangeLinkParams)} className="font-medium text-muted-foreground underline-offset-4 hover:underline">
|
|
572
812
|
Parser
|
|
573
813
|
</Link>
|
|
574
814
|
) : null}
|
package/app/pricing/page.tsx
CHANGED
|
@@ -8,7 +8,7 @@ export const dynamic = "force-dynamic";
|
|
|
8
8
|
export default async function PricingPage({
|
|
9
9
|
searchParams
|
|
10
10
|
}: {
|
|
11
|
-
searchParams?: Promise<{ model?: string }>;
|
|
11
|
+
searchParams?: Promise<{ model?: string; returnTo?: string }>;
|
|
12
12
|
}) {
|
|
13
13
|
const params = await searchParams;
|
|
14
14
|
const data = getAnalyticsData();
|
|
@@ -21,6 +21,7 @@ export default async function PricingPage({
|
|
|
21
21
|
<PricingSettings
|
|
22
22
|
initialRows={getPricingRows()}
|
|
23
23
|
initialModel={params?.model}
|
|
24
|
+
returnTo={params?.returnTo}
|
|
24
25
|
aliasSuggestions={data.modelAliasSuggestions}
|
|
25
26
|
/>
|
|
26
27
|
</div>
|