// src/pages/api/ai/enhance-input.ts import type { APIRoute } from 'astro'; import { withAPIAuth } from '../../../utils/auth.js'; import { apiError, apiServerError, createAuthErrorResponse } from '../../../utils/api.js'; import { enqueueApiCall } from '../../../utils/rateLimitedQueue.js'; export const prerender = false; function getEnv(key: string): string { const value = process.env[key]; if (!value) { throw new Error(`Missing environment variable: ${key}`); } return value; } const AI_MODEL = getEnv('AI_MODEL'); const rateLimitStore = new Map(); const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute const RATE_LIMIT_MAX = 5; // 5 enhancement requests per minute per user function sanitizeInput(input: string): string { return input .replace(/```[\s\S]*?```/g, '[CODE_BLOCK_REMOVED]') .replace(/\<\/?[^>]+(>|$)/g, '') .replace(/\b(system|assistant|user)\s*[:]/gi, '[ROLE_REMOVED]') .replace(/\b(ignore|forget|disregard)\s+(previous|all|your)\s+(instructions?|context|rules?)/gi, '[INSTRUCTION_REMOVED]') .trim() .slice(0, 1000); // Shorter limit for enhancement } function checkRateLimit(userId: string): boolean { const now = Date.now(); const userLimit = rateLimitStore.get(userId); if (!userLimit || now > userLimit.resetTime) { rateLimitStore.set(userId, { count: 1, resetTime: now + RATE_LIMIT_WINDOW }); return true; } if (userLimit.count >= RATE_LIMIT_MAX) { return false; } userLimit.count++; return true; } function cleanupExpiredRateLimits() { const now = Date.now(); for (const [userId, limit] of rateLimitStore.entries()) { if (now > limit.resetTime) { rateLimitStore.delete(userId); } } } // Clean up expired limits every 5 minutes setInterval(cleanupExpiredRateLimits, 5 * 60 * 1000); function createEnhancementPrompt(input: string): string { return ` Du bist eine KI für digitale Forensik. Der Nutzer beschreibt ein forensisches Szenario. Analysiere die Eingabe. Wenn die Beschreibung unvollständig oder vage ist, stelle bis zu drei präzise Rückfragen im JSON-Array-Format, um wichtige Details zu klären (z. B. Vorfalltyp, System, Ziel, Datenquellen, Zeit, Beteiligte, rechtlicher Rahmen). Wenn die Eingabe bereits klar, spezifisch und vollständig ist, gib stattdessen nur eine leere Liste [] zurück. Antwortformat strikt: \`\`\`json [ "Frage 1?", "Frage 2?", "Frage 3?" ] \`\`\` Nutzer-Eingabe: ${input} `.trim(); } export const POST: APIRoute = async ({ request }) => { try { const authResult = await withAPIAuth(request, 'ai'); if (!authResult.authenticated) { return createAuthErrorResponse(); } const userId = authResult.userId; if (!checkRateLimit(userId)) { return apiError.rateLimit('Enhancement rate limit exceeded'); } const body = await request.json(); const { input } = body; if (!input || typeof input !== 'string' || input.length < 20) { return apiError.badRequest('Input too short for enhancement'); } const sanitizedInput = sanitizeInput(input); if (sanitizedInput.length < 20) { return apiError.badRequest('Input too short after sanitization'); } const systemPrompt = createEnhancementPrompt(sanitizedInput); const taskId = `enhance_${userId}_${Date.now()}_${Math.random().toString(36).substr(2, 4)}`; const aiResponse = await enqueueApiCall(() => fetch(process.env.AI_API_ENDPOINT + '/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.AI_API_KEY}` }, body: JSON.stringify({ model: AI_MODEL, messages: [ { role: 'user', content: systemPrompt } ], max_tokens: 200, temperature: 0.7 }) }), taskId); if (!aiResponse.ok) { console.error('AI enhancement error:', await aiResponse.text()); return apiServerError.unavailable('Enhancement service unavailable'); } const aiData = await aiResponse.json(); const aiContent = aiData.choices?.[0]?.message?.content; if (!aiContent) { return apiServerError.unavailable('No enhancement response'); } let questions; try { const cleanedContent = aiContent .replace(/^```json\s*/i, '') .replace(/\s*```\s*$/, '') .trim(); questions = JSON.parse(cleanedContent); if (!Array.isArray(questions) || questions.length === 0) { throw new Error('Invalid questions format'); } // Validate and clean questions questions = questions .filter(q => typeof q === 'string' && q.length > 5 && q.length < 120) .slice(0, 3); if (questions.length === 0) { throw new Error('No valid questions found'); } } catch (error) { console.error('Failed to parse enhancement response:', aiContent); return apiServerError.unavailable('Invalid enhancement response format'); } console.log(`[AI Enhancement] User: ${userId}, Questions: ${questions.length}, Input length: ${sanitizedInput.length}`); return new Response(JSON.stringify({ success: true, questions, taskId }), { status: 200, headers: { 'Content-Type': 'application/json' } }); } catch (error) { console.error('Enhancement error:', error); return apiServerError.internal('Enhancement processing failed'); } };