edge function per interrogare openai, capire quali immagini sono di documenti d'identità e assegnarli al cliente
This commit is contained in:
136
supabase/functions/id_doc_manager/index.ts
Normal file
136
supabase/functions/id_doc_manager/index.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
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
|
||||
})
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user