import { useState, useCallback, useRef, useEffect } from "react"; import { chatWithFile, type BodyElement, type LlmModel } from "../../lib/api"; interface CliInputProps { fileId: string; onUpdate: (body: BodyElement[], summary: string | null) => void; } interface Message { id: string; type: "user" | "assistant" | "error"; content: string; toolCalls?: { name: string; success: boolean; message: string }[]; } const MODEL_OPTIONS: { value: LlmModel; label: string }[] = [ { value: "claude-opus", label: "Claude Opus" }, { value: "claude-sonnet", label: "Claude Sonnet" }, { value: "groq", label: "Groq Kimi" }, ]; export function CliInput({ fileId, onUpdate }: CliInputProps) { const [input, setInput] = useState(""); const [loading, setLoading] = useState(false); const [messages, setMessages] = useState([]); const [expanded, setExpanded] = useState(false); const [model, setModel] = useState("claude-opus"); const inputRef = useRef(null); const messagesRef = useRef(null); // Auto-scroll to bottom when messages change useEffect(() => { if (messagesRef.current) { messagesRef.current.scrollTop = messagesRef.current.scrollHeight; } }, [messages]); const handleSubmit = useCallback( async (e: React.FormEvent) => { e.preventDefault(); if (!input.trim() || loading) return; const userMessage = input.trim(); setInput(""); setExpanded(true); // Add user message const userMsgId = Date.now().toString(); setMessages((prev) => [ ...prev, { id: userMsgId, type: "user", content: userMessage }, ]); setLoading(true); try { const response = await chatWithFile(fileId, userMessage, model); // Add assistant response const assistantMsgId = (Date.now() + 1).toString(); setMessages((prev) => [ ...prev, { id: assistantMsgId, type: "assistant", content: response.response, toolCalls: response.toolCalls.map((tc) => ({ name: tc.name, success: tc.result.success, message: tc.result.message, })), }, ]); // Update parent with new body/summary onUpdate(response.updatedBody, response.updatedSummary); } catch (err) { const errorMsgId = (Date.now() + 1).toString(); setMessages((prev) => [ ...prev, { id: errorMsgId, type: "error", content: err instanceof Error ? err.message : "An error occurred", }, ]); } finally { setLoading(false); inputRef.current?.focus(); } }, [input, loading, fileId, model, onUpdate] ); const clearMessages = useCallback(() => { setMessages([]); }, []); return (
{/* Messages Panel (expandable) */} {expanded && messages.length > 0 && (
{messages.map((msg) => (
{msg.type === "user" && (
> {msg.content}
)} {msg.type === "assistant" && (
{msg.content}
{msg.toolCalls && msg.toolCalls.length > 0 && (
{msg.toolCalls.map((tc, i) => (
{tc.success ? "+" : "x"} {" "} {tc.name}: {tc.message}
))}
)}
)} {msg.type === "error" && (
{msg.content}
)}
))}
)} {/* Input Bar */}
> setInput(e.target.value)} placeholder={loading ? "Processing..." : "Add a heading, chart, or summary..."} disabled={loading} className="flex-1 bg-transparent border-none outline-none font-mono text-sm text-white placeholder-[#555]" /> {messages.length > 0 && ( )}
); }