@membank/dashboard 0.5.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/dist/bin.mjs +176 -0
- package/dist/client/assets/{MagnifyingGlass.es-CGxS0MwI.js → MagnifyingGlass.es-ERvQA8v5.js} +1 -1
- package/dist/client/assets/{index-DZxM00o8.js → index-BCPCyhdz.js} +2 -2
- package/dist/client/assets/index-ByUTKMgP.css +1 -0
- package/dist/client/assets/{memories-ja38P73o.js → memories-C4n5opTn.js} +1 -1
- package/dist/client/assets/{memories._id-DeaZiiQb.js → memories._id-CrT5UPVk.js} +1 -1
- package/dist/client/assets/{memories.index-DUtHo-Be.js → memories.index-Bg5p8A21.js} +1 -1
- package/dist/client/assets/{native-select-BpuM7pgJ.js → native-select-Boj2GMjk.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/index.cjs +37 -115
- package/dist/index.d.cts +4 -2
- package/dist/index.d.mts +4 -2
- package/dist/index.mjs +38 -116
- package/package.json +10 -3
- package/dist/client/assets/index-GVEqDPxR.css +0 -1
package/dist/index.cjs
CHANGED
|
@@ -45,46 +45,6 @@ const MIME = {
|
|
|
45
45
|
".png": "image/png",
|
|
46
46
|
".json": "application/json"
|
|
47
47
|
};
|
|
48
|
-
function parseReviewEvent(row) {
|
|
49
|
-
return {
|
|
50
|
-
id: row.id,
|
|
51
|
-
memoryId: row.memory_id,
|
|
52
|
-
conflictingMemoryId: row.conflicting_memory_id,
|
|
53
|
-
similarity: row.similarity,
|
|
54
|
-
conflictContentSnapshot: row.conflict_content_snapshot,
|
|
55
|
-
reason: row.reason,
|
|
56
|
-
createdAt: row.created_at,
|
|
57
|
-
resolvedAt: row.resolved_at
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
function getReviewEventsForMemories(db, ids) {
|
|
61
|
-
if (ids.length === 0) return /* @__PURE__ */ new Map();
|
|
62
|
-
const placeholders = ids.map(() => "?").join(", ");
|
|
63
|
-
const rows = db.db.prepare(`SELECT * FROM memory_review_events WHERE memory_id IN (${placeholders}) AND resolved_at IS NULL ORDER BY created_at DESC`).all(...ids);
|
|
64
|
-
const map = /* @__PURE__ */ new Map();
|
|
65
|
-
for (const row of rows) {
|
|
66
|
-
const event = parseReviewEvent(row);
|
|
67
|
-
const existing = map.get(event.memoryId) ?? [];
|
|
68
|
-
existing.push(event);
|
|
69
|
-
map.set(event.memoryId, existing);
|
|
70
|
-
}
|
|
71
|
-
return map;
|
|
72
|
-
}
|
|
73
|
-
function parseRow(row, projects = [], reviewEvents = []) {
|
|
74
|
-
return {
|
|
75
|
-
id: row.id,
|
|
76
|
-
content: row.content,
|
|
77
|
-
type: row.type,
|
|
78
|
-
tags: JSON.parse(row.tags),
|
|
79
|
-
projects,
|
|
80
|
-
sourceHarness: row.source,
|
|
81
|
-
accessCount: row.access_count,
|
|
82
|
-
pinned: row.pinned !== 0,
|
|
83
|
-
reviewEvents,
|
|
84
|
-
createdAt: row.created_at,
|
|
85
|
-
updatedAt: row.updated_at
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
48
|
function tryPort(port) {
|
|
89
49
|
return new Promise((resolve, reject) => {
|
|
90
50
|
const server = (0, node_net.createServer)();
|
|
@@ -108,29 +68,16 @@ async function findFreePort(preferred) {
|
|
|
108
68
|
});
|
|
109
69
|
}
|
|
110
70
|
}
|
|
111
|
-
function createApiApp(
|
|
71
|
+
function createApiApp(repo, projectRepo, embedder) {
|
|
112
72
|
const app = new hono.Hono();
|
|
113
73
|
app.get("/api/memories", (c) => {
|
|
114
74
|
const { type, pinned, needsReview, search, projectId } = c.req.query();
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
if (pinned === "true") conditions.push("m.pinned = 1");
|
|
122
|
-
if (needsReview === "true") conditions.push("EXISTS (SELECT 1 FROM memory_review_events e WHERE e.memory_id = m.id AND e.resolved_at IS NULL)");
|
|
123
|
-
if (projectId === "global") conditions.push("m.id NOT IN (SELECT memory_id FROM memory_projects)");
|
|
124
|
-
else if (projectId) {
|
|
125
|
-
conditions.push("m.id IN (SELECT memory_id FROM memory_projects WHERE project_id = ?)");
|
|
126
|
-
params.push(projectId);
|
|
127
|
-
}
|
|
128
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
129
|
-
const rows = db.db.prepare(`SELECT m.* FROM memories m ${where} ORDER BY m.created_at DESC`).all(...params);
|
|
130
|
-
const ids = rows.map((r) => r.id);
|
|
131
|
-
const projectMap = projectRepo.getProjectsForMemories(ids);
|
|
132
|
-
const eventMap = getReviewEventsForMemories(db, ids);
|
|
133
|
-
let memories = rows.map((r) => parseRow(r, projectMap.get(r.id) ?? [], eventMap.get(r.id) ?? []));
|
|
75
|
+
let memories = repo.list({
|
|
76
|
+
type,
|
|
77
|
+
pinned: pinned === "true" ? true : void 0,
|
|
78
|
+
needsReview: needsReview === "true" ? true : void 0,
|
|
79
|
+
projectId
|
|
80
|
+
});
|
|
134
81
|
if (search) {
|
|
135
82
|
const q = search.toLowerCase();
|
|
136
83
|
memories = memories.filter((m) => m.content.toLowerCase().includes(q));
|
|
@@ -138,50 +85,33 @@ function createApiApp(db, repo, projectRepo) {
|
|
|
138
85
|
return c.json(memories);
|
|
139
86
|
});
|
|
140
87
|
app.get("/api/memories/:id", (c) => {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const projectMap = projectRepo.getProjectsForMemories([id]);
|
|
145
|
-
const eventMap = getReviewEventsForMemories(db, [id]);
|
|
146
|
-
return c.json(parseRow(row, projectMap.get(id) ?? [], eventMap.get(id) ?? []));
|
|
88
|
+
const memory = repo.findById(c.req.param("id"));
|
|
89
|
+
if (!memory) return c.json({ error: "Not found" }, 404);
|
|
90
|
+
return c.json(memory);
|
|
147
91
|
});
|
|
148
92
|
app.patch("/api/memories/:id", async (c) => {
|
|
149
93
|
const id = c.req.param("id");
|
|
150
|
-
if (!
|
|
94
|
+
if (!repo.findById(id)) return c.json({ error: "Not found" }, 404);
|
|
151
95
|
const body = await c.req.json();
|
|
152
|
-
|
|
153
|
-
const sqlParams = [];
|
|
154
|
-
if (body.pinned !== void 0) {
|
|
155
|
-
sets.push("pinned = ?");
|
|
156
|
-
sqlParams.push(body.pinned ? 1 : 0);
|
|
157
|
-
}
|
|
158
|
-
if (body.type !== void 0) {
|
|
159
|
-
sets.push("type = ?");
|
|
160
|
-
sqlParams.push(body.type);
|
|
161
|
-
}
|
|
162
|
-
if (sets.length > 0) {
|
|
163
|
-
sets.push("updated_at = ?");
|
|
164
|
-
sqlParams.push((/* @__PURE__ */ new Date()).toISOString(), id);
|
|
165
|
-
db.db.prepare(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`).run(...sqlParams);
|
|
166
|
-
}
|
|
96
|
+
if (body.pinned !== void 0) repo.setPin(id, body.pinned);
|
|
167
97
|
if (body.needsReview === false) repo.resolveReviewEvents(id);
|
|
168
|
-
if (body.content !== void 0 || body.tags !== void 0) await
|
|
98
|
+
if (body.content !== void 0 || body.tags !== void 0 || body.type !== void 0) await (0, _membank_core.updateMemory)(id, {
|
|
169
99
|
content: body.content,
|
|
170
|
-
tags: body.tags
|
|
100
|
+
tags: body.tags,
|
|
101
|
+
type: body.type
|
|
102
|
+
}, {
|
|
103
|
+
repo,
|
|
104
|
+
embedder
|
|
171
105
|
});
|
|
172
|
-
|
|
173
|
-
const projectMap = projectRepo.getProjectsForMemories([id]);
|
|
174
|
-
const eventMap = getReviewEventsForMemories(db, [id]);
|
|
175
|
-
return c.json(parseRow(updated, projectMap.get(id) ?? [], eventMap.get(id) ?? []));
|
|
106
|
+
return c.json(repo.findById(id));
|
|
176
107
|
});
|
|
177
|
-
app.delete("/api/memories/:id",
|
|
178
|
-
|
|
108
|
+
app.delete("/api/memories/:id", (c) => {
|
|
109
|
+
repo.delete(c.req.param("id"));
|
|
179
110
|
return c.json({ ok: true });
|
|
180
111
|
});
|
|
181
112
|
app.post("/api/memories/:id/projects", async (c) => {
|
|
182
|
-
const memoryId = c.req.param("id");
|
|
183
113
|
const body = await c.req.json();
|
|
184
|
-
projectRepo.addAssociation(
|
|
114
|
+
projectRepo.addAssociation(c.req.param("id"), body.projectId);
|
|
185
115
|
return c.json({ ok: true });
|
|
186
116
|
});
|
|
187
117
|
app.delete("/api/memories/:id/projects/:projectId", (c) => {
|
|
@@ -192,30 +122,19 @@ function createApiApp(db, repo, projectRepo) {
|
|
|
192
122
|
return c.json(projectRepo.list());
|
|
193
123
|
});
|
|
194
124
|
app.patch("/api/projects/:id", async (c) => {
|
|
195
|
-
const id = c.req.param("id");
|
|
196
125
|
const body = await c.req.json();
|
|
197
126
|
try {
|
|
198
|
-
return c.json(projectRepo.rename(id, body.name));
|
|
127
|
+
return c.json(projectRepo.rename(c.req.param("id"), body.name));
|
|
199
128
|
} catch {
|
|
200
129
|
return c.json({ error: "Not found" }, 404);
|
|
201
130
|
}
|
|
202
131
|
});
|
|
203
132
|
app.get("/api/stats", (c) => {
|
|
204
|
-
const byType =
|
|
205
|
-
correction: 0,
|
|
206
|
-
preference: 0,
|
|
207
|
-
decision: 0,
|
|
208
|
-
learning: 0,
|
|
209
|
-
fact: 0
|
|
210
|
-
};
|
|
211
|
-
const typeRows = db.db.prepare("SELECT type, COUNT(*) as count FROM memories GROUP BY type").all();
|
|
212
|
-
for (const row of typeRows) if (row.type in byType) byType[row.type] = row.count;
|
|
213
|
-
const totals = db.db.prepare("SELECT COUNT(*) as total FROM memories").get() ?? { total: 0 };
|
|
214
|
-
const reviewRow = db.db.prepare("SELECT COUNT(DISTINCT memory_id) as needsReview FROM memory_review_events WHERE resolved_at IS NULL").get() ?? { needsReview: 0 };
|
|
133
|
+
const { byType, total, needsReview } = repo.stats();
|
|
215
134
|
return c.json({
|
|
216
135
|
byType,
|
|
217
|
-
total
|
|
218
|
-
needsReview
|
|
136
|
+
total,
|
|
137
|
+
needsReview
|
|
219
138
|
});
|
|
220
139
|
});
|
|
221
140
|
return app;
|
|
@@ -224,8 +143,8 @@ async function startDashboard(opts) {
|
|
|
224
143
|
const port = await findFreePort(opts?.port ?? PREFERRED_PORT);
|
|
225
144
|
const db = _membank_core.DatabaseManager.open();
|
|
226
145
|
const embedding = new _membank_core.EmbeddingService();
|
|
227
|
-
const projects =
|
|
228
|
-
const app = createApiApp(
|
|
146
|
+
const projects = (0, _membank_core.createProjectRepository)(db);
|
|
147
|
+
const app = createApiApp((0, _membank_core.createMemoryRepository)(db, projects), projects, embedding);
|
|
229
148
|
const clientDir = (0, node_path.join)((0, node_path.dirname)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href)), "client");
|
|
230
149
|
app.get("*", (c) => {
|
|
231
150
|
const filePath = (0, node_path.join)(clientDir, c.req.path === "/" ? "/index.html" : c.req.path);
|
|
@@ -241,13 +160,16 @@ async function startDashboard(opts) {
|
|
|
241
160
|
db.close();
|
|
242
161
|
process.exit(0);
|
|
243
162
|
});
|
|
244
|
-
(
|
|
245
|
-
|
|
246
|
-
|
|
163
|
+
await new Promise((resolve) => {
|
|
164
|
+
(0, _hono_node_server.serve)({
|
|
165
|
+
fetch: app.fetch,
|
|
166
|
+
port
|
|
167
|
+
}, () => {
|
|
168
|
+
opts?.onReady?.(port);
|
|
169
|
+
resolve();
|
|
170
|
+
});
|
|
247
171
|
});
|
|
248
|
-
|
|
249
|
-
process.stdout.write(` Press Ctrl+C to stop\n\n`);
|
|
250
|
-
await (0, open.default)(`http://localhost:${port}`);
|
|
172
|
+
if (opts?.open) await (0, open.default)(`http://localhost:${port}`);
|
|
251
173
|
await new Promise(() => {});
|
|
252
174
|
}
|
|
253
175
|
//#endregion
|
package/dist/index.d.cts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Embedder, MemoryRepository, ProjectRepository } from "@membank/core";
|
|
2
2
|
import { Hono } from "hono";
|
|
3
3
|
|
|
4
4
|
//#region src/server/index.d.ts
|
|
5
|
-
declare function createApiApp(
|
|
5
|
+
declare function createApiApp(repo: MemoryRepository, projectRepo: ProjectRepository, embedder: Embedder): Hono;
|
|
6
6
|
declare function startDashboard(opts?: {
|
|
7
7
|
port?: number;
|
|
8
|
+
open?: boolean;
|
|
9
|
+
onReady?: (port: number) => void;
|
|
8
10
|
}): Promise<void>;
|
|
9
11
|
//#endregion
|
|
10
12
|
export { createApiApp, startDashboard };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Embedder, MemoryRepository, ProjectRepository } from "@membank/core";
|
|
2
2
|
import { Hono } from "hono";
|
|
3
3
|
|
|
4
4
|
//#region src/server/index.d.ts
|
|
5
|
-
declare function createApiApp(
|
|
5
|
+
declare function createApiApp(repo: MemoryRepository, projectRepo: ProjectRepository, embedder: Embedder): Hono;
|
|
6
6
|
declare function startDashboard(opts?: {
|
|
7
7
|
port?: number;
|
|
8
|
+
open?: boolean;
|
|
9
|
+
onReady?: (port: number) => void;
|
|
8
10
|
}): Promise<void>;
|
|
9
11
|
//#endregion
|
|
10
12
|
export { createApiApp, startDashboard };
|
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { createServer } from "node:net";
|
|
|
3
3
|
import { dirname, extname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { serve } from "@hono/node-server";
|
|
6
|
-
import { DatabaseManager, EmbeddingService,
|
|
6
|
+
import { DatabaseManager, EmbeddingService, createMemoryRepository, createProjectRepository, updateMemory } from "@membank/core";
|
|
7
7
|
import { Hono } from "hono";
|
|
8
8
|
import open from "open";
|
|
9
9
|
//#region src/server/index.ts
|
|
@@ -21,46 +21,6 @@ const MIME = {
|
|
|
21
21
|
".png": "image/png",
|
|
22
22
|
".json": "application/json"
|
|
23
23
|
};
|
|
24
|
-
function parseReviewEvent(row) {
|
|
25
|
-
return {
|
|
26
|
-
id: row.id,
|
|
27
|
-
memoryId: row.memory_id,
|
|
28
|
-
conflictingMemoryId: row.conflicting_memory_id,
|
|
29
|
-
similarity: row.similarity,
|
|
30
|
-
conflictContentSnapshot: row.conflict_content_snapshot,
|
|
31
|
-
reason: row.reason,
|
|
32
|
-
createdAt: row.created_at,
|
|
33
|
-
resolvedAt: row.resolved_at
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
function getReviewEventsForMemories(db, ids) {
|
|
37
|
-
if (ids.length === 0) return /* @__PURE__ */ new Map();
|
|
38
|
-
const placeholders = ids.map(() => "?").join(", ");
|
|
39
|
-
const rows = db.db.prepare(`SELECT * FROM memory_review_events WHERE memory_id IN (${placeholders}) AND resolved_at IS NULL ORDER BY created_at DESC`).all(...ids);
|
|
40
|
-
const map = /* @__PURE__ */ new Map();
|
|
41
|
-
for (const row of rows) {
|
|
42
|
-
const event = parseReviewEvent(row);
|
|
43
|
-
const existing = map.get(event.memoryId) ?? [];
|
|
44
|
-
existing.push(event);
|
|
45
|
-
map.set(event.memoryId, existing);
|
|
46
|
-
}
|
|
47
|
-
return map;
|
|
48
|
-
}
|
|
49
|
-
function parseRow(row, projects = [], reviewEvents = []) {
|
|
50
|
-
return {
|
|
51
|
-
id: row.id,
|
|
52
|
-
content: row.content,
|
|
53
|
-
type: row.type,
|
|
54
|
-
tags: JSON.parse(row.tags),
|
|
55
|
-
projects,
|
|
56
|
-
sourceHarness: row.source,
|
|
57
|
-
accessCount: row.access_count,
|
|
58
|
-
pinned: row.pinned !== 0,
|
|
59
|
-
reviewEvents,
|
|
60
|
-
createdAt: row.created_at,
|
|
61
|
-
updatedAt: row.updated_at
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
24
|
function tryPort(port) {
|
|
65
25
|
return new Promise((resolve, reject) => {
|
|
66
26
|
const server = createServer();
|
|
@@ -84,29 +44,16 @@ async function findFreePort(preferred) {
|
|
|
84
44
|
});
|
|
85
45
|
}
|
|
86
46
|
}
|
|
87
|
-
function createApiApp(
|
|
47
|
+
function createApiApp(repo, projectRepo, embedder) {
|
|
88
48
|
const app = new Hono();
|
|
89
49
|
app.get("/api/memories", (c) => {
|
|
90
50
|
const { type, pinned, needsReview, search, projectId } = c.req.query();
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
if (pinned === "true") conditions.push("m.pinned = 1");
|
|
98
|
-
if (needsReview === "true") conditions.push("EXISTS (SELECT 1 FROM memory_review_events e WHERE e.memory_id = m.id AND e.resolved_at IS NULL)");
|
|
99
|
-
if (projectId === "global") conditions.push("m.id NOT IN (SELECT memory_id FROM memory_projects)");
|
|
100
|
-
else if (projectId) {
|
|
101
|
-
conditions.push("m.id IN (SELECT memory_id FROM memory_projects WHERE project_id = ?)");
|
|
102
|
-
params.push(projectId);
|
|
103
|
-
}
|
|
104
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
105
|
-
const rows = db.db.prepare(`SELECT m.* FROM memories m ${where} ORDER BY m.created_at DESC`).all(...params);
|
|
106
|
-
const ids = rows.map((r) => r.id);
|
|
107
|
-
const projectMap = projectRepo.getProjectsForMemories(ids);
|
|
108
|
-
const eventMap = getReviewEventsForMemories(db, ids);
|
|
109
|
-
let memories = rows.map((r) => parseRow(r, projectMap.get(r.id) ?? [], eventMap.get(r.id) ?? []));
|
|
51
|
+
let memories = repo.list({
|
|
52
|
+
type,
|
|
53
|
+
pinned: pinned === "true" ? true : void 0,
|
|
54
|
+
needsReview: needsReview === "true" ? true : void 0,
|
|
55
|
+
projectId
|
|
56
|
+
});
|
|
110
57
|
if (search) {
|
|
111
58
|
const q = search.toLowerCase();
|
|
112
59
|
memories = memories.filter((m) => m.content.toLowerCase().includes(q));
|
|
@@ -114,50 +61,33 @@ function createApiApp(db, repo, projectRepo) {
|
|
|
114
61
|
return c.json(memories);
|
|
115
62
|
});
|
|
116
63
|
app.get("/api/memories/:id", (c) => {
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const projectMap = projectRepo.getProjectsForMemories([id]);
|
|
121
|
-
const eventMap = getReviewEventsForMemories(db, [id]);
|
|
122
|
-
return c.json(parseRow(row, projectMap.get(id) ?? [], eventMap.get(id) ?? []));
|
|
64
|
+
const memory = repo.findById(c.req.param("id"));
|
|
65
|
+
if (!memory) return c.json({ error: "Not found" }, 404);
|
|
66
|
+
return c.json(memory);
|
|
123
67
|
});
|
|
124
68
|
app.patch("/api/memories/:id", async (c) => {
|
|
125
69
|
const id = c.req.param("id");
|
|
126
|
-
if (!
|
|
70
|
+
if (!repo.findById(id)) return c.json({ error: "Not found" }, 404);
|
|
127
71
|
const body = await c.req.json();
|
|
128
|
-
|
|
129
|
-
const sqlParams = [];
|
|
130
|
-
if (body.pinned !== void 0) {
|
|
131
|
-
sets.push("pinned = ?");
|
|
132
|
-
sqlParams.push(body.pinned ? 1 : 0);
|
|
133
|
-
}
|
|
134
|
-
if (body.type !== void 0) {
|
|
135
|
-
sets.push("type = ?");
|
|
136
|
-
sqlParams.push(body.type);
|
|
137
|
-
}
|
|
138
|
-
if (sets.length > 0) {
|
|
139
|
-
sets.push("updated_at = ?");
|
|
140
|
-
sqlParams.push((/* @__PURE__ */ new Date()).toISOString(), id);
|
|
141
|
-
db.db.prepare(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`).run(...sqlParams);
|
|
142
|
-
}
|
|
72
|
+
if (body.pinned !== void 0) repo.setPin(id, body.pinned);
|
|
143
73
|
if (body.needsReview === false) repo.resolveReviewEvents(id);
|
|
144
|
-
if (body.content !== void 0 || body.tags !== void 0) await
|
|
74
|
+
if (body.content !== void 0 || body.tags !== void 0 || body.type !== void 0) await updateMemory(id, {
|
|
145
75
|
content: body.content,
|
|
146
|
-
tags: body.tags
|
|
76
|
+
tags: body.tags,
|
|
77
|
+
type: body.type
|
|
78
|
+
}, {
|
|
79
|
+
repo,
|
|
80
|
+
embedder
|
|
147
81
|
});
|
|
148
|
-
|
|
149
|
-
const projectMap = projectRepo.getProjectsForMemories([id]);
|
|
150
|
-
const eventMap = getReviewEventsForMemories(db, [id]);
|
|
151
|
-
return c.json(parseRow(updated, projectMap.get(id) ?? [], eventMap.get(id) ?? []));
|
|
82
|
+
return c.json(repo.findById(id));
|
|
152
83
|
});
|
|
153
|
-
app.delete("/api/memories/:id",
|
|
154
|
-
|
|
84
|
+
app.delete("/api/memories/:id", (c) => {
|
|
85
|
+
repo.delete(c.req.param("id"));
|
|
155
86
|
return c.json({ ok: true });
|
|
156
87
|
});
|
|
157
88
|
app.post("/api/memories/:id/projects", async (c) => {
|
|
158
|
-
const memoryId = c.req.param("id");
|
|
159
89
|
const body = await c.req.json();
|
|
160
|
-
projectRepo.addAssociation(
|
|
90
|
+
projectRepo.addAssociation(c.req.param("id"), body.projectId);
|
|
161
91
|
return c.json({ ok: true });
|
|
162
92
|
});
|
|
163
93
|
app.delete("/api/memories/:id/projects/:projectId", (c) => {
|
|
@@ -168,30 +98,19 @@ function createApiApp(db, repo, projectRepo) {
|
|
|
168
98
|
return c.json(projectRepo.list());
|
|
169
99
|
});
|
|
170
100
|
app.patch("/api/projects/:id", async (c) => {
|
|
171
|
-
const id = c.req.param("id");
|
|
172
101
|
const body = await c.req.json();
|
|
173
102
|
try {
|
|
174
|
-
return c.json(projectRepo.rename(id, body.name));
|
|
103
|
+
return c.json(projectRepo.rename(c.req.param("id"), body.name));
|
|
175
104
|
} catch {
|
|
176
105
|
return c.json({ error: "Not found" }, 404);
|
|
177
106
|
}
|
|
178
107
|
});
|
|
179
108
|
app.get("/api/stats", (c) => {
|
|
180
|
-
const byType =
|
|
181
|
-
correction: 0,
|
|
182
|
-
preference: 0,
|
|
183
|
-
decision: 0,
|
|
184
|
-
learning: 0,
|
|
185
|
-
fact: 0
|
|
186
|
-
};
|
|
187
|
-
const typeRows = db.db.prepare("SELECT type, COUNT(*) as count FROM memories GROUP BY type").all();
|
|
188
|
-
for (const row of typeRows) if (row.type in byType) byType[row.type] = row.count;
|
|
189
|
-
const totals = db.db.prepare("SELECT COUNT(*) as total FROM memories").get() ?? { total: 0 };
|
|
190
|
-
const reviewRow = db.db.prepare("SELECT COUNT(DISTINCT memory_id) as needsReview FROM memory_review_events WHERE resolved_at IS NULL").get() ?? { needsReview: 0 };
|
|
109
|
+
const { byType, total, needsReview } = repo.stats();
|
|
191
110
|
return c.json({
|
|
192
111
|
byType,
|
|
193
|
-
total
|
|
194
|
-
needsReview
|
|
112
|
+
total,
|
|
113
|
+
needsReview
|
|
195
114
|
});
|
|
196
115
|
});
|
|
197
116
|
return app;
|
|
@@ -200,8 +119,8 @@ async function startDashboard(opts) {
|
|
|
200
119
|
const port = await findFreePort(opts?.port ?? PREFERRED_PORT);
|
|
201
120
|
const db = DatabaseManager.open();
|
|
202
121
|
const embedding = new EmbeddingService();
|
|
203
|
-
const projects =
|
|
204
|
-
const app = createApiApp(
|
|
122
|
+
const projects = createProjectRepository(db);
|
|
123
|
+
const app = createApiApp(createMemoryRepository(db, projects), projects, embedding);
|
|
205
124
|
const clientDir = join(dirname(fileURLToPath(import.meta.url)), "client");
|
|
206
125
|
app.get("*", (c) => {
|
|
207
126
|
const filePath = join(clientDir, c.req.path === "/" ? "/index.html" : c.req.path);
|
|
@@ -217,13 +136,16 @@ async function startDashboard(opts) {
|
|
|
217
136
|
db.close();
|
|
218
137
|
process.exit(0);
|
|
219
138
|
});
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
139
|
+
await new Promise((resolve) => {
|
|
140
|
+
serve({
|
|
141
|
+
fetch: app.fetch,
|
|
142
|
+
port
|
|
143
|
+
}, () => {
|
|
144
|
+
opts?.onReady?.(port);
|
|
145
|
+
resolve();
|
|
146
|
+
});
|
|
223
147
|
});
|
|
224
|
-
|
|
225
|
-
process.stdout.write(` Press Ctrl+C to stop\n\n`);
|
|
226
|
-
await open(`http://localhost:${port}`);
|
|
148
|
+
if (opts?.open) await open(`http://localhost:${port}`);
|
|
227
149
|
await new Promise(() => {});
|
|
228
150
|
}
|
|
229
151
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@membank/dashboard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/draekien-industries/membank.git",
|
|
8
8
|
"directory": "packages/dashboard"
|
|
9
9
|
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"membank-dashboard": "./dist/bin.mjs"
|
|
12
|
+
},
|
|
10
13
|
"exports": {
|
|
11
14
|
".": {
|
|
12
15
|
"import": {
|
|
@@ -20,7 +23,8 @@
|
|
|
20
23
|
}
|
|
21
24
|
},
|
|
22
25
|
"files": [
|
|
23
|
-
"dist"
|
|
26
|
+
"dist",
|
|
27
|
+
"!dist/**/*.map"
|
|
24
28
|
],
|
|
25
29
|
"dependencies": {
|
|
26
30
|
"@base-ui/react": "^1.0.0",
|
|
@@ -35,12 +39,15 @@
|
|
|
35
39
|
"@tanstack/react-hotkeys": "^0.10.0",
|
|
36
40
|
"@tanstack/react-router": "^1.168.25",
|
|
37
41
|
"@tanstack/router-plugin": "^1.167.28",
|
|
42
|
+
"chalk": "^5.6.2",
|
|
38
43
|
"class-variance-authority": "^0.7.1",
|
|
39
44
|
"clsx": "^2.1.1",
|
|
40
45
|
"cmdk": "^1.1.1",
|
|
46
|
+
"commander": "^14.0.3",
|
|
41
47
|
"hono": "^4.12.18",
|
|
42
48
|
"next-themes": "^0.4.6",
|
|
43
49
|
"open": "^10.1.0",
|
|
50
|
+
"ora": "^9.4.0",
|
|
44
51
|
"react": "^19.0.0",
|
|
45
52
|
"react-dom": "^19.0.0",
|
|
46
53
|
"recharts": "3.8.0",
|
|
@@ -49,7 +56,7 @@
|
|
|
49
56
|
"tailwind-merge": "^3.0.0",
|
|
50
57
|
"tw-animate-css": "^1.0.0",
|
|
51
58
|
"zod": "^4.3.6",
|
|
52
|
-
"@membank/core": "0.
|
|
59
|
+
"@membank/core": "0.10.0"
|
|
53
60
|
},
|
|
54
61
|
"devDependencies": {
|
|
55
62
|
"@tailwindcss/vite": "^4.0.0",
|