186 lines
5.5 KiB
TypeScript
186 lines
5.5 KiB
TypeScript
// 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<string, { count: number; resetTime: number }>();
|
||
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');
|
||
}
|
||
}; |