import type { H3Event } from 'h3' import { checkRateLimitJson } from '~/server/utils/rateLimitJson' interface ChatbotPenseesRequest { query: string mode?: 'hybrid' | 'local' | 'global' | 'naive' | 'mix' filter_couche?: 'fond' | 'forme' | 'structure' | null filter_ecole?: string | null history?: Array<{ role: 'user' | 'assistant'; content: string }> } interface LightRAGQueryResponse { response: string } const SYSTEM_PREFACE = `Tu es un agent du RAG Pensées Écologiques, infrastructure militante du collectif trans-former.fr. Tu réponds en t'appuyant STRICTEMENT sur le corpus ingéré (auteurs FRACAS Bonpote : écosocialisme, éco-anarchisme, écoféminismes, écologies décoloniales, technocritique, pensées du vivant, décroissance...). Règles : - Cite les sources (auteur, livre) à chaque assertion importante. - Si la question dépasse le corpus, dis-le clairement. Pas d'hallucination. - Ton politique direct, pas de neutralité fade. - Réponse en français, dense, sans délayage. - Distingue les positions selon les écoles quand elles divergent.` export default defineEventHandler(async (event: H3Event) => { const config = useRuntimeConfig(event) // 1. Rate limit (20 req/jour/IP, IP hashée RGPD) const ip = getHeader(event, 'x-forwarded-for')?.split(',')[0].trim() || event.node.req.socket?.remoteAddress || '0.0.0.0' const allowed = checkRateLimitJson(ip, 'chatbot-pensees', 20) if (!allowed) { throw createError({ statusCode: 429, message: 'Limite de 20 questions par jour atteinte.' }) } // 2. Body parse + validation const body = await readBody(event) if (!body?.query || body.query.trim().length < 3 || body.query.trim().length > 500) { throw createError({ statusCode: 400, message: 'Query invalide (3-500 caractères).' }) } const query = body.query.trim() const mode = body.mode || 'hybrid' const ragUrl = (config.ragPeUrl as string) || 'http://localhost:9621' // 3. Health guard — LightRAG down = erreur claire, pas de fallback hallucinatoire try { await $fetch(`${ragUrl}/health`, { timeout: 5000 }) } catch { throw createError({ statusCode: 503, message: 'RAG indisponible pour l\'instant — réessaie dans quelques minutes.', }) } // 4. Call LightRAG VPS — préface système injectée dans la query const ragQuery = `${SYSTEM_PREFACE}\n\nQuestion : ${query}` let ragResponse: LightRAGQueryResponse try { ragResponse = await $fetch(`${ragUrl}/query`, { method: 'POST', body: { query: ragQuery, mode }, timeout: 90000, }) } catch (e: any) { const status = e?.response?.status if (status === 429) { throw createError({ statusCode: 429, message: 'RAG saturé — réessaie dans quelques instants.' }) } throw createError({ statusCode: 504, message: 'RAG en cours de processing — réessaie dans quelques secondes.' }) } // 5. Retour formaté return { response: ragResponse.response ?? '', mode, filter: { couche: body.filter_couche ?? null, ecole: body.filter_ecole ?? null }, timestamp: new Date().toISOString(), } })