added localStorage functionality to the entire project, LLM keeps getting overloaded

This commit is contained in:
Ivan Rudik
2025-11-09 09:24:47 +00:00
parent 6421160279
commit 64be41f894
10 changed files with 447 additions and 702 deletions
+36 -133
View File
@@ -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<FormData>(initialState.data);
const [ideaResponse, setIdeaResponse] = useState<string>("");
// 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() {
<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>
@@ -259,12 +158,16 @@ export default function Home() {
/>
{ideaResponse === "" && <p className="text-gray-500">Submit the form to generate project ideas.</p>}
</div>
>>>>>>> 44b5a4dc49ea757e9790ba8309edc6b37d764105
<button
onClick={() => setShowForm(true)}
onClick={() => {
setShowForm(true);
if(typeof window !== 'undefined') localStorage.removeItem('formData');
setFormData(getDefaultFormData());
}
}
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
Create New Project
</button>
</>
)}