@cloudbase/agent-react-ui 0.0.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.
Files changed (109) hide show
  1. package/README.md +135 -0
  2. package/components.json +21 -0
  3. package/dist/index.css +4241 -0
  4. package/dist/index.css.map +1 -0
  5. package/dist/index.d.mts +59 -0
  6. package/dist/index.d.ts +59 -0
  7. package/dist/index.js +2169 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/index.mjs +2182 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/example/.env.sample +2 -0
  12. package/example/App.tsx +368 -0
  13. package/example/app.css +1 -0
  14. package/example/index.html +12 -0
  15. package/example/main.tsx +9 -0
  16. package/example/vite.config.ts +34 -0
  17. package/package.json +75 -0
  18. package/postcss.config.cjs +3 -0
  19. package/src/components/ai-elements/agent.tsx +140 -0
  20. package/src/components/ai-elements/artifact.tsx +147 -0
  21. package/src/components/ai-elements/attachments.tsx +421 -0
  22. package/src/components/ai-elements/audio-player.tsx +228 -0
  23. package/src/components/ai-elements/canvas.tsx +22 -0
  24. package/src/components/ai-elements/chain-of-thought.tsx +228 -0
  25. package/src/components/ai-elements/checkpoint.tsx +71 -0
  26. package/src/components/ai-elements/code-block.tsx +532 -0
  27. package/src/components/ai-elements/commit.tsx +448 -0
  28. package/src/components/ai-elements/confirmation.tsx +176 -0
  29. package/src/components/ai-elements/connection.tsx +28 -0
  30. package/src/components/ai-elements/context.tsx +408 -0
  31. package/src/components/ai-elements/controls.tsx +18 -0
  32. package/src/components/ai-elements/conversation.tsx +100 -0
  33. package/src/components/ai-elements/edge.tsx +140 -0
  34. package/src/components/ai-elements/environment-variables.tsx +295 -0
  35. package/src/components/ai-elements/file-tree.tsx +258 -0
  36. package/src/components/ai-elements/image.tsx +24 -0
  37. package/src/components/ai-elements/inline-citation.tsx +287 -0
  38. package/src/components/ai-elements/message.tsx +336 -0
  39. package/src/components/ai-elements/mic-selector.tsx +370 -0
  40. package/src/components/ai-elements/model-selector.tsx +211 -0
  41. package/src/components/ai-elements/node.tsx +71 -0
  42. package/src/components/ai-elements/open-in-chat.tsx +365 -0
  43. package/src/components/ai-elements/package-info.tsx +233 -0
  44. package/src/components/ai-elements/panel.tsx +15 -0
  45. package/src/components/ai-elements/persona.tsx +270 -0
  46. package/src/components/ai-elements/plan.tsx +142 -0
  47. package/src/components/ai-elements/prompt-input.tsx +1263 -0
  48. package/src/components/ai-elements/queue.tsx +274 -0
  49. package/src/components/ai-elements/reasoning.tsx +193 -0
  50. package/src/components/ai-elements/sandbox.tsx +126 -0
  51. package/src/components/ai-elements/schema-display.tsx +458 -0
  52. package/src/components/ai-elements/shimmer.tsx +64 -0
  53. package/src/components/ai-elements/snippet.tsx +139 -0
  54. package/src/components/ai-elements/sources.tsx +77 -0
  55. package/src/components/ai-elements/speech-input.tsx +301 -0
  56. package/src/components/ai-elements/stack-trace.tsx +482 -0
  57. package/src/components/ai-elements/suggestion.tsx +53 -0
  58. package/src/components/ai-elements/task.tsx +87 -0
  59. package/src/components/ai-elements/terminal.tsx +261 -0
  60. package/src/components/ai-elements/test-results.tsx +485 -0
  61. package/src/components/ai-elements/tool.tsx +174 -0
  62. package/src/components/ai-elements/toolbar.tsx +16 -0
  63. package/src/components/ai-elements/transcription.tsx +124 -0
  64. package/src/components/ai-elements/voice-selector.tsx +479 -0
  65. package/src/components/ai-elements/web-preview.tsx +263 -0
  66. package/src/components/chat/Chat.tsx +178 -0
  67. package/src/components/chat/Input.tsx +98 -0
  68. package/src/components/chat/Message.tsx +276 -0
  69. package/src/components/chat/index.ts +2 -0
  70. package/src/components/index.ts +1 -0
  71. package/src/components/ui/accordion.tsx +64 -0
  72. package/src/components/ui/alert.tsx +66 -0
  73. package/src/components/ui/avatar.tsx +107 -0
  74. package/src/components/ui/badge.tsx +48 -0
  75. package/src/components/ui/button-group.tsx +83 -0
  76. package/src/components/ui/button.tsx +64 -0
  77. package/src/components/ui/card.tsx +92 -0
  78. package/src/components/ui/carousel.tsx +239 -0
  79. package/src/components/ui/collapsible.tsx +31 -0
  80. package/src/components/ui/command.tsx +184 -0
  81. package/src/components/ui/dialog.tsx +158 -0
  82. package/src/components/ui/dropdown-menu.tsx +257 -0
  83. package/src/components/ui/hover-card.tsx +42 -0
  84. package/src/components/ui/input-group.tsx +168 -0
  85. package/src/components/ui/input.tsx +21 -0
  86. package/src/components/ui/popover.tsx +87 -0
  87. package/src/components/ui/progress.tsx +31 -0
  88. package/src/components/ui/scroll-area.tsx +56 -0
  89. package/src/components/ui/select.tsx +190 -0
  90. package/src/components/ui/separator.tsx +28 -0
  91. package/src/components/ui/spinner.tsx +16 -0
  92. package/src/components/ui/switch.tsx +33 -0
  93. package/src/components/ui/tabs.tsx +91 -0
  94. package/src/components/ui/textarea.tsx +18 -0
  95. package/src/components/ui/tooltip.tsx +61 -0
  96. package/src/css/global.css +123 -0
  97. package/src/css/index.css +1 -0
  98. package/src/hooks/index.ts +1 -0
  99. package/src/hooks/use-copy-to-clipboard.ts +31 -0
  100. package/src/index.ts +4 -0
  101. package/src/lib/utils.ts +6 -0
  102. package/src/locales/context.ts +8 -0
  103. package/src/locales/hooks.ts +20 -0
  104. package/src/locales/index.ts +3 -0
  105. package/src/locales/langs/en.ts +17 -0
  106. package/src/locales/langs/index.ts +12 -0
  107. package/src/locales/langs/zh-cn.ts +18 -0
  108. package/tsconfig.json +21 -0
  109. package/tsup.config.ts +21 -0
@@ -0,0 +1,368 @@
1
+ import { AgKitUI, type LocaleCode } from "..";
2
+ import {
3
+ AgKit,
4
+ CloudBaseTransport,
5
+ type ToolCallState,
6
+ useChat,
7
+ useClientTool,
8
+ useToolCall,
9
+ } from "@cloudbase/agent-react-core";
10
+ import {
11
+ Confirmation,
12
+ ConfirmationAccepted,
13
+ ConfirmationAction,
14
+ ConfirmationActions,
15
+ ConfirmationRejected,
16
+ ConfirmationRequest,
17
+ ConfirmationTitle,
18
+ } from "@/components/ai-elements/confirmation";
19
+ import { Button } from "@/components/ui/button";
20
+ import {
21
+ Select,
22
+ SelectContent,
23
+ SelectItem,
24
+ SelectTrigger,
25
+ SelectValue,
26
+ } from "@/components/ui/select";
27
+ import { z } from "zod";
28
+ import cloudbase from "@cloudbase/js-sdk";
29
+ import { useEffect, useRef, useState } from "react";
30
+ import "../dist/index.css";
31
+ import "./app.css";
32
+
33
+ const app = cloudbase.init({
34
+ env: import.meta.env.VITE_ENV,
35
+ });
36
+
37
+ app.auth.signInAnonymously();
38
+
39
+ // Create transport
40
+ const transport = new CloudBaseTransport({
41
+ app,
42
+ agentId: import.meta.env.VITE_AGENT_ID,
43
+ });
44
+
45
+ const randomNameParameters = z
46
+ .object({
47
+ count: z.number().optional(),
48
+ })
49
+ .toJSONSchema();
50
+
51
+ const weatherParameters = z
52
+ .object({
53
+ city: z.string().optional(),
54
+ })
55
+ .toJSONSchema();
56
+
57
+ const languageParameters = z
58
+ .object({
59
+ locale: z.string().optional(),
60
+ })
61
+ .toJSONSchema();
62
+
63
+ const themeParameters = z
64
+ .object({
65
+ theme: z.string().optional(),
66
+ })
67
+ .toJSONSchema();
68
+
69
+ type ThemeMode = "light" | "dark";
70
+
71
+ function normalizeLocale(value: unknown, fallback: LocaleCode): LocaleCode {
72
+ const normalized = String(value ?? "")
73
+ .trim()
74
+ .toLowerCase();
75
+ if (normalized === "en" || normalized === "en-us") {
76
+ return "en-US";
77
+ }
78
+ if (normalized === "zh" || normalized === "zh-cn") {
79
+ return "zh-CN";
80
+ }
81
+ return fallback;
82
+ }
83
+
84
+ function normalizeTheme(value: unknown, fallback: ThemeMode): ThemeMode {
85
+ const normalized = String(value ?? "")
86
+ .trim()
87
+ .toLowerCase();
88
+ if (normalized === "dark") return "dark";
89
+ if (normalized === "light") return "light";
90
+ if (normalized === "toggle") return fallback === "dark" ? "light" : "dark";
91
+ return fallback;
92
+ }
93
+
94
+ interface ThemeSwitchConfirmationProps {
95
+ toolCall: ToolCallState;
96
+ isDark: boolean;
97
+ setIsDark: (value: boolean) => void;
98
+ respond: (result: string) => void;
99
+ }
100
+
101
+ function ThemeSwitchConfirmation({
102
+ toolCall,
103
+ isDark,
104
+ setIsDark,
105
+ respond,
106
+ }: ThemeSwitchConfirmationProps) {
107
+ const [state, setState] = useState<
108
+ "approval-requested" | "approval-responded"
109
+ >("approval-requested");
110
+ const [approved, setApproved] = useState<boolean | undefined>(undefined);
111
+ const requestedTheme = normalizeTheme(
112
+ toolCall.args?.theme,
113
+ isDark ? "dark" : "light"
114
+ );
115
+ const nextIsDark = requestedTheme === "dark";
116
+ const nextThemeLabel = nextIsDark ? "Dark" : "Light";
117
+
118
+ return (
119
+ <Confirmation
120
+ approval={{ id: toolCall.toolCallId, approved }}
121
+ state={state as any}
122
+ className="bg-muted/30"
123
+ >
124
+ <ConfirmationTitle>
125
+ Theme switch requested: {nextThemeLabel} mode
126
+ </ConfirmationTitle>
127
+ <ConfirmationRequest>
128
+ <div className="text-sm text-muted-foreground">
129
+ Do you want to apply this theme change?
130
+ </div>
131
+ </ConfirmationRequest>
132
+ <ConfirmationActions>
133
+ <ConfirmationAction
134
+ variant="outline"
135
+ disabled={state !== "approval-requested"}
136
+ onClick={() => {
137
+ setApproved(false);
138
+ setState("approval-responded");
139
+ respond(
140
+ JSON.stringify({
141
+ success: false,
142
+ cancelled: true,
143
+ currentTheme: isDark ? "dark" : "light",
144
+ })
145
+ );
146
+ }}
147
+ >
148
+ Cancel
149
+ </ConfirmationAction>
150
+ <ConfirmationAction
151
+ disabled={state !== "approval-requested"}
152
+ onClick={() => {
153
+ setIsDark(nextIsDark);
154
+ setApproved(true);
155
+ setState("approval-responded");
156
+ respond(
157
+ JSON.stringify({
158
+ success: true,
159
+ theme: nextIsDark ? "dark" : "light",
160
+ })
161
+ );
162
+ }}
163
+ >
164
+ Confirm
165
+ </ConfirmationAction>
166
+ </ConfirmationActions>
167
+ <ConfirmationAccepted>
168
+ <div className="text-sm text-muted-foreground">
169
+ Theme changed to {nextThemeLabel} mode.
170
+ </div>
171
+ </ConfirmationAccepted>
172
+ <ConfirmationRejected>
173
+ <div className="text-sm text-muted-foreground">
174
+ Theme switch cancelled.
175
+ </div>
176
+ </ConfirmationRejected>
177
+ </Confirmation>
178
+ );
179
+ }
180
+
181
+ interface ExampleToolRegistrationsProps {
182
+ locale: LocaleCode;
183
+ setLocale: (locale: LocaleCode) => void;
184
+ isDark: boolean;
185
+ setIsDark: (value: boolean) => void;
186
+ }
187
+
188
+ function ExampleToolRegistrations({
189
+ locale,
190
+ setLocale,
191
+ isDark,
192
+ setIsDark,
193
+ }: ExampleToolRegistrationsProps) {
194
+ useClientTool({
195
+ name: "generate_random_name",
196
+ description: "Generate a random name",
197
+ parameters: randomNameParameters,
198
+ handler: ({ args }) => {
199
+ const count = Number(args.count ?? 1);
200
+ const names = ["John", "Emma", "Olivia", "Liam", "Noah"];
201
+ return Array.from({ length: Math.max(1, count) })
202
+ .map((_, index) => names[index % names.length])
203
+ .join(", ");
204
+ },
205
+ });
206
+
207
+ useClientTool({
208
+ name: "get_local_weather",
209
+ description: "Get local weather for a city",
210
+ parameters: weatherParameters,
211
+ handler: ({ args }) => {
212
+ const city = String(args.city || "Shanghai");
213
+ return JSON.stringify({
214
+ city,
215
+ weather: "sunny",
216
+ temperature: "26C",
217
+ });
218
+ },
219
+ });
220
+
221
+ useClientTool({
222
+ name: "switch_language",
223
+ description: "Switch the UI language locale to zh-CN or en-US",
224
+ parameters: languageParameters,
225
+ handler: ({ args }) => {
226
+ const nextLocale = normalizeLocale(args.locale, locale);
227
+ setLocale(nextLocale);
228
+ return JSON.stringify({
229
+ success: true,
230
+ locale: nextLocale,
231
+ });
232
+ },
233
+ });
234
+
235
+ useClientTool({
236
+ name: "switch_theme",
237
+ description: "Switch UI theme.",
238
+ parameters: themeParameters,
239
+ });
240
+
241
+ useToolCall({
242
+ name: "get_local_weather",
243
+ render: ({ toolCall }) => (
244
+ <div className="rounded-md border bg-muted/40 p-3 text-sm">
245
+ <div className="font-medium">Custom Weather Tool Renderer</div>
246
+ <div className="mt-1 text-muted-foreground">
247
+ Params: {JSON.stringify(toolCall.args || {})}
248
+ </div>
249
+ {toolCall.result !== undefined && (
250
+ <div className="mt-2">
251
+ Result:{" "}
252
+ <span className="font-medium">{String(toolCall.result)}</span>
253
+ </div>
254
+ )}
255
+ </div>
256
+ ),
257
+ });
258
+
259
+ useToolCall({
260
+ name: "switch_theme",
261
+ render: ({ toolCall, respond }) => (
262
+ <ThemeSwitchConfirmation
263
+ toolCall={toolCall}
264
+ isDark={isDark}
265
+ setIsDark={setIsDark}
266
+ respond={respond}
267
+ />
268
+ ),
269
+ });
270
+
271
+ return null;
272
+ }
273
+
274
+ // External component demonstrating useChat access
275
+ interface ExternalControlsProps {
276
+ locale: LocaleCode;
277
+ onLocaleChange: (nextLocale: LocaleCode) => void;
278
+ isDark: boolean;
279
+ onToggleTheme: () => void;
280
+ }
281
+
282
+ function ExternalControls({
283
+ locale,
284
+ onLocaleChange,
285
+ isDark,
286
+ onToggleTheme,
287
+ }: ExternalControlsProps) {
288
+ const { uiMessages, streaming, sendMessage } = useChat();
289
+
290
+ return (
291
+ <div className="p-4 border-b border-border bg-background flex items-center justify-between">
292
+ <span className="text-sm text-muted-foreground">
293
+ Messages: {uiMessages.length} | {streaming ? "Streaming..." : "Ready"}
294
+ </span>
295
+ <div className="flex items-center gap-2">
296
+ <Select
297
+ value={locale}
298
+ onValueChange={(value) => onLocaleChange(value as LocaleCode)}
299
+ >
300
+ <SelectTrigger className="w-28 bg-background text-foreground border-border">
301
+ <SelectValue placeholder="Language" />
302
+ </SelectTrigger>
303
+ <SelectContent className="bg-background text-foreground border-border">
304
+ <SelectItem value="zh-CN">中文</SelectItem>
305
+ <SelectItem value="en-US">English</SelectItem>
306
+ </SelectContent>
307
+ </Select>
308
+ <Button
309
+ variant="outline"
310
+ className="bg-background text-foreground border-border"
311
+ onClick={onToggleTheme}
312
+ >
313
+ {isDark ? "Dark" : "Light"}
314
+ </Button>
315
+ <Button onClick={() => sendMessage("Hello from external button!")}>
316
+ Send from outside
317
+ </Button>
318
+ </div>
319
+ </div>
320
+ );
321
+ }
322
+
323
+ export function App() {
324
+ const [locale, setLocale] = useState<LocaleCode>("zh-CN");
325
+ const [isDark, setIsDark] = useState(false);
326
+ const threadId = useRef(`threadId_${Date.now()}`);
327
+ const suggestions =
328
+ locale === "zh-CN"
329
+ ? ["切换到英文界面", "请切换到深色模式", "帮我查一下上海天气"]
330
+ : [
331
+ "Switch UI language to Chinese",
332
+ "Please switch to dark mode",
333
+ "Check weather in Shanghai",
334
+ ];
335
+
336
+ useEffect(() => {
337
+ document.documentElement.classList.toggle("dark", isDark);
338
+ return () => {
339
+ document.documentElement.classList.remove("dark");
340
+ };
341
+ }, [isDark]);
342
+
343
+ return (
344
+ <AgKit transport={transport} defaultThreadId={threadId.current}>
345
+ <div className="h-screen w-screen flex flex-col">
346
+ <ExampleToolRegistrations
347
+ locale={locale}
348
+ setLocale={setLocale}
349
+ isDark={isDark}
350
+ setIsDark={setIsDark}
351
+ />
352
+
353
+ {/* External component using useChat - shares the same state */}
354
+ <ExternalControls
355
+ locale={locale}
356
+ onLocaleChange={setLocale}
357
+ isDark={isDark}
358
+ onToggleTheme={() => setIsDark((prev) => !prev)}
359
+ />
360
+
361
+ {/* Main chat component */}
362
+ <div className="flex-1 max-h-[calc(100vh-60px)]">
363
+ <AgKitUI locale={locale} suggestions={suggestions} />
364
+ </div>
365
+ </div>
366
+ </AgKit>
367
+ );
368
+ }
@@ -0,0 +1 @@
1
+ @import "tailwindcss";
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Agent UI</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="./main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,9 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { App } from "./App";
4
+
5
+ createRoot(document.getElementById("root")!).render(
6
+ <StrictMode>
7
+ <App />
8
+ </StrictMode>
9
+ );
@@ -0,0 +1,34 @@
1
+ import { resolve } from 'node:path';
2
+ import { defineConfig } from 'vite';
3
+ import react from '@vitejs/plugin-react';
4
+ import tailwindcss from '@tailwindcss/vite'
5
+
6
+ const ROOT_DIR = resolve(__dirname);
7
+
8
+ export default defineConfig({
9
+ root: ROOT_DIR,
10
+ base: '/AgKitUI',
11
+
12
+ server: {
13
+ host: true,
14
+ port: 2024,
15
+ open: false,
16
+ },
17
+
18
+ plugins: [
19
+ react(),
20
+ tailwindcss(),
21
+ ],
22
+
23
+ resolve: {
24
+ alias: {
25
+ '@': resolve(ROOT_DIR, '../src'),
26
+ },
27
+ },
28
+
29
+ build: {
30
+ target: 'es2018',
31
+ emptyOutDir: true,
32
+ outDir: resolve(ROOT_DIR, 'dist'),
33
+ },
34
+ });
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@cloudbase/agent-react-ui",
3
+ "version": "0.0.23",
4
+ "description": "",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js"
12
+ },
13
+ "./styles.css": "./dist/index.css"
14
+ },
15
+ "types": "./dist/index.d.ts",
16
+ "keywords": [],
17
+ "author": "",
18
+ "license": "ISC",
19
+ "dependencies": {
20
+ "@radix-ui/react-use-controllable-state": "^1.2.2",
21
+ "@rive-app/react-webgl2": "^4.26.2",
22
+ "@streamdown/cjk": "^1.0.1",
23
+ "@streamdown/code": "^1.0.1",
24
+ "@streamdown/math": "^1.0.1",
25
+ "@streamdown/mermaid": "^1.0.1",
26
+ "@xyflow/react": "^12.10.0",
27
+ "ai": "^5.0.108",
28
+ "ai-elements": "^1.8.2",
29
+ "ansi-to-react": "^6.2.6",
30
+ "class-variance-authority": "^0.7.1",
31
+ "clsx": "^2.1.1",
32
+ "cmdk": "^1.1.1",
33
+ "embla-carousel-react": "^8.6.0",
34
+ "lucide-react": "^0.544.0",
35
+ "media-chrome": "^4.17.2",
36
+ "motion": "^12.23.25",
37
+ "nanoid": "^5.1.6",
38
+ "radix-ui": "^1.4.3",
39
+ "react": "19.2.0",
40
+ "shiki": "^3.22.0",
41
+ "streamdown": "^1.6.10",
42
+ "tailwind-merge": "^3.4.0",
43
+ "tokenlens": "^1.3.1",
44
+ "tw-animate-css": "^1.4.0",
45
+ "use-stick-to-bottom": "^1.1.1",
46
+ "uuid": "^13.0.0",
47
+ "vite": "^7.3.1",
48
+ "zod": "^4.3.6",
49
+ "@cloudbase/agent-react-core": "^0.0.23"
50
+ },
51
+ "devDependencies": {
52
+ "@cloudbase/js-sdk": "^2.25.4",
53
+ "@tailwindcss/postcss": "^4.1.18",
54
+ "@tailwindcss/vite": "^4.1.17",
55
+ "@types/react": "^19",
56
+ "@types/react-dom": "^19",
57
+ "@vitejs/plugin-react": "^5.1.2",
58
+ "autoprefixer": "^10.4.22",
59
+ "postcss": "^8.5.6",
60
+ "react-dom": "19.2.0",
61
+ "tailwindcss": "^4.1.18",
62
+ "tsup": "^8.5.1",
63
+ "zustand": "^5.0.11"
64
+ },
65
+ "peerDependencies": {
66
+ "react": "^18.0.0 || ^19.0.0"
67
+ },
68
+ "scripts": {
69
+ "dev": "tsup --watch --config tsup.config.ts",
70
+ "build": "tsup --config tsup.config.ts",
71
+ "example:dev": "vite --config example/vite.config.ts",
72
+ "example:build": "vite build --config example/vite.config.ts",
73
+ "example:preview": "vite preview --config example/vite.config.ts"
74
+ }
75
+ }
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ plugins: [require("@tailwindcss/postcss")(), require("autoprefixer")()],
3
+ };
@@ -0,0 +1,140 @@
1
+ "use client";
2
+
3
+ import {
4
+ Accordion,
5
+ AccordionContent,
6
+ AccordionItem,
7
+ AccordionTrigger,
8
+ } from "@/components/ui/accordion";
9
+ import { Badge } from "@/components/ui/badge";
10
+ import { cn } from "@/lib/utils";
11
+ import type { Tool } from "ai";
12
+ import { BotIcon } from "lucide-react";
13
+ import type { ComponentProps } from "react";
14
+ import { memo } from "react";
15
+ import { CodeBlock } from "./code-block";
16
+
17
+ export type AgentProps = ComponentProps<"div">;
18
+
19
+ export const Agent = memo(({ className, ...props }: AgentProps) => (
20
+ <div
21
+ className={cn("not-prose w-full rounded-md border", className)}
22
+ {...props}
23
+ />
24
+ ));
25
+
26
+ export type AgentHeaderProps = ComponentProps<"div"> & {
27
+ name: string;
28
+ model?: string;
29
+ };
30
+
31
+ export const AgentHeader = memo(
32
+ ({ className, name, model, ...props }: AgentHeaderProps) => (
33
+ <div
34
+ className={cn(
35
+ "flex w-full items-center justify-between gap-4 p-3",
36
+ className
37
+ )}
38
+ {...props}
39
+ >
40
+ <div className="flex items-center gap-2">
41
+ <BotIcon className="size-4 text-muted-foreground" />
42
+ <span className="font-medium text-sm">{name}</span>
43
+ {model && (
44
+ <Badge className="font-mono text-xs" variant="secondary">
45
+ {model}
46
+ </Badge>
47
+ )}
48
+ </div>
49
+ </div>
50
+ )
51
+ );
52
+
53
+ export type AgentContentProps = ComponentProps<"div">;
54
+
55
+ export const AgentContent = memo(
56
+ ({ className, ...props }: AgentContentProps) => (
57
+ <div className={cn("space-y-4 p-4 pt-0", className)} {...props} />
58
+ )
59
+ );
60
+
61
+ export type AgentInstructionsProps = ComponentProps<"div"> & {
62
+ children: string;
63
+ };
64
+
65
+ export const AgentInstructions = memo(
66
+ ({ className, children, ...props }: AgentInstructionsProps) => (
67
+ <div className={cn("space-y-2", className)} {...props}>
68
+ <span className="font-medium text-muted-foreground text-sm">
69
+ Instructions
70
+ </span>
71
+ <div className="rounded-md bg-muted/50 p-3 text-muted-foreground text-sm">
72
+ <p>{children}</p>
73
+ </div>
74
+ </div>
75
+ )
76
+ );
77
+
78
+ export type AgentToolsProps = ComponentProps<typeof Accordion>;
79
+
80
+ export const AgentTools = memo(({ className, ...props }: AgentToolsProps) => (
81
+ <div className={cn("space-y-2", className)}>
82
+ <span className="font-medium text-muted-foreground text-sm">Tools</span>
83
+ <Accordion className="rounded-md border" {...props} />
84
+ </div>
85
+ ));
86
+
87
+ export type AgentToolProps = ComponentProps<typeof AccordionItem> & {
88
+ tool: Tool;
89
+ };
90
+
91
+ export const AgentTool = memo(
92
+ ({ className, tool, value, ...props }: AgentToolProps) => {
93
+ const schema =
94
+ "jsonSchema" in tool && tool.jsonSchema
95
+ ? tool.jsonSchema
96
+ : tool.inputSchema;
97
+
98
+ return (
99
+ <AccordionItem
100
+ className={cn("border-b last:border-b-0", className)}
101
+ value={value}
102
+ {...props}
103
+ >
104
+ <AccordionTrigger className="px-3 py-2 text-sm hover:no-underline">
105
+ {tool.description ?? "No description"}
106
+ </AccordionTrigger>
107
+ <AccordionContent className="px-3 pb-3">
108
+ <div className="rounded-md bg-muted/50">
109
+ <CodeBlock code={JSON.stringify(schema, null, 2)} language="json" />
110
+ </div>
111
+ </AccordionContent>
112
+ </AccordionItem>
113
+ );
114
+ }
115
+ );
116
+
117
+ export type AgentOutputProps = ComponentProps<"div"> & {
118
+ schema: string;
119
+ };
120
+
121
+ export const AgentOutput = memo(
122
+ ({ className, schema, ...props }: AgentOutputProps) => (
123
+ <div className={cn("space-y-2", className)} {...props}>
124
+ <span className="font-medium text-muted-foreground text-sm">
125
+ Output Schema
126
+ </span>
127
+ <div className="rounded-md bg-muted/50">
128
+ <CodeBlock code={schema} language="typescript" />
129
+ </div>
130
+ </div>
131
+ )
132
+ );
133
+
134
+ Agent.displayName = "Agent";
135
+ AgentHeader.displayName = "AgentHeader";
136
+ AgentContent.displayName = "AgentContent";
137
+ AgentInstructions.displayName = "AgentInstructions";
138
+ AgentTools.displayName = "AgentTools";
139
+ AgentTool.displayName = "AgentTool";
140
+ AgentOutput.displayName = "AgentOutput";