Files
chat-proxy/frontend/src/ChatLayout.jsx
T
2025-09-04 10:32:05 +00:00

163 lines
4.7 KiB
React

// ChatLayout.jsx
import React, { useState, useEffect, useRef } from "react";
import ChatHeader from "./ChatHeader";
import ChatWindow from "./ChatWindow";
import ChatInput from "./ChatInput";
import SessionTable from "./SessionTable";
import NoSessionBox from "./NoSessionBox";
// ⏱ Tempo massimo configurabile (in millisecondi)
const MAX_EXECUTION_TIME_MS = 3 * 60 * 1000; // 3 minuti
export default function ChatLayout({
theme,
messages,
loading,
onSend,
onStop,
onToggleTheme,
onReloadHistory,
onFreshStart,
onCreateSession,
onEditSession,
onSelectSession,
userId,
sessionId,
sessionName
}) {
const [showSessionsPanel, setShowSessionsPanel] = useState(false);
const [timeLeft, setTimeLeft] = useState(null); // in secondi
const stopTimerRef = useRef(null);
const countdownIntervalRef = useRef(null);
// Gestione auto-stop e countdown
useEffect(() => {
if (loading) {
// Imposta il tempo rimanente
setTimeLeft(Math.floor(MAX_EXECUTION_TIME_MS / 1000));
// Avvia il timer di stop automatico
stopTimerRef.current = setTimeout(() => {
console.warn("⏱ Tempo massimo raggiunto, stop automatico");
onStop();
}, MAX_EXECUTION_TIME_MS);
// Avvia il countdown visivo
countdownIntervalRef.current = setInterval(() => {
setTimeLeft(prev => {
if (prev === null) return null;
if (prev <= 1) {
clearInterval(countdownIntervalRef.current);
return 0;
}
return prev - 1;
});
}, 1000);
} else {
// Reset se non sta più caricando
clearTimeout(stopTimerRef.current);
clearInterval(countdownIntervalRef.current);
stopTimerRef.current = null;
countdownIntervalRef.current = null;
setTimeLeft(null);
}
// Pulizia in caso di smontaggio
return () => {
clearTimeout(stopTimerRef.current);
clearInterval(countdownIntervalRef.current);
};
}, [loading, onStop]);
function getScrollbarWidth() {
return window.innerWidth - document.documentElement.clientWidth;
}
useEffect(() => {
document.body.classList.toggle("sessions-open", showSessionsPanel);
}, [showSessionsPanel]);
const openSessionManager = () => {
const scrollBarWidth = getScrollbarWidth();
document.body.style.overflow = "hidden";
document.body.style.paddingRight = `${scrollBarWidth}px`;
setShowSessionsPanel(true);
};
const closeSessionManager = () => {
document.body.style.overflow = "";
document.body.style.paddingRight = "";
setShowSessionsPanel(false);
document.body.classList.remove("sessions-open");
};
return (
<>
<div className={`chat-layout sessions-wrapper ${theme.bodyBg}`}>
{/* HEADER */}
<ChatHeader
theme={theme}
onToggleTheme={onToggleTheme}
onReloadHistory={onReloadHistory}
onFreshStart={onFreshStart}
onCreateSession={onCreateSession}
onEditSession={onEditSession}
onManageSession={openSessionManager}
sessionId={sessionId}
sessionName={sessionName}
/>
{/* AREA SCROLLABILE */}
<div className="flex-grow-1 overflow-auto" style={{ minHeight: 0 }}>
<ChatWindow messages={messages} loading={loading} theme={theme} />
{loading && (
<div className="p-2 text-center">
<button className="btn btn-warning btn-sm me-2" onClick={onStop}>
Stop Generating
</button>
{timeLeft !== null && (
<span className="text-muted small">
Auto-stop in {timeLeft}s
</span>
)}
</div>
)}
</div>
{/* INPUT SEMPRE IN BASSO */}
{sessionId ? (
<ChatInput onSend={onSend} onStop={onStop} loading={loading} />
) : (
<NoSessionBox onCreateSession={onCreateSession} />
)}
</div>
{/* PANEL SESSIONI */}
<div
className={`offcanvas offcanvas-start ${showSessionsPanel ? "show" : ""}`}
tabIndex="-1"
style={{ visibility: showSessionsPanel ? "visible" : "hidden" }}
>
<div className="offcanvas-header">
<h5 className="offcanvas-title">Manage Sessions</h5>
<button
type="button"
className="btn-close text-reset"
onClick={closeSessionManager}
></button>
</div>
<div className="offcanvas-body">
<SessionTable
userId={userId}
onSelectSession={onSelectSession}
onClosePanel={closeSessionManager}
/>
</div>
</div>
</>
);
}