mirror of
https://github.com/PinkSheep27/SBU-Hackathon.git
synced 2026-10-10 23:50:26 -04:00
Merge branch 'main' of https://github.com/PinkSheep27/SBU-Hackathon
This commit is contained in:
+28
-23
@@ -15,26 +15,23 @@ export default function Dashboard() {
|
|||||||
setProjects(storedProjects);
|
setProjects(storedProjects);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 5. Sorting logic now uses the state variable
|
|
||||||
const sortedProjects = [...projects].sort((a, b) => {
|
const sortedProjects = [...projects].sort((a, b) => {
|
||||||
const dateA = new Date(a.id).getTime(); // Use 'id' (timestamp) for sorting
|
const dateA = new Date(a.id).getTime();
|
||||||
const dateB = new Date(b.id).getTime();
|
const dateB = new Date(b.id).getTime();
|
||||||
|
|
||||||
if (sortOrder === "newest") {
|
if (sortOrder === "newest") {
|
||||||
return dateB - dateA; // Newest first
|
return dateB - dateA;
|
||||||
} else {
|
} else {
|
||||||
return dateA - dateB; // Oldest first
|
return dateA - dateB;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 6. Filter the sorted projects
|
|
||||||
const filtered = sortedProjects.filter((p) =>
|
const filtered = sortedProjects.filter((p) =>
|
||||||
p.title.toLowerCase().includes(query.toLowerCase())
|
p.title.toLowerCase().includes(query.toLowerCase())
|
||||||
);
|
);
|
||||||
|
|
||||||
// 7. Fix the "Create" button link
|
|
||||||
const handleCreateClick = () => {
|
const handleCreateClick = () => {
|
||||||
window.location.href = "/formfile"; // Changed from "/dashbaord/create"
|
window.location.href = "/formfile";
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleSortOrder = () => {
|
const toggleSortOrder = () => {
|
||||||
@@ -77,6 +74,12 @@ export default function Dashboard() {
|
|||||||
>
|
>
|
||||||
Settings
|
Settings
|
||||||
</p>
|
</p>
|
||||||
|
<Link
|
||||||
|
href="/auth/logout"
|
||||||
|
className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
@@ -106,25 +109,27 @@ export default function Dashboard() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main className="max-w-7xl w-full mx-auto px-6 py-10">
|
<main className="max-w-7xl w-full mx-auto px-6 py-10">
|
||||||
<h1 className="text-2xl font-semibold text-black dark:text-zinc-50 mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
Project Names
|
<h1 className="text-2xl font-semibold text-black dark:text-zinc-50">
|
||||||
</h1>
|
Project Names
|
||||||
<button
|
</h1>
|
||||||
type="button"
|
<button
|
||||||
onClick={toggleSortOrder}
|
type="button"
|
||||||
className="px-3 py-1 text-sm font-medium border rounded-md"
|
onClick={toggleSortOrder}
|
||||||
style={{
|
className="px-3 py-1 text-sm font-medium border rounded-md"
|
||||||
color: '#333',
|
style={{
|
||||||
backgroundColor: '#F7F7F7',
|
color: '#333',
|
||||||
borderColor: '#D1D1D1'
|
backgroundColor: '#F7F7F7',
|
||||||
}}
|
borderColor: '#D1D1D1'
|
||||||
>
|
}}
|
||||||
Sort by: {sortOrder === 'newest' ? 'Newest' : 'Oldest'}
|
>
|
||||||
</button>
|
Sort by: {sortOrder === 'newest' ? 'Newest' : 'Oldest'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<ul className="space-y-3">
|
<ul className="space-y-3">
|
||||||
{filtered.map((project, index) => (
|
{filtered.map((project) => (
|
||||||
<li
|
<li
|
||||||
key={project.id}
|
key={project.id}
|
||||||
className="flex justify-between p-4 bg-white dark:bg-zinc-900 border border-zinc-100 dark:border-zinc-800 rounded-md text-zinc-800 dark:text-zinc-100"
|
className="flex justify-between p-4 bg-white dark:bg-zinc-900 border border-zinc-100 dark:border-zinc-800 rounded-md text-zinc-800 dark:text-zinc-100"
|
||||||
|
|||||||
@@ -8,17 +8,19 @@ export async function POST(request: Request) {
|
|||||||
const { userMessage, history, settings } = await request.json() as {
|
const { userMessage, history, settings } = await request.json() as {
|
||||||
userMessage: string;
|
userMessage: string;
|
||||||
history: ChatHistory;
|
history: ChatHistory;
|
||||||
settings: ChatSetting;
|
settings?: ChatSetting; // settings is optional from the request
|
||||||
};
|
};
|
||||||
|
|
||||||
const aiResponse = await chatToGemini(userMessage, history, settings);
|
// Call the Gemini helper function
|
||||||
return NextResponse.json({ response: aiResponse });
|
const geminiResponse = await chatToGemini(userMessage, history, settings || {});
|
||||||
|
|
||||||
|
return NextResponse.json({ response: geminiResponse });
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "Error with the model response " },
|
{error: "Error with the model response " + String(error)},
|
||||||
{ status: 500 }
|
{status : 500}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-35
@@ -5,17 +5,15 @@ import { motion, useMotionValue, useTransform, useAnimation } from "framer-motio
|
|||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { CardData } from "@/app/exportType/types";
|
import { CardData } from "@/app/exportType/types";
|
||||||
|
|
||||||
// 1. Updated ColorWay interface
|
|
||||||
interface ColorWay {
|
interface ColorWay {
|
||||||
card: string;
|
card: string;
|
||||||
text: string;
|
text: string;
|
||||||
titleColor: string;
|
titleColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Updated COLOR_WAYS array
|
|
||||||
const COLOR_WAYS: ColorWay[] = [
|
const COLOR_WAYS: ColorWay[] = [
|
||||||
{ card: "#FEEEDF", text: "#173C46", titleColor: "#173C46" },
|
{ card: "#FEEEDF", text: "#173C46", titleColor: "#173C46" },
|
||||||
{ card: "#FEEEDF", text: "#EA627F", titleColor: "#173C46" }, // Example: Title is dark, text is pink
|
{ card: "#FEEEDF", text: "#EA627F", titleColor: "#173C46" },
|
||||||
{ card: "#DEE5D4", text: "#173C46", titleColor: "#EA627F" },
|
{ card: "#DEE5D4", text: "#173C46", titleColor: "#EA627F" },
|
||||||
{ card: "#DEE5D4", text: "#EA627F", titleColor: "#173C46" },
|
{ card: "#DEE5D4", text: "#EA627F", titleColor: "#173C46" },
|
||||||
{ card: "#B8C0AD", text: "#173C46", titleColor: "#173C46" },
|
{ card: "#B8C0AD", text: "#173C46", titleColor: "#173C46" },
|
||||||
@@ -29,8 +27,6 @@ const parseAiResponse = (markdownString: string): CardData[] => {
|
|||||||
ideaBlocks.forEach((block, index) => {
|
ideaBlocks.forEach((block, index) => {
|
||||||
const titleMatch = block.match(/^(.*?)\n/);
|
const titleMatch = block.match(/^(.*?)\n/);
|
||||||
const conceptMatch = block.match(/\*\*Concept:\*\*\s*(.*)/s);
|
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 tracksMatch = block.matchAll(/\*\*(?!Concept|Relevance|Core|Team|Bonus)(.*?):\*\*/g);
|
||||||
const tracks = [...tracksMatch].map(match => match[1].trim());
|
const tracks = [...tracksMatch].map(match => match[1].trim());
|
||||||
|
|
||||||
@@ -38,7 +34,7 @@ const parseAiResponse = (markdownString: string): CardData[] => {
|
|||||||
id: index + 1,
|
id: index + 1,
|
||||||
title: titleMatch ? titleMatch[1].trim() : `Idea ${index + 1}`,
|
title: titleMatch ? titleMatch[1].trim() : `Idea ${index + 1}`,
|
||||||
summary: conceptMatch ? conceptMatch[1].trim() : "No concept provided.",
|
summary: conceptMatch ? conceptMatch[1].trim() : "No concept provided.",
|
||||||
tracks: tracks.length > 0 ? tracks : ["General"], // Fallback
|
tracks: tracks.length > 0 ? tracks : ["General"],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return ideas;
|
return ideas;
|
||||||
@@ -53,20 +49,17 @@ export default function CardsPage() {
|
|||||||
|
|
||||||
const projectId = searchParams.get('projectId');
|
const projectId = searchParams.get('projectId');
|
||||||
const projectTitle = searchParams.get('projectTitle');
|
const projectTitle = searchParams.get('projectTitle');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') return;
|
||||||
|
|
||||||
// **** MODIFIED THIS SECTION ****
|
|
||||||
// Get the AI response from the form page
|
|
||||||
const aiResponse = localStorage.getItem('temp_aiResponse');
|
const aiResponse = localStorage.getItem('temp_aiResponse');
|
||||||
if (aiResponse) {
|
if (aiResponse) {
|
||||||
localStorage.removeItem('temp_aiResponse'); // Clean up
|
localStorage.removeItem('temp_aiResponse');
|
||||||
const parsedCards = parseAiResponse(aiResponse);
|
const parsedCards = parseAiResponse(aiResponse);
|
||||||
setCards(parsedCards.reverse());
|
setCards(parsedCards.reverse());
|
||||||
setInitialCards(parsedCards.reverse()); // Save for reset
|
setInitialCards(parsedCards.reverse());
|
||||||
} else {
|
} 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.");
|
console.warn("No AI response found. Using hardcoded cards as fallback.");
|
||||||
const hardcodedCards: CardData[] = [
|
const hardcodedCards: CardData[] = [
|
||||||
{ id: 1, title: "AI Project Idea 1", summary: "A cool project...", tracks: ["AI", "Web"] },
|
{ id: 1, title: "AI Project Idea 1", summary: "A cool project...", tracks: ["AI", "Web"] },
|
||||||
@@ -76,7 +69,8 @@ export default function CardsPage() {
|
|||||||
setInitialCards(hardcodedCards.reverse());
|
setInitialCards(hardcodedCards.reverse());
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, []); // Runs once on load
|
}, []);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <div className="grid h-screen w-full place-items-center bg-orange-100"><p>Loading cards...</p></div>
|
return <div className="grid h-screen w-full place-items-center bg-orange-100"><p>Loading cards...</p></div>
|
||||||
}
|
}
|
||||||
@@ -123,7 +117,6 @@ const SwipeCards: React.FC<SwipeCardsProps> = ({
|
|||||||
|
|
||||||
const handleAccept = (cardData: CardData) => {
|
const handleAccept = (cardData: CardData) => {
|
||||||
removeCard();
|
removeCard();
|
||||||
// Redirect to chatbot with card data
|
|
||||||
router.push(`/chatbot?projectId=${encodeURIComponent(projectId)}&projectTitle=${encodeURIComponent(projectTitle)}&cardTitle=${encodeURIComponent(cardData.title)}&cardSummary=${encodeURIComponent(cardData.summary)}`);
|
router.push(`/chatbot?projectId=${encodeURIComponent(projectId)}&projectTitle=${encodeURIComponent(projectTitle)}&cardTitle=${encodeURIComponent(cardData.title)}&cardSummary=${encodeURIComponent(cardData.summary)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,9 +124,7 @@ const SwipeCards: React.FC<SwipeCardsProps> = ({
|
|||||||
removeCard();
|
removeCard();
|
||||||
};
|
};
|
||||||
|
|
||||||
// **** FIXED THIS FUNCTION ****
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
// This now resets the deck to the initial set of cards
|
|
||||||
setCards(initialCards);
|
setCards(initialCards);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -141,8 +132,8 @@ const SwipeCards: React.FC<SwipeCardsProps> = ({
|
|||||||
<div
|
<div
|
||||||
className="grid h-screen w-full place-items-center bg-orange-100"
|
className="grid h-screen w-full place-items-center bg-orange-100"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'
|
backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'
|
||||||
viewBox='0 0 32 32' width='32' height='32' fill='none'
|
viewBox='0 0 32 32' width='32' height='32' fill='none'
|
||||||
stroke-width='2' stroke='%23d4d4d4'%3e%3cpath d='M0 .5H31.5V32'/%3e%3c/svg%3e")`,
|
stroke-width='2' stroke='%23d4d4d4'%3e%3cpath d='M0 .5H31.5V32'/%3e%3c/svg%3e")`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -190,7 +181,7 @@ interface CardProps {
|
|||||||
|
|
||||||
const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onAccept, onReject }) => {
|
const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onAccept, onReject }) => {
|
||||||
const { title, summary, tracks } = cardData;
|
const { title, summary, tracks } = cardData;
|
||||||
|
const controls = useAnimation();
|
||||||
const x = useMotionValue(0);
|
const x = useMotionValue(0);
|
||||||
const rotate = useTransform(x, [-250, 250], [-25, 25]);
|
const rotate = useTransform(x, [-250, 250], [-25, 25]);
|
||||||
const opacity = useTransform(
|
const opacity = useTransform(
|
||||||
@@ -198,7 +189,6 @@ const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onAccept, onRe
|
|||||||
[-250, -100, 0, 100, 250],
|
[-250, -100, 0, 100, 250],
|
||||||
[0, 1, 1, 1, 0],
|
[0, 1, 1, 1, 0],
|
||||||
);
|
);
|
||||||
const controls = useAnimation();
|
|
||||||
|
|
||||||
const acceptOpacity = useTransform(x, [50, 150], [0, 1]);
|
const acceptOpacity = useTransform(x, [50, 150], [0, 1]);
|
||||||
const rejectOpacity = useTransform(x, [-150, -50], [1, 0]);
|
const rejectOpacity = useTransform(x, [-150, -50], [1, 0]);
|
||||||
@@ -218,7 +208,7 @@ const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onAccept, onRe
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute flex h-[56rem] w-[42rem] flex-col justify-between overflow-hidden rounded-2xl p-6 shadow-2xl hover:cursor-grab active:cursor-grabbing"
|
className="absolute flex h-[48rem] w-[36rem] flex-col justify-between overflow-hidden rounded-2xl p-6 shadow-2xl hover:cursor-grab active:cursor-grabbing"
|
||||||
style={{
|
style={{
|
||||||
x,
|
x,
|
||||||
rotate,
|
rotate,
|
||||||
@@ -228,13 +218,15 @@ const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onAccept, onRe
|
|||||||
zIndex: isFront ? 2 : 1,
|
zIndex: isFront ? 2 : 1,
|
||||||
pointerEvents: isFront ? "auto" : "none",
|
pointerEvents: isFront ? "auto" : "none",
|
||||||
}}
|
}}
|
||||||
animate={controls}
|
animate={{
|
||||||
|
scale: isFront ? 1 : 0.95,
|
||||||
|
y: isFront ? 0 : -40,
|
||||||
|
}}
|
||||||
transition={{ type: "spring", stiffness: 300, damping: 30 }}
|
transition={{ type: "spring", stiffness: 300, damping: 30 }}
|
||||||
drag={isFront ? "x" : false}
|
drag={isFront ? "x" : false}
|
||||||
dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }}
|
dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }}
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
>
|
>
|
||||||
{/* Accept/Reject Badges */}
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute left-10 top-10 z-10 rounded-2xl border-8 border-green-500 px-10 py-4"
|
className="absolute left-10 top-10 z-10 rounded-2xl border-8 border-green-500 px-10 py-4"
|
||||||
style={{ opacity: acceptOpacity, rotate: badgeRotate }}
|
style={{ opacity: acceptOpacity, rotate: badgeRotate }}
|
||||||
@@ -245,29 +237,28 @@ const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onAccept, onRe
|
|||||||
className="absolute right-10 top-10 z-10 rounded-2xl border-8 border-red-500 px-10 py-4"
|
className="absolute right-10 top-10 z-10 rounded-2xl border-8 border-red-500 px-10 py-4"
|
||||||
style={{ opacity: rejectOpacity, rotate: badgeRotate }}
|
style={{ opacity: rejectOpacity, rotate: badgeRotate }}
|
||||||
>
|
>
|
||||||
<h3 className="text-5xl font-bold uppercase text-red-500">Reject</h3>
|
<h3 className="text-s5xl font-bold uppercase text-red-500">Reject</h3>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Card Content */}
|
|
||||||
<div className="relative z-0">
|
<div className="relative z-0">
|
||||||
{/* 3. Applied the specific titleColor here */}
|
|
||||||
<h2
|
<h2
|
||||||
className="mb-4 text-center text-3xl font-bold"
|
className="mb-4 text-center text-7xl font-bold"
|
||||||
style={{ color: colorWay.titleColor }}
|
style={{ color: colorWay.titleColor }}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
{/* This summary <p> will inherit colorWay.text from the parent */}
|
<p className="text-center text-3xl font-light">{summary}</p>
|
||||||
<p className="text-center text-xl font-light">{summary}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative z-0 flex flex-wrap items-center justify-center gap-2">
|
<div className="relative z-0 flex flex-wrap items-center justify-center gap-2">
|
||||||
{tracks.slice(0, 2).map((track, index) => (
|
<h3 className="mb-4 w-full text-center text-4xl font-semibold">
|
||||||
|
Tracks
|
||||||
|
</h3>
|
||||||
|
{tracks.map((track) => (
|
||||||
<span
|
<span
|
||||||
key={track}
|
key={track}
|
||||||
className="flex items-center justify-center rounded-full px-8 py-3 text-sm font-medium"
|
className="flex items-center justify-center rounded-full px-8 py-3 text-2xl font-medium"
|
||||||
style={{
|
style={{
|
||||||
// This logic now correctly contrasts with the main text color
|
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
colorWay.text === "#ffffff"
|
colorWay.text === "#ffffff"
|
||||||
? "rgba(255, 255, 255, 0.2)"
|
? "rgba(255, 255, 255, 0.2)"
|
||||||
@@ -275,11 +266,9 @@ const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onAccept, onRe
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{track}
|
{track}
|
||||||
{index === 0 && tracks.length > 1 && ", "}
|
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
};
|
|
||||||
|
|||||||
+24
-24
@@ -1,13 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useRef, useCallback } from "react";
|
import { useState, useEffect, useRef, useCallback, Suspense } from "react";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams, useRouter } from "next/navigation";
|
||||||
import ChatInput from "../utils/component/ui/ChatInput";
|
import ChatInput from "../utils/component/ui/ChatInput";
|
||||||
import MessageWindow from "../utils/component/ui/MessageWindow";
|
import MessageWindow from "../utils/component/ui/MessageWindow";
|
||||||
import { ChatHistory, ChatSetting, Message, MessageRole } from "@/app/exportType/types";
|
import { ChatHistory, ChatSetting, Message, MessageRole } from "@/app/exportType/types";
|
||||||
import { getChatSessionByProjectId, saveChatSession } from "../utils/storage";
|
import { getChatSessionByProjectId, saveChatSession } from "../utils/storage";
|
||||||
|
|
||||||
|
|
||||||
function getInitialChatState(
|
function getInitialChatState(
|
||||||
projectId: string | null,
|
projectId: string | null,
|
||||||
): ChatHistory {
|
): ChatHistory {
|
||||||
@@ -22,7 +21,16 @@ function getInitialChatState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<div>Loading chat...</div>}>
|
||||||
|
<ChatbotContent />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChatbotContent() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
const router = useRouter();
|
||||||
const projectId = searchParams.get('projectId');
|
const projectId = searchParams.get('projectId');
|
||||||
const projectTitle = searchParams.get('projectTitle');
|
const projectTitle = searchParams.get('projectTitle');
|
||||||
const cardTitle = searchParams.get('cardTitle');
|
const cardTitle = searchParams.get('cardTitle');
|
||||||
@@ -30,18 +38,17 @@ export default function Home() {
|
|||||||
|
|
||||||
const [history, setHistory] = useState<ChatHistory>(() => getInitialChatState(projectId));
|
const [history, setHistory] = useState<ChatHistory>(() => getInitialChatState(projectId));
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const historyRef = useRef<ChatHistory>(history); // Create a ref for history
|
const historyRef = useRef<ChatHistory>(history);
|
||||||
const [settings] = useState<ChatSetting>({
|
const [settings] = useState<ChatSetting>({
|
||||||
temperature: 1,
|
temperature: 1,
|
||||||
model: "gemini-2.5-pro",
|
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 initialMessageSent = useRef(false);
|
const initialMessageSent = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
historyRef.current = history; // Keep the ref updated with the latest history
|
historyRef.current = history;
|
||||||
if (history.length > 0 && projectId && projectTitle) {
|
if (history.length > 0 && projectId && projectTitle) {
|
||||||
saveChatSession(projectId, projectTitle, history);
|
saveChatSession(projectId, projectTitle, history);
|
||||||
}
|
}
|
||||||
@@ -63,7 +70,7 @@ export default function Home() {
|
|||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
userMessage: message,
|
userMessage: message,
|
||||||
history: historyRef.current, // Use the ref for history *before* the new user message
|
history: historyRef.current,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -73,7 +80,6 @@ export default function Home() {
|
|||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
console.error("AI Error:", data.error);
|
console.error("AI Error:", data.error);
|
||||||
// Add an error message to chat
|
|
||||||
const aiErrorMessage: Message = {
|
const aiErrorMessage: Message = {
|
||||||
role: "model",
|
role: "model",
|
||||||
parts: [{ text: `Error: ${data.error}` }],
|
parts: [{ text: `Error: ${data.error}` }],
|
||||||
@@ -87,8 +93,6 @@ export default function Home() {
|
|||||||
parts: [{ text: data.response }],
|
parts: [{ text: data.response }],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set the final history with the AI response
|
|
||||||
// This will trigger the useEffect to save
|
|
||||||
setHistory(prevHistory => [...prevHistory, aiMessage]);
|
setHistory(prevHistory => [...prevHistory, aiMessage]);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -100,16 +104,11 @@ export default function Home() {
|
|||||||
};
|
};
|
||||||
setHistory(prevHistory => [...prevHistory, aiErrorMessage]);
|
setHistory(prevHistory => [...prevHistory, aiErrorMessage]);
|
||||||
}
|
}
|
||||||
}, [settings, projectId, projectTitle]); // Added projectId and projectTitle
|
}, [settings, projectId, projectTitle]);
|
||||||
|
|
||||||
// This effect handles the *very first* message when selecting a card
|
|
||||||
useEffect(() => {
|
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) {
|
if (cardTitle && cardSummary && history.length === 0 && !initialMessageSent.current) {
|
||||||
initialMessageSent.current = true; // Mark as "attempted"
|
initialMessageSent.current = true;
|
||||||
setLoading(true);
|
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 message = `Explain the project titled '${cardTitle}' with the summary '${cardSummary}' in detail, and suggest suitable roles for a team working on it.`;
|
||||||
@@ -119,8 +118,6 @@ export default function Home() {
|
|||||||
parts: [{ text: message }],
|
parts: [{ text: message }],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add user message to history
|
|
||||||
// The useEffect for 'history' will save this
|
|
||||||
setHistory([newUserMessage]);
|
setHistory([newUserMessage]);
|
||||||
|
|
||||||
const sendInitialApiRequest = async () => {
|
const sendInitialApiRequest = async () => {
|
||||||
@@ -130,7 +127,7 @@ export default function Home() {
|
|||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
userMessage: message,
|
userMessage: message,
|
||||||
history: [], // Send empty history for the first message
|
history: [],
|
||||||
settings: settings,
|
settings: settings,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -140,6 +137,7 @@ export default function Home() {
|
|||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
console.error("AI Error:", data.error);
|
console.error("AI Error:", data.error);
|
||||||
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,8 +146,6 @@ export default function Home() {
|
|||||||
parts: [{ text: data.response }],
|
parts: [{ text: data.response }],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add AI response
|
|
||||||
// The useEffect for 'history' will save this new state
|
|
||||||
setHistory((prevHistory) => [...prevHistory, aiMessage]);
|
setHistory((prevHistory) => [...prevHistory, aiMessage]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Request Failed:", error);
|
console.error("Request Failed:", error);
|
||||||
@@ -161,10 +157,14 @@ export default function Home() {
|
|||||||
}
|
}
|
||||||
}, [cardTitle, cardSummary, history.length, settings, projectId, projectTitle]);
|
}, [cardTitle, cardSummary, history.length, settings, projectId, projectTitle]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col py-32">
|
<div className="flex flex-col py-32">
|
||||||
{/* Pass loading state to MessageWindow */}
|
<button
|
||||||
|
onClick={() => router.push('/Dashboard')}
|
||||||
|
className="absolute top-4 right-4 bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
|
||||||
|
>
|
||||||
|
Exit
|
||||||
|
</button>
|
||||||
<MessageWindow history={history} isLoading={loading} />
|
<MessageWindow history={history} isLoading={loading} />
|
||||||
<ChatInput onSend={handleSend} onOpenSettings={() => { }} />
|
<ChatInput onSend={handleSend} onOpenSettings={() => { }} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+54
-121
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { motion, useMotionValue, useTransform } from "framer-motion";
|
import { motion, useMotionValue, useTransform } from "framer-motion";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
// Removed local fallback JSX declaration — use the official React type definitions instead:
|
// 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.
|
// install @types/react and @types/react-dom so JSX.IntrinsicElements is provided by those packages.
|
||||||
@@ -29,126 +30,49 @@ interface CardData {
|
|||||||
tracks: string[];
|
tracks: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardData: CardData[] = [
|
|
||||||
{
|
|
||||||
|
|
||||||
id: 1,
|
|
||||||
|
|
||||||
title: " WEBSITE",
|
interface SwipeCardsProps {
|
||||||
|
initialCards: CardData[];
|
||||||
|
}
|
||||||
|
|
||||||
summary:
|
const SwipeCards: React.FC<SwipeCardsProps> = ({ initialCards }) => {
|
||||||
|
const [cards, setCards] = useState<CardData[]>(initialCards);
|
||||||
"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.",
|
const router = useRouter();
|
||||||
|
|
||||||
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[]>(cardData);
|
|
||||||
|
|
||||||
const onSwipe = () => {
|
const onSwipe = () => {
|
||||||
setCards((prev) => prev.slice(0, prev.length - 1));
|
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 = () => {
|
const handleReset = () => {
|
||||||
setCards(cardData);
|
setCards(initialCards);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -171,6 +95,7 @@ const SwipeCards: React.FC = () => {
|
|||||||
colorWay={colorWay}
|
colorWay={colorWay}
|
||||||
isFront={index === cards.length - 1}
|
isFront={index === cards.length - 1}
|
||||||
onSwipe={onSwipe}
|
onSwipe={onSwipe}
|
||||||
|
onRightSwipe={handleRightSwipe}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@@ -199,10 +124,11 @@ interface CardProps {
|
|||||||
cardData: CardData;
|
cardData: CardData;
|
||||||
colorWay: ColorWay;
|
colorWay: ColorWay;
|
||||||
isFront: boolean;
|
isFront: boolean;
|
||||||
onSwipe: () => void;
|
onSwipe: () => void; // For left swipe (reject)
|
||||||
|
onRightSwipe: (card: CardData) => void; // For right swipe (accept)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onSwipe }) => {
|
const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onSwipe, onRightSwipe }) => {
|
||||||
const { title, summary, tracks } = cardData;
|
const { title, summary, tracks } = cardData;
|
||||||
|
|
||||||
const x = useMotionValue(0);
|
const x = useMotionValue(0);
|
||||||
@@ -218,14 +144,18 @@ const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onSwipe }) =>
|
|||||||
const badgeRotate = useTransform(x, [-150, 150], [-15, 15]);
|
const badgeRotate = useTransform(x, [-150, 150], [-15, 15]);
|
||||||
|
|
||||||
const handleDragEnd = (event: MouseEvent | TouchEvent | PointerEvent, info: { offset: { x: number } }) => {
|
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();
|
onSwipe();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute flex h-[48rem] w-[36rem] flex-col justify-between overflow-hidden rounded-2xl p-6 shadow-2xl hover:cursor-grab active:cursor-grabbing"
|
className="absolute flex h-[40rem] w-[30rem] flex-col justify-between overflow-hidden rounded-2xl p-6 shadow-2xl hover:cursor-grab active:cursor-grabbing"
|
||||||
style={{
|
style={{
|
||||||
x,
|
x,
|
||||||
rotate,
|
rotate,
|
||||||
@@ -243,43 +173,46 @@ const Card: React.FC<CardProps> = ({ cardData, colorWay, isFront, onSwipe }) =>
|
|||||||
drag={isFront ? "x" : false}
|
drag={isFront ? "x" : false}
|
||||||
dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }}
|
dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }}
|
||||||
onDragEnd={handleDragEnd}
|
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 */}
|
{/* Accept/Reject Badges */}
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute left-10 top-10 z-10 rounded-2xl border-8 border-green-500 px-10 py-4"
|
className="absolute left-10 top-10 z-10 rounded-2xl border-8 border-green-500 px-10 py-4"
|
||||||
style={{ opacity: acceptOpacity, rotate: badgeRotate }}
|
style={{ opacity: acceptOpacity, rotate: badgeRotate }}
|
||||||
>
|
>
|
||||||
<h3 className="text-5xl font-bold uppercase text-green-500">Accept</h3>
|
<h3 className="text-2xl font-bold uppercase text-green-500">Accept</h3>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute right-10 top-10 z-10 rounded-2xl border-8 border-red-500 px-10 py-4"
|
className="absolute right-10 top-10 z-10 rounded-2xl border-8 border-red-500 px-10 py-4"
|
||||||
style={{ opacity: rejectOpacity, rotate: badgeRotate }}
|
style={{ opacity: rejectOpacity, rotate: badgeRotate }}
|
||||||
>
|
>
|
||||||
<h3 className="text-5xl font-bold uppercase text-red-500">Reject</h3>
|
<h3 className="text-2xl font-bold uppercase text-red-500">Reject</h3>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Card Content */}
|
{/* Card Content */}
|
||||||
<div className="relative z-0">
|
<div className="relative z-0">
|
||||||
{/* 3. Applied the specific titleColor here */}
|
{/* 3. Applied the specific titleColor here */}
|
||||||
<h2
|
<h2
|
||||||
className="mb-4 text-center text-7xl font-bold"
|
className="mb-4 text-center text-2xl font-bold"
|
||||||
style={{ color: colorWay.titleColor }}
|
style={{ color: colorWay.titleColor }}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
{/* This summary <p> will inherit colorWay.text from the parent */}
|
{/* This summary <p> will inherit colorWay.text from the parent */}
|
||||||
<p className="text-center text-3xl font-light">{summary}</p>
|
<p className="text-center text-sm font-light">{summary}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative z-0 flex flex-wrap items-center justify-center gap-2">
|
<div className="relative z-0 flex flex-wrap items-center justify-center gap-2">
|
||||||
{/* This <h3> will also inherit colorWay.text */}
|
{/* This <h3> will also inherit colorWay.text */}
|
||||||
<h3 className="mb-4 w-full text-center text-4xl font-semibold">
|
<h3 className="mb-4 w-full text-center text-md font-semibold">
|
||||||
Tracks
|
Tracks
|
||||||
</h3>
|
</h3>
|
||||||
{tracks.map((track) => (
|
{tracks.map((track) => (
|
||||||
<span
|
<span
|
||||||
key={track}
|
key={track}
|
||||||
className="flex items-center justify-center rounded-full px-8 py-3 text-2xl font-medium"
|
className="flex items-center justify-center rounded-full px-2 py-1 text-xs font-medium"
|
||||||
style={{
|
style={{
|
||||||
// This logic now correctly contrasts with the main text color
|
// This logic now correctly contrasts with the main text color
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ export default async function handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Destructure the data sent from the frontend
|
// Destructure the data sent from the frontend
|
||||||
let { prompt, settings } = req.body;
|
let prompt = req.body.prompt;
|
||||||
|
const settings = req.body.settings;
|
||||||
|
|
||||||
// Basic validation
|
// Basic validation
|
||||||
if (!prompt || typeof prompt !== 'string') {
|
if (!prompt || typeof prompt !== 'string') {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
import FormPage from './FormPage';
|
import FormPage from './FormPage';
|
||||||
|
import { CardData } from "../exportType/types";
|
||||||
import { ChatSetting } from "../exportType/types";
|
import { ChatSetting } from "../exportType/types";
|
||||||
import { FormData, PROFICIENCY_LEVELS } from './formTypes';
|
import { FormData, PROFICIENCY_LEVELS } from './formTypes';
|
||||||
import { saveProject } from '../utils/storage';
|
import { saveProject } from '../utils/storage';
|
||||||
@@ -41,7 +42,6 @@ const getInitialState = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const AI_SETTINGS: ChatSetting = {
|
const AI_SETTINGS: ChatSetting = {
|
||||||
temperature: 1,
|
temperature: 1,
|
||||||
model: "gemini-2.5-pro",
|
model: "gemini-2.5-pro",
|
||||||
@@ -56,19 +56,15 @@ const AI_SETTINGS: ChatSetting = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
|
||||||
const [initialState] = useState(getInitialState);
|
const [initialState] = useState(getInitialState);
|
||||||
const [showForm, setShowForm] = useState(initialState.showForm);
|
const [showForm, setShowForm] = useState(initialState.showForm);
|
||||||
const [formData, setFormData] = useState<FormData>(initialState.data);
|
const [formData, setFormData] = useState<FormData>(initialState.data);
|
||||||
const [ideaResponse, setIdeaResponse] = useState<string>("");
|
const [ideaResponse, setIdeaResponse] = useState<string>("");
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const handleFormSubmitFromFormPage = (submittedData: FormData) => {
|
const handleFormSubmitFromFormPage = (submittedData: FormData) => {
|
||||||
const savedProject = saveProject(submittedData);
|
const savedProject = saveProject(submittedData);
|
||||||
|
|
||||||
if (typeof window !== 'undefined') localStorage.removeItem('formData');
|
if (typeof window !== 'undefined') localStorage.removeItem('formData');
|
||||||
|
|
||||||
setFormData(submittedData);
|
setFormData(submittedData);
|
||||||
setShowForm(false);
|
setShowForm(false);
|
||||||
generateIdeas(submittedData, savedProject.id);
|
generateIdeas(submittedData, savedProject.id);
|
||||||
@@ -96,7 +92,6 @@ export default function Home() {
|
|||||||
|
|
||||||
Using the SYSTEM INSTRUCTIONS, generate 5 project ideas based on the data above.`;
|
Using the SYSTEM INSTRUCTIONS, generate 5 project ideas based on the data above.`;
|
||||||
|
|
||||||
// 3. API Call
|
|
||||||
try {
|
try {
|
||||||
setIdeaResponse("Generating ideas... Please wait.");
|
setIdeaResponse("Generating ideas... Please wait.");
|
||||||
|
|
||||||
@@ -132,14 +127,12 @@ export default function Home() {
|
|||||||
}
|
}
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
// Replaced useEffect
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialState.runEffect) {
|
if (initialState.runEffect) {
|
||||||
//generateIdeas(initialState.data, "some-resumed-id");
|
// generateIdeas(initialState.data, "some-resumed-id");
|
||||||
}
|
}
|
||||||
}, [initialState, generateIdeas]);
|
}, [initialState, generateIdeas]);
|
||||||
|
|
||||||
// --- JSX Rendering ---
|
|
||||||
return (
|
return (
|
||||||
<main className="min-h-screen bg-gray-50 flex items-start justify-center p-4 sm:p-6 font-inter">
|
<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">
|
<div className="w-full max-w-2xl bg-white p-8 shadow-2xl rounded-xl mt-8">
|
||||||
|
|||||||
+3
-1
@@ -1,3 +1,4 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Concert+One&family=Pixelify+Sans:wght@400;700&display=swap');
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@@ -27,4 +28,5 @@ body {
|
|||||||
}
|
}
|
||||||
h1,h2,h3,h4,h5,h6 {
|
h1,h2,h3,h4,h5,h6 {
|
||||||
font-family: var(--font-header);
|
font-family: var(--font-header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-7
@@ -9,9 +9,9 @@ export default function Home() {
|
|||||||
>
|
>
|
||||||
<header className="absolute top-6 left-8 right-8 flex items-center justify-between text-sm font-medium text-zinc-700 dark:text-zinc-300">
|
<header className="absolute top-6 left-8 right-8 flex items-center justify-between text-sm font-medium text-zinc-700 dark:text-zinc-300">
|
||||||
<Image
|
<Image
|
||||||
className="dark:invert"
|
|
||||||
src="/next.svg"
|
src="/logo.svg"
|
||||||
alt="Next.js logo"
|
alt="logo.js logo"
|
||||||
width={100}
|
width={100}
|
||||||
height={20}
|
height={20}
|
||||||
priority
|
priority
|
||||||
@@ -20,6 +20,9 @@ export default function Home() {
|
|||||||
<Link href="/Dashboard" className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
|
<Link href="/Dashboard" className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
|
||||||
Dashboard
|
Dashboard
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link href="/cards" className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
|
||||||
|
Cards
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -62,13 +65,10 @@ export default function Home() {
|
|||||||
style={{ backgroundColor: "#DEE5D4" }}
|
style={{ backgroundColor: "#DEE5D4" }}
|
||||||
className="w-full rounded-xl mt-32"
|
className="w-full rounded-xl mt-32"
|
||||||
>
|
>
|
||||||
{/* <section className="mt-32 w-full flex flex-col items-start gap-12"> */}
|
|
||||||
<section className="px-16 py-18 flex flex-col items-start gap-12 max-w-[1200px] mx-auto">
|
<section className="px-16 py-18 flex flex-col items-start gap-12 max-w-[1200px] mx-auto">
|
||||||
{/* <div> */}
|
|
||||||
<h2 className="text-[28px] font-semibold text-black dark:text-zinc-50 leading-9">
|
<h2 className="text-[28px] font-semibold text-black dark:text-zinc-50 leading-9">
|
||||||
Guide:
|
Guide:
|
||||||
</h2>
|
</h2>
|
||||||
{/* </div> */}
|
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-8 w-full">
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-8 w-full">
|
||||||
<div
|
<div
|
||||||
@@ -142,4 +142,4 @@ export default function Home() {
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ export default function MessageWindow({ history, isLoading }: MessageWindowProps
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="flex justify-start mb-4">
|
<div className="flex justify-start mb-4">
|
||||||
<div className="mr-2">
|
<div className="mr-2">
|
||||||
@@ -67,10 +67,24 @@ export default function MessageWindow({ history, isLoading }: MessageWindowProps
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Invisible element to help scroll to bottom */}
|
{isLoading && (
|
||||||
<div ref={messageEndRef} />
|
<div className="flex justify-start">
|
||||||
</div>
|
<div className="mr-2">
|
||||||
|
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-gray">
|
||||||
|
<Bot size={15} className="text-gray-700" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="px-4 py-2 shadow-sm rounded-lg bg-white text-gray-800 rounded-bl-none max-w-xs sm:max-w-md">
|
||||||
|
<div className="whitespace-pre-wrap break-words">
|
||||||
|
<span>Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* Invisible element to help scroll to bottom */}
|
||||||
|
<div ref={messageEndRef} />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+21
-17
@@ -10,21 +10,21 @@ if (!apiKey) {
|
|||||||
const genAI = new GoogleGenerativeAI(apiKey);
|
const genAI = new GoogleGenerativeAI(apiKey);
|
||||||
|
|
||||||
export async function chatToGemini(
|
export async function chatToGemini(
|
||||||
userMessage: string,
|
userMessage:string ,
|
||||||
history: ChatHistory,
|
history:ChatHistory,
|
||||||
settings: ChatSetting
|
settings:ChatSetting
|
||||||
): Promise<string> {
|
):Promise<string>{
|
||||||
const model = genAI.getGenerativeModel({
|
const model = genAI.getGenerativeModel({
|
||||||
model: settings.model || "gemini-2.5-flash",
|
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 = {
|
const generationConfig:GenerateConfig = {
|
||||||
temperature: settings.temperature || 1,
|
temperature : settings.temperature || 1,
|
||||||
topP: 0.9,
|
topP : 0.9,
|
||||||
responseMimeType: "text/plain",
|
responseMimeType: "text/plain",
|
||||||
maxOutputTokens: 1900, // Increased to allow for longer responses
|
maxOutputTokens: 1900, // Increased to allow for longer responses
|
||||||
}
|
}
|
||||||
|
|
||||||
const chatSession = model.startChat({
|
const chatSession = model.startChat({
|
||||||
generationConfig,
|
generationConfig,
|
||||||
@@ -35,8 +35,12 @@ export async function chatToGemini(
|
|||||||
const result = await chatSession.sendMessage(userMessage);
|
const result = await chatSession.sendMessage(userMessage);
|
||||||
return result.response.text();
|
return result.response.text();
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error: unknown) {
|
||||||
console.error("Gemini API Error:", error);
|
if (error instanceof Error) {
|
||||||
|
console.error("Error in chatToGemini:", error.name, error.message, error);
|
||||||
|
} else {
|
||||||
|
console.error("Unknown error in chatToGemini:", error);
|
||||||
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Generated
+12
@@ -66,6 +66,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
|
||||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/generator": "^7.28.5",
|
"@babel/generator": "^7.28.5",
|
||||||
@@ -1470,6 +1471,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
||||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
@@ -1526,6 +1528,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.3.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.3.tgz",
|
||||||
"integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==",
|
"integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.46.3",
|
"@typescript-eslint/scope-manager": "8.46.3",
|
||||||
"@typescript-eslint/types": "8.46.3",
|
"@typescript-eslint/types": "8.46.3",
|
||||||
@@ -2023,6 +2026,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -2341,6 +2345,7 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.8.19",
|
"baseline-browser-mapping": "^2.8.19",
|
||||||
"caniuse-lite": "^1.0.30001751",
|
"caniuse-lite": "^1.0.30001751",
|
||||||
@@ -2870,6 +2875,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
|
||||||
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@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",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
|
||||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rtsao/scc": "^1.1.0",
|
"@rtsao/scc": "^1.1.0",
|
||||||
"array-includes": "^3.1.9",
|
"array-includes": "^3.1.9",
|
||||||
@@ -5123,6 +5130,7 @@
|
|||||||
"version": "19.2.0",
|
"version": "19.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -5131,6 +5139,7 @@
|
|||||||
"version": "19.2.0",
|
"version": "19.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
||||||
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@@ -5776,6 +5785,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -5927,6 +5937,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -6187,6 +6198,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",
|
||||||
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
|
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user