From 6665bf4a7154255036c32bf079716f075241508c Mon Sep 17 00:00:00 2001 From: kumarabhirup Date: Sat, 21 Feb 2026 15:17:10 -0800 Subject: [PATCH] refactor: simplify web session management by removing unused functions and adding DELETE and PATCH endpoints - Removed unused functions related to session index management. - Added DELETE endpoint to remove a web chat session and its associated file. - Added PATCH endpoint to update session metadata, including renaming sessions. - Streamlined file writing for new session creation. --- apps/web/app/api/web-sessions/[id]/route.ts | 50 ++++++++++- apps/web/app/api/web-sessions/route.ts | 97 ++------------------- apps/web/app/api/web-sessions/shared.ts | 88 +++++++++++++++++++ 3 files changed, 142 insertions(+), 93 deletions(-) create mode 100644 apps/web/app/api/web-sessions/shared.ts diff --git a/apps/web/app/api/web-sessions/[id]/route.ts b/apps/web/app/api/web-sessions/[id]/route.ts index 1596f7be30d..83f98a72bb4 100644 --- a/apps/web/app/api/web-sessions/[id]/route.ts +++ b/apps/web/app/api/web-sessions/[id]/route.ts @@ -1,6 +1,7 @@ -import { readFileSync, existsSync } from "node:fs"; +import { readFileSync, existsSync, unlinkSync } from "node:fs"; import { join } from "node:path"; import { resolveWebChatDir } from "@/lib/workspace"; +import { readIndex, writeIndex } from "../shared"; export const dynamic = "force-dynamic"; @@ -44,3 +45,50 @@ export async function GET( return Response.json({ id, messages }); } + +/** DELETE /api/web-sessions/[id] — delete a web chat session */ +export async function DELETE( + _request: Request, + { params }: { params: Promise<{ id: string }> }, +) { + const { id } = await params; + + const sessions = readIndex(); + const idx = sessions.findIndex((s) => s.id === id); + if (idx === -1) { + return Response.json({ error: "Session not found" }, { status: 404 }); + } + + sessions.splice(idx, 1); + writeIndex(sessions); + + const filePath = join(resolveWebChatDir(), `${id}.jsonl`); + if (existsSync(filePath)) { + unlinkSync(filePath); + } + + return Response.json({ ok: true }); +} + +/** PATCH /api/web-sessions/[id] — update session metadata (e.g. rename) */ +export async function PATCH( + request: Request, + { params }: { params: Promise<{ id: string }> }, +) { + const { id } = await params; + const body = await request.json().catch(() => ({})); + + const sessions = readIndex(); + const session = sessions.find((s) => s.id === id); + if (!session) { + return Response.json({ error: "Session not found" }, { status: 404 }); + } + + if (typeof body.title === "string") { + session.title = body.title; + } + session.updatedAt = Date.now(); + writeIndex(sessions); + + return Response.json({ session }); +} diff --git a/apps/web/app/api/web-sessions/route.ts b/apps/web/app/api/web-sessions/route.ts index 204e81756ff..858de33ae8a 100644 --- a/apps/web/app/api/web-sessions/route.ts +++ b/apps/web/app/api/web-sessions/route.ts @@ -1,97 +1,11 @@ -import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from "node:fs"; -import { join } from "node:path"; +import { writeFileSync } from "node:fs"; import { randomUUID } from "node:crypto"; -import { resolveWebChatDir } from "@/lib/workspace"; +import { type WebSessionMeta, ensureDir, readIndex, writeIndex } from "./shared"; + +export { type WebSessionMeta }; export const dynamic = "force-dynamic"; -export type WebSessionMeta = { - id: string; - title: string; - createdAt: number; - updatedAt: number; - messageCount: number; - /** When set, this session is scoped to a specific workspace file. */ - filePath?: string; -}; - -function ensureDir() { - const dir = resolveWebChatDir(); - if (!existsSync(dir)) { - mkdirSync(dir, { recursive: true }); - } - return dir; -} - -/** - * Read the session index, auto-discovering any orphaned .jsonl files - * that aren't in the index (e.g. from profile switches or missing index). - */ -function readIndex(): WebSessionMeta[] { - const dir = ensureDir(); - const indexFile = join(dir, "index.json"); - let index: WebSessionMeta[] = []; - if (existsSync(indexFile)) { - try { - index = JSON.parse(readFileSync(indexFile, "utf-8")); - } catch { - index = []; - } - } - - // Scan for orphaned .jsonl files not in the index - try { - const indexed = new Set(index.map((s) => s.id)); - const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl")); - let dirty = false; - for (const file of files) { - const id = file.replace(/\.jsonl$/, ""); - if (indexed.has(id)) {continue;} - - // Build a minimal index entry from the file - const fp = join(dir, file); - const stat = statSync(fp); - let title = "New Chat"; - let messageCount = 0; - try { - const content = readFileSync(fp, "utf-8"); - const lines = content.split("\n").filter((l) => l.trim()); - messageCount = lines.length; - // Try to extract a title from the first user message - for (const line of lines) { - const parsed = JSON.parse(line); - if (parsed.role === "user" && parsed.content) { - const text = String(parsed.content); - title = text.length > 60 ? text.slice(0, 60) + "..." : text; - break; - } - } - } catch { /* best-effort */ } - - index.push({ - id, - title, - createdAt: stat.birthtimeMs || stat.mtimeMs, - updatedAt: stat.mtimeMs, - messageCount, - }); - dirty = true; - } - - if (dirty) { - index.sort((a, b) => b.updatedAt - a.updatedAt); - writeFileSync(indexFile, JSON.stringify(index, null, 2)); - } - } catch { /* best-effort */ } - - return index; -} - -function writeIndex(sessions: WebSessionMeta[]) { - const dir = ensureDir(); - writeFileSync(join(dir, "index.json"), JSON.stringify(sessions, null, 2)); -} - /** GET /api/web-sessions — list web chat sessions. * ?filePath=... → returns only sessions scoped to that file. * No filePath → returns only global (non-file) sessions. */ @@ -124,9 +38,8 @@ export async function POST(req: Request) { sessions.unshift(session); writeIndex(sessions); - // Create empty .jsonl file const dir = ensureDir(); - writeFileSync(join(dir, `${id}.jsonl`), ""); + writeFileSync(`${dir}/${id}.jsonl`, ""); return Response.json({ session }); } diff --git a/apps/web/app/api/web-sessions/shared.ts b/apps/web/app/api/web-sessions/shared.ts new file mode 100644 index 00000000000..03073ed9c50 --- /dev/null +++ b/apps/web/app/api/web-sessions/shared.ts @@ -0,0 +1,88 @@ +import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from "node:fs"; +import { join } from "node:path"; +import { resolveWebChatDir } from "@/lib/workspace"; + +export type WebSessionMeta = { + id: string; + title: string; + createdAt: number; + updatedAt: number; + messageCount: number; + /** When set, this session is scoped to a specific workspace file. */ + filePath?: string; +}; + +export function ensureDir() { + const dir = resolveWebChatDir(); + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } + return dir; +} + +/** + * Read the session index, auto-discovering any orphaned .jsonl files + * that aren't in the index (e.g. from profile switches or missing index). + */ +export function readIndex(): WebSessionMeta[] { + const dir = ensureDir(); + const indexFile = join(dir, "index.json"); + let index: WebSessionMeta[] = []; + if (existsSync(indexFile)) { + try { + index = JSON.parse(readFileSync(indexFile, "utf-8")); + } catch { + index = []; + } + } + + // Scan for orphaned .jsonl files not in the index + try { + const indexed = new Set(index.map((s) => s.id)); + const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl")); + let dirty = false; + for (const file of files) { + const id = file.replace(/\.jsonl$/, ""); + if (indexed.has(id)) {continue;} + + const fp = join(dir, file); + const stat = statSync(fp); + let title = "New Chat"; + let messageCount = 0; + try { + const content = readFileSync(fp, "utf-8"); + const lines = content.split("\n").filter((l) => l.trim()); + messageCount = lines.length; + for (const line of lines) { + const parsed = JSON.parse(line); + if (parsed.role === "user" && parsed.content) { + const text = String(parsed.content); + title = text.length > 60 ? text.slice(0, 60) + "..." : text; + break; + } + } + } catch { /* best-effort */ } + + index.push({ + id, + title, + createdAt: stat.birthtimeMs || stat.mtimeMs, + updatedAt: stat.mtimeMs, + messageCount, + }); + dirty = true; + } + + if (dirty) { + index.sort((a, b) => b.updatedAt - a.updatedAt); + writeFileSync(indexFile, JSON.stringify(index, null, 2)); + } + } catch { /* best-effort */ } + + return index; +} + +export function writeIndex(sessions: WebSessionMeta[]) { + const dir = ensureDir(); + writeFileSync(join(dir, "index.json"), JSON.stringify(sessions, null, 2)); +}