From ed00148f588250b2ce027b578eeb94ce157178f9 Mon Sep 17 00:00:00 2001 From: Rainyy21 Date: Sun, 9 Nov 2025 01:31:32 -0500 Subject: [PATCH 1/5] added a exit button on the chat bot and make sure it still working --- app/api/chat/route.ts | 2 +- app/chatbot/page.tsx | 20 +++++++++++++++++--- app/exportType/types.ts | 2 +- app/formfile/page.tsx | 2 +- app/page.tsx | 8 +++++--- app/utils/component/ui/MessageWindow.tsx | 21 ++++++++++++++++++--- app/utils/geminiHelper.ts | 2 +- package-lock.json | 12 ++++++++++++ 8 files changed, 56 insertions(+), 13 deletions(-) diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index eeb8b31..80e8c46 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -17,7 +17,7 @@ export async function POST(request: Request) { catch(error){ console.error(error); return NextResponse.json( - {error: "Error with the model response "}, + {error: "Error with the model response " + String(error)}, {status : 500} ); } diff --git a/app/chatbot/page.tsx b/app/chatbot/page.tsx index 363f852..6c5624c 100644 --- a/app/chatbot/page.tsx +++ b/app/chatbot/page.tsx @@ -1,20 +1,22 @@ "use client"; import { useState, useEffect, useRef, useCallback } from "react"; -import { useSearchParams } from "next/navigation"; +import { useSearchParams, useRouter } from "next/navigation"; import ChatInput from "../utils/component/ui/ChatInput"; import MessageWindow from "../utils/component/ui/MessageWindow"; import { ChatHistory, ChatSetting, Message , MessageRole } from "@/app/exportType/types"; export default function Home(){ const [history, setHistory] = useState([]) + const [loading, setLoading] = useState(false); 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" + systemInstruction:"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 router = useRouter(); const searchParams = useSearchParams(); const initialMessageSent = useRef(false); @@ -32,6 +34,7 @@ export default function Home(){ const currentHistory = [...prevHistory, newUserMessage]; const sendApiRequest = async () => { + setLoading(true); try{ const response = await fetch("/api/chat", { method: "POST", @@ -49,6 +52,7 @@ export default function Home(){ if (data.error) { console.error("AI Error:", data.error); + setLoading(false); return; } @@ -63,6 +67,7 @@ export default function Home(){ catch (error) { console.error("Request Failed:", error); } + setLoading(false); }; sendApiRequest(); @@ -88,6 +93,7 @@ export default function Home(){ }, 0); const sendInitialApiRequest = async () => { + setLoading(true); try { const response = await fetch("/api/chat", { method: "POST", @@ -105,6 +111,7 @@ export default function Home(){ if (data.error) { console.error("AI Error:", data.error); + setLoading(false); return; } @@ -117,6 +124,7 @@ export default function Home(){ } catch (error) { console.error("Request Failed:", error); } + setLoading(false); }; sendInitialApiRequest(); @@ -128,7 +136,13 @@ export default function Home(){ return (
- + + {}} />
); diff --git a/app/exportType/types.ts b/app/exportType/types.ts index eca26ad..b840e20 100644 --- a/app/exportType/types.ts +++ b/app/exportType/types.ts @@ -19,7 +19,7 @@ export interface GenerateConfig{ export interface ChatSetting{ temperature :number; model: string; - sysTemInstructions: string; + systemInstruction: string; } export interface CardData { diff --git a/app/formfile/page.tsx b/app/formfile/page.tsx index ccf46ef..dbfd792 100644 --- a/app/formfile/page.tsx +++ b/app/formfile/page.tsx @@ -34,7 +34,7 @@ export default function Home() { 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. + systemInstruction: `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] diff --git a/app/page.tsx b/app/page.tsx index f85cd0b..de9c3ba 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -5,11 +5,13 @@ import { useRouter } from 'next/navigation'; import Image from 'next/image'; import ProtectedNavButton from "../components/ProtectedNavButton"; import Link from 'next/link'; +import { style } from 'framer-motion/client'; + export default function Page() {   return (     
-      
+      
                  -           +                       Sign in            -           +                       Log out                    
diff --git a/app/utils/component/ui/MessageWindow.tsx b/app/utils/component/ui/MessageWindow.tsx index 6413cb3..d59e721 100644 --- a/app/utils/component/ui/MessageWindow.tsx +++ b/app/utils/component/ui/MessageWindow.tsx @@ -4,11 +4,12 @@ import { ChatHistory } from "@/app/exportType/types"; import {User, Bot} from "lucide-react" interface MessageWindowProps{ - history :ChatHistory + history :ChatHistory; + isLoading: boolean; } -export default function MessageWindow({history}:MessageWindowProps){ - const messageEndRef = useRef(null); +export default function MessageWindow({history, isLoading}:MessageWindowProps){ + const messageEndRef = useRef(null); useEffect(() =>{ messageEndRef.current?.scrollIntoView({behavior:"smooth"}); @@ -52,6 +53,20 @@ export default function MessageWindow({history}:MessageWindowProps){ ); })} + {isLoading && ( +
+
+
+ +
+
+
+
+ Loading... +
+
+
+ )} {/* Invisible element to help scroll to bottom */}
diff --git a/app/utils/geminiHelper.ts b/app/utils/geminiHelper.ts index 4fa619c..3a78f30 100644 --- a/app/utils/geminiHelper.ts +++ b/app/utils/geminiHelper.ts @@ -16,7 +16,7 @@ export async function chatToGemini( ):Promise{ const model = genAI.getGenerativeModel({ model:settings.model || "gemini-2.5-flash", - systemInstruction: settings.sysTemInstructions || "you are going to create a project idea, explain it in summery first before going to detail", + systemInstruction: settings.systemInstruction || "you are going to create a project idea, explain it in summery first before going to detail", }); const generationConfig:GenerateConfig = { diff --git a/package-lock.json b/package-lock.json index fa00b6d..299dff3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,6 +66,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1470,6 +1471,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "dev": true, + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -1526,6 +1528,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.3.tgz", "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.3", "@typescript-eslint/types": "8.46.3", @@ -2023,6 +2026,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2341,6 +2345,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -2870,6 +2875,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3047,6 +3053,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -5123,6 +5130,7 @@ "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -5131,6 +5139,7 @@ "version": "19.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -5776,6 +5785,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, + "peer": true, "engines": { "node": ">=12" }, @@ -5927,6 +5937,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6187,6 +6198,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "dev": true, + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From 3e3bff62fdf73a8581fc0e80ac2713d3047de5fd Mon Sep 17 00:00:00 2001 From: Rainyy21 Date: Sun, 9 Nov 2025 03:03:37 -0500 Subject: [PATCH 2/5] fix the ai and it should be working --- app/Dashboard/page.tsx | 11 +- app/api/chat/route.ts | 13 +- app/chatbot/page.tsx | 153 ++++-------- app/components/Cards.tsx | 172 ++++---------- app/formfile/generate-idea.ts | 3 +- app/formfile/page.tsx | 174 ++++---------- app/globals.css | 37 +-- app/page.tsx | 281 ----------------------- app/utils/component/ui/MessageWindow.tsx | 19 +- app/utils/geminiHelper.ts | 28 +-- 10 files changed, 160 insertions(+), 731 deletions(-) diff --git a/app/Dashboard/page.tsx b/app/Dashboard/page.tsx index 342cbc3..4a5ef08 100644 --- a/app/Dashboard/page.tsx +++ b/app/Dashboard/page.tsx @@ -1,8 +1,7 @@ "use client"; import React, { useState } from "react"; -// import { useRouter } from "next/navigation"; // Removed Next.js import -// import Link from 'next/link'; // Removed Next.js import +import Link from 'next/link'; export default function Dashboard() { // const router = useRouter(); // Removed Next.js hook @@ -79,23 +78,23 @@ export default function Dashboard() { className={`flex flex-col p-4 gap-3 text-zinc-700 dark:text-zinc-300 transition-opacity duration-200 ${sideBarOpen ? "opacity-100" : "opacity-0" }`} > - Home - +

Settings

- Logout - + diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 83d96ae..0056d7f 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -8,22 +8,19 @@ export async function POST(request: Request) { const { userMessage, history, settings } = await request.json() as { userMessage: string; history: ChatHistory; - settings: ChatSetting; + settings?: ChatSetting; // settings is optional from the request }; - const aiResponse = await chatToGemini(userMessage, history, settings); - return NextResponse.json({ response: aiResponse }); + // Call the Gemini helper function + const geminiResponse = await chatToGemini(userMessage, history, settings || {}); + + return NextResponse.json({ response: geminiResponse }); } catch (error) { console.error(error); return NextResponse.json( -<<<<<<< HEAD {error: "Error with the model response " + String(error)}, {status : 500} -======= - { error: "Error with the model response " }, - { status: 500 } ->>>>>>> ce9e6f34ed31b7bff736cc9ba9a1b8dcdec85f3a ); } } diff --git a/app/chatbot/page.tsx b/app/chatbot/page.tsx index 4be1b08..7cd0e95 100644 --- a/app/chatbot/page.tsx +++ b/app/chatbot/page.tsx @@ -1,90 +1,74 @@ "use client"; -import { useState, useEffect, useRef, useCallback } from "react"; +import { useState, useEffect, useRef, useCallback, Suspense } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import ChatInput from "../utils/component/ui/ChatInput"; import MessageWindow from "../utils/component/ui/MessageWindow"; import { ChatHistory, ChatSetting, Message, MessageRole } from "@/app/exportType/types"; export default function Home() { + return ( + Loading chat...}> + + + ); +} + +function ChatbotContent() { const [history, setHistory] = useState([]) const [loading, setLoading] = useState(false); - const historyRef = useRef([]); // Create a ref for history const [settings] = useState({ -<<<<<<< HEAD - temperature:1, - model:"gemini-2.5-pro", - systemInstruction:"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" - -======= 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" + systemInstruction: "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" ->>>>>>> ce9e6f34ed31b7bff736cc9ba9a1b8dcdec85f3a }); const router = useRouter(); 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 currentHistory = [...history, newUserMessage]; + setHistory(currentHistory); + setLoading(true); - const sendApiRequest = async () => { -<<<<<<< HEAD - setLoading(true); - try{ -======= - try { ->>>>>>> ce9e6f34ed31b7bff736cc9ba9a1b8dcdec85f3a - 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, - }), - }); + try { + const response = await fetch("/api/chat", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + userMessage: message, + history: currentHistory, + settings: settings, + }), + }); - const data = await response.json(); + const data = await response.json(); - if (data.error) { - console.error("AI Error:", data.error); - setLoading(false); - return; - } - - const aiMessage: Message = { - role: "model" as MessageRole, - parts: [{ text: data.response }], - }; - - setHistory(prevHistory => [...prevHistory, aiMessage]); - - } - catch (error) { - console.error("Request Failed:", error); - } + if (data.error) { + console.error("AI Error:", data.error); setLoading(false); - }; - sendApiRequest(); + return; + } - return currentHistory; - }); - }, [settings]); // Now handleSend only depends on settings + const aiMessage: Message = { + role: "model" as MessageRole, + parts: [{ text: data.response }], + }; + + setHistory((prevHistory) => [...prevHistory, aiMessage]); + } catch (error) { + console.error("Request Failed:", error); + } + setLoading(false); + }, [history, settings]); // Dependencies for useCallback useEffect(() => { if (!initialMessageSent.current) { @@ -93,61 +77,16 @@ export default function Home() { 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 () => { - setLoading(true); - 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); - setLoading(false); - return; - } - - const aiMessage: Message = { - role: "model" as MessageRole, - parts: [{ text: data.response }], - }; - - setHistory((prevHistory) => [...prevHistory, aiMessage]); - } catch (error) { - console.error("Request Failed:", error); - } - setLoading(false); - }; - - sendInitialApiRequest(); + // eslint-disable-next-line react-hooks/set-state-in-effect + handleSend(message); initialMessageSent.current = true; } } - }, [searchParams, settings]); // Dependencies for this useEffect + }, [searchParams, settings, handleSend]); // Dependencies for this useEffect return (
-<<<<<<< HEAD {}} /> -======= - - { }} /> ->>>>>>> ce9e6f34ed31b7bff736cc9ba9a1b8dcdec85f3a
); } \ No newline at end of file diff --git a/app/components/Cards.tsx b/app/components/Cards.tsx index 322b377..12476c9 100644 --- a/app/components/Cards.tsx +++ b/app/components/Cards.tsx @@ -2,6 +2,7 @@ import React, { useState } from "react"; import { motion, useMotionValue, useTransform } from "framer-motion"; +import { useRouter } from "next/navigation"; // Removed local fallback JSX declaration — use the official React type definitions instead: // install @types/react and @types/react-dom so JSX.IntrinsicElements is provided by those packages. @@ -29,126 +30,49 @@ interface CardData { tracks: string[]; } -const cardData: CardData[] = [ - { - id: 1, - title: " WEBSITE", +interface SwipeCardsProps { + initialCards: CardData[]; +} - summary: - - "FFind out what makes GoonMAXXER unique.With this innovative website you will be able to explore various features and functionalities that set GoonMAXXER apart from the rest.", - - tracks: ["Gemini", "Auth0", "VibeCODING", "SmellyMaxxing", "shitposting"], - - }, - - { - - id: 2, - - title: "Intro to React", - - summary: - - "Learn the fundamentals of React, including components, state, and props.", - - tracks: ["React", "JS", "Frontend"], - - }, - - { - - id: 3, - - title: "State Management", - - summary: - - "Explore complex state solutions with Zustand, Redux, and Context API.", - - tracks: ["React", "Zustand", "State"], - - }, - - { - - id: 4, - - title: "Next.js 14", - - summary: "Master the app router, server actions, and data fetching.", - - tracks: ["Next.js", "React", "Fullstack"], - - }, - - { - - id: 5, - - title: "UI/UX Principles", - - summary: - - "A complete guide to user-centric design, prototyping, and testing.", - - tracks: ["Design", "UI", "UX"], - - }, - - { - - id: 6, - - title: "Node.js Essentials", - - summary: - - "Build scalable, high-performance backend applications with Node.js.", - - tracks: ["Node", "Backend", "JS"], - - }, - - { - - id: 7, - - title: "TypeScript", - - summary: - - "Add static typing to your JavaScript projects for robust, large-scale apps.", - - tracks: ["TypeScript", "JS", "DevOps"], - - }, - - { - - id: 8, - - title: "Framer Motion", - - summary: "Create production-ready animations for your React applications.", - - tracks: ["React", "Animation", "Framer"], - - }, - -].reverse(); - -const SwipeCards: React.FC = () => { - const [cards, setCards] = useState(cardData); +const SwipeCards: React.FC = ({ initialCards }) => { + const [cards, setCards] = useState(initialCards); + const router = useRouter(); const onSwipe = () => { setCards((prev) => prev.slice(0, prev.length - 1)); }; + const handleRightSwipe = async (card: CardData) => { + try { + const response = await fetch("/api/chat", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + userMessage: `Explain the project: ${card.title}. Summary: ${card.summary}. Tracks: ${card.tracks.join(", ")}`, + history: [], // Provide an empty array for history + }), + }); + + if (!response.ok) { + const errorBody = await response.text(); // Read the response body + throw new Error(`HTTP error! status: ${response.status}, body: ${errorBody}`); + } + + // Assuming the chat API handles the message and we can then navigate + router.push(`/chatbot?title=${encodeURIComponent(card.title)}&summary=${encodeURIComponent(card.summary)}`); + } catch (error) { + console.error("Error sending project to chatbot:", error); + // Optionally, handle error state or show a message to the user + } + onSwipe(); // Remove the card after swiping right + }; + const handleReset = () => { - setCards(cardData); + setCards(initialCards); }; return ( @@ -171,6 +95,7 @@ const SwipeCards: React.FC = () => { colorWay={colorWay} isFront={index === cards.length - 1} onSwipe={onSwipe} + onRightSwipe={handleRightSwipe} /> ); }) @@ -199,10 +124,11 @@ interface CardProps { cardData: CardData; colorWay: ColorWay; isFront: boolean; - onSwipe: () => void; + onSwipe: () => void; // For left swipe (reject) + onRightSwipe: (card: CardData) => void; // For right swipe (accept) } -const Card: React.FC = ({ cardData, colorWay, isFront, onSwipe }) => { +const Card: React.FC = ({ cardData, colorWay, isFront, onSwipe, onRightSwipe }) => { const { title, summary, tracks } = cardData; const x = useMotionValue(0); @@ -218,14 +144,18 @@ const Card: React.FC = ({ cardData, colorWay, isFront, onSwipe }) => const badgeRotate = useTransform(x, [-150, 150], [-15, 15]); const handleDragEnd = (event: MouseEvent | TouchEvent | PointerEvent, info: { offset: { x: number } }) => { - if (Math.abs(info.offset.x) > 100) { + if (info.offset.x > 100) { + // Swiped right + onRightSwipe(cardData); + } else if (info.offset.x < -100) { + // Swiped left onSwipe(); } }; return ( = ({ cardData, colorWay, isFront, onSwipe }) => className="absolute left-10 top-10 z-10 rounded-2xl border-8 border-green-500 px-10 py-4" style={{ opacity: acceptOpacity, rotate: badgeRotate }} > -

Accept

+

Accept

-

Reject

+

Reject

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

{title}

{/* This summary

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

{summary}

+

{summary}

{/* This

will also inherit colorWay.text */} -

+

Tracks

{tracks.map((track) => ( { }; }; -<<<<<<< HEAD +export default function Home() { + const [ideaResponse, setIdeaResponse] = useState(""); const [generatedCards, setGeneratedCards] = useState([]); // New state for parsed cards + const [initialState] = useState(getInitialState); + const [showForm, setShowForm] = useState(initialState.showForm); + const initialIdeaGenerationAttempted = useRef(false); const [settings] = useState({ temperature: 1, model: "gemini-2.5-flash-lite", systemInstruction: `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. -======= -// FIX 1 (Memoization): Moved settings OUTSIDE the component. -// This makes it a true constant. -const AI_SETTINGS: ChatSetting = { - temperature: 1, - model: "gemini-2.5-pro", - sysTemInstructions: `You are an elite hackathon idea generator. Your task is to generate five different creative, feasible, and relevant project idea. ->>>>>>> ce9e6f34ed31b7bff736cc9ba9a1b8dcdec85f3a The output MUST be in Markdown format and strictly follow this structure: ### Project Idea Title -<<<<<<< HEAD **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; - }; -======= -**Relevance to Theme:** [Explain briefly] -**Core Technology:** [List 3-5 primary technologies] -**Team Fit:** [How the team's skills apply] -**Concept:** [A concise, single paragraph description of the product and its primary function.] -**Bonus Feature:** [A single, ambitious feature to impress judges.]`, -}; ->>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105 - -// ------------------------------------------------ - -export default function Home() { - - const [initialState] = useState(getInitialState); - const [showForm, setShowForm] = useState(initialState.showForm); - const [formData, setFormData] = useState(initialState.data); - const [ideaResponse, setIdeaResponse] = useState(""); - - // The 'settings' state [useState] is now removed from here. - - const handleFormSubmitFromFormPage = (submittedData: FormData) => { - setFormData(submittedData); - setShowForm(false); - generateIdeas(submittedData); - }; - - // Wrapped generateIdeas in useCallback const generateIdeas = useCallback(async (dataToProcess: FormData) => { - // 1. Data Cleaning + 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) => { + const titleMatch = block.match(/^(.*?)(?=\n\*\*Track\*\*|$)/s); + const trackMatch = block.match(/\*\*Track\*\*\s*:\s*(.*?)(?=\n\*\*Concept\*\*|$)/s); + 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() : ""; + 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 submissionData: FormData = { ...dataToProcess, tracksStack: dataToProcess.tracksStack.filter(tracks => tracks.trim() !== ''), @@ -132,7 +98,6 @@ export default function Home() { .filter(student => student.studentName.trim() !== '' || student.skills.length > 0), }; - // 2. Prompt Construction const prompt = ` Hackathon: ${submissionData.Hackathon} Main Theme: ${submissionData.mainTheme} @@ -143,11 +108,9 @@ export default function Home() { Using the SYSTEM INSTRUCTIONS, generate 5 project ideas based on the data above.`; - // 3. API Call try { setIdeaResponse("Generating ideas... Please wait."); -<<<<<<< HEAD - setGeneratedCards([]); // Clear previous cards + setGeneratedCards([]); const response = await fetch("/api/generate-idea", { method: "POST", headers: { @@ -161,24 +124,10 @@ export default function Home() { const data = await response.json(); -======= - - const response = await fetch("/api/generate-idea", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - prompt: prompt, - settings: AI_SETTINGS, // Use the constant - }), - }); - - const data = await response.json(); ->>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105 if (data.error || !response.ok) { console.error("AI Error:", data.error); setIdeaResponse(`Error: Failed to generate idea. ${data.error || response.statusText}`); return; -<<<<<<< HEAD } setIdeaResponse(data.response); @@ -195,42 +144,21 @@ export default function Home() { } setIdeaResponse(`A network error occurred. Please try again. Details: ${errorMessage}`); } - }, [setIdeaResponse, setGeneratedCards, settings, parseAiResponse]); + }, [settings]); 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'); -======= ->>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105 - } - setIdeaResponse(data.response); - - // FIX 2 (Unexpected any): Changed 'any' to 'unknown' and added a type check. - } catch (error: unknown) { - console.error("Request Failed:", error); - let errorMessage = "A network error occurred. Please try again."; - if (error instanceof Error) { - errorMessage += ` Details: ${error.message}`; - } - setIdeaResponse(errorMessage); - } - // FIX 3 (Linter): Disable the faulty memoization rule. - // The empty array is correct because setIdeaResponse is a stable state setter. - // eslint-disable-next-line react-hooks/preserve-manual-memoization - }, []); - - // Replaced useEffect - useEffect(() => { - if (initialState.runEffect) { + if (initialState.runEffect && !initialIdeaGenerationAttempted.current) { + // eslint-disable-next-line react-hooks/set-state-in-effect generateIdeas(initialState.data); + initialIdeaGenerationAttempted.current = true; } }, [initialState, generateIdeas]); + const handleFormSubmitFromFormPage = (submittedData: FormData) => { + setShowForm(false); + generateIdeas(submittedData); + }; + // --- JSX Rendering --- return (
@@ -239,7 +167,6 @@ export default function Home() { ) : ( <> -<<<<<<< HEAD {generatedCards.length > 0 ? ( ) : ( @@ -257,19 +184,6 @@ export default function Home() {
)} -======= -

- Generated Project Idea 💡 -

-
-

Generated Ideas:

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

Submit the form to generate project ideas.

} -
->>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105
); ->>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105 } - - -// {/* */} - - -// "use client"; - -// import React, { use, useState } from "react"; -// import { useRouter } from "next/navigation"; - -// export default function Dashboard() { -//   const router = useRouter();   -//   const[query, setQuery] = useState(""); -//   // const [projects, setProjects] = useState( [ -//   //     { id: 1, name: "Alpha Project", createdAt: new Date("2025-01-01")}, -//   //     { id: 2, name: "Beta Board", createdAt: new Date("2025-02-15")}, -//   //     { id: 3, name: "Gamma Task", createdAt: new Date("2025-03-10")} -//   // ]); -//   // const [newProjectName, setNewProjectName] = useState(""); -//   // const filtered = projects.filter( (p) => p.name.toLowerCase().includes(query.toLowerCase()) ); -//   // const handleCreate = () => { -//   //   if(!newProjectName.trim()) return; -//   //   const newProject = { -//   //     id: projects.length+1, name: newProjectName, createdAt: new Date(), -//   //   }; -//   // }; -//   const projects = [ -//     {name: "Alpha Project", createdAt: "2025-01-01"}, -//     {name: "Beta Board", createdAt: "2025-02-15"}, -//     {name: "Gamma Tasl", createdAt: "2025-03-10"}, -//   ] -//   const filtered = projects.filter( -//     (p) => p.name.toLowerCase().includes(query.toLowerCase()) -//   ); - -//  return ( -//    
-//      
-//        
-//          
-//            {/* */} -//             setQuery(e.target.value)} -//              placeholder="Search projects, boards or tasks..." -//              className="w-full max-w-md px-4 py-2 rounded-md border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-blue-500" -//            /> -//          
- -//          
-//            {/* setNewProjectName(e.target.value)} -//               placeholder="New Project Name" -//               className="px-3 py-2 rounded-md border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900  text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-blue-500" -//             /> */} -//              -//          
-//        
-//      
- -//      
-//        

-//          Project Names -//        

- -//        
-//          
    -//            {filtered.map((project, index) => ( -//              
  • -//                {project.name} -//                  -//                   {/* {project.createdAt.toLocaleDateString()} */} -//                   Created: {project.createdAt} -//                  -//              
  • -//            ))} - -//            {filtered.length === 0 && ( -//              
  • No results found
  • -//            )} -//          
-//        
-//      
-//    
-//  ); -// } \ No newline at end of file diff --git a/app/utils/component/ui/MessageWindow.tsx b/app/utils/component/ui/MessageWindow.tsx index b0083d3..c618df9 100644 --- a/app/utils/component/ui/MessageWindow.tsx +++ b/app/utils/component/ui/MessageWindow.tsx @@ -3,22 +3,13 @@ import { useRef, useEffect } from "react"; import { ChatHistory } from "@/app/exportType/types"; import { User, Bot } from "lucide-react" -<<<<<<< HEAD interface MessageWindowProps{ history :ChatHistory; isLoading: boolean; } export default function MessageWindow({history, isLoading}:MessageWindowProps){ - const messageEndRef = useRef(null); -======= -interface MessageWindowProps { - history: ChatHistory -} - -export default function MessageWindow({ history }: MessageWindowProps) { const messageEndRef = useRef(null); ->>>>>>> ce9e6f34ed31b7bff736cc9ba9a1b8dcdec85f3a useEffect(() => { messageEndRef.current?.scrollIntoView({ behavior: "smooth" }); @@ -59,7 +50,6 @@ export default function MessageWindow({ history }: MessageWindowProps) { )} -<<<<<<< HEAD ); })} @@ -79,14 +69,7 @@ export default function MessageWindow({ history }: MessageWindowProps) { )} {/* Invisible element to help scroll to bottom */}
-======= - ); - })} - - {/* Invisible element to help scroll to bottom */} -
-
->>>>>>> ce9e6f34ed31b7bff736cc9ba9a1b8dcdec85f3a
+ ); } \ No newline at end of file diff --git a/app/utils/geminiHelper.ts b/app/utils/geminiHelper.ts index 741f563..e5a11a5 100644 --- a/app/utils/geminiHelper.ts +++ b/app/utils/geminiHelper.ts @@ -10,7 +10,6 @@ if (!apiKey) { const genAI = new GoogleGenerativeAI(apiKey); export async function chatToGemini( -<<<<<<< HEAD userMessage:string , history:ChatHistory, settings:ChatSetting @@ -26,23 +25,6 @@ export async function chatToGemini( responseMimeType: "text/plain", maxOutputTokens: 1900, // Increased to allow for longer responses } -======= - userMessage: string, - history: ChatHistory, - settings: ChatSetting -): Promise { - const model = genAI.getGenerativeModel({ - model: settings.model || "gemini-2.5-flash", - systemInstruction: settings.sysTemInstructions || "you are going to create a project idea, explain it in summery first before going to detail", - }); ->>>>>>> ce9e6f34ed31b7bff736cc9ba9a1b8dcdec85f3a - - const generationConfig: GenerateConfig = { - temperature: settings.temperature || 1, - topP: 0.9, - responseMimeType: "text/plain", - maxOutputTokens: 1900, // Increased to allow for longer responses - } const chatSession = model.startChat({ generationConfig, @@ -53,8 +35,12 @@ export async function chatToGemini( const result = await chatSession.sendMessage(userMessage); return result.response.text(); } - catch (error) { - console.error(error) - throw error + catch (error: unknown) { + if (error instanceof Error) { + console.error("Error in chatToGemini:", error.name, error.message, error); + } else { + console.error("Unknown error in chatToGemini:", error); + } + throw error; } } \ No newline at end of file From 45f06bfe8e4cb0ed37f21323d05c62d6780c548b Mon Sep 17 00:00:00 2001 From: Rainyy21 Date: Sun, 9 Nov 2025 03:20:24 -0500 Subject: [PATCH 3/5] change model --- app/chatbot/page.tsx | 2 +- app/components/Cards.tsx | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/chatbot/page.tsx b/app/chatbot/page.tsx index 7cd0e95..36191b5 100644 --- a/app/chatbot/page.tsx +++ b/app/chatbot/page.tsx @@ -19,7 +19,7 @@ function ChatbotContent() { const [loading, setLoading] = useState(false); const [settings] = useState({ temperature: 1, - model: "gemini-2.5-pro", + model: "gemini-2.5-flash", systemInstruction: "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" }); diff --git a/app/components/Cards.tsx b/app/components/Cards.tsx index 12476c9..a4012f5 100644 --- a/app/components/Cards.tsx +++ b/app/components/Cards.tsx @@ -173,6 +173,9 @@ const Card: React.FC = ({ cardData, colorWay, isFront, onSwipe, onRig drag={isFront ? "x" : false} dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }} onDragEnd={handleDragEnd} + initial={{ x: 500, opacity: 0 }} // Start off-screen to the right and invisible + animate={{ x: 0, opacity: 1 }} // Animate to its natural position and fully visible + transition={{ type: "spring", stiffness: 100, damping: 20 }} // Smooth spring animation > {/* Accept/Reject Badges */} Date: Sun, 9 Nov 2025 09:24:47 +0000 Subject: [PATCH 4/5] added localStorage functionality to the entire project, LLM keeps getting overloaded --- app/Dashboard/page.tsx | 189 ++++++--------- app/cards/page.tsx | 139 ++++++++--- app/chatbot/page.tsx | 187 +++++++++------ app/exportType/types.ts | 5 +- app/formfile/page.tsx | 169 +++----------- app/globals.css | 57 +---- app/page.tsx | 281 +---------------------- app/utils/component/ui/MessageWindow.tsx | 22 +- app/utils/geminiHelper.ts | 6 +- app/utils/storage.ts | 94 ++++++++ 10 files changed, 447 insertions(+), 702 deletions(-) create mode 100644 app/utils/storage.ts diff --git a/app/Dashboard/page.tsx b/app/Dashboard/page.tsx index f175e89..0b7adb5 100644 --- a/app/Dashboard/page.tsx +++ b/app/Dashboard/page.tsx @@ -1,24 +1,46 @@ "use client"; -import React, { useState } from "react"; -import { useRouter } from "next/navigation"; +import React, { useState, useEffect } from "react"; +import { getStoredProjects, StoredProject } from "../utils/storage"; import Link from 'next/link'; -function Dashboard() { // 2. Changed from 'export default function' - const router = useRouter(); - const [query, setQuery] = useState(""); - const [sideBarOpen, setSidebarOpen] = useState(true); +export default function Dashboard() { + const [query, setQuery] = useState(""); + const [sideBarOpen, setSidebarOpen] = useState(true); + const [sortOrder, setSortOrder] = useState("newest"); + const [projects, setProjects] = useState([]); - const projects = [ - { name: "Alpha Project", createdAt: "2025-01-01" }, - { name: "Beta Board", createdAt: "2025-02-15" }, - { name: "Gamma Tasl", createdAt: "2025-03-10" }, - ]; - const filtered = projects.filter((p) => - p.name.toLowerCase().includes(query.toLowerCase()) - ); + useEffect(() => { + const storedProjects = getStoredProjects(); + setProjects(storedProjects); + }, []); + + // 5. Sorting logic now uses the state variable + const sortedProjects = [...projects].sort((a, b) => { + const dateA = new Date(a.id).getTime(); // Use 'id' (timestamp) for sorting + const dateB = new Date(b.id).getTime(); + + if (sortOrder === "newest") { + return dateB - dateA; // Newest first + } else { + return dateA - dateB; // Oldest first + } + }); + + // 6. Filter the sorted projects + const filtered = sortedProjects.filter((p) => + p.title.toLowerCase().includes(query.toLowerCase()) + ); + + // 7. Fix the "Create" button link + const handleCreateClick = () => { + window.location.href = "/formfile"; // Changed from "/dashbaord/create" + }; + + const toggleSortOrder = () => { + setSortOrder(currentOrder => (currentOrder === "newest" ? "oldest" : "newest")); + }; -<<<<<<< HEAD return (
@@ -81,7 +96,7 @@ function Dashboard() { // 2. Changed from 'export default function'
    {filtered.map((project, index) => (
  • - - -
    -
    -
    -
    - setQuery(e.target.value)} - placeholder="Search projects, boards or tasks..." - className="w-full max-w-md px-4 py-2 rounded-md border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-blue-500" - /> -
    - -
    - -
    -
    -
    - -
    -

    - Project Names -

    - -
    -
      - {filtered.map((project, index) => ( -
    • - {project.name} - - Created: {project.createdAt} - -
    • - ))} - {filtered.length === 0 && ( -
    • No results found
    • - )} -
    -
    -
    -
    -
- ); + {project.title} + + Created: {new Date(project.id).toLocaleDateString()} + + + ))} + {filtered.length === 0 && ( +
  • {projects.length > 0 ? "No results found" : "No projects yet. Click 'Create' to start!"}
  • + )} + + + +
    + + ); } \ No newline at end of file diff --git a/app/cards/page.tsx b/app/cards/page.tsx index 37faf57..36a50fc 100644 --- a/app/cards/page.tsx +++ b/app/cards/page.tsx @@ -2,13 +2,14 @@ import React, { useState, useEffect } from "react"; import { motion, useMotionValue, useTransform, useAnimation } from "framer-motion"; -import { useRouter } from "next/navigation"; +import { useRouter, useSearchParams } from "next/navigation"; +import { CardData } from "@/app/exportType/types"; // 1. Updated ColorWay interface interface ColorWay { card: string; text: string; - titleColor: string; // Added title color + titleColor: string; } // 2. Updated COLOR_WAYS array @@ -21,21 +22,100 @@ const COLOR_WAYS: ColorWay[] = [ { card: "#B8C0AD", text: "#173C46", titleColor: "#EA627F" }, ]; -interface CardData { - id: number; - title: string; - summary: string; - tracks: string[]; +const parseAiResponse = (markdownString: string): CardData[] => { + const ideas: CardData[] = []; + const ideaBlocks = markdownString.split("### ").filter(block => block.trim() !== ""); + + ideaBlocks.forEach((block, index) => { + const titleMatch = block.match(/^(.*?)\n/); + const conceptMatch = block.match(/\*\*Concept:\*\*\s*(.*)/s); + + // Simple track parsing: find all bolded text that isn't a main header + const tracksMatch = block.matchAll(/\*\*(?!Concept|Relevance|Core|Team|Bonus)(.*?):\*\*/g); + const tracks = [...tracksMatch].map(match => match[1].trim()); + + ideas.push({ + id: index + 1, + title: titleMatch ? titleMatch[1].trim() : `Idea ${index + 1}`, + summary: conceptMatch ? conceptMatch[1].trim() : "No concept provided.", + tracks: tracks.length > 0 ? tracks : ["General"], // Fallback + }); + }); + return ideas; +}; + +export default function CardsPage() { + const [cards, setCards] = useState([]); + const [initialCards, setInitialCards] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const router = useRouter(); + const searchParams = useSearchParams(); + + const projectId = searchParams.get('projectId'); + const projectTitle = searchParams.get('projectTitle'); + useEffect(() => { + if (typeof window === 'undefined') return; + + // **** MODIFIED THIS SECTION **** + // Get the AI response from the form page + const aiResponse = localStorage.getItem('temp_aiResponse'); + if (aiResponse) { + localStorage.removeItem('temp_aiResponse'); // Clean up + const parsedCards = parseAiResponse(aiResponse); + setCards(parsedCards.reverse()); + setInitialCards(parsedCards.reverse()); // Save for reset + } else { + // Handle case where user lands here directly + // Maybe redirect or show an error + console.warn("No AI response found. Using hardcoded cards as fallback."); + const hardcodedCards: CardData[] = [ + { id: 1, title: "AI Project Idea 1", summary: "A cool project...", tracks: ["AI", "Web"] }, + { id: 2, title: "AI Project Idea 2", summary: "Another cool project...", tracks: ["Mobile", "Game"] }, + ]; + setCards(hardcodedCards.reverse()); + setInitialCards(hardcodedCards.reverse()); + } + setIsLoading(false); + }, []); // Runs once on load + if (isLoading) { + return

    Loading cards...

    + } + + if (!projectId || !projectTitle) { + return ( +
    +

    Missing project information. Please start a new project.

    +
    + ) + } + + return ( + + ) } -const SwipeCards: React.FC<{ initialCards: CardData[] }> = ({ initialCards }) => { - const [cards, setCards] = useState(initialCards); - const router = useRouter(); +interface SwipeCardsProps { + initialCards: CardData[]; + currentCards: CardData[]; + setCards: React.Dispatch>; + projectId: string; + projectTitle: string; +} - // Update cards state when initialCards prop changes - useEffect(() => { - setCards(initialCards); - }, [initialCards]); +const SwipeCards: React.FC = ({ + initialCards, + currentCards, + setCards, + projectId, + projectTitle +}) => { + const router = useRouter(); const removeCard = () => { setCards((prev) => prev.slice(0, prev.length - 1)); @@ -44,43 +124,43 @@ const SwipeCards: React.FC<{ initialCards: CardData[] }> = ({ initialCards }) => const handleAccept = (cardData: CardData) => { removeCard(); // Redirect to chatbot with card data - router.push(`/chatbot?title=${encodeURIComponent(cardData.title)}&summary=${encodeURIComponent(cardData.summary)}`); + router.push(`/chatbot?projectId=${encodeURIComponent(projectId)}&projectTitle=${encodeURIComponent(projectTitle)}&cardTitle=${encodeURIComponent(cardData.title)}&cardSummary=${encodeURIComponent(cardData.summary)}`); }; const handleReject = () => { removeCard(); }; + // **** FIXED THIS FUNCTION **** const handleReset = () => { - setCards([]); // Reset to initial cards + // This now resets the deck to the initial set of cards + setCards(initialCards); }; return (
    - {cards.length > 0 ? ( - cards.map((card, index) => { + {currentCards.length > 0 ? ( + currentCards.map((card, index) => { const colorWay = COLOR_WAYS[card.id % COLOR_WAYS.length]; return ( ); }) ) : ( - // ... (empty state)

    No more cards! @@ -126,15 +206,12 @@ const Card: React.FC = ({ cardData, colorWay, isFront, onAccept, onRe 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 + await controls.start({ x: 1000, opacity: 0, transition: { duration: 0.1 } }); + onAccept(cardData); } 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 + await controls.start({ x: -1000, opacity: 0, transition: { duration: 0.1 } }); + onReject(); } 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 } }); } }; @@ -147,7 +224,7 @@ const Card: React.FC = ({ cardData, colorWay, isFront, onAccept, onRe rotate, opacity, backgroundColor: colorWay.card, - color: colorWay.text, // This is the default text color + color: colorWay.text, zIndex: isFront ? 2 : 1, pointerEvents: isFront ? "auto" : "none", }} @@ -205,6 +282,4 @@ const Card: React.FC = ({ cardData, colorWay, isFront, onAccept, onRe ); -}; - -export default SwipeCards; +}; \ No newline at end of file diff --git a/app/chatbot/page.tsx b/app/chatbot/page.tsx index 80f21ae..887a97b 100644 --- a/app/chatbot/page.tsx +++ b/app/chatbot/page.tsx @@ -5,22 +5,47 @@ 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 "@/app/exportType/types"; +import { getChatSessionByProjectId, saveChatSession } from "../utils/storage"; + + +function getInitialChatState( + projectId: string | null, +): ChatHistory { + if (typeof window === 'undefined' || !projectId) { + return []; + } + const existingSession = getChatSessionByProjectId(projectId); + if (existingSession) { + return existingSession.history; + } + return []; +} export default function Home() { - const [history, setHistory] = useState([]) - const historyRef = useRef([]); // Create a ref for history + const searchParams = useSearchParams(); + const projectId = searchParams.get('projectId'); + const projectTitle = searchParams.get('projectTitle'); + const cardTitle = searchParams.get('cardTitle'); + const cardSummary = searchParams.get('cardSummary'); + + const [history, setHistory] = useState(() => getInitialChatState(projectId)); + const [loading, setLoading] = useState(false); + const historyRef = useRef(history); // 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" + 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]); + if (history.length > 0 && projectId && projectTitle) { + saveChatSession(projectId, projectTitle, history); + } + }, [history, projectId, projectTitle]); const handleSend = useCallback(async (message: string) => { const newUserMessage: Message = { @@ -28,24 +53,90 @@ export default function Home() { parts: [{ text: message }], }; - setHistory(prevHistory => { - const currentHistory = [...prevHistory, newUserMessage]; + const currentHistory = [...historyRef.current, newUserMessage]; + setHistory(currentHistory); + setLoading(true); - 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 *before* the new user message + settings: settings, + }), + }); + + const data = await response.json(); + setLoading(false); + + if (data.error) { + console.error("AI Error:", data.error); + // Add an error message to chat + const aiErrorMessage: Message = { + role: "model", + parts: [{ text: `Error: ${data.error}` }], + }; + setHistory(prevHistory => [...prevHistory, aiErrorMessage]); + return; + } + + const aiMessage: Message = { + role: "model" as MessageRole, + parts: [{ text: data.response }], + }; + + // Set the final history with the AI response + // This will trigger the useEffect to save + setHistory(prevHistory => [...prevHistory, aiMessage]); + + } catch (error) { + console.error("Request Failed:", error); + setLoading(false); + const aiErrorMessage: Message = { + role: "model", + parts: [{ text: `Error: Could not connect to AI service.` }], + }; + setHistory(prevHistory => [...prevHistory, aiErrorMessage]); + } + }, [settings, projectId, projectTitle]); // Added projectId and projectTitle + + // This effect handles the *very first* message when selecting a card + useEffect(() => { + // Only run if: + // 1. We have a cardTitle (meaning we came from the cards page) + // 2. The history is empty (it's a new chat) + // 3. We haven't already tried to send this (initialMessageSent ref) + if (cardTitle && cardSummary && history.length === 0 && !initialMessageSent.current) { + initialMessageSent.current = true; // Mark as "attempted" + setLoading(true); + + const message = `Explain the project titled '${cardTitle}' with the summary '${cardSummary}' in detail, and suggest suitable roles for a team working on it.`; + + const newUserMessage: Message = { + role: "user", + parts: [{ text: message }], + }; + + // Add user message to history + // The useEffect for 'history' will save this + setHistory([newUserMessage]); + + const sendInitialApiRequest = async () => { try { const response = await fetch("/api/chat", { method: "POST", - headers: { - "Content-Type": "application/json", - }, + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userMessage: message, - history: historyRef.current, // Use the ref for history + history: [], // Send empty history for the first message settings: settings, }), }); const data = await response.json(); + setLoading(false); if (data.error) { console.error("AI Error:", data.error); @@ -57,78 +148,24 @@ export default function Home() { parts: [{ text: data.response }], }; - setHistory(prevHistory => [...prevHistory, aiMessage]); - - } - catch (error) { + // Add AI response + // The useEffect for 'history' will save this new state + setHistory((prevHistory) => [...prevHistory, aiMessage]); + } catch (error) { console.error("Request Failed:", error); + setLoading(false); } }; - 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; - } + sendInitialApiRequest(); } - }, [searchParams, settings]); // Dependencies for this useEffect + }, [cardTitle, cardSummary, history.length, settings, projectId, projectTitle]); return (
    - + {/* Pass loading state to MessageWindow */} + { }} />
    ); diff --git a/app/exportType/types.ts b/app/exportType/types.ts index d60f588..4399a65 100644 --- a/app/exportType/types.ts +++ b/app/exportType/types.ts @@ -14,16 +14,13 @@ export interface GenerateConfig { temperature: number; topP: number; responseMimeType: string; -<<<<<<< HEAD maxOutputTokens?: number; -======= ->>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105 } export interface ChatSetting { temperature: number; model: string; - sysTemInstructions: string; + systemInstructions: string; } export interface CardData { diff --git a/app/formfile/page.tsx b/app/formfile/page.tsx index a4abdf2..822b326 100644 --- a/app/formfile/page.tsx +++ b/app/formfile/page.tsx @@ -1,10 +1,12 @@ "use client"; + import React, { useState, useEffect, useCallback } from "react"; import FormPage from './FormPage'; import { ChatSetting } from "../exportType/types"; import { FormData, PROFICIENCY_LEVELS } from './formTypes'; +import { saveProject } from '../utils/storage'; +import { useRouter } from "next/navigation"; -// --- Helper function to get default form data --- const getDefaultFormData = (): FormData => ({ Hackathon: '', mainTheme: '', @@ -15,7 +17,6 @@ const getDefaultFormData = (): FormData => ({ }], }); -// --- New Initializer Function --- const getInitialState = () => { if (typeof window === 'undefined') { return { @@ -29,8 +30,8 @@ const getInitialState = () => { localStorage.removeItem('formData'); return { data: JSON.parse(storedFormData) as FormData, - showForm: false, - runEffect: true, + showForm: true, + runEffect: false, }; } return { @@ -40,58 +41,19 @@ const getInitialState = () => { }; }; -// FIX 1 (Memoization): Moved settings OUTSIDE the component. -// This makes it a true constant. + const AI_SETTINGS: ChatSetting = { temperature: 1, model: "gemini-2.5-pro", - sysTemInstructions: `You are an elite hackathon idea generator. Your task is to generate five different creative, feasible, and relevant project idea. -The output MUST be in Markdown format and strictly follow this structure: -### Project Idea Title -<<<<<<< HEAD -**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; - }; -======= -**Relevance to Theme:** [Explain briefly] -**Core Technology:** [List 3-5 primary technologies] -**Team Fit:** [How the team's skills apply] -**Concept:** [A concise, single paragraph description of the product and its primary function.] -**Bonus Feature:** [A single, ambitious feature to impress judges.]`, + systemInstructions: `You are an elite hackathon idea generator. Your task is to generate five different creative, feasible, and relevant project idea. + The output MUST be in Markdown format and strictly follow this structure: + ### Project Idea Title + **Relevance to Theme:** [Explain briefly] + **Core Technology:** [List 3-5 primary technologies] + **Team Fit:** [How the team's skills apply] + **Concept:** [A concise, single paragraph description of the product and its primary function.] + **Bonus Feature:** [A single, ambitious feature to impress judges.]` }; ->>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105 - -// ------------------------------------------------ export default function Home() { @@ -100,17 +62,19 @@ export default function Home() { const [formData, setFormData] = useState(initialState.data); const [ideaResponse, setIdeaResponse] = useState(""); - // The 'settings' state [useState] is now removed from here. + const router = useRouter(); const handleFormSubmitFromFormPage = (submittedData: FormData) => { + const savedProject = saveProject(submittedData); + + if (typeof window !== 'undefined') localStorage.removeItem('formData'); + setFormData(submittedData); setShowForm(false); - generateIdeas(submittedData); + generateIdeas(submittedData, savedProject.id); }; - // Wrapped generateIdeas in useCallback - const generateIdeas = useCallback(async (dataToProcess: FormData) => { - // 1. Data Cleaning + const generateIdeas = useCallback(async (dataToProcess: FormData, projectId: string) => { const submissionData: FormData = { ...dataToProcess, tracksStack: dataToProcess.tracksStack.filter(tracks => tracks.trim() !== ''), @@ -122,7 +86,6 @@ export default function Home() { .filter(student => student.studentName.trim() !== '' || student.skills.length > 0), }; - // 2. Prompt Construction const prompt = ` Hackathon: ${submissionData.Hackathon} Main Theme: ${submissionData.mainTheme} @@ -136,71 +99,29 @@ export default function Home() { // 3. API Call try { setIdeaResponse("Generating ideas... Please wait."); -<<<<<<< HEAD - 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(); - -======= const response = await fetch("/api/generate-idea", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt: prompt, - settings: AI_SETTINGS, // Use the constant + settings: AI_SETTINGS, }), }); const data = await response.json(); ->>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105 + if (data.error || !response.ok) { console.error("AI Error:", data.error); setIdeaResponse(`Error: Failed to generate idea. ${data.error || response.statusText}`); return; -<<<<<<< HEAD - } - - 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'); -======= ->>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105 } setIdeaResponse(data.response); - // FIX 2 (Unexpected any): Changed 'any' to 'unknown' and added a type check. + if (typeof window !== 'undefined') localStorage.setItem('temp_aiResponse', data.response); + + router.push(`/cards?projectId=${projectId}&projectTitle=${encodeURIComponent(dataToProcess.Hackathon)}`); + } catch (error: unknown) { console.error("Request Failed:", error); let errorMessage = "A network error occurred. Please try again."; @@ -209,15 +130,12 @@ export default function Home() { } setIdeaResponse(errorMessage); } - // FIX 3 (Linter): Disable the faulty memoization rule. - // The empty array is correct because setIdeaResponse is a stable state setter. - // eslint-disable-next-line react-hooks/preserve-manual-memoization - }, []); + }, [router]); // Replaced useEffect useEffect(() => { if (initialState.runEffect) { - generateIdeas(initialState.data); + //generateIdeas(initialState.data, "some-resumed-id"); } }, [initialState, generateIdeas]); @@ -229,25 +147,6 @@ export default function Home() { ) : ( <> -<<<<<<< HEAD - {generatedCards.length > 0 ? ( - - ) : ( - <> -

    - Generated Project Idea 💡 -

    -
    -

    Generated Ideas:

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

    Submit the form to generate project ideas.

    } -
    - - )} -=======

    Generated Project Idea 💡

    @@ -259,12 +158,16 @@ export default function Home() { /> {ideaResponse === "" &&

    Submit the form to generate project ideas.

    }
    ->>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105 )} diff --git a/app/globals.css b/app/globals.css index 63852fc..aa46b72 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,35 +1,3 @@ -<<<<<<< HEAD -======= -/* @import "tailwindcss"; - -:root { - --background: #ffffff; - --foreground: #171717; -} - -:root { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; -} */ -/* 1. Import BOTH fonts in one go */ -@import url('https://fonts.googleapis.com/css2?family=Concert+One&family=Pixelify+Sans:wght@400;700&display=swap'); - ->>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105 @import "tailwindcss"; :root { @@ -40,22 +8,8 @@ body { @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - - /* 3. Assign fonts to Tailwind's theme */ - -<<<<<<< HEAD - /* Tell Tailwind to use the variables already defined in app/layout.tsx */ - --font-sans: var(--font-sans); - --font-header: var(--font-header); -======= - /* --font-sans is for regular text (Concert One) */ --font-sans: var(--font-concert-one); - - /* We add a new font family for headers (Pixelify Sans) */ - /* This also creates a 'font-header' utility class */ --font-header: var(--font-pixelify); ->>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105 - --font-mono: var(--font-geist-mono); } @@ -69,17 +23,8 @@ body { body { background: var(--background); color: var(--foreground); - - /* 4. Body uses --font-sans (Concert One) by default */ font-family: var(--font-sans); } - -/* 5. (Optional but helpful) Apply header font to h-tags by default */ -h1, -h2, -h3, -h4, -h5, -h6 { +h1,h2,h3,h4,h5,h6 { font-family: var(--font-header); } \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index fc85691..68c3a5e 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,4 +1,5 @@ import Image from "next/image"; +import Link from "next/link"; export default function Home() { return ( @@ -16,135 +17,12 @@ export default function Home() { priority />
    - - Sign in - - - Register - + + Dashboard +
    -<<<<<<< HEAD -export default function Page() { - return ( -
    -
    - logo -
    - - Sign in - - - Log out - -
    -
    - -
    -
    - -
    -

    - (project name) -

    -

    - Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

    - - Get Started -
    - -
    -
    - Image Placeholder -
    -
    -
    - -
    -
    -

    - Guide: -

    -
    - -
    -
    -

    - First Text Block -

    -

    - This is the content for the first text block. You can write anything here. -

    -
    -
    -
    - Image Placeholder -
    -
    -
    - -
    -
    -
    - Image Placeholder -
    -
    - -
    -
    -

    - Second Text Block -

    -

    - This is the content for the second text block. You can write anything here. -

    -
    -
    -
    - -
    -
    -

    - Third Text Block -

    -

    - This is the content for the third text block. You can write anything here. -

    -
    -
    -
    - Image Placeholder -
    -
    -
    -
    -
    -
    - ); -=======
    @@ -159,30 +37,15 @@ export default function Page() { className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400" > Got a hackathon coming up? Tell us your track, your team’s skills, what you’re passionate about, and more — and we’ll provide you with the perfect project idea!{" "} - {/* -                Templates -              {" "} -              or the{" "} -               -                Learning -              {" "} */} - {/* center. */}

    - {/* Get Started */} - +
    @@ -279,132 +142,4 @@ export default function Page() {

    ); ->>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105 -} - - -// {/* */} - - -// "use client"; - -// import React, { use, useState } from "react"; -// import { useRouter } from "next/navigation"; - -// export default function Dashboard() { -//   const router = useRouter();   -//   const[query, setQuery] = useState(""); -//   // const [projects, setProjects] = useState( [ -//   //     { id: 1, name: "Alpha Project", createdAt: new Date("2025-01-01")}, -//   //     { id: 2, name: "Beta Board", createdAt: new Date("2025-02-15")}, -//   //     { id: 3, name: "Gamma Task", createdAt: new Date("2025-03-10")} -//   // ]); -//   // const [newProjectName, setNewProjectName] = useState(""); -//   // const filtered = projects.filter( (p) => p.name.toLowerCase().includes(query.toLowerCase()) ); -//   // const handleCreate = () => { -//   //   if(!newProjectName.trim()) return; -//   //   const newProject = { -//   //     id: projects.length+1, name: newProjectName, createdAt: new Date(), -//   //   }; -//   // }; -//   const projects = [ -//     {name: "Alpha Project", createdAt: "2025-01-01"}, -//     {name: "Beta Board", createdAt: "2025-02-15"}, -//     {name: "Gamma Tasl", createdAt: "2025-03-10"}, -//   ] -//   const filtered = projects.filter( -//     (p) => p.name.toLowerCase().includes(query.toLowerCase()) -//   ); - -//  return ( -//    
    -//      
    -//        
    -//          
    -//            {/* */} -//             setQuery(e.target.value)} -//              placeholder="Search projects, boards or tasks..." -//              className="w-full max-w-md px-4 py-2 rounded-md border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-blue-500" -//            /> -//          
    - -//          
    -//            {/* setNewProjectName(e.target.value)} -//               placeholder="New Project Name" -//               className="px-3 py-2 rounded-md border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900  text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-blue-500" -//             /> */} -//              -//          
    -//        
    -//      
    - -//      
    -//        

    -//          Project Names -//        

    - -//        
    -//          
      -//            {filtered.map((project, index) => ( -//              
    • -//                {project.name} -//                  -//                   {/* {project.createdAt.toLocaleDateString()} */} -//                   Created: {project.createdAt} -//                  -//              
    • -//            ))} - -//            {filtered.length === 0 && ( -//              
    • No results found
    • -//            )} -//          
    -//        
    -//      
    -//    
    -//  ); -// } \ No newline at end of file +} \ No newline at end of file diff --git a/app/utils/component/ui/MessageWindow.tsx b/app/utils/component/ui/MessageWindow.tsx index e78839d..9967add 100644 --- a/app/utils/component/ui/MessageWindow.tsx +++ b/app/utils/component/ui/MessageWindow.tsx @@ -4,15 +4,16 @@ import { ChatHistory } from "@/app/exportType/types"; import { User, Bot } from "lucide-react" interface MessageWindowProps { - history: ChatHistory + history: ChatHistory; + isLoading: boolean; } -export default function MessageWindow({ history }: MessageWindowProps) { +export default function MessageWindow({ history, isLoading }: MessageWindowProps) { const messageEndRef = useRef(null); useEffect(() => { messageEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }, [history]); + }, [history, isLoading]); return (
    @@ -51,6 +52,21 @@ export default function MessageWindow({ history }: MessageWindowProps) {
    ); })} + {isLoading && ( +
    +
    +
    + +
    +
    +
    +
    + {/* Simple loading dots animation */} + ... +
    +
    +
    + )} {/* Invisible element to help scroll to bottom */}
    diff --git a/app/utils/geminiHelper.ts b/app/utils/geminiHelper.ts index 18987fe..c05143e 100644 --- a/app/utils/geminiHelper.ts +++ b/app/utils/geminiHelper.ts @@ -16,7 +16,7 @@ export async function chatToGemini( ): Promise { const model = genAI.getGenerativeModel({ model: settings.model || "gemini-2.5-flash", - systemInstruction: settings.sysTemInstructions || "you are going to create a project idea, explain it in summery first before going to detail", + systemInstruction: settings.systemInstructions || "you are going to create a project idea, explain it in summery first before going to detail", }); const generationConfig: GenerateConfig = { @@ -36,7 +36,7 @@ export async function chatToGemini( return result.response.text(); } catch (error) { - console.error(error) - throw error + console.error("Gemini API Error:", error); + throw error; } } \ No newline at end of file diff --git a/app/utils/storage.ts b/app/utils/storage.ts new file mode 100644 index 0000000..887c8ba --- /dev/null +++ b/app/utils/storage.ts @@ -0,0 +1,94 @@ +import { FormData } from "@/app/formfile/formTypes"; +import { ChatHistory } from "@/app/exportType/types"; + +// 1. Define the data structures for storage +export interface StoredProject extends FormData { + id: string; + title: string; +} + +export interface StoredChatSession { + id: string; + projectId: string; + projectTitle: string; + history: ChatHistory; +} + +export interface UserData { + projects: StoredProject[]; + chatSessions: StoredChatSession[]; +} + +const STORAGE_KEY = "hackSparkUserData"; + +function safeGetLocalStorage(): UserData { + if (typeof window === 'undefined') { + return { projects: [], chatSessions: [] }; + } + try { + const data = localStorage.getItem(STORAGE_KEY); + return data ? JSON.parse(data) : { projects: [], chatSessions: [] }; + } catch (error) { + console.error("Failed to parse localStorage data:", error); + return { projects: [], chatSessions: [] }; + } +} + +//Retrieves all stored projects. +export function getStoredProjects(): StoredProject[] { + return safeGetLocalStorage().projects; +} + + +//Retrieves all stored chat sessions. +export function getStoredChatSessions(): StoredChatSession[] { + return safeGetLocalStorage().chatSessions; +} + +//Finds a specific chat session by its project ID. +export function getChatSessionByProjectId(projectId: string): StoredChatSession | undefined { + if (typeof window === 'undefined') return undefined; + const data = safeGetLocalStorage(); + return data.chatSessions.find(session => session.projectId === projectId); +} + +export function saveProject(formData: FormData): StoredProject { + if (typeof window === 'undefined') throw new Error("localStorage is not available."); + + const data = safeGetLocalStorage(); + const newProject: StoredProject = { + ...formData, + id: new Date().toISOString(), + title: formData.Hackathon || "Untitled Project", + }; + + data.projects.push(newProject); + localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); + return newProject; +} + +export function saveChatSession(projectId: string, projectTitle: string, history: ChatHistory) { + if (typeof window === 'undefined') return; + + const data = safeGetLocalStorage(); + const existingSessionIndex = data.chatSessions.findIndex(session => session.projectId === projectId); + + if (existingSessionIndex > -1) { + data.chatSessions[existingSessionIndex].history = history; + } else { + const newSession: StoredChatSession = { + id: new Date().toISOString(), + projectId, + projectTitle, + history, + }; + data.chatSessions.push(newSession); + } + + localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); +} + +export function setProjectForEditing(project: StoredProject) { + if (typeof window === 'undefined') return; + localStorage.setItem('formData', JSON.stringify(project)); +} \ No newline at end of file From ae0ee812c5d3c4784217cc99dc82ec207464de78 Mon Sep 17 00:00:00 2001 From: Ivan Rudik Date: Sun, 9 Nov 2025 10:04:21 +0000 Subject: [PATCH 5/5] minor tweak to page.tsx --- app/formfile/page.tsx | 115 +++++++++++++++++++++++++++--------------- 1 file changed, 75 insertions(+), 40 deletions(-) diff --git a/app/formfile/page.tsx b/app/formfile/page.tsx index 3e093c5..614722c 100644 --- a/app/formfile/page.tsx +++ b/app/formfile/page.tsx @@ -1,8 +1,8 @@ "use client"; - -import React, { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback, useRef } from "react"; import FormPage from './FormPage'; import { CardData } from "../exportType/types"; +import SwipeCards from "../components/Cards"; import { ChatSetting } from "../exportType/types"; import { FormData, PROFICIENCY_LEVELS } from './formTypes'; import { saveProject } from '../utils/storage'; @@ -42,35 +42,53 @@ const getInitialState = () => { }; }; -const AI_SETTINGS: ChatSetting = { - temperature: 1, - model: "gemini-2.5-pro", - systemInstructions: `You are an elite hackathon idea generator. Your task is to generate five different creative, feasible, and relevant project idea. - The output MUST be in Markdown format and strictly follow this structure: - ### Project Idea Title - **Relevance to Theme:** [Explain briefly] - **Core Technology:** [List 3-5 primary technologies] - **Team Fit:** [How the team's skills apply] - **Concept:** [A concise, single paragraph description of the product and its primary function.] - **Bonus Feature:** [A single, ambitious feature to impress judges.]` -}; - export default function Home() { + + const [formData, setFormData] = useState(getDefaultFormData()); + const [ideaResponse, setIdeaResponse] = useState(""); + const [generatedCards, setGeneratedCards] = useState([]); // New state for parsed cards const [initialState] = useState(getInitialState); const [showForm, setShowForm] = useState(initialState.showForm); - const [formData, setFormData] = useState(initialState.data); - const [ideaResponse, setIdeaResponse] = useState(""); + const initialIdeaGenerationAttempted = useRef(false); + const router = useRouter(); + 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 handleFormSubmitFromFormPage = (submittedData: FormData) => { - const savedProject = saveProject(submittedData); - if (typeof window !== 'undefined') localStorage.removeItem('formData'); - setFormData(submittedData); - setShowForm(false); - generateIdeas(submittedData, savedProject.id); - }; + const generateIdeas = useCallback(async (dataToProcess: FormData) => { + 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) => { + const titleMatch = block.match(/^(.*?)(?=\n\*\*Track\*\*|$)/s); + const trackMatch = block.match(/\*\*Track\*\*\s*:\s*(.*?)(?=\n\*\*Concept\*\*|$)/s); + 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() : ""; + 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 generateIdeas = useCallback(async (dataToProcess: FormData, projectId: string) => { const submissionData: FormData = { ...dataToProcess, tracksStack: dataToProcess.tracksStack.filter(tracks => tracks.trim() !== ''), @@ -94,13 +112,15 @@ export default function Home() { try { setIdeaResponse("Generating ideas... Please wait."); - + setGeneratedCards([]); const response = await fetch("/api/generate-idea", { method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + }, body: JSON.stringify({ prompt: prompt, - settings: AI_SETTINGS, + settings: settings, }), }); @@ -113,6 +133,8 @@ export default function Home() { } setIdeaResponse(data.response); + const projectId = data.projectId ?? `p-${Date.now()}`; + if (typeof window !== 'undefined') localStorage.setItem('temp_aiResponse', data.response); router.push(`/cards?projectId=${projectId}&projectTitle=${encodeURIComponent(dataToProcess.Hackathon)}`); @@ -127,12 +149,19 @@ export default function Home() { } }, [router]); + // Replaced useEffect useEffect(() => { if (initialState.runEffect) { - // generateIdeas(initialState.data, "some-resumed-id"); + //generateIdeas(initialState.data, "some-resumed-id"); } }, [initialState, generateIdeas]); + const handleFormSubmitFromFormPage = (submittedData: FormData) => { + setShowForm(false); + generateIdeas(submittedData); + }; + + // --- JSX Rendering --- return (
    @@ -140,17 +169,23 @@ export default function Home() { ) : ( <> -

    - Generated Project Idea 💡 -

    -
    -

    Generated Ideas:

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

    Submit the form to generate project ideas.

    } -
    + {generatedCards.length > 0 ? ( + + ) : ( + <> +

    + Generated Project Idea 💡 +

    +
    +

    Generated Ideas:

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

    Submit the form to generate project ideas.

    } +
    + + )}