From efaba0d319f7140729d9053a79f3f4cf7c8dd479 Mon Sep 17 00:00:00 2001 From: Rainyy21 Date: Sat, 8 Nov 2025 20:39:17 -0500 Subject: [PATCH] pasted all my ai file --- app/api/auth/[...auth0]/route.js | 7 + app/api/chat/route.ts | 24 ++ app/api/generate-idea/route.ts | 23 ++ app/cards/page.tsx | 210 ++++++++++++++++ app/chatbot/page.tsx | 135 +++++++++++ app/formfile/FormPage.tsx | 294 +++++++++++++++++++++++ app/formfile/formTypes.ts | 21 ++ app/formfile/generate-idea.ts | 71 ++++++ app/formfile/page.tsx | 188 +++++++++++++++ app/utils/component/ui/ChatInput.tsx | 96 ++++++++ app/utils/component/ui/MessageWindow.tsx | 60 +++++ app/utils/geminiHelper.ts | 42 ++++ 12 files changed, 1171 insertions(+) create mode 100644 app/api/auth/[...auth0]/route.js create mode 100644 app/api/chat/route.ts create mode 100644 app/api/generate-idea/route.ts create mode 100644 app/cards/page.tsx create mode 100644 app/chatbot/page.tsx create mode 100644 app/formfile/FormPage.tsx create mode 100644 app/formfile/formTypes.ts create mode 100644 app/formfile/generate-idea.ts create mode 100644 app/formfile/page.tsx create mode 100644 app/utils/component/ui/ChatInput.tsx create mode 100644 app/utils/component/ui/MessageWindow.tsx create mode 100644 app/utils/geminiHelper.ts diff --git a/app/api/auth/[...auth0]/route.js b/app/api/auth/[...auth0]/route.js new file mode 100644 index 0000000..1c99c77 --- /dev/null +++ b/app/api/auth/[...auth0]/route.js @@ -0,0 +1,7 @@ +// app/api/auth/[auth0]/route.js + +import { handleAuth } from '@auth0/nextjs-auth0'; + +export const runtime = 'nodejs'; + +export const GET = handleAuth(); \ No newline at end of file diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts new file mode 100644 index 0000000..eeb8b31 --- /dev/null +++ b/app/api/chat/route.ts @@ -0,0 +1,24 @@ +import { NextResponse } from "next/server"; +import { chatToGemini } from "@/app/utils/geminiHelper"; +import { ChatHistory, ChatSetting } from "@/app/exportType/types"; + + +export async function POST(request: Request) { + try { + const { userMessage, history , settings } = await request.json() as { + userMessage :string; + history : ChatHistory; + settings : ChatSetting; + }; + + const aiResponse = await chatToGemini(userMessage , history, settings); + return NextResponse.json({response : aiResponse}); + } + catch(error){ + console.error(error); + return NextResponse.json( + {error: "Error with the model response "}, + {status : 500} + ); + } +} diff --git a/app/api/generate-idea/route.ts b/app/api/generate-idea/route.ts new file mode 100644 index 0000000..c135cd4 --- /dev/null +++ b/app/api/generate-idea/route.ts @@ -0,0 +1,23 @@ +import { NextResponse } from "next/server"; +import { chatToGemini } from "@/app/utils/geminiHelper"; +import { ChatSetting } from "@/app/exportType/types"; + + +export async function POST(request: Request) { + try { + const { prompt, settings } = await request.json() as { + prompt :string; + settings : ChatSetting; + }; + + const aiResponse = await chatToGemini(prompt , [], settings); + return NextResponse.json({response : aiResponse}); + } + catch(error){ + console.error(error); + return NextResponse.json( + {error: "Error with the model response "}, + {status : 500} + ); + } +} diff --git a/app/cards/page.tsx b/app/cards/page.tsx new file mode 100644 index 0000000..0da2c42 --- /dev/null +++ b/app/cards/page.tsx @@ -0,0 +1,210 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { motion, useMotionValue, useTransform, useAnimation } from "framer-motion"; +import { useRouter } from "next/navigation"; + +// 1. Updated ColorWay interface +interface ColorWay { + card: string; + text: string; + titleColor: string; // Added title color +} + +// 2. Updated COLOR_WAYS array +const COLOR_WAYS: ColorWay[] = [ + { card: "#FEEEDF", text: "#173C46", titleColor: "#173C46" }, + { card: "#FEEEDF", text: "#EA627F", titleColor: "#173C46" }, // Example: Title is dark, text is pink + { card: "#DEE5D4", text: "#173C46", titleColor: "#EA627F" }, + { card: "#DEE5D4", text: "#EA627F", titleColor: "#173C46" }, + { card: "#B8C0AD", text: "#173C46", titleColor: "#173C46" }, + { card: "#B8C0AD", text: "#173C46", titleColor: "#EA627F" }, +]; + +interface CardData { + id: number; + title: string; + summary: string; + tracks: string[]; +} + +const SwipeCards: React.FC<{ initialCards: CardData[] }> = ({ initialCards }) => { + const [cards, setCards] = useState(initialCards); + const router = useRouter(); + + // Update cards state when initialCards prop changes + useEffect(() => { + setCards(initialCards); + }, [initialCards]); + + const removeCard = () => { + setCards((prev) => prev.slice(0, prev.length - 1)); + }; + + const handleAccept = (cardData: CardData) => { + removeCard(); + // Redirect to chatbot with card data + router.push(`/chatbot?title=${encodeURIComponent(cardData.title)}&summary=${encodeURIComponent(cardData.summary)}`); + }; + + const handleReject = () => { + removeCard(); + }; + + const handleReset = () => { + setCards([]); // Reset to initial cards + }; + + return ( +
+ {cards.length > 0 ? ( + cards.map((card, index) => { + const colorWay = COLOR_WAYS[card.id % COLOR_WAYS.length]; + return ( + + ); + }) + ) : ( + // ... (empty state) +
+

+ No more cards! +

+

+ You've swiped through them all. +

+ +
+ )} +
+ ); +}; + +interface CardProps { + cardData: CardData; + colorWay: ColorWay; + isFront: boolean; + onAccept: (cardData: CardData) => void; + onReject: () => void; +} + +const Card: React.FC = ({ cardData, colorWay, isFront, onAccept, onReject }) => { + const { title, summary, tracks } = cardData; + + const x = useMotionValue(0); + const rotate = useTransform(x, [-250, 250], [-25, 25]); + const opacity = useTransform( + x, + [-250, -100, 0, 100, 250], + [0, 1, 1, 1, 0], + ); + const controls = useAnimation(); + + const acceptOpacity = useTransform(x, [50, 150], [0, 1]); + const rejectOpacity = useTransform(x, [-150, -50], [1, 0]); + const badgeRotate = useTransform(x, [-150, 150], [-15, 15]); + + const handleDragEnd = async (event: MouseEvent | TouchEvent | PointerEvent, info: { offset: { x: number } }) => { + if (info.offset.x > 100) { + // Card accepted + await controls.start({ x: 1000, opacity: 0, transition: { duration: 0.1 } }); // Animate off-screen quickly + onAccept(cardData); // Then call onAccept which removes the card and redirects + } else if (info.offset.x < -100) { + // Card rejected + await controls.start({ x: -1000, opacity: 0, transition: { duration: 0.1 } }); // Animate off-screen quickly + onReject(); // Then call onReject which removes the card + } else { + // Card not swiped far enough, snap back to center + controls.start({ x: 0, rotate: 0, opacity: 1, transition: { type: "spring", stiffness: 300, damping: 30 } }); + } + }; + + return ( + + {/* Accept/Reject Badges */} + +

Accept

+
+ +

Reject

+
+ + {/* Card Content */} +
+ {/* 3. Applied the specific titleColor here */} +

+ {title} +

+ {/* This summary

will inherit colorWay.text from the parent */} +

{summary}

+
+ +
+ {tracks.slice(0, 2).map((track, index) => ( + + {track} + {index === 0 && tracks.length > 1 && ", "} + + ))} +
+
+ ); + +}; + +export default SwipeCards; diff --git a/app/chatbot/page.tsx b/app/chatbot/page.tsx new file mode 100644 index 0000000..df0c671 --- /dev/null +++ b/app/chatbot/page.tsx @@ -0,0 +1,135 @@ +"use client"; + +import { useState, useEffect, useRef, useCallback } from "react"; +import { useSearchParams } from "next/navigation"; +import ChatInput from "../utils/component/ui/ChatInput"; +import MessageWindow from "../utils/component/ui/MessageWindow"; +import { ChatHistory, ChatSetting, Message , MessageRole } from "./exportType/types"; + +export default function Home(){ + const [history, setHistory] = useState([]) + const historyRef = useRef([]); // Create a ref for history + const [settings] = useState({ + temperature:1, + model:"gemini-2.5-pro", + sysTemInstructions:"you are a ai helper. Your goal is to expand the project idea and interact with the user on any problem or question they have with the project. You are not allow to code the project for them and stray away from your job" + + }); + const searchParams = useSearchParams(); + const initialMessageSent = useRef(false); + + useEffect(() => { + historyRef.current = history; // Keep the ref updated with the latest history + }, [history]); + + const handleSend = useCallback(async(message:string)=>{ + const newUserMessage:Message = { + role:"user", + parts:[{text:message}], + }; + + setHistory(prevHistory => { + const currentHistory = [...prevHistory, newUserMessage]; + + const sendApiRequest = async () => { + try{ + const response = await fetch("/api/chat", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + userMessage: message, + history: historyRef.current, // Use the ref for history + settings: settings, + }), + }); + + const data = await response.json(); + + if (data.error) { + console.error("AI Error:", data.error); + return; + } + + const aiMessage: Message = { + role: "model" as MessageRole, + parts: [{ text: data.response }], + }; + + setHistory(prevHistory => [...prevHistory, aiMessage]); + + } + catch (error) { + console.error("Request Failed:", error); + } + }; + sendApiRequest(); + + return currentHistory; + }); + }, [settings]); // Now handleSend only depends on settings + + useEffect(() => { + if (!initialMessageSent.current) { + const title = searchParams.get('title'); + const summary = searchParams.get('summary'); + + if (title && summary) { + const message = `Explain the project titled '${title}' with the summary '${summary}' in detail, and suggest suitable roles for a team working on it.`; + + const newUserMessage: Message = { + role: "user", + parts: [{ text: message }], + }; + + setTimeout(() => { + setHistory((prevHistory) => [...prevHistory, newUserMessage]); // Add user message to history + }, 0); + + const sendInitialApiRequest = async () => { + try { + const response = await fetch("/api/chat", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + userMessage: message, + history: [...historyRef.current, newUserMessage], // Use ref for history + settings: settings, + }), + }); + + const data = await response.json(); + + if (data.error) { + console.error("AI Error:", data.error); + return; + } + + const aiMessage: Message = { + role: "model" as MessageRole, + parts: [{ text: data.response }], + }; + + setHistory((prevHistory) => [...prevHistory, aiMessage]); + } catch (error) { + console.error("Request Failed:", error); + } + }; + + sendInitialApiRequest(); + initialMessageSent.current = true; + } + } + }, [searchParams, settings]); // Dependencies for this useEffect + + + return ( +
+ + {}} /> +
+ ); +} \ No newline at end of file diff --git a/app/formfile/FormPage.tsx b/app/formfile/FormPage.tsx new file mode 100644 index 0000000..8059518 --- /dev/null +++ b/app/formfile/FormPage.tsx @@ -0,0 +1,294 @@ +"use client"; + +import React, { useState } from "react"; + +import { + FormData, + PROFICIENCY_LEVELS +} from './formTypes'; // Ensure path is correct + +interface FormPageProps { + onFormSubmit: (data: FormData) => void; +} + +const FormPage: React.FC = ({ onFormSubmit }) => { + const [formData, setFormData] = useState({ + Hackathon: '', + mainTheme: '', + tracksStack: [''], + students: [{ + studentName: '', + skills: [{ skillName: '', proficiency: PROFICIENCY_LEVELS[1] }] + }], + }); + + const handleChange = (event: React.ChangeEvent) => { + setFormData({ + ...formData, + [event.target.name]: event.target.value, + }); + }; + + const handleTracksChange = (event: React.ChangeEvent, index: number) => { + const newTracksStack = [...formData.tracksStack]; + newTracksStack[index] = event.target.value; + + setFormData({ + ...formData, + tracksStack: newTracksStack, + }); + }; + + const handleAddTracks = () => { + setFormData({ + ...formData, + tracksStack: [...formData.tracksStack, ''], + }); + }; + + const handleRemoveTracks = (index: number) => { + const newTracksStack = formData.tracksStack.filter((_, i) => i !== index); + setFormData({ + ...formData, + tracksStack: newTracksStack, + }); + }; + + const handleStudentNameChange = (e: React.ChangeEvent, studentIndex: number) => { + const newStudents = [...formData.students]; + newStudents[studentIndex].studentName = e.target.value; + setFormData({ ...formData, students: newStudents }); + }; + + const handleAddStudent = () => { + setFormData({ + ...formData, + students: [...formData.students, { studentName: '', skills: [{ skillName: '', proficiency: PROFICIENCY_LEVELS[1] }] }] + }); + }; + + const handleRemoveStudent = (studentIndex: number) => { + const newStudents = formData.students.filter((_, i) => i !== studentIndex); + setFormData({ ...formData, students: newStudents }); + }; + + const handleSkillChange = ( + e: React.ChangeEvent, + studentIndex: number, + skillIndex: number + ) => { + const newStudents = [...formData.students]; + const targetName = e.target.name; + const targetValue = e.target.value; + + if (targetName === 'skillName') { + newStudents[studentIndex].skills[skillIndex].skillName = targetValue; + } else if (targetName === 'proficiency') { + newStudents[studentIndex].skills[skillIndex].proficiency = targetValue; + } + + setFormData({ ...formData, students: newStudents }); + }; + + const handleAddSkill = (studentIndex: number) => { + const newStudents = [...formData.students]; + newStudents[studentIndex].skills.push({ skillName: '', proficiency: PROFICIENCY_LEVELS[1] }); + setFormData({ ...formData, students: newStudents }); + }; + + const handleRemoveSkill = (studentIndex: number, skillIndex: number) => { + const newStudents = [...formData.students]; + newStudents[studentIndex].skills = newStudents[studentIndex].skills.filter((_, i) => i !== skillIndex); + setFormData({ ...formData, students: newStudents }); + }; + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + onFormSubmit(formData); // Call the prop function with formData + }; + + return ( +
+
+

+ Generate Project Idea +

+ +
+
+

Project Context

+
+ + +
+ +
+ + +
+
+ +
+ +
+ {formData.tracksStack.map((tracks, index) => ( +
+ handleTracksChange(e, index)} + className="flex-grow px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:ring-green-500 focus:border-green-500 transition duration-150" + /> + {formData.tracksStack.length > 1 && ( + + )} +
+ ))} +
+ + +
+ +
+

Team Members and Skills

+ +
+ {formData.students.map((student, studentIndex) => ( +
+ +
+

Student #{studentIndex + 1}

+ {formData.students.length > 1 && ( + + )} +
+ +
+ + handleStudentNameChange(e, studentIndex)} + className="w-full px-4 py-2 border border-indigo-300 rounded-lg shadow-sm focus:ring-indigo-500 focus:border-indigo-500" + /> +
+ +
+
Skills
+ {student.skills.map((skill, skillIndex) => ( +
+ handleSkillChange(e, studentIndex, skillIndex)} + className="flex-grow px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-purple-500 focus:border-purple-500" + /> + + {student.skills.length > 1 && ( + + )} +
+ ))} + +
+
+ ))} +
+ + +
+ + +
+ +
+
+ ); +}; + +export default FormPage; \ No newline at end of file diff --git a/app/formfile/formTypes.ts b/app/formfile/formTypes.ts new file mode 100644 index 0000000..e3d23c6 --- /dev/null +++ b/app/formfile/formTypes.ts @@ -0,0 +1,21 @@ +// Define Skill structure +export interface Skill { + skillName: string; + proficiency: string; +} + +// Define Student structure +export interface Student { + studentName: string; + skills: Skill[]; +} + +// Updated main form interface +export interface FormData { + Hackathon: string; + mainTheme: string; + tracksStack: string[]; + students: Student[]; // Array for dynamic students +} + +export const PROFICIENCY_LEVELS = ['Beginner', 'Intermediate', 'Expert', 'Master']; \ No newline at end of file diff --git a/app/formfile/generate-idea.ts b/app/formfile/generate-idea.ts new file mode 100644 index 0000000..c9f1f3f --- /dev/null +++ b/app/formfile/generate-idea.ts @@ -0,0 +1,71 @@ +import { GoogleGenerativeAI } from "@google/generative-ai"; +import type { NextApiRequest, NextApiResponse } from 'next'; + +// Initialize the Gemini Client with your API Key +// IMPORTANT: The GEMINI_API_KEY should be an environment variable. +const apiKey = process.env.GEMINI_API_KEY; + +if(!apiKey){ + throw new Error("api key not found"); +} +const ai = new GoogleGenerativeAI(apiKey); + + + +// Define the handler function for the API route +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + // Only allow POST requests + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method Not Allowed' }); + } + + // Destructure the data sent from the frontend + let { prompt, settings } = req.body; + + // Basic validation + if (!prompt || typeof prompt !== 'string') { + return res.status(400).json({ error: 'Prompt is required' }); + } + + // Modify the prompt to instruct the AI to generate only the game, concept, and track + prompt = `You're going to fill out a card base off the document. + you will Generate the project short title. + you will list out the track you used ${prompt}`; + + try { + // 1. Call the Gemini API using the correct method signature + + const model = settings?.model || 'gemini-2.5-flash-lite'; + const generativeModel = ai.getGenerativeModel({ model }); + + // 1. Define the generation configuration object + const generationConfig = { + temperature: settings?.temperature || 0.9, + // The SDK property name for system instructions is 'systemInstruction' + systemInstruction: settings?.sysTemInstructions, + }; + + // 2. Call generateContent with the single GenerateContentRequest object. + const result = await generativeModel.generateContent({ + // contents must be an array of Content objects + contents: [{ + role: "user", + parts: [{ text: prompt }] + }], + + // The fixed property name that resolves the TypeScript error + generationConfig: generationConfig, + }); + + // 2. Extract the response text + // The result object now contains a property 'text' at the top level for simple text generation + const geminiResponseText = result.response.text(); res.status(200).json({ response: geminiResponseText }); + + } catch (error) { + console.error("Gemini API Error:", error); + res.status(500).json({ error: 'Failed to generate idea from AI.' }); + } +} diff --git a/app/formfile/page.tsx b/app/formfile/page.tsx new file mode 100644 index 0000000..4ae8426 --- /dev/null +++ b/app/formfile/page.tsx @@ -0,0 +1,188 @@ +// File: Home.tsx (Renamed from form.tsx for export consistency) + +"use client"; +import React, { useState, useEffect, useCallback } from "react"; +import FormPage from './FormPage'; +import { ChatSetting, CardData } from "../exportType/types"; +import { FormData, PROFICIENCY_LEVELS } from './formTypes'; +import SwipeCards from '../cards/page'; + +export default function Home() { + const [showForm, setShowForm] = useState(true); + const [formData, setFormData] = useState(() => { + if (typeof window !== 'undefined') { + const storedFormData = localStorage.getItem('formData'); + if (storedFormData) { + const parsedData: FormData = JSON.parse(storedFormData); + return parsedData; + } + } + return { + Hackathon: '', + mainTheme: '', + tracksStack: [''], + students: [{ + studentName: '', + skills: [{ skillName: '', proficiency: PROFICIENCY_LEVELS[1] }] + }], + }; + }); + + const [ideaResponse, setIdeaResponse] = useState(""); + const [generatedCards, setGeneratedCards] = useState([]); // New state for parsed cards + + const [settings] = useState({ + temperature: 1, + model: "gemini-2.5-flash-lite", + sysTemInstructions: `You are an elite hackathon idea generator. Your task is to choose at lease two track and generate five different creative, feasible, and relevant project idea. +The output MUST be in Markdown format and strictly follow this structure: +### Project Idea Title +**Track** [print out the track you used] +**Concept:** [A concise, single paragraph description of the product and its primary function.]`, + }); + +const parseAiResponse = (markdownString: string): CardData[] => { + console.log("Raw AI Response:", markdownString); + const ideas: CardData[] = []; + const ideaBlocks = markdownString.split("### ").filter(block => block.trim() !== ""); + + ideaBlocks.forEach((block, index) => { + // Match title: anything from the start of the block until the first newline or the start of "**Track**" + const titleMatch = block.match(/^(.*?)(?=\n\*\*Track\*\*|$)/s); + // Match track: "**Track**" followed by optional whitespace and then capture everything until the next "**Concept**" or end of block/line + const trackMatch = block.match(/\*\*Track\*\*\s*:\s*(.*?)(?=\n\*\*Concept\*\*|$)/s); // Added ':' and made whitespace more flexible + // Match concept: "**Concept:**" followed by optional whitespace and then capture everything until the end of the block + const conceptMatch = block.match(/\*\*Concept:\*\*\s*(.*)/s); + + const title = titleMatch ? titleMatch[1].trim() : `Idea ${index + 1}`; + const summary = conceptMatch ? conceptMatch[1].trim() : "No concept provided."; + const tracksRaw = trackMatch ? trackMatch[1].trim() : ""; + console.log("tracksRaw:", tracksRaw); // Added console.log for debugging + const tracks = tracksRaw.split(',').map(track => track.trim()).filter(track => track !== ''); + + ideas.push({ + id: index + 1, + title, + tracks, + summary, + }); + }); + console.log("Parsed Cards:", ideas); + return ideas; +}; + + const handleFormSubmitFromFormPage = (submittedData: FormData) => { + setFormData(submittedData); + setShowForm(false); + generateIdeas(submittedData); + }; + + // eslint-disable-next-line react-hooks/preserve-manual-memoization + const generateIdeas = useCallback(async (dataToProcess: FormData) => { + const submissionData: FormData = { + ...dataToProcess, + tracksStack: dataToProcess.tracksStack.filter(tracks => tracks.trim() !== ''), + students: dataToProcess.students + .map(student => ({ + ...student, + skills: student.skills.filter(skill => skill.skillName.trim() !== ''), + })) + .filter(student => student.studentName.trim() !== '' || student.skills.length > 0), + }; + + const prompt = ` + Hackathon: ${submissionData.Hackathon} + Main Theme: ${submissionData.mainTheme} + Required Tracks Stack: ${submissionData.tracksStack.join(', ')} + Team Members and Skills: ${submissionData.students.map(s => + `${s.studentName || 'Unamed Member'} (Skills: ${s.skills.map(sk => `${sk.skillName} - ${sk.proficiency}`).join(', ')})` + ).join('; ')} + + Using the SYSTEM INSTRUCTIONS, generate 5 project ideas based on the data above.`; + + try { + setIdeaResponse("Generating ideas... Please wait."); + setGeneratedCards([]); // Clear previous cards + const response = await fetch("/api/generate-idea", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + prompt: prompt, + settings: settings, + }), + }); + + const data = await response.json(); + + if (data.error || !response.ok) { + console.error("AI Error:", data.error); + setIdeaResponse(`Error: Failed to generate idea. ${data.error || response.statusText}`); + return; + } + + setIdeaResponse(data.response); + const parsedCards = parseAiResponse(data.response); + setGeneratedCards(parsedCards); + + } catch (error) { + console.error("Request Failed:", error); + let errorMessage = "An unknown error occurred."; + if (error instanceof Error) { + errorMessage = error.message; + } else if (typeof error === 'string') { + errorMessage = error; + } + setIdeaResponse(`A network error occurred. Please try again. Details: ${errorMessage}`); + } + }, [setIdeaResponse, setGeneratedCards, settings, parseAiResponse]); + + useEffect(() => { + if (typeof window !== 'undefined') { + const storedFormData = localStorage.getItem('formData'); + if (storedFormData) { + const parsedData: FormData = JSON.parse(storedFormData); + setTimeout(() => setShowForm(false), 0); + generateIdeas(parsedData); + localStorage.removeItem('formData'); + } + } + }, [generateIdeas]); + + return ( +
+
+ {showForm ? ( + + ) : ( + <> + {generatedCards.length > 0 ? ( + + ) : ( + <> +

+ Generated Project Idea 💡 +

+
+

Generated Ideas:

+
') }} + /> + {ideaResponse === "" &&

Submit the form to generate project ideas.

} +
+ + )} + + + )} +
+
+ ); +} \ No newline at end of file diff --git a/app/utils/component/ui/ChatInput.tsx b/app/utils/component/ui/ChatInput.tsx new file mode 100644 index 0000000..53c5bf8 --- /dev/null +++ b/app/utils/component/ui/ChatInput.tsx @@ -0,0 +1,96 @@ +//geminiclone/src/components/ui/ChatInput.tsx +"use client"; +import { useState } from "react"; +import { Settings, Send, X } from "lucide-react"; + +interface ChatInputProps { + onSend: (message: string) => void; + onOpenSettings: () => void; +} + +export default function ChatInput({ onSend, onOpenSettings }: ChatInputProps) { + const [message, setMessage] = useState(""); + + const handleChange = (e: React.ChangeEvent) => { + setMessage(e.target.value); + }; + + const handleSend = () => { + if (message.trim()) { + onSend(message.trim()); + setMessage(""); + } + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + handleSend(); + } + }; + + const handleClear = () => { + setMessage(""); + }; + + return ( +
+
+ +
+