import { useState, useCallback, useRef, useEffect } from "react";
import { chatWithFile, type BodyElement, type LlmModel } from "../../lib/api";
import { SimpleMarkdown } from "../SimpleMarkdown";
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<Message[]>([]);
const [expanded, setExpanded] = useState(false);
const [model, setModel] = useState<LlmModel>("claude-opus");
const inputRef = useRef<HTMLInputElement>(null);
const messagesRef = useRef<HTMLDivElement>(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 (
<div className="border-t border-[rgba(117,170,252,0.35)] bg-[#0d1b2d]">
{/* Messages Panel (expandable) */}
{expanded && messages.length > 0 && (
<div
ref={messagesRef}
className="max-h-48 overflow-y-auto p-3 space-y-2 border-b border-[rgba(117,170,252,0.2)]"
>
{messages.map((msg) => (
<div key={msg.id} className="font-mono text-xs">
{msg.type === "user" && (
<div className="flex gap-2">
<span className="text-[#9bc3ff]">></span>
<span className="text-white/80">{msg.content}</span>
</div>
)}
{msg.type === "assistant" && (
<div className="pl-4 space-y-1">
<SimpleMarkdown content={msg.content} className="text-[#75aafc]" />
{msg.toolCalls && msg.toolCalls.length > 0 && (
<div className="text-[#555] text-[10px] space-y-0.5">
{msg.toolCalls.map((tc, i) => (
<div key={i}>
<span
className={
tc.success ? "text-green-500" : "text-red-400"
}
>
{tc.success ? "+" : "x"}
</span>{" "}
{tc.name}: {tc.message}
</div>
))}
</div>
)}
</div>
)}
{msg.type === "error" && (
<div className="pl-4 text-red-400">{msg.content}</div>
)}
</div>
))}
</div>
)}
{/* Input Bar */}
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-3">
<select
value={model}
onChange={(e) => setModel(e.target.value as LlmModel)}
disabled={loading}
className="bg-[#0d1b2d] border border-[rgba(117,170,252,0.25)] text-[#9bc3ff] font-mono text-xs px-2 py-1 rounded-none outline-none focus:border-[#3f6fb3] disabled:opacity-50"
>
{MODEL_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
<span className="text-[#9bc3ff] font-mono text-sm">></span>
<input
ref={inputRef}
type="text"
value={input}
onChange={(e) => 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 && (
<button
type="button"
onClick={clearMessages}
className="text-[#555] hover:text-[#9bc3ff] font-mono text-xs transition-colors"
>
clear
</button>
)}
<button
type="submit"
disabled={loading || !input.trim()}
className="px-3 py-1 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] disabled:opacity-50 disabled:cursor-not-allowed transition-colors uppercase"
>
{loading ? "..." : "Send"}
</button>
</form>
</div>
);
}