Merge branch 'kumareth/workspaces' of https://github.com/denchHQ/ironclaw
This commit is contained in:
commit
30333857a1
@ -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 });
|
||||
}
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
|
||||
88
apps/web/app/api/web-sessions/shared.ts
Normal file
88
apps/web/app/api/web-sessions/shared.ts
Normal file
@ -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));
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user