diff --git a/frontend/package.json b/frontend/package.json index 8b65e4aa..2fd21795 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,7 +33,7 @@ "@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-separator": "^1.1.1", - "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.6", "@radix-ui/react-visually-hidden": "^1.1.1", diff --git a/frontend/src/app/(main)/page.tsx b/frontend/src/app/(main)/page.tsx index 0be78895..befe02a2 100644 --- a/frontend/src/app/(main)/page.tsx +++ b/frontend/src/app/(main)/page.tsx @@ -12,6 +12,7 @@ import { SignInModal } from '@/components/sign-in-modal'; import { SignUpModal } from '@/components/sign-up-modal'; import { useRouter } from 'next/navigation'; import { logger } from '../log/logger'; +import { AuroraText } from '@/components/magicui/aurora-text'; export default function HomePage() { // States for AuthChoiceModal const [showAuthChoice, setShowAuthChoice] = useState(false); @@ -31,6 +32,8 @@ export default function HomePage() { try { const chatId = await createProjectFromPrompt(message, isPublic, model); + + console.log('Project created with ID:', chatId); promptFormRef.current.clearMessage(); router.push(`/chat?id=${chatId}`); } catch (error) { @@ -39,85 +42,144 @@ export default function HomePage() { }; return ( -
- -
- CodeFox Logo -
- -
-
-

- Sentence to a project in seconds. -

-

- Codefox built AI agents crew for you to create your next project -

+
+
+
+
+
+ +
+ +
+ CodeFox Logo
-
- -
- setShowAuthChoice(true)} - isLoading={isLoading} - /> -
- -
- -
- - - {/* Choice Modal */} - setShowAuthChoice(false)} - onSignUpClick={() => { - setShowAuthChoice(false); - setTimeout(() => { - setShowSignUp(true); - }, 100); - }} - onSignInClick={() => { - setShowAuthChoice(false); - setTimeout(() => { - setShowSignIn(true); - }, 100); - }} - /> - - {/* SignInModal & SignUpModal */} - setShowSignIn(false)} /> - setShowSignUp(false)} /> - - + +
+ + From Idea to Full-Stack in Seconds + + + + CodeFox provides an AI-driven multi-agent crew to help you + create your next project instantly + +
+ + + + + + + +
+ +
+ setShowAuthChoice(true)} + isLoading={isLoading} + /> +
+
+ + + + +
+ + setShowAuthChoice(false)} + onSignUpClick={() => { + setShowAuthChoice(false); + setTimeout(() => { + setShowSignUp(true); + }, 100); + }} + onSignInClick={() => { + setShowAuthChoice(false); + setTimeout(() => { + setShowSignIn(true); + }, 100); + }} + /> + + setShowSignIn(false)} /> + setShowSignUp(false)} /> +
); } diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index ba273e10..0b10650f 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -168,8 +168,8 @@ .button-no-border { @apply !border-none !outline-none !shadow-none; } -.button-no-border:hover, -.button-no-border:focus, +.button-no-border:hover, +.button-no-border:focus, .button-no-border:active { @apply !border-none !outline-none !shadow-none !ring-0; } diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 5c78ab6d..ee27ef53 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,12 +1,16 @@ -//frontend/src/app/layout.tsx +// frontend/src/app/layout.tsx import type { Metadata, Viewport } from 'next'; -import { Inter } from 'next/font/google'; +import { Inter, Playfair_Display } from 'next/font/google'; import './globals.css'; import { BaseProviders } from '@/providers/BaseProvider'; import NavLayout from '@/components/root/nav-layout'; import RootLayout from '@/components/root/root-layout'; -const inter = Inter({ subsets: ['latin'] }); +const inter = Inter({ subsets: ['latin'], variable: '--font-inter' }); +const playfair = Playfair_Display({ + subsets: ['latin'], + variable: '--font-playfair', +}); export const metadata: Metadata = { title: 'Codefox - The best dev project generator', @@ -23,7 +27,7 @@ export const viewport: Viewport = { export default function Layout({ children }: { children: React.ReactNode }) { return ( - +
{children} diff --git a/frontend/src/components/chat/chat-bottombar.tsx b/frontend/src/components/chat/chat-bottombar.tsx index 501782d2..7435e9cd 100644 --- a/frontend/src/components/chat/chat-bottombar.tsx +++ b/frontend/src/components/chat/chat-bottombar.tsx @@ -1,51 +1,40 @@ 'use client'; - -import React, { useEffect } from 'react'; -import { ChatProps } from './chat-panel'; -import Link from 'next/link'; -import { cn } from '@/lib/utils'; -import { Button, buttonVariants } from '../ui/button'; -import TextareaAutosize from 'react-textarea-autosize'; +import React, { useEffect, useRef, useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; -import { - Cross2Icon, - ImageIcon, - PaperPlaneIcon, - StopIcon, -} from '@radix-ui/react-icons'; -import { Mic, SendHorizonal } from 'lucide-react'; -import useSpeechToText from '@/hooks/useSpeechRecognition'; -import MultiImagePicker from '../image-embedder'; -import useChatStore from '@/hooks/useChatStore'; +import TextareaAutosize from 'react-textarea-autosize'; +import { PaperclipIcon, Send, X } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { ChatProps } from './chat-panel'; import Image from 'next/image'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip'; export default function ChatBottombar({ messages, input, handleInputChange, handleSubmit, - stop, formRef, setInput, }: ChatProps) { - const [message, setMessage] = React.useState(input); - const [isMobile, setIsMobile] = React.useState(false); - const inputRef = React.useRef(null); - const base64Images = useChatStore((state) => state.base64Images); - const setBase64Images = useChatStore((state) => state.setBase64Images); - const env = process.env.NODE_ENV; + const [isMobile, setIsMobile] = useState(false); + const [isFocused, setIsFocused] = useState(false); + const [attachments, setAttachments] = useState([]); + const inputRef = useRef(null); + const fileInputRef = useRef(null); - React.useEffect(() => { + useEffect(() => { const checkScreenWidth = () => { setIsMobile(window.innerWidth <= 768); }; - // Initial check checkScreenWidth(); - // Event listener for screen width changes window.addEventListener('resize', checkScreenWidth); - // Cleanup the event listener on component unmount return () => { window.removeEventListener('resize', checkScreenWidth); @@ -55,26 +44,30 @@ export default function ChatBottombar({ const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); - handleSubmit(e as unknown as React.FormEvent); + submitWithAttachments(e as unknown as React.FormEvent); } }; - const { isListening, transcript, startListening, stopListening } = - useSpeechToText({ continuous: true }); + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files.length > 0) { + const fileArray = Array.from(e.target.files); + setAttachments((prev) => [...prev, ...fileArray]); + } + }; - const listen = () => { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - isListening ? stopVoiceInput() : startListening(); + const handleFileSelect = () => { + fileInputRef.current?.click(); }; - const stopVoiceInput = () => { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - setInput && setInput(transcript.length ? transcript : ''); - stopListening(); + const removeAttachment = (index: number) => { + setAttachments((prev) => prev.filter((_, i) => i !== index)); }; - const handleListenClick = () => { - listen(); + const submitWithAttachments = (e: React.FormEvent) => { + // Here you would normally handle attachments with your form submission + // For this example, we'll just clear them after submission + handleSubmit(e); + setAttachments([]); }; useEffect(() => { @@ -84,109 +77,169 @@ export default function ChatBottombar({ }, []); return ( -
- -
-
-
-
-
- -
- - -
- {isListening ? ( -
- -
- ) : ( - - )} - -
- -
- {base64Images && ( -
- {base64Images.map((image, index) => { - return ( -
-
+
+ + {/* Attachments preview */} + + {attachments.length > 0 && ( + + {attachments.map((file, index) => ( + +
+ {file.type.startsWith('image/') ? ( +
{''}
- -
- ); - })} -
- )} + ) : ( +
+ {file.name} +
+ )} +
+ + + ))} + + )} + + +
+ {/* Hidden file input */} + + + {/* Left icons with tooltips */} +
+ + + + + + +

Feature not available yet

+
+
+
+ + + + + + + +

Feature not available yet

+
+
+
+
+ + {/* Text input */} +
+ setIsFocused(true)} + onBlur={() => setIsFocused(false)} + name="message" + placeholder="Message Agent..." + className="resize-none px-2 py-2.5 w-full focus:outline-none bg-transparent text-gray-800 dark:text-zinc-200 text-sm placeholder:text-gray-400 dark:placeholder:text-zinc-400" + maxRows={5} + /> +
+ + {/* Right side - feedback & send */} +
+
+ Have feedback? +
+ +
-
- + +
); } diff --git a/frontend/src/components/chat/chat-list.tsx b/frontend/src/components/chat/chat-list.tsx index a3dfe1d9..e620538a 100644 --- a/frontend/src/components/chat/chat-list.tsx +++ b/frontend/src/components/chat/chat-list.tsx @@ -1,15 +1,15 @@ 'use client'; import React, { useRef, useEffect } from 'react'; -import { motion } from 'framer-motion'; +import { motion, AnimatePresence } from 'framer-motion'; import { cn } from '@/lib/utils'; -import { Avatar, AvatarFallback, AvatarImage, SmallAvatar } from '../ui/avatar'; +import { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar'; import Markdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import CodeDisplayBlock from '../code-display-block'; import { Message } from '../../const/MessageType'; import { Button } from '../ui/button'; -import { Pencil } from 'lucide-react'; +import { Check, Pencil, X, Code, Terminal } from 'lucide-react'; import { useAuthContext } from '@/providers/AuthProvider'; interface ChatListProps { @@ -19,6 +19,8 @@ interface ChatListProps { } const isUserMessage = (role: string) => role.toLowerCase() === 'user'; +const isToolCall = (content: string) => + content.includes('```') || content.includes('executing'); export default function ChatList({ messages, @@ -41,7 +43,7 @@ export default function ChatList({ }, [messages]); if (!user) { - return <>; + return null; } const handleEditStart = (message: Message) => { @@ -50,13 +52,18 @@ export default function ChatList({ }; const handleEditSubmit = (messageId: string) => { - if (onMessageEdit) { + if (onMessageEdit && editContent.trim()) { onMessageEdit(messageId, editContent); } setEditingMessageId(null); setEditContent(''); }; + const handleEditCancel = () => { + setEditingMessageId(null); + setEditContent(''); + }; + const renderMessageContent = (content: string) => { return content.split('```').map((part, index) => { if (index % 2 === 0) { @@ -67,9 +74,9 @@ export default function ChatList({ ); } return ( -
+        
-
+
); }); }; @@ -77,136 +84,201 @@ export default function ChatList({ if (messages.length === 0) { return (
-
+ AI -

- How can I help you today? +

Welcome to CodeFox

+

+ Ask me anything about coding, project development, or technical + problems. I'm here to help!

-
+
); } return ( -
-
- {messages.map((message, index) => { - const isUser = isUserMessage(message.role); - const isEditing = message.id === editingMessageId; - - return ( - -
- {isUser ? ( -
-
- {isEditing ? ( -
-