@copilotkitnext/react 0.0.3 → 0.0.4

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 (48) hide show
  1. package/package.json +3 -3
  2. package/.turbo/turbo-build$colon$css.log +0 -9
  3. package/.turbo/turbo-build.log +0 -30
  4. package/.turbo/turbo-check-types.log +0 -7
  5. package/.turbo/turbo-lint.log +0 -78
  6. package/.turbo/turbo-test.log +0 -79
  7. package/postcss.config.js +0 -7
  8. package/src/__tests__/setup.ts +0 -2
  9. package/src/components/chat/CopilotChat.tsx +0 -90
  10. package/src/components/chat/CopilotChatAssistantMessage.tsx +0 -478
  11. package/src/components/chat/CopilotChatAudioRecorder.tsx +0 -157
  12. package/src/components/chat/CopilotChatInput.tsx +0 -596
  13. package/src/components/chat/CopilotChatMessageView.tsx +0 -85
  14. package/src/components/chat/CopilotChatToolCallsView.tsx +0 -43
  15. package/src/components/chat/CopilotChatUserMessage.tsx +0 -337
  16. package/src/components/chat/CopilotChatView.tsx +0 -385
  17. package/src/components/chat/__tests__/CopilotChatAssistantMessage.test.tsx +0 -684
  18. package/src/components/chat/__tests__/CopilotChatInput.test.tsx +0 -531
  19. package/src/components/chat/__tests__/setup.ts +0 -1
  20. package/src/components/chat/index.ts +0 -35
  21. package/src/components/index.ts +0 -4
  22. package/src/components/ui/button.tsx +0 -123
  23. package/src/components/ui/dropdown-menu.tsx +0 -257
  24. package/src/components/ui/tooltip.tsx +0 -59
  25. package/src/hooks/index.ts +0 -6
  26. package/src/hooks/use-agent-context.tsx +0 -17
  27. package/src/hooks/use-agent.tsx +0 -48
  28. package/src/hooks/use-frontend-tool.tsx +0 -46
  29. package/src/hooks/use-human-in-the-loop.tsx +0 -76
  30. package/src/hooks/use-render-tool-call.tsx +0 -81
  31. package/src/index.ts +0 -4
  32. package/src/lib/__tests__/completePartialMarkdown.test.ts +0 -495
  33. package/src/lib/__tests__/renderSlot.test.tsx +0 -610
  34. package/src/lib/slots.tsx +0 -55
  35. package/src/lib/utils.ts +0 -6
  36. package/src/providers/CopilotChatConfigurationProvider.tsx +0 -81
  37. package/src/providers/CopilotKitProvider.tsx +0 -269
  38. package/src/providers/__tests__/CopilotKitProvider.test.tsx +0 -487
  39. package/src/providers/__tests__/CopilotKitProvider.wildcard.test.tsx +0 -261
  40. package/src/providers/index.ts +0 -14
  41. package/src/styles/globals.css +0 -302
  42. package/src/types/frontend-tool.ts +0 -8
  43. package/src/types/human-in-the-loop.ts +0 -33
  44. package/src/types/index.ts +0 -3
  45. package/src/types/react-tool-call-render.ts +0 -29
  46. package/tailwind.config.js +0 -9
  47. package/tsconfig.json +0 -23
  48. package/tsup.config.ts +0 -19
@@ -1,596 +0,0 @@
1
- import React, {
2
- useState,
3
- useRef,
4
- KeyboardEvent,
5
- ChangeEvent,
6
- useEffect,
7
- forwardRef,
8
- useImperativeHandle,
9
- } from "react";
10
- import { twMerge } from "tailwind-merge";
11
- import { Plus, Settings2, Mic, ArrowUp, X, Check } from "lucide-react";
12
-
13
- import {
14
- CopilotChatLabels,
15
- useCopilotChatConfiguration,
16
- } from "@/providers/CopilotChatConfigurationProvider";
17
- import { Button } from "@/components/ui/button";
18
- import {
19
- Tooltip,
20
- TooltipContent,
21
- TooltipTrigger,
22
- } from "@/components/ui/tooltip";
23
- import {
24
- DropdownMenu,
25
- DropdownMenuTrigger,
26
- DropdownMenuContent,
27
- DropdownMenuItem,
28
- DropdownMenuSub,
29
- DropdownMenuSubTrigger,
30
- DropdownMenuSubContent,
31
- DropdownMenuSeparator,
32
- } from "@/components/ui/dropdown-menu";
33
-
34
- import { CopilotChatAudioRecorder } from "./CopilotChatAudioRecorder";
35
- import { renderSlot, WithSlots } from "@/lib/slots";
36
-
37
- export type CopilotChatInputMode = "input" | "transcribe" | "processing";
38
-
39
- export type ToolsMenuItem = {
40
- label: string;
41
- } & (
42
- | {
43
- action: () => void;
44
- items?: never;
45
- }
46
- | {
47
- action?: never;
48
- items: (ToolsMenuItem | "-")[];
49
- }
50
- );
51
-
52
- export type CopilotChatInputProps = WithSlots<
53
- {
54
- textArea: typeof CopilotChatInput.TextArea;
55
- sendButton: typeof CopilotChatInput.SendButton;
56
- startTranscribeButton: typeof CopilotChatInput.StartTranscribeButton;
57
- cancelTranscribeButton: typeof CopilotChatInput.CancelTranscribeButton;
58
- finishTranscribeButton: typeof CopilotChatInput.FinishTranscribeButton;
59
- addFileButton: typeof CopilotChatInput.AddFileButton;
60
- toolsButton: typeof CopilotChatInput.ToolsButton;
61
- toolbar: typeof CopilotChatInput.Toolbar;
62
- audioRecorder: typeof CopilotChatAudioRecorder;
63
- },
64
- {
65
- mode?: CopilotChatInputMode;
66
- toolsMenu?: (ToolsMenuItem | "-")[];
67
- autoFocus?: boolean;
68
- additionalToolbarItems?: React.ReactNode;
69
- onSubmitMessage?: (value: string) => void;
70
- onStartTranscribe?: () => void;
71
- onCancelTranscribe?: () => void;
72
- onFinishTranscribe?: () => void;
73
- onAddFile?: () => void;
74
- value?: string;
75
- onChange?: (value: string) => void;
76
- } & Omit<React.HTMLAttributes<HTMLDivElement>, "onChange">
77
- >;
78
-
79
- export function CopilotChatInput({
80
- mode = "input",
81
- onSubmitMessage,
82
- onStartTranscribe,
83
- onCancelTranscribe,
84
- onFinishTranscribe,
85
- onAddFile,
86
- onChange,
87
- value,
88
- toolsMenu,
89
- autoFocus = true,
90
- additionalToolbarItems,
91
- textArea,
92
- sendButton,
93
- startTranscribeButton,
94
- cancelTranscribeButton,
95
- finishTranscribeButton,
96
- addFileButton,
97
- toolsButton,
98
- toolbar,
99
- audioRecorder,
100
- children,
101
- className,
102
- ...props
103
- }: CopilotChatInputProps) {
104
- const { inputValue, onSubmitInput, onChangeInput } =
105
- useCopilotChatConfiguration();
106
-
107
- value ??= inputValue;
108
- onSubmitMessage ??= onSubmitInput;
109
- onChange ??= onChangeInput;
110
-
111
- const inputRef = useRef<HTMLTextAreaElement>(null);
112
- const audioRecorderRef =
113
- useRef<React.ElementRef<typeof CopilotChatAudioRecorder>>(null);
114
-
115
- // Handle recording based on mode changes
116
- useEffect(() => {
117
- const recorder = audioRecorderRef.current;
118
- if (!recorder) {
119
- return;
120
- }
121
-
122
- if (mode === "transcribe") {
123
- // Start recording when entering transcribe mode
124
- recorder.start().catch(console.error);
125
- } else {
126
- // Stop recording when leaving transcribe mode
127
- if (recorder.state === "recording") {
128
- recorder.stop().catch(console.error);
129
- }
130
- }
131
- }, [mode]);
132
-
133
- // Handlers
134
- const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
135
- onChange?.(e.target.value);
136
- };
137
-
138
- const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
139
- if (e.key === "Enter" && !e.shiftKey) {
140
- e.preventDefault();
141
- send();
142
- }
143
- };
144
-
145
- const send = () => {
146
- const trimmed = value?.trim();
147
- if (trimmed) {
148
- onSubmitMessage?.(trimmed);
149
- // Refocus input after sending
150
- if (inputRef.current) {
151
- inputRef.current.focus();
152
- }
153
- }
154
- };
155
-
156
- const BoundTextArea = renderSlot(textArea, CopilotChatInput.TextArea, {
157
- ref: inputRef,
158
- value,
159
- onChange: handleChange,
160
- onKeyDown: handleKeyDown,
161
- autoFocus: autoFocus,
162
- });
163
-
164
- const BoundAudioRecorder = renderSlot(
165
- audioRecorder,
166
- CopilotChatAudioRecorder,
167
- {
168
- ref: audioRecorderRef,
169
- }
170
- );
171
-
172
- const BoundSendButton = renderSlot(sendButton, CopilotChatInput.SendButton, {
173
- onClick: send,
174
- disabled: !value?.trim() || !onSubmitMessage,
175
- });
176
-
177
- const BoundStartTranscribeButton = renderSlot(
178
- startTranscribeButton,
179
- CopilotChatInput.StartTranscribeButton,
180
- {
181
- onClick: onStartTranscribe,
182
- }
183
- );
184
-
185
- const BoundCancelTranscribeButton = renderSlot(
186
- cancelTranscribeButton,
187
- CopilotChatInput.CancelTranscribeButton,
188
- {
189
- onClick: onCancelTranscribe,
190
- }
191
- );
192
-
193
- const BoundFinishTranscribeButton = renderSlot(
194
- finishTranscribeButton,
195
- CopilotChatInput.FinishTranscribeButton,
196
- {
197
- onClick: onFinishTranscribe,
198
- }
199
- );
200
-
201
- const BoundAddFileButton = renderSlot(
202
- addFileButton,
203
- CopilotChatInput.AddFileButton,
204
- {
205
- onClick: onAddFile,
206
- disabled: mode === "transcribe",
207
- }
208
- );
209
-
210
- const BoundToolsButton = renderSlot(
211
- toolsButton,
212
- CopilotChatInput.ToolsButton,
213
- {
214
- disabled: mode === "transcribe",
215
- toolsMenu: toolsMenu,
216
- }
217
- );
218
-
219
- const BoundToolbar = renderSlot(
220
- typeof toolbar === "string" || toolbar === undefined
221
- ? twMerge(
222
- toolbar,
223
- "w-full h-[60px] bg-transparent flex items-center justify-between"
224
- )
225
- : toolbar,
226
- CopilotChatInput.Toolbar,
227
- {
228
- children: (
229
- <>
230
- <div className="flex items-center">
231
- {onAddFile && BoundAddFileButton}
232
- {BoundToolsButton}
233
- {additionalToolbarItems}
234
- </div>
235
- <div className="flex items-center">
236
- {mode === "transcribe" ? (
237
- <>
238
- {onCancelTranscribe && BoundCancelTranscribeButton}
239
- {onFinishTranscribe && BoundFinishTranscribeButton}
240
- </>
241
- ) : (
242
- <>
243
- {onStartTranscribe && BoundStartTranscribeButton}
244
- {BoundSendButton}
245
- </>
246
- )}
247
- </div>
248
- </>
249
- ),
250
- }
251
- );
252
-
253
- if (children) {
254
- return (
255
- <>
256
- {children({
257
- textArea: BoundTextArea,
258
- audioRecorder: BoundAudioRecorder,
259
- sendButton: BoundSendButton,
260
- startTranscribeButton: BoundStartTranscribeButton,
261
- cancelTranscribeButton: BoundCancelTranscribeButton,
262
- finishTranscribeButton: BoundFinishTranscribeButton,
263
- addFileButton: BoundAddFileButton,
264
- toolsButton: BoundToolsButton,
265
- toolbar: BoundToolbar,
266
- onSubmitMessage,
267
- onStartTranscribe,
268
- onCancelTranscribe,
269
- onFinishTranscribe,
270
- onAddFile,
271
- mode,
272
- toolsMenu,
273
- autoFocus,
274
- additionalToolbarItems,
275
- })}
276
- </>
277
- );
278
- }
279
-
280
- return (
281
- <div
282
- className={twMerge(
283
- // Layout
284
- "flex w-full flex-col items-center justify-center",
285
- // Interaction
286
- "cursor-text",
287
- // Overflow and clipping
288
- "overflow-visible bg-clip-padding contain-inline-size",
289
- // Background
290
- "bg-white dark:bg-[#303030]",
291
- // Visual effects
292
- "shadow-[0_4px_4px_0_#0000000a,0_0_1px_0_#0000009e] rounded-[28px]",
293
- className
294
- )}
295
- {...props}
296
- >
297
- {mode === "transcribe" ? BoundAudioRecorder : BoundTextArea}
298
- {BoundToolbar}
299
- </div>
300
- );
301
- }
302
-
303
- // eslint-disable-next-line @typescript-eslint/no-namespace
304
- export namespace CopilotChatInput {
305
- export const SendButton: React.FC<
306
- React.ButtonHTMLAttributes<HTMLButtonElement>
307
- > = ({ className, ...props }) => (
308
- <div className="mr-[10px]">
309
- <Button
310
- type="button"
311
- variant="chatInputToolbarPrimary"
312
- size="chatInputToolbarIcon"
313
- className={className}
314
- {...props}
315
- >
316
- <ArrowUp className="size-[18px]" />
317
- </Button>
318
- </div>
319
- );
320
-
321
- export const ToolbarButton: React.FC<
322
- React.ButtonHTMLAttributes<HTMLButtonElement> & {
323
- icon: React.ReactNode;
324
- labelKey: keyof CopilotChatLabels;
325
- defaultClassName?: string;
326
- }
327
- > = ({ icon, labelKey, defaultClassName, className, ...props }) => {
328
- const { labels } = useCopilotChatConfiguration();
329
- return (
330
- <Tooltip>
331
- <TooltipTrigger asChild>
332
- <Button
333
- type="button"
334
- variant="chatInputToolbarSecondary"
335
- size="chatInputToolbarIcon"
336
- className={twMerge(defaultClassName, className)}
337
- {...props}
338
- >
339
- {icon}
340
- </Button>
341
- </TooltipTrigger>
342
- <TooltipContent side="bottom">
343
- <p>{labels[labelKey]}</p>
344
- </TooltipContent>
345
- </Tooltip>
346
- );
347
- };
348
-
349
- export const StartTranscribeButton: React.FC<
350
- React.ButtonHTMLAttributes<HTMLButtonElement>
351
- > = (props) => (
352
- <ToolbarButton
353
- icon={<Mic className="size-[18px]" />}
354
- labelKey="chatInputToolbarStartTranscribeButtonLabel"
355
- defaultClassName="mr-2"
356
- {...props}
357
- />
358
- );
359
-
360
- export const CancelTranscribeButton: React.FC<
361
- React.ButtonHTMLAttributes<HTMLButtonElement>
362
- > = (props) => (
363
- <ToolbarButton
364
- icon={<X className="size-[18px]" />}
365
- labelKey="chatInputToolbarCancelTranscribeButtonLabel"
366
- defaultClassName="mr-2"
367
- {...props}
368
- />
369
- );
370
-
371
- export const FinishTranscribeButton: React.FC<
372
- React.ButtonHTMLAttributes<HTMLButtonElement>
373
- > = (props) => (
374
- <ToolbarButton
375
- icon={<Check className="size-[18px]" />}
376
- labelKey="chatInputToolbarFinishTranscribeButtonLabel"
377
- defaultClassName="mr-[10px]"
378
- {...props}
379
- />
380
- );
381
-
382
- export const AddFileButton: React.FC<
383
- React.ButtonHTMLAttributes<HTMLButtonElement>
384
- > = (props) => (
385
- <ToolbarButton
386
- icon={<Plus className="size-[20px]" />}
387
- labelKey="chatInputToolbarAddButtonLabel"
388
- defaultClassName="ml-2"
389
- {...props}
390
- />
391
- );
392
-
393
- export const ToolsButton: React.FC<
394
- React.ButtonHTMLAttributes<HTMLButtonElement> & {
395
- toolsMenu?: (ToolsMenuItem | "-")[];
396
- }
397
- > = ({ className, toolsMenu, ...props }) => {
398
- const { labels } = useCopilotChatConfiguration();
399
-
400
- const renderMenuItems = (
401
- items: (ToolsMenuItem | "-")[]
402
- ): React.ReactNode => {
403
- return items.map((item, index) => {
404
- if (item === "-") {
405
- // Separator
406
- return <DropdownMenuSeparator key={index} />;
407
- } else if (item.items && item.items.length > 0) {
408
- // Nested menu
409
- return (
410
- <DropdownMenuSub key={index}>
411
- <DropdownMenuSubTrigger>{item.label}</DropdownMenuSubTrigger>
412
- <DropdownMenuSubContent>
413
- {renderMenuItems(item.items)}
414
- </DropdownMenuSubContent>
415
- </DropdownMenuSub>
416
- );
417
- } else {
418
- // Regular menu item
419
- return (
420
- <DropdownMenuItem key={index} onClick={item.action}>
421
- {item.label}
422
- </DropdownMenuItem>
423
- );
424
- }
425
- });
426
- };
427
-
428
- // Only render if toolsMenu is provided and has items
429
- if (!toolsMenu || toolsMenu.length === 0) {
430
- return null;
431
- }
432
-
433
- // Render dropdown menu
434
- return (
435
- <DropdownMenu>
436
- <DropdownMenuTrigger asChild>
437
- <Button
438
- type="button"
439
- variant="chatInputToolbarSecondary"
440
- size="chatInputToolbarIconLabel"
441
- className={className}
442
- {...props}
443
- >
444
- <Settings2 className="size-[18px]" />
445
- <span className="text-sm font-normal">
446
- {labels.chatInputToolbarToolsButtonLabel}
447
- </span>
448
- </Button>
449
- </DropdownMenuTrigger>
450
- <DropdownMenuContent side="top" align="end">
451
- {renderMenuItems(toolsMenu)}
452
- </DropdownMenuContent>
453
- </DropdownMenu>
454
- );
455
- };
456
-
457
- export const Toolbar: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({
458
- className,
459
- ...props
460
- }) => (
461
- <div
462
- className={twMerge(
463
- "w-full h-[60px] bg-transparent flex items-center",
464
- className
465
- )}
466
- {...props}
467
- />
468
- );
469
-
470
- export interface TextAreaProps
471
- extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
472
- maxRows?: number;
473
- }
474
-
475
- export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
476
- function TextArea({ maxRows = 5, style, className, ...props }, ref) {
477
- const internalTextareaRef = useRef<HTMLTextAreaElement>(null);
478
- const [maxHeight, setMaxHeight] = useState<number>(0);
479
-
480
- const { labels } = useCopilotChatConfiguration();
481
-
482
- useImperativeHandle(
483
- ref,
484
- () => internalTextareaRef.current as HTMLTextAreaElement
485
- );
486
-
487
- const adjustHeight = () => {
488
- const textarea = internalTextareaRef.current;
489
- if (textarea && maxHeight > 0) {
490
- textarea.style.height = "auto";
491
- textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)}px`;
492
- }
493
- };
494
-
495
- useEffect(() => {
496
- const calculateMaxHeight = () => {
497
- const textarea = internalTextareaRef.current;
498
- if (textarea) {
499
- // Save current value
500
- const currentValue = textarea.value;
501
- // Clear content to measure single row height
502
- textarea.value = "";
503
- textarea.style.height = "auto";
504
-
505
- // Get computed styles to account for padding
506
- const computedStyle = window.getComputedStyle(textarea);
507
- const paddingTop = parseFloat(computedStyle.paddingTop);
508
- const paddingBottom = parseFloat(computedStyle.paddingBottom);
509
-
510
- // Calculate actual content height (without padding)
511
- const contentHeight =
512
- textarea.scrollHeight - paddingTop - paddingBottom;
513
-
514
- // Calculate max height: content height for maxRows + padding
515
- setMaxHeight(contentHeight * maxRows + paddingTop + paddingBottom);
516
-
517
- // Restore original value
518
- textarea.value = currentValue;
519
-
520
- // Adjust height after calculating maxHeight
521
- if (currentValue) {
522
- textarea.style.height = "auto";
523
- textarea.style.height = `${Math.min(textarea.scrollHeight, contentHeight * maxRows + paddingTop + paddingBottom)}px`;
524
- }
525
-
526
- if (props.autoFocus) {
527
- textarea.focus();
528
- }
529
- }
530
- };
531
-
532
- calculateMaxHeight();
533
- }, [maxRows, props.autoFocus]);
534
-
535
- // Adjust height when controlled value changes
536
- useEffect(() => {
537
- adjustHeight();
538
- }, [props.value, maxHeight]);
539
-
540
- // Handle input events for uncontrolled usage
541
- const handleInput = (e: React.FormEvent<HTMLTextAreaElement>) => {
542
- adjustHeight();
543
- // Call the original onChange if provided
544
- if (props.onChange) {
545
- props.onChange(e as React.ChangeEvent<HTMLTextAreaElement>);
546
- }
547
- };
548
-
549
- return (
550
- <textarea
551
- ref={internalTextareaRef}
552
- {...props}
553
- onChange={handleInput}
554
- style={{
555
- overflow: "auto",
556
- resize: "none",
557
- maxHeight: `${maxHeight}px`,
558
- ...style,
559
- }}
560
- placeholder={labels.chatInputPlaceholder}
561
- className={twMerge(
562
- // Layout and sizing
563
- "w-full p-5 pb-0",
564
- // Behavior
565
- "outline-none resize-none",
566
- // Background
567
- "bg-transparent",
568
- // Typography
569
- "antialiased font-regular leading-relaxed text-[16px]",
570
- // Placeholder styles
571
- "placeholder:text-[#00000077] dark:placeholder:text-[#fffc]",
572
- className
573
- )}
574
- rows={1}
575
- />
576
- );
577
- }
578
- );
579
-
580
- export const AudioRecorder = CopilotChatAudioRecorder;
581
- }
582
-
583
- CopilotChatInput.TextArea.displayName = "CopilotChatInput.TextArea";
584
- CopilotChatInput.SendButton.displayName = "CopilotChatInput.SendButton";
585
- CopilotChatInput.ToolbarButton.displayName = "CopilotChatInput.ToolbarButton";
586
- CopilotChatInput.StartTranscribeButton.displayName =
587
- "CopilotChatInput.StartTranscribeButton";
588
- CopilotChatInput.CancelTranscribeButton.displayName =
589
- "CopilotChatInput.CancelTranscribeButton";
590
- CopilotChatInput.FinishTranscribeButton.displayName =
591
- "CopilotChatInput.FinishTranscribeButton";
592
- CopilotChatInput.AddFileButton.displayName = "CopilotChatInput.AddButton";
593
- CopilotChatInput.ToolsButton.displayName = "CopilotChatInput.ToolsButton";
594
- CopilotChatInput.Toolbar.displayName = "CopilotChatInput.Toolbar";
595
-
596
- export default CopilotChatInput;
@@ -1,85 +0,0 @@
1
- import { WithSlots, renderSlot } from "@/lib/slots";
2
- import CopilotChatAssistantMessage from "./CopilotChatAssistantMessage";
3
- import CopilotChatUserMessage from "./CopilotChatUserMessage";
4
- import { Message } from "@ag-ui/core";
5
- import { twMerge } from "tailwind-merge";
6
-
7
- export type CopilotChatMessageViewProps = Omit<
8
- WithSlots<
9
- {
10
- assistantMessage: typeof CopilotChatAssistantMessage;
11
- userMessage: typeof CopilotChatUserMessage;
12
- cursor: typeof CopilotChatMessageView.Cursor;
13
- },
14
- {
15
- isLoading?: boolean;
16
- messages?: Message[];
17
- } & React.HTMLAttributes<HTMLDivElement>
18
- >,
19
- "children"
20
- > & {
21
- children?: (props: {
22
- isLoading: boolean;
23
- messages: Message[];
24
- messageElements: React.ReactElement[];
25
- }) => React.ReactElement;
26
- };
27
-
28
- export function CopilotChatMessageView({
29
- messages = [],
30
- assistantMessage,
31
- userMessage,
32
- cursor,
33
- isLoading = false,
34
- children,
35
- className,
36
- ...props
37
- }: CopilotChatMessageViewProps) {
38
- const messageElements: React.ReactElement[] = messages
39
- .map((message) => {
40
- if (message.role === "assistant") {
41
- return renderSlot(assistantMessage, CopilotChatAssistantMessage, {
42
- key: message.id,
43
- message,
44
- messages,
45
- isLoading,
46
- });
47
- } else if (message.role === "user") {
48
- return renderSlot(userMessage, CopilotChatUserMessage, {
49
- key: message.id,
50
- message,
51
- });
52
- }
53
-
54
- return;
55
- })
56
- .filter(Boolean) as React.ReactElement[];
57
-
58
- if (children) {
59
- return children({ messageElements, messages, isLoading });
60
- }
61
-
62
- return (
63
- <div className={twMerge("flex flex-col", className)} {...props}>
64
- {messageElements}
65
- {isLoading && renderSlot(cursor, CopilotChatMessageView.Cursor, {})}
66
- </div>
67
- );
68
- }
69
-
70
- CopilotChatMessageView.Cursor = function Cursor({
71
- className,
72
- ...props
73
- }: React.HTMLAttributes<HTMLDivElement>) {
74
- return (
75
- <div
76
- className={twMerge(
77
- "w-[11px] h-[11px] rounded-full bg-foreground animate-pulse-cursor ml-1",
78
- className
79
- )}
80
- {...props}
81
- />
82
- );
83
- };
84
-
85
- export default CopilotChatMessageView;