import { SendIcon, StopCircleIcon, X, ChevronDown, ChevronUp, AlertTriangle, AlertOctagon, FileText, Check, Loader2, Package, FileX, SendToBack, Database, ChevronsUpDown, ChevronsDownUp, BarChart2, } from "lucide-react"; import type React from "react"; import { useCallback, useEffect, useRef, useState } from "react"; import { ModelPicker } from "@/components/ModelPicker"; import { useSettings } from "@/hooks/useSettings"; import { IpcClient } from "@/ipc/ipc_client"; import { chatInputValueAtom, chatMessagesAtom, selectedChatIdAtom, } from "@/atoms/chatAtoms"; import { atom, useAtom, useSetAtom, useAtomValue } from "jotai"; import { useStreamChat } from "@/hooks/useStreamChat"; import { useChats } from "@/hooks/useChats"; import { selectedAppIdAtom } from "@/atoms/appAtoms"; import { useLoadApp } from "@/hooks/useLoadApp"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { useProposal } from "@/hooks/useProposal"; import { CodeProposal, ActionProposal, Proposal, SuggestedAction, ProposalResult, FileChange, SqlQuery, } from "@/lib/schemas"; import type { Message } from "@/ipc/ipc_types"; import { isPreviewOpenAtom } from "@/atoms/viewAtoms"; import { useRunApp } from "@/hooks/useRunApp"; import { AutoApproveSwitch } from "../AutoApproveSwitch"; import { usePostHog } from "posthog-js/react"; import { CodeHighlight } from "./CodeHighlight"; import { TokenBar } from "./TokenBar"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "../ui/tooltip"; import { useNavigate } from "@tanstack/react-router"; const showTokenBarAtom = atom(false); export function ChatInput({ chatId }: { chatId?: number }) { const posthog = usePostHog(); const [inputValue, setInputValue] = useAtom(chatInputValueAtom); const textareaRef = useRef(null); const { settings, updateSettings, isAnyProviderSetup } = useSettings(); const { streamMessage, isStreaming, setIsStreaming, error, setError } = useStreamChat(); const [selectedAppId] = useAtom(selectedAppIdAtom); const [showError, setShowError] = useState(true); const [isApproving, setIsApproving] = useState(false); // State for approving const [isRejecting, setIsRejecting] = useState(false); // State for rejecting const [messages, setMessages] = useAtom(chatMessagesAtom); const setIsPreviewOpen = useSetAtom(isPreviewOpenAtom); const [showTokenBar, setShowTokenBar] = useAtom(showTokenBarAtom); const { refreshAppIframe } = useRunApp(); // Use the hook to fetch the proposal const { proposalResult, isLoading: isProposalLoading, error: proposalError, refreshProposal, } = useProposal(chatId); const { proposal, chatId: proposalChatId, messageId } = proposalResult ?? {}; const adjustHeight = () => { const textarea = textareaRef.current; if (textarea) { textarea.style.height = "0px"; const scrollHeight = textarea.scrollHeight; textarea.style.height = `${scrollHeight + 4}px`; } }; useEffect(() => { adjustHeight(); }, [inputValue]); useEffect(() => { if (error) { setShowError(true); } }, [error]); const fetchChatMessages = useCallback(async () => { if (!chatId) { setMessages([]); return; } const chat = await IpcClient.getInstance().getChat(chatId); setMessages(chat.messages); }, [chatId, setMessages]); const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSubmit(); } }; const handleSubmit = async () => { if (!inputValue.trim() || isStreaming || !chatId) { return; } const currentInput = inputValue; setInputValue(""); await streamMessage({ prompt: currentInput, chatId }); posthog.capture("chat:submit"); }; const handleCancel = () => { if (chatId) { IpcClient.getInstance().cancelChatStream(chatId); } setIsStreaming(false); }; const dismissError = () => { setShowError(false); }; const handleApprove = async () => { if (!chatId || !messageId || isApproving || isRejecting || isStreaming) return; console.log( `Approving proposal for chatId: ${chatId}, messageId: ${messageId}` ); setIsApproving(true); posthog.capture("chat:approve"); try { const result = await IpcClient.getInstance().approveProposal({ chatId, messageId, }); if (result.success) { console.log("Proposal approved successfully"); // TODO: Maybe refresh proposal state or show confirmation? } else { console.error("Failed to approve proposal:", result.error); setError(result.error || "Failed to approve proposal"); } } catch (err) { console.error("Error approving proposal:", err); setError((err as Error)?.message || "An error occurred while approving"); } finally { setIsApproving(false); setIsPreviewOpen(true); refreshAppIframe(); // Keep same as handleReject refreshProposal(); fetchChatMessages(); } }; const handleReject = async () => { if (!chatId || !messageId || isApproving || isRejecting || isStreaming) return; console.log( `Rejecting proposal for chatId: ${chatId}, messageId: ${messageId}` ); setIsRejecting(true); posthog.capture("chat:reject"); try { const result = await IpcClient.getInstance().rejectProposal({ chatId, messageId, }); if (result.success) { console.log("Proposal rejected successfully"); // TODO: Maybe refresh proposal state or show confirmation? } else { console.error("Failed to reject proposal:", result.error); setError(result.error || "Failed to reject proposal"); } } catch (err) { console.error("Error rejecting proposal:", err); setError((err as Error)?.message || "An error occurred while rejecting"); } finally { setIsRejecting(false); // Keep same as handleApprove refreshProposal(); fetchChatMessages(); } }; if (!settings) { return null; // Or loading state } return ( <> {error && showError && (
{error}
)} {/* Display loading or error state for proposal */} {isProposalLoading && (
Loading proposal...
)} {proposalError && (
Error loading proposal: {proposalError}
)}
{/* Only render ChatInputActions if proposal is loaded */} {proposal && proposalResult?.chatId === chatId && ( )}