selftune 0.2.22 → 0.2.23
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 +4 -2
- package/apps/local-dashboard/dist/assets/index-CwOtTrUS.css +1 -0
- package/apps/local-dashboard/dist/assets/index-f1HQpbeH.js +59 -0
- package/apps/local-dashboard/dist/assets/vendor-ui-jVSaIZey.js +12 -0
- package/apps/local-dashboard/dist/index.html +3 -3
- package/cli/selftune/adapters/pi/hook.ts +273 -0
- package/cli/selftune/adapters/pi/install.ts +207 -0
- package/cli/selftune/constants.ts +10 -1
- package/cli/selftune/dashboard-contract.ts +14 -0
- package/cli/selftune/evolution/engines/judge-engine.ts +96 -0
- package/cli/selftune/evolution/engines/replay-engine.ts +158 -0
- package/cli/selftune/evolution/evidence.ts +2 -6
- package/cli/selftune/evolution/evolve-body.ts +73 -20
- package/cli/selftune/evolution/validate-body.ts +78 -42
- package/cli/selftune/evolution/validate-routing.ts +45 -104
- package/cli/selftune/hooks/skill-eval.ts +2 -1
- package/cli/selftune/hooks-shared/types.ts +1 -0
- package/cli/selftune/index.ts +23 -5
- package/cli/selftune/ingestors/pi-ingest.ts +726 -0
- package/cli/selftune/init.ts +11 -1
- package/cli/selftune/localdb/direct-write.ts +85 -0
- package/cli/selftune/localdb/materialize.ts +6 -7
- package/cli/selftune/localdb/queries.ts +126 -0
- package/cli/selftune/localdb/schema.ts +38 -0
- package/cli/selftune/observability.ts +8 -1
- package/cli/selftune/orchestrate.ts +43 -0
- package/cli/selftune/registry/client.ts +74 -0
- package/cli/selftune/registry/history.ts +54 -0
- package/cli/selftune/registry/index.ts +90 -0
- package/cli/selftune/registry/install.ts +141 -0
- package/cli/selftune/registry/list.ts +44 -0
- package/cli/selftune/registry/push.ts +171 -0
- package/cli/selftune/registry/rollback.ts +49 -0
- package/cli/selftune/registry/status.ts +62 -0
- package/cli/selftune/registry/sync.ts +125 -0
- package/cli/selftune/repair/skill-usage.ts +4 -1
- package/cli/selftune/status.ts +31 -0
- package/cli/selftune/sync.ts +127 -23
- package/cli/selftune/types.ts +2 -1
- package/cli/selftune/utils/jsonl.ts +1 -30
- package/cli/selftune/utils/skill-discovery.ts +22 -0
- package/node_modules/@selftune/telemetry-contract/fixtures/evidence-only-push.ts +1 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/golden.test.ts +0 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +1 -1
- package/node_modules/@selftune/telemetry-contract/package.json +1 -1
- package/node_modules/@selftune/telemetry-contract/src/index.ts +1 -0
- package/node_modules/@selftune/telemetry-contract/src/schemas.ts +22 -4
- package/node_modules/@selftune/telemetry-contract/src/types.ts +1 -12
- package/node_modules/@selftune/telemetry-contract/tests/compatibility.test.ts +0 -1
- package/package.json +1 -1
- package/packages/telemetry-contract/fixtures/evidence-only-push.ts +1 -1
- package/packages/telemetry-contract/fixtures/golden.test.ts +0 -1
- package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +1 -1
- package/packages/telemetry-contract/package.json +1 -1
- package/packages/telemetry-contract/src/index.ts +1 -0
- package/packages/telemetry-contract/src/schemas.ts +22 -4
- package/packages/telemetry-contract/src/types.ts +1 -12
- package/packages/telemetry-contract/tests/compatibility.test.ts +0 -1
- package/packages/ui/AGENTS.md +16 -0
- package/packages/ui/README.md +1 -1
- package/packages/ui/package.json +1 -1
- package/packages/ui/src/components/ActivityTimeline.tsx +152 -168
- package/packages/ui/src/components/AnalyticsCharts.tsx +344 -0
- package/packages/ui/src/components/EvidenceViewer.tsx +153 -443
- package/packages/ui/src/components/EvolutionTimeline.tsx +34 -87
- package/packages/ui/src/components/InfoTip.tsx +1 -2
- package/packages/ui/src/components/InvocationsPanel.tsx +413 -0
- package/packages/ui/src/components/JobHistoryTimeline.tsx +156 -0
- package/packages/ui/src/components/OrchestrateRunsPanel.tsx +18 -36
- package/packages/ui/src/components/OverviewPanels.tsx +652 -0
- package/packages/ui/src/components/PipelineStatusBar.tsx +65 -0
- package/packages/ui/src/components/SkillReportGuide.tsx +215 -0
- package/packages/ui/src/components/SkillReportPanels.tsx +919 -0
- package/packages/ui/src/components/SkillsLibrary.tsx +437 -0
- package/packages/ui/src/components/index.ts +56 -1
- package/packages/ui/src/components/section-cards.tsx +18 -35
- package/packages/ui/src/components/skill-health-grid.tsx +47 -37
- package/packages/ui/src/lib/constants.tsx +0 -1
- package/packages/ui/src/primitives/card.tsx +1 -1
- package/packages/ui/src/primitives/checkbox.tsx +1 -1
- package/packages/ui/src/primitives/dropdown-menu.tsx +2 -2
- package/packages/ui/src/primitives/select.tsx +2 -2
- package/packages/ui/src/types.ts +172 -4
- package/skill/SKILL.md +18 -4
- package/skill/Workflows/Ingest.md +60 -2
- package/skill/Workflows/Initialize.md +8 -5
- package/skill/Workflows/PlatformHooks.md +19 -3
- package/skill/Workflows/Registry.md +99 -0
- package/skill/Workflows/Sync.md +3 -1
- package/apps/local-dashboard/dist/assets/index-D8O-RG1I.js +0 -60
- package/apps/local-dashboard/dist/assets/index-_EcLywDg.css +0 -1
- package/apps/local-dashboard/dist/assets/vendor-ui-CGEmUayx.js +0 -12
- package/cli/selftune/utils/html.ts +0 -27
- package/packages/ui/src/components/RecentActivityFeed.tsx +0 -117
|
@@ -0,0 +1,919 @@
|
|
|
1
|
+
import { InfoTip } from "./InfoTip";
|
|
2
|
+
import { formatRate, timeAgo } from "../lib/format";
|
|
3
|
+
import {
|
|
4
|
+
Badge,
|
|
5
|
+
Button,
|
|
6
|
+
Card,
|
|
7
|
+
CardAction,
|
|
8
|
+
CardContent,
|
|
9
|
+
CardDescription,
|
|
10
|
+
CardHeader,
|
|
11
|
+
CardTitle,
|
|
12
|
+
Table,
|
|
13
|
+
TableBody,
|
|
14
|
+
TableCell,
|
|
15
|
+
TableHead,
|
|
16
|
+
TableHeader,
|
|
17
|
+
TableRow,
|
|
18
|
+
Tabs,
|
|
19
|
+
TabsContent,
|
|
20
|
+
TabsList,
|
|
21
|
+
TabsTrigger,
|
|
22
|
+
} from "../primitives";
|
|
23
|
+
import {
|
|
24
|
+
ActivityIcon,
|
|
25
|
+
BarChart3Icon,
|
|
26
|
+
DatabaseIcon,
|
|
27
|
+
GitBranchIcon,
|
|
28
|
+
SearchIcon,
|
|
29
|
+
TargetIcon,
|
|
30
|
+
} from "lucide-react";
|
|
31
|
+
import type { ReactNode } from "react";
|
|
32
|
+
|
|
33
|
+
import type {
|
|
34
|
+
ExampleRow,
|
|
35
|
+
TrustFields,
|
|
36
|
+
TrustState,
|
|
37
|
+
ObservationKind,
|
|
38
|
+
HistoricalContext,
|
|
39
|
+
} from "../types";
|
|
40
|
+
|
|
41
|
+
export function observationBadge(kind: ObservationKind | null | undefined): {
|
|
42
|
+
label: string;
|
|
43
|
+
variant: "default" | "secondary" | "destructive" | "outline";
|
|
44
|
+
} | null {
|
|
45
|
+
switch (kind) {
|
|
46
|
+
case "repaired_contextual_miss":
|
|
47
|
+
return { label: "repaired miss", variant: "destructive" };
|
|
48
|
+
case "repaired_trigger":
|
|
49
|
+
return { label: "repaired trigger", variant: "secondary" };
|
|
50
|
+
case "legacy_materialized":
|
|
51
|
+
return { label: "legacy row", variant: "outline" };
|
|
52
|
+
default:
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function historicalContextBadge(context: HistoricalContext | null | undefined): {
|
|
58
|
+
label: string;
|
|
59
|
+
variant: "default" | "secondary" | "destructive" | "outline";
|
|
60
|
+
} | null {
|
|
61
|
+
switch (context) {
|
|
62
|
+
case "previously_missed":
|
|
63
|
+
return { label: "previously missed", variant: "secondary" };
|
|
64
|
+
default:
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function ExampleRowItem({ row }: { row: ExampleRow }) {
|
|
70
|
+
const workspace = row.workspace_path ? row.workspace_path.split("/").slice(-2).join("/") : null;
|
|
71
|
+
const observation = observationBadge(row.observation_kind);
|
|
72
|
+
const historicalContext = historicalContextBadge(row.historical_context);
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<TableRow className={!row.triggered ? "bg-destructive/5" : ""}>
|
|
76
|
+
<TableCell
|
|
77
|
+
className="max-w-[420px] truncate py-2 text-[12px]"
|
|
78
|
+
title={row.query_text || undefined}
|
|
79
|
+
>
|
|
80
|
+
{row.query_text || (
|
|
81
|
+
<span className="italic text-muted-foreground/40">No prompt recorded</span>
|
|
82
|
+
)}
|
|
83
|
+
</TableCell>
|
|
84
|
+
<TableCell className="py-2">
|
|
85
|
+
<div className="flex items-center gap-1.5">
|
|
86
|
+
{row.triggered ? (
|
|
87
|
+
<Badge
|
|
88
|
+
variant="outline"
|
|
89
|
+
className="border-green-600/30 text-[10px] font-normal text-green-600"
|
|
90
|
+
>
|
|
91
|
+
triggered
|
|
92
|
+
</Badge>
|
|
93
|
+
) : (
|
|
94
|
+
<Badge variant="destructive" className="text-[10px] font-normal">
|
|
95
|
+
missed
|
|
96
|
+
</Badge>
|
|
97
|
+
)}
|
|
98
|
+
{observation && (
|
|
99
|
+
<Badge variant={observation.variant} className="text-[10px] font-normal">
|
|
100
|
+
{observation.label}
|
|
101
|
+
</Badge>
|
|
102
|
+
)}
|
|
103
|
+
{historicalContext && (
|
|
104
|
+
<Badge variant={historicalContext.variant} className="text-[10px] font-normal">
|
|
105
|
+
{historicalContext.label}
|
|
106
|
+
</Badge>
|
|
107
|
+
)}
|
|
108
|
+
</div>
|
|
109
|
+
</TableCell>
|
|
110
|
+
<TableCell className="py-2 font-mono text-[11px] tabular-nums text-muted-foreground">
|
|
111
|
+
{row.confidence != null ? `${Math.round(row.confidence * 100)}%` : "Not recorded"}
|
|
112
|
+
</TableCell>
|
|
113
|
+
<TableCell className="py-2">
|
|
114
|
+
{row.invocation_mode ? (
|
|
115
|
+
<Badge variant="secondary" className="text-[10px] font-normal">
|
|
116
|
+
{row.invocation_mode}
|
|
117
|
+
</Badge>
|
|
118
|
+
) : (
|
|
119
|
+
<span className="text-[11px] text-muted-foreground">Unknown mode</span>
|
|
120
|
+
)}
|
|
121
|
+
</TableCell>
|
|
122
|
+
<TableCell className="py-2 text-[11px] text-muted-foreground">
|
|
123
|
+
{row.prompt_kind ?? "Unclassified"}
|
|
124
|
+
</TableCell>
|
|
125
|
+
<TableCell className="py-2 text-[11px] text-muted-foreground">
|
|
126
|
+
{row.source ?? "No data"}
|
|
127
|
+
</TableCell>
|
|
128
|
+
<TableCell className="py-2 text-[11px] text-muted-foreground">
|
|
129
|
+
{row.platform ?? "No data"}
|
|
130
|
+
</TableCell>
|
|
131
|
+
<TableCell
|
|
132
|
+
className="py-2 font-mono text-[11px] text-muted-foreground"
|
|
133
|
+
title={row.workspace_path ?? undefined}
|
|
134
|
+
>
|
|
135
|
+
{workspace ?? "No data"}
|
|
136
|
+
</TableCell>
|
|
137
|
+
<TableCell className="py-2">
|
|
138
|
+
<Badge
|
|
139
|
+
variant={
|
|
140
|
+
row.query_origin === "inline_query"
|
|
141
|
+
? "outline"
|
|
142
|
+
: row.query_origin === "matched_prompt"
|
|
143
|
+
? "secondary"
|
|
144
|
+
: "destructive"
|
|
145
|
+
}
|
|
146
|
+
className="text-[10px] font-normal"
|
|
147
|
+
>
|
|
148
|
+
{row.query_origin}
|
|
149
|
+
</Badge>
|
|
150
|
+
</TableCell>
|
|
151
|
+
</TableRow>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function ExamplesTable({ rows, emptyMessage }: { rows: ExampleRow[]; emptyMessage: string }) {
|
|
156
|
+
if (rows.length === 0) {
|
|
157
|
+
return (
|
|
158
|
+
<div className="flex items-center justify-center py-12 text-sm text-muted-foreground">
|
|
159
|
+
{emptyMessage}
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<div className="themed-scroll max-h-[340px] overflow-auto">
|
|
166
|
+
<Table>
|
|
167
|
+
<TableHeader>
|
|
168
|
+
<TableRow className="sticky top-0 z-10 bg-muted/70 backdrop-blur hover:bg-muted/70">
|
|
169
|
+
<TableHead className="h-8 font-headline text-[10px] uppercase tracking-[0.15em]">
|
|
170
|
+
Prompt
|
|
171
|
+
</TableHead>
|
|
172
|
+
<TableHead className="h-8 w-[80px] font-headline text-[10px] uppercase tracking-[0.15em]">
|
|
173
|
+
Status
|
|
174
|
+
</TableHead>
|
|
175
|
+
<TableHead className="h-8 w-[70px] font-headline text-[10px] uppercase tracking-[0.15em]">
|
|
176
|
+
Confidence
|
|
177
|
+
</TableHead>
|
|
178
|
+
<TableHead className="h-8 w-[80px] font-headline text-[10px] uppercase tracking-[0.15em]">
|
|
179
|
+
Mode
|
|
180
|
+
</TableHead>
|
|
181
|
+
<TableHead className="h-8 w-[80px] font-headline text-[10px] uppercase tracking-[0.15em]">
|
|
182
|
+
Kind
|
|
183
|
+
</TableHead>
|
|
184
|
+
<TableHead className="h-8 w-[70px] font-headline text-[10px] uppercase tracking-[0.15em]">
|
|
185
|
+
Source
|
|
186
|
+
</TableHead>
|
|
187
|
+
<TableHead className="h-8 w-[70px] font-headline text-[10px] uppercase tracking-[0.15em]">
|
|
188
|
+
Platform
|
|
189
|
+
</TableHead>
|
|
190
|
+
<TableHead className="h-8 w-[100px] font-headline text-[10px] uppercase tracking-[0.15em]">
|
|
191
|
+
Workspace
|
|
192
|
+
</TableHead>
|
|
193
|
+
<TableHead className="h-8 w-[100px] font-headline text-[10px] uppercase tracking-[0.15em]">
|
|
194
|
+
Origin
|
|
195
|
+
</TableHead>
|
|
196
|
+
</TableRow>
|
|
197
|
+
</TableHeader>
|
|
198
|
+
<TableBody>
|
|
199
|
+
{rows.map((row, i) => (
|
|
200
|
+
<ExampleRowItem key={`${row.session_id}-${i}`} row={row} />
|
|
201
|
+
))}
|
|
202
|
+
</TableBody>
|
|
203
|
+
</Table>
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function RateBar({
|
|
209
|
+
label,
|
|
210
|
+
value,
|
|
211
|
+
warn,
|
|
212
|
+
}: {
|
|
213
|
+
label: string;
|
|
214
|
+
value: number | null | undefined;
|
|
215
|
+
warn?: boolean;
|
|
216
|
+
}) {
|
|
217
|
+
const pct = value != null ? Math.round(value * 100) : null;
|
|
218
|
+
return (
|
|
219
|
+
<div className="flex items-center gap-3">
|
|
220
|
+
<span className="w-40 shrink-0 font-headline text-[10px] uppercase tracking-[0.2em] text-muted-foreground">
|
|
221
|
+
{label}
|
|
222
|
+
</span>
|
|
223
|
+
<div className="h-2 flex-1 overflow-hidden rounded-full bg-muted">
|
|
224
|
+
{pct != null && (
|
|
225
|
+
<div
|
|
226
|
+
className={`h-full rounded-full transition-all ${warn ? "bg-destructive" : "bg-primary"}`}
|
|
227
|
+
style={{ width: `${Math.min(pct, 100)}%` }}
|
|
228
|
+
/>
|
|
229
|
+
)}
|
|
230
|
+
</div>
|
|
231
|
+
<span
|
|
232
|
+
className={`w-10 text-right font-mono text-xs tabular-nums ${warn ? "text-destructive" : "text-muted-foreground"}`}
|
|
233
|
+
>
|
|
234
|
+
{pct != null ? `${pct}%` : "No data"}
|
|
235
|
+
</span>
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function BreakdownTable({
|
|
241
|
+
title,
|
|
242
|
+
data,
|
|
243
|
+
}: {
|
|
244
|
+
title: string;
|
|
245
|
+
data: Array<{ source?: string; kind?: string; count: number }> | null | undefined;
|
|
246
|
+
}) {
|
|
247
|
+
if (!data || data.length === 0) return null;
|
|
248
|
+
|
|
249
|
+
const labelForValue = (value: string) => {
|
|
250
|
+
switch (value) {
|
|
251
|
+
case "repaired_contextual_miss":
|
|
252
|
+
return "repaired contextual miss";
|
|
253
|
+
case "repaired_trigger":
|
|
254
|
+
return "repaired trigger";
|
|
255
|
+
case "legacy_materialized":
|
|
256
|
+
return "legacy materialized";
|
|
257
|
+
default:
|
|
258
|
+
return value;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const entries = data
|
|
263
|
+
.map((d) => [labelForValue(d.source ?? d.kind ?? "(unknown)"), d.count] as [string, number])
|
|
264
|
+
.sort(([, a], [, b]) => b - a);
|
|
265
|
+
const total = entries.reduce((s, [, v]) => s + v, 0);
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<div>
|
|
269
|
+
<h4 className="mb-2 font-headline text-[10px] uppercase tracking-[0.2em] text-muted-foreground">
|
|
270
|
+
{title}
|
|
271
|
+
</h4>
|
|
272
|
+
<Table>
|
|
273
|
+
<TableHeader>
|
|
274
|
+
<TableRow className="hover:bg-transparent">
|
|
275
|
+
<TableHead className="h-7 font-headline text-[10px] uppercase tracking-[0.15em]">
|
|
276
|
+
Value
|
|
277
|
+
</TableHead>
|
|
278
|
+
<TableHead className="h-7 w-[80px] text-right font-headline text-[10px] uppercase tracking-[0.15em]">
|
|
279
|
+
Count
|
|
280
|
+
</TableHead>
|
|
281
|
+
<TableHead className="h-7 w-[80px] text-right font-headline text-[10px] uppercase tracking-[0.15em]">
|
|
282
|
+
Rate
|
|
283
|
+
</TableHead>
|
|
284
|
+
</TableRow>
|
|
285
|
+
</TableHeader>
|
|
286
|
+
<TableBody>
|
|
287
|
+
{entries.map(([value, count]) => (
|
|
288
|
+
<TableRow key={value}>
|
|
289
|
+
<TableCell className="py-2 text-[11px]">{value}</TableCell>
|
|
290
|
+
<TableCell className="py-2 text-right font-mono text-[11px]">{count}</TableCell>
|
|
291
|
+
<TableCell className="py-2 text-right font-mono text-[11px] text-muted-foreground">
|
|
292
|
+
{total > 0 ? `${Math.round((count / total) * 100)}%` : "0%"}
|
|
293
|
+
</TableCell>
|
|
294
|
+
</TableRow>
|
|
295
|
+
))}
|
|
296
|
+
</TableBody>
|
|
297
|
+
</Table>
|
|
298
|
+
</div>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function SkillReportTopRow({
|
|
303
|
+
nextAction,
|
|
304
|
+
latestDecision,
|
|
305
|
+
}: {
|
|
306
|
+
nextAction: {
|
|
307
|
+
icon: ReactNode;
|
|
308
|
+
text: string;
|
|
309
|
+
actionLabel: string;
|
|
310
|
+
variant: "default" | "secondary" | "destructive" | "outline";
|
|
311
|
+
};
|
|
312
|
+
latestDecision?:
|
|
313
|
+
| {
|
|
314
|
+
action: string;
|
|
315
|
+
timestamp: string | null;
|
|
316
|
+
evolutionCount: number;
|
|
317
|
+
}
|
|
318
|
+
| undefined;
|
|
319
|
+
}) {
|
|
320
|
+
const nextActionBorder =
|
|
321
|
+
nextAction.variant === "destructive"
|
|
322
|
+
? "border-destructive/25"
|
|
323
|
+
: nextAction.variant === "default"
|
|
324
|
+
? "border-primary/20"
|
|
325
|
+
: "border-border/15";
|
|
326
|
+
|
|
327
|
+
return (
|
|
328
|
+
<div className="grid grid-cols-1 gap-3 @4xl/main:grid-cols-12">
|
|
329
|
+
<Card
|
|
330
|
+
className={`rounded-xl border bg-muted/35 shadow-none ${latestDecision ? "@4xl/main:col-span-8" : "@4xl/main:col-span-12"} ${nextActionBorder}`}
|
|
331
|
+
>
|
|
332
|
+
<CardContent className="flex items-start gap-3 px-4 py-4">
|
|
333
|
+
<div className="shrink-0 pt-0.5">{nextAction.icon}</div>
|
|
334
|
+
<div className="flex-1">
|
|
335
|
+
<h3 className="mb-1 font-headline text-[10px] uppercase tracking-[0.18em] text-muted-foreground">
|
|
336
|
+
Next Best Action
|
|
337
|
+
</h3>
|
|
338
|
+
<p className="text-[15px] font-medium leading-6 text-foreground">{nextAction.text}</p>
|
|
339
|
+
</div>
|
|
340
|
+
<Badge variant={nextAction.variant} className="shrink-0 self-start text-[10px]">
|
|
341
|
+
{nextAction.actionLabel}
|
|
342
|
+
</Badge>
|
|
343
|
+
</CardContent>
|
|
344
|
+
</Card>
|
|
345
|
+
|
|
346
|
+
{latestDecision && (
|
|
347
|
+
<Card className="rounded-xl border border-border/10 bg-muted/20 @4xl/main:col-span-4">
|
|
348
|
+
<CardContent className="flex h-full items-start gap-3 px-4 py-4">
|
|
349
|
+
<GitBranchIcon className="mt-0.5 size-4 shrink-0 text-primary/80" />
|
|
350
|
+
<div className="min-w-0 flex-1">
|
|
351
|
+
<h3 className="mb-1 font-headline text-[10px] uppercase tracking-[0.2em] text-muted-foreground">
|
|
352
|
+
Latest Decision
|
|
353
|
+
</h3>
|
|
354
|
+
<p className="truncate text-sm font-medium leading-6">{latestDecision.action}</p>
|
|
355
|
+
{latestDecision.timestamp && (
|
|
356
|
+
<p className="mt-0.5 font-mono text-[10px] text-muted-foreground">
|
|
357
|
+
{timeAgo(latestDecision.timestamp)}
|
|
358
|
+
</p>
|
|
359
|
+
)}
|
|
360
|
+
</div>
|
|
361
|
+
<Badge variant="outline" className="shrink-0 self-start text-[9px]">
|
|
362
|
+
{latestDecision.evolutionCount} evolution
|
|
363
|
+
{latestDecision.evolutionCount !== 1 ? "s" : ""}
|
|
364
|
+
</Badge>
|
|
365
|
+
</CardContent>
|
|
366
|
+
</Card>
|
|
367
|
+
)}
|
|
368
|
+
</div>
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function narrativeObservedText({
|
|
373
|
+
checks,
|
|
374
|
+
sessions,
|
|
375
|
+
promptLinkRate,
|
|
376
|
+
}: {
|
|
377
|
+
checks: number;
|
|
378
|
+
sessions: number;
|
|
379
|
+
promptLinkRate: number | null | undefined;
|
|
380
|
+
}) {
|
|
381
|
+
const promptClause =
|
|
382
|
+
promptLinkRate != null
|
|
383
|
+
? ` It could link ${formatRate(promptLinkRate)} of those checks back to prompts.`
|
|
384
|
+
: "";
|
|
385
|
+
return `Selftune watched ${checks} skill checks across ${sessions} sessions.${promptClause}`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function narrativeDiagnosisText({
|
|
389
|
+
missRate,
|
|
390
|
+
missedTriggers,
|
|
391
|
+
systemLikeRate,
|
|
392
|
+
}: {
|
|
393
|
+
missRate: number | null | undefined;
|
|
394
|
+
missedTriggers: number | null | undefined;
|
|
395
|
+
systemLikeRate: number | null | undefined;
|
|
396
|
+
}) {
|
|
397
|
+
if ((missedTriggers ?? 0) > 0 && missRate != null) {
|
|
398
|
+
return `It found ${missedTriggers} likely misses (${formatRate(missRate)} miss rate), which means people asked for this skill and it probably should have triggered.`;
|
|
399
|
+
}
|
|
400
|
+
if ((systemLikeRate ?? 0) > 0.05) {
|
|
401
|
+
return `Routing looks mostly stable, but some records appear system-generated, so selftune is being cautious about making strong claims.`;
|
|
402
|
+
}
|
|
403
|
+
return `Routing looks consistent in the current sample, with no strong signs that this skill is missing obvious requests.`;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function narrativeDecisionText({
|
|
407
|
+
trustState,
|
|
408
|
+
latestAction,
|
|
409
|
+
nextActionText,
|
|
410
|
+
}: {
|
|
411
|
+
trustState: TrustState;
|
|
412
|
+
latestAction?: string | null;
|
|
413
|
+
nextActionText: string;
|
|
414
|
+
}) {
|
|
415
|
+
switch (trustState) {
|
|
416
|
+
case "validated":
|
|
417
|
+
return `Selftune found a candidate that looks promising, but it has not been deployed yet. ${nextActionText}`;
|
|
418
|
+
case "deployed":
|
|
419
|
+
return `A change has already been deployed for this skill. Selftune is now watching for regressions in real use.`;
|
|
420
|
+
case "rolled_back":
|
|
421
|
+
return `A previous change was rolled back, so the live skill is back on the safer version while selftune keeps observing.`;
|
|
422
|
+
case "watch":
|
|
423
|
+
return `Selftune sees enough signal to keep a close eye on this skill, but not enough to blindly change it. ${nextActionText}`;
|
|
424
|
+
case "observed":
|
|
425
|
+
return `Selftune is still learning how people use this skill before making stronger recommendations.`;
|
|
426
|
+
case "low_sample":
|
|
427
|
+
return `There is not enough evidence yet to trust a big change here. Selftune is still collecting examples.`;
|
|
428
|
+
default:
|
|
429
|
+
return latestAction
|
|
430
|
+
? `The latest automated decision for this skill was ${latestAction}. ${nextActionText}`
|
|
431
|
+
: nextActionText;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function StoryStep({ title, icon, body }: { title: string; icon: ReactNode; body: string }) {
|
|
436
|
+
return (
|
|
437
|
+
<div className="rounded-xl border border-border/10 bg-muted/20 p-4">
|
|
438
|
+
<div className="mb-2 flex items-center gap-2">
|
|
439
|
+
<div className="text-primary/80">{icon}</div>
|
|
440
|
+
<h3 className="font-headline text-[10px] uppercase tracking-[0.18em] text-muted-foreground">
|
|
441
|
+
{title}
|
|
442
|
+
</h3>
|
|
443
|
+
</div>
|
|
444
|
+
<p className="text-sm leading-6 text-foreground/90">{body}</p>
|
|
445
|
+
</div>
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export function SkillTrustNarrativePanel({
|
|
450
|
+
trustState,
|
|
451
|
+
coverage,
|
|
452
|
+
evidenceQuality,
|
|
453
|
+
routingQuality,
|
|
454
|
+
evolutionState,
|
|
455
|
+
dataHygiene,
|
|
456
|
+
fallbackChecks,
|
|
457
|
+
fallbackSessions,
|
|
458
|
+
nextActionText,
|
|
459
|
+
onOpenGuide,
|
|
460
|
+
}: {
|
|
461
|
+
trustState: TrustState;
|
|
462
|
+
coverage?: TrustFields["coverage"];
|
|
463
|
+
evidenceQuality?: TrustFields["evidence_quality"];
|
|
464
|
+
routingQuality?: TrustFields["routing_quality"];
|
|
465
|
+
evolutionState?: TrustFields["evolution_state"];
|
|
466
|
+
dataHygiene?: TrustFields["data_hygiene"];
|
|
467
|
+
fallbackChecks: number;
|
|
468
|
+
fallbackSessions: number;
|
|
469
|
+
nextActionText: string;
|
|
470
|
+
onOpenGuide?: () => void;
|
|
471
|
+
}) {
|
|
472
|
+
const checks = coverage?.checks ?? fallbackChecks;
|
|
473
|
+
const sessions = coverage?.sessions ?? fallbackSessions;
|
|
474
|
+
const rawChecks = dataHygiene?.raw_checks ?? checks;
|
|
475
|
+
const internalRows = dataHygiene?.internal_prompt_rows ?? 0;
|
|
476
|
+
const legacyRows = dataHygiene?.legacy_rows ?? 0;
|
|
477
|
+
const repairedRows = dataHygiene?.repaired_rows ?? 0;
|
|
478
|
+
const excludedRows = Math.max(rawChecks - checks, 0);
|
|
479
|
+
const showTrustNote = excludedRows > 0 || legacyRows > 0 || repairedRows > 0;
|
|
480
|
+
|
|
481
|
+
return (
|
|
482
|
+
<Card className="rounded-xl border border-border/10 bg-card/95">
|
|
483
|
+
<CardHeader className="gap-2 px-4 py-4">
|
|
484
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
485
|
+
<div className="space-y-1">
|
|
486
|
+
<CardTitle className="text-base">How selftune is improving this skill</CardTitle>
|
|
487
|
+
<CardDescription>
|
|
488
|
+
Read this first if you want the plain-English version before diving into the evidence
|
|
489
|
+
below.
|
|
490
|
+
</CardDescription>
|
|
491
|
+
</div>
|
|
492
|
+
{onOpenGuide && (
|
|
493
|
+
<Button variant="outline" size="sm" onClick={onOpenGuide}>
|
|
494
|
+
How to read this page
|
|
495
|
+
</Button>
|
|
496
|
+
)}
|
|
497
|
+
</div>
|
|
498
|
+
</CardHeader>
|
|
499
|
+
<CardContent className="space-y-4 px-4 pb-4 pt-0">
|
|
500
|
+
{showTrustNote && (
|
|
501
|
+
<div className="rounded-xl border border-primary/10 bg-primary/5 px-4 py-3 text-sm text-muted-foreground">
|
|
502
|
+
<span className="font-medium text-foreground">Trust note:</span> This summary is based
|
|
503
|
+
on <span className="font-medium text-foreground">{checks}</span> operational checks from
|
|
504
|
+
real usage.
|
|
505
|
+
{internalRows > 0 && (
|
|
506
|
+
<>
|
|
507
|
+
{" "}
|
|
508
|
+
<span className="font-medium text-foreground">{internalRows}</span> internal
|
|
509
|
+
selftune eval or optimizer prompts are excluded from trust scoring.
|
|
510
|
+
</>
|
|
511
|
+
)}
|
|
512
|
+
{legacyRows > 0 && (
|
|
513
|
+
<>
|
|
514
|
+
{" "}
|
|
515
|
+
<span className="font-medium text-foreground">{legacyRows}</span> legacy rows stay
|
|
516
|
+
in history only.
|
|
517
|
+
</>
|
|
518
|
+
)}
|
|
519
|
+
{repairedRows > 0 && (
|
|
520
|
+
<>
|
|
521
|
+
{" "}
|
|
522
|
+
<span className="font-medium text-foreground">{repairedRows}</span> repaired misses
|
|
523
|
+
come from transcript replay rather than first-party trigger events.
|
|
524
|
+
</>
|
|
525
|
+
)}
|
|
526
|
+
</div>
|
|
527
|
+
)}
|
|
528
|
+
<div className="grid grid-cols-1 gap-3 @4xl/main:grid-cols-3">
|
|
529
|
+
<StoryStep
|
|
530
|
+
title="What selftune saw"
|
|
531
|
+
icon={<ActivityIcon className="size-4" />}
|
|
532
|
+
body={narrativeObservedText({
|
|
533
|
+
checks,
|
|
534
|
+
sessions,
|
|
535
|
+
promptLinkRate: evidenceQuality?.prompt_link_rate,
|
|
536
|
+
})}
|
|
537
|
+
/>
|
|
538
|
+
<StoryStep
|
|
539
|
+
title="Why it acted"
|
|
540
|
+
icon={<SearchIcon className="size-4" />}
|
|
541
|
+
body={narrativeDiagnosisText({
|
|
542
|
+
missRate: routingQuality?.miss_rate,
|
|
543
|
+
missedTriggers: routingQuality?.missed_triggers,
|
|
544
|
+
systemLikeRate: evidenceQuality?.system_like_rate,
|
|
545
|
+
})}
|
|
546
|
+
/>
|
|
547
|
+
<StoryStep
|
|
548
|
+
title="What happened next"
|
|
549
|
+
icon={<GitBranchIcon className="size-4" />}
|
|
550
|
+
body={narrativeDecisionText({
|
|
551
|
+
trustState,
|
|
552
|
+
latestAction: evolutionState?.latest_action,
|
|
553
|
+
nextActionText,
|
|
554
|
+
})}
|
|
555
|
+
/>
|
|
556
|
+
</div>
|
|
557
|
+
<div className="rounded-xl border border-border/10 bg-muted/15 px-4 py-3 text-sm text-muted-foreground">
|
|
558
|
+
If a proposal is rejected or still pending, your live skill has not changed yet. Selftune
|
|
559
|
+
only earns trust by testing changes before deployment.
|
|
560
|
+
</div>
|
|
561
|
+
</CardContent>
|
|
562
|
+
</Card>
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export function TrustSignalsGrid({
|
|
567
|
+
coverage,
|
|
568
|
+
evidenceQuality,
|
|
569
|
+
routingQuality,
|
|
570
|
+
evolutionState,
|
|
571
|
+
fallbackChecks,
|
|
572
|
+
fallbackSessions,
|
|
573
|
+
fallbackEvidenceRows,
|
|
574
|
+
fallbackEvolutionRows,
|
|
575
|
+
fallbackLatestAction,
|
|
576
|
+
}: {
|
|
577
|
+
coverage?: TrustFields["coverage"];
|
|
578
|
+
evidenceQuality?: TrustFields["evidence_quality"];
|
|
579
|
+
routingQuality?: TrustFields["routing_quality"];
|
|
580
|
+
evolutionState?: TrustFields["evolution_state"];
|
|
581
|
+
fallbackChecks: number;
|
|
582
|
+
fallbackSessions: number;
|
|
583
|
+
fallbackEvidenceRows: number;
|
|
584
|
+
fallbackEvolutionRows: number;
|
|
585
|
+
fallbackLatestAction?: string;
|
|
586
|
+
}) {
|
|
587
|
+
const hasEvolutionData = (evolutionState?.evolution_rows ?? fallbackEvolutionRows) > 0;
|
|
588
|
+
|
|
589
|
+
return (
|
|
590
|
+
<div>
|
|
591
|
+
<h2 className="mb-2 font-headline text-[10px] uppercase tracking-[0.2em] text-muted-foreground">
|
|
592
|
+
Trust Signals
|
|
593
|
+
</h2>
|
|
594
|
+
<div className="grid grid-cols-1 gap-3 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
|
|
595
|
+
<Card className="rounded-xl border border-border/10 bg-muted/20 transition-colors hover:border-border/20 @container/card">
|
|
596
|
+
<CardHeader className="gap-2 px-4 py-3">
|
|
597
|
+
<CardDescription className="flex items-center gap-1.5">
|
|
598
|
+
<ActivityIcon className="size-3.5" />
|
|
599
|
+
<span className="font-headline text-[10px] uppercase tracking-[0.2em] text-muted-foreground">
|
|
600
|
+
Coverage
|
|
601
|
+
</span>
|
|
602
|
+
</CardDescription>
|
|
603
|
+
<CardTitle className="text-[32px] font-semibold leading-none tabular-nums text-foreground">
|
|
604
|
+
{coverage?.checks ?? fallbackChecks}
|
|
605
|
+
</CardTitle>
|
|
606
|
+
<CardAction>
|
|
607
|
+
<span className="font-mono text-[10px] text-muted-foreground">
|
|
608
|
+
{coverage?.sessions ?? fallbackSessions} sessions /{" "}
|
|
609
|
+
{coverage?.workspaces ?? "No data"} dirs
|
|
610
|
+
</span>
|
|
611
|
+
</CardAction>
|
|
612
|
+
</CardHeader>
|
|
613
|
+
</Card>
|
|
614
|
+
|
|
615
|
+
<Card className="rounded-xl border border-border/10 bg-muted/20 transition-colors hover:border-border/20 @container/card">
|
|
616
|
+
<CardHeader className="gap-2 px-4 py-3">
|
|
617
|
+
<CardDescription className="flex items-center gap-1.5">
|
|
618
|
+
<SearchIcon className="size-3.5" />
|
|
619
|
+
<span className="font-headline text-[10px] uppercase tracking-[0.2em] text-muted-foreground">
|
|
620
|
+
Evidence Quality
|
|
621
|
+
</span>
|
|
622
|
+
<InfoTip text="How well prompts are linked to invocations. Higher prompt-link rate = more trustworthy data." />
|
|
623
|
+
</CardDescription>
|
|
624
|
+
<CardTitle className="text-[32px] font-semibold leading-none tabular-nums text-foreground">
|
|
625
|
+
{evidenceQuality?.prompt_link_rate != null
|
|
626
|
+
? formatRate(evidenceQuality.prompt_link_rate)
|
|
627
|
+
: "No data"}
|
|
628
|
+
</CardTitle>
|
|
629
|
+
<CardAction>
|
|
630
|
+
<div className="flex flex-col items-end gap-0.5">
|
|
631
|
+
<span className="text-[10px] text-muted-foreground">
|
|
632
|
+
inline:{" "}
|
|
633
|
+
{evidenceQuality?.inline_query_rate != null
|
|
634
|
+
? formatRate(evidenceQuality.inline_query_rate)
|
|
635
|
+
: "No data"}
|
|
636
|
+
</span>
|
|
637
|
+
{(evidenceQuality?.system_like_rate ?? 0) > 0.05 && (
|
|
638
|
+
<Badge variant="destructive" className="text-[9px]">
|
|
639
|
+
{formatRate(evidenceQuality?.system_like_rate ?? 0)} system-like
|
|
640
|
+
</Badge>
|
|
641
|
+
)}
|
|
642
|
+
</div>
|
|
643
|
+
</CardAction>
|
|
644
|
+
</CardHeader>
|
|
645
|
+
</Card>
|
|
646
|
+
|
|
647
|
+
<Card className="rounded-xl border border-border/10 bg-muted/20 transition-colors hover:border-border/20 @container/card">
|
|
648
|
+
<CardHeader className="gap-2 px-4 py-3">
|
|
649
|
+
<CardDescription className="flex items-center gap-1.5">
|
|
650
|
+
<TargetIcon className="size-3.5" />
|
|
651
|
+
<span className="font-headline text-[10px] uppercase tracking-[0.2em] text-muted-foreground">
|
|
652
|
+
Routing
|
|
653
|
+
</span>
|
|
654
|
+
<InfoTip text="Routing accuracy: average confidence when triggering, and miss rate" />
|
|
655
|
+
</CardDescription>
|
|
656
|
+
<CardTitle className="text-[32px] font-semibold leading-none tabular-nums text-foreground">
|
|
657
|
+
{routingQuality?.avg_confidence != null
|
|
658
|
+
? formatRate(routingQuality.avg_confidence)
|
|
659
|
+
: "No data"}
|
|
660
|
+
</CardTitle>
|
|
661
|
+
<CardAction>
|
|
662
|
+
<div className="flex flex-col items-end gap-0.5">
|
|
663
|
+
<span className="text-[10px] text-muted-foreground">
|
|
664
|
+
miss:{" "}
|
|
665
|
+
{routingQuality?.miss_rate != null
|
|
666
|
+
? formatRate(routingQuality.miss_rate)
|
|
667
|
+
: "No data"}
|
|
668
|
+
</span>
|
|
669
|
+
<span className="text-[10px] tabular-nums text-muted-foreground">
|
|
670
|
+
{routingQuality?.missed_triggers ?? "No data"} missed
|
|
671
|
+
</span>
|
|
672
|
+
</div>
|
|
673
|
+
</CardAction>
|
|
674
|
+
</CardHeader>
|
|
675
|
+
</Card>
|
|
676
|
+
|
|
677
|
+
<Card className="rounded-xl border border-border/10 bg-muted/20 transition-colors hover:border-border/20 @container/card">
|
|
678
|
+
<CardHeader className="gap-2 px-4 py-3">
|
|
679
|
+
<CardDescription className="flex items-center gap-1.5">
|
|
680
|
+
<GitBranchIcon className="size-3.5" />
|
|
681
|
+
<span className="font-headline text-[10px] uppercase tracking-[0.2em] text-muted-foreground">
|
|
682
|
+
Evolution
|
|
683
|
+
</span>
|
|
684
|
+
</CardDescription>
|
|
685
|
+
{hasEvolutionData ? (
|
|
686
|
+
<>
|
|
687
|
+
<CardTitle className="text-sm font-medium leading-6">
|
|
688
|
+
{evolutionState?.latest_action ?? fallbackLatestAction ?? "No data"}
|
|
689
|
+
</CardTitle>
|
|
690
|
+
<CardAction>
|
|
691
|
+
<div className="flex flex-col items-end gap-0.5">
|
|
692
|
+
<span className="text-[10px] tabular-nums text-muted-foreground">
|
|
693
|
+
{evolutionState?.evidence_rows ?? fallbackEvidenceRows} evidence
|
|
694
|
+
</span>
|
|
695
|
+
<span className="text-[10px] tabular-nums text-muted-foreground">
|
|
696
|
+
{evolutionState?.evolution_rows ?? fallbackEvolutionRows} evolution
|
|
697
|
+
</span>
|
|
698
|
+
{evolutionState?.latest_timestamp && (
|
|
699
|
+
<span className="font-mono text-[10px] text-muted-foreground">
|
|
700
|
+
{timeAgo(evolutionState.latest_timestamp)}
|
|
701
|
+
</span>
|
|
702
|
+
)}
|
|
703
|
+
</div>
|
|
704
|
+
</CardAction>
|
|
705
|
+
</>
|
|
706
|
+
) : (
|
|
707
|
+
<CardTitle className="text-sm font-normal text-muted-foreground">
|
|
708
|
+
No evolution yet
|
|
709
|
+
</CardTitle>
|
|
710
|
+
)}
|
|
711
|
+
</CardHeader>
|
|
712
|
+
</Card>
|
|
713
|
+
</div>
|
|
714
|
+
</div>
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export function PromptEvidencePanel({ examples }: { examples?: TrustFields["examples"] }) {
|
|
719
|
+
if (!examples) return null;
|
|
720
|
+
if (examples.good.length === 0 && examples.missed.length === 0 && examples.noisy.length === 0) {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return (
|
|
725
|
+
<Card className="rounded-xl border border-border/10 bg-card/90">
|
|
726
|
+
<CardHeader className="px-4 pb-2 pt-4">
|
|
727
|
+
<div className="flex items-start justify-between gap-4">
|
|
728
|
+
<div>
|
|
729
|
+
<CardTitle className="font-headline text-[10px] uppercase tracking-[0.2em] text-muted-foreground">
|
|
730
|
+
Prompt Evidence
|
|
731
|
+
</CardTitle>
|
|
732
|
+
<CardDescription>
|
|
733
|
+
Representative real usage first. Internal selftune traffic is separated so it does not
|
|
734
|
+
masquerade as normal skill use.
|
|
735
|
+
</CardDescription>
|
|
736
|
+
</div>
|
|
737
|
+
<div className="hidden items-center gap-2 text-[10px] text-muted-foreground @3xl/main:flex">
|
|
738
|
+
<span>{examples.good.length} successful</span>
|
|
739
|
+
<span className="text-border">|</span>
|
|
740
|
+
<span>{examples.missed.length} missed</span>
|
|
741
|
+
<span className="text-border">|</span>
|
|
742
|
+
<span>{examples.noisy.length} internal/polluted</span>
|
|
743
|
+
</div>
|
|
744
|
+
</div>
|
|
745
|
+
</CardHeader>
|
|
746
|
+
<CardContent className="px-4 pb-4 pt-0">
|
|
747
|
+
<Tabs defaultValue="good">
|
|
748
|
+
<TabsList
|
|
749
|
+
variant="line"
|
|
750
|
+
className="min-h-0 rounded-xl border border-border/10 bg-muted/20 px-1.5 py-1"
|
|
751
|
+
>
|
|
752
|
+
<TabsTrigger
|
|
753
|
+
value="good"
|
|
754
|
+
className="rounded-lg px-3 data-active:bg-background/70 data-active:text-foreground"
|
|
755
|
+
>
|
|
756
|
+
Successful Triggers
|
|
757
|
+
<Badge variant="outline" className="ml-1.5 text-[10px]">
|
|
758
|
+
{examples.good.length}
|
|
759
|
+
</Badge>
|
|
760
|
+
</TabsTrigger>
|
|
761
|
+
<TabsTrigger
|
|
762
|
+
value="missed"
|
|
763
|
+
className="rounded-lg px-3 data-active:bg-background/70 data-active:text-foreground"
|
|
764
|
+
>
|
|
765
|
+
Missed Real Usage
|
|
766
|
+
<Badge
|
|
767
|
+
variant={examples.missed.length > 0 ? "destructive" : "outline"}
|
|
768
|
+
className="ml-1.5 text-[10px]"
|
|
769
|
+
>
|
|
770
|
+
{examples.missed.length}
|
|
771
|
+
</Badge>
|
|
772
|
+
</TabsTrigger>
|
|
773
|
+
<TabsTrigger
|
|
774
|
+
value="noisy"
|
|
775
|
+
className="rounded-lg px-3 data-active:bg-background/70 data-active:text-foreground"
|
|
776
|
+
>
|
|
777
|
+
Internal / Polluted
|
|
778
|
+
<Badge
|
|
779
|
+
variant={examples.noisy.length > 0 ? "destructive" : "outline"}
|
|
780
|
+
className="ml-1.5 text-[10px]"
|
|
781
|
+
>
|
|
782
|
+
{examples.noisy.length}
|
|
783
|
+
</Badge>
|
|
784
|
+
</TabsTrigger>
|
|
785
|
+
</TabsList>
|
|
786
|
+
<TabsContent value="good" className="mt-2">
|
|
787
|
+
<ExamplesTable rows={examples.good} emptyMessage="No successful trigger samples yet." />
|
|
788
|
+
</TabsContent>
|
|
789
|
+
<TabsContent value="missed" className="mt-2">
|
|
790
|
+
<ExamplesTable
|
|
791
|
+
rows={examples.missed}
|
|
792
|
+
emptyMessage="No missed real-usage samples detected."
|
|
793
|
+
/>
|
|
794
|
+
</TabsContent>
|
|
795
|
+
<TabsContent value="noisy" className="mt-2">
|
|
796
|
+
<ExamplesTable
|
|
797
|
+
rows={examples.noisy}
|
|
798
|
+
emptyMessage="No internal or polluted samples detected."
|
|
799
|
+
/>
|
|
800
|
+
</TabsContent>
|
|
801
|
+
</Tabs>
|
|
802
|
+
</CardContent>
|
|
803
|
+
</Card>
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
export function DataQualityPanel({
|
|
808
|
+
evidenceQuality,
|
|
809
|
+
dataHygiene,
|
|
810
|
+
}: {
|
|
811
|
+
evidenceQuality?: TrustFields["evidence_quality"];
|
|
812
|
+
dataHygiene?: TrustFields["data_hygiene"];
|
|
813
|
+
}) {
|
|
814
|
+
return (
|
|
815
|
+
<div className="grid grid-cols-1 gap-6 @5xl/main:grid-cols-2 @5xl/main:items-start">
|
|
816
|
+
<Card className="rounded-2xl border border-border/15 bg-card">
|
|
817
|
+
<CardHeader>
|
|
818
|
+
<CardTitle className="flex items-center gap-2 font-headline text-[10px] uppercase tracking-[0.2em] text-muted-foreground">
|
|
819
|
+
<BarChart3Icon className="size-4" />
|
|
820
|
+
Evidence Quality Rates
|
|
821
|
+
</CardTitle>
|
|
822
|
+
</CardHeader>
|
|
823
|
+
<CardContent className="space-y-3 p-4">
|
|
824
|
+
<RateBar label="Prompt-linked" value={evidenceQuality?.prompt_link_rate} />
|
|
825
|
+
<RateBar label="Inline query" value={evidenceQuality?.inline_query_rate} />
|
|
826
|
+
<RateBar label="User prompt" value={evidenceQuality?.user_prompt_rate} />
|
|
827
|
+
<RateBar label="Meta prompt" value={evidenceQuality?.meta_prompt_rate} />
|
|
828
|
+
<RateBar label="No prompt" value={evidenceQuality?.no_prompt_rate} />
|
|
829
|
+
<RateBar
|
|
830
|
+
label="System-like"
|
|
831
|
+
value={evidenceQuality?.system_like_rate}
|
|
832
|
+
warn={(evidenceQuality?.system_like_rate ?? 0) > 0.05}
|
|
833
|
+
/>
|
|
834
|
+
<div className="mt-3 border-t border-border/40 pt-3" />
|
|
835
|
+
<RateBar label="Invocation mode" value={evidenceQuality?.invocation_mode_coverage} />
|
|
836
|
+
<RateBar label="Confidence" value={evidenceQuality?.confidence_coverage} />
|
|
837
|
+
<RateBar label="Source" value={evidenceQuality?.source_coverage} />
|
|
838
|
+
<RateBar label="Scope" value={evidenceQuality?.scope_coverage} />
|
|
839
|
+
</CardContent>
|
|
840
|
+
</Card>
|
|
841
|
+
|
|
842
|
+
{dataHygiene && (
|
|
843
|
+
<Card className="rounded-2xl border border-border/15 bg-card">
|
|
844
|
+
<CardHeader>
|
|
845
|
+
<CardTitle className="flex items-center gap-2 font-headline text-[10px] uppercase tracking-[0.2em] text-muted-foreground">
|
|
846
|
+
<DatabaseIcon className="size-4" />
|
|
847
|
+
Data Hygiene
|
|
848
|
+
</CardTitle>
|
|
849
|
+
</CardHeader>
|
|
850
|
+
<CardContent className="space-y-6 p-4">
|
|
851
|
+
<div className="grid grid-cols-2 gap-3">
|
|
852
|
+
<div className="rounded-xl border border-border/15 bg-muted/30 p-3">
|
|
853
|
+
<div className="text-[10px] uppercase tracking-[0.2em] text-muted-foreground">
|
|
854
|
+
Raw vs Operational
|
|
855
|
+
</div>
|
|
856
|
+
<div className="mt-2 flex items-end gap-2">
|
|
857
|
+
<div className="text-lg font-semibold tabular-nums text-foreground">
|
|
858
|
+
{dataHygiene.operational_checks}
|
|
859
|
+
</div>
|
|
860
|
+
<div className="pb-0.5 text-xs text-muted-foreground">
|
|
861
|
+
of {dataHygiene.raw_checks} checks
|
|
862
|
+
</div>
|
|
863
|
+
</div>
|
|
864
|
+
<p className="mt-1 text-[11px] text-muted-foreground">
|
|
865
|
+
Operational checks exclude internal selftune eval and optimizer traffic.
|
|
866
|
+
</p>
|
|
867
|
+
</div>
|
|
868
|
+
<div className="rounded-xl border border-border/15 bg-muted/30 p-3">
|
|
869
|
+
<div className="text-[10px] uppercase tracking-[0.2em] text-muted-foreground">
|
|
870
|
+
Historical Residue
|
|
871
|
+
</div>
|
|
872
|
+
<div className="mt-2 flex items-end gap-2">
|
|
873
|
+
<div className="text-lg font-semibold tabular-nums text-foreground">
|
|
874
|
+
{dataHygiene.legacy_rows}
|
|
875
|
+
</div>
|
|
876
|
+
<div className="pb-0.5 text-xs text-muted-foreground">
|
|
877
|
+
legacy / {dataHygiene.repaired_rows} repaired
|
|
878
|
+
</div>
|
|
879
|
+
</div>
|
|
880
|
+
<p className="mt-1 text-[11px] text-muted-foreground">
|
|
881
|
+
Legacy rows are older materialized history. Repaired rows are transcript-based
|
|
882
|
+
reconstructions.
|
|
883
|
+
</p>
|
|
884
|
+
</div>
|
|
885
|
+
</div>
|
|
886
|
+
|
|
887
|
+
{dataHygiene.naming_variants && dataHygiene.naming_variants.length > 1 && (
|
|
888
|
+
<div>
|
|
889
|
+
<h4 className="mb-2 font-headline text-[10px] uppercase tracking-[0.2em] text-muted-foreground">
|
|
890
|
+
Naming Variants
|
|
891
|
+
</h4>
|
|
892
|
+
<div className="flex flex-wrap gap-1.5">
|
|
893
|
+
{dataHygiene.naming_variants.map((v) => (
|
|
894
|
+
<Badge key={v} variant="outline" className="font-mono text-[10px]">
|
|
895
|
+
{v}
|
|
896
|
+
</Badge>
|
|
897
|
+
))}
|
|
898
|
+
</div>
|
|
899
|
+
<p className="mt-1 text-[11px] text-muted-foreground">
|
|
900
|
+
Multiple naming variants may indicate inconsistent skill registration.
|
|
901
|
+
</p>
|
|
902
|
+
</div>
|
|
903
|
+
)}
|
|
904
|
+
|
|
905
|
+
<BreakdownTable title="Source Breakdown" data={dataHygiene.source_breakdown} />
|
|
906
|
+
<BreakdownTable
|
|
907
|
+
title="Prompt Kind Breakdown"
|
|
908
|
+
data={dataHygiene.prompt_kind_breakdown}
|
|
909
|
+
/>
|
|
910
|
+
<BreakdownTable
|
|
911
|
+
title="Observation Breakdown"
|
|
912
|
+
data={dataHygiene.observation_breakdown}
|
|
913
|
+
/>
|
|
914
|
+
</CardContent>
|
|
915
|
+
</Card>
|
|
916
|
+
)}
|
|
917
|
+
</div>
|
|
918
|
+
);
|
|
919
|
+
}
|