@copilotkit/react-ui 1.55.0-next.9 → 1.55.1-next.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/dist/index.mjs CHANGED
@@ -4,7 +4,7 @@ import * as React$1 from "react";
4
4
  import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { defaultCopilotContextCategories, shouldShowDevConsole, useCopilotChatInternal, useCopilotChatSuggestions as useCopilotChatSuggestions$1, useCopilotContext, useCopilotMessagesContext } from "@copilotkit/react-core";
7
- import { COPILOTKIT_VERSION, CopilotKitError, CopilotKitErrorCode, ErrorVisibility, Severity, isMacOS, randomUUID, styledConsole } from "@copilotkit/shared";
7
+ import { COPILOTKIT_VERSION, CopilotKitError, CopilotKitErrorCode, ErrorVisibility, Severity, exceedsMaxSize, formatFileSize, formatFileSize as formatFileSize$1, generateVideoThumbnail, getDocumentIcon, getModalityFromMimeType, getSourceUrl, isMacOS, matchesAcceptFilter, randomUUID, readFileAsBase64, styledConsole } from "@copilotkit/shared";
8
8
  import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
9
9
  import ReactMarkdown from "react-markdown";
10
10
  import { Light, Prism } from "react-syntax-highlighter";
@@ -885,6 +885,89 @@ const Header = ({}) => {
885
885
  });
886
886
  };
887
887
 
888
+ //#endregion
889
+ //#region src/components/chat/AttachmentRenderer.tsx
890
+ const ImageAttachment = memo(function ImageAttachment({ src, className }) {
891
+ const [error, setError] = useState(false);
892
+ if (error) return /* @__PURE__ */ jsx("div", {
893
+ className: `copilotKitImageRendering copilotKitImageRenderingError ${className ?? ""}`,
894
+ children: /* @__PURE__ */ jsx("div", {
895
+ className: "copilotKitImageRenderingErrorMessage",
896
+ children: "Failed to load image"
897
+ })
898
+ });
899
+ return /* @__PURE__ */ jsx("div", {
900
+ className: `copilotKitImageRendering ${className ?? ""}`,
901
+ children: /* @__PURE__ */ jsx("img", {
902
+ src,
903
+ alt: "Image attachment",
904
+ className: "copilotKitImageRenderingImage",
905
+ onError: () => setError(true)
906
+ })
907
+ });
908
+ });
909
+ const AudioAttachment = memo(function AudioAttachment({ src, filename, className }) {
910
+ return /* @__PURE__ */ jsxs("div", {
911
+ className: `copilotKitAttachment copilotKitAttachmentAudio ${className ?? ""}`,
912
+ children: [/* @__PURE__ */ jsx("audio", {
913
+ src,
914
+ controls: true,
915
+ preload: "metadata"
916
+ }), filename && /* @__PURE__ */ jsx("span", {
917
+ className: "copilotKitAttachmentFilename",
918
+ children: filename
919
+ })]
920
+ });
921
+ });
922
+ const VideoAttachment = memo(function VideoAttachment({ src, className }) {
923
+ return /* @__PURE__ */ jsx("div", {
924
+ className: `copilotKitAttachment copilotKitAttachmentVideo ${className ?? ""}`,
925
+ children: /* @__PURE__ */ jsx("video", {
926
+ src,
927
+ controls: true,
928
+ preload: "metadata"
929
+ })
930
+ });
931
+ });
932
+ const DocumentAttachment = memo(function DocumentAttachment({ source, filename, className }) {
933
+ return /* @__PURE__ */ jsxs("div", {
934
+ className: `copilotKitAttachment copilotKitAttachmentDocument ${className ?? ""}`,
935
+ children: [/* @__PURE__ */ jsx("div", {
936
+ className: "copilotKitAttachmentDocIcon",
937
+ children: getDocumentIcon(source.mimeType ?? "")
938
+ }), /* @__PURE__ */ jsx("div", {
939
+ className: "copilotKitAttachmentDocInfo",
940
+ children: /* @__PURE__ */ jsx("span", {
941
+ className: "copilotKitAttachmentDocName",
942
+ children: filename || source.mimeType || "Unknown type"
943
+ })
944
+ })]
945
+ });
946
+ });
947
+ const AttachmentRenderer = ({ type, source, filename, className }) => {
948
+ const src = getSourceUrl(source);
949
+ switch (type) {
950
+ case "image": return /* @__PURE__ */ jsx(ImageAttachment, {
951
+ src,
952
+ className
953
+ });
954
+ case "audio": return /* @__PURE__ */ jsx(AudioAttachment, {
955
+ src,
956
+ filename,
957
+ className
958
+ });
959
+ case "video": return /* @__PURE__ */ jsx(VideoAttachment, {
960
+ src,
961
+ className
962
+ });
963
+ case "document": return /* @__PURE__ */ jsx(DocumentAttachment, {
964
+ source,
965
+ filename,
966
+ className
967
+ });
968
+ }
969
+ };
970
+
888
971
  //#endregion
889
972
  //#region src/components/chat/messages/UserMessage.tsx
890
973
  const getTextContent = (content) => {
@@ -894,22 +977,35 @@ const getTextContent = (content) => {
894
977
  if (part.type === "text") return part.text;
895
978
  }).filter((value) => typeof value === "string" && value.length > 0).join(" ").trim() || void 0;
896
979
  };
980
+ const getMediaParts = (content) => {
981
+ if (!content || typeof content === "string") return [];
982
+ return content.filter((part) => part.type === "image" || part.type === "audio" || part.type === "video" || part.type === "document");
983
+ };
897
984
  const UserMessage = (props) => {
898
985
  const { message, ImageRenderer } = props;
986
+ const content = message?.content;
899
987
  if (message && "image" in message && Boolean(message.image)) {
900
- const imageMessage = message;
901
- const content = getTextContent(imageMessage?.content);
988
+ const legacyImage = message.image;
902
989
  return /* @__PURE__ */ jsx("div", {
903
990
  className: "copilotKitMessage copilotKitUserMessage",
904
991
  children: /* @__PURE__ */ jsx(ImageRenderer, {
905
- image: imageMessage.image,
906
- content
992
+ image: legacyImage,
993
+ content: getTextContent(content)
907
994
  })
908
995
  });
909
996
  }
910
- return /* @__PURE__ */ jsx("div", {
997
+ const textContent = getTextContent(content);
998
+ const mediaParts = getMediaParts(content);
999
+ if (mediaParts.length === 0) return /* @__PURE__ */ jsx("div", {
1000
+ className: "copilotKitMessage copilotKitUserMessage",
1001
+ children: textContent
1002
+ });
1003
+ return /* @__PURE__ */ jsxs("div", {
911
1004
  className: "copilotKitMessage copilotKitUserMessage",
912
- children: getTextContent(message?.content)
1005
+ children: [textContent && /* @__PURE__ */ jsx("div", { children: textContent }), mediaParts.map((part, index) => /* @__PURE__ */ jsx(AttachmentRenderer, {
1006
+ type: part.type,
1007
+ source: part.source
1008
+ }, index))]
913
1009
  });
914
1010
  };
915
1011
 
@@ -1379,16 +1475,18 @@ const AssistantMessage = (props) => {
1379
1475
  //#endregion
1380
1476
  //#region src/components/chat/messages/ImageRenderer.tsx
1381
1477
  /**
1382
- * Default image rendering component that can be customized by users.
1383
- * Uses CSS classes for styling so users can override styles.
1478
+ * @deprecated Use `CopilotChatAttachmentRenderer` from `@copilotkit/react-core/v2` instead.
1479
+ * `ImageRenderer` only handles images. The v2 attachment renderer supports images, audio, video, and documents.
1480
+ * See https://docs.copilotkit.ai/migration-guides/migrate-attachments
1481
+ * @since 1.56.0
1384
1482
  */
1385
- const ImageRenderer = ({ image, content, className = "" }) => {
1483
+ const ImageRenderer = ({ image, source, content, className = "" }) => {
1386
1484
  const [imageError, setImageError] = useState(false);
1387
- const imageSrc = `data:image/${image.format};base64,${image.bytes}`;
1485
+ let imageSrc;
1486
+ if (source) imageSrc = source.type === "url" ? source.value : `data:${source.mimeType};base64,${source.value}`;
1487
+ else if (image) imageSrc = `data:image/${image.format};base64,${image.bytes}`;
1488
+ else return null;
1388
1489
  const altText = content || "User uploaded image";
1389
- const handleImageError = () => {
1390
- setImageError(true);
1391
- };
1392
1490
  if (imageError) return /* @__PURE__ */ jsxs("div", {
1393
1491
  className: `copilotKitImageRendering copilotKitImageRenderingError ${className}`,
1394
1492
  children: [/* @__PURE__ */ jsx("div", {
@@ -1405,7 +1503,7 @@ const ImageRenderer = ({ image, content, className = "" }) => {
1405
1503
  src: imageSrc,
1406
1504
  alt: altText,
1407
1505
  className: "copilotKitImageRenderingImage",
1408
- onError: handleImageError
1506
+ onError: () => setImageError(true)
1409
1507
  }), content && /* @__PURE__ */ jsx("div", {
1410
1508
  className: "copilotKitImageRenderingContent",
1411
1509
  children: content
@@ -1923,61 +2021,101 @@ const Input = ({ inProgress, onSend, chatReady = false, onStop, onUpload, hideSt
1923
2021
  };
1924
2022
 
1925
2023
  //#endregion
1926
- //#region src/components/chat/ImageUploadQueue.tsx
1927
- const ImageUploadQueue = ({ images, onRemoveImage, className = "" }) => {
1928
- if (images.length === 0) return null;
2024
+ //#region src/components/chat/AttachmentQueue.tsx
2025
+ const AttachmentQueue = ({ attachments, onRemoveAttachment, className = "" }) => {
2026
+ if (attachments.length === 0) return null;
1929
2027
  return /* @__PURE__ */ jsx("div", {
1930
- className: `copilotKitImageUploadQueue ${className}`,
1931
- style: {
1932
- display: "flex",
1933
- flexWrap: "wrap",
1934
- gap: "8px",
1935
- margin: "8px",
1936
- padding: "8px"
1937
- },
1938
- children: images.map((image, index) => /* @__PURE__ */ jsxs("div", {
1939
- className: "copilotKitImageUploadQueueItem",
1940
- style: {
1941
- position: "relative",
1942
- display: "inline-block",
1943
- width: "60px",
1944
- height: "60px",
1945
- borderRadius: "4px",
1946
- overflow: "hidden"
1947
- },
1948
- children: [/* @__PURE__ */ jsx("img", {
1949
- src: `data:${image.contentType};base64,${image.bytes}`,
1950
- alt: `Selected image ${index + 1}`,
1951
- style: {
1952
- width: "100%",
1953
- height: "100%",
1954
- objectFit: "cover"
1955
- }
1956
- }), /* @__PURE__ */ jsx("button", {
1957
- onClick: () => onRemoveImage(index),
1958
- className: "copilotKitImageUploadQueueRemoveButton",
1959
- style: {
1960
- position: "absolute",
1961
- top: "2px",
1962
- right: "2px",
1963
- background: "rgba(0,0,0,0.6)",
1964
- color: "white",
1965
- border: "none",
1966
- borderRadius: "50%",
1967
- width: "18px",
1968
- height: "18px",
1969
- display: "flex",
1970
- alignItems: "center",
1971
- justifyContent: "center",
1972
- cursor: "pointer",
1973
- fontSize: "10px",
1974
- padding: 0
1975
- },
1976
- children: "✕"
1977
- })]
1978
- }, index))
2028
+ className: `copilotKitAttachmentQueue ${className}`,
2029
+ children: attachments.map((attachment) => /* @__PURE__ */ jsxs("div", {
2030
+ className: `copilotKitAttachmentQueueItem copilotKitAttachmentQueueItem--${attachment.type}`,
2031
+ children: [
2032
+ attachment.status === "uploading" && /* @__PURE__ */ jsx("div", {
2033
+ className: "copilotKitAttachmentQueueOverlay",
2034
+ children: /* @__PURE__ */ jsx("div", { className: "copilotKitAttachmentQueueSpinner" })
2035
+ }),
2036
+ /* @__PURE__ */ jsx(AttachmentPreview, { attachment }),
2037
+ /* @__PURE__ */ jsx("button", {
2038
+ onClick: () => onRemoveAttachment(attachment.id),
2039
+ className: "copilotKitAttachmentQueueRemoveButton",
2040
+ "aria-label": "Remove attachment",
2041
+ children: ""
2042
+ })
2043
+ ]
2044
+ }, attachment.id))
1979
2045
  });
1980
2046
  };
2047
+ function AttachmentPreview({ attachment }) {
2048
+ if (attachment.status === "uploading") return /* @__PURE__ */ jsx("div", { className: "copilotKitAttachmentQueuePreviewPlaceholder" });
2049
+ const src = getSourceUrl(attachment.source);
2050
+ switch (attachment.type) {
2051
+ case "image": return /* @__PURE__ */ jsx("img", {
2052
+ src,
2053
+ alt: attachment.filename || "Image attachment",
2054
+ className: "copilotKitAttachmentQueuePreviewImage"
2055
+ });
2056
+ case "audio": return /* @__PURE__ */ jsxs("div", {
2057
+ className: "copilotKitAttachmentQueuePreviewAudio",
2058
+ children: [/* @__PURE__ */ jsx("audio", {
2059
+ src,
2060
+ controls: true,
2061
+ preload: "metadata"
2062
+ }), attachment.filename && /* @__PURE__ */ jsx("span", {
2063
+ className: "copilotKitAttachmentQueueFilename",
2064
+ children: attachment.filename
2065
+ })]
2066
+ });
2067
+ case "video": return /* @__PURE__ */ jsx("div", {
2068
+ className: "copilotKitAttachmentQueuePreviewVideo",
2069
+ children: attachment.thumbnail ? /* @__PURE__ */ jsx("img", {
2070
+ src: attachment.thumbnail,
2071
+ alt: attachment.filename || "Video thumbnail",
2072
+ className: "copilotKitAttachmentQueuePreviewImage"
2073
+ }) : /* @__PURE__ */ jsx("video", {
2074
+ src,
2075
+ preload: "metadata",
2076
+ muted: true,
2077
+ className: "copilotKitAttachmentQueuePreviewImage"
2078
+ })
2079
+ });
2080
+ case "document": return /* @__PURE__ */ jsxs("div", {
2081
+ className: "copilotKitAttachmentQueuePreviewDocument",
2082
+ children: [/* @__PURE__ */ jsx("div", {
2083
+ className: "copilotKitAttachmentQueueDocIcon",
2084
+ children: getDocumentIcon(attachment.source.mimeType ?? "")
2085
+ }), /* @__PURE__ */ jsxs("div", {
2086
+ className: "copilotKitAttachmentQueueDocInfo",
2087
+ children: [/* @__PURE__ */ jsx("span", {
2088
+ className: "copilotKitAttachmentQueueFilename",
2089
+ children: attachment.filename || "Document"
2090
+ }), attachment.size != null && /* @__PURE__ */ jsx("span", {
2091
+ className: "copilotKitAttachmentQueueFileSize",
2092
+ children: formatFileSize(attachment.size)
2093
+ })]
2094
+ })]
2095
+ });
2096
+ }
2097
+ }
2098
+
2099
+ //#endregion
2100
+ //#region src/components/chat/attachment-utils.ts
2101
+ const suppressedWarnings = /* @__PURE__ */ new Set();
2102
+ let globalSuppress = false;
2103
+ /**
2104
+ * Issue a deprecation warning once per key per session.
2105
+ * Suppressed entirely if the user calls suppressDeprecationWarnings().
2106
+ */
2107
+ function deprecationWarning(key, message) {
2108
+ if (globalSuppress || suppressedWarnings.has(key)) return;
2109
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "production") return;
2110
+ suppressedWarnings.add(key);
2111
+ console.warn(`[CopilotKit] Deprecation: ${message}`);
2112
+ }
2113
+ /**
2114
+ * Suppress all CopilotKit deprecation warnings.
2115
+ */
2116
+ function suppressDeprecationWarnings() {
2117
+ globalSuppress = true;
2118
+ }
1981
2119
 
1982
2120
  //#endregion
1983
2121
  //#region src/components/chat/Suggestion.tsx
@@ -2077,10 +2215,25 @@ function Suggestions({ suggestions, onSuggestionClick, isLoading }) {
2077
2215
  * ```
2078
2216
  * For more information about how to customize the styles, check out the [Customize Look & Feel](/guides/custom-look-and-feel/customize-built-in-ui-components) guide.
2079
2217
  */
2080
- function CopilotChat({ instructions, suggestions = "auto", onSubmitMessage, makeSystemMessage, disableSystemMessage, onInProgress, onStopGeneration, onReloadMessages, onRegenerate, onCopy, onThumbsUp, onThumbsDown, markdownTagRenderers, Messages: Messages$2 = Messages, RenderMessage: RenderMessage$1 = RenderMessage, RenderSuggestionsList = Suggestions, Input: Input$2 = Input, className, icons, labels, AssistantMessage: AssistantMessage$2 = AssistantMessage, UserMessage: UserMessage$3 = UserMessage, ImageRenderer: ImageRenderer$1 = ImageRenderer, ErrorMessage, imageUploadsEnabled, inputFileAccept = "image/*", hideStopButton, observabilityHooks, renderError, onError, RenderTextMessage, RenderActionExecutionMessage, RenderAgentStateMessage, RenderResultMessage, RenderImageMessage }) {
2218
+ function CopilotChat({ instructions, suggestions = "auto", onSubmitMessage, makeSystemMessage, disableSystemMessage, onInProgress, onStopGeneration, onReloadMessages, onRegenerate, onCopy, onThumbsUp, onThumbsDown, markdownTagRenderers, Messages: Messages$2 = Messages, RenderMessage: RenderMessage$1 = RenderMessage, RenderSuggestionsList = Suggestions, Input: Input$2 = Input, className, icons, labels, AssistantMessage: AssistantMessage$2 = AssistantMessage, UserMessage: UserMessage$3 = UserMessage, ImageRenderer: ImageRenderer$1 = ImageRenderer, ErrorMessage, imageUploadsEnabled, inputFileAccept = "image/*", attachments, hideStopButton, observabilityHooks, renderError, onError, RenderTextMessage, RenderActionExecutionMessage, RenderAgentStateMessage, RenderResultMessage, RenderImageMessage }) {
2081
2219
  const { additionalInstructions, setChatInstructions, copilotApiConfig, setBannerError, setInternalErrorHandler, removeInternalErrorHandler } = useCopilotContext();
2082
2220
  const { publicApiKey, chatApiEndpoint } = copilotApiConfig;
2083
- const [selectedImages, setSelectedImages] = useState([]);
2221
+ const resolvedAttachments = (() => {
2222
+ if (attachments) return attachments;
2223
+ if (imageUploadsEnabled) {
2224
+ deprecationWarning("imageUploadsEnabled", "imageUploadsEnabled is deprecated. Use attachments={{ enabled: true }} instead. See https://docs.copilotkit.ai/migration-guides/migrate-attachments");
2225
+ return {
2226
+ enabled: true,
2227
+ accept: inputFileAccept || "image/*"
2228
+ };
2229
+ }
2230
+ })();
2231
+ const attachmentsEnabled = resolvedAttachments?.enabled ?? false;
2232
+ const attachmentsAccept = resolvedAttachments?.accept ?? "*/*";
2233
+ const attachmentsMaxSize = resolvedAttachments?.maxSize ?? 20 * 1024 * 1024;
2234
+ const [selectedAttachments, setSelectedAttachments] = useState([]);
2235
+ const [dragOver, setDragOver] = useState(false);
2236
+ const processFilesRef = useRef(async () => {});
2084
2237
  const [chatError, setChatError] = useState(null);
2085
2238
  const [messageFeedback, setMessageFeedback] = useState({});
2086
2239
  const fileInputRef = useRef(null);
@@ -2101,8 +2254,10 @@ function CopilotChat({ instructions, suggestions = "auto", onSubmitMessage, make
2101
2254
  setBannerError
2102
2255
  ]);
2103
2256
  const triggerChatError = useCallback((error, operation, originalError) => {
2257
+ const errorMessage = error?.message || error?.toString() || "An error occurred";
2258
+ console.error(`[CopilotKit] ${operation} error:`, errorMessage, originalError ?? error);
2104
2259
  setChatError({
2105
- message: error?.message || error?.toString() || "An error occurred",
2260
+ message: errorMessage,
2106
2261
  operation,
2107
2262
  timestamp: Date.now()
2108
2263
  });
@@ -2156,40 +2311,27 @@ function CopilotChat({ instructions, suggestions = "auto", onSubmitMessage, make
2156
2311
  removeInternalErrorHandler
2157
2312
  ]);
2158
2313
  useEffect(() => {
2159
- if (!imageUploadsEnabled) return;
2314
+ if (!attachmentsEnabled) return;
2160
2315
  const handlePaste = async (e) => {
2161
2316
  if (!e.target.parentElement?.classList.contains("copilotKitInput")) return;
2162
- const imageItems = Array.from(e.clipboardData?.items || []).filter((item) => item.type.startsWith("image/"));
2163
- if (imageItems.length === 0) return;
2317
+ const fileItems = Array.from(e.clipboardData?.items || []).filter((item) => item.kind === "file" && item.getAsFile() !== null && matchesAcceptFilter(item.getAsFile(), attachmentsAccept));
2318
+ if (fileItems.length === 0) return;
2164
2319
  e.preventDefault();
2165
- const imagePromises = imageItems.map((item) => {
2166
- const file = item.getAsFile();
2167
- if (!file) return Promise.resolve(null);
2168
- return new Promise((resolve, reject) => {
2169
- const reader = new FileReader();
2170
- reader.onload = (e) => {
2171
- const base64String = (e.target?.result)?.split(",")[1];
2172
- if (base64String) resolve({
2173
- contentType: file.type,
2174
- bytes: base64String
2175
- });
2176
- else resolve(null);
2177
- };
2178
- reader.onerror = reject;
2179
- reader.readAsDataURL(file);
2180
- });
2181
- });
2320
+ const files = fileItems.map((item) => item.getAsFile()).filter((f) => f !== null);
2182
2321
  try {
2183
- const loadedImages = (await Promise.all(imagePromises)).filter((img) => img !== null);
2184
- setSelectedImages((prev) => [...prev, ...loadedImages]);
2322
+ await processFilesRef.current(files);
2185
2323
  } catch (error) {
2186
- triggerChatError(error, "processClipboardImages", error);
2187
- console.error("Error processing pasted images:", error);
2324
+ triggerChatError(error, "pasteUpload", error);
2188
2325
  }
2189
2326
  };
2190
2327
  document.addEventListener("paste", handlePaste);
2191
2328
  return () => document.removeEventListener("paste", handlePaste);
2192
- }, [imageUploadsEnabled, triggerChatError]);
2329
+ }, [
2330
+ attachmentsEnabled,
2331
+ attachmentsAccept,
2332
+ attachmentsMaxSize,
2333
+ triggerChatError
2334
+ ]);
2193
2335
  useEffect(() => {
2194
2336
  if (!additionalInstructions?.length) {
2195
2337
  setChatInstructions(instructions || "");
@@ -2217,9 +2359,38 @@ function CopilotChat({ instructions, suggestions = "auto", onSubmitMessage, make
2217
2359
  }
2218
2360
  }, [isLoading, triggerObservabilityHook]);
2219
2361
  const handleSendMessage = (text) => {
2220
- setSelectedImages([]);
2362
+ if (selectedAttachments.some((a) => a.status === "uploading")) {
2363
+ triggerChatError(/* @__PURE__ */ new Error("Attachment(s) still uploading. Please wait."), "sendMessage");
2364
+ return Promise.resolve({
2365
+ id: randomUUID(),
2366
+ content: text,
2367
+ role: "user"
2368
+ });
2369
+ }
2370
+ const currentAttachments = selectedAttachments.filter((a) => a.status === "ready");
2371
+ setSelectedAttachments([]);
2221
2372
  if (fileInputRef.current) fileInputRef.current.value = "";
2222
2373
  triggerObservabilityHook("onMessageSent", text);
2374
+ if (currentAttachments.length > 0) {
2375
+ const contentParts = [];
2376
+ if (text.trim()) contentParts.push({
2377
+ type: "text",
2378
+ text
2379
+ });
2380
+ for (const attachment of currentAttachments) contentParts.push({
2381
+ type: attachment.type,
2382
+ source: attachment.source,
2383
+ metadata: {
2384
+ ...attachment.filename ? { filename: attachment.filename } : {},
2385
+ ...attachment.metadata
2386
+ }
2387
+ });
2388
+ return sendMessage({
2389
+ id: randomUUID(),
2390
+ content: contentParts,
2391
+ role: "user"
2392
+ });
2393
+ }
2223
2394
  return sendMessage({
2224
2395
  id: randomUUID(),
2225
2396
  content: text,
@@ -2237,34 +2408,108 @@ function CopilotChat({ instructions, suggestions = "auto", onSubmitMessage, make
2237
2408
  if (onCopy) onCopy(message);
2238
2409
  triggerObservabilityHook("onMessageCopied", message);
2239
2410
  };
2240
- const handleImageUpload = async (event) => {
2241
- if (!event.target.files || event.target.files.length === 0) return;
2242
- const files = Array.from(event.target.files).filter((file) => file.type.startsWith("image/"));
2243
- if (files.length === 0) return;
2244
- const fileReadPromises = files.map((file) => {
2245
- return new Promise((resolve, reject) => {
2246
- const reader = new FileReader();
2247
- reader.onload = (e) => {
2248
- const base64String = (e.target?.result)?.split(",")[1] || "";
2249
- if (base64String) resolve({
2250
- contentType: file.type,
2251
- bytes: base64String
2252
- });
2253
- };
2254
- reader.onerror = reject;
2255
- reader.readAsDataURL(file);
2411
+ const processFiles = async (files) => {
2412
+ const validFiles = files.filter((file) => matchesAcceptFilter(file, attachmentsAccept));
2413
+ const rejectedFiles = files.filter((file) => !matchesAcceptFilter(file, attachmentsAccept));
2414
+ for (const file of rejectedFiles) {
2415
+ const message = `File "${file.name}" is not accepted. Supported types: ${attachmentsAccept}`;
2416
+ triggerChatError(new Error(message), "fileUpload");
2417
+ resolvedAttachments?.onUploadFailed?.({
2418
+ reason: "invalid-type",
2419
+ file,
2420
+ message
2256
2421
  });
2257
- });
2422
+ }
2423
+ for (const file of validFiles) {
2424
+ if (exceedsMaxSize(file, attachmentsMaxSize)) {
2425
+ const message = `File "${file.name}" exceeds the maximum size of ${formatFileSize$1(attachmentsMaxSize)}`;
2426
+ triggerChatError(new Error(message), "fileUpload");
2427
+ resolvedAttachments?.onUploadFailed?.({
2428
+ reason: "file-too-large",
2429
+ file,
2430
+ message
2431
+ });
2432
+ continue;
2433
+ }
2434
+ const modality = getModalityFromMimeType(file.type);
2435
+ const placeholderId = randomUUID();
2436
+ const placeholder = {
2437
+ id: placeholderId,
2438
+ type: modality,
2439
+ source: {
2440
+ type: "data",
2441
+ value: "",
2442
+ mimeType: file.type
2443
+ },
2444
+ filename: file.name,
2445
+ size: file.size,
2446
+ status: "uploading"
2447
+ };
2448
+ setSelectedAttachments((prev) => [...prev, placeholder]);
2449
+ try {
2450
+ let source;
2451
+ let uploadMetadata;
2452
+ if (resolvedAttachments?.onUpload) {
2453
+ const { metadata: meta, ...uploadSource } = await resolvedAttachments.onUpload(file);
2454
+ source = uploadSource;
2455
+ uploadMetadata = meta;
2456
+ } else source = {
2457
+ type: "data",
2458
+ value: await readFileAsBase64(file),
2459
+ mimeType: file.type
2460
+ };
2461
+ let thumbnail;
2462
+ if (modality === "video") thumbnail = await generateVideoThumbnail(file);
2463
+ setSelectedAttachments((prev) => prev.map((att) => att.id === placeholderId ? {
2464
+ ...att,
2465
+ source,
2466
+ status: "ready",
2467
+ thumbnail,
2468
+ metadata: uploadMetadata
2469
+ } : att));
2470
+ } catch (error) {
2471
+ setSelectedAttachments((prev) => prev.filter((att) => att.id !== placeholderId));
2472
+ const message = error instanceof Error ? error.message : String(error);
2473
+ triggerChatError(/* @__PURE__ */ new Error(`Failed to upload "${file.name}": ${message}`), "fileUpload", error);
2474
+ resolvedAttachments?.onUploadFailed?.({
2475
+ reason: "upload-failed",
2476
+ file,
2477
+ message: `Failed to upload "${file.name}": ${message}`
2478
+ });
2479
+ }
2480
+ }
2481
+ };
2482
+ processFilesRef.current = processFiles;
2483
+ const handleFileUpload = async (event) => {
2484
+ if (!event.target.files || event.target.files.length === 0) return;
2258
2485
  try {
2259
- const loadedImages = await Promise.all(fileReadPromises);
2260
- setSelectedImages((prev) => [...prev, ...loadedImages]);
2486
+ await processFiles(Array.from(event.target.files));
2261
2487
  } catch (error) {
2262
- triggerChatError(error, "processUploadedImages", error);
2263
- console.error("Error reading files:", error);
2488
+ triggerChatError(error, "fileUpload", error);
2264
2489
  }
2265
2490
  };
2266
- const removeSelectedImage = (index) => {
2267
- setSelectedImages((prev) => prev.filter((_, i) => i !== index));
2491
+ const handleDragOver = (e) => {
2492
+ if (!attachmentsEnabled) return;
2493
+ e.preventDefault();
2494
+ e.stopPropagation();
2495
+ setDragOver(true);
2496
+ };
2497
+ const handleDragLeave = (e) => {
2498
+ e.preventDefault();
2499
+ e.stopPropagation();
2500
+ setDragOver(false);
2501
+ };
2502
+ const handleDrop = async (e) => {
2503
+ e.preventDefault();
2504
+ e.stopPropagation();
2505
+ setDragOver(false);
2506
+ if (!attachmentsEnabled) return;
2507
+ const files = Array.from(e.dataTransfer.files);
2508
+ if (files.length > 0) try {
2509
+ await processFiles(files);
2510
+ } catch (error) {
2511
+ triggerChatError(error, "dropUpload", error);
2512
+ }
2268
2513
  };
2269
2514
  const handleThumbsUp = (message) => {
2270
2515
  if (onThumbsUp) onThumbsUp(message);
@@ -2282,65 +2527,71 @@ function CopilotChat({ instructions, suggestions = "auto", onSubmitMessage, make
2282
2527
  }));
2283
2528
  triggerObservabilityHook("onFeedbackGiven", message.id, "thumbsDown");
2284
2529
  };
2285
- return /* @__PURE__ */ jsxs(WrappedCopilotChat, {
2530
+ return /* @__PURE__ */ jsx(WrappedCopilotChat, {
2286
2531
  icons,
2287
2532
  labels,
2288
2533
  className,
2289
- children: [
2290
- chatError && renderError && renderError({
2291
- ...chatError,
2292
- onDismiss: () => setChatError(null),
2293
- onRetry: () => {
2294
- setChatError(null);
2295
- }
2296
- }),
2297
- /* @__PURE__ */ jsx(Messages$2, {
2298
- AssistantMessage: AssistantMessage$2,
2299
- UserMessage: UserMessage$3,
2300
- RenderMessage: RenderMessage$1,
2301
- messages,
2302
- inProgress: isLoading,
2303
- onRegenerate: handleRegenerate,
2304
- onCopy: handleCopy,
2305
- onThumbsUp: handleThumbsUp,
2306
- onThumbsDown: handleThumbsDown,
2307
- messageFeedback,
2308
- markdownTagRenderers,
2309
- ImageRenderer: ImageRenderer$1,
2310
- ErrorMessage,
2311
- chatError,
2312
- RenderTextMessage,
2313
- RenderActionExecutionMessage,
2314
- RenderAgentStateMessage,
2315
- RenderResultMessage,
2316
- RenderImageMessage,
2317
- children: currentSuggestions.length > 0 && /* @__PURE__ */ jsx(RenderSuggestionsList, {
2318
- onSuggestionClick: handleSendMessage,
2319
- suggestions: currentSuggestions,
2320
- isLoading: isLoadingSuggestions
2534
+ children: /* @__PURE__ */ jsxs("div", {
2535
+ onDragOver: handleDragOver,
2536
+ onDragLeave: handleDragLeave,
2537
+ onDrop: handleDrop,
2538
+ className: dragOver ? "copilotKitDragOver" : "",
2539
+ children: [
2540
+ chatError && renderError && renderError({
2541
+ ...chatError,
2542
+ onDismiss: () => setChatError(null),
2543
+ onRetry: () => {
2544
+ setChatError(null);
2545
+ }
2546
+ }),
2547
+ /* @__PURE__ */ jsx(Messages$2, {
2548
+ AssistantMessage: AssistantMessage$2,
2549
+ UserMessage: UserMessage$3,
2550
+ RenderMessage: RenderMessage$1,
2551
+ messages,
2552
+ inProgress: isLoading,
2553
+ onRegenerate: handleRegenerate,
2554
+ onCopy: handleCopy,
2555
+ onThumbsUp: handleThumbsUp,
2556
+ onThumbsDown: handleThumbsDown,
2557
+ messageFeedback,
2558
+ markdownTagRenderers,
2559
+ ImageRenderer: ImageRenderer$1,
2560
+ ErrorMessage,
2561
+ chatError,
2562
+ RenderTextMessage,
2563
+ RenderActionExecutionMessage,
2564
+ RenderAgentStateMessage,
2565
+ RenderResultMessage,
2566
+ RenderImageMessage,
2567
+ children: currentSuggestions.length > 0 && /* @__PURE__ */ jsx(RenderSuggestionsList, {
2568
+ onSuggestionClick: handleSendMessage,
2569
+ suggestions: currentSuggestions,
2570
+ isLoading: isLoadingSuggestions
2571
+ })
2572
+ }),
2573
+ attachmentsEnabled && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(AttachmentQueue, {
2574
+ attachments: selectedAttachments,
2575
+ onRemoveAttachment: (id) => setSelectedAttachments((prev) => prev.filter((att) => att.id !== id))
2576
+ }), /* @__PURE__ */ jsx("input", {
2577
+ type: "file",
2578
+ multiple: true,
2579
+ ref: fileInputRef,
2580
+ onChange: handleFileUpload,
2581
+ accept: attachmentsAccept,
2582
+ style: { display: "none" }
2583
+ })] }),
2584
+ /* @__PURE__ */ jsx(Input$2, {
2585
+ inProgress: isLoading,
2586
+ chatReady: Boolean(agent),
2587
+ onSend: handleSendMessage,
2588
+ isVisible,
2589
+ onStop: stopGeneration,
2590
+ onUpload: attachmentsEnabled ? () => fileInputRef.current?.click() : void 0,
2591
+ hideStopButton
2321
2592
  })
2322
- }),
2323
- imageUploadsEnabled && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ImageUploadQueue, {
2324
- images: selectedImages,
2325
- onRemoveImage: removeSelectedImage
2326
- }), /* @__PURE__ */ jsx("input", {
2327
- type: "file",
2328
- multiple: true,
2329
- ref: fileInputRef,
2330
- onChange: handleImageUpload,
2331
- accept: inputFileAccept,
2332
- style: { display: "none" }
2333
- })] }),
2334
- /* @__PURE__ */ jsx(Input$2, {
2335
- inProgress: isLoading,
2336
- chatReady: Boolean(agent),
2337
- onSend: handleSendMessage,
2338
- isVisible,
2339
- onStop: stopGeneration,
2340
- onUpload: imageUploadsEnabled ? () => fileInputRef.current?.click() : void 0,
2341
- hideStopButton
2342
- })
2343
- ]
2593
+ ]
2594
+ })
2344
2595
  });
2345
2596
  }
2346
2597
  function WrappedCopilotChat({ children, icons, labels, className }) {
@@ -2393,7 +2644,10 @@ const CopilotModalInner = ({ observabilityHooks, onSetOpen, clickOutsideToClose,
2393
2644
  triggerObservabilityHook
2394
2645
  ]);
2395
2646
  const memoizedHeader = useMemo(() => /* @__PURE__ */ jsx(Header, {}), [Header]);
2396
- return /* @__PURE__ */ jsxs(Fragment, { children: [useMemo(() => children, [children]), /* @__PURE__ */ jsxs("div", {
2647
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
2648
+ className: "copilotKitModalChildrenWrapper",
2649
+ children: useMemo(() => children, [children])
2650
+ }), /* @__PURE__ */ jsxs("div", {
2397
2651
  className,
2398
2652
  children: [/* @__PURE__ */ jsx(Button, {}), /* @__PURE__ */ jsxs(Window, {
2399
2653
  clickOutsideToClose,
@@ -2684,5 +2938,5 @@ function useCopilotChatSuggestions(config, dependencies = []) {
2684
2938
  }
2685
2939
 
2686
2940
  //#endregion
2687
- export { AssistantMessage, CopilotChat, CopilotDevConsole, CopilotPopup, CopilotSidebar, ImageRenderer, Markdown, Suggestion as RenderSuggestion, Suggestions as RenderSuggestionsList, UserMessage, shouldShowDevConsole, useChatContext, useCopilotChatSuggestions };
2941
+ export { AssistantMessage, CopilotChat, CopilotDevConsole, CopilotPopup, CopilotSidebar, ImageRenderer, Markdown, Suggestion as RenderSuggestion, Suggestions as RenderSuggestionsList, UserMessage, shouldShowDevConsole, suppressDeprecationWarnings, useChatContext, useCopilotChatSuggestions };
2688
2942
  //# sourceMappingURL=index.mjs.map