Refactor input bar UI

This commit is contained in:
zaidmukaddam 2024-10-16 23:36:53 +05:30
parent dc104ea3ca
commit be141ead3a
2 changed files with 222 additions and 196 deletions

View File

@ -133,8 +133,6 @@ declare global {
} }
} }
const MAX_IMAGES = 3;
interface Attachment { interface Attachment {
name: string; name: string;
contentType: string; contentType: string;
@ -244,12 +242,22 @@ const HomeContent = () => {
id: "1", id: "1",
title: "Dark mode is here!", title: "Dark mode is here!",
images: [ images: [
"https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-dark-mode.png", "https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-dark-mode-promo.png",
"https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-new-input-bar-promo.png",
"https://metwm7frkvew6tn1.public.blob.vercel-storage.com/mplx-changelogs/mplx-gpt-4o-back-Lwzx44RD4XofYLAmrEsLD3Fngnn33K.png"
], ],
content: content:
`## **Dark Mode** `## **Dark Mode**
The most requested feature is finally here! You can now toggle between light and dark mode. Default is set to your system preference.`, The most requested feature is finally here! You can now toggle between light and dark mode. Default is set to your system preference.
## **New Input Bar Design**
The input bar has been redesigned to make it more focused, user-friendly and accessible. The model selection dropdown has been moved to the bottom left corner inside the input bar.
## **GPT-4o is back!**
GPT-4o has been re-enabled! You can use it by selecting the model from the dropdown.`,
} }
]; ];
@ -1742,14 +1750,6 @@ The most requested feature is finally here! You can now toggle between light and
); );
}; };
interface UploadingAttachment {
file: File;
progress: number;
}
const SuggestionCards: React.FC<{ selectedModel: string }> = ({ selectedModel }) => { const SuggestionCards: React.FC<{ selectedModel: string }> = ({ selectedModel }) => {
return ( return (
<div className="flex gap-3 mt-4"> <div className="flex gap-3 mt-4">
@ -1773,110 +1773,22 @@ The most requested feature is finally here! You can now toggle between light and
); );
}; };
const models = [
{ value: "azure:gpt4o-mini", label: "OpenAI", icon: Zap, description: "High speed, lower quality", color: "emerald" },
{ value: "anthropicVertex:claude-3-5-sonnet@20240620", label: "Claude", icon: Sparkles, description: "High quality, lower speed", color: "indigo" },
]
interface ModelSwitcherProps {
selectedModel: string;
setSelectedModel: (value: string) => void;
className?: string;
}
const ModelSwitcher: React.FC<ModelSwitcherProps> = ({ selectedModel, setSelectedModel, className }) => {
const selectedModelData = models.find(model => model.value === selectedModel) || models[0];
const [isOpen, setIsOpen] = useState(false);
const getColorClasses = (color: string, isSelected: boolean = false) => {
switch (color) {
case 'emerald':
return isSelected
? '!bg-emerald-500 dark:!bg-emerald-700 !text-white hover:!bg-emerald-600 dark:hover:!bg-emerald-800'
: '!text-emerald-700 dark:!text-emerald-300 hover:!bg-emerald-100 dark:hover:!bg-emerald-800/30';
case 'indigo':
return isSelected
? '!bg-indigo-500 dark:!bg-indigo-700 !text-white hover:!bg-indigo-600 dark:hover:!bg-indigo-800'
: '!text-indigo-700 dark:!text-indigo-300 hover:!bg-indigo-100 dark:hover:!bg-indigo-800/30';
case 'blue':
return isSelected
? '!bg-blue-500 dark:!bg-blue-700 !text-white hover:!bg-blue-600 dark:hover:!bg-blue-800'
: '!text-blue-700 dark:!text-blue-300 hover:!bg-blue-100 dark:hover:!bg-blue-800/30';
default:
return isSelected
? 'bg-neutral-500 dark:bg-neutral-600 text-white hover:bg-neutral-600 dark:hover:bg-neutral-700'
: 'text-neutral-700 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-800/30';
}
}
return (
<DropdownMenu onOpenChange={setIsOpen}>
<DropdownMenuTrigger
className={cn(
"flex items-center gap-1.5 px-3 py-1.5 rounded-full transition-all duration-300 shadow-sm text-sm",
getColorClasses(selectedModelData.color, true),
"focus:outline-none focus:ring-none",
"transform hover:scale-105 active:scale-95",
"disabled:opacity-50 disabled:cursor-not-allowed",
className
)}
disabled={isLoading}
>
<selectedModelData.icon className="w-4 h-4" />
<span className="font-medium">{selectedModelData.label}</span>
<ChevronDown className={cn(
"w-3 h-3 transition-transform duration-200",
isOpen && "transform rotate-180"
)} />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[200px] p-1 !font-sans ml-2 sm:m-auto rounded-lg shadow-md bg-white dark:bg-neutral-800">
{models.map((model) => (
<DropdownMenuItem
key={model.value}
onSelect={() => setSelectedModel(model.value)}
className={cn(
"flex items-start gap-2 px-2 py-1.5 rounded-lg text-sm mb-1 last:mb-0",
"transition-colors duration-200",
getColorClasses(model.color, selectedModel === model.value),
selectedModel === model.value && "hover:opacity-90"
)}
>
<model.icon className={cn(
"w-5 h-5 mt-0.5",
selectedModel === model.value ? "text-white" : `text-${model.color}-500 dark:text-${model.color}-400`
)} />
<div>
<div className={cn(
"font-bold",
selectedModel === model.value ? "text-white" : `text-${model.color}-700 dark:text-${model.color}-300`
)}>
{model.label}
</div>
<div className={cn(
"text-xs",
selectedModel === model.value ? "text-white/80" : `text-${model.color}-600 dark:text-${model.color}-400`
)}>
{model.description}
</div>
</div>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)
}
const handleModelChange = useCallback((newModel: string) => { const handleModelChange = useCallback((newModel: string) => {
setSelectedModel(newModel); setSelectedModel(newModel);
setSuggestedQuestions([]); setSuggestedQuestions([]);
reload({ body: { model: newModel } }); reload({ body: { model: newModel } });
}, [reload]); }, [reload]);
const resetSuggestedQuestions = useCallback(() => {
setSuggestedQuestions([]);
}, []);
return ( return (
<div className="flex flex-col font-sans items-center justify-center p-2 sm:p-4 bg-background text-foreground transition-all duration-500"> <div className="flex flex-col font-sans items-center justify-center p-2 sm:p-4 bg-background text-foreground transition-all duration-500">
<Navbar /> <Navbar />
<div className={`w-full max-w-[90%] sm:max-w-2xl space-y-6 p-0 ${hasSubmitted ? 'mt-16 sm:mt-20' : 'mt-[20vh] sm:mt-[30vh]'}`}> <div className={`w-full max-w-[90%] sm:max-w-2xl space-y-6 p-0 ${hasSubmitted ? 'mt-16 sm:mt-20' : 'mt-[20vh] sm:mt-[25vh]'}`}>
{!hasSubmitted && ( {!hasSubmitted && (
<div className="text-center"> <div className="text-center">
<Badge <Badge
@ -1892,11 +1804,6 @@ The most requested feature is finally here! You can now toggle between light and
</h2> </h2>
</div> </div>
)} )}
{!hasSubmitted && (
<div className="flex items-center justify-between !-mb-2">
<ModelSwitcher selectedModel={selectedModel} setSelectedModel={handleModelChange} />
</div>
)}
<AnimatePresence> <AnimatePresence>
{!hasSubmitted && ( {!hasSubmitted && (
<motion.div <motion.div
@ -1918,6 +1825,9 @@ The most requested feature is finally here! You can now toggle between light and
stop={stop} stop={stop}
messages={messages} messages={messages}
append={append} append={append}
selectedModel={selectedModel}
setSelectedModel={handleModelChange}
resetSuggestedQuestions={resetSuggestedQuestions}
/> />
<SuggestionCards selectedModel={selectedModel} /> <SuggestionCards selectedModel={selectedModel} />
</motion.div> </motion.div>
@ -2007,11 +1917,6 @@ The most requested feature is finally here! You can now toggle between light and
<h2 className="text-base font-semibold text-neutral-800 dark:text-neutral-200">Answer</h2> <h2 className="text-base font-semibold text-neutral-800 dark:text-neutral-200">Answer</h2>
</div> </div>
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<ModelSwitcher
selectedModel={selectedModel}
setSelectedModel={handleModelChange}
className="!px-4 rounded-full"
/>
<CopyButton text={message.content} /> <CopyButton text={message.content} />
</div> </div>
</div> </div>
@ -2081,6 +1986,9 @@ The most requested feature is finally here! You can now toggle between light and
stop={stop} stop={stop}
messages={messages} messages={messages}
append={append} append={append}
selectedModel={selectedModel}
setSelectedModel={handleModelChange}
resetSuggestedQuestions={resetSuggestedQuestions}
/> />
</motion.div> </motion.div>
)} )}

View File

@ -1,13 +1,101 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import React, { useState, useRef, useEffect, useCallback } from 'react'; import React, { useState, useRef, useEffect, useCallback } from 'react';
import { motion, AnimatePresence } from 'framer-motion'; import { motion } from 'framer-motion';
import { ChatRequestOptions, CreateMessage, Message } from 'ai'; import { ChatRequestOptions, CreateMessage, Message } from 'ai';
import { track } from '@vercel/analytics';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Button } from '../ui/button'; import { Button } from '../ui/button';
import { Textarea } from '../ui/textarea'; import { Textarea } from '../ui/textarea';
import { cn } from '@/lib/utils';
import useWindowSize from '@/hooks/use-window-size'; import useWindowSize from '@/hooks/use-window-size';
import { X } from 'lucide-react'; import { Sparkles, X, Zap, Cpu } from 'lucide-react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { cn } from '@/lib/utils';
interface ModelSwitcherProps {
selectedModel: string;
setSelectedModel: (value: string) => void;
className?: string;
}
const models = [
{ value: "azure:gpt4o-mini", label: "GPT-4o Mini", icon: Zap, description: "High speed, good quality", color: "emerald" },
{ value: "anthropicVertex:claude-3-5-sonnet@20240620", label: "Claude", icon: Sparkles, description: "High quality, lower speed", color: "indigo" },
{ value: "azure:gpt-4o", label: "GPT-4o", icon: Cpu, description: "Higher quality, normal speed", color: "blue" },
];
const getColorClasses = (color: string, isSelected: boolean = false) => {
const baseClasses = "transition-colors duration-200";
const selectedClasses = isSelected ? "!bg-opacity-90 dark:!bg-opacity-90" : "";
switch (color) {
case 'emerald':
return isSelected
? `${baseClasses} ${selectedClasses} !bg-emerald-500 dark:!bg-emerald-600 !text-white hover:!bg-emerald-600 dark:hover:!bg-emerald-700`
: `${baseClasses} !text-emerald-700 dark:!text-emerald-300 hover:!bg-emerald-200 dark:hover:!bg-emerald-800/70`;
case 'indigo':
return isSelected
? `${baseClasses} ${selectedClasses} !bg-indigo-500 dark:!bg-indigo-600 !text-white hover:!bg-indigo-600 dark:hover:!bg-indigo-700`
: `${baseClasses} !text-indigo-700 dark:!text-indigo-300 hover:!bg-indigo-200 dark:hover:!bg-indigo-800/70`;
case 'blue':
return isSelected
? `${baseClasses} ${selectedClasses} !bg-blue-500 dark:!bg-blue-600 !text-white hover:!bg-blue-600 dark:hover:!bg-blue-700`
: `${baseClasses} !text-blue-700 dark:!text-blue-300 hover:!bg-blue-200 dark:hover:!bg-blue-800/70`;
default:
return isSelected
? `${baseClasses} ${selectedClasses} !bg-neutral-500 dark:!bg-neutral-600 !text-white hover:!bg-neutral-600 dark:hover:!bg-neutral-700`
: `${baseClasses} !text-neutral-700 dark:!text-neutral-300 hover:!bg-neutral-200 dark:hover:!bg-neutral-800/70`;
}
}
const ModelSwitcher: React.FC<ModelSwitcherProps> = ({ selectedModel, setSelectedModel, className }) => {
const selectedModelData = models.find(model => model.value === selectedModel) || models[0];
const [isOpen, setIsOpen] = useState(false);
return (
<DropdownMenu onOpenChange={setIsOpen}>
<DropdownMenuTrigger
className={cn(
"flex items-center justify-center w-8 h-8 rounded-full transition-all duration-300",
getColorClasses(selectedModelData.color, true),
"focus:outline-none focus:ring-2 focus:ring-opacity-50",
`focus:ring-${selectedModelData.color}-500`,
className
)}
>
<selectedModelData.icon className="w-4 h-4" />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[220px] p-1 !font-sans rounded-md shadow-md bg-white dark:bg-neutral-800 ml-4 sm:m-auto">
{models.map((model) => (
<DropdownMenuItem
key={model.value}
onSelect={() => setSelectedModel(model.value)}
className={cn(
"flex items-start gap-2 px-2 py-1.5 rounded-md text-xs mb-1 last:mb-0",
getColorClasses(model.color, selectedModel === model.value)
)}
>
<model.icon className={cn(
"w-4 h-4 mt-0.5",
selectedModel === model.value ? "text-white" : `text-${model.color}-500 dark:text-${model.color}-400`
)} />
<div>
<div className="font-bold">{model.label}</div>
<div className="text-xs opacity-70">{model.description}</div>
</div>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)
}
interface Attachment { interface Attachment {
name: string; name: string;
@ -98,6 +186,9 @@ interface FormComponentProps {
message: Message | CreateMessage, message: Message | CreateMessage,
chatRequestOptions?: ChatRequestOptions, chatRequestOptions?: ChatRequestOptions,
) => Promise<string | null | undefined>; ) => Promise<string | null | undefined>;
selectedModel: string;
setSelectedModel: (value: string) => void;
resetSuggestedQuestions: () => void;
} }
const AttachmentPreview: React.FC<{ attachment: Attachment | UploadingAttachment, onRemove: () => void, isUploading: boolean }> = ({ attachment, onRemove, isUploading }) => { const AttachmentPreview: React.FC<{ attachment: Attachment | UploadingAttachment, onRemove: () => void, isUploading: boolean }> = ({ attachment, onRemove, isUploading }) => {
@ -118,7 +209,7 @@ const AttachmentPreview: React.FC<{ attachment: Attachment | UploadingAttachment
animate={{ opacity: 1, scale: 1 }} animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }} exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="relative flex items-center bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg p-2 pr-8 gap-2 cursor-pointer shadow-sm flex-shrink-0" className="relative flex items-center bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg p-2 pr-8 gap-2 shadow-sm flex-shrink-0"
> >
{isUploading ? ( {isUploading ? (
<div className="w-10 h-10 flex items-center justify-center"> <div className="w-10 h-10 flex items-center justify-center">
@ -203,9 +294,13 @@ const FormComponent: React.FC<FormComponentProps> = ({
handleSubmit, handleSubmit,
fileInputRef, fileInputRef,
inputRef, inputRef,
stop stop,
messages,
append,
selectedModel,
setSelectedModel,
resetSuggestedQuestions,
}) => { }) => {
const [uploadingAttachments, setUploadingAttachments] = useState<UploadingAttachment[]>([]);
const [uploadQueue, setUploadQueue] = useState<Array<string>>([]); const [uploadQueue, setUploadQueue] = useState<Array<string>>([]);
const { width } = useWindowSize(); const { width } = useWindowSize();
const postSubmitFileInputRef = useRef<HTMLInputElement>(null); const postSubmitFileInputRef = useRef<HTMLInputElement>(null);
@ -253,6 +348,12 @@ const FormComponent: React.FC<FormComponentProps> = ({
const handleFileChange = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => { const handleFileChange = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(event.target.files || []); const files = Array.from(event.target.files || []);
const totalAttachments = attachments.length + files.length;
if (totalAttachments > MAX_IMAGES) {
toast.error(`You can only attach up to ${MAX_IMAGES} images.`);
return;
}
setUploadQueue(files.map((file) => file.name)); setUploadQueue(files.map((file) => file.name));
@ -265,11 +366,12 @@ const FormComponent: React.FC<FormComponentProps> = ({
]); ]);
} catch (error) { } catch (error) {
console.error("Error uploading files!", error); console.error("Error uploading files!", error);
toast.error("Failed to upload one or more files. Please try again.");
} finally { } finally {
setUploadQueue([]); setUploadQueue([]);
event.target.value = ''; event.target.value = '';
} }
}, [setAttachments]); }, [attachments, setAttachments]);
const removeAttachment = (index: number) => { const removeAttachment = (index: number) => {
setAttachments(prev => prev.filter((_, i) => i !== index)); setAttachments(prev => prev.filter((_, i) => i !== index));
@ -281,56 +383,53 @@ const FormComponent: React.FC<FormComponentProps> = ({
if (input.trim() || attachments.length > 0) { if (input.trim() || attachments.length > 0) {
setHasSubmitted(true); setHasSubmitted(true);
track("search input", {query: input.trim()})
handleSubmit(event, { handleSubmit(event, {
experimental_attachments: attachments, experimental_attachments: attachments,
}); });
setAttachments([]); setAttachments([]);
setUploadingAttachments([]);
if (fileInputRef.current) { if (fileInputRef.current) {
fileInputRef.current.value = ''; fileInputRef.current.value = '';
} }
} else { } else {
toast.error("Please enter a search query or attach an image."); toast.error("Please enter a search query or attach an image.");
} }
}, [input, attachments, setHasSubmitted, handleSubmit, setAttachments, setUploadingAttachments, fileInputRef]); }, [input, attachments, setHasSubmitted, handleSubmit, setAttachments, fileInputRef]);
const submitForm = useCallback(() => { const submitForm = useCallback(() => {
onSubmit({ preventDefault: () => { }, stopPropagation: () => { } } as React.FormEvent<HTMLFormElement>); onSubmit({ preventDefault: () => { }, stopPropagation: () => { } } as React.FormEvent<HTMLFormElement>);
resetSuggestedQuestions();
if (width && width > 768) { if (width && width > 768) {
inputRef.current?.focus(); inputRef.current?.focus();
} }
}, [onSubmit, width, inputRef]); }, [onSubmit, resetSuggestedQuestions, width, inputRef]);
const triggerFileInput = useCallback(() => { const triggerFileInput = useCallback(() => {
if (attachments.length >= MAX_IMAGES) {
toast.error(`You can only attach up to ${MAX_IMAGES} images.`);
return;
}
if (hasSubmitted) { if (hasSubmitted) {
postSubmitFileInputRef.current?.click(); postSubmitFileInputRef.current?.click();
} else { } else {
fileInputRef.current?.click(); fileInputRef.current?.click();
} }
}, [hasSubmitted, fileInputRef]); }, [attachments.length, hasSubmitted, fileInputRef]);
return ( return (
<div className="relative w-full flex flex-col gap-4"> <div className={cn(
<input "relative w-full flex flex-col gap-2 rounded-lg transition-all duration-300",
type="file" attachments.length > 0 || uploadQueue.length > 0
className="hidden" ? "bg-gray-100/70 dark:bg-neutral-800 p-1"
ref={fileInputRef} : "bg-transparent"
multiple )}>
onChange={handleFileChange} <input type="file" className="hidden" ref={fileInputRef} multiple onChange={handleFileChange} tabIndex={-1} />
tabIndex={-1} <input type="file" className="hidden" ref={postSubmitFileInputRef} multiple onChange={handleFileChange} tabIndex={-1} />
/>
<input
type="file"
className="hidden"
ref={postSubmitFileInputRef}
multiple
onChange={handleFileChange}
tabIndex={-1}
/>
{(attachments.length > 0 || uploadQueue.length > 0) && ( {(attachments.length > 0 || uploadQueue.length > 0) && (
<div className="flex flex-row gap-2 overflow-x-auto py-2 max-h-32 z-10"> <div className="flex flex-row gap-2 overflow-x-auto py-2 max-h-32 z-10">
@ -342,7 +441,6 @@ const FormComponent: React.FC<FormComponentProps> = ({
isUploading={false} isUploading={false}
/> />
))} ))}
{uploadQueue.map((filename) => ( {uploadQueue.map((filename) => (
<AttachmentPreview <AttachmentPreview
key={filename} key={filename}
@ -359,60 +457,80 @@ const FormComponent: React.FC<FormComponentProps> = ({
</div> </div>
)} )}
<Textarea <div className="relative">
ref={inputRef} <Textarea
placeholder={hasSubmitted ? "Ask a new question..." : "Ask a question..."} ref={inputRef}
value={input} placeholder={hasSubmitted ? "Ask a new question..." : "Ask a question..."}
onChange={handleInput} value={input}
className="min-h-[24px] overflow-hidden resize-none rounded-lg text-base bg-muted" onChange={handleInput}
rows={3} disabled={isLoading}
onKeyDown={(event) => { className={cn(
if (event.key === "Enter" && !event.shiftKey) { "min-h-[48px] overflow-hidden resize-none rounded-lg text-base",
event.preventDefault(); "bg-neutral-100 dark:bg-neutral-900",
"text-neutral-900 dark:text-neutral-100",
if (isLoading) { "border border-neutral-200 dark:border-neutral-700",
toast.error("Please wait for the model to finish its response!"); "focus:border-neutral-300 dark:focus:border-neutral-600",
} else { "focus:ring-2 focus:ring-neutral-300 dark:focus:ring-neutral-600",
submitForm(); "pr-20 py-2"
)}
rows={3}
onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
if (isLoading) {
toast.error("Please wait for the model to finish its response!");
} else {
submitForm();
}
} }
}
}}
/>
{isLoading ? (
<Button
className="rounded-full p-1.5 h-fit absolute bottom-2 right-2 m-0.5"
onClick={(event) => {
event.preventDefault();
stop();
}} }}
> />
<StopIcon size={14} />
</Button>
) : (
<Button
className="rounded-full p-1.5 h-fit absolute bottom-2 right-2 m-0.5"
onClick={(event) => {
event.preventDefault();
submitForm();
}}
disabled={input.length === 0 && attachments.length === 0 || uploadQueue.length > 0}
>
<ArrowUpIcon size={14} />
</Button>
)}
<Button <div className="absolute left-2 bottom-2">
className="rounded-full p-1.5 h-fit absolute bottom-2 right-10 m-0.5 dark:border-zinc-700" <ModelSwitcher
onClick={(event) => { selectedModel={selectedModel}
event.preventDefault(); setSelectedModel={setSelectedModel}
triggerFileInput(); />
}} </div>
variant="outline"
disabled={isLoading} <div className="absolute right-2 bottom-2 flex items-center gap-2">
> <Button
<PaperclipIcon size={14} /> className="rounded-full p-1.5 h-8 w-8 bg-white dark:bg-neutral-700 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-300 dark:hover:bg-neutral-600"
</Button> onClick={(event) => {
event.preventDefault();
triggerFileInput();
}}
variant="outline"
disabled={isLoading}
>
<PaperclipIcon size={14} />
</Button>
{isLoading ? (
<Button
className="rounded-full p-1.5 h-8 w-8"
onClick={(event) => {
event.preventDefault();
stop();
}}
variant="destructive"
>
<StopIcon size={14} />
</Button>
) : (
<Button
className="rounded-full p-1.5 h-8 w-8 "
onClick={(event) => {
event.preventDefault();
submitForm();
}}
disabled={input.length === 0 && attachments.length === 0 || uploadQueue.length > 0}
>
<ArrowUpIcon size={14} />
</Button>
)}
</div>
</div>
</div> </div>
); );
}; };