Complete graphical review and session manage for vers 1

This commit is contained in:
Samuele E. Locatelli
2025-08-22 17:11:11 +00:00
parent e8a12a86d4
commit 1885f08012
5 changed files with 69 additions and 47 deletions
+15 -1
View File
@@ -67,7 +67,21 @@ body.sessions-open .sessions-wrapper {
box-shadow: 6px 0 20px rgba(0, 0, 0, 0.08);
}
/* rest of your existing chat styles unchanged... */
.offcanvas-left {
position: fixed;
top: 0;
left: -300px; /* panel width */
width: 300px;
height: 100%;
background: var(--bs-body-bg, #fff);
box-shadow: 2px 0 5px rgba(0,0,0,0.3);
transition: left 0.3s ease;
z-index: 1050;
}
.offcanvas-left.show {
left: 0;
}
/* Chat styles */
+1 -1
View File
@@ -54,7 +54,7 @@ export default function ChatHeader({
</div>
{/* Right column: theme toggle */}
<div className="col-3 text-end">
<div className="col-3 d-flex flex-row-reverse">
<button
className="btn btn-sm btn-outline-light d-flex align-items-center gap-1"
onClick={onToggleTheme}
+29 -28
View File
@@ -4,15 +4,6 @@ import ChatHeader from "./ChatHeader";
import ChatWindow from "./ChatWindow";
import ChatInput from "./ChatInput";
import SessionTable from "./SessionTable";
import { setSessionId, getUserId } from "./useSessionId";
function handleSelectSession(sessionId) {
setSessionId(sessionId); // ✅ overwrite current
const userId = getUserId();
fetch(`/v1/history?user_id=${userId}&session_id=${sessionId}`)
.then(res => res.json())
.then(history => setMessages(history));
}
export default function ChatLayout({
theme,
@@ -24,22 +15,38 @@ export default function ChatLayout({
onReloadHistory,
onFreshStart,
onCreateSession,
onEditSession,
onSelectSession,
userId
}) {
const [showOffcanvas, setShowOffcanvas] = useState(false);
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]);
const openSessionManager = () => setShowSessionsPanel(true);
const closeSessionManager = () => setShowSessionsPanel(false);
// 2️⃣ revised toggles
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);
};
return (
<>
{/* SHIFTING CONTENT */}
<div className={`sessions-wrapper d-flex flex-column vh-100 ${theme.bodyBg}`}>
<ChatHeader
theme={theme}
@@ -47,7 +54,8 @@ export default function ChatLayout({
onReloadHistory={onReloadHistory}
onFreshStart={onFreshStart}
onCreateSession={onCreateSession}
onManageSession={openSessionManager}
onEditSession={onEditSession}
onManageSession={openSessionManager} // opens panel
/>
<div className="flex-grow-1 overflow-auto">
@@ -65,13 +73,6 @@ export default function ChatLayout({
<ChatInput onSend={onSend} loading={loading} />
</div>
{/* DARK OVERLAY */}
<div
className={`sessions-overlay ${showSessionsPanel ? "show" : ""}`}
onClick={closeSessionManager}
></div>
{/* FIXED OFFCANVAS */}
<div
className={`offcanvas offcanvas-start ${showSessionsPanel ? "show" : ""}`}
tabIndex="-1"
@@ -82,15 +83,15 @@ export default function ChatLayout({
<button
type="button"
className="btn-close text-reset"
onClick={closeSessionManager}
onClick={closeSessionManager} // closes panel
></button>
</div>
<div className="offcanvas-body small">
<SessionTable
userId={userId}
onSelectSession={onSelectSession}
onClosePanel={() => setShowOffcanvas(false)}
/>
<div className="offcanvas-body">
<SessionTable
userId={userId}
onSelectSession={onSelectSession}
onClosePanel={closeSessionManager}
/>
</div>
</div>
</>
+15 -12
View File
@@ -60,10 +60,10 @@ export default function SessionTable({ userId, onSelectSession, onClosePanel })
<table className="table table-striped table-hover align-middle">
<thead>
<tr>
<th>Session Name</th>
<th></th>
<th>Session</th>
<th>Created</th>
<th>Messages</th>
<th>Size (bytes)</th>
<th className="text-nowrap"># mess</th>
<th></th>
</tr>
</thead>
@@ -77,6 +77,13 @@ export default function SessionTable({ userId, onSelectSession, onClosePanel })
key={s.session_id}
className={s.session_id === activeSessionId ? "table-primary" : ""}
>
<td className="text-end text-nowrap">
<button
className="btn btn-sm px-1 btn-outline-secondary me-1"
onClick={() => startEditing(s)}
title="Rename"
></button>
</td>
<td>
{editingSessionId === s.session_id ? (
<input
@@ -106,16 +113,12 @@ export default function SessionTable({ userId, onSelectSession, onClosePanel })
)}
</td>
<td>{new Date(s.created_at).toLocaleString()}</td>
<td>{s.message_count}</td>
<td>{s.history_size_bytes}</td>
<td className="text-end">
<td title={s.history_size_bytes}>
{s.message_count}
</td>
<td className="text-end text-nowrap">
<button
className="btn btn-sm btn-outline-secondary me-1"
onClick={() => startEditing(s)}
title="Rename"
></button>
<button
className="btn btn-sm btn-outline-danger"
className="btn btn-sm px-1 btn-outline-danger"
onClick={() => deleteSession(s.session_id)}
title="Delete"
>🗑</button>
+9 -5
View File
@@ -3,18 +3,22 @@ import React from "react";
export default function UserMessage({ content, theme }) {
return (
<div className="mb-2 text-end">
// This outer div aligns the *bubble* to the right
<div className="mb-2 d-flex justify-content-end">
<div
className={`d-inline-block p-2 rounded ${theme.userBg}`}
style={{ maxWidth: "95%" }}
className={`p-2 rounded ${theme.userBg}`}
style={{
maxWidth: "95%",
textAlign: "left", // ensures text inside is left-aligned
}}
>
<pre
className="m-0 p-0"
style={{
whiteSpace: "pre-wrap",
fontFamily: "inherit",
backgroundColor: "transparent", // kill default <pre> background
color: "inherit", // match bubble text color
backgroundColor: "transparent",
color: "inherit",
}}
>
{content}