@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,532 @@
1
+ "use client";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+ import {
5
+ Select,
6
+ SelectContent,
7
+ SelectItem,
8
+ SelectTrigger,
9
+ SelectValue,
10
+ } from "@/components/ui/select";
11
+ import { cn } from "@/lib/utils";
12
+ import { CheckIcon, CopyIcon } from "lucide-react";
13
+ import {
14
+ type ComponentProps,
15
+ type CSSProperties,
16
+ createContext,
17
+ type HTMLAttributes,
18
+ memo,
19
+ useContext,
20
+ useEffect,
21
+ useMemo,
22
+ useRef,
23
+ useState,
24
+ } from "react";
25
+ import {
26
+ type BundledLanguage,
27
+ type BundledTheme,
28
+ createHighlighter,
29
+ type HighlighterGeneric,
30
+ type ThemedToken,
31
+ } from "shiki";
32
+
33
+ // Shiki uses bitflags for font styles: 1=italic, 2=bold, 4=underline
34
+ // biome-ignore lint/suspicious/noBitwiseOperators: shiki bitflag check
35
+ const isItalic = (fontStyle: number | undefined) => fontStyle && fontStyle & 1;
36
+ // biome-ignore lint/suspicious/noBitwiseOperators: shiki bitflag check
37
+ const isBold = (fontStyle: number | undefined) => fontStyle && fontStyle & 2;
38
+ const isUnderline = (fontStyle: number | undefined) =>
39
+ // biome-ignore lint/suspicious/noBitwiseOperators: shiki bitflag check
40
+ fontStyle && fontStyle & 4;
41
+
42
+ // Transform tokens to include pre-computed keys to avoid noArrayIndexKey lint
43
+ interface KeyedToken {
44
+ token: ThemedToken;
45
+ key: string;
46
+ }
47
+ interface KeyedLine {
48
+ tokens: KeyedToken[];
49
+ key: string;
50
+ }
51
+
52
+ const addKeysToTokens = (lines: ThemedToken[][]): KeyedLine[] =>
53
+ lines.map((line, lineIdx) => ({
54
+ key: `line-${lineIdx}`,
55
+ tokens: line.map((token, tokenIdx) => ({
56
+ token,
57
+ key: `line-${lineIdx}-${tokenIdx}`,
58
+ })),
59
+ }));
60
+
61
+ // Token rendering component
62
+ const TokenSpan = ({ token }: { token: ThemedToken }) => (
63
+ <span
64
+ className="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]"
65
+ style={
66
+ {
67
+ color: token.color,
68
+ backgroundColor: token.bgColor,
69
+ ...token.htmlStyle,
70
+ fontStyle: isItalic(token.fontStyle) ? "italic" : undefined,
71
+ fontWeight: isBold(token.fontStyle) ? "bold" : undefined,
72
+ textDecoration: isUnderline(token.fontStyle) ? "underline" : undefined,
73
+ } as CSSProperties
74
+ }
75
+ >
76
+ {token.content}
77
+ </span>
78
+ );
79
+
80
+ // Line rendering component
81
+ const LineSpan = ({
82
+ keyedLine,
83
+ showLineNumbers,
84
+ }: {
85
+ keyedLine: KeyedLine;
86
+ showLineNumbers: boolean;
87
+ }) => (
88
+ <span className={showLineNumbers ? LINE_NUMBER_CLASSES : "block"}>
89
+ {keyedLine.tokens.length === 0
90
+ ? "\n"
91
+ : keyedLine.tokens.map(({ token, key }) => (
92
+ <TokenSpan key={key} token={token} />
93
+ ))}
94
+ </span>
95
+ );
96
+
97
+ // Types
98
+ type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {
99
+ code: string;
100
+ language: BundledLanguage;
101
+ showLineNumbers?: boolean;
102
+ };
103
+
104
+ interface TokenizedCode {
105
+ tokens: ThemedToken[][];
106
+ fg: string;
107
+ bg: string;
108
+ }
109
+
110
+ interface CodeBlockContextType {
111
+ code: string;
112
+ }
113
+
114
+ // Context
115
+ const CodeBlockContext = createContext<CodeBlockContextType>({
116
+ code: "",
117
+ });
118
+
119
+ // Highlighter cache (singleton per language)
120
+ const highlighterCache = new Map<
121
+ string,
122
+ Promise<HighlighterGeneric<BundledLanguage, BundledTheme>>
123
+ >();
124
+
125
+ // Token cache
126
+ const tokensCache = new Map<string, TokenizedCode>();
127
+
128
+ // Subscribers for async token updates
129
+ const subscribers = new Map<string, Set<(result: TokenizedCode) => void>>();
130
+
131
+ const getTokensCacheKey = (code: string, language: BundledLanguage) => {
132
+ const start = code.slice(0, 100);
133
+ const end = code.length > 100 ? code.slice(-100) : "";
134
+ return `${language}:${code.length}:${start}:${end}`;
135
+ };
136
+
137
+ const getHighlighter = (
138
+ language: BundledLanguage
139
+ ): Promise<HighlighterGeneric<BundledLanguage, BundledTheme>> => {
140
+ const cached = highlighterCache.get(language);
141
+ if (cached) {
142
+ return cached;
143
+ }
144
+
145
+ const highlighterPromise = createHighlighter({
146
+ themes: ["github-light", "github-dark"],
147
+ langs: [language],
148
+ });
149
+
150
+ highlighterCache.set(language, highlighterPromise);
151
+ return highlighterPromise;
152
+ };
153
+
154
+ // Create raw tokens for immediate display while highlighting loads
155
+ const createRawTokens = (code: string): TokenizedCode => ({
156
+ tokens: code.split("\n").map((line) =>
157
+ line === ""
158
+ ? []
159
+ : [
160
+ {
161
+ content: line,
162
+ color: "inherit",
163
+ } as ThemedToken,
164
+ ]
165
+ ),
166
+ fg: "inherit",
167
+ bg: "transparent",
168
+ });
169
+
170
+ // Synchronous highlight with callback for async results
171
+ export function highlightCode(
172
+ code: string,
173
+ language: BundledLanguage,
174
+ callback?: (result: TokenizedCode) => void
175
+ ): TokenizedCode | null {
176
+ const tokensCacheKey = getTokensCacheKey(code, language);
177
+
178
+ // Return cached result if available
179
+ const cached = tokensCache.get(tokensCacheKey);
180
+ if (cached) {
181
+ return cached;
182
+ }
183
+
184
+ // Subscribe callback if provided
185
+ if (callback) {
186
+ if (!subscribers.has(tokensCacheKey)) {
187
+ subscribers.set(tokensCacheKey, new Set());
188
+ }
189
+ subscribers.get(tokensCacheKey)?.add(callback);
190
+ }
191
+
192
+ // Start highlighting in background
193
+ getHighlighter(language)
194
+ .then((highlighter) => {
195
+ const availableLangs = highlighter.getLoadedLanguages();
196
+ const langToUse = availableLangs.includes(language) ? language : "text";
197
+
198
+ const result = highlighter.codeToTokens(code, {
199
+ lang: langToUse,
200
+ themes: {
201
+ light: "github-light",
202
+ dark: "github-dark",
203
+ },
204
+ });
205
+
206
+ const tokenized: TokenizedCode = {
207
+ tokens: result.tokens,
208
+ fg: result.fg ?? "inherit",
209
+ bg: result.bg ?? "transparent",
210
+ };
211
+
212
+ // Cache the result
213
+ tokensCache.set(tokensCacheKey, tokenized);
214
+
215
+ // Notify all subscribers
216
+ const subs = subscribers.get(tokensCacheKey);
217
+ if (subs) {
218
+ for (const sub of subs) {
219
+ sub(tokenized);
220
+ }
221
+ subscribers.delete(tokensCacheKey);
222
+ }
223
+ })
224
+ .catch((error) => {
225
+ console.error("Failed to highlight code:", error);
226
+ subscribers.delete(tokensCacheKey);
227
+ });
228
+
229
+ return null;
230
+ }
231
+
232
+ // Line number styles using CSS counters
233
+ const LINE_NUMBER_CLASSES = cn(
234
+ "block",
235
+ "before:content-[counter(line)]",
236
+ "before:inline-block",
237
+ "before:[counter-increment:line]",
238
+ "before:w-8",
239
+ "before:mr-4",
240
+ "before:text-right",
241
+ "before:text-muted-foreground/50",
242
+ "before:font-mono",
243
+ "before:select-none"
244
+ );
245
+
246
+ const CodeBlockBody = memo(
247
+ ({
248
+ tokenized,
249
+ showLineNumbers,
250
+ className,
251
+ }: {
252
+ tokenized: TokenizedCode;
253
+ showLineNumbers: boolean;
254
+ className?: string;
255
+ }) => {
256
+ const preStyle = useMemo(
257
+ () => ({
258
+ backgroundColor: tokenized.bg,
259
+ color: tokenized.fg,
260
+ }),
261
+ [tokenized.bg, tokenized.fg]
262
+ );
263
+
264
+ const keyedLines = useMemo(
265
+ () => addKeysToTokens(tokenized.tokens),
266
+ [tokenized.tokens]
267
+ );
268
+
269
+ return (
270
+ <pre
271
+ className={cn(
272
+ "dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)] m-0 p-4 text-sm",
273
+ className
274
+ )}
275
+ style={preStyle}
276
+ >
277
+ <code
278
+ className={cn(
279
+ "font-mono text-sm",
280
+ showLineNumbers && "[counter-increment:line_0] [counter-reset:line]"
281
+ )}
282
+ >
283
+ {keyedLines.map((keyedLine) => (
284
+ <LineSpan
285
+ key={keyedLine.key}
286
+ keyedLine={keyedLine}
287
+ showLineNumbers={showLineNumbers}
288
+ />
289
+ ))}
290
+ </code>
291
+ </pre>
292
+ );
293
+ },
294
+ (prevProps, nextProps) =>
295
+ prevProps.tokenized === nextProps.tokenized &&
296
+ prevProps.showLineNumbers === nextProps.showLineNumbers &&
297
+ prevProps.className === nextProps.className
298
+ );
299
+
300
+ export const CodeBlockContainer = ({
301
+ className,
302
+ language,
303
+ style,
304
+ ...props
305
+ }: HTMLAttributes<HTMLDivElement> & { language: string }) => (
306
+ <div
307
+ className={cn(
308
+ "group relative w-full overflow-hidden rounded-md border bg-background text-foreground",
309
+ className
310
+ )}
311
+ data-language={language}
312
+ style={{
313
+ contentVisibility: "auto",
314
+ containIntrinsicSize: "auto 200px",
315
+ ...style,
316
+ }}
317
+ {...props}
318
+ />
319
+ );
320
+
321
+ export const CodeBlockHeader = ({
322
+ children,
323
+ className,
324
+ ...props
325
+ }: HTMLAttributes<HTMLDivElement>) => (
326
+ <div
327
+ className={cn(
328
+ "flex items-center justify-between border-b bg-muted/80 px-3 py-2 text-muted-foreground text-xs",
329
+ className
330
+ )}
331
+ {...props}
332
+ >
333
+ {children}
334
+ </div>
335
+ );
336
+
337
+ export const CodeBlockTitle = ({
338
+ children,
339
+ className,
340
+ ...props
341
+ }: HTMLAttributes<HTMLDivElement>) => (
342
+ <div className={cn("flex items-center gap-2", className)} {...props}>
343
+ {children}
344
+ </div>
345
+ );
346
+
347
+ export const CodeBlockFilename = ({
348
+ children,
349
+ className,
350
+ ...props
351
+ }: HTMLAttributes<HTMLSpanElement>) => (
352
+ <span className={cn("font-mono", className)} {...props}>
353
+ {children}
354
+ </span>
355
+ );
356
+
357
+ export const CodeBlockActions = ({
358
+ children,
359
+ className,
360
+ ...props
361
+ }: HTMLAttributes<HTMLDivElement>) => (
362
+ <div
363
+ className={cn("-my-1 -mr-1 flex items-center gap-2", className)}
364
+ {...props}
365
+ >
366
+ {children}
367
+ </div>
368
+ );
369
+
370
+ export const CodeBlockContent = ({
371
+ code,
372
+ language,
373
+ showLineNumbers = false,
374
+ }: {
375
+ code: string;
376
+ language: BundledLanguage;
377
+ showLineNumbers?: boolean;
378
+ }) => {
379
+ // Memoized raw tokens for immediate display
380
+ const rawTokens = useMemo(() => createRawTokens(code), [code]);
381
+
382
+ // Try to get cached result synchronously, otherwise use raw tokens
383
+ const [tokenized, setTokenized] = useState<TokenizedCode>(
384
+ () => highlightCode(code, language) ?? rawTokens
385
+ );
386
+
387
+ useEffect(() => {
388
+ // Reset to raw tokens when code changes (shows current code, not stale tokens)
389
+ setTokenized(highlightCode(code, language) ?? rawTokens);
390
+
391
+ // Subscribe to async highlighting result
392
+ highlightCode(code, language, setTokenized);
393
+ }, [code, language, rawTokens]);
394
+
395
+ return (
396
+ <div className="relative overflow-auto">
397
+ <CodeBlockBody showLineNumbers={showLineNumbers} tokenized={tokenized} />
398
+ </div>
399
+ );
400
+ };
401
+
402
+ export const CodeBlock = ({
403
+ code,
404
+ language,
405
+ showLineNumbers = false,
406
+ className,
407
+ children,
408
+ ...props
409
+ }: CodeBlockProps) => (
410
+ <CodeBlockContext.Provider value={{ code }}>
411
+ <CodeBlockContainer className={className} language={language} {...props}>
412
+ {children}
413
+ <CodeBlockContent
414
+ code={code}
415
+ language={language}
416
+ showLineNumbers={showLineNumbers}
417
+ />
418
+ </CodeBlockContainer>
419
+ </CodeBlockContext.Provider>
420
+ );
421
+
422
+ export type CodeBlockCopyButtonProps = ComponentProps<typeof Button> & {
423
+ onCopy?: () => void;
424
+ onError?: (error: Error) => void;
425
+ timeout?: number;
426
+ };
427
+
428
+ export const CodeBlockCopyButton = ({
429
+ onCopy,
430
+ onError,
431
+ timeout = 2000,
432
+ children,
433
+ className,
434
+ ...props
435
+ }: CodeBlockCopyButtonProps) => {
436
+ const [isCopied, setIsCopied] = useState(false);
437
+ const timeoutRef = useRef<number>(0);
438
+ const { code } = useContext(CodeBlockContext);
439
+
440
+ const copyToClipboard = async () => {
441
+ if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
442
+ onError?.(new Error("Clipboard API not available"));
443
+ return;
444
+ }
445
+
446
+ try {
447
+ if (!isCopied) {
448
+ await navigator.clipboard.writeText(code);
449
+ setIsCopied(true);
450
+ onCopy?.();
451
+ timeoutRef.current = window.setTimeout(
452
+ () => setIsCopied(false),
453
+ timeout
454
+ );
455
+ }
456
+ } catch (error) {
457
+ onError?.(error as Error);
458
+ }
459
+ };
460
+
461
+ useEffect(
462
+ () => () => {
463
+ window.clearTimeout(timeoutRef.current);
464
+ },
465
+ []
466
+ );
467
+
468
+ const Icon = isCopied ? CheckIcon : CopyIcon;
469
+
470
+ return (
471
+ <Button
472
+ className={cn("shrink-0", className)}
473
+ onClick={copyToClipboard}
474
+ size="icon"
475
+ variant="ghost"
476
+ {...props}
477
+ >
478
+ {children ?? <Icon size={14} />}
479
+ </Button>
480
+ );
481
+ };
482
+
483
+ export type CodeBlockLanguageSelectorProps = ComponentProps<typeof Select>;
484
+
485
+ export const CodeBlockLanguageSelector = (
486
+ props: CodeBlockLanguageSelectorProps
487
+ ) => <Select {...props} />;
488
+
489
+ export type CodeBlockLanguageSelectorTriggerProps = ComponentProps<
490
+ typeof SelectTrigger
491
+ >;
492
+
493
+ export const CodeBlockLanguageSelectorTrigger = ({
494
+ className,
495
+ ...props
496
+ }: CodeBlockLanguageSelectorTriggerProps) => (
497
+ <SelectTrigger
498
+ className={cn(
499
+ "h-7 border-none bg-transparent px-2 text-xs shadow-none",
500
+ className
501
+ )}
502
+ size="sm"
503
+ {...props}
504
+ />
505
+ );
506
+
507
+ export type CodeBlockLanguageSelectorValueProps = ComponentProps<
508
+ typeof SelectValue
509
+ >;
510
+
511
+ export const CodeBlockLanguageSelectorValue = (
512
+ props: CodeBlockLanguageSelectorValueProps
513
+ ) => <SelectValue {...props} />;
514
+
515
+ export type CodeBlockLanguageSelectorContentProps = ComponentProps<
516
+ typeof SelectContent
517
+ >;
518
+
519
+ export const CodeBlockLanguageSelectorContent = ({
520
+ align = "end",
521
+ ...props
522
+ }: CodeBlockLanguageSelectorContentProps) => (
523
+ <SelectContent align={align} {...props} />
524
+ );
525
+
526
+ export type CodeBlockLanguageSelectorItemProps = ComponentProps<
527
+ typeof SelectItem
528
+ >;
529
+
530
+ export const CodeBlockLanguageSelectorItem = (
531
+ props: CodeBlockLanguageSelectorItemProps
532
+ ) => <SelectItem {...props} />;