import React, { useCallback, useMemo, useState } from 'react';
import { ArrowLeft, PanelRight } from 'lucide-react';
import { ProposalSession } from '../api/types';
import { useProposalChat } from '../hooks/useProposalChat';
import { ChatMessageList } from './ChatMessageList';
import { ChatInput } from './ChatInput';
import { ElicitationDialog } from './ElicitationDialog';
import { ProposalChangesList } from './ProposalChangesList';
import { ProposalActions } from './ProposalActions';
import { ChangesDrawer } from './ChangesDrawer';
interface ProposalChatProps {
projectId: string;
sessionId: string;
onBack: () => void;
onMerge: () => void;
onClose: () => void;
onClickChange?: (changeId: string) => void;
isLoading?: boolean;
}
function statusPlaceholder(status: 'ready' | 'submitted' | 'streaming' | 'recovering' | 'error', wsConnected: boolean): string {
if (status === 'recovering') {
return 'Reconnecting to recover active turn...';
}
if (!wsConnected) {
return 'Disconnected. Message will be queued and sent on reconnect.';
}
switch (status) {
case 'submitted':
return 'Message submitted. Waiting for agent response...';
case 'streaming':
return 'Agent is responding...';
case 'error':
return 'Last turn failed. Adjust your message and retry.';
case 'ready':
default:
return 'Type a message... (Shift+Enter for newline, Enter to send)';
}
}
export function ProposalChat({
projectId,
sessionId,
onBack,
onMerge,
onClose,
onClickChange,
isLoading = false,
}: ProposalChatProps) {
const [isChangesDrawerOpen, setIsChangesDrawerOpen] = useState(false);
const {
messages,
status,
sendMessage,
retryMessage,
stop,
error,
activeElicitation,
sendElicitationResponse,
wsConnected,
submissionLock,
} = useProposalChat(projectId, sessionId);
const handleExamplePromptSelect = useCallback(
(content: string) => {
sendMessage(content);
},
[sendMessage],
);
const activeSession: ProposalSession = useMemo(
() => ({
id: sessionId,
project_id: projectId,
status: 'active',
worktree_branch: sessionId,
is_dirty: false,
uncommitted_files: [],
created_at: '',
updated_at: '',
}),
[projectId, sessionId],
);
return (
<div className="flex flex-1 flex-col overflow-hidden">
<div className="flex items-center justify-between border-b border-border px-3 py-2">
<div className="flex items-center gap-2">
<button
onClick={onBack}
className="rounded p-1 text-text-subtle transition-colors hover:text-text-muted"
aria-label="Back to project"
>
<ArrowLeft className="size-4" />
</button>
<div className="flex items-center gap-1.5">
<span className="text-sm font-medium text-text">Proposal Session</span>
<span className="rounded bg-border px-1.5 py-0.5 font-mono text-xs text-text-muted">
{sessionId}
</span>
<span
className={`size-2 rounded-full ${wsConnected ? 'bg-success' : 'bg-text-subtle'}`}
title={wsConnected ? 'Connected' : 'Disconnected'}
/>
</div>
</div>
<div className="flex items-center gap-2">
<button
type="button"
className="rounded p-1 text-[#52525b] transition-colors hover:text-[#a1a1aa] md:hidden"
aria-label="Open changes drawer"
onClick={() => {
setIsChangesDrawerOpen(true);
}}
>
<PanelRight className="size-4" />
</button>
<ProposalActions session={activeSession} onMerge={onMerge} onClose={onClose} isLoading={isLoading} />
</div>
</div>
{error && (
<div className="border-b border-red-900/60 bg-red-950/40 px-3 py-2 text-xs text-red-300">
{error}
</div>
)}
<div className="flex flex-1 overflow-hidden">
<div className="flex flex-1 flex-col overflow-hidden">
<ChatMessageList
messages={messages}
isAgentResponding={status === 'submitted' || status === 'streaming' || status === 'recovering'}
onExamplePromptSelect={handleExamplePromptSelect}
onRetryMessage={retryMessage}
/>
<ChatInput
onSend={sendMessage}
isSubmissionLocked={submissionLock.isLocked}
placeholder={statusPlaceholder(status, wsConnected)}
clearVersion={submissionLock.clearVersion}
/>
{(status === 'submitted' || status === 'streaming' || status === 'recovering') && (
<div className="border-t border-border px-3 py-2 text-xs text-text-subtle">
<button
type="button"
className="rounded border border-border px-2 py-1 text-text-muted hover:text-text"
onClick={stop}
>
Stop generation
</button>
</div>
)}
</div>
<div className="hidden w-56 shrink-0 flex-col border-l border-border md:flex">
<ProposalChangesList
projectId={projectId}
sessionId={sessionId}
onClickChange={onClickChange}
/>
</div>
</div>
<ChangesDrawer
isOpen={isChangesDrawerOpen}
onClose={() => {
setIsChangesDrawerOpen(false);
}}
title="Changes"
>
<ProposalChangesList
projectId={projectId}
sessionId={sessionId}
onClickChange={(changeId) => {
onClickChange?.(changeId);
setIsChangesDrawerOpen(false);
}}
/>
</ChangesDrawer>
{activeElicitation && (
<ElicitationDialog
elicitation={activeElicitation}
onSubmit={(data) => sendElicitationResponse(activeElicitation.id, 'accept', data)}
onDecline={() => sendElicitationResponse(activeElicitation.id, 'decline')}
onCancel={() => sendElicitationResponse(activeElicitation.id, 'cancel')}
/>
)}
</div>
);
}