import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useCallback, useState } from 'react'; import { ActivityIndicator, Alert, Pressable, RefreshControl, ScrollView, Text, TextInput, View, } from 'react-native'; import { UI } from '@/constants/uiTheme'; import { agentAlertesScan, agentDvf, agentImmobilier, agentMarchand, agentRedaction, agentVeille, } from '@/services/agentsApi'; import { getCurrentUserId, pb } from '@/services/pocketbase'; import type { AlerteRechercheRecord, AnnonceVeilleRecord, CourrierImmobilierRecord, RechercheSauvegardeeRecord, TransactionSecteurRecord, } from '@/types/collections'; import { formatPocketBaseError } from '@/utils/pocketbaseErrors'; function showLong(title: string, body: string) { Alert.alert(title, body.length > 3500 ? `${body.slice(0, 3500)}…` : body); } export function VeilleAgentsTab() { const uid = getCurrentUserId(); const qc = useQueryClient(); const [nomRecherche, setNomRecherche] = useState(''); const [critereJson, setCritereJson] = useState('{}'); const [titreAnnonce, setTitreAnnonce] = useState(''); const [urlAnnonce, setUrlAnnonce] = useState(''); const [libelleDvf, setLibelleDvf] = useState(''); const [prixM2, setPrixM2] = useState(''); const [nbVentes, setNbVentes] = useState(''); const invalidateVeille = useCallback(() => { void qc.invalidateQueries({ queryKey: ['veille'] }); }, [qc]); const recherches = useQuery({ queryKey: ['veille', 'recherches_sauvegardees', uid], queryFn: () => pb.collection('recherches_sauvegardees').getFullList({ sort: '-updated' }), enabled: Boolean(uid), }); const alertes = useQuery({ queryKey: ['veille', 'alertes_recherche', uid], queryFn: () => pb.collection('alertes_recherche').getFullList({ sort: '-updated' }), enabled: Boolean(uid), }); const annonces = useQuery({ queryKey: ['veille', 'annonces_veille', uid], queryFn: () => pb.collection('annonces_veille').getFullList({ sort: '-updated' }), enabled: Boolean(uid), }); const trans = useQuery({ queryKey: ['veille', 'transactions_secteur', uid], queryFn: () => pb.collection('transactions_secteur').getFullList({ sort: '-updated' }), enabled: Boolean(uid), }); const courriers = useQuery({ queryKey: ['veille', 'courriers_immobilier', uid], queryFn: () => pb.collection('courriers_immobilier').getFullList({ sort: '-updated' }), enabled: Boolean(uid), }); const createRecherche = useMutation({ mutationFn: async () => { if (!uid) throw new Error('Non connecté'); const nom = nomRecherche.trim(); if (!nom) throw new Error('Nom requis'); return pb.collection('recherches_sauvegardees').create({ user: uid, nom, critere_json: critereJson.trim() || '{}', actif: true, }); }, onSuccess: () => { setNomRecherche(''); invalidateVeille(); }, }); const createAlerteSimple = useMutation({ mutationFn: async () => { if (!uid) throw new Error('Non connecté'); return pb.collection('alertes_recherche').create({ user: uid, nom: `Veille ${new Date().toLocaleDateString('fr-FR')}`, canal: 'in_app', actif: true, }); }, onSuccess: invalidateVeille, onError: (err) => Alert.alert('Erreur', formatPocketBaseError(err)), }); const scanAlertes = useMutation({ mutationFn: () => agentAlertesScan(), onSuccess: (r) => { invalidateVeille(); Alert.alert('Scan alertes', `${r.processed} alertes mises à jour.\n${r.note ?? ''}`); }, onError: (e) => Alert.alert('Erreur', formatPocketBaseError(e)), }); const pushAnnonce = useMutation({ mutationFn: () => agentVeille({ titre: titreAnnonce.trim(), url: urlAnnonce.trim() || undefined, source: 'manuel', }), onSuccess: (r) => { setTitreAnnonce(''); setUrlAnnonce(''); invalidateVeille(); Alert.alert('Veille', r.dedupe ? 'Doublon ignoré.' : `Enregistrée (${r.id}).`); }, onError: (e) => Alert.alert('Erreur', formatPocketBaseError(e)), }); const pushDvf = useMutation({ mutationFn: () => { const lib = libelleDvf.trim(); if (!lib) throw new Error('Libellé requis'); const pm = Number(String(prixM2).replace(',', '.')); const nv = Number(String(nbVentes).trim()); return agentDvf({ libelle: lib, prix_m2_median: Number.isFinite(pm) ? pm : undefined, nb_ventes: Number.isFinite(nv) ? nv : undefined, }); }, onSuccess: (r) => { setLibelleDvf(''); setPrixM2(''); setNbVentes(''); invalidateVeille(); showLong('Synthèse marché', r.synthese); }, onError: (e) => Alert.alert('Erreur', formatPocketBaseError(e)), }); const runImmobilier = useMutation({ mutationFn: () => agentImmobilier({ objectif: 'Prospection ciblée secteur', contexte: 'Génère un plan + un message court pour relancer des mandataires potentiels.', save: false, }), onSuccess: (r) => showLong('Agent immobilier', r.brouillon), onError: (e) => Alert.alert('Erreur', formatPocketBaseError(e)), }); const runMarchand = useMutation({ mutationFn: () => agentMarchand({ titre: titreAnnonce.trim() || 'Annonce test', notes: 'Comparer avec ma grille perso (onglet Grille de prix).', }), onSuccess: (r) => showLong('Agent marchand de biens', r.analyse), onError: (e) => Alert.alert('Erreur', formatPocketBaseError(e)), }); const runRedaction = useMutation({ mutationFn: () => agentRedaction({ kind: 'annonce_agence', bullets: ['Lumineux', 'Proche transports', 'Charges faibles'], save: false, }), onSuccess: (r) => showLong('Agent rédaction', r.texte), onError: (e) => Alert.alert('Erreur', formatPocketBaseError(e)), }); const loading = recherches.isPending || alertes.isPending || annonces.isPending || trans.isPending || courriers.isPending; return ( { void recherches.refetch(); void alertes.refetch(); void annonces.refetch(); void trans.refetch(); void courriers.refetch(); }} /> } > Agents IA (MVP) Connexion serveur + clé Anthropic requises. Les données restent dans PocketBase. Lancer un agent runImmobilier.mutate()} /> runMarchand.mutate()} /> runRedaction.mutate()} /> Agent data / secteur (stub DVF) pushDvf.mutate()} disabled={pushDvf.isPending || !libelleDvf.trim()} > {pushDvf.isPending ? ( ) : ( Enregistrer + synthèse IA )} Agent veille (dédoublonnage MD5) pushAnnonce.mutate()} disabled={pushAnnonce.isPending || !titreAnnonce.trim()} > {pushAnnonce.isPending ? ( ) : ( Ajouter à la veille )} Recherches sauvegardées createRecherche.mutate()} disabled={createRecherche.isPending} > Créer la recherche {recherches.data?.map((r) => ( • {r.nom} ))} Agent alertes (stub scan) createAlerteSimple.mutate()} disabled={createAlerteSimple.isPending} > Nouvelle alerte in-app scanAlertes.mutate()} disabled={scanAlertes.isPending} > {scanAlertes.isPending ? ( ) : ( Scanner mes alertes actives )} {alertes.data?.map((a) => ( • {a.nom} ({a.canal}) {a.actif === false ? '— off' : ''} ))} Annonces veille ({annonces.data?.length ?? 0}) {annonces.data?.slice(0, 8).map((a) => ( [{a.statut}] {a.titre} ))} Transactions secteur ({trans.data?.length ?? 0}) {trans.data?.slice(0, 6).map((t) => ( {t.libelle} {t.prix_m2_median != null ? ` — ${t.prix_m2_median} €/m²` : ''} ))} Courriers ({courriers.data?.length ?? 0}) {courriers.data?.slice(0, 5).map((c) => ( {c.titre} ({c.kind}/{c.etat}) ))} ); } function AgentButton(props: { label: string; loading: boolean; onPress: () => void }) { return ( {props.loading ? : {props.label}} ); }