diff --git a/src/app/api/generate/route.ts b/src/app/api/generate/route.ts index dd12777..21b2b1e 100644 --- a/src/app/api/generate/route.ts +++ b/src/app/api/generate/route.ts @@ -2,11 +2,13 @@ import { NextResponse } from "next/server"; import { getGeminiModel } from "@/lib/gemini"; import { getRepoData, getRepoContents } from "@/lib/octokit"; -// Ensure API keys are read at runtime export const dynamic = "force-dynamic"; +/** + * AI README Generation Endpoint + * Optimized for data accuracy and clean prompt interpolation. + */ export async function POST(req: Request) { - // 1. Safe JSON Body Parsing let rawUrl: string; try { const body = await req.json(); @@ -16,7 +18,6 @@ export async function POST(req: Request) { } try { - // 2. Strict URL Validation const trimmedUrl = rawUrl?.trim(); if (!trimmedUrl) { return NextResponse.json({ error: "GitHub URL is required" }, { status: 400 }); @@ -29,12 +30,10 @@ export async function POST(req: Request) { return NextResponse.json({ error: "Please provide a valid URL" }, { status: 400 }); } - // Hostname Guard if (parsedUrl.hostname !== "github.com" && parsedUrl.hostname !== "www.github.com") { return NextResponse.json({ error: "Only GitHub URLs are supported" }, { status: 400 }); } - // Extract Owner and Repo from path const pathSegments = parsedUrl.pathname.split("/").filter(Boolean); const owner = pathSegments[0]; const repo = pathSegments[1]; @@ -43,41 +42,81 @@ export async function POST(req: Request) { return NextResponse.json({ error: "URL must include owner and repository name" }, { status: 400 }); } - // 3. Parallel Data Fetching const [repoInfo, repoContents] = await Promise.all([ getRepoData(owner, repo), getRepoContents(owner, repo) ]); - // 4. Type-Safe File Mapping (Fixes the 'any' linting error) - // We define the shape { name: string } inline to satisfy ESLint - const fileList = Array.isArray(repoContents) && repoContents.length > 0 - ? repoContents.map((f: { name: string }) => f.name).join(", ") - : "Standard repository structure"; + const files = Array.isArray(repoContents) ? repoContents.map((f: { name: string }) => f.name) : []; + const fileListString = files.length > 0 ? files.join(", ") : "Standard repository structure"; + + // Tech Stack detection logic + const hasNode = files.includes("package.json"); + const hasPython = files.includes("requirements.txt") || files.includes("setup.py"); + const hasDocker = files.includes("Dockerfile") || files.includes("docker-compose.yml"); + + // Fix: Cleanly joined Tech Stack labels + const stackLabels = [ + hasNode && "Node.js Environment", + hasPython && "Python Environment", + hasDocker && "Containerized" + ].filter(Boolean).join(", ") || "Generic Software Environment"; + + // Fix: Dynamic License detection + const licenseName = repoInfo?.license?.name || repoInfo?.license?.spdx_id || "the repository's license file"; - // 5. Initialize Gemini 2.5 const model = getGeminiModel(); - // 6. The "Expert Prompt" with Fallbacks + // Fix: Prompt updated with neutral fallbacks and dynamic license const prompt = ` - You are an expert Technical Writer. Generate a professional README.md for: - - Name: ${repo} - Description: ${repoInfo?.description || "A modern software project."} - Primary Language: ${repoInfo?.language || "Not specified"} - Root Directory Files: ${fileList} - - Requirements: - - Include professional SVG badges from shields.io. - - Create a visual "Directory Structure" section (tree style). - - Include "Features", "Installation", and "Usage" sections. - - If 'package.json' exists, provide Node.js installation steps. - - Ensure a welcoming, professional developer-centric tone. - - Return ONLY the Markdown content. +**Role**: You are a Principal Solutions Architect and World-Class Technical Writer. +**Task**: Generate a professional, high-conversion README.md for the GitHub repository: "${repo}". + +--- +### 1. PROJECT CONTEXT (VERIFIED DATA) +- **Project Name**: ${repo} +- **Description**: ${repoInfo?.description || "No description provided."} +- **Primary Language**: ${repoInfo?.language || "Language unknown"} +- **Detected Root Files**: ${fileListString} +- **Tech Stack Context**: ${stackLabels} + +--- +### 2. STRICT README STRUCTURE REQUIREMENTS + +1. **Visual Header**: + - Center-aligned H1 with project name. + - A compelling 1-sentence tagline describing the **Value Proposition**. + - A centered row of Shields.io badges (Build, License, PRs Welcome, Stars). + +2. **The Strategic "Why" (Overview)**: + - **The Problem**: Use a blockquote to describe the real-world pain point this project solves. + - **The Solution**: Explain how this project provides a superior outcome for the user. + +3. **Key Features**: + - Minimum 5 features. Use emojis and focus on **User Benefits**. + +4. **Technical Architecture**: + - Provide a table of the tech stack: | Technology | Purpose | Key Benefit |. + - Create a tree-style directory structure code block using 📁 for folders and 📄 for files based on the file manifest provided. + +5. **Operational Setup**: + - **Prerequisites**: List required runtimes. + - **Installation**: Provide step-by-step terminal commands. + ${hasNode ? "- Use npm/yarn/pnpm since package.json was detected." : ""} + ${hasPython ? "- Use pip/venv since Python markers were detected." : ""} + - **Environment**: If any .env or config files are in the manifest, include a configuration section. + +6. **Community & Governance**: + - Professional "Contributing" section (Fork -> Branch -> PR). + - Detailed "License" section: Reference ${licenseName} and provide a summary of permissions. + +--- +### 3. TONE & STYLE +- **Tone**: Authoritative, polished, and developer-centric. +- **Visuals**: Extensive use of Markdown formatting. +- **Constraint**: Return ONLY the raw Markdown. No conversational filler. `; - // 7. AI Generation const result = await model.generateContent(prompt); const response = await result.response; const markdown = response.text(); diff --git a/src/lib/prompts.ts b/src/lib/prompts.ts new file mode 100644 index 0000000..e69de29