Fix scrolling problem

This commit is contained in:
Samuele E. Locatelli
2025-09-03 13:47:51 +00:00
parent 37c788dbf2
commit afddf8d0db
6 changed files with 103 additions and 58 deletions
+64 -33
View File
@@ -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;
}
+1
View File
@@ -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();
-2
View File
@@ -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(/<think>([\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
+22 -21
View File
@@ -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 (
<>
<div className={`sessions-wrapper d-flex flex-column vh-100 ${theme.bodyBg}`}>
<div className={`chat-layout sessions-wrapper ${theme.bodyBg}`}>
{/* HEADER */}
<ChatHeader
theme={theme}
onToggleTheme={onToggleTheme}
@@ -59,24 +57,25 @@ export default function ChatLayout({
onFreshStart={onFreshStart}
onCreateSession={onCreateSession}
onEditSession={onEditSession}
onManageSession={openSessionManager} // opens panel
sessionId={sessionId} // ID sessione
sessionName={sessionName} // nome sessione
/>
onManageSession={openSessionManager}
sessionId={sessionId}
sessionName={sessionName}
/>
<div className="flex-grow-1 overflow-auto">
{/* 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" onClick={onStop}>
Stop Generating
</button>
</div>
)}
</div>
{loading && (
<div className="p-2 text-center">
<button className="btn btn-warning btn-sm" onClick={onStop}>
Stop Generating
</button>
</div>
)}
{/* Se non c'è sessionId, mostra il box per crearne una */}
{/* INPUT SEMPRE IN BASSO */}
{sessionId ? (
<ChatInput onSend={onSend} onStop={onStop} loading={loading} />
) : (
@@ -84,6 +83,7 @@ export default function ChatLayout({
)}
</div>
{/* PANEL SESSIONI */}
<div
className={`offcanvas offcanvas-start ${showSessionsPanel ? "show" : ""}`}
tabIndex="-1"
@@ -94,7 +94,7 @@ export default function ChatLayout({
<button
type="button"
className="btn-close text-reset"
onClick={closeSessionManager} // closes panel
onClick={closeSessionManager}
></button>
</div>
<div className="offcanvas-body">
@@ -109,3 +109,4 @@ export default function ChatLayout({
);
}
+16 -1
View File
@@ -11,7 +11,7 @@ export default function ChatWindow({ messages, loading, theme }) {
}, [messages]);
return (
<main className={`flex-grow-1 overflow-auto p-3 ${theme.bodyBg}`}>
<main className={`flex-1 overflow-y-auto p-3 ${theme.bodyBg}`}>
{messages.map((msg, idx) =>
msg.role === "user" ? (
<UserMessage
@@ -34,6 +34,21 @@ export default function ChatWindow({ messages, loading, theme }) {
/>
)
)}
{loading && (
<div
className="thinking-block text-muted small fst-italic"
style={{
maxHeight: "150px",
overflowY: "auto",
padding: "0.5rem",
border: "1px solid #ddd",
borderRadius: "4px"
}}
>
The model is processing...
</div>
)}
{loading && (
<div className="text-muted small fst-italic">
The model is processing...
-1
View File
@@ -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';