diff --git a/frontend/src/App.css b/frontend/src/App.css index f08a45a..9df215b 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,9 +1,13 @@ /* src/App.css */ + +/* ========================= + RESET & BASE + ========================= */ html, body, #root { height: 100%; margin: 0; padding: 0; - overflow: hidden; + overflow: hidden; /* lo scroll è gestito dal main della chat */ } body { @@ -11,26 +15,19 @@ body { font-family: sans-serif; } -/* Animate only the wrapper, not the whole page */ +/* ========================= + SESSIONS WRAPPER & OFFCANVAS + ========================= */ .sessions-wrapper { transition: transform 300ms ease, filter 300ms ease; will-change: transform, filter; } + body.sessions-open .sessions-wrapper { transform: translateX(280px) scale(0.985); filter: saturate(0.95); } -/* Offcanvas stays fixed to the viewport */ -.offcanvas-start { - width: 280px; - border-right: 1px solid #dee2e6; -} -.offcanvas.show { - box-shadow: 6px 0 20px rgba(0, 0, 0, 0.08); -} - -/* Dark overlay for sessions panel */ .sessions-overlay { position: fixed; inset: 0; @@ -38,31 +35,19 @@ body.sessions-open .sessions-wrapper { opacity: 0; pointer-events: none; transition: opacity 250ms ease; - z-index: 1030; /* just under offcanvas (Bootstrap sets .offcanvas at 1045) */ + z-index: 1030; /* sotto offcanvas (Bootstrap .offcanvas = 1045) */ } + .sessions-overlay.show { opacity: 1; pointer-events: auto; } -/* Animate only wrapper */ -.sessions-wrapper { - transition: transform 300ms ease, filter 300ms ease; - will-change: transform, filter; -} -body.sessions-open .sessions-wrapper { - transform: translateX(280px) scale(0.985); - filter: saturate(0.95); -} - -.session-table { - font-size: 0.85rem; -} - .offcanvas-start { - width: 420px; + width: 280px; border-right: 1px solid #dee2e6; } + .offcanvas.show { box-shadow: 6px 0 20px rgba(0, 0, 0, 0.08); } @@ -70,8 +55,8 @@ body.sessions-open .sessions-wrapper { .offcanvas-left { position: fixed; top: 0; - left: -300px; /* panel width */ - width: 300px; + left: -280px; + width: 280px; height: 100%; background: var(--bs-body-bg, #fff); box-shadow: 2px 0 5px rgba(0,0,0,0.3); @@ -83,15 +68,32 @@ body.sessions-open .sessions-wrapper { left: 0; } +.session-table { + font-size: 0.85rem; +} -/* Chat styles */ -.chat-container { +/* ========================= + CHAT LAYOUT + ========================= */ +.chat-layout { display: flex; flex-direction: column; height: 100vh; - background-color: #f3f4f6; } +.chat-layout > .flex-grow-1 { + flex: 1; + min-height: 0; /* fondamentale per Safari iOS */ + overflow-y: auto; +} + +.chat-layout footer { + flex-shrink: 0; +} + +/* ========================= + CHAT STYLES + ========================= */ .chat-box { flex: 1; overflow-y: auto; @@ -108,9 +110,11 @@ body.sessions-open .sessions-wrapper { max-width: 95%; word-wrap: break-word; } + .message:last-child { overflow-anchor: auto; } + @media (min-width: 768px) { .message { max-width: 75%; } } @@ -122,6 +126,7 @@ body.sessions-open .sessions-wrapper { text-align: right; border-top-right-radius: 0; } + .message.assistant { background-color: #e9ecef; color: #212529; @@ -130,12 +135,16 @@ body.sessions-open .sessions-wrapper { border-top-left-radius: 0; } +/* ========================= + INPUT BAR + ========================= */ .input-bar { display: flex; padding: 1rem; background-color: white; border-top: 1px solid #ccc; } + .chat-input { flex: 1; padding: 0.75rem; @@ -143,6 +152,7 @@ body.sessions-open .sessions-wrapper { border-radius: 8px; font-size: 1rem; } + .send-button { margin-left: 0.5rem; padding: 0.75rem 1rem; @@ -153,6 +163,9 @@ body.sessions-open .sessions-wrapper { cursor: pointer; } +/* ========================= + CODE BLOCKS + ========================= */ pre { background-color: #212529; color: #f8f9fa; @@ -160,20 +173,38 @@ pre { border-radius: 0.375rem; overflow-x: auto; } + code { font-family: 'Fira Code', monospace; font-size: 0.9rem; } + .btn-copy { font-size: 0.75rem; } +/* ========================= + THINKING BLOCK (limit growth) + ========================= */ +.message.thinking { + max-height: 40vh; + overflow-y: auto; + display: flex; + flex-direction: column; +} + .think-block { opacity: 1; transition: opacity 0.6s ease; + max-height: 40vh; /* non occupa più del 40% dell'altezza viewport */ + overflow-y: auto; /* scroll interno se il contenuto è troppo */ + padding: 0.5rem; /* un po’ di respiro interno */ + box-sizing: border-box; /* padding incluso nel calcolo altezza */ + background: rgba(0,0,0,0.03); } .think-block.fade-out { opacity: 0; } + diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index ec1e1f6..6f13d8b 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5,6 +5,7 @@ import { themes } from "./themes"; import ChatLayout from "./ChatLayout"; import { useChatStream } from "./useChatStream"; import { getSessionId, getUserId, clearSessionId, setSessionId } from "./useSessionId"; +import "katex/dist/katex.min.css"; // <-- IMPORTANTE export default function App() { const { messages, loading, sendMessage, stopGenerating, setMessages } = useChatStream(); diff --git a/frontend/src/AssistantMessage.jsx b/frontend/src/AssistantMessage.jsx index 1eed295..aa32669 100644 --- a/frontend/src/AssistantMessage.jsx +++ b/frontend/src/AssistantMessage.jsx @@ -16,13 +16,11 @@ export default function AssistantMessage({ const [showThink, setShowThink] = useState(false); const [fadeOut, setFadeOut] = useState(false); - // timestamp da mostrare: priorità a timestamp salvato, poi endedAt, poi startedAt const ts = timestamp ?? endedAt ?? startedAt; const thinkMatch = content?.match(/([\s\S]*?)<\/think>/i); const thinkContent = thinkMatch ? thinkMatch[1].trim() : null; - // Consideriamo "completo" se isFinal è true oppure se il messaggio ha un timestamp salvato const isComplete = isFinal || Boolean(timestamp || endedAt); const visibleContent = isComplete diff --git a/frontend/src/ChatLayout.jsx b/frontend/src/ChatLayout.jsx index a10abb8..a8e9eb5 100644 --- a/frontend/src/ChatLayout.jsx +++ b/frontend/src/ChatLayout.jsx @@ -4,7 +4,7 @@ import ChatHeader from "./ChatHeader"; import ChatWindow from "./ChatWindow"; import ChatInput from "./ChatInput"; import SessionTable from "./SessionTable"; -import NoSessionBox from './NoSessionBox'; +import NoSessionBox from "./NoSessionBox"; export default function ChatLayout({ theme, @@ -24,17 +24,14 @@ export default function ChatLayout({ }) { const [showSessionsPanel, setShowSessionsPanel] = useState(false); - // 1️⃣ helper at the top (inside the component is fine) function getScrollbarWidth() { return window.innerWidth - document.documentElement.clientWidth; } - // update body class when panel state changes useEffect(() => { document.body.classList.toggle("sessions-open", showSessionsPanel); }, [showSessionsPanel]); - // 2️⃣ revised toggles const openSessionManager = () => { const scrollBarWidth = getScrollbarWidth(); document.body.style.overflow = "hidden"; @@ -46,12 +43,13 @@ export default function ChatLayout({ document.body.style.overflow = ""; document.body.style.paddingRight = ""; setShowSessionsPanel(false); - document.body.classList.remove("sessions-open"); // extra sicurezza + document.body.classList.remove("sessions-open"); }; return ( <> -
+
+ {/* HEADER */} + onManageSession={openSessionManager} + sessionId={sessionId} + sessionName={sessionName} + /> -
+ {/* AREA SCROLLABILE */} +
+ + {loading && ( +
+ +
+ )}
- {loading && ( -
- -
- )} - - {/* Se non c'è sessionId, mostra il box per crearne una */} + {/* INPUT SEMPRE IN BASSO */} {sessionId ? ( ) : ( @@ -84,6 +83,7 @@ export default function ChatLayout({ )}
+ {/* PANEL SESSIONI */}
@@ -109,3 +109,4 @@ export default function ChatLayout({ ); } + diff --git a/frontend/src/ChatWindow.jsx b/frontend/src/ChatWindow.jsx index cd84290..b08373a 100644 --- a/frontend/src/ChatWindow.jsx +++ b/frontend/src/ChatWindow.jsx @@ -11,7 +11,7 @@ export default function ChatWindow({ messages, loading, theme }) { }, [messages]); return ( -
+
{messages.map((msg, idx) => msg.role === "user" ? ( ) )} + {loading && ( +
+ The model is processing... +
+ )} + {loading && (
The model is processing... diff --git a/frontend/src/MessageContent.jsx b/frontend/src/MessageContent.jsx index 2f9891f..c835b52 100644 --- a/frontend/src/MessageContent.jsx +++ b/frontend/src/MessageContent.jsx @@ -4,7 +4,6 @@ import ReactMarkdown from 'react-markdown'; import remarkMath from 'remark-math'; import remarkGfm from 'remark-gfm'; import rehypeKatex from 'rehype-katex'; -import 'katex/dist/katex.min.css'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; import './MessageContent.css';