mirror of https://github.com/kortix-ai/suna.git
feat: new chat input on landing
This commit is contained in:
parent
10825990dd
commit
5b5204e364
|
@ -17,7 +17,6 @@ import { useThreadQuery } from '@/hooks/react-query/threads/use-threads';
|
||||||
import { generateThreadName } from '@/lib/actions/threads';
|
import { generateThreadName } from '@/lib/actions/threads';
|
||||||
import GoogleSignIn from '@/components/GoogleSignIn';
|
import GoogleSignIn from '@/components/GoogleSignIn';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { SubmitButton } from '@/components/ui/submit-button';
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
|
@ -32,6 +31,13 @@ import { useAccounts } from '@/hooks/use-accounts';
|
||||||
import { isLocalMode, config } from '@/lib/config';
|
import { isLocalMode, config } from '@/lib/config';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useModal } from '@/hooks/use-modal-store';
|
import { useModal } from '@/hooks/use-modal-store';
|
||||||
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Send, ArrowUp, Paperclip } from 'lucide-react';
|
||||||
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
|
import ChatDropdown from '@/components/thread/chat-input/chat-dropdown';
|
||||||
|
|
||||||
// Custom dialog overlay with blur effect
|
// Custom dialog overlay with blur effect
|
||||||
const BlurredDialogOverlay = () => (
|
const BlurredDialogOverlay = () => (
|
||||||
|
@ -63,7 +69,6 @@ export function HeroSection() {
|
||||||
|
|
||||||
// Auth dialog state
|
// Auth dialog state
|
||||||
const [authDialogOpen, setAuthDialogOpen] = useState(false);
|
const [authDialogOpen, setAuthDialogOpen] = useState(false);
|
||||||
const [authError, setAuthError] = useState<string | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
|
@ -174,37 +179,15 @@ export function HeroSection() {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle Enter key press
|
// Handle Enter key press
|
||||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {
|
||||||
e.preventDefault(); // Prevent default form submission
|
e.preventDefault(); // Prevent default form submission
|
||||||
e.stopPropagation(); // Stop event propagation
|
e.stopPropagation(); // Stop event propagation
|
||||||
handleSubmit();
|
handleSubmit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle auth form submission
|
|
||||||
const handleSignIn = async (prevState: any, formData: FormData) => {
|
|
||||||
setAuthError(null);
|
|
||||||
try {
|
|
||||||
// Implement sign in logic here
|
|
||||||
const email = formData.get('email') as string;
|
|
||||||
const password = formData.get('password') as string;
|
|
||||||
|
|
||||||
// Add the returnUrl to the form data for proper redirection
|
|
||||||
formData.append('returnUrl', '/dashboard');
|
|
||||||
|
|
||||||
// Call your authentication function here
|
|
||||||
|
|
||||||
// Return any error state
|
|
||||||
return { message: 'Invalid credentials' };
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Sign in error:', error);
|
|
||||||
setAuthError(
|
|
||||||
error instanceof Error ? error.message : 'An error occurred',
|
|
||||||
);
|
|
||||||
return { message: 'An error occurred during sign in' };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="hero" className="w-full relative overflow-hidden">
|
<section id="hero" className="w-full relative overflow-hidden">
|
||||||
|
@ -260,7 +243,7 @@ export function HeroSection() {
|
||||||
{hero.badge}
|
{hero.badge}
|
||||||
</p> */}
|
</p> */}
|
||||||
|
|
||||||
<Link
|
{/* <Link
|
||||||
href={hero.githubUrl}
|
href={hero.githubUrl}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
@ -288,8 +271,8 @@ export function HeroSection() {
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link> */}
|
||||||
<div className="flex flex-col items-center justify-center gap-5">
|
<div className="flex flex-col items-center justify-center gap-5 pt-16">
|
||||||
<h1 className="text-3xl md:text-4xl lg:text-5xl xl:text-6xl font-medium tracking-tighter text-balance text-center">
|
<h1 className="text-3xl md:text-4xl lg:text-5xl xl:text-6xl font-medium tracking-tighter text-balance text-center">
|
||||||
<span className="text-secondary">Suna</span>
|
<span className="text-secondary">Suna</span>
|
||||||
<span className="text-primary">, your AI Employee.</span>
|
<span className="text-primary">, your AI Employee.</span>
|
||||||
|
@ -298,37 +281,76 @@ export function HeroSection() {
|
||||||
{hero.description}
|
{hero.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center w-full max-w-xl gap-2 flex-wrap justify-center">
|
<div className="flex items-center w-full max-w-4xl gap-2 flex-wrap justify-center">
|
||||||
<form className="w-full relative" onSubmit={handleSubmit}>
|
<form className="w-full relative" onSubmit={handleSubmit}>
|
||||||
{/* ChatGPT-like input with glow effect */}
|
{/* Input that looks exactly like ChatInput */}
|
||||||
<div className="relative z-10">
|
<div className="relative z-10">
|
||||||
<div className="flex items-center rounded-full border border-border bg-background/80 backdrop-blur px-4 shadow-lg transition-all duration-200 hover:border-secondary/50 focus-within:border-secondary/50 focus-within:shadow-[0_0_15px_rgba(var(--secondary),0.3)]">
|
<Card className="shadow-none w-full max-w-8xl mx-auto bg-transparent border-none rounded-3xl overflow-hidden">
|
||||||
<input
|
<div className="w-full text-sm flex flex-col justify-between items-start rounded-2xl">
|
||||||
type="text"
|
<CardContent className="w-full p-1.5 pb-2 bg-sidebar rounded-3xl border">
|
||||||
value={inputValue}
|
<div className="relative flex flex-col w-full h-full gap-2 justify-between">
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
<div className="flex flex-col gap-1 px-2">
|
||||||
onKeyDown={handleKeyDown}
|
<Textarea
|
||||||
placeholder={hero.inputPlaceholder}
|
value={inputValue}
|
||||||
className="flex-1 h-12 md:h-14 rounded-full px-2 bg-transparent focus:outline-none text-sm md:text-base py-2"
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
disabled={isSubmitting}
|
onKeyDown={handleKeyDown}
|
||||||
/>
|
placeholder="Describe what you need help with..."
|
||||||
<button
|
className={cn(
|
||||||
type="submit"
|
'w-full bg-transparent dark:bg-transparent border-none shadow-none focus-visible:ring-0 px-2 pb-6 pt-4 !text-[15px] min-h-[36px] max-h-[200px] overflow-y-auto resize-none'
|
||||||
className={`rounded-full p-2 md:p-3 transition-all duration-200 ${
|
)}
|
||||||
inputValue.trim()
|
disabled={isSubmitting}
|
||||||
? 'bg-secondary text-secondary-foreground hover:bg-secondary/80'
|
rows={1}
|
||||||
: 'bg-muted text-muted-foreground'
|
/>
|
||||||
}`}
|
</div>
|
||||||
disabled={!inputValue.trim() || isSubmitting}
|
|
||||||
aria-label="Submit"
|
<div className="flex items-center justify-between mt-0 mb-1 px-2">
|
||||||
>
|
<div className="flex items-center gap-3">
|
||||||
{isSubmitting ? (
|
{/* Attach files button */}
|
||||||
<div className="h-4 md:h-5 w-4 md:w-5 border-2 border-secondary-foreground border-t-transparent rounded-full animate-spin" />
|
<TooltipProvider>
|
||||||
) : (
|
<Tooltip>
|
||||||
<ArrowRight className="size-4 md:size-5" />
|
<TooltipTrigger asChild>
|
||||||
)}
|
<Button
|
||||||
</button>
|
type="button"
|
||||||
</div>
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 px-3 py-2 bg-transparent border border-border rounded-xl text-muted-foreground hover:text-foreground hover:bg-accent/50 flex items-center gap-2"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
>
|
||||||
|
<Paperclip className="h-4 w-4" />
|
||||||
|
<span className="text-sm">Attach</span>
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Please login to attach files</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<ChatDropdown />
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
size="sm"
|
||||||
|
className={cn(
|
||||||
|
'w-8 h-8 flex-shrink-0 self-end rounded-xl',
|
||||||
|
(!inputValue.trim() || isSubmitting) ? 'opacity-50' : '',
|
||||||
|
)}
|
||||||
|
disabled={!inputValue.trim() || isSubmitting}
|
||||||
|
>
|
||||||
|
{isSubmitting ? (
|
||||||
|
<div className="h-5 w-5 animate-spin rounded-full border-2 border-primary-foreground border-t-transparent" />
|
||||||
|
) : (
|
||||||
|
<ArrowUp className="h-5 w-5" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
{/* Subtle glow effect */}
|
{/* Subtle glow effect */}
|
||||||
<div className="absolute -bottom-4 inset-x-0 h-6 bg-secondary/20 blur-xl rounded-full -z-10 opacity-70"></div>
|
<div className="absolute -bottom-4 inset-x-0 h-6 bg-secondary/20 blur-xl rounded-full -z-10 opacity-70"></div>
|
||||||
|
@ -336,7 +358,7 @@ export function HeroSection() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-10 max-w-4xl mx-auto">
|
<div className="mb-10 sm:mt-40 max-w-4xl mx-auto">
|
||||||
<HeroVideoSection />
|
<HeroVideoSection />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -361,13 +383,7 @@ export function HeroSection() {
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
{/* Auth error message */}
|
|
||||||
{authError && (
|
|
||||||
<div className="mb-4 p-3 rounded-lg flex items-center gap-3 bg-secondary/10 border border-secondary/20 text-secondary">
|
|
||||||
<AlertCircle className="h-5 w-5 flex-shrink-0 text-secondary" />
|
|
||||||
<span className="text-sm font-medium">{authError}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Google Sign In */}
|
{/* Google Sign In */}
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
|
@ -411,13 +427,13 @@ export function HeroSection() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4 pt-4">
|
<div className="space-y-4 pt-4">
|
||||||
<SubmitButton
|
<Link
|
||||||
formAction={handleSignIn}
|
href={`/auth?returnUrl=${encodeURIComponent('/dashboard')}`}
|
||||||
className="w-full h-12 rounded-full bg-primary text-primary-foreground hover:bg-primary/90 transition-all shadow-md"
|
className="flex h-12 items-center justify-center w-full text-center rounded-full bg-primary text-primary-foreground hover:bg-primary/90 transition-all shadow-md"
|
||||||
pendingText="Signing in..."
|
onClick={() => setAuthDialogOpen(false)}
|
||||||
>
|
>
|
||||||
Sign in
|
Sign in
|
||||||
</SubmitButton>
|
</Link>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
href={`/auth?mode=signup&returnUrl=${encodeURIComponent('/dashboard')}`}
|
href={`/auth?mode=signup&returnUrl=${encodeURIComponent('/dashboard')}`}
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { ChevronDown, User, LogIn, Lock, Bot, Zap, Code, FileText } from 'lucide-react'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu'
|
||||||
|
|
||||||
|
const ChatDropdown = () => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="px-3 py-2 text-sm font-medium hover:bg-accent"
|
||||||
|
style={{
|
||||||
|
borderRadius: '12px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<User size={16} />
|
||||||
|
<span>Suna</span>
|
||||||
|
<ChevronDown size={14} className="opacity-50" />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
|
||||||
|
<DropdownMenuContent
|
||||||
|
align="end"
|
||||||
|
className="w-64 p-0 border"
|
||||||
|
sideOffset={4}
|
||||||
|
style={{
|
||||||
|
borderRadius: '16px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="flex items-center gap-3 px-4 py-3 cursor-pointer hover:bg-accent border-b m-0"
|
||||||
|
style={{
|
||||||
|
borderRadius: '0'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<User size={18} />
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="font-semibold text-sm">Suna</span>
|
||||||
|
<span className="text-xs text-muted-foreground">Default</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
{/* Dummy agents behind the overlay */}
|
||||||
|
<DropdownMenuItem className="flex items-center gap-3 px-4 py-3 cursor-pointer hover:bg-accent border-b m-0">
|
||||||
|
<Bot size={18} />
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="font-semibold text-sm">Code Assistant</span>
|
||||||
|
<span className="text-xs text-muted-foreground">Help with coding</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
|
<DropdownMenuItem className="flex items-center gap-3 px-4 py-3 cursor-pointer hover:bg-accent border-b m-0">
|
||||||
|
<Zap size={18} />
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="font-semibold text-sm">Quick Writer</span>
|
||||||
|
<span className="text-xs text-muted-foreground">Fast content creation</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
|
<DropdownMenuItem className="flex items-center gap-3 px-4 py-3 cursor-pointer hover:bg-accent m-0">
|
||||||
|
<FileText size={18} />
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="font-semibold text-sm">Document Helper</span>
|
||||||
|
<span className="text-xs text-muted-foreground">Analyze documents</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
|
{/* Overlay like the upgrade component */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-background via-background/95 to-transparent flex items-end justify-center">
|
||||||
|
<div className="w-full p-3">
|
||||||
|
<div className="rounded-xl bg-gradient-to-br from-slate-50/80 to-slate-100/70 dark:from-slate-900/40 dark:to-slate-800/30 shadow-sm border border-slate-200/50 dark:border-slate-700/50 p-3">
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<Lock className="h-4 w-4 text-slate-500 mr-2 flex-shrink-0" />
|
||||||
|
<p className="text-sm font-medium">Login to explore all agents</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChatDropdown
|
Loading…
Reference in New Issue