@copilotkit/react-ui 1.55.0-next.8 → 1.55.0
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.
- package/CHANGELOG.md +31 -3
- package/dist/index.cjs +429 -174
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +334 -25
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +62 -6
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +62 -6
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +430 -176
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +440 -179
- package/dist/index.umd.js.map +1 -1
- package/oxlint-rules/copilotkit-plugin.mjs +10 -0
- package/oxlint-rules/require-cpk-prefix.mjs +547 -0
- package/package.json +33 -34
- package/src/components/chat/AttachmentQueue.tsx +125 -0
- package/src/components/chat/AttachmentRenderer.tsx +133 -0
- package/src/components/chat/Chat.tsx +367 -149
- package/src/components/chat/Modal.tsx +1 -1
- package/src/components/chat/attachment-utils.ts +32 -0
- package/src/components/chat/index.tsx +1 -0
- package/src/components/chat/messages/ImageRenderer.tsx +20 -8
- package/src/components/chat/messages/UserMessage.tsx +42 -8
- package/src/components/chat/props.ts +25 -2
- package/src/css/attachments.css +227 -0
- package/src/css/colors.css +8 -4
- package/src/css/console.css +34 -9
- package/src/css/input.css +5 -2
- package/src/css/markdown.css +1 -1
- package/src/css/messages.css +11 -4
- package/src/css/popup.css +15 -3
- package/src/css/sidebar.css +28 -3
- package/src/css/suggestions.css +4 -2
- package/src/styles.css +2 -1
|
@@ -108,7 +108,18 @@ import {
|
|
|
108
108
|
UserMessageProps,
|
|
109
109
|
} from "./props";
|
|
110
110
|
|
|
111
|
-
import {
|
|
111
|
+
import { AttachmentQueue } from "./AttachmentQueue";
|
|
112
|
+
import type { Attachment, AttachmentsConfig } from "./props";
|
|
113
|
+
import {
|
|
114
|
+
getModalityFromMimeType,
|
|
115
|
+
exceedsMaxSize,
|
|
116
|
+
readFileAsBase64,
|
|
117
|
+
generateVideoThumbnail,
|
|
118
|
+
matchesAcceptFilter,
|
|
119
|
+
formatFileSize,
|
|
120
|
+
deprecationWarning,
|
|
121
|
+
} from "./attachment-utils";
|
|
122
|
+
import type { InputContent } from "@copilotkit/shared";
|
|
112
123
|
import { Suggestions as DefaultRenderSuggestionsList } from "./Suggestions";
|
|
113
124
|
|
|
114
125
|
/**
|
|
@@ -201,16 +212,48 @@ export interface CopilotChatProps {
|
|
|
201
212
|
labels?: CopilotChatLabels;
|
|
202
213
|
|
|
203
214
|
/**
|
|
215
|
+
* @deprecated Use `attachments={{ enabled: true }}` instead.
|
|
216
|
+
* `imageUploadsEnabled` only supports images. The new `attachments` prop supports
|
|
217
|
+
* images, audio, video, and documents.
|
|
218
|
+
* See https://docs.copilotkit.ai/migration-guides/migrate-attachments
|
|
219
|
+
* @since 1.56.0
|
|
220
|
+
*
|
|
204
221
|
* Enable image upload button (image inputs only supported on some models)
|
|
205
222
|
*/
|
|
206
223
|
imageUploadsEnabled?: boolean;
|
|
207
224
|
|
|
208
225
|
/**
|
|
226
|
+
* @deprecated Use `attachments={{ enabled: true, accept: "..." }}` instead.
|
|
227
|
+
* The `accept` field on the `attachments` prop replaces `inputFileAccept`.
|
|
228
|
+
* See https://docs.copilotkit.ai/migration-guides/migrate-attachments
|
|
229
|
+
* @since 1.56.0
|
|
230
|
+
*
|
|
209
231
|
* The 'accept' attribute for the file input used for image uploads.
|
|
210
232
|
* Defaults to "image/*".
|
|
211
233
|
*/
|
|
212
234
|
inputFileAccept?: string;
|
|
213
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Configuration for file attachments in the chat input.
|
|
238
|
+
* Enables users to attach images, audio, video, and documents.
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```tsx
|
|
242
|
+
* <CopilotChat
|
|
243
|
+
* attachments={{
|
|
244
|
+
* enabled: true,
|
|
245
|
+
* accept: "image/*,application/pdf",
|
|
246
|
+
* maxSize: 10 * 1024 * 1024, // 10MB
|
|
247
|
+
* onUpload: async (file) => {
|
|
248
|
+
* const url = await uploadToS3(file);
|
|
249
|
+
* return { url, mimeType: file.type };
|
|
250
|
+
* },
|
|
251
|
+
* }}
|
|
252
|
+
* />
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
attachments?: AttachmentsConfig;
|
|
256
|
+
|
|
214
257
|
/**
|
|
215
258
|
* A function that takes in context string and instructions and returns
|
|
216
259
|
* the system message to include in the chat request.
|
|
@@ -290,6 +333,9 @@ export interface CopilotChatProps {
|
|
|
290
333
|
Input?: React.ComponentType<InputProps>;
|
|
291
334
|
|
|
292
335
|
/**
|
|
336
|
+
* @deprecated Use the v2 `CopilotChat` attachment system instead.
|
|
337
|
+
* See https://docs.copilotkit.ai/migration-guides/migrate-attachments
|
|
338
|
+
*
|
|
293
339
|
* A custom image rendering component to use instead of the default.
|
|
294
340
|
*/
|
|
295
341
|
ImageRenderer?: React.ComponentType<ImageRendererProps>;
|
|
@@ -330,6 +376,13 @@ export interface CopilotChatProps {
|
|
|
330
376
|
onError?: CopilotErrorHandler;
|
|
331
377
|
}
|
|
332
378
|
|
|
379
|
+
/**
|
|
380
|
+
* @deprecated Use the `Attachment` type from `@copilotkit/react-ui` instead.
|
|
381
|
+
* `ImageUpload` only described image payloads. `Attachment` supports images,
|
|
382
|
+
* audio, video, and documents.
|
|
383
|
+
* See https://docs.copilotkit.ai/migration-guides/migrate-attachments
|
|
384
|
+
* @since 1.56.0
|
|
385
|
+
*/
|
|
333
386
|
export type ImageUpload = {
|
|
334
387
|
contentType: string;
|
|
335
388
|
bytes: string;
|
|
@@ -362,6 +415,7 @@ export function CopilotChat({
|
|
|
362
415
|
ErrorMessage,
|
|
363
416
|
imageUploadsEnabled,
|
|
364
417
|
inputFileAccept = "image/*",
|
|
418
|
+
attachments,
|
|
365
419
|
hideStopButton,
|
|
366
420
|
observabilityHooks,
|
|
367
421
|
renderError,
|
|
@@ -384,7 +438,33 @@ export function CopilotChat({
|
|
|
384
438
|
|
|
385
439
|
// Destructure stable values to avoid object reference changes
|
|
386
440
|
const { publicApiKey, chatApiEndpoint } = copilotApiConfig;
|
|
387
|
-
|
|
441
|
+
|
|
442
|
+
// Resolve attachments config with deprecation bridge
|
|
443
|
+
const resolvedAttachments: AttachmentsConfig | undefined = (() => {
|
|
444
|
+
if (attachments) return attachments;
|
|
445
|
+
if (imageUploadsEnabled) {
|
|
446
|
+
deprecationWarning(
|
|
447
|
+
"imageUploadsEnabled",
|
|
448
|
+
"imageUploadsEnabled is deprecated. Use attachments={{ enabled: true }} instead. " +
|
|
449
|
+
"See https://docs.copilotkit.ai/migration-guides/migrate-attachments",
|
|
450
|
+
);
|
|
451
|
+
return { enabled: true, accept: inputFileAccept || "image/*" };
|
|
452
|
+
}
|
|
453
|
+
return undefined;
|
|
454
|
+
})();
|
|
455
|
+
|
|
456
|
+
const attachmentsEnabled = resolvedAttachments?.enabled ?? false;
|
|
457
|
+
const attachmentsAccept = resolvedAttachments?.accept ?? "*/*";
|
|
458
|
+
const attachmentsMaxSize = resolvedAttachments?.maxSize ?? 20 * 1024 * 1024;
|
|
459
|
+
|
|
460
|
+
const [selectedAttachments, setSelectedAttachments] = useState<Attachment[]>(
|
|
461
|
+
[],
|
|
462
|
+
);
|
|
463
|
+
const [dragOver, setDragOver] = useState(false);
|
|
464
|
+
const processFilesRef = useRef<(files: File[]) => Promise<void>>(
|
|
465
|
+
async () => {},
|
|
466
|
+
);
|
|
467
|
+
|
|
388
468
|
const [chatError, setChatError] = useState<ChatError | null>(null);
|
|
389
469
|
const [messageFeedback, setMessageFeedback] = useState<
|
|
390
470
|
Record<string, "thumbsUp" | "thumbsDown">
|
|
@@ -418,6 +498,12 @@ export function CopilotChat({
|
|
|
418
498
|
const errorMessage =
|
|
419
499
|
error?.message || error?.toString() || "An error occurred";
|
|
420
500
|
|
|
501
|
+
console.error(
|
|
502
|
+
`[CopilotKit] ${operation} error:`,
|
|
503
|
+
errorMessage,
|
|
504
|
+
originalError ?? error,
|
|
505
|
+
);
|
|
506
|
+
|
|
421
507
|
// Set chat error state for rendering
|
|
422
508
|
setChatError({
|
|
423
509
|
message: errorMessage,
|
|
@@ -490,58 +576,42 @@ export function CopilotChat({
|
|
|
490
576
|
|
|
491
577
|
// Clipboard paste handler
|
|
492
578
|
useEffect(() => {
|
|
493
|
-
if (!
|
|
579
|
+
if (!attachmentsEnabled) return;
|
|
494
580
|
|
|
495
581
|
const handlePaste = async (e: ClipboardEvent) => {
|
|
496
582
|
const target = e.target as HTMLElement;
|
|
497
583
|
if (!target.parentElement?.classList.contains("copilotKitInput")) return;
|
|
498
584
|
|
|
499
585
|
const items = Array.from(e.clipboardData?.items || []);
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const imagePromises: Promise<ImageUpload | null>[] = imageItems.map(
|
|
507
|
-
(item) => {
|
|
508
|
-
const file = item.getAsFile();
|
|
509
|
-
if (!file) return Promise.resolve(null);
|
|
510
|
-
|
|
511
|
-
return new Promise<ImageUpload | null>((resolve, reject) => {
|
|
512
|
-
const reader = new FileReader();
|
|
513
|
-
reader.onload = (e) => {
|
|
514
|
-
const base64String = (e.target?.result as string)?.split(",")[1];
|
|
515
|
-
if (base64String) {
|
|
516
|
-
resolve({
|
|
517
|
-
contentType: file.type,
|
|
518
|
-
bytes: base64String,
|
|
519
|
-
});
|
|
520
|
-
} else {
|
|
521
|
-
resolve(null);
|
|
522
|
-
}
|
|
523
|
-
};
|
|
524
|
-
reader.onerror = reject;
|
|
525
|
-
reader.readAsDataURL(file);
|
|
526
|
-
});
|
|
527
|
-
},
|
|
586
|
+
const fileItems = items.filter(
|
|
587
|
+
(item) =>
|
|
588
|
+
item.kind === "file" &&
|
|
589
|
+
item.getAsFile() !== null &&
|
|
590
|
+
matchesAcceptFilter(item.getAsFile()!, attachmentsAccept),
|
|
528
591
|
);
|
|
529
592
|
|
|
593
|
+
if (fileItems.length === 0) return;
|
|
594
|
+
e.preventDefault();
|
|
595
|
+
|
|
596
|
+
const files = fileItems
|
|
597
|
+
.map((item) => item.getAsFile())
|
|
598
|
+
.filter((f): f is File => f !== null);
|
|
599
|
+
|
|
530
600
|
try {
|
|
531
|
-
|
|
532
|
-
(img) => img !== null,
|
|
533
|
-
);
|
|
534
|
-
setSelectedImages((prev) => [...prev, ...loadedImages]);
|
|
601
|
+
await processFilesRef.current(files);
|
|
535
602
|
} catch (error) {
|
|
536
|
-
|
|
537
|
-
triggerChatError(error, "processClipboardImages", error);
|
|
538
|
-
console.error("Error processing pasted images:", error);
|
|
603
|
+
triggerChatError(error, "pasteUpload", error);
|
|
539
604
|
}
|
|
540
605
|
};
|
|
541
606
|
|
|
542
607
|
document.addEventListener("paste", handlePaste);
|
|
543
608
|
return () => document.removeEventListener("paste", handlePaste);
|
|
544
|
-
}, [
|
|
609
|
+
}, [
|
|
610
|
+
attachmentsEnabled,
|
|
611
|
+
attachmentsAccept,
|
|
612
|
+
attachmentsMaxSize,
|
|
613
|
+
triggerChatError,
|
|
614
|
+
]);
|
|
545
615
|
|
|
546
616
|
useEffect(() => {
|
|
547
617
|
if (!additionalInstructions?.length) {
|
|
@@ -598,10 +668,28 @@ export function CopilotChat({
|
|
|
598
668
|
}
|
|
599
669
|
}, [isLoading, triggerObservabilityHook]);
|
|
600
670
|
|
|
601
|
-
// Wrapper for sendMessage to clear selected
|
|
671
|
+
// Wrapper for sendMessage to clear selected attachments and build multimodal content
|
|
602
672
|
const handleSendMessage = (text: string) => {
|
|
603
|
-
const
|
|
604
|
-
|
|
673
|
+
const hasUploading = selectedAttachments.some(
|
|
674
|
+
(a) => a.status === "uploading",
|
|
675
|
+
);
|
|
676
|
+
if (hasUploading) {
|
|
677
|
+
triggerChatError(
|
|
678
|
+
new Error("Attachment(s) still uploading. Please wait."),
|
|
679
|
+
"sendMessage",
|
|
680
|
+
);
|
|
681
|
+
// Return a promise that resolves to a dummy message to satisfy the return type
|
|
682
|
+
return Promise.resolve({
|
|
683
|
+
id: randomUUID(),
|
|
684
|
+
content: text,
|
|
685
|
+
role: "user" as const,
|
|
686
|
+
} as Message);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const currentAttachments = selectedAttachments.filter(
|
|
690
|
+
(a) => a.status === "ready",
|
|
691
|
+
);
|
|
692
|
+
setSelectedAttachments([]);
|
|
605
693
|
if (fileInputRef.current) {
|
|
606
694
|
fileInputRef.current.value = "";
|
|
607
695
|
}
|
|
@@ -609,7 +697,33 @@ export function CopilotChat({
|
|
|
609
697
|
// Trigger message sent event
|
|
610
698
|
triggerObservabilityHook("onMessageSent", text);
|
|
611
699
|
|
|
612
|
-
//
|
|
700
|
+
// Build content: if we have attachments, use InputContent[]
|
|
701
|
+
if (currentAttachments.length > 0) {
|
|
702
|
+
const contentParts: InputContent[] = [];
|
|
703
|
+
|
|
704
|
+
if (text.trim()) {
|
|
705
|
+
contentParts.push({ type: "text", text });
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
for (const attachment of currentAttachments) {
|
|
709
|
+
contentParts.push({
|
|
710
|
+
type: attachment.type,
|
|
711
|
+
source: attachment.source,
|
|
712
|
+
metadata: {
|
|
713
|
+
...(attachment.filename ? { filename: attachment.filename } : {}),
|
|
714
|
+
...attachment.metadata,
|
|
715
|
+
},
|
|
716
|
+
} as InputContent);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return sendMessage({
|
|
720
|
+
id: randomUUID(),
|
|
721
|
+
content: contentParts,
|
|
722
|
+
role: "user",
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Plain text message
|
|
613
727
|
return sendMessage({
|
|
614
728
|
id: randomUUID(),
|
|
615
729
|
content: text,
|
|
@@ -640,50 +754,143 @@ export function CopilotChat({
|
|
|
640
754
|
triggerObservabilityHook("onMessageCopied", message);
|
|
641
755
|
};
|
|
642
756
|
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
757
|
+
const processFiles = async (files: File[]) => {
|
|
758
|
+
const validFiles = files.filter((file) =>
|
|
759
|
+
matchesAcceptFilter(file, attachmentsAccept),
|
|
760
|
+
);
|
|
761
|
+
const rejectedFiles = files.filter(
|
|
762
|
+
(file) => !matchesAcceptFilter(file, attachmentsAccept),
|
|
763
|
+
);
|
|
764
|
+
for (const file of rejectedFiles) {
|
|
765
|
+
const message = `File "${file.name}" is not accepted. Supported types: ${attachmentsAccept}`;
|
|
766
|
+
triggerChatError(new Error(message), "fileUpload");
|
|
767
|
+
resolvedAttachments?.onUploadFailed?.({
|
|
768
|
+
reason: "invalid-type",
|
|
769
|
+
file,
|
|
770
|
+
message,
|
|
771
|
+
});
|
|
648
772
|
}
|
|
649
773
|
|
|
650
|
-
const
|
|
651
|
-
file
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
774
|
+
for (const file of validFiles) {
|
|
775
|
+
if (exceedsMaxSize(file, attachmentsMaxSize)) {
|
|
776
|
+
const message = `File "${file.name}" exceeds the maximum size of ${formatFileSize(attachmentsMaxSize)}`;
|
|
777
|
+
triggerChatError(new Error(message), "fileUpload");
|
|
778
|
+
resolvedAttachments?.onUploadFailed?.({
|
|
779
|
+
reason: "file-too-large",
|
|
780
|
+
file,
|
|
781
|
+
message,
|
|
782
|
+
});
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const modality = getModalityFromMimeType(file.type);
|
|
787
|
+
|
|
788
|
+
// Use a unique ID to track this placeholder across concurrent uploads
|
|
789
|
+
const placeholderId = randomUUID();
|
|
790
|
+
const placeholder: Attachment = {
|
|
791
|
+
id: placeholderId,
|
|
792
|
+
type: modality,
|
|
793
|
+
source: { type: "data", value: "", mimeType: file.type },
|
|
794
|
+
filename: file.name,
|
|
795
|
+
size: file.size,
|
|
796
|
+
status: "uploading",
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
setSelectedAttachments((prev) => [...prev, placeholder]);
|
|
800
|
+
|
|
801
|
+
try {
|
|
802
|
+
let source: Attachment["source"];
|
|
803
|
+
let uploadMetadata: Record<string, unknown> | undefined;
|
|
804
|
+
|
|
805
|
+
if (resolvedAttachments?.onUpload) {
|
|
806
|
+
const { metadata: meta, ...uploadSource } =
|
|
807
|
+
await resolvedAttachments.onUpload(file);
|
|
808
|
+
source = uploadSource;
|
|
809
|
+
uploadMetadata = meta;
|
|
810
|
+
} else {
|
|
811
|
+
const base64 = await readFileAsBase64(file);
|
|
812
|
+
source = { type: "data", value: base64, mimeType: file.type };
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Generate video thumbnail if applicable
|
|
816
|
+
let thumbnail: string | undefined;
|
|
817
|
+
if (modality === "video") {
|
|
818
|
+
thumbnail = await generateVideoThumbnail(file);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
setSelectedAttachments((prev) =>
|
|
822
|
+
prev.map((att) =>
|
|
823
|
+
att.id === placeholderId
|
|
824
|
+
? {
|
|
825
|
+
...att,
|
|
826
|
+
source,
|
|
827
|
+
status: "ready" as const,
|
|
828
|
+
thumbnail,
|
|
829
|
+
metadata: uploadMetadata,
|
|
830
|
+
}
|
|
831
|
+
: att,
|
|
832
|
+
),
|
|
833
|
+
);
|
|
834
|
+
} catch (error) {
|
|
835
|
+
// Remove the failed placeholder
|
|
836
|
+
setSelectedAttachments((prev) =>
|
|
837
|
+
prev.filter((att) => att.id !== placeholderId),
|
|
838
|
+
);
|
|
839
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
840
|
+
triggerChatError(
|
|
841
|
+
new Error(`Failed to upload "${file.name}": ${message}`),
|
|
842
|
+
"fileUpload",
|
|
843
|
+
error,
|
|
844
|
+
);
|
|
845
|
+
resolvedAttachments?.onUploadFailed?.({
|
|
846
|
+
reason: "upload-failed",
|
|
847
|
+
file,
|
|
848
|
+
message: `Failed to upload "${file.name}": ${message}`,
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
processFilesRef.current = processFiles;
|
|
674
854
|
|
|
855
|
+
const handleFileUpload = async (
|
|
856
|
+
event: React.ChangeEvent<HTMLInputElement>,
|
|
857
|
+
) => {
|
|
858
|
+
if (!event.target.files || event.target.files.length === 0) return;
|
|
675
859
|
try {
|
|
676
|
-
|
|
677
|
-
setSelectedImages((prev) => [...prev, ...loadedImages]);
|
|
860
|
+
await processFiles(Array.from(event.target.files));
|
|
678
861
|
} catch (error) {
|
|
679
|
-
|
|
680
|
-
triggerChatError(error, "processUploadedImages", error);
|
|
681
|
-
console.error("Error reading files:", error);
|
|
862
|
+
triggerChatError(error, "fileUpload", error);
|
|
682
863
|
}
|
|
683
864
|
};
|
|
684
865
|
|
|
685
|
-
|
|
686
|
-
|
|
866
|
+
// Drag-and-drop handlers
|
|
867
|
+
const handleDragOver = (e: React.DragEvent) => {
|
|
868
|
+
if (!attachmentsEnabled) return;
|
|
869
|
+
e.preventDefault();
|
|
870
|
+
e.stopPropagation();
|
|
871
|
+
setDragOver(true);
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
const handleDragLeave = (e: React.DragEvent) => {
|
|
875
|
+
e.preventDefault();
|
|
876
|
+
e.stopPropagation();
|
|
877
|
+
setDragOver(false);
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
const handleDrop = async (e: React.DragEvent) => {
|
|
881
|
+
e.preventDefault();
|
|
882
|
+
e.stopPropagation();
|
|
883
|
+
setDragOver(false);
|
|
884
|
+
if (!attachmentsEnabled) return;
|
|
885
|
+
|
|
886
|
+
const files = Array.from(e.dataTransfer.files);
|
|
887
|
+
if (files.length > 0) {
|
|
888
|
+
try {
|
|
889
|
+
await processFiles(files);
|
|
890
|
+
} catch (error) {
|
|
891
|
+
triggerChatError(error, "dropUpload", error);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
687
894
|
};
|
|
688
895
|
|
|
689
896
|
const handleThumbsUp = (message: Message) => {
|
|
@@ -718,78 +925,89 @@ export function CopilotChat({
|
|
|
718
925
|
|
|
719
926
|
return (
|
|
720
927
|
<WrappedCopilotChat icons={icons} labels={labels} className={className}>
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
onDismiss: () => setChatError(null),
|
|
727
|
-
onRetry: () => {
|
|
728
|
-
// Clear error and potentially retry based on operation
|
|
729
|
-
setChatError(null);
|
|
730
|
-
// TODO: Implement specific retry logic based on operation type
|
|
731
|
-
},
|
|
732
|
-
})}
|
|
733
|
-
|
|
734
|
-
<Messages
|
|
735
|
-
AssistantMessage={AssistantMessage}
|
|
736
|
-
UserMessage={UserMessage}
|
|
737
|
-
RenderMessage={RenderMessage}
|
|
738
|
-
messages={messages}
|
|
739
|
-
inProgress={isLoading}
|
|
740
|
-
onRegenerate={handleRegenerate}
|
|
741
|
-
onCopy={handleCopy}
|
|
742
|
-
onThumbsUp={handleThumbsUp}
|
|
743
|
-
onThumbsDown={handleThumbsDown}
|
|
744
|
-
messageFeedback={messageFeedback}
|
|
745
|
-
markdownTagRenderers={markdownTagRenderers}
|
|
746
|
-
ImageRenderer={ImageRenderer}
|
|
747
|
-
ErrorMessage={ErrorMessage}
|
|
748
|
-
chatError={chatError}
|
|
749
|
-
// Legacy props - passed through to Messages component
|
|
750
|
-
RenderTextMessage={RenderTextMessage}
|
|
751
|
-
RenderActionExecutionMessage={RenderActionExecutionMessage}
|
|
752
|
-
RenderAgentStateMessage={RenderAgentStateMessage}
|
|
753
|
-
RenderResultMessage={RenderResultMessage}
|
|
754
|
-
RenderImageMessage={RenderImageMessage}
|
|
928
|
+
<div
|
|
929
|
+
onDragOver={handleDragOver}
|
|
930
|
+
onDragLeave={handleDragLeave}
|
|
931
|
+
onDrop={handleDrop}
|
|
932
|
+
className={dragOver ? "copilotKitDragOver" : ""}
|
|
755
933
|
>
|
|
756
|
-
{
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
934
|
+
{/* Render error above messages if present */}
|
|
935
|
+
{chatError &&
|
|
936
|
+
renderError &&
|
|
937
|
+
renderError({
|
|
938
|
+
...chatError,
|
|
939
|
+
onDismiss: () => setChatError(null),
|
|
940
|
+
onRetry: () => {
|
|
941
|
+
// Clear error and potentially retry based on operation
|
|
942
|
+
setChatError(null);
|
|
943
|
+
// TODO: Implement specific retry logic based on operation type
|
|
944
|
+
},
|
|
945
|
+
})}
|
|
946
|
+
|
|
947
|
+
<Messages
|
|
948
|
+
AssistantMessage={AssistantMessage}
|
|
949
|
+
UserMessage={UserMessage}
|
|
950
|
+
RenderMessage={RenderMessage}
|
|
951
|
+
messages={messages}
|
|
952
|
+
inProgress={isLoading}
|
|
953
|
+
onRegenerate={handleRegenerate}
|
|
954
|
+
onCopy={handleCopy}
|
|
955
|
+
onThumbsUp={handleThumbsUp}
|
|
956
|
+
onThumbsDown={handleThumbsDown}
|
|
957
|
+
messageFeedback={messageFeedback}
|
|
958
|
+
markdownTagRenderers={markdownTagRenderers}
|
|
959
|
+
ImageRenderer={ImageRenderer}
|
|
960
|
+
ErrorMessage={ErrorMessage}
|
|
961
|
+
chatError={chatError}
|
|
962
|
+
// Legacy props - passed through to Messages component
|
|
963
|
+
RenderTextMessage={RenderTextMessage}
|
|
964
|
+
RenderActionExecutionMessage={RenderActionExecutionMessage}
|
|
965
|
+
RenderAgentStateMessage={RenderAgentStateMessage}
|
|
966
|
+
RenderResultMessage={RenderResultMessage}
|
|
967
|
+
RenderImageMessage={RenderImageMessage}
|
|
968
|
+
>
|
|
969
|
+
{currentSuggestions.length > 0 && (
|
|
970
|
+
<RenderSuggestionsList
|
|
971
|
+
onSuggestionClick={handleSendMessage}
|
|
972
|
+
suggestions={currentSuggestions}
|
|
973
|
+
isLoading={isLoadingSuggestions}
|
|
974
|
+
/>
|
|
975
|
+
)}
|
|
976
|
+
</Messages>
|
|
977
|
+
|
|
978
|
+
{attachmentsEnabled && (
|
|
979
|
+
<>
|
|
980
|
+
<AttachmentQueue
|
|
981
|
+
attachments={selectedAttachments}
|
|
982
|
+
onRemoveAttachment={(id) =>
|
|
983
|
+
setSelectedAttachments((prev) =>
|
|
984
|
+
prev.filter((att) => att.id !== id),
|
|
985
|
+
)
|
|
986
|
+
}
|
|
987
|
+
/>
|
|
988
|
+
<input
|
|
989
|
+
type="file"
|
|
990
|
+
multiple
|
|
991
|
+
ref={fileInputRef}
|
|
992
|
+
onChange={handleFileUpload}
|
|
993
|
+
accept={attachmentsAccept}
|
|
994
|
+
style={{ display: "none" }}
|
|
995
|
+
/>
|
|
996
|
+
</>
|
|
762
997
|
)}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
accept={inputFileAccept}
|
|
777
|
-
style={{ display: "none" }}
|
|
778
|
-
/>
|
|
779
|
-
</>
|
|
780
|
-
)}
|
|
781
|
-
<Input
|
|
782
|
-
inProgress={isLoading}
|
|
783
|
-
chatReady={Boolean(agent)}
|
|
784
|
-
// @ts-ignore
|
|
785
|
-
onSend={handleSendMessage}
|
|
786
|
-
isVisible={isVisible}
|
|
787
|
-
onStop={stopGeneration}
|
|
788
|
-
onUpload={
|
|
789
|
-
imageUploadsEnabled ? () => fileInputRef.current?.click() : undefined
|
|
790
|
-
}
|
|
791
|
-
hideStopButton={hideStopButton}
|
|
792
|
-
/>
|
|
998
|
+
<Input
|
|
999
|
+
inProgress={isLoading}
|
|
1000
|
+
chatReady={Boolean(agent)}
|
|
1001
|
+
// @ts-ignore
|
|
1002
|
+
onSend={handleSendMessage}
|
|
1003
|
+
isVisible={isVisible}
|
|
1004
|
+
onStop={stopGeneration}
|
|
1005
|
+
onUpload={
|
|
1006
|
+
attachmentsEnabled ? () => fileInputRef.current?.click() : undefined
|
|
1007
|
+
}
|
|
1008
|
+
hideStopButton={hideStopButton}
|
|
1009
|
+
/>
|
|
1010
|
+
</div>
|
|
793
1011
|
</WrappedCopilotChat>
|
|
794
1012
|
);
|
|
795
1013
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Re-export utilities from shared
|
|
2
|
+
export {
|
|
3
|
+
getModalityFromMimeType,
|
|
4
|
+
formatFileSize,
|
|
5
|
+
exceedsMaxSize,
|
|
6
|
+
readFileAsBase64,
|
|
7
|
+
generateVideoThumbnail,
|
|
8
|
+
matchesAcceptFilter,
|
|
9
|
+
} from "@copilotkit/shared";
|
|
10
|
+
|
|
11
|
+
// Deprecation warning helpers — react-ui specific
|
|
12
|
+
const suppressedWarnings = new Set<string>();
|
|
13
|
+
let globalSuppress = false;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Issue a deprecation warning once per key per session.
|
|
17
|
+
* Suppressed entirely if the user calls suppressDeprecationWarnings().
|
|
18
|
+
*/
|
|
19
|
+
export function deprecationWarning(key: string, message: string) {
|
|
20
|
+
if (globalSuppress || suppressedWarnings.has(key)) return;
|
|
21
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV === "production")
|
|
22
|
+
return;
|
|
23
|
+
suppressedWarnings.add(key);
|
|
24
|
+
console.warn(`[CopilotKit] Deprecation: ${message}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Suppress all CopilotKit deprecation warnings.
|
|
29
|
+
*/
|
|
30
|
+
export function suppressDeprecationWarnings() {
|
|
31
|
+
globalSuppress = true;
|
|
32
|
+
}
|
|
@@ -9,3 +9,4 @@ export { ImageRenderer } from "./messages/ImageRenderer";
|
|
|
9
9
|
export { useChatContext } from "./ChatContext";
|
|
10
10
|
export { Suggestions as RenderSuggestionsList } from "./Suggestions";
|
|
11
11
|
export { Suggestion as RenderSuggestion } from "./Suggestion";
|
|
12
|
+
export { suppressDeprecationWarnings } from "./attachment-utils";
|