@vercel/agent-eval-playground 0.0.1
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 +50 -0
- package/app/compare/page.tsx +40 -0
- package/app/evals/[name]/page.tsx +22 -0
- package/app/evals/page.tsx +18 -0
- package/app/experiments/[name]/[timestamp]/page.tsx +23 -0
- package/app/experiments/page.tsx +28 -0
- package/app/globals.css +126 -0
- package/app/layout.tsx +102 -0
- package/app/page.tsx +179 -0
- package/app/transcript/[experiment]/[timestamp]/[evalName]/[run]/page.tsx +43 -0
- package/bin.mjs +86 -0
- package/components/ComparePage.tsx +312 -0
- package/components/EvalDetail.tsx +114 -0
- package/components/EvalsPage.tsx +80 -0
- package/components/ExperimentDetail.tsx +162 -0
- package/components/ExperimentList.tsx +103 -0
- package/components/O11ySummary.tsx +114 -0
- package/components/RunResultCard.tsx +72 -0
- package/components/ShowMore.tsx +60 -0
- package/components/TranscriptPage.tsx +46 -0
- package/components/TranscriptViewer.tsx +201 -0
- package/components/ui/alert-dialog.tsx +184 -0
- package/components/ui/badge.tsx +45 -0
- package/components/ui/button.tsx +60 -0
- package/components/ui/card.tsx +94 -0
- package/components/ui/collapsible.tsx +34 -0
- package/components/ui/combobox.tsx +297 -0
- package/components/ui/dropdown-menu.tsx +269 -0
- package/components/ui/field.tsx +227 -0
- package/components/ui/input-group.tsx +147 -0
- package/components/ui/input.tsx +19 -0
- package/components/ui/label.tsx +24 -0
- package/components/ui/progress.tsx +31 -0
- package/components/ui/scroll-area.tsx +58 -0
- package/components/ui/select.tsx +191 -0
- package/components/ui/separator.tsx +28 -0
- package/components/ui/table.tsx +116 -0
- package/components/ui/tabs.tsx +91 -0
- package/components/ui/textarea.tsx +18 -0
- package/components/ui/tooltip.tsx +57 -0
- package/components.json +25 -0
- package/lib/data.ts +297 -0
- package/lib/types.ts +113 -0
- package/lib/utils.ts +6 -0
- package/next.config.ts +5 -0
- package/package.json +51 -0
- package/postcss.config.mjs +7 -0
- package/public/vercel.svg +1 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Badge } from "@/components/ui/badge";
|
|
5
|
+
import { Card, CardContent } from "@/components/ui/card";
|
|
6
|
+
import {
|
|
7
|
+
Collapsible,
|
|
8
|
+
CollapsibleContent,
|
|
9
|
+
CollapsibleTrigger,
|
|
10
|
+
} from "@/components/ui/collapsible";
|
|
11
|
+
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
12
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
13
|
+
import { Separator } from "@/components/ui/separator";
|
|
14
|
+
import { ChevronRight, Terminal, FileText, Brain, AlertCircle, MessageSquare, Wrench } from "lucide-react";
|
|
15
|
+
import type { TranscriptEvent, Transcript } from "@/lib/types";
|
|
16
|
+
import { O11ySummary } from "./O11ySummary";
|
|
17
|
+
import { ShowMore } from "./ShowMore";
|
|
18
|
+
|
|
19
|
+
interface TranscriptViewerProps {
|
|
20
|
+
transcript: Transcript;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const EVENT_CONFIG: Record<
|
|
24
|
+
TranscriptEvent["type"],
|
|
25
|
+
{ label: string; variant: "default" | "secondary" | "destructive" | "outline"; icon: React.ElementType }
|
|
26
|
+
> = {
|
|
27
|
+
message: { label: "Message", variant: "default", icon: MessageSquare },
|
|
28
|
+
tool_call: { label: "Tool Call", variant: "secondary", icon: Wrench },
|
|
29
|
+
tool_result: { label: "Tool Result", variant: "outline", icon: Terminal },
|
|
30
|
+
thinking: { label: "Thinking", variant: "outline", icon: Brain },
|
|
31
|
+
error: { label: "Error", variant: "destructive", icon: AlertCircle },
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function TranscriptEventCard({ event, index }: { event: TranscriptEvent; index: number }) {
|
|
35
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
36
|
+
const config = EVENT_CONFIG[event.type];
|
|
37
|
+
const Icon = config.icon;
|
|
38
|
+
|
|
39
|
+
const hasExpandableContent =
|
|
40
|
+
event.tool?.args ||
|
|
41
|
+
event.tool?.result ||
|
|
42
|
+
(event.content && event.content.length > 200);
|
|
43
|
+
|
|
44
|
+
const preview = event.content
|
|
45
|
+
? event.content.slice(0, 200) + (event.content.length > 200 ? "..." : "")
|
|
46
|
+
: event.tool
|
|
47
|
+
? `${event.tool.originalName}${event.tool.success === false ? " (failed)" : ""}`
|
|
48
|
+
: null;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
|
52
|
+
<Card size="sm" className="border-l-4 overflow-hidden py-0! gap-0!" style={{ borderLeftColor: getEventColor(event.type) }}>
|
|
53
|
+
<CollapsibleTrigger className="w-full text-left cursor-pointer transition-colors hover:bg-muted rounded-t-lg" disabled={!hasExpandableContent}>
|
|
54
|
+
<div className="flex items-center gap-2 min-w-0 px-4 py-3">
|
|
55
|
+
<span className="text-xs text-muted-foreground w-5 text-right font-mono shrink-0">
|
|
56
|
+
{index + 1}
|
|
57
|
+
</span>
|
|
58
|
+
<Icon className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
|
59
|
+
<Badge variant={config.variant} className="text-xs shrink-0 max-w-32 truncate">
|
|
60
|
+
{event.type === "tool_call" && event.tool
|
|
61
|
+
? event.tool.originalName
|
|
62
|
+
: config.label}
|
|
63
|
+
</Badge>
|
|
64
|
+
{event.role && (
|
|
65
|
+
<Badge variant="outline" className="text-xs shrink-0">
|
|
66
|
+
{event.role}
|
|
67
|
+
</Badge>
|
|
68
|
+
)}
|
|
69
|
+
<p className="flex-1 min-w-0 text-xs text-muted-foreground truncate">
|
|
70
|
+
{preview}
|
|
71
|
+
</p>
|
|
72
|
+
{event.tool?.durationMs !== undefined && (
|
|
73
|
+
<span className="text-xs text-muted-foreground shrink-0">
|
|
74
|
+
{event.tool.durationMs}ms
|
|
75
|
+
</span>
|
|
76
|
+
)}
|
|
77
|
+
{hasExpandableContent && (
|
|
78
|
+
<ChevronRight
|
|
79
|
+
className={`h-3.5 w-3.5 text-muted-foreground shrink-0 transition-transform ${
|
|
80
|
+
isOpen ? "rotate-90" : ""
|
|
81
|
+
}`}
|
|
82
|
+
/>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
</CollapsibleTrigger>
|
|
86
|
+
<CollapsibleContent>
|
|
87
|
+
<div className="px-4 pb-3 space-y-2">
|
|
88
|
+
<Separator />
|
|
89
|
+
{event.content && (
|
|
90
|
+
<pre className="text-xs font-mono bg-muted rounded p-3 overflow-x-auto whitespace-pre-wrap max-h-96 overflow-y-auto">
|
|
91
|
+
{event.content}
|
|
92
|
+
</pre>
|
|
93
|
+
)}
|
|
94
|
+
{event.tool?.args && (
|
|
95
|
+
<div>
|
|
96
|
+
<div className="text-xs text-muted-foreground mb-1">Arguments:</div>
|
|
97
|
+
<pre className="text-xs font-mono bg-muted rounded p-3 overflow-x-auto whitespace-pre-wrap max-h-64 overflow-y-auto">
|
|
98
|
+
{JSON.stringify(event.tool.args, null, 2)}
|
|
99
|
+
</pre>
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
{event.tool?.result !== undefined && (
|
|
103
|
+
<div>
|
|
104
|
+
<div className="text-xs text-muted-foreground mb-1">Result:</div>
|
|
105
|
+
<pre className="text-xs font-mono bg-muted rounded p-3 overflow-x-auto whitespace-pre-wrap max-h-64 overflow-y-auto">
|
|
106
|
+
{typeof event.tool.result === "string"
|
|
107
|
+
? event.tool.result
|
|
108
|
+
: JSON.stringify(event.tool.result, null, 2)}
|
|
109
|
+
</pre>
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
</CollapsibleContent>
|
|
114
|
+
</Card>
|
|
115
|
+
</Collapsible>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getEventColor(type: TranscriptEvent["type"]): string {
|
|
120
|
+
switch (type) {
|
|
121
|
+
case "message":
|
|
122
|
+
return "hsl(217, 91%, 60%)"; // blue
|
|
123
|
+
case "tool_call":
|
|
124
|
+
return "hsl(271, 91%, 65%)"; // purple
|
|
125
|
+
case "tool_result":
|
|
126
|
+
return "hsl(215, 14%, 50%)"; // gray
|
|
127
|
+
case "thinking":
|
|
128
|
+
return "hsl(48, 96%, 53%)"; // yellow
|
|
129
|
+
case "error":
|
|
130
|
+
return "hsl(0, 84%, 60%)"; // red
|
|
131
|
+
default:
|
|
132
|
+
return "hsl(215, 14%, 50%)";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function TranscriptViewer({ transcript }: TranscriptViewerProps) {
|
|
137
|
+
return (
|
|
138
|
+
<Tabs defaultValue="timeline" className="w-full">
|
|
139
|
+
<TabsList>
|
|
140
|
+
<TabsTrigger value="timeline">Timeline</TabsTrigger>
|
|
141
|
+
<TabsTrigger value="raw">Raw JSON</TabsTrigger>
|
|
142
|
+
</TabsList>
|
|
143
|
+
|
|
144
|
+
<TabsContent value="timeline" className="mt-4">
|
|
145
|
+
<div className="space-y-6">
|
|
146
|
+
{/* Summary */}
|
|
147
|
+
<div className="flex flex-wrap items-center gap-x-5 gap-y-1 text-sm">
|
|
148
|
+
<div>
|
|
149
|
+
<span className="text-muted-foreground">Agent </span>
|
|
150
|
+
<span className="font-medium">{transcript.agent}</span>
|
|
151
|
+
</div>
|
|
152
|
+
{transcript.model && (
|
|
153
|
+
<div>
|
|
154
|
+
<span className="text-muted-foreground">Model </span>
|
|
155
|
+
<span className="font-medium">{transcript.model}</span>
|
|
156
|
+
</div>
|
|
157
|
+
)}
|
|
158
|
+
<div>
|
|
159
|
+
<span className="text-muted-foreground">Events </span>
|
|
160
|
+
<span className="font-medium">{transcript.events.length}</span>
|
|
161
|
+
</div>
|
|
162
|
+
<div>
|
|
163
|
+
<span className="text-muted-foreground">Status </span>
|
|
164
|
+
<Badge
|
|
165
|
+
variant={transcript.parseSuccess ? "default" : "destructive"}
|
|
166
|
+
className="text-xs"
|
|
167
|
+
>
|
|
168
|
+
{transcript.parseSuccess ? "Success" : "Partial"}
|
|
169
|
+
</Badge>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<O11ySummary summary={transcript.summary} />
|
|
174
|
+
|
|
175
|
+
{/* Timeline */}
|
|
176
|
+
{transcript.events.length === 0 ? (
|
|
177
|
+
<Card>
|
|
178
|
+
<CardContent className="py-8 text-center text-muted-foreground">
|
|
179
|
+
No transcript events found.
|
|
180
|
+
</CardContent>
|
|
181
|
+
</Card>
|
|
182
|
+
) : (
|
|
183
|
+
<ShowMore limit={50} className="space-y-2">
|
|
184
|
+
{transcript.events.map((event, i) => (
|
|
185
|
+
<TranscriptEventCard key={i} event={event} index={i} />
|
|
186
|
+
))}
|
|
187
|
+
</ShowMore>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
</TabsContent>
|
|
191
|
+
|
|
192
|
+
<TabsContent value="raw" className="mt-4">
|
|
193
|
+
<ScrollArea className="h-[calc(100vh-280px)]">
|
|
194
|
+
<pre className="text-xs font-mono bg-muted rounded p-4 whitespace-pre-wrap break-all">
|
|
195
|
+
{JSON.stringify(transcript, null, 2)}
|
|
196
|
+
</pre>
|
|
197
|
+
</ScrollArea>
|
|
198
|
+
</TabsContent>
|
|
199
|
+
</Tabs>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { AlertDialog as AlertDialogPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
import { Button } from "@/components/ui/button"
|
|
8
|
+
|
|
9
|
+
function AlertDialog({
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
|
12
|
+
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function AlertDialogTrigger({
|
|
16
|
+
...props
|
|
17
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
|
|
18
|
+
return (
|
|
19
|
+
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function AlertDialogPortal({
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
|
|
26
|
+
return (
|
|
27
|
+
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function AlertDialogOverlay({
|
|
32
|
+
className,
|
|
33
|
+
...props
|
|
34
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
|
|
35
|
+
return (
|
|
36
|
+
<AlertDialogPrimitive.Overlay
|
|
37
|
+
data-slot="alert-dialog-overlay"
|
|
38
|
+
className={cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50", className)}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function AlertDialogContent({
|
|
45
|
+
className,
|
|
46
|
+
size = "default",
|
|
47
|
+
...props
|
|
48
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {
|
|
49
|
+
size?: "default" | "sm"
|
|
50
|
+
}) {
|
|
51
|
+
return (
|
|
52
|
+
<AlertDialogPortal>
|
|
53
|
+
<AlertDialogOverlay />
|
|
54
|
+
<AlertDialogPrimitive.Content
|
|
55
|
+
data-slot="alert-dialog-content"
|
|
56
|
+
data-size={size}
|
|
57
|
+
className={cn(
|
|
58
|
+
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-3 rounded-xl p-4 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-64 data-[size=default]:sm:max-w-sm group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none",
|
|
59
|
+
className
|
|
60
|
+
)}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
</AlertDialogPortal>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function AlertDialogHeader({
|
|
68
|
+
className,
|
|
69
|
+
...props
|
|
70
|
+
}: React.ComponentProps<"div">) {
|
|
71
|
+
return (
|
|
72
|
+
<div
|
|
73
|
+
data-slot="alert-dialog-header"
|
|
74
|
+
className={cn("grid grid-rows-[auto_1fr] place-items-center gap-1 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]", className)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function AlertDialogFooter({
|
|
81
|
+
className,
|
|
82
|
+
...props
|
|
83
|
+
}: React.ComponentProps<"div">) {
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
data-slot="alert-dialog-footer"
|
|
87
|
+
className={cn(
|
|
88
|
+
"flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
|
|
89
|
+
className
|
|
90
|
+
)}
|
|
91
|
+
{...props}
|
|
92
|
+
/>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function AlertDialogMedia({
|
|
97
|
+
className,
|
|
98
|
+
...props
|
|
99
|
+
}: React.ComponentProps<"div">) {
|
|
100
|
+
return (
|
|
101
|
+
<div
|
|
102
|
+
data-slot="alert-dialog-media"
|
|
103
|
+
className={cn("bg-muted mb-2 inline-flex size-8 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-4", className)}
|
|
104
|
+
{...props}
|
|
105
|
+
/>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function AlertDialogTitle({
|
|
110
|
+
className,
|
|
111
|
+
...props
|
|
112
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
|
|
113
|
+
return (
|
|
114
|
+
<AlertDialogPrimitive.Title
|
|
115
|
+
data-slot="alert-dialog-title"
|
|
116
|
+
className={cn("text-sm font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2", className)}
|
|
117
|
+
{...props}
|
|
118
|
+
/>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function AlertDialogDescription({
|
|
123
|
+
className,
|
|
124
|
+
...props
|
|
125
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
|
|
126
|
+
return (
|
|
127
|
+
<AlertDialogPrimitive.Description
|
|
128
|
+
data-slot="alert-dialog-description"
|
|
129
|
+
className={cn("text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3", className)}
|
|
130
|
+
{...props}
|
|
131
|
+
/>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function AlertDialogAction({
|
|
136
|
+
className,
|
|
137
|
+
variant = "default",
|
|
138
|
+
size = "default",
|
|
139
|
+
...props
|
|
140
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Action> &
|
|
141
|
+
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
|
142
|
+
return (
|
|
143
|
+
<Button variant={variant} size={size} asChild>
|
|
144
|
+
<AlertDialogPrimitive.Action
|
|
145
|
+
data-slot="alert-dialog-action"
|
|
146
|
+
className={cn(className)}
|
|
147
|
+
{...props}
|
|
148
|
+
/>
|
|
149
|
+
</Button>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function AlertDialogCancel({
|
|
154
|
+
className,
|
|
155
|
+
variant = "outline",
|
|
156
|
+
size = "default",
|
|
157
|
+
...props
|
|
158
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel> &
|
|
159
|
+
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
|
160
|
+
return (
|
|
161
|
+
<Button variant={variant} size={size} asChild>
|
|
162
|
+
<AlertDialogPrimitive.Cancel
|
|
163
|
+
data-slot="alert-dialog-cancel"
|
|
164
|
+
className={cn(className)}
|
|
165
|
+
{...props}
|
|
166
|
+
/>
|
|
167
|
+
</Button>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export {
|
|
172
|
+
AlertDialog,
|
|
173
|
+
AlertDialogAction,
|
|
174
|
+
AlertDialogCancel,
|
|
175
|
+
AlertDialogContent,
|
|
176
|
+
AlertDialogDescription,
|
|
177
|
+
AlertDialogFooter,
|
|
178
|
+
AlertDialogHeader,
|
|
179
|
+
AlertDialogMedia,
|
|
180
|
+
AlertDialogOverlay,
|
|
181
|
+
AlertDialogPortal,
|
|
182
|
+
AlertDialogTitle,
|
|
183
|
+
AlertDialogTrigger,
|
|
184
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { Slot } from "radix-ui"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
const badgeVariants = cva(
|
|
8
|
+
"h-5 gap-1 rounded-full border border-transparent px-2 py-0.5 text-[0.625rem] font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-2.5! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive overflow-hidden group/badge",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
13
|
+
secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
|
14
|
+
destructive: "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
|
|
15
|
+
outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground bg-input/20 dark:bg-input/30",
|
|
16
|
+
ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
|
17
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultVariants: {
|
|
21
|
+
variant: "default",
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
function Badge({
|
|
27
|
+
className,
|
|
28
|
+
variant = "default",
|
|
29
|
+
asChild = false,
|
|
30
|
+
...props
|
|
31
|
+
}: React.ComponentProps<"span"> &
|
|
32
|
+
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
|
33
|
+
const Comp = asChild ? Slot.Root : "span"
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Comp
|
|
37
|
+
data-slot="badge"
|
|
38
|
+
data-variant={variant}
|
|
39
|
+
className={cn(badgeVariants({ variant }), className)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { Badge, badgeVariants }
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { Slot } from "radix-ui"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
const buttonVariants = cva(
|
|
8
|
+
"cursor-pointer focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-xs/relaxed font-medium focus-visible:ring-2 aria-invalid:ring-2 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
|
13
|
+
outline: "border-border dark:bg-input/30 hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
|
|
14
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
15
|
+
ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
|
|
16
|
+
destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
|
|
17
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
18
|
+
},
|
|
19
|
+
size: {
|
|
20
|
+
default: "h-7 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
21
|
+
xs: "h-5 gap-1 rounded-sm px-2 text-[0.625rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-2.5",
|
|
22
|
+
sm: "h-6 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
23
|
+
lg: "h-8 gap-1 px-2.5 text-xs/relaxed has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-4",
|
|
24
|
+
icon: "size-7 [&_svg:not([class*='size-'])]:size-3.5",
|
|
25
|
+
"icon-xs": "size-5 rounded-sm [&_svg:not([class*='size-'])]:size-2.5",
|
|
26
|
+
"icon-sm": "size-6 [&_svg:not([class*='size-'])]:size-3",
|
|
27
|
+
"icon-lg": "size-8 [&_svg:not([class*='size-'])]:size-4",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
defaultVariants: {
|
|
31
|
+
variant: "default",
|
|
32
|
+
size: "default",
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
function Button({
|
|
38
|
+
className,
|
|
39
|
+
variant = "default",
|
|
40
|
+
size = "default",
|
|
41
|
+
asChild = false,
|
|
42
|
+
...props
|
|
43
|
+
}: React.ComponentProps<"button"> &
|
|
44
|
+
VariantProps<typeof buttonVariants> & {
|
|
45
|
+
asChild?: boolean
|
|
46
|
+
}) {
|
|
47
|
+
const Comp = asChild ? Slot.Root : "button"
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Comp
|
|
51
|
+
data-slot="button"
|
|
52
|
+
data-variant={variant}
|
|
53
|
+
data-size={size}
|
|
54
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { Button, buttonVariants }
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
function Card({
|
|
6
|
+
className,
|
|
7
|
+
size = "default",
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
data-slot="card"
|
|
13
|
+
data-size={size}
|
|
14
|
+
className={cn("ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-lg py-4 text-xs/relaxed ring-1 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg group/card flex flex-col", className)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
21
|
+
return (
|
|
22
|
+
<div
|
|
23
|
+
data-slot="card-header"
|
|
24
|
+
className={cn(
|
|
25
|
+
"gap-1 rounded-t-lg px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
|
|
26
|
+
className
|
|
27
|
+
)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
data-slot="card-title"
|
|
37
|
+
className={cn("text-sm font-medium", className)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
data-slot="card-description"
|
|
47
|
+
className={cn("text-muted-foreground text-xs/relaxed", className)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
data-slot="card-action"
|
|
57
|
+
className={cn(
|
|
58
|
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
59
|
+
className
|
|
60
|
+
)}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
data-slot="card-content"
|
|
70
|
+
className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
|
|
71
|
+
{...props}
|
|
72
|
+
/>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
77
|
+
return (
|
|
78
|
+
<div
|
|
79
|
+
data-slot="card-footer"
|
|
80
|
+
className={cn("rounded-b-lg px-4 group-data-[size=sm]/card:px-3 [.border-t]:pt-4 group-data-[size=sm]/card:[.border-t]:pt-3 flex items-center", className)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
Card,
|
|
88
|
+
CardHeader,
|
|
89
|
+
CardFooter,
|
|
90
|
+
CardTitle,
|
|
91
|
+
CardAction,
|
|
92
|
+
CardDescription,
|
|
93
|
+
CardContent,
|
|
94
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Collapsible as CollapsiblePrimitive } from "radix-ui"
|
|
4
|
+
|
|
5
|
+
function Collapsible({
|
|
6
|
+
...props
|
|
7
|
+
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
|
|
8
|
+
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function CollapsibleTrigger({
|
|
12
|
+
...props
|
|
13
|
+
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
|
|
14
|
+
return (
|
|
15
|
+
<CollapsiblePrimitive.CollapsibleTrigger
|
|
16
|
+
data-slot="collapsible-trigger"
|
|
17
|
+
className="cursor-pointer"
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function CollapsibleContent({
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
|
|
26
|
+
return (
|
|
27
|
+
<CollapsiblePrimitive.CollapsibleContent
|
|
28
|
+
data-slot="collapsible-content"
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|