import { serve } from "https://deno.land/std@0.168.0/http/server.ts" import { createClient } from "https://esm.sh/@supabase/supabase-js@2" const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apiKey, content-type', } serve(async (req) => { if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders }) try { const payload = await req.json() const { table, type, record } = payload // 🥷 1. FILTRO EVENTI: Agiamo solo sui nuovi allegati if (table !== 'attachments' || type !== 'INSERT') { return new Response(JSON.stringify({ message: "Ignorato: non è un INSERT su attachments." }), { status: 200, headers: corsHeaders }) } // Se l'allegato ha GIÀ un customer_id, l'operatore l'ha caricato direttamente // dalla scheda cliente. Non serve sprecare soldi con l'AI. if (record.customer_id) { return new Response(JSON.stringify({ message: "Customer già presente, analisi saltata." }), { status: 200, headers: corsHeaders }) } // Se non è collegato a un ticket, a un'operazione o a una nota, non abbiamo // modo di risalire al proprietario, quindi fermiamo tutto. if (!record.operation_id && !record.ticket_id && !record.note_id) { return new Response(JSON.stringify({ message: "Nessuna entità collegata da cui estrarre il customer." }), { status: 200, headers: corsHeaders }) } // 🥷 2. FILTRO FORMATI: Passiamo a OpenAI solo immagini vere (niente zip o pdf per ora) // Nota: OpenAI supporta anche i PDF in vision, ma le immagini sono più sicure/economiche per i documenti. const validExtensions = ['jpg', 'jpeg', 'png', 'webp'] const ext = record.extension?.replace('.', '').toLowerCase() if (!validExtensions.includes(ext)) { return new Response(JSON.stringify({ message: "Formato non supportato da Vision." }), { status: 200, headers: corsHeaders }) } const supabase = createClient( Deno.env.get('SUPABASE_URL') ?? '', Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '' ) // 🥷 3. GENERAZIONE URL SICURO // Dal momento che il tuo storage bucket è (spero) privato, dobbiamo creare un URL // temporaneo firmato da passare a OpenAI, valido solo per i prossimi 60 secondi. const { data: urlData, error: urlError } = await supabase.storage .from('documents') .createSignedUrl(record.storage_path, 60) if (urlError || !urlData) throw new Error(`Errore generazione URL: ${urlError?.message}`) const imageUrl = urlData.signedUrl // 🥷 4. L'INTERROGAZIONE ALL'AI (Il cuore del sistema) console.log(`Analizzo immagine ${record.id} con OpenAI...`) const openAiResponse = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Authorization': `Bearer ${Deno.env.get('OPENAI_API_KEY')}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ model: "gpt-4o-mini", // Il modello super veloce ed economico response_format: { type: "json_object" }, // Vogliamo solo codice, niente chiacchiere messages: [ { role: "user", content: [ { type: "text", text: "Questa immagine è una foto o una scansione di un documento d'identità personale valido (es. carta d'identità, patente di guida, passaporto, tessera sanitaria, codice fiscale)? Rispondi ESCLUSIVAMENTE con un JSON in questo formato: { \"is_id\": true } oppure { \"is_id\": false }" }, { type: "image_url", image_url: { "url": imageUrl } } ] } ] }) }) const aiData = await openAiResponse.json() const resultText = aiData.choices[0].message.content const result = JSON.parse(resultText) // 🥷 5. LA LOGICA DI SMISTAMENTO if (result.is_id === true) { console.log(`🎯 Documento rilevato! Cerco il padrone...`) let targetCustomerId = null // Andiamo a pescare il customer_id esplorando l'albero delle relazioni if (record.operation_id) { const { data } = await supabase.from('operations').select('customer_id').eq('id', record.operation_id).single() targetCustomerId = data?.customer_id } else if (record.ticket_id) { const { data } = await supabase.from('tickets').select('customer_id').eq('id', record.ticket_id).single() targetCustomerId = data?.customer_id } else if (record.note_id) { // Se le note hanno una FK verso i clienti: const { data } = await supabase.from('notes').select('customer_id').eq('id', record.note_id).single() targetCustomerId = data?.customer_id } // Se abbiamo trovato il proprietario, facciamo l'update dell'allegato! if (targetCustomerId) { await supabase .from('attachments') .update({ customer_id: targetCustomerId }) .eq('id', record.id) console.log(`✅ Allegato aggiornato. Legato al cliente: ${targetCustomerId}`) } else { console.log(`⚠️ Documento rilevato, ma non c'è nessun cliente legato all'entità genitore.`) } } else { console.log(`L'immagine non è un documento (è uno scontrino, una foto di un router, ecc.).`) } return new Response(JSON.stringify({ success: true, is_id: result.is_id }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, status: 200 }) } catch (error) { console.error("ERRORE FATALE NELLA FUNZIONE:", error) return new Response(JSON.stringify({ error: error.toString() }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, status: 500 }) } })