|
|
|
@@ -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" });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------
|
|
|
|
|