Files
SBU-Hackathon/app/formfile/page.tsx
T
Rainyy21 507b256943 testing
2025-11-09 01:43:33 -05:00

284 lines
11 KiB
TypeScript

"use client";
import React, { useState, useEffect, useCallback } from "react";
import FormPage from './FormPage';
import { ChatSetting } from "../exportType/types";
import { FormData, PROFICIENCY_LEVELS } from './formTypes';
// --- Helper function to get default form data ---
const getDefaultFormData = (): FormData => ({
Hackathon: '',
mainTheme: '',
tracksStack: [''],
students: [{
studentName: '',
skills: [{ skillName: '', proficiency: PROFICIENCY_LEVELS[1] }]
}],
});
// --- New Initializer Function ---
const getInitialState = () => {
if (typeof window === 'undefined') {
return {
data: getDefaultFormData(),
showForm: true,
runEffect: false
};
}
const storedFormData = localStorage.getItem('formData');
if (storedFormData) {
localStorage.removeItem('formData');
return {
data: JSON.parse(storedFormData) as FormData,
showForm: false,
runEffect: true,
};
}
return {
data: getDefaultFormData(),
showForm: true,
runEffect: false
};
};
<<<<<<< HEAD
const [ideaResponse, setIdeaResponse] = useState<string>("");
const [generatedCards, setGeneratedCards] = useState<CardData[]>([]); // New state for parsed cards
const [settings] = useState<ChatSetting>({
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<FormData>(initialState.data);
const [ideaResponse, setIdeaResponse] = useState<string>("");
// 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 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),
};
// 2. Prompt Construction
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.`;
// 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
}),
});
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.
} 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) {
generateIdeas(initialState.data);
}
}, [initialState, generateIdeas]);
// --- JSX Rendering ---
return (
<main className="min-h-screen bg-gray-50 flex items-start justify-center p-4 sm:p-6 font-inter">
<div className="w-full max-w-2xl bg-white p-8 shadow-2xl rounded-xl mt-8">
{showForm ? (
<FormPage onFormSubmit={handleFormSubmitFromFormPage} />
) : (
<>
<<<<<<< HEAD
{generatedCards.length > 0 ? (
<SwipeCards initialCards={generatedCards} />
) : (
<>
<h1 className="text-3xl font-extrabold text-center text-gray-800 mb-8 tracking-tight">
Generated Project Idea 💡
</h1>
<div className="mt-8 p-4 bg-yellow-50 rounded-lg border border-yellow-200">
<h2 className="text-xl font-bold text-gray-700 mb-2">Generated Ideas:</h2>
<div
className="prose max-w-none"
dangerouslySetInnerHTML={{ __html: ideaResponse.replace(/\n/g, '<br/>') }}
/>
{ideaResponse === "" && <p className="text-gray-500">Submit the form to generate project ideas.</p>}
</div>
</>
)}
=======
<h1 className="text-3xl font-extrabold text-center text-gray-800 mb-8 tracking-tight">
Generated Project Idea 💡
</h1>
<div className="mt-8 p-4 bg-yellow-50 rounded-lg border border-yellow-200">
<h2 className="text-xl font-bold text-gray-700 mb-2">Generated Ideas:</h2>
<div
className="prose max-w-none"
dangerouslySetInnerHTML={{ __html: ideaResponse.replace(/\n/g, '<br/>') }}
/>
{ideaResponse === "" && <p className="text-gray-500">Submit the form to generate project ideas.</p>}
</div>
>>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105
<button
onClick={() => setShowForm(true)}
className="mt-6 w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-lg text-lg font-semibold text-white bg-blue-600 hover:bg-blue-700 transition duration-150"
>
Go Back to Form
</button>
</>
)}
</div>
</main>
);
}