Fix scrolling problem
This commit is contained in:
+64
-33
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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({
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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...
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user