mirror of
https://github.com/PinkSheep27/SBU-Hackathon.git
synced 2026-10-11 00:00:26 -04:00
got rid of the excess whitespaces and added the maxOutputTokens into the GenerateConfig in the types.ts
This commit is contained in:
@@ -21,9 +21,8 @@ export default function Dashboard() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen bg-zinc-50 dark:bg-black font-sans transition-all duration-300">
|
<div className="flex min-h-screen bg-zinc-50 dark:bg-black font-sans transition-all duration-300">
|
||||||
<aside
|
<aside
|
||||||
className={`${
|
className={`${sideBarOpen ? "w-64" : "w-16"
|
||||||
sideBarOpen ? "w-64" : "w-16"
|
} bg-white dark:bg-zinc-900 border-r border-zinc-200 dark:border-zinc-800 transition-all duration-300 flex flex-col`}
|
||||||
} bg-white dark:bg-zinc-900 border-r border-zinc-200 dark:border-zinc-800 transition-all duration-300 flex flex-col`}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between p-4 border-b border-zinc-200 dark:border-zinc-800">
|
<div className="flex items-center justify-between p-4 border-b border-zinc-200 dark:border-zinc-800">
|
||||||
{sideBarOpen && (
|
{sideBarOpen && (
|
||||||
@@ -41,9 +40,8 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav
|
<nav
|
||||||
className={`flex flex-col p-4 gap-3 text-zinc-700 dark:text-zinc-300 transition-opacity duration-200 ${
|
className={`flex flex-col p-4 gap-3 text-zinc-700 dark:text-zinc-300 transition-opacity duration-200 ${sideBarOpen ? "opacity-100" : "opacity-0"
|
||||||
sideBarOpen ? "opacity-100" : "opacity-0"
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
|
|||||||
@@ -5,20 +5,20 @@ import { ChatHistory, ChatSetting } from "@/app/exportType/types";
|
|||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
try {
|
try {
|
||||||
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
const aiResponse = await chatToGemini(userMessage , history, settings);
|
const aiResponse = await chatToGemini(userMessage, history, settings);
|
||||||
return NextResponse.json({response : aiResponse});
|
return NextResponse.json({ response: aiResponse });
|
||||||
}
|
}
|
||||||
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 " },
|
||||||
{status : 500}
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,18 @@ import { ChatSetting } from "@/app/exportType/types";
|
|||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
try {
|
try {
|
||||||
const { prompt, settings } = await request.json() as {
|
const { prompt, settings } = await request.json() as {
|
||||||
prompt :string;
|
prompt: string;
|
||||||
settings : ChatSetting;
|
settings: ChatSetting;
|
||||||
};
|
};
|
||||||
|
|
||||||
const aiResponse = await chatToGemini(prompt , [], settings);
|
const aiResponse = await chatToGemini(prompt, [], settings);
|
||||||
return NextResponse.json({response : aiResponse});
|
return NextResponse.json({ response: aiResponse });
|
||||||
}
|
}
|
||||||
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 " },
|
||||||
{status : 500}
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -58,7 +58,7 @@ const SwipeCards: React.FC<{ initialCards: CardData[] }> = ({ initialCards }) =>
|
|||||||
return (
|
return (
|
||||||
<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"
|
||||||
// -gradient-to-tr from-orange-200 to-orange-100
|
// -gradient-to-tr from-orange-200 to-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'
|
||||||
|
|||||||
+18
-18
@@ -4,16 +4,16 @@ import { useState, useEffect, useRef, useCallback } from "react";
|
|||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } 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";
|
||||||
|
|
||||||
export default function Home(){
|
export default function Home() {
|
||||||
const [history, setHistory] = useState<ChatHistory>([])
|
const [history, setHistory] = useState<ChatHistory>([])
|
||||||
const historyRef = useRef<ChatHistory>([]); // Create a ref for history
|
const historyRef = useRef<ChatHistory>([]); // Create a ref for 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 searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const initialMessageSent = useRef(false);
|
const initialMessageSent = useRef(false);
|
||||||
@@ -22,22 +22,22 @@ export default function Home(){
|
|||||||
historyRef.current = history; // Keep the ref updated with the latest history
|
historyRef.current = history; // Keep the ref updated with the latest history
|
||||||
}, [history]);
|
}, [history]);
|
||||||
|
|
||||||
const handleSend = useCallback(async(message:string)=>{
|
const handleSend = useCallback(async (message: string) => {
|
||||||
const newUserMessage:Message = {
|
const newUserMessage: Message = {
|
||||||
role:"user",
|
role: "user",
|
||||||
parts:[{text:message}],
|
parts: [{ text: message }],
|
||||||
};
|
};
|
||||||
|
|
||||||
setHistory(prevHistory => {
|
setHistory(prevHistory => {
|
||||||
const currentHistory = [...prevHistory, newUserMessage];
|
const currentHistory = [...prevHistory, newUserMessage];
|
||||||
|
|
||||||
const sendApiRequest = async () => {
|
const sendApiRequest = async () => {
|
||||||
try{
|
try {
|
||||||
const response = await fetch("/api/chat", {
|
const response = await fetch("/api/chat", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
userMessage: message,
|
userMessage: message,
|
||||||
history: historyRef.current, // Use the ref for history
|
history: historyRef.current, // Use the ref for history
|
||||||
@@ -58,8 +58,8 @@ export default function Home(){
|
|||||||
};
|
};
|
||||||
|
|
||||||
setHistory(prevHistory => [...prevHistory, aiMessage]);
|
setHistory(prevHistory => [...prevHistory, aiMessage]);
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error("Request Failed:", error);
|
console.error("Request Failed:", error);
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ export default function Home(){
|
|||||||
|
|
||||||
if (title && 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 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 = {
|
const newUserMessage: Message = {
|
||||||
role: "user",
|
role: "user",
|
||||||
parts: [{ text: message }],
|
parts: [{ text: message }],
|
||||||
@@ -129,7 +129,7 @@ export default function Home(){
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col py-32">
|
<div className="flex flex-col py-32">
|
||||||
<MessageWindow history={history} />
|
<MessageWindow history={history} />
|
||||||
<ChatInput onSend={handleSend} onOpenSettings={() => {}} />
|
<ChatInput onSend={handleSend} onOpenSettings={() => { }} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+13
-12
@@ -4,27 +4,28 @@ export type MessagePart = {
|
|||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Message{
|
export interface Message {
|
||||||
role: MessageRole;
|
role: MessageRole;
|
||||||
parts:MessagePart[];
|
parts: MessagePart[];
|
||||||
}
|
}
|
||||||
export type ChatHistory = Message[];
|
export type ChatHistory = Message[];
|
||||||
|
|
||||||
export interface GenerateConfig{
|
export interface GenerateConfig {
|
||||||
temperature : number;
|
temperature: number;
|
||||||
topP : number;
|
topP: number;
|
||||||
responseMimeType : string;
|
responseMimeType: string;
|
||||||
|
maxOutputTokens?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatSetting{
|
export interface ChatSetting {
|
||||||
temperature :number;
|
temperature: number;
|
||||||
model: string;
|
model: string;
|
||||||
sysTemInstructions: string;
|
sysTemInstructions: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CardData {
|
export interface CardData {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
tracks: string[];
|
tracks: string[];
|
||||||
summary: string;
|
summary: string;
|
||||||
}
|
}
|
||||||
+10
-10
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FormData,
|
FormData,
|
||||||
PROFICIENCY_LEVELS
|
PROFICIENCY_LEVELS
|
||||||
} from './formTypes'; // Ensure path is correct
|
} from './formTypes'; // Ensure path is correct
|
||||||
|
|
||||||
interface FormPageProps {
|
interface FormPageProps {
|
||||||
@@ -16,9 +16,9 @@ const FormPage: React.FC<FormPageProps> = ({ onFormSubmit }) => {
|
|||||||
Hackathon: '',
|
Hackathon: '',
|
||||||
mainTheme: '',
|
mainTheme: '',
|
||||||
tracksStack: [''],
|
tracksStack: [''],
|
||||||
students: [{
|
students: [{
|
||||||
studentName: '',
|
studentName: '',
|
||||||
skills: [{ skillName: '', proficiency: PROFICIENCY_LEVELS[1] }]
|
skills: [{ skillName: '', proficiency: PROFICIENCY_LEVELS[1] }]
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -121,12 +121,12 @@ const FormPage: React.FC<FormPageProps> = ({ onFormSubmit }) => {
|
|||||||
<label htmlFor="hackathonName" className="block text-sm font-medium text-gray-700 mb-1">
|
<label htmlFor="hackathonName" className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Hackathon Name
|
Hackathon Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="hackathonName"
|
id="hackathonName"
|
||||||
required
|
required
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="e.g., Google Code Jam"
|
placeholder="e.g., Google Code Jam"
|
||||||
name="Hackathon"
|
name="Hackathon"
|
||||||
value={formData.Hackathon}
|
value={formData.Hackathon}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="mt-1 block w-full px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500 transition duration-150"
|
className="mt-1 block w-full px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500 transition duration-150"
|
||||||
@@ -142,7 +142,7 @@ const FormPage: React.FC<FormPageProps> = ({ onFormSubmit }) => {
|
|||||||
required
|
required
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="e.g., Sustainability or AI Ethics"
|
placeholder="e.g., Sustainability or AI Ethics"
|
||||||
name="mainTheme"
|
name="mainTheme"
|
||||||
value={formData.mainTheme}
|
value={formData.mainTheme}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="mt-1 block w-full px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500 transition duration-150"
|
className="mt-1 block w-full px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500 transition duration-150"
|
||||||
@@ -161,7 +161,7 @@ const FormPage: React.FC<FormPageProps> = ({ onFormSubmit }) => {
|
|||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
placeholder={`Tracks #${index + 1}`}
|
placeholder={`Tracks #${index + 1}`}
|
||||||
name={`tracksStack-${index}`}
|
name={`tracksStack-${index}`}
|
||||||
value={tracks}
|
value={tracks}
|
||||||
onChange={(e) => handleTracksChange(e, index)}
|
onChange={(e) => handleTracksChange(e, index)}
|
||||||
className="flex-grow px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:ring-green-500 focus:border-green-500 transition duration-150"
|
className="flex-grow px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:ring-green-500 focus:border-green-500 transition duration-150"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export interface Student {
|
|||||||
// Updated main form interface
|
// Updated main form interface
|
||||||
export interface FormData {
|
export interface FormData {
|
||||||
Hackathon: string;
|
Hackathon: string;
|
||||||
mainTheme: string;
|
mainTheme: string;
|
||||||
tracksStack: string[];
|
tracksStack: string[];
|
||||||
students: Student[]; // Array for dynamic students
|
students: Student[]; // Array for dynamic students
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
|||||||
// IMPORTANT: The GEMINI_API_KEY should be an environment variable.
|
// IMPORTANT: The GEMINI_API_KEY should be an environment variable.
|
||||||
const apiKey = process.env.GEMINI_API_KEY;
|
const apiKey = process.env.GEMINI_API_KEY;
|
||||||
|
|
||||||
if(!apiKey){
|
if (!apiKey) {
|
||||||
throw new Error("api key not found");
|
throw new Error("api key not found");
|
||||||
}
|
}
|
||||||
const ai = new GoogleGenerativeAI(apiKey);
|
const ai = new GoogleGenerativeAI(apiKey);
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ 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, settings } = req.body;
|
||||||
|
|
||||||
// Basic validation
|
// Basic validation
|
||||||
if (!prompt || typeof prompt !== 'string') {
|
if (!prompt || typeof prompt !== 'string') {
|
||||||
return res.status(400).json({ error: 'Prompt is required' });
|
return res.status(400).json({ error: 'Prompt is required' });
|
||||||
@@ -37,32 +37,32 @@ export default async function handler(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Call the Gemini API using the correct method signature
|
// 1. Call the Gemini API using the correct method signature
|
||||||
|
|
||||||
const model = settings?.model || 'gemini-2.5-flash-lite';
|
const model = settings?.model || 'gemini-2.5-flash-lite';
|
||||||
const generativeModel = ai.getGenerativeModel({ model });
|
const generativeModel = ai.getGenerativeModel({ model });
|
||||||
|
|
||||||
// 1. Define the generation configuration object
|
// 1. Define the generation configuration object
|
||||||
const generationConfig = {
|
const generationConfig = {
|
||||||
temperature: settings?.temperature || 0.9,
|
temperature: settings?.temperature || 0.9,
|
||||||
// The SDK property name for system instructions is 'systemInstruction'
|
// The SDK property name for system instructions is 'systemInstruction'
|
||||||
systemInstruction: settings?.sysTemInstructions,
|
systemInstruction: settings?.sysTemInstructions,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. Call generateContent with the single GenerateContentRequest object.
|
// 2. Call generateContent with the single GenerateContentRequest object.
|
||||||
const result = await generativeModel.generateContent({
|
const result = await generativeModel.generateContent({
|
||||||
// contents must be an array of Content objects
|
// contents must be an array of Content objects
|
||||||
contents: [{
|
contents: [{
|
||||||
role: "user",
|
role: "user",
|
||||||
parts: [{ text: prompt }]
|
parts: [{ text: prompt }]
|
||||||
}],
|
}],
|
||||||
|
|
||||||
// The fixed property name that resolves the TypeScript error
|
// The fixed property name that resolves the TypeScript error
|
||||||
generationConfig: generationConfig,
|
generationConfig: generationConfig,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Extract the response text
|
// 2. Extract the response text
|
||||||
// The result object now contains a property 'text' at the top level for simple text generation
|
// The result object now contains a property 'text' at the top level for simple text generation
|
||||||
const geminiResponseText = result.response.text(); res.status(200).json({ response: geminiResponseText });
|
const geminiResponseText = result.response.text(); res.status(200).json({ response: geminiResponseText });
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Gemini API Error:", error);
|
console.error("Gemini API Error:", error);
|
||||||
|
|||||||
+69
-69
@@ -41,35 +41,35 @@ The output MUST be in Markdown format and strictly follow this structure:
|
|||||||
**Concept:** [A concise, single paragraph description of the product and its primary function.]`,
|
**Concept:** [A concise, single paragraph description of the product and its primary function.]`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const parseAiResponse = (markdownString: string): CardData[] => {
|
const parseAiResponse = (markdownString: string): CardData[] => {
|
||||||
console.log("Raw AI Response:", markdownString);
|
console.log("Raw AI Response:", markdownString);
|
||||||
const ideas: CardData[] = [];
|
const ideas: CardData[] = [];
|
||||||
const ideaBlocks = markdownString.split("### ").filter(block => block.trim() !== "");
|
const ideaBlocks = markdownString.split("### ").filter(block => block.trim() !== "");
|
||||||
|
|
||||||
ideaBlocks.forEach((block, index) => {
|
ideaBlocks.forEach((block, index) => {
|
||||||
// Match title: anything from the start of the block until the first newline or the start of "**Track**"
|
// 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);
|
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
|
// 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
|
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
|
// 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 conceptMatch = block.match(/\*\*Concept:\*\*\s*(.*)/s);
|
||||||
|
|
||||||
const title = titleMatch ? titleMatch[1].trim() : `Idea ${index + 1}`;
|
const title = titleMatch ? titleMatch[1].trim() : `Idea ${index + 1}`;
|
||||||
const summary = conceptMatch ? conceptMatch[1].trim() : "No concept provided.";
|
const summary = conceptMatch ? conceptMatch[1].trim() : "No concept provided.";
|
||||||
const tracksRaw = trackMatch ? trackMatch[1].trim() : "";
|
const tracksRaw = trackMatch ? trackMatch[1].trim() : "";
|
||||||
console.log("tracksRaw:", tracksRaw); // Added console.log for debugging
|
console.log("tracksRaw:", tracksRaw); // Added console.log for debugging
|
||||||
const tracks = tracksRaw.split(',').map(track => track.trim()).filter(track => track !== '');
|
const tracks = tracksRaw.split(',').map(track => track.trim()).filter(track => track !== '');
|
||||||
|
|
||||||
ideas.push({
|
ideas.push({
|
||||||
id: index + 1,
|
id: index + 1,
|
||||||
title,
|
title,
|
||||||
tracks,
|
tracks,
|
||||||
summary,
|
summary,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
console.log("Parsed Cards:", ideas);
|
||||||
console.log("Parsed Cards:", ideas);
|
return ideas;
|
||||||
return ideas;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const handleFormSubmitFromFormPage = (submittedData: FormData) => {
|
const handleFormSubmitFromFormPage = (submittedData: FormData) => {
|
||||||
setFormData(submittedData);
|
setFormData(submittedData);
|
||||||
@@ -80,14 +80,14 @@ const parseAiResponse = (markdownString: string): CardData[] => {
|
|||||||
// eslint-disable-next-line react-hooks/preserve-manual-memoization
|
// eslint-disable-next-line react-hooks/preserve-manual-memoization
|
||||||
const generateIdeas = useCallback(async (dataToProcess: FormData) => {
|
const generateIdeas = useCallback(async (dataToProcess: FormData) => {
|
||||||
const submissionData: FormData = {
|
const submissionData: FormData = {
|
||||||
...dataToProcess,
|
...dataToProcess,
|
||||||
tracksStack: dataToProcess.tracksStack.filter(tracks => tracks.trim() !== ''),
|
tracksStack: dataToProcess.tracksStack.filter(tracks => tracks.trim() !== ''),
|
||||||
students: dataToProcess.students
|
students: dataToProcess.students
|
||||||
.map(student => ({
|
.map(student => ({
|
||||||
...student,
|
...student,
|
||||||
skills: student.skills.filter(skill => skill.skillName.trim() !== ''),
|
skills: student.skills.filter(skill => skill.skillName.trim() !== ''),
|
||||||
}))
|
}))
|
||||||
.filter(student => student.studentName.trim() !== '' || student.skills.length > 0),
|
.filter(student => student.studentName.trim() !== '' || student.skills.length > 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
const prompt = `
|
const prompt = `
|
||||||
@@ -95,46 +95,46 @@ const parseAiResponse = (markdownString: string): CardData[] => {
|
|||||||
Main Theme: ${submissionData.mainTheme}
|
Main Theme: ${submissionData.mainTheme}
|
||||||
Required Tracks Stack: ${submissionData.tracksStack.join(', ')}
|
Required Tracks Stack: ${submissionData.tracksStack.join(', ')}
|
||||||
Team Members and Skills: ${submissionData.students.map(s =>
|
Team Members and Skills: ${submissionData.students.map(s =>
|
||||||
`${s.studentName || 'Unamed Member'} (Skills: ${s.skills.map(sk => `${sk.skillName} - ${sk.proficiency}`).join(', ')})`
|
`${s.studentName || 'Unamed Member'} (Skills: ${s.skills.map(sk => `${sk.skillName} - ${sk.proficiency}`).join(', ')})`
|
||||||
).join('; ')}
|
).join('; ')}
|
||||||
|
|
||||||
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.`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIdeaResponse("Generating ideas... Please wait.");
|
setIdeaResponse("Generating ideas... Please wait.");
|
||||||
setGeneratedCards([]); // Clear previous cards
|
setGeneratedCards([]); // Clear previous cards
|
||||||
const response = await fetch("/api/generate-idea", {
|
const response = await fetch("/api/generate-idea", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
prompt: prompt,
|
prompt: prompt,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.error || !response.ok) {
|
if (data.error || !response.ok) {
|
||||||
console.error("AI Error:", data.error);
|
console.error("AI Error:", data.error);
|
||||||
setIdeaResponse(`Error: Failed to generate idea. ${data.error || response.statusText}`);
|
setIdeaResponse(`Error: Failed to generate idea. ${data.error || response.statusText}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIdeaResponse(data.response);
|
setIdeaResponse(data.response);
|
||||||
const parsedCards = parseAiResponse(data.response);
|
const parsedCards = parseAiResponse(data.response);
|
||||||
setGeneratedCards(parsedCards);
|
setGeneratedCards(parsedCards);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Request Failed:", error);
|
console.error("Request Failed:", error);
|
||||||
let errorMessage = "An unknown error occurred.";
|
let errorMessage = "An unknown error occurred.";
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
errorMessage = error.message;
|
errorMessage = error.message;
|
||||||
} else if (typeof error === 'string') {
|
} else if (typeof error === 'string') {
|
||||||
errorMessage = error;
|
errorMessage = error;
|
||||||
}
|
}
|
||||||
setIdeaResponse(`A network error occurred. Please try again. Details: ${errorMessage}`);
|
setIdeaResponse(`A network error occurred. Please try again. Details: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}, [setIdeaResponse, setGeneratedCards, settings, parseAiResponse]);
|
}, [setIdeaResponse, setGeneratedCards, settings, parseAiResponse]);
|
||||||
|
|
||||||
@@ -165,12 +165,12 @@ const parseAiResponse = (markdownString: string): CardData[] => {
|
|||||||
Generated Project Idea 💡
|
Generated Project Idea 💡
|
||||||
</h1>
|
</h1>
|
||||||
<div className="mt-8 p-4 bg-yellow-50 rounded-lg border border-yellow-200">
|
<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>
|
<h2 className="text-xl font-bold text-gray-700 mb-2">Generated Ideas:</h2>
|
||||||
<div
|
<div
|
||||||
className="prose max-w-none"
|
className="prose max-w-none"
|
||||||
dangerouslySetInnerHTML={{ __html: ideaResponse.replace(/\n/g, '<br/>') }}
|
dangerouslySetInnerHTML={{ __html: ideaResponse.replace(/\n/g, '<br/>') }}
|
||||||
/>
|
/>
|
||||||
{ideaResponse === "" && <p className="text-gray-500">Submit the form to generate project ideas.</p>}
|
{ideaResponse === "" && <p className="text-gray-500">Submit the form to generate project ideas.</p>}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+11
-12
@@ -1,6 +1,3 @@
|
|||||||
/* 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');
|
|
||||||
|
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@@ -13,13 +10,10 @@
|
|||||||
--color-foreground: var(--foreground);
|
--color-foreground: var(--foreground);
|
||||||
|
|
||||||
/* 3. Assign fonts to Tailwind's theme */
|
/* 3. Assign fonts to Tailwind's theme */
|
||||||
|
|
||||||
/* --font-sans is for regular text (Concert One) */
|
/* Tell Tailwind to use the variables already defined in app/layout.tsx */
|
||||||
--font-sans: var(--font-concert-one);
|
--font-sans: var(--font-sans);
|
||||||
|
--font-header: var(--font-header);
|
||||||
/* We add a new font family for headers (Pixelify Sans) */
|
|
||||||
/* This also creates a 'font-header' utility class */
|
|
||||||
--font-header: var(--font-pixelify);
|
|
||||||
|
|
||||||
--font-mono: var(--font-geist-mono);
|
--font-mono: var(--font-geist-mono);
|
||||||
}
|
}
|
||||||
@@ -34,12 +28,17 @@
|
|||||||
body {
|
body {
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
|
|
||||||
/* 4. Body uses --font-sans (Concert One) by default */
|
/* 4. Body uses --font-sans (Concert One) by default */
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 5. (Optional but helpful) Apply header font to h-tags by default */
|
/* 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);
|
font-family: var(--font-header);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
// app/layout.tsx
|
|
||||||
|
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
// 1. Import your two new fonts
|
// 1. Import your two new fonts
|
||||||
import { Concert_One, Pixelify_Sans } from "next/font/google";
|
import { Concert_One, Pixelify_Sans } from "next/font/google";
|
||||||
|
|||||||
+110
-110
@@ -7,121 +7,121 @@ import ProtectedNavButton from "../components/ProtectedNavButton";
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||||
<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=""
|
className=""
|
||||||
src="/logo.svg"
|
src="/logo.svg"
|
||||||
alt="logo"
|
alt="logo"
|
||||||
width={100}
|
width={100}
|
||||||
height={20}
|
height={20}
|
||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Link href = "/auth/login" className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
|
<Link href="/auth/login" className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
|
||||||
Sign in
|
Sign in
|
||||||
</Link>
|
</Link>
|
||||||
<Link href = "/auth/logout" className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
|
<Link href="/auth/logout" className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
|
||||||
Log out
|
Log out
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main className="flex min-h-screen w-full flex-col justify-between py-32 px-16 bg-white dark:bg-black">
|
<main className="flex min-h-screen w-full flex-col justify-between py-32 px-16 bg-white dark:bg-black">
|
||||||
<div className="flex flex-col sm:flex-row items-start justify-between gap-12 text-left w-full">
|
<div className="flex flex-col sm:flex-row items-start justify-between gap-12 text-left w-full">
|
||||||
|
|
||||||
<div className="flex-1 flex flex-col gap-5 justify-center">
|
|
||||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
|
||||||
(project name)
|
|
||||||
</h1>
|
|
||||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
|
||||||
Looking for a starting point or more instructions? Head over to{" "}
|
|
||||||
<Link
|
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
|
||||||
>
|
|
||||||
Templates
|
|
||||||
</Link>{" "}
|
|
||||||
or the{" "}
|
|
||||||
<Link
|
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
|
||||||
>
|
|
||||||
Learning
|
|
||||||
</Link>{" "}
|
|
||||||
center.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<ProtectedNavButton>Get Started</ProtectedNavButton>
|
<div className="flex-1 flex flex-col gap-5 justify-center">
|
||||||
</div>
|
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||||
|
(project name)
|
||||||
|
</h1>
|
||||||
|
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||||
|
Looking for a starting point or more instructions? Head over to{" "}
|
||||||
|
<Link
|
||||||
|
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||||
|
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||||
|
>
|
||||||
|
Templates
|
||||||
|
</Link>{" "}
|
||||||
|
or the{" "}
|
||||||
|
<Link
|
||||||
|
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||||
|
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||||
|
>
|
||||||
|
Learning
|
||||||
|
</Link>{" "}
|
||||||
|
center.
|
||||||
|
</p>
|
||||||
|
|
||||||
<div className="flex-1 flex justify-center">
|
<ProtectedNavButton>Get Started</ProtectedNavButton>
|
||||||
<div className="w-full max-w-[400px] h-[300px] bg-zinc-200 dark:bg-zinc-800 rounded-xl shadow-md flex items-center justify-center text-zinc-500">
|
</div>
|
||||||
Image Placeholder
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section className="mt-32 w-full flex flex-col items-start gap-12">
|
<div className="flex-1 flex justify-center">
|
||||||
<div>
|
<div className="w-full max-w-[400px] h-[300px] bg-zinc-200 dark:bg-zinc-800 rounded-xl shadow-md flex items-center justify-center text-zinc-500">
|
||||||
<h2 className="text-[28px] font-semibold text-black dark:text-zinc-50 leading-9">
|
Image Placeholder
|
||||||
Guide:
|
</div>
|
||||||
</h2>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row items-start justify-between gap-8 w-full">
|
<section className="mt-32 w-full flex flex-col items-start gap-12">
|
||||||
<div className="w-full max-w-xl">
|
<div>
|
||||||
<h2 className="text-2xl font-semibold text-black dark:text-zinc-50 mb-2">
|
<h2 className="text-[28px] font-semibold text-black dark:text-zinc-50 leading-9">
|
||||||
First Text Block
|
Guide:
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-lg text-zinc-600 dark:text-zinc-400">
|
</div>
|
||||||
This is the content for the first text block. You can write anything here.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="sm:w-1/2 flex justify-center items-center">
|
|
||||||
<div className="w-full max-w-[300px] h-[200px] bg-zinc-200 dark:bg-zinc-800 rounded-xl shadow-md flex items-center justify-center text-zinc-500">
|
|
||||||
Image Placeholder
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row items-start justify-between gap-8 w-full">
|
|
||||||
<div className="sm:w-1/2 flex justify-center items-center">
|
|
||||||
<div className="w-full max-w-[300px] h-[200px] bg-zinc-200 dark:bg-zinc-800 rounded-xl shadow-md flex items-center justify-center text-zinc-500">
|
|
||||||
Image Placeholder
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex w-full justify-end">
|
<div className="flex flex-col sm:flex-row items-start justify-between gap-8 w-full">
|
||||||
<div className="w-full max-w-xl">
|
<div className="w-full max-w-xl">
|
||||||
<h2 className="text-2xl font-semibold text-black dark:text-zinc-50 mb-2">
|
<h2 className="text-2xl font-semibold text-black dark:text-zinc-50 mb-2">
|
||||||
Second Text Block
|
First Text Block
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-lg text-zinc-600 dark:text-zinc-400">
|
<p className="text-lg text-zinc-600 dark:text-zinc-400">
|
||||||
This is the content for the second text block. You can write anything here.
|
This is the content for the first text block. You can write anything here.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="sm:w-1/2 flex justify-center items-center">
|
||||||
</div>
|
<div className="w-full max-w-[300px] h-[200px] bg-zinc-200 dark:bg-zinc-800 rounded-xl shadow-md flex items-center justify-center text-zinc-500">
|
||||||
|
Image Placeholder
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row items-start justify-between gap-8 w-full">
|
<div className="flex flex-col sm:flex-row items-start justify-between gap-8 w-full">
|
||||||
<div className="w-full max-w-xl">
|
<div className="sm:w-1/2 flex justify-center items-center">
|
||||||
<h2 className="text-2xl font-semibold text-black dark:text-zinc-50 mb-2">
|
<div className="w-full max-w-[300px] h-[200px] bg-zinc-200 dark:bg-zinc-800 rounded-xl shadow-md flex items-center justify-center text-zinc-500">
|
||||||
Third Text Block
|
Image Placeholder
|
||||||
</h2>
|
</div>
|
||||||
<p className="text-lg text-zinc-600 dark:text-zinc-400">
|
</div>
|
||||||
This is the content for the third text block. You can write anything here.
|
|
||||||
</p>
|
<div className="flex w-full justify-end">
|
||||||
</div>
|
<div className="w-full max-w-xl">
|
||||||
<div className="sm:w-1/2 flex justify-center items-center">
|
<h2 className="text-2xl font-semibold text-black dark:text-zinc-50 mb-2">
|
||||||
<div className="w-full max-w-[300px] h-[200px] bg-zinc-200 dark:bg-zinc-800 rounded-xl shadow-md flex items-center justify-center text-zinc-500">
|
Second Text Block
|
||||||
Image Placeholder
|
</h2>
|
||||||
</div>
|
<p className="text-lg text-zinc-600 dark:text-zinc-400">
|
||||||
</div>
|
This is the content for the second text block. You can write anything here.
|
||||||
</div>
|
</p>
|
||||||
</section>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
|
<div className="flex flex-col sm:flex-row items-start justify-between gap-8 w-full">
|
||||||
|
<div className="w-full max-w-xl">
|
||||||
|
<h2 className="text-2xl font-semibold text-black dark:text-zinc-50 mb-2">
|
||||||
|
Third Text Block
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-zinc-600 dark:text-zinc-400">
|
||||||
|
This is the content for the third text block. You can write anything here.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="sm:w-1/2 flex justify-center items-center">
|
||||||
|
<div className="w-full max-w-[300px] h-[200px] bg-zinc-200 dark:bg-zinc-800 rounded-xl shadow-md flex items-center justify-center text-zinc-500">
|
||||||
|
Image Placeholder
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ export default function ChatInput({ onSend, onOpenSettings }: ChatInputProps) {
|
|||||||
|
|
||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
if (message.trim()) {
|
if (message.trim()) {
|
||||||
onSend(message.trim());
|
onSend(message.trim());
|
||||||
setMessage("");
|
setMessage("");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,62 +34,61 @@ export default function ChatInput({ onSend, onOpenSettings }: ChatInputProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed bottom-0 left-0 right-0 flex flex-col max-w-4xl mx-auto w-full">
|
<div className="fixed bottom-0 left-0 right-0 flex flex-col max-w-4xl mx-auto w-full">
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
flex items-center border border-black/10 p-3 mx-4 rounded-t-3xl
|
flex items-center border border-black/10 p-3 mx-4 rounded-t-3xl
|
||||||
bg-white
|
bg-white
|
||||||
shadow-md transition-all duration-200
|
shadow-md transition-all duration-200
|
||||||
|
|
||||||
`}
|
`}
|
||||||
>
|
|
||||||
|
|
||||||
<div className="relative flex-1 mx-2">
|
|
||||||
<textarea
|
|
||||||
className="w-full px-3 py-2 bg-transparent border-none focus:outline-none"
|
|
||||||
placeholder="Type a message..."
|
|
||||||
value={message}
|
|
||||||
onChange={handleChange}
|
|
||||||
onKeyDown={handleKeyPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{message && (
|
|
||||||
<button
|
|
||||||
className="absolute right-0 top-1/2 transform -translate-y-1/2 p-1 text-gray-400 hover:text-gray-600 "
|
|
||||||
onClick={handleClear}
|
|
||||||
aria-label="Clear input"
|
|
||||||
>
|
|
||||||
<X size={16} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div className="flex items-center">
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="p-2 text-gray-500 hover:text-purple-600 rounded-full hover:bg-gray-100 0 mr-1"
|
|
||||||
onClick={onOpenSettings}
|
|
||||||
aria-label="Open settings"
|
|
||||||
>
|
>
|
||||||
<Settings size={20} />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
<div className="relative flex-1 mx-2">
|
||||||
className={`
|
<textarea
|
||||||
|
className="w-full px-3 py-2 bg-transparent border-none focus:outline-none"
|
||||||
|
placeholder="Type a message..."
|
||||||
|
value={message}
|
||||||
|
onChange={handleChange}
|
||||||
|
onKeyDown={handleKeyPress}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{message && (
|
||||||
|
<button
|
||||||
|
className="absolute right-0 top-1/2 transform -translate-y-1/2 p-1 text-gray-400 hover:text-gray-600 "
|
||||||
|
onClick={handleClear}
|
||||||
|
aria-label="Clear input"
|
||||||
|
>
|
||||||
|
<X size={16} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="p-2 text-gray-500 hover:text-purple-600 rounded-full hover:bg-gray-100 0 mr-1"
|
||||||
|
onClick={onOpenSettings}
|
||||||
|
aria-label="Open settings"
|
||||||
|
>
|
||||||
|
<Settings size={20} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className={`
|
||||||
p-2 rounded-full transition-all
|
p-2 rounded-full transition-all
|
||||||
${
|
${message.trim()
|
||||||
message.trim()
|
? "bg-purple-600 text-white hover:bg-purple-700"
|
||||||
? "bg-purple-600 text-white hover:bg-purple-700"
|
: "bg-gray-200 text-gray-400 cursor-not-allowed"
|
||||||
: "bg-gray-200 text-gray-400 cursor-not-allowed"
|
}
|
||||||
}
|
|
||||||
`}
|
`}
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
disabled={!message.trim()}
|
disabled={!message.trim()}
|
||||||
aria-label="Send message"
|
aria-label="Send message"
|
||||||
>
|
>
|
||||||
<Send size={20} />
|
<Send size={20} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useRef, useEffect } from "react";
|
import { useRef, useEffect } from "react";
|
||||||
import { ChatHistory } from "@/app/exportType/types";
|
import { ChatHistory } from "@/app/exportType/types";
|
||||||
import {User, Bot} from "lucide-react"
|
import { User, Bot } from "lucide-react"
|
||||||
|
|
||||||
interface MessageWindowProps{
|
interface MessageWindowProps {
|
||||||
history :ChatHistory
|
history: ChatHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MessageWindow({history}:MessageWindowProps){
|
export default function MessageWindow({ history }: MessageWindowProps) {
|
||||||
const messageEndRef = useRef<HTMLDivElement>(null);
|
const messageEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() =>{
|
useEffect(() => {
|
||||||
messageEndRef.current?.scrollIntoView({behavior:"smooth"});
|
messageEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||||
}, [history]);
|
}, [history]);
|
||||||
|
|
||||||
return(
|
return (
|
||||||
<div className="flex-1 p-3 overflow-y-auto">
|
<div className="flex-1 p-3 overflow-y-auto">
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
{history.map((msg,index)=>{
|
{history.map((msg, index) => {
|
||||||
const isUser = msg.role === "user"
|
const isUser = msg.role === "user"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`flex ${isUser ? "justify-end" : "justify-start"}`}>
|
className={`flex ${isUser ? "justify-end" : "justify-start"}`}>
|
||||||
{!isUser &&(
|
{!isUser && (
|
||||||
<div className="mr-2">
|
<div className="mr-2">
|
||||||
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-gray">
|
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-gray">
|
||||||
<Bot size={15} className="text-gray-700">
|
<Bot size={15} className="text-gray-700">
|
||||||
@@ -41,20 +41,20 @@ export default function MessageWindow({history}:MessageWindowProps){
|
|||||||
<span key={idx}>{part.text}</span>))}
|
<span key={idx}>{part.text}</span>))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isUser && (
|
{isUser && (
|
||||||
<div className="ml-2">
|
<div className="ml-2">
|
||||||
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-purple-500">
|
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-purple-500">
|
||||||
<User size={16} className="text-white" />
|
<User size={16} className="text-white" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{/* Invisible element to help scroll to bottom */}
|
{/* Invisible element to help scroll to bottom */}
|
||||||
<div ref={messageEndRef} />
|
<div ref={messageEndRef} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+30
-30
@@ -1,42 +1,42 @@
|
|||||||
import { GoogleGenerativeAI } from "@google/generative-ai";
|
import { GoogleGenerativeAI } from "@google/generative-ai";
|
||||||
import { ChatHistory, GenerateConfig,ChatSetting } from "../exportType/types";
|
import { ChatHistory, GenerateConfig, ChatSetting } from "../exportType/types";
|
||||||
|
|
||||||
const apiKey = process.env.GEMINI_API_KEY;
|
const apiKey = process.env.GEMINI_API_KEY;
|
||||||
|
|
||||||
if(!apiKey){
|
if (!apiKey) {
|
||||||
throw new Error("api key not found");
|
throw new Error("api key not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = {
|
|
||||||
temperature : settings.temperature || 1,
|
|
||||||
topP : 0.9,
|
|
||||||
responseMimeType: "text/plain",
|
|
||||||
maxOutputTokens: 1900, // Increased to allow for longer responses
|
|
||||||
}
|
|
||||||
|
|
||||||
const chatSession = model.startChat({
|
const generationConfig: GenerateConfig = {
|
||||||
generationConfig,
|
temperature: settings.temperature || 1,
|
||||||
history
|
topP: 0.9,
|
||||||
})
|
responseMimeType: "text/plain",
|
||||||
|
maxOutputTokens: 1900, // Increased to allow for longer responses
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
const chatSession = model.startChat({
|
||||||
const result = await chatSession.sendMessage(userMessage);
|
generationConfig,
|
||||||
return result.response.text();
|
history
|
||||||
}
|
})
|
||||||
catch(error){
|
|
||||||
console.error(error)
|
try {
|
||||||
throw error
|
const result = await chatSession.sendMessage(userMessage);
|
||||||
}
|
return result.response.text();
|
||||||
}
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user