// Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // Project: chat.spboucher.ai "use client"; import { useRef, useState } from "react"; import { estimateTokensClient, formatTokens, perMillion, providerGlyph, type ApiModel } from "./types"; interface ComposerProps { model: ApiModel | null; contextTokens: number; streaming: boolean; onSend: (content: string) => void; onStop: () => void; onOpenModelSheet: () => void; } export function Composer({ model, contextTokens, streaming, onSend, onStop, onOpenModelSheet }: ComposerProps) { const [text, setText] = useState(""); const taRef = useRef(null); const totalTokens = contextTokens + estimateTokensClient(text); const ctxLimit = model?.contextLength; const ctxPct = ctxLimit ? Math.min((totalTokens / ctxLimit) * 100, 100) : 0; const send = () => { const content = text.trim(); if (!content || streaming || !model) return; setText(""); if (taRef.current) taRef.current.style.height = "auto"; onSend(content); }; const onKeyDown = (e: React.KeyboardEvent) => { // Enter sends on desktop; on touch keyboards Enter makes a newline. if (e.key === "Enter" && !e.shiftKey && !isTouchDevice()) { e.preventDefault(); send(); } }; const autoGrow = () => { const ta = taRef.current; if (!ta) return; ta.style.height = "auto"; ta.style.height = `${Math.min(ta.scrollHeight, window.innerHeight * 0.4)}px`; }; return (
{/* Model Rail — the machined cartridge. Tap to open the model sheet. */}