Domify-Academy-Bot / pasted_content.txt
Domify's picture
Upload 35 files
93c19dc verified
Hello menu i got task for you i ant to improve the UI and nature of my bot on site i use api key from hugging face and some free model there but i got a free key from Nvidia that has many models here are prompt from 21dev don't code yet i got some things to show you first You are given a task to integrate an existing React component in the codebase
The codebase should support:
- shadcn project structure
- Tailwind CSS
- Typescript
If it doesn't, provide instructions on how to setup project via shadcn CLI, install Tailwind or Typescript.
Determine the default path for components and styles.
If default path for components is not /components/ui, provide instructions on why it's important to create this folder
Copy-paste this component to /components/ui folder:
```tsx
ai-prompt-box.tsx
import React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { ArrowUp, Paperclip, Square, X, StopCircle, Mic, Globe, BrainCog, FolderCode } from "lucide-react";
import { motion, AnimatePresence } from "framer-motion";
// Utility function for className merging
const cn = (...classes: (string | undefined | null | false)[]) => classes.filter(Boolean).join(" ");
// Embedded CSS for minimal custom styles
const styles = `
*:focus-visible {
outline-offset: 0 !important;
--ring-offset: 0 !important;
}
textarea::-webkit-scrollbar {
width: 6px;
}
textarea::-webkit-scrollbar-track {
background: transparent;
}
textarea::-webkit-scrollbar-thumb {
background-color: #444444;
border-radius: 3px;
}
textarea::-webkit-scrollbar-thumb:hover {
background-color: #555555;
}
`;
// Inject styles into document
const styleSheet = document.createElement("style");
styleSheet.innerText = styles;
document.head.appendChild(styleSheet);
// Textarea Component
interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
className?: string;
}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(({ className, ...props }, ref) => (
<textarea
className={cn(
"flex w-full rounded-md border-none bg-transparent px-3 py-2.5 text-base text-gray-100 placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50 min-h-[44px] resize-none scrollbar-thin scrollbar-thumb-[#444444] scrollbar-track-transparent hover:scrollbar-thumb-[#555555]",
className
)}
ref={ref}
rows={1}
{...props}
/>
));
Textarea.displayName = "Textarea";
// Tooltip Components
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border border-[#333333] bg-[#1F2023] px-3 py-1.5 text-sm text-white shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
// Dialog Components
const Dialog = DialogPrimitive.Root;
const DialogPortal = DialogPrimitive.Portal;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/60 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-[90vw] md:max-w-[800px] translate-x-[-50%] translate-y-[-50%] gap-4 border border-[#333333] bg-[#1F2023] p-0 shadow-xl duration-300 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 rounded-2xl",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 z-10 rounded-full bg-[#2E3033]/80 p-2 hover:bg-[#2E3033] transition-all">
<X className="h-5 w-5 text-gray-200 hover:text-white" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold leading-none tracking-tight text-gray-100", className)}
{...props}
/>
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;
// Button Component
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "default" | "outline" | "ghost";
size?: "default" | "sm" | "lg" | "icon";
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = "default", size = "default", ...props }, ref) => {
const variantClasses = {
default: "bg-white hover:bg-white/80 text-black",
outline: "border border-[#444444] bg-transparent hover:bg-[#3A3A40]",
ghost: "bg-transparent hover:bg-[#3A3A40]",
};
const sizeClasses = {
default: "h-10 px-4 py-2",
sm: "h-8 px-3 text-sm",
lg: "h-12 px-6",
icon: "h-8 w-8 rounded-full aspect-[1/1]",
};
return (
<button
className={cn(
"inline-flex items-center justify-center font-medium transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50",
variantClasses[variant],
sizeClasses[size],
className
)}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = "Button";
// VoiceRecorder Component
interface VoiceRecorderProps {
isRecording: boolean;
onStartRecording: () => void;
onStopRecording: (duration: number) => void;
visualizerBars?: number;
}
const VoiceRecorder: React.FC<VoiceRecorderProps> = ({
isRecording,
onStartRecording,
onStopRecording,
visualizerBars = 32,
}) => {
const [time, setTime] = React.useState(0);
const timerRef = React.useRef<NodeJS.Timeout | null>(null);
React.useEffect(() => {
if (isRecording) {
onStartRecording();
timerRef.current = setInterval(() => setTime((t) => t + 1), 1000);
} else {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
onStopRecording(time);
setTime(0);
}
return () => {
if (timerRef.current) clearInterval(timerRef.current);
};
}, [isRecording, time, onStartRecording, onStopRecording]);
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
};
return (
<div
className={cn(
"flex flex-col items-center justify-center w-full transition-all duration-300 py-3",
isRecording ? "opacity-100" : "opacity-0 h-0"
)}
>
<div className="flex items-center gap-2 mb-3">
<div className="h-2 w-2 rounded-full bg-red-500 animate-pulse" />
<span className="font-mono text-sm text-white/80">{formatTime(time)}</span>
</div>
<div className="w-full h-10 flex items-center justify-center gap-0.5 px-4">
{[...Array(visualizerBars)].map((_, i) => (
<div
key={i}
className="w-0.5 rounded-full bg-white/50 animate-pulse"
style={{
height: `${Math.max(15, Math.random() * 100)}%`,
animationDelay: `${i * 0.05}s`,
animationDuration: `${0.5 + Math.random() * 0.5}s`,
}}
/>
))}
</div>
</div>
);
};
// ImageViewDialog Component
interface ImageViewDialogProps {
imageUrl: string | null;
onClose: () => void;
}
const ImageViewDialog: React.FC<ImageViewDialogProps> = ({ imageUrl, onClose }) => {
if (!imageUrl) return null;
return (
<Dialog open={!!imageUrl} onOpenChange={onClose}>
<DialogContent className="p-0 border-none bg-transparent shadow-none max-w-[90vw] md:max-w-[800px]">
<DialogTitle className="sr-only">Image Preview</DialogTitle>
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2, ease: "easeOut" }}
className="relative bg-[#1F2023] rounded-2xl overflow-hidden shadow-2xl"
>
<img
src={imageUrl}
alt="Full preview"
className="w-full max-h-[80vh] object-contain rounded-2xl"
/>
</motion.div>
</DialogContent>
</Dialog>
);
};
// PromptInput Context and Components
interface PromptInputContextType {
isLoading: boolean;
value: string;
setValue: (value: string) => void;
maxHeight: number | string;
onSubmit?: () => void;
disabled?: boolean;
}
const PromptInputContext = React.createContext<PromptInputContextType>({
isLoading: false,
value: "",
setValue: () => {},
maxHeight: 240,
onSubmit: undefined,
disabled: false,
});
function usePromptInput() {
const context = React.useContext(PromptInputContext);
if (!context) throw new Error("usePromptInput must be used within a PromptInput");
return context;
}
interface PromptInputProps {
isLoading?: boolean;
value?: string;
onValueChange?: (value: string) => void;
maxHeight?: number | string;
onSubmit?: () => void;
children: React.ReactNode;
className?: string;
disabled?: boolean;
onDragOver?: (e: React.DragEvent) => void;
onDragLeave?: (e: React.DragEvent) => void;
onDrop?: (e: React.DragEvent) => void;
}
const PromptInput = React.forwardRef<HTMLDivElement, PromptInputProps>(
(
{
className,
isLoading = false,
maxHeight = 240,
value,
onValueChange,
onSubmit,
children,
disabled = false,
onDragOver,
onDragLeave,
onDrop,
},
ref
) => {
const [internalValue, setInternalValue] = React.useState(value || "");
const handleChange = (newValue: string) => {
setInternalValue(newValue);
onValueChange?.(newValue);
};
return (
<TooltipProvider>
<PromptInputContext.Provider
value={{
isLoading,
value: value ?? internalValue,
setValue: onValueChange ?? handleChange,
maxHeight,
onSubmit,
disabled,
}}
>
<div
ref={ref}
className={cn(
"rounded-3xl border border-[#444444] bg-[#1F2023] p-2 shadow-[0_8px_30px_rgba(0,0,0,0.24)] transition-all duration-300",
isLoading && "border-red-500/70",
className
)}
onDragOver={onDragOver}
onDragLeave={onDragLeave}
onDrop={onDrop}
>
{children}
</div>
</PromptInputContext.Provider>
</TooltipProvider>
);
}
);
PromptInput.displayName = "PromptInput";
interface PromptInputTextareaProps {
disableAutosize?: boolean;
placeholder?: string;
}
const PromptInputTextarea: React.FC<PromptInputTextareaProps & React.ComponentProps<typeof Textarea>> = ({
className,
onKeyDown,
disableAutosize = false,
placeholder,
...props
}) => {
const { value, setValue, maxHeight, onSubmit, disabled } = usePromptInput();
const textareaRef = React.useRef<HTMLTextAreaElement>(null);
React.useEffect(() => {
if (disableAutosize || !textareaRef.current) return;
textareaRef.current.style.height = "auto";
textareaRef.current.style.height =
typeof maxHeight === "number"
? `${Math.min(textareaRef.current.scrollHeight, maxHeight)}px`
: `min(${textareaRef.current.scrollHeight}px, ${maxHeight})`;
}, [value, maxHeight, disableAutosize]);
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
onSubmit?.();
}
onKeyDown?.(e);
};
return (
<Textarea
ref={textareaRef}
value={value}
onChange={(e) => setValue(e.target.value)}
onKeyDown={handleKeyDown}
className={cn("text-base", className)}
disabled={disabled}
placeholder={placeholder}
{...props}
/>
);
};
interface PromptInputActionsProps extends React.HTMLAttributes<HTMLDivElement> {}
const PromptInputActions: React.FC<PromptInputActionsProps> = ({ children, className, ...props }) => (
<div className={cn("flex items-center gap-2", className)} {...props}>
{children}
</div>
);
interface PromptInputActionProps extends React.ComponentProps<typeof Tooltip> {
tooltip: React.ReactNode;
children: React.ReactNode;
side?: "top" | "bottom" | "left" | "right";
}
const PromptInputAction: React.FC<PromptInputActionProps> = ({
tooltip,
children,
className,
side = "top",
...props
}) => {
const { disabled } = usePromptInput();
return (
<Tooltip {...props}>
<TooltipTrigger asChild disabled={disabled}>
{children}
</TooltipTrigger>
<TooltipContent side={side} className={className}>
{tooltip}
</TooltipContent>
</Tooltip>
);
};
// Custom Divider Component
const CustomDivider: React.FC = () => (
<div className="relative h-6 w-[1.5px] mx-1">
<div
className="absolute inset-0 bg-gradient-to-t from-transparent via-[#9b87f5]/70 to-transparent rounded-full"
style={{
clipPath: "polygon(0% 0%, 100% 0%, 100% 40%, 140% 50%, 100% 60%, 100% 100%, 0% 100%, 0% 60%, -40% 50%, 0% 40%)",
}}
/>
</div>
);
// Main PromptInputBox Component
interface PromptInputBoxProps {
onSend?: (message: string, files?: File[]) => void;
isLoading?: boolean;
placeholder?: string;
className?: string;
}
export const PromptInputBox = React.forwardRef((props: PromptInputBoxProps, ref: React.Ref<HTMLDivElement>) => {
const { onSend = () => {}, isLoading = false, placeholder = "Type your message here...", className } = props;
const [input, setInput] = React.useState("");
const [files, setFiles] = React.useState<File[]>([]);
const [filePreviews, setFilePreviews] = React.useState<{ [key: string]: string }>({});
const [selectedImage, setSelectedImage] = React.useState<string | null>(null);
const [isRecording, setIsRecording] = React.useState(false);
const [showSearch, setShowSearch] = React.useState(false);
const [showThink, setShowThink] = React.useState(false);
const [showCanvas, setShowCanvas] = React.useState(false);
const uploadInputRef = React.useRef<HTMLInputElement>(null);
const promptBoxRef = React.useRef<HTMLDivElement>(null);
const handleToggleChange = (value: string) => {
if (value === "search") {
setShowSearch((prev) => !prev);
setShowThink(false);
} else if (value === "think") {
setShowThink((prev) => !prev);
setShowSearch(false);
}
};
const handleCanvasToggle = () => setShowCanvas((prev) => !prev);
const isImageFile = (file: File) => file.type.startsWith("image/");
const processFile = (file: File) => {
if (!isImageFile(file)) {
console.log("Only image files are allowed");
return;
}
if (file.size > 10 * 1024 * 1024) {
console.log("File too large (max 10MB)");
return;
}
setFiles([file]);
const reader = new FileReader();
reader.onload = (e) => setFilePreviews({ [file.name]: e.target?.result as string });
reader.readAsDataURL(file);
};
const handleDragOver = React.useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
}, []);
const handleDragLeave = React.useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
}, []);
const handleDrop = React.useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
const files = Array.from(e.dataTransfer.files);
const imageFiles = files.filter((file) => isImageFile(file));
if (imageFiles.length > 0) processFile(imageFiles[0]);
}, []);
const handleRemoveFile = (index: number) => {
const fileToRemove = files[index];
if (fileToRemove && filePreviews[fileToRemove.name]) setFilePreviews({});
setFiles([]);
};
const openImageModal = (imageUrl: string) => setSelectedImage(imageUrl);
const handlePaste = React.useCallback((e: ClipboardEvent) => {
const items = e.clipboardData?.items;
if (!items) return;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf("image") !== -1) {
const file = items[i].getAsFile();
if (file) {
e.preventDefault();
processFile(file);
break;
}
}
}
}, []);
React.useEffect(() => {
document.addEventListener("paste", handlePaste);
return () => document.removeEventListener("paste", handlePaste);
}, [handlePaste]);
const handleSubmit = () => {
if (input.trim() || files.length > 0) {
let messagePrefix = "";
if (showSearch) messagePrefix = "[Search: ";
else if (showThink) messagePrefix = "[Think: ";
else if (showCanvas) messagePrefix = "[Canvas: ";
const formattedInput = messagePrefix ? `${messagePrefix}${input}]` : input;
onSend(formattedInput, files);
setInput("");
setFiles([]);
setFilePreviews({});
}
};
const handleStartRecording = () => console.log("Started recording");
const handleStopRecording = (duration: number) => {
console.log(`Stopped recording after ${duration} seconds`);
setIsRecording(false);
onSend(`[Voice message - ${duration} seconds]`, []);
};
const hasContent = input.trim() !== "" || files.length > 0;
return (
<>
<PromptInput
value={input}
onValueChange={setInput}
isLoading={isLoading}
onSubmit={handleSubmit}
className={cn(
"w-full bg-[#1F2023] border-[#444444] shadow-[0_8px_30px_rgba(0,0,0,0.24)] transition-all duration-300 ease-in-out",
isRecording && "border-red-500/70",
className
)}
disabled={isLoading || isRecording}
ref={ref || promptBoxRef}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
{files.length > 0 && !isRecording && (
<div className="flex flex-wrap gap-2 p-0 pb-1 tYou are given a task to integrate an existing React component in the codebase
The codebase should support:
- shadcn project structure
- Tailwind CSS
- Typescript
If it doesn't, provide instructions on how to setup project via shadcn CLI, install Tailwind or Typescript.
Determine the default path for components and styles.
If default path for components is not /components/ui, provide instructions on why it's important to create this folder
Copy-paste this component to /components/ui folder:
```tsx
animated-ai-chat.tsx
"use client";
import { useEffect, useRef, useCallback, useTransition } from "react";
import { useState } from "react";
import { cn } from "@/lib/utils";
import {
ImageIcon,
FileUp,
Figma,
MonitorIcon,
CircleUserRound,
ArrowUpIcon,
Paperclip,
PlusIcon,
SendIcon,
XIcon,
LoaderIcon,
Sparkles,
Command,
} from "lucide-react";
import { motion, AnimatePresence } from "framer-motion";
import * as React from "react"
interface UseAutoResizeTextareaProps {
minHeight: number;
maxHeight?: number;
}
function useAutoResizeTextarea({
minHeight,
maxHeight,
}: UseAutoResizeTextareaProps) {
const textareaRef = useRef<HTMLTextAreaElement>(null);
const adjustHeight = useCallback(
(reset?: boolean) => {
const textarea = textareaRef.current;
if (!textarea) return;
if (reset) {
textarea.style.height = `${minHeight}px`;
return;
}
textarea.style.height = `${minHeight}px`;
const newHeight = Math.max(
minHeight,
Math.min(
textarea.scrollHeight,
maxHeight ?? Number.POSITIVE_INFINITY
)
);
textarea.style.height = `${newHeight}px`;
},
[minHeight, maxHeight]
);
useEffect(() => {
const textarea = textareaRef.current;
if (textarea) {
textarea.style.height = `${minHeight}px`;
}
}, [minHeight]);
useEffect(() => {
const handleResize = () => adjustHeight();
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, [adjustHeight]);
return { textareaRef, adjustHeight };
}
interface CommandSuggestion {
icon: React.ReactNode;
label: string;
description: string;
prefix: string;
}
interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
containerClassName?: string;
showRing?: boolean;
}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, containerClassName, showRing = true, ...props }, ref) => {
const [isFocused, setIsFocused] = React.useState(false);
return (
<div className={cn(
"relative",
containerClassName
)}>
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
"transition-all duration-200 ease-in-out",
"placeholder:text-muted-foreground",
"disabled:cursor-not-allowed disabled:opacity-50",
showRing ? "focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0" : "",
className
)}
ref={ref}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
{...props}
/>
{showRing && isFocused && (
<motion.span
className="absolute inset-0 rounded-md pointer-events-none ring-2 ring-offset-0 ring-violet-500/30"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
/>
)}
{props.onChange && (
<div
className="absolute bottom-2 right-2 opacity-0 w-2 h-2 bg-violet-500 rounded-full"
style={{
animation: 'none',
}}
id="textarea-ripple"
/>
)}
</div>
)
}
)
Textarea.displayName = "Textarea"
export function AnimatedAIChat() {
const [value, setValue] = useState("");
const [attachments, setAttachments] = useState<string[]>([]);
const [isTyping, setIsTyping] = useState(false);
const [isPending, startTransition] = useTransition();
const [activeSuggestion, setActiveSuggestion] = useState<number>(-1);
const [showCommandPalette, setShowCommandPalette] = useState(false);
const [recentCommand, setRecentCommand] = useState<string | null>(null);
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
const { textareaRef, adjustHeight } = useAutoResizeTextarea({
minHeight: 60,
maxHeight: 200,
});
const [inputFocused, setInputFocused] = useState(false);
const commandPaletteRef = useRef<HTMLDivElement>(null);
const commandSuggestions: CommandSuggestion[] = [
{
icon: <ImageIcon className="w-4 h-4" />,
label: "Clone UI",
description: "Generate a UI from a screenshot",
prefix: "/clone"
},
{
icon: <Figma className="w-4 h-4" />,
label: "Import Figma",
description: "Import a design from Figma",
prefix: "/figma"
},
{
icon: <MonitorIcon className="w-4 h-4" />,
label: "Create Page",
description: "Generate a new web page",
prefix: "/page"
},
{
icon: <Sparkles className="w-4 h-4" />,
label: "Improve",
description: "Improve existing UI design",
prefix: "/improve"
},
];
useEffect(() => {
if (value.startsWith('/') && !value.includes(' ')) {
setShowCommandPalette(true);
const matchingSuggestionIndex = commandSuggestions.findIndex(
(cmd) => cmd.prefix.startsWith(value)
);
if (matchingSuggestionIndex >= 0) {
setActiveSuggestion(matchingSuggestionIndex);
} else {
setActiveSuggestion(-1);
}
} else {
setShowCommandPalette(false);
}
}, [value]);
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
setMousePosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
const target = event.target as Node;
const commandButton = document.querySelector('[data-command-button]');
if (commandPaletteRef.current &&
!commandPaletteRef.current.contains(target) &&
!commandButton?.contains(target)) {
setShowCommandPalette(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (showCommandPalette) {
if (e.key === 'ArrowDown') {
e.preventDefault();
setActiveSuggestion(prev =>
prev < commandSuggestions.length - 1 ? prev + 1 : 0
);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setActiveSuggestion(prev =>
prev > 0 ? prev - 1 : commandSuggestions.length - 1
);
} else if (e.key === 'Tab' || e.key === 'Enter') {
e.preventDefault();
if (activeSuggestion >= 0) {
const selectedCommand = commandSuggestions[activeSuggestion];
setValue(selectedCommand.prefix + ' ');
setShowCommandPalette(false);
setRecentCommand(selectedCommand.label);
setTimeout(() => setRecentCommand(null), 3500);
}
} else if (e.key === 'Escape') {
e.preventDefault();
setShowCommandPalette(false);
}
} else if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
if (value.trim()) {
handleSendMessage();
}
}
};
const handleSendMessage = () => {
if (value.trim()) {
startTransition(() => {
setIsTyping(true);
setTimeout(() => {
setIsTyping(false);
setValue("");
adjustHeight(true);
}, 3000);
});
}
};
const handleAttachFile = () => {
const mockFileName = `file-${Math.floor(Math.random() * 1000)}.pdf`;
setAttachments(prev => [...prev, mockFileName]);
};
const removeAttachment = (index: number) => {
setAttachments(prev => prev.filter((_, i) => i !== index));
};
const selectCommandSuggestion = (index: number) => {
const selectedCommand = commandSuggestions[index];
setValue(selectedCommand.prefix + ' ');
setShowCommandPalette(false);
setRecentCommand(selectedCommand.label);
setTimeout(() => setRecentCommand(null), 2000);
};
return (
<div className="min-h-screen flex flex-col w-full items-center justify-center bg-transparent text-white p-6 relative overflow-hidden">
<div className="absolute inset-0 w-full h-full overflow-hidden">
<div className="absolute top-0 left-1/4 w-96 h-96 bg-violet-500/10 rounded-full mix-blend-normal filter blur-[128px] animate-pulse" />
<div className="absolute bottom-0 right-1/4 w-96 h-96 bg-indigo-500/10 rounded-full mix-blend-normal filter blur-[128px] animate-pulse delay-700" />
<div className="absolute top-1/4 right-1/3 w-64 h-64 bg-fuchsia-500/10 rounded-full mix-blend-normal filter blur-[96px] animate-pulse delay-1000" />
</div>
<div className="w-full max-w-2xl mx-auto relative">
<motion.div
className="relative z-10 space-y-12"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: "easeOut" }}
>
<div className="text-center space-y-3">
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2, duration: 0.5 }}
className="inline-block"
>
<h1 className="text-3xl font-medium tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-white/90 to-white/40 pb-1">
How can I help today?
</h1>
<motion.div
className="h-px bg-gradient-to-r from-transparent via-white/20 to-transparent"
initial={{ width: 0, opacity: 0 }}
animate={{ width: "100%", opacity: 1 }}
transition={{ delay: 0.5, duration: 0.8 }}
/>
</motion.div>
<motion.p
className="text-sm text-white/40"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.3 }}
>
Type a command or ask a question
</motion.p>
</div>
<motion.div
className="relative backdrop-blur-2xl bg-white/[0.02] rounded-2xl border border-white/[0.05] shadow-2xl"
initial={{ scale: 0.98 }}
animate={{ scale: 1 }}
transition={{ delay: 0.1 }}
>
<AnimatePresence>
{showCommandPalette && (
<motion.div
ref={commandPaletteRef}
className="absolute left-4 right-4 bottom-full mb-2 backdrop-blur-xl bg-black/90 rounded-lg z-50 shadow-lg border border-white/10 overflow-hidden"
initial={{ opacity: 0, y: 5 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 5 }}
transition={{ duration: 0.15 }}
>
<div className="py-1 bg-black/95">
{commandSuggestions.map((suggestion, index) => (
<motion.div
key={suggestion.prefix}
className={cn(
"flex items-center gap-2 px-3 py-2 text-xs transition-colors cursor-pointer",
activeSuggestion === index
? "bg-white/10 text-white"
: "text-white/70 hover:bg-white/5"
)}
onClick={() => selectCommandSuggestion(index)}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: index * 0.03 }}
>
<div className="w-5 h-5 flex items-center justify-center text-white/60">
{suggestion.icon}
</div>
<div className="font-medium">{suggestion.label}</div>
<div className="text-white/40 text-xs ml-1">
{suggestion.prefix}
</div>
</motion.div>
))}
</div>
</motion.div>
)}
</AnimatePresence>
<div className="p-4">
<Textarea
ref={textareaRef}
value={value}
onChange={(e) => {
setValue(e.target.value);
adjustHeight();
}}
onKeyDown={handleKeyDown}
onFocus={() => setInputFocused(true)}
onBlur={() => setInputFocused(false)}
placeholder="Ask zap a question..."
containerClassName="w-full"
className={cn(
"w-full px-4 py-3",
"resize-none",
"bg-transparent",
"border-none",
"text-white/90 text-sm",
"focus:outline-none",
"placeholder:text-white/20",
"min-h-[60px]"
)}
style={{
overflow: "hidden",
}}
showRing={false}
/>
</div>
<AnimatePresence>
{attachments.length > 0 && (
<motion.div
className="px-4 pb-3 flex gap-2 flex-wrap"
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
>
{attachments.map((file, index) => (
<motion.div
key={index}
className="flex items-center gap-2 text-xs bg-white/[0.03] py-1.5 px-3 rounded-lg text-white/70"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
>
<span>{file}</span>
<button
onClick={() => removeAttachment(index)}
className="text-white/40 hover:text-white transition-colors"
>
<XIcon className="w-3 h-3" />
</button>
</motion.div>
))}
</motion.div>
)}
</AnimatePresence>
<div className="p-4 border-t border-white/[0.05] flex items-center justify-between gap-4">
<div className="flex items-center gap-3">
<motion.button
type="button"
onClick={handleAttachFile}
whileTap={{ scale: 0.94 }}
className="p-2 text-white/40 hover:text-white/90 rounded-lg transition-colors relative group"
>
<Paperclip className="w-4 h-4" />
<motion.span
className="absolute inset-0 bg-white/[0.05] rounded-lg opacity-0 group-hover:opacity-100 transition-opacity"
layoutId="button-highlight"
/>
</motion.button>
ransition-all duration-300">
{files.map((file, index) => (
<div key={index} className="relative group">
{file.type.startsWith("image/") && filePreviews[file.name] && (
<div
className="w-16 h-16 rounded-xl overflow-hidden cursor-pointer transition-all duration-300"
onClick={() => openImageModal(filePreviews[file.name])}
>
<img
src={filePreviews[file.name]}
alt={file.name}
className
That is two prompt