From b4613ac30c707ea2ec50d86dc2813362ad43551e Mon Sep 17 00:00:00 2001 From: serversdwn Date: Sun, 21 Dec 2025 15:50:52 -0500 Subject: [PATCH] sessions improved, v0.7.0 --- core/relay/server.js | 184 +++++++++++++++++- core/relay/sessions/sess-6rxu7eia.meta.json | 3 + core/relay/sessions/sess-dnm44wyb.json | 18 ++ core/relay/sessions/sess-dnm44wyb.meta.json | 1 + core/relay/sessions/sess-l08ndm60.meta.json | 1 + core/ui/index.html | 198 ++++++++++++++++---- core/ui/style.css | 60 ++++++ 7 files changed, 424 insertions(+), 41 deletions(-) create mode 100644 core/relay/sessions/sess-6rxu7eia.meta.json create mode 100644 core/relay/sessions/sess-dnm44wyb.json create mode 100644 core/relay/sessions/sess-dnm44wyb.meta.json create mode 100644 core/relay/sessions/sess-l08ndm60.meta.json diff --git a/core/relay/server.js b/core/relay/server.js index 273d2a6..1008cad 100644 --- a/core/relay/server.js +++ b/core/relay/server.js @@ -4,9 +4,17 @@ import express from "express"; import dotenv from "dotenv"; import cors from "cors"; +import fs from "fs/promises"; +import path from "path"; +import { fileURLToPath } from "url"; 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(); app.use(cors()); app.use(express.json()); @@ -173,20 +181,182 @@ app.post("/chat", async (req, res) => { // ----------------------------------------------------- // SESSION ENDPOINTS (for UI) // ----------------------------------------------------- -// In-memory session storage (could be replaced with a database) -const sessions = new Map(); +// Helper functions for session persistence +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 history = sessions.get(sessionId) || []; + const history = await loadSession(sessionId); 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 history = req.body; - sessions.set(sessionId, history); - res.json({ ok: true, saved: history.length }); + const success = await saveSession(sessionId, history); + + 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" }); + } }); // ----------------------------------------------------- diff --git a/core/relay/sessions/sess-6rxu7eia.meta.json b/core/relay/sessions/sess-6rxu7eia.meta.json new file mode 100644 index 0000000..486dee3 --- /dev/null +++ b/core/relay/sessions/sess-6rxu7eia.meta.json @@ -0,0 +1,3 @@ +{ + "name": "My Coding Session" +} \ No newline at end of file diff --git a/core/relay/sessions/sess-dnm44wyb.json b/core/relay/sessions/sess-dnm44wyb.json new file mode 100644 index 0000000..9d8a32e --- /dev/null +++ b/core/relay/sessions/sess-dnm44wyb.json @@ -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?" + } +] \ No newline at end of file diff --git a/core/relay/sessions/sess-dnm44wyb.meta.json b/core/relay/sessions/sess-dnm44wyb.meta.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/core/relay/sessions/sess-dnm44wyb.meta.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/core/relay/sessions/sess-l08ndm60.meta.json b/core/relay/sessions/sess-l08ndm60.meta.json new file mode 100644 index 0000000..4305934 --- /dev/null +++ b/core/relay/sessions/sess-l08ndm60.meta.json @@ -0,0 +1 @@ +{"name":"Session 2"} diff --git a/core/ui/index.html b/core/ui/index.html index 8ffc51f..0eb44bb 100644 --- a/core/ui/index.html +++ b/core/ui/index.html @@ -81,6 +81,14 @@ + +
+

Session Management

+

Manage your saved chat sessions:

+
+

Loading sessions...

+
+