use client;
import useState useEffect from react;
import useRouter useSearchParams from next/navigation;
import MessageSquare ChevronLeft from lucide-react;
import type
AnalyzeRFPContentOutput
GenerateClientProfileOutput
DraftProposalSectionsOutput
GenerateComplianceMatrixOutput
ChatMessage
KnowledgeItem
ProposalType
AttachedDocument
from @/lib/types;
import Button from @/components/ui/button;
import RfpInputForm from @/components/rfp-input-form;
import ResultsDisplay from @/components/results-display;
import ChatPanel from @/components/chat-panel;
import Logo from @/components/logo;
import useToast from @/hooks/use-toast;
import MOCKKBCONTENT from @/lib/mock-data;
import AttachedDocuments from @/components/attached-documents;
//Types
type LoadingStates
analysis: boolean;
capture: boolean;
outline: boolean;
draft: boolean;
compliance: boolean;
;
//Retry helper
async function retry(fn: () > Promise retries 3 delay 1000): Promise
let lastError: Error undefined;
for (let i 0; i < retries; i)
try
return await fn();
catch (error: unknown)
lastError error as Error;
if ((503))
(Attempt $i 1 failed with 503. Retrying in $delayms...);
await new Promise(res > setTimeout(res delay));
else
throw lastError;
throw lastError;
//Component
interface Props
proposalId: string;
export default function ProposalPageClient( proposalId : Readonly)
const router useRouter();
const searchParams useSearchParams();
const name (name) Unnamed Proposal;
const isNew (new) true;
const mode (mode) edit; // default edit
const isViewOnly mode view;
// State
const proposalName setProposalName useState();
const rfpContent setRfpContent useState();
const isRfpSubmitted setIsRfpSubmitted useState(false);
const loading setLoading useState(
analysis: false
capture: false
outline: false
draft: false
compliance: false
);
const knowledgeBase useState(MOCKKBCONTENT);
const relevantKb setRelevantKb useState();
const analysisResult setAnalysisResult useState(null);
const captureResult setCaptureResult useState(null);
const proposalDraft setProposalDraft useState(null);
const complianceMatrix setComplianceMatrix useState(null);
const chatMessages setChatMessages useState();
const isChatOpen setIsChatOpen useState(false);
const isDocsModalOpen setIsDocsModalOpen useState(false);
const attachedDocs setAttachedDocs useState();
const toast useToast();
//Load proposal
useEffect(() >
const fetchProposal async () >
if (isNew)
setProposalName(New Proposal);
setIsRfpSubmitted(false);
setAttachedDocs();
setRfpContent();
else
try
const apiUrl ;
if (!apiUrl) throw new Error(API URL not defined);
const res await fetch($apiUrl/api/proposals/$proposalId);
if (!) throw new Error(Failed to fetch proposal);
const data await ();
setProposalName( Proposal $name);
setRfpContent( );
setIsRfpSubmitted(true);
// Map backend files to state
if (() && > 0)
const files: AttachedDocument ((f: any) > (
id:
name:
type:
textContent:
size: undefined
));
setAttachedDocs(files);
else
setAttachedDocs();
// Knowledge base setup
const filteredKb (
item > General
);
const kbContent (item > ).join(nn);
setRelevantKb(kbContent);
catch (err: any)
toast(
variant: destructive
title: Error fetching proposal
description: Something went wrong
);
;
fetchProposal();
proposalId isNew name knowledgeBase toast);
//Listen for analysisUpdated
useEffect(() >
const handleAnalysisUpdate (event: Event) >
const customEvent event as CustomEvent;
if ()
setAnalysisResult(); // update analysis directly
;
(analysisUpdated handleAnalysisUpdate);
return () >
(analysisUpdated handleAnalysisUpdate);
;
);
//Submit RFP
const handleRfpSubmit (
content: string
name: string
type: ProposalType
initialDocs: AttachedDocument
) >
setRfpContent(content);
setProposalName(name);
if (initialDocs) setAttachedDocs(initialDocs);
const filteredKb (item > General type);
const kbContent (item > ).join(nn);
setRelevantKb(kbContent);
setIsRfpSubmitted(true);
triggerAnalysisAndCapture(content kbContent);
;
//Run analysis
const triggerAnalysisAndCapture async (content: string kbContent: string) >
setLoading( analysis: true capture: true compliance: true outline: true draft: false );
setAnalysisResult(null);
setCaptureResult(null);
setComplianceMatrix(null);
setProposalDraft(null);
setChatMessages();
try
const analysis capture compliance await (
retry(() > analyzeRFPContent( rfpContent: content knowledgeBaseContent: kbContent ))
retry(() > generateClientProfile( rfpContent: content knowledgeBaseContent: kbContent ))
retry(() > generateComplianceMatrix( rfpContent: content knowledgeBaseContent: kbContent ))
);
setAnalysisResult(analysis);
setCaptureResult(capture);
setComplianceMatrix(compliance);
catch (error)
(Error during initial analysis: error);
toast(
variant: destructive
title: Analysis Failed
description: There was an error processing the RFP content. Please try again.
);
finally
setLoading(prev > ( ...prev analysis: false capture: false compliance: false outline: false ));
;
//Generate draft
const handleGenerateDraft async (proposalOutline: string) >
setLoading(prev > ( ...prev draft: true ));
setProposalDraft(null);
try
const draft await retry(() >
draftProposalSections(
rfpContent
proposalOutline
knowledgeBaseContent: relevantKb
)
);
setProposalDraft(draft);
catch (error)
(Error generating proposal draft: error);
toast(
variant: destructive
title: Draft Generation Failed
description: Could not generate the proposal draft. Please try again.
);
finally
setLoading(prev > ( ...prev draft: false ));
;
//Render
return (
/* Header */
(/) classNameh-8 w-8>
proposalName
isRfpSubmitted && (
setIsDocsModalOpen(true)>
Attached Documents
setIsChatOpen(true)>
Chat with RFP
)
/* Main content */
!isRfpSubmitted (
) : (
/* Attached Documents Modal */
isDocsModalOpen && (
setIsDocsModalOpen(false)
classNameabsolute top-3 right-3 text-gray-500 hover:text-gray-700
>
)
)
/* Chat */
);