Repository: index-panayotov/ppanayotov-com
Pull request #1: Ai gen text
Author: login: index-panayotov
Created: 2025-05-26T15:06:05Z | Merged: 2025-05-27T18:39:13Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced AI-powered input and textarea components for one-click professional content enhancement across admin dialogs and profile editing.
  - Added an AI Writing Assistant API for focused CV section improvements with authentication, rate limiting, and error handling.
  - Implemented an API endpoint to analyze experience data and suggest top professional skills.
- **Documentation**
  - Expanded README with detailed AI and image upload features, updated tech stack, environment setup, and deployment instructions.
  - Removed standalone image upload documentation file.
- **Bug Fixes**
  - Enhanced error handling and response validation for automatic skill generation.
- **Chores**
  - Added utility functions and types to support OpenRouter AI integration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/README.md b/README.md
index e56fbf3..a7b75c4 100644
--- a/README.md
+++ b/README.md
@@ -18,8 +18,14 @@
 - **Clean UI with Expandable Sections**  
   Designed for clarity and professionalism, with optional future sections for customization.
 
-- **AI-Assisted Development**  
-  This project was approximately 99% generated with AI assistance, showcasing modern development workflows.
+- **AI-Assisted Content Enhancement**  
+  Includes AI-powered tools to help polish and improve CV content, job descriptions, and professional summaries.
+
+- **Image Upload with Optimization**  
+  Upload and automatically optimize profile images for both web display and PDF exports.
+
+- **Modern Component Library**  
+  Built using a comprehensive collection of accessible UI components based on Radix UI.
 
 > ⚠️ **Security Note:**  
 > It is strongly recommended not to upload the `admin/` folder to your production server unless properly secured. You can run the admin interface locally or restrict access during deployment.
@@ -30,10 +36,11 @@
 
 Planned features under development:
 
-- [x] Add image upload support (e.g., profile picture)
+- [x] Add image upload support with optimization for web and PDF
+- [x] Add AI assistance to improve and polish CV text
 - [ ] Expand contact and social media link options
-- [ ] Add AI assistance to improve and polish CV text
 - [ ] Introduce multiple visual templates for printable CVs
+- [ ] Add support for multiple languages/localization
 
 Have an idea? [Open an issue](https://github.com/index-panayotov/ppanayotov-com/issues) or contribute directly!
 
@@ -43,6 +50,9 @@ Have an idea? [Open an issue](https://github.com/index-panayotov/ppanayotov-com/
 
 - **Framework**: [Next.js](https://nextjs.org/) – React-based frontend framework
 - **Styling**: [Tailwind CSS](https://tailwindcss.com/) – Utility-first CSS framework
+- **UI Components**: [Radix UI](https://www.radix-ui.com/) – Headless, accessible UI primitives
+- **AI Integration**: [OpenRouter](https://openrouter.ai/) – API for accessing various AI models
+- **Image Processing**: [Sharp](https://sharp.pixelplumbing.com/) – High-performance image processing
 - **Hosting**: [Vercel](https://vercel.com/) – Serverless deployment platform
 - **Authentication**: Environment variable-based password protection for admin panel
 
@@ -71,6 +81,8 @@ Create a `.env` file at the root of your project:
 
 ```env
 ADMIN_PASSWORD=your-secure-password
+OPENROUTER_KEY=your-openrouter-api-key
+OPENROUTER_MODEL=openai/gpt-4.1-nano
 ```
 
 4. **Run the development server**
@@ -82,22 +94,29 @@ npm run dev
 Make all changes.
 Visit `http://localhost:3000` to view your site.
 
+5. **Build and deploy**
+
 ```bash
 npm run build
+npm run start   # Optional: run in production mode locally
 ```
 
-## To build your project and deploy it.
-
 ## 📁 Folder Structure
 
 ```
 /app            → Main application (routing, layouts, API, admin)
+  /api          → Backend API routes
+  /admin        → Admin panel pages and API routes
 /components     → Reusable UI and admin components
+  /admin        → Admin-specific components including AI-enhanced inputs
+  /ui           → Reusable UI components based on Radix UI
 /data           → Static data files (CV, skills, user profile)
 /hooks          → Custom React hooks
 /lib            → Utility functions and helpers
 /public         → Static assets (images, icons, etc.)
   /fonts        → Font files for image generation or custom web fonts
+  /uploads      → User-uploaded images with optimized versions
+/services       → API service integrations (e.g., OpenRouter)
 /styles         → Global and component styles
 /types          → TypeScript type definitions
 .env            → Environment variables (e.g., admin password)
@@ -148,6 +167,27 @@ Thanks to all contributors and open-source libraries that make this project poss
 
 ---
 
+## 📷 Image Upload Feature
+
+The project includes a comprehensive image upload feature with optimization for different contexts:
+
+- **Web-optimized Images**: 400x400px, WebP format, 85% quality for fast web loading
+- **PDF-optimized Images**: 200x200px, WebP format, 80% quality for PDF exports
+- **Drag & Drop Support**: Easy uploading via drag and drop or file browser
+- **External URL Support**: Option to use images hosted elsewhere
+- **Validation**: File type and size validation to prevent issues
+
+## 🧠 AI Enhancement Features
+
+The admin interface includes AI-powered content enhancement:
+
+- **Smart Text Improvement**: AI assistance for polishing job descriptions, summaries, and other text
+- **Context-Aware Suggestions**: AI understands the context of different CV sections
+- **Professional Tone**: Ensures content maintains a professional, impactful style
+- **One-Click Enhancement**: Simple button to request AI improvements
+
+These features help create more professional, polished content with minimal effort.
+
 ## 🖼️ Image Uploads & Version Control
 
 Uploaded images (such as profile pictures) are stored in the `/public/uploads` directory. This folder is included in `.gitignore` by default to avoid accidentally committing large or sensitive files. A `gitkeep` file is present to ensure the folder exists in the repository structure.
@@ -164,3 +204,16 @@ git add .\public\uploads\profile-* -f
 - Remember to commit and push after adding.
 
 > **Note:** If you do not force-add, new or updated images in `/public/uploads` will not be tracked by git and will not appear on your deployed site.
+
+## 📚 Environment Variables
+
+The following environment variables should be set in your `.env` file:
+
+```
+# Admin authentication
+ADMIN_PASSWORD=your-secure-password
+
+# OpenRouter API for AI features
+OPENROUTER_KEY=your-openrouter-api-key
+OPENROUTER_MODEL=openai/gpt-4.1-nano
+```
diff --git a/app/admin/handlers.ts b/app/admin/handlers.ts
index fc92839..1d82535 100644
--- a/app/admin/handlers.ts
+++ b/app/admin/handlers.ts
@@ -328,12 +328,16 @@ export const generateAutomaticTopSkills = async (
       },
       body: JSON.stringify({ experiences })
     });
+    const data = await res.json();
 
     if (!res.ok) {
-      throw new Error("Failed to generate top skills");
+      throw new Error(data.error || "Failed to generate top skills");
+    }
+
+    if (!data.topSkills || !Array.isArray(data.topSkills)) {
+      throw new Error("Invalid response format from AI service");
     }
 
-    const data = await res.json();
     setTopSkills(data.topSkills);
 
     toast({
diff --git a/app/api/admin/autoskills/route.ts b/app/api/admin/autoskills/route.ts
new file mode 100644
index 0000000..fd593ba
--- /dev/null
+++ b/app/api/admin/autoskills/route.ts
@@ -0,0 +1,64 @@
+import { NextRequest, NextResponse } from "next/server";
+import { ExperienceEntry } from "@/types";
+import { experiences } from "@/data/cv-data";
+import { userProfile } from "@/data/user-profile";
+
+// Only allow in development mode
+const isDev = process.env.NODE_ENV === "development";
+
+/**
+ * Handles a POST request to analyze experience data and return the top 10 most frequently used skills.
+ *
+ * Accepts a JSON payload containing an array of experience entries; if not provided, uses default imported data. Only available in development mode.
+ *
+ * @returns A JSON response with a `topSkills` array listing the most common skill tags.
+ *
+ * @remark Returns a 403 error if accessed outside development mode, and a 500 error if processing fails.
+ */
+export async function POST(request: NextRequest) {
+  if (!isDev) {
+    return NextResponse.json(
+      { error: "Admin API only available in development mode" },
+      { status: 403 }
+    );
+  }
+
+  try {
+    // Get experiences from the request body or fallback to the imported data
+    const reqBody = await request.json();
+    const experienceData: ExperienceEntry[] =
+      reqBody.experiences || experiences;
+
+    // Collect all tags from experience entries
+    const allTags: string[] = [];
+    experienceData.forEach((exp) => {
+      if (exp.tags && Array.isArray(exp.tags)) {
+        exp.tags.forEach((tag) => {
+          allTags.push(tag);
+        });
+      }
+    });
+
+    // Count occurrences of each tag
+    const tagCounts: Record<string, number> = {};
+    allTags.forEach((tag) => {
+      tagCounts[tag] = (tagCounts[tag] || 0) + 1;
+    });
+
+    // Sort tags by frequency (most frequent first)
+    const sortedTags = Object.entries(tagCounts)
+      .sort((a, b) => b[1] - a[1]) // Sort by count (descending)
+      .map((entry) => entry[0]); // Extract just the tag names
+
+    // Return the top 10 skills
+    return NextResponse.json({
+      topSkills: sortedTags.slice(0, 10)
+    });
+  } catch (error) {
+    console.error("Error generating top skills:", error);
+    return NextResponse.json(
+      { error: "Failed to generate top skills" },
+      { status: 500 }
+    );
+  }
+}
diff --git a/app/api/ai/route.ts b/app/api/ai/route.ts
new file mode 100644
index 0000000..284ec9d
--- /dev/null
+++ b/app/api/ai/route.ts
@@ -0,0 +1,344 @@
+import { NextRequest, NextResponse } from "next/server";
+import { getOpenRouterAnswer } from "@/services/openrouter";
+
+// In-memory storage for rate limiting
+// Note: This will reset when the serverless function cold starts
+type RequestRecord = {
+  timestamps: number[]; // Array of timestamps for each request
+  lastCleanup: number; // Last time we cleaned up old timestamps
+};
+
+const ipRequestMap = new Map<string, RequestRecord>();
+const REQUESTS_PER_MINUTE = 10;
+const WINDOW_MS = 60 * 1000; // 1 minute
+const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // Clean up every 5 minutes
+
+/**
+ * Checks if the client IP has exceeded the rate limit.
+ * @param clientIp The client's IP address
+ * @returns Whether the client has been rate limited
+ */
+async function isRateLimited(clientIp: string): Promise<boolean> {
+  const now = Date.now();
+
+  // Skip rate limiting for development
+  if (process.env.NODE_ENV === "development") {
+    return false;
+  }
+
+  // Create or get the record for this IP
+  if (!ipRequestMap.has(clientIp)) {
+    ipRequestMap.set(clientIp, { timestamps: [], lastCleanup: now });
+  }
+
+  const record = ipRequestMap.get(clientIp)!;
+
+  // Clean up old timestamps if needed
+  if (now - record.lastCleanup > CLEANUP_INTERVAL_MS) {
+    record.timestamps = record.timestamps.filter(
+      (timestamp) => now - timestamp < WINDOW_MS
+    );
+    record.lastCleanup = now;
+  }
+
+  // Filter to only include timestamps within the current window
+  const recentTimestamps = record.timestamps.filter(
+    (timestamp) => now - timestamp < WINDOW_MS
+  );
+
+  // Check if rate limit is exceeded
+  if (recentTimestamps.length >= REQUESTS_PER_MINUTE) {
+    return true;
+  }
+
+  // Record this request
+  record.timestamps.push(now);
+  return false;
+}
+
+// Define the comprehensive system prompt for the CV Writing Assistant
+const CV_WRITING_ASSISTANT_PROMPT = `
+AI System Prompt: Professional CV Writing Assistant
+
+Role: You are a professional CV Writing Assistant. Your expertise lies in transforming raw information and existing text into concise, impactful, and professional language suitable for a modern CV. You understand common CV structures, best practices for different fields (e.g., achievements, skills, summaries), and how to tailor language for maximum effect.
+
+Objective: To assist the user in updating their CV *one specific field or section at a time* by providing suggested professional text based on their input for that particular field.
+
+Mode of Operation:
+1.  You will wait for the user to explicitly state *which specific CV field or section* they want to work on next (e.g., "Summary," "Skills," "Work Experience - Job Title X," "Education," "Achievements," etc.).
+2.  The user will provide you with their current text for that field, key points they want to include, or a description of what they need help writing for that specific part of the CV.
+3.  Based *only* on the input for that single, requested field, you will provide polished, professional text options or suggestions.
+4.  You will ask clarifying questions *only* if necessary to improve the suggestions for the *current field*.
+5.  You will maintain a focused conversation on the *currently requested field* until the user indicates they are ready to move to another.
+
+Capabilities:
+*   Suggest professional wording and phrasing for any CV section.
+*   Help condense lengthy descriptions into concise bullet points or summaries.
+*   Identify opportunities to use stronger action verbs.
+*   Suggest ways to quantify achievements where applicable (if the user provides data).
+*   Tailor language to sound more impactful and relevant.
+*   Format suggestions clearly (e.g., bullet points for experience/skills, paragraph for summary).
+*   Maintain a professional, encouraging, and patient tone.
+
+Constraints & Rules:
+*   **CRITICAL:** DO NOT jump ahead or anticipate which field the user will want to work on next.
+*   **CRITICAL:** DO NOT list all possible CV sections or create a step-by-step plan *unless specifically asked by the user*. Wait for the user to guide the flow by requesting the next field.
+*   Focus *only* on the specific field the user has currently requested help with.
+*   Do not ask for information about other CV fields unless it's directly relevant to improving the *current* field (e.g., asking about the industry to tailor skill descriptions).
+*   Keep responses helpful, professional, and directly related to the user's input for the current field.
+
+Starting Instruction:
+(This instruction helps reinforce the starting behavior for the AI. When the user sends their first message to this endpoint after it receives this prompt, the AI should respond according to this instruction.)
+Hello! I'm your Professional CV Writing Assistant, ready to help you refine your CV field by field. Just tell me which specific section you'd like to work on, share your current text or ideas for it, and I'll provide some professional suggestions. Which part would you like to start with?
+`;
+/**
+ * Handles POST requests to the Professional CV Writing Assistant API endpoint.
+ *
+ * Accepts user input for a specific CV section and returns AI-generated professional text suggestions tailored to that section. The AI operates under a detailed system prompt designed to guide the writing process, with optional customization of creativity and prompt.
+ *
+ * @returns A JSON response containing the AI-generated suggestion for the provided CV section, or an error message if the request is invalid or processing fails.
+ *
+ * @throws {Error} If the request body is missing the required `data` field, responds with a 400 status and an error message. If an unexpected error occurs during processing, responds with a 500 status and an error message.
+ */
+
+export async function POST(req: NextRequest) {
+  try {
+    // Authentication check: Verify user identity
+    const isDev = process.env.NODE_ENV === "development";
+
+    // Check authentication by verifying the admin_authenticated cookie
+    const isAuthenticated = req.cookies.has("admin_authenticated");
+
+    // Get the API key from the request header if provided
+    const apiKey = req.headers.get("x-api-key");
+    const validApiKey = process.env.AI_API_KEY;
+    const hasValidApiKey = apiKey && validApiKey && apiKey === validApiKey;
+
+    // If not in development mode and neither authenticated via cookie nor valid API key, reject the request
+    if (!isDev && !isAuthenticated && !hasValidApiKey) {
+      return NextResponse.json(
+        { error: "Unauthorized: Authentication required" },
+        { status: 401 }
+      );
+    }
+
+    // Implement basic rate limiting
+    const clientIp =
+      req.headers.get("x-forwarded-for")?.split(",")[0] ||
+      req.headers.get("x-real-ip") ||
+      "unknown-ip";
+
+    // Apply rate limiting check
+    if (await isRateLimited(clientIp)) {
+      return NextResponse.json(
+        { error: "Rate limit exceeded: Please try again later" },
+        { status: 429 }
+      );
+    }
+
+    // Parse the request body with error handling
+    let body;
+    try {
+      body = await req.json();
+    } catch (error) {
+      return NextResponse.json(
+        { error: "Invalid JSON: Could not parse request body" },
+        { status: 400 }
+      );
+    }
+
+    // Comprehensive validation of the request body
+    if (!body || typeof body !== "object") {
+      return NextResponse.json(
+        { error: "Invalid request: Request body must be a valid JSON object" },
+        { status: 400 }
+      );
+    } // Extract and validate required fields
+    const { data, creativity, systemInput: userSystemInput, model } = body;
+
+    // Validate data field (required)
+    if (data === undefined) {
+      return NextResponse.json(
+        { error: "Missing required field: data is required" },
+        { status: 400 }
+      );
+    }
+
+    // Validate data type and content
+    if (typeof data !== "string") {
+      return NextResponse.json(
+        { error: "Invalid data format: data must be a string" },
+        { status: 400 }
+      );
+    }
+
+    // Check data length to prevent abuse
+    if (data.length > 10000) {
+      return NextResponse.json(
+        {
+          error:
+            "Invalid data: content exceeds maximum length (10000 characters)"
+        },
+        { status: 400 }
+      );
+    }
+
+    // Check for empty content after trimming
+    if (data.trim().length === 0) {
+      return NextResponse.json(
+        { error: "Invalid data: content cannot be empty" },
+        { status: 400 }
+      );
+    }
+
+    // Validate creativity if provided
+    if (creativity !== undefined) {
+      if (typeof creativity !== "number") {
+        return NextResponse.json(
+          { error: "Invalid creativity format: must be a number" },
+          { status: 400 }
+        );
+      }
+
+      if (creativity < 0 || creativity > 1) {
+        return NextResponse.json(
+          { error: "Invalid creativity value: must be between 0 and 1" },
+          { status: 400 }
+        );
+      }
+    }
+
+    // Validate systemInput if provided
+    if (userSystemInput !== undefined) {
+      if (typeof userSystemInput !== "string") {
+        return NextResponse.json(
+          { error: "Invalid systemInput format: must be a string" },
+          { status: 400 }
+        );
+      }
+
+      // Check for potential prompt injection or malicious input
+      if (userSystemInput.length > 20000) {
+        return NextResponse.json(
+          {
+            error:
+              "Invalid systemInput: exceeds maximum length (20000 characters)"
+          },
+          { status: 400 }
+        );
+      }
+    }
+
+    // Validate model if provided (optional enhancement)
+    if (model !== undefined) {
+      if (typeof model !== "string") {
+        return NextResponse.json(
+          { error: "Invalid model format: must be a string" },
+          { status: 400 }
+        );
+      }
+
+      // Validate against allowed models if needed
+      const allowedModels = ["gpt-4", "gpt-3.5-turbo", "claude-3"]; // Example
+      if (!allowedModels.includes(model)) {
+        return NextResponse.json(
+          {
+            error: `Invalid model: must be one of ${allowedModels.join(", ")}`
+          },
+          { status: 400 }
+        );
+      }
+    } // Use the specific CV writing assistant prompt as the system input if no custom systemInput provided
+    const systemInput = userSystemInput || CV_WRITING_ASSISTANT_PROMPT;
+
+    // Set up request timeout with AbortController
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), 60000); // 60-second timeout
+
+    let aiResponse: string;
+
+    try {
+      // Call OpenRouter service with validated inputs
+      // 'data' from the request body will serve as the user's input for the current field
+      aiResponse = await getOpenRouterAnswer({
+        systemInput,
+        data, // This contains the user's specific input for the current field
+        creativity: creativity !== undefined ? creativity : 0.2, // Allow user to override, default to 0.2
+        customModel: model, // Pass the model if provided, using the correct property name
+        signal: controller.signal // Pass the abort signal for timeout handling
+      });
+
+      // Clear the timeout
+      clearTimeout(timeoutId);
+
+      // Validate AI response more thoroughly
+      if (!aiResponse) {
+        throw new Error("Failed to get response from AI service");
+      }
+
+      if (typeof aiResponse !== "string") {
+        throw new Error("Invalid response format from AI service");
+      }
+
+      // Log successful API calls (optional)
+      console.log(`AI API call successful: ${new Date().toISOString()}`);
+
+      // Return the AI response
+      return NextResponse.json({ response: aiResponse });
+    } catch (error) {
+      // Make sure to clear the timeout if there was an error
+      clearTimeout(timeoutId);
+      throw error;
+    }
+  } catch (error: any) {
+    console.error("AI endpoint error:", error);
+
+    // Provide more specific error messages based on error types
+    if (error.message?.includes("OPENROUTER_KEY")) {
+      return NextResponse.json(
+        { error: "AI service configuration error: Missing API key" },
+        { status: 500 }
+      );
+    } else if (error.message?.includes("OpenRouter API error")) {
+      return NextResponse.json(
+        { error: "AI service error: Failed to connect to AI provider" },
+        { status: 503 }
+      );
+    } else if (
+      error.message?.includes("timeout") ||
+      error.name === "AbortError"
+    ) {
+      return NextResponse.json(
+        { error: "Request timeout: The AI service took too long to respond" },
+        { status: 504 }
+      );
+    } else if (error.name === "SyntaxError") {
+      return NextResponse.json(
+        { error: "Invalid response from AI service" },
+        { status: 502 }
+      );
+    } else if (
+      error.message?.includes("429") ||
+      error.message?.includes("too many requests")
+    ) {
+      return NextResponse.json(
+        { error: "Rate limit exceeded: Please try again later" },
+        { status: 429 }
+      );
+    } else if (
+      error.message?.includes("403") ||
+      error.message?.includes("forbidden")
+    ) {
+      return NextResponse.json(
+        { error: "API access forbidden: Permission denied" },
+        { status: 403 }
+      );
+    } else {
+      // Generic error handler
+      return NextResponse.json(
+        { error: error.message || "Failed to process AI request" },
+        { status: 500 }
+      );
+    }
+  }
+}
diff --git a/components/admin/ai-components.ts b/components/admin/ai-components.ts
new file mode 100644
index 0000000..aa197c5
--- /dev/null
+++ b/components/admin/ai-components.ts
@@ -0,0 +1,7 @@
+export * from "./ai-enhanced-input";
+export * from "./ai-enhanced-textarea";
+
+/**
+ * This file exports all AI-enhanced input components that are used throughout the admin panel.
+ * These components provide AI-powered content improvement capabilities via the OpenRouter API.
+ */
diff --git a/components/admin/ai-enhanced-input.tsx b/components/admin/ai-enhanced-input.tsx
new file mode 100644
index 0000000..7bf0382
--- /dev/null
+++ b/components/admin/ai-enhanced-input.tsx
@@ -0,0 +1,166 @@
+"use client";
+
+import React, { useState } from "react";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { Loader2 } from "lucide-react";
+import { RiRobot2Fill } from "react-icons/ri";
+import { toast } from "@/components/ui/use-toast";
+
+interface AIEnhancedInputProps
+  extends React.InputHTMLAttributes<HTMLInputElement> {
+  onValueChange?: (value: string) => void;
+  fieldName?: string;
+}
+
+export const AIEnhancedInput = React.forwardRef<
+  HTMLInputElement,
+  AIEnhancedInputProps
+>(({ className, onValueChange, fieldName, value, onChange, ...props }, ref) => {
+  const [isLoading, setIsLoading] = useState(false);
+
+  const handleAIClick = async () => {
+    if (!value) {
+      toast({
+        title: "No content to enhance",
+        description: "Please add some content to enhance with AI.",
+        variant: "destructive"
+      });
+      return;
+    }
+
+    setIsLoading(true);
+
+    // Set up AbortController with timeout
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => {
+      controller.abort();
+    }, 30000); // 30-second timeout
+
+    try {
+      const response = await fetch("/api/ai", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json"
+        },
+        body: JSON.stringify({
+          systemInput: `You are a professional content enhancer specializing in CV and resume content. 
+Your task is to improve the provided ${fieldName ||
+            "text"} to make it more professional, concise, and impactful.
+Keep the same meaning but enhance the wording. Respond with ONLY the improved text without any explanations or additional text.`,
+          data: String(value),
+          creativity: 0.3
+        }),
+        signal: controller.signal
+      });
+
+      // Clear the timeout since the request completed
+      clearTimeout(timeoutId);
+
+      // Handle non-OK responses before attempting to parse JSON
+      if (!response.ok) {
+        const errorText = await response.text().catch(() => "");
+        throw new Error(
+          errorText ||
+            `Server responded with ${response.status}: ${response.statusText}`
+        );
+      }
+
+      // Parse JSON with error handling
+      let data;
+      try {
+        data = await response.json();
+      } catch (parseError) {
+        throw new Error("Failed to parse server response");
+      }
+
+      // Validate that the response contains the expected data
+      if (!data || typeof data.response !== "string") {
+        throw new Error("Invalid response format from AI service");
+      }
+
+      // Call the parent's onChange handler with a synthetic event
+      if (onChange) {
+        const syntheticEvent = {
+          target: {
+            value: data.response,
+            name: props.name
+          }
+        } as React.ChangeEvent<HTMLInputElement>;
+
+        onChange(syntheticEvent);
+      }
+
+      // Also call the onValueChange callback if provided
+      if (onValueChange) {
+        onValueChange(data.response);
+      }
+
+      toast({
+        title: "Content enhanced",
+        description: "AI has enhanced your content."
+      });
+    } catch (error) {
+      console.error("AI Enhancement error:", error);
+
+      // Clear the timeout to prevent memory leaks
+      clearTimeout(timeoutId);
+
+      // Handle specific error types
+      let errorMessage = "Failed to enhance content with AI";
+
+      if (error instanceof DOMException && error.name === "AbortError") {
+        errorMessage = "Request timed out. Please try again later.";
+      } else if (error instanceof Error) {
+        if (
+          error.message.includes("NetworkError") ||
+          error.message.includes("network")
+        ) {
+          errorMessage =
+            "Network error. Please check your connection and try again.";
+        } else {
+          errorMessage = error.message;
+        }
+      }
+
+      toast({
+        title: "Enhancement failed",
+        description: errorMessage,
+        variant: "destructive"
+      });
+    } finally {
+      setIsLoading(false);
+    }
+  };
+  return (
+    <div className="flex w-full items-center gap-0">
+      <div className="flex-grow">
+        <Input
+          className={`${className} pr-2`}
+          value={value}
+          onChange={onChange}
+          ref={ref}
+          {...props}
+        />
+      </div>
+      <Button
+        type="button"
+        variant="ghost"
+        size="icon"
+        className="min-w-10 h-10 rounded-l-none border border-l-0 border-input flex-shrink-0"
+        onClick={handleAIClick}
+        disabled={isLoading}
+        aria-label={
+          isLoading ? "Enhancing content with AI..." : "Enhance content with AI"
+        }
+        title="AI enhance"
+      >
+        {isLoading
+          ? <Loader2 className="h-4 w-4 animate-spin" />
+          : <RiRobot2Fill className="h-4 w-4" />}
+      </Button>
+    </div>
+  );
+});
+
+AIEnhancedInput.displayName = "AIEnhancedInput";
diff --git a/components/admin/ai-enhanced-textarea.tsx b/components/admin/ai-enhanced-textarea.tsx
new file mode 100644
index 0000000..9bd034e
--- /dev/null
+++ b/components/admin/ai-enhanced-textarea.tsx
@@ -0,0 +1,166 @@
+"use client";
+
+import React, { useState } from "react";
+import { Textarea } from "@/components/ui/textarea";
+import { Button } from "@/components/ui/button";
+import { Loader2 } from "lucide-react";
+import { RiRobot2Fill } from "react-icons/ri";
+import { toast } from "@/components/ui/use-toast";
+
+interface AIEnhancedTextareaProps
+  extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
+  onValueChange?: (value: string) => void;
+  fieldName?: string;
+}
+
+export const AIEnhancedTextarea = React.forwardRef<
+  HTMLTextAreaElement,
+  AIEnhancedTextareaProps
+>(({ className, onValueChange, fieldName, value, onChange, ...props }, ref) => {
+  const [isLoading, setIsLoading] = useState(false);
+  const handleAIClick = async () => {
+    if (!value) {
+      toast({
+        title: "No content to enhance",
+        description: "Please add some content to enhance with AI.",
+        variant: "destructive"
+      });
+      return;
+    }
+
+    setIsLoading(true);
+
+    // Set up AbortController with timeout
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => {
+      controller.abort();
+    }, 30000); // 30-second timeout
+
+    try {
+      const response = await fetch("/api/ai", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json"
+        },
+        body: JSON.stringify({
+          systemInput: `You are a professional content enhancer specializing in CV and resume content. 
+Your task is to improve the provided ${fieldName ||
+            "text"} to make it more professional, concise, and impactful.
+Keep the same meaning but enhance the wording and structure. Format appropriately with bullet points if it's a list of accomplishments.
+Respond with ONLY the improved text without any explanations or additional text.`,
+          data: String(value),
+          creativity: 0.3
+        }),
+        signal: controller.signal
+      });
+
+      // Clear the timeout since the request completed
+      clearTimeout(timeoutId);
+
+      // Handle non-OK responses before attempting to parse JSON
+      if (!response.ok) {
+        const errorText = await response.text().catch(() => "");
+        throw new Error(
+          errorText ||
+            `Server responded with ${response.status}: ${response.statusText}`
+        );
+      }
+
+      // Parse JSON with error handling
+      let data;
+      try {
+        data = await response.json();
+      } catch (parseError) {
+        throw new Error("Failed to parse server response");
+      }
+
+      // Validate that the response contains the expected data
+      if (!data || typeof data.response !== "string") {
+        throw new Error("Invalid response format from AI service");
+      }
+
+      // Call the parent's onChange handler with a synthetic event
+      if (onChange) {
+        const syntheticEvent = {
+          target: {
+            value: data.response,
+            name: props.name
+          }
+        } as React.ChangeEvent<HTMLTextAreaElement>;
+
+        onChange(syntheticEvent);
+      }
+
+      // Also call the onValueChange callback if provided
+      if (onValueChange) {
+        onValueChange(data.response);
+      }
+
+      toast({
+        title: "Content enhanced",
+        description: "AI has enhanced your content."
+      });
+    } catch (error) {
+      console.error("AI Enhancement error:", error);
+
+      // Clear the timeout to prevent memory leaks
+      clearTimeout(timeoutId);
+
+      // Handle specific error types
+      let errorMessage = "Failed to enhance content with AI";
+
+      if (error instanceof DOMException && error.name === "AbortError") {
+        errorMessage = "Request timed out. Please try again later.";
+      } else if (error instanceof Error) {
+        if (
+          error.message.includes("NetworkError") ||
+          error.message.includes("network")
+        ) {
+          errorMessage =
+            "Network error. Please check your connection and try again.";
+        } else {
+          errorMessage = error.message;
+        }
+      }
+
+      toast({
+        title: "Enhancement failed",
+        description: errorMessage,
+        variant: "destructive"
+      });
+    } finally {
+      setIsLoading(false);
+    }
+  };
+  return (
+    <div className="relative">
+      <Textarea
+        className={`${className} pr-12 pb-10 resize-none`}
+        value={value}
+        onChange={onChange}
+        ref={ref}
+        {...props}
+      />
+      <Button
+        type="button"
+        variant="ghost"
+        size="icon"
+        className="absolute top-2 right-2 bg-background hover:bg-gray-100"
+        onClick={handleAIClick}
+        disabled={isLoading}
+        aria-label={
+          isLoading
+            ? "Enhancing content with AI..."
+            : "Enhance content with AI using machine learning"
+        }
+        title="AI enhance content"
+      >
+        {isLoading
+          ? <Loader2 className="h-4 w-4 animate-spin" />
+          : <RiRobot2Fill className="h-4 w-4" />}
+      </Button>
+    </div>
+  );
+});
+
+AIEnhancedTextarea.displayName = "AIEnhancedTextarea";
diff --git a/components/admin/certification-dialog.tsx b/components/admin/certification-dialog.tsx
index c1244c3..842cc21 100644
--- a/components/admin/certification-dialog.tsx
+++ b/components/admin/certification-dialog.tsx
@@ -9,6 +9,7 @@ import {
 } from '@/components/ui/dialog';
 import { Input } from '@/components/ui/input';
 import { Button } from '@/components/ui/button';
+import { AIEnhancedInput } from '@/components/admin/ai-enhanced-input';
 import { CertificationDialogProps } from '@/types/admin-components';
 
 const CertificationDialog: React.FC<CertificationDialogProps> = ({
@@ -31,25 +32,25 @@ const CertificationDialog: React.FC<CertificationDialogProps> = ({
       {currentCertification && (
         <div className="grid gap-4 py-4">
           <div className="space-y-2">
-            <label htmlFor="name" className="text-sm font-medium">Certification Name</label>
-            <Input 
+            <label htmlFor="name" className="text-sm font-medium">Certification Name</label>            <AIEnhancedInput 
               id="name" 
+              fieldName="certification name"
               value={currentCertification.name} 
               onChange={(e) => setCurrentCertification({ ...currentCertification, name: e.target.value })}
             />
           </div>
           <div className="space-y-2">
-            <label htmlFor="issuer" className="text-sm font-medium">Issuing Organization</label>
-            <Input 
+            <label htmlFor="issuer" className="text-sm font-medium">Issuing Organization</label>            <AIEnhancedInput 
               id="issuer" 
+              fieldName="issuing organization"
               value={currentCertification.issuer || ''} 
               onChange={(e) => setCurrentCertification({ ...currentCertification, issuer: e.target.value })}
             />
           </div>
           <div className="space-y-2">
-            <label htmlFor="date" className="text-sm font-medium">Date</label>
-            <Input 
+            <label htmlFor="date" className="text-sm font-medium">Date</label>            <AIEnhancedInput 
               id="date" 
+              fieldName="certification date"
               value={currentCertification.date || ''} 
               onChange={(e) => setCurrentCertification({ ...currentCertification, date: e.target.value })}
               placeholder="e.g. June 2023"
diff --git a/components/admin/education-dialog.tsx b/components/admin/education-dialog.tsx
index 16193df..a558fdd 100644
--- a/components/admin/education-dialog.tsx
+++ b/components/admin/education-dialog.tsx
@@ -9,6 +9,7 @@ import {
 } from '@/components/ui/dialog';
 import { Input } from '@/components/ui/input';
 import { Button } from '@/components/ui/button';
+import { AIEnhancedInput } from '@/components/admin/ai-enhanced-input';
 
 interface EducationDialogProps {
   open: boolean;
@@ -38,33 +39,33 @@ const EducationDialog: React.FC<EducationDialogProps> = ({
       {currentEducation && (
         <div className="grid gap-4 py-4">
           <div className="space-y-2">
-            <label htmlFor="institution" className="text-sm font-medium">Institution</label>
-            <Input 
+            <label htmlFor="institution" className="text-sm font-medium">Institution</label>            <AIEnhancedInput 
               id="institution" 
+              fieldName="educational institution"
               value={currentEducation.institution} 
               onChange={(e) => setCurrentEducation({ ...currentEducation, institution: e.target.value })}
             />
           </div>
           <div className="space-y-2">
-            <label htmlFor="degree" className="text-sm font-medium">Degree</label>
-            <Input 
+            <label htmlFor="degree" className="text-sm font-medium">Degree</label>            <AIEnhancedInput 
               id="degree" 
+              fieldName="academic degree"
               value={currentEducation.degree} 
               onChange={(e) => setCurrentEducation({ ...currentEducation, degree: e.target.value })}
             />
           </div>
           <div className="space-y-2">
-            <label htmlFor="field" className="text-sm font-medium">Field of Study</label>
-            <Input 
+            <label htmlFor="field" className="text-sm font-medium">Field of Study</label>            <AIEnhancedInput 
               id="field" 
+              fieldName="field of study"
               value={currentEducation.field} 
               onChange={(e) => setCurrentEducation({ ...currentEducation, field: e.target.value })}
             />
           </div>
           <div className="space-y-2">
-            <label htmlFor="dateRange" className="text-sm font-medium">Date Range</label>
-            <Input 
+            <label htmlFor="dateRange" className="text-sm font-medium">Date Range</label>            <AIEnhancedInput 
               id="dateRange" 
+              fieldName="education date range"
               value={currentEducation.dateRange} 
               onChange={(e) => setCurrentEducation({ ...currentEducation, dateRange: e.target.value })}
               placeholder="e.g. 2010 - 2014"
diff --git a/components/admin/experience-dialog.tsx b/components/admin/experience-dialog.tsx
index 3483725..ce56615 100644
--- a/components/admin/experience-dialog.tsx
+++ b/components/admin/experience-dialog.tsx
@@ -11,6 +11,8 @@ import { Input } from '@/components/ui/input';
 import { Button } from '@/components/ui/button';
 import { Textarea } from '@/components/ui/textarea';
 import { Badge } from '@/components/ui/badge';
+import { AIEnhancedInput } from '@/components/admin/ai-enhanced-input';
+import { AIEnhancedTextarea } from '@/components/admin/ai-enhanced-textarea';
 import { X } from 'lucide-react';
 import { ExperienceEntry } from '@/types';
 
@@ -49,46 +51,48 @@ const ExperienceDialog: React.FC<ExperienceDialogProps> = ({
       </DialogHeader>
       {currentExperience && (
         <div className="grid gap-4 py-4">
-          <div className="grid grid-cols-2 gap-4">
-            <div className="space-y-2">
+          <div className="grid grid-cols-2 gap-4">            <div className="space-y-2">
               <label htmlFor="title" className="text-sm font-medium">Title</label>
-              <Input 
+              <AIEnhancedInput 
                 id="title" 
+                fieldName="job title"
                 value={currentExperience.title} 
                 onChange={(e) => setCurrentExperience({ ...currentExperience, title: e.target.value })}
               />
             </div>
             <div className="space-y-2">
               <label htmlFor="company" className="text-sm font-medium">Company</label>
-              <Input 
+              <AIEnhancedInput 
                 id="company" 
+                fieldName="company name"
                 value={currentExperience.company} 
                 onChange={(e) => setCurrentExperience({ ...currentExperience, company: e.target.value })}
               />
             </div>
           </div>
-          <div className="grid grid-cols-2 gap-4">
-            <div className="space-y-2">
+          <div className="grid grid-cols-2 gap-4">            <div className="space-y-2">
               <label htmlFor="dateRange" className="text-sm font-medium">Date Range</label>
-              <Input 
+              <AIEnhancedInput 
                 id="dateRange" 
+                fieldName="date range"
                 value={currentExperience.dateRange} 
                 onChange={(e) => setCurrentExperience({ ...currentExperience, dateRange: e.target.value })}
               />
             </div>
             <div className="space-y-2">
               <label htmlFor="location" className="text-sm font-medium">Location (optional)</label>
-              <Input 
+              <AIEnhancedInput 
                 id="location" 
+                fieldName="job location"
                 value={currentExperience.location || ''} 
                 onChange={(e) => setCurrentExperience({ ...currentExperience, location: e.target.value })}
               />
             </div>
-          </div>
-          <div className="space-y-2">
+          </div>          <div className="space-y-2">
             <label htmlFor="description" className="text-sm font-medium">Description</label>
-            <Textarea 
+            <AIEnhancedTextarea 
               id="description" 
+              fieldName="job description"
               rows={5}
               value={currentExperience.description} 
               onChange={(e) => setCurrentExperience({ ...currentExperience, description: e.target.value })}
@@ -107,9 +111,9 @@ const ExperienceDialog: React.FC<ExperienceDialogProps> = ({
                 </Badge>
               ))}
             </div>
-            <div className="flex gap-2">
-              <Input 
+            <div className="flex gap-2">              <AIEnhancedInput 
                 placeholder="Add new tag" 
+                fieldName="skill tag"
                 value={newSkill} 
                 onChange={(e) => setNewSkill(e.target.value)}
                 onKeyDown={(e) => e.key === 'Enter' && addTag()}
diff --git a/components/admin/language-dialog.tsx b/components/admin/language-dialog.tsx
index 7862085..b058a3f 100644
--- a/components/admin/language-dialog.tsx
+++ b/components/admin/language-dialog.tsx
@@ -9,6 +9,7 @@ import {
 } from '@/components/ui/dialog';
 import { Input } from '@/components/ui/input';
 import { Button } from '@/components/ui/button';
+import { AIEnhancedInput } from '@/components/admin/ai-enhanced-input';
 import { LanguageProficiency } from '@/types/profile';
 
 interface LanguageDialogProps {
@@ -39,9 +40,9 @@ const LanguageDialog: React.FC<LanguageDialogProps> = ({
       {currentLanguage && (
         <div className="grid gap-4 py-4">
           <div className="space-y-2">
-            <label htmlFor="name" className="text-sm font-medium">Language Name</label>
-            <Input 
+            <label htmlFor="name" className="text-sm font-medium">Language Name</label>            <AIEnhancedInput 
               id="name" 
+              fieldName="language name"
               value={currentLanguage.name} 
               onChange={(e) => setCurrentLanguage({ ...currentLanguage, name: e.target.value })}
             />
diff --git a/components/admin/profile-data-tab.tsx b/components/admin/profile-data-tab.tsx
index 53149c4..5997ea2 100644
--- a/components/admin/profile-data-tab.tsx
+++ b/components/admin/profile-data-tab.tsx
@@ -4,6 +4,8 @@ import { useState } from 'react';
 import { Button } from '@/components/ui/button';
 import { Textarea } from '@/components/ui/textarea';
 import { Input } from '@/components/ui/input';
+import { AIEnhancedInput } from '@/components/admin/ai-enhanced-input';
+import { AIEnhancedTextarea } from '@/components/admin/ai-enhanced-textarea';
 import { 
   Card, 
   CardContent, 
@@ -16,6 +18,34 @@ import { ArrowUp, ArrowDown, Edit, Trash2, Plus } from 'lucide-react';
 import { ProfileDataTabProps } from '@/types/admin-components';
 import ImageUpload from '@/components/admin/image-upload';
 
+/**
+ * Renders a user profile editing interface with both visual and JSON editing modes.
+ *
+ * Provides structured forms for editing basic profile information, languages, education, and certifications, as well as a JSON editor for advanced editing. Supports adding, editing, deleting, and reordering list items, and allows profile image uploads. All data changes and persistence actions are delegated to handler functions provided via props.
+ *
+ * @param profileData - The current user profile data to display and edit.
+ * @param setProfileData - Updates the profile data state.
+ * @param editMode - The current editing mode, either "visual" or "json".
+ * @param setEditMode - Changes the editing mode.
+ * @param saving - Indicates whether a save operation is in progress.
+ * @param handleSave - Invoked to persist the profile data.
+ * @param handleProfileDataChange - Handles changes in the JSON editor.
+ * @param handleProfileFieldChange - Handles changes to individual profile fields in the visual editor.
+ * @param addLanguage - Adds a new language entry.
+ * @param editLanguage - Edits an existing language entry.
+ * @param deleteLanguage - Deletes a language entry.
+ * @param moveLanguage - Moves a language entry up or down in the list.
+ * @param addEducation - Adds a new education entry.
+ * @param editEducation - Edits an existing education entry.
+ * @param deleteEducation - Deletes an education entry.
+ * @param moveEducation - Moves an education entry up or down in the list.
+ * @param addCertification - Adds a new certification entry.
+ * @param editCertification - Edits an existing certification entry.
+ * @param deleteCertification - Deletes a certification entry.
+ * @param moveCertification - Moves a certification entry up or down in the list.
+ *
+ * @remark The component does not perform internal validation or error handling; all data management is handled externally via the provided handler functions.
+ */
 export default function ProfileDataTab({
   profileData,
   setProfileData,
@@ -71,57 +101,60 @@ export default function ProfileDataTab({
               <CardTitle>Basic Information</CardTitle>
             </CardHeader>
             <CardContent className="space-y-4">
-              <div className="grid grid-cols-2 gap-4">
-                <div className="space-y-2">
+              <div className="grid grid-cols-2 gap-4">                <div className="space-y-2">
                   <label htmlFor="name" className="text-sm font-medium">Name</label>
-                  <Input 
+                  <AIEnhancedInput 
                     id="name" 
-                    value={profileData.name || ''} 
+                    value={profileData.name || ''}
+                    fieldName="name" 
                     onChange={(e) => handleProfileFieldChange('name', e.target.value)}
                   />
                 </div>
                 <div className="space-y-2">
                   <label htmlFor="title" className="text-sm font-medium">Title</label>
-                  <Input 
+                  <AIEnhancedInput 
                     id="title" 
-                    value={profileData.title || ''} 
+                    value={profileData.title || ''}
+                    fieldName="professional title" 
                     onChange={(e) => handleProfileFieldChange('title', e.target.value)}
                   />
                 </div>
               </div>
               
-              <div className="grid grid-cols-2 gap-4">
-                <div className="space-y-2">
+              <div className="grid grid-cols-2 gap-4">                <div className="space-y-2">
                   <label htmlFor="location" className="text-sm font-medium">Location</label>
-                  <Input 
+                  <AIEnhancedInput 
                     id="location" 
+                    fieldName="location"
                     value={profileData.location || ''} 
                     onChange={(e) => handleProfileFieldChange('location', e.target.value)}
                   />
                 </div>
                 <div className="space-y-2">
                   <label htmlFor="email" className="text-sm font-medium">Email</label>
-                  <Input 
+                  <AIEnhancedInput 
                     id="email" 
+                    fieldName="email"
                     type="email"
                     value={profileData.email || ''} 
                     onChange={(e) => handleProfileFieldChange('email', e.target.value)}
                   />
                 </div>
               </div>
-                <div className="grid grid-cols-2 gap-4">
-                <div className="space-y-2">
+                <div className="grid grid-cols-2 gap-4">                <div className="space-y-2">
                   <label htmlFor="linkedin" className="text-sm font-medium">LinkedIn</label>
-                  <Input 
+                  <AIEnhancedInput 
                     id="linkedin" 
+                    fieldName="LinkedIn URL"
                     value={profileData.linkedin || ''} 
                     onChange={(e) => handleProfileFieldChange('linkedin', e.target.value)}
                   />
                 </div>
                 <div className="space-y-2">
                   <label htmlFor="phone" className="text-sm font-medium">Phone</label>
-                  <Input 
+                  <AIEnhancedInput 
                     id="phone" 
+                    fieldName="phone"
                     type="tel"
                     value={profileData.phone || ''} 
                     onChange={(e) => handleProfileFieldChange('phone', e.target.value)}
@@ -143,11 +176,11 @@ export default function ProfileDataTab({
                   });
                 }}
               />
-              
-              <div className="space-y-2">
+                <div className="space-y-2">
                 <label htmlFor="summary" className="text-sm font-medium">Summary</label>
-                <Textarea 
+                <AIEnhancedTextarea 
                   id="summary" 
+                  fieldName="professional summary"
                   rows={5}
                   value={profileData.summary || ''} 
                   onChange={(e) => handleProfileFieldChange('summary', e.target.value)}
diff --git a/components/admin/top-skills-tab.tsx b/components/admin/top-skills-tab.tsx
index dd43877..bc71223 100644
--- a/components/admin/top-skills-tab.tsx
+++ b/components/admin/top-skills-tab.tsx
@@ -4,11 +4,17 @@ import { useState } from "react";
 import { Button } from "@/components/ui/button";
 import { Textarea } from "@/components/ui/textarea";
 import { Input } from "@/components/ui/input";
+import { AIEnhancedInput } from "./ai-enhanced-input";
 import { Card, CardContent } from "@/components/ui/card";
 import { Badge } from "@/components/ui/badge";
 import { X, ArrowUp, ArrowDown } from "lucide-react";
 import { TopSkillsTabProps } from "@/types/admin-components";
 
+/**
+ * Displays and manages a list of top professional skills with both visual and JSON editing modes.
+ *
+ * Provides an interface for adding, removing, reordering, and saving top skills, as well as automatically generating skill suggestions. Users can switch between a visual editor and direct JSON editing.
+ */
 export default function TopSkillsTab({
   topSkills,
   setTopSkills,
@@ -60,8 +66,9 @@ export default function TopSkillsTab({
           />
         : <div className="space-y-4">
             <div className="flex gap-2">
-              <Input
+              {" "}<AIEnhancedInput
                 placeholder="Add new skill"
+                fieldName="professional skill"
                 value={newSkill}
                 onChange={e => setNewSkill(e.target.value)}
                 onKeyDown={e => e.key === "Enter" && addTopSkill()}
diff --git a/docs/IMAGE_UPLOAD.md b/docs/IMAGE_UPLOAD.md
deleted file mode 100644
index 7d9b583..0000000
--- a/docs/IMAGE_UPLOAD.md
+++ /dev/null
@@ -1,151 +0,0 @@
-# Image Upload Feature
-
-This document describes the image upload functionality implemented for the admin panel.
-
-## Features
-
-### 1. Image Upload with Optimization
-
-- **Drag & Drop Support**: Users can drag and drop images directly onto the upload area
-- **File Browser**: Click to browse and select files from the file system
-- **Automatic Optimization**: Images are automatically optimized for both web and PDF display
-- **Multiple Format Support**: JPG, PNG, WebP, and GIF formats are supported
-- **File Size Validation**: Maximum file size of 5MB enforced
-- **Real-time Preview**: Instant preview of uploaded images
-
-### 2. External URL Support
-
-- **URL Input**: Support for external image URLs (e.g., from image hosting services)
-- **Automatic Detection**: System automatically detects whether an image is local or external
-- **Flexible Storage**: External URLs are stored as-is, local images use relative paths
-
-### 3. Image Optimization
-
-- **Web Version**: 400x400px, WebP format, 85% quality for web display
-- **PDF Version**: 200x200px, WebP format, 80% quality for PDF generation
-- **Smart Cropping**: Images are cropped to square format with center positioning
-- **Performance**: Optimized file sizes for fast loading
-
-## Technical Implementation
-
-### API Endpoints
-
-#### `/api/upload` (POST)
-
-Handles image upload and optimization:
-
-```typescript
-// Request: FormData with 'file' field
-// Response: { success: boolean, webUrl: string, pdfUrl: string, message: string }
-```
-
-#### `/api/upload` (DELETE)
-
-Handles deletion of uploaded images:
-
-```typescript
-// Request: Query params 'webUrl' and 'pdfUrl'
-// Response: { success: boolean, message: string }
-```
-
-### File Structure
-
-```
-public/
-  uploads/
-    profile-{timestamp}-web.webp    # Web optimized version
-    profile-{timestamp}-pdf.webp    # PDF optimized version
-```
-
-### Data Structure
-
-```typescript
-interface UserProfile {
-  profileImageUrl: string; // Main image URL (web version for local, original for external)
-  profileImageWebUrl?: string; // Web optimized version URL
-  profileImagePdfUrl?: string; // PDF optimized version URL
-}
-```
-
-### Helper Functions
-
-#### `getProfileImageUrl(profile, context)`
-
-Returns the appropriate image URL based on context:
-
-- `context: 'web'` - Returns web-optimized version
-- `context: 'pdf'` - Returns PDF-optimized version
-- Falls back to main profile image URL
-
-#### `validateImageFile(file)`
-
-Validates image files:
-
-- File type validation (must be image/\*)
-- File size validation (max 5MB)
-- Format validation (JPG, PNG, WebP, GIF)
-
-## Usage Examples
-
-### In Admin Panel
-
-The image upload component is automatically integrated into the Profile Data tab:
-
-```tsx
-<ImageUpload
-  currentImageUrl={profileData.profileImageUrl || ""}
-  currentWebUrl={profileData.profileImageWebUrl}
-  currentPdfUrl={profileData.profileImagePdfUrl}
-  onImageChange={(imageUrl, webUrl, pdfUrl) => {
-    // Update profile data with new image URLs
-  }}
-/>
-```
-
-### In Frontend Components
-
-Use the helper function to get optimized images:
-
-```tsx
-import { getProfileImageUrl } from '@/lib/image-utils';
-
-// For web display
-<Image src={getProfileImageUrl(userProfile, 'web')} />
-
-// For PDF generation
-<Image src={getProfileImageUrl(userProfile, 'pdf')} />
-```
-
-## Security Considerations
-
-1. **File Type Validation**: Only image files are accepted
-2. **File Size Limits**: 5MB maximum to prevent abuse
-3. **Development Only**: Upload API only works in development mode
-4. **Path Sanitization**: File paths are sanitized to prevent directory traversal
-
-## Error Handling
-
-The system provides comprehensive error handling:
-
-- Invalid file types
-- File size exceeded
-- Upload failures
-- Network errors
-- File deletion errors
-
-All errors are displayed to users via toast notifications with clear, actionable messages.
-
-## Browser Compatibility
-
-- **Drag & Drop**: Supported in all modern browsers
-- **File API**: Requires modern browser support for File API
-- **WebP Format**: Falls back gracefully in older browsers
-- **Image Optimization**: Server-side processing ensures compatibility
-
-## Performance Benefits
-
-1. **Reduced Loading Times**: Optimized image sizes
-2. **Better User Experience**: Faster page loads
-3. **Mobile Optimization**: Smaller images for mobile devices
-4. **PDF Generation**: Optimized images for PDF export
-5. **Storage Efficiency**: Compressed images save disk space
diff --git a/services/openrouter.ts b/services/openrouter.ts
new file mode 100644
index 0000000..3d76a95
--- /dev/null
+++ b/services/openrouter.ts
@@ -0,0 +1,64 @@
+import { OpenRouterOptions } from "../types/index";
+
+/**
+ * Requests a single answer from the OpenRouter language model API using the provided system prompt and user input.
+ *
+ * @param options - Contains the system prompt, user input, and optional creativity level for the model's response.
+ * @returns The trimmed answer string generated by the model, or an empty string if no answer is returned.
+ *
+ * @throws {Error} If the `OPENROUTER_KEY` environment variable is not set or if the OpenRouter API responds with an error.
+ */
+export async function getOpenRouterAnswer({
+  systemInput,
+  data,
+  creativity = 0.2,
+  signal,
+  customModel
+}: OpenRouterOptions): Promise<string> {
+  // Validate that systemInput and data are not empty
+  if (!systemInput || systemInput.trim() === "") {
+    throw new Error("System input cannot be empty");
+  }
+
+  if (!data || data.trim() === "") {
+    throw new Error("User data cannot be empty");
+  }
+
+  const apiKey = process.env.OPENROUTER_KEY;
+  // Use customModel if provided, otherwise fall back to env variable or default
+  const model =
+    customModel || process.env.OPENROUTER_MODEL || "openai/gpt-4.1-nano";
+  if (!apiKey) throw new Error("OPENROUTER_KEY is not set in environment");
+
+  const response = await fetch(
+    "https://openrouter.ai/api/v1/chat/completions",
+    {
+      method: "POST",
+      headers: {
+        Authorization: `Bearer ${apiKey}`,
+        "Content-Type": "application/json"
+      },
+      // Add signal for timeout handling if provided
+      ...(signal && { signal }),
+      body: JSON.stringify({
+        model,
+        messages: [
+          { role: "system", content: systemInput },
+          { role: "user", content: data }
+        ],
+        max_tokens: 512,
+        temperature: creativity,
+        n: 1
+      })
+    }
+  );
+
+  if (!response.ok) {
+    const error = await response.text();
+    throw new Error(`OpenRouter API error: ${error}`);
+  }
+
+  const result = await response.json();
+  // Return exactly one answer
+  return result.choices?.[0]?.message?.content?.trim() || "";
+}
diff --git a/types/index.ts b/types/index.ts
index 4561d0e..563ac0d 100644
--- a/types/index.ts
+++ b/types/index.ts
@@ -6,3 +6,11 @@ export interface ExperienceEntry {
   description: string;
   tags: string[];
 }
+
+export interface OpenRouterOptions {
+  systemInput: string;
+  data: string;
+  creativity?: number; // 0 = deterministic, 1 = max creativity
+  signal?: AbortSignal; // For request timeout handling
+  customModel?: string; // Optional model override
+}


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 8**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>components/admin/ai-enhanced-textarea.tsx (1)</summary><blockquote>

`22-91`: _⚠️ Potential issue_

**Apply the same security and timeout improvements as AIEnhancedInput.**

This component has identical security vulnerabilities and missing timeout handling as the AIEnhancedInput component. Please apply the same fixes:

1. Add request timeout and abort controller
2. Sanitize user input and validate AI response
3. Implement XSS prevention measures

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (11)</summary><blockquote>

<details>
<summary>services/openrouter.ts (2)</summary><blockquote>

`17-36`: **Consider adding timeout and error resilience.**

The fetch request lacks timeout handling, which could cause the function to hang indefinitely if the OpenRouter API is unresponsive.



```diff
  const response = await fetch(
    "https://openrouter.ai/api/v1/chat/completions",
    {
      method: "POST",
+      signal: AbortSignal.timeout(30000), // 30 second timeout
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        model,
        messages: [
          { role: "system", content: systemInput },
          { role: "user", content: data }
        ],
        max_tokens: 512,
        temperature: creativity,
        n: 1
      })
    }
  );
```

---

`43-46`: **Improve response validation and error handling.**

Consider adding validation for the response structure and handling edge cases more gracefully.



```diff
  const result = await response.json();
-  // Return exactly one answer
-  return result.choices?.[0]?.message?.content?.trim() || "";
+  // Validate response structure
+  if (!result.choices || !Array.isArray(result.choices) || result.choices.length === 0) {
+    throw new Error("Invalid response format from OpenRouter API");
+  }
+  
+  const content = result.choices[0]?.message?.content;
+  if (typeof content !== 'string') {
+    throw new Error("No valid content received from OpenRouter API");
+  }
+  
+  return content.trim();
```

</blockquote></details>
<details>
<summary>app/api/admin/autoskills/route.ts (2)</summary><blockquote>

`4-4`: **Remove unused import.**

The `userProfile` import is not used anywhere in the code and should be removed to keep the imports clean.


```diff
-import { userProfile } from "@/data/user-profile";
```

---

`21-35`: **Consider input validation and optimization.**

The tag extraction logic works correctly but could be improved for better validation and performance.


Consider these improvements:

```diff
-    // Get experiences from the request body or fallback to the imported data
-    const reqBody = await request.json();
-    const experienceData: ExperienceEntry[] =
-      reqBody.experiences || experiences;
+    // Get experiences from the request body or fallback to the imported data
+    const reqBody = await request.json().catch(() => ({}));
+    const experienceData: ExperienceEntry[] =
+      Array.isArray(reqBody.experiences) ? reqBody.experiences : experiences;

-    // Collect all tags from experience entries
-    const allTags: string[] = [];
-    experienceData.forEach((exp) => {
-      if (exp.tags && Array.isArray(exp.tags)) {
-        exp.tags.forEach((tag) => {
-          allTags.push(tag);
-        });
-      }
-    });
+    // Collect all tags from experience entries
+    const allTags: string[] = experienceData
+      .filter(exp => exp.tags && Array.isArray(exp.tags))
+      .flatMap(exp => exp.tags);
```

This provides better error handling for malformed JSON and uses more functional programming patterns.

</blockquote></details>
<details>
<summary>app/api/ai/route.ts (2)</summary><blockquote>

`4-38`: **Consider the length and cost implications of the system prompt.**

The system prompt is very comprehensive but extremely long (900+ tokens). This will consume significant token allowance on every API call and increase costs.


Consider condensing the prompt while maintaining key instructions:

```diff
const CV_WRITING_ASSISTANT_PROMPT = `
-AI System Prompt: Professional CV Writing Assistant
-
-Role: You are a professional CV Writing Assistant. Your expertise lies in transforming raw information and existing text into concise, impactful, and professional language suitable for a modern CV. You understand common CV structures, best practices for different fields (e.g., achievements, skills, summaries), and how to tailor language for maximum effect.
-
-Objective: To assist the user in updating their CV *one specific field or section at a time* by providing suggested professional text based on their input for that particular field.
-
-Mode of Operation:
-1.  You will wait for the user to explicitly state *which specific CV field or section* they want to work on next (e.g., "Summary," "Skills," "Work Experience - Job Title X," "Education," "Achievements," etc.).
-2.  The user will provide you with their current text for that field, key points they want to include, or a description of what they need help writing for that specific part of the CV.
-3.  Based *only* on the input for that single, requested field, you will provide polished, professional text options or suggestions.
-4.  You will ask clarifying questions *only* if necessary to improve the suggestions for the *current field*.
-5.  You will maintain a focused conversation on the *currently requested field* until the user indicates they are ready to move to another.
-
-Capabilities:
-*   Suggest professional wording and phrasing for any CV section.
-*   Help condense lengthy descriptions into concise bullet points or summaries.
-*   Identify opportunities to use stronger action verbs.
-*   Suggest ways to quantify achievements where applicable (if the user provides data).
-*   Tailor language to sound more impactful and relevant.
-*   Format suggestions clearly (e.g., bullet points for experience/skills, paragraph for summary).
-*   Maintain a professional, encouraging, and patient tone.
-
-Constraints & Rules:
-*   **CRITICAL:** DO NOT jump ahead or anticipate which field the user will want to work on next.
-*   **CRITICAL:** DO NOT list all possible CV sections or create a step-by-step plan *unless specifically asked by the user*. Wait for the user to guide the flow by requesting the next field.
-*   Focus *only* on the specific field the user has currently requested help with.
-*   Do not ask for information about other CV fields unless it's directly relevant to improving the *current* field (e.g., asking about the industry to tailor skill descriptions).
-*   Keep responses helpful, professional, and directly related to the user's input for the current field.
-
-Starting Instruction:
-(This instruction helps reinforce the starting behavior for the AI. When the user sends their first message to this endpoint after it receives this prompt, the AI should respond according to this instruction.)
-Hello! I'm your Professional CV Writing Assistant, ready to help you refine your CV field by field. Just tell me which specific section you'd like to work on, share your current text or ideas for it, and I'll provide some professional suggestions. Which part would you like to start with?
+You are a professional CV Writing Assistant. Transform user input into concise, impactful CV content. 

+Rules:
+- Focus only on the specific field provided
+- Use professional language and strong action verbs
+- Keep responses concise and directly applicable
+- Format appropriately (bullets for experience, paragraphs for summaries)
+- Quantify achievements when possible

+Respond with ONLY the improved text, no explanations.
`;
```

This reduces token usage while maintaining core functionality.

---

`68-76`: **Enhance error handling for production use.**

The error handling is basic but could be improved for better debugging and security.


Consider more specific error handling:

```diff
  } catch (error: any) {
    console.error("AI endpoint error:", error);

+    // Don't expose internal error details in production
+    const isDev = process.env.NODE_ENV === 'development';
+    const errorMessage = isDev ? error.message || "Failed to process AI request" : "Internal server error";

    return NextResponse.json(
-      { error: error.message || "Failed to process AI request" },
+      { error: errorMessage },
      { status: 500 }
    );
  }
```

</blockquote></details>
<details>
<summary>components/admin/ai-enhanced-input.tsx (1)</summary><blockquote>

`58-66`: **Improve synthetic event creation for better type safety.**

The synthetic event creation could be more robust and type-safe.



```diff
-  const syntheticEvent = {
-    target: {
-      value: data.response,
-      name: props.name
-    }
-  } as React.ChangeEvent<HTMLInputElement>;
+  const syntheticEvent = {
+    target: {
+      value: sanitizedResponse,
+      name: props.name || '',
+      type: 'text'
+    },
+    currentTarget: {
+      value: sanitizedResponse,
+      name: props.name || ''
+    }
+  } as React.ChangeEvent<HTMLInputElement>;
```

</blockquote></details>
<details>
<summary>components/admin/ai-enhanced-textarea.tsx (1)</summary><blockquote>

`44-44`: **Enhance system prompt for textarea-specific content.**

The textarea prompt includes bullet point formatting which is good, but could be more specific about preserving line breaks and structure.



```diff
-Keep the same meaning but enhance the wording and structure. Format appropriately with bullet points if it's a list of accomplishments.
+Keep the same meaning but enhance the wording and structure. Preserve line breaks and formatting. Use bullet points for lists of accomplishments or responsibilities.
```

</blockquote></details>
<details>
<summary>README.md (3)</summary><blockquote>

`106-123`: **Add language specification to the code block.**

The folder structure code block is missing a language specification, which affects syntax highlighting and accessibility.



```diff
-```
+```
/app            → Main application (routing, layouts, API, admin)
   /api          → Backend API routes
   /admin        → Admin panel pages and API routes
```
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

106-106: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>

---

`212-219`: **Add language specification for environment variables code block.**

The environment variables code block is missing language specification for proper syntax highlighting.



```diff
-```
+```env
# Admin authentication
ADMIN_PASSWORD=your-secure-password

# OpenRouter API for AI features
OPENROUTER_KEY=your-openrouter-api-key
OPENROUTER_MODEL=openai/gpt-4.1-nano
```
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

212-212: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>

---

`24-25`: **Fix verb agreement in the optimization feature description.**

The static analysis correctly identifies a verb agreement issue.



```diff
-- **Image Upload with Optimization**  
-  Upload and automatically optimize profile images for both web display and PDF exports.
+- **Image Upload with Optimization**  
+  Upload and automatically optimizes profile images for both web display and PDF exports.
```

Or alternatively:

```diff
-- **Image Upload with Optimization**  
-  Upload and automatically optimize profile images for both web display and PDF exports.
+- **Image Upload with Optimization**  
+  Uploads and automatically optimizes profile images for both web display and PDF exports.
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[uncategorized] ~25-~25: This verb does not appear to agree with the subject. Consider using a different form.
Context: ...mization**     Upload and automatically optimize profile images for both web display and...

(AI_EN_LECTOR_REPLACEMENT_VERB_AGREEMENT)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 794b7c53774ad63f3643fbb3a5c2e5730bf5e841 and c1975e58b45e7f7c2c123580bcf2753b009e6ac2.

</details>

<details>
<summary>📒 Files selected for processing (16)</summary>

* `README.md` (7 hunks)
* `app/admin/handlers.ts` (1 hunks)
* `app/api/admin/autoskills/route.ts` (1 hunks)
* `app/api/ai/route.ts` (1 hunks)
* `components/admin/ai-components.ts` (1 hunks)
* `components/admin/ai-enhanced-input.tsx` (1 hunks)
* `components/admin/ai-enhanced-textarea.tsx` (1 hunks)
* `components/admin/certification-dialog.tsx` (2 hunks)
* `components/admin/education-dialog.tsx` (2 hunks)
* `components/admin/experience-dialog.tsx` (3 hunks)
* `components/admin/language-dialog.tsx` (2 hunks)
* `components/admin/profile-data-tab.tsx` (3 hunks)
* `components/admin/top-skills-tab.tsx` (2 hunks)
* `docs/IMAGE_UPLOAD.md` (0 hunks)
* `services/openrouter.ts` (1 hunks)
* `types/index.ts` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* docs/IMAGE_UPLOAD.md

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (7)</summary>

<details>
<summary>components/admin/top-skills-tab.tsx (1)</summary><blockquote>

<details>
<summary>components/admin/ai-enhanced-input.tsx (1)</summary>

* `AIEnhancedInput` (16-115)

</details>

</blockquote></details>
<details>
<summary>app/api/admin/autoskills/route.ts (2)</summary><blockquote>

<details>
<summary>types/index.ts (1)</summary>

* `ExperienceEntry` (1-8)

</details>
<details>
<summary>data/cv-data.ts (1)</summary>

* `experiences` (3-249)

</details>

</blockquote></details>
<details>
<summary>components/admin/language-dialog.tsx (1)</summary><blockquote>

<details>
<summary>components/admin/ai-enhanced-input.tsx (1)</summary>

* `AIEnhancedInput` (16-115)

</details>

</blockquote></details>
<details>
<summary>components/admin/experience-dialog.tsx (2)</summary><blockquote>

<details>
<summary>components/admin/ai-enhanced-input.tsx (1)</summary>

* `AIEnhancedInput` (16-115)

</details>
<details>
<summary>components/admin/ai-enhanced-textarea.tsx (1)</summary>

* `AIEnhancedTextarea` (16-116)

</details>

</blockquote></details>
<details>
<summary>components/admin/certification-dialog.tsx (1)</summary><blockquote>

<details>
<summary>components/admin/ai-enhanced-input.tsx (1)</summary>

* `AIEnhancedInput` (16-115)

</details>

</blockquote></details>
<details>
<summary>components/admin/education-dialog.tsx (1)</summary><blockquote>

<details>
<summary>components/admin/ai-enhanced-input.tsx (1)</summary>

* `AIEnhancedInput` (16-115)

</details>

</blockquote></details>
<details>
<summary>services/openrouter.ts (1)</summary><blockquote>

<details>
<summary>types/index.ts (1)</summary>

* `OpenRouterOptions` (10-14)

</details>

</blockquote></details>

</details><details>
<summary>🪛 LanguageTool</summary>

<details>
<summary>README.md</summary>

[uncategorized] ~25-~25: This verb does not appear to agree with the subject. Consider using a different form.
Context: ...mization**     Upload and automatically optimize profile images for both web display and...

(AI_EN_LECTOR_REPLACEMENT_VERB_AGREEMENT)

---

[style] ~223-~223: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing  Contributions are welcome! Please feel free to submit a Pull Request.  1. Fork the rep...

(FEEL_FREE_TO_STYLE_ME)

---

[style] ~227-~227: Consider using a more formal and expressive alternative to ‘amazing’.
Context: ...azing-feature`) 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 4. Push to the branch (`git ...

(AWESOME)

</details>

</details>
<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

<details>
<summary>README.md</summary>

106-106: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

---

212-212: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

---

221-221: Multiple headings with the same content
null

(MD024, no-duplicate-heading)

---

231-231: Multiple headings with the same content
null

(MD024, no-duplicate-heading)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (24)</summary><blockquote>

<details>
<summary>types/index.ts (1)</summary>

`10-14`: **Well-defined interface with clear documentation.**

The `OpenRouterOptions` interface is cleanly structured with appropriate property types and helpful documentation for the `creativity` parameter range.

</details>
<details>
<summary>components/admin/top-skills-tab.tsx (2)</summary>

`7-7`: **Good integration of AI-enhanced input component.**

The import of `AIEnhancedInput` is properly added to support the AI enhancement feature.

---

`64-70`: **Appropriate use of AI enhancement with contextual field name.**

The replacement of the standard `Input` with `AIEnhancedInput` is well-implemented, and the `fieldName="professional skill"` prop provides meaningful context for AI content enhancement.

</details>
<details>
<summary>components/admin/language-dialog.tsx (2)</summary>

`12-12`: **Proper import addition for AI enhancement.**

The import statement correctly adds the `AIEnhancedInput` component for the AI enhancement functionality.

---

`43-48`: **Clean integration of AI-enhanced input with appropriate context.**

The replacement of the standard input with `AIEnhancedInput` is well-executed, and the `fieldName="language name"` prop provides clear context for the AI enhancement feature.

</details>
<details>
<summary>components/admin/ai-components.ts (1)</summary>

`1-8`: **LGTM! Clean barrel export pattern.**

This file follows a clean barrel export pattern for the AI-enhanced components, making it easy to import them consistently throughout the admin panel. The documentation comment clearly explains the purpose and integration.

</details>
<details>
<summary>components/admin/profile-data-tab.tsx (2)</summary>

`7-8`: **Good import organization for AI components.**

The imports for AI-enhanced components are properly added and will integrate seamlessly with the existing form structure.

---

`78-83`: **Well-implemented AI enhancement integration.**

The systematic replacement of standard Input and Textarea components with AI-enhanced versions is well-executed:

- Each component receives appropriate `fieldName` props that provide meaningful context for AI enhancement
- All existing event handlers and state management are preserved
- The field names are descriptive and will help the AI provide relevant improvements

The integration maintains backward compatibility while adding the new AI capabilities.




Also applies to: 87-93, 98-103, 107-114, 118-124, 127-134, 153-160

</details>
<details>
<summary>app/admin/handlers.ts (1)</summary>

`331-339`: **Excellent improvement to error handling.**

The enhanced error handling in `generateAutomaticTopSkills` provides much better resilience:

1. **Early JSON parsing** (line 331) enables access to structured error responses
2. **Better error extraction** (lines 334-335) uses the API's error field when available
3. **Response validation** (lines 337-339) ensures the expected data structure before proceeding
4. **User-friendly error messages** provide clearer feedback

This is a significant improvement that will make debugging easier and provide better user experience when the AI service encounters issues.

</details>
<details>
<summary>components/admin/experience-dialog.tsx (2)</summary>

`14-15`: **Proper import additions for AI components.**

The AI-enhanced component imports are correctly added and align with the pattern used throughout the admin panel.

---

`56-61`: **Consistent AI enhancement implementation.**

The replacement of standard form inputs with AI-enhanced versions is well-executed:

- **Semantic field names**: Each `fieldName` prop provides clear context (e.g., "job title", "company name", "job description")
- **Preserved functionality**: All existing event handlers and state management remain intact
- **Consistent pattern**: Follows the same implementation approach as other admin components

The "skill tag" field name for the tag input is particularly thoughtful, as it will help the AI suggest professional terminology for skills.




Also applies to: 65-70, 75-80, 84-89, 93-99, 114-120

</details>
<details>
<summary>components/admin/education-dialog.tsx (5)</summary>

`12-12`: **Import addition looks good.**

The import of `AIEnhancedInput` is properly added and will enable AI-powered content enhancement for the education form fields.

---

`42-47`: **Institution field enhancement implemented correctly.**

The replacement of the standard `Input` with `AIEnhancedInput` is well-implemented. The `fieldName="educational institution"` prop provides appropriate context for the AI enhancement.

---

`50-55`: **Degree field enhancement implemented correctly.**

Good implementation with the `fieldName="academic degree"` prop providing clear context for AI enhancement of degree information.

---

`58-63`: **Field of study enhancement implemented correctly.**

The `fieldName="field of study"` prop is appropriately descriptive for AI enhancement of this academic field.

---

`66-72`: **Date range field enhancement implemented correctly.**

The implementation includes the original placeholder text and uses `fieldName="education date range"` which provides good context for AI enhancement.

</details>
<details>
<summary>components/admin/certification-dialog.tsx (4)</summary>

`12-12`: **Import addition is consistent and correct.**

The import follows the same pattern as other enhanced dialogs and properly imports the `AIEnhancedInput` component.

---

`35-40`: **Certification name field enhancement implemented correctly.**

The `fieldName="certification name"` prop provides clear context for AI enhancement of certification names.

---

`43-48`: **Issuing organization field enhancement implemented correctly.**

The `fieldName="issuing organization"` prop is descriptive and appropriate for AI enhancement of organization names.

---

`51-57`: **Certification date field enhancement implemented correctly.**

The implementation maintains the original placeholder and uses `fieldName="certification date"` which provides good context for AI enhancement.

</details>
<details>
<summary>app/api/admin/autoskills/route.ts (3)</summary>

`6-19`: **Development mode restriction is well implemented.**

The check for development mode provides good security by preventing unauthorized access to admin functionality in production.

---

`37-51`: **Skill counting and sorting logic is correct.**

The implementation correctly counts tag occurrences, sorts by frequency, and returns the top 10 skills. The logic is sound and efficient.

---

`52-59`: **Error handling is appropriate.**

Good error handling with logging and proper HTTP status codes for debugging and client feedback.

</details>
<details>
<summary>app/api/ai/route.ts (1)</summary>

`55-66`: **Implementation logic is sound.**

The core logic for handling the AI request is well-structured. The fallback to a default system prompt and creativity parameter is good design.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T15:09:17Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>♻️ Duplicate comments (3)</summary><blockquote>

<details>
<summary>app/api/ai/route.ts (2)</summary><blockquote>

`78-117`: **Comprehensive input validation addresses security concerns.**

The extensive input validation properly handles:
- JSON parsing errors
- Required field validation
- Type checking for all parameters
- Length limits to prevent abuse
- Empty content detection

This addresses the previous review comments about input validation and security.

---

`59-66`: **Rate limiting structure is prepared but not implemented.**

The code includes placeholder comments for rate limiting but doesn't implement the actual logic. This was flagged in previous reviews as a production concern.



Consider implementing the rate limiting logic or removing the placeholder comments to avoid confusion.

</blockquote></details>
<details>
<summary>components/admin/ai-enhanced-textarea.tsx (1)</summary><blockquote>

`135-161`: **Accessibility improvements successfully implemented.**

The component addresses the previous review concerns:
- Dynamic aria-label provides proper accessibility context
- `pr-10 resize-none` classes prevent button overlap with content
- Button positioning and styling are appropriate

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (7)</summary><blockquote>

<details>
<summary>types/index.ts (1)</summary><blockquote>

`10-16`: **Consider adding type constraint for creativity parameter.**

The interface is well-designed with clear documentation. Consider using a branded type or union type to enforce the creativity range at compile time.



```diff
export interface OpenRouterOptions {
  systemInput: string;
  data: string;
-  creativity?: number; // 0 = deterministic, 1 = max creativity
+  creativity?: number & { __brand: "Creativity0To1" }; // 0 = deterministic, 1 = max creativity
  signal?: AbortSignal; // For request timeout handling
  customModel?: string; // Optional model override
}
```

Alternatively, use a union type for common values:
```typescript
creativity?: 0 | 0.1 | 0.2 | 0.3 | 0.4 | 0.5 | 0.6 | 0.7 | 0.8 | 0.9 | 1 | number;
```

</blockquote></details>
<details>
<summary>services/openrouter.ts (1)</summary><blockquote>

`58-61`: **Add response validation for malformed JSON.**

Consider adding validation for the response structure to ensure it contains the expected `choices` array.



```diff
  const result = await response.json();
+  
+  if (!result.choices || !Array.isArray(result.choices) || result.choices.length === 0) {
+    throw new Error("Invalid response structure from OpenRouter API");
+  }
+  
+  const content = result.choices[0]?.message?.content;
+  if (typeof content !== 'string') {
+    throw new Error("Invalid response content type from OpenRouter API");
+  }
+  
-  // Return exactly one answer
-  return result.choices?.[0]?.message?.content?.trim() || "";
+  return content.trim();
```

</blockquote></details>
<details>
<summary>components/admin/ai-enhanced-input.tsx (1)</summary><blockquote>

`46-53`: **Consider input length validation for better UX.**

The component sends the full input value to the API. Consider adding client-side validation for extremely long inputs to provide immediate feedback.



```diff
    try {
+      // Validate input length before sending
+      if (String(value).length > 5000) {
+        toast({
+          title: "Content too long",
+          description: "Please limit content to 5000 characters for better AI processing.",
+          variant: "destructive"
+        });
+        return;
+      }
+      
      const response = await fetch("/api/ai", {
```

</blockquote></details>
<details>
<summary>README.md (3)</summary><blockquote>

`25-25`: **Fix verb agreement issue.**

The verb "optimize" doesn't agree with the subject "Upload". Consider revising for better grammar.



```diff
-  Upload and automatically optimize profile images for both web display and PDF exports.
+  Upload and automatically optimizes profile images for both web display and PDF exports.
```

Or alternatively:

```diff
-  Upload and automatically optimize profile images for both web display and PDF exports.
+  Automatically upload and optimize profile images for both web display and PDF exports.
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[uncategorized] ~25-~25: This verb does not appear to agree with the subject. Consider using a different form.
Context: ...mization**     Upload and automatically optimize profile images for both web display and...

(AI_EN_LECTOR_REPLACEMENT_VERB_AGREEMENT)

</details>

</details>

---

`106-106`: **Add language specification to code block.**

The folder structure code block should specify a language for better syntax highlighting and markdown compliance.



```diff
-```
+```text
 /app            → Main application (routing, layouts, API, admin)
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

106-106: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>

---

`212-212`: **Add language specification to code block.**

The environment variables code block should specify a language for better syntax highlighting and markdown compliance.



```diff
-```
+```bash
 # Admin authentication
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

212-212: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>

</blockquote></details>
<details>
<summary>components/admin/ai-enhanced-textarea.tsx (1)</summary><blockquote>

`46-52`: **Consider parameterizing the creativity value.**

The creativity parameter is hardcoded to 0.3. Consider making this configurable through props for different use cases.



```diff
 interface AIEnhancedTextareaProps
   extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
   onValueChange?: (value: string) => void;
   fieldName?: string;
+  creativity?: number;
 }
```

```diff
-          creativity: 0.3
+          creativity: creativity ?? 0.3
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between c1975e58b45e7f7c2c123580bcf2753b009e6ac2 and aa12924aa95e3d1be738db825b9e3f3f77542743.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `README.md` (7 hunks)
* `app/api/ai/route.ts` (1 hunks)
* `components/admin/ai-enhanced-input.tsx` (1 hunks)
* `components/admin/ai-enhanced-textarea.tsx` (1 hunks)
* `services/openrouter.ts` (1 hunks)
* `types/index.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>services/openrouter.ts (1)</summary><blockquote>

<details>
<summary>types/index.ts (1)</summary>

* `OpenRouterOptions` (10-16)

</details>

</blockquote></details>
<details>
<summary>components/admin/ai-enhanced-textarea.tsx (2)</summary><blockquote>

<details>
<summary>components/ui/textarea.tsx (1)</summary>

* `Textarea` (22-22)

</details>
<details>
<summary>components/ui/button.tsx (1)</summary>

* `Button` (56-56)

</details>

</blockquote></details>

</details><details>
<summary>🪛 LanguageTool</summary>

<details>
<summary>README.md</summary>

[uncategorized] ~25-~25: This verb does not appear to agree with the subject. Consider using a different form.
Context: ...mization**     Upload and automatically optimize profile images for both web display and...

(AI_EN_LECTOR_REPLACEMENT_VERB_AGREEMENT)

</details>

</details>
<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

<details>
<summary>README.md</summary>

106-106: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

---

212-212: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>
<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>app/api/ai/route.ts</summary>

[error] 183-183: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement


(parse)

---

[error] 186-186: Expected a catch clause but instead found ')'.

Expected a catch clause here.


(parse)

---

[error] 208-208: Expected a statement but instead found 'catch (error: any)'.

Expected a statement here.


(parse)

---

[error] 213-216: Illegal return statement outside of a function



(parse)

---

[error] 218-221: Illegal return statement outside of a function



(parse)

---

[error] 223-226: Illegal return statement outside of a function



(parse)

---

[error] 228-231: Illegal return statement outside of a function



(parse)

---

[error] 233-236: Illegal return statement outside of a function



(parse)

---

[error] 238-241: Illegal return statement outside of a function



(parse)

---

[error] 244-247: Illegal return statement outside of a function



(parse)

---

[error] 249-250: Expected a statement but instead found '}'.

Expected a statement here.


(parse)

---

[error] 208-249: This block statement doesn't serve any purpose and can be safely removed.

Standalone block statements without any block-level declarations are redundant in JavaScript and can be removed to simplify the code.

(lint/complexity/noUselessLoneBlockStatements)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (13)</summary><blockquote>

<details>
<summary>services/openrouter.ts (2)</summary>

`15-22`: **Excellent input validation implementation.**

The input validation has been properly implemented to address previous security concerns. The checks for empty strings and trimming handle edge cases well.

---

`30-51`: **Well-implemented API call with proper error handling.**

The fetch implementation correctly handles:
- Environment variable configuration
- Custom model override logic  
- AbortSignal for timeout handling
- Proper HTTP headers

</details>
<details>
<summary>components/admin/ai-enhanced-input.tsx (3)</summary>

`34-39`: **Good timeout implementation.**

The AbortController with 30-second timeout properly addresses the previous concern about requests hanging indefinitely.

---

`60-80`: **Excellent error handling and response validation.**

The comprehensive error handling addresses previous security concerns:
- Validates response format before parsing JSON
- Checks response structure
- Handles parsing errors gracefully

---

`135-163`: **Improved layout and accessibility.**

The layout now uses flexbox to avoid absolute positioning issues, and the button includes proper aria-label and title attributes for accessibility.

</details>
<details>
<summary>app/api/ai/route.ts (1)</summary>

`42-58`: **Good authentication implementation with multiple methods.**

The authentication logic properly handles:
- Development mode bypass
- Cookie-based authentication  
- API key authentication
- Appropriate error responses

</details>
<details>
<summary>README.md (1)</summary>

`21-189`: **Excellent documentation of new AI and image features.**

The documentation comprehensively covers the new AI-assisted content enhancement and image upload features. The structure is clear, technical details are well-explained, and the feature descriptions align with the implementation described in the AI summary.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[uncategorized] ~25-~25: This verb does not appear to agree with the subject. Consider using a different form.
Context: ...mization**     Upload and automatically optimize profile images for both web display and...

(AI_EN_LECTOR_REPLACEMENT_VERB_AGREEMENT)

---

[uncategorized] ~94-~94: You might be missing the article “the” here.
Context: ...er**  ```bash npm run dev ```  Make all changes. Visit `http://localhost:3000` to view ...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

</details>
<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

106-106: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>

</details>
<details>
<summary>components/admin/ai-enhanced-textarea.tsx (6)</summary>

`16-19`: **Well-structured component interface and forwardRef implementation.**

The component properly extends textarea props and uses forwardRef pattern correctly. The interface is clean and the optional props provide good flexibility.

---

`21-29`: **Good input validation with user-friendly feedback.**

The early validation for empty content with appropriate toast notification prevents unnecessary API calls and provides clear user guidance.

---

`33-37`: **Excellent timeout implementation.**

The AbortController with 30-second timeout is a best practice for preventing hanging requests and improving user experience.

---

`60-80`: **Robust error handling and response validation.**

The response validation and error handling is comprehensive, checking for HTTP status, JSON parsing errors, and response format validation. This prevents runtime errors and provides meaningful feedback.

---

`82-97`: **Excellent event handling pattern.**

The synthetic event creation maintains compatibility with standard React patterns while also supporting the custom onValueChange callback. This provides flexibility for different usage scenarios.

---

`103-134`: **Comprehensive error handling with user-friendly messages.**

The error handling covers all major failure scenarios (timeout, network errors, parsing errors) with appropriate user-facing messages. The cleanup logic prevents memory leaks.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T18:05:52Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>app/api/ai/route.ts (1)</summary><blockquote>

`207-215`: _⚠️ Potential issue_

**Fix try-catch structure and unreachable code.**

The code after the catch block is unreachable and the `aiResponse` variable is used outside its scope. The success logging and response return should be inside the try block.



Apply this diff to fix the structure:

```diff
       if (typeof aiResponse !== 'string') {
         throw new Error("Invalid response format from AI service");
       }
+      
+      // Clear the timeout
+      clearTimeout(timeoutId);
+      
+      // Log successful API calls (optional)
+      console.log(`AI API call successful: ${new Date().toISOString()}`);
+      
+      // Return the AI response
+      return NextResponse.json({ response: aiResponse });
     } catch (error) {
       // Make sure to clear the timeout if there was an error
       clearTimeout(timeoutId);
       throw error;
-    }// Log successful API calls (optional)
-    console.log(`AI API call successful: ${new Date().toISOString()}`);
-    
-    // Return the AI response
-    return NextResponse.json({ response: aiResponse });
+    }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between aa12924aa95e3d1be738db825b9e3f3f77542743 and d6b1ecf704bc6d201c6b74f200354e1ecc802a67.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `app/api/admin/autoskills/route.ts` (1 hunks)
* `app/api/ai/route.ts` (1 hunks)
* `components/admin/profile-data-tab.tsx` (4 hunks)
* `components/admin/top-skills-tab.tsx` (2 hunks)
* `services/openrouter.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (4)</summary>

* app/api/admin/autoskills/route.ts
* components/admin/top-skills-tab.tsx
* components/admin/profile-data-tab.tsx
* services/openrouter.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>app/api/ai/route.ts</summary>

[error] 191-191: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement


(parse)

---

[error] 194-194: Expected a catch clause but instead found ')'.

Expected a catch clause here.


(parse)

---

[error] 216-216: Expected a statement but instead found 'catch (error: any)'.

Expected a statement here.


(parse)

---

[error] 221-224: Illegal return statement outside of a function



(parse)

---

[error] 226-229: Illegal return statement outside of a function



(parse)

---

[error] 231-234: Illegal return statement outside of a function



(parse)

---

[error] 236-239: Illegal return statement outside of a function



(parse)

---

[error] 241-244: Illegal return statement outside of a function



(parse)

---

[error] 246-249: Illegal return statement outside of a function



(parse)

---

[error] 252-255: Illegal return statement outside of a function



(parse)

---

[error] 257-258: Expected a statement but instead found '}'.

Expected a statement here.


(parse)

---

[error] 216-257: This block statement doesn't serve any purpose and can be safely removed.

Standalone block statements without any block-level declarations are redundant in JavaScript and can be removed to simplify the code.

(lint/complexity/noUselessLoneBlockStatements)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>app/api/ai/route.ts (3)</summary>

`1-46`: **Well-structured system prompt and imports.**

The CV writing assistant prompt is comprehensive and well-defined with clear operational rules. The imports are appropriate for the Next.js API route functionality.

---

`48-66`: **Robust authentication implementation.**

The authentication logic properly handles multiple scenarios - development mode bypass, admin cookie verification, and API key authentication. This provides flexibility while maintaining security.

---

`86-178`: **Comprehensive input validation.**

The validation logic is thorough, covering all required and optional parameters with appropriate type checking, length limits, and security considerations. The error messages are descriptive and user-friendly.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-27T18:02:04Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>app/api/ai/route.ts (1)</summary><blockquote>

`68-76`: **Remove rate limiting placeholder or implement actual enforcement.**

The rate limiting logic remains as placeholder code without any actual enforcement, exactly as flagged in previous reviews.



Consider implementing actual rate limiting using a service like Upstash Redis or remove the misleading placeholder entirely.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>app/api/ai/route.ts (2)</summary><blockquote>

`170-189`: **Consider making model validation configurable.**

The hardcoded allowed models list could be moved to environment variables or configuration to avoid code changes when adding new models.



```diff
-      // Validate against allowed models if needed
-      const allowedModels = ["gpt-4", "gpt-3.5-turbo", "claude-3"]; // Example
-      if (!allowedModels.includes(model)) {
+      // Validate against allowed models from environment or config
+      const allowedModels = process.env.ALLOWED_AI_MODELS?.split(',') || ["gpt-4", "gpt-3.5-turbo", "claude-3"];
+      if (!allowedModels.includes(model)) {
```

---

`221-222`: **Replace console.log with proper logging in production.**

Using console.log directly in API routes isn't ideal for production environments where structured logging is preferred.



Consider using a proper logging library or at least conditional logging:

```diff
-      // Log successful API calls (optional)
-      console.log(`AI API call successful: ${new Date().toISOString()}`);
+      // Log successful API calls (optional)
+      if (process.env.NODE_ENV !== 'production') {
+        console.log(`AI API call successful: ${new Date().toISOString()}`);
+      }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d6b1ecf704bc6d201c6b74f200354e1ecc802a67 and d7fa0a7aef55ba73064ffdb2b33d58cade02fffa.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `app/api/ai/route.ts` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>app/api/ai/route.ts (5)</summary>

`1-38`: **LGTM: Well-structured imports and comprehensive system prompt.**

The imports are appropriate for a Next.js API route, and the CV Writing Assistant prompt is well-designed with clear role definition, operational guidelines, and constraints.

---

`49-67`: **LGTM: Robust authentication implementation.**

The authentication logic properly handles multiple auth methods (cookie-based and API key) with appropriate development mode bypass. This addresses the authentication concerns raised in previous reviews.

---

`77-131`: **LGTM: Comprehensive input validation.**

The JSON parsing and data validation is thorough, with appropriate error messages and length limits. This addresses the input validation concerns from previous reviews.

---

`190-230`: **LGTM: Well-structured AI service integration.**

The timeout handling, abort controller usage, and response validation are well-implemented. The try-catch structure is now correct, addressing previous syntax concerns.

---

`231-281`: **LGTM: Comprehensive error handling.**

The error handling covers multiple scenarios with appropriate HTTP status codes and user-friendly error messages. This provides excellent debugging information while maintaining security.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-27T18:30:34Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>app/api/ai/route.ts (3)</summary><blockquote>

`155-156`: **Fix formatting: Missing line break.**

There's a formatting issue where the closing brace and comment are on the same line.

```diff
-    } // Extract and validate required fields
+    }
+    
+    // Extract and validate required fields
```

---

`250-252`: **Fix formatting: Missing line break.**

Another formatting issue where the closing brace and comment are merged.

```diff
-    } // Use the specific CV writing assistant prompt as the system input if no custom systemInput provided
+    }
+    
+    // Use the specific CV writing assistant prompt as the system input if no custom systemInput provided
```

---

`4-14`: **Consider persistent storage for production rate limiting.**

While the in-memory rate limiting works well for development and small deployments, consider using persistent storage (Redis, database) for production to maintain rate limits across serverless cold starts and multiple instances.


For production deployment, you might want to implement persistent rate limiting:

```typescript
// Example with Redis (if available)
import { Redis } from '@upstash/redis'

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL,
  token: process.env.UPSTASH_REDIS_REST_TOKEN,
})

async function isRateLimited(clientIp: string): Promise<boolean> {
  const key = `rate_limit:${clientIp}`;
  const current = await redis.incr(key);
  
  if (current === 1) {
    await redis.expire(key, 60); // 1 minute window
  }
  
  return current > REQUESTS_PER_MINUTE;
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d7fa0a7aef55ba73064ffdb2b33d58cade02fffa and f6d792781cab7ce387a498dc4b951400e82cc95c.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `app/api/ai/route.ts` (1 hunks)
* `components/admin/ai-enhanced-textarea.tsx` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* components/admin/ai-enhanced-textarea.tsx

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>app/api/ai/route.ts (7)</summary>

`1-15`: **LGTM! Well-structured imports and rate limiting setup.**

The imports are clean and the in-memory rate limiting configuration with TypeScript types provides a solid foundation for the API endpoint.

---

`21-57`: **Excellent rate limiting implementation!**

This addresses the previous concerns about placeholder rate limiting code. The implementation includes:
- Proper in-memory tracking with cleanup
- Development mode bypass
- Sliding window rate limiting
- Efficient timestamp filtering

The solution is much improved from the previous placeholder implementation.

---

`59-93`: **Comprehensive and well-structured system prompt.**

The CV Writing Assistant prompt is detailed, professional, and includes clear operational rules and constraints. This provides excellent guidance for the AI to deliver focused, field-specific assistance.

---

`106-123`: **Robust authentication implementation.**

The dual authentication approach (admin cookie + API key) with development bypass provides flexibility while maintaining security. This properly addresses the previous authentication concerns.

---

`139-251`: **Excellent comprehensive input validation!**

The validation covers all the essential aspects:
- JSON parsing with error handling
- Required field validation
- Type checking for all parameters
- Length limits to prevent abuse
- Model validation against allowed list

This thoroughly addresses the previous input validation concerns.

---

`254-292`: **Well-implemented timeout and AI service call handling.**

The AbortController usage for timeout management and the comprehensive response validation provide robust error handling. The 60-second timeout is appropriate for AI requests.

---

`293-343`: **Comprehensive error handling with specific error types.**

The error handling covers various scenarios with appropriate HTTP status codes and descriptive messages. This provides good debugging information while maintaining security.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-27T18:37:53Z

Review comments:
diff_hunk: @@ -0,0 +1,46 @@
+import { OpenRouterOptions } from "../types/index";
+
+/**
+ * Calls the OpenRouter API and returns exactly one answer.
+ * @param options { systemInput, data, creativity }
+ * @returns The answer string from the model
+ */
+export async function getOpenRouterAnswer({
+  systemInput,
+  data,
+  creativity = 0.2
+}: OpenRouterOptions): Promise<string> {
+  const apiKey = process.env.OPENROUTER_KEY;
+  const model = process.env.OPENROUTER_MODEL || "openai/gpt-4.1-nano";
+  if (!apiKey) throw new Error("OPENROUTER_KEY is not set in environment");
+
path: services/openrouter.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add input validation for required parameters.**

Consider validating that `systemInput` and `data` are not empty strings, as sending empty prompts to the AI API could result in poor responses or wasted API calls.



```diff
export async function getOpenRouterAnswer({
  systemInput,
  data,
  creativity = 0.2
}: OpenRouterOptions): Promise<string> {
+  if (!systemInput.trim()) {
+    throw new Error("systemInput cannot be empty");
+  }
+  if (!data.trim()) {
+    throw new Error("data cannot be empty");
+  }
+  
  const apiKey = process.env.OPENROUTER_KEY;
  const model = process.env.OPENROUTER_MODEL || "openai/gpt-4.1-nano";
  if (!apiKey) throw new Error("OPENROUTER_KEY is not set in environment");
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
export async function getOpenRouterAnswer({
  systemInput,
  data,
  creativity = 0.2
}: OpenRouterOptions): Promise<string> {
  if (!systemInput.trim()) {
    throw new Error("systemInput cannot be empty");
  }
  if (!data.trim()) {
    throw new Error("data cannot be empty");
  }

  const apiKey = process.env.OPENROUTER_KEY;
  const model = process.env.OPENROUTER_MODEL || "openai/gpt-4.1-nano";
  if (!apiKey) throw new Error("OPENROUTER_KEY is not set in environment");
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In services/openrouter.ts around lines 8 to 16, add validation checks to ensure
that the parameters systemInput and data are not empty strings before
proceeding. If either is empty, throw an error or handle it appropriately to
prevent sending empty prompts to the AI API, which can cause poor responses or
unnecessary API usage.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit aa12924
created_at: 2025-05-26T15:09:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,76 @@
+import { NextRequest, NextResponse } from "next/server";
+import { getOpenRouterAnswer } from "@/services/openrouter";
+
+// Define the comprehensive system prompt for the CV Writing Assistant
+const CV_WRITING_ASSISTANT_PROMPT = `
+AI System Prompt: Professional CV Writing Assistant
+
+Role: You are a professional CV Writing Assistant. Your expertise lies in transforming raw information and existing text into concise, impactful, and professional language suitable for a modern CV. You understand common CV structures, best practices for different fields (e.g., achievements, skills, summaries), and how to tailor language for maximum effect.
+
+Objective: To assist the user in updating their CV *one specific field or section at a time* by providing suggested professional text based on their input for that particular field.
+
+Mode of Operation:
+1.  You will wait for the user to explicitly state *which specific CV field or section* they want to work on next (e.g., "Summary," "Skills," "Work Experience - Job Title X," "Education," "Achievements," etc.).
+2.  The user will provide you with their current text for that field, key points they want to include, or a description of what they need help writing for that specific part of the CV.
+3.  Based *only* on the input for that single, requested field, you will provide polished, professional text options or suggestions.
+4.  You will ask clarifying questions *only* if necessary to improve the suggestions for the *current field*.
+5.  You will maintain a focused conversation on the *currently requested field* until the user indicates they are ready to move to another.
+
+Capabilities:
+*   Suggest professional wording and phrasing for any CV section.
+*   Help condense lengthy descriptions into concise bullet points or summaries.
+*   Identify opportunities to use stronger action verbs.
+*   Suggest ways to quantify achievements where applicable (if the user provides data).
+*   Tailor language to sound more impactful and relevant.
+*   Format suggestions clearly (e.g., bullet points for experience/skills, paragraph for summary).
+*   Maintain a professional, encouraging, and patient tone.
+
+Constraints & Rules:
+*   **CRITICAL:** DO NOT jump ahead or anticipate which field the user will want to work on next.
+*   **CRITICAL:** DO NOT list all possible CV sections or create a step-by-step plan *unless specifically asked by the user*. Wait for the user to guide the flow by requesting the next field.
+*   Focus *only* on the specific field the user has currently requested help with.
+*   Do not ask for information about other CV fields unless it's directly relevant to improving the *current* field (e.g., asking about the industry to tailor skill descriptions).
+*   Keep responses helpful, professional, and directly related to the user's input for the current field.
+
+Starting Instruction:
+(This instruction helps reinforce the starting behavior for the AI. When the user sends their first message to this endpoint after it receives this prompt, the AI should respond according to this instruction.)
+Hello! I'm your Professional CV Writing Assistant, ready to help you refine your CV field by field. Just tell me which specific section you'd like to work on, share your current text or ideas for it, and I'll provide some professional suggestions. Which part would you like to start with?
+`; // Using backticks for multi-line string
+
+export async function POST(req: NextRequest) {
+  try {
+    // Parse the request body
+    const body = await req.json();
+
+    // Validate required fields
+    const { data, creativity } = body;
+
+    if (!data) {
+      return NextResponse.json(
+        { error: "Missing required field: data is required" },
+        { status: 400 }
+      );
+    }
+
+    // Use the specific CV writing assistant prompt as the system input if no custom systemInput provided
+    const systemInput = body.systemInput || CV_WRITING_ASSISTANT_PROMPT;
+
+    // Call OpenRouter service
+    // 'data' from the request body will serve as the user's input for the current field
+    const aiResponse = await getOpenRouterAnswer({
+      systemInput,
+      data, // This contains the user's specific input for the current field
+      creativity: creativity !== undefined ? creativity : 0.2 // Allow user to override, default to 0.2
+    });
+
+    // Return the AI response
+    return NextResponse.json({ response: aiResponse });
+  } catch (error: any) {
+    console.error("AI endpoint error:", error);
+
+    return NextResponse.json(
+      { error: error.message || "Failed to process AI request" },
+      { status: 500 }
+    );
+  }
+}
path: app/api/ai/route.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**Consider adding rate limiting for production deployment.**

The API endpoint lacks rate limiting, which could lead to abuse and high costs from AI service usage.


For production deployment, consider implementing rate limiting to prevent abuse:



This will help identify if rate limiting is already implemented elsewhere in the application.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if rate limiting middleware or configuration exists in the codebase
rg -i "rate.?limit" --type ts --type js -A 3 -B 3
```

Length of output: 51

---



</details>

**Add rate limiting to the AI endpoint before production deployment**

We didn’t find any existing rate-limiting configuration in the codebase (no matches for `"rate.?limit"` in `.ts`/`.js` files). To prevent abuse and runaway costs, introduce a rate limiter around `app/api/ai/route.ts`. For example, using Upstash Redis and the `@upstash/ratelimit` package:

```ts
// app/api/ai/route.ts
import { NextRequest, NextResponse } from "next/server";
import { Ratelimit } from "@upstash/ratelimit";
import Redis from "@upstash/redis";
import { getOpenRouterAnswer, CV_WRITING_ASSISTANT_PROMPT } from "@/lib/ai";

// Initialize a sliding-window rate limiter: 5 requests per minute per IP
const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(5, "1 m"),
});

export async function POST(req: NextRequest) {
  // Enforce rate limit keying on client IP
  const ip = req.ip || "unknown";
  const { success } = await ratelimit.limit(`ai:endpoint:${ip}`);
  if (!success) {
    return NextResponse.json(
      { error: "Too many requests, please try again later." },
      { status: 429 }
    );
  }

  try {
    const body = await req.json();
    const { data, creativity } = body;
    if (!data) {
      return NextResponse.json(
        { error: "Missing required field: data is required" },
        { status: 400 }
      );
    }

    const systemInput = body.systemInput || CV_WRITING_ASSISTANT_PROMPT;
    const aiResponse = await getOpenRouterAnswer({
      systemInput,
      data,
      creativity: creativity !== undefined ? creativity : 0.2,
    });

    return NextResponse.json({ response: aiResponse });
  } catch (error: any) {
    console.error("AI endpoint error:", error);
    return NextResponse.json(
      { error: error.message || "Failed to process AI request" },
      { status: 500 }
    );
  }
}
```

• If you prefer another solution, consider middleware-based rate limiting (e.g., `express-rate-limit`, Next.js middleware)  
• Adjust thresholds (requests/minute) and Redis provider per your SLAs  
• Ensure environment variables for Redis are set in production

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In app/api/ai/route.ts around lines 40 to 76, the POST handler lacks rate
limiting, which can lead to abuse and excessive costs. To fix this, integrate a
rate limiter such as @upstash/ratelimit with Redis by importing and initializing
it at the top of the file. Then, at the start of the POST function, enforce the
rate limit keyed by the client's IP address, returning a 429 response if the
limit is exceeded. This will protect the endpoint from excessive requests before
processing the AI logic.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit f6d7927
created_at: 2025-05-26T15:09:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,76 @@
+import { NextRequest, NextResponse } from "next/server";
+import { getOpenRouterAnswer } from "@/services/openrouter";
+
+// Define the comprehensive system prompt for the CV Writing Assistant
+const CV_WRITING_ASSISTANT_PROMPT = `
+AI System Prompt: Professional CV Writing Assistant
+
+Role: You are a professional CV Writing Assistant. Your expertise lies in transforming raw information and existing text into concise, impactful, and professional language suitable for a modern CV. You understand common CV structures, best practices for different fields (e.g., achievements, skills, summaries), and how to tailor language for maximum effect.
+
+Objective: To assist the user in updating their CV *one specific field or section at a time* by providing suggested professional text based on their input for that particular field.
+
+Mode of Operation:
+1.  You will wait for the user to explicitly state *which specific CV field or section* they want to work on next (e.g., "Summary," "Skills," "Work Experience - Job Title X," "Education," "Achievements," etc.).
+2.  The user will provide you with their current text for that field, key points they want to include, or a description of what they need help writing for that specific part of the CV.
+3.  Based *only* on the input for that single, requested field, you will provide polished, professional text options or suggestions.
+4.  You will ask clarifying questions *only* if necessary to improve the suggestions for the *current field*.
+5.  You will maintain a focused conversation on the *currently requested field* until the user indicates they are ready to move to another.
+
+Capabilities:
+*   Suggest professional wording and phrasing for any CV section.
+*   Help condense lengthy descriptions into concise bullet points or summaries.
+*   Identify opportunities to use stronger action verbs.
+*   Suggest ways to quantify achievements where applicable (if the user provides data).
+*   Tailor language to sound more impactful and relevant.
+*   Format suggestions clearly (e.g., bullet points for experience/skills, paragraph for summary).
+*   Maintain a professional, encouraging, and patient tone.
+
+Constraints & Rules:
+*   **CRITICAL:** DO NOT jump ahead or anticipate which field the user will want to work on next.
+*   **CRITICAL:** DO NOT list all possible CV sections or create a step-by-step plan *unless specifically asked by the user*. Wait for the user to guide the flow by requesting the next field.
+*   Focus *only* on the specific field the user has currently requested help with.
+*   Do not ask for information about other CV fields unless it's directly relevant to improving the *current* field (e.g., asking about the industry to tailor skill descriptions).
+*   Keep responses helpful, professional, and directly related to the user's input for the current field.
+
+Starting Instruction:
+(This instruction helps reinforce the starting behavior for the AI. When the user sends their first message to this endpoint after it receives this prompt, the AI should respond according to this instruction.)
+Hello! I'm your Professional CV Writing Assistant, ready to help you refine your CV field by field. Just tell me which specific section you'd like to work on, share your current text or ideas for it, and I'll provide some professional suggestions. Which part would you like to start with?
+`; // Using backticks for multi-line string
+
+export async function POST(req: NextRequest) {
+  try {
+    // Parse the request body
+    const body = await req.json();
+
+    // Validate required fields
+    const { data, creativity } = body;
+
+    if (!data) {
+      return NextResponse.json(
+        { error: "Missing required field: data is required" },
+        { status: 400 }
+      );
+    }
path: app/api/ai/route.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add authentication and input validation.**

The API endpoint lacks authentication and comprehensive input validation, which could be problematic in production.


Consider adding these security measures:

```diff
export async function POST(req: NextRequest) {
+  // Add authentication check
+  const authHeader = req.headers.get('authorization');
+  if (!authHeader) {
+    return NextResponse.json(
+      { error: "Authentication required" },
+      { status: 401 }
+    );
+  }

  try {
    // Parse the request body
    const body = await req.json();

    // Validate required fields
    const { data, creativity } = body;

    if (!data) {
      return NextResponse.json(
        { error: "Missing required field: data is required" },
        { status: 400 }
      );
    }

+    // Validate data type and length
+    if (typeof data !== 'string' || data.length > 5000) {
+      return NextResponse.json(
+        { error: "Data must be a string with max 5000 characters" },
+        { status: 400 }
+      );
+    }
+
+    // Validate creativity parameter
+    if (creativity !== undefined && (typeof creativity !== 'number' || creativity < 0 || creativity > 1)) {
+      return NextResponse.json(
+        { error: "Creativity must be a number between 0 and 1" },
+        { status: 400 }
+      );
+    }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
export async function POST(req: NextRequest) {
  // Add authentication check
  const authHeader = req.headers.get('authorization');
  if (!authHeader) {
    return NextResponse.json(
      { error: "Authentication required" },
      { status: 401 }
    );
  }

  try {
    // Parse the request body
    const body = await req.json();

    // Validate required fields
    const { data, creativity } = body;

    if (!data) {
      return NextResponse.json(
        { error: "Missing required field: data is required" },
        { status: 400 }
      );
    }

    // Validate data type and length
    if (typeof data !== 'string' || data.length > 5000) {
      return NextResponse.json(
        { error: "Data must be a string with max 5000 characters" },
        { status: 400 }
      );
    }

    // Validate creativity parameter
    if (
      creativity !== undefined &&
      (typeof creativity !== 'number' || creativity < 0 || creativity > 1)
    ) {
      return NextResponse.json(
        { error: "Creativity must be a number between 0 and 1" },
        { status: 400 }
      );
    }

    // ...rest of your logic...
  } catch (err) {
    // existing error handling
  }
}
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In app/api/ai/route.ts between lines 40 and 53, the POST function currently
lacks authentication and thorough input validation. To fix this, implement an
authentication check at the start of the function to verify the user's identity
or API key before processing the request. Additionally, enhance input validation
by checking all expected fields for correct types and acceptable values,
returning appropriate error responses if validation fails. This will secure the
endpoint and ensure only valid data is processed.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit aa12924
created_at: 2025-05-26T15:09:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+"use client";
+
+import React, { useState } from "react";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { Loader2 } from "lucide-react";
+import { RiRobot2Fill } from "react-icons/ri";
+import { toast } from "@/components/ui/use-toast";
+
+interface AIEnhancedInputProps
+  extends React.InputHTMLAttributes<HTMLInputElement> {
+  onValueChange?: (value: string) => void;
+  fieldName?: string;
+}
+
+export const AIEnhancedInput = React.forwardRef<
+  HTMLInputElement,
+  AIEnhancedInputProps
+>(({ className, onValueChange, fieldName, value, onChange, ...props }, ref) => {
+  const [isLoading, setIsLoading] = useState(false);
+
+  const handleAIClick = async () => {
+    if (!value) {
+      toast({
+        title: "No content to enhance",
+        description: "Please add some content to enhance with AI.",
+        variant: "destructive"
+      });
+      return;
+    }
+
+    setIsLoading(true);
+
+    try {
+      const response = await fetch("/api/ai", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json"
+        },
+        body: JSON.stringify({
+          systemInput: `You are a professional content enhancer specializing in CV and resume content. 
+Your task is to improve the provided ${fieldName ||
+            "text"} to make it more professional, concise, and impactful.
+Keep the same meaning but enhance the wording. Respond with ONLY the improved text without any explanations or additional text.`,
+          data: String(value),
+          creativity: 0.3
+        })
+      });
path: components/admin/ai-enhanced-input.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add request timeout and improve error handling for the API call.**

The fetch request lacks timeout handling and could hang indefinitely. Also consider implementing request cancellation for better UX.



```diff
+  const controller = new AbortController();
+  const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout

   const response = await fetch("/api/ai", {
     method: "POST",
     headers: {
       "Content-Type": "application/json"
     },
+    signal: controller.signal,
     body: JSON.stringify({
       systemInput: `You are a professional content enhancer specializing in CV and resume content. 
 Your task is to improve the provided ${fieldName ||
           "text"} to make it more professional, concise, and impactful.
 Keep the same meaning but enhance the wording. Respond with ONLY the improved text without any explanations or additional text.`,
       data: String(value),
       creativity: 0.3
     })
   });
+
+  clearTimeout(timeoutId);
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
      // add a timeout + abort controller to avoid hanging requests
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout

      const response = await fetch("/api/ai", {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        signal: controller.signal,
        body: JSON.stringify({
          systemInput: `You are a professional content enhancer specializing in CV and resume content. 
Your task is to improve the provided ${fieldName ||
            "text"} to make it more professional, concise, and impactful.
Keep the same meaning but enhance the wording. Respond with ONLY the improved text without any explanations or additional text.`,
          data: String(value),
          creativity: 0.3
        })
      });

      clearTimeout(timeoutId);
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In components/admin/ai-enhanced-input.tsx around lines 35 to 48, the fetch call
to the /api/ai endpoint lacks timeout and error handling, which can cause the
request to hang indefinitely. Implement a timeout mechanism using
AbortController to cancel the request if it exceeds a set duration, and add
try-catch blocks to handle errors gracefully. This will improve user experience
by preventing indefinite waits and allowing proper error feedback.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit aa12924
created_at: 2025-05-26T15:09:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+"use client";
+
+import React, { useState } from "react";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { Loader2 } from "lucide-react";
+import { RiRobot2Fill } from "react-icons/ri";
+import { toast } from "@/components/ui/use-toast";
+
+interface AIEnhancedInputProps
+  extends React.InputHTMLAttributes<HTMLInputElement> {
+  onValueChange?: (value: string) => void;
+  fieldName?: string;
+}
+
+export const AIEnhancedInput = React.forwardRef<
+  HTMLInputElement,
+  AIEnhancedInputProps
+>(({ className, onValueChange, fieldName, value, onChange, ...props }, ref) => {
+  const [isLoading, setIsLoading] = useState(false);
+
+  const handleAIClick = async () => {
+    if (!value) {
+      toast({
+        title: "No content to enhance",
+        description: "Please add some content to enhance with AI.",
+        variant: "destructive"
+      });
+      return;
+    }
+
+    setIsLoading(true);
+
+    try {
+      const response = await fetch("/api/ai", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json"
+        },
+        body: JSON.stringify({
+          systemInput: `You are a professional content enhancer specializing in CV and resume content. 
+Your task is to improve the provided ${fieldName ||
+            "text"} to make it more professional, concise, and impactful.
+Keep the same meaning but enhance the wording. Respond with ONLY the improved text without any explanations or additional text.`,
+          data: String(value),
+          creativity: 0.3
+        })
+      });
+
+      const data = await response.json();
+
+      if (!response.ok) {
+        throw new Error(data.error || "Failed to get AI suggestions");
+      }
+
+      // Call the parent's onChange handler with a synthetic event
+      if (onChange) {
+        const syntheticEvent = {
+          target: {
+            value: data.response,
+            name: props.name
+          }
+        } as React.ChangeEvent<HTMLInputElement>;
+
+        onChange(syntheticEvent);
+      }
+
+      // Also call the onValueChange callback if provided
+      if (onValueChange) {
+        onValueChange(data.response);
+      }
+
+      toast({
+        title: "Content enhanced",
+        description: "AI has enhanced your content."
+      });
+    } catch (error) {
+      console.error("AI Enhancement error:", error);
+      toast({
+        title: "Enhancement failed",
+        description:
+          error instanceof Error
+            ? error.message
+            : "Failed to enhance content with AI",
+        variant: "destructive"
+      });
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  return (
+    <div className="relative flex w-full">
+      <Input
+        className={className}
+        value={value}
+        onChange={onChange}
+        ref={ref}
+        {...props}
+      />
+      <Button
+        type="button"
+        variant="ghost"
+        size="icon"
+        className="absolute right-0 top-0 rounded-l-none h-full"
+        onClick={handleAIClick}
+        disabled={isLoading}
+      >
+        {isLoading
+          ? <Loader2 className="h-4 w-4 animate-spin" />
+          : <RiRobot2Fill className="h-4 w-4" />}
+      </Button>
+    </div>
path: components/admin/ai-enhanced-input.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Improve accessibility and button positioning.**

The absolute positioning might cause accessibility issues and the button could overlap with input content.



```diff
   return (
-    <div className="relative flex w-full">
+    <div className="relative flex w-full items-center">
       <Input
-        className={className}
+        className={`${className} pr-10`} // Add right padding for button
         value={value}
         onChange={onChange}
         ref={ref}
         {...props}
       />
       <Button
         type="button"
         variant="ghost"
         size="icon"
-        className="absolute right-0 top-0 rounded-l-none h-full"
+        className="absolute right-1 top-1/2 -translate-y-1/2 h-8 w-8"
+        aria-label={`Enhance ${fieldName || 'content'} with AI`}
         onClick={handleAIClick}
         disabled={isLoading}
       >
```


> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In components/admin/ai-enhanced-input.tsx around lines 93 to 113, the Button is
absolutely positioned which may cause it to overlap input content and create
accessibility issues. To fix this, adjust the layout to avoid absolute
positioning by using flexbox or grid so the button is inline with the input
without overlapping. Ensure the button remains visually distinct and accessible
by proper spacing and focus management.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit aa12924
created_at: 2025-05-26T15:09:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+"use client";
+
+import React, { useState } from "react";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { Loader2 } from "lucide-react";
+import { RiRobot2Fill } from "react-icons/ri";
+import { toast } from "@/components/ui/use-toast";
+
+interface AIEnhancedInputProps
+  extends React.InputHTMLAttributes<HTMLInputElement> {
+  onValueChange?: (value: string) => void;
+  fieldName?: string;
+}
+
+export const AIEnhancedInput = React.forwardRef<
+  HTMLInputElement,
+  AIEnhancedInputProps
+>(({ className, onValueChange, fieldName, value, onChange, ...props }, ref) => {
+  const [isLoading, setIsLoading] = useState(false);
+
+  const handleAIClick = async () => {
+    if (!value) {
+      toast({
+        title: "No content to enhance",
+        description: "Please add some content to enhance with AI.",
+        variant: "destructive"
+      });
+      return;
+    }
+
+    setIsLoading(true);
+
+    try {
+      const response = await fetch("/api/ai", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json"
+        },
+        body: JSON.stringify({
+          systemInput: `You are a professional content enhancer specializing in CV and resume content. 
+Your task is to improve the provided ${fieldName ||
+            "text"} to make it more professional, concise, and impactful.
+Keep the same meaning but enhance the wording. Respond with ONLY the improved text without any explanations or additional text.`,
+          data: String(value),
+          creativity: 0.3
+        })
path: components/admin/ai-enhanced-input.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Sanitize user input and validate AI response to prevent XSS attacks.**

The component directly uses user input in the API request and sets AI response without sanitization, creating potential XSS vulnerabilities.



```diff
   body: JSON.stringify({
     systemInput: `You are a professional content enhancer specializing in CV and resume content. 
 Your task is to improve the provided ${fieldName ||
           "text"} to make it more professional, concise, and impactful.
 Keep the same meaning but enhance the wording. Respond with ONLY the improved text without any explanations or additional text.`,
-    data: String(value),
+    data: String(value).slice(0, 5000), // Limit input length
     creativity: 0.3
   })
```

Also add response validation:

```diff
   const data = await response.json();

   if (!response.ok) {
     throw new Error(data.error || "Failed to get AI suggestions");
   }

+  // Validate and sanitize AI response
+  if (!data.response || typeof data.response !== 'string') {
+    throw new Error("Invalid AI response format");
+  }
+
+  // Basic XSS prevention - strip potential HTML/script tags
+  const sanitizedResponse = data.response.replace(/<[^>]*>/g, '');
```


> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In components/admin/ai-enhanced-input.tsx around lines 40 to 47, sanitize the
user input before including it in the API request to prevent XSS attacks.
Additionally, after receiving the AI response, validate and sanitize the
response content before using it in the component to ensure no malicious scripts
can be executed. Use appropriate sanitization libraries or functions for both
input and output handling.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-26T15:09:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,118 @@
+"use client";
+
+import React, { useState } from "react";
+import { Textarea } from "@/components/ui/textarea";
+import { Button } from "@/components/ui/button";
+import { Loader2 } from "lucide-react";
+import { RiRobot2Fill } from "react-icons/ri";
+import { toast } from "@/components/ui/use-toast";
+
+interface AIEnhancedTextareaProps
+  extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
+  onValueChange?: (value: string) => void;
+  fieldName?: string;
+}
+
+export const AIEnhancedTextarea = React.forwardRef<
+  HTMLTextAreaElement,
+  AIEnhancedTextareaProps
+>(({ className, onValueChange, fieldName, value, onChange, ...props }, ref) => {
+  const [isLoading, setIsLoading] = useState(false);
+
+  const handleAIClick = async () => {
+    if (!value) {
+      toast({
+        title: "No content to enhance",
+        description: "Please add some content to enhance with AI.",
+        variant: "destructive"
+      });
+      return;
+    }
+
+    setIsLoading(true);
+
+    try {
+      const response = await fetch("/api/ai", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json"
+        },
+        body: JSON.stringify({
+          systemInput: `You are a professional content enhancer specializing in CV and resume content. 
+Your task is to improve the provided ${fieldName ||
+            "text"} to make it more professional, concise, and impactful.
+Keep the same meaning but enhance the wording and structure. Format appropriately with bullet points if it's a list of accomplishments.
+Respond with ONLY the improved text without any explanations or additional text.`,
+          data: String(value),
+          creativity: 0.3
+        })
+      });
+
+      const data = await response.json();
+
+      if (!response.ok) {
+        throw new Error(data.error || "Failed to get AI suggestions");
+      }
+
+      // Call the parent's onChange handler with a synthetic event
+      if (onChange) {
+        const syntheticEvent = {
+          target: {
+            value: data.response,
+            name: props.name
+          }
+        } as React.ChangeEvent<HTMLTextAreaElement>;
+
+        onChange(syntheticEvent);
+      }
+
+      // Also call the onValueChange callback if provided
+      if (onValueChange) {
+        onValueChange(data.response);
+      }
+
+      toast({
+        title: "Content enhanced",
+        description: "AI has enhanced your content."
+      });
+    } catch (error) {
+      console.error("AI Enhancement error:", error);
+      toast({
+        title: "Enhancement failed",
+        description:
+          error instanceof Error
+            ? error.message
+            : "Failed to enhance content with AI",
+        variant: "destructive"
+      });
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  return (
+    <div className="relative">
+      <Textarea
+        className={className}
+        value={value}
+        onChange={onChange}
+        ref={ref}
+        {...props}
+      />
+      <Button
+        type="button"
+        variant="ghost"
+        size="icon"
+        className="absolute top-2 right-2 bg-background"
+        onClick={handleAIClick}
+        disabled={isLoading}
+      >
+        {isLoading
+          ? <Loader2 className="h-4 w-4 animate-spin" />
+          : <RiRobot2Fill className="h-4 w-4" />}
+      </Button>
path: components/admin/ai-enhanced-textarea.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Improve button accessibility and prevent content overlap.**

The button positioning could interfere with textarea content and lacks proper accessibility attributes.



```diff
       <Button
         type="button"
         variant="ghost"
         size="icon"
-        className="absolute top-2 right-2 bg-background"
+        className="absolute top-2 right-2 bg-background/80 hover:bg-background"
+        aria-label={`Enhance ${fieldName || 'content'} with AI`}
         onClick={handleAIClick}
         disabled={isLoading}
       >
```

Also consider adding `resize-none` or padding to prevent button overlap:

```diff
       <Textarea
-        className={className}
+        className={`${className} pr-12 pb-12`} // Prevent overlap with button
         value={value}
         onChange={onChange}
         ref={ref}
         {...props}
       />
```

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In components/admin/ai-enhanced-textarea.tsx around lines 102 to 113, the button
is absolutely positioned and may overlap the textarea content, and it lacks
accessibility attributes. To fix this, add appropriate aria-label attributes to
the button for screen readers, and adjust the textarea styling by adding padding
or the class `resize-none` to prevent content overlap with the button.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit f6d7927
created_at: 2025-05-26T15:09:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -164,3 +204,30 @@ git add .\public\uploads\profile-* -f
 - Remember to commit and push after adding.
 
 > **Note:** If you do not force-add, new or updated images in `/public/uploads` will not be tracked by git and will not appear on your deployed site.
+
+## 📚 Environment Variables
+
+The following environment variables should be set in your `.env` file:
+
+```
+# Admin authentication
+ADMIN_PASSWORD=your-secure-password
+
+# OpenRouter API for AI features
+OPENROUTER_KEY=your-openrouter-api-key
+OPENROUTER_MODEL=openai/gpt-4.1-nano
+```
+
+## 🤝 Contributing
+
+Contributions are welcome! Please feel free to submit a Pull Request.
+
+1. Fork the repository
+2. Create your feature branch (`git checkout -b feature/amazing-feature`)
+3. Commit your changes (`git commit -m 'Add some amazing feature'`)
+4. Push to the branch (`git push origin feature/amazing-feature`)
+5. Open a Pull Request
+
+## 📄 License
+
+This project is open source and available under the MIT License.
path: README.md
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Remove duplicate headings to improve document structure.**

The "Contributing" and "License" sections are duplicated in the document, which creates confusion and violates markdown best practices.



```diff
-## 🤝 Contributing
-
-Contributions are welcome! Please feel free to submit a Pull Request.
-
-1. Fork the repository
-2. Create your feature branch (`git checkout -b feature/amazing-feature`)
-3. Commit your changes (`git commit -m 'Add some amazing feature'`)
-4. Push to the branch (`git push origin feature/amazing-feature`)
-5. Open a Pull Request
-
-## 📄 License
-
-This project is open source and available under the MIT License.
```

The original "Contributing" section (lines 145-154) and "License" section (lines 158-160) should be sufficient.


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

```

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[style] ~223-~223: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing  Contributions are welcome! Please feel free to submit a Pull Request.  1. Fork the rep...

(FEEL_FREE_TO_STYLE_ME)

---

[style] ~227-~227: Consider using a more formal and expressive alternative to ‘amazing’.
Context: ...azing-feature`) 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 4. Push to the branch (`git ...

(AWESOME)

</details>
<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

221-221: Multiple headings with the same content
null

(MD024, no-duplicate-heading)

---

231-231: Multiple headings with the same content
null

(MD024, no-duplicate-heading)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In README.md between lines 221 and 233, remove the duplicated "Contributing" and
"License" sections to avoid redundancy and confusion. Keep only the original
"Contributing" section at lines 145-154 and the "License" section at lines
158-160, ensuring the document structure is clear and follows markdown best
practices.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit aa12924
created_at: 2025-05-26T15:09:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,250 @@
+import { NextRequest, NextResponse } from "next/server";
+import { getOpenRouterAnswer } from "@/services/openrouter";
+
+// Define the comprehensive system prompt for the CV Writing Assistant
+const CV_WRITING_ASSISTANT_PROMPT = `
+AI System Prompt: Professional CV Writing Assistant
+
+Role: You are a professional CV Writing Assistant. Your expertise lies in transforming raw information and existing text into concise, impactful, and professional language suitable for a modern CV. You understand common CV structures, best practices for different fields (e.g., achievements, skills, summaries), and how to tailor language for maximum effect.
+
+Objective: To assist the user in updating their CV *one specific field or section at a time* by providing suggested professional text based on their input for that particular field.
+
+Mode of Operation:
+1.  You will wait for the user to explicitly state *which specific CV field or section* they want to work on next (e.g., "Summary," "Skills," "Work Experience - Job Title X," "Education," "Achievements," etc.).
+2.  The user will provide you with their current text for that field, key points they want to include, or a description of what they need help writing for that specific part of the CV.
+3.  Based *only* on the input for that single, requested field, you will provide polished, professional text options or suggestions.
+4.  You will ask clarifying questions *only* if necessary to improve the suggestions for the *current field*.
+5.  You will maintain a focused conversation on the *currently requested field* until the user indicates they are ready to move to another.
+
+Capabilities:
+*   Suggest professional wording and phrasing for any CV section.
+*   Help condense lengthy descriptions into concise bullet points or summaries.
+*   Identify opportunities to use stronger action verbs.
+*   Suggest ways to quantify achievements where applicable (if the user provides data).
+*   Tailor language to sound more impactful and relevant.
+*   Format suggestions clearly (e.g., bullet points for experience/skills, paragraph for summary).
+*   Maintain a professional, encouraging, and patient tone.
+
+Constraints & Rules:
+*   **CRITICAL:** DO NOT jump ahead or anticipate which field the user will want to work on next.
+*   **CRITICAL:** DO NOT list all possible CV sections or create a step-by-step plan *unless specifically asked by the user*. Wait for the user to guide the flow by requesting the next field.
+*   Focus *only* on the specific field the user has currently requested help with.
+*   Do not ask for information about other CV fields unless it's directly relevant to improving the *current* field (e.g., asking about the industry to tailor skill descriptions).
+*   Keep responses helpful, professional, and directly related to the user's input for the current field.
+
+Starting Instruction:
+(This instruction helps reinforce the starting behavior for the AI. When the user sends their first message to this endpoint after it receives this prompt, the AI should respond according to this instruction.)
+Hello! I'm your Professional CV Writing Assistant, ready to help you refine your CV field by field. Just tell me which specific section you'd like to work on, share your current text or ideas for it, and I'll provide some professional suggestions. Which part would you like to start with?
+`; // Using backticks for multi-line string
+
+export async function POST(req: NextRequest) {
+  try {
+    // Authentication check: Verify user identity
+    const isDev = process.env.NODE_ENV === "development";
+
+    // Check authentication by verifying the admin_authenticated cookie
+    const isAuthenticated = req.cookies.has("admin_authenticated");
+    
+    // Get the API key from the request header if provided
+    const apiKey = req.headers.get("x-api-key");
+    const validApiKey = process.env.AI_API_KEY;
+    const hasValidApiKey = apiKey && validApiKey && apiKey === validApiKey;
+
+    // If not in development mode and neither authenticated via cookie nor valid API key, reject the request
+    if (!isDev && !isAuthenticated && !hasValidApiKey) {
+      return NextResponse.json(
+        { error: "Unauthorized: Authentication required" },
+        { status: 401 }
+      );
+    }    // Check rate limiting (optional enhancement)
+    // This is a simple example - in production you would use a more robust solution
+    const clientIp = req.headers.get("x-forwarded-for") || req.headers.get("x-real-ip") || "";
+    const requestsPerMinuteLimit = 10; // Adjust as needed
+    
+    // In a real implementation, you would check against a cache/database
+    // For now we'll skip the actual implementation but keep the structure
+    
+    // Parse the request body with error handling
+    let body;
+    try {
+      body = await req.json();
+    } catch (error) {
+      return NextResponse.json(
+        { error: "Invalid JSON: Could not parse request body" },
+        { status: 400 }
+      );
+    }
+
+    // Comprehensive validation of the request body
+    if (!body || typeof body !== "object") {
+      return NextResponse.json(
+        { error: "Invalid request: Request body must be a valid JSON object" },
+        { status: 400 }
+      );
+    }    // Extract and validate required fields
+    const { data, creativity, systemInput: userSystemInput, model } = body;
+    
+    // Validate data field (required)
+    if (data === undefined) {
+      return NextResponse.json(
+        { error: "Missing required field: data is required" },
+        { status: 400 }
+      );
+    }
+
+    // Validate data type and content
+    if (typeof data !== "string") {
+      return NextResponse.json(
+        { error: "Invalid data format: data must be a string" },
+        { status: 400 }
+      );
+    }
+    
+    // Check data length to prevent abuse
+    if (data.length > 10000) {
+      return NextResponse.json(
+        { error: "Invalid data: content exceeds maximum length (10000 characters)" },
+        { status: 400 }
+      );
+    }
+    
+    // Check for empty content after trimming
+    if (data.trim().length === 0) {
+      return NextResponse.json(
+        { error: "Invalid data: content cannot be empty" },
+        { status: 400 }
+      );
+    }
+
+    // Validate creativity if provided
+    if (creativity !== undefined) {
+      if (typeof creativity !== "number") {
+        return NextResponse.json(
+          { error: "Invalid creativity format: must be a number" },
+          { status: 400 }
+        );
+      }
+      
+      if (creativity < 0 || creativity > 1) {
+        return NextResponse.json(
+          { error: "Invalid creativity value: must be between 0 and 1" },
+          { status: 400 }
+        );
+      }
+    }
+    
+    // Validate systemInput if provided
+    if (userSystemInput !== undefined) {
+      if (typeof userSystemInput !== "string") {
+        return NextResponse.json(
+          { error: "Invalid systemInput format: must be a string" },
+          { status: 400 }
+        );
+      }
+      
+      // Check for potential prompt injection or malicious input
+      if (userSystemInput.length > 20000) {
+        return NextResponse.json(
+          { error: "Invalid systemInput: exceeds maximum length (20000 characters)" },
+          { status: 400 }
+        );
+      }
+    }
+    
+    // Validate model if provided (optional enhancement)
+    if (model !== undefined) {
+      if (typeof model !== "string") {
+        return NextResponse.json(
+          { error: "Invalid model format: must be a string" },
+          { status: 400 }
+        );
+      }
+      
+      // Validate against allowed models if needed
+      const allowedModels = ["gpt-4", "gpt-3.5-turbo", "claude-3"]; // Example
+      if (!allowedModels.includes(model)) {
+        return NextResponse.json(
+          { error: `Invalid model: must be one of ${allowedModels.join(", ")}` },
+          { status: 400 }
+        );
+      }
+    }    // Use the specific CV writing assistant prompt as the system input if no custom systemInput provided
+    const systemInput = userSystemInput || CV_WRITING_ASSISTANT_PROMPT;
+
+    // Set up request timeout with AbortController
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), 60000); // 60-second timeout
+    
+    try {
+      // Call OpenRouter service with validated inputs
+      // 'data' from the request body will serve as the user's input for the current field      const aiResponse = await getOpenRouterAnswer({
+        systemInput,
+        data, // This contains the user's specific input for the current field
+        creativity: creativity !== undefined ? creativity : 0.2, // Allow user to override, default to 0.2
+        customModel: model, // Pass the model if provided, using the correct property name
+        signal: controller.signal // Pass the abort signal for timeout handling
+      });
+
+      // Clear the timeout
+      clearTimeout(timeoutId);
+      
+      // Validate AI response more thoroughly
+      if (!aiResponse) {
+        throw new Error("Failed to get response from AI service");
+      }
+      
+      if (typeof aiResponse !== 'string') {
+        throw new Error("Invalid response format from AI service");
+      }
+    } catch (error) {
+      // Make sure to clear the timeout if there was an error
+      clearTimeout(timeoutId);
+      throw error;
+    }// Log successful API calls (optional)
path: app/api/ai/route.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix missing try-catch structure.**

The code is missing proper try-catch block structure:



```diff
      if (typeof aiResponse !== 'string') {
        throw new Error("Invalid response format from AI service");
      }
+      
+      // Clear the timeout
+      clearTimeout(timeoutId);
+      
+      // Log successful API calls (optional)
+      console.log(`AI API call successful: ${new Date().toISOString()}`);
+      
+      // Return the AI response
+      return NextResponse.json({ response: aiResponse });
    } catch (error) {
      // Make sure to clear the timeout if there was an error
      clearTimeout(timeoutId);
      throw error;
-    }// Log successful API calls (optional)
-    console.log(`AI API call successful: ${new Date().toISOString()}`);
-    
-    // Return the AI response
-    return NextResponse.json({ response: aiResponse });
+    }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
      if (typeof aiResponse !== 'string') {
        throw new Error("Invalid response format from AI service");
      }
      
      // Clear the timeout
      clearTimeout(timeoutId);
      
      // Log successful API calls (optional)
      console.log(`AI API call successful: ${new Date().toISOString()}`);
      
      // Return the AI response
      return NextResponse.json({ response: aiResponse });
    } catch (error) {
      // Make sure to clear the timeout if there was an error
      clearTimeout(timeoutId);
      throw error;
    }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In app/api/ai/route.ts around lines 199 to 203, the catch block is present but
the corresponding try block is missing or incomplete. Wrap the code that may
throw an error inside a try block preceding this catch block to ensure proper
error handling. This will fix the missing try-catch structure and allow the
clearTimeout and error rethrow logic to function correctly.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit d7fa0a7
created_at: 2025-05-26T18:05:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,258 @@
+import { NextRequest, NextResponse } from "next/server";
+import { getOpenRouterAnswer } from "@/services/openrouter";
+
+// Define the comprehensive system prompt for the CV Writing Assistant
+const CV_WRITING_ASSISTANT_PROMPT = `
+AI System Prompt: Professional CV Writing Assistant
+
+Role: You are a professional CV Writing Assistant. Your expertise lies in transforming raw information and existing text into concise, impactful, and professional language suitable for a modern CV. You understand common CV structures, best practices for different fields (e.g., achievements, skills, summaries), and how to tailor language for maximum effect.
+
+Objective: To assist the user in updating their CV *one specific field or section at a time* by providing suggested professional text based on their input for that particular field.
+
+Mode of Operation:
+1.  You will wait for the user to explicitly state *which specific CV field or section* they want to work on next (e.g., "Summary," "Skills," "Work Experience - Job Title X," "Education," "Achievements," etc.).
+2.  The user will provide you with their current text for that field, key points they want to include, or a description of what they need help writing for that specific part of the CV.
+3.  Based *only* on the input for that single, requested field, you will provide polished, professional text options or suggestions.
+4.  You will ask clarifying questions *only* if necessary to improve the suggestions for the *current field*.
+5.  You will maintain a focused conversation on the *currently requested field* until the user indicates they are ready to move to another.
+
+Capabilities:
+*   Suggest professional wording and phrasing for any CV section.
+*   Help condense lengthy descriptions into concise bullet points or summaries.
+*   Identify opportunities to use stronger action verbs.
+*   Suggest ways to quantify achievements where applicable (if the user provides data).
+*   Tailor language to sound more impactful and relevant.
+*   Format suggestions clearly (e.g., bullet points for experience/skills, paragraph for summary).
+*   Maintain a professional, encouraging, and patient tone.
+
+Constraints & Rules:
+*   **CRITICAL:** DO NOT jump ahead or anticipate which field the user will want to work on next.
+*   **CRITICAL:** DO NOT list all possible CV sections or create a step-by-step plan *unless specifically asked by the user*. Wait for the user to guide the flow by requesting the next field.
+*   Focus *only* on the specific field the user has currently requested help with.
+*   Do not ask for information about other CV fields unless it's directly relevant to improving the *current* field (e.g., asking about the industry to tailor skill descriptions).
+*   Keep responses helpful, professional, and directly related to the user's input for the current field.
+
+Starting Instruction:
+(This instruction helps reinforce the starting behavior for the AI. When the user sends their first message to this endpoint after it receives this prompt, the AI should respond according to this instruction.)
+Hello! I'm your Professional CV Writing Assistant, ready to help you refine your CV field by field. Just tell me which specific section you'd like to work on, share your current text or ideas for it, and I'll provide some professional suggestions. Which part would you like to start with?
+`; /**
+ * Handles POST requests to the Professional CV Writing Assistant API endpoint.
+ *
+ * Accepts user input for a specific CV section and returns AI-generated professional text suggestions tailored to that section. The AI operates under a detailed system prompt designed to guide the writing process, with optional customization of creativity and prompt.
+ *
+ * @returns A JSON response containing the AI-generated suggestion for the provided CV section, or an error message if the request is invalid or processing fails.
+ *
+ * @throws {Error} If the request body is missing the required `data` field, responds with a 400 status and an error message. If an unexpected error occurs during processing, responds with a 500 status and an error message.
+ */
+
+export async function POST(req: NextRequest) {
+  try {
+    // Authentication check: Verify user identity
+    const isDev = process.env.NODE_ENV === "development";
+
+    // Check authentication by verifying the admin_authenticated cookie
+    const isAuthenticated = req.cookies.has("admin_authenticated");
+    
+    // Get the API key from the request header if provided
+    const apiKey = req.headers.get("x-api-key");
+    const validApiKey = process.env.AI_API_KEY;
+    const hasValidApiKey = apiKey && validApiKey && apiKey === validApiKey;
+
+    // If not in development mode and neither authenticated via cookie nor valid API key, reject the request
+    if (!isDev && !isAuthenticated && !hasValidApiKey) {
+      return NextResponse.json(
+        { error: "Unauthorized: Authentication required" },
+        { status: 401 }
+      );
+    }    // Check rate limiting (optional enhancement)
+    // This is a simple example - in production you would use a more robust solution
+    const clientIp = req.headers.get("x-forwarded-for") || req.headers.get("x-real-ip") || "";
+    const requestsPerMinuteLimit = 10; // Adjust as needed
+    
+    // In a real implementation, you would check against a cache/database
+    // For now we'll skip the actual implementation but keep the structure
+    
path: app/api/ai/route.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Implement actual rate limiting or remove placeholder code.**

The rate limiting logic is currently just a placeholder with no actual enforcement. Either implement proper rate limiting or remove the misleading comments.



Consider implementing actual rate limiting:

```diff
-    // Check rate limiting (optional enhancement)
-    // This is a simple example - in production you would use a more robust solution
-    const clientIp = req.headers.get("x-forwarded-for") || req.headers.get("x-real-ip") || "";
-    const requestsPerMinuteLimit = 10; // Adjust as needed
-    
-    // In a real implementation, you would check against a cache/database
-    // For now we'll skip the actual implementation but keep the structure
+    // Rate limiting implementation (consider using @upstash/ratelimit for production)
+    // const clientIp = req.headers.get("x-forwarded-for") || req.headers.get("x-real-ip") || "unknown";
+    // Implementation would go here when ready for production
```

Or remove the placeholder entirely if not needed yet.


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
    }
    // Rate limiting implementation (consider using @upstash/ratelimit for production)
    // const clientIp = req.headers.get("x-forwarded-for") || req.headers.get("x-real-ip") || "unknown";
    // Implementation would go here when ready for production
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In app/api/ai/route.ts around lines 67 to 74, the current rate limiting code is
only a placeholder without any enforcement, which can be misleading. To fix
this, either implement a real rate limiting mechanism using a cache or database
to track requests per client IP and enforce the limit, or remove the placeholder
code and comments entirely if rate limiting is not yet required.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit f6d7927
created_at: 2025-05-27T18:02:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,258 @@
+import { NextRequest, NextResponse } from "next/server";
+import { getOpenRouterAnswer } from "@/services/openrouter";
+
+// Define the comprehensive system prompt for the CV Writing Assistant
+const CV_WRITING_ASSISTANT_PROMPT = `
+AI System Prompt: Professional CV Writing Assistant
+
+Role: You are a professional CV Writing Assistant. Your expertise lies in transforming raw information and existing text into concise, impactful, and professional language suitable for a modern CV. You understand common CV structures, best practices for different fields (e.g., achievements, skills, summaries), and how to tailor language for maximum effect.
+
+Objective: To assist the user in updating their CV *one specific field or section at a time* by providing suggested professional text based on their input for that particular field.
+
+Mode of Operation:
+1.  You will wait for the user to explicitly state *which specific CV field or section* they want to work on next (e.g., "Summary," "Skills," "Work Experience - Job Title X," "Education," "Achievements," etc.).
+2.  The user will provide you with their current text for that field, key points they want to include, or a description of what they need help writing for that specific part of the CV.
+3.  Based *only* on the input for that single, requested field, you will provide polished, professional text options or suggestions.
+4.  You will ask clarifying questions *only* if necessary to improve the suggestions for the *current field*.
+5.  You will maintain a focused conversation on the *currently requested field* until the user indicates they are ready to move to another.
+
+Capabilities:
+*   Suggest professional wording and phrasing for any CV section.
+*   Help condense lengthy descriptions into concise bullet points or summaries.
+*   Identify opportunities to use stronger action verbs.
+*   Suggest ways to quantify achievements where applicable (if the user provides data).
+*   Tailor language to sound more impactful and relevant.
+*   Format suggestions clearly (e.g., bullet points for experience/skills, paragraph for summary).
+*   Maintain a professional, encouraging, and patient tone.
+
+Constraints & Rules:
+*   **CRITICAL:** DO NOT jump ahead or anticipate which field the user will want to work on next.
+*   **CRITICAL:** DO NOT list all possible CV sections or create a step-by-step plan *unless specifically asked by the user*. Wait for the user to guide the flow by requesting the next field.
+*   Focus *only* on the specific field the user has currently requested help with.
+*   Do not ask for information about other CV fields unless it's directly relevant to improving the *current* field (e.g., asking about the industry to tailor skill descriptions).
+*   Keep responses helpful, professional, and directly related to the user's input for the current field.
+
+Starting Instruction:
+(This instruction helps reinforce the starting behavior for the AI. When the user sends their first message to this endpoint after it receives this prompt, the AI should respond according to this instruction.)
+Hello! I'm your Professional CV Writing Assistant, ready to help you refine your CV field by field. Just tell me which specific section you'd like to work on, share your current text or ideas for it, and I'll provide some professional suggestions. Which part would you like to start with?
+`; /**
+ * Handles POST requests to the Professional CV Writing Assistant API endpoint.
+ *
+ * Accepts user input for a specific CV section and returns AI-generated professional text suggestions tailored to that section. The AI operates under a detailed system prompt designed to guide the writing process, with optional customization of creativity and prompt.
+ *
+ * @returns A JSON response containing the AI-generated suggestion for the provided CV section, or an error message if the request is invalid or processing fails.
+ *
+ * @throws {Error} If the request body is missing the required `data` field, responds with a 400 status and an error message. If an unexpected error occurs during processing, responds with a 500 status and an error message.
+ */
+
+export async function POST(req: NextRequest) {
+  try {
+    // Authentication check: Verify user identity
+    const isDev = process.env.NODE_ENV === "development";
+
+    // Check authentication by verifying the admin_authenticated cookie
+    const isAuthenticated = req.cookies.has("admin_authenticated");
+    
+    // Get the API key from the request header if provided
+    const apiKey = req.headers.get("x-api-key");
+    const validApiKey = process.env.AI_API_KEY;
+    const hasValidApiKey = apiKey && validApiKey && apiKey === validApiKey;
+
+    // If not in development mode and neither authenticated via cookie nor valid API key, reject the request
+    if (!isDev && !isAuthenticated && !hasValidApiKey) {
+      return NextResponse.json(
+        { error: "Unauthorized: Authentication required" },
+        { status: 401 }
+      );
+    }    // Check rate limiting (optional enhancement)
+    // This is a simple example - in production you would use a more robust solution
+    const clientIp = req.headers.get("x-forwarded-for") || req.headers.get("x-real-ip") || "";
+    const requestsPerMinuteLimit = 10; // Adjust as needed
+    
+    // In a real implementation, you would check against a cache/database
+    // For now we'll skip the actual implementation but keep the structure
+    
+    // Parse the request body with error handling
+    let body;
+    try {
+      body = await req.json();
+    } catch (error) {
+      return NextResponse.json(
+        { error: "Invalid JSON: Could not parse request body" },
+        { status: 400 }
+      );
+    }
+
+    // Comprehensive validation of the request body
+    if (!body || typeof body !== "object") {
+      return NextResponse.json(
+        { error: "Invalid request: Request body must be a valid JSON object" },
+        { status: 400 }
+      );
+    }    // Extract and validate required fields
+    const { data, creativity, systemInput: userSystemInput, model } = body;
+    
+    // Validate data field (required)
+    if (data === undefined) {
+      return NextResponse.json(
+        { error: "Missing required field: data is required" },
+        { status: 400 }
+      );
+    }
+
+    // Validate data type and content
+    if (typeof data !== "string") {
+      return NextResponse.json(
+        { error: "Invalid data format: data must be a string" },
+        { status: 400 }
+      );
+    }
+    
+    // Check data length to prevent abuse
+    if (data.length > 10000) {
+      return NextResponse.json(
+        { error: "Invalid data: content exceeds maximum length (10000 characters)" },
+        { status: 400 }
+      );
+    }
+    
+    // Check for empty content after trimming
+    if (data.trim().length === 0) {
+      return NextResponse.json(
+        { error: "Invalid data: content cannot be empty" },
+        { status: 400 }
+      );
+    }
+
+    // Validate creativity if provided
+    if (creativity !== undefined) {
+      if (typeof creativity !== "number") {
+        return NextResponse.json(
+          { error: "Invalid creativity format: must be a number" },
+          { status: 400 }
+        );
+      }
+      
+      if (creativity < 0 || creativity > 1) {
+        return NextResponse.json(
+          { error: "Invalid creativity value: must be between 0 and 1" },
+          { status: 400 }
+        );
+      }
+    }
+    
+    // Validate systemInput if provided
+    if (userSystemInput !== undefined) {
+      if (typeof userSystemInput !== "string") {
+        return NextResponse.json(
+          { error: "Invalid systemInput format: must be a string" },
+          { status: 400 }
+        );
+      }
+      
+      // Check for potential prompt injection or malicious input
+      if (userSystemInput.length > 20000) {
+        return NextResponse.json(
+          { error: "Invalid systemInput: exceeds maximum length (20000 characters)" },
+          { status: 400 }
+        );
+      }
+    }
+    
+    // Validate model if provided (optional enhancement)
+    if (model !== undefined) {
+      if (typeof model !== "string") {
+        return NextResponse.json(
+          { error: "Invalid model format: must be a string" },
+          { status: 400 }
+        );
+      }
+      
+      // Validate against allowed models if needed
+      const allowedModels = ["gpt-4", "gpt-3.5-turbo", "claude-3"]; // Example
+      if (!allowedModels.includes(model)) {
+        return NextResponse.json(
+          { error: `Invalid model: must be one of ${allowedModels.join(", ")}` },
+          { status: 400 }
+        );
+      }
+    }    // Use the specific CV writing assistant prompt as the system input if no custom systemInput provided
+    const systemInput = userSystemInput || CV_WRITING_ASSISTANT_PROMPT;
+
+    // Set up request timeout with AbortController
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), 60000); // 60-second timeout
+    
+    try {
+      // Call OpenRouter service with validated inputs
+      // 'data' from the request body will serve as the user's input for the current field      const aiResponse = await getOpenRouterAnswer({
+        systemInput,
+        data, // This contains the user's specific input for the current field
+        creativity: creativity !== undefined ? creativity : 0.2, // Allow user to override, default to 0.2
+        customModel: model, // Pass the model if provided, using the correct property name
+        signal: controller.signal // Pass the abort signal for timeout handling
+      });
path: app/api/ai/route.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix critical syntax error preventing compilation.**

There's a missing semicolon and improper line formatting that breaks the code structure.



Apply this diff to fix the syntax error:

```diff
-      // 'data' from the request body will serve as the user's input for the current field      const aiResponse = await getOpenRouterAnswer({
+      // 'data' from the request body will serve as the user's input for the current field
+      const aiResponse = await getOpenRouterAnswer({
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
      // 'data' from the request body will serve as the user's input for the current field
      const aiResponse = await getOpenRouterAnswer({
        systemInput,
        data, // This contains the user's specific input for the current field
        creativity: creativity !== undefined ? creativity : 0.2, // Allow user to override, default to 0.2
        customModel: model, // Pass the model if provided, using the correct property name
        signal: controller.signal // Pass the abort signal for timeout handling
      });
```

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 191-191: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement


(parse)

---

[error] 194-194: Expected a catch clause but instead found ')'.

Expected a catch clause here.


(parse)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In app/api/ai/route.ts around lines 188 to 194, there is a missing semicolon and
improper line formatting causing a syntax error. Add the missing semicolon at
the end of the statement and ensure each line is properly separated and
formatted to maintain correct code structure and allow successful compilation.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit d7fa0a7
created_at: 2025-05-27T18:02:03Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-05-26T15:06:09Z
body: [vc]: #SyiwKxNc4/pYbWofRlJeFnIex15TvT7AbJb+ml0t1aU=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJwcGFuYXlvdG92LmNvbSIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJwcGFuYXlvdG92Y29tLWdpdC1haS1nZW4tdGV4dC1pbmRleC1wYW5heW90b3ZzLXByb2plY3RzLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2luZGV4LXBhbmF5b3RvdnMtcHJvamVjdHMvcHBhbmF5b3Rvdi5jb20vRVY3QnB3OEZqYjVmcVBnbmIzVHA4UjdLVTM4SyIsInByZXZpZXdVcmwiOiJwcGFuYXlvdG92Y29tLWdpdC1haS1nZW4tdGV4dC1pbmRleC1wYW5heW90b3ZzLXByb2plY3RzLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJyb290RGlyZWN0b3J5IjpudWxsfV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **ppanayotov.com** | ✅ Ready ([Inspect](https://vercel.com/index-panayotovs-projects/ppanayotov.com/EV7Bpw8Fjb5fqPgnb3Tp8R7KU38K)) | [Visit Preview](https://ppanayotovcom-git-ai-gen-text-index-panayotovs-projects.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/ppanayotovcom-git-ai-gen-text-index-panayotovs-projects.vercel.app?via=pr-comment-feedback-link) | May 27, 2025 6:37pm |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-05-26T15:06:12Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This update introduces AI-powered content enhancement features throughout the admin panel, including new AI-enhanced input and textarea components for CV editing. Two new API endpoints are added: one for AI-driven CV field improvement and another for automatically identifying top skills from experience data. Documentation is updated to reflect these features, and a helper for OpenRouter AI integration is implemented.

## Changes

| File(s)                                                                                  | Change Summary                                                                                                                        |
|------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| README.md                                                                               | Expanded documentation for AI and image upload features, updated tech stack, environment, folder structure, and contributing sections. |
| docs/IMAGE_UPLOAD.md                                                                    | Deleted detailed documentation for the image upload feature.                                                                         |
| app/api/ai/route.ts                                                                     | Added new POST API route for AI-powered CV Writing Assistant, handling prompts and OpenRouter integration.                            |
| app/api/admin/autoskills/route.ts                                                       | Added new POST API route to analyze experience data and return top 10 most common skills (dev-only).                                 |
| app/admin/handlers.ts                                                                   | Improved error handling and response validation in `generateAutomaticTopSkills` function.                                            |
| services/openrouter.ts                                                                  | Added `getOpenRouterAnswer` function to call OpenRouter API for AI-generated responses.                                              |
| types/index.ts                                                                          | Added `OpenRouterOptions` interface for OpenRouter API integration.                                                                  |
| components/admin/ai-components.ts                                                       | New module re-exporting all AI-enhanced input components for admin panel use.                                                        |
| components/admin/ai-enhanced-input.tsx, components/admin/ai-enhanced-textarea.tsx       | Added AI-enhanced input and textarea React components with OpenRouter-powered content improvement.                                    |
| components/admin/profile-data-tab.tsx, components/admin/experience-dialog.tsx, components/admin/education-dialog.tsx, components/admin/certification-dialog.tsx, components/admin/language-dialog.tsx, components/admin/top-skills-tab.tsx | Replaced standard input/textarea components with new AI-enhanced versions, adding `fieldName` props for contextual AI prompts.       |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant AdminPanel
    participant AIEnhancedInput/AIEnhancedTextarea
    participant API_AI_Route
    participant OpenRouterService

    User->>AIEnhancedInput/AIEnhancedTextarea: Click "Enhance" button
    AIEnhancedInput/AIEnhancedTextarea->>API_AI_Route: POST /api/ai { prompt, value, fieldName }
    API_AI_Route->>OpenRouterService: getOpenRouterAnswer({ systemInput, data, creativity })
    OpenRouterService-->>API_AI_Route: AI-generated improved text
    API_AI_Route-->>AIEnhancedInput/AIEnhancedTextarea: JSON { improvedText }
    AIEnhancedInput/AIEnhancedTextarea-->>AdminPanel: Update field with improved text
```

```mermaid
sequenceDiagram
    participant AdminPanel
    participant API_Autoskills_Route

    AdminPanel->>API_Autoskills_Route: POST /api/admin/autoskills { experiences }
    API_Autoskills_Route-->>AdminPanel: JSON { topSkills }
```

## Poem

> 🐇✨  
> AI now lends a helping paw,  
> To polish words and skills it saw.  
> Inputs enhanced, suggestions bright,  
> CVs gleam with AI’s light.  
> Uploads, docs, and skills anew—  
> This bunny’s proud of what you do!  
> 📝🤖

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=index-panayotov/ppanayotov-com&utm_content=1):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-05-26T15:08:17Z
body: > [!NOTE]
> Generated docstrings for this pull request at https://github.com/index-panayotov/ppanayotov-com/pull/2
author_association: CONTRIBUTOR