Skip to main content

AI Coach API

The AI Coach endpoint provides conversational coaching and text translation using the Groq API (llama3-8b-8192 model).

Endpoint

POST /api/ai
File: /home/daytona/workspace/source/api/ai.ts

Rate Limiting

Limit: 30 requests per minute per IP addressTracked using in-memory map with 60-second sliding window.
// Rate limit exceeded response
{
  "error": "Demasiadas peticiones",
  "code": 429
}

Actions

The endpoint supports two actions: chat and translate.

Chat Action

Provides personalized coaching based on user context.

Request Parameters

action
string
required
Must be "chat"
message
string
required
User’s message to the AI coach (max 2000 characters)
context
object
User context for personalizationFields:
  • userName (string): User’s display name
  • mainSport (string): Primary sport/activity
  • activeTasks (string[]): List of active task titles
  • todayPomodoros (number): Completed pomodoros today
  • language (string): Response language (default: ‘español’)

Example Request

const response = await fetch('/api/ai', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    action: 'chat',
    message: '¿Cómo organizo mi tiempo de estudio hoy?',
    context: {
      userName: 'Carlos',
      mainSport: 'Fútbol',
      activeTasks: ['Matemáticas Cap 5', 'Ensayo de Historia'],
      todayPomodoros: 4,
      language: 'español'
    }
  })
});

const data = await response.json();
console.log(data.text); // AI response

System Prompt

The AI uses this context-aware system prompt:
`Eres Routine Optimizer AI Coach. Ayudas a estudiantes-deportistas.
Contexto del usuario:
- Nombre: ${context?.userName || 'Estudiante'}
- Deporte principal: ${context?.mainSport || 'Ninguno'}
- Tareas activas: ${context?.activeTasks?.join(', ') || 'Ninguna'}
- Pomodoros de hoy: ${context?.todayPomodoros || 0}
Responde en ${context?.language || 'español'}, sé directo, motivador y claro.`

Response

text
string
The AI-generated coaching response
{
  "text": "¡Hola Carlos! Con 4 pomodoros completados, vas muy bien. Te sugiero dedicar 2 pomodoros más a Matemáticas y luego descansar antes del entrenamiento de fútbol."
}

Translate Action

Translates text to a target language.

Request Parameters

action
string
required
Must be "translate"
text
string
required
Text to translate (max 2000 characters)
targetLang
string
required
Target language code (max 10 characters)Examples: ‘en’, ‘es’, ‘fr’, ‘de’, ‘english’, ‘spanish’

Example Request

const response = await fetch('/api/ai', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    action: 'translate',
    text: 'Buenos días, ¿cómo estás?',
    targetLang: 'english'
  })
});

const data = await response.json();
console.log(data.text); // "Good morning, how are you?"

System Prompt

`Traduce el siguiente texto al idioma ${targetLang}. Responde ÚNICAMENTE con la traducción, sin más comentarios.`

Response

text
string
The translated text
{
  "text": "Good morning, how are you?"
}

Error Responses

400 - Bad Request

Missing action:
{
  "error": "Campo 'action' requerido y debe ser texto"
}
Missing message (chat):
{
  "error": "Campo 'message' requerido para acción chat"
}
Message too long:
{
  "error": "El mensaje excede el límite de 2000 caracteres"
}
Missing text (translate):
{
  "error": "Campo 'text' requerido para acción translate"
}
Invalid target language:
{
  "error": "Campo 'targetLang' requerido y debe ser un código de idioma válido"
}
Invalid action:
{
  "error": "Acción no válida"
}

405 - Method Not Allowed

{
  "error": "Método no permitido"
}
Only POST requests are accepted (besides OPTIONS for CORS).

429 - Too Many Requests

{
  "error": "Demasiadas peticiones",
  "code": 429
}
Triggered after 30 requests in 60 seconds from the same IP.

500 - Internal Server Error

{
  "error": "Clave de Groq no encontrada en el servidor",
  "code": 500
}
Indicates missing GROQ_API_KEY environment variable.

503 - Service Unavailable

{
  "error": "IA no disponible",
  "code": 503
}
or
{
  "error": "IA fallida o no disponible",
  "code": 503
}
Indicates Groq API is down or returned an error.

CORS Configuration

The endpoint includes CORS headers:
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Origin', process.env.VITE_APP_URL || 'http://localhost:5173');
res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS,PATCH,DELETE,POST,PUT');
res.setHeader('Access-Control-Allow-Headers', 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version');
Allowed Origin: Configured via VITE_APP_URL environment variable.

Environment Variables

GROQ_API_KEY
string
required
Your Groq API key for llama3-8b-8192 model access
VITE_APP_URL
string
Allowed CORS origin (defaults to http://localhost:5173)

Implementation Details

IP Detection

Extracts real IP for rate limiting:
const ip = req.headers['x-real-ip'] || req.socket.remoteAddress || 'desconocida';
const direccionIp = Array.isArray(ip) ? ip[0] : ip;

Rate Limit Storage

const rateLimit = new Map<string, number[]>();  // IP -> timestamps[]

const tiempos = rateLimit.get(direccionIp) || [];
const tiemposValidos = tiempos.filter(t => ahora - t < 60000); // Last 60s

if (tiemposValidos.length >= 30) {
  return res.status(429).json({ error: "Demasiadas peticiones", code: 429 });
}

Groq API Request

const groqResponse = await fetch('https://api.groq.com/openai/v1/chat/completions', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    model: 'llama3-8b-8192',
    messages: [
      { role: 'system', content: systemPrompt },
      { role: 'user', content: message }
    ]
  })
});

Usage in App

Chat Hook Example

import { useState } from 'react';

export function useAICoach() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const sendMessage = async (message: string, context?: any) => {
    setLoading(true);
    setError(null);

    try {
      const response = await fetch('/api/ai', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ action: 'chat', message, context })
      });

      const data = await response.json();

      if (!response.ok) {
        throw new Error(data.error || 'AI request failed');
      }

      return data.text;
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error');
      return null;
    } finally {
      setLoading(false);
    }
  };

  return { sendMessage, loading, error };
}

Translation Hook Example

export async function translateText(text: string, targetLang: string): Promise<string> {
  const response = await fetch('/api/ai', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ action: 'translate', text, targetLang })
  });

  const data = await response.json();

  if (!response.ok) {
    throw new Error(data.error || 'Translation failed');
  }

  return data.text;
}

Best Practices

Rate Limit Awareness: Implement client-side debouncing and caching to stay under 30 req/min.
Context Quality: Provide rich context for better coaching responses.
Error Handling: Always handle 429 and 503 errors gracefully with retry logic.
Input Validation: Validate message/text length on client before sending.
Loading States: Show clear loading indicators during AI operations.

Build docs developers (and LLMs) love