import { useEffect, useRef, useState } from "react"; import { useStore } from "../store/useStore"; import { ChevronDown, LokiMark, SendIcon } from "../components/Icon"; import { ToolCard } from "../components/ToolCard"; import { MessageContent } from "../components/MessageContent"; import { ProjectChip } from "../components/ProjectChip"; import { ModelSelector } from "../components/ModelSelector"; import type { Message, ToolCall } from "../api/client"; /** Panneau central : barre de contexte, fil de conversation, composer. */ export function ChatPanel() { const { selectedModel, messages, streaming, streamingSessionId, streamContent, streamThinking, streamStatus, streamNotice, streamTools, streamPlan, streamPlanDone, sendMessage, currentSessionId, config, pendingShell, approveShell, rejectShell, sessions, stopStreaming, } = useStore(); const activeTools = config ? Object.values(config.tools).filter(Boolean).length : 0; const [draft, setDraft] = useState(""); const scrollRef = useRef(null); // Auto-scroll vers le bas à chaque token / message. useEffect(() => { scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight }); }, [messages, streamContent, streamThinking, streamTools, streamPlan, streamPlanDone]); const submit = () => { if (!draft.trim() || streaming) return; sendMessage(draft); setDraft(""); }; const onKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submit(); } }; const showingStreaming = streaming && currentSessionId === streamingSessionId; const workingSession = sessions.find((s) => s.id === streamingSessionId); const empty = messages.length === 0 && !showingStreaming; return (
{/* Barre de contexte */}
⚑ Invite système {activeTools} outil{activeTools > 1 ? "s" : ""} Temp{" "} {config ? config.temperature.toFixed(1) : "—"}
{currentSessionId ? `${messages.length} message${messages.length > 1 ? "s" : ""}` : "aucune session"}
{/* Messages */}
{empty ? ( setDraft(text)} /> ) : (
{messages.map((m) => ( ))} {showingStreaming && ( )} {/* La validation shell persiste APRÈS la fin du flux : l'agent termine son tour en attendant l'utilisateur, donc le streaming s'arrête — la carte doit rester tant qu'on n'a pas tranché. */} {pendingShell && ( )}
)}
{/* Composer */}