CR fixes
This commit is contained in:
@@ -117,8 +117,7 @@ export function Thread() {
|
|||||||
parseAsBoolean.withDefault(false),
|
parseAsBoolean.withDefault(false),
|
||||||
);
|
);
|
||||||
const [input, setInput] = useState("");
|
const [input, setInput] = useState("");
|
||||||
const [imageUrlList, setImageUrlList] = useState<Base64ContentBlock[]>([]);
|
const [contentBlocks, setContentBlocks] = useState<Base64ContentBlock[]>([]);
|
||||||
const [pdfUrlList, setPdfUrlList] = useState<Base64ContentBlock[]>([]);
|
|
||||||
const [firstTokenReceived, setFirstTokenReceived] = useState(false);
|
const [firstTokenReceived, setFirstTokenReceived] = useState(false);
|
||||||
const isLargeScreen = useMediaQuery("(min-width: 1024px)");
|
const isLargeScreen = useMediaQuery("(min-width: 1024px)");
|
||||||
|
|
||||||
@@ -174,12 +173,7 @@ export function Thread() {
|
|||||||
|
|
||||||
const handleSubmit = (e: FormEvent) => {
|
const handleSubmit = (e: FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (
|
if ((input.trim().length === 0 && contentBlocks.length === 0) || isLoading)
|
||||||
(input.trim().length === 0 &&
|
|
||||||
imageUrlList.length === 0 &&
|
|
||||||
pdfUrlList.length === 0) ||
|
|
||||||
isLoading
|
|
||||||
)
|
|
||||||
return;
|
return;
|
||||||
setFirstTokenReceived(false);
|
setFirstTokenReceived(false);
|
||||||
|
|
||||||
@@ -188,12 +182,10 @@ export function Thread() {
|
|||||||
type: "human",
|
type: "human",
|
||||||
content: [
|
content: [
|
||||||
...(input.trim().length > 0 ? [{ type: "text", text: input }] : []),
|
...(input.trim().length > 0 ? [{ type: "text", text: input }] : []),
|
||||||
...pdfUrlList,
|
...contentBlocks,
|
||||||
...imageUrlList,
|
|
||||||
] as Message["content"],
|
] as Message["content"],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const toolMessages = ensureToolCallsHaveResponses(stream.messages);
|
const toolMessages = ensureToolCallsHaveResponses(stream.messages);
|
||||||
stream.submit(
|
stream.submit(
|
||||||
{ messages: [...toolMessages, newHumanMessage] },
|
{ messages: [...toolMessages, newHumanMessage] },
|
||||||
@@ -211,19 +203,33 @@ export function Thread() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
setInput("");
|
setInput("");
|
||||||
setImageUrlList([]);
|
setContentBlocks([]);
|
||||||
setPdfUrlList([]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const SUPPORTED_IMAGE_TYPES = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
const SUPPORTED_IMAGE_TYPES = [
|
||||||
|
"image/jpeg",
|
||||||
|
"image/png",
|
||||||
|
"image/gif",
|
||||||
|
"image/webp",
|
||||||
|
];
|
||||||
const SUPPORTED_FILE_TYPES = [...SUPPORTED_IMAGE_TYPES, "application/pdf"];
|
const SUPPORTED_FILE_TYPES = [...SUPPORTED_IMAGE_TYPES, "application/pdf"];
|
||||||
|
|
||||||
const isDuplicate = (file: File, images: Base64ContentBlock[], pdfs: Base64ContentBlock[]) => {
|
const isDuplicate = (file: File, blocks: Base64ContentBlock[]) => {
|
||||||
if (SUPPORTED_IMAGE_TYPES.includes(file.type)) {
|
if (SUPPORTED_IMAGE_TYPES.includes(file.type)) {
|
||||||
return images.some(img => img.metadata?.name === file.name && img.mime_type === file.type);
|
return blocks.some(
|
||||||
|
(b) =>
|
||||||
|
b.type === "image" &&
|
||||||
|
b.metadata?.name === file.name &&
|
||||||
|
b.mime_type === file.type,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (file.type === "application/pdf") {
|
if (file.type === "application/pdf") {
|
||||||
return pdfs.some(pdf => pdf.metadata?.filename === file.name);
|
return blocks.some(
|
||||||
|
(b) =>
|
||||||
|
b.type === "file" &&
|
||||||
|
b.mime_type === "application/pdf" &&
|
||||||
|
b.metadata?.filename === file.name,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
@@ -232,10 +238,18 @@ export function Thread() {
|
|||||||
const files = e.target.files;
|
const files = e.target.files;
|
||||||
if (!files) return;
|
if (!files) return;
|
||||||
const fileArray = Array.from(files);
|
const fileArray = Array.from(files);
|
||||||
const validFiles = fileArray.filter((file) => SUPPORTED_FILE_TYPES.includes(file.type));
|
const validFiles = fileArray.filter((file) =>
|
||||||
const invalidFiles = fileArray.filter((file) => !SUPPORTED_FILE_TYPES.includes(file.type));
|
SUPPORTED_FILE_TYPES.includes(file.type),
|
||||||
const duplicateFiles = validFiles.filter((file) => isDuplicate(file, imageUrlList, pdfUrlList));
|
);
|
||||||
const uniqueFiles = validFiles.filter((file) => !isDuplicate(file, imageUrlList, pdfUrlList));
|
const invalidFiles = fileArray.filter(
|
||||||
|
(file) => !SUPPORTED_FILE_TYPES.includes(file.type),
|
||||||
|
);
|
||||||
|
const duplicateFiles = validFiles.filter((file) =>
|
||||||
|
isDuplicate(file, contentBlocks),
|
||||||
|
);
|
||||||
|
const uniqueFiles = validFiles.filter(
|
||||||
|
(file) => !isDuplicate(file, contentBlocks),
|
||||||
|
);
|
||||||
|
|
||||||
if (invalidFiles.length > 0) {
|
if (invalidFiles.length > 0) {
|
||||||
toast.error(
|
toast.error(
|
||||||
@@ -244,22 +258,24 @@ export function Thread() {
|
|||||||
}
|
}
|
||||||
if (duplicateFiles.length > 0) {
|
if (duplicateFiles.length > 0) {
|
||||||
toast.error(
|
toast.error(
|
||||||
`Duplicate file(s) detected: ${duplicateFiles.map(f => f.name).join(", ")}. Each file can only be uploaded once per message.`,
|
`Duplicate file(s) detected: ${duplicateFiles.map((f) => f.name).join(", ")}. Each file can only be uploaded once per message.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageFiles = uniqueFiles.filter((file) => SUPPORTED_IMAGE_TYPES.includes(file.type));
|
const imageFiles = uniqueFiles.filter((file) =>
|
||||||
const pdfFiles = uniqueFiles.filter((file) => file.type === "application/pdf");
|
SUPPORTED_IMAGE_TYPES.includes(file.type),
|
||||||
|
);
|
||||||
|
const pdfFiles = uniqueFiles.filter(
|
||||||
|
(file) => file.type === "application/pdf",
|
||||||
|
);
|
||||||
|
|
||||||
if (imageFiles.length) {
|
const imageBlocks = imageFiles.length
|
||||||
const imageBlocks = await Promise.all(imageFiles.map(fileToImageBlock));
|
? await Promise.all(imageFiles.map(fileToImageBlock))
|
||||||
setImageUrlList((prev) => [...prev, ...imageBlocks]);
|
: [];
|
||||||
}
|
const pdfBlocks = pdfFiles.length
|
||||||
|
? await Promise.all(pdfFiles.map(fileToPDFBlock))
|
||||||
if (pdfFiles.length) {
|
: [];
|
||||||
const pdfBlocks = await Promise.all(pdfFiles.map(fileToPDFBlock));
|
setContentBlocks((prev) => [...prev, ...imageBlocks, ...pdfBlocks]);
|
||||||
setPdfUrlList((prev) => [...prev, ...pdfBlocks]);
|
|
||||||
}
|
|
||||||
e.target.value = "";
|
e.target.value = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -295,10 +311,18 @@ export function Thread() {
|
|||||||
if (!e.dataTransfer) return;
|
if (!e.dataTransfer) return;
|
||||||
|
|
||||||
const files = Array.from(e.dataTransfer.files);
|
const files = Array.from(e.dataTransfer.files);
|
||||||
const validFiles = files.filter((file) => SUPPORTED_FILE_TYPES.includes(file.type));
|
const validFiles = files.filter((file) =>
|
||||||
const invalidFiles = files.filter((file) => !SUPPORTED_FILE_TYPES.includes(file.type));
|
SUPPORTED_FILE_TYPES.includes(file.type),
|
||||||
const duplicateFiles = validFiles.filter((file) => isDuplicate(file, imageUrlList, pdfUrlList));
|
);
|
||||||
const uniqueFiles = validFiles.filter((file) => !isDuplicate(file, imageUrlList, pdfUrlList));
|
const invalidFiles = files.filter(
|
||||||
|
(file) => !SUPPORTED_FILE_TYPES.includes(file.type),
|
||||||
|
);
|
||||||
|
const duplicateFiles = validFiles.filter((file) =>
|
||||||
|
isDuplicate(file, contentBlocks),
|
||||||
|
);
|
||||||
|
const uniqueFiles = validFiles.filter(
|
||||||
|
(file) => !isDuplicate(file, contentBlocks),
|
||||||
|
);
|
||||||
|
|
||||||
if (invalidFiles.length > 0) {
|
if (invalidFiles.length > 0) {
|
||||||
toast.error(
|
toast.error(
|
||||||
@@ -307,26 +331,24 @@ export function Thread() {
|
|||||||
}
|
}
|
||||||
if (duplicateFiles.length > 0) {
|
if (duplicateFiles.length > 0) {
|
||||||
toast.error(
|
toast.error(
|
||||||
`Duplicate file(s) detected: ${duplicateFiles.map(f => f.name).join(", ")}. Each file can only be uploaded once per message.`,
|
`Duplicate file(s) detected: ${duplicateFiles.map((f) => f.name).join(", ")}. Each file can only be uploaded once per message.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageFiles = uniqueFiles.filter((file) => SUPPORTED_IMAGE_TYPES.includes(file.type));
|
const imageFiles = uniqueFiles.filter((file) =>
|
||||||
const pdfFiles = uniqueFiles.filter((file) => file.type === "application/pdf");
|
SUPPORTED_IMAGE_TYPES.includes(file.type),
|
||||||
|
);
|
||||||
|
const pdfFiles = uniqueFiles.filter(
|
||||||
|
(file) => file.type === "application/pdf",
|
||||||
|
);
|
||||||
|
|
||||||
if (imageFiles.length) {
|
const imageBlocks: Base64ContentBlock[] = imageFiles.length
|
||||||
const imageBlocks: Base64ContentBlock[] = await Promise.all(
|
? await Promise.all(imageFiles.map(fileToImageBlock))
|
||||||
imageFiles.map(fileToImageBlock),
|
: [];
|
||||||
);
|
const pdfBlocks: Base64ContentBlock[] = pdfFiles.length
|
||||||
setImageUrlList((prev) => [...prev, ...imageBlocks]);
|
? await Promise.all(pdfFiles.map(fileToPDFBlock))
|
||||||
}
|
: [];
|
||||||
|
setContentBlocks((prev) => [...prev, ...imageBlocks, ...pdfBlocks]);
|
||||||
if (pdfFiles.length) {
|
|
||||||
const pdfBlocks: Base64ContentBlock[] = await Promise.all(
|
|
||||||
pdfFiles.map(fileToPDFBlock),
|
|
||||||
);
|
|
||||||
setPdfUrlList((prev) => [...prev, ...pdfBlocks]);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragEnter = (e: DragEvent) => {
|
const handleDragEnter = (e: DragEvent) => {
|
||||||
@@ -544,30 +566,50 @@ export function Thread() {
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className="mx-auto grid max-w-3xl grid-rows-[1fr_auto] gap-2"
|
className="mx-auto grid max-w-3xl grid-rows-[1fr_auto] gap-2"
|
||||||
>
|
>
|
||||||
{imageUrlList.length > 0 && (
|
{contentBlocks.filter((b) => b.type === "image").length >
|
||||||
|
0 && (
|
||||||
<div className="flex flex-wrap gap-2 p-3.5 pb-0">
|
<div className="flex flex-wrap gap-2 p-3.5 pb-0">
|
||||||
{imageUrlList.map((imageBlock, idx) => (
|
{contentBlocks
|
||||||
<MultimodalPreview
|
.filter((b) => b.type === "image")
|
||||||
key={idx}
|
.map((imageBlock, idx) => (
|
||||||
block={imageBlock}
|
<MultimodalPreview
|
||||||
removable
|
key={idx}
|
||||||
onRemove={() => setImageUrlList(imageUrlList.filter((_, i) => i !== idx))}
|
block={imageBlock}
|
||||||
size="md"
|
removable
|
||||||
/>
|
onRemove={() =>
|
||||||
))}
|
setContentBlocks(
|
||||||
|
contentBlocks.filter((_, i) => i !== idx),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{pdfUrlList.length > 0 && (
|
{contentBlocks.filter(
|
||||||
|
(b) =>
|
||||||
|
b.type === "file" && b.mime_type === "application/pdf",
|
||||||
|
).length > 0 && (
|
||||||
<div className="flex flex-wrap gap-2 p-3.5 pb-0">
|
<div className="flex flex-wrap gap-2 p-3.5 pb-0">
|
||||||
{pdfUrlList.map((pdfBlock, idx) => (
|
{contentBlocks
|
||||||
<MultimodalPreview
|
.filter(
|
||||||
key={idx}
|
(b) =>
|
||||||
block={pdfBlock}
|
b.type === "file" &&
|
||||||
removable
|
b.mime_type === "application/pdf",
|
||||||
onRemove={() => setPdfUrlList(pdfUrlList.filter((_, i) => i !== idx))}
|
)
|
||||||
size="md"
|
.map((pdfBlock, idx) => (
|
||||||
/>
|
<MultimodalPreview
|
||||||
))}
|
key={idx}
|
||||||
|
block={pdfBlock}
|
||||||
|
removable
|
||||||
|
onRemove={() =>
|
||||||
|
setContentBlocks(
|
||||||
|
contentBlocks.filter((_, i) => i !== idx),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<textarea
|
<textarea
|
||||||
@@ -637,9 +679,7 @@ export function Thread() {
|
|||||||
className="shadow-md transition-all"
|
className="shadow-md transition-all"
|
||||||
disabled={
|
disabled={
|
||||||
isLoading ||
|
isLoading ||
|
||||||
(!input.trim() &&
|
(!input.trim() && contentBlocks.length === 0)
|
||||||
imageUrlList.length === 0 &&
|
|
||||||
pdfUrlList.length === 0)
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Send
|
Send
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useStreamContext } from "@/providers/Stream";
|
import { useStreamContext } from "@/providers/Stream";
|
||||||
import { Message } from "@langchain/langgraph-sdk";
|
import { Message } from "@langchain/langgraph-sdk";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {getContentString } from "../utils";
|
import { getContentString } from "../utils";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { BranchSwitcher, CommandBar } from "./shared";
|
import { BranchSwitcher, CommandBar } from "./shared";
|
||||||
@@ -36,7 +36,8 @@ function EditableContent({
|
|||||||
|
|
||||||
// Type guard for Base64ContentBlock
|
// Type guard for Base64ContentBlock
|
||||||
function isBase64ContentBlock(block: unknown): block is Base64ContentBlock {
|
function isBase64ContentBlock(block: unknown): block is Base64ContentBlock {
|
||||||
if (typeof block !== "object" || block === null || !("type" in block)) return false;
|
if (typeof block !== "object" || block === null || !("type" in block))
|
||||||
|
return false;
|
||||||
// file type (legacy)
|
// file type (legacy)
|
||||||
if (
|
if (
|
||||||
(block as { type: unknown }).type === "file" &&
|
(block as { type: unknown }).type === "file" &&
|
||||||
@@ -119,14 +120,21 @@ export function HumanMessage({
|
|||||||
{/* Render images and files if no text */}
|
{/* Render images and files if no text */}
|
||||||
{Array.isArray(message.content) && message.content.length > 0 && (
|
{Array.isArray(message.content) && message.content.length > 0 && (
|
||||||
<div className="flex flex-col items-end gap-2">
|
<div className="flex flex-col items-end gap-2">
|
||||||
{message.content.reduce<React.ReactNode[]>((acc, block, idx) => {
|
{message.content.reduce<React.ReactNode[]>(
|
||||||
if (isBase64ContentBlock(block)) {
|
(acc, block, idx) => {
|
||||||
acc.push(
|
if (isBase64ContentBlock(block)) {
|
||||||
<MultimodalPreview key={idx} block={block} size="md" />
|
acc.push(
|
||||||
);
|
<MultimodalPreview
|
||||||
}
|
key={idx}
|
||||||
return acc;
|
block={block}
|
||||||
}, [])}
|
size="md"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Render text if present, otherwise fallback to file/image name */}
|
{/* Render text if present, otherwise fallback to file/image name */}
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
|
|||||||
md: "h-16 w-16 text-lg",
|
md: "h-16 w-16 text-lg",
|
||||||
lg: "h-24 w-24 text-xl",
|
lg: "h-24 w-24 text-xl",
|
||||||
};
|
};
|
||||||
const iconSize: string = typeof sizeMap[size] === "string" ? sizeMap[size] : sizeMap["md"];
|
const iconSize: string =
|
||||||
|
typeof sizeMap[size] === "string" ? sizeMap[size] : sizeMap["md"];
|
||||||
|
|
||||||
// Image block
|
// Image block
|
||||||
if (
|
if (
|
||||||
@@ -37,7 +38,9 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
|
|||||||
if (size === "sm") imgClass = "rounded-md object-cover h-10 w-10 text-base";
|
if (size === "sm") imgClass = "rounded-md object-cover h-10 w-10 text-base";
|
||||||
if (size === "lg") imgClass = "rounded-md object-cover h-24 w-24 text-xl";
|
if (size === "lg") imgClass = "rounded-md object-cover h-24 w-24 text-xl";
|
||||||
return (
|
return (
|
||||||
<div className={`relative inline-block${className ? ` ${className}` : ''}`}>
|
<div
|
||||||
|
className={`relative inline-block${className ? ` ${className}` : ""}`}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={url}
|
src={url}
|
||||||
alt={String(block.metadata?.name || "uploaded image")}
|
alt={String(block.metadata?.name || "uploaded image")}
|
||||||
@@ -63,12 +66,25 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
|
|||||||
block.source_type === "base64" &&
|
block.source_type === "base64" &&
|
||||||
block.mime_type === "application/pdf"
|
block.mime_type === "application/pdf"
|
||||||
) {
|
) {
|
||||||
const filename = block.metadata?.filename || block.metadata?.name || "PDF file";
|
const filename =
|
||||||
const fileClass = `relative flex items-center gap-2 rounded-md border bg-gray-100 px-3 py-2${className ? ` ${className}` : ''}`;
|
block.metadata?.filename || block.metadata?.name || "PDF file";
|
||||||
|
const fileClass = `relative flex items-center gap-2 rounded-md border bg-gray-100 px-3 py-2${className ? ` ${className}` : ""}`;
|
||||||
return (
|
return (
|
||||||
<div className={fileClass}>
|
<div className={fileClass}>
|
||||||
<File className={"text-teal-700 flex-shrink-0 " + (size === "sm" ? "h-5 w-5" : "h-7 w-7")} />
|
<File
|
||||||
<span className={"truncate text-sm text-gray-800 " + (size === "sm" ? "max-w-[80px]" : "max-w-[160px]")}>{String(filename)}</span>
|
className={
|
||||||
|
"flex-shrink-0 text-teal-700 " +
|
||||||
|
(size === "sm" ? "h-5 w-5" : "h-7 w-7")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
"truncate text-sm text-gray-800 " +
|
||||||
|
(size === "sm" ? "max-w-[80px]" : "max-w-[160px]")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{String(filename)}
|
||||||
|
</span>
|
||||||
{removable && (
|
{removable && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -84,7 +100,7 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback for unknown types
|
// Fallback for unknown types
|
||||||
const fallbackClass = `flex items-center gap-2 rounded-md border bg-gray-100 px-3 py-2 text-gray-500${className ? ` ${className}` : ''}`;
|
const fallbackClass = `flex items-center gap-2 rounded-md border bg-gray-100 px-3 py-2 text-gray-500${className ? ` ${className}` : ""}`;
|
||||||
return (
|
return (
|
||||||
<div className={fallbackClass}>
|
<div className={fallbackClass}>
|
||||||
<File className="h-5 w-5 flex-shrink-0" />
|
<File className="h-5 w-5 flex-shrink-0" />
|
||||||
@@ -101,4 +117,4 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Base64ContentBlock } from "@langchain/core/messages";
|
import type { Base64ContentBlock } from "@langchain/core/messages";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
// Returns a Promise of a typed multimodal block for images
|
// Returns a Promise of a typed multimodal block for images
|
||||||
export async function fileToImageBlock(
|
export async function fileToImageBlock(
|
||||||
@@ -6,9 +7,10 @@ export async function fileToImageBlock(
|
|||||||
): Promise<Base64ContentBlock> {
|
): Promise<Base64ContentBlock> {
|
||||||
const supportedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
const supportedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
||||||
if (!supportedTypes.includes(file.type)) {
|
if (!supportedTypes.includes(file.type)) {
|
||||||
throw new Error(
|
toast.error(
|
||||||
`Unsupported image type: ${file.type}. Supported types are: ${supportedTypes.join(", ")}`,
|
`Unsupported image type: ${file.type}. Supported types are: ${supportedTypes.join(", ")}`,
|
||||||
);
|
);
|
||||||
|
return Promise.reject(new Error(`Unsupported image type: ${file.type}`));
|
||||||
}
|
}
|
||||||
const data = await fileToBase64(file);
|
const data = await fileToBase64(file);
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user