import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles'; import type { TDetectedRecipientSchema } from '@documenso/lib/server-only/ai/envelope/detect-recipients/schema'; import { Alert, AlertDescription } from '@documenso/ui/primitives/alert'; import { AvatarWithText } from '@documenso/ui/primitives/avatar'; import { Button } from '@documenso/ui/primitives/button'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Plural, Trans } from '@lingui/react/macro'; import { CheckIcon, ShieldCheckIcon, UserIcon, XIcon } from 'lucide-react'; import { useCallback, useEffect, useState } from 'react'; import { AiApiError, type DetectRecipientsProgressEvent, detectRecipients, } from '../../../server/api/ai/detect-recipients.client'; import { AnimatedDocumentScanner } from '../general/animated-document-scanner'; type DialogState = 'PROMPT' | 'PROCESSING' | 'REVIEW' | 'ERROR' | 'RATE_LIMITED'; type AiRecipientDetectionDialogProps = { open: boolean; onOpenChange: (open: boolean) => void; onComplete: (recipients: TDetectedRecipientSchema[]) => void; envelopeId: string; teamId: number; }; const PROCESSING_MESSAGES = [ msg`Reading your document`, msg`Analyzing pages`, msg`Looking for signature fields`, msg`Identifying recipients`, msg`Extracting contact details`, msg`Almost done`, ] as const; export const AiRecipientDetectionDialog = ({ open, onOpenChange, onComplete, envelopeId, teamId, }: AiRecipientDetectionDialogProps) => { const { _ } = useLingui(); const [state, setState] = useState('PROMPT'); const [messageIndex, setMessageIndex] = useState(0); const [detectedRecipients, setDetectedRecipients] = useState([]); const [error, setError] = useState(null); const [progress, setProgress] = useState(null); const onDetectClick = useCallback(async () => { setState('PROCESSING'); setMessageIndex(0); setError(null); setProgress(null); try { await detectRecipients({ request: { envelopeId, teamId, }, onProgress: (progressEvent) => { setProgress(progressEvent); }, onComplete: (event) => { setDetectedRecipients(event.recipients); setState('REVIEW'); }, onError: (err) => { console.error('Detection failed:', err); if (err.status === 429) { setState('RATE_LIMITED'); return; } setError(err.message); setState('ERROR'); }, }); } catch (err) { console.error('Detection failed:', err); if (err instanceof AiApiError && err.status === 429) { setState('RATE_LIMITED'); return; } setError(err instanceof Error ? err.message : 'Failed to detect recipients'); setState('ERROR'); } }, [envelopeId, teamId]); const handleRemoveRecipient = (index: number) => { setDetectedRecipients((prev) => prev.filter((_, i) => i !== index)); }; const onAddRecipients = () => { onComplete(detectedRecipients); onOpenChange(false); setState('PROMPT'); setDetectedRecipients([]); }; const onClose = () => { onOpenChange(false); setState('PROMPT'); setDetectedRecipients([]); setError(null); setProgress(null); }; useEffect(() => { if (state !== 'PROCESSING') { return; } const interval = setInterval(() => { setMessageIndex((prev) => (prev + 1) % PROCESSING_MESSAGES.length); }, 4000); return () => clearInterval(interval); }, [state]); return ( {state === 'PROMPT' && ( <> Detect recipients

We'll scan your document to find signature fields and identify who needs to sign. Detected recipients will be suggested for you to review.

Your document is processed securely using AI services that don't retain your data.
)} {state === 'PROCESSING' && ( <> Detecting recipients

{_(PROCESSING_MESSAGES[messageIndex])}

{progress && (

Page {progress.pagesProcessed} of {progress.totalPages} - # recipient found } other={ Page {progress.pagesProcessed} of {progress.totalPages} - # recipients found } />

)}

This can take a minute or two depending on the size of your document.

{PROCESSING_MESSAGES.map((_, index) => (
))}
)} {state === 'REVIEW' && ( <> Detected recipients
{detectedRecipients.length === 0 ? (

No recipients were detected in your document.

You can add recipients manually in the editor.

) : ( <>

    {detectedRecipients.map((recipient, index) => (
  • {recipient.name || _(msg`Unknown name`)}

    } secondaryText={

    {recipient.email || _(msg`No email detected`)}

    {_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}

    } />
  • ))}
)}
{detectedRecipients.length > 0 && ( )} )} {state === 'ERROR' && ( <> Detection failed

Something went wrong while detecting recipients.

{error &&

{error}

}
)} {state === 'RATE_LIMITED' && ( <> Too many requests

You've made too many detection requests. Please wait a minute before trying again.

)}
); };