add in firecrawl search
This commit is contained in:
+104
-17
@@ -8,6 +8,13 @@ interface HeroInputProps {
|
||||
onSubmit: () => void;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
showSearchFeatures?: boolean;
|
||||
}
|
||||
|
||||
function isURL(str: string): boolean {
|
||||
// Check if string contains a dot and looks like a URL
|
||||
const urlPattern = /^(https?:\/\/)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(\/.*)?$/;
|
||||
return urlPattern.test(str.trim());
|
||||
}
|
||||
|
||||
export default function HeroInput({
|
||||
@@ -15,10 +22,13 @@ export default function HeroInput({
|
||||
onChange,
|
||||
onSubmit,
|
||||
placeholder = "Describe what you want to build...",
|
||||
className = ""
|
||||
className = "",
|
||||
showSearchFeatures = true
|
||||
}: HeroInputProps) {
|
||||
// const [isFocused, setIsFocused] = useState(false); // Reserved for future focus effects
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [showTiles, setShowTiles] = useState(false);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const isURLInput = showSearchFeatures ? isURL(value) : false;
|
||||
|
||||
// Reset textarea height when value changes (especially when cleared)
|
||||
useEffect(() => {
|
||||
@@ -26,7 +36,14 @@ export default function HeroInput({
|
||||
textareaRef.current.style.height = 'auto';
|
||||
textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px';
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
// Show tiles animation for search terms (only if search features are enabled)
|
||||
if (showSearchFeatures && value.trim() && !isURL(value) && isFocused) {
|
||||
setShowTiles(true);
|
||||
} else {
|
||||
setShowTiles(false);
|
||||
}
|
||||
}, [value, isFocused]);
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
@@ -44,20 +61,52 @@ export default function HeroInput({
|
||||
<div className="relative">
|
||||
<label className="p-16 flex gap-8 items-start w-full relative border-b border-black-alpha-5">
|
||||
<div className="mt-2 flex-shrink-0">
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="opacity-40"
|
||||
>
|
||||
<circle cx="10" cy="10" r="9.5" stroke="currentColor"/>
|
||||
<path d="M10 2C10 5.5 10 14.5 10 18" stroke="currentColor" strokeLinecap="round"/>
|
||||
<path d="M2 10C5.5 10 14.5 10 18 10" stroke="currentColor" strokeLinecap="round"/>
|
||||
<ellipse cx="10" cy="10" rx="3.5" ry="9.5" stroke="currentColor"/>
|
||||
<ellipse cx="10" cy="10" rx="6" ry="9.5" stroke="currentColor"/>
|
||||
</svg>
|
||||
{showSearchFeatures ? (
|
||||
isURLInput ? (
|
||||
// Link icon for URLs
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="opacity-40"
|
||||
>
|
||||
<path d="M9 11L11 9M11 9L15 5M11 9L5 15M15 5L13 3M15 5L17 7" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path d="M7 13L5 15L3 13" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path d="M13 7L15 5L17 7" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
) : (
|
||||
// Search icon for search terms
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="opacity-40"
|
||||
>
|
||||
<circle cx="8.5" cy="8.5" r="5.5" stroke="currentColor" strokeWidth="1.5"/>
|
||||
<path d="M12.5 12.5L16.5 16.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/>
|
||||
</svg>
|
||||
)
|
||||
) : (
|
||||
// Default globe icon for generation page
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="opacity-40"
|
||||
>
|
||||
<circle cx="10" cy="10" r="9.5" stroke="currentColor"/>
|
||||
<path d="M10 2C10 5.5 10 14.5 10 18" stroke="currentColor" strokeLinecap="round"/>
|
||||
<path d="M2 10C5.5 10 14.5 10 18 10" stroke="currentColor" strokeLinecap="round"/>
|
||||
<ellipse cx="10" cy="10" rx="3.5" ry="9.5" stroke="currentColor"/>
|
||||
<ellipse cx="10" cy="10" rx="6" ry="9.5" stroke="currentColor"/>
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
@@ -115,6 +164,44 @@ export default function HeroInput({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Animated tiles for search results */}
|
||||
{showTiles && (
|
||||
<div className="mt-16 grid grid-cols-3 gap-12 px-16">
|
||||
{[0, 1, 2].map((index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="tile-animation relative aspect-[4/3] bg-black-alpha-4 rounded-12 overflow-hidden"
|
||||
style={{
|
||||
animationDelay: `${index * 100}ms`
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-black-alpha-4 to-transparent" />
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="w-8 h-8 bg-black-alpha-8 rounded-full animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<style jsx>{`
|
||||
@keyframes tileSlideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.tile-animation {
|
||||
animation: tileSlideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,11 +6,20 @@ import Button from "@/components/shared/button/Button";
|
||||
|
||||
export default function HeroInputSubmitButton({
|
||||
dirty,
|
||||
buttonText = "Re-imagine Site",
|
||||
disabled = false,
|
||||
}: {
|
||||
dirty: boolean;
|
||||
buttonText?: string;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<Button className="hero-input-button !p-0 bg-heat-100 hover:bg-heat-200" size="large" variant="primary">
|
||||
<Button
|
||||
className={`hero-input-button !p-0 ${disabled ? 'bg-gray-400 hover:bg-gray-400 cursor-wait' : 'bg-heat-100 hover:bg-heat-200'}`}
|
||||
size="large"
|
||||
variant="primary"
|
||||
disabled={disabled}
|
||||
>
|
||||
<AnimatedWidth>
|
||||
<AnimatePresence initial={false} mode="popLayout">
|
||||
<motion.div
|
||||
@@ -20,7 +29,9 @@ export default function HeroInputSubmitButton({
|
||||
key={dirty ? "dirty" : "clean"}
|
||||
>
|
||||
{dirty ? (
|
||||
<div className="py-8 w-126 text-center text-white">Re-imagine Site</div>
|
||||
<div className="py-8 w-126 text-center text-white">
|
||||
{buttonText}
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-60 py-8 flex-center">
|
||||
<ArrowRight />
|
||||
|
||||
@@ -59,10 +59,20 @@ export default function SidebarInput({ onSubmit, disabled = false }: SidebarInpu
|
||||
<div className="p-4 border-b border-gray-100">
|
||||
{/* URL Input */}
|
||||
<div className="flex gap-3 items-center">
|
||||
<Globe />
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="opacity-40 flex-shrink-0"
|
||||
>
|
||||
<rect x="3" y="3" width="14" height="14" rx="2" stroke="currentColor" strokeWidth="1.5"/>
|
||||
<path d="M7 10L9 12L13 8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
<input
|
||||
className="flex-1 bg-transparent text-sm text-gray-900 placeholder:text-gray-400 focus:outline-none"
|
||||
placeholder="example.com"
|
||||
placeholder="Enter URL to scrape..."
|
||||
type="text"
|
||||
value={url}
|
||||
disabled={disabled}
|
||||
@@ -150,7 +160,7 @@ export default function SidebarInput({ onSubmit, disabled = false }: SidebarInpu
|
||||
}
|
||||
`}
|
||||
>
|
||||
{disabled ? 'Generating...' : 'Generate Website'}
|
||||
{disabled ? 'Scraping...' : 'Scrape Site'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user