sessions improved, v0.7.0
This commit is contained in:
@@ -4,9 +4,17 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
|
import fs from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
|
// ES module __dirname workaround
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const SESSIONS_DIR = path.join(__dirname, "sessions");
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
@@ -173,20 +181,182 @@ app.post("/chat", async (req, res) => {
|
|||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
// SESSION ENDPOINTS (for UI)
|
// SESSION ENDPOINTS (for UI)
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
// In-memory session storage (could be replaced with a database)
|
// Helper functions for session persistence
|
||||||
const sessions = new Map();
|
async function ensureSessionsDir() {
|
||||||
|
try {
|
||||||
|
await fs.mkdir(SESSIONS_DIR, { recursive: true });
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to create sessions directory:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
app.get("/sessions/:id", (req, res) => {
|
async function loadSession(sessionId) {
|
||||||
|
try {
|
||||||
|
const sessionPath = path.join(SESSIONS_DIR, `${sessionId}.json`);
|
||||||
|
const data = await fs.readFile(sessionPath, "utf-8");
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (err) {
|
||||||
|
// File doesn't exist or is invalid - return empty array
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveSession(sessionId, history, metadata = {}) {
|
||||||
|
try {
|
||||||
|
await ensureSessionsDir();
|
||||||
|
const sessionPath = path.join(SESSIONS_DIR, `${sessionId}.json`);
|
||||||
|
const metadataPath = path.join(SESSIONS_DIR, `${sessionId}.meta.json`);
|
||||||
|
|
||||||
|
// Save history
|
||||||
|
await fs.writeFile(sessionPath, JSON.stringify(history, null, 2), "utf-8");
|
||||||
|
|
||||||
|
// Save metadata (name, etc.)
|
||||||
|
await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to save session ${sessionId}:`, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSessionMetadata(sessionId) {
|
||||||
|
try {
|
||||||
|
const metadataPath = path.join(SESSIONS_DIR, `${sessionId}.meta.json`);
|
||||||
|
const data = await fs.readFile(metadataPath, "utf-8");
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (err) {
|
||||||
|
// No metadata file, return default
|
||||||
|
return { name: sessionId };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveSessionMetadata(sessionId, metadata) {
|
||||||
|
try {
|
||||||
|
await ensureSessionsDir();
|
||||||
|
const metadataPath = path.join(SESSIONS_DIR, `${sessionId}.meta.json`);
|
||||||
|
await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to save metadata for ${sessionId}:`, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listSessions() {
|
||||||
|
try {
|
||||||
|
await ensureSessionsDir();
|
||||||
|
const files = await fs.readdir(SESSIONS_DIR);
|
||||||
|
const sessions = [];
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (file.endsWith(".json") && !file.endsWith(".meta.json")) {
|
||||||
|
const sessionId = file.replace(".json", "");
|
||||||
|
const sessionPath = path.join(SESSIONS_DIR, file);
|
||||||
|
const stats = await fs.stat(sessionPath);
|
||||||
|
|
||||||
|
// Try to read the session to get message count
|
||||||
|
let messageCount = 0;
|
||||||
|
try {
|
||||||
|
const data = await fs.readFile(sessionPath, "utf-8");
|
||||||
|
const history = JSON.parse(data);
|
||||||
|
messageCount = history.length;
|
||||||
|
} catch (e) {
|
||||||
|
// Invalid JSON, skip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load metadata (name)
|
||||||
|
const metadata = await loadSessionMetadata(sessionId);
|
||||||
|
|
||||||
|
sessions.push({
|
||||||
|
id: sessionId,
|
||||||
|
name: metadata.name || sessionId,
|
||||||
|
lastModified: stats.mtime,
|
||||||
|
messageCount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by last modified (newest first)
|
||||||
|
sessions.sort((a, b) => b.lastModified - a.lastModified);
|
||||||
|
return sessions;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to list sessions:", err);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteSession(sessionId) {
|
||||||
|
try {
|
||||||
|
const sessionPath = path.join(SESSIONS_DIR, `${sessionId}.json`);
|
||||||
|
const metadataPath = path.join(SESSIONS_DIR, `${sessionId}.meta.json`);
|
||||||
|
|
||||||
|
// Delete session file
|
||||||
|
await fs.unlink(sessionPath);
|
||||||
|
|
||||||
|
// Delete metadata file (if exists)
|
||||||
|
try {
|
||||||
|
await fs.unlink(metadataPath);
|
||||||
|
} catch (e) {
|
||||||
|
// Metadata file doesn't exist, that's ok
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to delete session ${sessionId}:`, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /sessions - List all sessions
|
||||||
|
app.get("/sessions", async (req, res) => {
|
||||||
|
const sessions = await listSessions();
|
||||||
|
res.json(sessions);
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /sessions/:id - Get specific session history
|
||||||
|
app.get("/sessions/:id", async (req, res) => {
|
||||||
const sessionId = req.params.id;
|
const sessionId = req.params.id;
|
||||||
const history = sessions.get(sessionId) || [];
|
const history = await loadSession(sessionId);
|
||||||
res.json(history);
|
res.json(history);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/sessions/:id", (req, res) => {
|
// POST /sessions/:id - Save session history
|
||||||
|
app.post("/sessions/:id", async (req, res) => {
|
||||||
const sessionId = req.params.id;
|
const sessionId = req.params.id;
|
||||||
const history = req.body;
|
const history = req.body;
|
||||||
sessions.set(sessionId, history);
|
const success = await saveSession(sessionId, history);
|
||||||
res.json({ ok: true, saved: history.length });
|
|
||||||
|
if (success) {
|
||||||
|
res.json({ ok: true, saved: history.length });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ error: "Failed to save session" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// PATCH /sessions/:id/metadata - Update session metadata (name, etc.)
|
||||||
|
app.patch("/sessions/:id/metadata", async (req, res) => {
|
||||||
|
const sessionId = req.params.id;
|
||||||
|
const metadata = req.body;
|
||||||
|
const success = await saveSessionMetadata(sessionId, metadata);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
res.json({ ok: true, metadata });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ error: "Failed to update metadata" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// DELETE /sessions/:id - Delete a session
|
||||||
|
app.delete("/sessions/:id", async (req, res) => {
|
||||||
|
const sessionId = req.params.id;
|
||||||
|
const success = await deleteSession(sessionId);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
res.json({ ok: true, deleted: sessionId });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ error: "Failed to delete session" });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
|
|||||||
3
core/relay/sessions/sess-6rxu7eia.meta.json
Normal file
3
core/relay/sessions/sess-6rxu7eia.meta.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"name": "My Coding Session"
|
||||||
|
}
|
||||||
18
core/relay/sessions/sess-dnm44wyb.json
Normal file
18
core/relay/sessions/sess-dnm44wyb.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "Hello! this is a new test session. Do you know who i am?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Hi! In this session, I don't have information about your previous interactions. You can tell me who you are or any other details you'd like to share. How can I assist you today?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "sure im brian! i am designing you... you are a robot!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Hello Brian! Nice to meet you. As an AI, I don't have physical design capabilities, but I'm here to help with any information or tasks you need. How can I assist you in your design process?"
|
||||||
|
}
|
||||||
|
]
|
||||||
1
core/relay/sessions/sess-dnm44wyb.meta.json
Normal file
1
core/relay/sessions/sess-dnm44wyb.meta.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
1
core/relay/sessions/sess-l08ndm60.meta.json
Normal file
1
core/relay/sessions/sess-l08ndm60.meta.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"Session 2"}
|
||||||
@@ -81,6 +81,14 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-section" style="margin-top: 24px;">
|
||||||
|
<h4>Session Management</h4>
|
||||||
|
<p class="settings-desc">Manage your saved chat sessions:</p>
|
||||||
|
<div id="sessionList" class="session-list">
|
||||||
|
<p style="color: var(--text-fade); font-size: 0.85rem;">Loading sessions...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button id="saveSettingsBtn" class="primary-btn">Save</button>
|
<button id="saveSettingsBtn" class="primary-btn">Save</button>
|
||||||
@@ -99,21 +107,28 @@
|
|||||||
|
|
||||||
let history = [];
|
let history = [];
|
||||||
let currentSession = localStorage.getItem("currentSession") || null;
|
let currentSession = localStorage.getItem("currentSession") || null;
|
||||||
let sessions = JSON.parse(localStorage.getItem("sessions") || "[]");
|
let sessions = []; // Now loaded from server
|
||||||
|
|
||||||
function saveSessions() {
|
async function loadSessionsFromServer() {
|
||||||
localStorage.setItem("sessions", JSON.stringify(sessions));
|
try {
|
||||||
localStorage.setItem("currentSession", currentSession);
|
const resp = await fetch(`${RELAY_BASE}/sessions`);
|
||||||
|
const serverSessions = await resp.json();
|
||||||
|
sessions = serverSessions;
|
||||||
|
return sessions;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to load sessions from server:", e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSessions() {
|
async function renderSessions() {
|
||||||
const select = document.getElementById("sessions");
|
const select = document.getElementById("sessions");
|
||||||
select.innerHTML = "";
|
select.innerHTML = "";
|
||||||
|
|
||||||
sessions.forEach(s => {
|
sessions.forEach(s => {
|
||||||
const opt = document.createElement("option");
|
const opt = document.createElement("option");
|
||||||
opt.value = s.id;
|
opt.value = s.id;
|
||||||
opt.textContent = s.name;
|
opt.textContent = s.name || s.id;
|
||||||
if (s.id === currentSession) opt.selected = true;
|
if (s.id === currentSession) opt.selected = true;
|
||||||
select.appendChild(opt);
|
select.appendChild(opt);
|
||||||
});
|
});
|
||||||
@@ -121,7 +136,21 @@
|
|||||||
|
|
||||||
function getSessionName(id) {
|
function getSessionName(id) {
|
||||||
const s = sessions.find(s => s.id === id);
|
const s = sessions.find(s => s.id === id);
|
||||||
return s ? s.name : id;
|
return s ? (s.name || s.id) : id;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveSessionMetadata(sessionId, name) {
|
||||||
|
try {
|
||||||
|
await fetch(`${RELAY_BASE}/sessions/${sessionId}/metadata`, {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ name })
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to save session metadata:", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSession(id) {
|
async function loadSession(id) {
|
||||||
@@ -258,55 +287,75 @@
|
|||||||
localStorage.setItem("theme", isDark ? "dark" : "light");
|
localStorage.setItem("theme", isDark ? "dark" : "light");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sessions
|
// Sessions - Load from server
|
||||||
// Populate dropdown initially
|
(async () => {
|
||||||
renderSessions();
|
await loadSessionsFromServer();
|
||||||
// Ensure we have at least one session
|
await renderSessions();
|
||||||
if (!currentSession) {
|
|
||||||
const id = generateSessionId();
|
|
||||||
const name = "default";
|
|
||||||
sessions.push({ id, name });
|
|
||||||
currentSession = id;
|
|
||||||
saveSessions();
|
|
||||||
renderSessions();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load current session history (if it exists on Relay)
|
// Ensure we have at least one session
|
||||||
loadSession(currentSession);
|
if (sessions.length === 0) {
|
||||||
|
const id = generateSessionId();
|
||||||
|
const name = "default";
|
||||||
|
currentSession = id;
|
||||||
|
history = [];
|
||||||
|
await saveSession(); // Create empty session on server
|
||||||
|
await saveSessionMetadata(id, name);
|
||||||
|
await loadSessionsFromServer();
|
||||||
|
await renderSessions();
|
||||||
|
localStorage.setItem("currentSession", currentSession);
|
||||||
|
} else {
|
||||||
|
// If no current session or current session doesn't exist, use first one
|
||||||
|
if (!currentSession || !sessions.find(s => s.id === currentSession)) {
|
||||||
|
currentSession = sessions[0].id;
|
||||||
|
localStorage.setItem("currentSession", currentSession);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load current session history
|
||||||
|
if (currentSession) {
|
||||||
|
await loadSession(currentSession);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
// Switch session
|
// Switch session
|
||||||
document.getElementById("sessions").addEventListener("change", async e => {
|
document.getElementById("sessions").addEventListener("change", async e => {
|
||||||
currentSession = e.target.value;
|
currentSession = e.target.value;
|
||||||
history = [];
|
history = [];
|
||||||
saveSessions();
|
localStorage.setItem("currentSession", currentSession);
|
||||||
addMessage("system", `Switched to session: ${getSessionName(currentSession)}`);
|
addMessage("system", `Switched to session: ${getSessionName(currentSession)}`);
|
||||||
await loadSession(currentSession); // ✅ load the chat history from Relay
|
await loadSession(currentSession);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Create new session
|
// Create new session
|
||||||
document.getElementById("newSessionBtn").addEventListener("click", () => {
|
document.getElementById("newSessionBtn").addEventListener("click", async () => {
|
||||||
const name = prompt("Enter new session name:");
|
const name = prompt("Enter new session name:");
|
||||||
if (!name) return;
|
if (!name) return;
|
||||||
const id = generateSessionId();
|
const id = generateSessionId();
|
||||||
sessions.push({ id, name });
|
|
||||||
currentSession = id;
|
currentSession = id;
|
||||||
history = [];
|
history = [];
|
||||||
saveSessions();
|
localStorage.setItem("currentSession", currentSession);
|
||||||
renderSessions();
|
|
||||||
|
// Create session on server
|
||||||
|
await saveSession();
|
||||||
|
await saveSessionMetadata(id, name);
|
||||||
|
await loadSessionsFromServer();
|
||||||
|
await renderSessions();
|
||||||
|
|
||||||
addMessage("system", `Created session: ${name}`);
|
addMessage("system", `Created session: ${name}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Rename session
|
// Rename session
|
||||||
document.getElementById("renameSessionBtn").addEventListener("click", () => {
|
document.getElementById("renameSessionBtn").addEventListener("click", async () => {
|
||||||
const session = sessions.find(s => s.id === currentSession);
|
const session = sessions.find(s => s.id === currentSession);
|
||||||
if (!session) return;
|
if (!session) return;
|
||||||
const newName = prompt("Rename session:", session.name);
|
const newName = prompt("Rename session:", session.name || currentSession);
|
||||||
if (!newName) return;
|
if (!newName) return;
|
||||||
session.name = newName;
|
|
||||||
saveSessions();
|
// Update metadata on server
|
||||||
renderSessions();
|
await saveSessionMetadata(currentSession, newName);
|
||||||
|
await loadSessionsFromServer();
|
||||||
|
await renderSessions();
|
||||||
|
|
||||||
addMessage("system", `Session renamed to: ${newName}`);
|
addMessage("system", `Session renamed to: ${newName}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -335,9 +384,90 @@
|
|||||||
document.querySelector(`input[name="backend"][value="${savedBackend}"]`).checked = true;
|
document.querySelector(`input[name="backend"][value="${savedBackend}"]`).checked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show modal
|
// Session management functions
|
||||||
|
async function loadSessionList() {
|
||||||
|
try {
|
||||||
|
// Reload from server to get latest
|
||||||
|
await loadSessionsFromServer();
|
||||||
|
|
||||||
|
const sessionListEl = document.getElementById("sessionList");
|
||||||
|
if (sessions.length === 0) {
|
||||||
|
sessionListEl.innerHTML = '<p style="color: var(--text-fade); font-size: 0.85rem;">No saved sessions found</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionListEl.innerHTML = "";
|
||||||
|
sessions.forEach(sess => {
|
||||||
|
const sessionItem = document.createElement("div");
|
||||||
|
sessionItem.className = "session-item";
|
||||||
|
|
||||||
|
const sessionInfo = document.createElement("div");
|
||||||
|
sessionInfo.className = "session-info";
|
||||||
|
|
||||||
|
const sessionName = sess.name || sess.id;
|
||||||
|
const lastModified = new Date(sess.lastModified).toLocaleString();
|
||||||
|
|
||||||
|
sessionInfo.innerHTML = `
|
||||||
|
<strong>${sessionName}</strong>
|
||||||
|
<small>${sess.messageCount} messages • ${lastModified}</small>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const deleteBtn = document.createElement("button");
|
||||||
|
deleteBtn.className = "session-delete-btn";
|
||||||
|
deleteBtn.textContent = "🗑️";
|
||||||
|
deleteBtn.title = "Delete session";
|
||||||
|
deleteBtn.onclick = async () => {
|
||||||
|
if (!confirm(`Delete session "${sessionName}"?`)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetch(`${RELAY_BASE}/sessions/${sess.id}`, { method: "DELETE" });
|
||||||
|
|
||||||
|
// Reload sessions from server
|
||||||
|
await loadSessionsFromServer();
|
||||||
|
|
||||||
|
// If we deleted the current session, switch to another or create new
|
||||||
|
if (currentSession === sess.id) {
|
||||||
|
if (sessions.length > 0) {
|
||||||
|
currentSession = sessions[0].id;
|
||||||
|
localStorage.setItem("currentSession", currentSession);
|
||||||
|
history = [];
|
||||||
|
await loadSession(currentSession);
|
||||||
|
} else {
|
||||||
|
const id = generateSessionId();
|
||||||
|
const name = "default";
|
||||||
|
currentSession = id;
|
||||||
|
localStorage.setItem("currentSession", currentSession);
|
||||||
|
history = [];
|
||||||
|
await saveSession();
|
||||||
|
await saveSessionMetadata(id, name);
|
||||||
|
await loadSessionsFromServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh both the dropdown and the settings list
|
||||||
|
await renderSessions();
|
||||||
|
await loadSessionList();
|
||||||
|
|
||||||
|
addMessage("system", `Deleted session: ${sessionName}`);
|
||||||
|
} catch (e) {
|
||||||
|
alert("Failed to delete session: " + e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sessionItem.appendChild(sessionInfo);
|
||||||
|
sessionItem.appendChild(deleteBtn);
|
||||||
|
sessionListEl.appendChild(sessionItem);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
const sessionListEl = document.getElementById("sessionList");
|
||||||
|
sessionListEl.innerHTML = '<p style="color: #ff3333; font-size: 0.85rem;">Failed to load sessions</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show modal and load session list
|
||||||
settingsBtn.addEventListener("click", () => {
|
settingsBtn.addEventListener("click", () => {
|
||||||
settingsModal.classList.add("show");
|
settingsModal.classList.add("show");
|
||||||
|
loadSessionList(); // Refresh session list when opening settings
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hide modal functions
|
// Hide modal functions
|
||||||
|
|||||||
@@ -362,3 +362,63 @@ select:hover {
|
|||||||
background: #ff7a33;
|
background: #ff7a33;
|
||||||
box-shadow: var(--accent-glow);
|
box-shadow: var(--accent-glow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Session List */
|
||||||
|
.session-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid rgba(255,102,0,0.3);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(255,102,0,0.05);
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-item:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: rgba(255,102,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-info strong {
|
||||||
|
color: var(--text-main);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-info small {
|
||||||
|
color: var(--text-fade);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-delete-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid rgba(255,102,0,0.5);
|
||||||
|
color: var(--accent);
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-delete-btn:hover {
|
||||||
|
background: rgba(255,0,0,0.2);
|
||||||
|
border-color: #ff3333;
|
||||||
|
color: #ff3333;
|
||||||
|
box-shadow: 0 0 8px rgba(255,0,0,0.3);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user