@cloudbase/agent-react-ui 1.0.1-alpha.32
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 +123 -0
- package/components.json +21 -0
- package/dist/index.css +4241 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +59 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +2169 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2182 -0
- package/dist/index.mjs.map +1 -0
- package/example/.env.sample +2 -0
- package/example/App.tsx +368 -0
- package/example/app.css +1 -0
- package/example/index.html +12 -0
- package/example/main.tsx +9 -0
- package/example/vite.config.ts +34 -0
- package/package.json +75 -0
- package/postcss.config.cjs +3 -0
- package/src/components/ai-elements/agent.tsx +140 -0
- package/src/components/ai-elements/artifact.tsx +147 -0
- package/src/components/ai-elements/attachments.tsx +421 -0
- package/src/components/ai-elements/audio-player.tsx +228 -0
- package/src/components/ai-elements/canvas.tsx +22 -0
- package/src/components/ai-elements/chain-of-thought.tsx +228 -0
- package/src/components/ai-elements/checkpoint.tsx +71 -0
- package/src/components/ai-elements/code-block.tsx +532 -0
- package/src/components/ai-elements/commit.tsx +448 -0
- package/src/components/ai-elements/confirmation.tsx +176 -0
- package/src/components/ai-elements/connection.tsx +28 -0
- package/src/components/ai-elements/context.tsx +408 -0
- package/src/components/ai-elements/controls.tsx +18 -0
- package/src/components/ai-elements/conversation.tsx +100 -0
- package/src/components/ai-elements/edge.tsx +140 -0
- package/src/components/ai-elements/environment-variables.tsx +295 -0
- package/src/components/ai-elements/file-tree.tsx +258 -0
- package/src/components/ai-elements/image.tsx +24 -0
- package/src/components/ai-elements/inline-citation.tsx +287 -0
- package/src/components/ai-elements/message.tsx +336 -0
- package/src/components/ai-elements/mic-selector.tsx +370 -0
- package/src/components/ai-elements/model-selector.tsx +211 -0
- package/src/components/ai-elements/node.tsx +71 -0
- package/src/components/ai-elements/open-in-chat.tsx +365 -0
- package/src/components/ai-elements/package-info.tsx +233 -0
- package/src/components/ai-elements/panel.tsx +15 -0
- package/src/components/ai-elements/persona.tsx +270 -0
- package/src/components/ai-elements/plan.tsx +142 -0
- package/src/components/ai-elements/prompt-input.tsx +1263 -0
- package/src/components/ai-elements/queue.tsx +274 -0
- package/src/components/ai-elements/reasoning.tsx +193 -0
- package/src/components/ai-elements/sandbox.tsx +126 -0
- package/src/components/ai-elements/schema-display.tsx +458 -0
- package/src/components/ai-elements/shimmer.tsx +64 -0
- package/src/components/ai-elements/snippet.tsx +139 -0
- package/src/components/ai-elements/sources.tsx +77 -0
- package/src/components/ai-elements/speech-input.tsx +301 -0
- package/src/components/ai-elements/stack-trace.tsx +482 -0
- package/src/components/ai-elements/suggestion.tsx +53 -0
- package/src/components/ai-elements/task.tsx +87 -0
- package/src/components/ai-elements/terminal.tsx +261 -0
- package/src/components/ai-elements/test-results.tsx +485 -0
- package/src/components/ai-elements/tool.tsx +174 -0
- package/src/components/ai-elements/toolbar.tsx +16 -0
- package/src/components/ai-elements/transcription.tsx +124 -0
- package/src/components/ai-elements/voice-selector.tsx +479 -0
- package/src/components/ai-elements/web-preview.tsx +263 -0
- package/src/components/chat/Chat.tsx +178 -0
- package/src/components/chat/Input.tsx +98 -0
- package/src/components/chat/Message.tsx +276 -0
- package/src/components/chat/index.ts +2 -0
- package/src/components/index.ts +1 -0
- package/src/components/ui/accordion.tsx +64 -0
- package/src/components/ui/alert.tsx +66 -0
- package/src/components/ui/avatar.tsx +107 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button-group.tsx +83 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/carousel.tsx +239 -0
- package/src/components/ui/collapsible.tsx +31 -0
- package/src/components/ui/command.tsx +184 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/hover-card.tsx +42 -0
- package/src/components/ui/input-group.tsx +168 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/popover.tsx +87 -0
- package/src/components/ui/progress.tsx +31 -0
- package/src/components/ui/scroll-area.tsx +56 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/switch.tsx +33 -0
- package/src/components/ui/tabs.tsx +91 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/css/global.css +123 -0
- package/src/css/index.css +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-copy-to-clipboard.ts +31 -0
- package/src/index.ts +4 -0
- package/src/lib/utils.ts +6 -0
- package/src/locales/context.ts +8 -0
- package/src/locales/hooks.ts +20 -0
- package/src/locales/index.ts +3 -0
- package/src/locales/langs/en.ts +17 -0
- package/src/locales/langs/index.ts +12 -0
- package/src/locales/langs/zh-cn.ts +18 -0
- package/tsconfig.json +21 -0
- package/tsup.config.ts +21 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Badge } from "@/components/ui/badge";
|
|
4
|
+
import {
|
|
5
|
+
Collapsible,
|
|
6
|
+
CollapsibleContent,
|
|
7
|
+
CollapsibleTrigger,
|
|
8
|
+
} from "@/components/ui/collapsible";
|
|
9
|
+
import { cn } from "@/lib/utils";
|
|
10
|
+
import {
|
|
11
|
+
CheckCircle2Icon,
|
|
12
|
+
ChevronRightIcon,
|
|
13
|
+
CircleDotIcon,
|
|
14
|
+
CircleIcon,
|
|
15
|
+
XCircleIcon,
|
|
16
|
+
} from "lucide-react";
|
|
17
|
+
import {
|
|
18
|
+
type ComponentProps,
|
|
19
|
+
createContext,
|
|
20
|
+
type HTMLAttributes,
|
|
21
|
+
useContext,
|
|
22
|
+
} from "react";
|
|
23
|
+
|
|
24
|
+
type TestStatus = "passed" | "failed" | "skipped" | "running";
|
|
25
|
+
|
|
26
|
+
interface TestResultsSummary {
|
|
27
|
+
passed: number;
|
|
28
|
+
failed: number;
|
|
29
|
+
skipped: number;
|
|
30
|
+
total: number;
|
|
31
|
+
duration?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface TestResultsContextType {
|
|
35
|
+
summary?: TestResultsSummary;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const TestResultsContext = createContext<TestResultsContextType>({});
|
|
39
|
+
|
|
40
|
+
export type TestResultsProps = HTMLAttributes<HTMLDivElement> & {
|
|
41
|
+
summary?: TestResultsSummary;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const TestResults = ({
|
|
45
|
+
summary,
|
|
46
|
+
className,
|
|
47
|
+
children,
|
|
48
|
+
...props
|
|
49
|
+
}: TestResultsProps) => (
|
|
50
|
+
<TestResultsContext.Provider value={{ summary }}>
|
|
51
|
+
<div
|
|
52
|
+
className={cn("rounded-lg border bg-background", className)}
|
|
53
|
+
{...props}
|
|
54
|
+
>
|
|
55
|
+
{children ??
|
|
56
|
+
(summary && (
|
|
57
|
+
<TestResultsHeader>
|
|
58
|
+
<TestResultsSummary />
|
|
59
|
+
<TestResultsDuration />
|
|
60
|
+
</TestResultsHeader>
|
|
61
|
+
))}
|
|
62
|
+
</div>
|
|
63
|
+
</TestResultsContext.Provider>
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
export type TestResultsHeaderProps = HTMLAttributes<HTMLDivElement>;
|
|
67
|
+
|
|
68
|
+
export const TestResultsHeader = ({
|
|
69
|
+
className,
|
|
70
|
+
children,
|
|
71
|
+
...props
|
|
72
|
+
}: TestResultsHeaderProps) => (
|
|
73
|
+
<div
|
|
74
|
+
className={cn(
|
|
75
|
+
"flex items-center justify-between border-b px-4 py-3",
|
|
76
|
+
className
|
|
77
|
+
)}
|
|
78
|
+
{...props}
|
|
79
|
+
>
|
|
80
|
+
{children}
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
export type TestResultsSummaryProps = HTMLAttributes<HTMLDivElement>;
|
|
85
|
+
|
|
86
|
+
export const TestResultsSummary = ({
|
|
87
|
+
className,
|
|
88
|
+
children,
|
|
89
|
+
...props
|
|
90
|
+
}: TestResultsSummaryProps) => {
|
|
91
|
+
const { summary } = useContext(TestResultsContext);
|
|
92
|
+
|
|
93
|
+
if (!summary) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div className={cn("flex items-center gap-3", className)} {...props}>
|
|
99
|
+
{children ?? (
|
|
100
|
+
<>
|
|
101
|
+
<Badge
|
|
102
|
+
className="gap-1 bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400"
|
|
103
|
+
variant="secondary"
|
|
104
|
+
>
|
|
105
|
+
<CheckCircle2Icon className="size-3" />
|
|
106
|
+
{summary.passed} passed
|
|
107
|
+
</Badge>
|
|
108
|
+
{summary.failed > 0 && (
|
|
109
|
+
<Badge
|
|
110
|
+
className="gap-1 bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"
|
|
111
|
+
variant="secondary"
|
|
112
|
+
>
|
|
113
|
+
<XCircleIcon className="size-3" />
|
|
114
|
+
{summary.failed} failed
|
|
115
|
+
</Badge>
|
|
116
|
+
)}
|
|
117
|
+
{summary.skipped > 0 && (
|
|
118
|
+
<Badge
|
|
119
|
+
className="gap-1 bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400"
|
|
120
|
+
variant="secondary"
|
|
121
|
+
>
|
|
122
|
+
<CircleIcon className="size-3" />
|
|
123
|
+
{summary.skipped} skipped
|
|
124
|
+
</Badge>
|
|
125
|
+
)}
|
|
126
|
+
</>
|
|
127
|
+
)}
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export type TestResultsDurationProps = HTMLAttributes<HTMLSpanElement>;
|
|
133
|
+
|
|
134
|
+
export const TestResultsDuration = ({
|
|
135
|
+
className,
|
|
136
|
+
children,
|
|
137
|
+
...props
|
|
138
|
+
}: TestResultsDurationProps) => {
|
|
139
|
+
const { summary } = useContext(TestResultsContext);
|
|
140
|
+
|
|
141
|
+
if (!summary?.duration) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const formatDuration = (ms: number) => {
|
|
146
|
+
if (ms < 1000) {
|
|
147
|
+
return `${ms}ms`;
|
|
148
|
+
}
|
|
149
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<span className={cn("text-muted-foreground text-sm", className)} {...props}>
|
|
154
|
+
{children ?? formatDuration(summary.duration)}
|
|
155
|
+
</span>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export type TestResultsProgressProps = HTMLAttributes<HTMLDivElement>;
|
|
160
|
+
|
|
161
|
+
export const TestResultsProgress = ({
|
|
162
|
+
className,
|
|
163
|
+
children,
|
|
164
|
+
...props
|
|
165
|
+
}: TestResultsProgressProps) => {
|
|
166
|
+
const { summary } = useContext(TestResultsContext);
|
|
167
|
+
|
|
168
|
+
if (!summary) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const passedPercent = (summary.passed / summary.total) * 100;
|
|
173
|
+
const failedPercent = (summary.failed / summary.total) * 100;
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<div className={cn("space-y-2", className)} {...props}>
|
|
177
|
+
{children ?? (
|
|
178
|
+
<>
|
|
179
|
+
<div className="flex h-2 overflow-hidden rounded-full bg-muted">
|
|
180
|
+
<div
|
|
181
|
+
className="bg-green-500 transition-all"
|
|
182
|
+
style={{ width: `${passedPercent}%` }}
|
|
183
|
+
/>
|
|
184
|
+
<div
|
|
185
|
+
className="bg-red-500 transition-all"
|
|
186
|
+
style={{ width: `${failedPercent}%` }}
|
|
187
|
+
/>
|
|
188
|
+
</div>
|
|
189
|
+
<div className="flex justify-between text-muted-foreground text-xs">
|
|
190
|
+
<span>
|
|
191
|
+
{summary.passed}/{summary.total} tests passed
|
|
192
|
+
</span>
|
|
193
|
+
<span>{passedPercent.toFixed(0)}%</span>
|
|
194
|
+
</div>
|
|
195
|
+
</>
|
|
196
|
+
)}
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
export type TestResultsContentProps = HTMLAttributes<HTMLDivElement>;
|
|
202
|
+
|
|
203
|
+
export const TestResultsContent = ({
|
|
204
|
+
className,
|
|
205
|
+
children,
|
|
206
|
+
...props
|
|
207
|
+
}: TestResultsContentProps) => (
|
|
208
|
+
<div className={cn("space-y-2 p-4", className)} {...props}>
|
|
209
|
+
{children}
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
interface TestSuiteContextType {
|
|
214
|
+
name: string;
|
|
215
|
+
status: TestStatus;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const TestSuiteContext = createContext<TestSuiteContextType>({
|
|
219
|
+
name: "",
|
|
220
|
+
status: "passed",
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
export type TestSuiteProps = ComponentProps<typeof Collapsible> & {
|
|
224
|
+
name: string;
|
|
225
|
+
status: TestStatus;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export const TestSuite = ({
|
|
229
|
+
name,
|
|
230
|
+
status,
|
|
231
|
+
className,
|
|
232
|
+
children,
|
|
233
|
+
...props
|
|
234
|
+
}: TestSuiteProps) => (
|
|
235
|
+
<TestSuiteContext.Provider value={{ name, status }}>
|
|
236
|
+
<Collapsible className={cn("rounded-lg border", className)} {...props}>
|
|
237
|
+
{children}
|
|
238
|
+
</Collapsible>
|
|
239
|
+
</TestSuiteContext.Provider>
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
export type TestSuiteNameProps = ComponentProps<typeof CollapsibleTrigger>;
|
|
243
|
+
|
|
244
|
+
export const TestSuiteName = ({
|
|
245
|
+
className,
|
|
246
|
+
children,
|
|
247
|
+
...props
|
|
248
|
+
}: TestSuiteNameProps) => {
|
|
249
|
+
const { name, status } = useContext(TestSuiteContext);
|
|
250
|
+
|
|
251
|
+
return (
|
|
252
|
+
<CollapsibleTrigger
|
|
253
|
+
className={cn(
|
|
254
|
+
"group flex w-full items-center gap-2 px-4 py-3 text-left transition-colors hover:bg-muted/50",
|
|
255
|
+
className
|
|
256
|
+
)}
|
|
257
|
+
{...props}
|
|
258
|
+
>
|
|
259
|
+
<ChevronRightIcon className="size-4 shrink-0 text-muted-foreground transition-transform group-data-[state=open]:rotate-90" />
|
|
260
|
+
<TestStatusIcon status={status} />
|
|
261
|
+
<span className="font-medium text-sm">{children ?? name}</span>
|
|
262
|
+
</CollapsibleTrigger>
|
|
263
|
+
);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export type TestSuiteStatsProps = HTMLAttributes<HTMLDivElement> & {
|
|
267
|
+
passed?: number;
|
|
268
|
+
failed?: number;
|
|
269
|
+
skipped?: number;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
export const TestSuiteStats = ({
|
|
273
|
+
passed = 0,
|
|
274
|
+
failed = 0,
|
|
275
|
+
skipped = 0,
|
|
276
|
+
className,
|
|
277
|
+
children,
|
|
278
|
+
...props
|
|
279
|
+
}: TestSuiteStatsProps) => (
|
|
280
|
+
<div
|
|
281
|
+
className={cn("ml-auto flex items-center gap-2 text-xs", className)}
|
|
282
|
+
{...props}
|
|
283
|
+
>
|
|
284
|
+
{children ?? (
|
|
285
|
+
<>
|
|
286
|
+
{passed > 0 && (
|
|
287
|
+
<span className="text-green-600 dark:text-green-400">
|
|
288
|
+
{passed} passed
|
|
289
|
+
</span>
|
|
290
|
+
)}
|
|
291
|
+
{failed > 0 && (
|
|
292
|
+
<span className="text-red-600 dark:text-red-400">
|
|
293
|
+
{failed} failed
|
|
294
|
+
</span>
|
|
295
|
+
)}
|
|
296
|
+
{skipped > 0 && (
|
|
297
|
+
<span className="text-yellow-600 dark:text-yellow-400">
|
|
298
|
+
{skipped} skipped
|
|
299
|
+
</span>
|
|
300
|
+
)}
|
|
301
|
+
</>
|
|
302
|
+
)}
|
|
303
|
+
</div>
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
export type TestSuiteContentProps = ComponentProps<typeof CollapsibleContent>;
|
|
307
|
+
|
|
308
|
+
export const TestSuiteContent = ({
|
|
309
|
+
className,
|
|
310
|
+
children,
|
|
311
|
+
...props
|
|
312
|
+
}: TestSuiteContentProps) => (
|
|
313
|
+
<CollapsibleContent className={cn("border-t", className)} {...props}>
|
|
314
|
+
<div className="divide-y">{children}</div>
|
|
315
|
+
</CollapsibleContent>
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
interface TestContextType {
|
|
319
|
+
name: string;
|
|
320
|
+
status: TestStatus;
|
|
321
|
+
duration?: number;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const TestContext = createContext<TestContextType>({
|
|
325
|
+
name: "",
|
|
326
|
+
status: "passed",
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
export type TestProps = HTMLAttributes<HTMLDivElement> & {
|
|
330
|
+
name: string;
|
|
331
|
+
status: TestStatus;
|
|
332
|
+
duration?: number;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
export const Test = ({
|
|
336
|
+
name,
|
|
337
|
+
status,
|
|
338
|
+
duration,
|
|
339
|
+
className,
|
|
340
|
+
children,
|
|
341
|
+
...props
|
|
342
|
+
}: TestProps) => (
|
|
343
|
+
<TestContext.Provider value={{ name, status, duration }}>
|
|
344
|
+
<div
|
|
345
|
+
className={cn("flex items-center gap-2 px-4 py-2 text-sm", className)}
|
|
346
|
+
{...props}
|
|
347
|
+
>
|
|
348
|
+
{children ?? (
|
|
349
|
+
<>
|
|
350
|
+
<TestStatus />
|
|
351
|
+
<TestName />
|
|
352
|
+
{duration !== undefined && <TestDuration />}
|
|
353
|
+
</>
|
|
354
|
+
)}
|
|
355
|
+
</div>
|
|
356
|
+
</TestContext.Provider>
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
const statusStyles: Record<TestStatus, string> = {
|
|
360
|
+
passed: "text-green-600 dark:text-green-400",
|
|
361
|
+
failed: "text-red-600 dark:text-red-400",
|
|
362
|
+
skipped: "text-yellow-600 dark:text-yellow-400",
|
|
363
|
+
running: "text-blue-600 dark:text-blue-400",
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const statusIcons: Record<TestStatus, React.ReactNode> = {
|
|
367
|
+
passed: <CheckCircle2Icon className="size-4" />,
|
|
368
|
+
failed: <XCircleIcon className="size-4" />,
|
|
369
|
+
skipped: <CircleIcon className="size-4" />,
|
|
370
|
+
running: <CircleDotIcon className="size-4 animate-pulse" />,
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const TestStatusIcon = ({ status }: { status: TestStatus }) => (
|
|
374
|
+
<span className={cn("shrink-0", statusStyles[status])}>
|
|
375
|
+
{statusIcons[status]}
|
|
376
|
+
</span>
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
export type TestStatusProps = HTMLAttributes<HTMLSpanElement>;
|
|
380
|
+
|
|
381
|
+
export const TestStatus = ({
|
|
382
|
+
className,
|
|
383
|
+
children,
|
|
384
|
+
...props
|
|
385
|
+
}: TestStatusProps) => {
|
|
386
|
+
const { status } = useContext(TestContext);
|
|
387
|
+
|
|
388
|
+
return (
|
|
389
|
+
<span
|
|
390
|
+
className={cn("shrink-0", statusStyles[status], className)}
|
|
391
|
+
{...props}
|
|
392
|
+
>
|
|
393
|
+
{children ?? statusIcons[status]}
|
|
394
|
+
</span>
|
|
395
|
+
);
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
export type TestNameProps = HTMLAttributes<HTMLSpanElement>;
|
|
399
|
+
|
|
400
|
+
export const TestName = ({ className, children, ...props }: TestNameProps) => {
|
|
401
|
+
const { name } = useContext(TestContext);
|
|
402
|
+
|
|
403
|
+
return (
|
|
404
|
+
<span className={cn("flex-1", className)} {...props}>
|
|
405
|
+
{children ?? name}
|
|
406
|
+
</span>
|
|
407
|
+
);
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
export type TestDurationProps = HTMLAttributes<HTMLSpanElement>;
|
|
411
|
+
|
|
412
|
+
export const TestDuration = ({
|
|
413
|
+
className,
|
|
414
|
+
children,
|
|
415
|
+
...props
|
|
416
|
+
}: TestDurationProps) => {
|
|
417
|
+
const { duration } = useContext(TestContext);
|
|
418
|
+
|
|
419
|
+
if (duration === undefined) {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return (
|
|
424
|
+
<span
|
|
425
|
+
className={cn("ml-auto text-muted-foreground text-xs", className)}
|
|
426
|
+
{...props}
|
|
427
|
+
>
|
|
428
|
+
{children ?? `${duration}ms`}
|
|
429
|
+
</span>
|
|
430
|
+
);
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
export type TestErrorProps = HTMLAttributes<HTMLDivElement>;
|
|
434
|
+
|
|
435
|
+
export const TestError = ({
|
|
436
|
+
className,
|
|
437
|
+
children,
|
|
438
|
+
...props
|
|
439
|
+
}: TestErrorProps) => (
|
|
440
|
+
<div
|
|
441
|
+
className={cn(
|
|
442
|
+
"mt-2 rounded-md bg-red-50 p-3 dark:bg-red-900/20",
|
|
443
|
+
className
|
|
444
|
+
)}
|
|
445
|
+
{...props}
|
|
446
|
+
>
|
|
447
|
+
{children}
|
|
448
|
+
</div>
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
export type TestErrorMessageProps = HTMLAttributes<HTMLParagraphElement>;
|
|
452
|
+
|
|
453
|
+
export const TestErrorMessage = ({
|
|
454
|
+
className,
|
|
455
|
+
children,
|
|
456
|
+
...props
|
|
457
|
+
}: TestErrorMessageProps) => (
|
|
458
|
+
<p
|
|
459
|
+
className={cn(
|
|
460
|
+
"font-medium text-red-700 text-sm dark:text-red-400",
|
|
461
|
+
className
|
|
462
|
+
)}
|
|
463
|
+
{...props}
|
|
464
|
+
>
|
|
465
|
+
{children}
|
|
466
|
+
</p>
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
export type TestErrorStackProps = HTMLAttributes<HTMLPreElement>;
|
|
470
|
+
|
|
471
|
+
export const TestErrorStack = ({
|
|
472
|
+
className,
|
|
473
|
+
children,
|
|
474
|
+
...props
|
|
475
|
+
}: TestErrorStackProps) => (
|
|
476
|
+
<pre
|
|
477
|
+
className={cn(
|
|
478
|
+
"mt-2 overflow-auto font-mono text-red-600 text-xs dark:text-red-400",
|
|
479
|
+
className
|
|
480
|
+
)}
|
|
481
|
+
{...props}
|
|
482
|
+
>
|
|
483
|
+
{children}
|
|
484
|
+
</pre>
|
|
485
|
+
);
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Badge } from "@/components/ui/badge";
|
|
4
|
+
import {
|
|
5
|
+
Collapsible,
|
|
6
|
+
CollapsibleContent,
|
|
7
|
+
CollapsibleTrigger,
|
|
8
|
+
} from "@/components/ui/collapsible";
|
|
9
|
+
import { cn } from "@/lib/utils";
|
|
10
|
+
import type { DynamicToolUIPart, ToolUIPart } from "ai";
|
|
11
|
+
import {
|
|
12
|
+
CheckCircleIcon,
|
|
13
|
+
ChevronDownIcon,
|
|
14
|
+
CircleIcon,
|
|
15
|
+
ClockIcon,
|
|
16
|
+
WrenchIcon,
|
|
17
|
+
XCircleIcon,
|
|
18
|
+
} from "lucide-react";
|
|
19
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
20
|
+
import { isValidElement } from "react";
|
|
21
|
+
import { CodeBlock } from "./code-block";
|
|
22
|
+
|
|
23
|
+
export type ToolProps = ComponentProps<typeof Collapsible>;
|
|
24
|
+
|
|
25
|
+
export const Tool = ({ className, ...props }: ToolProps) => (
|
|
26
|
+
<Collapsible
|
|
27
|
+
className={cn("group not-prose mb-4 w-full rounded-md border", className)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export type ToolPart = ToolUIPart | DynamicToolUIPart;
|
|
33
|
+
|
|
34
|
+
export type ToolHeaderProps = {
|
|
35
|
+
title?: string;
|
|
36
|
+
className?: string;
|
|
37
|
+
} & (
|
|
38
|
+
| { type: ToolUIPart["type"]; state: ToolUIPart["state"]; toolName?: never }
|
|
39
|
+
| {
|
|
40
|
+
type: DynamicToolUIPart["type"];
|
|
41
|
+
state: DynamicToolUIPart["state"];
|
|
42
|
+
toolName: string;
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export const getStatusBadge = (status: ToolPart["state"]) => {
|
|
47
|
+
const labels: Record<ToolPart["state"], string> = {
|
|
48
|
+
"input-streaming": "Pending",
|
|
49
|
+
"input-available": "Running",
|
|
50
|
+
"approval-requested": "Awaiting Approval",
|
|
51
|
+
"approval-responded": "Responded",
|
|
52
|
+
"output-available": "Completed",
|
|
53
|
+
"output-error": "Error",
|
|
54
|
+
"output-denied": "Denied",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const icons: Record<ToolPart["state"], ReactNode> = {
|
|
58
|
+
"input-streaming": <CircleIcon className="size-4" />,
|
|
59
|
+
"input-available": <ClockIcon className="size-4 animate-pulse" />,
|
|
60
|
+
"approval-requested": <ClockIcon className="size-4 text-yellow-600" />,
|
|
61
|
+
"approval-responded": <CheckCircleIcon className="size-4 text-blue-600" />,
|
|
62
|
+
"output-available": <CheckCircleIcon className="size-4 text-green-600" />,
|
|
63
|
+
"output-error": <XCircleIcon className="size-4 text-red-600" />,
|
|
64
|
+
"output-denied": <XCircleIcon className="size-4 text-orange-600" />,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Badge className="gap-1.5 rounded-full text-xs" variant="secondary">
|
|
69
|
+
{icons[status]}
|
|
70
|
+
{labels[status]}
|
|
71
|
+
</Badge>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const ToolHeader = ({
|
|
76
|
+
className,
|
|
77
|
+
title,
|
|
78
|
+
type,
|
|
79
|
+
state,
|
|
80
|
+
toolName,
|
|
81
|
+
...props
|
|
82
|
+
}: ToolHeaderProps) => {
|
|
83
|
+
const derivedName =
|
|
84
|
+
type === "dynamic-tool" ? toolName : type.split("-").slice(1).join("-");
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<CollapsibleTrigger
|
|
88
|
+
className={cn(
|
|
89
|
+
"flex w-full items-center justify-between gap-4 p-3",
|
|
90
|
+
className
|
|
91
|
+
)}
|
|
92
|
+
{...props}
|
|
93
|
+
>
|
|
94
|
+
<div className="flex items-center gap-2">
|
|
95
|
+
<WrenchIcon className="size-4 text-muted-foreground" />
|
|
96
|
+
<span className="font-medium text-sm">{title ?? derivedName}</span>
|
|
97
|
+
{getStatusBadge(state)}
|
|
98
|
+
</div>
|
|
99
|
+
<ChevronDownIcon className="size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
|
|
100
|
+
</CollapsibleTrigger>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export type ToolContentProps = ComponentProps<typeof CollapsibleContent>;
|
|
105
|
+
|
|
106
|
+
export const ToolContent = ({ className, ...props }: ToolContentProps) => (
|
|
107
|
+
<CollapsibleContent
|
|
108
|
+
className={cn(
|
|
109
|
+
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 space-y-4 p-4 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
|
|
110
|
+
className
|
|
111
|
+
)}
|
|
112
|
+
{...props}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
export type ToolInputProps = ComponentProps<"div"> & {
|
|
117
|
+
input: ToolPart["input"];
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const ToolInput = ({ className, input, ...props }: ToolInputProps) => (
|
|
121
|
+
<div className={cn("space-y-2 overflow-hidden", className)} {...props}>
|
|
122
|
+
<h4 className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
|
123
|
+
Parameters
|
|
124
|
+
</h4>
|
|
125
|
+
<div className="rounded-md bg-muted/50">
|
|
126
|
+
<CodeBlock code={JSON.stringify(input, null, 2)} language="json" />
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
export type ToolOutputProps = ComponentProps<"div"> & {
|
|
132
|
+
output: ToolPart["output"];
|
|
133
|
+
errorText: ToolPart["errorText"];
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const ToolOutput = ({
|
|
137
|
+
className,
|
|
138
|
+
output,
|
|
139
|
+
errorText,
|
|
140
|
+
...props
|
|
141
|
+
}: ToolOutputProps) => {
|
|
142
|
+
if (!(output || errorText)) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let Output = <div>{output as ReactNode}</div>;
|
|
147
|
+
|
|
148
|
+
if (typeof output === "object" && !isValidElement(output)) {
|
|
149
|
+
Output = (
|
|
150
|
+
<CodeBlock code={JSON.stringify(output, null, 2)} language="json" />
|
|
151
|
+
);
|
|
152
|
+
} else if (typeof output === "string") {
|
|
153
|
+
Output = <CodeBlock code={output} language="json" />;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<div className={cn("space-y-2", className)} {...props}>
|
|
158
|
+
<h4 className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
|
159
|
+
{errorText ? "Error" : "Result"}
|
|
160
|
+
</h4>
|
|
161
|
+
<div
|
|
162
|
+
className={cn(
|
|
163
|
+
"overflow-x-auto rounded-md text-xs [&_table]:w-full",
|
|
164
|
+
errorText
|
|
165
|
+
? "bg-destructive/10 text-destructive"
|
|
166
|
+
: "bg-muted/50 text-foreground"
|
|
167
|
+
)}
|
|
168
|
+
>
|
|
169
|
+
{errorText && <div>{errorText}</div>}
|
|
170
|
+
{Output}
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
import { NodeToolbar, Position } from "@xyflow/react";
|
|
3
|
+
import type { ComponentProps } from "react";
|
|
4
|
+
|
|
5
|
+
type ToolbarProps = ComponentProps<typeof NodeToolbar>;
|
|
6
|
+
|
|
7
|
+
export const Toolbar = ({ className, ...props }: ToolbarProps) => (
|
|
8
|
+
<NodeToolbar
|
|
9
|
+
className={cn(
|
|
10
|
+
"flex items-center gap-1 rounded-sm border bg-background p-1.5",
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
position={Position.Bottom}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
);
|