From 00395031bc57b853decfe71f2aa37ddbc5123fea Mon Sep 17 00:00:00 2001 From: carson-fn <180336862+carson-fn@users.noreply.github.com> Date: Thu, 19 Mar 2026 23:56:39 -0600 Subject: [PATCH] Restore missing files from previous merge --- README.md | 10 ++ backend/.env.example | 5 +- backend/package-lock.json | 10 ++ backend/package.json | 1 + backend/src/controllers/geminiController.ts | 30 +++++ backend/src/index.ts | 4 +- backend/src/routes/geminiRoute.ts | 8 ++ backend/src/services/geminiService.ts | 48 +++++++ frontend/src/api/geminiApi.ts | 27 ++++ .../src/pages/geminiDemo/GeminiDemoPage.tsx | 76 +++++++++++ .../pages/geminiDemo/styles/geminiDemo.css | 120 ++++++++++++++++++ frontend/src/pages/landing/LandingPage.tsx | 2 +- 12 files changed, 338 insertions(+), 3 deletions(-) create mode 100644 backend/src/controllers/geminiController.ts create mode 100644 backend/src/routes/geminiRoute.ts create mode 100644 backend/src/services/geminiService.ts create mode 100644 frontend/src/api/geminiApi.ts create mode 100644 frontend/src/pages/geminiDemo/GeminiDemoPage.tsx create mode 100644 frontend/src/pages/geminiDemo/styles/geminiDemo.css diff --git a/README.md b/README.md index c0da981..3a35683 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,16 @@ cd ../backend npm install ``` +### 4. Configure environment variables + +Create a `.env` file in `backend/` based on `.env.example` and add: + +``` +DATABASE_URL=postgresql://:@:/ +GEMINI_API_KEY=your-gemini-api-key +GEMINI_MODEL=gemini-2.5-flash # select model here +``` + ## Running the Application ### Start the frontend diff --git a/backend/.env.example b/backend/.env.example index c901e2f..0864a45 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1 +1,4 @@ -DATABASE_URL=postgresql://:@:/ \ No newline at end of file +DATABASE_URL=postgresql://:@:/ + +GEMINI_API_KEY=your-gemini-api-key +GEMINI_MODEL=gemini-2.5-flash diff --git a/backend/package-lock.json b/backend/package-lock.json index 37ffc45..82810f6 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@google/generative-ai": "^0.24.1", "cors": "^2.8.6", "express": "^5.2.1", "pg": "^8.20.0" @@ -465,6 +466,15 @@ "node": ">=18" } }, + "node_modules/@google/generative-ai": { + "version": "0.24.1", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.1.tgz", + "integrity": "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", diff --git a/backend/package.json b/backend/package.json index e76efca..2b92dd1 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,6 +12,7 @@ "license": "ISC", "type": "module", "dependencies": { + "@google/generative-ai": "^0.24.1", "cors": "^2.8.6", "express": "^5.2.1", "pg": "^8.20.0" diff --git a/backend/src/controllers/geminiController.ts b/backend/src/controllers/geminiController.ts new file mode 100644 index 0000000..023b881 --- /dev/null +++ b/backend/src/controllers/geminiController.ts @@ -0,0 +1,30 @@ +import type { Request, Response } from "express"; +import { runPrompt } from "../services/geminiService.js"; + +// Demo to test prompts for now +// Once tournament generation is complete, this will be integrated +export const runGeminiDemo = async (req: Request, res: Response) => { + const { prompt } = req.body ?? {}; + + if (typeof prompt !== "string" || prompt.trim().length === 0) { + return res.status(400).json({ error: "Prompt is required" }); + } + + try { + const result = await runPrompt(prompt); + return res.json(result); + } catch (err) { + console.error("GEMINI DEMO ERROR:", err); + + const message = + err instanceof Error + ? err.message + : "Gemini request failed. Verify GEMINI_API_KEY and GEMINI_MODEL."; + + return res.status(500).json({ + error: message, + hint: + "Ensure GEMINI_API_KEY is valid and GEMINI_MODEL (default gemini-2.5-flash) is supported. Try a smaller Flash/Pro variant if needed.", + }); + } +}; diff --git a/backend/src/index.ts b/backend/src/index.ts index 3aa8f0d..494a08d 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,6 +1,7 @@ import express from "express"; import cors from "cors"; import authRoutes from "./routes/authRoute.js"; +import geminiRoutes from "./routes/geminiRoute.js"; const app = express(); @@ -26,4 +27,5 @@ app.get("/api/hello", (req, res) => { res.json({message: "Hello world!"}); }) -app.use("/api", authRoutes); \ No newline at end of file +app.use("/api", authRoutes); +app.use("/api", geminiRoutes); diff --git a/backend/src/routes/geminiRoute.ts b/backend/src/routes/geminiRoute.ts new file mode 100644 index 0000000..9c82686 --- /dev/null +++ b/backend/src/routes/geminiRoute.ts @@ -0,0 +1,8 @@ +import express from "express"; +import { runGeminiDemo } from "../controllers/geminiController.js"; + +const router = express.Router(); + +router.post("/llm/demo", runGeminiDemo); + +export default router; diff --git a/backend/src/services/geminiService.ts b/backend/src/services/geminiService.ts new file mode 100644 index 0000000..c179345 --- /dev/null +++ b/backend/src/services/geminiService.ts @@ -0,0 +1,48 @@ +import { GoogleGenerativeAI } from "@google/generative-ai"; + +// Load environment variables from .env in non-production environments +if (process.env.NODE_ENV !== "production") { + const dotenv = await import("dotenv"); + dotenv.config(); +} + +const apiKey = process.env.GEMINI_API_KEY; +// Allow overriding model, default to a supported flash model +const modelName = process.env.GEMINI_MODEL ?? "gemini-2.5-flash"; + +let gemini: GoogleGenerativeAI | null = null; + +if (!apiKey) { + console.warn("GEMINI_API_KEY is not set, Gemini requests will fail."); +} else { + gemini = new GoogleGenerativeAI(apiKey); +} + +export const runPrompt = async (prompt: string) => { + if (!gemini) { + throw new Error("Gemini API key not configured"); + } + + const model = gemini.getGenerativeModel({ model: modelName }); + + try { + // Send the prompt + const result = await model.generateContent(prompt); + const text = result.response.text(); + + return { text, model: modelName }; + + } catch (err: unknown) { + if (typeof err === "object" && err !== null && "status" in err) { + const status = (err as { status?: number }).status; + if (status === 404) { + throw new Error( + `Model ${modelName} not found. Update GEMINI_MODEL to a supported model for your API key.` + ); + } + } + + throw err; + } + +}; diff --git a/frontend/src/api/geminiApi.ts b/frontend/src/api/geminiApi.ts new file mode 100644 index 0000000..00ad951 --- /dev/null +++ b/frontend/src/api/geminiApi.ts @@ -0,0 +1,27 @@ +export type GeminiDemoResponse = { + text: string; + model: string; +}; + +export async function callGeminiDemo(prompt: string): Promise { + if (!prompt || prompt.trim().length === 0) { + throw new Error("Prompt cannot be empty"); + } + + const res = await fetch("/api/llm/demo", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ prompt }), + }); + + if (!res.ok) { + // Attempt to get server-provided error, otherwise fallback + const errBody = await res.json().catch(() => ({})); + const message = (errBody as { error?: string }).error ?? `Request failed with status ${res.status}`; + throw new Error(message); + } + + return res.json(); +} diff --git a/frontend/src/pages/geminiDemo/GeminiDemoPage.tsx b/frontend/src/pages/geminiDemo/GeminiDemoPage.tsx new file mode 100644 index 0000000..31eb549 --- /dev/null +++ b/frontend/src/pages/geminiDemo/GeminiDemoPage.tsx @@ -0,0 +1,76 @@ +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { callGeminiDemo, type GeminiDemoResponse } from "../../api/geminiApi"; +import "./styles/geminiDemo.css"; + +function GeminiDemoPage() { + const [prompt, setPrompt] = useState(""); + const [result, setResult] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + + const handleSubmit = async () => { + const trimmed = prompt.trim(); + if (!trimmed) { + setError("Please enter a prompt to try the demo."); + setResult(null); + return; + } + + setLoading(true); + setError(null); + setResult(null); + + try { + const data = await callGeminiDemo(trimmed); + setResult(data); + } catch (err) { + const message = err instanceof Error ? err.message : "Something went wrong"; + setError(message); + } finally { + setLoading(false); + } + }; + + return ( +
+
+

Gemini LLM Demo

+

+ Enter a prompt to test the backend demo endpoint. +

+ +