import vine from '@vinejs/vine' import { Exception } from '@adonisjs/core/exceptions' import type { HttpContext } from '@adonisjs/core/http' import { generateRelance } from '#services/ai_relance_generator' const RELANCE_TONES = ['amical', 'courtois', 'ferme', 'mise_en_demeure'] as const const generateRelanceValidator = vine.create({ tone: vine.enum(RELANCE_TONES), offsetDays: vine.number().min(-30).max(180), // Brief libre. On accepte vide : Mistral génère alors un message standard // pour la tonalité + timing donnés. prompt: vine.string().maxLength(1000).optional(), // Contexte du plan parent — nom + description, pour cohérence inter-étapes. planName: vine.string().maxLength(80).optional(), planDescription: vine.string().maxLength(500).optional(), }) /** * Endpoints IA. V1 : uniquement génération de templates de relance pour le * wizard de création de plan custom. Mistral est déjà utilisé pour l'OCR * (cf. mistral_ocr_provider.ts) — on réutilise la même clé API. */ export default class AiController { /** * POST /ai/generate-relance * * Génère subject + body avec des placeholders Mustache prêts à insérer. * L'utilisateur peut régénérer pour avoir une variante. */ async generateRelance({ auth, request, response }: HttpContext) { auth.getUserOrFail() // auth requise const payload = await request.validateUsing(generateRelanceValidator) try { const result = await generateRelance({ tone: payload.tone, offsetDays: payload.offsetDays, prompt: payload.prompt ?? '', planName: payload.planName, planDescription: payload.planDescription, }) return response.json({ data: result }) } catch (err) { // On wrap pour passer par le handler global et garder le format // d'erreur uniforme côté front. throw new Exception( err instanceof Error ? err.message : 'Génération IA indisponible', { status: 502, code: 'ai_generation_failed' } ) } } }