From 548a74ab8936d41b221c19a38384424b6f45617e Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Tue, 18 Nov 2025 21:53:36 +0000 Subject: [PATCH] chore: improve detection prompts --- apps/remix/app/app.css | 1 + .../recipient-detection-prompt-dialog.tsx | 95 +++++++++++-------- .../envelope/envelope-drop-zone-wrapper.tsx | 31 +++--- .../envelope/envelope-upload-button.tsx | 31 +++--- .../server/api/document-analysis/prompts.ts | 4 +- packages/tailwind-config/index.cjs | 6 ++ 6 files changed, 102 insertions(+), 66 deletions(-) diff --git a/apps/remix/app/app.css b/apps/remix/app/app.css index 9255cdb6a..be547bddb 100644 --- a/apps/remix/app/app.css +++ b/apps/remix/app/app.css @@ -69,3 +69,4 @@ --font-noto: 'Noto Sans', 'Noto Sans Korean', 'Noto Sans Japanese', 'Noto Sans Chinese'; } } + diff --git a/apps/remix/app/components/dialogs/recipient-detection-prompt-dialog.tsx b/apps/remix/app/components/dialogs/recipient-detection-prompt-dialog.tsx index 9ed0d10d9..c972f4cde 100644 --- a/apps/remix/app/components/dialogs/recipient-detection-prompt-dialog.tsx +++ b/apps/remix/app/components/dialogs/recipient-detection-prompt-dialog.tsx @@ -1,19 +1,19 @@ import { useEffect, useState } from 'react'; import { Trans } from '@lingui/react/macro'; -import { LoaderIcon } from 'lucide-react'; import { match } from 'ts-pattern'; import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out'; -import { Button } from '@documenso/ui/primitives/button'; import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@documenso/ui/primitives/dialog'; + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@documenso/ui/primitives/alert-dialog'; type RecipientDetectionStep = 'PROMPT' | 'PROCESSING'; @@ -32,14 +32,14 @@ export const RecipientDetectionPromptDialog = ({ }: RecipientDetectionPromptDialogProps) => { const [currentStep, setCurrentStep] = useState('PROMPT'); - // Reset to first step when dialog closes useEffect(() => { if (!open) { setCurrentStep('PROMPT'); } }, [open]); - const handleStartDetection = () => { + const handleStartDetection = (e: React.MouseEvent) => { + e.preventDefault(); setCurrentStep('PROCESSING'); Promise.resolve(onAccept()).catch(() => { @@ -52,54 +52,75 @@ export const RecipientDetectionPromptDialog = ({ }; return ( - - + +
- + {match(currentStep) .with('PROMPT', () => ( <> - - + + Auto-detect recipients? - - + + Would you like to automatically detect recipients in your document? This can save you time in setting up your document. - - + + - - - - + + )) .with('PROCESSING', () => ( - <> - - - +
+
+
+
+
+
+ +
+
+
+
+ +
+
+ + + Analyzing your document - - + + Scanning your document to detect recipient names, emails, and signing order. - - - + + +
)) .exhaustive()}
-
-
+ + ); }; diff --git a/apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx b/apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx index 706bd48e4..4400a2984 100644 --- a/apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx +++ b/apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx @@ -58,10 +58,10 @@ export const EnvelopeDropZoneWrapper = ({ const organisation = useCurrentOrganisation(); const [isLoading, setIsLoading] = useState(false); - const [showRecipientDetectionPrompt, setShowRecipientDetectionPrompt] = useState(false); + const [showExtractionPrompt, setShowExtractionPrompt] = useState(false); const [uploadedDocumentId, setUploadedDocumentId] = useState(null); const [pendingRecipients, setPendingRecipients] = useState(null); - const [showSuggestedRecipientsDialog, setShowSuggestedRecipientsDialog] = useState(false); + const [showRecipientsDialog, setShowRecipientsDialog] = useState(false); const [shouldNavigateAfterPromptClose, setShouldNavigateAfterPromptClose] = useState(true); const userTimezone = @@ -124,9 +124,9 @@ export const EnvelopeDropZoneWrapper = ({ // Show AI prompt dialog for documents setUploadedDocumentId(id); setPendingRecipients(null); - setShowSuggestedRecipientsDialog(false); + setShowRecipientsDialog(false); setShouldNavigateAfterPromptClose(true); - setShowRecipientDetectionPrompt(true); + setShowExtractionPrompt(true); } else { // Templates - navigate immediately const pathPrefix = formatTemplatesPath(team.url); @@ -242,15 +242,18 @@ export const EnvelopeDropZoneWrapper = ({ duration: 5000, }); - throw new Error('NO_RECIPIENTS_DETECTED'); + setShouldNavigateAfterPromptClose(true); + setShowExtractionPrompt(false); + navigateToEnvelopeEditor(); + return; } const recipientsWithEmails = ensureRecipientEmails(recipients, uploadedDocumentId); setPendingRecipients(recipientsWithEmails); setShouldNavigateAfterPromptClose(false); - setShowRecipientDetectionPrompt(false); - setShowSuggestedRecipientsDialog(true); + setShowExtractionPrompt(false); + setShowRecipientsDialog(true); } catch (error) { if (!(error instanceof Error && error.message === 'NO_RECIPIENTS_DETECTED')) { const parsedError = AppError.parseError(error); @@ -269,12 +272,12 @@ export const EnvelopeDropZoneWrapper = ({ const handleSkipRecipientDetection = () => { setShouldNavigateAfterPromptClose(true); - setShowRecipientDetectionPrompt(false); + setShowExtractionPrompt(false); navigateToEnvelopeEditor(); }; const handleRecipientsCancel = () => { - setShowSuggestedRecipientsDialog(false); + setShowRecipientsDialog(false); setPendingRecipients(null); navigateToEnvelopeEditor(); }; @@ -296,7 +299,7 @@ export const EnvelopeDropZoneWrapper = ({ duration: 5000, }); - setShowSuggestedRecipientsDialog(false); + setShowRecipientsDialog(false); setPendingRecipients(null); navigateToEnvelopeEditor(); } catch (error) { @@ -314,7 +317,7 @@ export const EnvelopeDropZoneWrapper = ({ }; const handlePromptDialogOpenChange = (open: boolean) => { - setShowRecipientDetectionPrompt(open); + setShowExtractionPrompt(open); if (open) { setShouldNavigateAfterPromptClose(true); @@ -394,20 +397,20 @@ export const EnvelopeDropZoneWrapper = ({ )} { if (!open) { handleRecipientsCancel(); } else { - setShowSuggestedRecipientsDialog(true); + setShowRecipientsDialog(true); } }} onCancel={handleRecipientsCancel} diff --git a/apps/remix/app/components/general/envelope/envelope-upload-button.tsx b/apps/remix/app/components/general/envelope/envelope-upload-button.tsx index 1bfcd3576..2a829ec85 100644 --- a/apps/remix/app/components/general/envelope/envelope-upload-button.tsx +++ b/apps/remix/app/components/general/envelope/envelope-upload-button.tsx @@ -62,10 +62,10 @@ export const EnvelopeUploadButton = ({ className, type, folderId }: EnvelopeUplo const { quota, remaining, refreshLimits, maximumEnvelopeItemCount } = useLimits(); const [isLoading, setIsLoading] = useState(false); - const [showRecipientDetectionPrompt, setShowAiPromptDialog] = useState(false); + const [showExtractionPrompt, setShowExtractionPrompt] = useState(false); const [uploadedDocumentId, setUploadedDocumentId] = useState(null); const [pendingRecipients, setPendingRecipients] = useState(null); - const [showSuggestedRecipientsDialog, setShowAiRecipientsDialog] = useState(false); + const [showRecipientsDialog, setShowRecipientsDialog] = useState(false); const [shouldNavigateAfterPromptClose, setShouldNavigateAfterPromptClose] = useState(true); const { mutateAsync: createEnvelope } = trpc.envelope.create.useMutation(); @@ -125,9 +125,9 @@ export const EnvelopeUploadButton = ({ className, type, folderId }: EnvelopeUplo if (type === EnvelopeType.DOCUMENT) { setUploadedDocumentId(id); setPendingRecipients(null); - setShowAiRecipientsDialog(false); + setShowRecipientsDialog(false); setShouldNavigateAfterPromptClose(true); - setShowAiPromptDialog(true); + setShowExtractionPrompt(true); toast({ title: t`Document uploaded`, @@ -219,15 +219,18 @@ export const EnvelopeUploadButton = ({ className, type, folderId }: EnvelopeUplo duration: 5000, }); - throw new Error('NO_RECIPIENTS_DETECTED'); + setShouldNavigateAfterPromptClose(true); + setShowExtractionPrompt(false); + navigateToEnvelopeEditor(); + return; } const recipientsWithEmails = ensureRecipientEmails(recipients, uploadedDocumentId); setPendingRecipients(recipientsWithEmails); setShouldNavigateAfterPromptClose(false); - setShowAiPromptDialog(false); - setShowAiRecipientsDialog(true); + setShowExtractionPrompt(false); + setShowRecipientsDialog(true); } catch (err) { if (!(err instanceof Error && err.message === 'NO_RECIPIENTS_DETECTED')) { const error = AppError.parseError(err); @@ -246,12 +249,12 @@ export const EnvelopeUploadButton = ({ className, type, folderId }: EnvelopeUplo const handleSkipRecipientDetection = () => { setShouldNavigateAfterPromptClose(true); - setShowAiPromptDialog(false); + setShowExtractionPrompt(false); navigateToEnvelopeEditor(); }; const handleRecipientsCancel = () => { - setShowAiRecipientsDialog(false); + setShowRecipientsDialog(false); setPendingRecipients(null); navigateToEnvelopeEditor(); }; @@ -273,7 +276,7 @@ export const EnvelopeUploadButton = ({ className, type, folderId }: EnvelopeUplo duration: 5000, }); - setShowAiRecipientsDialog(false); + setShowRecipientsDialog(false); setPendingRecipients(null); navigateToEnvelopeEditor(); } catch (err) { @@ -291,7 +294,7 @@ export const EnvelopeUploadButton = ({ className, type, folderId }: EnvelopeUplo }; const handlePromptDialogOpenChange = (open: boolean) => { - setShowAiPromptDialog(open); + setShowExtractionPrompt(open); if (open) { setShouldNavigateAfterPromptClose(true); @@ -337,20 +340,20 @@ export const EnvelopeUploadButton = ({ className, type, folderId }: EnvelopeUplo { if (!open) { handleRecipientsCancel(); } else { - setShowAiRecipientsDialog(true); + setShowRecipientsDialog(true); } }} onCancel={handleRecipientsCancel} diff --git a/apps/remix/server/api/document-analysis/prompts.ts b/apps/remix/server/api/document-analysis/prompts.ts index 38dcf32b6..27287ded9 100644 --- a/apps/remix/server/api/document-analysis/prompts.ts +++ b/apps/remix/server/api/document-analysis/prompts.ts @@ -106,6 +106,7 @@ IMPORTANT: - If no clear ordering, omit signingOrder - Return empty array if absolutely no recipients can be detected - Do NOT invent recipients - only extract what's clearly present +- If a signature line exists but no name is associated with it, DO NOT return a recipient with name "". Skip it. EXAMPLES: Good: @@ -116,4 +117,5 @@ Good: Bad: - Extracting the document title as a recipient name - Making up email addresses that aren't in the document - - Adding people not mentioned in the document`; + - Adding people not mentioned in the document + - Using placeholder names like "", "Unknown", "Signer"`; diff --git a/packages/tailwind-config/index.cjs b/packages/tailwind-config/index.cjs index 1170b6ac5..46ebfc6e4 100644 --- a/packages/tailwind-config/index.cjs +++ b/packages/tailwind-config/index.cjs @@ -142,11 +142,17 @@ module.exports = { '0%,70%,100%': { opacity: '1' }, '20%,50%': { opacity: '0' }, }, + scan: { + '0%': { top: '0%' }, + '50%': { top: '95%' }, + '100%': { top: '0%' }, + }, }, animation: { 'accordion-down': 'accordion-down 0.2s ease-out', 'accordion-up': 'accordion-up 0.2s ease-out', 'caret-blink': 'caret-blink 1.25s ease-out infinite', + scan: 'scan 4s linear infinite', }, screens: { '3xl': '1920px',