@sero-ai/ui 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/.turbo/turbo-typecheck.log +1 -1
  2. package/LICENSE +201 -0
  3. package/package.json +22 -18
  4. package/src/components/ai-elements/attachments.tsx +4 -4
  5. package/src/components/ai-elements/audio-player.tsx +1 -1
  6. package/src/components/ai-elements/chain-of-thought.tsx +2 -2
  7. package/src/components/ai-elements/confirmation.tsx +2 -2
  8. package/src/components/ai-elements/context.tsx +2 -2
  9. package/src/components/ai-elements/environment-variables.tsx +6 -6
  10. package/src/components/ai-elements/file-tree.tsx +3 -3
  11. package/src/components/ai-elements/inline-citation.tsx +2 -2
  12. package/src/components/ai-elements/jsx-preview.tsx +2 -2
  13. package/src/components/ai-elements/message.tsx +2 -2
  14. package/src/components/ai-elements/mic-selector.tsx +8 -8
  15. package/src/components/ai-elements/open-in-chat.tsx +2 -2
  16. package/src/components/ai-elements/package-info.tsx +4 -4
  17. package/src/components/ai-elements/plan.tsx +2 -2
  18. package/src/components/ai-elements/prompt-input-context.tsx +252 -0
  19. package/src/components/ai-elements/prompt-input-elements.tsx +371 -0
  20. package/src/components/ai-elements/prompt-input-textarea.tsx +117 -0
  21. package/src/components/ai-elements/prompt-input.tsx +59 -990
  22. package/src/components/ai-elements/queue.tsx +1 -1
  23. package/src/components/ai-elements/reasoning.tsx +3 -3
  24. package/src/components/ai-elements/schema-display.tsx +7 -7
  25. package/src/components/ai-elements/snippet.tsx +3 -3
  26. package/src/components/ai-elements/sources.tsx +2 -2
  27. package/src/components/ai-elements/terminal.tsx +5 -5
  28. package/src/components/ai-elements/test-results.tsx +8 -8
  29. package/src/components/ai-elements/transcription.tsx +2 -2
  30. package/src/components/ai-elements/voice-selector-accent.tsx +50 -0
  31. package/src/components/ai-elements/voice-selector.tsx +8 -177
  32. package/src/components/ai-elements/web-preview.tsx +5 -6
  33. package/src/components/model-selection/available-model-picker.tsx +231 -0
  34. package/src/components/model-selection/model-warning-list.tsx +33 -0
  35. package/src/components/model-selection/thinking-level-picker.tsx +55 -0
  36. package/src/components/ui/alert-dialog.tsx +1 -1
  37. package/src/components/ui/calendar.tsx +1 -1
  38. package/src/components/ui/carousel.tsx +1 -1
  39. package/src/components/ui/chart.tsx +3 -3
  40. package/src/components/ui/command.tsx +2 -2
  41. package/src/components/ui/dialog.tsx +1 -1
  42. package/src/components/ui/field.tsx +2 -2
  43. package/src/components/ui/form.tsx +2 -2
  44. package/src/components/ui/input-otp.tsx +2 -2
  45. package/src/components/ui/input.tsx +3 -1
  46. package/src/components/ui/menubar.tsx +1 -1
  47. package/src/components/ui/native-select.tsx +2 -0
  48. package/src/components/ui/navigation-menu.tsx +1 -1
  49. package/src/components/ui/plugin-safety-disclaimer.tsx +25 -0
  50. package/src/components/ui/progress.tsx +1 -1
  51. package/src/components/ui/resizable.tsx +1 -1
  52. package/src/components/ui/search-input.tsx +39 -0
  53. package/src/components/ui/slider.tsx +7 -2
  54. package/src/components/ui/textarea.tsx +3 -1
  55. package/src/components/ui/toggle-group.tsx +1 -1
  56. package/src/components/ui/tree.tsx +3 -3
  57. package/src/index.ts +128 -7
  58. package/src/styles/globals.css +91 -26
  59. package/src/styles/plugin.css +4 -0
  60. package/src/theme/apply-theme.ts +250 -0
  61. package/src/theme/index.ts +2 -0
  62. package/src/theme/types.ts +253 -0
@@ -154,7 +154,7 @@ export const QueueItemImage = ({
154
154
  }: QueueItemImageProps) => (
155
155
  <img
156
156
  alt=""
157
- className={cn("h-8 w-8 rounded border object-cover", className)}
157
+ className={cn("size-8 rounded border object-cover", className)}
158
158
  height={32}
159
159
  width={32}
160
160
  {...props}
@@ -18,7 +18,7 @@ import {
18
18
  createContext,
19
19
  memo,
20
20
  useCallback,
21
- useContext,
21
+ use,
22
22
  useEffect,
23
23
  useMemo,
24
24
  useRef,
@@ -38,7 +38,7 @@ interface ReasoningContextValue {
38
38
  const ReasoningContext = createContext<ReasoningContextValue | null>(null);
39
39
 
40
40
  export const useReasoning = () => {
41
- const context = useContext(ReasoningContext);
41
+ const context = use(ReasoningContext);
42
42
  if (!context) {
43
43
  throw new Error("Reasoning components must be used within Reasoning");
44
44
  }
@@ -157,7 +157,7 @@ export type ReasoningTriggerProps = ComponentProps<
157
157
 
158
158
  const defaultGetThinkingMessage = (isStreaming: boolean, duration?: number) => {
159
159
  if (isStreaming || duration === 0) {
160
- return <Shimmer duration={1}>Thinking...</Shimmer>;
160
+ return <Shimmer duration={1}>Thinking…</Shimmer>;
161
161
  }
162
162
  if (duration === undefined) {
163
163
  return <p>Thought for a few seconds</p>;
@@ -10,7 +10,7 @@ import {
10
10
  } from "../ui/collapsible";
11
11
  import { cn } from "../../lib/utils";
12
12
  import { ChevronRightIcon } from "lucide-react";
13
- import { createContext, useContext, useMemo } from "react";
13
+ import { createContext, use, useMemo } from "react";
14
14
 
15
15
  type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
16
16
 
@@ -144,7 +144,7 @@ export const SchemaDisplayMethod = ({
144
144
  children,
145
145
  ...props
146
146
  }: SchemaDisplayMethodProps) => {
147
- const { method } = useContext(SchemaDisplayContext);
147
+ const { method } = use(SchemaDisplayContext);
148
148
 
149
149
  return (
150
150
  <Badge
@@ -164,7 +164,7 @@ export const SchemaDisplayPath = ({
164
164
  children,
165
165
  ...props
166
166
  }: SchemaDisplayPathProps) => {
167
- const { path } = useContext(SchemaDisplayContext);
167
+ const { path } = use(SchemaDisplayContext);
168
168
 
169
169
  // Highlight path parameters
170
170
  const highlightedPath = path.replaceAll(
@@ -191,7 +191,7 @@ export const SchemaDisplayDescription = ({
191
191
  children,
192
192
  ...props
193
193
  }: SchemaDisplayDescriptionProps) => {
194
- const { description } = useContext(SchemaDisplayContext);
194
+ const { description } = use(SchemaDisplayContext);
195
195
 
196
196
  return (
197
197
  <p
@@ -225,7 +225,7 @@ export const SchemaDisplayParameters = ({
225
225
  children,
226
226
  ...props
227
227
  }: SchemaDisplayParametersProps) => {
228
- const { parameters } = useContext(SchemaDisplayContext);
228
+ const { parameters } = use(SchemaDisplayContext);
229
229
 
230
230
  return (
231
231
  <Collapsible className={cn(className)} defaultOpen {...props}>
@@ -293,7 +293,7 @@ export const SchemaDisplayRequest = ({
293
293
  children,
294
294
  ...props
295
295
  }: SchemaDisplayRequestProps) => {
296
- const { requestBody } = useContext(SchemaDisplayContext);
296
+ const { requestBody } = use(SchemaDisplayContext);
297
297
 
298
298
  return (
299
299
  <Collapsible className={cn(className)} defaultOpen {...props}>
@@ -320,7 +320,7 @@ export const SchemaDisplayResponse = ({
320
320
  children,
321
321
  ...props
322
322
  }: SchemaDisplayResponseProps) => {
323
- const { responseBody } = useContext(SchemaDisplayContext);
323
+ const { responseBody } = use(SchemaDisplayContext);
324
324
 
325
325
  return (
326
326
  <Collapsible className={cn(className)} defaultOpen {...props}>
@@ -14,7 +14,7 @@ import { CheckIcon, CopyIcon } from "lucide-react";
14
14
  import {
15
15
  createContext,
16
16
  useCallback,
17
- useContext,
17
+ use,
18
18
  useEffect,
19
19
  useRef,
20
20
  useState,
@@ -66,7 +66,7 @@ export type SnippetInputProps = Omit<
66
66
  >;
67
67
 
68
68
  export const SnippetInput = ({ className, ...props }: SnippetInputProps) => {
69
- const { code } = useContext(SnippetContext);
69
+ const { code } = use(SnippetContext);
70
70
 
71
71
  return (
72
72
  <InputGroupInput
@@ -94,7 +94,7 @@ export const SnippetCopyButton = ({
94
94
  }: SnippetCopyButtonProps) => {
95
95
  const [isCopied, setIsCopied] = useState(false);
96
96
  const timeoutRef = useRef<number>(0);
97
- const { code } = useContext(SnippetContext);
97
+ const { code } = use(SnippetContext);
98
98
 
99
99
  const copyToClipboard = useCallback(async () => {
100
100
  if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
@@ -36,7 +36,7 @@ export const SourcesTrigger = ({
36
36
  {children ?? (
37
37
  <>
38
38
  <p className="font-medium">Used {count} sources</p>
39
- <ChevronDownIcon className="h-4 w-4" />
39
+ <ChevronDownIcon className="size-4" />
40
40
  </>
41
41
  )}
42
42
  </CollapsibleTrigger>
@@ -70,7 +70,7 @@ export const Source = ({ href, title, children, ...props }: SourceProps) => (
70
70
  >
71
71
  {children ?? (
72
72
  <>
73
- <BookIcon className="h-4 w-4" />
73
+ <BookIcon className="size-4" />
74
74
  <span className="block font-medium">{title}</span>
75
75
  </>
76
76
  )}
@@ -9,7 +9,7 @@ import { CheckIcon, CopyIcon, TerminalIcon, Trash2Icon } from "lucide-react";
9
9
  import {
10
10
  createContext,
11
11
  useCallback,
12
- useContext,
12
+ use,
13
13
  useEffect,
14
14
  useMemo,
15
15
  useRef,
@@ -122,7 +122,7 @@ export const TerminalStatus = ({
122
122
  children,
123
123
  ...props
124
124
  }: TerminalStatusProps) => {
125
- const { isStreaming } = useContext(TerminalContext);
125
+ const { isStreaming } = use(TerminalContext);
126
126
 
127
127
  if (!isStreaming) {
128
128
  return null;
@@ -166,7 +166,7 @@ export const TerminalCopyButton = ({
166
166
  }: TerminalCopyButtonProps) => {
167
167
  const [isCopied, setIsCopied] = useState(false);
168
168
  const timeoutRef = useRef<number>(0);
169
- const { output } = useContext(TerminalContext);
169
+ const { output } = use(TerminalContext);
170
170
 
171
171
  const copyToClipboard = useCallback(async () => {
172
172
  if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
@@ -216,7 +216,7 @@ export const TerminalClearButton = ({
216
216
  className,
217
217
  ...props
218
218
  }: TerminalClearButtonProps) => {
219
- const { onClear } = useContext(TerminalContext);
219
+ const { onClear } = use(TerminalContext);
220
220
 
221
221
  if (!onClear) {
222
222
  return null;
@@ -245,7 +245,7 @@ export const TerminalContent = ({
245
245
  children,
246
246
  ...props
247
247
  }: TerminalContentProps) => {
248
- const { output, isStreaming, autoScroll } = useContext(TerminalContext);
248
+ const { output, isStreaming, autoScroll } = use(TerminalContext);
249
249
  const containerRef = useRef<HTMLDivElement>(null);
250
250
 
251
251
  // biome-ignore lint/correctness/useExhaustiveDependencies: output triggers auto-scroll when new content arrives
@@ -16,7 +16,7 @@ import {
16
16
  CircleIcon,
17
17
  XCircleIcon,
18
18
  } from "lucide-react";
19
- import { createContext, useContext, useMemo } from "react";
19
+ import { createContext, use, useMemo } from "react";
20
20
 
21
21
  type TestStatus = "passed" | "failed" | "skipped" | "running";
22
22
 
@@ -96,7 +96,7 @@ export const TestResultsSummary = ({
96
96
  children,
97
97
  ...props
98
98
  }: TestResultsSummaryProps) => {
99
- const { summary } = useContext(TestResultsContext);
99
+ const { summary } = use(TestResultsContext);
100
100
 
101
101
  if (!summary) {
102
102
  return null;
@@ -144,7 +144,7 @@ export const TestResultsDuration = ({
144
144
  children,
145
145
  ...props
146
146
  }: TestResultsDurationProps) => {
147
- const { summary } = useContext(TestResultsContext);
147
+ const { summary } = use(TestResultsContext);
148
148
 
149
149
  if (!summary?.duration) {
150
150
  return null;
@@ -164,7 +164,7 @@ export const TestResultsProgress = ({
164
164
  children,
165
165
  ...props
166
166
  }: TestResultsProgressProps) => {
167
- const { summary } = useContext(TestResultsContext);
167
+ const { summary } = use(TestResultsContext);
168
168
 
169
169
  if (!summary) {
170
170
  return null;
@@ -251,7 +251,7 @@ export const TestSuiteName = ({
251
251
  children,
252
252
  ...props
253
253
  }: TestSuiteNameProps) => {
254
- const { name, status } = useContext(TestSuiteContext);
254
+ const { name, status } = use(TestSuiteContext);
255
255
 
256
256
  return (
257
257
  <CollapsibleTrigger
@@ -395,7 +395,7 @@ export const TestStatus = ({
395
395
  children,
396
396
  ...props
397
397
  }: TestStatusProps) => {
398
- const { status } = useContext(TestContext);
398
+ const { status } = use(TestContext);
399
399
 
400
400
  return (
401
401
  <span
@@ -410,7 +410,7 @@ export const TestStatus = ({
410
410
  export type TestNameProps = HTMLAttributes<HTMLSpanElement>;
411
411
 
412
412
  export const TestName = ({ className, children, ...props }: TestNameProps) => {
413
- const { name } = useContext(TestContext);
413
+ const { name } = use(TestContext);
414
414
 
415
415
  return (
416
416
  <span className={cn("flex-1", className)} {...props}>
@@ -426,7 +426,7 @@ export const TestDuration = ({
426
426
  children,
427
427
  ...props
428
428
  }: TestDurationProps) => {
429
- const { duration } = useContext(TestContext);
429
+ const { duration } = use(TestContext);
430
430
 
431
431
  if (duration === undefined) {
432
432
  return null;
@@ -5,7 +5,7 @@ import type { ComponentProps, ReactNode } from "react";
5
5
 
6
6
  import { useControllableState } from "@radix-ui/react-use-controllable-state";
7
7
  import { cn } from "../../lib/utils";
8
- import { createContext, useCallback, useContext, useMemo } from "react";
8
+ import { createContext, useCallback, use, useMemo } from "react";
9
9
 
10
10
  type TranscriptionSegment = TranscriptionResult["segments"][number];
11
11
 
@@ -21,7 +21,7 @@ const TranscriptionContext = createContext<TranscriptionContextValue | null>(
21
21
  );
22
22
 
23
23
  const useTranscription = () => {
24
- const context = useContext(TranscriptionContext);
24
+ const context = use(TranscriptionContext);
25
25
  if (!context) {
26
26
  throw new Error(
27
27
  "Transcription components must be used within Transcription"
@@ -0,0 +1,50 @@
1
+ import type { ComponentProps } from 'react';
2
+ import { Languages } from 'lucide-react';
3
+ import { cn } from '../../lib/utils';
4
+
5
+ export type VoiceSelectorAccentValue =
6
+ | 'american'
7
+ | 'british'
8
+ | 'australian'
9
+ | 'canadian'
10
+ | 'irish'
11
+ | 'scottish'
12
+ | 'indian'
13
+ | 'south-african'
14
+ | 'new-zealand'
15
+ | 'spanish'
16
+ | 'french'
17
+ | 'german'
18
+ | 'italian'
19
+ | 'portuguese'
20
+ | 'brazilian'
21
+ | 'mexican'
22
+ | 'argentinian'
23
+ | 'japanese'
24
+ | 'chinese'
25
+ | 'korean'
26
+ | 'russian'
27
+ | 'arabic'
28
+ | 'dutch'
29
+ | 'swedish'
30
+ | 'norwegian'
31
+ | 'danish'
32
+ | 'finnish'
33
+ | 'polish'
34
+ | 'turkish'
35
+ | 'greek'
36
+ | string;
37
+
38
+ export type VoiceSelectorAccentProps = ComponentProps<'span'> & {
39
+ value?: VoiceSelectorAccentValue;
40
+ };
41
+
42
+ export const VoiceSelectorAccent = ({
43
+ className,
44
+ children,
45
+ ...props
46
+ }: VoiceSelectorAccentProps) => (
47
+ <span className={cn('text-muted-foreground text-xs', className)} {...props}>
48
+ {children ?? <Languages className="size-4" />}
49
+ </span>
50
+ );
@@ -3,6 +3,10 @@
3
3
  import type { ComponentProps, ReactNode } from "react";
4
4
 
5
5
  import { useControllableState } from "@radix-ui/react-use-controllable-state";
6
+ import {
7
+ VoiceSelectorAccent,
8
+ type VoiceSelectorAccentProps,
9
+ } from "./voice-selector-accent";
6
10
  import { Button } from "../ui/button";
7
11
  import {
8
12
  Command,
@@ -34,7 +38,7 @@ import {
34
38
  VenusAndMarsIcon,
35
39
  VenusIcon,
36
40
  } from "lucide-react";
37
- import { createContext, useCallback, useContext, useMemo } from "react";
41
+ import { createContext, useCallback, use, useMemo } from "react";
38
42
 
39
43
  interface VoiceSelectorContextValue {
40
44
  value: string | undefined;
@@ -48,7 +52,7 @@ const VoiceSelectorContext = createContext<VoiceSelectorContextValue | null>(
48
52
  );
49
53
 
50
54
  export const useVoiceSelector = () => {
51
- const context = useContext(VoiceSelectorContext);
55
+ const context = use(VoiceSelectorContext);
52
56
  if (!context) {
53
57
  throw new Error(
54
58
  "VoiceSelector components must be used within VoiceSelector"
@@ -238,181 +242,8 @@ export const VoiceSelectorGender = ({
238
242
  );
239
243
  };
240
244
 
241
- export type VoiceSelectorAccentProps = ComponentProps<"span"> & {
242
- value?:
243
- | "american"
244
- | "british"
245
- | "australian"
246
- | "canadian"
247
- | "irish"
248
- | "scottish"
249
- | "indian"
250
- | "south-african"
251
- | "new-zealand"
252
- | "spanish"
253
- | "french"
254
- | "german"
255
- | "italian"
256
- | "portuguese"
257
- | "brazilian"
258
- | "mexican"
259
- | "argentinian"
260
- | "japanese"
261
- | "chinese"
262
- | "korean"
263
- | "russian"
264
- | "arabic"
265
- | "dutch"
266
- | "swedish"
267
- | "norwegian"
268
- | "danish"
269
- | "finnish"
270
- | "polish"
271
- | "turkish"
272
- | "greek"
273
- | string;
274
- };
275
-
276
- export const VoiceSelectorAccent = ({
277
- className,
278
- value,
279
- children,
280
- ...props
281
- }: VoiceSelectorAccentProps) => {
282
- let emoji: string | null = null;
283
-
284
- switch (value) {
285
- case "american": {
286
- emoji = "🇺🇸";
287
- break;
288
- }
289
- case "british": {
290
- emoji = "🇬🇧";
291
- break;
292
- }
293
- case "australian": {
294
- emoji = "🇦🇺";
295
- break;
296
- }
297
- case "canadian": {
298
- emoji = "🇨🇦";
299
- break;
300
- }
301
- case "irish": {
302
- emoji = "🇮🇪";
303
- break;
304
- }
305
- case "scottish": {
306
- emoji = "🏴󠁧󠁢󠁳󠁣󠁴󠁿";
307
- break;
308
- }
309
- case "indian": {
310
- emoji = "🇮🇳";
311
- break;
312
- }
313
- case "south-african": {
314
- emoji = "🇿🇦";
315
- break;
316
- }
317
- case "new-zealand": {
318
- emoji = "🇳🇿";
319
- break;
320
- }
321
- case "spanish": {
322
- emoji = "🇪🇸";
323
- break;
324
- }
325
- case "french": {
326
- emoji = "🇫🇷";
327
- break;
328
- }
329
- case "german": {
330
- emoji = "🇩🇪";
331
- break;
332
- }
333
- case "italian": {
334
- emoji = "🇮🇹";
335
- break;
336
- }
337
- case "portuguese": {
338
- emoji = "🇵🇹";
339
- break;
340
- }
341
- case "brazilian": {
342
- emoji = "🇧🇷";
343
- break;
344
- }
345
- case "mexican": {
346
- emoji = "🇲🇽";
347
- break;
348
- }
349
- case "argentinian": {
350
- emoji = "🇦🇷";
351
- break;
352
- }
353
- case "japanese": {
354
- emoji = "🇯🇵";
355
- break;
356
- }
357
- case "chinese": {
358
- emoji = "🇨🇳";
359
- break;
360
- }
361
- case "korean": {
362
- emoji = "🇰🇷";
363
- break;
364
- }
365
- case "russian": {
366
- emoji = "🇷🇺";
367
- break;
368
- }
369
- case "arabic": {
370
- emoji = "🇸🇦";
371
- break;
372
- }
373
- case "dutch": {
374
- emoji = "🇳🇱";
375
- break;
376
- }
377
- case "swedish": {
378
- emoji = "🇸🇪";
379
- break;
380
- }
381
- case "norwegian": {
382
- emoji = "🇳🇴";
383
- break;
384
- }
385
- case "danish": {
386
- emoji = "🇩🇰";
387
- break;
388
- }
389
- case "finnish": {
390
- emoji = "🇫🇮";
391
- break;
392
- }
393
- case "polish": {
394
- emoji = "🇵🇱";
395
- break;
396
- }
397
- case "turkish": {
398
- emoji = "🇹🇷";
399
- break;
400
- }
401
- case "greek": {
402
- emoji = "🇬🇷";
403
- break;
404
- }
405
- default: {
406
- emoji = null;
407
- }
408
- }
409
-
410
- return (
411
- <span className={cn("text-muted-foreground text-xs", className)} {...props}>
412
- {children ?? emoji}
413
- </span>
414
- );
415
- };
245
+ export { VoiceSelectorAccent };
246
+ export type { VoiceSelectorAccentProps };
416
247
 
417
248
  export type VoiceSelectorAgeProps = ComponentProps<"span">;
418
249
 
@@ -20,7 +20,7 @@ import { ChevronDownIcon } from "lucide-react";
20
20
  import {
21
21
  createContext,
22
22
  useCallback,
23
- useContext,
23
+ use,
24
24
  useMemo,
25
25
  useState,
26
26
  } from "react";
@@ -35,7 +35,7 @@ export interface WebPreviewContextValue {
35
35
  const WebPreviewContext = createContext<WebPreviewContextValue | null>(null);
36
36
 
37
37
  const useWebPreview = () => {
38
- const context = useContext(WebPreviewContext);
38
+ const context = use(WebPreviewContext);
39
39
  if (!context) {
40
40
  throw new Error("WebPreview components must be used within a WebPreview");
41
41
  }
@@ -120,7 +120,7 @@ export const WebPreviewNavigationButton = ({
120
120
  <Tooltip>
121
121
  <TooltipTrigger asChild>
122
122
  <Button
123
- className="h-8 w-8 p-0 hover:text-foreground"
123
+ className="size-8 p-0 hover:text-foreground"
124
124
  disabled={disabled}
125
125
  onClick={onClick}
126
126
  size="sm"
@@ -199,7 +199,6 @@ export const WebPreviewBody = ({
199
199
  <div className="flex-1">
200
200
  <iframe
201
201
  className={cn("size-full", className)}
202
- // oxlint-disable-next-line eslint-plugin-react(iframe-missing-sandbox)
203
202
  sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-presentation"
204
203
  src={(src ?? url) || undefined}
205
204
  title="Preview"
@@ -241,7 +240,7 @@ export const WebPreviewConsole = ({
241
240
  Console
242
241
  <ChevronDownIcon
243
242
  className={cn(
244
- "h-4 w-4 transition-transform duration-200",
243
+ "size-4 transition-transform duration-200",
245
244
  consoleOpen && "rotate-180"
246
245
  )}
247
246
  />
@@ -265,7 +264,7 @@ export const WebPreviewConsole = ({
265
264
  log.level === "warn" && "text-yellow-600",
266
265
  log.level === "log" && "text-foreground"
267
266
  )}
268
- key={`${log.timestamp.getTime()}-${index}`}
267
+ key={`${log.timestamp.getTime()}-${log.level}-${log.message}`}
269
268
  >
270
269
  <span className="text-muted-foreground">
271
270
  {log.timestamp.toLocaleTimeString()}