Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .hintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"extends": [
"development"
],
"hints": {
"compat-api/css": [
"default",
{
"ignore": [
"scrollbar-width"
]
}
]
}
}
11 changes: 11 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"cSpell.words": [
"dataforge",
"genai",
"Leetcode",
"msword",
"officedocument",
"supabase",
"wordprocessingml"
]
}
53 changes: 43 additions & 10 deletions app/api/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@
/**
* API Route for Gemini AI Generation
* Handles POST requests to generate AI responses
* Using @google/genai package
*/

export const runtime = "nodejs";

import { NextResponse } from "next/server";
import { GoogleGenAI } from "@google/genai";

// Initialize Gemini AI client
const ai = new GoogleGenAI({
apiKey: process.env.GEMINI_API_KEY!,
});

// ============================================
// TYPE DEFINITIONS
// ============================================
Expand Down Expand Up @@ -42,11 +38,19 @@ function asRecord(x: unknown): AnyRecord {
function extractTextFromResponse(response: unknown): string | null {
const r = asRecord(response);

// v0.2+ returns response.text directly
// Check for text property directly
if (typeof r["text"] === "string") {
return r["text"];
}

// Check for response.text() method result
if (r["response"]) {
const resp = asRecord(r["response"]);
if (typeof resp["text"] === "string") {
return resp["text"];
}
}

// Fallback: check candidates → content → parts structure
const candidates = r["candidates"];
if (Array.isArray(candidates) && candidates.length > 0) {
Expand Down Expand Up @@ -76,6 +80,20 @@ function extractTextFromResponse(response: unknown): string | null {
*/
export async function POST(request: Request): Promise<NextResponse> {
try {
// Get API key from request header (sent by client) or fallback to env variable
const apiKey = request.headers.get("X-Gemini-Key") || process.env.GEMINI_API_KEY;

// Validate API key exists
if (!apiKey) {
return NextResponse.json(
{ error: "Gemini API key not configured. Please add it in Settings > Developer Settings." },
{ status: 401 }
);
}

// Initialize Gemini AI client with API key (per-request, not module level)
const ai = new GoogleGenAI({ apiKey });

// Parse request body safely
const body = await request.json().catch(() => ({}));
const bRec = asRecord(body);
Expand All @@ -84,7 +102,10 @@ export async function POST(request: Request): Promise<NextResponse> {
const prompt = typeof bRec["prompt"] === "string" ? bRec["prompt"] : null;
const model = typeof bRec["model"] === "string"
? bRec["model"]
: "gemini-2.0-flash-exp";
: "gemini-2.0-flash-lite";

// ✅ NEW: optional parts (for files/images) coming from the frontend
const rawParts = Array.isArray(bRec["parts"]) ? (bRec["parts"] as unknown[]) : null;

// Validate prompt
if (!prompt) {
Expand All @@ -94,12 +115,24 @@ export async function POST(request: Request): Promise<NextResponse> {
);
}

// ✅ Build parts array: always include text prompt, then any extra parts
const userParts: AnyRecord[] = [{ text: prompt }];

if (rawParts && rawParts.length > 0) {
for (const p of rawParts) {
if (typeof p === "object" && p !== null) {
// These are the inlineData objects you build on the client
userParts.push(p as AnyRecord);
}
}
}

// Call Gemini API with correct format
const response = await ai.models.generateContent({
model,
contents: [{
role: "user",
parts: [{ text: prompt }]
parts: userParts,
}],
});

Expand All @@ -109,7 +142,7 @@ export async function POST(request: Request): Promise<NextResponse> {
// Return successful response
return NextResponse.json({
text,
raw: response, // Include for debugging (optional)
raw: response, // Include for debugging (remove in production)
});

} catch (err: unknown) {
Expand All @@ -125,4 +158,4 @@ export async function POST(request: Request): Promise<NextResponse> {
{ status: 500 }
);
}
}
}
21 changes: 20 additions & 1 deletion app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -377,4 +377,23 @@

.landing-scroll *::-webkit-scrollbar-thumb:hover {
background-color: var(--tertiary);
} */
} */

/* Make code-block scrollbars thin & subtle */
.code-scroll {
scrollbar-width: thin; /* Firefox */
}

/* WebKit (Chrome / Edge / Safari) */
.code-scroll::-webkit-scrollbar {
height: 4px; /* 👈 controls that fat green bar thickness */
}

.code-scroll::-webkit-scrollbar-track {
background: transparent; /* no big black strip */
}

.code-scroll::-webkit-scrollbar-thumb {
background-color: #048304; /* or whatever color you like */
border-radius: 9999px;
}
Loading