oonclusin remark fallback added

This commit is contained in:
laxmanhalaki 2026-01-12 15:05:49 +05:30
parent e8caafa7a1
commit fc46f32282
5 changed files with 143 additions and 25 deletions

View File

@ -197,6 +197,9 @@ function CustomRequestDetailInner({ requestId: propRequestId, onBack, dynamicReq
aiGenerated, aiGenerated,
handleGenerateConclusion, handleGenerateConclusion,
handleFinalizeConclusion, handleFinalizeConclusion,
generationAttempts,
generationFailed,
maxAttemptsReached,
} = useConclusionRemark(request, requestIdentifier, isInitiator, refreshDetails, onBack, setActionStatus, setShowActionStatusModal); } = useConclusionRemark(request, requestIdentifier, isInitiator, refreshDetails, onBack, setActionStatus, setShowActionStatusModal);
// Load system policy on mount // Load system policy on mount
@ -510,6 +513,9 @@ function CustomRequestDetailInner({ requestId: propRequestId, onBack, dynamicReq
currentUserIsApprover={!!currentApprovalLevel} currentUserIsApprover={!!currentApprovalLevel}
pausedByUserId={request?.pauseInfo?.pausedBy?.userId} pausedByUserId={request?.pauseInfo?.pausedBy?.userId}
currentUserId={(user as any)?.userId} currentUserId={(user as any)?.userId}
generationAttempts={generationAttempts}
generationFailed={generationFailed}
maxAttemptsReached={maxAttemptsReached}
/> />
</TabsContent> </TabsContent>

View File

@ -41,6 +41,9 @@ interface ClaimManagementOverviewTabProps {
aiGenerated?: boolean; aiGenerated?: boolean;
handleGenerateConclusion?: () => void; handleGenerateConclusion?: () => void;
handleFinalizeConclusion?: () => void; handleFinalizeConclusion?: () => void;
generationAttempts?: number;
generationFailed?: boolean;
maxAttemptsReached?: boolean;
} }
export function ClaimManagementOverviewTab({ export function ClaimManagementOverviewTab({
@ -58,6 +61,9 @@ export function ClaimManagementOverviewTab({
aiGenerated = false, aiGenerated = false,
handleGenerateConclusion, handleGenerateConclusion,
handleFinalizeConclusion, handleFinalizeConclusion,
generationAttempts = 0,
generationFailed = false,
maxAttemptsReached = false,
}: ClaimManagementOverviewTabProps) { }: ClaimManagementOverviewTabProps) {
// Check if this is a claim management request // Check if this is a claim management request
if (!isClaimManagementRequest(apiRequest)) { if (!isClaimManagementRequest(apiRequest)) {
@ -182,17 +188,24 @@ export function ClaimManagementOverviewTab({
</CardDescription> </CardDescription>
</div> </div>
{handleGenerateConclusion && ( {handleGenerateConclusion && (
<Button <div className="flex flex-col items-end gap-1.5">
variant="outline" <Button
size="sm" variant="outline"
onClick={handleGenerateConclusion} size="sm"
disabled={conclusionLoading} onClick={handleGenerateConclusion}
className="gap-2 shrink-0" disabled={conclusionLoading || maxAttemptsReached}
data-testid="generate-ai-conclusion-button" className="gap-2 shrink-0 h-9"
> data-testid="generate-ai-conclusion-button"
<RefreshCw className={`w-3.5 h-3.5 ${conclusionLoading ? 'animate-spin' : ''}`} /> >
{aiGenerated ? 'Regenerate' : 'Generate with AI'} <RefreshCw className={`w-3.5 h-3.5 ${conclusionLoading ? 'animate-spin' : ''}`} />
</Button> {aiGenerated ? 'Regenerate' : 'Generate with AI'}
</Button>
{aiGenerated && !maxAttemptsReached && !generationFailed && (
<span className="text-[10px] text-gray-500 font-medium px-1">
{2 - generationAttempts} attempts remaining
</span>
)}
</div>
)} )}
</div> </div>
</CardHeader> </CardHeader>

View File

@ -230,6 +230,9 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
aiGenerated, aiGenerated,
handleGenerateConclusion, handleGenerateConclusion,
handleFinalizeConclusion, handleFinalizeConclusion,
generationAttempts,
generationFailed,
maxAttemptsReached,
} = useConclusionRemark( } = useConclusionRemark(
request, request,
requestIdentifier, requestIdentifier,
@ -587,6 +590,9 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
aiGenerated={aiGenerated} aiGenerated={aiGenerated}
handleGenerateConclusion={handleGenerateConclusion} handleGenerateConclusion={handleGenerateConclusion}
handleFinalizeConclusion={handleFinalizeConclusion} handleFinalizeConclusion={handleFinalizeConclusion}
generationAttempts={generationAttempts}
generationFailed={generationFailed}
maxAttemptsReached={maxAttemptsReached}
/> />
</TabsContent> </TabsContent>

View File

@ -42,6 +42,18 @@ export function useConclusionRemark(
// State: Tracks if current conclusion was AI-generated (shows badge in UI) // State: Tracks if current conclusion was AI-generated (shows badge in UI)
const [aiGenerated, setAiGenerated] = useState(false); const [aiGenerated, setAiGenerated] = useState(false);
// State: Tracks number of AI generation attempts
const [generationAttempts, setGenerationAttempts] = useState(0);
// State: Tracks if AI generation failed (unable to generate)
const [generationFailed, setGenerationFailed] = useState(false);
// State: Tracks if max attempts (3 for success, 1 for fail) reached
const [maxAttemptsReached, setMaxAttemptsReached] = useState(false);
// State: Tracks number of AI generation failures
const [failureAttempts, setFailureAttempts] = useState(0);
/** /**
* Function: fetchExistingConclusion * Function: fetchExistingConclusion
* *
@ -113,8 +125,12 @@ export function useConclusionRemark(
* 5. Handle errors silently (user can type manually) * 5. Handle errors silently (user can type manually)
*/ */
const handleGenerateConclusion = async () => { const handleGenerateConclusion = async () => {
// Safety check: Prevent generation if max attempts already reached
if (maxAttemptsReached) return;
try { try {
setConclusionLoading(true); setConclusionLoading(true);
setGenerationFailed(false);
// Lazy load: Import conclusion API // Lazy load: Import conclusion API
const { generateConclusion } = await import('@/services/conclusionApi'); const { generateConclusion } = await import('@/services/conclusionApi');
@ -122,14 +138,74 @@ export function useConclusionRemark(
// API Call: Generate AI conclusion based on request data // API Call: Generate AI conclusion based on request data
const result = await generateConclusion(request.requestId || requestIdentifier); const result = await generateConclusion(request.requestId || requestIdentifier);
const newAttempts = generationAttempts + 1;
setGenerationAttempts(newAttempts);
// Check for "unable to generate" or similar keywords in proper response
const isUnableToGenerate = !result?.aiGeneratedRemark ||
result.aiGeneratedRemark.toLowerCase().includes('unable to generate') ||
result.aiGeneratedRemark.toLowerCase().includes('sorry');
if (isUnableToGenerate) {
const newFailures = failureAttempts + 1;
setFailureAttempts(newFailures);
if (newFailures >= 2) {
setMaxAttemptsReached(true);
setActionStatus?.({
success: false,
title: 'AI Generation Limit Reached',
message: "We're unable to process a conclusion remark at this time after 2 attempts. Please proceed with a manual approach using the editor below."
});
} else {
setActionStatus?.({
success: false,
title: 'System Note',
message: "We're unable to process a conclusion remark at the moment. You have one more attempt remaining, or you can proceed manually."
});
}
setShowActionStatusModal?.(true);
setConclusionRemark(result?.aiGeneratedRemark || '');
setAiGenerated(false);
return;
}
// Success: Load AI-generated remark // Success: Load AI-generated remark
setConclusionRemark(result.aiGeneratedRemark); setConclusionRemark(result.aiGeneratedRemark);
setAiGenerated(true); setAiGenerated(true);
setFailureAttempts(0); // Reset failures on success
// Limit to 2 successful attempts
if (newAttempts >= 2) {
setMaxAttemptsReached(true);
setActionStatus?.({
success: true,
title: 'Maximum Attempts Reached',
message: "You've reached the maximum of 2 regeneration attempts. Feel free to manually edit the current suggestion to fit your specific needs."
});
setShowActionStatusModal?.(true);
}
} catch (err) { } catch (err) {
// Fail silently: User can write conclusion manually
console.error('[useConclusionRemark] AI generation failed:', err); console.error('[useConclusionRemark] AI generation failed:', err);
setConclusionRemark(''); const newFailures = failureAttempts + 1;
setFailureAttempts(newFailures);
setAiGenerated(false); setAiGenerated(false);
if (newFailures >= 2) {
setMaxAttemptsReached(true);
setActionStatus?.({
success: false,
title: 'System Note',
message: "We're unable to process your request at the moment. Since the maximum of 2 attempts is reached, please proceed with a manual approach."
});
} else {
setActionStatus?.({
success: false,
title: 'System Note',
message: "We're unable to process your request at the moment. You have one more attempt remaining, or you can proceed manually."
});
}
setShowActionStatusModal?.(true);
} finally { } finally {
setConclusionLoading(false); setConclusionLoading(false);
} }
@ -276,7 +352,10 @@ export function useConclusionRemark(
conclusionSubmitting, conclusionSubmitting,
aiGenerated, aiGenerated,
handleGenerateConclusion, handleGenerateConclusion,
handleFinalizeConclusion handleFinalizeConclusion,
generationAttempts,
generationFailed,
maxAttemptsReached
}; };
} }

View File

@ -32,6 +32,9 @@ interface OverviewTabProps {
currentUserIsApprover?: boolean; currentUserIsApprover?: boolean;
pausedByUserId?: string; pausedByUserId?: string;
currentUserId?: string; currentUserId?: string;
generationAttempts?: number;
generationFailed?: boolean;
maxAttemptsReached?: boolean;
} }
export function OverviewTab({ export function OverviewTab({
@ -51,6 +54,9 @@ export function OverviewTab({
currentUserIsApprover = false, currentUserIsApprover = false,
pausedByUserId: _pausedByUserId, pausedByUserId: _pausedByUserId,
currentUserId: _currentUserId, currentUserId: _currentUserId,
generationAttempts = 0,
generationFailed = false,
maxAttemptsReached = false,
}: OverviewTabProps) { }: OverviewTabProps) {
void _onPause; // Marked as intentionally unused - available for future use void _onPause; // Marked as intentionally unused - available for future use
const { user } = useAuth(); const { user } = useAuth();
@ -64,6 +70,7 @@ export function OverviewTab({
// Retrigger: Only for initiator when approver paused (initiator asks approver to resume) // Retrigger: Only for initiator when approver paused (initiator asks approver to resume)
const canRetrigger = isPaused && isInitiator && pausedByUserId && pausedByUserId !== currentUserId && onRetrigger; const canRetrigger = isPaused && isInitiator && pausedByUserId && pausedByUserId !== currentUserId && onRetrigger;
return ( return (
<div className="space-y-4 sm:space-y-6" data-testid="overview-tab-content"> <div className="space-y-4 sm:space-y-6" data-testid="overview-tab-content">
{/* Request Initiator Card */} {/* Request Initiator Card */}
@ -345,17 +352,24 @@ export function OverviewTab({
: 'All approvals are complete. Please review and finalize the conclusion to close this request.'} : 'All approvals are complete. Please review and finalize the conclusion to close this request.'}
</CardDescription> </CardDescription>
</div> </div>
<Button <div className="flex flex-col items-end gap-1.5">
variant="outline" <Button
size="sm" variant="outline"
onClick={handleGenerateConclusion} size="sm"
disabled={conclusionLoading} onClick={handleGenerateConclusion}
className="gap-2 shrink-0" disabled={conclusionLoading || maxAttemptsReached}
data-testid="generate-ai-conclusion-button" className="gap-2 shrink-0 h-9"
> data-testid="generate-ai-conclusion-button"
<RefreshCw className={`w-3.5 h-3.5 ${conclusionLoading ? 'animate-spin' : ''}`} /> >
{aiGenerated ? 'Regenerate' : 'Generate with AI'} <RefreshCw className={`w-3.5 h-3.5 ${conclusionLoading ? 'animate-spin' : ''}`} />
</Button> {aiGenerated ? 'Regenerate' : 'Generate with AI'}
</Button>
{aiGenerated && !maxAttemptsReached && !generationFailed && (
<span className="text-[10px] text-gray-500 font-medium px-1">
{2 - generationAttempts} attempts remaining
</span>
)}
</div>
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="pt-4"> <CardContent className="pt-4">