@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.
Files changed (109) hide show
  1. package/README.md +123 -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,263 @@
1
+ "use client";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+ import {
5
+ Collapsible,
6
+ CollapsibleContent,
7
+ CollapsibleTrigger,
8
+ } from "@/components/ui/collapsible";
9
+ import { Input } from "@/components/ui/input";
10
+ import {
11
+ Tooltip,
12
+ TooltipContent,
13
+ TooltipProvider,
14
+ TooltipTrigger,
15
+ } from "@/components/ui/tooltip";
16
+ import { cn } from "@/lib/utils";
17
+ import { ChevronDownIcon } from "lucide-react";
18
+ import type { ComponentProps, ReactNode } from "react";
19
+ import { createContext, useContext, useEffect, useState } from "react";
20
+
21
+ export interface WebPreviewContextValue {
22
+ url: string;
23
+ setUrl: (url: string) => void;
24
+ consoleOpen: boolean;
25
+ setConsoleOpen: (open: boolean) => void;
26
+ }
27
+
28
+ const WebPreviewContext = createContext<WebPreviewContextValue | null>(null);
29
+
30
+ const useWebPreview = () => {
31
+ const context = useContext(WebPreviewContext);
32
+ if (!context) {
33
+ throw new Error("WebPreview components must be used within a WebPreview");
34
+ }
35
+ return context;
36
+ };
37
+
38
+ export type WebPreviewProps = ComponentProps<"div"> & {
39
+ defaultUrl?: string;
40
+ onUrlChange?: (url: string) => void;
41
+ };
42
+
43
+ export const WebPreview = ({
44
+ className,
45
+ children,
46
+ defaultUrl = "",
47
+ onUrlChange,
48
+ ...props
49
+ }: WebPreviewProps) => {
50
+ const [url, setUrl] = useState(defaultUrl);
51
+ const [consoleOpen, setConsoleOpen] = useState(false);
52
+
53
+ const handleUrlChange = (newUrl: string) => {
54
+ setUrl(newUrl);
55
+ onUrlChange?.(newUrl);
56
+ };
57
+
58
+ const contextValue: WebPreviewContextValue = {
59
+ url,
60
+ setUrl: handleUrlChange,
61
+ consoleOpen,
62
+ setConsoleOpen,
63
+ };
64
+
65
+ return (
66
+ <WebPreviewContext.Provider value={contextValue}>
67
+ <div
68
+ className={cn(
69
+ "flex size-full flex-col rounded-lg border bg-card",
70
+ className
71
+ )}
72
+ {...props}
73
+ >
74
+ {children}
75
+ </div>
76
+ </WebPreviewContext.Provider>
77
+ );
78
+ };
79
+
80
+ export type WebPreviewNavigationProps = ComponentProps<"div">;
81
+
82
+ export const WebPreviewNavigation = ({
83
+ className,
84
+ children,
85
+ ...props
86
+ }: WebPreviewNavigationProps) => (
87
+ <div
88
+ className={cn("flex items-center gap-1 border-b p-2", className)}
89
+ {...props}
90
+ >
91
+ {children}
92
+ </div>
93
+ );
94
+
95
+ export type WebPreviewNavigationButtonProps = ComponentProps<typeof Button> & {
96
+ tooltip?: string;
97
+ };
98
+
99
+ export const WebPreviewNavigationButton = ({
100
+ onClick,
101
+ disabled,
102
+ tooltip,
103
+ children,
104
+ ...props
105
+ }: WebPreviewNavigationButtonProps) => (
106
+ <TooltipProvider>
107
+ <Tooltip>
108
+ <TooltipTrigger asChild>
109
+ <Button
110
+ className="h-8 w-8 p-0 hover:text-foreground"
111
+ disabled={disabled}
112
+ onClick={onClick}
113
+ size="sm"
114
+ variant="ghost"
115
+ {...props}
116
+ >
117
+ {children}
118
+ </Button>
119
+ </TooltipTrigger>
120
+ <TooltipContent>
121
+ <p>{tooltip}</p>
122
+ </TooltipContent>
123
+ </Tooltip>
124
+ </TooltipProvider>
125
+ );
126
+
127
+ export type WebPreviewUrlProps = ComponentProps<typeof Input>;
128
+
129
+ export const WebPreviewUrl = ({
130
+ value,
131
+ onChange,
132
+ onKeyDown,
133
+ ...props
134
+ }: WebPreviewUrlProps) => {
135
+ const { url, setUrl } = useWebPreview();
136
+ const [inputValue, setInputValue] = useState(url);
137
+
138
+ // Sync input value with context URL when it changes externally
139
+ useEffect(() => {
140
+ setInputValue(url);
141
+ }, [url]);
142
+
143
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
144
+ setInputValue(event.target.value);
145
+ onChange?.(event);
146
+ };
147
+
148
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
149
+ if (event.key === "Enter") {
150
+ const target = event.target as HTMLInputElement;
151
+ setUrl(target.value);
152
+ }
153
+ onKeyDown?.(event);
154
+ };
155
+
156
+ return (
157
+ <Input
158
+ className="h-8 flex-1 text-sm"
159
+ onChange={onChange ?? handleChange}
160
+ onKeyDown={handleKeyDown}
161
+ placeholder="Enter URL..."
162
+ value={value ?? inputValue}
163
+ {...props}
164
+ />
165
+ );
166
+ };
167
+
168
+ export type WebPreviewBodyProps = ComponentProps<"iframe"> & {
169
+ loading?: ReactNode;
170
+ };
171
+
172
+ export const WebPreviewBody = ({
173
+ className,
174
+ loading,
175
+ src,
176
+ ...props
177
+ }: WebPreviewBodyProps) => {
178
+ const { url } = useWebPreview();
179
+
180
+ return (
181
+ <div className="flex-1">
182
+ <iframe
183
+ className={cn("size-full", className)}
184
+ sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-presentation"
185
+ src={(src ?? url) || undefined}
186
+ title="Preview"
187
+ {...props}
188
+ />
189
+ {loading}
190
+ </div>
191
+ );
192
+ };
193
+
194
+ export type WebPreviewConsoleProps = ComponentProps<"div"> & {
195
+ logs?: Array<{
196
+ level: "log" | "warn" | "error";
197
+ message: string;
198
+ timestamp: Date;
199
+ }>;
200
+ };
201
+
202
+ export const WebPreviewConsole = ({
203
+ className,
204
+ logs = [],
205
+ children,
206
+ ...props
207
+ }: WebPreviewConsoleProps) => {
208
+ const { consoleOpen, setConsoleOpen } = useWebPreview();
209
+
210
+ return (
211
+ <Collapsible
212
+ className={cn("border-t bg-muted/50 font-mono text-sm", className)}
213
+ onOpenChange={setConsoleOpen}
214
+ open={consoleOpen}
215
+ {...props}
216
+ >
217
+ <CollapsibleTrigger asChild>
218
+ <Button
219
+ className="flex w-full items-center justify-between p-4 text-left font-medium hover:bg-muted/50"
220
+ variant="ghost"
221
+ >
222
+ Console
223
+ <ChevronDownIcon
224
+ className={cn(
225
+ "h-4 w-4 transition-transform duration-200",
226
+ consoleOpen && "rotate-180"
227
+ )}
228
+ />
229
+ </Button>
230
+ </CollapsibleTrigger>
231
+ <CollapsibleContent
232
+ className={cn(
233
+ "px-4 pb-4",
234
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 outline-none data-[state=closed]:animate-out data-[state=open]:animate-in"
235
+ )}
236
+ >
237
+ <div className="max-h-48 space-y-1 overflow-y-auto">
238
+ {logs.length === 0 ? (
239
+ <p className="text-muted-foreground">No console output</p>
240
+ ) : (
241
+ logs.map((log, index) => (
242
+ <div
243
+ className={cn(
244
+ "text-xs",
245
+ log.level === "error" && "text-destructive",
246
+ log.level === "warn" && "text-yellow-600",
247
+ log.level === "log" && "text-foreground"
248
+ )}
249
+ key={`${log.timestamp.getTime()}-${index}`}
250
+ >
251
+ <span className="text-muted-foreground">
252
+ {log.timestamp.toLocaleTimeString()}
253
+ </span>{" "}
254
+ {log.message}
255
+ </div>
256
+ ))
257
+ )}
258
+ {children}
259
+ </div>
260
+ </CollapsibleContent>
261
+ </Collapsible>
262
+ );
263
+ };
@@ -0,0 +1,178 @@
1
+ import { Input } from "./Input";
2
+ import { useChat } from "@cloudbase/agent-react-core";
3
+ import { ChatMessage } from "./Message";
4
+ import { v4 as uuidv4 } from "uuid";
5
+ import { LocaleContext, type LocaleCode, useLocale } from "@/locales";
6
+ import { useMemo } from "react";
7
+ import { cn } from "@/lib/utils";
8
+ import { Suggestion, Suggestions } from "@/components/ai-elements/suggestion";
9
+
10
+ export interface AgKitUIProps {
11
+ /** Optional class name for the container */
12
+ className?: string;
13
+ /** Optional extra class name for the root container */
14
+ containerClassName?: string;
15
+ /** Optional class name for the message list container */
16
+ messagesClassName?: string;
17
+ /** Optional class name for the input wrapper */
18
+ inputClassName?: string;
19
+ /** Optional class name for the empty title */
20
+ emptyTitleClassName?: string;
21
+ /** Optional placeholder for the input */
22
+ inputPlaceholder?: string;
23
+ /** Optional suggested prompts shown above input when chat is empty */
24
+ suggestions?: string[];
25
+ /** Optional callback when a suggestion is clicked */
26
+ onSuggestionClick?: (suggestion: string) => void;
27
+ /** Optional locale code for built-in UI copy */
28
+ locale?: LocaleCode;
29
+ }
30
+
31
+ export function AgKitUI({
32
+ className = "h-full min-h-0 mx-auto max-w-225 flex flex-col overflow-hidden px-4",
33
+ containerClassName,
34
+ messagesClassName,
35
+ inputClassName,
36
+ emptyTitleClassName,
37
+ inputPlaceholder,
38
+ suggestions,
39
+ onSuggestionClick,
40
+ locale = "zh-CN",
41
+ }: AgKitUIProps) {
42
+ const contextValue = useMemo(() => ({ locale }), [locale]);
43
+
44
+ return (
45
+ <LocaleContext.Provider value={contextValue}>
46
+ <AgKitUIContent
47
+ className={className}
48
+ containerClassName={containerClassName}
49
+ messagesClassName={messagesClassName}
50
+ inputClassName={inputClassName}
51
+ emptyTitleClassName={emptyTitleClassName}
52
+ inputPlaceholder={inputPlaceholder}
53
+ suggestions={suggestions}
54
+ onSuggestionClick={onSuggestionClick}
55
+ />
56
+ </LocaleContext.Provider>
57
+ );
58
+ }
59
+
60
+ interface AgKitUIContentProps {
61
+ className?: string;
62
+ containerClassName?: string;
63
+ messagesClassName?: string;
64
+ inputClassName?: string;
65
+ emptyTitleClassName?: string;
66
+ inputPlaceholder?: string;
67
+ suggestions?: string[];
68
+ onSuggestionClick?: (suggestion: string) => void;
69
+ }
70
+
71
+ function AgKitUIContent({
72
+ className,
73
+ containerClassName,
74
+ messagesClassName,
75
+ inputClassName,
76
+ emptyTitleClassName,
77
+ inputPlaceholder,
78
+ suggestions,
79
+ onSuggestionClick,
80
+ }: AgKitUIContentProps) {
81
+ const { locale } = useLocale("chat");
82
+ const { uiMessages, streaming, sendMessage, actions, abort } = useChat();
83
+
84
+ // Handle tool response
85
+ const handleRespond = (toolCallId: string, result: string) => {
86
+ sendMessage({
87
+ id: uuidv4(),
88
+ role: "tool",
89
+ content: result,
90
+ toolCallId,
91
+ });
92
+ };
93
+
94
+ const visibleMessages = uiMessages.filter(
95
+ (message) => message.role === "user" || message.role === "assistant"
96
+ );
97
+ const isEmpty = visibleMessages.length === 0;
98
+
99
+ const inputNode = (
100
+ <div className={inputClassName}>
101
+ {isEmpty && suggestions && suggestions.length > 0 && (
102
+ <Suggestions className="mb-3">
103
+ {suggestions
104
+ .map((suggestion) => suggestion.trim())
105
+ .filter(Boolean)
106
+ .map((suggestion, index) => (
107
+ <Suggestion
108
+ key={`${suggestion}-${index}`}
109
+ suggestion={suggestion}
110
+ variant="ghost"
111
+ className="max-w-full border border-border/80 text-muted-foreground"
112
+ disabled={streaming}
113
+ onClick={(value) => {
114
+ onSuggestionClick?.(value);
115
+ sendMessage(value);
116
+ }}
117
+ />
118
+ ))}
119
+ </Suggestions>
120
+ )}
121
+ <Input
122
+ placeholder={inputPlaceholder}
123
+ streaming={streaming}
124
+ onStop={() => {
125
+ if (!streaming) return;
126
+ abort();
127
+ }}
128
+ onSend={(message) => {
129
+ sendMessage(message.text);
130
+ }}
131
+ />
132
+ </div>
133
+ );
134
+
135
+ return (
136
+ <div
137
+ className={cn(
138
+ "bg-background text-foreground h-full min-h-0 w-full",
139
+ containerClassName
140
+ )}
141
+ >
142
+ <div className={cn(className)}>
143
+ {isEmpty ? (
144
+ <div className="flex min-h-0 flex-1 items-center justify-center">
145
+ <div className="w-full">
146
+ <div
147
+ className={cn(
148
+ "mb-5 text-center text-xl font-medium text-foreground",
149
+ emptyTitleClassName
150
+ )}
151
+ >
152
+ {locale.emptyTitle}
153
+ </div>
154
+ {inputNode}
155
+ </div>
156
+ </div>
157
+ ) : (
158
+ <>
159
+ <div
160
+ className={cn(
161
+ "min-h-0 flex-1 overflow-y-auto py-4",
162
+ messagesClassName
163
+ )}
164
+ >
165
+ <ChatMessage
166
+ messages={uiMessages}
167
+ isLoading={streaming}
168
+ actions={actions}
169
+ onRespond={handleRespond}
170
+ />
171
+ </div>
172
+ {inputNode}
173
+ </>
174
+ )}
175
+ </div>
176
+ </div>
177
+ );
178
+ }
@@ -0,0 +1,98 @@
1
+ import {
2
+ Attachment,
3
+ AttachmentPreview,
4
+ AttachmentRemove,
5
+ Attachments,
6
+ } from "@/components/ai-elements/attachments";
7
+ import {
8
+ PromptInput,
9
+ PromptInputBody,
10
+ PromptInputFooter,
11
+ type PromptInputMessage,
12
+ PromptInputProvider,
13
+ PromptInputSubmit,
14
+ PromptInputTextarea,
15
+ PromptInputTools,
16
+ usePromptInputAttachments,
17
+ } from "@/components/ai-elements/prompt-input";
18
+ import { useLocale } from "@/locales";
19
+
20
+ export interface InputProps {
21
+ onSend?: (message: PromptInputMessage) => void;
22
+ onStop?: () => void;
23
+ placeholder?: string;
24
+ streaming?: boolean;
25
+ }
26
+
27
+ const PromptInputAttachmentsDisplay = () => {
28
+ const attachments = usePromptInputAttachments();
29
+
30
+ if (attachments.files.length === 0) {
31
+ return null;
32
+ }
33
+
34
+ return (
35
+ <Attachments variant="inline">
36
+ {attachments.files.map((attachment) => (
37
+ <Attachment
38
+ data={attachment}
39
+ key={attachment.id}
40
+ onRemove={() => attachments.remove(attachment.id)}
41
+ >
42
+ <AttachmentPreview />
43
+ <AttachmentRemove />
44
+ </Attachment>
45
+ ))}
46
+ </Attachments>
47
+ );
48
+ };
49
+
50
+ export function Input({
51
+ onSend,
52
+ onStop,
53
+ placeholder,
54
+ streaming = false,
55
+ }: InputProps) {
56
+ const { locale } = useLocale("input");
57
+ const status: "submitted" | "streaming" | "ready" | "error" = streaming
58
+ ? "streaming"
59
+ : "ready";
60
+
61
+ const handleSubmit = (message: PromptInputMessage) => {
62
+ const text = message.text.trim();
63
+ const hasText = Boolean(text);
64
+ const hasAttachments = Boolean(message.files?.length);
65
+
66
+ if (!(hasText || hasAttachments)) {
67
+ return;
68
+ }
69
+
70
+ // Trigger onSend callback when submitting message
71
+ onSend?.({
72
+ ...message,
73
+ text,
74
+ });
75
+ };
76
+
77
+ return (
78
+ <div className="pb-4">
79
+ <PromptInputProvider>
80
+ <PromptInput globalDrop multiple onSubmit={handleSubmit}>
81
+ <PromptInputAttachmentsDisplay />
82
+ <PromptInputBody>
83
+ <PromptInputTextarea
84
+ placeholder={placeholder ?? locale.placeholder}
85
+ />
86
+ </PromptInputBody>
87
+ <PromptInputFooter>
88
+ <PromptInputTools></PromptInputTools>
89
+ <PromptInputSubmit status={status} onStop={onStop} />
90
+ </PromptInputFooter>
91
+ </PromptInput>
92
+ <div className="text-center text-xs opacity-50 mt-3">
93
+ {locale.disclaimer}
94
+ </div>
95
+ </PromptInputProvider>
96
+ </div>
97
+ );
98
+ }